diff --git a/.idea/misc.xml b/.idea/misc.xml index 1c61456..e5c450e 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,5 +1,8 @@ + + http://www.w3.org/1999/xhtml + diff --git a/.idea/workspace.xml b/.idea/workspace.xml index 0f139e4..0623b09 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -2,6 +2,8 @@ + + @@ -27,7 +29,7 @@ - + @@ -38,7 +40,7 @@ - + @@ -46,12 +48,18 @@ + + + + + @@ -116,9 +124,44 @@ + + + + + - + + + + + + + - + + + + + 1344343952101 @@ -171,16 +234,16 @@ + - - + + - - + + - @@ -244,14 +307,14 @@ - + - + diff --git a/engine.py b/engine.py index fab84e8..8a9a59e 100644 --- a/engine.py +++ b/engine.py @@ -85,7 +85,7 @@ def draw(self): for ent in self.ents: self.draw_entity(ent) -simulation = optboids.FlockSimulation(100) +simulation = optboids.FlockSimulation(200) world = World(simulation.swarm) window = pyglet.window.Window(800, 750, vsync=True) diff --git a/log3.pstat b/log3.pstat new file mode 100644 index 0000000..1680d7d Binary files /dev/null and b/log3.pstat differ diff --git a/optboids.py b/optboids.py index 5df4c53..87428c2 100644 --- a/optboids.py +++ b/optboids.py @@ -67,22 +67,22 @@ def add_boids(self,point_list): Do it this way so that we can maintain numpy arrays of positions, vel, acc, and boid objects """ - num_boids = len(point_list) - self.vels = np.zeros(num_boids,2) - self.accels = np.zeros(num_boids,2) self.positions = np.asarray(point_list) + num_boids = len(point_list) + self.vels = np.zeros((num_boids,2)) + self.accels = np.zeros((num_boids,2)) + for i,p in enumerate(self.positions): v = self.vels[i] a = self.accels[i] - self.boids.append( Boid(p,v,a)) - + self.boids.append( Boid(p,v,a,i)) + #self.boids = np.asarray(self.boids) self.rebuild() - - def _insert(self, b): - c = self.find_cell_containing(b.x, b.y) - c.append(b) - + def get_neighbour_data(self, x, y, radius): + indexes = self.find_near(x,y,radius) + return self.positions[indexes],self.vels[indexes] + def cell_num(self,x, y): """ Find table cell containing position x,y @@ -100,22 +100,22 @@ def find_cell_containing(self, x, y): """returns the cell containing a position x,y""" return self.cell_table[self.cell_num(x,y)] - def find_near(self, x, y, influence_range): - """return objects within radius influence_range of point x,y""" - if influence_range == 0: - _nearObjects = self.find_cell_containing(x,y) - elif influence_range <= self.cell_width: - _nearObjects = self.find_neighbour_cells(x,y) + def find_near(self, x, y, radius): + """return indexes of objects within radius influence_range of point x,y""" + if radius == 0: + nearObjects = self.find_cell_containing(x,y) + elif radius <= self.cell_width: + nearObjects = self.get_neighbour_cells(x,y) else: - ext = ceil(influence_range/self.cell_width) - _nearObjects = self.find_extended(x,y,ext) + ext = ceil(radius/self.cell_width) + nearObjects = self.get_extended(x,y,ext) - return _nearObjects + return nearObjects - def find_neighbour_cells(self, x, y): + def get_neighbour_cells(self, x, y): return self.cell_table[self.cell_num(x,y)] - def find_extended(self, x, y, d): + def get_extended(self, x, y, d): """ use to find set of cells surrounding the cell containing position x,y @@ -133,7 +133,8 @@ def rebuild(self): for cell in self.cell_table.values(): cell[:] = [] for b in self.boids: - self._insert(b) + c = self.find_cell_containing(b.position[0], b.position[1]) + c.append(b.id) #only store indexes in the table, not objects class Boid(object): """ @@ -154,9 +155,15 @@ class Boid(object): align_strength *= max_steering_force sep_strength *= max_steering_force - # Get and set x and y - def _getx(self): return self.pos[0] + # Get and set x and y as alternative to pos vector + # probably don't need/shudn't use this :S + def _getx(self): return self.position[0] + def _setx(self, x): self.position[0] = x + x = property(_getx,_setx) + def _gety(self): return self.position[1] + def _sety(self, y): self.position[1] = y + y = property(_gety,_sety) # Get and set the speed as a scalar def _get_speed(self): @@ -186,7 +193,7 @@ def _set_rotation(self,r): rotation = property(_get_rotation,_set_rotation) def __init__(self,pos,v,a, index): - self.pos = pos #init pos vector + self.position = pos #init pos vector """ create a new boid at x,y """ self.x = pos[0] self.y = pos[1] @@ -194,6 +201,7 @@ def __init__(self,pos,v,a, index): self.velocity = v #should point this to numpy array slice self.velocity[0] = random.uniform(-self.max_speed, self.max_speed) self.velocity[1] = random.uniform(-self.max_speed, self.max_speed) + self.id = index def __repr__(self): @@ -206,13 +214,13 @@ def borders(self, top, bottom, left, right): m = np.identity(2) # 1 0 # 0 1 - if self.x < left: + if self.position[0] < left: self.velocity += m[0,:] * self.speed / 5 - if self.y < top: + if self.position[1] < top: self.velocity += m[1,:] * self.speed / 5 - if self.x > right: + if self.position[0] > right: self.velocity -= m[0,:] * self.speed / 5 - if self.y > bottom: + if self.position[1] > bottom: self.velocity -= m[1,:] * self.speed / 5 def update(self,t): @@ -225,60 +233,59 @@ def update(self,t): #limit(self.velocity,self.max_speed) self.position += self.velocity * t - def interact(self, actors): + def interact(self, positions, velocities): """ Unit-unit interaction method, combining a separation force, and velocity alignment force, and a cohesion force + Accepts numpy arrays of positions and velocities of neighbouring boids + We don't need to know anything else about the neighbours in this simple example + For more complex systems, could also pass an array of neighbour boid objects to + make use of their properties (e.g. friend/foe) """ - separate_force = Vector2(0,0) - align_force = Vector2(0,0) - cohesion_sum = Vector2(0,0) - cohesion_force = Vector2(0,0) - - count = 0 - for other in actors: - #vector pointing from neighbors to self - - diff = self.position - other.position - d = np.sqrt(diff.dot(diff)) + #using numpy to vectorize algorithm + #Cohesion force. Need to get the "average" position of neighbourhood + delta = self.position - positions #array of vector deltas + dist = np.sum(np.abs(delta)**2,axis=-1)**(1./2) - #Only perform on "neighbor" actors, i.e. ones closer than arbitrary - #dist or if the distance is not 0 (you are yourself) - if d > 0 and d < self.influence_range: - count = count+1 - - diff /= d - if d < self.minsep: - diff *= self.max_steering_force - else: - diff /= d # Weight by distance - - cohesion_sum += other.position # Add position - - separate_force += diff + select = np.where((dist < self.influence_range) & (0 0: - #calc the average of the separation vector - separate_force /=count - separate_force *= self.sep_strength - - #calc the average direction (normed avg velocity) - align_force /=count - align_force *= self.align_strength - - #calc the average position and calc steering vector towards it - cohesion_sum /= count - cohesion_force = self.steer(cohesion_sum,True) - cohesion_force *= self.cohesion_strength + ds_new=dist[:,np.newaxis] + filtered_delta /= ds_new #normalize each row vector + #divide again by distance to weight force + # otherwise, all diff magnitudes are equal to 1 (normalized) + filtered_delta /= ds_new #select correct elements from diff and ds + filtered_delta = np.clip(filtered_delta,0,self.max_steering_force) + s=np.where(dist < self.minsep) + if s[0].size != 0: + filtered_delta[s] += self.max_steering_force + + filtered_positions = positions[select] + filtered_velocities = velocities[select] + count = filtered_positions.size + if not count: return + + cohesion_sum = filtered_positions.sum(axis=0) + cohesion_sum /= count + #calc the average position and calc steering vector towards it + cohesion_force = self.steer(cohesion_sum,True) + cohesion_force *= self.cohesion_strength + + #calc the average of the separation vector + separate_force = filtered_delta.sum(axis=0) / count + separate_force *= self.sep_strength - #finally add the velocities - sum = separate_force + cohesion_force + align_force + #calc the average direction (normed avg velocity) + align_force = filtered_velocities.sum(axis=0) /count + align_force *= self.align_strength - #smooth the acceleration var - self.acceleration = 0.2*self.acceleration + sum + # add the forces + sum = separate_force + cohesion_force + align_force + + #smooth the acceleration var + self.acceleration = 0.02*self.acceleration + sum def steer(self, desired, slowdown=False): """ @@ -286,18 +293,18 @@ def steer(self, desired, slowdown=False): If slowdown is true the steering force is reduced as it approaches the target """ desired -= self.position - d = abs(desired) + d = np.linalg.norm(desired) #If the distance is greater than 0, calc steering (otherwise return zero vector) if d > 0: - desired.normalize() + desired /= d if slowdown and (d < 30.0): - desired *= self.max_steering_force*d/self.damp + desired *= self.max_steering_force * d / self.damp else: desired *= self.max_steering_force - + #TODO: wtf? steer = desired - self.velocity else: - steer = Vector2(0,0) + steer = np.zeros(2) return steer def seek(self, target, m=1): @@ -318,19 +325,17 @@ def __init__(self,starting_units = 100, field_size = 800): self.swarm = BoidSwarm(field_size+2*40,Boid.influence_range+5) self.field_size = field_size self.pad = self.swarm.cell_width #use to keep boids inside the play field - + starting_points = [] for i in range(starting_units): - b = Boid(random.uniform(100, 600), - random.uniform(100, 600)) - self.swarm.boids.append(b) + starting_points.append([random.uniform(100, 600), random.uniform(100, 600)]) + self.swarm.add_boids(starting_points) self.swarm.rebuild() - self._cumltime = 0 #calculation var - + def update(self,dt): """dt is in seconds""" for b in self.swarm.boids: - close_boids = self.swarm.find_near(b.x,b.y,b.influence_range) - b.interact(close_boids) + positions, velocities = self.swarm.get_neighbour_data(b.position[0],b.position[1],b.influence_range) + b.interact(positions, velocities) b.update(dt) w=self.field_size p=self.pad diff --git a/output2.png b/output2.png new file mode 100644 index 0000000..8b01efc Binary files /dev/null and b/output2.png differ diff --git a/output3.png b/output3.png new file mode 100644 index 0000000..7bc6826 Binary files /dev/null and b/output3.png differ