OLD | NEW |
| (Empty) |
1 // Copyright (c) 2011 The Native Client Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 /** | |
6 * @file | |
7 * The Goose object. This implements a single goose that flocks with other | |
8 * geese in the flocking simulation. Taken almost directly from | |
9 * http://processingjs.org/learning/topic/flocking with references to | |
10 * http://harry.me/2011/02/17/neat-algorithms---flocking. | |
11 */ | |
12 | |
13 | |
14 goog.provide('Goose'); | |
15 | |
16 goog.require('goog.Disposable'); | |
17 goog.require('goog.math.Vec2'); | |
18 | |
19 /** | |
20 * A Goose. Starts the goose with a location and a velocity. | |
21 * @param {?goog.math.Vec2} opt_location The initial location of this | |
22 * goose. If not given, then the goose is started at (0, 0). | |
23 * @param {?goog.math.Vec2} opt_velocity The initial velocity of this goose. | |
24 * If not given, then the goose is assigned a random velocity. | |
25 * @constructor | |
26 * @extends {goog.Disposable} | |
27 */ | |
28 Goose = function(opt_location, opt_velocity) { | |
29 goog.Disposable.call(this); | |
30 /** | |
31 * The goose's current location. | |
32 * @type {goog.math.Vec2} | |
33 * @private | |
34 */ | |
35 if (opt_location) { | |
36 this.location_ = opt_location.clone(); | |
37 } else { | |
38 this.location_ = new goog.math.Vec2(0, 0); | |
39 } | |
40 | |
41 /** | |
42 * The goose's current velocity. | |
43 * @type {goog.math.Vec2} | |
44 * @private | |
45 */ | |
46 if (opt_velocity) { | |
47 this.velocity_ = opt_velocity.clone(); | |
48 } else { | |
49 this.velocity_ = new goog.math.Vec2.randomUnit(); | |
50 } | |
51 }; | |
52 goog.inherits(Goose, goog.Disposable); | |
53 | |
54 /** | |
55 * The maximum speed of a goose. Measured in meters/second. | |
56 * @type {number} | |
57 */ | |
58 Goose.prototype.MAX_SPEED = 2.0; | |
59 | |
60 /** | |
61 * The maximum force that can be applied to turn a goose when computing the | |
62 * aligment. Measured in meters/second/second. | |
63 * @type {number} | |
64 */ | |
65 Goose.prototype.MAX_TURNING_FORCE = 0.05; | |
66 | |
67 /** | |
68 * The neighbour radius of a goose. Only geese within this radius will affect | |
69 * the flocking computations of this goose. Measured in pixels. | |
70 * @type {number} | |
71 */ | |
72 Goose.prototype.NEIGHBOUR_RADIUS = 64.0; | |
73 | |
74 /** | |
75 * The minimum distance that a goose can be from this goose. If another goose | |
76 * comes within this distance of this goose, the flocking algorithm tries to | |
77 * move the geese apart. Measured in pixels. | |
78 * @type {number} | |
79 */ | |
80 Goose.prototype.PERSONAL_SPACE = 32.0; | |
81 | |
82 /** | |
83 * The distance at which attractors have effect on a goose's direction. | |
84 * @type {number} | |
85 */ | |
86 Goose.prototype.ATTRACTOR_RADIUS = 320.0; | |
87 | |
88 /** | |
89 * The goose will try to turn towards geese within this distance (computed | |
90 * during the cohesion phase). Measured in pixels. | |
91 * @type {number} | |
92 */ | |
93 Goose.prototype.MAX_TURNING_DISTANCE = 100.0; | |
94 | |
95 /** | |
96 * The weights used when computing the weighted sum the three flocking | |
97 * components. | |
98 * @type {number} | |
99 */ | |
100 Goose.prototype.SEPARATION_WEIGHT = 2.0; | |
101 Goose.prototype.ALIGNMENT_WEIGHT = 1.0; | |
102 Goose.prototype.COHESION_WEIGHT = 1.0; | |
103 | |
104 /** | |
105 * Override of disposeInternal() to dispose of retained objects and unhook all | |
106 * events. | |
107 * @override | |
108 */ | |
109 Goose.prototype.disposeInternal = function() { | |
110 this.domElement_ = null; | |
111 Speedometer.superClass_.disposeInternal.call(this); | |
112 } | |
113 | |
114 /** | |
115 * Accessor for location. | |
116 * @return {goog.math.Vec2} The current location. | |
117 */ | |
118 Goose.prototype.location = function() { | |
119 return this.location_; | |
120 } | |
121 | |
122 /** | |
123 * Accessor for velocity. | |
124 * @return {goog.math.Vec2} The current velocity. | |
125 */ | |
126 Goose.prototype.velocity = function() { | |
127 return this.velocity_; | |
128 } | |
129 | |
130 /** | |
131 * Run one tick of the simulation. Compute a new acceleration based on the | |
132 * flocking algorithm (see Goose.flock()) and update the goose's location | |
133 * by integrating acceleration and velocity. | |
134 * @param {!Array.<Goose>} geese The list of all the geese in the flock. | |
135 * @param {!Array.<goog.math.Vec2>} attractors The list of attractors. | |
136 * Geese have affinity for these points. | |
137 * @param {?goog.math.Rect} opt_flockBox The geese will stay inside of this | |
138 * box. If the parameter is not given, the geese don't have boundaries. | |
139 */ | |
140 Goose.prototype.simulationTick = function(geese, attractors, opt_flockBox) { | |
141 var acceleration = this.flock(geese, attractors); | |
142 this.velocity_.add(acceleration); | |
143 // Limit the velocity to a maximum speed. | |
144 this.velocity_.clamp(this.MAX_SPEED); | |
145 this.location_.add(this.velocity_); | |
146 // Wrap the goose location to the flock box. | |
147 if (opt_flockBox) { | |
148 if (this.location_.x < opt_flockBox.left) { | |
149 this.location_.x = opt_flockBox.left + opt_flockBox.width; | |
150 } | |
151 if (this.location_.x > (opt_flockBox.left + opt_flockBox.width)) { | |
152 this.location_.x = opt_flockBox.left; | |
153 } | |
154 if (this.location_.y < opt_flockBox.top) { | |
155 this.location_.y = opt_flockBox.top + opt_flockBox.height; | |
156 } | |
157 if (this.location_.y > (opt_flockBox.top + opt_flockBox.height)) { | |
158 this.location_.y = opt_flockBox.top; | |
159 } | |
160 } | |
161 } | |
162 | |
163 /** | |
164 * Implement the flocking algorithm in five steps: | |
165 * 1. Compute the separation component, | |
166 * 2. Compute the alignment component, | |
167 * 3. Compute the flock cohesion component. | |
168 * 4. Compute the effect of the attractors and blend this in with the | |
169 * cohesion component. | |
170 * 5. Create a weighted sum of the three components and use this as the | |
171 * new acceleration for the goose. | |
172 * This is an O(n^2) version of the algorithm. There are ways to speed this | |
173 * up using spatial coherence techniques, but this version is much simpler. | |
174 * @param {!Array.<Goose>} geese The list of all the neighbouring geese (in | |
175 * this implementation, this is all the geese in the flock). | |
176 * @param {!Array.<goog.math.Vec2>} attractors The list of attractors. | |
177 * Geese have affinity for these points. | |
178 * @return {goog.math.Vec2} The acceleration vector for this goose based on the | |
179 * flocking algorithm. | |
180 */ | |
181 Goose.prototype.flock = function(geese, attractors) { | |
182 // Loop over all the neighbouring geese in the flock, accumulating | |
183 // the separation mean, the alignment mean and the cohesion mean. | |
184 var separationCount = 0; | |
185 var separation = new goog.math.Vec2(0, 0); | |
186 var alignCount = 0; | |
187 var alignment = new goog.math.Vec2(0, 0); | |
188 var cohesionCount = 0; | |
189 var cohesion = new goog.math.Vec2(0, 0); | |
190 for (var i = 0; i < geese.length; i++) { | |
191 var goose = geese[i]; | |
192 // Compute the distance from this goose to its neighbour. | |
193 var gooseDirection = goog.math.Vec2.difference( | |
194 this.location_, goose.location()); | |
195 var distance = gooseDirection.magnitude(); | |
196 separationCount = this.accumulateSeparation_( | |
197 distance, gooseDirection, separation, separationCount); | |
198 alignCount = this.accumulateAlignment_( | |
199 distance, goose, alignment, alignCount); | |
200 cohesionCount = this.accumulateCohesion_( | |
201 distance, goose, cohesion, cohesionCount); | |
202 } | |
203 // Compute the means and create a weighted sum. This becomes the goose's new | |
204 // acceleration. | |
205 if (separationCount > 0) { | |
206 separation.scale(1.0 / separationCount); | |
207 } | |
208 if (alignCount > 0) { | |
209 alignment.scale(1.0 / alignCount); | |
210 // Limit the effect that alignment has on the final acceleration. The | |
211 // alignment component can overpower the others if there is a big | |
212 // difference between this goose's velocity and its neighbours'. | |
213 alignment.clamp(this.MAX_TURNING_FORCE); | |
214 } | |
215 | |
216 // Compute the effect of the attractors and blend this in with the flock | |
217 // cohesion component. An attractor has to be within ATTRACTOR_RADIUS to | |
218 // effect the heading of a goose. | |
219 for (var i = 0; i < attractors.length; i++) { | |
220 var attractorDirection = goog.math.Vec2.difference( | |
221 attractors[i], this.location_); | |
222 var distance = attractorDirection.magnitude(); | |
223 if (distance < this.ATTRACTOR_RADIUS) { | |
224 attractorDirection.scale(1000); // Each attractor acts like 1000 geese. | |
225 cohesion.add(attractorDirection); | |
226 cohesionCount += 1; | |
227 } | |
228 } | |
229 | |
230 // If there is a non-0 cohesion component, steer the goose so that it tries | |
231 // to follow the flock. | |
232 if (cohesionCount > 0) { | |
233 cohesion.scale(1.0 / cohesionCount); | |
234 cohesion = this.turnTowardsTarget(cohesion); | |
235 } | |
236 | |
237 // Compute the weighted sum. | |
238 separation.scale(this.SEPARATION_WEIGHT); | |
239 alignment.scale(this.ALIGNMENT_WEIGHT); | |
240 cohesion.scale(this.COHESION_WEIGHT); | |
241 var weightedSum = cohesion.clone(); | |
242 weightedSum.add(alignment); | |
243 weightedSum.add(separation); | |
244 return weightedSum; | |
245 } | |
246 | |
247 /** | |
248 * Add a neighbouring goose's contribution to the separation mean. Only | |
249 * consider geese that have moved inside of this goose's personal space. | |
250 * Modifies the separation accumulator |separation| in-place. | |
251 * @param {!number} distance The distance from this goose to the neighbouring | |
252 * goose. | |
253 * @param {!goog.math.Vec2} gooseDirection The direction vector from this | |
254 * goose to the neighbour. | |
255 * @param {!goog.math.Vec2} separation The accumulated separation from all the | |
256 * neighbouring geese. | |
257 * @param {!number} separationCount The current number of geese that have | |
258 * contributed to the separation component so far. | |
259 * @return {!number} The new count of geese that contribute to the separation | |
260 * component. If the goose under consideration does not contribute, this | |
261 * value is the same as |separationCount|. | |
262 * @private | |
263 */ | |
264 Goose.prototype.accumulateSeparation_ = function(distance, | |
265 gooseDirection, | |
266 separation, | |
267 separationCount) { | |
268 if (distance > 0 && distance < this.PERSONAL_SPACE) { | |
269 var weightedDirection = gooseDirection; | |
270 weightedDirection.normalize(); | |
271 weightedDirection.scale(1.0 / distance); | |
272 separation.add(weightedDirection); | |
273 separationCount++; | |
274 } | |
275 return separationCount; | |
276 } | |
277 | |
278 /** | |
279 * Add a neighbouring goose's contribution to the alignment mean. Alignment is | |
280 * the average velocity of the neighbours. Only consider geese that are within | |
281 * |NEIGHBOUR_RADIUS|. Modifies the alignment accumulator |alignment| in-place. | |
282 * @param {!number} distance The distance from this goose to the neighbouring | |
283 * goose. | |
284 * @param {!Goose} goose The neighbouring goose under consideration. | |
285 * @param {!goog.math.Vec2} alignment The accumulated alignment from all the | |
286 * neighbouring geese. | |
287 * @param {!number} alignCount The current number of geese that have | |
288 * contributed to the alignment component so far. | |
289 * @return {!number} The new count of geese that contribute to the alignment | |
290 * component. If the goose under consideration does not contribute, this | |
291 * value is the same as |alignCount|. | |
292 * @private | |
293 */ | |
294 Goose.prototype.accumulateAlignment_ = function(distance, | |
295 goose, | |
296 alignment, | |
297 alignCount) { | |
298 if (distance > 0 && distance < this.NEIGHBOUR_RADIUS) { | |
299 alignment.add(goose.velocity()); | |
300 alignCount++; | |
301 } | |
302 return alignCount; | |
303 } | |
304 | |
305 /** | |
306 * Add a neighbouring goose's contribution to the cohesion mean. Cohesion is | |
307 * based on the average location of the neighbours. The goose attempts to | |
308 * point to this average location. Only consider geese that are within | |
309 * |NEIGHBOUR_RADIUS|. Modifies the cohesion accumulator |cohesion| in-place. | |
310 * @param {!number} distance The distance from this goose to the neighbouring | |
311 * goose. | |
312 * @param {!Goose} goose The neighbouring goose under consideration. | |
313 * @param {!goog.math.Vec2} cohesion The accumulated cohesion from all the | |
314 * neighbouring geese. | |
315 * @param {!number} cohesionCount The current number of geese that have | |
316 * contributed to the cohesion component so far. | |
317 * @return {!number} The new count of geese that contribute to the cohesion | |
318 * component. If the goose under consideration does not contribute, this | |
319 * value is the same as |cohesionCount|. | |
320 * @private | |
321 */ | |
322 Goose.prototype.accumulateCohesion_ = function(distance, | |
323 goose, | |
324 cohesion, | |
325 cohesionCount) { | |
326 if (distance > 0 && distance < this.NEIGHBOUR_RADIUS) { | |
327 cohesion.add(goose.location()); | |
328 cohesionCount++; | |
329 } | |
330 return cohesionCount; | |
331 } | |
332 | |
333 /** | |
334 * Turn the goose towards a target. The amount of turning force is clamped | |
335 * to |MAX_TURNING_FORCE|. | |
336 * @param {!goog.math.Vec2} target Turn the goose towards this target. | |
337 * @return {goog.math.Vec2} A vector representing the new direction of the | |
338 * goose. | |
339 */ | |
340 Goose.prototype.turnTowardsTarget = function(target) { | |
341 var desiredDirection = goog.math.Vec2.difference(target, this.location_); | |
342 var distance = desiredDirection.magnitude(); | |
343 var newDirection = new goog.math.Vec2(0, 0); | |
344 if (distance > 0) { | |
345 desiredDirection.normalize(); | |
346 // If the target is within the turning affinity distance, then make the | |
347 // desired direction based on distance to the target. Otherwise, base | |
348 // the desired direction on MAX_SPEED. | |
349 if (distance < this.MAX_TURNING_DISTANCE) { | |
350 // Some pretty arbitrary dampening. | |
351 desiredDirection.scale(this.MAX_SPEED * distance / 100.0); | |
352 } else { | |
353 desiredDirection.scale(this.MAX_SPEED); | |
354 } | |
355 newDirection = goog.math.Vec2.difference(desiredDirection, this.velocity_); | |
356 newDirection.clamp(this.MAX_TURNING_FORCE); | |
357 } | |
358 return newDirection; | |
359 } | |
360 | |
361 /** | |
362 * Clamp a vector to a maximum magnitude. Works on the vector in-place. | |
363 * @param {!goog.math.Vec2} v The vector. | |
364 * @param {!number} max_mag The maximum magnitude of the vector. | |
365 */ | |
366 goog.math.Vec2.prototype.clamp = function(max_mag) { | |
367 var mag = this.magnitude(); | |
368 if (mag > max_mag && max_mag != 0) { | |
369 this.normalize(); | |
370 this.scale(max_mag); | |
371 } | |
372 } | |
373 | |
374 /** | |
375 * Compute the "heading" of a vector - this is the angle in radians between | |
376 * the vector and the x-axis. | |
377 * @return {!number} The "heading" angle in radians. | |
378 */ | |
379 goog.math.Vec2.prototype.heading = function() { | |
380 var angle = Math.atan2(this.y, this.x); | |
381 return angle; | |
382 } | |
383 | |
OLD | NEW |