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 @@
+
+
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -154,9 +197,29 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ false
+
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