diff --git a/README.md b/README.md index 8ebca6f..af3f497 100644 --- a/README.md +++ b/README.md @@ -24,3 +24,11 @@ Code Layout 2. js/verlet-js/constraint.js: _constraint code_ 3. js/verlet-js/verlet.js: _verlet-js engine_ 4. js/verlet-js/objects.js: _shapes and objects (triangles, circles, tires..)_ + +Build for npm +------------- + +``` js +npm run build +``` + diff --git a/css/style.css b/css/style.css index 726ffd7..5e4a3a7 100644 --- a/css/style.css +++ b/css/style.css @@ -86,9 +86,15 @@ canvas { margin: 34px auto; background: #fff; box-shadow: 2px 2px 8px 0px rgba(0,0,0,0.1); + clear: both; + + /* disable selection of canvas */ -moz-user-select: none; - -webkit-user-select: none; - user-select: none; + -webkit-user-select: none; + -o-user-select:none; + -ms-user-select:none; + -khtml-user-select:none; + user-select: none; } @@ -107,3 +113,22 @@ canvas { padding-top: 20px; border-top: 1px solid rgba(0,0,0,0.05); } + + +#bsa { + display: inline-block; + float: right; + width: 150px; + padding-top: 30px; + font-family: sans-serif; +} + +body .one .bsa_it_ad { background: transparent; border: none; font-family: inherit; padding: 0 15px 0 10px; margin: 0; text-align: left; } +body .one .bsa_it_ad:hover img { -moz-box-shadow: 0 0 3px #000; -webkit-box-shadow: 0 0 3px #000; box-shadow: 0 0 3px #000; } +body .one .bsa_it_ad .bsa_it_i { display: block; padding: 0; float: none; margin: 0 0 5px; } +body .one .bsa_it_ad .bsa_it_i img { padding: 0; border: none; } +body .one .bsa_it_ad .bsa_it_t { padding: 6px 0; } +body .one .bsa_it_ad .bsa_it_d { padding: 0; font-size: 10px; color: #333; } +body .one .bsa_it_p { display: none; } +body #bsap_aplink, body #bsap_aplink:hover { display: block; font-size: 9px; margin: 12px 15px 0; text-align: right; } + diff --git a/examples/cloth.html b/examples/cloth.html index 9dd57ba..d46431d 100644 --- a/examples/cloth.html +++ b/examples/cloth.html @@ -5,14 +5,15 @@ - - - - + + - - - - \ No newline at end of file diff --git a/examples/shapes.html b/examples/shapes.html index 86f0d8f..d0e7161 100644 --- a/examples/shapes.html +++ b/examples/shapes.html @@ -5,14 +5,15 @@ - - - - + + - - - - \ No newline at end of file diff --git a/examples/spiderweb.html b/examples/spiderweb.html index 24edb50..f000809 100644 --- a/examples/spiderweb.html +++ b/examples/spiderweb.html @@ -5,14 +5,15 @@ - - - - + + - - - - \ No newline at end of file diff --git a/examples/tree.html b/examples/tree.html index b3f33a9..9c30192 100644 --- a/examples/tree.html +++ b/examples/tree.html @@ -5,14 +5,15 @@ - - - - + + - - - - \ No newline at end of file diff --git a/index.html b/index.html index aed34cf..6a50a5c 100644 --- a/index.html +++ b/index.html @@ -7,8 +7,12 @@ + - - \ No newline at end of file diff --git a/js/verlet-1.0.0.js b/js/verlet-1.0.0.js new file mode 100644 index 0000000..bdf342e --- /dev/null +++ b/js/verlet-1.0.0.js @@ -0,0 +1,667 @@ +;(function(e,t,n){function i(n,s){if(!t[n]){if(!e[n]){var o=typeof require=="function"&&require;if(!s&&o)return o(n,!0);if(r)return r(n,!0);throw new Error("Cannot find module '"+n+"'")}var u=t[n]={exports:{}};e[n][0].call(u.exports,function(t){var r=e[n][1][t];return i(r?r:t)},u,u.exports)}return t[n].exports}var r=typeof require=="function"&&require;for(var s=0;s= Math.PI) + diff -= 2*Math.PI; + + diff *= stepCoef*this.stiffness; + + this.a.pos = this.a.pos.rotate(this.b.pos, diff); + this.c.pos = this.c.pos.rotate(this.b.pos, -diff); + this.b.pos = this.b.pos.rotate(this.a.pos, diff); + this.b.pos = this.b.pos.rotate(this.c.pos, -diff); +} + +AngleConstraint.prototype.draw = function(ctx) { + ctx.beginPath(); + ctx.moveTo(this.a.pos.x, this.a.pos.y); + ctx.lineTo(this.b.pos.x, this.b.pos.y); + ctx.lineTo(this.c.pos.x, this.c.pos.y); + var tmp = ctx.lineWidth; + ctx.lineWidth = 5; + ctx.strokeStyle = "rgba(255,255,0,0.2)"; + ctx.stroke(); + ctx.lineWidth = tmp; +} + +},{}],5:[function(require,module,exports){ + +/* +Copyright 2013 Sub Protocol and other contributors +http://subprotocol.com/ + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +// A simple 2-dimensional vector implementation + +module.exports = Vec2 + +function Vec2(x, y) { + this.x = x || 0; + this.y = y || 0; +} + +Vec2.prototype.add = function(v) { + return new Vec2(this.x + v.x, this.y + v.y); +} + +Vec2.prototype.sub = function(v) { + return new Vec2(this.x - v.x, this.y - v.y); +} + +Vec2.prototype.mul = function(v) { + return new Vec2(this.x * v.x, this.y * v.y); +} + +Vec2.prototype.div = function(v) { + return new Vec2(this.x / v.x, this.y / v.y); +} + +Vec2.prototype.scale = function(coef) { + return new Vec2(this.x*coef, this.y*coef); +} + +Vec2.prototype.mutableSet = function(v) { + this.x = v.x; + this.y = v.y; + return this; +} + +Vec2.prototype.mutableAdd = function(v) { + this.x += v.x; + this.y += v.y; + return this; +} + +Vec2.prototype.mutableSub = function(v) { + this.x -= v.x; + this.y -= v.y; + return this; +} + +Vec2.prototype.mutableMul = function(v) { + this.x *= v.x; + this.y *= v.y; + return this; +} + +Vec2.prototype.mutableDiv = function(v) { + this.x /= v.x; + this.y /= v.y; + return this; +} + +Vec2.prototype.mutableScale = function(coef) { + this.x *= coef; + this.y *= coef; + return this; +} + +Vec2.prototype.equals = function(v) { + return this.x == v.x && this.y == v.y; +} + +Vec2.prototype.epsilonEquals = function(v, epsilon) { + return Math.abs(this.x - v.x) <= epsilon && Math.abs(this.y - v.y) <= epsilon; +} + +Vec2.prototype.length = function(v) { + return Math.sqrt(this.x*this.x + this.y*this.y); +} + +Vec2.prototype.length2 = function(v) { + return this.x*this.x + this.y*this.y; +} + +Vec2.prototype.dist = function(v) { + return Math.sqrt(this.dist2(v)); +} + +Vec2.prototype.dist2 = function(v) { + var x = v.x - this.x; + var y = v.y - this.y; + return x*x + y*y; +} + +Vec2.prototype.normal = function() { + var m = Math.sqrt(this.x*this.x + this.y*this.y); + return new Vec2(this.x/m, this.y/m); +} + +Vec2.prototype.dot = function(v) { + return this.x*v.x + this.y*v.y; +} + +Vec2.prototype.angle = function(v) { + return Math.atan2(this.x*v.y-this.y*v.x,this.x*v.x+this.y*v.y); +} + +Vec2.prototype.angle2 = function(vLeft, vRight) { + return vLeft.sub(this).angle(vRight.sub(this)); +} + +Vec2.prototype.rotate = function(origin, theta) { + var x = this.x - origin.x; + var y = this.y - origin.y; + return new Vec2(x*Math.cos(theta) - y*Math.sin(theta) + origin.x, x*Math.sin(theta) + y*Math.cos(theta) + origin.y); +} + +Vec2.prototype.toString = function() { + return "(" + this.x + ", " + this.y + ")"; +} + +function test_Vec2() { + var assert = function(label, expression) { + console.log("Vec2(" + label + "): " + (expression == true ? "PASS" : "FAIL")); + if (expression != true) + throw "assertion failed"; + }; + + assert("equality", (new Vec2(5,3).equals(new Vec2(5,3)))); + assert("epsilon equality", (new Vec2(1,2).epsilonEquals(new Vec2(1.01,2.02), 0.03))); + assert("epsilon non-equality", !(new Vec2(1,2).epsilonEquals(new Vec2(1.01,2.02), 0.01))); + assert("addition", (new Vec2(1,1)).add(new Vec2(2, 3)).equals(new Vec2(3, 4))); + assert("subtraction", (new Vec2(4,3)).sub(new Vec2(2, 1)).equals(new Vec2(2, 2))); + assert("multiply", (new Vec2(2,4)).mul(new Vec2(2, 1)).equals(new Vec2(4, 4))); + assert("divide", (new Vec2(4,2)).div(new Vec2(2, 2)).equals(new Vec2(2, 1))); + assert("scale", (new Vec2(4,3)).scale(2).equals(new Vec2(8, 6))); + assert("mutable set", (new Vec2(1,1)).mutableSet(new Vec2(2, 3)).equals(new Vec2(2, 3))); + assert("mutable addition", (new Vec2(1,1)).mutableAdd(new Vec2(2, 3)).equals(new Vec2(3, 4))); + assert("mutable subtraction", (new Vec2(4,3)).mutableSub(new Vec2(2, 1)).equals(new Vec2(2, 2))); + assert("mutable multiply", (new Vec2(2,4)).mutableMul(new Vec2(2, 1)).equals(new Vec2(4, 4))); + assert("mutable divide", (new Vec2(4,2)).mutableDiv(new Vec2(2, 2)).equals(new Vec2(2, 1))); + assert("mutable scale", (new Vec2(4,3)).mutableScale(2).equals(new Vec2(8, 6))); + assert("length", Math.abs((new Vec2(4,4)).length() - 5.65685) <= 0.00001); + assert("length2", (new Vec2(2,4)).length2() == 20); + assert("dist", Math.abs((new Vec2(2,4)).dist(new Vec2(3,5)) - 1.4142135) <= 0.000001); + assert("dist2", (new Vec2(2,4)).dist2(new Vec2(3,5)) == 2); + + var normal = (new Vec2(2,4)).normal() + assert("normal", Math.abs(normal.length() - 1.0) <= 0.00001 && normal.epsilonEquals(new Vec2(0.4472, 0.89443), 0.0001)); + assert("dot", (new Vec2(2,3)).dot(new Vec2(4,1)) == 11); + assert("angle", (new Vec2(0,-1)).angle(new Vec2(1,0))*(180/Math.PI) == 90); + assert("angle2", (new Vec2(1,1)).angle2(new Vec2(1,0), new Vec2(2,1))*(180/Math.PI) == 90); + assert("rotate", (new Vec2(2,0)).rotate(new Vec2(1,0), Math.PI/2).equals(new Vec2(1,1))); + assert("toString", (new Vec2(2,4)) == "(2, 4)"); +} + + +},{}],4:[function(require,module,exports){ + +/* +Copyright 2013 Sub Protocol and other contributors +http://subprotocol.com/ + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +// generic verlet entities + +var VerletJS = require('./verlet') +var Particle = VerletJS.Particle +var constraints = require('./constraint') +var DistanceConstraint = constraints.DistanceConstraint + +VerletJS.prototype.point = function(pos) { + var composite = new this.Composite(); + composite.particles.push(new Particle(pos)); + this.composites.push(composite); + return composite; +} + +VerletJS.prototype.lineSegments = function(vertices, stiffness) { + var i; + + var composite = new this.Composite(); + + for (i in vertices) { + composite.particles.push(new Particle(vertices[i])); + if (i > 0) + composite.constraints.push(new DistanceConstraint(composite.particles[i], composite.particles[i-1], stiffness)); + } + + this.composites.push(composite); + return composite; +} + +VerletJS.prototype.cloth = function(origin, width, height, segments, pinMod, stiffness) { + + var composite = new this.Composite(); + + var xStride = width/segments; + var yStride = height/segments; + + var x,y; + for (y=0;y 0) + composite.constraints.push(new DistanceConstraint(composite.particles[y*segments+x], composite.particles[y*segments+x-1], stiffness)); + + if (y > 0) + composite.constraints.push(new DistanceConstraint(composite.particles[y*segments+x], composite.particles[(y-1)*segments+x], stiffness)); + } + } + + for (x=0;x this.height-1) + particle.pos.y = this.height-1; + + if (particle.pos.x < 0) + particle.pos.x = 0; + + if (particle.pos.x > this.width-1) + particle.pos.x = this.width-1; + } + + var _this = this; + + // prevent context menu + this.canvas.oncontextmenu = function(e) { + e.preventDefault(); + }; + + this.canvas.onmousedown = function(e) { + _this.mouseDown = true; + var nearest = _this.nearestEntity(); + if (nearest) { + _this.draggedEntity = nearest; + } + }; + + this.canvas.onmouseup = function(e) { + _this.mouseDown = false; + _this.draggedEntity = null; + }; + + this.canvas.onmousemove = function(e) { + var rect = _this.canvas.getBoundingClientRect(); + _this.mouse.x = e.clientX - rect.left; + _this.mouse.y = e.clientY - rect.top; + }; + + // simulation params + this.gravity = new Vec2(0,0.2); + this.friction = 0.99; + this.groundFriction = 0.8; + + // holds composite entities + this.composites = []; +} + +VerletJS.prototype.Composite = Composite + +function Composite() { + this.particles = []; + this.constraints = []; + + this.drawParticles = null; + this.drawConstraints = null; +} + +Composite.prototype.pin = function(index, pos) { + pos = pos || this.particles[index].pos; + var pc = new PinConstraint(this.particles[index], pos); + this.constraints.push(pc); + return pc; +} + +VerletJS.prototype.frame = function(step) { + var i, j, c; + + for (c in this.composites) { + for (i in this.composites[c].particles) { + var particles = this.composites[c].particles; + + // calculate velocity + var velocity = particles[i].pos.sub(particles[i].lastPos).scale(this.friction); + + // ground friction + if (particles[i].pos.y >= this.height-1 && velocity.length2() > 0.000001) { + var m = velocity.length(); + velocity.x /= m; + velocity.y /= m; + velocity.mutableScale(m*this.groundFriction); + } + + // save last good state + particles[i].lastPos.mutableSet(particles[i].pos); + + // gravity + particles[i].pos.mutableAdd(this.gravity); + + // inertia + particles[i].pos.mutableAdd(velocity); + } + } + + // handle dragging of entities + if (this.draggedEntity) + this.draggedEntity.pos.mutableSet(this.mouse); + + // relax + var stepCoef = 1/step; + for (c in this.composites) { + var constraints = this.composites[c].constraints; + for (i=0;i=Math.PI)diff-=2*Math.PI;diff*=stepCoef*this.stiffness;this.a.pos=this.a.pos.rotate(this.b.pos,diff);this.c.pos=this.c.pos.rotate(this.b.pos,-diff);this.b.pos=this.b.pos.rotate(this.a.pos,diff);this.b.pos=this.b.pos.rotate(this.c.pos,-diff)};AngleConstraint.prototype.draw=function(ctx){ctx.beginPath();ctx.moveTo(this.a.pos.x,this.a.pos.y);ctx.lineTo(this.b.pos.x,this.b.pos.y);ctx.lineTo(this.c.pos.x,this.c.pos.y);var tmp=ctx.lineWidth;ctx.lineWidth=5;ctx.strokeStyle="rgba(255,255,0,0.2)";ctx.stroke();ctx.lineWidth=tmp}},{}],5:[function(require,module,exports){module.exports=Vec2;function Vec2(x,y){this.x=x||0;this.y=y||0}Vec2.prototype.add=function(v){return new Vec2(this.x+v.x,this.y+v.y)};Vec2.prototype.sub=function(v){return new Vec2(this.x-v.x,this.y-v.y)};Vec2.prototype.mul=function(v){return new Vec2(this.x*v.x,this.y*v.y)};Vec2.prototype.div=function(v){return new Vec2(this.x/v.x,this.y/v.y)};Vec2.prototype.scale=function(coef){return new Vec2(this.x*coef,this.y*coef)};Vec2.prototype.mutableSet=function(v){this.x=v.x;this.y=v.y;return this};Vec2.prototype.mutableAdd=function(v){this.x+=v.x;this.y+=v.y;return this};Vec2.prototype.mutableSub=function(v){this.x-=v.x;this.y-=v.y;return this};Vec2.prototype.mutableMul=function(v){this.x*=v.x;this.y*=v.y;return this};Vec2.prototype.mutableDiv=function(v){this.x/=v.x;this.y/=v.y;return this};Vec2.prototype.mutableScale=function(coef){this.x*=coef;this.y*=coef;return this};Vec2.prototype.equals=function(v){return this.x==v.x&&this.y==v.y};Vec2.prototype.epsilonEquals=function(v,epsilon){return Math.abs(this.x-v.x)<=epsilon&&Math.abs(this.y-v.y)<=epsilon};Vec2.prototype.length=function(v){return Math.sqrt(this.x*this.x+this.y*this.y)};Vec2.prototype.length2=function(v){return this.x*this.x+this.y*this.y};Vec2.prototype.dist=function(v){return Math.sqrt(this.dist2(v))};Vec2.prototype.dist2=function(v){var x=v.x-this.x;var y=v.y-this.y;return x*x+y*y};Vec2.prototype.normal=function(){var m=Math.sqrt(this.x*this.x+this.y*this.y);return new Vec2(this.x/m,this.y/m)};Vec2.prototype.dot=function(v){return this.x*v.x+this.y*v.y};Vec2.prototype.angle=function(v){return Math.atan2(this.x*v.y-this.y*v.x,this.x*v.x+this.y*v.y)};Vec2.prototype.angle2=function(vLeft,vRight){return vLeft.sub(this).angle(vRight.sub(this))};Vec2.prototype.rotate=function(origin,theta){var x=this.x-origin.x;var y=this.y-origin.y;return new Vec2(x*Math.cos(theta)-y*Math.sin(theta)+origin.x,x*Math.sin(theta)+y*Math.cos(theta)+origin.y)};Vec2.prototype.toString=function(){return"("+this.x+", "+this.y+")"};function test_Vec2(){var assert=function(label,expression){console.log("Vec2("+label+"): "+(expression==true?"PASS":"FAIL"));if(expression!=true)throw"assertion failed"};assert("equality",new Vec2(5,3).equals(new Vec2(5,3)));assert("epsilon equality",new Vec2(1,2).epsilonEquals(new Vec2(1.01,2.02),.03));assert("epsilon non-equality",!new Vec2(1,2).epsilonEquals(new Vec2(1.01,2.02),.01));assert("addition",new Vec2(1,1).add(new Vec2(2,3)).equals(new Vec2(3,4)));assert("subtraction",new Vec2(4,3).sub(new Vec2(2,1)).equals(new Vec2(2,2)));assert("multiply",new Vec2(2,4).mul(new Vec2(2,1)).equals(new Vec2(4,4)));assert("divide",new Vec2(4,2).div(new Vec2(2,2)).equals(new Vec2(2,1)));assert("scale",new Vec2(4,3).scale(2).equals(new Vec2(8,6)));assert("mutable set",new Vec2(1,1).mutableSet(new Vec2(2,3)).equals(new Vec2(2,3)));assert("mutable addition",new Vec2(1,1).mutableAdd(new Vec2(2,3)).equals(new Vec2(3,4)));assert("mutable subtraction",new Vec2(4,3).mutableSub(new Vec2(2,1)).equals(new Vec2(2,2)));assert("mutable multiply",new Vec2(2,4).mutableMul(new Vec2(2,1)).equals(new Vec2(4,4)));assert("mutable divide",new Vec2(4,2).mutableDiv(new Vec2(2,2)).equals(new Vec2(2,1)));assert("mutable scale",new Vec2(4,3).mutableScale(2).equals(new Vec2(8,6)));assert("length",Math.abs(new Vec2(4,4).length()-5.65685)<=1e-5);assert("length2",new Vec2(2,4).length2()==20);assert("dist",Math.abs(new Vec2(2,4).dist(new Vec2(3,5))-1.4142135)<=1e-6);assert("dist2",new Vec2(2,4).dist2(new Vec2(3,5))==2);var normal=new Vec2(2,4).normal();assert("normal",Math.abs(normal.length()-1)<=1e-5&&normal.epsilonEquals(new Vec2(.4472,.89443),1e-4));assert("dot",new Vec2(2,3).dot(new Vec2(4,1))==11);assert("angle",new Vec2(0,-1).angle(new Vec2(1,0))*(180/Math.PI)==90);assert("angle2",new Vec2(1,1).angle2(new Vec2(1,0),new Vec2(2,1))*(180/Math.PI)==90);assert("rotate",new Vec2(2,0).rotate(new Vec2(1,0),Math.PI/2).equals(new Vec2(1,1)));assert("toString",new Vec2(2,4)=="(2, 4)")}},{}],4:[function(require,module,exports){var VerletJS=require("./verlet");var Particle=VerletJS.Particle;var constraints=require("./constraint");var DistanceConstraint=constraints.DistanceConstraint;VerletJS.prototype.point=function(pos){var composite=new this.Composite;composite.particles.push(new Particle(pos));this.composites.push(composite);return composite};VerletJS.prototype.lineSegments=function(vertices,stiffness){var i;var composite=new this.Composite;for(i in vertices){composite.particles.push(new Particle(vertices[i]));if(i>0)composite.constraints.push(new DistanceConstraint(composite.particles[i],composite.particles[i-1],stiffness))}this.composites.push(composite);return composite};VerletJS.prototype.cloth=function(origin,width,height,segments,pinMod,stiffness){var composite=new this.Composite;var xStride=width/segments;var yStride=height/segments;var x,y;for(y=0;y0)composite.constraints.push(new DistanceConstraint(composite.particles[y*segments+x],composite.particles[y*segments+x-1],stiffness));if(y>0)composite.constraints.push(new DistanceConstraint(composite.particles[y*segments+x],composite.particles[(y-1)*segments+x],stiffness))}}for(x=0;xthis.height-1)particle.pos.y=this.height-1;if(particle.pos.x<0)particle.pos.x=0;if(particle.pos.x>this.width-1)particle.pos.x=this.width-1};var _this=this;this.canvas.oncontextmenu=function(e){e.preventDefault()};this.canvas.onmousedown=function(e){_this.mouseDown=true;var nearest=_this.nearestEntity();if(nearest){_this.draggedEntity=nearest}};this.canvas.onmouseup=function(e){_this.mouseDown=false;_this.draggedEntity=null};this.canvas.onmousemove=function(e){var rect=_this.canvas.getBoundingClientRect();_this.mouse.x=e.clientX-rect.left;_this.mouse.y=e.clientY-rect.top};this.gravity=new Vec2(0,.2);this.friction=.99;this.groundFriction=.8;this.composites=[]}VerletJS.prototype.Composite=Composite;function Composite(){this.particles=[];this.constraints=[];this.drawParticles=null;this.drawConstraints=null}Composite.prototype.pin=function(index,pos){pos=pos||this.particles[index].pos;var pc=new PinConstraint(this.particles[index],pos);this.constraints.push(pc);return pc};VerletJS.prototype.frame=function(step){var i,j,c;for(c in this.composites){for(i in this.composites[c].particles){var particles=this.composites[c].particles;var velocity=particles[i].pos.sub(particles[i].lastPos).scale(this.friction);if(particles[i].pos.y>=this.height-1&&velocity.length2()>1e-6){var m=velocity.length();velocity.x/=m;velocity.y/=m;velocity.mutableScale(m*this.groundFriction)}particles[i].lastPos.mutableSet(particles[i].pos);particles[i].pos.mutableAdd(this.gravity);particles[i].pos.mutableAdd(velocity)}}if(this.draggedEntity)this.draggedEntity.pos.mutableSet(this.mouse);var stepCoef=1/step;for(c in this.composites){var constraints=this.composites[c].constraints;for(i=0;i ./js/verlet-$V.min.js" + }, + "repository": { + "type": "git", + "url": "git://github.com/subprotocol/verlet-js.git" + }, + "keywords": [ + "physics", + "2d", + "verlet" + ], + "author": "subprotocol", + "license": "BSD", + "readmeFilename": "README.md", + "gitHead": "283d23fa020d5147e95e84c5ff3541c17ea2abe5", + "bugs": { + "url": "https://github.com/subprotocol/verlet-js/issues" + }, + "dependencies": { + "vec2": "~1.3.0" + }, + "devDependencies": { + "browserify": "~2.18.1", + "uglify-js": "~2.3.6" + } +} diff --git a/site/js/common.js b/site/js/common.js new file mode 100644 index 0000000..533f759 --- /dev/null +++ b/site/js/common.js @@ -0,0 +1,10 @@ + +if (window.location.host == "subprotocol.com") { + (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ + (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), + m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) + })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); + + ga('create', 'UA-83795-7', 'subprotocol.com'); + ga('send', 'pageview'); +}