├── 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 |
--------------------------------------------------------------------------------