Hi @hamoid, thank you for the reply.
I eventually opted for a dictionnary to keep trace of the relations between ‘mother’ and ‘child’ cells.
Regarding the second question, trying to use the toxiclibs library was a nightmare (for this case specifically) and I decided to implement my own Spring
class instead (based on this tutorial from Daniel Shiffman).
However the end result is far from what being perfect:
- springs get mixed-up
- some cells are overlapping
- others are jammed in an intertwining mess
It shouldn’t be that way obviously (cf. the example picture above) and I don’t understand the exact reason behind this chaotic behavior.
I suspect, however, the interact()
function to be flawed (from line 130 to 138). Something might be off with the way I’m pushing the cells when they enter another cell’s perimeter:
def interact(self):
#Check every other cells
for a in collection:
if a is not self:
#Distance between other cells
d = PVector.dist(self.location, a.location)
#If distance < sum of the 2 radii
if d < (a.radius + self.radius + 2):
#Push other cells outside its perimeter
gap = (a.radius + self.radius + 2) - d
diff = PVector.sub(self.location, a.location)
a.velocity.sub(diff.setMag(gap))
I would really appreciate if someone could help me find what’s going on here.
Full script with annotations
id = 0
def setup():
global collection, d
size(480, 360, P2D)
smooth(8)
#Array list containing cells
collection = [Cell(width>>1, height>>1)]
#Dictionnary to keep trace of the 'mother' <-> 'child' relations
d = {}
def draw():
background(255)
#Lock cell 0
collection[0].location = PVector(width>>1, height>>1)
#Grow cells
for i, c in enumerate(collection):
c.update()
c.growth()
c.interact()
c.display()
###Connecting 'mother' and 'children' cells with springs###
for e in d:
#Attach a spring to every mother cells (with a length of 10)
s = Spring(collection[e].location.x, collection[e].location.y, 10)
for id in d[e]:
#Connect that spring to their children
s.connect(collection[id])
#Constrain length to the sum of the 2 radii (mother radius + child radius + sum of stroke weights)
s.constrainLength(collection[id], 10, collection[id].radius + collection[e].radius + 4)
#Draw springs
s.displayLine(collection[id])
#saveFrame("movie/mitosis_####.png")
class Cell(object):
growthInterval = 5
divisionInterval = 50
def __init__(self, x, y):
self.location = PVector(x, y)
self.velocity = PVector()
self.acceleration = PVector()
self.radius = 10
self.time = 0
self.id = 0
self.mass = 5
self.damping = .1
def applyForce(self, f):
f = f / self.mass
self.velocity += f
def update(self):
self.velocity *= self.damping
self.location += self.velocity
self.velocity += self.acceleration
self.acceleration *= 0
def growth(self):
global id
#Increment cell time
self.time += 1
#While less than 40 cells
if len(collection) < 40:
#If growing time is ellapsed
if self.time > 0 and self.time%Cell.divisionInterval == 0:
#Add randomness to the division process
if random(1) > .5:
#Increment id
id += 1
#Divide radius by 2
self.radius *= .5
#Find a random vector
dir = PVector.random2D()
#Add this vector to new cell (child) position
child = Cell(self.location.x + dir.x, self.location.y + dir.y)
#Set child's id
child.id = id
#Store children cells' id for each mother cell
if self.id not in d:
d[self.id] = [child.id]
else:
d[self.id].append(child.id)
#Store cells in array list 'collection'
collection.append(child)
#Grow cell every 5 iterations
if self.time%Cell.growthInterval == 0:
self.radius += .5
def interact(self):
#Check every other cells
for a in collection:
if a is not self:
#Distance between other cells
d = PVector.dist(self.location, a.location)
#If distance < sum of the 2 radii
if d < (a.radius + self.radius + 2):
#Push other cells outside its perimeter
gap = (a.radius + self.radius + 2) - d
diff = PVector.sub(self.location, a.location)
a.velocity.sub(diff.setMag(gap))
def display(self):
stroke(0)
strokeWeight(4)
point(self.location.x, self.location.y)
noFill()
strokeWeight(1)
ellipse(self.location.x, self.location.y, self.radius*2, self.radius*2)
class Spring(object):
def __init__(self, x, y, l):
self.anchor = PVector(x, y)
self.k = .2
self.len_ = l
def connect(self, b):
force = PVector.sub(b.location, self.anchor)
d = force.mag()
stretch = d - self.len_
force.normalize()
force *= -1 * self.k * stretch
b.applyForce(force)
def constrainLength(self, b, minl, maxl):
dir = PVector.sub(b.location, self.anchor)
d = dir.mag()
if d < minl:
dir.normalize()
dir *= minl
b.location = PVector.add(self.anchor, dir)
b.velocity *= 0
if d > maxl:
dir.normalize()
dir *= maxl
b.location = PVector.add(self.anchor, dir)
b.velocity *= 0
def displayLine(self, b):
strokeWeight(1)
stroke(255, 20, 30)
line(b.location.x, b.location.y, self.anchor.x, self.anchor.y)
def keyPressed():
noLoop()