1 | <!DOCTYPE html>
|
---|
2 | <html>
|
---|
3 | <head>
|
---|
4 | <meta charset="UTF-8">
|
---|
5 | <title>Tadpoles</title>
|
---|
6 | <link rel="stylesheet" href="../css/style.css">
|
---|
7 | <script type="text/javascript" src="../../dist/paper.js"></script>
|
---|
8 | <script type="text/paperscript" canvas="canvas">
|
---|
9 | // Adapted from Flocking Processing example by Daniel Schiffman:
|
---|
10 | // http://processing.org/learning/topics/flocking.html
|
---|
11 |
|
---|
12 | var Boid = Base.extend({
|
---|
13 | initialize: function(position, maxSpeed, maxForce) {
|
---|
14 | var strength = Math.random() * 0.5;
|
---|
15 | this.acceleration = new Point();
|
---|
16 | this.vector = Point.random() * 2 - 1;
|
---|
17 | this.position = position.clone();
|
---|
18 | this.radius = 30;
|
---|
19 | this.maxSpeed = maxSpeed + strength;
|
---|
20 | this.maxForce = maxForce + strength;
|
---|
21 | this.points = [];
|
---|
22 | for (var i = 0, l = strength * 10 + 10; i < l; i++) {
|
---|
23 | this.points.push(new Point());
|
---|
24 | }
|
---|
25 | this.count = 0;
|
---|
26 | this.lastAngle = 0;
|
---|
27 | this.distances = [];
|
---|
28 | this.createItems();
|
---|
29 | },
|
---|
30 |
|
---|
31 | run: function(boids) {
|
---|
32 | this.lastLoc = this.position.clone();
|
---|
33 | if (!groupTogether) {
|
---|
34 | this.flock(boids);
|
---|
35 | } else {
|
---|
36 | this.align(boids);
|
---|
37 | }
|
---|
38 | this.borders();
|
---|
39 | this.update();
|
---|
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;
|
---|
58 | lastVector = vector;
|
---|
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;
|
---|
84 | this.shortPath.segments = this.points.slice(0, 3);
|
---|
85 |
|
---|
86 | this.head.position = this.position;
|
---|
87 | var angle = this.vector.angle;
|
---|
88 | this.head.rotate(angle - this.lastAngle);
|
---|
89 | this.lastAngle = angle;
|
---|
90 | },
|
---|
91 |
|
---|
92 | // We accumulate a new acceleration each time based on three rules
|
---|
93 | flock: function(boids) {
|
---|
94 | this.calculateDistances(boids);
|
---|
95 | var separation = this.separate(boids) * 3;
|
---|
96 | var alignment = this.align(boids);
|
---|
97 | var cohesion = this.cohesion(boids);
|
---|
98 | this.acceleration += separation + alignment + cohesion;
|
---|
99 | },
|
---|
100 |
|
---|
101 | calculateDistances: function(boids) {
|
---|
102 | for (var i = 0, l = boids.length; i < l; i++) {
|
---|
103 | var other = boids[i];
|
---|
104 | this.distances[i] = other.position.getDistance(this.position, true);
|
---|
105 | }
|
---|
106 | },
|
---|
107 |
|
---|
108 | update: function() {
|
---|
109 | // Update velocity
|
---|
110 | this.vector += this.acceleration;
|
---|
111 | // Limit speed (vector#limit?)
|
---|
112 | this.vector.length = Math.min(this.maxSpeed, this.vector.length);
|
---|
113 | this.position += this.vector;
|
---|
114 | // Reset acceleration to 0 each cycle
|
---|
115 | this.acceleration = new Point();
|
---|
116 | },
|
---|
117 |
|
---|
118 | seek: function(target) {
|
---|
119 | this.acceleration += this.steer(target, false);
|
---|
120 | },
|
---|
121 |
|
---|
122 | arrive: function(target) {
|
---|
123 | this.acceleration += this.steer(target, true);
|
---|
124 | },
|
---|
125 |
|
---|
126 | borders: function() {
|
---|
127 | var vector = new Point();
|
---|
128 | var position = this.position;
|
---|
129 | var radius = this.radius;
|
---|
130 | var size = view.size;
|
---|
131 | if (position.x < -radius) vector.x = size.width + radius;
|
---|
132 | if (position.y < -radius) vector.y = size.height + radius;
|
---|
133 | if (position.x > size.width + radius) vector.x = -size.width -radius;
|
---|
134 | if (position.y > size.height + radius) vector.y = -size.height -radius;
|
---|
135 | if (!vector.isZero()) {
|
---|
136 | this.position += vector;
|
---|
137 | var points = this.points;
|
---|
138 | for (var i = 0, l = points.length; i < l; i++) {
|
---|
139 | points[i] += vector;
|
---|
140 | }
|
---|
141 | }
|
---|
142 | },
|
---|
143 |
|
---|
144 | // A method that calculates a steering vector towards a target
|
---|
145 | // Takes a second argument, if true, it slows down as it approaches
|
---|
146 | // the target
|
---|
147 | steer: function(target, slowdown) {
|
---|
148 | var steer,
|
---|
149 | desired = target - this.position;
|
---|
150 | var distance = desired.length;
|
---|
151 | // Two options for desired vector magnitude
|
---|
152 | // (1 -- based on distance, 2 -- maxSpeed)
|
---|
153 | if (slowdown && distance < 100) {
|
---|
154 | // This damping is somewhat arbitrary:
|
---|
155 | desired.length = this.maxSpeed * (distance * 0.001);
|
---|
156 | } else {
|
---|
157 | desired.length = this.maxSpeed;
|
---|
158 | }
|
---|
159 | steer = desired - this.vector;
|
---|
160 | steer.length = Math.min(this.maxForce, steer.length);
|
---|
161 | return steer;
|
---|
162 | },
|
---|
163 |
|
---|
164 | separate: function(boids) {
|
---|
165 | var desiredSeperation = 3600;
|
---|
166 | var steer = new Point();
|
---|
167 | var count = 0;
|
---|
168 | // For every boid in the system, check if it's too close
|
---|
169 | for (var i = 0, l = boids.length; i < l; i++) {
|
---|
170 | var distance = this.distances[i];
|
---|
171 | if (distance > 0 && distance < desiredSeperation) {
|
---|
172 | // Calculate vector pointing away from neighbor
|
---|
173 | var delta = this.position - boids[i].position;
|
---|
174 | delta.length = 1 / distance;
|
---|
175 | steer += delta;
|
---|
176 | count++;
|
---|
177 | }
|
---|
178 | }
|
---|
179 | // Average -- divide by how many
|
---|
180 | if (count > 0)
|
---|
181 | steer /= count;
|
---|
182 | if (!steer.isZero()) {
|
---|
183 | // Implement Reynolds: Steering = Desired - Velocity
|
---|
184 | steer.length = this.maxSpeed;
|
---|
185 | steer -= this.vector;
|
---|
186 | steer.length = Math.min(steer.length, this.maxForce);
|
---|
187 | }
|
---|
188 | return steer;
|
---|
189 | },
|
---|
190 |
|
---|
191 | // Alignment
|
---|
192 | // For every nearby boid in the system, calculate the average velocity
|
---|
193 | align: function(boids) {
|
---|
194 | var neighborDist = 25;
|
---|
195 | var steer = new Point();
|
---|
196 | var count = 0;
|
---|
197 | for (var i = 0, l = boids.length; i < l; i++) {
|
---|
198 | var distance = this.distances[i];
|
---|
199 | if (distance > 0 && distance < neighborDist) {
|
---|
200 | steer += boids[i].vector;
|
---|
201 | count++;
|
---|
202 | }
|
---|
203 | }
|
---|
204 |
|
---|
205 | if (count > 0)
|
---|
206 | steer /= count;
|
---|
207 | if (!steer.isZero()) {
|
---|
208 | // Implement Reynolds: Steering = Desired - Velocity
|
---|
209 | steer.length = this.maxSpeed;
|
---|
210 | steer -= this.vector;
|
---|
211 | steer.length = Math.min(steer.length, this.maxForce);
|
---|
212 | }
|
---|
213 | return steer;
|
---|
214 | },
|
---|
215 |
|
---|
216 | // Cohesion
|
---|
217 | // For the average location (i.e. center) of all nearby boids,
|
---|
218 | // calculate steering vector towards that location
|
---|
219 | cohesion: function(boids) {
|
---|
220 | var neighborDist = 10000;
|
---|
221 | var sum = new Point(0, 0);
|
---|
222 | var count = 0;
|
---|
223 | for (var i = 0, l = boids.length; i < l; i++) {
|
---|
224 | var distance = this.distances[i];
|
---|
225 | if (distance > 0 && distance < neighborDist) {
|
---|
226 | sum += boids[i].position; // Add location
|
---|
227 | count++;
|
---|
228 | }
|
---|
229 | }
|
---|
230 | if (count > 0) {
|
---|
231 | sum /= count;
|
---|
232 | // Steer towards the location
|
---|
233 | return this.steer(sum, false);
|
---|
234 | }
|
---|
235 | return sum;
|
---|
236 | }
|
---|
237 | });
|
---|
238 |
|
---|
239 | 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"}]');
|
---|
240 | var pathLength = heartPath.length;
|
---|
241 |
|
---|
242 | var boids = [];
|
---|
243 | var groupTogether = false;
|
---|
244 |
|
---|
245 | // Add the boids:
|
---|
246 | for (var i = 0; i < 30; i++) {
|
---|
247 | var position = Point.random() * view.size;
|
---|
248 | boids.push(new Boid(position, 10, 0.05));
|
---|
249 | }
|
---|
250 |
|
---|
251 |
|
---|
252 | function onFrame(event) {
|
---|
253 | for (var i = 0, l = boids.length; i < l; i++) {
|
---|
254 | if (groupTogether) {
|
---|
255 | var length = ((i + event.count / 30) % l) / l * pathLength;
|
---|
256 | var point = heartPath.getPointAt(length);
|
---|
257 | if (point)
|
---|
258 | boids[i].arrive(point);
|
---|
259 | }
|
---|
260 | boids[i].run(boids);
|
---|
261 | }
|
---|
262 | }
|
---|
263 |
|
---|
264 | // Reposition the heart path whenever the window is resized:
|
---|
265 | function onResize(event) {
|
---|
266 | heartPath.fitBounds(view.bounds);
|
---|
267 | heartPath.scale(0.8);
|
---|
268 | pathLength = heartPath.length;
|
---|
269 | }
|
---|
270 |
|
---|
271 | function onMouseDown(event) {
|
---|
272 | groupTogether = !groupTogether;
|
---|
273 | }
|
---|
274 |
|
---|
275 | function onKeyDown(event) {
|
---|
276 | if (event.key == 'space') {
|
---|
277 | var layer = project.activeLayer;
|
---|
278 | layer.selected = !layer.selected;
|
---|
279 | return false;
|
---|
280 | }
|
---|
281 | }
|
---|
282 | </script>
|
---|
283 | <style>
|
---|
284 | body {
|
---|
285 | background: black;
|
---|
286 | }
|
---|
287 | </style>
|
---|
288 | </head>
|
---|
289 | <body>
|
---|
290 | <canvas id="canvas" resize></canvas>
|
---|
291 | </body>
|
---|
292 | </html> |
---|