home processing download documents tutorial python tutorial gallery source about
 Python Tutorials (back to the list of tutorials)

Multi-Agent on A Surface (requires iGeo version 7.4.0 or higher)

     Line Agent on A Surface

This section shows techniques to have agents running on a surface. The basic logic to do it is that, first, limit the behavior of the agents mostly within 2 dimensional x-y plane and within the range of 0.0 - 1.0 in x and y, secondly map the x-y location of agent as u-v parameter of the surface, to get 3 dimensional location on the surface, and thirdly, use the mapped x-y-z coordinates (vector) to create geometries of the agents.

The example below shows how to re-write the line branching agents shown before (without collision detection) to have them on a surface.

add_library('igeo')

def setup() :
    size(480, 360, IG.GL)
    
    IG.duration(120)
    IG.open("surface13.3dm")
    srf = IG.surface(0) #first surface in the server
    srf.del()
    for i in range(120) : 
        LineAgentOnSurface(IVec(IRand.get(),0,0),\ #random only in x 
                           IVec(0,0.01,0),\ #direction is y. length is less than 1.0 
                           srf).clr(0)

class LineAgentOnSurface(IAgent) : 
    
    def __init__(self, pt, dir, s) : 
        self.pos = pt 
        self.dir = dir
        self.surf = s
    
    def update(self) : 
        if self.pos.x() < 0 or self.pos.x() > 1.0 :  #out of u-v boundary
            self.del()
            return 
        
        pos2 = self.pos.dup().add(self.dir)
        srfPt = self.surf.pt(self.pos.x(), self.pos.y())
        srfPt2 = self.surf.pt(pos2.x(), pos2.y())
        ICurve(srfPt, srfPt2).clr(self.clr())
        
        for i in range(2) : 
            if IRand.pct(50) : 
                dir2 = self.dir.dup()
                angle = IRand.get(-PI/20, PI/20)
                dir2.rot(IVec(0, 0, 1), angle)
                
                r = self.clr().getRed() + IRand.getInt(-10, 10)
                g = self.clr().getGreen() + IRand.getInt(-10, 10)
                b = self.clr().getBlue() + IRand.getInt(-10, 10)
                LineAgentOnSurface(pos2, dir2, self.surf).clr(r,g,b)
        self.del()

The below is the input surface to put the agents on. The file of the surface used in this example is this file.

surface13.3dm

This is the result of the agents running on the input surface. at the line of
if(pos.x < 0 || pos.x > 1.0){
it checks if the x position of the agent is inside the boundary of u-v parameter of the surface ( 0.0 - 1.0 ) and if it's outside, it deletes itself. Then at these two lines,
IVec srfPt = surf.pt(pos.x, pos.y);
IVec srfPt2 = surf.pt(pos2.x, pos2.y);
The position of the agent is interpreted as u-v parameter of the surface and mapped onto the 3 dimensional location (IVec) on the surface. Another thing to be noted is that in the setup() method at the constructor of LineAgentOnSurface class,
    new LineAgentOnSurface(new IVec(IRandom.get(),0,0),
                                                  new IVec(0,0.01,0), srf)
the first argument is a vector variable to specify the starting location of the agent and it's set to be bottom of v direction on the surface. The second argument is another vector variable to specify the direction and the length of the line and the length is set to be smaller than 1.0 because the full range of the v parameter on the surface is 1.0. And at the third argument, it passes the base surface into the agent.


     Box Agent on A Surface

The following example re-writes the code of the box agent example shown before to map boxes onto the input surface.
First, the branching is limited into 3 direction (left/right/up) instead of 5 direction (left/right/up/down/above) and agents start where y is 0, and move up in y direction.
Secondly, because the amount of movement to create a new child agent happens in u-v parametric space, whereas the size of the box is defined in x-y-z 3D space, the variable size is used only for the size of box and not used to specify the amount of movement. Instead, there's another variable move inside update() method. Because now there are two different variables for the size of box and the distance to the next box and one is in x-y-z 3D space and another is in u-v 2D parametric space, it's difficult to align the faces of boxes which are next to each other. The output of this example shows overlapping boxes and gap between boxes.

The input surface used in the code is this file.

surface1.3dm

add_library('igeo')

def setup() : 
    size(480, 360, IG.GL)
    IG.duration(300)
    IG.open("surface1.3dm")
    srf = IG.surface(0) #first surface in the server
    for i in range(20) : 
        MyAgent(IVec(IRand.get(),0,0),0.5,srf).clr(0)
  
    IG.transparent()  #transparent graphic mode

class MyAgent(IAgent) : 
    
    def __init__(self, pt, sz, s) : 
        self.pos = pt 
        self.size = sz
        self.surf = s
    
    def update(self) : 
        surfPt = self.surf.pt(self.pos.x(), self.pos.y())
        IBox(surfPt, self.size).clr(self.clr())
        nextPos = self.pos.dup()
        move = 0.01 #less than 1.0
        #random direction
        dir = IRand.getInt(0, 2)
        if dir==0 : 
            nextPos.add(move, 0, 0) #right
        elif dir==1 :
            nextPos.add(-move, 0, 0) #left
        elif dir==2 : 
            nextPos.add(0, move, 0) #up
        
        # slightly chaning the color
        r = self.clr().getRed() + IRand.getInt(-10, 10)
        g = self.clr().getGreen() + IRand.getInt(-10, 10)
        b = self.clr().getBlue() + IRand.getInt(-10, 10)
        
        MyAgent(nextPos, self.size, self.surf).clr(r,g,b)
        self.del()


     Mapping Agents with U, V Tangent and Normal

If you use tangent vectors in u and v direction, and also normal vectors, you can put geometries along the internal grid of the NURBS surface. The example below shows a way to create boxes in u and v tangents and normal direction.
In this code, the variable size doesn't mean the size in x-y-z 3D space anymore but size in u-v 2D parametric space because the variable size controls the length of u and v tangent vectors. Because of this, the variable move which was in the previous code is removed again and size is used to define the amount of the movement to put child agent, like the original code. In this way, you can align the faces of boxes but it's not theoretically accurate but just approximation.

add_library('igeo')

def setup() : 
    size(480, 360, IG.GL)
    IG.duration(300)
    IG.open("surface1.3dm")
    surf = IG.surface(0)
    for i in range(20) : 
        MyAgent(IVec(IRand.get(),0,0),0.01,surf).clr(0)
    IG.transparent() #transparent graphic mode

class MyAgent(IAgent) : 
    
    def __init__(self, pt, sz, s) : 
        self.pos = pt 
        self.size = sz
        self.surf = s
    
    def update(self) :
        utan = self.surf.utan(self.pos.x(), self.pos.y())
        vtan = self.surf.vtan(self.pos.x(), self.pos.y())
        nml = self.surf.nml(self.pos.x(), self.pos.y())
        surfPt = self.surf.pt(self.pos.x(), self.pos.y())
        
        utan.mul(self.size) #original length of u tangent vector is almost full width of surface in u.
        vtan.mul(self.size) #original length of v tangent vector is almost full height of surface in v.
        nml.mul(self.size*self.size) #original length of normal vector is multiplication of u tangent and v tangent
        
        IBox(surfPt,utan,vtan,nml).clr(self.clr())
        nextPos = self.pos.dup()
        #random direction
        dir = IRand.getInt(0, 2)
        if dir==0 : 
            nextPos.add(self.size, 0, 0) #right
        elif dir==1 : 
            nextPos.add(-self.size, 0, 0) #left
        elif dir==2 : 
            nextPos.add(0, self.size, 0) #up
        
        # slightly chaning the color
        r = self.clr().getRed() + IRand.getInt(-10, 10)
        g = self.clr().getGreen() + IRand.getInt(-10, 10)
        b = self.clr().getBlue() + IRand.getInt(-10, 10)
        MyAgent(nextPos, self.size, self.surf).clr(r, g, b)
        self.del()


     Another Example of Mapping Agent on A Surface

Here is another example of mapping agents on a surface. This example re-writes the branching agents with collision detection shown before to have agents on an input surface. The agents are taking an input surface when it's constructed through the constructor. The interact(IDynamics) method has no change from the original branching algorithm but the update() method has change to map two end points of the line onto the surface. The update() method also has additional lines to remove agents who get out of the boundary of u-v parameter space (0.0 - 1.0 in each u and v direction) on the input NURBS surface.

add_library('igeo')

def setup() : 
    size(480, 360, IG.GL)
    IG.duration(200)
    IG.open("surface13.3dm")
    surf = IG.surface(0)
    surf.del()
    LineAgent(IVec(0,0,0), IVec(1,0,0),surf)

class LineAgent(IAgent) : 
    length = 0.01 #length in u-v space, less than 1.0
    clearance = 0.0099 #less than length
    
    def __init__(self, pt, dir, s) : 
        self.pt1 = pt
        self.pt2 = pt.dup().add(dir.dup().len(LineAgent.length))
        self.surf = s
        self.isColliding = False
    
    def interact(self, agents) : 
        if self.time() == 0 : #only in the first time
            for agent in agents : 
                if isinstance(agent, LineAgent) : 
                    if agent is not self : 
                        # checking clearance of end point
                        if agent.pt2.dist(self.pt2) < LineAgent.clearance : 
                            self.isColliding=True
    
    def update(self) : 
        # is inside surface?
        if self.pt2.x() < 0.0 or self.pt2.x() > 1.0 or \
           self.pt2.y() < 0.0 or self.pt2.y() > 1.0 : 
            self.isColliding = True
        
        if self.isColliding : 
            self.del()
        elif self.time() == 0 : #if not colliding
            surfPt1 = self.surf.pt(self.pt1.x(), self.pt1.y())
            surfPt2 = self.surf.pt(self.pt2.x(), self.pt2.y())
            ICurve(surfPt1,surfPt2).clr(0)
            
            dir = self.pt2.dif(self.pt1)
            if IRand.pct(40) : #bend
                LineAgent(self.pt2,dir.dup().rot(PI/3),self.surf)
            if IRand.pct(40) : #bend the other way
                LineAgent(self.pt2,dir.dup().rot(-PI/3),self.surf)
            if IRand.pct(80) : #straight
                LineAgent(self.pt2,dir.dup(),self.surf)

The code below creates a fin surface instead of line. It gets offset points on the surface in a specified depth to get two more points out of the two end points of the line. Additionally, color differentiation algorithm in gray scale is also inserted.

add_library('igeo')

def setup() : 
    size(480, 360, IG.GL)
    IG.duration(200)
    IG.open("surface13.3dm")
    surf = IG.surface(0)
    surf.del()
    LineAgent(IVec(0,0,0), IVec(1,0,0),surf).clr(0.2)

class LineAgent(IAgent) : 
    length = 0.01 #length in u-v space, less than 1.0
    clearance = 0.0099 #less than length
    
    def __init__(self, pt, dir, s) : 
        self.pt1 = pt
        self.pt2 = pt.dup().add(dir.dup().len(LineAgent.length))
        self.surf = s
        self.isColliding = False
    
    def interact(self, agents) : 
        if self.time() == 0 : #only in the first time
            for agent in agents : 
                if isinstance(agent, LineAgent) : 
                    if agent is not self : 
                        # checking clearance of end point
                        if agent.pt2.dist(self.pt2) < LineAgent.clearance : 
                            self.isColliding=True
    
    def update(self) : 
        # is inside surface?
        if self.pt2.x() < 0.0 or self.pt2.x() > 1.0 or \
           self.pt2.y() < 0.0 or self.pt2.y() > 1.0 : 
            self.isColliding = True
        
        if self.isColliding : 
            self.del()
        elif self.time() == 0 : #if not colliding
            surfPt1 = self.surf.pt(self.pt1.x(), self.pt1.y())
            surfPt2 = self.surf.pt(self.pt2.x(), self.pt2.y())
            
            offsetDepth = 1
            surfPt1d = self.surf.pt(self.pt1.x(), self.pt1.y(), offsetDepth)
            surfPt2d = self.surf.pt(self.pt2.x(), self.pt2.y(), offsetDepth)
            ISurface(surfPt1,surfPt2,surfPt2d,surfPt1d).clr(self.clr())
            
            # slightly chaning the gray color
            gray = (self.clr().getRed()+self.clr().getGreen()+self.clr().getBlue())/3
            gray += IRand.getInt(-10, 10)
            
            dir = self.pt2.dif(self.pt1)
            if IRand.pct(40) : #bend
                LineAgent(self.pt2,dir.dup().rot(PI/3),self.surf).clr(gray)
            if IRand.pct(40) : #bend the other way
                LineAgent(self.pt2,dir.dup().rot(-PI/3),self.surf).clr(gray)
            if IRand.pct(80) : #straight
                LineAgent(self.pt2,dir.dup(),self.surf).clr(gray)

The code below combines the code above and the geometric technique to put continous curvature keeping the same tangency shown in this section. The surface is created taking 3 by 2 control points and 3 of them are midpoint of the current agent, end point of the current agent and midpoint of the next agent. Other 3 are offset points of those 3 points on the surface. Additionally, the offset depth of the second control point of the first 3 points is controlled by the agent too. It alternates the value making it smaller or larger than the offset depth of the end points.

add_library('igeo')

def setup() : 
    size(480, 360, IG.GL)
    IG.duration(200)
    IG.open("surface13.3dm")
    surf = IG.surface(0)
    surf.del()
    LineAgent(IVec(0,0,0), IVec(1,0,0),surf,0.5,0.1).clr(0.5)

class LineAgent(IAgent) : 
    length = 0.01 #length in u-v space, less than 1.0
    clearance = 0.0099 #less than length
    
    def __init__(self, pt, dir, s, depth1, depth2) : 
        self.pt1 = pt
        self.pt2 = pt.dup().add(dir.dup().len(LineAgent.length))
        self.surf = s
        self.isColliding = False
        self.offsetDepth1 = depth1
        self.offsetDepth2 = depth2
    
    def interact(self, agents) : 
        if self.time() == 0 : #only in the first time
            for agent in agents : 
                if isinstance(agent, LineAgent) : 
                    if agent is not self : 
                        # checking clearance of end point
                        if agent.pt2.dist(self.pt2) < LineAgent.clearance : 
                            self.isColliding=True
    
    def update(self) : 
        # is inside surface?
        if self.pt2.x() < 0.0 or self.pt2.x() > 1.0 or \
           self.pt2.y() < 0.0 or self.pt2.y() > 1.0 : 
            self.isColliding = True
        
        if self.isColliding : 
            self.del()
        elif self.time() == 0 : #if not colliding
            #midpoint of the current agent
            mid = self.pt1.mid(self.pt2)
            surfPt1 = self.surf.pt(mid.x(), mid.y())
            surfPt1d = self.surf.pt(mid.x(), mid.y(), self.offsetDepth1)
            
            #end point of the current agent
            surfPt2 = self.surf.pt(self.pt2.x(), self.pt2.y())
            surfPt2d = self.surf.pt(self.pt2.x(), self.pt2.y(), self.offsetDepth2)
            
            nextDepth = 0
            #alternating offsetDepth2
            if self.offsetDepth2 > self.offsetDepth1 : 
                nextDepth = self.offsetDepth1-(self.offsetDepth2-self.offsetDepth1)
            else : 
                nextDepth = self.offsetDepth1+(self.offsetDepth1-self.offsetDepth2)
            
            # slightly chaning the color
            r = self.clr().getRed() + IRand.getInt(-10, 10)
            g = self.clr().getGreen() + IRand.getInt(-10, 10)
            b = self.clr().getBlue() + IRand.getInt(-10, 10)
            
            dir = self.pt2.dif(self.pt1)
            if IRand.pct(40) : #bend
                nextDir = dir.dup().rot(PI/3)
                #midpoint of the next agent
                mid2 = self.pt2.cp(nextDir.dup().len(LineAgent.length/2))
                surfPt3 = self.surf.pt(mid2.x(),mid2.y())
                surfPt3d = self.surf.pt(mid2.x(),mid2.y(),self.offsetDepth1)
                #3 by 2 control points
                cpts = [ [surfPt1, surfPt1d],\
                         [surfPt2, surfPt2d],\
                         [surfPt3, surfPt3d] ]
                #u degree 2, v degree 1 surface
                ISurface(cpts, 2, 1).clr(self.clr())
                
                LineAgent(self.pt2,nextDir,self.surf,self.offsetDepth1,nextDepth).clr(r,g,b)
            
            if IRand.pct(40) : #bend the other way
                nextDir = dir.dup().rot(-PI/3)
                #midpoint of the next agent
                mid2 = self.pt2.cp(nextDir.dup().len(LineAgent.length/2))
                surfPt3 = self.surf.pt(mid2.x(),mid2.y())
                surfPt3d = self.surf.pt(mid2.x(),mid2.y(),self.offsetDepth1)
                #3 by 2 control points
                cpts = [ [surfPt1, surfPt1d],\
                         [surfPt2, surfPt2d],\
                         [surfPt3, surfPt3d] ]
                #u degree 2, v degree 1 surface
                ISurface(cpts, 2, 1).clr(self.clr())
                
                LineAgent(self.pt2,nextDir,self.surf,self.offsetDepth1,nextDepth).clr(r,g,b)
            
            if IRand.pct(80) : #straight
                nextDir = dir
                #midpoint of the next agent
                mid2 = self.pt2.cp(nextDir.dup().len(LineAgent.length/2))
                surfPt3 = self.surf.pt(mid2.x(),mid2.y())
                surfPt3d = self.surf.pt(mid2.x(),mid2.y(),self.offsetDepth1)
                cpts = [ [surfPt1, surfPt1d],\
                         [surfPt2, surfPt2d],\
                         [surfPt3, surfPt3d] ]
                #u degree 2, v degree 1 surface
                ISurface(cpts, 2, 1).clr(self.clr())
                
                LineAgent(self.pt2,nextDir,self.surf,self.offsetDepth1,nextDepth).clr(r,g,b)


     Cellular Automaton on A Surface

The code below shows an example to implement cellular automata on a surface. Although update mechanism of cellular automata on the code is the same with the previous automata example, the location of each automaton is fixed and only states of them propagate among automata who sits on the grid of the input surface. The panelization algorithm is used to locate automata on the grid of the input surface.
By changing the automaton's update rule table, you can change the behavior of the automaton, sometimes drastically. You can also change the behavior drastically by changing the initial state. On the code below, only the line at i==0 is initialized with the state 1.

add_library('igeo')

def setup() : 
    size(480, 360, IG.GL)
    IConfig.syncDrawAndDynamics=True
    IG.duration(60)
    IG.open("surface13.3dm")
    surf = IG.surface(0)
    # put automaton as panel
    unum = 50
    vnum = 50
    uinc=1.0/unum
    vinc=1.0/vnum
    automata = []
    for i in range(unum) : 
        automata.append([])
        for j in range(vnum) : 
            automata[i].append(MyAutomaton(IVec(i*uinc, j*vinc, 0),\
                                           uinc, vinc, -1, surf))
    
    #connecting adjacent automata
    for i in range(unum) : 
        for j in range(vnum) : 
            if i > 0 :
                automata[i][j].left = automata[i-1][j]
            if i < unum-1 :
                automata[i][j].right = automata[i+1][j]
            if j > 0 :
                automata[i][j].down = automata[i][j-1]
            if j < vnum-1 :
                automata[i][j].up = automata[i][j+1]
            if i==0 : 
                automata[i][j].state = 1  # activated
    surf.del() 
    IG.fill()


class MyAutomaton(IAgent) :
    
    def __init__(self, pt, w, h, d, s) : 
        self.pos = pt
        self.width = w
        self.height = h
        self.depth = d
        self.surf = s
        self.state = 0 # current state
        self.lst = 0 # left state
        self.rst = 0 # right state
        self.dst = 0 # down state
        self.ust = 0 # up state
        self.box = None
        self.left = None # left automaton
        self.right = None # right automaton
        self.up = None # up automaton
        self.down = None # down automaton
    
    def interact(self, agents) : 
        # reset states
        self.lst=self.rst=self.dst=self.ust=0
        if self.left is not None :
            self.lst = self.left.state
        if self.right is not None : 
            self.rst = self.right.state
        if self.down is not None :
            self.dst = self.down.state
        if self.up is not None :
            self.ust = self.up.state
  
    def update(self) : 
        if self.state==0 and self.box is not None : 
            self.box.del()
            self.box = None
        elif self.state==1 and self.box is None :
            boxPts = [[[ self.surf.pt(self.pos.x(), self.pos.y()),\
                         self.surf.pt(self.pos.x(), self.pos.y(), self.depth) ],\
                       [ self.surf.pt(self.pos.x(), self.pos.y()+self.height),\
                         self.surf.pt(self.pos.x(), self.pos.y()+self.height, self.depth) ]],\
                      [[ self.surf.pt(self.pos.x()+self.width, self.pos.y()),\
                         self.surf.pt(self.pos.x()+self.width, self.pos.y(), self.depth) ],\
                       [ self.surf.pt(self.pos.x()+self.width, self.pos.y()+self.height),\
                         self.surf.pt(self.pos.x()+self.width, self.pos.y()+self.height, self.depth)]]]
            self.box = IBox(boxPts)
        # automaton update rule table
        if self.lst==0 and self.rst==0 and self.dst==0 and self.ust==0 : 
            self.state=0 
        elif self.lst==1 and self.rst==0 and self.dst==0 and self.ust==0 : 
            self.state=1 
        elif self.lst==0 and self.rst==1 and self.dst==0 and self.ust==0 : 
            self.state=1 
        elif self.lst==1 and self.rst==1 and self.dst==0 and self.ust==0 : 
            self.state=1 
        elif self.lst==0 and self.rst==0 and self.dst==1 and self.ust==0 : 
            self.state=1 
        elif self.lst==1 and self.rst==0 and self.dst==1 and self.ust==0 : 
            self.state=0 
        elif self.lst==0 and self.rst==1 and self.dst==1 and self.ust==0 : 
            self.state=0 
        elif self.lst==1 and self.rst==1 and self.dst==1 and self.ust==0 : 
            self.state=0 
        elif self.lst==0 and self.rst==0 and self.dst==0 and self.ust==1 : 
            self.state=0 
        elif self.lst==1 and self.rst==0 and self.dst==0 and self.ust==1 : 
            self.state=0 
        elif self.lst==0 and self.rst==1 and self.dst==0 and self.ust==1 : 
            self.state=0 
        elif self.lst==1 and self.rst==1 and self.dst==0 and self.ust==1 : 
            self.state=0 
        elif self.lst==0 and self.rst==0 and self.dst==1 and self.ust==1 : 
            self.state=0 
        elif self.lst==1 and self.rst==0 and self.dst==1 and self.ust==1 : 
            self.state=1 
        elif self.lst==0 and self.rst==1 and self.dst==1 and self.ust==1 : 
            self.state=1 
        elif self.lst==1 and self.rst==1 and self.dst==1 and self.ust==1 : 
            self.state=0 

The code above had interact method with the longer version of interact(ArrayList< IDynamics >). This is because the state variables of lst, rst, dst, ust are updated only once at the whole system update process. If you use interact(IDynamics), they are updated unnecessarily as many as the number of total agents in the system.

The code below shows an example to put diferent type of geometries on an automaton from a box. When the state of the automaton is 0, it puts simple rectangular panel, but when the state is 1, it puts a surface with an aperture whose size and depth corresponds the time length of the state being 1, by the integer variable count.

add_library('igeo')

def setup() : 
    size(480, 360, IG.GL)
    IConfig.syncDrawAndDynamics=True
    IG.duration(60)
    IG.open("surface13.3dm")
    surf = IG.surface(0)
    # put automaton as panel
    unum = 50
    vnum = 50
    uinc=1.0/unum
    vinc=1.0/vnum
    automata = []
    for i in range(unum) : 
        automata.append([])
        for j in range(vnum) : 
            automata[i].append(MyAutomaton(IVec(i*uinc, j*vinc, 0),\
                                           uinc, vinc, 1, surf))
    
    #connecting adjacent automata
    for i in range(unum) : 
        for j in range(vnum) : 
            if i > 0 :
                automata[i][j].left = automata[i-1][j]
            if i < unum-1 :
                automata[i][j].right = automata[i+1][j]
            if j > 0 :
                automata[i][j].down = automata[i][j-1]
            if j < vnum-1 :
                automata[i][j].up = automata[i][j+1]
            if i==0 : 
                automata[i][j].state = 1  # activated
    surf.del() 
    IG.fill()


class MyAutomaton(IAgent) :
    
    def __init__(self, pt, w, h, d, s) : 
        self.pos = pt
        self.width = w
        self.height = h
        self.depth = d
        self.surf = s
        self.state = 0 # current state
        self.lst = 0 # left state
        self.rst = 0 # right state
        self.dst = 0 # down state
        self.ust = 0 # up state
        self.panelSurf = None
        self.left = None # left automaton
        self.right = None # right automaton
        self.up = None # up automaton
        self.down = None # down automaton
        self.count = 0
    
    def interact(self, agents) : 
        # reset states
        self.lst=self.rst=self.dst=self.ust=0
        if self.left is not None :
            self.lst = self.left.state
        if self.right is not None : 
            self.rst = self.right.state
        if self.down is not None :
            self.dst = self.down.state
        if self.up is not None :
            self.ust = self.up.state
  
    def update(self) : 
        prevState = self.state
        # automaton update rule table
        if self.lst==0 and self.rst==0 and self.dst==0 and self.ust==0 : 
            self.state=0 
        elif self.lst==1 and self.rst==0 and self.dst==0 and self.ust==0 : 
            self.state=1 
        elif self.lst==0 and self.rst==1 and self.dst==0 and self.ust==0 : 
            self.state=1 
        elif self.lst==1 and self.rst==1 and self.dst==0 and self.ust==0 : 
            self.state=1 
        elif self.lst==0 and self.rst==0 and self.dst==1 and self.ust==0 : 
            self.state=1 
        elif self.lst==1 and self.rst==0 and self.dst==1 and self.ust==0 : 
            self.state=0 
        elif self.lst==0 and self.rst==1 and self.dst==1 and self.ust==0 : 
            self.state=0 
        elif self.lst==1 and self.rst==1 and self.dst==1 and self.ust==0 : 
            self.state=0 
        elif self.lst==0 and self.rst==0 and self.dst==0 and self.ust==1 : 
            self.state=0 
        elif self.lst==1 and self.rst==0 and self.dst==0 and self.ust==1 : 
            self.state=0 
        elif self.lst==0 and self.rst==1 and self.dst==0 and self.ust==1 : 
            self.state=0 
        elif self.lst==1 and self.rst==1 and self.dst==0 and self.ust==1 : 
            self.state=0 
        elif self.lst==0 and self.rst==0 and self.dst==1 and self.ust==1 : 
            self.state=0 
        elif self.lst==1 and self.rst==0 and self.dst==1 and self.ust==1 : 
            self.state=1 
        elif self.lst==0 and self.rst==1 and self.dst==1 and self.ust==1 : 
            self.state=1 
        elif self.lst==1 and self.rst==1 and self.dst==1 and self.ust==1 : 
            self.state=0 
    
        if self.state != prevState : # state change
            
            if self.panelSurf is not None : 
                self.panelSurf.del()
            if self.state==0 : 
                # just rectangular panel
                panelPts = [[ self.surf.pt(self.pos.x(), self.pos.y()), 
                              self.surf.pt(self.pos.x(), self.pos.y()+self.height) ], 
                            [ self.surf.pt(self.pos.x()+self.width, self.pos.y()), 
                              self.surf.pt(self.pos.x()+self.width, self.pos.y()+self.height) ]]
                self.panelSurf = ISurface(panelPts).clr(self.count*0.02)
            elif self.state==1 : 
                #3 by 4 control points
                panelPts = []
                panelPts.append([])
                panelPts.append([])
                panelPts.append([])
                #rectangular border
                panelPts[0].append(self.surf.pt(self.pos.x(), self.pos.y()))
                panelPts[0].append(self.surf.pt(self.pos.x()+self.width, self.pos.y()))
                panelPts[0].append(self.surf.pt(self.pos.x()+self.width, self.pos.y()+self.height))
                panelPts[0].append(self.surf.pt(self.pos.x(), self.pos.y()+self.height))
                #rotated central opening
                angle = self.count*PI*0.0025 
                factor = self.count*0.015
                center = self.surf.pt(self.pos.x()+self.width/2, self.pos.y()+self.height/2)
                nml = self.surf.nml(self.pos.x()+self.width/2, self.pos.y()+self.height/2)
                panelPts[1].append(panelPts[0][0].dup().scale(center,factor).rot(center,nml,angle))
                panelPts[1].append(panelPts[0][1].dup().scale(center,factor).rot(center,nml,angle))
                panelPts[1].append(panelPts[0][2].dup().scale(center,factor).rot(center,nml,angle))
                panelPts[1].append(panelPts[0][3].dup().scale(center,factor).rot(center,nml,angle))
                #rotated and pushed central opening
                shift = nml.dup().len(self.depth*self.count*0.02)
                panelPts[2].append(panelPts[1][0].dup().add(shift).rot(center,nml,angle))
                panelPts[2].append(panelPts[1][1].dup().add(shift).rot(center,nml,angle))
                panelPts[2].append(panelPts[1][2].dup().add(shift).rot(center,nml,angle))
                panelPts[2].append(panelPts[1][3].dup().add(shift).rot(center,nml,angle))
                # u degree 1, v degree 2, closed in u, open in v
                self.panelSurf = ISurface(panelPts,2,1,False,True).clr(self.count*0.02)
        
        if self.state > 0 :
            self.count += 1 # count time of state being positive


(back to the list of tutorials)

HOME
FOR PROCESSING
DOWNLOAD
DOCUMENTS
TUTORIALS (Java / Python)
GALLERY
SOURCE CODE(GitHub)
ABOUT