| demos\blackmajic\canvas\js\blobsallad.js |
1 function Vector(x, y)
2 {
3 this.x = x;
4 this.y = y;
5
6 this.equal = function(v)
7 {
8 return this.x == v.getX() && this.y == v.getY();
9 }
10 this.getX = function()
11 {
12 return this.x;
13 }
14 this.getY = function()
15 {
16 return this.y;
17 }
18 this.setX = function(x)
19 {
20 this.x = x;
21 }
22 this.setY = function(y)
23 {
24 this.y = y;
25 }
26 this.addX = function(x)
27 {
28 this.x += x;
29 }
30 this.addY = function(y)
31 {
32 this.y += y;
33 }
34 this.set = function(v)
35 {
36 this.x = v.getX();
37 this.y = v.getY();
38 }
39 this.add = function(v)
40 {
41 this.x += v.getX();
42 this.y += v.getY();
43 }
44 this.sub = function(v)
45 {
46 this.x -= v.getX();
47 this.y -= v.getY();
48 }
49 this.dotProd = function(v)
50 {
51 return this.x * v.getX() + this.y * v.getY();
52 }
53 this.length = function()
54 {
55 return Math.sqrt(this.x * this.x + this.y * this.y);
56 }
57 this.scale = function(scaleFactor)
58 {
59 this.x *= scaleFactor;
60 this.y *= scaleFactor;
61 }
62 this.toString = function()
63 {
64 return " X: " + this.x + " Y: " + this.y;
65 }
66 }
67
68 function Environment(x, y, w, h)
69 {
70 this.left = x;
71 this.right = x + w;
72 this.top = y;
73 this.buttom = y + h;
74 this.r = new Vector(0.0, 0.0);
75
76 this.collision = function(curPos, prevPos)
77 {
78 var collide = false;
79 var i;
80
81 if(curPos.getX() < this.left)
82 {
83 curPos.setX(this.left);
84 collide = true;
85 }
86 else if(curPos.getX() > this.right)
87 {
88 curPos.setX(this.right);
89 collide = true;
90 }
91 if(curPos.getY() < this.top)
92 {
93 curPos.setY(this.top);
94 collide = true;
95 }
96 else if(curPos.getY() > this.buttom)
97 {
98 curPos.setY(this.buttom);
99 collide = true;
100 }
101 return collide;
102 }
103 this.draw = function(ctx, scaleFactor)
104 {
105 }
106 }
107
108 function PointMass(cx, cy, mass)
109 {
110 this.cur = new Vector(cx, cy);
111 this.prev = new Vector(cx, cy);
112 this.mass = mass;
113 this.force = new Vector(0.0, 0.0);
114 this.result = new Vector(0.0, 0.0);
115 this.friction = 0.01;
116
117 this.getXPos = function()
118 {
119 return this.cur.getX();
120 }
121 this.getYPos = function()
122 {
123 return this.cur.getY();
124 }
125 this.getPos = function()
126 {
127 return this.cur;
128 }
129 this.getXPrevPos = function()
130 {
131 return this.prev.getX();
132 }
133 this.getYPrevPos = function()
134 {
135 return this.prev.getY();
136 }
137 this.getPrevPos = function()
138 {
139 return this.prev;
140 }
141 this.addXPos = function(dx)
142 {
143 this.cur.addX(dx);
144 }
145 this.addYPos = function(dy)
146 {
147 this.cur.addY(dy);
148 }
149 this.setForce = function(force)
150 {
151 this.force.set(force);
152 }
153 this.addForce = function(force)
154 {
155 this.force.add(force);
156 }
157 this.getMass = function()
158 {
159 return this.mass;
160 }
161 this.setMass = function(mass)
162 {
163 this.mass = mass;
164 }
165 this.move = function(dt)
166 {
167 var t, a, c, dtdt;
168
169 dtdt = dt * dt;
170
171 a = this.force.getX() / this.mass;
172 c = this.cur.getX();
173 t = (2.0 - this.friction) * c - (1.0 - this.friction) * this.prev.getX() + a * dtdt;
174 this.prev.setX(c);
175 this.cur.setX(t);
176
177 a = this.force.getY() / this.mass;
178 c = this.cur.getY();
179 t = (2.0 - this.friction) * c - (1.0 - this.friction) * this.prev.getY() + a * dtdt;
180 this.prev.setY(c);
181 this.cur.setY(t);
182 }
183 this.setFriction = function(friction)
184 {
185 this.friction = friction;
186 }
187 this.getVelocity = function()
188 {
189 var cXpX, cYpY;
190
191 cXpX = this.cur.getX() - this.prev.getX();
192 cYpY = this.cur.getY() - this.prev.getY();
193
194 return cXpX * cXpX + cYpY * cYpY;
195 }
196 this.draw = function(ctx, scaleFactor)
197 {
198 ctx.lineWidth = 2;
199 ctx.fillStyle = '#000000';
200 ctx.strokeStyle = '#000000';
201 ctx.beginPath();
202 ctx.arc(this.cur.getX() * scaleFactor,
203 this.cur.getY() * scaleFactor,
204 4.0, 0.0, Math.PI * 2.0, true);
205 ctx.fill();
206 }
207 }
208
209 function ConstraintY(pointMass, y, shortConst, longConst)
210 {
211 this.pointMass = pointMass;
212 this.y = y;
213 this.delta = new Vector(0.0, 0.0);
214 this.shortConst = shortConst;
215 this.longConst = longConst;
216
217 this.sc = function()
218 {
219 var dist,scaleFactor;
220
221 dist = Math.abs(this.pointMass.getYPos() - this.y);
222 this.delta.setY(-dist);
223
224 if(this.shortConst != 0.0 && dist < this.shortConst)
225 {
226
227 scaleFactor = this.shortConst / dist;
228 this.delta.scale(scaleFactor);
229 pointMass.getPos().sub(this.delta);
230 }
231 else if(this.longConst != 0.0 && dist > this.longConst)
232 {
233
234 scaleFactor = this.longConst / dist;
235 this.delta.scale(scaleFactor);
236 pointMass.getPos().sub(this.delta);
237 }
238 }
239 }
240
241
242 function Joint(pointMassA, pointMassB, shortConst, longConst)
243 {
244 this.pointMassA = pointMassA;
245 this.pointMassB = pointMassB;
246 this.delta = new Vector(0.0, 0.0);
247 this.pointMassAPos = pointMassA.getPos();
248 this.pointMassBPos = pointMassB.getPos();
249
250 this.delta.set(this.pointMassBPos);
251 this.delta.sub(this.pointMassAPos);
252
253 this.shortConst = this.delta.length() * shortConst;
254 this.longConst = this.delta.length() * longConst;
255 this.scSquared = this.shortConst * this.shortConst;
256 this.lcSquared = this.longConst * this.longConst;
257
258 this.setDist = function(shortConst, longConst)
259 {
260 this.shortConst = shortConst;
261 this.longConst = longConst;
262 this.scSquared = this.shortConst * this.shortConst;
263 this.lcSquared = this.longConst * this.longConst;
264 }
265 this.scale = function(scaleFactor)
266 {
267 this.shortConst = this.shortConst * scaleFactor;
268 this.longConst = this.longConst * scaleFactor;
269 this.scSquared = this.shortConst * this.shortConst;
270 this.lcSquared = this.longConst * this.longConst;
271 }
272 this.sc = function()
273 {
274 this.delta.set(this.pointMassBPos);
275 this.delta.sub(this.pointMassAPos);
276
277 var dp = this.delta.dotProd(this.delta);
278 var scaleFactor;
279
280 if(this.shortConst != 0.0 && dp < this.scSquared)
281 {
282
283 scaleFactor = this.scSquared / (dp + this.scSquared) - 0.5;
284
285 this.delta.scale(scaleFactor);
286
287 this.pointMassAPos.sub(this.delta);
288 this.pointMassBPos.add(this.delta);
289 }
290 else if(this.longConst != 0.0 && dp > this.lcSquared)
291 {
292
293 scaleFactor = this.lcSquared / (dp + this.lcSquared) - 0.5;
294
295 this.delta.scale(scaleFactor);
296
297 this.pointMassAPos.sub(this.delta);
298 this.pointMassBPos.add(this.delta);
299 }
300 }
301 }
302
303 function Stick(pointMassA, pointMassB)
304 {
305 function pointMassDist(pointMassA, pointMassB)
306 {
307 var aXbX, aYbY;
308
309 aXbX = pointMassA.getXPos() - pointMassB.getXPos();
310 aYbY = pointMassA.getYPos() - pointMassB.getYPos();
311
312 return Math.sqrt(aXbX * aXbX + aYbY * aYbY);
313 }
314
315 this.length = pointMassDist(pointMassA, pointMassB);
316 this.lengthSquared = this.length * this.length;
317 this.pointMassA = pointMassA;
318 this.pointMassB = pointMassB;
319 this.delta = new Vector(0.0, 0.0);
320
321 this.getPointMassA = function()
322 {
323 return this.pointMassA;
324 }
325 this.getPointMassB = function()
326 {
327 return this.pointMassB;
328 }
329 this.scale = function(scaleFactor)
330 {
331 this.length *= scaleFactor;
332 this.lengthSquared = this.length * this.length;
333 }
334 this.sc = function(env)
335 {
336 var dotProd, scaleFactor;
337 var pointMassAPos, pointMassBPos;
338
339 pointMassAPos = this.pointMassA.getPos();
340 pointMassBPos = this.pointMassB.getPos();
341
342 this.delta.set(pointMassBPos);
343 this.delta.sub(pointMassAPos);
344
345 dotProd = this.delta.dotProd(this.delta);
346
347 scaleFactor = this.lengthSquared / (dotProd + this.lengthSquared) - 0.5;
348 this.delta.scale(scaleFactor);
349
350 pointMassAPos.sub(this.delta);
351 pointMassBPos.add(this.delta);
352 }
353 this.setForce = function(force)
354 {
355 this.pointMassA.setForce(force);
356 this.pointMassB.setForce(force);
357 }
358 this.addForce = function(force)
359 {
360 this.pointMassA.addForce(force);
361 this.pointMassB.addForce(force);
362 }
363 this.move = function(dt)
364 {
365 this.pointMassA.move(dt);
366 this.pointMassB.move(dt);
367 }
368 this.draw = function(ctx, scaleFactor)
369 {
370 this.pointMassA.draw(ctx, scaleFactor);
371 this.pointMassB.draw(ctx, scaleFactor);
372
373 ctx.lineWidth = 3;
374 ctx.fillStyle = '#000000';
375 ctx.strokeStyle = '#000000';
376 ctx.beginPath();
377 ctx.moveTo(this.pointMassA.getXPos() * scaleFactor,
378 this.pointMassA.getYPos() * scaleFactor);
379 ctx.lineTo(this.pointMassB.getXPos() * scaleFactor,
380 this.pointMassB.getYPos() * scaleFactor);
381 ctx.stroke();
382 }
383 }
384
385 function Spring(restLength, stiffness, damper, pointMassA, pointMassB)
386 {
387 this.restLength = restLength;
388 this.stiffness = stiffness;
389 this.damper = damper;
390 this.pointMassA = pointMassA;
391 this.pointMassB = pointMassB;
392 this.tmp = Vector(0.0, 0.0);
393
394 this.sc = function(env)
395 {
396 env.collistion(this.pointMassA.getPos(), this.pointMassA.getPrevPos());
397 env.collistion(this.pointMassB.getPos(), this.pointMassB.getPrevPos());
398 }
399
400 this.move = function(dt)
401 {
402 var aXbX;
403 var aYbY;
404 var springForce;
405 var length;
406
407 aXbX = this.pointMassA.getXPos() - this.pointMassB.getXPos();
408 aYbY = this.pointMassA.getYPos() - this.pointMassB.getYPos();
409
410 length = Math.sqrt(aXbX * aXbX + aYbY * aYbY);
411 springForce = this.stiffness * (length / this.restLength - 1.0);
412
413 var avXbvX;
414 var avYbvY;
415 var damperForce;
416
417 avXbvX = this.pointMassA.getXVel() - this.pointMassB.getXVel();
418 avYbvY = this.pointMassA.getYVel() - this.pointMassB.getYVel();
419
420 damperForce = avXbvX * aXbX + avYbvY * aYbY;
421 damperForce *= this.damper;
422
423 var fx;
424 var fy;
425
426 fx = (springForce + damperForce) * aXbX;
427 fy = (springForce + damperForce) * aYbY;
428
429 this.tmp.setX(-fx);
430 this.tmp.setY(-fy);
431 this.pointMassA.addForce(this.tmp);
432
433 this.tmp.setX(fx);
434 this.tmp.setY(fy);
435 this.pointMassB.addForce(this.tmp);
436
437 this.pointMassA.move(dt);
438 this.pointMassB.move(dt);
439 }
440 this.addForce = function(force)
441 {
442 this.pointMassA.addForce(force);
443 this.pointMassB.addForce(force);
444 }
445 this.draw = function(ctx, scaleFactor)
446 {
447 this.pointMassA.draw(ctx, scaleFactor);
448 this.pointMassB.draw(ctx, scaleFactor);
449
450 ctx.fillStyle = '#000000';
451 ctx.strokeStyle = '#000000';
452 ctx.beginPath();
453 ctx.moveTo(this.pointMassA.getXPos() * scaleFactor,
454 this.pointMassA.getYPos() * scaleFactor);
455 ctx.lineTo(this.pointMassB.getXPos() * scaleFactor,
456 this.pointMassB.getXPos() * scaleFactor);
457 ctx.stroke();
458 }
459 }
460
461 function Blob(x, y, radius, numPointMasses)
462 {
463 this.x = x;
464 this.y = y;
465 this.sticks = new Array();
466 this.pointMasses = new Array();
467 this.joints = new Array();
468 this.middlePointMass=null;
469 this.radius = radius;
470 this.drawFaceStyle = 1;
471 this.drawEyeStyle = 1;
472 this.selected = false;
473
474 numPointMasses = 8;
475
476 var f = 0.1;
477 var low = 0.95, high = 1.05;
478 var t, i, p;
479
480 function clampIndex(index, maxIndex)
481 {
482 index += maxIndex;
483 return index % maxIndex;
484 }
485
486 for(i = 0, t = 0.0; i < numPointMasses; i++)
487 {
488 this.pointMasses[i] = new PointMass(Math.cos(t) * radius + x, Math.sin(t) * radius + y, 1.0);
489 t += 2.0 * Math.PI / numPointMasses;
490 }
491
492 this.middlePointMass = new PointMass(x, y, 1.0);
493
494 this.pointMasses[0].setMass(4.0);
495 this.pointMasses[1].setMass(4.0);
496
497 for(i = 0; i < numPointMasses; i++)
498 {
499 this.sticks[i] = new Stick(this.pointMasses[i], this.pointMasses[clampIndex(i + 1, numPointMasses)]);
500 }
501
502 for(i = 0, p = 0; i < numPointMasses; i++)
503 {
504 this.joints[p++] = new Joint(this.pointMasses[i], this.pointMasses[clampIndex(i + numPointMasses / 2 + 1, numPointMasses)], low, high);
505 this.joints[p++] = new Joint(this.pointMasses[i], this.middlePointMass, high * 0.9, low * 1.1);
506 }
507
508 this.addBlob = function(blob)
509 {
510 var index = this.joints.length;
511 var dist;
512
513 this.joints[index] = new Joint(this.middlePointMass, blob.getMiddlePointMass(), 0.0, 0.0);
514 dist = this.radius + blob.getRadius();
515 this.joints[index].setDist(dist * 0.95, 0.0);
516 }
517 this.getMiddlePointMass = function()
518 {
519 return this.middlePointMass;
520 }
521 this.getRadius = function()
522 {
523 return this.radius;
524 }
525 this.getXPos = function()
526 {
527 return this.middlePointMass.getXPos();
528 }
529 this.getYPos = function()
530 {
531 return this.middlePointMass.getYPos();
532 }
533 this.scale = function(scaleFactor)
534 {
535 var i;
536
537 for(i = 0; i < this.joints.length; i++)
538 {
539 this.joints[i].scale(scaleFactor);
540 }
541 for(i = 0; i < this.sticks.length; i++)
542 {
543 this.sticks[i].scale(scaleFactor);
544 }
545 this.radius *= scaleFactor;
546 }
547
548 this.move = function(dt)
549 {
550 var i;
551
552 for(i = 0; i < this.pointMasses.length; i++)
553 {
554 this.pointMasses[i].move(dt);
555 }
556 this.middlePointMass.move(dt);
557 }
558 this.sc = function(env)
559 {
560 var i, j;
561
562 for(j = 0; j < 4; j++)
563 {
564 for(i = 0; i < this.pointMasses.length; i++)
565 {
566 if(env.collision(this.pointMasses[i].getPos(), this.pointMasses[i].getPrevPos()) == true)
567 {
568 this.pointMasses[i].setFriction(0.75);
569 }
570 else
571 {
572 this.pointMasses[i].setFriction(0.01);
573 }
574 }
575 for(i = 0; i < this.sticks.length; i++)
576 {
577 this.sticks[i].sc(env);
578 }
579 for(i = 0; i < this.joints.length; i++)
580 {
581 this.joints[i].sc();
582 }
583 }
584 }
585 this.setForce = function(force)
586 {
587 var i;
588
589 for(i = 0; i < this.pointMasses.length; i++)
590 {
591 this.pointMasses[i].setForce(force);
592 }
593 this.middlePointMass.setForce(force);
594 }
595 this.addForce = function(force)
596 {
597 var i;
598
599 for(i = 0; i < this.pointMasses.length; i++)
600 {
601 this.pointMasses[i].addForce(force);
602 }
603 this.middlePointMass.addForce(force);
604 this.pointMasses[0].addForce(force);
605 this.pointMasses[0].addForce(force);
606 this.pointMasses[0].addForce(force);
607 this.pointMasses[0].addForce(force);
608 }
609 this.moveTo = function(x, y)
610 {
611 var i, blobPos;
612
613 blobPos = this.middlePointMass.getPos();
614 x -= blobPos.getX(x);
615 y -= blobPos.getY(y);
616
617 for(i = 0; i < this.pointMasses.length; i++)
618 {
619 blobPos = this.pointMasses[i].getPos();
620 blobPos.addX(x);
621 blobPos.addY(y);
622 }
623 blobPos = this.middlePointMass.getPos();
624 blobPos.addX(x);
625 blobPos.addY(y);
626 }
627 this.setSelected = function(selected)
628 {
629 this.selected = selected;
630 }
631
632 this.drawEars = function(ctx, scaleFactor)
633 {
634 ctx.strokeStyle = "#000000";
635 ctx.fillStyle = "#FFFFFF";
636 ctx.lineWidth = 2;
637
638 ctx.beginPath();
639 ctx.moveTo((-0.55 * this.radius) * scaleFactor, (-0.35 * this.radius) * scaleFactor);
640 ctx.lineTo((-0.52 * this.radius) * scaleFactor, (-0.55 * this.radius) * scaleFactor);
641 ctx.lineTo((-0.45 * this.radius) * scaleFactor, (-0.40 * this.radius) * scaleFactor);
642 ctx.fill();
643 ctx.stroke();
644
645 ctx.beginPath();
646 ctx.moveTo((0.55 * this.radius) * scaleFactor, (-0.35 * this.radius) * scaleFactor);
647 ctx.lineTo((0.52 * this.radius) * scaleFactor, (-0.55 * this.radius) * scaleFactor);
648 ctx.lineTo((0.45 * this.radius) * scaleFactor, (-0.40 * this.radius) * scaleFactor);
649 ctx.fill();
650 ctx.stroke();
651 }
652
653 this.drawHappyEyes1 = function(ctx, scaleFactor)
654 {
655 ctx.lineWidth = 1;
656 ctx.fillStyle = "#FFFFFF";
657 ctx.beginPath();
658 ctx.arc((-0.15 * this.radius) * scaleFactor,
659 (-0.20 * this.radius) * scaleFactor,
660 this.radius * 0.12 * scaleFactor, 0, 2.0 * Math.PI, false);
661 ctx.fill();
662 ctx.stroke();
663
664 ctx.beginPath();
665 ctx.arc(( 0.15 * this.radius) * scaleFactor,
666 (-0.20 * this.radius) * scaleFactor,
667 this.radius * 0.12 * scaleFactor, 0, 2.0 * Math.PI, false);
668 ctx.fill();
669 ctx.stroke();
670
671 ctx.fillStyle = "#000000";
672 ctx.beginPath();
673 ctx.arc((-0.15 * this.radius) * scaleFactor,
674 (-0.17 * this.radius) * scaleFactor,
675 this.radius * 0.06 * scaleFactor, 0, 2.0 * Math.PI, false);
676 ctx.fill();
677
678 ctx.beginPath();
679 ctx.arc(( 0.15 * this.radius) * scaleFactor,
680 (-0.17 * this.radius) * scaleFactor,
681 this.radius * 0.06 * scaleFactor, 0, 2.0 * Math.PI, false);
682 ctx.fill();
683 }
684 this.drawHappyEyes2 = function(ctx, scaleFactor)
685 {
686 ctx.lineWidth = 1;
687 ctx.fillStyle = "#FFFFFF";
688 ctx.beginPath();
689 ctx.arc((-0.15 * this.radius) * scaleFactor,
690 (-0.20 * this.radius) * scaleFactor,
691 this.radius * 0.12 * scaleFactor, 0, 2.0 * Math.PI, false);
692 ctx.stroke();
693
694 ctx.beginPath();
695 ctx.arc(( 0.15 * this.radius) * scaleFactor,
696 (-0.20 * this.radius) * scaleFactor,
697 this.radius * 0.12 * scaleFactor, 0, 2.0 * Math.PI, false);
698 ctx.stroke();
699
700 ctx.lineWidth = 1;
701 ctx.beginPath();
702 ctx.moveTo((-0.25 * this.radius) * scaleFactor,
703 (-0.20 * this.radius) * scaleFactor);
704 ctx.lineTo((-0.05 * this.radius) * scaleFactor,
705 (-0.20 * this.radius) * scaleFactor);
706 ctx.stroke();
707
708 ctx.beginPath();
709 ctx.moveTo(( 0.25 * this.radius) * scaleFactor,
710 (-0.20 * this.radius) * scaleFactor);
711 ctx.lineTo(( 0.05 * this.radius) * scaleFactor,
712 (-0.20 * this.radius) * scaleFactor);
713 ctx.stroke();
714 }
715 this.drawHappyFace1 = function(ctx, scaleFactor)
716 {
717 ctx.lineWidth = 2;
718 ctx.strokeStyle = "#000000";
719 ctx.fillStyle = "#000000";
720 ctx.beginPath();
721 ctx.arc(0.0, 0.0,
722 this.radius * 0.25 * scaleFactor, 0, Math.PI, false);
723 ctx.stroke();
724 }
725 this.drawHappyFace2 = function(ctx, scaleFactor)
726 {
727 ctx.lineWidth = 2;
728 ctx.strokeStyle = "#000000";
729 ctx.fillStyle = "#000000";
730 ctx.beginPath();
731 ctx.arc(0.0, 0.0,
732 this.radius * 0.25 * scaleFactor, 0, Math.PI, false);
733 ctx.fill();
734 }
735 this.drawOohFace = function(ctx, scaleFactor)
736 {
737 ctx.lineWidth = 2;
738 ctx.strokeStyle = "#000000";
739 ctx.fillStyle = "#000000";
740 ctx.beginPath();
741 ctx.arc(0.0, (0.1 * this.radius) * scaleFactor,
742 this.radius * 0.25 * scaleFactor, 0, Math.PI, false);
743 ctx.fill();
744
745 ctx.beginPath();
746
747 ctx.moveTo((-0.25 * this.radius) * scaleFactor, (-0.3 * this.radius) * scaleFactor);
748 ctx.lineTo((-0.05 * this.radius) * scaleFactor, (-0.2 * this.radius) * scaleFactor);
749 ctx.lineTo((-0.25 * this.radius) * scaleFactor, (-0.1 * this.radius) * scaleFactor);
750
751 ctx.moveTo((0.25 * this.radius) * scaleFactor, (-0.3 * this.radius) * scaleFactor);
752 ctx.lineTo((0.05 * this.radius) * scaleFactor, (-0.2 * this.radius) * scaleFactor);
753 ctx.lineTo((0.25 * this.radius) * scaleFactor, (-0.1 * this.radius) * scaleFactor);
754
755 ctx.stroke();
756 }
757 this.drawFace = function(ctx, scaleFactor)
758 {
759 if(this.drawFaceStyle == 1 && Math.random() < 0.05)
760 {
761 this.drawFaceStyle = 2;
762 }
763 else if(this.drawFaceStyle == 2 && Math.random() < 0.1)
764 {
765 this.drawFaceStyle = 1;
766 }
767
768 if(this.drawEyeStyle == 1 && Math.random() < 0.025)
769 {
770 this.drawEyeStyle = 2;
771 }
772 else if(this.drawEyeStyle == 2 && Math.random() < 0.3)
773 {
774 this.drawEyeStyle = 1;
775 }
776
777 if(this.middlePointMass.getVelocity() > 0.004)
778 {
779 this.drawOohFace(ctx, scaleFactor);
780 }
781 else
782 {
783 if(this.drawFaceStyle == 1)
784 {
785 this.drawHappyFace1(ctx, scaleFactor, 0.0, -0.3);
786 }
787 else
788 {
789 this.drawHappyFace2(ctx, scaleFactor, 0.0, -0.3);
790 }
791
792 if(this.drawEyeStyle == 1)
793 {
794 this.drawHappyEyes1(ctx, scaleFactor, 0.0, -0.3);
795 }
796 else
797 {
798 this.drawHappyEyes2(ctx, scaleFactor, 0.0, -0.3);
799 }
800 }
801 }
802 this.getPointMass = function(index)
803 {
804 index += this.pointMasses.length;
805 index = index % this.pointMasses.length;
806 return this.pointMasses[index];
807 }
808 this.drawBody = function(ctx, scaleFactor)
809 {
810 var i;
811
812 ctx.strokeStyle = "#000000";
813 if(this.selected == true)
814 {
815 ctx.fillStyle = "#FFCCCC";
816 }
817 else
818 {
819 ctx.fillStyle = "#FFFFFF";
820 }
821 ctx.lineWidth = 5;
822 ctx.beginPath();
823 ctx.moveTo(this.pointMasses[0].getXPos() * scaleFactor,
824 this.pointMasses[0].getYPos() * scaleFactor);
825
826 for(i = 0; i < this.pointMasses.length; i++)
827 {
828 var px, py, nx, ny, tx, ty, cx, cy;
829 var prevPointMass, currentPointMass, nextPointMass, nextNextPointMass;
830
831 prevPointMass = this.getPointMass(i - 1);
832 currentPointMass = this.pointMasses[i];
833 nextPointMass = this.getPointMass(i + 1);
834 nextNextPointMass = this.getPointMass(i + 2);
835
836 tx = nextPointMass.getXPos();
837 ty = nextPointMass.getYPos();
838
839 cx = currentPointMass.getXPos();
840 cy = currentPointMass.getYPos();
841
842 px = cx * 0.5 + tx * 0.5;
843 py = cy * 0.5 + ty * 0.5;
844
845 nx = cx - prevPointMass.getXPos() + tx - nextNextPointMass.getXPos();
846 ny = cy - prevPointMass.getYPos() + ty - nextNextPointMass.getYPos();
847
848 px += nx * 0.16;
849 py += ny * 0.16;
850
851 px = px * scaleFactor;
852 py = py * scaleFactor;
853
854 tx = tx * scaleFactor;
855 ty = ty * scaleFactor;
856
857 ctx.bezierCurveTo(px, py, tx, ty, tx, ty);
858 }
859
860 ctx.closePath();
861 ctx.stroke();
862 ctx.fill();
863 }
864 this.drawSimpleBody = function(ctx, scaleFactor)
865 {
866 for(i = 0; i < this.sticks.length; i++)
867 {
868 this.sticks[i].draw(ctx, scaleFactor);
869 }
870 }
871
872 this.draw = function(ctx, scaleFactor)
873 {
874 var i;
875 var up, ori, ang;
876
877 this.drawBody(ctx, scaleFactor);
878
879 ctx.strokeStyle = "#000000";
880 ctx.fillStyle = "#000000"
881
882 ctx.save();
883 ctx.translate(this.middlePointMass.getXPos() * scaleFactor,
884 (this.middlePointMass.getYPos() - 0.35 * this.radius) * scaleFactor);
885
886 up = new Vector(0.0, -1.0);
887 ori = new Vector(0.0, 0.0);
888 ori.set(this.pointMasses[0].getPos());
889 ori.sub(this.middlePointMass.getPos());
890 ang = Math.acos(ori.dotProd(up) / ori.length());
891 if(ori.getX() < 0.0)
892 {
893 ctx.rotate(-ang);
894 }
895 else
896 {
897 ctx.rotate(ang);
898 }
899
900
901 this.drawFace(ctx, scaleFactor);
902
903 ctx.restore();
904 }
905 }
906
907 function BlobCollective(x, y, startNum, maxNum)
908 {
909 this.paused=false
910 this.maxNum = maxNum;
911 this.numActive = 1;
912 this.blobs = new Array();
913 this.tmpForce = new Vector(0.0, 0.0);
914 this.selectedBlob = null;
915 this.blobAnchor=null;
916 this.blobs[0] = new Blob(x, y, 0.4, 8);
917
918 this.split = function()
919 {
920 var i, maxIndex = 0, maxRadius = 0.0;
921 var emptySlot;
922 var motherBlob, newBlob;
923
924 if(this.numActive == this.maxNum)
925 {
926 return;
927 }
928
929 emptySlot = this.blobs.length;
930 for(i = 0; i < this.blobs.length; i++)
931 {
932 if(this.blobs[i] != null && this.blobs[i].getRadius() > maxRadius)
933 {
934 maxRadius = this.blobs[i].getRadius();
935 motherBlob = this.blobs[i];
936 }
937 else if(this.blobs[i] == null)
938 {
939 emptySlot = i;
940 }
941 }
942
943 motherBlob.scale(0.75);
944 newBlob = new Blob(motherBlob.getXPos(),
945 motherBlob.getYPos(), motherBlob.getRadius(), 8);
946
947 for(i = 0; i < this.blobs.length; i++)
948 {
949 if(this.blobs[i] == null)
950 {
951 continue;
952 }
953 this.blobs[i].addBlob(newBlob);
954 newBlob.addBlob(this.blobs[i]);
955 }
956 this.blobs[emptySlot] = newBlob;
957
958 this.numActive++;
959 }
960
961 this.findSmallest = function(exclude)
962 {
963 var minRadius = 1000.0, minIndex;
964 var i;
965
966 for(i = 0; i < this.blobs.length; i++)
967 {
968 if(i == exclude || this.blobs[i] == null)
969 {
970 continue;
971 }
972 if(this.blobs[i].getRadius() < minRadius)
973 {
974 minIndex = i;
975 minRadius = this.blobs[i].getRadius();
976 }
977 }
978 return minIndex;
979 }
980 this.findClosest = function(exclude)
981 {
982 var minDist = 1000.0, foundIndex, dist, aXbX, aYbY;
983 var i;
984 var myPointMass, otherPointMass;
985
986 myPointMass = this.blobs[exclude].getMiddlePointMass();
987 for(i = 0; i < this.blobs.length; i++)
988 {
989 if(i == exclude || this.blobs[i] == null)
990 {
991 continue;
992 }
993
994 otherPointMass = this.blobs[i].getMiddlePointMass();
995 aXbX = myPointMass.getXPos() - otherPointMass.getXPos();
996 aYbY = myPointMass.getYPos() - otherPointMass.getYPos();
997 dist = aXbX * aXbX + aYbY * aYbY;
998 if(dist < minDist)
999 {
1000 minDist = dist;
1001 foundIndex = i;
1002 }
1003 }
1004 return foundIndex;
1005 }
1006 this.join = function()
1007 {
1008 var blob1Index, blob2Index, blob1, blob2;
1009 var r1, r2, r3;
1010
1011 if(this.numActive == 1)
1012 {
1013 return;
1014 }
1015
1016 blob1Index = this.findSmallest(-1);
1017 blob2Index = this.findClosest(blob1Index);
1018
1019 r1 = this.blobs[blob1Index].getRadius();
1020 r2 = this.blobs[blob2Index].getRadius();
1021 r3 = Math.sqrt(r1 * r1 + r2 * r2);
1022
1023 this.blobs[blob1Index] = null;
1024 this.blobs[blob2Index].scale(0.945 * r3 / r2);
1025
1026 this.numActive--;
1027 }
1028
1029 this.selectBlob = function(x, y)
1030 {
1031 var i, minDist = 10000.0;
1032 var otherPointMass;
1033 var selectedBlob;
1034 var selectOffset = null;
1035
1036 if(this.selectedBlob != null)
1037 {
1038 return null;
1039 }
1040
1041 for(i = 0; i < this.blobs.length; i++)
1042 {
1043 if(this.blobs[i] == null)
1044 {
1045 continue;
1046 }
1047
1048 otherPointMass = this.blobs[i].getMiddlePointMass();
1049 var aXbX = x - otherPointMass.getXPos();
1050 var aYbY = y - otherPointMass.getYPos();
1051 var dist = aXbX * aXbX + aYbY * aYbY;
1052 if(dist < minDist)
1053 {
1054 minDist = dist;
1055 if(dist < this.blobs[i].getRadius() * 0.5)
1056 {
1057 this.selectedBlob = this.blobs[i];
1058 selectOffset = { x : aXbX, y : aYbY };
1059 }
1060 }
1061 }
1062
1063 if(this.selectedBlob != null)
1064 {
1065 this.selectedBlob.setSelected(true);
1066 }
1067 return selectOffset;
1068 }
1069 this.unselectBlob = function()
1070 {
1071 if(this.selectedBlob == null)
1072 {
1073 return;
1074 }
1075 this.selectedBlob.setSelected(false);
1076 this.selectedBlob = null;
1077 }
1078 this.selectedBlobMoveTo = function(x, y)
1079 {
1080 if(this.selectedBlob == null)
1081 {
1082 return;
1083 }
1084 this.selectedBlob.moveTo(x, y);
1085 }
1086
1087 this.move = function(dt)
1088 {
1089 var i;
1090
1091 for(i = 0; i < this.blobs.length; i++)
1092 {
1093 if(this.blobs[i] == null)
1094 {
1095 continue;
1096 }
1097 this.blobs[i].move(dt);
1098 }
1099 }
1100 this.sc = function(env)
1101 {
1102 var i;
1103
1104 for(i = 0; i < this.blobs.length; i++)
1105 {
1106 if(this.blobs[i] == null)
1107 {
1108 continue;
1109 }
1110 this.blobs[i].sc(env);
1111 }
1112 if(this.blobAnchor != null)
1113 {
1114 this.blobAnchor.sc();
1115 }
1116 }
1117 this.setForce = function(force)
1118 {
1119 var i;
1120
1121 for(i = 0; i < this.blobs.length; i++)
1122 {
1123 if(this.blobs[i] == null)
1124 {
1125 continue;
1126 }
1127 if(this.blobs[i] == this.selectedBlob)
1128 {
1129 this.blobs[i].setForce(new Vector(0.0, 0.0));
1130 continue;
1131 }
1132 this.blobs[i].setForce(force);
1133 }
1134 }
1135 this.addForce = function(force)
1136 {
1137 var i;
1138
1139 for(i = 0; i < this.blobs.length; i++)
1140 {
1141 if(this.blobs[i] == null)
1142 {
1143 continue;
1144 }
1145 if(this.blobs[i] == this.selectedBlob)
1146 {
1147 continue;
1148 }
1149 this.tmpForce.setX(force.getX() * (Math.random() * 0.75 + 0.25));
1150 this.tmpForce.setY(force.getY() * (Math.random() * 0.75 + 0.25));
1151 this.blobs[i].addForce(this.tmpForce);
1152 }
1153 }
1154 this.draw = function(ctx, scaleFactor)
1155 {
1156 var i;
1157
1158 for(i = 0; i < this.blobs.length; i++)
1159 {
1160 if(this.blobs[i] == null)
1161 {
1162 continue;
1163 }
1164 this.blobs[i].draw(ctx, scaleFactor);
1165 }
1166 }
1167 }
1168
1169 function debug(msg, okFunc, cancelFunc)
1170 {
1171 if(confirm(msg) == true && okFunc != null)
1172 {
1173 okFunc();
1174 }
1175 else if(cancelFunc != null)
1176 {
1177 cancelFunc();
1178 }
1179 }
1180
1181 var env;
1182 var scaleFactor;
1183 var blobColl;
1184 var gravity;
1185 var savedMouseCoords = null;
1186 var selectOffset = null;
1187
1188 function update()
1189 {
1190 var dt = 0.05;
1191
1192 if(savedMouseCoords != null && selectOffset != null)
1193 {
1194 blobColl.selectedBlobMoveTo(savedMouseCoords.x - selectOffset.x,
1195 savedMouseCoords.y - selectOffset.y);
1196 }
1197
1198 blobColl.move(dt);
1199 blobColl.sc(env);
1200 blobColl.setForce(gravity);
1201 }
1202
1203 function draw()
1204 {
1205 var canvas = document.getElementById('blobCanvas');
1206 if(canvas==null)
1207 {
1208 return;
1209 }
1210
1211 var ctx = canvas.getContext('2d');
1212
1213 ctx.clearRect(0, 0, canvas.width, canvas.height);
1214
1215 env.draw(ctx, scaleFactor);
1216 blobColl.draw(ctx, scaleFactor);
1217 }
1218
1219 function timeout()
1220 {
1221 if(blobColl && blobColl.paused==true) {
1222 return;
1223 }
1224 draw();
1225 update();
1226 setTimeout(timeout, 30);
1227 }
1228 function stopBlob() {
1229 if(blobColl) {
1230 blobColl.paused=true
1231 }
1232 }
1233 function startBlob() {
1234 if(blobColl && blobColl.paused==true) {
1235 blobColl.paused=false
1236 timeout()
1237 }
1238 else {
1239 initBlob()
1240 }
1241 }
1242 function initBlob()
1243 {
1244 var canvas = widget
1245 canvas.focus()
1246 if (canvas.my_initialized) {
1247 return
1248 }
1249 scaleFactor=Math.floor(canvas.height/2)
1250 canvas.my_initialized=true
1251 canvas.onkeydown = function(event)
1252 {
1253 var keyCode;
1254
1255 if(event == null)
1256 {
1257 keyCode = window.event.keyCode;
1258 }
1259 else
1260 {
1261 keyCode = event.keyCode;
1262 }
1263
1264 switch(keyCode)
1265 {
1266
1267 case 37:
1268 blobColl.addForce(new Vector(-50.0, 0.0));
1269 break;
1270
1271
1272 case 38:
1273 blobColl.addForce(new Vector(0.0, -50.0));
1274 break;
1275
1276
1277 case 39:
1278 blobColl.addForce(new Vector(50.0, 0.0));
1279 break;
1280
1281
1282 case 40:
1283 blobColl.addForce(new Vector(0.0, 50.0));
1284 break;
1285
1286
1287 case 74:
1288 blobColl.join();
1289 break;
1290
1291
1292 case 72:
1293 blobColl.split();
1294 break;
1295
1296
1297 case 71:
1298 toggleGravity();
1299 break;
1300
1301 default:
1302 break;
1303 }
1304 }
1305
1306
1307 function getMouseCoords(event)
1308 {
1309 if(event == null)
1310 {
1311 event = window.event;
1312 }
1313 if(event == null)
1314 {
1315 return null
1316 }
1317 var x=event.offsetLeft
1318 var y=event.offsetTop
1319 if(x<0) {
1320 x=0
1321 }
1322 if(y<0) {
1323 y=0
1324 }
1325 if(x>widget.width) {
1326 x=widget.width
1327 }
1328 if(y>widget.height) {
1329 y=widget.height
1330 }
1331 return {x:x / scaleFactor, y:y / scaleFactor};
1332 }
1333 canvas.onmousedown = function(event)
1334 {
1335 var mouseCoords;
1336
1337 mouseCoords = getMouseCoords(event);
1338 if(mouseCoords == null)
1339 {
1340 return;
1341 }
1342 selectOffset = blobColl.selectBlob(mouseCoords.x, mouseCoords.y);
1343 }
1344 canvas.onmouseup = function(event)
1345 {
1346 blobColl.unselectBlob();
1347 savedMouseCoords = null;
1348 selectOffset = null;
1349 }
1350 canvas.onmousedragged = function(event)
1351 {
1352 var mouseCoords;
1353 if(selectOffset == null)
1354 {
1355 return;
1356 }
1357 mouseCoords = getMouseCoords(event);
1358 if(mouseCoords == null)
1359 {
1360 return;
1361 }
1362 blobColl.selectedBlobMoveTo(mouseCoords.x - selectOffset.x, mouseCoords.y - selectOffset.y);
1363
1364 savedMouseCoords = mouseCoords;
1365 }
1366
1367 env = new Environment(0.2, 0.2, 2.6, 1.6);
1368 blobColl = new BlobCollective(1.0, 1.0, 1, 200);
1369 gravity = new Vector(0.0, 10.0);
1370 timeout();
1371 }
1372
1373 function splitBlob() {
1374 blobColl.split();
1375 }
1376 function rejoinBlob() {
1377 blobColl.join();
1378 }
1379 function toggleGravity()
1380 {
1381 if(gravity.getY() > 0.0)
1382 {
1383 gravity.setY(0.0);
1384 }
1385 else
1386 {
1387 gravity.setY(10.0);
1388 }
1389 }
1390
1391
1392