1. NodeBox 1
    1. Homepage
    2. NodeBox 3Node-based app for generative design and data visualization
    3. NodeBox OpenGLHardware-accelerated cross-platform graphics library
    4. NodeBox 1Generate 2D visuals using Python code (Mac OS X only)
  2. Gallery
  3. Documentation
  4. Forum
  5. Blog

Obstacle avoidance

classes-creature3

# --- GEOMETRY --------------------------------------------------------------
 
from math import degrees, atan2
from math import sqrt, pow
from math import radians, sin, cos
 
def angle(x0, y0, x1, y1):
    """ Returns the angle between two points.
    """
    return degrees( atan2(y1-y0, x1-x0) )    
 
def distance(x0, y0, x1, y1):
    """ Returns the distance between two points.
    """        
    return sqrt(pow(x1-x0, 2) + pow(y1-y0, 2))
 
def coordinates(x0, y0, distance, angle):
    """ Returns the coordinates of given distance and angle from a point.
    """
    return (x0 + cos(radians(angle)) * distance, 
            y0 + sin(radians(angle)) * distance)
 
# --- WORLD -----------------------------------------------------------------
 
class World:
    def __init__(self):
        self.obstacles = []
 
# --- OBSTACLE --------------------------------------------------------------
 
class Obstacle:
    def __init__(self, x, y, radius):
        self.x = x
        self.y = y
        self.radius = radius
 
# --- CREATURE --------------------------------------------------------------
 
class Creature:    
 
    def __init__(self, world, x, y, speed=1.0, size=4):        
 
        self.x = x
        self.y = y
        self.speed = speed
        self.size = size        
 
        self.world = world
        self.feeler_length = 25        
 
        self._vx = 0
        self._vy = 0    
 
    def heading(self):        
 
        """ Returns the creature's heading as angle in degrees.
        """ 
 
        return angle(self.x, self.y, self.x+self._vx, self.y+self._vy)    
 
    def avoid_obstacles(self, m=0.4, collision=4):
 
        # Find out where the creature is going.
        a = self.heading()        
 
        for obstacle in self.world.obstacles:            
 
            # Calculate the distance between the creature and the obstacle.
            d = distance(self.x, self.y, obstacle.x, obstacle.y)            
 
            # Respond faster if the creature is very close to an obstacle.
            if d - obstacle.radius < 4: m *= 10            
 
            # Check if the tip of the feeler falls inside the obstacle.
            # This is never true if the feeler length 
            # is smaller than the distance to the obstable.
            if d - obstacle.radius <= self.feeler_length:
                tip_x, tip_y = coordinates(self.x, self.y, d, a)    
                if distance(obstacle.x, obstacle.y, 
                            tip_x, tip_y) < obstacle.radius:                    
 
                    # Nudge the creature away from the obstacle.
                    m *= self.speed
 
                    if tip_x < obstacle.x: self._vx -= random(m)
                    if tip_y < obstacle.y: self._vy -= random(m)
                    if tip_x > obstacle.x: self._vx += random(m)
                    if tip_y > obstacle.y: self._vy += random(m) 
 
                    if d - obstacle.radius < 4: return
 
    def roam(self):
 
        """ Creature changes heading aimlessly.
        With its feeler it will scan for obstacles and steer away.
        """        
 
        self.avoid_obstacles()        
 
        v = self.speed
        self._vx += random(-v, v)
        self._vy += random(-v, v)
        self._vx = max(-v, min(self._vx, v))
        self._vy = max(-v, min(self._vy, v))         
 
        self.x += self._vx
        self.y += self._vy
 
# ---------------------------------------------------------------------------
 
size(550, 200)
 
# Create a world with obstacles at random positions.
world = World()
for i in range(15):
    obstacle = Obstacle(random(WIDTH), random(HEIGHT), random(10, 30))
    world.obstacles.append(obstacle)
 
# Create a number of ants.
ants = []
for i in range(4):
    ant = Creature(world, 225, 100, speed=2.0)
    ants.append(ant)
 
speed(30)
def draw():    
 
    background(0.2)    
 
    # Draw all the obstacles in the world.
    fill(0.5, 0.5)
    for obstacle in world.obstacles:
        oval(obstacle.x - obstacle.radius,
             obstacle.y - obstacle.radius,
             obstacle.radius * 2,
             obstacle.radius * 2)    
 
    # Draw each ant and its feeler.
    stroke(1)
    fill(1, 0.5)
    for ant in ants:
        push()
        transform(CORNER)
        translate(ant.x, ant.y)
        rotate(-ant.heading())
        line(0, 0, ant.feeler_length, 0)
        pop()
        oval(ant.x-ant.size*0.5, ant.y-ant.size*0.5, ant.size, ant.size)    
 
    # Move all the ants around.
    for ant in ants:
        ant.roam()