-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathboid.js
More file actions
241 lines (185 loc) · 7.69 KB
/
boid.js
File metadata and controls
241 lines (185 loc) · 7.69 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
// For randomizing vectors
var SEPARATION_MULTIPLIER = 1.5;
var ALIGNMENT_MULTIPLIER = 0.5;
var COHESION_MULTIPLIER = 0.5;
var SEPARATION_DISTANCE = 25; // Minimum distance boids are from each other
var ALIGNMENT_DISTANCE = 50;
var COHESION_DISTANCE = 50;
var MAX_SPEED = 2;
var MAX_STEER = 0.05;
function Boid(game, x, y, radius) {
this.image = ASSET_MANAGER.getAsset("./img/fish.png");
// Boid properties
this.x = x;
this.y = y;
this.radius = radius;
// Boid physics
this.angle = Math.random() * Math.PI * 2;
this.position = new Victor(this.x, this.y);
this.velocity = new Victor(Math.cos(this.angle), Math.sin(this.angle));
this.acceleration = new Victor(0, 0);
// Craig Reynold's flocking algorithm
this.separationVector = new Victor(0, 0);
this.alignmentVector = new Victor(0, 0);
this.cohesionVector = new Victor(0, 0);
Entity.call(this, game, 0, this.y, this.width, this.height);
}
Boid.prototype = new Entity();
Boid.prototype.constructor = Boid;
Boid.prototype.update = function () {
// First apply flocking algorithms' forces to this boid
this.applyFlockingRules(boids);
// Then update velocity
this.velocity.add(this.acceleration);
// Cap speeds at defined limit
if (this.velocity.x > MAX_SPEED) this.velocity.x = MAX_SPEED;
if (this.velocity.y > MAX_SPEED) this.velocity.y = MAX_SPEED;
if (this.velocity.x < -MAX_SPEED) this.velocity.x = -MAX_SPEED;
if (this.velocity.y < -MAX_SPEED) this.velocity.y = -MAX_SPEED;
// Update boid's position
this.position.add(this.velocity);
this.x = this.position.x;
this.y = this.position.y;
// Update angle
// Reset acceleration to 0
this.acceleration.multiplyScalar(0);
// Check for border wrap-arounds
if (this.position.x < -this.radius) this.position.x = SURFACE_WIDTH + this.radius;
if (this.position.y < -this.radius) this.position.y = SURFACE_HEIGHT + this.radius;
if (this.position.x > SURFACE_WIDTH + this.radius) this.position.x = -this.radius;
if (this.position.y > SURFACE_HEIGHT + this.radius) this.position.y = -this.radius;
Entity.prototype.update.call(this);
};
Boid.prototype.draw = function (ctx) {
// ctx.drawImage(ASSET_MANAGER.getAsset("./img/fish.png"), this.position.x, this.position.y, 50, 50);
// TODO: Draw arrows representing separation, alignment, cohesion vectors
Entity.prototype.draw.call(this, ctx);
};
Boid.prototype.applyFlockingRules = function (boids) {
// Obtain the three components of Craig Reynold's flocking algorithm:
this.separationVector = this.separate(boids);
this.alignmentVector = this.align(boids);
this.cohesionVector = this.cohesion(boids);
// Give a weight to the forces
this.separationVector.multiplyScalar(SEPARATION_MULTIPLIER);
this.alignmentVector.multiplyScalar(ALIGNMENT_MULTIPLIER);
this.cohesionVector.multiplyScalar(COHESION_MULTIPLIER);
// Apply these forces to this boid's acceleration
this.acceleration.add(this.separationVector);
this.acceleration.add(this.alignmentVector);
this.acceleration.add(this.separationVector);
};
Boid.prototype.separate = function (boids) {
var steer = new Victor(0, 0); // Final direction the boid wants to steer to
var neighbors = 0;
// For each boid, check
for (var i = 0, len = boids.length; i < len; i++) {
if (this != boids[i]) { // Ignore self
// Euclidean distance between this boid and other
var distance = this.position.distance(boids[i].position);
// Ignore comparing to self and only consider boids that are too close
if (distance < SEPARATION_DISTANCE) {
// Find vector pointing away from other boid
var oppositeDir = this.position.clone(); // Clone this boid's position
oppositeDir = oppositeDir.subtract(boids[i].position);
// Normalize the vector
oppositeDir.normalize(); // TODO: Normalize i think works
oppositeDir.x /= distance;
oppositeDir.y /= distance;
//oppositeDir.divideScalar(distance); // TODO: Not sure if this is the right function call
// Add this steer to overall steering vector
//steer.add(oppositeDir); // TODO: Not sure if this add works
steer.x += oppositeDir.x;
steer.y += oppositeDir.y;
// Increase the number of boids that are too close
neighbors++;
}
}
// Average the steering vector by number of nearby boids
if (neighbors > 0) {
steer.x /= neighbors;
steer.y /= neighbors;
}
// If magnitude is great than 0,
if (steer.magnitude() > 0) {
steer.normalize();
steer.multiplyScalar(MAX_SPEED);
steer.subtract(this.velocity);
// Cap speeds at defined limit
if (steer.x > MAX_STEER) steer.x = MAX_STEER;
if (steer.y > MAX_STEER) steer.y = MAX_STEER;
if (steer.x < -MAX_STEER) steer.x = -MAX_STEER;
if (steer.y < -MAX_STEER)steer.y = -MAX_STEER;
}
//console.log(steer.toString());
return steer;
}
};
// Alignment: Calculate average velocity of neraby birds
Boid.prototype.align = function (boids) {
var steer = new Victor(0, 0);
var neighbors = 0;
// Calculate average velocity of nearby boids
for (var i = 0, len = boids.length; i < len; i++) {
if (this != boids[i]) { // Ignore self
// Euclidean distance between this boid and other
var distance = this.position.distance(boids[i].position);
// Ignore comparing to self and only consider boids that are too close
if (distance < ALIGNMENT_DISTANCE) {
steer.add(boids[i].velocity);
neighbors++;
}
}
}
// Intended steering = desired velocity - boid's velocity
if (neighbors > 0) {
steer.divideScalar(neighbors);
steer.normalize();
steer.multiplyScalar(MAX_SPEED);
return steer.subtract(this.velocity);
} else {
return new Victor(0, 0);
}
};
// Steer boid to average location of all boids
Boid.prototype.cohesion = function (boids) {
// Steering towards average of all boids' location
var steer = new Victor(0, 0);
var neighbors = 0;
// Calculate average velocity of nearby boids
for (var i = 0, len = boids.length; i < len; i++) {
if (this != boids[i]) { // Ignore self
// Euclidean distance between this boid and other
var distance = this.position.distance(boids[i].position);
// Ignore comparing to self and only consider boids that are 'neighbors'
if (distance < COHESION_DISTANCE) {
steer.add(boids[i].position);
neighbors++;
}
}
}
if (neighbors > 0) {
steer.divideScalar(neighbors);
return this.steer(steer);
} else {
return new Victor(0, 0);
}
};
// Calculate and apply steering force towards the target vector
Boid.prototype.steer = function (vector) {
//console.log(vector.toString());
var destination = vector.clone();
destination.subtract(this.position);
destination.normalize();
destination.multiplyScalar(MAX_SPEED);
var steer = destination.subtract(this.velocity);
// Cap speeds at defined limit
if (steer.x > MAX_STEER) steer.x = MAX_STEER;
if (steer.y > MAX_STEER) steer.y = MAX_STEER;
if (steer.x < -MAX_STEER)steer.x = -MAX_STEER;
if (steer.y < -MAX_STEER)steer.y = -MAX_STEER;
return steer;
};
Boid.prototype.toString = function () {
return 'Boid';
};