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