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