├── README.md ├── LICENSE ├── css └── style.css ├── index.html ├── examples ├── shapes.html ├── cloth.html ├── tree.html └── spiderweb.html └── js └── verlet-js ├── constraint.js ├── objects.js ├── vec2.js └── verlet.js /README.md: -------------------------------------------------------------------------------- 1 | verlet-js 2 | ========= 3 | 4 | A simple Verlet (pronounced 'ver-ley') physics engine written in javascript. 5 | 6 | Particles, distance constraints, and angular constraints are all supported by verlet-js. From these primitives it is possible to construct just about anything you can imagine. 7 | 8 | License 9 | ------- 10 | You may use verlet-js under the terms of the MIT License (See [LICENSE](LICENSE)). 11 | 12 | 13 | Examples 14 | -------- 15 | 1. [Shapes (verlet-js Hello world)](http://subprotocol.com/verlet-js/examples/shapes.html) 16 | 2. [Fractal Trees](http://subprotocol.com/verlet-js/examples/tree.html) 17 | 3. [Cloth](http://subprotocol.com/verlet-js/examples/cloth.html) 18 | 4. [Spiderweb](http://subprotocol.com/verlet-js/examples/spiderweb.html) 19 | 20 | 21 | Code Layout 22 | ----------- 23 | 1. js/verlet-js/vec2.js: _2d vector implementation_ 24 | 2. js/verlet-js/constraint.js: _constraint code_ 25 | 3. js/verlet-js/verlet.js: _verlet-js engine_ 26 | 4. js/verlet-js/objects.js: _shapes and objects (triangles, circles, tires..)_ 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2013 Sub Protocol and other contributors 2 | http://subprotocol.com/ 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining 5 | a copy of this software and associated documentation files (the 6 | "Software"), to deal in the Software without restriction, including 7 | without limitation the rights to use, copy, modify, merge, publish, 8 | distribute, sublicense, and/or sell copies of the Software, and to 9 | permit persons to whom the Software is furnished to do so, subject to 10 | the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 19 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 20 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 21 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /css/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | background: #e4e6e8; 3 | background: -moz-linear-gradient(top, #ffffff 0%, #e4e8ee 20%, #e4e8ee 80%, #ffffff 100%); /* FF3.6+ */ 4 | background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#ffffff), color-stop(20%,#e4e8ee), color-stop(80%,#e4e8ee), color-stop(100%,#ffffff)); /* Chrome,Safari4+ */ 5 | background: -webkit-linear-gradient(top, #ffffff 0%,#e4e8ee 20%,#e4e8ee 80%,#ffffff 100%); /* Chrome10+,Safari5.1+ */ 6 | background: -o-linear-gradient(top, #ffffff 0%,#e4e8ee 20%,#e4e8ee 80%,#ffffff 100%); /* Opera 11.10+ */ 7 | background: -ms-linear-gradient(top, #ffffff 0%,#e4e8ee 20%,#e4e8ee 80%,#ffffff 100%); /* IE10+ */ 8 | background: linear-gradient(to bottom, #ffffff 0%,#e4e8ee 20%,#e4e8ee 80%,#ffffff 100%); /* W3C */ 9 | filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#ffffff', endColorstr='#ffffff',GradientType=0 ); /* IE6-9 */ 10 | 11 | color: #969ba3; 12 | font-family: "Libre Baskerville", sans-serif; 13 | font-weight: 400; 14 | line-height: 1.2em; 15 | } 16 | 17 | a { 18 | color: #26ae90; 19 | } 20 | 21 | a:hover { 22 | color: #0b6b6a; 23 | } 24 | 25 | b { 26 | color: #696e76; 27 | } 28 | 29 | ul { 30 | list-style: square; 31 | } 32 | 33 | li { 34 | margin: 4px 0px; 35 | } 36 | 37 | #header { 38 | width: 800px; 39 | margin: 0px auto; 40 | font-size: 10pt; 41 | } 42 | 43 | h1 { 44 | display: block; 45 | font-weight: 700; 46 | margin: 50px 0px 0px 0px; 47 | padding-bottom: 3px; 48 | border-bottom: 1px solid rgba(0,0,0,0.05); 49 | text-shadow: 1px 1px 0px #fff; 50 | } 51 | 52 | h1:after { 53 | content: ""; 54 | display: block; 55 | padding-bottom: 20px; 56 | border-bottom: 1px solid rgba(0,0,0,0.05); 57 | } 58 | 59 | h1 a { 60 | color: #969ba3; 61 | text-decoration: none; 62 | } 63 | 64 | h1 a:hover { 65 | color: #585a5d; 66 | text-decoration: none; 67 | } 68 | 69 | h1 em { 70 | color: #444; 71 | font-style: normal; 72 | } 73 | 74 | 75 | 76 | h4 { 77 | color: #444; 78 | font-weight: 700; 79 | font-size: 12pt; 80 | margin: 20px 0px 0px 0px; 81 | } 82 | 83 | 84 | canvas { 85 | display: block; 86 | margin: 34px auto; 87 | background: #fff; 88 | box-shadow: 2px 2px 8px 0px rgba(0,0,0,0.1); 89 | -moz-user-select: none; 90 | -webkit-user-select: none; 91 | user-select: none; 92 | } 93 | 94 | 95 | #footer { 96 | line-height: 1.5em; 97 | font-size: 8pt; 98 | border-top: 1px solid rgba(0,0,0,0.05); 99 | padding-top: 3px; 100 | width: 800px; 101 | margin: 0px auto 100px auto; 102 | } 103 | 104 | #footer:before { 105 | content: ""; 106 | display: block; 107 | padding-top: 20px; 108 | border-top: 1px solid rgba(0,0,0,0.05); 109 | } 110 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Verlet-js 5 | 6 | 7 | 8 | 9 | 10 | 48 | 49 | 53 | 54 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /examples/shapes.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Verlet Shapes 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 27 | 28 | 68 | 72 | 73 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /js/verlet-js/constraint.js: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | Copyright 2013 Sub Protocol and other contributors 4 | http://subprotocol.com/ 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining 7 | a copy of this software and associated documentation files (the 8 | "Software"), to deal in the Software without restriction, including 9 | without limitation the rights to use, copy, modify, merge, publish, 10 | distribute, sublicense, and/or sell copies of the Software, and to 11 | permit persons to whom the Software is furnished to do so, subject to 12 | the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be 15 | included in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 20 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 21 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 22 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 23 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 | */ 25 | 26 | // DistanceConstraint -- constrains to initial distance 27 | // PinConstraint -- constrains to static/fixed point 28 | // AngleConstraint -- constrains 3 particles to an angle 29 | 30 | function DistanceConstraint(a, b, stiffness, distance /*optional*/) { 31 | this.a = a; 32 | this.b = b; 33 | this.distance = typeof distance != "undefined" ? distance : a.pos.sub(b.pos).length(); 34 | this.stiffness = stiffness; 35 | } 36 | 37 | DistanceConstraint.prototype.relax = function(stepCoef) { 38 | var normal = this.a.pos.sub(this.b.pos); 39 | var m = normal.length2(); 40 | normal.mutableScale(((this.distance*this.distance - m)/m)*this.stiffness*stepCoef); 41 | this.a.pos.mutableAdd(normal); 42 | this.b.pos.mutableSub(normal); 43 | } 44 | 45 | DistanceConstraint.prototype.draw = function(ctx) { 46 | ctx.beginPath(); 47 | ctx.moveTo(this.a.pos.x, this.a.pos.y); 48 | ctx.lineTo(this.b.pos.x, this.b.pos.y); 49 | ctx.strokeStyle = "#d8dde2"; 50 | ctx.stroke(); 51 | } 52 | 53 | 54 | 55 | function PinConstraint(a, pos) { 56 | this.a = a; 57 | this.pos = (new Vec2()).mutableSet(pos); 58 | } 59 | 60 | PinConstraint.prototype.relax = function(stepCoef) { 61 | this.a.pos.mutableSet(this.pos); 62 | } 63 | 64 | PinConstraint.prototype.draw = function(ctx) { 65 | ctx.beginPath(); 66 | ctx.arc(this.pos.x, this.pos.y, 6, 0, 2*Math.PI); 67 | ctx.fillStyle = "rgba(0,153,255,0.1)"; 68 | ctx.fill(); 69 | } 70 | 71 | 72 | function AngleConstraint(a, b, c, stiffness) { 73 | this.a = a; 74 | this.b = b; 75 | this.c = c; 76 | this.angle = this.b.pos.angle2(this.a.pos, this.c.pos); 77 | this.stiffness = stiffness; 78 | } 79 | 80 | AngleConstraint.prototype.relax = function(stepCoef) { 81 | var angle = this.b.pos.angle2(this.a.pos, this.c.pos); 82 | var diff = angle - this.angle; 83 | 84 | if (diff <= -Math.PI) 85 | diff += 2*Math.PI; 86 | else if (diff >= Math.PI) 87 | diff -= 2*Math.PI; 88 | 89 | diff *= stepCoef*this.stiffness; 90 | 91 | this.a.pos = this.a.pos.rotate(this.b.pos, diff); 92 | this.c.pos = this.c.pos.rotate(this.b.pos, -diff); 93 | this.b.pos = this.b.pos.rotate(this.a.pos, diff); 94 | this.b.pos = this.b.pos.rotate(this.c.pos, -diff); 95 | } 96 | 97 | AngleConstraint.prototype.draw = function(ctx) { 98 | ctx.beginPath(); 99 | ctx.moveTo(this.a.pos.x, this.a.pos.y); 100 | ctx.lineTo(this.b.pos.x, this.b.pos.y); 101 | ctx.lineTo(this.c.pos.x, this.c.pos.y); 102 | var tmp = ctx.lineWidth; 103 | ctx.lineWidth = 5; 104 | ctx.strokeStyle = "rgba(255,255,0,0.2)"; 105 | ctx.stroke(); 106 | ctx.lineWidth = tmp; 107 | } 108 | -------------------------------------------------------------------------------- /js/verlet-js/objects.js: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | Copyright 2013 Sub Protocol and other contributors 4 | http://subprotocol.com/ 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining 7 | a copy of this software and associated documentation files (the 8 | "Software"), to deal in the Software without restriction, including 9 | without limitation the rights to use, copy, modify, merge, publish, 10 | distribute, sublicense, and/or sell copies of the Software, and to 11 | permit persons to whom the Software is furnished to do so, subject to 12 | the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be 15 | included in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 20 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 21 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 22 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 23 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 | */ 25 | 26 | // generic verlet entities 27 | 28 | VerletJS.prototype.point = function(pos) { 29 | var composite = new this.Composite(); 30 | composite.particles.push(new Particle(pos)); 31 | this.composites.push(composite); 32 | return composite; 33 | } 34 | 35 | VerletJS.prototype.point = function(v, stiffness) { 36 | var composite = new this.Composite(); 37 | composite.particles.push(new Particle(v)); 38 | this.composites.push(composite); 39 | return composite; 40 | } 41 | 42 | VerletJS.prototype.lineSegments = function(vertices, stiffness) { 43 | var i; 44 | 45 | var composite = new this.Composite(); 46 | 47 | for (i in vertices) { 48 | composite.particles.push(new Particle(vertices[i])); 49 | if (i > 0) 50 | composite.constraints.push(new DistanceConstraint(composite.particles[i], composite.particles[i-1], stiffness)); 51 | } 52 | 53 | this.composites.push(composite); 54 | return composite; 55 | } 56 | 57 | VerletJS.prototype.cloth = function(origin, width, height, segments, pinMod, stiffness) { 58 | 59 | var composite = new this.Composite(); 60 | 61 | var xStride = width/segments; 62 | var yStride = height/segments; 63 | 64 | var x,y; 65 | for (y=0;y 0) 72 | composite.constraints.push(new DistanceConstraint(composite.particles[y*segments+x], composite.particles[y*segments+x-1], stiffness)); 73 | 74 | if (y > 0) 75 | composite.constraints.push(new DistanceConstraint(composite.particles[y*segments+x], composite.particles[(y-1)*segments+x], stiffness)); 76 | } 77 | } 78 | 79 | for (x=0;x 2 | 3 | 4 | Verlet Cloth Simulation 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 27 | 28 | 116 | 120 | 121 | 132 | 133 | 134 | -------------------------------------------------------------------------------- /examples/tree.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Verlet Fractal Trees 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 27 | 28 | 158 | 162 | 163 | 174 | 175 | 176 | -------------------------------------------------------------------------------- /js/verlet-js/vec2.js: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | Copyright 2013 Sub Protocol and other contributors 4 | http://subprotocol.com/ 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining 7 | a copy of this software and associated documentation files (the 8 | "Software"), to deal in the Software without restriction, including 9 | without limitation the rights to use, copy, modify, merge, publish, 10 | distribute, sublicense, and/or sell copies of the Software, and to 11 | permit persons to whom the Software is furnished to do so, subject to 12 | the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be 15 | included in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 20 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 21 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 22 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 23 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 | */ 25 | 26 | 27 | // A simple 2-dimensional vector implementation 28 | 29 | function Vec2(x, y) { 30 | this.x = x || 0; 31 | this.y = y || 0; 32 | } 33 | 34 | Vec2.prototype.add = function(v) { 35 | return new Vec2(this.x + v.x, this.y + v.y); 36 | } 37 | 38 | Vec2.prototype.sub = function(v) { 39 | return new Vec2(this.x - v.x, this.y - v.y); 40 | } 41 | 42 | Vec2.prototype.mul = function(v) { 43 | return new Vec2(this.x * v.x, this.y * v.y); 44 | } 45 | 46 | Vec2.prototype.div = function(v) { 47 | return new Vec2(this.x / v.x, this.y / v.y); 48 | } 49 | 50 | Vec2.prototype.scale = function(coef) { 51 | return new Vec2(this.x*coef, this.y*coef); 52 | } 53 | 54 | Vec2.prototype.mutableSet = function(v) { 55 | this.x = v.x; 56 | this.y = v.y; 57 | return this; 58 | } 59 | 60 | Vec2.prototype.mutableAdd = function(v) { 61 | this.x += v.x; 62 | this.y += v.y; 63 | return this; 64 | } 65 | 66 | Vec2.prototype.mutableSub = function(v) { 67 | this.x -= v.x; 68 | this.y -= v.y; 69 | return this; 70 | } 71 | 72 | Vec2.prototype.mutableMul = function(v) { 73 | this.x *= v.x; 74 | this.y *= v.y; 75 | return this; 76 | } 77 | 78 | Vec2.prototype.mutableDiv = function(v) { 79 | this.x /= v.x; 80 | this.y /= v.y; 81 | return this; 82 | } 83 | 84 | Vec2.prototype.mutableScale = function(coef) { 85 | this.x *= coef; 86 | this.y *= coef; 87 | return this; 88 | } 89 | 90 | Vec2.prototype.equals = function(v) { 91 | return this.x == v.x && this.y == v.y; 92 | } 93 | 94 | Vec2.prototype.epsilonEquals = function(v, epsilon) { 95 | return Math.abs(this.x - v.x) <= epsilon && Math.abs(this.y - v.y) <= epsilon; 96 | } 97 | 98 | Vec2.prototype.length = function(v) { 99 | return Math.sqrt(this.x*this.x + this.y*this.y); 100 | } 101 | 102 | Vec2.prototype.length2 = function(v) { 103 | return this.x*this.x + this.y*this.y; 104 | } 105 | 106 | Vec2.prototype.dist = function(v) { 107 | return Math.sqrt(this.dist2(v)); 108 | } 109 | 110 | Vec2.prototype.dist2 = function(v) { 111 | var x = v.x - this.x; 112 | var y = v.y - this.y; 113 | return x*x + y*y; 114 | } 115 | 116 | Vec2.prototype.normal = function() { 117 | var m = Math.sqrt(this.x*this.x + this.y*this.y); 118 | return new Vec2(this.x/m, this.y/m); 119 | } 120 | 121 | Vec2.prototype.dot = function(v) { 122 | return this.x*v.x + this.y*v.y; 123 | } 124 | 125 | Vec2.prototype.angle = function(v) { 126 | return Math.atan2(this.x*v.y-this.y*v.x,this.x*v.x+this.y*v.y); 127 | } 128 | 129 | Vec2.prototype.angle2 = function(vLeft, vRight) { 130 | return vLeft.sub(this).angle(vRight.sub(this)); 131 | } 132 | 133 | Vec2.prototype.rotate = function(origin, theta) { 134 | var x = this.x - origin.x; 135 | var y = this.y - origin.y; 136 | return new Vec2(x*Math.cos(theta) - y*Math.sin(theta) + origin.x, x*Math.sin(theta) + y*Math.cos(theta) + origin.y); 137 | } 138 | 139 | Vec2.prototype.toString = function() { 140 | return "(" + this.x + ", " + this.y + ")"; 141 | } 142 | 143 | function test_Vec2() { 144 | var assert = function(label, expression) { 145 | console.log("Vec2(" + label + "): " + (expression == true ? "PASS" : "FAIL")); 146 | if (expression != true) 147 | throw "assertion failed"; 148 | }; 149 | 150 | assert("equality", (new Vec2(5,3).equals(new Vec2(5,3)))); 151 | assert("epsilon equality", (new Vec2(1,2).epsilonEquals(new Vec2(1.01,2.02), 0.03))); 152 | assert("epsilon non-equality", !(new Vec2(1,2).epsilonEquals(new Vec2(1.01,2.02), 0.01))); 153 | assert("addition", (new Vec2(1,1)).add(new Vec2(2, 3)).equals(new Vec2(3, 4))); 154 | assert("subtraction", (new Vec2(4,3)).sub(new Vec2(2, 1)).equals(new Vec2(2, 2))); 155 | assert("multiply", (new Vec2(2,4)).mul(new Vec2(2, 1)).equals(new Vec2(4, 4))); 156 | assert("divide", (new Vec2(4,2)).div(new Vec2(2, 2)).equals(new Vec2(2, 1))); 157 | assert("scale", (new Vec2(4,3)).scale(2).equals(new Vec2(8, 6))); 158 | assert("mutable set", (new Vec2(1,1)).mutableSet(new Vec2(2, 3)).equals(new Vec2(2, 3))); 159 | assert("mutable addition", (new Vec2(1,1)).mutableAdd(new Vec2(2, 3)).equals(new Vec2(3, 4))); 160 | assert("mutable subtraction", (new Vec2(4,3)).mutableSub(new Vec2(2, 1)).equals(new Vec2(2, 2))); 161 | assert("mutable multiply", (new Vec2(2,4)).mutableMul(new Vec2(2, 1)).equals(new Vec2(4, 4))); 162 | assert("mutable divide", (new Vec2(4,2)).mutableDiv(new Vec2(2, 2)).equals(new Vec2(2, 1))); 163 | assert("mutable scale", (new Vec2(4,3)).mutableScale(2).equals(new Vec2(8, 6))); 164 | assert("length", Math.abs((new Vec2(4,4)).length() - 5.65685) <= 0.00001); 165 | assert("length2", (new Vec2(2,4)).length2() == 20); 166 | assert("dist", Math.abs((new Vec2(2,4)).dist(new Vec2(3,5)) - 1.4142135) <= 0.000001); 167 | assert("dist2", (new Vec2(2,4)).dist2(new Vec2(3,5)) == 2); 168 | 169 | var normal = (new Vec2(2,4)).normal() 170 | assert("normal", Math.abs(normal.length() - 1.0) <= 0.00001 && normal.epsilonEquals(new Vec2(0.4472, 0.89443), 0.0001)); 171 | assert("dot", (new Vec2(2,3)).dot(new Vec2(4,1)) == 11); 172 | assert("angle", (new Vec2(0,-1)).angle(new Vec2(1,0))*(180/Math.PI) == 90); 173 | assert("angle2", (new Vec2(1,1)).angle2(new Vec2(1,0), new Vec2(2,1))*(180/Math.PI) == 90); 174 | assert("rotate", (new Vec2(2,0)).rotate(new Vec2(1,0), Math.PI/2).equals(new Vec2(1,1))); 175 | assert("toString", (new Vec2(2,4)) == "(2, 4)"); 176 | } 177 | 178 | -------------------------------------------------------------------------------- /js/verlet-js/verlet.js: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | Copyright 2013 Sub Protocol and other contributors 4 | http://subprotocol.com/ 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining 7 | a copy of this software and associated documentation files (the 8 | "Software"), to deal in the Software without restriction, including 9 | without limitation the rights to use, copy, modify, merge, publish, 10 | distribute, sublicense, and/or sell copies of the Software, and to 11 | permit persons to whom the Software is furnished to do so, subject to 12 | the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be 15 | included in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 20 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 21 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 22 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 23 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 | */ 25 | 26 | window.requestAnimFrame = window.requestAnimationFrame 27 | || window.webkitRequestAnimationFrame 28 | || window.mozRequestAnimationFrame 29 | || window.oRequestAnimationFrame 30 | || window.msRequestAnimationFrame 31 | || function(callback) { 32 | window.setTimeout(callback, 1000 / 60); 33 | }; 34 | 35 | function Particle(pos) { 36 | this.pos = (new Vec2()).mutableSet(pos); 37 | this.lastPos = (new Vec2()).mutableSet(pos); 38 | } 39 | 40 | Particle.prototype.draw = function(ctx) { 41 | ctx.beginPath(); 42 | ctx.arc(this.pos.x, this.pos.y, 2, 0, 2*Math.PI); 43 | ctx.fillStyle = "#2dad8f"; 44 | ctx.fill(); 45 | } 46 | 47 | var VerletJS = function(width, height, canvas) { 48 | this.width = width; 49 | this.height = height; 50 | this.canvas = canvas; 51 | this.ctx = canvas.getContext("2d"); 52 | this.mouse = new Vec2(0,0); 53 | this.mouseDown = false; 54 | this.draggedEntity = null; 55 | this.selectionRadius = 20; 56 | this.highlightColor = "#4f545c"; 57 | 58 | this.bounds = function (particle) { 59 | if (particle.pos.y > this.height-1) 60 | particle.pos.y = this.height-1; 61 | 62 | if (particle.pos.x < 0) 63 | particle.pos.x = 0; 64 | 65 | if (particle.pos.x > this.width-1) 66 | particle.pos.x = this.width-1; 67 | } 68 | 69 | var _this = this; 70 | 71 | // prevent context menu 72 | this.canvas.oncontextmenu = function(e) { 73 | e.preventDefault(); 74 | }; 75 | 76 | this.canvas.onmousedown = function(e) { 77 | _this.mouseDown = true; 78 | var nearest = _this.nearestEntity(); 79 | if (nearest) { 80 | _this.draggedEntity = nearest; 81 | } 82 | }; 83 | 84 | this.canvas.onmouseup = function(e) { 85 | _this.mouseDown = false; 86 | _this.draggedEntity = null; 87 | }; 88 | 89 | this.canvas.onmousemove = function(e) { 90 | var rect = _this.canvas.getBoundingClientRect(); 91 | _this.mouse.x = e.clientX - rect.left; 92 | _this.mouse.y = e.clientY - rect.top; 93 | }; 94 | 95 | // simulation params 96 | this.gravity = new Vec2(0,0.2); 97 | this.friction = 0.99; 98 | this.groundFriction = 0.8; 99 | 100 | // holds composite entities 101 | this.composites = []; 102 | } 103 | 104 | VerletJS.prototype.Composite = function() { 105 | this.particles = []; 106 | this.constraints = []; 107 | 108 | this.drawParticles = null; 109 | this.drawConstraints = null; 110 | } 111 | 112 | VerletJS.prototype.Composite.prototype.pin = function(index, pos) { 113 | pos = pos || this.particles[index].pos; 114 | var pc = new PinConstraint(this.particles[index], pos); 115 | this.constraints.push(pc); 116 | return pc; 117 | } 118 | 119 | VerletJS.prototype.frame = function(step) { 120 | var i, j, c; 121 | 122 | for (c in this.composites) { 123 | for (i in this.composites[c].particles) { 124 | var particles = this.composites[c].particles; 125 | 126 | // calculate velocity 127 | var velocity = particles[i].pos.sub(particles[i].lastPos).scale(this.friction); 128 | 129 | // ground friction 130 | if (particles[i].pos.y >= this.height-1 && velocity.length2() > 0.000001) { 131 | var m = velocity.length(); 132 | velocity.x /= m; 133 | velocity.y /= m; 134 | velocity.mutableScale(m*this.groundFriction); 135 | } 136 | 137 | // save last good state 138 | particles[i].lastPos.mutableSet(particles[i].pos); 139 | 140 | // gravity 141 | particles[i].pos.mutableAdd(this.gravity); 142 | 143 | // inertia 144 | particles[i].pos.mutableAdd(velocity); 145 | } 146 | } 147 | 148 | // handle dragging of entities 149 | if (this.draggedEntity) 150 | this.draggedEntity.pos.mutableSet(this.mouse); 151 | 152 | // relax 153 | var stepCoef = 1/step; 154 | for (c in this.composites) { 155 | var constraints = this.composites[c].constraints; 156 | for (i=0;i 2 | 3 | 4 | Verlet Spiderweb 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 33 | 34 | 367 | 371 | 372 | 383 | 384 | 385 | --------------------------------------------------------------------------------