|
9 | 9 | // Adapted from Flocking Processing example by Daniel Schiffman: |
10 | 10 | // http://processing.org/learning/topics/flocking.html |
11 | 11 |
|
12 | | - project.currentStyle = { |
13 | | - strokeColor: 'white', |
14 | | - strokeWidth: 2, |
15 | | - strokeCap: 'round' |
16 | | - }; |
17 | | - |
18 | | - var head = new Path.Ellipse([0, 0], [13, 8]); |
19 | | - head.fillColor = 'white'; |
20 | | - head.strokeColor = null; |
21 | | - var headSymbol = new Symbol(head); |
22 | | - |
23 | | - var size = view.size; |
24 | | - |
25 | 12 | var Boid = Base.extend({ |
26 | 13 | initialize: function(position, maxSpeed, maxForce) { |
27 | 14 | var strength = Math.random() * 0.5; |
28 | | - this.acc = new Point(0, 0); |
29 | | - this.vel = Point.random() * 2 - 1; |
30 | | - this.loc = position.clone(); |
31 | | - this.r = 30; |
| 15 | + this.acceleration = new Point(); |
| 16 | + this.vector = Point.random() * 2 - 1; |
| 17 | + this.position = position.clone(); |
| 18 | + this.radius = 50; |
32 | 19 | this.maxSpeed = maxSpeed + strength; |
33 | 20 | this.maxForce = maxForce + strength; |
34 | | - this.head = headSymbol.place(); |
35 | | - this.path = new Path(); |
36 | | - this.shortPath = new Path(); |
37 | | - this.shortPath.strokeWidth = 4; |
| 21 | + this.points = []; |
38 | 22 | for (var i = 0, l = strength * 10 + 10; i < l; i++) { |
39 | | - this.path.add(this.loc); |
| 23 | + this.points.push(new Point()); |
40 | 24 | } |
41 | | - |
42 | | - this.firstSegment = this.path.firstSegment; |
43 | 25 | this.count = 0; |
44 | | - this.lastRot = 0; |
| 26 | + this.lastAngle = 0; |
| 27 | + this.distances = []; |
| 28 | + this.createItems(); |
45 | 29 | }, |
46 | 30 |
|
47 | 31 | run: function(boids) { |
48 | | - this.lastLoc = this.loc.clone(); |
| 32 | + this.lastLoc = this.position.clone(); |
49 | 33 | if (!groupTogether) { |
50 | 34 | this.flock(boids); |
51 | 35 | } else { |
52 | 36 | this.align(boids); |
53 | 37 | } |
54 | 38 | this.borders(); |
55 | | - |
56 | 39 | this.update(); |
57 | | - this.firstSegment.point = this.loc; |
58 | | - var lastPoint = this.firstSegment.point; |
59 | | - var lastVector = this.loc - this.lastLoc; |
60 | | - var segments = this.path.segments; |
61 | | - for (var i = 1, l = segments.length; i < l; i++) { |
62 | | - var segment = segments[i]; |
63 | | - var vector = lastPoint - segment.point; |
64 | | - this.count += this.vel.length * 10; |
65 | | - var rotLength = Math.sin((this.count + i * 3) / 300); |
66 | | - var rotated = lastVector.rotate(90).normalize(rotLength); |
67 | | - lastPoint += lastVector.normalize(-5 - this.vel.length / 3); |
68 | | - segment.point = lastPoint; |
69 | | - segment.point += rotated; |
| 40 | + this.calculateTail(); |
| 41 | + this.updateItems(); |
| 42 | + }, |
| 43 | + |
| 44 | + calculateTail: function() { |
| 45 | + var points = this.points; |
| 46 | + var speed = this.vector.length; |
| 47 | + var pieceLength = 5 + speed * 0.3; |
| 48 | + var point = points[0] = this.position.clone(); |
| 49 | + var lastVector = this.vector.clone(); |
| 50 | + for (var i = 1, l = points.length; i < l; i++) { |
| 51 | + this.count += speed * 15; |
| 52 | + var vector = point - points[i]; |
| 53 | + var rotated = lastVector.rotate(90); |
| 54 | + rotated.length = Math.sin((this.count + i * 3) * 0.003); |
| 55 | + lastVector.length = -pieceLength; |
| 56 | + point += lastVector; |
| 57 | + points[i] = point + rotated; |
70 | 58 | lastVector = vector; |
71 | 59 | } |
| 60 | + }, |
| 61 | + |
| 62 | + createItems: function() { |
| 63 | + this.head = (project.symbols[0] |
| 64 | + ? project.symbols[0] |
| 65 | + : new Symbol(new Path.Ellipse({ |
| 66 | + from: [0, 0], |
| 67 | + to: [13, 8], |
| 68 | + fillColor: 'white' |
| 69 | + }))).place(); |
| 70 | + this.path = new Path({ |
| 71 | + strokeColor: 'white', |
| 72 | + strokeWidth: 2, |
| 73 | + strokeCap: 'round' |
| 74 | + }); |
| 75 | + this.shortPath = new Path({ |
| 76 | + strokeColor: 'white', |
| 77 | + strokeWidth: 4, |
| 78 | + strokeCap: 'round' |
| 79 | + }); |
| 80 | + }, |
| 81 | + |
| 82 | + updateItems: function() { |
| 83 | + this.path.segments = this.points; |
72 | 84 | this.path.smooth(); |
73 | | - this.head.position = this.loc; |
74 | | - var vector = this.loc - this.lastLoc; |
75 | | - var rot = vector.angle; |
76 | | - this.head.rotate(rot - this.lastRot); |
77 | | - this.lastRot = rot; |
78 | | - this.shortPath.segments = segments.slice(0, 3); |
| 85 | + this.shortPath.segments = this.points.slice(0, 3); |
| 86 | + |
| 87 | + this.head.position = this.position; |
| 88 | + var angle = this.vector.angle; |
| 89 | + this.head.rotate(angle - this.lastAngle); |
| 90 | + this.lastAngle = angle; |
79 | 91 | }, |
80 | 92 |
|
81 | 93 | // We accumulate a new acceleration each time based on three rules |
82 | 94 | flock: function(boids) { |
83 | | - var sep = this.separate(boids) * 3; |
84 | | - var ali = this.align(boids); |
85 | | - var coh = this.cohesion(boids); |
86 | | - this.acc += sep + ali + coh; |
| 95 | + this.calculateDistances(boids); |
| 96 | + var separation = this.separate(boids) * 3; |
| 97 | + var alignment = this.align(boids); |
| 98 | + var cohesion = this.cohesion(boids); |
| 99 | + this.acceleration += separation + alignment + cohesion; |
| 100 | + }, |
| 101 | + |
| 102 | + calculateDistances: function(boids) { |
| 103 | + for (var i = 0, l = boids.length; i < l; i++) { |
| 104 | + var other = boids[i]; |
| 105 | + this.distances[i] = other.position.getDistance(this.position, true); |
| 106 | + } |
87 | 107 | }, |
88 | 108 |
|
89 | 109 | update: function() { |
90 | 110 | // Update velocity |
91 | | - this.vel += this.acc; |
| 111 | + this.vector += this.acceleration; |
92 | 112 | // Limit speed (vector#limit?) |
93 | | - this.vel.length = Math.min(this.maxSpeed, this.vel.length); |
94 | | - this.loc += this.vel; |
| 113 | + this.vector.length = Math.min(this.maxSpeed, this.vector.length); |
| 114 | + this.position += this.vector; |
95 | 115 | // Reset acceleration to 0 each cycle |
96 | | - this.acc.length = 0; |
| 116 | + this.acceleration = new Point(); |
97 | 117 | }, |
98 | 118 |
|
99 | 119 | seek: function(target) { |
100 | | - this.acc += this.steer(target, false); |
| 120 | + this.acceleration += this.steer(target, false); |
101 | 121 | }, |
102 | 122 |
|
103 | 123 | arrive: function(target) { |
104 | | - this.acc += this.steer(target, true); |
| 124 | + this.acceleration += this.steer(target, true); |
| 125 | + }, |
| 126 | + |
| 127 | + borders: function() { |
| 128 | + var vector = new Point(); |
| 129 | + var position = this.position; |
| 130 | + var radius = this.radius; |
| 131 | + var size = view.size; |
| 132 | + if (position.x < -radius) vector.x = size.width + radius; |
| 133 | + if (position.y < -radius) vector.y = size.height + radius; |
| 134 | + if (position.x > size.width + radius) vector.x = -radius; |
| 135 | + if (position.y > size.height + radius) vector.y = -radius; |
| 136 | + if (!vector.isZero()) |
| 137 | + this.position += vector; |
105 | 138 | }, |
106 | 139 |
|
107 | 140 | // A method that calculates a steering vector towards a target |
108 | 141 | // Takes a second argument, if true, it slows down as it approaches |
109 | 142 | // the target |
110 | 143 | steer: function(target, slowdown) { |
111 | 144 | var steer, |
112 | | - desired = target - this.loc, |
113 | | - d = desired.length; |
114 | | - if (d > 0) { |
115 | | - // Two options for desired vector magnitude |
116 | | - // (1 -- based on distance, 2 -- maxSpeed) |
117 | | - if (slowdown && d < 100) { |
118 | | - // This damping is somewhat arbitrary: |
119 | | - desired.length = this.maxSpeed * (d / 100); |
120 | | - } else { |
121 | | - desired.length = this.maxSpeed; |
122 | | - } |
123 | | - steer = desired - this.vel; |
124 | | - steer.length = Math.min(this.maxForce, steer.length); |
| 145 | + desired = target - this.position; |
| 146 | + var distance = desired.length; |
| 147 | + // Two options for desired vector magnitude |
| 148 | + // (1 -- based on distance, 2 -- maxSpeed) |
| 149 | + if (slowdown && distance < 100) { |
| 150 | + // This damping is somewhat arbitrary: |
| 151 | + desired.length = this.maxSpeed * (distance * 0.001); |
125 | 152 | } else { |
126 | | - steer = new Point(0, 0); |
| 153 | + desired.length = this.maxSpeed; |
127 | 154 | } |
| 155 | + steer = desired - this.vector; |
| 156 | + steer.length = Math.min(this.maxForce, steer.length); |
128 | 157 | return steer; |
129 | 158 | }, |
130 | 159 |
|
131 | | - borders: function() { |
132 | | - var loc = this.loc; |
133 | | - var r = this.r; |
134 | | - var oldLoc = this.loc.clone(); |
135 | | - var width = size.width; |
136 | | - var height = size.height; |
137 | | - if (loc.x < -r) loc.x = width + r; |
138 | | - if (loc.y < -r) loc.y = height + r; |
139 | | - if (loc.x > width + r) loc.x = -r; |
140 | | - if (loc.y > height + r) loc.y = -r; |
141 | | - var vector = this.loc - oldLoc; |
142 | | - if (!vector.isZero()) |
143 | | - this.path.position += vector; |
144 | | - }, |
145 | | - |
146 | 160 | separate: function(boids) { |
147 | | - var desiredSeperation = 60; |
148 | | - var steer = new Point(0, 0); |
| 161 | + var desiredSeperation = 3600; |
| 162 | + var steer = new Point(); |
149 | 163 | var count = 0; |
150 | 164 | // For every boid in the system, check if it's too close |
151 | 165 | for (var i = 0, l = boids.length; i < l; i++) { |
152 | | - var other = boids[i]; |
153 | | - var d = other.loc.getDistance(this.loc); |
154 | | - if (d > 0 && d < desiredSeperation) { |
| 166 | + var distance = this.distances[i]; |
| 167 | + if (distance > 0 && distance < desiredSeperation) { |
155 | 168 | // Calculate vector pointing away from neighbor |
156 | | - var diff = this.loc - other.loc; |
157 | | - steer += diff.normalize(1 / d); |
| 169 | + var delta = this.position - boids[i].position; |
| 170 | + delta.length = 1 / distance; |
| 171 | + steer += delta; |
158 | 172 | count++; |
159 | 173 | } |
160 | 174 | } |
161 | 175 | // Average -- divide by how many |
162 | 176 | if (count > 0) |
163 | 177 | steer /= count; |
164 | | - if (steer.length > 0) { |
| 178 | + if (!steer.isZero()) { |
165 | 179 | // Implement Reynolds: Steering = Desired - Velocity |
166 | 180 | steer.length = this.maxSpeed; |
167 | | - steer -= this.vel; |
| 181 | + steer -= this.vector; |
168 | 182 | steer.length = Math.min(steer.length, this.maxForce); |
169 | 183 | } |
170 | 184 | return steer; |
|
174 | 188 | // For every nearby boid in the system, calculate the average velocity |
175 | 189 | align: function(boids) { |
176 | 190 | var neighborDist = 25; |
177 | | - var steer = new Point(0, 0); |
| 191 | + var steer = new Point(); |
178 | 192 | var count = 0; |
179 | | - var nearest = 999; |
180 | | - var closestPoint; |
181 | 193 | for (var i = 0, l = boids.length; i < l; i++) { |
182 | | - var other = boids[i]; |
183 | | - var d = this.loc.getDistance(other.loc); |
184 | | - if (d > 0 && d < nearest) { |
185 | | - closestPoint = other.loc; |
186 | | - nearest = d; |
187 | | - } |
188 | | - if (d > 0 && d < neighborDist) { |
189 | | - steer += other.vel; |
| 194 | + var distance = this.distances[i]; |
| 195 | + if (distance > 0 && distance < neighborDist) { |
| 196 | + steer += boids[i].vector; |
190 | 197 | count++; |
191 | 198 | } |
192 | 199 | } |
193 | 200 |
|
194 | 201 | if (count > 0) |
195 | 202 | steer /= count; |
196 | | - if (steer.length > 0) { |
| 203 | + if (!steer.isZero()) { |
197 | 204 | // Implement Reynolds: Steering = Desired - Velocity |
198 | 205 | steer.length = this.maxSpeed; |
199 | | - steer -= this.vel; |
| 206 | + steer -= this.vector; |
200 | 207 | steer.length = Math.min(steer.length, this.maxForce); |
201 | 208 | } |
202 | 209 | return steer; |
|
206 | 213 | // For the average location (i.e. center) of all nearby boids, |
207 | 214 | // calculate steering vector towards that location |
208 | 215 | cohesion: function(boids) { |
209 | | - var neighborDist = 100; |
| 216 | + var neighborDist = 10000; |
210 | 217 | var sum = new Point(0, 0); |
211 | 218 | var count = 0; |
212 | 219 | for (var i = 0, l = boids.length; i < l; i++) { |
213 | | - var other = boids[i]; |
214 | | - var d = this.loc.getDistance(other.loc); |
215 | | - if (d > 0 && d < neighborDist) { |
216 | | - sum += other.loc; // Add location |
| 220 | + var distance = this.distances[i]; |
| 221 | + if (distance > 0 && distance < neighborDist) { |
| 222 | + sum += boids[i].position; // Add location |
217 | 223 | count++; |
218 | 224 | } |
219 | 225 | } |
|
226 | 232 | } |
227 | 233 | }); |
228 | 234 |
|
229 | | - var heartPath = Project.importJson('["Path",{"pathData":"M514.69629,624.70313c-7.10205,-27.02441 -17.2373,-52.39453 -30.40576,-76.10059c-13.17383,-23.70703 -38.65137,-60.52246 -76.44434,-110.45801c-27.71631,-36.64355 -44.78174,-59.89355 -51.19189,-69.74414c-10.5376,-16.02979 -18.15527,-30.74951 -22.84717,-44.14893c-4.69727,-13.39893 -7.04297,-26.97021 -7.04297,-40.71289c0,-25.42432 8.47119,-46.72559 25.42383,-63.90381c16.94775,-17.17871 37.90527,-25.76758 62.87354,-25.76758c25.19287,0 47.06885,8.93262 65.62158,26.79834c13.96826,13.28662 25.30615,33.10059 34.01318,59.4375c7.55859,-25.88037 18.20898,-45.57666 31.95215,-59.09424c19.00879,-18.32178 40.99707,-27.48535 65.96484,-27.48535c24.7373,0 45.69531,8.53564 62.87305,25.5957c17.17871,17.06592 25.76855,37.39551 25.76855,60.98389c0,20.61377 -5.04102,42.08691 -15.11719,64.41895c-10.08203,22.33203 -29.54687,51.59521 -58.40723,87.78271c-37.56738,47.41211 -64.93457,86.35352 -82.11328,116.8125c-13.51758,24.0498 -23.82422,49.24902 -30.9209,75.58594z","strokeColor":["rgb",1,1,1],"strokeWidth":2,"strokeCap":"round"}]'); |
230 | | - heartPath.position = view.center; |
231 | | - heartPath.strokeColor = null; |
232 | | - heartPath.scale(1.5); |
233 | | - |
234 | | - var groupTogether = false; |
| 235 | + var heartPath = Project.importJson('["Path",{"pathData":"M514.69629,624.70313c-7.10205,-27.02441 -17.2373,-52.39453 -30.40576,-76.10059c-13.17383,-23.70703 -38.65137,-60.52246 -76.44434,-110.45801c-27.71631,-36.64355 -44.78174,-59.89355 -51.19189,-69.74414c-10.5376,-16.02979 -18.15527,-30.74951 -22.84717,-44.14893c-4.69727,-13.39893 -7.04297,-26.97021 -7.04297,-40.71289c0,-25.42432 8.47119,-46.72559 25.42383,-63.90381c16.94775,-17.17871 37.90527,-25.76758 62.87354,-25.76758c25.19287,0 47.06885,8.93262 65.62158,26.79834c13.96826,13.28662 25.30615,33.10059 34.01318,59.4375c7.55859,-25.88037 18.20898,-45.57666 31.95215,-59.09424c19.00879,-18.32178 40.99707,-27.48535 65.96484,-27.48535c24.7373,0 45.69531,8.53564 62.87305,25.5957c17.17871,17.06592 25.76855,37.39551 25.76855,60.98389c0,20.61377 -5.04102,42.08691 -15.11719,64.41895c-10.08203,22.33203 -29.54687,51.59521 -58.40723,87.78271c-37.56738,47.41211 -64.93457,86.35352 -82.11328,116.8125c-13.51758,24.0498 -23.82422,49.24902 -30.9209,75.58594z","strokeWidth":2,"strokeCap":"round"}]'); |
235 | 236 | var pathLength = heartPath.length; |
236 | | - var mouseDown = false; |
| 237 | + |
237 | 238 | var boids = []; |
| 239 | + var groupTogether = false; |
238 | 240 |
|
239 | 241 | // Add the boids: |
240 | 242 | for (var i = 0; i < 30; i++) { |
241 | | - var position = Point.random() * size; |
| 243 | + var position = Point.random() * view.size; |
242 | 244 | boids.push(new Boid(position, 10, 0.05)); |
243 | 245 | } |
244 | 246 |
|
| 247 | + |
245 | 248 | function onFrame(event) { |
246 | 249 | for (var i = 0, l = boids.length; i < l; i++) { |
247 | 250 | if (groupTogether) { |
248 | 251 | var length = ((i + event.count / 30) % l) / l * pathLength; |
249 | 252 | var point = heartPath.getPointAt(length); |
250 | | - boids[i].arrive(point); |
| 253 | + if (point) |
| 254 | + boids[i].arrive(point); |
251 | 255 | } |
252 | 256 | boids[i].run(boids); |
253 | 257 | } |
254 | 258 | } |
255 | 259 |
|
256 | 260 | // Reposition the heart path whenever the window is resized: |
257 | 261 | function onResize(event) { |
258 | | - size = view.size; |
259 | | - heartPath.position = view.center; |
| 262 | + heartPath.fitBounds(view.bounds); |
| 263 | + heartPath.scale(0.8); |
| 264 | + pathLength = heartPath.length; |
260 | 265 | } |
261 | 266 |
|
262 | 267 | function onMouseDown(event) { |
263 | 268 | groupTogether = !groupTogether; |
264 | 269 | } |
265 | 270 |
|
266 | | - var layer = project.activeLayer; |
267 | 271 | function onKeyDown(event) { |
268 | 272 | if (event.key == 'space') { |
| 273 | + var layer = project.activeLayer; |
269 | 274 | layer.selected = !layer.selected; |
270 | 275 | return false; |
271 | 276 | } |
|
0 commit comments