├── .gitignore
├── benchmark
├── bench.html
└── mersenne.js
├── demo
├── ball.html
├── Convex.html
├── Joints.html
├── Query.html
├── buoyancy.html
├── PyramidStack.html
├── PyramidTopple.html
├── ball.js
├── index.html
├── Convex.js
├── PyramidStack.js
├── PyramidTopple.js
├── Query.js
├── buoyancy.js
├── LogoSmash.js
├── Joints.js
└── demo.js
├── package.json
├── Makefile
├── lib
├── constraints
│ ├── cpSimpleMotor.js
│ ├── cpGearJoint.js
│ ├── cpConstraint.js
│ ├── cpDampedRotarySpring.js
│ ├── cpRotaryLimitJoint.js
│ ├── cpPivotJoint.js
│ ├── cpDampedSpring.js
│ ├── cpPinJoint.js
│ ├── cpRatchetJoint.js
│ ├── cpSlideJoint.js
│ ├── cpGrooveJoint.js
│ └── util.js
├── cpBB.js
├── cpSpatialIndex.js
├── cpSpaceQuery.js
├── cpVect.js
├── cpPolyShape.js
├── chipmunk.js
├── cpSpaceComponent.js
├── cpCollision.js
├── cpBody.js
├── cpSpaceStep.js
└── cpShape.js
├── README.md
└── cp.extra.js
/.gitignore:
--------------------------------------------------------------------------------
1 | *.swp
2 | .DS_Store
3 |
--------------------------------------------------------------------------------
/benchmark/bench.html:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/demo/ball.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
8 |
--------------------------------------------------------------------------------
/demo/Convex.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
8 |
--------------------------------------------------------------------------------
/demo/Joints.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
8 |
--------------------------------------------------------------------------------
/demo/Query.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
8 |
--------------------------------------------------------------------------------
/demo/buoyancy.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
8 |
--------------------------------------------------------------------------------
/demo/PyramidStack.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
8 |
--------------------------------------------------------------------------------
/demo/PyramidTopple.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
8 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "author": "Joseph Gentle (josephg.com)",
3 | "name": "chipmunk",
4 | "description": "Chipmunk 2d physics engine, in Javascript",
5 | "version": "6.1.2",
6 | "homepage": "https://github.com/josephg/chipmunk-js",
7 | "repository": {
8 | "type": "git",
9 | "url": "git://github.com/josephg/Chipmunk-js.git"
10 | },
11 | "main": "cp.js",
12 | "engines": {
13 | "node": "~0.6.5"
14 | },
15 | "keywords": [
16 | "physics",
17 | "engine",
18 | "chipmunk"
19 | ],
20 | "dependencies": {},
21 | "devDependencies": {}
22 | }
23 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | .PHONY: clean, all
2 |
3 | all: cp.min.js cp.extra.min.js bench.js demos.js
4 |
5 | js = chipmunk.js \
6 | cpVect.js \
7 | cpBB.js \
8 | cpShape.js \
9 | cpPolyShape.js \
10 | cpBody.js \
11 | cpSpatialIndex.js \
12 | cpBBTree.js \
13 | cpArbiter.js \
14 | cpCollision.js \
15 | cpSpace.js \
16 | cpSpaceComponent.js \
17 | cpSpaceQuery.js \
18 | cpSpaceStep.js
19 |
20 | constraints = \
21 | util.js \
22 | cpConstraint.js \
23 | cpPinJoint.js \
24 | cpSlideJoint.js \
25 | cpPivotJoint.js \
26 | cpGrooveJoint.js \
27 | cpDampedSpring.js \
28 | cpDampedRotarySpring.js \
29 | cpRotaryLimitJoint.js \
30 | cpRatchetJoint.js \
31 | cpGearJoint.js \
32 | cpSimpleMotor.js
33 |
34 | demos = demo.js \
35 | ball.js \
36 | PyramidStack.js \
37 | PyramidTopple.js
38 |
39 | jsfiles = $(addprefix lib/, $(js))
40 | constraintfiles = $(addprefix lib/constraints/, $(constraints))
41 | demofiles = $(addprefix demo/, $(demos))
42 |
43 | cp.js: $(jsfiles) $(constraintfiles)
44 | echo '(function(){' > $@
45 | cat $+ >> $@
46 | echo "})();" >> $@
47 |
48 | %.min.js: %.js
49 | uglifyjs $+ -m -c > $@
50 |
51 | bench.js: $(jsfiles) $(constraintfiles) benchmark/mersenne.js benchmark/bench.js
52 | cat $+ > $@
53 |
54 | demos.js: $(demofiles)
55 | cat $+ > $@
56 |
57 | clean:
58 | rm -f cp.js cp.min.js cp.extra.min.js bench.js demos.js
59 |
--------------------------------------------------------------------------------
/demo/ball.js:
--------------------------------------------------------------------------------
1 | var Balls = function() {
2 | Demo.call(this);
3 |
4 | var space = this.space;
5 | space.iterations = 60;
6 | space.gravity = v(0, -500);
7 | space.sleepTimeThreshold = 0.5;
8 | space.collisionSlop = 0.5;
9 | space.sleepTimeThreshold = 0.5;
10 |
11 | this.addFloor();
12 | this.addWalls();
13 |
14 | var width = 50;
15 | var height = 60;
16 | var mass = width * height * 1/1000;
17 | var rock = space.addBody(new cp.Body(mass, cp.momentForBox(mass, width, height)));
18 | rock.setPos(v(500, 100));
19 | rock.setAngle(1);
20 | shape = space.addShape(new cp.BoxShape(rock, width, height));
21 | shape.setFriction(0.3);
22 | shape.setElasticity(0.3);
23 |
24 | for (var i = 1; i <= 10; i++) {
25 | var radius = 20;
26 | mass = 3;
27 | var body = space.addBody(new cp.Body(mass, cp.momentForCircle(mass, 0, radius, v(0, 0))));
28 | body.setPos(v(200 + i, (2 * radius + 5) * i));
29 | var circle = space.addShape(new cp.CircleShape(body, radius, v(0, 0)));
30 | circle.setElasticity(0.8);
31 | circle.setFriction(1);
32 | }
33 | /*
34 | * atom.canvas.onmousedown = function(e) {
35 | radius = 10;
36 | mass = 3;
37 | body = space.addBody(new cp.Body(mass, cp.momentForCircle(mass, 0, radius, v(0, 0))));
38 | body.setPos(v(e.clientX, e.clientY));
39 | circle = space.addShape(new cp.CircleShape(body, radius, v(0, 0)));
40 | circle.setElasticity(0.5);
41 | return circle.setFriction(1);
42 | };
43 | */
44 |
45 | this.ctx.strokeStyle = "black";
46 |
47 | var ramp = space.addShape(new cp.SegmentShape(space.staticBody, v(100, 100), v(300, 200), 10));
48 | ramp.setElasticity(1);
49 | ramp.setFriction(1);
50 | ramp.setLayers(NOT_GRABABLE_MASK);
51 | };
52 |
53 | Balls.prototype = Object.create(Demo.prototype);
54 |
55 | addDemo('Balls', Balls);
56 |
57 |
--------------------------------------------------------------------------------
/demo/index.html:
--------------------------------------------------------------------------------
1 |
2 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
87 |
--------------------------------------------------------------------------------
/demo/Convex.js:
--------------------------------------------------------------------------------
1 |
2 | var DENSITY = 1/10000;
3 |
4 |
5 | var Convex = function() {
6 | Demo.call(this);
7 |
8 | this.message = "Right click and drag to change the block's shape.";
9 |
10 | var space = this.space;
11 | space.iterations = 30;
12 | space.gravity = v(0, -500);
13 | space.sleepTimeThreshold = 0.5;
14 | space.collisionSlop = 0.5;
15 |
16 | var body, staticBody = space.staticBody;
17 |
18 | // Create segments around the edge of the screen.
19 | this.addFloor();
20 |
21 | var width = 50;
22 | var height = 70;
23 | var mass = width*height*DENSITY;
24 | var moment = cp.momentForBox(mass, width, height);
25 |
26 | body = space.addBody(new cp.Body(mass, moment));
27 | body.setPos(v(320, height / 2));
28 |
29 | this.shape = space.addShape(new cp.BoxShape(body, width, height));
30 | this.shape.setFriction(0.6);
31 | };
32 |
33 | Convex.prototype = Object.create(Demo.prototype);
34 |
35 | Convex.prototype.update = function(dt) {
36 | var tolerance = 2;
37 |
38 | info = this.shape.nearestPointQuery(this.mouse);
39 | if(this.rightClick && info.d > tolerance){
40 | var shape = this.shape;
41 | var body = shape.getBody();
42 | var count = shape.getNumVerts();
43 |
44 | // Allocate the space for the new vertexes on the stack.
45 | var verts = new Array((count + 1)*2);
46 |
47 | for(var i=0; i this.max) {
44 | pdist = this.max - dist;
45 | } else if(dist < this.min) {
46 | pdist = this.min - dist;
47 | }
48 |
49 | // calculate moment of inertia coefficient.
50 | this.iSum = 1/(1/a.i + 1/b.i);
51 |
52 | // calculate bias velocity
53 | var maxBias = this.maxBias;
54 | this.bias = clamp(-bias_coef(this.errorBias, dt)*pdist/dt, -maxBias, maxBias);
55 |
56 | // compute max impulse
57 | this.jMax = this.maxForce * dt;
58 |
59 | // If the bias is 0, the joint is not at a limit. Reset the impulse.
60 | if(!this.bias) this.jAcc = 0;
61 | };
62 |
63 | RotaryLimitJoint.prototype.applyCachedImpulse = function(dt_coef)
64 | {
65 | var a = this.a;
66 | var b = this.b;
67 |
68 | var j = this.jAcc*dt_coef;
69 | a.w -= j*a.i_inv;
70 | b.w += j*b.i_inv;
71 | };
72 |
73 | RotaryLimitJoint.prototype.applyImpulse = function()
74 | {
75 | if(!this.bias) return; // early exit
76 |
77 | var a = this.a;
78 | var b = this.b;
79 |
80 | // compute relative rotational velocity
81 | var wr = b.w - a.w;
82 |
83 | // compute normal impulse
84 | var j = -(this.bias + wr)*this.iSum;
85 | var jOld = this.jAcc;
86 | if(this.bias < 0){
87 | this.jAcc = clamp(jOld + j, 0, this.jMax);
88 | } else {
89 | this.jAcc = clamp(jOld + j, -this.jMax, 0);
90 | }
91 | j = this.jAcc - jOld;
92 |
93 | // apply impulse
94 | a.w -= j*a.i_inv;
95 | b.w += j*b.i_inv;
96 | };
97 |
98 | RotaryLimitJoint.prototype.getImpulse = function()
99 | {
100 | return Math.abs(joint.jAcc);
101 | };
102 |
103 |
--------------------------------------------------------------------------------
/lib/constraints/cpPivotJoint.js:
--------------------------------------------------------------------------------
1 | /* Copyright (c) 2007 Scott Lembcke
2 | *
3 | * Permission is hereby granted, free of charge, to any person obtaining a copy
4 | * of this software and associated documentation files (the "Software"), to deal
5 | * in the Software without restriction, including without limitation the rights
6 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | * copies of the Software, and to permit persons to whom the Software is
8 | * furnished to do so, subject to the following conditions:
9 | *
10 | * The above copyright notice and this permission notice shall be included in
11 | * all copies or substantial portions of the Software.
12 | *
13 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | * SOFTWARE.
20 | */
21 |
22 | // Pivot joints can also be created with (a, b, pivot);
23 | var PivotJoint = cp.PivotJoint = function(a, b, anchr1, anchr2)
24 | {
25 | Constraint.call(this, a, b);
26 |
27 | if(typeof anchr2 === 'undefined') {
28 | var pivot = anchr1;
29 |
30 | anchr1 = (a ? a.world2Local(pivot) : pivot);
31 | anchr2 = (b ? b.world2Local(pivot) : pivot);
32 | }
33 |
34 | this.anchr1 = anchr1;
35 | this.anchr2 = anchr2;
36 |
37 | this.r1 = this.r2 = vzero;
38 |
39 | this.k1 = new Vect(0,0); this.k2 = new Vect(0,0);
40 |
41 | this.jAcc = vzero;
42 |
43 | this.jMaxLen = 0;
44 | this.bias = vzero;
45 | };
46 |
47 | PivotJoint.prototype = Object.create(Constraint.prototype);
48 |
49 | PivotJoint.prototype.preStep = function(dt)
50 | {
51 | var a = this.a;
52 | var b = this.b;
53 |
54 | this.r1 = vrotate(this.anchr1, a.rot);
55 | this.r2 = vrotate(this.anchr2, b.rot);
56 |
57 | // Calculate mass tensor. Result is stored into this.k1 & this.k2.
58 | k_tensor(a, b, this.r1, this.r2, this.k1, this.k2);
59 |
60 | // compute max impulse
61 | this.jMaxLen = this.maxForce * dt;
62 |
63 | // calculate bias velocity
64 | var delta = vsub(vadd(b.p, this.r2), vadd(a.p, this.r1));
65 | this.bias = vclamp(vmult(delta, -bias_coef(this.errorBias, dt)/dt), this.maxBias);
66 | };
67 |
68 | PivotJoint.prototype.applyCachedImpulse = function(dt_coef)
69 | {
70 | apply_impulses(this.a, this.b, this.r1, this.r2, this.jAcc.x * dt_coef, this.jAcc.y * dt_coef);
71 | };
72 |
73 | PivotJoint.prototype.applyImpulse = function()
74 | {
75 | var a = this.a;
76 | var b = this.b;
77 |
78 | var r1 = this.r1;
79 | var r2 = this.r2;
80 |
81 | // compute relative velocity
82 | var vr = relative_velocity(a, b, r1, r2);
83 |
84 | // compute normal impulse
85 | var j = mult_k(vsub(this.bias, vr), this.k1, this.k2);
86 | var jOld = this.jAcc;
87 | this.jAcc = vclamp(vadd(this.jAcc, j), this.jMaxLen);
88 |
89 | // apply impulse
90 | apply_impulses(a, b, this.r1, this.r2, this.jAcc.x - jOld.x, this.jAcc.y - jOld.y);
91 | };
92 |
93 | PivotJoint.prototype.getImpulse = function()
94 | {
95 | return vlength(this.jAcc);
96 | };
97 |
98 |
--------------------------------------------------------------------------------
/lib/constraints/cpDampedSpring.js:
--------------------------------------------------------------------------------
1 | /* Copyright (c) 2007 Scott Lembcke
2 | *
3 | * Permission is hereby granted, free of charge, to any person obtaining a copy
4 | * of this software and associated documentation files (the "Software"), to deal
5 | * in the Software without restriction, including without limitation the rights
6 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | * copies of the Software, and to permit persons to whom the Software is
8 | * furnished to do so, subject to the following conditions:
9 | *
10 | * The above copyright notice and this permission notice shall be included in
11 | * all copies or substantial portions of the Software.
12 | *
13 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | * SOFTWARE.
20 | */
21 |
22 | var defaultSpringForce = function(spring, dist){
23 | return (spring.restLength - dist)*spring.stiffness;
24 | };
25 |
26 | var DampedSpring = cp.DampedSpring = function(a, b, anchr1, anchr2, restLength, stiffness, damping)
27 | {
28 | Constraint.call(this, a, b);
29 |
30 | this.anchr1 = anchr1;
31 | this.anchr2 = anchr2;
32 |
33 | this.restLength = restLength;
34 | this.stiffness = stiffness;
35 | this.damping = damping;
36 | this.springForceFunc = defaultSpringForce;
37 |
38 | this.target_vrn = this.v_coef = 0;
39 |
40 | this.r1 = this.r2 = null;
41 | this.nMass = 0;
42 | this.n = null;
43 | };
44 |
45 | DampedSpring.prototype = Object.create(Constraint.prototype);
46 |
47 | DampedSpring.prototype.preStep = function(dt)
48 | {
49 | var a = this.a;
50 | var b = this.b;
51 |
52 | this.r1 = vrotate(this.anchr1, a.rot);
53 | this.r2 = vrotate(this.anchr2, b.rot);
54 |
55 | var delta = vsub(vadd(b.p, this.r2), vadd(a.p, this.r1));
56 | var dist = vlength(delta);
57 | this.n = vmult(delta, 1/(dist ? dist : Infinity));
58 |
59 | var k = k_scalar(a, b, this.r1, this.r2, this.n);
60 | assertSoft(k !== 0, "Unsolvable this.");
61 | this.nMass = 1/k;
62 |
63 | this.target_vrn = 0;
64 | this.v_coef = 1 - Math.exp(-this.damping*dt*k);
65 |
66 | // apply this force
67 | var f_spring = this.springForceFunc(this, dist);
68 | apply_impulses(a, b, this.r1, this.r2, this.n.x * f_spring * dt, this.n.y * f_spring * dt);
69 | };
70 |
71 | DampedSpring.prototype.applyCachedImpulse = function(dt_coef){};
72 |
73 | DampedSpring.prototype.applyImpulse = function()
74 | {
75 | var a = this.a;
76 | var b = this.b;
77 |
78 | var n = this.n;
79 | var r1 = this.r1;
80 | var r2 = this.r2;
81 |
82 | // compute relative velocity
83 | var vrn = normal_relative_velocity(a, b, r1, r2, n);
84 |
85 | // compute velocity loss from drag
86 | var v_damp = (this.target_vrn - vrn)*this.v_coef;
87 | this.target_vrn = vrn + v_damp;
88 |
89 | v_damp *= this.nMass;
90 | apply_impulses(a, b, this.r1, this.r2, this.n.x * v_damp, this.n.y * v_damp);
91 | };
92 |
93 | DampedSpring.prototype.getImpulse = function()
94 | {
95 | return 0;
96 | };
97 |
98 |
--------------------------------------------------------------------------------
/lib/constraints/cpPinJoint.js:
--------------------------------------------------------------------------------
1 | /* Copyright (c) 2007 Scott Lembcke
2 | *
3 | * Permission is hereby granted, free of charge, to any person obtaining a copy
4 | * of this software and associated documentation files (the "Software"), to deal
5 | * in the Software without restriction, including without limitation the rights
6 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | * copies of the Software, and to permit persons to whom the Software is
8 | * furnished to do so, subject to the following conditions:
9 | *
10 | * The above copyright notice and this permission notice shall be included in
11 | * all copies or substantial portions of the Software.
12 | *
13 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | * SOFTWARE.
20 | */
21 |
22 | var PinJoint = cp.PinJoint = function(a, b, anchr1, anchr2)
23 | {
24 | Constraint.call(this, a, b);
25 |
26 | this.anchr1 = anchr1;
27 | this.anchr2 = anchr2;
28 |
29 | // STATIC_BODY_CHECK
30 | var p1 = (a ? vadd(a.p, vrotate(anchr1, a.rot)) : anchr1);
31 | var p2 = (b ? vadd(b.p, vrotate(anchr2, b.rot)) : anchr2);
32 | this.dist = vlength(vsub(p2, p1));
33 |
34 | assertSoft(this.dist > 0, "You created a 0 length pin joint. A pivot joint will be much more stable.");
35 |
36 | this.r1 = this.r2 = null;
37 | this.n = null;
38 | this.nMass = 0;
39 |
40 | this.jnAcc = this.jnMax = 0;
41 | this.bias = 0;
42 | };
43 |
44 | PinJoint.prototype = Object.create(Constraint.prototype);
45 |
46 | PinJoint.prototype.preStep = function(dt)
47 | {
48 | var a = this.a;
49 | var b = this.b;
50 |
51 | this.r1 = vrotate(this.anchr1, a.rot);
52 | this.r2 = vrotate(this.anchr2, b.rot);
53 |
54 | var delta = vsub(vadd(b.p, this.r2), vadd(a.p, this.r1));
55 | var dist = vlength(delta);
56 | this.n = vmult(delta, 1/(dist ? dist : Infinity));
57 |
58 | // calculate mass normal
59 | this.nMass = 1/k_scalar(a, b, this.r1, this.r2, this.n);
60 |
61 | // calculate bias velocity
62 | var maxBias = this.maxBias;
63 | this.bias = clamp(-bias_coef(this.errorBias, dt)*(dist - this.dist)/dt, -maxBias, maxBias);
64 |
65 | // compute max impulse
66 | this.jnMax = this.maxForce * dt;
67 | };
68 |
69 | PinJoint.prototype.applyCachedImpulse = function(dt_coef)
70 | {
71 | var j = vmult(this.n, this.jnAcc*dt_coef);
72 | apply_impulses(this.a, this.b, this.r1, this.r2, j.x, j.y);
73 | };
74 |
75 | PinJoint.prototype.applyImpulse = function()
76 | {
77 | var a = this.a;
78 | var b = this.b;
79 | var n = this.n;
80 |
81 | // compute relative velocity
82 | var vrn = normal_relative_velocity(a, b, this.r1, this.r2, n);
83 |
84 | // compute normal impulse
85 | var jn = (this.bias - vrn)*this.nMass;
86 | var jnOld = this.jnAcc;
87 | this.jnAcc = clamp(jnOld + jn, -this.jnMax, this.jnMax);
88 | jn = this.jnAcc - jnOld;
89 |
90 | // apply impulse
91 | apply_impulses(a, b, this.r1, this.r2, n.x*jn, n.y*jn);
92 | };
93 |
94 | PinJoint.prototype.getImpulse = function()
95 | {
96 | return Math.abs(this.jnAcc);
97 | };
98 |
99 |
--------------------------------------------------------------------------------
/lib/constraints/cpRatchetJoint.js:
--------------------------------------------------------------------------------
1 | /* Copyright (c) 2007 Scott Lembcke
2 | *
3 | * Permission is hereby granted, free of charge, to any person obtaining a copy
4 | * of this software and associated documentation files (the "Software"), to deal
5 | * in the Software without restriction, including without limitation the rights
6 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | * copies of the Software, and to permit persons to whom the Software is
8 | * furnished to do so, subject to the following conditions:
9 | *
10 | * The above copyright notice and this permission notice shall be included in
11 | * all copies or substantial portions of the Software.
12 | *
13 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | * SOFTWARE.
20 | */
21 |
22 | var RatchetJoint = cp.RatchetJoint = function(a, b, phase, ratchet)
23 | {
24 | Constraint.call(this, a, b);
25 |
26 | this.angle = 0;
27 | this.phase = phase;
28 | this.ratchet = ratchet;
29 |
30 | // STATIC_BODY_CHECK
31 | this.angle = (b ? b.a : 0) - (a ? a.a : 0);
32 |
33 | this.iSum = this.bias = this.jAcc = this.jMax = 0;
34 | };
35 |
36 | RatchetJoint.prototype = Object.create(Constraint.prototype);
37 |
38 | RatchetJoint.prototype.preStep = function(dt)
39 | {
40 | var a = this.a;
41 | var b = this.b;
42 |
43 | var angle = this.angle;
44 | var phase = this.phase;
45 | var ratchet = this.ratchet;
46 |
47 | var delta = b.a - a.a;
48 | var diff = angle - delta;
49 | var pdist = 0;
50 |
51 | if(diff*ratchet > 0){
52 | pdist = diff;
53 | } else {
54 | this.angle = Math.floor((delta - phase)/ratchet)*ratchet + phase;
55 | }
56 |
57 | // calculate moment of inertia coefficient.
58 | this.iSum = 1/(a.i_inv + b.i_inv);
59 |
60 | // calculate bias velocity
61 | var maxBias = this.maxBias;
62 | this.bias = clamp(-bias_coef(this.errorBias, dt)*pdist/dt, -maxBias, maxBias);
63 |
64 | // compute max impulse
65 | this.jMax = this.maxForce * dt;
66 |
67 | // If the bias is 0, the joint is not at a limit. Reset the impulse.
68 | if(!this.bias) this.jAcc = 0;
69 | };
70 |
71 | RatchetJoint.prototype.applyCachedImpulse = function(dt_coef)
72 | {
73 | var a = this.a;
74 | var b = this.b;
75 |
76 | var j = this.jAcc*dt_coef;
77 | a.w -= j*a.i_inv;
78 | b.w += j*b.i_inv;
79 | };
80 |
81 | RatchetJoint.prototype.applyImpulse = function()
82 | {
83 | if(!this.bias) return; // early exit
84 |
85 | var a = this.a;
86 | var b = this.b;
87 |
88 | // compute relative rotational velocity
89 | var wr = b.w - a.w;
90 | var ratchet = this.ratchet;
91 |
92 | // compute normal impulse
93 | var j = -(this.bias + wr)*this.iSum;
94 | var jOld = this.jAcc;
95 | this.jAcc = clamp((jOld + j)*ratchet, 0, this.jMax*Math.abs(ratchet))/ratchet;
96 | j = this.jAcc - jOld;
97 |
98 | // apply impulse
99 | a.w -= j*a.i_inv;
100 | b.w += j*b.i_inv;
101 | };
102 |
103 | RatchetJoint.prototype.getImpulse = function(joint)
104 | {
105 | return Math.abs(joint.jAcc);
106 | };
107 |
108 |
--------------------------------------------------------------------------------
/lib/constraints/cpSlideJoint.js:
--------------------------------------------------------------------------------
1 | /* Copyright (c) 2007 Scott Lembcke
2 | *
3 | * Permission is hereby granted, free of charge, to any person obtaining a copy
4 | * of this software and associated documentation files (the "Software"), to deal
5 | * in the Software without restriction, including without limitation the rights
6 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | * copies of the Software, and to permit persons to whom the Software is
8 | * furnished to do so, subject to the following conditions:
9 | *
10 | * The above copyright notice and this permission notice shall be included in
11 | * all copies or substantial portions of the Software.
12 | *
13 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | * SOFTWARE.
20 | */
21 |
22 | var SlideJoint = cp.SlideJoint = function(a, b, anchr1, anchr2, min, max)
23 | {
24 | Constraint.call(this, a, b);
25 |
26 | this.anchr1 = anchr1;
27 | this.anchr2 = anchr2;
28 | this.min = min;
29 | this.max = max;
30 |
31 | this.r1 = this.r2 = this.n = null;
32 | this.nMass = 0;
33 |
34 | this.jnAcc = this.jnMax = 0;
35 | this.bias = 0;
36 | };
37 |
38 | SlideJoint.prototype = Object.create(Constraint.prototype);
39 |
40 | SlideJoint.prototype.preStep = function(dt)
41 | {
42 | var a = this.a;
43 | var b = this.b;
44 |
45 | this.r1 = vrotate(this.anchr1, a.rot);
46 | this.r2 = vrotate(this.anchr2, b.rot);
47 |
48 | var delta = vsub(vadd(b.p, this.r2), vadd(a.p, this.r1));
49 | var dist = vlength(delta);
50 | var pdist = 0;
51 | if(dist > this.max) {
52 | pdist = dist - this.max;
53 | this.n = vnormalize_safe(delta);
54 | } else if(dist < this.min) {
55 | pdist = this.min - dist;
56 | this.n = vneg(vnormalize_safe(delta));
57 | } else {
58 | this.n = vzero;
59 | this.jnAcc = 0;
60 | }
61 |
62 | // calculate mass normal
63 | this.nMass = 1/k_scalar(a, b, this.r1, this.r2, this.n);
64 |
65 | // calculate bias velocity
66 | var maxBias = this.maxBias;
67 | this.bias = clamp(-bias_coef(this.errorBias, dt)*pdist/dt, -maxBias, maxBias);
68 |
69 | // compute max impulse
70 | this.jnMax = this.maxForce * dt;
71 | };
72 |
73 | SlideJoint.prototype.applyCachedImpulse = function(dt_coef)
74 | {
75 | var jn = this.jnAcc * dt_coef;
76 | apply_impulses(this.a, this.b, this.r1, this.r2, this.n.x * jn, this.n.y * jn);
77 | };
78 |
79 | SlideJoint.prototype.applyImpulse = function()
80 | {
81 | if(this.n.x === 0 && this.n.y === 0) return; // early exit
82 |
83 | var a = this.a;
84 | var b = this.b;
85 |
86 | var n = this.n;
87 | var r1 = this.r1;
88 | var r2 = this.r2;
89 |
90 | // compute relative velocity
91 | var vr = relative_velocity(a, b, r1, r2);
92 | var vrn = vdot(vr, n);
93 |
94 | // compute normal impulse
95 | var jn = (this.bias - vrn)*this.nMass;
96 | var jnOld = this.jnAcc;
97 | this.jnAcc = clamp(jnOld + jn, -this.jnMax, 0);
98 | jn = this.jnAcc - jnOld;
99 |
100 | // apply impulse
101 | apply_impulses(a, b, this.r1, this.r2, n.x * jn, n.y * jn);
102 | };
103 |
104 | SlideJoint.prototype.getImpulse = function()
105 | {
106 | return Math.abs(this.jnAcc);
107 | };
108 |
109 |
--------------------------------------------------------------------------------
/demo/Query.js:
--------------------------------------------------------------------------------
1 | // This code demonstrates making point and segment queries in a space.
2 | //
3 | // Take a look at update(), below for the calls to space.*Query.
4 |
5 | var Query = function() {
6 | Demo.call(this);
7 |
8 | var space = this.space;
9 |
10 | space.iterations = 5;
11 |
12 | { // add a fat segment
13 | var mass = 1;
14 | var length = 100;
15 | var a = v(-length/2, 0), b = v(length/2, 0);
16 |
17 | var body = space.addBody(new cp.Body(mass, cp.momentForSegment(mass, a, b)));
18 | body.setPos(v(320, 340));
19 |
20 | space.addShape(new cp.SegmentShape(body, a, b, 20));
21 | }
22 |
23 | { // add a static segment
24 | space.addShape(new cp.SegmentShape(space.staticBody, v(320, 540), v(620, 240), 0));
25 | }
26 |
27 | { // add a pentagon
28 | var mass = 1;
29 | var NUM_VERTS = 5;
30 |
31 | var verts = new Array(NUM_VERTS * 2);
32 | for(var i=0; i= other.r && bb.b <= other.b && bb.t >= other.t);
63 | };
64 |
65 | /// Returns true if @c bb contains @c v.
66 | var bbContainsVect = function(bb, v)
67 | {
68 | return (bb.l <= v.x && bb.r >= v.x && bb.b <= v.y && bb.t >= v.y);
69 | };
70 | var bbContainsVect2 = function(l, b, r, t, v)
71 | {
72 | return (l <= v.x && r >= v.x && b <= v.y && t >= v.y);
73 | };
74 |
75 | /// Returns a bounding box that holds both bounding boxes.
76 | var bbMerge = function(a, b){
77 | return new BB(
78 | min(a.l, b.l),
79 | min(a.b, b.b),
80 | max(a.r, b.r),
81 | max(a.t, b.t)
82 | );
83 | };
84 |
85 | /// Returns a bounding box that holds both @c bb and @c v.
86 | var bbExpand = function(bb, v){
87 | return new BB(
88 | min(bb.l, v.x),
89 | min(bb.b, v.y),
90 | max(bb.r, v.x),
91 | max(bb.t, v.y)
92 | );
93 | };
94 |
95 | /// Returns the area of the bounding box.
96 | var bbArea = function(bb)
97 | {
98 | return (bb.r - bb.l)*(bb.t - bb.b);
99 | };
100 |
101 | /// Merges @c a and @c b and returns the area of the merged bounding box.
102 | var bbMergedArea = function(a, b)
103 | {
104 | return (max(a.r, b.r) - min(a.l, b.l))*(max(a.t, b.t) - min(a.b, b.b));
105 | };
106 |
107 | var bbMergedArea2 = function(bb, l, b, r, t)
108 | {
109 | return (max(bb.r, r) - min(bb.l, l))*(max(bb.t, t) - min(bb.b, b));
110 | };
111 |
112 | /// Return true if the bounding box intersects the line segment with ends @c a and @c b.
113 | var bbIntersectsSegment = function(bb, a, b)
114 | {
115 | return (bbSegmentQuery(bb, a, b) != Infinity);
116 | };
117 |
118 | /// Clamp a vector to a bounding box.
119 | var bbClampVect = function(bb, v)
120 | {
121 | var x = min(max(bb.l, v.x), bb.r);
122 | var y = min(max(bb.b, v.y), bb.t);
123 | return new Vect(x, y);
124 | };
125 |
126 | // TODO edge case issue
127 | /// Wrap a vector to a bounding box.
128 | var bbWrapVect = function(bb, v)
129 | {
130 | var ix = Math.abs(bb.r - bb.l);
131 | var modx = (v.x - bb.l) % ix;
132 | var x = (modx > 0) ? modx : modx + ix;
133 |
134 | var iy = Math.abs(bb.t - bb.b);
135 | var mody = (v.y - bb.b) % iy;
136 | var y = (mody > 0) ? mody : mody + iy;
137 |
138 | return new Vect(x + bb.l, y + bb.b);
139 | };
140 |
--------------------------------------------------------------------------------
/lib/constraints/cpGrooveJoint.js:
--------------------------------------------------------------------------------
1 | /* Copyright (c) 2007 Scott Lembcke
2 | *
3 | * Permission is hereby granted, free of charge, to any person obtaining a copy
4 | * of this software and associated documentation files (the "Software"), to deal
5 | * in the Software without restriction, including without limitation the rights
6 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | * copies of the Software, and to permit persons to whom the Software is
8 | * furnished to do so, subject to the following conditions:
9 | *
10 | * The above copyright notice and this permission notice shall be included in
11 | * all copies or substantial portions of the Software.
12 | *
13 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | * SOFTWARE.
20 | */
21 |
22 | var GrooveJoint = cp.GrooveJoint = function(a, b, groove_a, groove_b, anchr2)
23 | {
24 | Constraint.call(this, a, b);
25 |
26 | this.grv_a = groove_a;
27 | this.grv_b = groove_b;
28 | this.grv_n = vperp(vnormalize(vsub(groove_b, groove_a)));
29 | this.anchr2 = anchr2;
30 |
31 | this.grv_tn = null;
32 | this.clamp = 0;
33 | this.r1 = this.r2 = null;
34 |
35 | this.k1 = new Vect(0,0);
36 | this.k2 = new Vect(0,0);
37 |
38 | this.jAcc = vzero;
39 | this.jMaxLen = 0;
40 | this.bias = null;
41 | };
42 |
43 | GrooveJoint.prototype = Object.create(Constraint.prototype);
44 |
45 | GrooveJoint.prototype.preStep = function(dt)
46 | {
47 | var a = this.a;
48 | var b = this.b;
49 |
50 | // calculate endpoints in worldspace
51 | var ta = a.local2World(this.grv_a);
52 | var tb = a.local2World(this.grv_b);
53 |
54 | // calculate axis
55 | var n = vrotate(this.grv_n, a.rot);
56 | var d = vdot(ta, n);
57 |
58 | this.grv_tn = n;
59 | this.r2 = vrotate(this.anchr2, b.rot);
60 |
61 | // calculate tangential distance along the axis of r2
62 | var td = vcross(vadd(b.p, this.r2), n);
63 | // calculate clamping factor and r2
64 | if(td <= vcross(ta, n)){
65 | this.clamp = 1;
66 | this.r1 = vsub(ta, a.p);
67 | } else if(td >= vcross(tb, n)){
68 | this.clamp = -1;
69 | this.r1 = vsub(tb, a.p);
70 | } else {
71 | this.clamp = 0;
72 | this.r1 = vsub(vadd(vmult(vperp(n), -td), vmult(n, d)), a.p);
73 | }
74 |
75 | // Calculate mass tensor
76 | k_tensor(a, b, this.r1, this.r2, this.k1, this.k2);
77 |
78 | // compute max impulse
79 | this.jMaxLen = this.maxForce * dt;
80 |
81 | // calculate bias velocity
82 | var delta = vsub(vadd(b.p, this.r2), vadd(a.p, this.r1));
83 | this.bias = vclamp(vmult(delta, -bias_coef(this.errorBias, dt)/dt), this.maxBias);
84 | };
85 |
86 | GrooveJoint.prototype.applyCachedImpulse = function(dt_coef)
87 | {
88 | apply_impulses(this.a, this.b, this.r1, this.r2, this.jAcc.x * dt_coef, this.jAcc.y * dt_coef);
89 | };
90 |
91 | GrooveJoint.prototype.grooveConstrain = function(j){
92 | var n = this.grv_tn;
93 | var jClamp = (this.clamp*vcross(j, n) > 0) ? j : vproject(j, n);
94 | return vclamp(jClamp, this.jMaxLen);
95 | };
96 |
97 | GrooveJoint.prototype.applyImpulse = function()
98 | {
99 | var a = this.a;
100 | var b = this.b;
101 |
102 | var r1 = this.r1;
103 | var r2 = this.r2;
104 |
105 | // compute impulse
106 | var vr = relative_velocity(a, b, r1, r2);
107 |
108 | var j = mult_k(vsub(this.bias, vr), this.k1, this.k2);
109 | var jOld = this.jAcc;
110 | this.jAcc = this.grooveConstrain(vadd(jOld, j));
111 |
112 | // apply impulse
113 | apply_impulses(a, b, this.r1, this.r2, this.jAcc.x - jOld.x, this.jAcc.y - jOld.y);
114 | };
115 |
116 | GrooveJoint.prototype.getImpulse = function()
117 | {
118 | return vlength(this.jAcc);
119 | };
120 |
121 | GrooveJoint.prototype.setGrooveA = function(value)
122 | {
123 | this.grv_a = value;
124 | this.grv_n = vperp(vnormalize(vsub(this.grv_b, value)));
125 |
126 | this.activateBodies();
127 | };
128 |
129 | GrooveJoint.prototype.setGrooveB = function(value)
130 | {
131 | this.grv_b = value;
132 | this.grv_n = vperp(vnormalize(vsub(value, this.grv_a)));
133 |
134 | this.activateBodies();
135 | };
136 |
137 |
--------------------------------------------------------------------------------
/lib/cpSpatialIndex.js:
--------------------------------------------------------------------------------
1 | /* Copyright (c) 2010 Scott Lembcke
2 | *
3 | * Permission is hereby granted, free of charge, to any person obtaining a copy
4 | * of this software and associated documentation files (the "Software"), to deal
5 | * in the Software without restriction, including without limitation the rights
6 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | * copies of the Software, and to permit persons to whom the Software is
8 | * furnished to do so, subject to the following conditions:
9 | *
10 | * The above copyright notice and this permission notice shall be included in
11 | * all copies or substantial portions of the Software.
12 | *
13 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | * SOFTWARE.
20 | */
21 |
22 | /**
23 | @defgroup cpSpatialIndex cpSpatialIndex
24 |
25 | Spatial indexes are data structures that are used to accelerate collision detection
26 | and spatial queries. Chipmunk provides a number of spatial index algorithms to pick from
27 | and they are programmed in a generic way so that you can use them for holding more than
28 | just Shapes.
29 |
30 | It works by using pointers to the objects you add and using a callback to ask your code
31 | for bounding boxes when it needs them. Several types of queries can be performed an index as well
32 | as reindexing and full collision information. All communication to the spatial indexes is performed
33 | through callback functions.
34 |
35 | Spatial indexes should be treated as opaque structs.
36 | This means you shouldn't be reading any of the fields directly.
37 |
38 | All spatial indexes define the following methods:
39 |
40 | // The number of objects in the spatial index.
41 | count = 0;
42 |
43 | // Iterate the objects in the spatial index. @c func will be called once for each object.
44 | each(func);
45 |
46 | // Returns true if the spatial index contains the given object.
47 | // Most spatial indexes use hashed storage, so you must provide a hash value too.
48 | contains(obj, hashid);
49 |
50 | // Add an object to a spatial index.
51 | insert(obj, hashid);
52 |
53 | // Remove an object from a spatial index.
54 | remove(obj, hashid);
55 |
56 | // Perform a full reindex of a spatial index.
57 | reindex();
58 |
59 | // Reindex a single object in the spatial index.
60 | reindexObject(obj, hashid);
61 |
62 | // Perform a point query against the spatial index, calling @c func for each potential match.
63 | // A pointer to the point will be passed as @c obj1 of @c func.
64 | // func(shape);
65 | pointQuery(point, func);
66 |
67 | // Perform a segment query against the spatial index, calling @c func for each potential match.
68 | // func(shape);
69 | segmentQuery(vect a, vect b, t_exit, func);
70 |
71 | // Perform a rectangle query against the spatial index, calling @c func for each potential match.
72 | // func(shape);
73 | query(bb, func);
74 |
75 | // Simultaneously reindex and find all colliding objects.
76 | // @c func will be called once for each potentially overlapping pair of objects found.
77 | // If the spatial index was initialized with a static index, it will collide it's objects against that as well.
78 | reindexQuery(func);
79 | */
80 |
81 | var SpatialIndex = cp.SpatialIndex = function(staticIndex)
82 | {
83 | this.staticIndex = staticIndex;
84 |
85 |
86 | if(staticIndex){
87 | if(staticIndex.dynamicIndex){
88 | throw new Error("This static index is already associated with a dynamic index.");
89 | }
90 | staticIndex.dynamicIndex = this;
91 | }
92 | };
93 |
94 | // Collide the objects in a dynamic index against the objects in a static index using the query callback function.
95 | // This only does broad phase collision checking.
96 | SpatialIndex.prototype.collideStatic = function(staticIndex, func)
97 | {
98 | if(staticIndex.count > 0){
99 | this.each(function(obj1) {
100 | staticIndex.query(new BB(obj1.bb_l, obj1.bb_b, obj1.bb_r, obj1.bb_t), function (obj2) {
101 | func(obj1, obj2);
102 | });
103 | });
104 | }
105 | };
106 |
107 |
108 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Chipmunk for Javascript!
2 |
3 | This is a port of the [Chipmunk Physics](http://chipmunk-physics.net/) library to Javascript!
4 |
5 | Check out the sexy [demos](http://dl.dropbox.com/u/2494815/demo/index.html)! (Surprisingly, works best in Safari)
6 |
7 | ## Caveats
8 |
9 | - It is slower than the C version (duh, its in javascript). Specifically, physics simulations using chipmunk-js runs about 3 times slower than the C equivalent.
10 | - I haven't implemented chipmunk's spatial hash, and I have no intention to do so.
11 | - The feature set is lagged a little from the real Chipmunk library. Chipmunk-js currently corresponds to Chipmunk *6.1.1* published in August 2012.
12 |
13 | # Usage
14 |
15 | The API for Chipmunk-js is almost identical to chipmunk-physics. Except:
16 |
17 | - The `cp` prefix has been replaced by a `cp` namespace. (Eg, `cpvadd(a, b)` becomes `cp.vadd(a, b)`.)
18 | - Most functions are wrapped by their containing objects
19 | - Some functions took an array-and-length. Arrays are now all simply javascript arrays, and the length argument has been removed. Eg:
20 |
21 | ```c
22 | cpMomentForPoly(mass, numVerts, *verts, offset);
23 | ```
24 |
25 | becomes:
26 |
27 | ```javascript
28 | cp.momentForPoly(mass, verts, offset);
29 | ```
30 |
31 | - Lots of trivial getters and setters have been removed.
32 | - Lists of verticies are described using a flattened JS array of `[x1,y1, x2,y2, ...]` instead of an array of objects. For example, use `[0,0, 0,1, 1,1, 1,0]` instead of `[cp.v(0,0), cp.v(0,1), cp.v(1,1), cp.v(1,0)]`.
33 |
34 | ## On a website
35 |
36 | ```html
37 |
38 |
39 |
44 | ```
45 |
46 | If any exceptions are thrown or anything, use `cp.js` instead of cp.min.js and post the stack trace you get to the issue page.
47 |
48 | ## From nodejs
49 |
50 | `npm install chipmunk`
51 |
52 | ```javascript
53 | cp = require('chipmunk');
54 |
55 | var space = new cp.Space();
56 | space.addBody(new cp.Body(...))
57 | // ...
58 | ```
59 |
60 | # Example
61 |
62 | This code creates a new space, sets 10 physics iterations per update (these increase simulation stability). Then it adds a bunch of line segments to the space.
63 |
64 | In C, this code looked like:
65 |
66 | ```C
67 | cpSpace *space = cpSpaceNew();
68 | space->iterations = 10;
69 |
70 | cpVect offset = cpv(-320, -240);
71 | for(int i=0; i<(bouncy_terrain_count - 1); i++){
72 | cpVect a = bouncy_terrain_verts[i], b = bouncy_terrain_verts[i+1];
73 | cpShape *shape = cpSpaceAddShape(space, cpSegmentShapeNew(cpSpaceGetStaticBody(space), cpvadd(a, offset), cpvadd(b, offset), 0.0f));
74 | cpShapeSetElasticity(shape, 1.0f);
75 | }
76 | ```
77 |
78 | In javascript, the equivalent code is:
79 |
80 | ```javascript
81 | var space = new cp.Space();
82 | space.iterations = 10;
83 |
84 | var offset = cp.v(-320, -240);
85 | for(var i=0; i<(bouncy_terrain_verts.length - 1); i++){
86 | var a = bouncy_terrain_verts[i], b = bouncy_terrain_verts[i+1];
87 | var shape = space.addShape(new cp.SegmentShape(space.staticBody, cp.vadd(a, offset), cp.vadd(b, offset), 0));
88 | shape.setElasticity(1);
89 | }
90 | ```
91 |
92 | # License
93 |
94 | Like Chipmunk, chipmunk-js is MIT licensed.
95 |
96 | ```
97 | Copyright (c) 2007 Scott Lembcke and Joseph Gentle
98 |
99 | Permission is hereby granted, free of charge, to any person obtaining a copy
100 | of this software and associated documentation files (the "Software"), to deal
101 | in the Software without restriction, including without limitation the rights
102 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
103 | copies of the Software, and to permit persons to whom the Software is
104 | furnished to do so, subject to the following conditions:
105 |
106 | The above copyright notice and this permission notice shall be included in
107 | all copies or substantial portions of the Software.
108 |
109 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
110 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
111 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
112 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
113 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
114 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
115 | SOFTWARE.
116 | ```
117 |
--------------------------------------------------------------------------------
/cp.extra.js:
--------------------------------------------------------------------------------
1 | // This file contains extra functions not found in normal chipmunk. Include this
2 | // file after you include cp.js.
3 | //
4 | // Particularly, this lets you call draw() on any shape or joint.
5 |
6 | // This is the utility code to drive the chipmunk demos. The demos are rendered using
7 | // a single canvas on the page.
8 |
9 | (function() {
10 | var v = cp.v;
11 |
12 | var drawCircle = function(ctx, c, radius) {
13 | ctx.beginPath();
14 | ctx.arc(c.x, c.y, radius, 0, 2*Math.PI, false);
15 | ctx.fill();
16 | ctx.stroke();
17 | };
18 |
19 | var drawLine = function(ctx, a, b) {
20 | ctx.beginPath();
21 | ctx.moveTo(a.x, a.y);
22 | ctx.lineTo(b.x, b.y);
23 | ctx.stroke();
24 | };
25 |
26 | var springPoints = [
27 | v(0.00, 0.0),
28 | v(0.20, 0.0),
29 | v(0.25, 0.5),
30 | v(0.30,-1.0),
31 | v(0.35, 1.0),
32 | v(0.40,-1.0),
33 | v(0.45, 1.0),
34 | v(0.50,-1.0),
35 | v(0.55, 1.0),
36 | v(0.60,-1.0),
37 | v(0.65, 1.0),
38 | v(0.70,-1.0),
39 | v(0.75, 0.5),
40 | v(0.80, 0.0),
41 | v(1.00, 0.0)
42 | ];
43 |
44 | var drawSpring = function(ctx, a, b, width) {
45 | if (width == null) width = 6;
46 |
47 | ctx.beginPath();
48 | ctx.moveTo(a.x, a.y);
49 |
50 | var delta = v.sub(b, a);
51 | var len = v.len(delta);
52 | var rot = v.mult(delta, 1/len);
53 |
54 | for(var i = 1; i < springPoints.length; i++) {
55 |
56 | var p = v.add(a, v.rotate(v(springPoints[i].x * len, springPoints[i].y * width), rot));
57 |
58 | //var p = v.add(a, v.rotate(springPoints[i], delta));
59 |
60 | ctx.lineTo(p.x, p.y);
61 | }
62 |
63 | ctx.stroke();
64 | };
65 |
66 |
67 | // **** Draw methods for Shapes
68 |
69 | cp.PolyShape.prototype.draw = function(ctx)
70 | {
71 | ctx.beginPath();
72 |
73 | var verts = this.tVerts;
74 | var len = verts.length;
75 | var lastPoint = new cp.Vect(verts[len - 2], verts[len - 1]);
76 | ctx.moveTo(lastPoint.x, lastPoint.y);
77 |
78 | for(var i = 0; i < len; i+=2){
79 | var p = new cp.Vect(verts[i], verts[i+1]);
80 | ctx.lineTo(p.x, p.y);
81 | }
82 | ctx.fill();
83 | ctx.stroke();
84 | };
85 |
86 | cp.SegmentShape.prototype.draw = function(ctx) {
87 | var oldLineWidth = ctx.lineWidth;
88 | ctx.lineWidth = Math.max(1, this.r * 2);
89 | drawLine(ctx, this.ta, this.tb);
90 | ctx.lineWidth = oldLineWidth;
91 | };
92 |
93 | cp.CircleShape.prototype.draw = function(ctx) {
94 | drawCircle(ctx, this.tc, this.r);
95 |
96 | // And draw a little radian so you can see the circle roll.
97 | drawLine(ctx, this.tc, cp.v.mult(this.body.rot, this.r).add(this.tc));
98 | };
99 |
100 |
101 | // Draw methods for constraints
102 |
103 | cp.PinJoint.prototype.draw = function(ctx) {
104 | var a = this.a.local2World(this.anchr1);
105 | var b = this.b.local2World(this.anchr2);
106 |
107 | ctx.lineWidth = 2;
108 | ctx.strokeStyle = "grey";
109 | drawLine(ctx, a, b);
110 | };
111 |
112 | cp.SlideJoint.prototype.draw = function(ctx) {
113 | var a = this.a.local2World(this.anchr1);
114 | var b = this.b.local2World(this.anchr2);
115 | var midpoint = v.add(a, v.clamp(v.sub(b, a), this.min));
116 |
117 | ctx.lineWidth = 2;
118 | ctx.strokeStyle = "grey";
119 | drawLine(ctx, a, b);
120 | ctx.strokeStyle = "red";
121 | drawLine(ctx, a, midpoint);
122 | };
123 |
124 | cp.PivotJoint.prototype.draw = function(ctx) {
125 | var a = this.a.local2World(this.anchr1);
126 | var b = this.b.local2World(this.anchr2);
127 | ctx.strokeStyle = "grey";
128 | ctx.fillStyle = "grey";
129 | drawCircle(ctx, a, 2);
130 | drawCircle(ctx, b, 2);
131 | };
132 |
133 | cp.GrooveJoint.prototype.draw = function(ctx) {
134 | var a = this.a.local2World(this.grv_a);
135 | var b = this.a.local2World(this.grv_b);
136 | var c = this.b.local2World(this.anchr2);
137 |
138 | ctx.strokeStyle = "grey";
139 | drawLine(ctx, a, b);
140 | drawCircle(ctx, c, 3);
141 | };
142 |
143 | cp.DampedSpring.prototype.draw = function(ctx) {
144 | var a = this.a.local2World(this.anchr1);
145 | var b = this.b.local2World(this.anchr2);
146 |
147 | ctx.strokeStyle = "grey";
148 | drawSpring(ctx, a, b);
149 | };
150 |
151 | var randColor = function() {
152 | return Math.floor(Math.random() * 256);
153 | };
154 |
155 | var styles = [];
156 | for (var i = 0; i < 100; i++) {
157 | styles.push("rgb(" + randColor() + ", " + randColor() + ", " + randColor() + ")");
158 | }
159 |
160 | //styles = ['rgba(255,0,0,0.5)', 'rgba(0,255,0,0.5)', 'rgba(0,0,255,0.5)'];
161 |
162 | cp.Shape.prototype.style = function() {
163 | var body;
164 | if (this.sensor) {
165 | return "rgba(255,255,255,0)";
166 | } else {
167 | body = this.body;
168 | if (body.isSleeping()) {
169 | return "rgb(50,50,50)";
170 | } else if (body.nodeIdleTime > this.space.sleepTimeThreshold) {
171 | return "rgb(170,170,170)";
172 | } else {
173 | return styles[this.hashid % styles.length];
174 | }
175 | }
176 | };
177 | })();
178 |
--------------------------------------------------------------------------------
/lib/constraints/util.js:
--------------------------------------------------------------------------------
1 | /* Copyright (c) 2007 Scott Lembcke
2 | *
3 | * Permission is hereby granted, free of charge, to any person obtaining a copy
4 | * of this software and associated documentation files (the "Software"), to deal
5 | * in the Software without restriction, including without limitation the rights
6 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | * copies of the Software, and to permit persons to whom the Software is
8 | * furnished to do so, subject to the following conditions:
9 | *
10 | * The above copyright notice and this permission notice shall be included in
11 | * all copies or substantial portions of the Software.
12 | *
13 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | * SOFTWARE.
20 | */
21 |
22 | // These are utility routines to use when creating custom constraints.
23 | // I'm not sure if this should be part of the private API or not.
24 | // I should probably clean up the naming conventions if it is...
25 |
26 | //#define J_MAX(constraint, dt) (((cpConstraint *)constraint)->maxForce*(dt))
27 |
28 | // a and b are bodies.
29 | var relative_velocity = function(a, b, r1, r2){
30 | //var v1_sum = vadd(a.v, vmult(vperp(r1), a.w));
31 | var v1_sumx = a.vx + (-r1.y) * a.w;
32 | var v1_sumy = a.vy + ( r1.x) * a.w;
33 |
34 | //var v2_sum = vadd(b.v, vmult(vperp(r2), b.w));
35 | var v2_sumx = b.vx + (-r2.y) * b.w;
36 | var v2_sumy = b.vy + ( r2.x) * b.w;
37 |
38 | // return vsub(v2_sum, v1_sum);
39 | return new Vect(v2_sumx - v1_sumx, v2_sumy - v1_sumy);
40 | };
41 |
42 | var normal_relative_velocity = function(a, b, r1, r2, n){
43 | //return vdot(relative_velocity(a, b, r1, r2), n);
44 | var v1_sumx = a.vx + (-r1.y) * a.w;
45 | var v1_sumy = a.vy + ( r1.x) * a.w;
46 | var v2_sumx = b.vx + (-r2.y) * b.w;
47 | var v2_sumy = b.vy + ( r2.x) * b.w;
48 |
49 | return vdot2(v2_sumx - v1_sumx, v2_sumy - v1_sumy, n.x, n.y);
50 | };
51 |
52 | /*
53 | var apply_impulse = function(body, j, r){
54 | body.v = vadd(body.v, vmult(j, body.m_inv));
55 | body.w += body.i_inv*vcross(r, j);
56 | };
57 |
58 | var apply_impulses = function(a, b, r1, r2, j)
59 | {
60 | apply_impulse(a, vneg(j), r1);
61 | apply_impulse(b, j, r2);
62 | };
63 | */
64 |
65 | var apply_impulse = function(body, jx, jy, r){
66 | // body.v = body.v.add(vmult(j, body.m_inv));
67 | body.vx += jx * body.m_inv;
68 | body.vy += jy * body.m_inv;
69 | // body.w += body.i_inv*vcross(r, j);
70 | body.w += body.i_inv*(r.x*jy - r.y*jx);
71 | };
72 |
73 | var apply_impulses = function(a, b, r1, r2, jx, jy)
74 | {
75 | apply_impulse(a, -jx, -jy, r1);
76 | apply_impulse(b, jx, jy, r2);
77 | };
78 |
79 | var apply_bias_impulse = function(body, jx, jy, r)
80 | {
81 | //body.v_bias = vadd(body.v_bias, vmult(j, body.m_inv));
82 | body.v_biasx += jx * body.m_inv;
83 | body.v_biasy += jy * body.m_inv;
84 | body.w_bias += body.i_inv*vcross2(r.x, r.y, jx, jy);
85 | };
86 |
87 | /*
88 | var apply_bias_impulses = function(a, b, r1, r2, j)
89 | {
90 | apply_bias_impulse(a, vneg(j), r1);
91 | apply_bias_impulse(b, j, r2);
92 | };*/
93 |
94 | var k_scalar_body = function(body, r, n)
95 | {
96 | var rcn = vcross(r, n);
97 | return body.m_inv + body.i_inv*rcn*rcn;
98 | };
99 |
100 | var k_scalar = function(a, b, r1, r2, n)
101 | {
102 | var value = k_scalar_body(a, r1, n) + k_scalar_body(b, r2, n);
103 | assertSoft(value !== 0, "Unsolvable collision or constraint.");
104 |
105 | return value;
106 | };
107 |
108 | // k1 and k2 are modified by the function to contain the outputs.
109 | var k_tensor = function(a, b, r1, r2, k1, k2)
110 | {
111 | // calculate mass matrix
112 | // If I wasn't lazy and wrote a proper matrix class, this wouldn't be so gross...
113 | var k11, k12, k21, k22;
114 | var m_sum = a.m_inv + b.m_inv;
115 |
116 | // start with I*m_sum
117 | k11 = m_sum; k12 = 0;
118 | k21 = 0; k22 = m_sum;
119 |
120 | // add the influence from r1
121 | var a_i_inv = a.i_inv;
122 | var r1xsq = r1.x * r1.x * a_i_inv;
123 | var r1ysq = r1.y * r1.y * a_i_inv;
124 | var r1nxy = -r1.x * r1.y * a_i_inv;
125 | k11 += r1ysq; k12 += r1nxy;
126 | k21 += r1nxy; k22 += r1xsq;
127 |
128 | // add the influnce from r2
129 | var b_i_inv = b.i_inv;
130 | var r2xsq = r2.x * r2.x * b_i_inv;
131 | var r2ysq = r2.y * r2.y * b_i_inv;
132 | var r2nxy = -r2.x * r2.y * b_i_inv;
133 | k11 += r2ysq; k12 += r2nxy;
134 | k21 += r2nxy; k22 += r2xsq;
135 |
136 | // invert
137 | var determinant = k11*k22 - k12*k21;
138 | assertSoft(determinant !== 0, "Unsolvable constraint.");
139 |
140 | var det_inv = 1/determinant;
141 |
142 | k1.x = k22*det_inv; k1.y = -k12*det_inv;
143 | k2.x = -k21*det_inv; k2.y = k11*det_inv;
144 | };
145 |
146 | var mult_k = function(vr, k1, k2)
147 | {
148 | return new Vect(vdot(vr, k1), vdot(vr, k2));
149 | };
150 |
151 | var bias_coef = function(errorBias, dt)
152 | {
153 | return 1 - Math.pow(errorBias, dt);
154 | };
155 |
156 |
--------------------------------------------------------------------------------
/demo/buoyancy.js:
--------------------------------------------------------------------------------
1 | var FLUID_DENSITY = 0.00014;
2 | var FLUID_DRAG = 2.0;
3 |
4 | var Buoyancy = function() {
5 | Demo.call(this);
6 |
7 | var space = this.space;
8 | space.iterations = 30;
9 | space.gravity = cp.v(0,-500);
10 | // cpSpaceSetDamping(space, 0.5);
11 | space.sleepTimeThreshold = 0.5;
12 | space.collisionSlop = 0.5;
13 |
14 | var staticBody = space.staticBody;
15 |
16 | // Create segments around the edge of the screen.
17 | var shape = space.addShape( new cp.SegmentShape(staticBody, cp.v(0,0), cp.v(0,480), 0.0));
18 | shape.setElasticity(1.0);
19 | shape.setFriction(1.0);
20 | shape.setLayers(NOT_GRABABLE_MASK);
21 |
22 | shape = space.addShape( new cp.SegmentShape(staticBody, cp.v(640,0), cp.v(640,480), 0.0));
23 | shape.setElasticity(1.0);
24 | shape.setFriction(1.0);
25 | shape.setLayers(NOT_GRABABLE_MASK);
26 |
27 | shape = space.addShape( new cp.SegmentShape(staticBody, cp.v(0,0), cp.v(640,0), 0.0));
28 | shape.setElasticity(1.0);
29 | shape.setFriction(1.0);
30 | shape.setLayers(NOT_GRABABLE_MASK);
31 |
32 | shape = space.addShape( new cp.SegmentShape(staticBody, cp.v(0,480), cp.v(640,480), 0.0));
33 | shape.setElasticity(1.0);
34 | shape.setFriction(1.0);
35 | shape.setLayers(NOT_GRABABLE_MASK);
36 |
37 | // {
38 | // Add the edges of the bucket
39 | var bb = new cp.BB(20, 40, 420, 240);
40 | var radius = 5.0;
41 |
42 | shape = space.addShape( new cp.SegmentShape(staticBody, cp.v(bb.l, bb.b), cp.v(bb.l, bb.t), radius));
43 | shape.setElasticity(1.0);
44 | shape.setFriction(1.0);
45 | shape.setLayers(NOT_GRABABLE_MASK);
46 |
47 | shape = space.addShape( new cp.SegmentShape(staticBody, cp.v(bb.r, bb.b), cp.v(bb.r, bb.t), radius));
48 | shape.setElasticity(1.0);
49 | shape.setFriction(1.0);
50 | shape.setLayers(NOT_GRABABLE_MASK);
51 |
52 | shape = space.addShape( new cp.SegmentShape(staticBody, cp.v(bb.l, bb.b), cp.v(bb.r, bb.b), radius));
53 | shape.setElasticity(1.0);
54 | shape.setFriction(1.0);
55 | shape.setLayers(NOT_GRABABLE_MASK);
56 |
57 | // Add the sensor for the water.
58 | shape = space.addShape( new cp.BoxShape2(staticBody, bb) );
59 | shape.setSensor(true);
60 | shape.setCollisionType(1);
61 | // }
62 |
63 |
64 | // {
65 | var width = 200.0;
66 | var height = 50.0;
67 | var mass = 0.3*FLUID_DENSITY*width*height;
68 | var moment = cp.momentForBox(mass, width, height);
69 |
70 | body = space.addBody( new cp.Body(mass, moment));
71 | body.setPos( cp.v(270, 140));
72 | body.setVel( cp.v(0, -100));
73 | body.setAngVel( 1 );
74 |
75 | shape = space.addShape( new cp.BoxShape(body, width, height));
76 | shape.setFriction(0.8);
77 | // }
78 |
79 | // {
80 | width = 40.0;
81 | height = width*2;
82 | mass = 0.3*FLUID_DENSITY*width*height;
83 | moment = cp.momentForBox(mass, width, height);
84 |
85 | body = space.addBody( new cp.Body(mass, moment));
86 | body.setPos(cp.v(120, 190));
87 | body.setVel(cp.v(0, -100));
88 | body.setAngVel(1);
89 |
90 | shape = space.addShape(new cp.BoxShape(body, width, height));
91 | shape.setFriction(0.8);
92 | // }
93 |
94 | space.addCollisionHandler( 1, 0, null, this.waterPreSolve, null, null);
95 | };
96 |
97 | Buoyancy.prototype = Object.create(Demo.prototype);
98 |
99 | Buoyancy.prototype.update = function(dt)
100 | {
101 | var steps = 3;
102 | dt /= steps;
103 | for (var i = 0; i < 3; i++){
104 | this.space.step(dt);
105 | }
106 | };
107 |
108 | Buoyancy.prototype.waterPreSolve = function(arb, space, ptr) {
109 | var shapes = arb.getShapes();
110 | var water = shapes[0];
111 | var poly = shapes[1];
112 |
113 | var body = poly.getBody();
114 |
115 | // Get the top of the water sensor bounding box to use as the water level.
116 | var level = water.getBB().t;
117 |
118 | // Clip the polygon against the water level
119 | var count = poly.getNumVerts();
120 |
121 | var clipped = [];
122 |
123 | var j=count-1;
124 | for(var i=0; i>3) + y*image_row_length]>>(~x&0x7)) & 1;
67 | };
68 |
69 | var make_ball = function(x, y)
70 | {
71 | var body = new cp.Body(1, Infinity);
72 | body.setPos(cp.v(x, y));
73 |
74 | var shape = new cp.CircleShape(body, 0.95, cp.vzero);
75 | shape.setElasticity(0);
76 | shape.setFriction(0);
77 |
78 | return shape;
79 | };
80 |
81 | var LogoSmash = function()
82 | {
83 | Demo.call(this);
84 |
85 | var space = this.space;
86 | space.setIterations(1);
87 |
88 | // The space will contain a very large number of similary sized objects.
89 | // This is the perfect candidate for using the spatial hash.
90 | // Generally you will never need to do this.
91 | //
92 | // (... Except the spatial hash isn't implemented in JS)
93 | //cpSpaceUseSpatialHash(space, 2.0, 10000);
94 |
95 | bodyCount = 0;
96 |
97 | var body;
98 | var shape;
99 |
100 | for(var y=0; y len*len) ? vmult(vnormalize(v), len) : v;
214 | };
215 |
216 | /// Linearly interpolate between v1 towards v2 by distance d.
217 | var vlerpconst = cp.v.lerpconst = function(v1, v2, d)
218 | {
219 | return vadd(v1, vclamp(vsub(v2, v1), d));
220 | };
221 |
222 | /// Returns the distance between v1 and v2.
223 | var vdist = cp.v.dist = function(v1, v2)
224 | {
225 | return vlength(vsub(v1, v2));
226 | };
227 |
228 | /// Returns the squared distance between v1 and v2. Faster than vdist() when you only need to compare distances.
229 | var vdistsq = cp.v.distsq = function(v1, v2)
230 | {
231 | return vlengthsq(vsub(v1, v2));
232 | };
233 |
234 | /// Returns true if the distance between v1 and v2 is less than dist.
235 | var vnear = cp.v.near = function(v1, v2, dist)
236 | {
237 | return vdistsq(v1, v2) < dist*dist;
238 | };
239 |
240 | /// Spherical linearly interpolate between v1 and v2.
241 | var vslerp = cp.v.slerp = function(v1, v2, t)
242 | {
243 | var omega = Math.acos(vdot(v1, v2));
244 |
245 | if(omega) {
246 | var denom = 1/Math.sin(omega);
247 | return vadd(vmult(v1, Math.sin((1 - t)*omega)*denom), vmult(v2, Math.sin(t*omega)*denom));
248 | } else {
249 | return v1;
250 | }
251 | };
252 |
253 | /// Spherical linearly interpolate between v1 towards v2 by no more than angle a radians
254 | var vslerpconst = cp.v.slerpconst = function(v1, v2, a)
255 | {
256 | var angle = Math.acos(vdot(v1, v2));
257 | return vslerp(v1, v2, min(a, angle)/angle);
258 | };
259 |
260 | /// Returns the unit length vector for the given angle (in radians).
261 | var vforangle = cp.v.forangle = function(a)
262 | {
263 | return new Vect(Math.cos(a), Math.sin(a));
264 | };
265 |
266 | /// Returns the angular direction v is pointing in (in radians).
267 | var vtoangle = cp.v.toangle = function(v)
268 | {
269 | return Math.atan2(v.y, v.x);
270 | };
271 |
272 | /// Returns a string representation of v. Intended mostly for debugging purposes and not production use.
273 | var vstr = cp.v.str = function(v)
274 | {
275 | return "(" + v.x.toFixed(3) + ", " + v.y.toFixed(3) + ")";
276 | };
277 |
278 |
--------------------------------------------------------------------------------
/lib/cpPolyShape.js:
--------------------------------------------------------------------------------
1 | /* Copyright (c) 2007 Scott Lembcke
2 | *
3 | * Permission is hereby granted, free of charge, to any person obtaining a copy
4 | * of this software and associated documentation files (the "Software"), to deal
5 | * in the Software without restriction, including without limitation the rights
6 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | * copies of the Software, and to permit persons to whom the Software is
8 | * furnished to do so, subject to the following conditions:
9 | *
10 | * The above copyright notice and this permission notice shall be included in
11 | * all copies or substantial portions of the Software.
12 | *
13 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | * SOFTWARE.
20 | */
21 |
22 | /// Check that a set of vertexes is convex and has a clockwise winding.
23 | var polyValidate = function(verts)
24 | {
25 | var len = verts.length;
26 | for(var i=0; i 0){
35 | if(vcross2(bx - ax, by - ay, cx - bx, cy - by) > 0){
36 | return false;
37 | }
38 | }
39 |
40 | return true;
41 | };
42 |
43 | /// Initialize a polygon shape.
44 | /// The vertexes must be convex and have a clockwise winding.
45 | var PolyShape = cp.PolyShape = function(body, verts, offset)
46 | {
47 | this.setVerts(verts, offset);
48 | this.type = 'poly';
49 | Shape.call(this, body);
50 | };
51 |
52 | PolyShape.prototype = Object.create(Shape.prototype);
53 |
54 | var SplittingPlane = function(n, d)
55 | {
56 | this.n = n;
57 | this.d = d;
58 | };
59 |
60 | SplittingPlane.prototype.compare = function(v)
61 | {
62 | return vdot(this.n, v) - this.d;
63 | };
64 |
65 | PolyShape.prototype.setVerts = function(verts, offset)
66 | {
67 | assert(verts.length >= 4, "Polygons require some verts");
68 | assert(typeof(verts[0]) === 'number',
69 | 'Polygon verticies should be specified in a flattened list (eg [x1,y1,x2,y2,x3,y3,...])');
70 |
71 | // Fail if the user attempts to pass a concave poly, or a bad winding.
72 | assert(polyValidate(verts), "Polygon is concave or has a reversed winding. Consider using cpConvexHull()");
73 |
74 | var len = verts.length;
75 | var numVerts = len >> 1;
76 |
77 | // This a pretty bad way to do this in javascript. As a first pass, I want to keep
78 | // the code similar to the C.
79 | this.verts = new Array(len);
80 | this.tVerts = new Array(len);
81 | this.planes = new Array(numVerts);
82 | this.tPlanes = new Array(numVerts);
83 |
84 | for(var i=0; i>1] = new SplittingPlane(n, vdot2(n.x, n.y, ax, ay));
98 | this.tPlanes[i>>1] = new SplittingPlane(new Vect(0,0), 0);
99 | }
100 | };
101 |
102 | /// Initialize a box shaped polygon shape.
103 | var BoxShape = cp.BoxShape = function(body, width, height)
104 | {
105 | var hw = width/2;
106 | var hh = height/2;
107 |
108 | return BoxShape2(body, new BB(-hw, -hh, hw, hh));
109 | };
110 |
111 | /// Initialize an offset box shaped polygon shape.
112 | var BoxShape2 = cp.BoxShape2 = function(body, box)
113 | {
114 | var verts = [
115 | box.l, box.b,
116 | box.l, box.t,
117 | box.r, box.t,
118 | box.r, box.b,
119 | ];
120 |
121 | return new PolyShape(body, verts, vzero);
122 | };
123 |
124 | PolyShape.prototype.transformVerts = function(p, rot)
125 | {
126 | var src = this.verts;
127 | var dst = this.tVerts;
128 |
129 | var l = Infinity, r = -Infinity;
130 | var b = Infinity, t = -Infinity;
131 |
132 | for(var i=0; i (' + vx + ',' + vy + ')');
141 |
142 | dst[i] = vx;
143 | dst[i+1] = vy;
144 |
145 | l = min(l, vx);
146 | r = max(r, vx);
147 | b = min(b, vy);
148 | t = max(t, vy);
149 | }
150 |
151 | this.bb_l = l;
152 | this.bb_b = b;
153 | this.bb_r = r;
154 | this.bb_t = t;
155 | };
156 |
157 | PolyShape.prototype.transformAxes = function(p, rot)
158 | {
159 | var src = this.planes;
160 | var dst = this.tPlanes;
161 |
162 | for(var i=0; i 0) outside = true;
188 |
189 | var v1x = verts[i*2];
190 | var v1y = verts[i*2 + 1];
191 | var closest = closestPointOnSegment2(p.x, p.y, v0x, v0y, v1x, v1y);
192 |
193 | var dist = vdist(p, closest);
194 | if(dist < minDist){
195 | minDist = dist;
196 | closestPoint = closest;
197 | }
198 |
199 | v0x = v1x;
200 | v0y = v1y;
201 | }
202 |
203 | return new NearestPointQueryInfo(this, closestPoint, (outside ? minDist : -minDist));
204 | };
205 |
206 | PolyShape.prototype.segmentQuery = function(a, b)
207 | {
208 | var axes = this.tPlanes;
209 | var verts = this.tVerts;
210 | var numVerts = axes.length;
211 | var len = numVerts * 2;
212 |
213 | for(var i=0; i an) continue;
217 |
218 | var bn = vdot(b, n);
219 | var t = (axes[i].d - an)/(bn - an);
220 | if(t < 0 || 1 < t) continue;
221 |
222 | var point = vlerp(a, b, t);
223 | var dt = -vcross(n, point);
224 | var dtMin = -vcross2(n.x, n.y, verts[i*2], verts[i*2+1]);
225 | var dtMax = -vcross2(n.x, n.y, verts[(i*2+2)%len], verts[(i*2+3)%len]);
226 |
227 | if(dtMin <= dt && dt <= dtMax){
228 | // josephg: In the original C code, this function keeps
229 | // looping through axes after finding a match. I *think*
230 | // this code is equivalent...
231 | return new SegmentQueryInfo(this, t, n);
232 | }
233 | }
234 | };
235 |
236 | PolyShape.prototype.valueOnAxis = function(n, d)
237 | {
238 | var verts = this.tVerts;
239 | var m = vdot2(n.x, n.y, verts[0], verts[1]);
240 |
241 | for(var i=2; i 0) return false;
256 | }
257 |
258 | return true;
259 | };
260 |
261 | PolyShape.prototype.containsVertPartial = function(vx, vy, n)
262 | {
263 | var planes = this.tPlanes;
264 |
265 | for(var i=0; i 0) return false;
270 | }
271 |
272 | return true;
273 | };
274 |
275 | // These methods are provided for API compatibility with Chipmunk. I recommend against using
276 | // them - just access the poly.verts list directly.
277 | PolyShape.prototype.getNumVerts = function() { return this.verts.length / 2; };
278 | PolyShape.prototype.getVert = function(i)
279 | {
280 | return new Vect(this.verts[i * 2], this.verts[i * 2 + 1]);
281 | };
282 |
283 |
--------------------------------------------------------------------------------
/demo/Joints.js:
--------------------------------------------------------------------------------
1 | /* Copyright (c) 2007 Scott Lembcke
2 | *
3 | * Permission is hereby granted, free of charge, to any person obtaining a copy
4 | * of this software and associated documentation files (the "Software"), to deal
5 | * in the Software without restriction, including without limitation the rights
6 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | * copies of the Software, and to permit persons to whom the Software is
8 | * furnished to do so, subject to the following conditions:
9 | *
10 | * The above copyright notice and this permission notice shall be included in
11 | * all copies or substantial portions of the Software.
12 | *
13 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | * SOFTWARE.
20 | */
21 |
22 | var Joints = function() {
23 | Demo.call(this);
24 |
25 | var space = this.space;
26 | var boxOffset;
27 |
28 | var addBall = function(pos)
29 | {
30 | var radius = 15;
31 | var mass = 1;
32 | var body = space.addBody(new cp.Body(mass, cp.momentForCircle(mass, 0, radius, v(0,0))));
33 | body.setPos(v.add(pos, boxOffset));
34 |
35 | var shape = space.addShape(new cp.CircleShape(body, radius, v(0,0)));
36 | shape.setElasticity(0);
37 | shape.setFriction(0.7);
38 |
39 | return body;
40 | };
41 |
42 | var addLever = function(pos)
43 | {
44 | var mass = 1;
45 | var a = v(0, 15);
46 | var b = v(0, -15);
47 |
48 | var body = space.addBody(new cp.Body(mass, cp.momentForSegment(mass, a, b)));
49 | body.setPos(v.add(pos, v.add(boxOffset, v(0, -15))));
50 |
51 | var shape = space.addShape(new cp.SegmentShape(body, a, b, 5));
52 | shape.setElasticity(0);
53 | shape.setFriction(0.7);
54 |
55 | return body;
56 | };
57 |
58 | var addBar = function(pos)
59 | {
60 | var mass = 2;
61 | var a = v(0, 30);
62 | var b = v(0, -30);
63 |
64 | var body = space.addBody(new cp.Body(mass, cp.momentForSegment(mass, a, b)));
65 | body.setPos(v.add(pos, boxOffset));
66 |
67 | var shape = space.addShape(new cp.SegmentShape(body, a, b, 5));
68 | shape.setElasticity(0);
69 | shape.setFriction(0.7);
70 |
71 | return body;
72 | };
73 |
74 | var addWheel = function(pos)
75 | {
76 | var radius = 15;
77 | var mass = 1;
78 | var body = space.addBody(new cp.Body(mass, cp.momentForCircle(mass, 0, radius, v(0,0))));
79 | body.setPos(v.add(pos, boxOffset));
80 |
81 | var shape = space.addShape(new cp.CircleShape(body, radius, v(0,0)));
82 | shape.setElasticity(0);
83 | shape.setFriction(0.7);
84 | shape.group = 1; // use a group to keep the car parts from colliding
85 |
86 | return body;
87 | };
88 |
89 | var addChassis = function(pos)
90 | {
91 | var mass = 5;
92 | var width = 80;
93 | var height = 30;
94 |
95 | var body = space.addBody(new cp.Body(mass, cp.momentForBox(mass, width, height)));
96 | body.setPos(v.add(pos, boxOffset));
97 |
98 | var shape = space.addShape(new cp.BoxShape(body, width, height));
99 | shape.setElasticity(0);
100 | shape.setFriction(0.7);
101 | shape.group = 1; // use a group to keep the car parts from colliding
102 |
103 | return body;
104 | };
105 |
106 | space.iterations = 10;
107 | space.gravity = v(0, -100);
108 | space.sleepTimeThreshold = 0.5;
109 |
110 | var staticBody = space.staticBody;
111 | var shape;
112 |
113 | for(var y = 480; y >= 0; y -= 120) {
114 | shape = space.addShape(new cp.SegmentShape(staticBody, v(0,y), v(640,y), 0));
115 | shape.setElasticity(1);
116 | shape.setFriction(1);
117 | shape.layers = NOT_GRABABLE_MASK;
118 | }
119 |
120 | for(var x = 0; x <= 640; x += 160) {
121 | shape = space.addShape(new cp.SegmentShape(staticBody, v(x,0), v(x,480), 0));
122 | shape.setElasticity(1);
123 | shape.setFriction(1);
124 | shape.layers = NOT_GRABABLE_MASK;
125 | }
126 |
127 | var body1, body2;
128 |
129 | var posA = v( 50, 60);
130 | var posB = v(110, 60);
131 |
132 | var POS_A = function() { return v.add(boxOffset, posA); };
133 | var POS_B = function() { return v.add(boxOffset, posB); };
134 | //#define POS_A vadd(boxOffset, posA)
135 | //#define POS_B vadd(boxOffset, posB)
136 |
137 | this.labels = labels = [];
138 | var label = function(text) {
139 | labels.push({text:text, pos:boxOffset});
140 | };
141 |
142 | // Pin Joints - Link shapes with a solid bar or pin.
143 | // Keeps the anchor points the same distance apart from when the joint was created.
144 | boxOffset = v(0, 0);
145 | label('Pin Joint');
146 | body1 = addBall(posA);
147 | body2 = addBall(posB);
148 | body2.setAngle(Math.PI);
149 | space.addConstraint(new cp.PinJoint(body1, body2, v(15,0), v(15,0)));
150 |
151 | // Slide Joints - Like pin joints but with a min/max distance.
152 | // Can be used for a cheap approximation of a rope.
153 | boxOffset = v(160, 0);
154 | label('Slide Joint');
155 | body1 = addBall(posA);
156 | body2 = addBall(posB);
157 | body2.setAngle(Math.PI);
158 | space.addConstraint(new cp.SlideJoint(body1, body2, v(15,0), v(15,0), 20, 40));
159 |
160 | // Pivot Joints - Holds the two anchor points together. Like a swivel.
161 | boxOffset = v(320, 0);
162 | label('Pivot Joint');
163 | body1 = addBall(posA);
164 | body2 = addBall(posB);
165 | body2.setAngle(Math.PI);
166 | // cp.PivotJoint(a, b, v) takes it's anchor parameter in world coordinates. The anchors are calculated from that
167 | // Alternately, specify two anchor points using cp.PivotJoint(a, b, anch1, anch2)
168 | space.addConstraint(new cp.PivotJoint(body1, body2, v.add(boxOffset, v(80,60))));
169 |
170 | // Groove Joints - Like a pivot joint, but one of the anchors is a line segment that the pivot can slide in
171 | boxOffset = v(480, 0);
172 | label('Groove Joint');
173 | body1 = addBall(posA);
174 | body2 = addBall(posB);
175 | space.addConstraint(new cp.GrooveJoint(body1, body2, v(30,30), v(30,-30), v(-30,0)));
176 |
177 | // Damped Springs
178 | boxOffset = v(0, 120);
179 | label('Damped Spring');
180 | body1 = addBall(posA);
181 | body2 = addBall(posB);
182 | body2.setAngle(Math.PI);
183 | space.addConstraint(new cp.DampedSpring(body1, body2, v(15,0), v(15,0), 20, 5, 0.3));
184 |
185 | // Damped Rotary Springs
186 | boxOffset = v(160, 120);
187 | label('Damped Rotary Spring');
188 | body1 = addBar(posA);
189 | body2 = addBar(posB);
190 | // Add some pin joints to hold the circles in place.
191 | space.addConstraint(new cp.PivotJoint(body1, staticBody, POS_A()));
192 | space.addConstraint(new cp.PivotJoint(body2, staticBody, POS_B()));
193 | space.addConstraint(new cp.DampedRotarySpring(body1, body2, 0, 3000, 60));
194 |
195 | // Rotary Limit Joint
196 | boxOffset = v(320, 120);
197 | label('Rotary Limit Joint');
198 | body1 = addLever(posA);
199 | body2 = addLever(posB);
200 | // Add some pin joints to hold the circles in place.
201 | space.addConstraint(new cp.PivotJoint(body1, staticBody, POS_A()));
202 | space.addConstraint(new cp.PivotJoint(body2, staticBody, POS_B()));
203 | // Hold their rotation within 90 degrees of each other.
204 | space.addConstraint(new cp.RotaryLimitJoint(body1, body2, -Math.PI/2, Math.PI/2));
205 |
206 | // Ratchet Joint - A rotary ratchet, like a socket wrench
207 | boxOffset = v(480, 120);
208 | label('Ratchet Joint');
209 | body1 = addLever(posA);
210 | body2 = addLever(posB);
211 | // Add some pin joints to hold the circles in place.
212 | space.addConstraint(new cp.PivotJoint(body1, staticBody, POS_A()));
213 | space.addConstraint(new cp.PivotJoint(body2, staticBody, POS_B()));
214 | // Ratchet every 90 degrees
215 | space.addConstraint(new cp.RatchetJoint(body1, body2, 0, Math.PI/2));
216 |
217 | // Gear Joint - Maintain a specific angular velocity ratio
218 | boxOffset = v(0, 240);
219 | label('Gear Joint');
220 | body1 = addBar(posA);
221 | body2 = addBar(posB);
222 | // Add some pin joints to hold the circles in place.
223 | space.addConstraint(new cp.PivotJoint(body1, staticBody, POS_A()));
224 | space.addConstraint(new cp.PivotJoint(body2, staticBody, POS_B()));
225 | // Force one to sping 2x as fast as the other
226 | space.addConstraint(new cp.GearJoint(body1, body2, 0, 2));
227 |
228 | // Simple Motor - Maintain a specific angular relative velocity
229 | boxOffset = v(160, 240);
230 | label('Simple Motor');
231 | body1 = addBar(posA);
232 | body2 = addBar(posB);
233 | // Add some pin joints to hold the circles in place.
234 | space.addConstraint(new cp.PivotJoint(body1, staticBody, POS_A()));
235 | space.addConstraint(new cp.PivotJoint(body2, staticBody, POS_B()));
236 | // Make them spin at 1/2 revolution per second in relation to each other.
237 | space.addConstraint(new cp.SimpleMotor(body1, body2, Math.PI));
238 |
239 | // Make a car with some nice soft suspension
240 | boxOffset = v(320, 240);
241 | var wheel1 = addWheel(posA);
242 | var wheel2 = addWheel(posB);
243 | var chassis = addChassis(v(80, 100));
244 |
245 | space.addConstraint(new cp.GrooveJoint(chassis, wheel1, v(-30, -10), v(-30, -40), v(0,0)));
246 | space.addConstraint(new cp.GrooveJoint(chassis, wheel2, v( 30, -10), v( 30, -40), v(0,0)));
247 |
248 | space.addConstraint(new cp.DampedSpring(chassis, wheel1, v(-30, 0), v(0,0), 50, 20, 10));
249 | space.addConstraint(new cp.DampedSpring(chassis, wheel2, v( 30, 0), v(0,0), 50, 20, 10));
250 | };
251 |
252 | Joints.prototype = Object.create(Demo.prototype);
253 |
254 | Joints.prototype.draw = function() {
255 | Demo.prototype.draw.call(this);
256 |
257 | this.ctx.textAlign = 'center';
258 | this.ctx.textBaseline = 'top';
259 |
260 | for(var i = 0; i < this.labels.length; i++) {
261 | var l = this.labels[i];
262 | var p = this.point2canvas(v.add(l.pos, v(80, 115)));
263 | this.ctx.fillText(l.text, p.x, p.y);
264 | }
265 | };
266 |
267 | addDemo('Joints', Joints);
268 |
269 |
--------------------------------------------------------------------------------
/lib/chipmunk.js:
--------------------------------------------------------------------------------
1 | /* Copyright (c) 2007 Scott Lembcke
2 | *
3 | * Permission is hereby granted, free of charge, to any person obtaining a copy
4 | * of this software and associated documentation files (the "Software"), to deal
5 | * in the Software without restriction, including without limitation the rights
6 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | * copies of the Software, and to permit persons to whom the Software is
8 | * furnished to do so, subject to the following conditions:
9 | *
10 | * The above copyright notice and this permission notice shall be included in
11 | * all copies or substantial portions of the Software.
12 | *
13 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | * SOFTWARE.
20 | */
21 |
22 | Object.create = Object.create || function(o) {
23 | function F() {}
24 | F.prototype = o;
25 | return new F();
26 | };
27 |
28 | //var VERSION = CP_VERSION_MAJOR + "." + CP_VERSION_MINOR + "." + CP_VERSION_RELEASE;
29 |
30 | var cp;
31 | if(typeof exports === 'undefined'){
32 | cp = {};
33 |
34 | if(typeof window === 'object'){
35 | window.cp = cp;
36 | }
37 | } else {
38 | cp = exports;
39 | }
40 |
41 | var assert = function(value, message)
42 | {
43 | if (!value) {
44 | throw new Error('Assertion failed: ' + message);
45 | }
46 | };
47 |
48 | var assertSoft = function(value, message)
49 | {
50 | if(!value && console && console.warn) {
51 | console.warn("ASSERTION FAILED: " + message);
52 | if(console.trace) {
53 | console.trace();
54 | }
55 | }
56 | };
57 |
58 | var mymin = function(a, b)
59 | {
60 | return a < b ? a : b;
61 | };
62 | var mymax = function(a, b)
63 | {
64 | return a > b ? a : b;
65 | };
66 |
67 | var min, max;
68 | if (typeof window === 'object' && window.navigator.userAgent.indexOf('Firefox') > -1){
69 | // On firefox, Math.min and Math.max are really fast:
70 | // http://jsperf.com/math-vs-greater-than/8
71 | min = Math.min;
72 | max = Math.max;
73 | } else {
74 | // On chrome and safari, Math.min / max are slooow. The ternery operator above is faster
75 | // than the builtins because we only have to deal with 2 arguments that are always numbers.
76 | min = mymin;
77 | max = mymax;
78 | }
79 |
80 | /* The hashpair function takes two numbers and returns a hash code for them.
81 | * Required that hashPair(a, b) === hashPair(b, a).
82 | * Chipmunk's hashPair function is defined as:
83 | * #define CP_HASH_COEF (3344921057ul)
84 | * #define CP_HASH_PAIR(A, B) ((cpHashValue)(A)*CP_HASH_COEF ^ (cpHashValue)(B)*CP_HASH_COEF)
85 | * But thats not suitable in javascript because multiplying by a large number will make the number
86 | * a large float.
87 | *
88 | * The result of hashPair is used as the key in objects, so it returns a string.
89 | */
90 | var hashPair = function(a, b)
91 | {
92 | //assert(typeof(a) === 'number', "HashPair used on something not a number");
93 | return a < b ? a + ' ' + b : b + ' ' + a;
94 | };
95 |
96 | var deleteObjFromList = function(arr, obj)
97 | {
98 | for(var i=0; i> 1;
227 | for(var i=1; i maxx || (x == maxx && y > maxy)){
236 | maxx = x;
237 | maxy = y;
238 | end = i;
239 | }
240 | }
241 | return [start, end];
242 | };
243 |
244 | var SWAP = function(arr, idx1, idx2)
245 | {
246 | var tmp = arr[idx1*2];
247 | arr[idx1*2] = arr[idx2*2];
248 | arr[idx2*2] = tmp;
249 |
250 | tmp = arr[idx1*2+1];
251 | arr[idx1*2+1] = arr[idx2*2+1];
252 | arr[idx2*2+1] = tmp;
253 | };
254 |
255 | var QHullPartition = function(verts, offs, count, a, b, tol)
256 | {
257 | if(count === 0) return 0;
258 |
259 | var max = 0;
260 | var pivot = offs;
261 |
262 | var delta = vsub(b, a);
263 | var valueTol = tol * vlength(delta);
264 |
265 | var head = offs;
266 | for(var tail = offs+count-1; head <= tail;){
267 | var v = new Vect(verts[head * 2], verts[head * 2 + 1]);
268 | var value = vcross(delta, vsub(v, a));
269 | if(value > valueTol){
270 | if(value > max){
271 | max = value;
272 | pivot = head;
273 | }
274 |
275 | head++;
276 | } else {
277 | SWAP(verts, head, tail);
278 | tail--;
279 | }
280 | }
281 |
282 | // move the new pivot to the front if it's not already there.
283 | if(pivot != offs) SWAP(verts, offs, pivot);
284 | return head - offs;
285 | };
286 |
287 | var QHullReduce = function(tol, verts, offs, count, a, pivot, b, resultPos)
288 | {
289 | if(count < 0){
290 | return 0;
291 | } else if(count == 0) {
292 | verts[resultPos*2] = pivot.x;
293 | verts[resultPos*2+1] = pivot.y;
294 | return 1;
295 | } else {
296 | var left_count = QHullPartition(verts, offs, count, a, pivot, tol);
297 | var left = new Vect(verts[offs*2], verts[offs*2+1]);
298 | var index = QHullReduce(tol, verts, offs + 1, left_count - 1, a, left, pivot, resultPos);
299 |
300 | var pivotPos = resultPos + index++;
301 | verts[pivotPos*2] = pivot.x;
302 | verts[pivotPos*2+1] = pivot.y;
303 |
304 | var right_count = QHullPartition(verts, offs + left_count, count - left_count, pivot, b, tol);
305 | var right = new Vect(verts[(offs+left_count)*2], verts[(offs+left_count)*2+1]);
306 | return index + QHullReduce(tol, verts, offs + left_count + 1, right_count - 1, pivot, right, b, resultPos + index);
307 | }
308 | };
309 |
310 | // QuickHull seemed like a neat algorithm, and efficient-ish for large input sets.
311 | // My implementation performs an in place reduction using the result array as scratch space.
312 | //
313 | // Pass an Array into result to put the result of the calculation there. Otherwise, pass null
314 | // and the verts list will be edited in-place.
315 | //
316 | // Expects the verts to be described in the same way as cpPolyShape - which is to say, it should
317 | // be a list of [x1,y1,x2,y2,x3,y3,...].
318 | //
319 | // tolerance is in world coordinates. Eg, 2.
320 | cp.convexHull = function(verts, result, tolerance)
321 | {
322 | if(result){
323 | // Copy the line vertexes into the empty part of the result polyline to use as a scratch buffer.
324 | for (var i = 0; i < verts.length; i++){
325 | result[i] = verts[i];
326 | }
327 | } else {
328 | // If a result array was not specified, reduce the input instead.
329 | result = verts;
330 | }
331 |
332 | // Degenerate case, all points are the same.
333 | var indexes = loopIndexes(verts);
334 | var start = indexes[0], end = indexes[1];
335 | if(start == end){
336 | //if(first) (*first) = 0;
337 | result.length = 2;
338 | return result;
339 | }
340 |
341 | SWAP(result, 0, start);
342 | SWAP(result, 1, end == 0 ? start : end);
343 |
344 | var a = new Vect(result[0], result[1]);
345 | var b = new Vect(result[2], result[3]);
346 |
347 | var count = verts.length >> 1;
348 | //if(first) (*first) = start;
349 | var resultCount = QHullReduce(tolerance, result, 2, count - 2, a, b, a, 1) + 1;
350 | result.length = resultCount*2;
351 |
352 | assertSoft(polyValidate(result),
353 | "Internal error: cpConvexHull() and cpPolyValidate() did not agree." +
354 | "Please report this error with as much info as you can.");
355 | return result;
356 | };
357 |
358 | /// Clamp @c f to be between @c min and @c max.
359 | var clamp = function(f, minv, maxv)
360 | {
361 | return min(max(f, minv), maxv);
362 | };
363 |
364 | /// Clamp @c f to be between 0 and 1.
365 | var clamp01 = function(f)
366 | {
367 | return max(0, min(f, 1));
368 | };
369 |
370 | /// Linearly interpolate (or extrapolate) between @c f1 and @c f2 by @c t percent.
371 | var lerp = function(f1, f2, t)
372 | {
373 | return f1*(1 - t) + f2*t;
374 | };
375 |
376 | /// Linearly interpolate from @c f1 to @c f2 by no more than @c d.
377 | var lerpconst = function(f1, f2, d)
378 | {
379 | return f1 + clamp(f2 - f1, -d, d);
380 | };
381 |
382 |
--------------------------------------------------------------------------------
/lib/cpSpaceComponent.js:
--------------------------------------------------------------------------------
1 | /* Copyright (c) 2007 Scott Lembcke
2 | *
3 | * Permission is hereby granted, free of charge, to any person obtaining a copy
4 | * of this software and associated documentation files (the "Software"), to deal
5 | * in the Software without restriction, including without limitation the rights
6 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | * copies of the Software, and to permit persons to whom the Software is
8 | * furnished to do so, subject to the following conditions:
9 | *
10 | * The above copyright notice and this permission notice shall be included in
11 | * all copies or substantial portions of the Software.
12 | *
13 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | * SOFTWARE.
20 | */
21 |
22 | /// **** Sleeping Functions
23 |
24 | Space.prototype.activateBody = function(body)
25 | {
26 | assert(!body.isRogue(), "Internal error: Attempting to activate a rogue body.");
27 |
28 | if(this.locked){
29 | // cpSpaceActivateBody() is called again once the space is unlocked
30 | if(this.rousedBodies.indexOf(body) === -1) this.rousedBodies.push(body);
31 | } else {
32 | this.bodies.push(body);
33 |
34 | for(var i = 0; i < body.shapeList.length; i++){
35 | var shape = body.shapeList[i];
36 | this.staticShapes.remove(shape, shape.hashid);
37 | this.activeShapes.insert(shape, shape.hashid);
38 | }
39 |
40 | for(var arb = body.arbiterList; arb; arb = arb.next(body)){
41 | var bodyA = arb.body_a;
42 | if(body === bodyA || bodyA.isStatic()){
43 | //var contacts = arb.contacts;
44 |
45 | // Restore contact values back to the space's contact buffer memory
46 | //arb.contacts = cpContactBufferGetArray(this);
47 | //memcpy(arb.contacts, contacts, numContacts*sizeof(cpContact));
48 | //cpSpacePushContacts(this, numContacts);
49 |
50 | // Reinsert the arbiter into the arbiter cache
51 | var a = arb.a, b = arb.b;
52 | this.cachedArbiters[hashPair(a.hashid, b.hashid)] = arb;
53 |
54 | // Update the arbiter's state
55 | arb.stamp = this.stamp;
56 | arb.handler = this.lookupHandler(a.collision_type, b.collision_type);
57 | this.arbiters.push(arb);
58 | }
59 | }
60 |
61 | for(var constraint = body.constraintList; constraint; constraint = constraint.nodeNext){
62 | var bodyA = constraint.a;
63 | if(body === bodyA || bodyA.isStatic()) this.constraints.push(constraint);
64 | }
65 | }
66 | };
67 |
68 | Space.prototype.deactivateBody = function(body)
69 | {
70 | assert(!body.isRogue(), "Internal error: Attempting to deactivate a rogue body.");
71 |
72 | deleteObjFromList(this.bodies, body);
73 |
74 | for(var i = 0; i < body.shapeList.length; i++){
75 | var shape = body.shapeList[i];
76 | this.activeShapes.remove(shape, shape.hashid);
77 | this.staticShapes.insert(shape, shape.hashid);
78 | }
79 |
80 | for(var arb = body.arbiterList; arb; arb = arb.next(body)){
81 | var bodyA = arb.body_a;
82 | if(body === bodyA || bodyA.isStatic()){
83 | this.uncacheArbiter(arb);
84 |
85 | // Save contact values to a new block of memory so they won't time out
86 | //size_t bytes = arb.numContacts*sizeof(cpContact);
87 | //cpContact *contacts = (cpContact *)cpcalloc(1, bytes);
88 | //memcpy(contacts, arb.contacts, bytes);
89 | //arb.contacts = contacts;
90 | }
91 | }
92 |
93 | for(var constraint = body.constraintList; constraint; constraint = constraint.nodeNext){
94 | var bodyA = constraint.a;
95 | if(body === bodyA || bodyA.isStatic()) deleteObjFromList(this.constraints, constraint);
96 | }
97 | };
98 |
99 | var componentRoot = function(body)
100 | {
101 | return (body ? body.nodeRoot : null);
102 | };
103 |
104 | var componentActivate = function(root)
105 | {
106 | if(!root || !root.isSleeping(root)) return;
107 | assert(!root.isRogue(), "Internal Error: componentActivate() called on a rogue body.");
108 |
109 | var space = root.space;
110 | var body = root;
111 | while(body){
112 | var next = body.nodeNext;
113 |
114 | body.nodeIdleTime = 0;
115 | body.nodeRoot = null;
116 | body.nodeNext = null;
117 | space.activateBody(body);
118 |
119 | body = next;
120 | }
121 |
122 | deleteObjFromList(space.sleepingComponents, root);
123 | };
124 |
125 | Body.prototype.activate = function()
126 | {
127 | if(!this.isRogue()){
128 | this.nodeIdleTime = 0;
129 | componentActivate(componentRoot(this));
130 | }
131 | };
132 |
133 | Body.prototype.activateStatic = function(filter)
134 | {
135 | assert(this.isStatic(), "Body.activateStatic() called on a non-static body.");
136 |
137 | for(var arb = this.arbiterList; arb; arb = arb.next(this)){
138 | if(!filter || filter == arb.a || filter == arb.b){
139 | (arb.body_a == this ? arb.body_b : arb.body_a).activate();
140 | }
141 | }
142 |
143 | // TODO should also activate joints!
144 | };
145 |
146 | Body.prototype.pushArbiter = function(arb)
147 | {
148 | assertSoft((arb.body_a === this ? arb.thread_a_next : arb.thread_b_next) === null,
149 | "Internal Error: Dangling contact graph pointers detected. (A)");
150 | assertSoft((arb.body_a === this ? arb.thread_a_prev : arb.thread_b_prev) === null,
151 | "Internal Error: Dangling contact graph pointers detected. (B)");
152 |
153 | var next = this.arbiterList;
154 | assertSoft(next === null || (next.body_a === this ? next.thread_a_prev : next.thread_b_prev) === null,
155 | "Internal Error: Dangling contact graph pointers detected. (C)");
156 |
157 | if(arb.body_a === this){
158 | arb.thread_a_next = next;
159 | } else {
160 | arb.thread_b_next = next;
161 | }
162 |
163 | if(next){
164 | if (next.body_a === this){
165 | next.thread_a_prev = arb;
166 | } else {
167 | next.thread_b_prev = arb;
168 | }
169 | }
170 | this.arbiterList = arb;
171 | };
172 |
173 | var componentAdd = function(root, body){
174 | body.nodeRoot = root;
175 |
176 | if(body !== root){
177 | body.nodeNext = root.nodeNext;
178 | root.nodeNext = body;
179 | }
180 | };
181 |
182 | var floodFillComponent = function(root, body)
183 | {
184 | // Rogue bodies cannot be put to sleep and prevent bodies they are touching from sleeping anyway.
185 | // Static bodies (which are a type of rogue body) are effectively sleeping all the time.
186 | if(!body.isRogue()){
187 | var other_root = componentRoot(body);
188 | if(other_root == null){
189 | componentAdd(root, body);
190 | for(var arb = body.arbiterList; arb; arb = arb.next(body)){
191 | floodFillComponent(root, (body == arb.body_a ? arb.body_b : arb.body_a));
192 | }
193 | for(var constraint = body.constraintList; constraint; constraint = constraint.next(body)){
194 | floodFillComponent(root, (body == constraint.a ? constraint.b : constraint.a));
195 | }
196 | } else {
197 | assertSoft(other_root === root, "Internal Error: Inconsistency detected in the contact graph.");
198 | }
199 | }
200 | };
201 |
202 | var componentActive = function(root, threshold)
203 | {
204 | for(var body = root; body; body = body.nodeNext){
205 | if(body.nodeIdleTime < threshold) return true;
206 | }
207 |
208 | return false;
209 | };
210 |
211 | Space.prototype.processComponents = function(dt)
212 | {
213 | var sleep = (this.sleepTimeThreshold !== Infinity);
214 | var bodies = this.bodies;
215 |
216 | // These checks can be removed at some stage (if DEBUG == undefined)
217 | for(var i=0; i keThreshold ? 0 : body.nodeIdleTime + dt);
235 | }
236 | }
237 |
238 | // Awaken any sleeping bodies found and then push arbiters to the bodies' lists.
239 | var arbiters = this.arbiters;
240 | for(var i=0, count=arbiters.length; i= mindist*mindist) return;
49 |
50 | var dist = Math.sqrt(distsq);
51 |
52 | // Allocate and initialize the contact.
53 | return new Contact(
54 | vadd(p1, vmult(delta, 0.5 + (r1 - 0.5*mindist)/(dist ? dist : Infinity))),
55 | (dist ? vmult(delta, 1/dist) : new Vect(1, 0)),
56 | dist - mindist,
57 | 0
58 | );
59 | };
60 |
61 | // Collide circle shapes.
62 | var circle2circle = function(circ1, circ2)
63 | {
64 | var contact = circle2circleQuery(circ1.tc, circ2.tc, circ1.r, circ2.r);
65 | return contact ? [contact] : NONE;
66 | };
67 |
68 | var circle2segment = function(circleShape, segmentShape)
69 | {
70 | var seg_a = segmentShape.ta;
71 | var seg_b = segmentShape.tb;
72 | var center = circleShape.tc;
73 |
74 | var seg_delta = vsub(seg_b, seg_a);
75 | var closest_t = clamp01(vdot(seg_delta, vsub(center, seg_a))/vlengthsq(seg_delta));
76 | var closest = vadd(seg_a, vmult(seg_delta, closest_t));
77 |
78 | var contact = circle2circleQuery(center, closest, circleShape.r, segmentShape.r);
79 | if(contact){
80 | var n = contact.n;
81 |
82 | // Reject endcap collisions if tangents are provided.
83 | return (
84 | (closest_t === 0 && vdot(n, segmentShape.a_tangent) < 0) ||
85 | (closest_t === 1 && vdot(n, segmentShape.b_tangent) < 0)
86 | ) ? NONE : [contact];
87 | } else {
88 | return NONE;
89 | }
90 | }
91 |
92 | // Find the minimum separating axis for the given poly and axis list.
93 | //
94 | // This function needs to return two values - the index of the min. separating axis and
95 | // the value itself. Short of inlining MSA, returning values through a global like this
96 | // is the fastest implementation.
97 | //
98 | // See: http://jsperf.com/return-two-values-from-function/2
99 | var last_MSA_min = 0;
100 | var findMSA = function(poly, planes)
101 | {
102 | var min_index = 0;
103 | var min = poly.valueOnAxis(planes[0].n, planes[0].d);
104 | if(min > 0) return -1;
105 |
106 | for(var i=1; i 0) {
109 | return -1;
110 | } else if(dist > min){
111 | min = dist;
112 | min_index = i;
113 | }
114 | }
115 |
116 | last_MSA_min = min;
117 | return min_index;
118 | };
119 |
120 | // Add contacts for probably penetrating vertexes.
121 | // This handles the degenerate case where an overlap was detected, but no vertexes fall inside
122 | // the opposing polygon. (like a star of david)
123 | var findVertsFallback = function(poly1, poly2, n, dist)
124 | {
125 | var arr = [];
126 |
127 | var verts1 = poly1.tVerts;
128 | for(var i=0; i>1)));
159 | }
160 | }
161 |
162 | var verts2 = poly2.tVerts;
163 | for(var i=0; i>1)));
168 | }
169 | }
170 |
171 | return (arr.length ? arr : findVertsFallback(poly1, poly2, n, dist));
172 | };
173 |
174 | // Collide poly shapes together.
175 | var poly2poly = function(poly1, poly2)
176 | {
177 | var mini1 = findMSA(poly2, poly1.tPlanes);
178 | if(mini1 == -1) return NONE;
179 | var min1 = last_MSA_min;
180 |
181 | var mini2 = findMSA(poly1, poly2.tPlanes);
182 | if(mini2 == -1) return NONE;
183 | var min2 = last_MSA_min;
184 |
185 | // There is overlap, find the penetrating verts
186 | if(min1 > min2)
187 | return findVerts(poly1, poly2, poly1.tPlanes[mini1].n, min1);
188 | else
189 | return findVerts(poly1, poly2, vneg(poly2.tPlanes[mini2].n), min2);
190 | };
191 |
192 | // Like cpPolyValueOnAxis(), but for segments.
193 | var segValueOnAxis = function(seg, n, d)
194 | {
195 | var a = vdot(n, seg.ta) - seg.r;
196 | var b = vdot(n, seg.tb) - seg.r;
197 | return min(a, b) - d;
198 | };
199 |
200 | // Identify vertexes that have penetrated the segment.
201 | var findPointsBehindSeg = function(arr, seg, poly, pDist, coef)
202 | {
203 | var dta = vcross(seg.tn, seg.ta);
204 | var dtb = vcross(seg.tn, seg.tb);
205 | var n = vmult(seg.tn, coef);
206 |
207 | var verts = poly.tVerts;
208 | for(var i=0; i= dt && dt >= dtb){
214 | arr.push(new Contact(new Vect(vx, vy), n, pDist, hashPair(poly.hashid, i)));
215 | }
216 | }
217 | }
218 | };
219 |
220 | // This one is complicated and gross. Just don't go there...
221 | // TODO: Comment me!
222 | var seg2poly = function(seg, poly)
223 | {
224 | var arr = [];
225 |
226 | var planes = poly.tPlanes;
227 | var numVerts = planes.length;
228 |
229 | var segD = vdot(seg.tn, seg.ta);
230 | var minNorm = poly.valueOnAxis(seg.tn, segD) - seg.r;
231 | var minNeg = poly.valueOnAxis(vneg(seg.tn), -segD) - seg.r;
232 | if(minNeg > 0 || minNorm > 0) return NONE;
233 |
234 | var mini = 0;
235 | var poly_min = segValueOnAxis(seg, planes[0].n, planes[0].d);
236 | if(poly_min > 0) return NONE;
237 | for(var i=0; i 0){
240 | return NONE;
241 | } else if(dist > poly_min){
242 | poly_min = dist;
243 | mini = i;
244 | }
245 | }
246 |
247 | var poly_n = vneg(planes[mini].n);
248 |
249 | var va = vadd(seg.ta, vmult(poly_n, seg.r));
250 | var vb = vadd(seg.tb, vmult(poly_n, seg.r));
251 | if(poly.containsVert(va.x, va.y))
252 | arr.push(new Contact(va, poly_n, poly_min, hashPair(seg.hashid, 0)));
253 | if(poly.containsVert(vb.x, vb.y))
254 | arr.push(new Contact(vb, poly_n, poly_min, hashPair(seg.hashid, 1)));
255 |
256 | // Floating point precision problems here.
257 | // This will have to do for now.
258 | // poly_min -= cp_collision_slop; // TODO is this needed anymore?
259 |
260 | if(minNorm >= poly_min || minNeg >= poly_min) {
261 | if(minNorm > minNeg)
262 | findPointsBehindSeg(arr, seg, poly, minNorm, 1);
263 | else
264 | findPointsBehindSeg(arr, seg, poly, minNeg, -1);
265 | }
266 |
267 | // If no other collision points are found, try colliding endpoints.
268 | if(arr.length === 0){
269 | var mini2 = mini * 2;
270 | var verts = poly.tVerts;
271 |
272 | var poly_a = new Vect(verts[mini2], verts[mini2+1]);
273 |
274 | var con;
275 | if((con = circle2circleQuery(seg.ta, poly_a, seg.r, 0, arr))) return [con];
276 | if((con = circle2circleQuery(seg.tb, poly_a, seg.r, 0, arr))) return [con];
277 |
278 | var len = numVerts * 2;
279 | var poly_b = new Vect(verts[(mini2+2)%len], verts[(mini2+3)%len]);
280 | if((con = circle2circleQuery(seg.ta, poly_b, seg.r, 0, arr))) return [con];
281 | if((con = circle2circleQuery(seg.tb, poly_b, seg.r, 0, arr))) return [con];
282 | }
283 |
284 | // console.log(poly.tVerts, poly.tPlanes);
285 | // console.log('seg2poly', arr);
286 | return arr;
287 | };
288 |
289 | // This one is less gross, but still gross.
290 | // TODO: Comment me!
291 | var circle2poly = function(circ, poly)
292 | {
293 | var planes = poly.tPlanes;
294 |
295 | var mini = 0;
296 | var min = vdot(planes[0].n, circ.tc) - planes[0].d - circ.r;
297 | for(var i=0; i 0){
300 | return NONE;
301 | } else if(dist > min) {
302 | min = dist;
303 | mini = i;
304 | }
305 | }
306 |
307 | var n = planes[mini].n;
308 |
309 | var verts = poly.tVerts;
310 | var len = verts.length;
311 | var mini2 = mini<<1;
312 |
313 | //var a = poly.tVerts[mini];
314 | //var b = poly.tVerts[(mini + 1)%poly.tVerts.length];
315 | var ax = verts[mini2];
316 | var ay = verts[mini2+1];
317 | var bx = verts[(mini2+2)%len];
318 | var by = verts[(mini2+3)%len];
319 |
320 | var dta = vcross2(n.x, n.y, ax, ay);
321 | var dtb = vcross2(n.x, n.y, bx, by);
322 | var dt = vcross(n, circ.tc);
323 |
324 | if(dt < dtb){
325 | var con = circle2circleQuery(circ.tc, new Vect(bx, by), circ.r, 0, con);
326 | return con ? [con] : NONE;
327 | } else if(dt < dta) {
328 | return [new Contact(
329 | vsub(circ.tc, vmult(n, circ.r + min/2)),
330 | vneg(n),
331 | min,
332 | 0
333 | )];
334 | } else {
335 | var con = circle2circleQuery(circ.tc, new Vect(ax, ay), circ.r, 0, con);
336 | return con ? [con] : NONE;
337 | }
338 | };
339 |
340 | // The javascripty way to do this would be either nested object or methods on the prototypes.
341 | //
342 | // However, the *fastest* way is the method below.
343 | // See: http://jsperf.com/dispatch
344 |
345 | // These are copied from the prototypes into the actual objects in the Shape constructor.
346 | CircleShape.prototype.collisionCode = 0;
347 | SegmentShape.prototype.collisionCode = 1;
348 | PolyShape.prototype.collisionCode = 2;
349 |
350 | CircleShape.prototype.collisionTable = [
351 | circle2circle,
352 | circle2segment,
353 | circle2poly
354 | ];
355 |
356 | SegmentShape.prototype.collisionTable = [
357 | null,
358 | function(segA, segB) { return NONE; }, // seg2seg
359 | seg2poly
360 | ];
361 |
362 | PolyShape.prototype.collisionTable = [
363 | null,
364 | null,
365 | poly2poly
366 | ];
367 |
368 | var collideShapes = cp.collideShapes = function(a, b)
369 | {
370 | assert(a.collisionCode <= b.collisionCode, 'Collided shapes must be sorted by type');
371 | return a.collisionTable[b.collisionCode](a, b);
372 | };
373 |
374 |
--------------------------------------------------------------------------------
/benchmark/mersenne.js:
--------------------------------------------------------------------------------
1 | // copied from the npm module 'mersenne', may 2011
2 | //
3 | // this program is a JavaScript version of Mersenne Twister, with concealment and encapsulation in class,
4 | // an almost straight conversion from the original program, mt19937ar.c,
5 | // translated by y. okada on July 17, 2006.
6 | // and modified a little at july 20, 2006, but there are not any substantial differences.
7 | // in this program, procedure descriptions and comments of original source code were not removed.
8 | // lines commented with //c// were originally descriptions of c procedure. and a few following lines are appropriate JavaScript descriptions.
9 | // lines commented with /* and */ are original comments.
10 | // lines commented with // are additional comments in this JavaScript version.
11 | // before using this version, create at least one instance of MersenneTwister19937 class, and initialize the each state, given below in c comments, of all the instances.
12 | /*
13 | A C-program for MT19937, with initialization improved 2002/1/26.
14 | Coded by Takuji Nishimura and Makoto Matsumoto.
15 |
16 | Before using, initialize the state by using init_genrand(seed)
17 | or init_by_array(init_key, key_length).
18 |
19 | Copyright (C) 1997 - 2002, Makoto Matsumoto and Takuji Nishimura,
20 | All rights reserved.
21 |
22 | Redistribution and use in source and binary forms, with or without
23 | modification, are permitted provided that the following conditions
24 | are met:
25 |
26 | 1. Redistributions of source code must retain the above copyright
27 | notice, this list of conditions and the following disclaimer.
28 |
29 | 2. Redistributions in binary form must reproduce the above copyright
30 | notice, this list of conditions and the following disclaimer in the
31 | documentation and/or other materials provided with the distribution.
32 |
33 | 3. The names of its contributors may not be used to endorse or promote
34 | products derived from this software without specific prior written
35 | permission.
36 |
37 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
38 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
39 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
40 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
41 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
42 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
43 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
44 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
45 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
46 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
47 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
48 |
49 |
50 | Any feedback is very welcome.
51 | http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/emt.html
52 | email: m-mat @ math.sci.hiroshima-u.ac.jp (remove space)
53 | */
54 |
55 | function MersenneTwister19937()
56 | {
57 | /* Period parameters */
58 | //c//#define N 624
59 | //c//#define M 397
60 | //c//#define MATRIX_A 0x9908b0dfUL /* constant vector a */
61 | //c//#define UPPER_MASK 0x80000000UL /* most significant w-r bits */
62 | //c//#define LOWER_MASK 0x7fffffffUL /* least significant r bits */
63 | var N = 624;
64 | var M = 397;
65 | var MATRIX_A = 0x9908b0df; /* constant vector a */
66 | var UPPER_MASK = 0x80000000; /* most significant w-r bits */
67 | var LOWER_MASK = 0x7fffffff; /* least significant r bits */
68 | //c//static unsigned long mt[N]; /* the array for the state vector */
69 | //c//static int mti=N+1; /* mti==N+1 means mt[N] is not initialized */
70 | var mt = new Array(N); /* the array for the state vector */
71 | var mti = N+1; /* mti==N+1 means mt[N] is not initialized */
72 |
73 | function unsigned32 (n1) // returns a 32-bits unsiged integer from an operand to which applied a bit operator.
74 | {
75 | return n1 < 0 ? (n1 ^ UPPER_MASK) + UPPER_MASK : n1;
76 | }
77 |
78 | function subtraction32 (n1, n2) // emulates lowerflow of a c 32-bits unsiged integer variable, instead of the operator -. these both arguments must be non-negative integers expressible using unsigned 32 bits.
79 | {
80 | return n1 < n2 ? unsigned32((0x100000000 - (n2 - n1)) & 0xffffffff) : n1 - n2;
81 | }
82 |
83 | function addition32 (n1, n2) // emulates overflow of a c 32-bits unsiged integer variable, instead of the operator +. these both arguments must be non-negative integers expressible using unsigned 32 bits.
84 | {
85 | return unsigned32((n1 + n2) & 0xffffffff)
86 | }
87 |
88 | function multiplication32 (n1, n2) // emulates overflow of a c 32-bits unsiged integer variable, instead of the operator *. these both arguments must be non-negative integers expressible using unsigned 32 bits.
89 | {
90 | var sum = 0;
91 | for (var i = 0; i < 32; ++i){
92 | if ((n1 >>> i) & 0x1){
93 | sum = addition32(sum, unsigned32(n2 << i));
94 | }
95 | }
96 | return sum;
97 | }
98 |
99 | /* initializes mt[N] with a seed */
100 | //c//void init_genrand(unsigned long s)
101 | this.init_genrand = function (s)
102 | {
103 | //c//mt[0]= s & 0xffffffff;
104 | mt[0]= unsigned32(s & 0xffffffff);
105 | for (mti=1; mti> 30)) + mti);
108 | addition32(multiplication32(1812433253, unsigned32(mt[mti-1] ^ (mt[mti-1] >>> 30))), mti);
109 | /* See Knuth TAOCP Vol2. 3rd Ed. P.106 for multiplier. */
110 | /* In the previous versions, MSBs of the seed affect */
111 | /* only MSBs of the array mt[]. */
112 | /* 2002/01/09 modified by Makoto Matsumoto */
113 | //c//mt[mti] &= 0xffffffff;
114 | mt[mti] = unsigned32(mt[mti] & 0xffffffff);
115 | /* for >32 bit machines */
116 | }
117 | }
118 |
119 | /* initialize by an array with array-length */
120 | /* init_key is the array for initializing keys */
121 | /* key_length is its length */
122 | /* slight change for C++, 2004/2/26 */
123 | //c//void init_by_array(unsigned long init_key[], int key_length)
124 | this.init_by_array = function (init_key, key_length)
125 | {
126 | //c//int i, j, k;
127 | var i, j, k;
128 | //c//init_genrand(19650218);
129 | this.init_genrand(19650218);
130 | i=1; j=0;
131 | k = (N>key_length ? N : key_length);
132 | for (; k; k--) {
133 | //c//mt[i] = (mt[i] ^ ((mt[i-1] ^ (mt[i-1] >> 30)) * 1664525))
134 | //c// + init_key[j] + j; /* non linear */
135 | mt[i] = addition32(addition32(unsigned32(mt[i] ^ multiplication32(unsigned32(mt[i-1] ^ (mt[i-1] >>> 30)), 1664525)), init_key[j]), j);
136 | mt[i] =
137 | //c//mt[i] &= 0xffffffff; /* for WORDSIZE > 32 machines */
138 | unsigned32(mt[i] & 0xffffffff);
139 | i++; j++;
140 | if (i>=N) { mt[0] = mt[N-1]; i=1; }
141 | if (j>=key_length) j=0;
142 | }
143 | for (k=N-1; k; k--) {
144 | //c//mt[i] = (mt[i] ^ ((mt[i-1] ^ (mt[i-1] >> 30)) * 1566083941))
145 | //c//- i; /* non linear */
146 | mt[i] = subtraction32(unsigned32((dbg=mt[i]) ^ multiplication32(unsigned32(mt[i-1] ^ (mt[i-1] >>> 30)), 1566083941)), i);
147 | //c//mt[i] &= 0xffffffff; /* for WORDSIZE > 32 machines */
148 | mt[i] = unsigned32(mt[i] & 0xffffffff);
149 | i++;
150 | if (i>=N) { mt[0] = mt[N-1]; i=1; }
151 | }
152 | mt[0] = 0x80000000; /* MSB is 1; assuring non-zero initial array */
153 | }
154 |
155 | /* moved outside of genrand_int32() by jwatte 2010-11-17; generate less garbage */
156 | var mag01 = [0x0, MATRIX_A];
157 |
158 | /* generates a random number on [0,0xffffffff]-interval */
159 | //c//unsigned long genrand_int32(void)
160 | this.genrand_int32 = function ()
161 | {
162 | //c//unsigned long y;
163 | //c//static unsigned long mag01[2]={0x0UL, MATRIX_A};
164 | var y;
165 | /* mag01[x] = x * MATRIX_A for x=0,1 */
166 |
167 | if (mti >= N) { /* generate N words at one time */
168 | //c//int kk;
169 | var kk;
170 |
171 | if (mti == N+1) /* if init_genrand() has not been called, */
172 | //c//init_genrand(5489); /* a default initial seed is used */
173 | this.init_genrand(5489); /* a default initial seed is used */
174 |
175 | for (kk=0;kk> 1) ^ mag01[y & 0x1];
178 | y = unsigned32((mt[kk]&UPPER_MASK)|(mt[kk+1]&LOWER_MASK));
179 | mt[kk] = unsigned32(mt[kk+M] ^ (y >>> 1) ^ mag01[y & 0x1]);
180 | }
181 | for (;kk> 1) ^ mag01[y & 0x1];
184 | y = unsigned32((mt[kk]&UPPER_MASK)|(mt[kk+1]&LOWER_MASK));
185 | mt[kk] = unsigned32(mt[kk+(M-N)] ^ (y >>> 1) ^ mag01[y & 0x1]);
186 | }
187 | //c//y = (mt[N-1]&UPPER_MASK)|(mt[0]&LOWER_MASK);
188 | //c//mt[N-1] = mt[M-1] ^ (y >> 1) ^ mag01[y & 0x1];
189 | y = unsigned32((mt[N-1]&UPPER_MASK)|(mt[0]&LOWER_MASK));
190 | mt[N-1] = unsigned32(mt[M-1] ^ (y >>> 1) ^ mag01[y & 0x1]);
191 | mti = 0;
192 | }
193 |
194 | y = mt[mti++];
195 |
196 | /* Tempering */
197 | //c//y ^= (y >> 11);
198 | //c//y ^= (y << 7) & 0x9d2c5680;
199 | //c//y ^= (y << 15) & 0xefc60000;
200 | //c//y ^= (y >> 18);
201 | y = unsigned32(y ^ (y >>> 11));
202 | y = unsigned32(y ^ ((y << 7) & 0x9d2c5680));
203 | y = unsigned32(y ^ ((y << 15) & 0xefc60000));
204 | y = unsigned32(y ^ (y >>> 18));
205 |
206 | return y;
207 | }
208 |
209 | /* generates a random number on [0,0x7fffffff]-interval */
210 | //c//long genrand_int31(void)
211 | this.genrand_int31 = function ()
212 | {
213 | //c//return (genrand_int32()>>1);
214 | return (this.genrand_int32()>>>1);
215 | }
216 |
217 | /* generates a random number on [0,1]-real-interval */
218 | //c//double genrand_real1(void)
219 | this.genrand_real1 = function ()
220 | {
221 | //c//return genrand_int32()*(1.0/4294967295.0);
222 | return this.genrand_int32()*(1.0/4294967295.0);
223 | /* divided by 2^32-1 */
224 | }
225 |
226 | /* generates a random number on [0,1)-real-interval */
227 | //c//double genrand_real2(void)
228 | this.genrand_real2 = function ()
229 | {
230 | //c//return genrand_int32()*(1.0/4294967296.0);
231 | return this.genrand_int32()*(1.0/4294967296.0);
232 | /* divided by 2^32 */
233 | }
234 |
235 | /* generates a random number on (0,1)-real-interval */
236 | //c//double genrand_real3(void)
237 | this.genrand_real3 = function ()
238 | {
239 | //c//return ((genrand_int32()) + 0.5)*(1.0/4294967296.0);
240 | return ((this.genrand_int32()) + 0.5)*(1.0/4294967296.0);
241 | /* divided by 2^32 */
242 | }
243 |
244 | /* generates a random number on [0,1) with 53-bit resolution*/
245 | //c//double genrand_res53(void)
246 | this.genrand_res53 = function ()
247 | {
248 | //c//unsigned long a=genrand_int32()>>5, b=genrand_int32()>>6;
249 | var a=this.genrand_int32()>>>5, b=this.genrand_int32()>>>6;
250 | return(a*67108864.0+b)*(1.0/9007199254740992.0);
251 | }
252 | /* These real versions are due to Isaku Wada, 2002/01/09 added */
253 | }
254 |
255 | // Exports: Public API
256 | var mersenne = {};
257 |
258 | // Export the twister class
259 | mersenne.MersenneTwister19937 = MersenneTwister19937;
260 |
261 | // Export a simplified function to generate random numbers
262 | var gen = new MersenneTwister19937;
263 | gen.init_genrand((new Date).getTime() % 1000000000);
264 | mersenne.rand = function(N) {
265 | if (!N)
266 | {
267 | N = 32768;
268 | }
269 | return Math.floor(gen.genrand_real2() * N);
270 | }
271 | mersenne.rand_real = function () {
272 | return gen.genrand_real2()
273 | }
274 | mersenne.seed = function(S) {
275 | if (typeof(S) != 'number')
276 | {
277 | throw new Error("seed(S) must take numeric argument; is " + typeof(S));
278 | }
279 | gen.init_genrand(S);
280 | }
281 | mersenne.seed_array = function(A) {
282 | if (typeof(A) != 'object')
283 | {
284 | throw new Error("seed_array(A) must take array of numbers; is " + typeof(A));
285 | }
286 | gen.init_by_array(A);
287 | }
288 |
289 |
290 |
--------------------------------------------------------------------------------
/lib/cpBody.js:
--------------------------------------------------------------------------------
1 | /* Copyright (c) 2007 Scott Lembcke
2 | *
3 | * Permission is hereby granted, free of charge, to any person obtaining a copy
4 | * of this software and associated documentation files (the "Software"), to deal
5 | * in the Software without restriction, including without limitation the rights
6 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | * copies of the Software, and to permit persons to whom the Software is
8 | * furnished to do so, subject to the following conditions:
9 | *
10 | * The above copyright notice and this permission notice shall be included in
11 | * all copies or substantial portions of the Software.
12 | *
13 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | * SOFTWARE.
20 | */
21 |
22 | /// @defgroup cpBody cpBody
23 | /// Chipmunk's rigid body type. Rigid bodies hold the physical properties of an object like
24 | /// it's mass, and position and velocity of it's center of gravity. They don't have an shape on their own.
25 | /// They are given a shape by creating collision shapes (cpShape) that point to the body.
26 | /// @{
27 |
28 | var Body = cp.Body = function(m, i) {
29 | /// Mass of the body.
30 | /// Must agree with cpBody.m_inv! Use body.setMass() when changing the mass for this reason.
31 | //this.m;
32 | /// Mass inverse.
33 | //this.m_inv;
34 |
35 | /// Moment of inertia of the body.
36 | /// Must agree with cpBody.i_inv! Use body.setMoment() when changing the moment for this reason.
37 | //this.i;
38 | /// Moment of inertia inverse.
39 | //this.i_inv;
40 |
41 | /// Position of the rigid body's center of gravity.
42 | this.p = new Vect(0,0);
43 | /// Velocity of the rigid body's center of gravity.
44 | this.vx = this.vy = 0;
45 | /// Force acting on the rigid body's center of gravity.
46 | this.f = new Vect(0,0);
47 |
48 | /// Rotation of the body around it's center of gravity in radians.
49 | /// Must agree with cpBody.rot! Use cpBodySetAngle() when changing the angle for this reason.
50 | //this.a;
51 | /// Angular velocity of the body around it's center of gravity in radians/second.
52 | this.w = 0;
53 | /// Torque applied to the body around it's center of gravity.
54 | this.t = 0;
55 |
56 | /// Cached unit length vector representing the angle of the body.
57 | /// Used for fast rotations using cpvrotate().
58 | //cpVect rot;
59 |
60 | /// Maximum velocity allowed when updating the velocity.
61 | this.v_limit = Infinity;
62 | /// Maximum rotational rate (in radians/second) allowed when updating the angular velocity.
63 | this.w_limit = Infinity;
64 |
65 | // This stuff is all private.
66 | this.v_biasx = this.v_biasy = 0;
67 | this.w_bias = 0;
68 |
69 | this.space = null;
70 |
71 | this.shapeList = [];
72 | this.arbiterList = null; // These are both wacky linked lists.
73 | this.constraintList = null;
74 |
75 | // This stuff is used to track information on the collision graph.
76 | this.nodeRoot = null;
77 | this.nodeNext = null;
78 | this.nodeIdleTime = 0;
79 |
80 | // Set this.m and this.m_inv
81 | this.setMass(m);
82 |
83 | // Set this.i and this.i_inv
84 | this.setMoment(i);
85 |
86 | // Set this.a and this.rot
87 | this.rot = new Vect(0,0);
88 | this.setAngle(0);
89 | };
90 |
91 | // I wonder if this should use the constructor style like Body...
92 | var createStaticBody = function()
93 | {
94 | var body = new Body(Infinity, Infinity);
95 | body.nodeIdleTime = Infinity;
96 |
97 | return body;
98 | };
99 |
100 | if (typeof DEBUG !== 'undefined' && DEBUG) {
101 | var v_assert_nan = function(v, message){assert(v.x == v.x && v.y == v.y, message); };
102 | var v_assert_infinite = function(v, message){assert(Math.abs(v.x) !== Infinity && Math.abs(v.y) !== Infinity, message);};
103 | var v_assert_sane = function(v, message){v_assert_nan(v, message); v_assert_infinite(v, message);};
104 |
105 | Body.prototype.sanityCheck = function()
106 | {
107 | assert(this.m === this.m && this.m_inv === this.m_inv, "Body's mass is invalid.");
108 | assert(this.i === this.i && this.i_inv === this.i_inv, "Body's moment is invalid.");
109 |
110 | v_assert_sane(this.p, "Body's position is invalid.");
111 | v_assert_sane(this.f, "Body's force is invalid.");
112 | assert(this.vx === this.vx && Math.abs(this.vx) !== Infinity, "Body's velocity is invalid.");
113 | assert(this.vy === this.vy && Math.abs(this.vy) !== Infinity, "Body's velocity is invalid.");
114 |
115 | assert(this.a === this.a && Math.abs(this.a) !== Infinity, "Body's angle is invalid.");
116 | assert(this.w === this.w && Math.abs(this.w) !== Infinity, "Body's angular velocity is invalid.");
117 | assert(this.t === this.t && Math.abs(this.t) !== Infinity, "Body's torque is invalid.");
118 |
119 | v_assert_sane(this.rot, "Body's rotation vector is invalid.");
120 |
121 | assert(this.v_limit === this.v_limit, "Body's velocity limit is invalid.");
122 | assert(this.w_limit === this.w_limit, "Body's angular velocity limit is invalid.");
123 | };
124 | } else {
125 | Body.prototype.sanityCheck = function(){};
126 | }
127 |
128 | Body.prototype.getPos = function() { return this.p; };
129 | Body.prototype.getVel = function() { return new Vect(this.vx, this.vy); };
130 | Body.prototype.getAngVel = function() { return this.w; };
131 |
132 | /// Returns true if the body is sleeping.
133 | Body.prototype.isSleeping = function()
134 | {
135 | return this.nodeRoot !== null;
136 | };
137 |
138 | /// Returns true if the body is static.
139 | Body.prototype.isStatic = function()
140 | {
141 | return this.nodeIdleTime === Infinity;
142 | };
143 |
144 | /// Returns true if the body has not been added to a space.
145 | Body.prototype.isRogue = function()
146 | {
147 | return this.space === null;
148 | };
149 |
150 | // It would be nicer to use defineProperty for this, but its about 30x slower:
151 | // http://jsperf.com/defineproperty-vs-setter
152 | Body.prototype.setMass = function(mass)
153 | {
154 | assert(mass > 0, "Mass must be positive and non-zero.");
155 |
156 | //activate is defined in cpSpaceComponent
157 | this.activate();
158 | this.m = mass;
159 | this.m_inv = 1/mass;
160 | };
161 |
162 | Body.prototype.setMoment = function(moment)
163 | {
164 | assert(moment > 0, "Moment of Inertia must be positive and non-zero.");
165 |
166 | this.activate();
167 | this.i = moment;
168 | this.i_inv = 1/moment;
169 | };
170 |
171 | Body.prototype.addShape = function(shape)
172 | {
173 | this.shapeList.push(shape);
174 | };
175 |
176 | Body.prototype.removeShape = function(shape)
177 | {
178 | // This implementation has a linear time complexity with the number of shapes.
179 | // The original implementation used linked lists instead, which might be faster if
180 | // you're constantly editing the shape of a body. I expect most bodies will never
181 | // have their shape edited, so I'm just going to use the simplest possible implemention.
182 | deleteObjFromList(this.shapeList, shape);
183 | };
184 |
185 | var filterConstraints = function(node, body, filter)
186 | {
187 | if(node === filter){
188 | return node.next(body);
189 | } else if(node.a === body){
190 | node.next_a = filterConstraints(node.next_a, body, filter);
191 | } else {
192 | node.next_b = filterConstraints(node.next_b, body, filter);
193 | }
194 |
195 | return node;
196 | };
197 |
198 | Body.prototype.removeConstraint = function(constraint)
199 | {
200 | // The constraint must be in the constraints list when this is called.
201 | this.constraintList = filterConstraints(this.constraintList, this, constraint);
202 | };
203 |
204 | Body.prototype.setPos = function(pos)
205 | {
206 | this.activate();
207 | this.sanityCheck();
208 | // If I allow the position to be set to vzero, vzero will get changed.
209 | if (pos === vzero) {
210 | pos = cp.v(0,0);
211 | }
212 | this.p = pos;
213 | };
214 |
215 | Body.prototype.setVel = function(velocity)
216 | {
217 | this.activate();
218 | this.vx = velocity.x;
219 | this.vy = velocity.y;
220 | };
221 |
222 | Body.prototype.setAngVel = function(w)
223 | {
224 | this.activate();
225 | this.w = w;
226 | };
227 |
228 | Body.prototype.setAngleInternal = function(angle)
229 | {
230 | assert(!isNaN(angle), "Internal Error: Attempting to set body's angle to NaN");
231 | this.a = angle;//fmod(a, (cpFloat)M_PI*2.0f);
232 |
233 | //this.rot = vforangle(angle);
234 | this.rot.x = Math.cos(angle);
235 | this.rot.y = Math.sin(angle);
236 | };
237 |
238 | Body.prototype.setAngle = function(angle)
239 | {
240 | this.activate();
241 | this.sanityCheck();
242 | this.setAngleInternal(angle);
243 | };
244 |
245 | Body.prototype.velocity_func = function(gravity, damping, dt)
246 | {
247 | //this.v = vclamp(vadd(vmult(this.v, damping), vmult(vadd(gravity, vmult(this.f, this.m_inv)), dt)), this.v_limit);
248 | var vx = this.vx * damping + (gravity.x + this.f.x * this.m_inv) * dt;
249 | var vy = this.vy * damping + (gravity.y + this.f.y * this.m_inv) * dt;
250 |
251 | //var v = vclamp(new Vect(vx, vy), this.v_limit);
252 | //this.vx = v.x; this.vy = v.y;
253 | var v_limit = this.v_limit;
254 | var lensq = vx * vx + vy * vy;
255 | var scale = (lensq > v_limit*v_limit) ? v_limit / Math.sqrt(lensq) : 1;
256 | this.vx = vx * scale;
257 | this.vy = vy * scale;
258 |
259 | var w_limit = this.w_limit;
260 | this.w = clamp(this.w*damping + this.t*this.i_inv*dt, -w_limit, w_limit);
261 |
262 | this.sanityCheck();
263 | };
264 |
265 | Body.prototype.position_func = function(dt)
266 | {
267 | //this.p = vadd(this.p, vmult(vadd(this.v, this.v_bias), dt));
268 |
269 | //this.p = this.p + (this.v + this.v_bias) * dt;
270 | this.p.x += (this.vx + this.v_biasx) * dt;
271 | this.p.y += (this.vy + this.v_biasy) * dt;
272 |
273 | this.setAngleInternal(this.a + (this.w + this.w_bias)*dt);
274 |
275 | this.v_biasx = this.v_biasy = 0;
276 | this.w_bias = 0;
277 |
278 | this.sanityCheck();
279 | };
280 |
281 | Body.prototype.resetForces = function()
282 | {
283 | this.activate();
284 | this.f = new Vect(0,0);
285 | this.t = 0;
286 | };
287 |
288 | Body.prototype.applyForce = function(force, r)
289 | {
290 | this.activate();
291 | this.f = vadd(this.f, force);
292 | this.t += vcross(r, force);
293 | };
294 |
295 | Body.prototype.applyImpulse = function(j, r)
296 | {
297 | this.activate();
298 | apply_impulse(this, j.x, j.y, r);
299 | };
300 |
301 | Body.prototype.getVelAtPoint = function(r)
302 | {
303 | return vadd(new Vect(this.vx, this.vy), vmult(vperp(r), this.w));
304 | };
305 |
306 | /// Get the velocity on a body (in world units) at a point on the body in world coordinates.
307 | Body.prototype.getVelAtWorldPoint = function(point)
308 | {
309 | return this.getVelAtPoint(vsub(point, this.p));
310 | };
311 |
312 | /// Get the velocity on a body (in world units) at a point on the body in local coordinates.
313 | Body.prototype.getVelAtLocalPoint = function(point)
314 | {
315 | return this.getVelAtPoint(vrotate(point, this.rot));
316 | };
317 |
318 | Body.prototype.eachShape = function(func)
319 | {
320 | for(var i = 0, len = this.shapeList.length; i < len; i++) {
321 | func(this.shapeList[i]);
322 | }
323 | };
324 |
325 | Body.prototype.eachConstraint = function(func)
326 | {
327 | var constraint = this.constraintList;
328 | while(constraint) {
329 | var next = constraint.next(this);
330 | func(constraint);
331 | constraint = next;
332 | }
333 | };
334 |
335 | Body.prototype.eachArbiter = function(func)
336 | {
337 | var arb = this.arbiterList;
338 | while(arb){
339 | var next = arb.next(this);
340 |
341 | arb.swappedColl = (this === arb.body_b);
342 | func(arb);
343 |
344 | arb = next;
345 | }
346 | };
347 |
348 | /// Convert body relative/local coordinates to absolute/world coordinates.
349 | Body.prototype.local2World = function(v)
350 | {
351 | return vadd(this.p, vrotate(v, this.rot));
352 | };
353 |
354 | /// Convert body absolute/world coordinates to relative/local coordinates.
355 | Body.prototype.world2Local = function(v)
356 | {
357 | return vunrotate(vsub(v, this.p), this.rot);
358 | };
359 |
360 | /// Get the kinetic energy of a body.
361 | Body.prototype.kineticEnergy = function()
362 | {
363 | // Need to do some fudging to avoid NaNs
364 | var vsq = this.vx*this.vx + this.vy*this.vy;
365 | var wsq = this.w * this.w;
366 | return (vsq ? vsq*this.m : 0) + (wsq ? wsq*this.i : 0);
367 | };
368 |
369 |
--------------------------------------------------------------------------------
/lib/cpSpaceStep.js:
--------------------------------------------------------------------------------
1 | /* Copyright (c) 2007 Scott Lembcke
2 | *
3 | * Permission is hereby granted, free of charge, to any person obtaining a copy
4 | * of this software and associated documentation files (the "Software"), to deal
5 | * in the Software without restriction, including without limitation the rights
6 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | * copies of the Software, and to permit persons to whom the Software is
8 | * furnished to do so, subject to the following conditions:
9 | *
10 | * The above copyright notice and this permission notice shall be included in
11 | * all copies or substantial portions of the Software.
12 | *
13 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | * SOFTWARE.
20 | */
21 |
22 | // **** Post Step Callback Functions
23 |
24 | /// Schedule a post-step callback to be called when cpSpaceStep() finishes.
25 | Space.prototype.addPostStepCallback = function(func)
26 | {
27 | assertSoft(this.locked,
28 | "Adding a post-step callback when the space is not locked is unnecessary. " +
29 | "Post-step callbacks will not called until the end of the next call to cpSpaceStep() or the next query.");
30 |
31 | this.postStepCallbacks.push(func);
32 | };
33 |
34 | Space.prototype.runPostStepCallbacks = function()
35 | {
36 | // Don't cache length because post step callbacks may add more post step callbacks
37 | // directly or indirectly.
38 | for(var i = 0; i < this.postStepCallbacks.length; i++){
39 | this.postStepCallbacks[i]();
40 | }
41 | this.postStepCallbacks = [];
42 | };
43 |
44 | // **** Locking Functions
45 |
46 | Space.prototype.lock = function()
47 | {
48 | this.locked++;
49 | };
50 |
51 | Space.prototype.unlock = function(runPostStep)
52 | {
53 | this.locked--;
54 | assert(this.locked >= 0, "Internal Error: Space lock underflow.");
55 |
56 | if(this.locked === 0 && runPostStep){
57 | var waking = this.rousedBodies;
58 | for(var i=0; i this.collisionPersistence){
99 | // The tail buffer is available, rotate the ring
100 | var tail = head.next;
101 | tail.stamp = stamp;
102 | tail.contacts.length = 0;
103 | this.contactBuffersHead = tail;
104 | } else {
105 | // Allocate a new buffer and push it into the ring
106 | var buffer = new ContactBuffer(stamp, head);
107 | this.contactBuffersHead = head.next = buffer;
108 | }
109 | };
110 |
111 | cpContact *
112 | cpContactBufferGetArray(cpSpace *space)
113 | {
114 | if(space.contactBuffersHead.numContacts + CP_MAX_CONTACTS_PER_ARBITER > CP_CONTACTS_BUFFER_SIZE){
115 | // contact buffer could overflow on the next collision, push a fresh one.
116 | space.pushFreshContactBuffer();
117 | }
118 |
119 | cpContactBufferHeader *head = space.contactBuffersHead;
120 | return ((cpContactBuffer *)head)->contacts + head.numContacts;
121 | }
122 |
123 | void
124 | cpSpacePushContacts(cpSpace *space, int count)
125 | {
126 | cpAssertHard(count <= CP_MAX_CONTACTS_PER_ARBITER, "Internal Error: Contact buffer overflow!");
127 | space.contactBuffersHead.numContacts += count;
128 | }
129 |
130 | static void
131 | cpSpacePopContacts(cpSpace *space, int count){
132 | space.contactBuffersHead.numContacts -= count;
133 | }
134 | */
135 |
136 | // **** Collision Detection Functions
137 |
138 | /* Use this to re-enable object pools.
139 | static void *
140 | cpSpaceArbiterSetTrans(cpShape **shapes, cpSpace *space)
141 | {
142 | if(space.pooledArbiters.num == 0){
143 | // arbiter pool is exhausted, make more
144 | int count = CP_BUFFER_BYTES/sizeof(cpArbiter);
145 | cpAssertHard(count, "Internal Error: Buffer size too small.");
146 |
147 | cpArbiter *buffer = (cpArbiter *)cpcalloc(1, CP_BUFFER_BYTES);
148 | cpArrayPush(space.allocatedBuffers, buffer);
149 |
150 | for(int i=0; i b.collisionCode){
184 | var temp = a;
185 | a = b;
186 | b = temp;
187 | }
188 |
189 | // Narrow-phase collision detection.
190 | //cpContact *contacts = cpContactBufferGetArray(space);
191 | //int numContacts = cpCollideShapes(a, b, contacts);
192 | var contacts = collideShapes(a, b);
193 | if(contacts.length === 0) return; // Shapes are not colliding.
194 | //cpSpacePushContacts(space, numContacts);
195 |
196 | // Get an arbiter from space.arbiterSet for the two shapes.
197 | // This is where the persistant contact magic comes from.
198 | var arbHash = hashPair(a.hashid, b.hashid);
199 | var arb = space.cachedArbiters[arbHash];
200 | if (!arb){
201 | arb = space.cachedArbiters[arbHash] = new Arbiter(a, b);
202 | }
203 |
204 | arb.update(contacts, handler, a, b);
205 |
206 | // Call the begin function first if it's the first step
207 | if(arb.state == 'first coll' && !handler.begin(arb, space)){
208 | arb.ignore(); // permanently ignore the collision until separation
209 | }
210 |
211 | if(
212 | // Ignore the arbiter if it has been flagged
213 | (arb.state !== 'ignore') &&
214 | // Call preSolve
215 | handler.preSolve(arb, space) &&
216 | // Process, but don't add collisions for sensors.
217 | !sensor
218 | ){
219 | space.arbiters.push(arb);
220 | } else {
221 | //cpSpacePopContacts(space, numContacts);
222 |
223 | arb.contacts = null;
224 |
225 | // Normally arbiters are set as used after calling the post-solve callback.
226 | // However, post-solve callbacks are not called for sensors or arbiters rejected from pre-solve.
227 | if(arb.state !== 'ignore') arb.state = 'normal';
228 | }
229 |
230 | // Time stamp the arbiter so we know it was used recently.
231 | arb.stamp = space.stamp;
232 | };
233 | };
234 |
235 | // Hashset filter func to throw away old arbiters.
236 | Space.prototype.arbiterSetFilter = function(arb)
237 | {
238 | var ticks = this.stamp - arb.stamp;
239 |
240 | var a = arb.body_a, b = arb.body_b;
241 |
242 | // TODO should make an arbiter state for this so it doesn't require filtering arbiters for
243 | // dangling body pointers on body removal.
244 | // Preserve arbiters on sensors and rejected arbiters for sleeping objects.
245 | // This prevents errant separate callbacks from happenening.
246 | if(
247 | (a.isStatic() || a.isSleeping()) &&
248 | (b.isStatic() || b.isSleeping())
249 | ){
250 | return true;
251 | }
252 |
253 | // Arbiter was used last frame, but not this one
254 | if(ticks >= 1 && arb.state != 'cached'){
255 | arb.callSeparate(this);
256 | arb.state = 'cached';
257 | }
258 |
259 | if(ticks >= this.collisionPersistence){
260 | arb.contacts = null;
261 |
262 | //cpArrayPush(this.pooledArbiters, arb);
263 | return false;
264 | }
265 |
266 | return true;
267 | };
268 |
269 | // **** All Important cpSpaceStep() Function
270 |
271 | var updateFunc = function(shape)
272 | {
273 | var body = shape.body;
274 | shape.update(body.p, body.rot);
275 | };
276 |
277 | /// Step the space forward in time by @c dt.
278 | Space.prototype.step = function(dt)
279 | {
280 | // don't step if the timestep is 0!
281 | if(dt === 0) return;
282 |
283 | assert(vzero.x === 0 && vzero.y === 0, "vzero is invalid");
284 |
285 | this.stamp++;
286 |
287 | var prev_dt = this.curr_dt;
288 | this.curr_dt = dt;
289 |
290 | var i;
291 | var j;
292 | var hash;
293 | var bodies = this.bodies;
294 | var constraints = this.constraints;
295 | var arbiters = this.arbiters;
296 |
297 | // Reset and empty the arbiter lists.
298 | for(i=0; iprev || (shape->body && shape->body->shapeList == shape);
92 | return this.body && this.body.shapeList.indexOf(this) !== -1;
93 | };
94 |
95 | Shape.prototype.setBody = function(body)
96 | {
97 | assert(!this.active(), "You cannot change the body on an active shape. You must remove the shape from the space before changing the body.");
98 | this.body = body;
99 | };
100 |
101 | Shape.prototype.cacheBB = function()
102 | {
103 | return this.update(this.body.p, this.body.rot);
104 | };
105 |
106 | Shape.prototype.update = function(pos, rot)
107 | {
108 | assert(!isNaN(rot.x), 'Rotation is NaN');
109 | assert(!isNaN(pos.x), 'Position is NaN');
110 | this.cacheData(pos, rot);
111 | };
112 |
113 | Shape.prototype.pointQuery = function(p)
114 | {
115 | var info = this.nearestPointQuery(p);
116 | if (info.d < 0) return info;
117 | };
118 |
119 | Shape.prototype.getBB = function()
120 | {
121 | return new BB(this.bb_l, this.bb_b, this.bb_r, this.bb_t);
122 | };
123 |
124 | /* Not implemented - all these getters and setters. Just edit the object directly.
125 | CP_DefineShapeStructGetter(cpBody*, body, Body);
126 | void cpShapeSetBody(cpShape *shape, cpBody *body);
127 |
128 | CP_DefineShapeStructGetter(cpBB, bb, BB);
129 | CP_DefineShapeStructProperty(cpBool, sensor, Sensor, cpTrue);
130 | CP_DefineShapeStructProperty(cpFloat, e, Elasticity, cpFalse);
131 | CP_DefineShapeStructProperty(cpFloat, u, Friction, cpTrue);
132 | CP_DefineShapeStructProperty(cpVect, surface_v, SurfaceVelocity, cpTrue);
133 | CP_DefineShapeStructProperty(cpDataPointer, data, UserData, cpFalse);
134 | CP_DefineShapeStructProperty(cpCollisionType, collision_type, CollisionType, cpTrue);
135 | CP_DefineShapeStructProperty(cpGroup, group, Group, cpTrue);
136 | CP_DefineShapeStructProperty(cpLayers, layers, Layers, cpTrue);
137 | */
138 |
139 | /// Extended point query info struct. Returned from calling pointQuery on a shape.
140 | var PointQueryExtendedInfo = function(shape)
141 | {
142 | /// Shape that was hit, NULL if no collision occurred.
143 | this.shape = shape;
144 | /// Depth of the point inside the shape.
145 | this.d = Infinity;
146 | /// Direction of minimum norm to the shape's surface.
147 | this.n = vzero;
148 | };
149 |
150 | var NearestPointQueryInfo = function(shape, p, d)
151 | {
152 | /// The nearest shape, NULL if no shape was within range.
153 | this.shape = shape;
154 | /// The closest point on the shape's surface. (in world space coordinates)
155 | this.p = p;
156 | /// The distance to the point. The distance is negative if the point is inside the shape.
157 | this.d = d;
158 | };
159 |
160 | var SegmentQueryInfo = function(shape, t, n)
161 | {
162 | /// The shape that was hit, NULL if no collision occured.
163 | this.shape = shape;
164 | /// The normalized distance along the query segment in the range [0, 1].
165 | this.t = t;
166 | /// The normal of the surface hit.
167 | this.n = n;
168 | };
169 |
170 | /// Get the hit point for a segment query.
171 | SegmentQueryInfo.prototype.hitPoint = function(start, end)
172 | {
173 | return vlerp(start, end, this.t);
174 | };
175 |
176 | /// Get the hit distance for a segment query.
177 | SegmentQueryInfo.prototype.hitDist = function(start, end)
178 | {
179 | return vdist(start, end) * this.t;
180 | };
181 |
182 | // Circles.
183 |
184 | var CircleShape = cp.CircleShape = function(body, radius, offset)
185 | {
186 | this.c = this.tc = offset;
187 | this.r = radius;
188 |
189 | this.type = 'circle';
190 |
191 | Shape.call(this, body);
192 | };
193 |
194 | CircleShape.prototype = Object.create(Shape.prototype);
195 |
196 | CircleShape.prototype.cacheData = function(p, rot)
197 | {
198 | //var c = this.tc = vadd(p, vrotate(this.c, rot));
199 | var c = this.tc = vrotate(this.c, rot).add(p);
200 | //this.bb = bbNewForCircle(c, this.r);
201 | var r = this.r;
202 | this.bb_l = c.x - r;
203 | this.bb_b = c.y - r;
204 | this.bb_r = c.x + r;
205 | this.bb_t = c.y + r;
206 | };
207 |
208 | /// Test if a point lies within a shape.
209 | /*CircleShape.prototype.pointQuery = function(p)
210 | {
211 | var delta = vsub(p, this.tc);
212 | var distsq = vlengthsq(delta);
213 | var r = this.r;
214 |
215 | if(distsq < r*r){
216 | var info = new PointQueryExtendedInfo(this);
217 |
218 | var dist = Math.sqrt(distsq);
219 | info.d = r - dist;
220 | info.n = vmult(delta, 1/dist);
221 | return info;
222 | }
223 | };*/
224 |
225 | CircleShape.prototype.nearestPointQuery = function(p)
226 | {
227 | var deltax = p.x - this.tc.x;
228 | var deltay = p.y - this.tc.y;
229 | var d = vlength2(deltax, deltay);
230 | var r = this.r;
231 |
232 | var nearestp = new Vect(this.tc.x + deltax * r/d, this.tc.y + deltay * r/d);
233 | return new NearestPointQueryInfo(this, nearestp, d - r);
234 | };
235 |
236 | var circleSegmentQuery = function(shape, center, r, a, b, info)
237 | {
238 | // offset the line to be relative to the circle
239 | a = vsub(a, center);
240 | b = vsub(b, center);
241 |
242 | var qa = vdot(a, a) - 2*vdot(a, b) + vdot(b, b);
243 | var qb = -2*vdot(a, a) + 2*vdot(a, b);
244 | var qc = vdot(a, a) - r*r;
245 |
246 | var det = qb*qb - 4*qa*qc;
247 |
248 | if(det >= 0)
249 | {
250 | var t = (-qb - Math.sqrt(det))/(2*qa);
251 | if(0 <= t && t <= 1){
252 | return new SegmentQueryInfo(shape, t, vnormalize(vlerp(a, b, t)));
253 | }
254 | }
255 | };
256 |
257 | CircleShape.prototype.segmentQuery = function(a, b)
258 | {
259 | return circleSegmentQuery(this, this.tc, this.r, a, b);
260 | };
261 |
262 | // The C API has these, and also getters. Its not idiomatic to
263 | // write getters and setters in JS.
264 | /*
265 | CircleShape.prototype.setRadius = function(radius)
266 | {
267 | this.r = radius;
268 | }
269 |
270 | CircleShape.prototype.setOffset = function(offset)
271 | {
272 | this.c = offset;
273 | }*/
274 |
275 | // Segment shape
276 |
277 | var SegmentShape = cp.SegmentShape = function(body, a, b, r)
278 | {
279 | this.a = a;
280 | this.b = b;
281 | this.n = vperp(vnormalize(vsub(b, a)));
282 |
283 | this.ta = this.tb = this.tn = null;
284 |
285 | this.r = r;
286 |
287 | this.a_tangent = vzero;
288 | this.b_tangent = vzero;
289 |
290 | this.type = 'segment';
291 | Shape.call(this, body);
292 | };
293 |
294 | SegmentShape.prototype = Object.create(Shape.prototype);
295 |
296 | SegmentShape.prototype.cacheData = function(p, rot)
297 | {
298 | this.ta = vadd(p, vrotate(this.a, rot));
299 | this.tb = vadd(p, vrotate(this.b, rot));
300 | this.tn = vrotate(this.n, rot);
301 |
302 | var l,r,b,t;
303 |
304 | if(this.ta.x < this.tb.x){
305 | l = this.ta.x;
306 | r = this.tb.x;
307 | } else {
308 | l = this.tb.x;
309 | r = this.ta.x;
310 | }
311 |
312 | if(this.ta.y < this.tb.y){
313 | b = this.ta.y;
314 | t = this.tb.y;
315 | } else {
316 | b = this.tb.y;
317 | t = this.ta.y;
318 | }
319 |
320 | var rad = this.r;
321 |
322 | this.bb_l = l - rad;
323 | this.bb_b = b - rad;
324 | this.bb_r = r + rad;
325 | this.bb_t = t + rad;
326 | };
327 |
328 | SegmentShape.prototype.nearestPointQuery = function(p)
329 | {
330 | var closest = closestPointOnSegment(p, this.ta, this.tb);
331 |
332 | var deltax = p.x - closest.x;
333 | var deltay = p.y - closest.y;
334 | var d = vlength2(deltax, deltay);
335 | var r = this.r;
336 |
337 | var nearestp = (d ? vadd(closest, vmult(new Vect(deltax, deltay), r/d)) : closest);
338 | return new NearestPointQueryInfo(this, nearestp, d - r);
339 | };
340 |
341 | SegmentShape.prototype.segmentQuery = function(a, b)
342 | {
343 | var n = this.tn;
344 | var d = vdot(vsub(this.ta, a), n);
345 | var r = this.r;
346 |
347 | var flipped_n = (d > 0 ? vneg(n) : n);
348 | var n_offset = vsub(vmult(flipped_n, r), a);
349 |
350 | var seg_a = vadd(this.ta, n_offset);
351 | var seg_b = vadd(this.tb, n_offset);
352 | var delta = vsub(b, a);
353 |
354 | if(vcross(delta, seg_a)*vcross(delta, seg_b) <= 0){
355 | var d_offset = d + (d > 0 ? -r : r);
356 | var ad = -d_offset;
357 | var bd = vdot(delta, n) - d_offset;
358 |
359 | if(ad*bd < 0){
360 | return new SegmentQueryInfo(this, ad/(ad - bd), flipped_n);
361 | }
362 | } else if(r !== 0){
363 | var info1 = circleSegmentQuery(this, this.ta, this.r, a, b);
364 | var info2 = circleSegmentQuery(this, this.tb, this.r, a, b);
365 |
366 | if (info1){
367 | return info2 && info2.t < info1.t ? info2 : info1;
368 | } else {
369 | return info2;
370 | }
371 | }
372 | };
373 |
374 | SegmentShape.prototype.setNeighbors = function(prev, next)
375 | {
376 | this.a_tangent = vsub(prev, this.a);
377 | this.b_tangent = vsub(next, this.b);
378 | };
379 |
380 | SegmentShape.prototype.setEndpoints = function(a, b)
381 | {
382 | this.a = a;
383 | this.b = b;
384 | this.n = vperp(vnormalize(vsub(b, a)));
385 | };
386 |
387 | /*
388 | cpSegmentShapeSetRadius(cpShape *shape, cpFloat radius)
389 | {
390 | this.r = radius;
391 | }*/
392 |
393 | /*
394 | CP_DeclareShapeGetter(cpSegmentShape, cpVect, A);
395 | CP_DeclareShapeGetter(cpSegmentShape, cpVect, B);
396 | CP_DeclareShapeGetter(cpSegmentShape, cpVect, Normal);
397 | CP_DeclareShapeGetter(cpSegmentShape, cpFloat, Radius);
398 | */
399 |
400 |
--------------------------------------------------------------------------------
/demo/demo.js:
--------------------------------------------------------------------------------
1 | // This is the utility code to drive the chipmunk demos. The demos are rendered using
2 | // a single canvas on the page.
3 |
4 | var v = cp.v;
5 |
6 | var ctx;
7 |
8 | var GRABABLE_MASK_BIT = 1<<31;
9 | var NOT_GRABABLE_MASK = ~GRABABLE_MASK_BIT;
10 |
11 | var Demo = function() {
12 | var space = this.space = new cp.Space();
13 | this.remainder = 0;
14 | this.fps = 0;
15 | this.mouse = v(0,0);
16 | this.simulationTime = 0;
17 | this.drawTime = 0;
18 |
19 | var self = this;
20 | var canvas2point = this.canvas2point = function(x, y) {
21 | return v(x / self.scale, 480 - y / self.scale);
22 | };
23 |
24 | this.point2canvas = function(point) {
25 | return v(point.x * self.scale, (480 - point.y) * self.scale);
26 | };
27 |
28 | // HACK HACK HACK - its awful having this here, and its going to break when we
29 | // have multiple demos open at the same time.
30 | this.canvas.onmousemove = function(e) {
31 | self.mouse = canvas2point(e.clientX, e.clientY);
32 | };
33 |
34 | /*
35 | this.canvas.onmousedown = function(e) {
36 | radius = 10;
37 | mass = 3;
38 | body = space.addBody(new cp.Body(mass, cp.momentForCircle(mass, 0, radius, v(0, 0))));
39 | body.setPos(canvas2point(e.clientX, e.clientY));
40 | circle = space.addShape(new cp.CircleShape(body, radius, v(0, 0)));
41 | circle.setElasticity(0.5);
42 | return circle.setFriction(1);
43 | };*/
44 |
45 | var mouseBody = this.mouseBody = new cp.Body(Infinity, Infinity);
46 |
47 | this.canvas.oncontextmenu = function(e) { return false; }
48 |
49 | this.canvas.onmousedown = function(e) {
50 | e.preventDefault();
51 | var rightclick = e.which === 3; // or e.button === 2;
52 | self.mouse = canvas2point(e.clientX, e.clientY);
53 |
54 | if(!rightclick && !self.mouseJoint) {
55 | var point = canvas2point(e.clientX, e.clientY);
56 |
57 | var shape = space.pointQueryFirst(point, GRABABLE_MASK_BIT, cp.NO_GROUP);
58 | if(shape){
59 | var body = shape.body;
60 | var mouseJoint = self.mouseJoint = new cp.PivotJoint(mouseBody, body, v(0,0), body.world2Local(point));
61 |
62 | mouseJoint.maxForce = 50000;
63 | mouseJoint.errorBias = Math.pow(1 - 0.15, 60);
64 | space.addConstraint(mouseJoint);
65 | }
66 | }
67 |
68 | if(rightclick) {
69 | self.rightClick = true;
70 | }
71 | };
72 |
73 | this.canvas.onmouseup = function(e) {
74 | var rightclick = e.which === 3; // or e.button === 2;
75 | self.mouse = canvas2point(e.clientX, e.clientY);
76 |
77 | if(!rightclick) {
78 | if(self.mouseJoint) {
79 | space.removeConstraint(self.mouseJoint);
80 | self.mouseJoint = null;
81 | }
82 | }
83 |
84 | if(rightclick) {
85 | self.rightClick = false;
86 | }
87 | };
88 |
89 | };
90 |
91 | var canvas = Demo.prototype.canvas = document.getElementsByTagName('canvas')[0];
92 |
93 | var ctx = Demo.prototype.ctx = canvas.getContext('2d');
94 |
95 | // The physics space size is 640x480, with the origin in the bottom left.
96 | // Its really an arbitrary number except for the ratio - everything is done
97 | // in floating point maths anyway.
98 |
99 | window.onresize = function(e) {
100 | var width = Demo.prototype.width = canvas.width = window.innerWidth;
101 | var height = Demo.prototype.height = canvas.height = window.innerHeight;
102 | if (width/height > 640/480) {
103 | Demo.prototype.scale = height / 480;
104 | } else {
105 | Demo.prototype.scale = width / 640;
106 | }
107 |
108 | Demo.resized = true;
109 | };
110 | window.onresize();
111 |
112 | var raf = window.requestAnimationFrame
113 | || window.webkitRequestAnimationFrame
114 | || window.mozRequestAnimationFrame
115 | || window.oRequestAnimationFrame
116 | || window.msRequestAnimationFrame
117 | || function(callback) {
118 | return window.setTimeout(callback, 1000 / 60);
119 | };
120 |
121 | // These should be overridden by the demo itself.
122 | Demo.prototype.update = function(dt) {
123 | this.space.step(dt);
124 | };
125 |
126 | Demo.prototype.drawInfo = function() {
127 | var space = this.space;
128 |
129 | var maxWidth = this.width - 20;
130 |
131 | this.ctx.textAlign = 'start';
132 | this.ctx.textBaseline = 'alphabetic';
133 | this.ctx.fillStyle = "black";
134 | //this.ctx.fillText(this.ctx.font, 100, 100);
135 | var fpsStr = Math.floor(this.fps * 10) / 10;
136 | if (space.activeShapes.count === 0) {
137 | fpsStr = '--';
138 | }
139 | this.ctx.fillText("FPS: " + fpsStr, 10, 50, maxWidth);
140 | this.ctx.fillText("Step: " + space.stamp, 10, 80, maxWidth);
141 |
142 | var arbiters = space.arbiters.length;
143 | this.maxArbiters = this.maxArbiters ? Math.max(this.maxArbiters, arbiters) : arbiters;
144 | this.ctx.fillText("Arbiters: " + arbiters + " (Max: " + this.maxArbiters + ")", 10, 110, maxWidth);
145 |
146 | var contacts = 0;
147 | for(var i = 0; i < arbiters; i++) {
148 | contacts += space.arbiters[i].contacts.length;
149 | }
150 | this.maxContacts = this.maxContacts ? Math.max(this.maxContacts, contacts) : contacts;
151 | this.ctx.fillText("Contact points: " + contacts + " (Max: " + this.maxContacts + ")", 10, 140, maxWidth);
152 | this.ctx.fillText("Simulation time: " + this.simulationTime + " ms", 10, 170, maxWidth);
153 | this.ctx.fillText("Draw time: " + this.drawTime + " ms", 10, 200, maxWidth);
154 |
155 | if (this.message) {
156 | this.ctx.fillText(this.message, 10, this.height - 50, maxWidth);
157 | }
158 | };
159 |
160 | Demo.prototype.draw = function() {
161 | var ctx = this.ctx;
162 |
163 | var self = this;
164 |
165 | // Draw shapes
166 | ctx.strokeStyle = 'black';
167 | ctx.clearRect(0, 0, this.width, this.height);
168 |
169 | this.ctx.font = "16px sans-serif";
170 | this.ctx.lineCap = 'round';
171 |
172 | this.space.eachShape(function(shape) {
173 | ctx.fillStyle = shape.style();
174 | shape.draw(ctx, self.scale, self.point2canvas);
175 | });
176 |
177 | // Draw collisions
178 | /*
179 | ctx.strokeStyle = "red";
180 | ctx.lineWidth = 2;
181 |
182 | var arbiters = this.space.arbiters;
183 | for (var i = 0; i < arbiters.length; i++) {
184 | var contacts = arbiters[i].contacts;
185 | for (var j = 0; j < contacts.length; j++) {
186 | var p = this.point2canvas(contacts[j].p);
187 |
188 | ctx.beginPath()
189 | ctx.moveTo(p.x - 2, p.y - 2);
190 | ctx.lineTo(p.x + 2, p.y + 2);
191 | ctx.stroke();
192 |
193 | ctx.beginPath();
194 | ctx.moveTo(p.x + 2, p.y - 2);
195 | ctx.lineTo(p.x - 2, p.y + 2);
196 | ctx.stroke();
197 | }
198 | }*/
199 |
200 | if (this.mouseJoint) {
201 | ctx.beginPath();
202 | var c = this.point2canvas(this.mouseBody.p);
203 | ctx.arc(c.x, c.y, this.scale * 5, 0, 2*Math.PI, false);
204 | ctx.fill();
205 | ctx.stroke();
206 | }
207 |
208 | this.space.eachConstraint(function(c) {
209 | if(c.draw) {
210 | c.draw(ctx, self.scale, self.point2canvas);
211 | }
212 | });
213 |
214 | this.drawInfo();
215 | };
216 |
217 | Demo.prototype.run = function() {
218 | this.running = true;
219 |
220 | var self = this;
221 |
222 | var lastTime = 0;
223 | var step = function(time) {
224 | self.step(time - lastTime);
225 | lastTime = time;
226 |
227 | if (self.running) {
228 | raf(step);
229 | }
230 | };
231 |
232 | step(0);
233 | };
234 |
235 | var soon = function(fn) { setTimeout(fn, 1); };
236 |
237 | Demo.prototype.benchmark = function() {
238 | this.draw();
239 |
240 | var self = this;
241 | soon(function() {
242 | console.log("Benchmarking... waiting for the space to come to rest");
243 | var start = Date.now();
244 | while (self.space.activeShapes.count !== 0) {
245 | self.update(1/60);
246 | }
247 | var end = Date.now();
248 |
249 | console.log('took ' + (end - start) + 'ms');
250 | self.draw();
251 | });
252 | };
253 |
254 | Demo.prototype.stop = function() {
255 | this.running = false;
256 | };
257 |
258 | Demo.prototype.step = function(dt) {
259 | // Update FPS
260 | if(dt > 0) {
261 | this.fps = 0.9*this.fps + 0.1*(1000/dt);
262 | }
263 |
264 | // Move mouse body toward the mouse
265 | var newPoint = v.lerp(this.mouseBody.p, this.mouse, 0.25);
266 | this.mouseBody.v = v.mult(v.sub(newPoint, this.mouseBody.p), 60);
267 | this.mouseBody.p = newPoint;
268 |
269 | var lastNumActiveShapes = this.space.activeShapes.count;
270 |
271 | var now = Date.now();
272 | this.update(1/60);
273 | this.simulationTime += Date.now() - now;
274 |
275 | // Only redraw if the simulation isn't asleep.
276 | if (lastNumActiveShapes > 0 || Demo.resized) {
277 | now = Date.now();
278 | this.draw();
279 | this.drawTime += Date.now() - now;
280 | Demo.resized = false;
281 | }
282 | };
283 |
284 | Demo.prototype.addFloor = function() {
285 | var space = this.space;
286 | var floor = space.addShape(new cp.SegmentShape(space.staticBody, v(0, 0), v(640, 0), 0));
287 | floor.setElasticity(1);
288 | floor.setFriction(1);
289 | floor.setLayers(NOT_GRABABLE_MASK);
290 | };
291 |
292 | Demo.prototype.addWalls = function() {
293 | var space = this.space;
294 | var wall1 = space.addShape(new cp.SegmentShape(space.staticBody, v(0, 0), v(0, 480), 0));
295 | wall1.setElasticity(1);
296 | wall1.setFriction(1);
297 | wall1.setLayers(NOT_GRABABLE_MASK);
298 |
299 | var wall2 = space.addShape(new cp.SegmentShape(space.staticBody, v(640, 0), v(640, 480), 0));
300 | wall2.setElasticity(1);
301 | wall2.setFriction(1);
302 | wall2.setLayers(NOT_GRABABLE_MASK);
303 | };
304 |
305 | // Drawing helper methods
306 |
307 | var drawCircle = function(ctx, scale, point2canvas, c, radius) {
308 | var c = point2canvas(c);
309 | ctx.beginPath();
310 | ctx.arc(c.x, c.y, scale * radius, 0, 2*Math.PI, false);
311 | ctx.fill();
312 | ctx.stroke();
313 | };
314 |
315 | var drawLine = function(ctx, point2canvas, a, b) {
316 | a = point2canvas(a); b = point2canvas(b);
317 |
318 | ctx.beginPath();
319 | ctx.moveTo(a.x, a.y);
320 | ctx.lineTo(b.x, b.y);
321 | ctx.stroke();
322 | };
323 |
324 | var drawRect = function(ctx, point2canvas, pos, size) {
325 | var pos_ = point2canvas(pos);
326 | var size_ = cp.v.sub(point2canvas(cp.v.add(pos, size)), pos_);
327 | ctx.fillRect(pos_.x, pos_.y, size_.x, size_.y);
328 | };
329 |
330 | var springPoints = [
331 | v(0.00, 0.0),
332 | v(0.20, 0.0),
333 | v(0.25, 3.0),
334 | v(0.30,-6.0),
335 | v(0.35, 6.0),
336 | v(0.40,-6.0),
337 | v(0.45, 6.0),
338 | v(0.50,-6.0),
339 | v(0.55, 6.0),
340 | v(0.60,-6.0),
341 | v(0.65, 6.0),
342 | v(0.70,-3.0),
343 | v(0.75, 6.0),
344 | v(0.80, 0.0),
345 | v(1.00, 0.0)
346 | ];
347 |
348 | var drawSpring = function(ctx, scale, point2canvas, a, b) {
349 | a = point2canvas(a); b = point2canvas(b);
350 |
351 | ctx.beginPath();
352 | ctx.moveTo(a.x, a.y);
353 |
354 | var delta = v.sub(b, a);
355 | var len = v.len(delta);
356 | var rot = v.mult(delta, 1/len);
357 |
358 | for(var i = 1; i < springPoints.length; i++) {
359 |
360 | var p = v.add(a, v.rotate(v(springPoints[i].x * len, springPoints[i].y * scale), rot));
361 |
362 | //var p = v.add(a, v.rotate(springPoints[i], delta));
363 |
364 | ctx.lineTo(p.x, p.y);
365 | }
366 |
367 | ctx.stroke();
368 | };
369 |
370 |
371 | // **** Draw methods for Shapes
372 |
373 | cp.PolyShape.prototype.draw = function(ctx, scale, point2canvas)
374 | {
375 | ctx.beginPath();
376 |
377 | var verts = this.tVerts;
378 | var len = verts.length;
379 | var lastPoint = point2canvas(new cp.Vect(verts[len - 2], verts[len - 1]));
380 | ctx.moveTo(lastPoint.x, lastPoint.y);
381 |
382 | for(var i = 0; i < len; i+=2){
383 | var p = point2canvas(new cp.Vect(verts[i], verts[i+1]));
384 | ctx.lineTo(p.x, p.y);
385 | }
386 | ctx.fill();
387 | ctx.stroke();
388 | };
389 |
390 | cp.SegmentShape.prototype.draw = function(ctx, scale, point2canvas) {
391 | var oldLineWidth = ctx.lineWidth;
392 | ctx.lineWidth = Math.max(1, this.r * scale * 2);
393 | drawLine(ctx, point2canvas, this.ta, this.tb);
394 | ctx.lineWidth = oldLineWidth;
395 | };
396 |
397 | cp.CircleShape.prototype.draw = function(ctx, scale, point2canvas) {
398 | drawCircle(ctx, scale, point2canvas, this.tc, this.r);
399 |
400 | // And draw a little radian so you can see the circle roll.
401 | drawLine(ctx, point2canvas, this.tc, cp.v.mult(this.body.rot, this.r).add(this.tc));
402 | };
403 |
404 |
405 | // Draw methods for constraints
406 |
407 | cp.PinJoint.prototype.draw = function(ctx, scale, point2canvas) {
408 | var a = this.a.local2World(this.anchr1);
409 | var b = this.b.local2World(this.anchr2);
410 |
411 | ctx.lineWidth = 2;
412 | ctx.strokeStyle = "grey";
413 | drawLine(ctx, point2canvas, a, b);
414 | };
415 |
416 | cp.SlideJoint.prototype.draw = function(ctx, scale, point2canvas) {
417 | var a = this.a.local2World(this.anchr1);
418 | var b = this.b.local2World(this.anchr2);
419 | var midpoint = v.add(a, v.clamp(v.sub(b, a), this.min));
420 |
421 | ctx.lineWidth = 2;
422 | ctx.strokeStyle = "grey";
423 | drawLine(ctx, point2canvas, a, b);
424 | ctx.strokeStyle = "red";
425 | drawLine(ctx, point2canvas, a, midpoint);
426 | };
427 |
428 | cp.PivotJoint.prototype.draw = function(ctx, scale, point2canvas) {
429 | var a = this.a.local2World(this.anchr1);
430 | var b = this.b.local2World(this.anchr2);
431 | ctx.strokeStyle = "grey";
432 | ctx.fillStyle = "grey";
433 | drawCircle(ctx, scale, point2canvas, a, 2);
434 | drawCircle(ctx, scale, point2canvas, b, 2);
435 | };
436 |
437 | cp.GrooveJoint.prototype.draw = function(ctx, scale, point2canvas) {
438 | var a = this.a.local2World(this.grv_a);
439 | var b = this.a.local2World(this.grv_b);
440 | var c = this.b.local2World(this.anchr2);
441 |
442 | ctx.strokeStyle = "grey";
443 | drawLine(ctx, point2canvas, a, b);
444 | drawCircle(ctx, scale, point2canvas, c, 3);
445 | };
446 |
447 | cp.DampedSpring.prototype.draw = function(ctx, scale, point2canvas) {
448 | var a = this.a.local2World(this.anchr1);
449 | var b = this.b.local2World(this.anchr2);
450 |
451 | ctx.strokeStyle = "grey";
452 | drawSpring(ctx, scale, point2canvas, a, b);
453 | };
454 |
455 | var randColor = function() {
456 | return Math.floor(Math.random() * 256);
457 | };
458 |
459 | var styles = [];
460 | for (var i = 0; i < 100; i++) {
461 | styles.push("rgb(" + randColor() + ", " + randColor() + ", " + randColor() + ")");
462 | }
463 |
464 | //styles = ['rgba(255,0,0,0.5)', 'rgba(0,255,0,0.5)', 'rgba(0,0,255,0.5)'];
465 |
466 | cp.Shape.prototype.style = function() {
467 | var body;
468 | if (this.sensor) {
469 | return "rgba(255,255,255,0)";
470 | } else {
471 | body = this.body;
472 | if (body.isSleeping()) {
473 | return "rgb(50,50,50)";
474 | } else if (body.nodeIdleTime > this.space.sleepTimeThreshold) {
475 | return "rgb(170,170,170)";
476 | } else {
477 | return styles[this.hashid % styles.length];
478 | }
479 | }
480 | };
481 |
482 | var demos = [];
483 | var addDemo = function(name, demo) {
484 | demos.push({name:name, demo:demo});
485 | };
486 |
487 |
--------------------------------------------------------------------------------