├── LICENSE
├── README.md
├── css
└── style.css
├── examples
├── cloth.html
├── shapes.html
├── spiderweb.html
└── tree.html
├── index.html
├── js
├── verlet-1.0.0.js
└── verlet-1.0.0.min.js
├── lib
├── constraint.js
├── dist.js
├── objects.js
├── vec2.js
└── verlet.js
├── package.json
└── site
└── js
└── common.js
/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.
--------------------------------------------------------------------------------
/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 |
28 | Build for npm
29 | -------------
30 |
31 | ``` js
32 | npm run build
33 | ```
34 |
35 |
--------------------------------------------------------------------------------
/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 | clear: both;
90 |
91 | /* disable selection of canvas */
92 | -moz-user-select: none;
93 | -webkit-user-select: none;
94 | -o-user-select:none;
95 | -ms-user-select:none;
96 | -khtml-user-select:none;
97 | user-select: none;
98 | }
99 |
100 |
101 | #footer {
102 | line-height: 1.5em;
103 | font-size: 8pt;
104 | border-top: 1px solid rgba(0,0,0,0.05);
105 | padding-top: 3px;
106 | width: 800px;
107 | margin: 0px auto 100px auto;
108 | }
109 |
110 | #footer:before {
111 | content: "";
112 | display: block;
113 | padding-top: 20px;
114 | border-top: 1px solid rgba(0,0,0,0.05);
115 | }
116 |
117 |
118 | #bsa {
119 | display: inline-block;
120 | float: right;
121 | width: 150px;
122 | padding-top: 30px;
123 | font-family: sans-serif;
124 | }
125 |
126 | body .one .bsa_it_ad { background: transparent; border: none; font-family: inherit; padding: 0 15px 0 10px; margin: 0; text-align: left; }
127 | 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; }
128 | body .one .bsa_it_ad .bsa_it_i { display: block; padding: 0; float: none; margin: 0 0 5px; }
129 | body .one .bsa_it_ad .bsa_it_i img { padding: 0; border: none; }
130 | body .one .bsa_it_ad .bsa_it_t { padding: 6px 0; }
131 | body .one .bsa_it_ad .bsa_it_d { padding: 0; font-size: 10px; color: #333; }
132 | body .one .bsa_it_p { display: none; }
133 | body #bsap_aplink, body #bsap_aplink:hover { display: block; font-size: 9px; margin: 12px 15px 0; text-align: right; }
134 |
135 |
--------------------------------------------------------------------------------
/examples/cloth.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Verlet Cloth Simulation
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
28 |
29 |
117 |
121 |
122 |
--------------------------------------------------------------------------------
/examples/shapes.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Verlet Shapes
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
28 |
29 |
69 |
73 |
74 |
--------------------------------------------------------------------------------
/examples/spiderweb.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Verlet Spiderweb
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
34 |
35 |
368 |
372 |
373 |
--------------------------------------------------------------------------------
/examples/tree.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Verlet Fractal Trees
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
28 |
29 |
159 |
163 |
164 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Verlet-js
5 |
6 |
7 |
8 |
9 |
10 |
11 |
52 |
53 |
57 |
58 |
59 |
--------------------------------------------------------------------------------
/js/verlet-1.0.0.js:
--------------------------------------------------------------------------------
1 | ;(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)
110 | diff -= 2*Math.PI;
111 |
112 | diff *= stepCoef*this.stiffness;
113 |
114 | this.a.pos = this.a.pos.rotate(this.b.pos, diff);
115 | this.c.pos = this.c.pos.rotate(this.b.pos, -diff);
116 | this.b.pos = this.b.pos.rotate(this.a.pos, diff);
117 | this.b.pos = this.b.pos.rotate(this.c.pos, -diff);
118 | }
119 |
120 | AngleConstraint.prototype.draw = function(ctx) {
121 | ctx.beginPath();
122 | ctx.moveTo(this.a.pos.x, this.a.pos.y);
123 | ctx.lineTo(this.b.pos.x, this.b.pos.y);
124 | ctx.lineTo(this.c.pos.x, this.c.pos.y);
125 | var tmp = ctx.lineWidth;
126 | ctx.lineWidth = 5;
127 | ctx.strokeStyle = "rgba(255,255,0,0.2)";
128 | ctx.stroke();
129 | ctx.lineWidth = tmp;
130 | }
131 |
132 | },{}],5:[function(require,module,exports){
133 |
134 | /*
135 | Copyright 2013 Sub Protocol and other contributors
136 | http://subprotocol.com/
137 |
138 | Permission is hereby granted, free of charge, to any person obtaining
139 | a copy of this software and associated documentation files (the
140 | "Software"), to deal in the Software without restriction, including
141 | without limitation the rights to use, copy, modify, merge, publish,
142 | distribute, sublicense, and/or sell copies of the Software, and to
143 | permit persons to whom the Software is furnished to do so, subject to
144 | the following conditions:
145 |
146 | The above copyright notice and this permission notice shall be
147 | included in all copies or substantial portions of the Software.
148 |
149 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
150 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
151 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
152 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
153 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
154 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
155 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
156 | */
157 |
158 | // A simple 2-dimensional vector implementation
159 |
160 | module.exports = Vec2
161 |
162 | function Vec2(x, y) {
163 | this.x = x || 0;
164 | this.y = y || 0;
165 | }
166 |
167 | Vec2.prototype.add = function(v) {
168 | return new Vec2(this.x + v.x, this.y + v.y);
169 | }
170 |
171 | Vec2.prototype.sub = function(v) {
172 | return new Vec2(this.x - v.x, this.y - v.y);
173 | }
174 |
175 | Vec2.prototype.mul = function(v) {
176 | return new Vec2(this.x * v.x, this.y * v.y);
177 | }
178 |
179 | Vec2.prototype.div = function(v) {
180 | return new Vec2(this.x / v.x, this.y / v.y);
181 | }
182 |
183 | Vec2.prototype.scale = function(coef) {
184 | return new Vec2(this.x*coef, this.y*coef);
185 | }
186 |
187 | Vec2.prototype.mutableSet = function(v) {
188 | this.x = v.x;
189 | this.y = v.y;
190 | return this;
191 | }
192 |
193 | Vec2.prototype.mutableAdd = function(v) {
194 | this.x += v.x;
195 | this.y += v.y;
196 | return this;
197 | }
198 |
199 | Vec2.prototype.mutableSub = function(v) {
200 | this.x -= v.x;
201 | this.y -= v.y;
202 | return this;
203 | }
204 |
205 | Vec2.prototype.mutableMul = function(v) {
206 | this.x *= v.x;
207 | this.y *= v.y;
208 | return this;
209 | }
210 |
211 | Vec2.prototype.mutableDiv = function(v) {
212 | this.x /= v.x;
213 | this.y /= v.y;
214 | return this;
215 | }
216 |
217 | Vec2.prototype.mutableScale = function(coef) {
218 | this.x *= coef;
219 | this.y *= coef;
220 | return this;
221 | }
222 |
223 | Vec2.prototype.equals = function(v) {
224 | return this.x == v.x && this.y == v.y;
225 | }
226 |
227 | Vec2.prototype.epsilonEquals = function(v, epsilon) {
228 | return Math.abs(this.x - v.x) <= epsilon && Math.abs(this.y - v.y) <= epsilon;
229 | }
230 |
231 | Vec2.prototype.length = function(v) {
232 | return Math.sqrt(this.x*this.x + this.y*this.y);
233 | }
234 |
235 | Vec2.prototype.length2 = function(v) {
236 | return this.x*this.x + this.y*this.y;
237 | }
238 |
239 | Vec2.prototype.dist = function(v) {
240 | return Math.sqrt(this.dist2(v));
241 | }
242 |
243 | Vec2.prototype.dist2 = function(v) {
244 | var x = v.x - this.x;
245 | var y = v.y - this.y;
246 | return x*x + y*y;
247 | }
248 |
249 | Vec2.prototype.normal = function() {
250 | var m = Math.sqrt(this.x*this.x + this.y*this.y);
251 | return new Vec2(this.x/m, this.y/m);
252 | }
253 |
254 | Vec2.prototype.dot = function(v) {
255 | return this.x*v.x + this.y*v.y;
256 | }
257 |
258 | Vec2.prototype.angle = function(v) {
259 | return Math.atan2(this.x*v.y-this.y*v.x,this.x*v.x+this.y*v.y);
260 | }
261 |
262 | Vec2.prototype.angle2 = function(vLeft, vRight) {
263 | return vLeft.sub(this).angle(vRight.sub(this));
264 | }
265 |
266 | Vec2.prototype.rotate = function(origin, theta) {
267 | var x = this.x - origin.x;
268 | var y = this.y - origin.y;
269 | return new Vec2(x*Math.cos(theta) - y*Math.sin(theta) + origin.x, x*Math.sin(theta) + y*Math.cos(theta) + origin.y);
270 | }
271 |
272 | Vec2.prototype.toString = function() {
273 | return "(" + this.x + ", " + this.y + ")";
274 | }
275 |
276 | function test_Vec2() {
277 | var assert = function(label, expression) {
278 | console.log("Vec2(" + label + "): " + (expression == true ? "PASS" : "FAIL"));
279 | if (expression != true)
280 | throw "assertion failed";
281 | };
282 |
283 | assert("equality", (new Vec2(5,3).equals(new Vec2(5,3))));
284 | assert("epsilon equality", (new Vec2(1,2).epsilonEquals(new Vec2(1.01,2.02), 0.03)));
285 | assert("epsilon non-equality", !(new Vec2(1,2).epsilonEquals(new Vec2(1.01,2.02), 0.01)));
286 | assert("addition", (new Vec2(1,1)).add(new Vec2(2, 3)).equals(new Vec2(3, 4)));
287 | assert("subtraction", (new Vec2(4,3)).sub(new Vec2(2, 1)).equals(new Vec2(2, 2)));
288 | assert("multiply", (new Vec2(2,4)).mul(new Vec2(2, 1)).equals(new Vec2(4, 4)));
289 | assert("divide", (new Vec2(4,2)).div(new Vec2(2, 2)).equals(new Vec2(2, 1)));
290 | assert("scale", (new Vec2(4,3)).scale(2).equals(new Vec2(8, 6)));
291 | assert("mutable set", (new Vec2(1,1)).mutableSet(new Vec2(2, 3)).equals(new Vec2(2, 3)));
292 | assert("mutable addition", (new Vec2(1,1)).mutableAdd(new Vec2(2, 3)).equals(new Vec2(3, 4)));
293 | assert("mutable subtraction", (new Vec2(4,3)).mutableSub(new Vec2(2, 1)).equals(new Vec2(2, 2)));
294 | assert("mutable multiply", (new Vec2(2,4)).mutableMul(new Vec2(2, 1)).equals(new Vec2(4, 4)));
295 | assert("mutable divide", (new Vec2(4,2)).mutableDiv(new Vec2(2, 2)).equals(new Vec2(2, 1)));
296 | assert("mutable scale", (new Vec2(4,3)).mutableScale(2).equals(new Vec2(8, 6)));
297 | assert("length", Math.abs((new Vec2(4,4)).length() - 5.65685) <= 0.00001);
298 | assert("length2", (new Vec2(2,4)).length2() == 20);
299 | assert("dist", Math.abs((new Vec2(2,4)).dist(new Vec2(3,5)) - 1.4142135) <= 0.000001);
300 | assert("dist2", (new Vec2(2,4)).dist2(new Vec2(3,5)) == 2);
301 |
302 | var normal = (new Vec2(2,4)).normal()
303 | assert("normal", Math.abs(normal.length() - 1.0) <= 0.00001 && normal.epsilonEquals(new Vec2(0.4472, 0.89443), 0.0001));
304 | assert("dot", (new Vec2(2,3)).dot(new Vec2(4,1)) == 11);
305 | assert("angle", (new Vec2(0,-1)).angle(new Vec2(1,0))*(180/Math.PI) == 90);
306 | assert("angle2", (new Vec2(1,1)).angle2(new Vec2(1,0), new Vec2(2,1))*(180/Math.PI) == 90);
307 | assert("rotate", (new Vec2(2,0)).rotate(new Vec2(1,0), Math.PI/2).equals(new Vec2(1,1)));
308 | assert("toString", (new Vec2(2,4)) == "(2, 4)");
309 | }
310 |
311 |
312 | },{}],4:[function(require,module,exports){
313 |
314 | /*
315 | Copyright 2013 Sub Protocol and other contributors
316 | http://subprotocol.com/
317 |
318 | Permission is hereby granted, free of charge, to any person obtaining
319 | a copy of this software and associated documentation files (the
320 | "Software"), to deal in the Software without restriction, including
321 | without limitation the rights to use, copy, modify, merge, publish,
322 | distribute, sublicense, and/or sell copies of the Software, and to
323 | permit persons to whom the Software is furnished to do so, subject to
324 | the following conditions:
325 |
326 | The above copyright notice and this permission notice shall be
327 | included in all copies or substantial portions of the Software.
328 |
329 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
330 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
331 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
332 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
333 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
334 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
335 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
336 | */
337 |
338 | // generic verlet entities
339 |
340 | var VerletJS = require('./verlet')
341 | var Particle = VerletJS.Particle
342 | var constraints = require('./constraint')
343 | var DistanceConstraint = constraints.DistanceConstraint
344 |
345 | VerletJS.prototype.point = function(pos) {
346 | var composite = new this.Composite();
347 | composite.particles.push(new Particle(pos));
348 | this.composites.push(composite);
349 | return composite;
350 | }
351 |
352 | VerletJS.prototype.lineSegments = function(vertices, stiffness) {
353 | var i;
354 |
355 | var composite = new this.Composite();
356 |
357 | for (i in vertices) {
358 | composite.particles.push(new Particle(vertices[i]));
359 | if (i > 0)
360 | composite.constraints.push(new DistanceConstraint(composite.particles[i], composite.particles[i-1], stiffness));
361 | }
362 |
363 | this.composites.push(composite);
364 | return composite;
365 | }
366 |
367 | VerletJS.prototype.cloth = function(origin, width, height, segments, pinMod, stiffness) {
368 |
369 | var composite = new this.Composite();
370 |
371 | var xStride = width/segments;
372 | var yStride = height/segments;
373 |
374 | var x,y;
375 | for (y=0;y 0)
382 | composite.constraints.push(new DistanceConstraint(composite.particles[y*segments+x], composite.particles[y*segments+x-1], stiffness));
383 |
384 | if (y > 0)
385 | composite.constraints.push(new DistanceConstraint(composite.particles[y*segments+x], composite.particles[(y-1)*segments+x], stiffness));
386 | }
387 | }
388 |
389 | for (x=0;x this.height-1)
491 | particle.pos.y = this.height-1;
492 |
493 | if (particle.pos.x < 0)
494 | particle.pos.x = 0;
495 |
496 | if (particle.pos.x > this.width-1)
497 | particle.pos.x = this.width-1;
498 | }
499 |
500 | var _this = this;
501 |
502 | // prevent context menu
503 | this.canvas.oncontextmenu = function(e) {
504 | e.preventDefault();
505 | };
506 |
507 | this.canvas.onmousedown = function(e) {
508 | _this.mouseDown = true;
509 | var nearest = _this.nearestEntity();
510 | if (nearest) {
511 | _this.draggedEntity = nearest;
512 | }
513 | };
514 |
515 | this.canvas.onmouseup = function(e) {
516 | _this.mouseDown = false;
517 | _this.draggedEntity = null;
518 | };
519 |
520 | this.canvas.onmousemove = function(e) {
521 | var rect = _this.canvas.getBoundingClientRect();
522 | _this.mouse.x = e.clientX - rect.left;
523 | _this.mouse.y = e.clientY - rect.top;
524 | };
525 |
526 | // simulation params
527 | this.gravity = new Vec2(0,0.2);
528 | this.friction = 0.99;
529 | this.groundFriction = 0.8;
530 |
531 | // holds composite entities
532 | this.composites = [];
533 | }
534 |
535 | VerletJS.prototype.Composite = Composite
536 |
537 | function Composite() {
538 | this.particles = [];
539 | this.constraints = [];
540 |
541 | this.drawParticles = null;
542 | this.drawConstraints = null;
543 | }
544 |
545 | Composite.prototype.pin = function(index, pos) {
546 | pos = pos || this.particles[index].pos;
547 | var pc = new PinConstraint(this.particles[index], pos);
548 | this.constraints.push(pc);
549 | return pc;
550 | }
551 |
552 | VerletJS.prototype.frame = function(step) {
553 | var i, j, c;
554 |
555 | for (c in this.composites) {
556 | for (i in this.composites[c].particles) {
557 | var particles = this.composites[c].particles;
558 |
559 | // calculate velocity
560 | var velocity = particles[i].pos.sub(particles[i].lastPos).scale(this.friction);
561 |
562 | // ground friction
563 | if (particles[i].pos.y >= this.height-1 && velocity.length2() > 0.000001) {
564 | var m = velocity.length();
565 | velocity.x /= m;
566 | velocity.y /= m;
567 | velocity.mutableScale(m*this.groundFriction);
568 | }
569 |
570 | // save last good state
571 | particles[i].lastPos.mutableSet(particles[i].pos);
572 |
573 | // gravity
574 | particles[i].pos.mutableAdd(this.gravity);
575 |
576 | // inertia
577 | particles[i].pos.mutableAdd(velocity);
578 | }
579 | }
580 |
581 | // handle dragging of entities
582 | if (this.draggedEntity)
583 | this.draggedEntity.pos.mutableSet(this.mouse);
584 |
585 | // relax
586 | var stepCoef = 1/step;
587 | for (c in this.composites) {
588 | var constraints = this.composites[c].constraints;
589 | 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= Math.PI)
91 | diff -= 2*Math.PI;
92 |
93 | diff *= stepCoef*this.stiffness;
94 |
95 | this.a.pos = this.a.pos.rotate(this.b.pos, diff);
96 | this.c.pos = this.c.pos.rotate(this.b.pos, -diff);
97 | this.b.pos = this.b.pos.rotate(this.a.pos, diff);
98 | this.b.pos = this.b.pos.rotate(this.c.pos, -diff);
99 | }
100 |
101 | AngleConstraint.prototype.draw = function(ctx) {
102 | ctx.beginPath();
103 | ctx.moveTo(this.a.pos.x, this.a.pos.y);
104 | ctx.lineTo(this.b.pos.x, this.b.pos.y);
105 | ctx.lineTo(this.c.pos.x, this.c.pos.y);
106 | var tmp = ctx.lineWidth;
107 | ctx.lineWidth = 5;
108 | ctx.strokeStyle = "rgba(255,255,0,0.2)";
109 | ctx.stroke();
110 | ctx.lineWidth = tmp;
111 | }
112 |
--------------------------------------------------------------------------------
/lib/dist.js:
--------------------------------------------------------------------------------
1 |
2 | //this exports all the verlet methods globally, so that the demos work.
3 |
4 | var VerletJS = require('./verlet')
5 | var constraint = require('./constraint')
6 | require('./objects') //patches VerletJS.prototype (bad)
7 | window.Vec2 = require('./vec2')
8 | window.VerletJS = VerletJS
9 |
10 | window.Particle = VerletJS.Particle
11 |
12 | window.DistanceConstraint = constraint.DistanceConstraint
13 | window.PinConstraint = constraint.PinConstraint
14 | window.AngleConstraint = constraint.AngleConstraint
15 |
16 |
17 |
--------------------------------------------------------------------------------
/lib/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 | var VerletJS = require('./verlet')
29 | var Particle = VerletJS.Particle
30 | var constraints = require('./constraint')
31 | var DistanceConstraint = constraints.DistanceConstraint
32 |
33 | VerletJS.prototype.point = function(pos) {
34 | var composite = new this.Composite();
35 | composite.particles.push(new Particle(pos));
36 | this.composites.push(composite);
37 | return composite;
38 | }
39 |
40 | VerletJS.prototype.lineSegments = function(vertices, stiffness) {
41 | var i;
42 |
43 | var composite = new this.Composite();
44 |
45 | for (i in vertices) {
46 | composite.particles.push(new Particle(vertices[i]));
47 | if (i > 0)
48 | composite.constraints.push(new DistanceConstraint(composite.particles[i], composite.particles[i-1], stiffness));
49 | }
50 |
51 | this.composites.push(composite);
52 | return composite;
53 | }
54 |
55 | VerletJS.prototype.cloth = function(origin, width, height, segments, pinMod, stiffness) {
56 |
57 | var composite = new this.Composite();
58 |
59 | var xStride = width/segments;
60 | var yStride = height/segments;
61 |
62 | var x,y;
63 | for (y=0;y 0)
70 | composite.constraints.push(new DistanceConstraint(composite.particles[y*segments+x], composite.particles[y*segments+x-1], stiffness));
71 |
72 | if (y > 0)
73 | composite.constraints.push(new DistanceConstraint(composite.particles[y*segments+x], composite.particles[(y-1)*segments+x], stiffness));
74 | }
75 | }
76 |
77 | for (x=0;x this.height-1)
66 | particle.pos.y = this.height-1;
67 |
68 | if (particle.pos.x < 0)
69 | particle.pos.x = 0;
70 |
71 | if (particle.pos.x > this.width-1)
72 | particle.pos.x = this.width-1;
73 | }
74 |
75 | var _this = this;
76 |
77 | // prevent context menu
78 | this.canvas.oncontextmenu = function(e) {
79 | e.preventDefault();
80 | };
81 |
82 | this.canvas.onmousedown = function(e) {
83 | _this.mouseDown = true;
84 | var nearest = _this.nearestEntity();
85 | if (nearest) {
86 | _this.draggedEntity = nearest;
87 | }
88 | };
89 |
90 | this.canvas.onmouseup = function(e) {
91 | _this.mouseDown = false;
92 | _this.draggedEntity = null;
93 | };
94 |
95 | this.canvas.onmousemove = function(e) {
96 | var rect = _this.canvas.getBoundingClientRect();
97 | _this.mouse.x = e.clientX - rect.left;
98 | _this.mouse.y = e.clientY - rect.top;
99 | };
100 |
101 | // simulation params
102 | this.gravity = new Vec2(0,0.2);
103 | this.friction = 0.99;
104 | this.groundFriction = 0.8;
105 |
106 | // holds composite entities
107 | this.composites = [];
108 | }
109 |
110 | VerletJS.prototype.Composite = Composite
111 |
112 | function Composite() {
113 | this.particles = [];
114 | this.constraints = [];
115 |
116 | this.drawParticles = null;
117 | this.drawConstraints = null;
118 | }
119 |
120 | Composite.prototype.pin = function(index, pos) {
121 | pos = pos || this.particles[index].pos;
122 | var pc = new PinConstraint(this.particles[index], pos);
123 | this.constraints.push(pc);
124 | return pc;
125 | }
126 |
127 | VerletJS.prototype.frame = function(step) {
128 | var i, j, c;
129 |
130 | for (c in this.composites) {
131 | for (i in this.composites[c].particles) {
132 | var particles = this.composites[c].particles;
133 |
134 | // calculate velocity
135 | var velocity = particles[i].pos.sub(particles[i].lastPos).scale(this.friction);
136 |
137 | // ground friction
138 | if (particles[i].pos.y >= this.height-1 && velocity.length2() > 0.000001) {
139 | var m = velocity.length();
140 | velocity.x /= m;
141 | velocity.y /= m;
142 | velocity.mutableScale(m*this.groundFriction);
143 | }
144 |
145 | // save last good state
146 | particles[i].lastPos.mutableSet(particles[i].pos);
147 |
148 | // gravity
149 | particles[i].pos.mutableAdd(this.gravity);
150 |
151 | // inertia
152 | particles[i].pos.mutableAdd(velocity);
153 | }
154 | }
155 |
156 | // handle dragging of entities
157 | if (this.draggedEntity)
158 | this.draggedEntity.pos.mutableSet(this.mouse);
159 |
160 | // relax
161 | var stepCoef = 1/step;
162 | for (c in this.composites) {
163 | var constraints = this.composites[c].constraints;
164 | for (i=0;i ./js/verlet-$V.min.js"
12 | },
13 | "repository": {
14 | "type": "git",
15 | "url": "git://github.com/subprotocol/verlet-js.git"
16 | },
17 | "keywords": [
18 | "physics",
19 | "2d",
20 | "verlet"
21 | ],
22 | "author": "subprotocol",
23 | "license": "BSD",
24 | "readmeFilename": "README.md",
25 | "gitHead": "283d23fa020d5147e95e84c5ff3541c17ea2abe5",
26 | "bugs": {
27 | "url": "https://github.com/subprotocol/verlet-js/issues"
28 | },
29 | "dependencies": {
30 | "vec2": "~1.3.0"
31 | },
32 | "devDependencies": {
33 | "browserify": "~2.18.1",
34 | "uglify-js": "~2.3.6"
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/site/js/common.js:
--------------------------------------------------------------------------------
1 |
2 | if (window.location.host == "subprotocol.com") {
3 | (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
4 | (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
5 | m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
6 | })(window,document,'script','//www.google-analytics.com/analytics.js','ga');
7 |
8 | ga('create', 'UA-83795-7', 'subprotocol.com');
9 | ga('send', 'pageview');
10 | }
11 |
--------------------------------------------------------------------------------