Cell Division and Growth Algorithm: Adding springs

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()