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); // 0.8, 1.2 works  
 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       // this.drawEars(ctx, scaleFactor); 
 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         // left 
1267         case 37: 
1268           blobColl.addForce(new Vector(-50.0, 0.0)); 
1269           break; 
1270          
1271         // up 
1272         case 38: 
1273           blobColl.addForce(new Vector(0.0, -50.0)); 
1274           break; 
1275           
1276         // right 
1277         case 39: 
1278           blobColl.addForce(new Vector(50.0, 0.0)); 
1279           break; 
1280         
1281         // down
1282         case 40: 
1283           blobColl.addForce(new Vector(0.0, 50.0)); 
1284           break; 
1285           
1286         // join 'j' 
1287         case 74:
1288           blobColl.join(); 
1289           break;  
1290         
1291         // split 'h'
1292         case 72: 
1293           blobColl.split(); 
1294           break; 
1295           
1296         // toggle gravity 'g'
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