diff --git a/.gitignore b/.gitignore index 0f8e6e38..8498a661 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ node_modules .DS_Store _SpecRunner.html _working -.sass-cache \ No newline at end of file +.sass-cache +_site/ diff --git a/README.md b/README.md index 6d070acb..140de59c 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ A modular, extendable, and easy-to-use physics engine for javascript. -Latest version: 0.6.0 (beta) +Latest version: 0.7.0 (beta) ## Usage diff --git a/bower.json b/bower.json index d78df971..58976f99 100644 --- a/bower.json +++ b/bower.json @@ -1,12 +1,12 @@ { "name": "PhysicsJS", - "version": "0.6.0", + "version": "0.7.0", "homepage": "http://wellcaffeinated.net/PhysicsJS", "authors": [ "Jasper Palfree " ], "description": "A modular, extendable, and easy-to-use physics engine for javascript", - "main": "dist/physicsjs-full-0.6.0.js", + "main": "dist/physicsjs-full.js", "moduleType": [ "amd", "globals", diff --git a/dist/behaviors/attractor.js b/dist/behaviors/attractor.js index beefb411..ba1b2b31 100644 --- a/dist/behaviors/attractor.js +++ b/dist/behaviors/attractor.js @@ -1,5 +1,5 @@ /** - * PhysicsJS v0.6.0 - 2014-04-22 + * PhysicsJS v0.7.0 - 2014-12-08 * A modular, extendable, and easy-to-use physics engine for javascript * http://wellcaffeinated.net/PhysicsJS * diff --git a/dist/behaviors/body-collision-detection.js b/dist/behaviors/body-collision-detection.js index 4913c072..2121e2c1 100644 --- a/dist/behaviors/body-collision-detection.js +++ b/dist/behaviors/body-collision-detection.js @@ -1,5 +1,5 @@ /** - * PhysicsJS v0.6.0 - 2014-04-22 + * PhysicsJS v0.7.0 - 2014-12-08 * A modular, extendable, and easy-to-use physics engine for javascript * http://wellcaffeinated.net/PhysicsJS * @@ -33,7 +33,7 @@ * bodyB: // the second body * norm: // the normal vector (Vectorish) * mtv: // the minimum transit vector. (the direction and length needed to extract bodyB from bodyA) - * pos: // the collision point + * pos: // the collision point relative to bodyA * overlap: // the amount bodyA overlaps bodyB * } * ``` @@ -61,42 +61,46 @@ ; if ( !fn ){ - fn = supportFnStack[ hash ] = function( searchDir ){ + fn = supportFnStack[ hash ] = function pairSupportFunction( searchDir ){ - var scratch = Physics.scratchpad() - ,tA = fn.tA + var tA = fn.tA ,tB = fn.tB - ,vA = scratch.vector() - ,vB = scratch.vector() - ,marginA = fn.marginA - ,marginB = fn.marginB + ,vA = fn.tmpv1 + ,vB = fn.tmpv2 ; if ( fn.useCore ){ - vA = bodyA.geometry.getFarthestCorePoint( searchDir.rotateInv( tA ), vA, marginA ).transform( tA ); - vB = bodyB.geometry.getFarthestCorePoint( searchDir.rotate( tA ).rotateInv( tB ).negate(), vB, marginB ).transform( tB ); + vA = bodyA.geometry.getFarthestCorePoint( searchDir.rotateInv( tA ), vA, fn.marginA ); + vB = bodyB.geometry.getFarthestCorePoint( searchDir.rotate( tA ).rotateInv( tB ).negate(), vB, fn.marginB ); } else { - vA = bodyA.geometry.getFarthestHullPoint( searchDir.rotateInv( tA ), vA ).transform( tA ); - vB = bodyB.geometry.getFarthestHullPoint( searchDir.rotate( tA ).rotateInv( tB ).negate(), vB ).transform( tB ); + vA = bodyA.geometry.getFarthestHullPoint( searchDir.rotateInv( tA ), vA ); + vB = bodyB.geometry.getFarthestHullPoint( searchDir.rotate( tA ).rotateInv( tB ).negate(), vB ); } + vA.vadd( bodyA.offset ).transform( tA ); + vB.vadd( bodyB.offset ).transform( tB ); searchDir.negate().rotate( tB ); - return scratch.done({ + return { a: vA.values(), b: vB.values(), pt: vA.vsub( vB ).values() - }); + }; }; - fn.tA = Physics.transform(); - fn.tB = Physics.transform(); + // transforms for coordinate transformations + fn.tA = new Physics.transform(); + fn.tB = new Physics.transform(); + + // temp vectors (used too frequently to justify scratchpad) + fn.tmpv1 = new Physics.vector(); + fn.tmpv2 = new Physics.vector(); } fn.useCore = false; fn.margin = 0; - fn.tA.setTranslation( bodyA.state.pos ).setRotation( bodyA.state.angular.pos ); - fn.tB.setTranslation( bodyB.state.pos ).setRotation( bodyB.state.angular.pos ); + fn.tA.setRotation( bodyA.state.angular.pos ).setTranslation( bodyA.state.pos ); + fn.tB.setRotation( bodyB.state.angular.pos ).setTranslation( bodyB.state.pos ); fn.bodyA = bodyA; fn.bodyB = bodyB; @@ -116,9 +120,11 @@ var scratch = Physics.scratchpad() ,d = scratch.vector() ,tmp = scratch.vector() + ,os = scratch.vector() ,overlap ,result ,support + ,inc ,collision = false ,aabbA = bodyA.aabb() ,dimA = Math.min( aabbA.hw, aabbA.hh ) @@ -128,7 +134,11 @@ // just check the overlap first support = getSupportFn( bodyA, bodyB ); - d.clone( bodyA.state.pos ).vsub( bodyB.state.pos ); + d.clone( bodyA.state.pos ) + .vadd( bodyA.getGlobalOffset( os ) ) + .vsub( bodyB.state.pos ) + .vsub( bodyB.getGlobalOffset( os ) ) + ; result = Physics.gjk(support, d, true); if ( result.overlap ){ @@ -139,17 +149,23 @@ bodyB: bodyB }; + // inc by 1% of the smallest dim. + inc = 1e-2 * Math.min(dimA || 1, dimB || 1); + // first get the min distance of between core objects support.useCore = true; support.marginA = 0; support.marginB = 0; - while ( result.overlap && (support.marginA < dimA || support.marginB < dimB) ){ + // while there's still an overlap (or we don't have a positive distance) + // and the support margins aren't bigger than the shapes... + // search for the distance data + while ( (result.overlap || result.distance === 0) && (support.marginA < dimA || support.marginB < dimB) ){ if ( support.marginA < dimA ){ - support.marginA += 1; + support.marginA += inc; } if ( support.marginB < dimB ){ - support.marginB += 1; + support.marginB += inc; } result = Physics.gjk(support, d); @@ -161,13 +177,18 @@ } // calc overlap - overlap = Math.max(0, (support.marginA + support.marginB) - result.distance); + overlap = (support.marginA + support.marginB) - result.distance; + + if ( overlap <= 0 ){ + return scratch.done(false); + } + collision.overlap = overlap; // @TODO: for now, just let the normal be the mtv collision.norm = d.clone( result.closest.b ).vsub( tmp.clone( result.closest.a ) ).normalize().values(); collision.mtv = d.mult( overlap ).values(); // get a corresponding hull point for one of the core points.. relative to body A - collision.pos = d.clone( collision.norm ).mult( support.margin ).vadd( tmp.clone( result.closest.a ) ).vsub( bodyA.state.pos ).values(); + collision.pos = d.clone( collision.norm ).mult( support.marginA ).vadd( tmp.clone( result.closest.a ) ).vsub( bodyA.state.pos ).values(); } return scratch.done( collision ); @@ -190,7 +211,11 @@ ,collision = false ; - d.clone( bodyB.state.pos ).vsub( bodyA.state.pos ); + d.clone( bodyB.state.pos ) + .vadd( bodyB.getGlobalOffset( tmp ) ) + .vsub( bodyA.state.pos ) + .vsub( bodyA.getGlobalOffset( tmp ) ) // save offset for later + ; overlap = d.norm() - (bodyA.geometry.radius + bodyB.geometry.radius); // hmm... they overlap exactly... choose a direction @@ -199,12 +224,6 @@ d.set( 1, 0 ); } - // if ( overlap > 0 ){ - // // check the future - // d.vadd( tmp.clone(bodyB.state.vel).mult( dt ) ).vsub( tmp.clone(bodyA.state.vel).mult( dt ) ); - // overlap = d.norm() - (bodyA.geometry.radius + bodyB.geometry.radius); - // } - if ( overlap <= 0 ){ collision = { @@ -212,7 +231,7 @@ bodyB: bodyB, norm: d.normalize().values(), mtv: d.mult( -overlap ).values(), - pos: d.normalize().mult( bodyA.geometry.radius ).values(), + pos: d.mult( -bodyA.geometry.radius/overlap ).vadd( tmp ).values(), overlap: -overlap }; } @@ -221,7 +240,7 @@ }; /* - * checkPair( bodyA, bodyB ) -> Object + * checkPair( bodyA, bodyB[, disp] ) -> Object * - bodyA (Object): First body * - bodyB (Object): Second body * + (Object): Collision result @@ -242,6 +261,68 @@ return checkCircles( bodyA, bodyB ); + } else if ( bodyA.geometry.name === 'compound' || bodyB.geometry.name === 'compound' ){ + // compound bodies are special. We can't use gjk because + // they could have concavities. so we do the pieces individually + var test = (bodyA.geometry.name === 'compound') + ,compound = test ? bodyA : bodyB + ,other = test ? bodyB : bodyA + ,cols + ,ch + ,ret = [] + ,scratch = Physics.scratchpad() + ,vec = scratch.vector() + ,oldPos = scratch.vector() + ,otherAABB = other.aabb() + ,i + ,l + ; + + for ( i = 0, l = compound.children.length; i < l; i++ ){ + + ch = compound.children[ i ]; + // move body to fake position + oldPos.clone( ch.state.pos ); + ch.offset.vadd( oldPos.vadd( compound.offset ).rotate( -ch.state.angular.pos ) ); + ch.state.pos.clone( compound.state.pos ); + ch.state.angular.pos += compound.state.angular.pos; + + // check it if the aabbs overlap + if ( Physics.aabb.overlap(otherAABB, ch.aabb()) ){ + + cols = checkPair( other, ch ); + + if ( cols instanceof Array ){ + for ( var j = 0, c, ll = cols.length; j < ll; j++ ){ + c = cols[j]; + // set body to be the compound body + if ( c.bodyA === ch ){ + c.bodyA = compound; + } else { + c.bodyB = compound; + } + ret.push( c ); + } + + } else if ( cols ) { + // set body to be the compound body + if ( cols.bodyA === ch ){ + cols.bodyA = compound; + } else { + cols.bodyB = compound; + } + ret.push( cols ); + } + } + + // transform it back + ch.state.angular.pos -= compound.state.angular.pos; + ch.offset.vsub( oldPos ); + ch.state.pos.clone( oldPos.rotate( ch.state.angular.pos ).vsub( compound.offset ) ); + } + + return scratch.done( ret ); + } else { return checkGJK( bodyA, bodyB ); @@ -286,11 +367,11 @@ if ( this.options.check === true ){ - world.off( 'integrate:velocities', this.checkAll ); + world.off( 'integrate:velocities', this.checkAll, this ); } else { - world.off( this.options.check, this.check ); + world.off( this.options.check, this.check, this ); } }, @@ -307,6 +388,10 @@ ,targets = this.getTargets() ,collisions = [] ,ret + ,prevContacts = this.prevContacts || {} + ,contactList = {} + ,pairHash = Physics.util.pairHash + ,hash ; for ( var i = 0, l = candidates.length; i < l; ++i ){ @@ -320,12 +405,30 @@ ){ ret = checkPair( pair.bodyA, pair.bodyB ); - if ( ret ){ + if ( ret instanceof Array ){ + + for ( var j = 0, r, ll = ret.length; j < ll; j++ ){ + r = ret[j]; + if ( r ){ + hash = pairHash( pair.bodyA.uid, pair.bodyB.uid ); + contactList[ hash ] = true; + r.collidedPreviously = prevContacts[ hash ]; + collisions.push( r ); + } + } + + } else if ( ret ){ + hash = pairHash( pair.bodyA.uid, pair.bodyB.uid ); + contactList[ hash ] = true; + ret.collidedPreviously = prevContacts[ hash ]; + collisions.push( ret ); } } } + this.prevContacts = contactList; + if ( collisions.length ){ this._world.emit( this.options.channel, { @@ -348,6 +451,10 @@ ,bodyB ,collisions = [] ,ret + ,prevContacts = this.prevContacts || {} + ,contactList = {} + ,pairHash = Physics.util.pairHash + ,hash ; for ( var j = 0, l = bodies.length; j < l; j++ ){ @@ -360,12 +467,30 @@ ret = checkPair( bodyA, bodyB ); - if ( ret ){ + if ( ret instanceof Array ){ + + for ( var k = 0, r, ll = ret.length; k < ll; k++ ){ + r = ret[k]; + if ( r ){ + hash = pairHash( bodyA.uid, bodyB.uid ); + contactList[ hash ] = true; + r.collidedPreviously = prevContacts[ hash ]; + collisions.push( r ); + } + } + + } else if ( ret ){ + hash = pairHash( bodyA.uid, bodyB.uid ); + contactList[ hash ] = true; + ret.collidedPreviously = prevContacts[ hash ]; + collisions.push( ret ); } } } + this.prevContacts = contactList; + if ( collisions.length ){ this._world.emit( this.options.channel, { diff --git a/dist/behaviors/body-impulse-response.js b/dist/behaviors/body-impulse-response.js index 641df871..63f1d8d1 100644 --- a/dist/behaviors/body-impulse-response.js +++ b/dist/behaviors/body-impulse-response.js @@ -1,5 +1,5 @@ /** - * PhysicsJS v0.6.0 - 2014-04-22 + * PhysicsJS v0.7.0 - 2014-12-08 * A modular, extendable, and easy-to-use physics engine for javascript * http://wellcaffeinated.net/PhysicsJS * @@ -25,14 +25,46 @@ * * Additional options include: * - check: channel to listen to for collisions (default: `collisions:detected`). + * - mtvThreshold: apply partial extraction of bodies if the minimum transit vector is less than this value ( default: `1`) + * this will depend on your simulation characteristic length scale + * - bodyExtractDropoff: every body overlap correction (underneith mtvThreshold) will only extract by this fraction (0..1). Helps with stablizing contacts. (default: `0.5`) + * - forceWakeupAboveOverlapThreshold: force bodies to wake up if the overlap is above mtvThreshold ( default: `true` ) **/ Physics.behavior('body-impulse-response', function( parent ){ var defaults = { // channel to listen to for collisions check: 'collisions:detected' + // apply partial extraction of bodies if the minimum transit vector is less than this value + // this will depend on your simulation characteristic length scale + ,mtvThreshold: 1 + // every body overlap correction (underneith mtvThreshold) will only extract by this fraction (0..1) + // helps with stablizing contacts. + ,bodyExtractDropoff: 0.5 + // force bodies to wake up if the overlap is above mtvThreshold + ,forceWakeupAboveOverlapThreshold: true }; + function getUid( b ){ + return b.uid; + } + + function clampMTV( totalV, mtv, into ){ + + var m, n; + n = mtv.norm(); + m = n - totalV.proj( mtv ); + m = Math.max( 0, Math.min( n, m ) ); + + if ( n === 0 ){ + into.zero(); + } else { + into.clone( mtv ).mult( m/n ); + } + + return into; + } + return { // extended @@ -41,6 +73,8 @@ parent.init.call( this ); this.options.defaults( defaults ); this.options( options ); + + this._bodyList = []; }, // no applyTo method @@ -55,7 +89,7 @@ // extended disconnect: function( world ){ - world.off( this.options.check, this.respond ); + world.off( this.options.check, this.respond, this ); }, /** internal @@ -84,24 +118,6 @@ return; } - if ( fixedA ){ - - // extract bodies - bodyB.state.pos.vadd( mtv ); - - } else if ( fixedB ){ - - // extract bodies - bodyA.state.pos.vsub( mtv ); - - } else { - - // extract bodies - mtv.mult( 0.5 ); - bodyA.state.pos.vsub( mtv ); - bodyB.state.pos.vadd( mtv ); - } - // inverse masses and moments of inertia. // give fixed bodies infinite mass and moi var invMoiA = fixedA ? 0 : 1 / bodyA.moi @@ -109,18 +125,20 @@ ,invMassA = fixedA ? 0 : 1 / bodyA.mass ,invMassB = fixedB ? 0 : 1 / bodyB.mass // coefficient of restitution between bodies - ,cor = contact ? 0 : bodyA.restitution * bodyB.restitution + ,cor = bodyA.restitution * bodyB.restitution // coefficient of friction between bodies ,cof = bodyA.cof * bodyB.cof // normal vector ,n = scratch.vector().clone( normal ) // vector perpendicular to n ,perp = scratch.vector().clone( n ).perp() + ,tmp = scratch.vector() // collision point from A's center ,rA = scratch.vector().clone( point ) // collision point from B's center - ,rB = scratch.vector().clone( point ).vadd( bodyA.state.pos ).vsub( bodyB.state.pos ) - ,tmp = scratch.vector() + ,rB = scratch.vector().clone( point ) + .vadd( bodyA.state.pos ) + .vsub( bodyB.state.pos ) ,angVelA = bodyA.state.angular.vel ,angVelB = bodyB.state.angular.vel // relative velocity towards B at collision point @@ -138,9 +156,37 @@ ,impulse ,sign ,max - ,inContact = false + ,ratio + ,inContact = contact ; + if ( contact ){ + + if ( fixedA ){ + + clampMTV( bodyB._mtvTotal, mtv, tmp ); + bodyB._mtvTotal.vadd( tmp ); + + } else if ( fixedB ){ + + clampMTV( bodyA._mtvTotal, mtv.negate(), tmp ); + bodyA._mtvTotal.vadd( tmp ); + mtv.negate(); + + } else { + + ratio = 0.5; //bodyA.mass / ( bodyA.mass + bodyB.mass ); + mtv.mult( ratio ); + clampMTV( bodyB._mtvTotal, mtv, tmp ); + bodyB._mtvTotal.vadd( tmp ); + + mtv.clone( mtrans ).mult( ratio - 1 ); + clampMTV( bodyA._mtvTotal, mtv, tmp ); + bodyA._mtvTotal.vadd( tmp ); + + } + } + // if moving away from each other... don't bother. if (vproj >= 0){ scratch.done(); @@ -193,21 +239,15 @@ // allowed amount // maximum impulse allowed by kinetic friction - max = vreg / ( invMassA + invMassB + (invMoiA * rAproj * rAproj) + (invMoiB * rBproj * rBproj) ); - - if (!inContact){ - // the sign of vreg ( plus or minus 1 ) - sign = vreg < 0 ? -1 : 1; - - // get impulse due to friction - impulse *= sign * cof; - // make sure the impulse isn't giving the system energy - impulse = (sign === 1) ? Math.min( impulse, max ) : Math.max( impulse, max ); + max = Math.abs(vreg) / ( invMassA + invMassB + (invMoiA * rAproj * rAproj) + (invMoiB * rBproj * rBproj) ); + // the sign of vreg ( plus or minus 1 ) + sign = vreg < 0 ? -1 : 1; - } else { - - impulse = max; - } + // get impulse due to friction + impulse = cof * Math.abs( impulse ); + // constrain the impulse within the "friction cone" ( max < mu * impulse) + impulse = Math.min( impulse, max ); + impulse *= sign; if ( fixedA ){ @@ -231,9 +271,25 @@ } } + // wake up bodies if necessary + if ( bodyA.sleep() ){ + bodyA.sleepCheck(); + } + if ( bodyB.sleep() ){ + bodyB.sleepCheck(); + } + scratch.done(); }, + // internal + _pushUniq: function( body ){ + var idx = Physics.util.sortedIndex( this._bodyList, body, getUid ); + if ( this._bodyList[ idx ] !== body ){ + this._bodyList.splice( idx, 0, body ); + } + }, + /** internal * BodyImpulseResponseBehavior#respond( data ) * - data (Object): event data @@ -244,20 +300,49 @@ var self = this ,col - ,collisions = Physics.util.shuffle(data.collisions) + ,collisions = data.collisions// Physics.util.shuffle(data.collisions) + ,i,l,b ; - for ( var i = 0, l = collisions.length; i < l; ++i ){ + for ( i = 0, l = collisions.length; i < l; ++i ){ col = collisions[ i ]; + // add bodies to list for later + this._pushUniq( col.bodyA ); + this._pushUniq( col.bodyB ); + // ensure they have mtv stat vectors + col.bodyA._mtvTotal = col.bodyA._mtvTotal || new Physics.vector(); + col.bodyB._mtvTotal = col.bodyB._mtvTotal || new Physics.vector(); + col.bodyA._oldmtvTotal = col.bodyA._oldmtvTotal || new Physics.vector(); + col.bodyB._oldmtvTotal = col.bodyB._oldmtvTotal || new Physics.vector(); + self.collideBodies( col.bodyA, col.bodyB, col.norm, col.pos, - col.mtv + col.mtv, + col.collidedPreviously ); } + + // apply mtv vectors from the average mtv vector + for ( i = 0, l = this._bodyList.length; i < l; ++i ){ + b = this._bodyList.pop(); + // clampMTV( b._oldmtvTotal, b._mtvTotal, b._mtvTotal ); + + if ( b._mtvTotal.normSq() < this.options.mtvThreshold ){ + b._mtvTotal.mult( this.options.bodyExtractDropoff ); + } else if ( this.options.forceWakeupAboveOverlapThreshold ) { + // wake up bodies if necessary + b.sleep( false ); + } + + b.state.pos.vadd( b._mtvTotal ); + b.state.old.pos.vadd( b._mtvTotal ); + b._oldmtvTotal.swap( b._mtvTotal ); + b._mtvTotal.zero(); + } } }; }); diff --git a/dist/behaviors/constant-acceleration.js b/dist/behaviors/constant-acceleration.js index 49bd59c3..2a453116 100644 --- a/dist/behaviors/constant-acceleration.js +++ b/dist/behaviors/constant-acceleration.js @@ -1,5 +1,5 @@ /** - * PhysicsJS v0.6.0 - 2014-04-22 + * PhysicsJS v0.7.0 - 2014-12-08 * A modular, extendable, and easy-to-use physics engine for javascript * http://wellcaffeinated.net/PhysicsJS * @@ -45,7 +45,7 @@ this.options( options ); // extend options - this._acc = Physics.vector(); + this._acc = new Physics.vector(); this.setAcceleration( this.options.acc ); delete this.options.acc; }, diff --git a/dist/behaviors/edge-collision-detection.js b/dist/behaviors/edge-collision-detection.js index c29cacd7..5d774a8f 100644 --- a/dist/behaviors/edge-collision-detection.js +++ b/dist/behaviors/edge-collision-detection.js @@ -1,5 +1,5 @@ /** - * PhysicsJS v0.6.0 - 2014-04-22 + * PhysicsJS v0.7.0 - 2014-12-08 * A modular, extendable, and easy-to-use physics engine for javascript * http://wellcaffeinated.net/PhysicsJS * @@ -16,7 +16,7 @@ } }(this, function (Physics) { 'use strict'; - /** + /** * class EdgeCollisionDetectionBehavior < Behavior * * `Physics.behavior('edge-collision-detection')`. @@ -37,7 +37,7 @@ * - bounds (Physics.aabb): The boundary * - dummy: (Body): The dummy body to publish as the static other body it collides with * + (Array): The collision data - * + * * Check if a body collides with the boundary */ var checkGeneral = function checkGeneral( body, bounds, dummy ){ @@ -45,6 +45,7 @@ var overlap ,aabb = body.aabb() ,scratch = Physics.scratchpad() + ,offset = body.getGlobalOffset( scratch.vector() ) ,trans = scratch.transform() ,dir = scratch.vector() ,result = scratch.vector() @@ -71,7 +72,7 @@ x: overlap, y: 0 }, - pos: body.geometry.getFarthestHullPoint( dir, result ).rotate( trans ).values() + pos: body.geometry.getFarthestHullPoint( dir, result ).rotate( trans ).vadd( offset ).values() }; collisions.push(collision); @@ -96,7 +97,7 @@ x: 0, y: overlap }, - pos: body.geometry.getFarthestHullPoint( dir, result ).rotate( trans ).values() + pos: body.geometry.getFarthestHullPoint( dir, result ).rotate( trans ).vadd( offset ).values() }; collisions.push(collision); @@ -121,7 +122,7 @@ x: -overlap, y: 0 }, - pos: body.geometry.getFarthestHullPoint( dir, result ).rotate( trans ).values() + pos: body.geometry.getFarthestHullPoint( dir, result ).rotate( trans ).vadd( offset ).values() }; collisions.push(collision); @@ -146,7 +147,7 @@ x: 0, y: -overlap }, - pos: body.geometry.getFarthestHullPoint( dir, result ).rotate( trans ).values() + pos: body.geometry.getFarthestHullPoint( dir, result ).rotate( trans ).vadd( offset ).values() }; collisions.push(collision); @@ -162,7 +163,7 @@ * - bounds (Physics.aabb): The boundary * - dummy: (Body): The dummy body to publish as the static other body it collides with * + (Array): The collision data - * + * * Check if a body collides with the boundary */ var checkEdgeCollide = function checkEdgeCollide( body, bounds, dummy ){ @@ -189,8 +190,8 @@ this.setAABB( this.options.aabb ); this.restitution = this.options.restitution; - - this.body = Physics.body('point', { + + this.body = Physics.body('point', { treatment: 'static', restitution: this.options.restitution, cof: this.options.cof @@ -200,7 +201,7 @@ /** * EdgeCollisionDetectionBehavior#setAABB( aabb ) -> this * - aabb (Physics.aabb): The aabb to use as the boundary - * + * * Set the boundaries of the edge. **/ setAABB: function( aabb ){ @@ -216,7 +217,7 @@ }, max: { x: (aabb.x + aabb.hw), - y: (aabb.y + aabb.hh) + y: (aabb.y + aabb.hh) } }; @@ -226,23 +227,23 @@ // extended connect: function( world ){ - world.on( 'integrate:velocities', this.checkAll, this ); + world.on( 'integrate:positions', this.checkAll, this, 2 ); }, // extended disconnect: function( world ){ - world.off( 'integrate:velocities', this.checkAll ); + world.off( 'integrate:positions', this.checkAll, this, 2 ); }, /** internal * EdgeCollisionDetectionBehavior#checkAll( data ) * - data (Object): Event data - * + * * Event callback to check all bodies for collisions with the edge **/ checkAll: function( data ){ - + var bodies = this.getTargets() ,dt = data.dt ,body @@ -250,6 +251,10 @@ ,ret ,bounds = this._edges ,dummy = this.body + ,prevContacts = this.prevContacts || {} + ,contactList = {} + ,pairHash = Physics.util.pairHash + ,hash ; for ( var i = 0, l = bodies.length; i < l; i++ ){ @@ -258,15 +263,24 @@ // only detect dynamic bodies if ( body.treatment === 'dynamic' ){ - + ret = checkEdgeCollide( body, bounds, dummy ); if ( ret ){ + hash = pairHash( body.uid, dummy.uid ); + + for ( var j = 0, ll = ret.length; j < ll; j++ ){ + contactList[ hash ] = true; + ret[ j ].collidedPreviously = prevContacts[ hash ]; + } + collisions.push.apply( collisions, ret ); } } } + this.prevContacts = contactList; + if ( collisions.length ){ this._world.emit( this.options.channel, { @@ -277,6 +291,7 @@ }; }); + // end module: behaviors/edge-collision-detection.js return Physics; }));// UMD \ No newline at end of file diff --git a/dist/behaviors/interactive.js b/dist/behaviors/interactive.js index 7f2ea26f..2d9ca6eb 100644 --- a/dist/behaviors/interactive.js +++ b/dist/behaviors/interactive.js @@ -1,5 +1,5 @@ /** - * PhysicsJS v0.6.0 - 2014-04-22 + * PhysicsJS v0.7.0 - 2014-12-08 * A modular, extendable, and easy-to-use physics engine for javascript * http://wellcaffeinated.net/PhysicsJS * @@ -23,7 +23,7 @@ * * User interaction helper. * - * Used to get mouse/touch events and add a mouse grab interaction. + * Used to get mouse/touch events and add grab interactions. * * Additional options include: * - el: The element of the renderer. What you input as the `el` for the renderer. @@ -44,17 +44,21 @@ * data.x; // the x coord * data.y; // the y coord * }); + * // when a mouse or pointer moves * world.on('interact:move', function( data ){ * data.x; // the x coord * data.y; // the y coord - * data.body; // the body that was grabbed (if applicable) + * data.body; // the grabbed body that was moved (if applicable) * }); * // when the viewport is released (mouseup, touchend) * world.on('interact:release', function( data ){ * data.x; // the x coord * data.y; // the y coord + * data.body; // the body that was grabbed (if applicable) * }); * ``` + * + * The behavior also sets body.isGrabbed = true for any grabbed bodies while they are grabbed. **/ Physics.behavior('interactive', function( parent ){ @@ -87,28 +91,13 @@ return { left: curleft, top: curtop }; } - ,getCoords = function( e ){ - var offset = getElementOffset( e.target ) - ,obj = ( e.changedTouches && e.changedTouches[0] ) || e - ,x = obj.pageX - offset.left - ,y = obj.pageY - offset.top - ; - - return { - x: x - ,y: y - }; - } ; return { // extended init: function( options ){ - var self = this - ,prevTreatment - ,time - ; + var self = this; // call parent init method parent.init.call( this ); @@ -116,9 +105,8 @@ this.options( options ); // vars - this.mousePos = new Physics.vector(); - this.mousePosOld = new Physics.vector(); - this.offset = new Physics.vector(); + this.bodyData = {}; + this.bodyDataByUID = {}; this.el = typeof this.options.el === 'string' ? document.getElementById(this.options.el) : this.options.el; @@ -127,90 +115,183 @@ } // init events - var grab = function grab( e ){ - var pos = getCoords( e ) + // when there are multiple touchdowns, grab is usually called separately for each, + // but we loop through e.changedTouches just in case + self.grab = function grab( e ){ + var pos ,body + ,touchId + ,touch + ,offset + ,data + ,touchIndex + ,l ; if ( self._world ){ - body = self._world.findOne({ $at: new Physics.vector( pos.x, pos.y ) }); - - if ( body ){ - // we're trying to grab a body - // fix the body in place - prevTreatment = body.treatment; - body.treatment = 'kinematic'; - body.state.vel.zero(); - body.state.angular.vel = 0; - // remember the currently grabbed body - self.body = body; - // remember the mouse offset - self.mousePos.clone( pos ); - self.offset.clone( pos ).vsub( body.state.pos ); - - pos.body = body; - self._world.emit('interact:grab', pos); - - } else { + // Adjust for PointerEvent and older browsers + if ( !e.changedTouches ) { + e.changedTouches = [ e ]; + } - self._world.emit('interact:poke', pos); + offset = getElementOffset( e.target ); + + for ( touchIndex = 0, l = e.changedTouches.length; touchIndex < l; touchIndex++) { + touch = e.changedTouches[touchIndex]; + touchId = touch.identifier || touch.pointerId || "mouse"; + pos = { idx: touchId, x: touch.pageX - offset.left, y: touch.pageY - offset.top }; + body = self._world.findOne({ $at: new Physics.vector( pos ), $in: self.getTargets() }); + + if ( body ){ + // we're trying to grab a body + + // fix the body in place + body.state.vel.zero(); + body.state.angular.vel = 0; + body.isGrabbed = true; + // remember the currently grabbed bodies + data = self.bodyData[touchId] || {}; + data.body = body; + // wake the body up + body.sleep( false ); + data.time = Physics.util.ticker.now(); + + // if we're grabbing the same body twice we don't want to remember the wrong treatment. + data.treatment = self.bodyDataByUID[ body.uid ] ? self.bodyDataByUID[ body.uid ].treatment : body.treatment; + // change its treatment but remember its old treatment + body.treatment = 'kinematic'; + // remember the click/touch offset + data.pos = data.pos || new Physics.vector(); + data.pos.clone( pos ); + + data.offset = data.offset || new Physics.vector(); + data.offset.clone( pos ).vsub( body.state.pos ); + // init touchPointsOld here, too, so we don't have to do it in "move" + data.oldPos = data.oldPos || new Physics.vector(); + data.oldPos.clone( pos ); + + pos.body = body; + self.bodyData[touchId] = data; + self.bodyDataByUID[ body.uid ] = data; + self._world.emit('interact:grab', pos); + + } else { + + self._world.emit('interact:poke', pos); + } } } }; - var move = Physics.util.throttle(function move( e ){ - var pos = getCoords( e ) + // when there are multiple touchdowns, move is called once + // and e.changedTouches will have one or more touches in it + self.move = Physics.util.throttle(function move( e ){ + var pos ,state + ,body + ,touchId + ,touch + ,offset + ,data + ,touchIndex + ,l ; - if ( self.body ){ - time = Physics.util.ticker.now(); + if ( self._world ){ + + // Adjust for PointerEvent and older browsers + if ( !e.changedTouches ) { + e.changedTouches = [ e ]; + } - self.mousePosOld.clone( self.mousePos ); - // get new mouse position - self.mousePos.set(pos.x, pos.y); + offset = getElementOffset( self.el ); - pos.body = self.body; - } + for ( touchIndex = 0, l = e.changedTouches.length; touchIndex < l; touchIndex++) { + touch = e.changedTouches[touchIndex]; + touchId = touch.identifier || touch.pointerId || "mouse"; + pos = { idx: touchId, x: touch.pageX - offset.left, y: touch.pageY - offset.top }; + data = self.bodyData[touchId]; + + if ( data ){ + body = data.body; - self._world.emit('interact:move', pos); + // wake the body up + body.sleep( false ); + data.time = Physics.util.ticker.now(); + + // set old mouse position + data.oldPos.clone( data.pos ); + // get new mouse position + data.pos.clone( pos ); + + pos.body = body; + } + + self._world.emit('interact:move', pos); + } + } }, self.options.moveThrottle); - var release = function release( e ){ - var pos = getCoords( e ) + // when there are multiple touchups, release is called once + // and e.changedTouches will have one or more touches in it + self.release = function release( e ){ + var pos ,body - ,dt = Math.max(Physics.util.ticker.now() - time, self.options.moveThrottle) + ,touchId + ,touch + ,offset + ,data + ,dt + ,touchIndex + ,l ; - // get new mouse position - self.mousePos.set(pos.x, pos.y); - - // release the body - if (self.body){ - self.body.treatment = prevTreatment; - // calculate the release velocity - self.body.state.vel.clone( self.mousePos ).vsub( self.mousePosOld ).mult( 1 / dt ); - // make sure it's not too big - self.body.state.vel.clamp( self.options.minVel, self.options.maxVel ); - self.body = false; - } - if ( self._world ){ - self._world.emit('interact:release', pos); + // Adjust for PointerEvent and older browsers + if ( !e.changedTouches ) { + e.changedTouches = [ e ]; + } + + for ( touchIndex = 0, l = e.changedTouches.length; touchIndex < l; touchIndex++) { + offset = getElementOffset( self.el ); + touch = e.changedTouches[touchIndex]; + touchId = touch.identifier || touch.pointerId || "mouse"; + pos = { idx: touchId, x: touch.pageX - offset.left, y: touch.pageY - offset.top }; + data = self.bodyData[touchId]; + + // release the body + if ( data ){ + body = data.body; + // wake the body up + body.sleep( false ); + // get new mouse position + data.pos.clone( pos ); + + dt = Math.max(Physics.util.ticker.now() - data.time, self.options.moveThrottle); + body.treatment = data.treatment; + // calculate the release velocity + body.state.vel.clone( data.pos ).vsub( data.oldPos ).mult( 1 / dt ); + // make sure it's not too big + body.state.vel.clamp( self.options.minVel, self.options.maxVel ); + + body.isGrabbed = false; + pos.body = body; + + delete body.isGrabbed; + } + + // emit before we delete the vars in case + // the listeners need the body + self._world.emit('interact:release', pos); + + // remove vars + delete self.bodyData[touchId]; + } } }; - - this.el.addEventListener('mousedown', grab); - this.el.addEventListener('touchstart', grab); - - this.el.addEventListener('mousemove', move); - this.el.addEventListener('touchmove', move); - - this.el.addEventListener('mouseup', release); - this.el.addEventListener('touchend', release); }, // extended @@ -218,13 +299,51 @@ // subscribe the .behave() method to the position integration step world.on('integrate:positions', this.behave, this); + + if ( window.PointerEvent ) { + + this.el.addEventListener('pointerdown', this.grab); + window.addEventListener('pointermove', this.move); + window.addEventListener('pointerup', this.release); + + } else { + + this.el.addEventListener('mousedown', this.grab); + this.el.addEventListener('touchstart', this.grab); + + window.addEventListener('mousemove', this.move); + window.addEventListener('touchmove', this.move); + + window.addEventListener('mouseup', this.release); + window.addEventListener('touchend', this.release); + + } }, // extended disconnect: function( world ){ // unsubscribe when disconnected - world.off('integrate:positions', this.behave); + world.off('integrate:positions', this.behave, this); + + if ( window.PointerEvent ) { + + this.el.removeEventListener('pointerdown', this.grab); + window.removeEventListener('pointermove', this.move); + window.removeEventListener('pointerup', this.release); + + } else { + + this.el.removeEventListener('mousedown', this.grab); + this.el.removeEventListener('touchstart', this.grab); + + window.removeEventListener('mousemove', this.move); + window.removeEventListener('touchmove', this.move); + + window.removeEventListener('mouseup', this.release); + window.removeEventListener('touchend', this.release); + + } }, // extended @@ -233,14 +352,17 @@ var self = this ,state ,dt = Math.max(data.dt, self.options.moveThrottle) + ,body + ,d ; - if ( self.body ){ - - // if we have a body, we need to move it the the new mouse position. - // we'll do this by adjusting the velocity so it gets there at the next step - state = self.body.state; - state.vel.clone( self.mousePos ).vsub( self.offset ).vsub( state.pos ).mult( 1 / dt ); + // if we have one or more bodies grabbed, we need to move them to the new mouse/finger positions. + // we'll do this by adjusting the velocity so they get there at the next step + for ( var touchId in self.bodyData ) { + d = self.bodyData[touchId]; + body = d.body; + state = body.state; + state.vel.clone( d.pos ).vsub( d.offset ).vsub( state.pos ).mult( 1 / dt ); } } }; diff --git a/dist/behaviors/newtonian.js b/dist/behaviors/newtonian.js index 46219d39..748aca01 100644 --- a/dist/behaviors/newtonian.js +++ b/dist/behaviors/newtonian.js @@ -1,5 +1,5 @@ /** - * PhysicsJS v0.6.0 - 2014-04-22 + * PhysicsJS v0.7.0 - 2014-12-08 * A modular, extendable, and easy-to-use physics engine for javascript * http://wellcaffeinated.net/PhysicsJS * @@ -16,7 +16,7 @@ } }(this, function (Physics) { 'use strict'; - /** + /** * class NewtonianBehavior < Behavior * * `Physics.behavior('newtonian')`. @@ -54,42 +54,95 @@ }); this.options( options ); }, - + + calcPotential: function( posA, posB, out ){ + + var strength = this.options.strength + ,minDistSq = this._minDistSq + ,maxDistSq = this._maxDistSq + ,normsq + ,g + ,pos + ; + + pos = out || new Physics.vector(); + + // clone the position + pos.clone( posB ).vsub( posA ); + // get the square distance + normsq = pos.normSq(); + + if (normsq > minDistSq && normsq < maxDistSq){ + + g = strength / normsq; + return pos.normalize().mult( g ); + } + + return pos.zero(); + }, + // extended behave: function( data ){ var bodies = this.getTargets() ,body ,other - ,strength = this.options.strength - ,minDistSq = this._minDistSq - ,maxDistSq = this._maxDistSq ,scratch = Physics.scratchpad() - ,pos = scratch.vector() - ,normsq - ,g + ,potential = scratch.vector() + ,comp + ,bodyA + ,bodyB + ,posA = scratch.vector() + ,posB = scratch.vector() + ,i, j, k, m, l, ll, lll ; - for ( var j = 0, l = bodies.length; j < l; j++ ){ - + for ( j = 0, l = bodies.length; j < l; j++ ){ + body = bodies[ j ]; - for ( var i = j + 1; i < l; i++ ){ - + for ( i = j + 1; i < l; i++ ){ + other = bodies[ i ]; - // clone the position - pos.clone( other.state.pos ); - pos.vsub( body.state.pos ); - // get the square distance - normsq = pos.normSq(); - if (normsq > minDistSq && normsq < maxDistSq){ + if ( body.name === 'compound' ){ + comp = body; + } else if ( other.name === 'compound' ){ + comp = other; + other = body; + } + + if ( comp ){ + if ( other.name === 'compound' ){ + for ( k = 0, ll = comp.children.length; k < ll; k++ ){ + bodyA = comp.children[ k ]; + comp.toWorldCoords( posA.clone( bodyA.state.pos ).vadd( comp.offset ) ); + for ( m = 0, lll = other.children.length; m < lll; m++ ){ + bodyB = other.children[ m ]; + other.toWorldCoords( posB.clone( bodyB.state.pos ).vadd( other.offset ) ); + this.calcPotential( posA, posB, potential ); + comp.accelerate( potential.mult( bodyB.mass ) ); + other.accelerate( potential.mult( bodyA.mass/bodyB.mass ).negate() ); + } + } + } else { + for ( k = 0, ll = comp.children.length; k < ll; k++ ){ + bodyA = comp.children[ k ]; + comp.toWorldCoords( posA.clone( bodyA.state.pos ).vadd( comp.offset ) ); + this.calcPotential( posA, other.state.pos, potential ); + comp.accelerate( potential.mult( other.mass ) ); + other.accelerate( potential.mult( bodyA.mass/other.mass ).negate() ); + } + } - g = strength / normsq; + } else { - body.accelerate( pos.normalize().mult( g * other.mass ) ); - other.accelerate( pos.mult( body.mass/other.mass ).negate() ); + this.calcPotential( body.state.pos, other.state.pos, potential ); + body.accelerate( potential.mult( other.mass ) ); + other.accelerate( potential.mult( body.mass/other.mass ).negate() ); } + + comp = null; } } diff --git a/dist/behaviors/sweep-prune.js b/dist/behaviors/sweep-prune.js index 0b1a3a09..4076a3e5 100644 --- a/dist/behaviors/sweep-prune.js +++ b/dist/behaviors/sweep-prune.js @@ -1,5 +1,5 @@ /** - * PhysicsJS v0.6.0 - 2014-04-22 + * PhysicsJS v0.7.0 - 2014-12-08 * A modular, extendable, and easy-to-use physics engine for javascript * http://wellcaffeinated.net/PhysicsJS * @@ -85,7 +85,7 @@ world.on( 'add:body', this.trackBody, this ); world.on( 'remove:body', this.untrackBody, this ); - world.on( 'integrate:velocities', this.sweep, this ); + world.on( 'integrate:positions', this.sweep, this, 1 ); // add current bodies var bodies = world.getBodies(); @@ -98,9 +98,9 @@ // extended disconnect: function( world ){ - world.off( 'add:body', this.trackBody ); - world.off( 'remove:body', this.untrackBody ); - world.off( 'integrate:velocities', this.sweep ); + world.off( 'add:body', this.trackBody, this ); + world.off( 'remove:body', this.untrackBody, this ); + world.off( 'integrate:positions', this.sweep, this, 1 ); this.clear(); }, @@ -114,6 +114,11 @@ this.updateIntervals(); this.sortIntervalLists(); + + if ( this._world ){ + this._world.emit('sweep-prune:intervals', this.intervalLists); + } + return this.checkOverlaps(); }, @@ -214,6 +219,10 @@ }; } + if ( doCreate){ + c.flag = 1; + } + return c; }, @@ -282,7 +291,8 @@ ,candidates = this.candidates ; - encounters.length = candidates.length = 0; + Physics.util.clearArray( encounters ); + Physics.util.clearArray( candidates ); for ( var xyz = 0; xyz < maxDof; ++xyz ){ @@ -330,11 +340,7 @@ // if it's the x axis, create a pair c = this.getPair( tr1, tr2, isX ); - if ( c ){ - - if ( c.flag > collisionFlag ){ - c.flag = 1; - } + if ( c && c.flag < collisionFlag ){ // if it's greater than the axis index, set the flag // to = 0. @@ -377,10 +383,7 @@ var tr ,intr - ,scratch = Physics.scratchpad() - ,pos = scratch.vector() ,aabb - ,span = scratch.vector() ,list = this.tracked ,i = list.length ; @@ -390,17 +393,13 @@ tr = list[ i ]; intr = tr.interval; - pos.clone( tr.body.state.pos ); aabb = tr.body.aabb(); - span.set( aabb.hw, aabb.hh ); // copy the position (plus or minus) the aabb half-dimensions // into the min/max intervals - intr.min.val.clone( pos ).vsub( span ); - intr.max.val.clone( pos ).vadd( span ); + intr.min.val.clone( aabb ).sub( aabb.hw, aabb.hh ); + intr.max.val.clone( aabb ).add( aabb.hw, aabb.hh ); } - - scratch.done(); }, /** internal @@ -421,13 +420,13 @@ min: { type: false, //min - val: Physics.vector(), + val: new Physics.vector(), tracker: tracker }, max: { type: true, //max - val: Physics.vector(), + val: new Physics.vector(), tracker: tracker } } diff --git a/dist/behaviors/verlet-constraints.js b/dist/behaviors/verlet-constraints.js index 8b2eeb8e..3a2b643b 100644 --- a/dist/behaviors/verlet-constraints.js +++ b/dist/behaviors/verlet-constraints.js @@ -1,5 +1,5 @@ /** - * PhysicsJS v0.6.0 - 2014-04-22 + * PhysicsJS v0.7.0 - 2014-12-08 * A modular, extendable, and easy-to-use physics engine for javascript * http://wellcaffeinated.net/PhysicsJS * @@ -16,7 +16,7 @@ } }(this, function (Physics) { 'use strict'; - /** + /** * class VerletConstraintsBehavior < Behavior * * `Physics.behavior('verlet-constraints')`. @@ -67,12 +67,12 @@ // extended disconnect: function( world ){ - world.off('integrate:positions', this.resolve); + world.off('integrate:positions', this.resolve, this); }, /** * VerletConstraintsBehavior#drop() -> this - * + * * Remove all constraints **/ drop: function(){ @@ -90,7 +90,7 @@ * - stiffness (Number): A number between 0 and 1 that represents the stiffness of the constraint. Defaults to: `0.5` * - targetLength (Number): Target length. defaults to current distance between the bodies * + (Object): The constraint data object - * + * * Constrain two bodies to a target relative distance. * * Returns constraint data that can be used to remove the constraint later. @@ -133,7 +133,7 @@ * - stiffness (Number): A number between 0 and 1 that represents the stiffness of the constraint. Defaults to: `0.5` * - targetAngle (Number): Target angle. Defaults to the current angle between bodies * + (Object): The constraint data object - * + * * Constrain three bodies to a target relative angle * * Returns constraint data that can be used to remove the constraint later. @@ -172,7 +172,7 @@ * VerletConstraintsBehavior#remove( constraintId ) -> this * - constraintData (Object): The constraint data returned when creating a constraint * - constraintId (String): The constraint id - * + * * Remove a constraint **/ remove: function( cstrOrId ){ @@ -192,7 +192,7 @@ if ( isObj ){ for ( i = 0, l = constraints.length; i < l; ++i ){ - + if ( constraints[ i ] === cstrOrId ){ constraints.splice( i, 1 ); @@ -202,7 +202,7 @@ } else { for ( i = 0, l = constraints.length; i < l; ++i ){ - + if ( constraints[ i ].id === cstrOrId ){ constraints.splice( i, 1 ); @@ -217,7 +217,7 @@ /** internal * VerletConstraintsBehavior#resolveAngleConstraints( coef ) * - coef (Number): Coefficient for this resolution phase - * + * * Resolve angle constraints. **/ resolveAngleConstraints: function( coef ){ @@ -233,7 +233,7 @@ ; for ( var i = 0, l = constraints.length; i < l; ++i ){ - + con = constraints[ i ]; ang = con.bodyB.state.pos.angle2( con.bodyA.state.pos, con.bodyC.state.pos ); @@ -244,11 +244,11 @@ continue; } else if (corr <= -Math.PI){ - + corr += TWOPI; } else if (corr >= Math.PI){ - + corr -= TWOPI; } @@ -263,7 +263,7 @@ if ( con.bodyA.treatment === 'dynamic' ){ if ( con.bodyB.treatment === 'dynamic' && con.bodyC.treatment === 'dynamic' ){ - + ang = corr * (con.bodyB.mass + con.bodyC.mass) * invMassSum; } else if ( con.bodyB.treatment !== 'dynamic' ){ @@ -275,7 +275,6 @@ ang = corr * con.bodyB.mass / ( con.bodyB.mass + con.bodyA.mass ); } - // ang = corr; trans.setRotation( ang ); con.bodyA.state.pos.translateInv( trans ); @@ -286,20 +285,18 @@ if ( con.bodyC.treatment === 'dynamic' ){ if ( con.bodyA.treatment === 'dynamic' && con.bodyB.treatment === 'dynamic' ){ - + ang = -corr * (con.bodyB.mass + con.bodyA.mass) * invMassSum; } else if ( con.bodyB.treatment !== 'dynamic' ){ ang = -corr * con.bodyA.mass / ( con.bodyC.mass + con.bodyA.mass ); - + } else { ang = -corr * con.bodyB.mass / ( con.bodyB.mass + con.bodyC.mass ); } - // ang = -corr; - trans.setRotation( ang ); con.bodyC.state.pos.translateInv( trans ); con.bodyC.state.pos.rotate( trans ); @@ -309,13 +306,13 @@ if ( con.bodyB.treatment === 'dynamic' ){ if ( con.bodyA.treatment === 'dynamic' && con.bodyC.treatment === 'dynamic' ){ - + ang = corr * (con.bodyA.mass + con.bodyC.mass) * invMassSum; } else if ( con.bodyA.treatment !== 'dynamic' ){ ang = corr * con.bodyC.mass / ( con.bodyC.mass + con.bodyB.mass ); - + } else { ang = corr * con.bodyA.mass / ( con.bodyA.mass + con.bodyC.mass ); @@ -333,6 +330,10 @@ con.bodyB.state.pos.rotateInv( trans ); con.bodyB.state.pos.translate( trans ); } + + con.bodyA.sleepCheck(); + con.bodyB.sleepCheck(); + con.bodyC.sleepCheck(); } scratch.done(); @@ -341,7 +342,7 @@ /** internal * VerletConstraintsBehavior#resolveDistanceConstraints( coef ) * - coef (Number): Coefficient for this resolution phase - * + * * Resolve distance constraints. **/ resolveDistanceConstraints: function( coef ){ @@ -356,7 +357,7 @@ ; for ( var i = 0, l = constraints.length; i < l; ++i ){ - + con = constraints[ i ]; // move constrained bodies to target length based on their @@ -364,7 +365,7 @@ BA.clone( con.bodyB.state.pos ).vsub( con.bodyA.state.pos ); len = BA.normSq() || Math.random() * 0.0001; corr = coef * con.stiffness * ( len - con.targetLengthSq ) / len; - + BA.mult( corr ); proportion = (con.bodyA.treatment !== 'dynamic' || con.bodyB.treatment !== 'dynamic') ? 1 : con.bodyB.mass / (con.bodyA.mass + con.bodyB.mass); @@ -389,6 +390,9 @@ con.bodyB.state.pos.vsub( BA ); } + + con.bodyA.sleepCheck(); + con.bodyB.sleepCheck(); } scratch.done(); @@ -396,7 +400,7 @@ /** internal * VerletConstraintsBehavior#shuffleConstraints() - * + * * Mix up the constraints. **/ shuffleConstraints: function(){ @@ -407,7 +411,7 @@ /** internal * VerletConstraintsBehavior#resolve() - * + * * Resolve all constraints. **/ resolve: function(){ @@ -427,7 +431,7 @@ /** * VerletConstraintsBehavior#getConstraints() -> Object * + (Object): The object containing copied arrays of the constraints - * + * * Get all constraints. **/ getConstraints: function(){ diff --git a/dist/bodies/circle.js b/dist/bodies/circle.js index 98b156f6..d04f31ba 100644 --- a/dist/bodies/circle.js +++ b/dist/bodies/circle.js @@ -1,5 +1,5 @@ /** - * PhysicsJS v0.6.0 - 2014-04-22 + * PhysicsJS v0.7.0 - 2014-12-08 * A modular, extendable, and easy-to-use physics engine for javascript * http://wellcaffeinated.net/PhysicsJS * diff --git a/dist/bodies/compound.js b/dist/bodies/compound.js new file mode 100644 index 00000000..be742341 --- /dev/null +++ b/dist/bodies/compound.js @@ -0,0 +1,213 @@ +/** + * PhysicsJS v0.7.0 - 2014-12-08 + * A modular, extendable, and easy-to-use physics engine for javascript + * http://wellcaffeinated.net/PhysicsJS + * + * Copyright (c) 2014 Jasper Palfree + * Licensed MIT + */ +(function (root, factory) { + if (typeof define === 'function' && define.amd) { + define(['physicsjs','../geometries/compound'], factory); + } else if (typeof exports === 'object') { + module.exports = factory.apply(root, ['physicsjs','../geometries/compound'].map(require)); + } else { + factory.call(root, root.Physics); + } +}(this, function (Physics) { + 'use strict'; + /* + * @requires geometries/compound + */ + /** + * class CompoundBody < Body + * + * Physics.body('compound') + * + * Not a body in itself. It's a container to group other bodies. The position of the body is the center of mass. + * It must have at least one child before being added to the world. + * + * Additional config options: + * + * - children: Array of [[Body]] objects. + * + * Example: + * + * ```javascript + * var thing = Physics.body('compound', { + * // place the center of mass at (300, 200) + * x: 300, + * y: 200, + * // the center of mass is automatically calculated and used to position the shape + * children: [ + * body1, + * body2, + * // ... + * ] + * }); + * ``` + **/ + Physics.body('compound', function( parent ){ + + var defaults = { + + }; + + return { + + // extended + init: function( options ){ + + // call parent init method + parent.init.call(this, options); + + this.mass = 0; + this.moi = 0; + + this.children = []; + this.geometry = Physics.geometry('compound'); + this.addChildren( options.children ); + }, + + // extended + connect: function( world ){ + // sanity check + if ( this.mass <= 0 ){ + throw 'Can not add empty compound body to world.'; + } + }, + + /** + * CompoundBody#addChild( body ) -> this + * - body (Body): The child to add + * + * Add a body as a child. + **/ + addChild: function( body ){ + + this.addChildren([ body ]); + return this; + }, + + /** + * CompoundBody#addChildren( bodies ) -> this + * - bodies (Array): The list of children to add + * + * Add an array of children to the compound. + **/ + addChildren: function( bodies ){ + + var self = this + ,scratch = Physics.scratchpad() + ,com = scratch.vector().zero() + ,b + ,pos + ,i + ,l = bodies && bodies.length + ,M = 0 + ; + + if ( !l ){ + return scratch.done( this ); + } + + for ( i = 0; i < l; i++ ){ + b = bodies[ i ]; + // remove body from world if applicable + if ( b._world ){ + b._world.remove( b ); + } + // add child + this.children.push( b ); + // add child to geometry + this.geometry.addChild( + b.geometry, + new Physics.vector(b.offset) + .rotate(b.state.angular.pos) + .vadd(b.state.pos), + b.state.angular.pos + ); + // calc com contribution + pos = b.state.pos; + com.add( pos._[0] * b.mass, pos._[1] * b.mass ); + M += b.mass; + } + + // add mass + this.mass += M; + // com adjustment (assuming com is currently at (0,0) body coords) + com.mult( 1 / this.mass ); + + // shift the center of mass + this.offset.vsub( com ); + + // refresh view on next render + if ( this._world ){ + this._world.one('render', function(){ + self.view = null; + }); + } + this.recalc(); + + return scratch.done( this ); + }, + + /** + * CompoundBody#clear() -> this + * + * Remove all children. + **/ + clear: function(){ + + this._aabb = null; + this.moi = 0; + this.mass = 0; + this.offset.zero(); + this.children = []; + this.geometry.clear(); + + return this; + }, + + /** + * CompoundBody#refreshGeometry() -> this + * + * If the children's positions change, `refreshGeometry()` should be called to fix the shape. + **/ + refreshGeometry: function(){ + + this.geometry.clear(); + + for ( var i = 0, b, l = this.children.length; i < l; i++ ) { + b = this.children[ i ]; + this.geometry.addChild( b.geometry, new Physics.vector(b.state.pos).vadd(b.offset), b.state.angular.pos ); + } + + return this; + }, + + // extended + recalc: function(){ + + parent.recalc.call(this); + // moment of inertia + var b + ,moi = 0 + ; + + for ( var i = 0, l = this.children.length; i < l; i++ ) { + b = this.children[ i ]; + b.recalc(); + // parallel axis theorem + moi += b.moi + b.mass * b.state.pos.normSq(); + } + + this.moi = moi; + return this; + } + }; + }); + + // end module: bodies/compound.js + return Physics; +}));// UMD \ No newline at end of file diff --git a/dist/bodies/convex-polygon.js b/dist/bodies/convex-polygon.js index 3dde568b..a68736ea 100644 --- a/dist/bodies/convex-polygon.js +++ b/dist/bodies/convex-polygon.js @@ -1,5 +1,5 @@ /** - * PhysicsJS v0.6.0 - 2014-04-22 + * PhysicsJS v0.7.0 - 2014-12-08 * A modular, extendable, and easy-to-use physics engine for javascript * http://wellcaffeinated.net/PhysicsJS * @@ -19,7 +19,7 @@ /* * @requires geometries/convex-polygon */ - /** + /** * class ConvexPolygonBody < Body * * Physics.body('convex-polygon') @@ -27,7 +27,7 @@ * Body for convex polygons. The position of the body is the centroid of the polygon. * * Additional config options: - * + * * - vertices: Array of [[Vectorish]] objects representing the polygon vertices in clockwise (or counterclockwise) order. * * Example: @@ -51,7 +51,7 @@ Physics.body('convex-polygon', function( parent ){ var defaults = { - + }; return { diff --git a/dist/bodies/rectangle.js b/dist/bodies/rectangle.js index 7e0be6ea..22235652 100644 --- a/dist/bodies/rectangle.js +++ b/dist/bodies/rectangle.js @@ -1,5 +1,5 @@ /** - * PhysicsJS v0.6.0 - 2014-04-22 + * PhysicsJS v0.7.0 - 2014-12-08 * A modular, extendable, and easy-to-use physics engine for javascript * http://wellcaffeinated.net/PhysicsJS * diff --git a/dist/geometries/circle.js b/dist/geometries/circle.js index 20962223..6ff8d95e 100644 --- a/dist/geometries/circle.js +++ b/dist/geometries/circle.js @@ -1,5 +1,5 @@ /** - * PhysicsJS v0.6.0 - 2014-04-22 + * PhysicsJS v0.7.0 - 2014-12-08 * A modular, extendable, and easy-to-use physics engine for javascript * http://wellcaffeinated.net/PhysicsJS * @@ -80,7 +80,7 @@ // extended getFarthestHullPoint: function( dir, result ){ - result = result || Physics.vector(); + result = result || new Physics.vector(); return result.clone( dir ).normalize().mult( this.radius ); }, @@ -88,7 +88,7 @@ // extended getFarthestCorePoint: function( dir, result, margin ){ - result = result || Physics.vector(); + result = result || new Physics.vector(); // we can use the center of the circle as the core object // because we can project a point to the hull in any direction diff --git a/dist/geometries/compound.js b/dist/geometries/compound.js new file mode 100644 index 00000000..93589fc1 --- /dev/null +++ b/dist/geometries/compound.js @@ -0,0 +1,195 @@ +/** + * PhysicsJS v0.7.0 - 2014-12-08 + * A modular, extendable, and easy-to-use physics engine for javascript + * http://wellcaffeinated.net/PhysicsJS + * + * Copyright (c) 2014 Jasper Palfree + * Licensed MIT + */ +(function (root, factory) { + if (typeof define === 'function' && define.amd) { + define(['physicsjs'], factory); + } else if (typeof exports === 'object') { + module.exports = factory.apply(root, ['physicsjs'].map(require)); + } else { + factory.call(root, root.Physics); + } +}(this, function (Physics) { + 'use strict'; + /** + * class CompoundGeometry < Geometry + * + * Physics.geometry('compound') + * + * Geometry for compound shapes. + * + * Example: + * + * ```javascript + * var thing = Physics.geometry('compound'); + * thing.addChild( child, pos, rotation ); + * ``` + **/ + Physics.geometry('compound', function( parent ){ + + var defaults = { + + }; + + return { + + // extended + init: function( options ){ + + var self = this; + + // call parent init method + parent.init.call(this, options); + + this.options.defaults( defaults ); + this.options( options ); + + this.children = []; + }, + + /** + * CompoundGeometry#addChild( geometry, pos ) -> this + * - geometry (Geometry): The child to add. + * - pos (Physics.vector): The position to add the child at. + * - angle (Number): The rotation angle + * + * Add a child at relative position. + **/ + addChild: function( geometry, pos, angle ){ + + this._aabb = null; + this.children.push({ + g: geometry + ,pos: new Physics.vector( pos ) + ,angle: angle + }); + + return this; + }, + + /** + * CompoundGeometry#clear() -> this + * + * Remove all children. + **/ + clear: function(){ + + this._aabb = null; + this.children = []; + + return this; + }, + + // extended + aabb: function( angle ){ + + if (!angle && this._aabb){ + return Physics.aabb.clone( this._aabb ); + } + + var b + ,aabb + ,ch + ,ret + ,scratch = Physics.scratchpad() + ,pos = Physics.vector() + ; + + angle = angle || 0; + + for ( var i = 0, l = this.children.length; i < l; i++ ) { + ch = this.children[ i ]; + // the aabb rotated by overall angle and the child rotation + aabb = ch.g.aabb( angle + ch.angle ); + pos.clone( ch.pos ); + if ( angle ){ + // get the child's position rotated if needed + pos.rotate( angle ); + } + // move the aabb to the child's position + aabb.x += pos._[0]; + aabb.y += pos._[1]; + ret = ret ? Physics.aabb.union(ret, aabb, true) : aabb; + } + + if ( !angle ){ + // if we don't have an angle specified (or it's zero) + // then we can cache this result + this._aabb = Physics.aabb.clone( ret ); + } + + return scratch.done( ret ); + }, + + // extended + // NOTE: unlike other geometries this can't be used in the + // GJK algorithm because the shape isn't garanteed to be convex + getFarthestHullPoint: function( dir, result ){ + + var ch + ,i + ,l = this.children.length + ,scratch = Physics.scratchpad() + ,v = scratch.vector() + ,len = 0 + ,maxlen = 0 + ; + + result = result || new Physics.vector(); + + // find the one with the largest projection along dir + for ( i = 0; i < l; i++ ) { + ch = this.children[ i ]; + ch.g.getFarthestHullPoint( dir.rotate(-ch.angle), v ); + len = v.rotate(ch.angle).vadd( ch.pos ).proj( dir.rotate(ch.angle) ); + + if ( len > maxlen ){ + maxlen = len; + result.swap( v ); + } + } + + return scratch.done( result ); + }, + + // extended + // NOTE: unlike other geometries this can't be used in the + // GJK algorithm because the shape isn't garanteed to be convex + getFarthestCorePoint: function( dir, result, margin ){ + + var ch + ,i + ,l = this.children.length + ,scratch = Physics.scratchpad() + ,v = scratch.vector() + ,len = 0 + ,maxlen = 0 + ; + + result = result || new Physics.vector(); + + // find the one with the largest projection along dir + for ( i = 0; i < l; i++ ) { + ch = this.children[ i ]; + ch.g.getFarthestCorePoint(dir.rotate(-ch.angle), v, margin ); + len = v.rotate(ch.angle).vadd( ch.pos ).proj( dir.rotate(ch.angle) ); + + if ( len > maxlen ){ + maxlen = len; + result.swap( v ); + } + } + + return scratch.done( result ); + } + }; + }); + + // end module: geometries/compound.js + return Physics; +}));// UMD \ No newline at end of file diff --git a/dist/geometries/convex-polygon.js b/dist/geometries/convex-polygon.js index 300830bb..82859fb7 100644 --- a/dist/geometries/convex-polygon.js +++ b/dist/geometries/convex-polygon.js @@ -1,5 +1,5 @@ /** - * PhysicsJS v0.6.0 - 2014-04-22 + * PhysicsJS v0.7.0 - 2014-12-08 * A modular, extendable, and easy-to-use physics engine for javascript * http://wellcaffeinated.net/PhysicsJS * @@ -94,14 +94,12 @@ // then add the vertex as a vector to this.vertices for ( var i = 0, l = hull.length; i < l; ++i ){ - verts.push( Physics.vector( hull[ i ] ).translate( transl ) ); + verts.push( new Physics.vector( hull[ i ] ).translate( transl ) ); } this._area = Physics.geometry.getPolygonArea( verts ); - this._aabb = false; - scratch.done(); - return this; + return scratch.done(this); }, // extended @@ -146,7 +144,7 @@ ,idx ; - result = result || Physics.vector(); + result = result || new Physics.vector(); if ( l < 2 ){ if ( data ){ diff --git a/dist/geometries/rectangle.js b/dist/geometries/rectangle.js index 41844e9c..f10b93df 100644 --- a/dist/geometries/rectangle.js +++ b/dist/geometries/rectangle.js @@ -1,5 +1,5 @@ /** - * PhysicsJS v0.6.0 - 2014-04-22 + * PhysicsJS v0.7.0 - 2014-12-08 * A modular, extendable, and easy-to-use physics engine for javascript * http://wellcaffeinated.net/PhysicsJS * diff --git a/dist/integrators/improved-euler.js b/dist/integrators/improved-euler.js index e31dc1b1..5c308763 100644 --- a/dist/integrators/improved-euler.js +++ b/dist/integrators/improved-euler.js @@ -1,5 +1,5 @@ /** - * PhysicsJS v0.6.0 - 2014-04-22 + * PhysicsJS v0.7.0 - 2014-12-08 * A modular, extendable, and easy-to-use physics engine for javascript * http://wellcaffeinated.net/PhysicsJS * @@ -19,7 +19,7 @@ Physics.integrator('improved-euler', function( parent ){ return { - /** + /** * class ImprovedEuler < Integrator * * `Physics.integrator('improved-euler')`. @@ -33,7 +33,7 @@ // call parent init parent.init.call(this, options); }, - + // extended integrateVelocities: function( bodies, dt ){ @@ -49,15 +49,15 @@ state = body.state; // only integrate if the body isn't fixed - if ( body.treatment !== 'static' ){ + if ( body.treatment !== 'static' && !body.sleep( dt ) ){ // Inspired from https://github.com/soulwire/Coffee-Physics // @licence MIT - // + // // x += (v * dt) + (a * 0.5 * dt * dt) // v += a * dt - + // Scale force to mass. // state.acc.mult( body.massInv ); @@ -83,7 +83,7 @@ // // Angular components - // + // state.old.angular.vel = state.angular.vel; state.angular.vel += state.angular.acc * dt; @@ -119,7 +119,7 @@ state = body.state; // only integrate if the body isn't fixed - if ( body.treatment !== 'static' ){ + if ( body.treatment !== 'static' && !body.sleep() ){ // Store previous location. @@ -137,7 +137,7 @@ // // Angular components - // + // state.old.angular.pos = state.angular.pos; state.angular.pos += state.old.angular.vel * dt + state.old.angular.acc * halfdtdt; @@ -151,7 +151,6 @@ }; }); - // end module: integrators/improved-euler.js return Physics; }));// UMD \ No newline at end of file diff --git a/dist/integrators/velocity-verlet-alt.js b/dist/integrators/velocity-verlet-alt.js new file mode 100644 index 00000000..414aedf1 --- /dev/null +++ b/dist/integrators/velocity-verlet-alt.js @@ -0,0 +1,175 @@ +/** + * PhysicsJS v0.7.0 - 2014-12-08 + * A modular, extendable, and easy-to-use physics engine for javascript + * http://wellcaffeinated.net/PhysicsJS + * + * Copyright (c) 2014 Jasper Palfree + * Licensed MIT + */ +(function (root, factory) { + if (typeof define === 'function' && define.amd) { + define(['physicsjs'], factory); + } else if (typeof exports === 'object') { + module.exports = factory.apply(root, ['physicsjs'].map(require)); + } else { + factory.call(root, root.Physics); + } +}(this, function (Physics) { + 'use strict'; + Physics.integrator('velocity-verlet-alt', function( parent ){ + + // for this integrator we need to know if the object has been integrated before + // so let's add a mixin to bodies + + Physics.body.mixin({ + + started: function( val ){ + if ( val !== undefined ){ + this._started = true; + } + + return !!this._started; + } + }); + + + return { + /** + * class VelocityVerlet < Integrator + * + * `Physics.integrator('velocity-verlet')`. + * + * The velocity-verlet integrator. + **/ + + // extended + init: function( options ){ + + // call parent init + parent.init.call(this, options); + }, + + // extended + integrateVelocities: function( bodies, dt ){ + + // half the timestep + var dtdt = dt * dt + ,drag = 1 - this.options.drag + ,body = null + ,state + ; + + for ( var i = 0, l = bodies.length; i < l; ++i ){ + + body = bodies[ i ]; + state = body.state; + + // only integrate if the body isn't static + if ( body.treatment !== 'static' ){ + + // v = v_prev + 0.5 * (a_prev + a) * dt + // x = x_prev + v_prev * dt + 0.5 * a_prev * dt * dt + + // use the velocity in vel if the velocity has been changed manually + if ( !body.started() ){ + + // Set old vals on first integration + state.old.acc.clone( state.acc ); + state.old.acc.mult( dt ); + state.old.vel.clone( state.vel ).vsub( state.old.acc ); + state.old.acc.mult( 1/dt ); + } + + // Apply "air resistance". + if ( drag ){ + + state.vel.mult( drag ); + } + + // Apply acceleration + // v += 0.5 * (a_prev + a) * dt + state.vel.vadd( state.old.acc.vadd( state.acc ).mult( 0.5 * dt ) ); + + // Reset accel + // state.acc.zero(); + + // + // Angular components + // + + if ( !body.started() ){ + + // Set old vals on first integration + state.old.angular.acc = state.angular.acc; + state.old.angular.vel = state.angular.vel - state.old.angular.acc * dt; + } + + state.angular.vel += 0.5 * (state.angular.acc + state.old.angular.acc) * dt; + state.angular.acc = 0; + + body.started( true ); + + } else { + // set the velocity and acceleration to zero! + state.vel.zero(); + state.acc.zero(); + state.angular.vel = 0; + state.angular.acc = 0; + } + } + }, + + // extended + integratePositions: function( bodies, dt ){ + + // half the timestep + var dtdt = dt * dt + ,body = null + ,state + ; + + for ( var i = 0, l = bodies.length; i < l; ++i ){ + + body = bodies[ i ]; + state = body.state; + + // only integrate if the body isn't static + if ( body.treatment !== 'static' ){ + + // x = x_prev + v_prev * dt + 0.5 * a_prev * dt * dt + + // Store old position. + // xold = x + state.old.pos.clone( state.pos ); + + state.old.vel.mult( dt ); + state.old.acc.mult( 0.5 * dtdt ); + state.pos.vadd( state.old.vel ).vadd( state.old.acc ); + + // store calculated velocity + state.old.vel.clone( state.vel ); + + // store old acc + state.old.acc.clone( state.acc ); + + // Reset accel + state.acc.zero(); + + // + // Angular components + // + state.old.angular.pos = state.angular.pos; + + state.angular.pos += state.angular.vel * dt + 0.5 * state.old.angular.acc * dtdt; + state.old.angular.vel = state.angular.vel; + state.old.angular.acc = state.angular.acc; + state.angular.acc = 0; + } + } + } + }; + }); + + // end module: integrators/velocity-verlet-alt.js + return Physics; +}));// UMD \ No newline at end of file diff --git a/dist/integrators/velocity-verlet.js b/dist/integrators/velocity-verlet.js new file mode 100644 index 00000000..26d74947 --- /dev/null +++ b/dist/integrators/velocity-verlet.js @@ -0,0 +1,209 @@ +/** + * PhysicsJS v0.7.0 - 2014-12-08 + * A modular, extendable, and easy-to-use physics engine for javascript + * http://wellcaffeinated.net/PhysicsJS + * + * Copyright (c) 2014 Jasper Palfree + * Licensed MIT + */ +(function (root, factory) { + if (typeof define === 'function' && define.amd) { + define(['physicsjs'], factory); + } else if (typeof exports === 'object') { + module.exports = factory.apply(root, ['physicsjs'].map(require)); + } else { + factory.call(root, root.Physics); + } +}(this, function (Physics) { + 'use strict'; + Physics.integrator('velocity-verlet', function( parent ){ + + // for this integrator we need to know if the object has been integrated before + // so let's add a mixin to bodies + + Physics.body.mixin({ + + started: function( val ){ + if ( val !== undefined ){ + this._started = true; + } + + return !!this._started; + } + }); + + + return { + /** + * class VelocityVerlet < Integrator + * + * `Physics.integrator('velocity-verlet')`. + * + * The velocity-verlet integrator. + **/ + + // extended + init: function( options ){ + + // call parent init + parent.init.call(this, options); + }, + + /** + * Integrator#integrate( bodies, dt ) -> this + * - bodies (Array): List of bodies to integrate + * - dt (Number): Timestep size + * + * Integrate bodies by timestep. + * + * Will emit `integrate:velocities` and `integrate:positions` + * events on the world. + **/ + integrate: function( bodies, dt ){ + + var world = this._world; + + this.integratePositions( bodies, dt ); + + if ( world ){ + world.emit('integrate:positions', { + bodies: bodies, + dt: dt + }); + } + + this.integrateVelocities( bodies, dt ); + + if ( world ){ + world.emit('integrate:velocities', { + bodies: bodies, + dt: dt + }); + } + + return this; + }, + + // extended + integrateVelocities: function( bodies, dt ){ + + // half the timestep + var dtdt = dt * dt + ,drag = 1 - this.options.drag + ,body = null + ,state + ; + + for ( var i = 0, l = bodies.length; i < l; ++i ){ + + body = bodies[ i ]; + state = body.state; + + // only integrate if the body isn't static + if ( body.treatment !== 'static' && !body.sleep() ){ + + // v = v_prev + 0.5 * (a_prev + a) * dt + // x = x_prev + v_prev * dt + 0.5 * a_prev * dt * dt + + // Apply "air resistance". + if ( drag ){ + + state.vel.mult( drag ); + } + + // Apply acceleration + // v += 0.5 * (a_prev + a) * dt + state.old.vel.clone( state.vel ); + state.vel.vadd( state.old.acc.vadd( state.acc ).mult( 0.5 * dt ) ); + + // Reset accel + state.old.acc.clone( state.acc ); + state.acc.zero(); + + // + // Angular components + // + + state.old.angular.vel = state.angular.vel; + state.old.angular.acc = state.angular.acc; + + state.angular.vel += 0.5 * (state.angular.acc + state.old.angular.acc) * dt; + + state.angular.acc = 0; + + body.started( true ); + + } else { + // set the velocity and acceleration to zero! + state.vel.zero(); + state.acc.zero(); + state.angular.vel = 0; + state.angular.acc = 0; + } + } + }, + + // extended + integratePositions: function( bodies, dt ){ + + // half the timestep + var dtdt = dt * dt + ,body = null + ,state + ; + + for ( var i = 0, l = bodies.length; i < l; ++i ){ + + body = bodies[ i ]; + state = body.state; + + // only integrate if the body isn't static + if ( body.treatment !== 'static' && !body.sleep( dt ) ){ + + // x = x_prev + v_prev * dt + 0.5 * a_prev * dt * dt + + // use the velocity in vel if the velocity has been changed manually + if ( !body.started() ){ + + // Set old vals on first integration + state.old.acc.clone( state.acc ); + state.old.acc.mult( dt ); + state.old.vel.clone( state.vel ).vsub( state.old.acc ); + state.old.acc.mult( 1/dt ); + } + + // Store old position. + // xold = x + state.old.pos.clone( state.pos ); + + state.old.vel.mult( dt ); + state.old.acc.mult( 0.5 * dtdt ); + state.pos.vadd( state.old.vel ).vadd( state.old.acc ); + + // revert + state.old.vel.mult( 1/dt ); + state.old.acc.mult( 2 / dtdt ); + + // + // Angular components + // + + if ( !body.started() ){ + + // Set old vals on first integration + state.old.angular.acc = state.angular.acc; + state.old.angular.vel = state.angular.vel - state.old.angular.acc * dt; + } + + state.old.angular.pos = state.angular.pos; + + state.angular.pos += state.angular.vel * dt + 0.5 * state.old.angular.acc * dtdt; + } + } + } + }; + }); + + // end module: integrators/velocity-verlet.js + return Physics; +}));// UMD \ No newline at end of file diff --git a/dist/physicsjs-0.6.0.min.js b/dist/physicsjs-0.6.0.min.js deleted file mode 100644 index 6582c080..00000000 --- a/dist/physicsjs-0.6.0.min.js +++ /dev/null @@ -1,9 +0,0 @@ -/** - * PhysicsJS v0.6.0 - 2014-04-22 - * A modular, extendable, and easy-to-use physics engine for javascript - * http://wellcaffeinated.net/PhysicsJS - * - * Copyright (c) 2014 Jasper Palfree - * Licensed MIT - */ -!function(a,b){"object"==typeof exports?module.exports=b.call(a):"function"==typeof define&&define.amd?define(function(){return b.call(a)}):a.Physics=b.call(a)}(this,function(){"use strict";var a=this,b=a.document,c=function e(){return e.world.apply(e,arguments)};c.util={},function(){c.aabb=function(a,b,c,d){var e={x:0,y:0,hw:0,hh:0};return void 0===a?e:(a&&void 0!==a.x&&(c=b.x,d=b.y,b=a.y,a=a.x),void 0===d&&void 0!==a&&void 0!==b?(e.hw=.5*a,e.hh=.5*b,c&&void 0!==c.x&&(e.x=c.x,e.y=c.y),e):(e.hw=.5*Math.abs(c-a),e.hh=.5*Math.abs(d-b),e.x=.5*(c+a),e.y=.5*(d+b),e))},c.aabb.contains=function(a,b){return b.x>a.x-a.hw&&b.xa.y-a.hh&&b.y=d&&f>=e||f>=c&&e>=f?(c=a.y-a.hh,d=b.y-b.hh,e=a.y+a.hh,f=b.y+b.hh,e>=d&&f>=e||f>=c&&e>=f):!1}}(),function(){var a=1e-4,b=100,d=function(a,b,c){var d=b.normSq()-b.dot(a),e=b.dot(a)-a.normSq();return 0>d?c.clone(b).negate():e>0?c.clone(a).negate():(c.clone(b).vsub(a),c.perp(a.cross(c)>0))},e=function(a){var b,d,e=a.length,f=a[e-2],g=a[e-3],h=c.scratchpad(),i=h.vector().clone(f.pt),j=h.vector().clone(g.pt).vsub(i);return j.equals(c.vector.zero)?h.done({a:f.a,b:f.b}):(b=-j.dot(i)/j.normSq(),d=1-b,0>=d?h.done({a:g.a,b:g.b}):0>=b?h.done({a:f.a,b:f.b}):h.done({a:i.clone(f.a).mult(d).vadd(j.clone(g.a).mult(b)).values(),b:i.clone(f.b).mult(d).vadd(j.clone(g.b).mult(b)).values()}))},f=function(f,g,h,i){var j,k,l,m,n=!1,o=!1,p=!1,q=[],r=1,s=c.scratchpad(),t=s.vector().clone(g||c.vector.axis[0]),u=s.vector(),v=s.vector(),w=s.vector(),x=s.vector(),y=0;for(m=f(t),r=q.push(m),u.clone(m.pt),t.negate();++y;){if(u.swap(v),m=f(t),r=q.push(m),u.clone(m.pt),i&&i(q),u.equals(c.vector.zero)){n=!0;break}if(!o&&u.dot(t)<=0){if(h)break;o=!0}if(2===r)t=d(u,v,t);else if(o){if(t.normalize(),m=v.dot(t),Math.abs(m-u.dot(t))0,l^u.cross(j)>0)q.shift(),j.perp(!l),t.swap(j);else{if(!(l^k.cross(u)>0)){n=!0;break}q.splice(1,1),k.perp(l),t.swap(j)}if(y>b)return s.done(),{simplex:q,iterations:y,distance:0,maxIterationsReached:!0}}return s.done(),m={overlap:n,simplex:q,iterations:y},p!==!1&&(m.distance=p,m.closest=e(q)),m};c.gjk=f}(),function(){var a=function b(a,d,e){return this instanceof b?(this.v=c.vector(),this.o=c.vector(),a instanceof b?(this.clone(a),void 0):(a&&this.setTranslation(a),this.setRotation(d||0,e),void 0)):new b(a,d)};a.prototype.setTranslation=function(a){return this.v.clone(a),this},a.prototype.setRotation=function(a,b){return this.cosA=Math.cos(a),this.sinA=Math.sin(a),b?this.o.clone(b):this.o.zero(),this},a.prototype.clone=function(b){return b?(this.setTranslation(b.v),this.cosA=b.cosA,this.sinA=b.sinA,this.o.clone(b.o),this):new a(this)},c.transform=a}(),function(a){var b=Math.sqrt,d=Math.min,e=Math.max,f=(Math.acos,Math.atan2),g=2*Math.PI,h=!!a.Float64Array,i=function j(a,b){return this instanceof j?(this._=h?new Float64Array(5):[],a&&(void 0!==a.x||a._&&a._.length)?this.clone(a):(this.recalc=!0,this.set(a,b)),void 0):new j(a,b)};Object.defineProperties(i.prototype,{x:{get:function(){return+this._[0]},set:function(a){a=+a||0,this.recalc=a===this._[0],this._[0]=a}},y:{get:function(){return+this._[1]},set:function(a){a=+a||0,this.recalc=a===this._[1],this._[1]=a}}}),i.prototype.set=function(a,b){return this.recalc=!0,this._[0]=+a||0,this._[1]=+b||0,this},i.prototype.get=function(a){return this._[a]},i.prototype.vadd=function(a){return this.recalc=!0,this._[0]+=a._[0],this._[1]+=a._[1],this},i.prototype.vsub=function(a){return this.recalc=!0,this._[0]-=a._[0],this._[1]-=a._[1],this},i.prototype.add=function(a,b){return this.recalc=!0,this._[0]+=+a||0,this._[1]+=+b||0,this},i.prototype.sub=function(a,b){return this.recalc=!0,this._[0]-=a,this._[1]-=void 0===b?0:b,this},i.prototype.mult=function(a){return this.recalc||(this._[4]*=a*a,this._[3]*=a),this._[0]*=a,this._[1]*=a,this},i.prototype.dot=function(a){return this._[0]*a._[0]+this._[1]*a._[1]},i.prototype.cross=function(a){return-this._[0]*a._[1]+this._[1]*a._[0]},i.prototype.proj=function(a){return this.dot(a)/a.norm()},i.prototype.vproj=function(a){var b=this.dot(a)/a.normSq();return this.clone(a).mult(b)},i.prototype.angle=function(a){var b;if(this.equals(i.zero))return a?a.angle():0/0;for(b=a&&!a.equals(i.zero)?f(this._[1]*a._[0]-this._[0]*a._[1],this._[0]*a._[0]+this._[1]*a._[1]):f(this._[1],this._[0]);b>Math.PI;)b-=g;for(;b<-Math.PI;)b+=g;return b},i.prototype.angle2=function(a,b){for(var c=a._[0]-this._[0],d=a._[1]-this._[1],e=b._[0]-this._[0],h=b._[1]-this._[1],i=f(d*e-c*h,c*e+d*h);i>Math.PI;)i-=g;for(;i<-Math.PI;)i+=g;return i},i.prototype.norm=function(){return this.recalc&&(this.recalc=!1,this._[4]=this._[0]*this._[0]+this._[1]*this._[1],this._[3]=b(this._[4])),this._[3]},i.prototype.normSq=function(){return this.recalc&&(this.recalc=!1,this._[4]=this._[0]*this._[0]+this._[1]*this._[1],this._[3]=b(this._[4])),this._[4]},i.prototype.dist=function(a){var c,d;return b((c=a._[0]-this._[0])*c+(d=a._[1]-this._[1])*d)},i.prototype.distSq=function(a){var b,c;return(b=a._[0]-this._[0])*b+(c=a._[1]-this._[1])*c},i.prototype.perp=function(a){var b=this._[0];return a?(this._[0]=this._[1],this._[1]=-b):(this._[0]=-this._[1],this._[1]=b),this},i.prototype.normalize=function(){var a=this.norm();return 0===a?this:(a=1/a,this._[0]*=a,this._[1]*=a,this._[3]=1,this._[4]=1,this)},i.prototype.transform=function(a){var b=a.sinA,c=a.cosA,d=a.o._[0],e=a.o._[1];return this._[0]-=d,this._[1]-=e,this.set(this._[0]*c-this._[1]*b+d+a.v._[0],this._[0]*b+this._[1]*c+e+a.v._[1])},i.prototype.transformInv=function(a){var b=a.sinA,c=a.cosA,d=a.o._[0],e=a.o._[1];return this._[0]-=d+a.v._[0],this._[1]-=e+a.v._[1],this.set(this._[0]*c+this._[1]*b+d,-this._[0]*b+this._[1]*c+e)},i.prototype.rotate=function(a,b){var c,d,e=0,f=0;return"number"==typeof a?(c=Math.sin(a),d=Math.cos(a),b&&(e=0|(b.x||b._[0]),f=0|(b.y||b._[1]))):(c=a.sinA,d=a.cosA,e=a.o._[0],f=a.o._[1]),this._[0]-=e,this._[1]-=f,this.set(this._[0]*d-this._[1]*c+e,this._[0]*c+this._[1]*d+f)},i.prototype.rotateInv=function(a){return this.set((this._[0]-a.o._[0])*a.cosA+(this._[1]-a.o._[1])*a.sinA+a.o._[0],-(this._[0]-a.o._[0])*a.sinA+(this._[1]-a.o._[1])*a.cosA+a.o._[1])},i.prototype.translate=function(a){return this.vadd(a.v)},i.prototype.translateInv=function(a){return this.vsub(a.v)},i.prototype.clone=function(a){return a?a._?(this.recalc=a.recalc,a.recalc||(this._[3]=a._[3],this._[4]=a._[4]),this._[0]=a._[0],this._[1]=a._[1],this):this.set(a.x,a.y):new i(this)},i.prototype.swap=function(a){var b=this._;return this._=a._,a._=b,b=this.recalc,this.recalc=a.recalc,a.recalc=b,this},i.prototype.values=function(){return{x:this._[0],y:this._[1]}},i.prototype.zero=function(){return this._[3]=0,this._[4]=0,this._[0]=0,this._[1]=0,this},i.prototype.negate=function(a){return void 0!==a?(this._[a]=-this._[a],this):(this._[0]=-this._[0],this._[1]=-this._[1],this)},i.prototype.clamp=function(a,b){return this._[0]=d(e(this._[0],a.x),b.x),this._[1]=d(e(this._[1],a.y),b.y),this.recalc=!0,this},i.prototype.toString=function(){return"("+this._[0]+", "+this._[1]+")"},i.prototype.equals=function(a){return this._[0]===a._[0]&&this._[1]===a._[1]&&this._[2]===a._[2]},i.axis=[new i(1,0),new i(0,1)],i.zero=new i(0,0),c.vector=i}(this),function(a){var b=a.Physics;c.noConflict=function(){return a.Physics===c&&(a.Physics=b),c}}(this);var d=c.util.decorator=function(a,b){var d={},e={},f=function(a,b){var d,e;for(e in b)d=Object.getOwnPropertyDescriptor(b,e),d.get||d.set?Object.defineProperty(a,e,d):c.util.isFunction(d.value)&&(a[e]=d.value);return a},g=Object.getPrototypeOf;"function"!=typeof g&&(g="object"==typeof"test".__proto__?function(a){return a.__proto__}:function(a){return a.constructor.prototype});var h=Object.create;"function"!=typeof h&&(h=function(a){function b(){}return b.prototype=a,new b});var i=function(b,d){return"object"==typeof b?(e=f(e,b),e.type=a,void 0):("type"!==b&&c.util.isFunction(d)&&(e[b]=d),void 0)};i(b);var j=function(b,c,i,j){var k,l=e;if("string"!=typeof c)j=i,i=c;else{if(l=d[c],!l)throw'Error: "'+c+'" '+a+" not defined";l=l.prototype}if("function"==typeof i)k=d[b],k?k.prototype=f(k.prototype,i(g(k.prototype))):(k=d[b]=function(a){this.init&&this.init(a)},k.prototype=h(l),k.prototype=f(k.prototype,i(l,k.prototype))),k.prototype.type=a,k.prototype.name=b;else if(j=i||{},k=d[b],!k)throw'Error: "'+b+'" '+a+" not defined";return j?new k(j):void 0};return j.mixin=i,j};return c.util.indexOf=function(a,b){for(var c=0,d=a.length;d>c;){if(d--,a[c]===b)return c;if(a[d]===b)return d;c++}return-1},c.util.throttle=function(a,b,c){var d,e,f=!1,g=function(){clearTimeout(d),f?(f=!1,d=setTimeout(g,b),a.apply(c,e)):d=!1};return c=c||null,function(){f=!0,e=arguments,d||g()}},c.util.options=function(a,b){var d,e={},f=[];return d=function(a){c.util.extend(b,a,null);for(var d=0,e=f.length;e>d;++d)f[d](b);return b},d.defaults=function(a){return c.util.extend(e,a),c.util.defaults(b,e),e},d.onChange=function(a){f.push(a)},b=b||d,d.defaults(a),d},c.util.pairHash=function(a,b){return a=0|a,b=0|b,(0|a)===(0|b)?-1:0|((0|a)>(0|b)?a<<16|65535&b:b<<16|65535&a)},c.util.bind=Function.prototype.bind?function(a,b,c){return c=Array.prototype.slice.call(arguments,1),Function.prototype.bind.apply(a,c)}:function(a,b,c){return c=Array.prototype.slice.call(arguments,2),function(){return a.apply(b,c.concat(Array.prototype.slice.call(arguments)))}},c.util.find=function(a,b){var c,d,e=a.length;for(c=0;e>c;c++)if(d=a[c],b(d,c,a))return d},c.util.filter=function(a,b){var c,d,e=a.length,f=[];for(c=0;e>c;c++)d=a[c],b(d,c,a)&&f.push(d);return f},function(){function a(a){a.length=0,v.length-1?0:-1:a?0:-1}function g(a){var b=this.cache,c=typeof a;if("boolean"===c||null==a)b[a]=!0;else{"number"!==c&&"string"!==c&&(c="object");var d="number"===c?a:y+a,e=b[c]||(b[c]={});"object"===c?(e[d]||(e[d]=[])).push(a):e[d]=!0}}function h(a){var b=-1,c=a.length,e=a[0],f=a[0|c/2],h=a[c-1];if(e&&"object"==typeof e&&f&&"object"==typeof f&&h&&"object"==typeof h)return!1;var i=d();i["false"]=i["null"]=i["true"]=i.undefined=!1;var j=d();for(j.array=a,j.cache=i,j.push=g;++b=u&&k===c.util.indexOf,o=i||n?e():m;if(n){var p=h(o);k=f,o=p}for(;++jb;b++)d=a[b],e=i(0,++f),h[f]=h[e],h[e]=d;return h},c.util.isObject=function(a){return!(!a||!n[typeof a])},c.util.isFunction=j,c.util.isArray=Array.isArray||function(a){return a&&"object"==typeof a&&"number"==typeof a.length&&s.call(a)===p||!1};var C=RegExp("^"+String(s).replace(/[.*+?^${}()|[\]\\]/g,"\\$&").replace(/toString| for [^\]]+/g,".*?")+"$");c.util.isPlainObject=Object.getPrototypeOf?function(a){if(!a||s.call(a)!==q)return!1;var b=a.valueOf,c=k(b)&&(c=Object.getPrototypeOf(b))&&Object.getPrototypeOf(c);return c?a===c||Object.getPrototypeOf(a)===c:l(a)}:l,c.util.uniq=function(a,b,c){return"boolean"!=typeof b&&null!=b&&(c=b,b=!1),m(a,b,c)};var D=function(a,b,c){var d,e=a,f=e;if(!e)return f;var g,h=arguments,i=0,j="number"==typeof c?2:h.length;for(j>2&&"function"==typeof h[j-1]&&(g=h[--j]);++id;){var f=d+e>>>1;c(a[f])=b.maxScratches)throw f},a.prototype={done:function(a){this._active=!1;for(var b=0;j>b;++b)this[b]=0;return h.push(this),a}},b=function k(b){if(b)return k.fn(b);var c=h.pop()||new a;return c._active=!0,c},b.maxScratches=100,b.maxIndex=20,b.fn=function(b){for(var c=[],d=0,e=b.length;e>d;d++)c.push(d);c="a"+c.join(",a");var f=new Function("fn, scratches, Scratch","return function("+c+"){ "+"var scratch = scratches.pop() || new Scratch( scratches );"+"scratch._active = true;"+"return scratch.done( fn(scratch, "+c+") );"+"};");return f(b,h,a)},b.register=function(c,f,h){var i=a.prototype,k=j++,l="_"+c+"Stack",m=h&&h.useFactory;if(c in i)throw g;i[c]=function(){var a=this[l]||(this[l]=[]),c=0|this[k];if(this[k]=c+1,!this._active)throw d;if(c>=b.maxIndex)throw e;return a[c]||(a[c]=m?f():new f)}},b.register("vector",c.vector),b.register("transform",c.transform),b}(),function(){function a(a){return a._priority_}c.scratchpad.register("event",function(){return{}},{useFactory:!0});var b=function d(){return this instanceof d?void 0:new d};b.prototype={on:function(b,d,e,f){var g,h,i;if(this._topics=this._topics||(this._topics={}),c.util.isObject(b)){for(var j in b)this.on(j,b[j],d,e);return this}return g=this._topics[b]||(this._topics[b]=[]),h=d,c.util.isObject(e)?(d=c.util.bind(d,e),d._bindfn_=h,d._one_=h._one_):f||(f=e),d._priority_=f,i=c.util.sortedIndex(g,d,a),g.splice(i,0,d),this},off:function(a,b){var d,e;if(!this._topics)return this;if(a===!0)return this._topics={},this;if(c.util.isObject(a)){for(var f in a)this.off(f,a[f]);return this}if(d=this._topics[a],!d)return this;if(b===!0)return this._topics[a]=[],this;for(var g=0,h=d.length;h>g;g++)if(e=d[g],e._bindfn_===b||e===b){d.splice(g,1);break}return this},emit:function(a,b){if(!this._topics)return this;var d,e,f=this._topics[a],g=f&&f.length,h=c.scratchpad();if(!g)return h.done(this);for(e=h.event(),e.topic=a,e.handler=d;g--;)d=f[g],d(b,e),d._one_&&f.splice(g,1);return h.done(this)},one:function(a,b,d){if(c.util.isObject(a)){for(var e in a)this.one(e,a[e],b,d);return this}return b._one_=!0,this.on(a,b,d),this}},c.util.pubsub=b}(),function(a){function b(){return l&&l.now?l.now()+l.timing.navigationStart:Date.now()}function d(){var c;j&&(c=b(),c&&(a.requestAnimationFrame(d),k.emit("tick",c)))}function e(){return j=!0,d(),this}function f(){return j=!1,this}function g(a){return k.on("tick",a),this}function h(a){return k.off("tick",a),this}function i(){return!!j}var j=!1,k=c.util.pubsub(),l=a.performance;c.util.ticker={now:b,start:e,stop:f,on:g,off:h,isActive:i}}(this),function(){var a=function(){return!0},b=c.util.indexOf,d=function(a,b){return function(c){return a(c[b])}},e=function(a,d){return function(e){e=d?e[d]:e;var f,g=0;if(c.util.isArray(e)){if(c.util.isArray(a)){if(f=e.length,f!==a.length)return!1;for(;f>g;){if(f--,-1===b(a,e[g])||-1===b(a,e[f]))return!1;g++}return!0}return b(e,a)>-1}return e===a}},f=function(a,b){var c=e(a,b);return function(a){return!c(a)}},g=function(a,d){return function(e){e=d?e[d]:e;var f,g=0;if(c.util.isArray(e)){for(f=e.length;f>g;){if(f--,b(a,e[g])>-1||b(a,e[f])>-1)return!0;g++}return!1}return b(a,e)>-1}},h=function(a,b){var c=g(a,b);return function(a){return!c(a)}},i=function(a){return a=c.vector(a),function(b){var d=b.aabb();return c.aabb.contains(d,a)}},j=function(a){return a.next?function(b){for(var c=a;c;){if(!c(b))return!1;c=c.next}return!0}:a},k=function(a){return a.next?function(b){for(var c=a;c;){if(c(b))return!0;c=c.next}return!1}:a},l={$eq:e,$ne:f,$in:g,$nin:h,$at:i},m=function n(b,f){var g,h,i,m,o,p;if(f){if("$or"===f||"$and"===f){for(g=0,h=b.length;h>g;++g)p=n(b[g]),o=o?o.next=p:m=p;return"$or"===f?k(m):j(m)}if(g=l[f])return g(b);throw"Unknown query operation: "+f}for(g in b)i=b[g],p="$"===g[0]?n(i,g):c.util.isPlainObject(i)?d(n(i),g):e(i,g),o=o?o.next=p:m=p;return j(m||a)};c.query=m}(this),function(){var a={priority:0};c.behavior=d("behavior",{init:function(b){this.options=c.util.options(a),this.options(b)},applyTo:function(a){return this._targets=a===!0?null:c.util.uniq(a),this},getTargets:function(){return this._targets||(this._world?this._world._bodies:[])},setWorld:function(a){return this.disconnect&&this._world&&this.disconnect(this._world),this._world=a,this.connect&&a&&this.connect(a),this},connect:function(a){this.behave&&a.on("integrate:positions",this.behave,this,this.options.priority)},disconnect:function(a){this.behave&&a.off("integrate:positions",this.behave)},behave:null})}(),function(){var a={hidden:!1,treatment:"dynamic",mass:1,restitution:1,cof:.8,view:null},b=1;c.body=d("body",{init:function(d){var e=c.vector;if(this.options=c.util.options(a,this),this.options(d),this.state={pos:e(this.x,this.y),vel:e(this.vx,this.vy),acc:e(),angular:{pos:this.angle||0,vel:this.angularVelocity||0,acc:0},old:{pos:e(),vel:e(),acc:e(),angular:{pos:0,vel:0,acc:0}}},delete this.x,delete this.y,delete this.vx,delete this.vy,delete this.angle,delete this.angularVelocity,0===this.mass)throw"Error: Bodies must have non-zero mass";this.uid=b++,this.geometry=c.geometry("point")},setWorld:function(a){return this.disconnect&&this._world&&this.disconnect(this._world),this._world=a,this.connect&&a&&this.connect(a),this},accelerate:function(a){return"dynamic"===this.treatment&&this.state.acc.vadd(a),this},applyForce:function(a,b){if("dynamic"!==this.treatment)return this;var d,e=c.scratchpad(),f=e.vector();return b&&this.moi&&(d=this.state,f.clone(b),this.state.angular.acc-=f.cross(a)/this.moi),this.accelerate(f.clone(a).mult(1/this.mass)),e.done(),this},aabb:function(){var a=this.state.angular.pos,b=this.geometry.aabb(a);return b.x+=this.state.pos.x,b.y+=this.state.pos.y,b},recalc:function(){return this}})}(),function(){c.geometry=d("geometry",{init:function(a){this.options=c.util.options(),this.options(a),this._aabb=new c.aabb},aabb:function(){return c.aabb.clone(this._aabb)},getFarthestHullPoint:function(a,b){return b=b||c.vector(),b.set(0,0)},getFarthestCorePoint:function(a,b){return b=b||c.vector(),b.set(0,0)}})}(),c.geometry.isPolygonConvex=function(a){var b=c.scratchpad(),d=b.vector(),e=b.vector(),f=b.vector(),g=!0,h=!1,i=a.length;if(!a||!i)return!1;if(3>i)return b.done(),g;d.clone(a[0]).vsub(f.clone(a[i-1]));for(var j=1;i>=j;++j){if(e.clone(a[j%i]).vsub(f.clone(a[(j-1)%i])),h===!1)h=d.cross(e);else if(h>0^d.cross(e)>0){g=!1;break}e.swap(d)}return b.done(),g},c.geometry.getPolygonMOI=function(a){var b,d=c.scratchpad(),e=d.vector(),f=d.vector(),g=0,h=0,i=a.length;if(2>i)return d.done(),0;if(2===i)return b=f.clone(a[1]).distSq(e.clone(a[0])),d.done(),b/12;e.clone(a[0]);for(var j=1;i>j;++j)f.clone(a[j]),b=Math.abs(f.cross(e)),g+=b*(f.normSq()+f.dot(e)+e.normSq()),h+=b,e.swap(f);return d.done(),g/(6*h)},c.geometry.isPointInPolygon=function(a,b){var d=c.scratchpad(),e=d.vector().clone(a),f=d.vector(),g=d.vector(),h=0,i=b.length;if(2>i)return h=e.equals(f.clone(b[0])),d.done(),h;if(2===i)return h=e.angle(f.clone(b[0])),h+=e.angle(f.clone(b[1])),d.done(),Math.abs(h)===Math.PI;f.clone(b[0]).vsub(e);for(var j=1;i>=j;++j)g.clone(b[j%i]).vsub(e),h+=g.angle(f),f.swap(g);return d.done(),Math.abs(h)>1e-6},c.geometry.getPolygonArea=function(a){var b=c.scratchpad(),d=b.vector(),e=b.vector(),f=0,g=a.length;if(3>g)return b.done(),0;d.clone(a[g-1]);for(var h=0;g>h;++h)e.clone(a[h]),f+=d.cross(e),d.swap(e);return b.done(),f/2},c.geometry.getPolygonCentroid=function(a){var b,d=c.scratchpad(),e=d.vector(),f=d.vector(),g=c.vector(),h=a.length;if(2>h)return d.done(),c.vector(a[0]);if(2===h)return d.done(),c.vector((a[1].x+a[0].x)/2,(a[1].y+a[0].y)/2);e.clone(a[h-1]);for(var i=0;h>i;++i)f.clone(a[i]),b=e.cross(f),e.vadd(f).mult(b),g.vadd(e),e.swap(f);return b=1/(6*c.geometry.getPolygonArea(a)),d.done(),g.mult(b)},c.geometry.nearestPointOnLine=function(a,b,d){var e,f,g=c.scratchpad(),h=g.vector().clone(a),i=g.vector().clone(b).vsub(h),j=g.vector().clone(d).vsub(h).vsub(i);return j.equals(c.vector.zero)?(g.done(),c.vector(b)):(e=-j.dot(i)/j.normSq(),f=1-e,0>=f?(g.done(),c.vector(d)):0>=e?(g.done(),c.vector(b)):(h=c.vector(d).mult(e).vadd(i.clone(b).mult(f)),g.done(),h))},function(){var a={drag:0};c.integrator=d("integrator",{init:function(){this.options=c.util.options(a)},setWorld:function(a){return this.disconnect&&this._world&&this.disconnect(this._world),this._world=a,this.connect&&a&&this.connect(a),this},integrate:function(a,b){var c=this._world;return this.integrateVelocities(a,b),c&&c.emit("integrate:velocities",{bodies:a,dt:b}),this.integratePositions(a,b),c&&c.emit("integrate:positions",{bodies:a,dt:b}),this},connect:null,disconnect:null,integrateVelocities:function(){throw"The integrator.integrateVelocities() method must be overriden"},integratePositions:function(){throw"The integrator.integratePositions() method must be overriden"}})}(),function(){var a={meta:!1,metaRefresh:200,width:600,height:600};c.renderer=d("renderer",{init:function(d){var e="string"==typeof d.el?b.getElementById(d.el):d.el;this.options=c.util.extend({},a,d),this.el=e?e:b.body,this.drawMeta=c.util.throttle(c.util.bind(this.drawMeta,this),this.options.metaRefresh)},setWorld:function(a){return this.disconnect&&this._world&&this.disconnect(this._world),this._world=a,this.connect&&a&&this.connect(a),this},render:function(a,b){var c,d;this.beforeRender&&this.beforeRender(),this._world.emit("beforeRender",{renderer:this,bodies:a,meta:b}),this.options.meta&&this.drawMeta(b),this._interpolateTime=b.interpolateTime;for(var e=0,f=a.length;f>e;++e)c=a[e],d=c.view||(c.view=this.createView(c.geometry,c.styles)),c.hidden||this.drawBody(c,d);return this},createView:function(){throw"You must override the renderer.createView() method."},drawMeta:function(){throw"You must override the renderer.drawMeta() method."},drawBody:function(){throw"You must override the renderer.drawBody() method."}})}(),function(){var a=function e(a,b,c){for(var d,f,g=function(){return e(a,b,c)};d=a.shift();)if(f=d.apply(b,c),f&&f.then)return f.then(g)},b={timestep:1e3/120,maxIPF:16,webworker:!1,integrator:"verlet"},d=function f(a,b){return this instanceof f?(this.init(a,b),void 0):new f(a,b)};d.prototype=c.util.extend({},c.util.pubsub.prototype,{init:function(d,e){var f=this;(c.util.isFunction(d)||c.util.isArray(d))&&(e=d,d={}),this._meta={fps:0,ipf:0},this._bodies=[],this._behaviors=[],this._integrator=null,this._renderer=null,this._paused=!1,this._warp=1,this._time=0,this.options=c.util.options(b),this.options.onChange(function(a){f.timestep(a.timestep)}),this.options(d),this.add(c.integrator(this.options.integrator)),c.util.isFunction(e)?a([e],this,[this,c]):c.util.isArray(e)&&a(e,this,[this,c])},options:null,add:function(a){var b=0,c=a&&a.length||0,d=c?a[0]:a;if(!d)return this;do switch(d.type){case"behavior":this.addBehavior(d);break;case"integrator":this.integrator(d);break;case"renderer":this.renderer(d);break;case"body":this.addBody(d);break;default:throw'Error: failed to add item of unknown type "'+d.type+'" to world'}while(++b-1},integrator:function(a){return void 0===a?this._integrator:this._integrator===a?this:(this._integrator&&(this._integrator.setWorld(null),this.emit("remove:integrator",{integrator:this._integrator})),a&&(this._integrator=a,this._integrator.setWorld(this),this.emit("add:integrator",{integrator:this._integrator})),this)},renderer:function(a){return void 0===a?this._renderer:this._renderer===a?this:(this._renderer&&(this._renderer.setWorld(null),this.emit("remove:renderer",{renderer:this._renderer})),a&&(this._renderer=a,this._renderer.setWorld(this),this.emit("add:renderer",{renderer:this._renderer})),this)},timestep:function(a){return a?(this._dt=a,this._maxJump=a*this.options.maxIPF,this):this._dt},addBehavior:function(a){return this.has(a)?this:(a.setWorld(this),this._behaviors.push(a),this.emit("add:behavior",{behavior:a}),this)},getBehaviors:function(){return[].concat(this._behaviors)},removeBehavior:function(a){var b=this._behaviors;if(a)for(var c=0,d=b.length;d>c;++c)if(a===b[c]){b.splice(c,1),a.setWorld(null),this.emit("remove:behavior",{behavior:a});break}return this},addBody:function(a){return this.has(a)?this:(a.setWorld(this),this._bodies.push(a),this.emit("add:body",{body:a}),this)},getBodies:function(){return[].concat(this._bodies)},removeBody:function(a){var b=this._bodies;if(a)for(var c=0,d=b.length;d>c;++c)if(a===b[c]){b.splice(c,1),a.setWorld(null),this.emit("remove:body",{body:a});break}return this},findOne:function(a){var b=this,d="function"==typeof a?a:c.query(a);return c.util.find(b._bodies,d)||!1},find:function(a){var b=this,d="function"==typeof a?a:c.query(a);return c.util.filter(b._bodies,d)},iterate:function(a){this._integrator.integrate(this._bodies,a)},step:function(a){var b,d,e,f=this._time,g=this._warp,h=1/g,i=this._dt,j=i*h,k=this._maxJump*h,l=this._meta;if(this._paused||void 0===this._animTime)return this._animTime=a||this._animTime||c.util.ticker.now(),this._paused||this.emit("step",l),this;if(a=a||this._animTime+j,b=a-this._animTime,b>k&&(this._animTime=a-k,b=k),d=b*g,e=f+d-i,e>=f)for(;e>=f;)f+=i,this._animTime+=j,this._time=f,this.iterate(i);return l.fps=1e3/(a-this._lastTime),l.ipf=(d/i).toFixed(2),l.interpolateTime=i+e-f,this._lastTime=a,this.emit("step",l),this},warp:function(a){return void 0===a?this._warp:(this._warp=a||1,this)},render:function(){if(!this._renderer)throw"No renderer added to world";return this._renderer.render(this._bodies,this._meta),this.emit("render",{bodies:this._bodies,meta:this._meta,renderer:this._renderer}),this},pause:function(){return this._paused=!0,this.emit("pause"),this},unpause:function(){return this._paused=!1,this.emit("unpause"),this},isPaused:function(){return!!this._paused},destroy:function(){var a=this;a.pause(),this.emit("destroy"),a.off(!0),a.remove(a.getBodies()),a.remove(a.getBehaviors()),a.integrator(null),a.renderer(null)}}),c.world=d}(),c.integrator("verlet",function(a){return c.body.mixin({started:function(a){return void 0!==a&&(this._started=!0),!!this._started}}),{init:function(b){a.init.call(this,b)},integrateVelocities:function(a,b){for(var c,d=b*b,e=1-this.options.drag,f=null,g=0,h=a.length;h>g;++g)f=a[g],c=f.state,"static"!==f.treatment?(c.vel.equals(c.old.vel)&&f.started()?c.vel.clone(c.pos).vsub(c.old.pos):(c.old.pos.clone(c.pos).vsub(c.vel),c.vel.mult(b)),e&&c.vel.mult(e),c.vel.vadd(c.acc.mult(d)),c.vel.mult(1/b),c.old.vel.clone(c.vel),c.acc.zero(),c.angular.vel===c.old.angular.vel&&f.started()?c.angular.vel=c.angular.pos-c.old.angular.pos:(c.old.angular.pos=c.angular.pos-c.angular.vel,c.angular.vel*=b),c.angular.vel+=c.angular.acc*d,c.angular.vel/=b,c.old.angular.vel=c.angular.vel,c.angular.acc=0,f.started(!0)):(c.vel.zero(),c.acc.zero(),c.angular.vel=0,c.angular.acc=0)},integratePositions:function(a,b){for(var c,d=null,e=0,f=a.length;f>e;++e)d=a[e],c=d.state,"static"!==d.treatment&&(c.vel.mult(b),c.old.pos.clone(c.pos),c.pos.vadd(c.vel),c.vel.mult(1/b),c.old.vel.clone(c.vel),c.angular.vel*=b,c.old.angular.pos=c.angular.pos,c.angular.pos+=c.angular.vel,c.angular.vel/=b,c.old.angular.vel=c.angular.vel)}}}),c.geometry("point",function(){}),c.body("point",function(a){return{init:function(b){a.init.call(this,b),this.moi=0}}}),c}); \ No newline at end of file diff --git a/dist/physicsjs-full-0.6.0.min.js b/dist/physicsjs-full-0.6.0.min.js deleted file mode 100644 index dd23e032..00000000 --- a/dist/physicsjs-full-0.6.0.min.js +++ /dev/null @@ -1,11 +0,0 @@ -/** - * PhysicsJS v0.6.0 - 2014-04-22 - * A modular, extendable, and easy-to-use physics engine for javascript - * http://wellcaffeinated.net/PhysicsJS - * - * Copyright (c) 2014 Jasper Palfree - * Licensed MIT - */ -!function(a,b){"object"==typeof exports?module.exports=b.call(a):"function"==typeof define&&define.amd?define(function(){return b.call(a)}):a.Physics=b.call(a)}(this,function(){"use strict";var a=this,b=a.document,c=function e(){return e.world.apply(e,arguments)};c.util={},function(){c.aabb=function(a,b,c,d){var e={x:0,y:0,hw:0,hh:0};return void 0===a?e:(a&&void 0!==a.x&&(c=b.x,d=b.y,b=a.y,a=a.x),void 0===d&&void 0!==a&&void 0!==b?(e.hw=.5*a,e.hh=.5*b,c&&void 0!==c.x&&(e.x=c.x,e.y=c.y),e):(e.hw=.5*Math.abs(c-a),e.hh=.5*Math.abs(d-b),e.x=.5*(c+a),e.y=.5*(d+b),e))},c.aabb.contains=function(a,b){return b.x>a.x-a.hw&&b.xa.y-a.hh&&b.y=d&&f>=e||f>=c&&e>=f?(c=a.y-a.hh,d=b.y-b.hh,e=a.y+a.hh,f=b.y+b.hh,e>=d&&f>=e||f>=c&&e>=f):!1}}(),function(){var a=1e-4,b=100,d=function(a,b,c){var d=b.normSq()-b.dot(a),e=b.dot(a)-a.normSq();return 0>d?c.clone(b).negate():e>0?c.clone(a).negate():(c.clone(b).vsub(a),c.perp(a.cross(c)>0))},e=function(a){var b,d,e=a.length,f=a[e-2],g=a[e-3],h=c.scratchpad(),i=h.vector().clone(f.pt),j=h.vector().clone(g.pt).vsub(i);return j.equals(c.vector.zero)?h.done({a:f.a,b:f.b}):(b=-j.dot(i)/j.normSq(),d=1-b,0>=d?h.done({a:g.a,b:g.b}):0>=b?h.done({a:f.a,b:f.b}):h.done({a:i.clone(f.a).mult(d).vadd(j.clone(g.a).mult(b)).values(),b:i.clone(f.b).mult(d).vadd(j.clone(g.b).mult(b)).values()}))},f=function(f,g,h,i){var j,k,l,m,n=!1,o=!1,p=!1,q=[],r=1,s=c.scratchpad(),t=s.vector().clone(g||c.vector.axis[0]),u=s.vector(),v=s.vector(),w=s.vector(),x=s.vector(),y=0;for(m=f(t),r=q.push(m),u.clone(m.pt),t.negate();++y;){if(u.swap(v),m=f(t),r=q.push(m),u.clone(m.pt),i&&i(q),u.equals(c.vector.zero)){n=!0;break}if(!o&&u.dot(t)<=0){if(h)break;o=!0}if(2===r)t=d(u,v,t);else if(o){if(t.normalize(),m=v.dot(t),Math.abs(m-u.dot(t))0,l^u.cross(j)>0)q.shift(),j.perp(!l),t.swap(j);else{if(!(l^k.cross(u)>0)){n=!0;break}q.splice(1,1),k.perp(l),t.swap(j)}if(y>b)return s.done(),{simplex:q,iterations:y,distance:0,maxIterationsReached:!0}}return s.done(),m={overlap:n,simplex:q,iterations:y},p!==!1&&(m.distance=p,m.closest=e(q)),m};c.gjk=f}(),function(){var a=function b(a,d,e){return this instanceof b?(this.v=c.vector(),this.o=c.vector(),a instanceof b?(this.clone(a),void 0):(a&&this.setTranslation(a),this.setRotation(d||0,e),void 0)):new b(a,d)};a.prototype.setTranslation=function(a){return this.v.clone(a),this},a.prototype.setRotation=function(a,b){return this.cosA=Math.cos(a),this.sinA=Math.sin(a),b?this.o.clone(b):this.o.zero(),this},a.prototype.clone=function(b){return b?(this.setTranslation(b.v),this.cosA=b.cosA,this.sinA=b.sinA,this.o.clone(b.o),this):new a(this)},c.transform=a}(),function(a){var b=Math.sqrt,d=Math.min,e=Math.max,f=(Math.acos,Math.atan2),g=2*Math.PI,h=!!a.Float64Array,i=function j(a,b){return this instanceof j?(this._=h?new Float64Array(5):[],a&&(void 0!==a.x||a._&&a._.length)?this.clone(a):(this.recalc=!0,this.set(a,b)),void 0):new j(a,b)};Object.defineProperties(i.prototype,{x:{get:function(){return+this._[0]},set:function(a){a=+a||0,this.recalc=a===this._[0],this._[0]=a}},y:{get:function(){return+this._[1]},set:function(a){a=+a||0,this.recalc=a===this._[1],this._[1]=a}}}),i.prototype.set=function(a,b){return this.recalc=!0,this._[0]=+a||0,this._[1]=+b||0,this},i.prototype.get=function(a){return this._[a]},i.prototype.vadd=function(a){return this.recalc=!0,this._[0]+=a._[0],this._[1]+=a._[1],this},i.prototype.vsub=function(a){return this.recalc=!0,this._[0]-=a._[0],this._[1]-=a._[1],this},i.prototype.add=function(a,b){return this.recalc=!0,this._[0]+=+a||0,this._[1]+=+b||0,this},i.prototype.sub=function(a,b){return this.recalc=!0,this._[0]-=a,this._[1]-=void 0===b?0:b,this},i.prototype.mult=function(a){return this.recalc||(this._[4]*=a*a,this._[3]*=a),this._[0]*=a,this._[1]*=a,this},i.prototype.dot=function(a){return this._[0]*a._[0]+this._[1]*a._[1]},i.prototype.cross=function(a){return-this._[0]*a._[1]+this._[1]*a._[0]},i.prototype.proj=function(a){return this.dot(a)/a.norm()},i.prototype.vproj=function(a){var b=this.dot(a)/a.normSq();return this.clone(a).mult(b)},i.prototype.angle=function(a){var b;if(this.equals(i.zero))return a?a.angle():0/0;for(b=a&&!a.equals(i.zero)?f(this._[1]*a._[0]-this._[0]*a._[1],this._[0]*a._[0]+this._[1]*a._[1]):f(this._[1],this._[0]);b>Math.PI;)b-=g;for(;b<-Math.PI;)b+=g;return b},i.prototype.angle2=function(a,b){for(var c=a._[0]-this._[0],d=a._[1]-this._[1],e=b._[0]-this._[0],h=b._[1]-this._[1],i=f(d*e-c*h,c*e+d*h);i>Math.PI;)i-=g;for(;i<-Math.PI;)i+=g;return i},i.prototype.norm=function(){return this.recalc&&(this.recalc=!1,this._[4]=this._[0]*this._[0]+this._[1]*this._[1],this._[3]=b(this._[4])),this._[3]},i.prototype.normSq=function(){return this.recalc&&(this.recalc=!1,this._[4]=this._[0]*this._[0]+this._[1]*this._[1],this._[3]=b(this._[4])),this._[4]},i.prototype.dist=function(a){var c,d;return b((c=a._[0]-this._[0])*c+(d=a._[1]-this._[1])*d)},i.prototype.distSq=function(a){var b,c;return(b=a._[0]-this._[0])*b+(c=a._[1]-this._[1])*c},i.prototype.perp=function(a){var b=this._[0];return a?(this._[0]=this._[1],this._[1]=-b):(this._[0]=-this._[1],this._[1]=b),this},i.prototype.normalize=function(){var a=this.norm();return 0===a?this:(a=1/a,this._[0]*=a,this._[1]*=a,this._[3]=1,this._[4]=1,this)},i.prototype.transform=function(a){var b=a.sinA,c=a.cosA,d=a.o._[0],e=a.o._[1];return this._[0]-=d,this._[1]-=e,this.set(this._[0]*c-this._[1]*b+d+a.v._[0],this._[0]*b+this._[1]*c+e+a.v._[1])},i.prototype.transformInv=function(a){var b=a.sinA,c=a.cosA,d=a.o._[0],e=a.o._[1];return this._[0]-=d+a.v._[0],this._[1]-=e+a.v._[1],this.set(this._[0]*c+this._[1]*b+d,-this._[0]*b+this._[1]*c+e)},i.prototype.rotate=function(a,b){var c,d,e=0,f=0;return"number"==typeof a?(c=Math.sin(a),d=Math.cos(a),b&&(e=0|(b.x||b._[0]),f=0|(b.y||b._[1]))):(c=a.sinA,d=a.cosA,e=a.o._[0],f=a.o._[1]),this._[0]-=e,this._[1]-=f,this.set(this._[0]*d-this._[1]*c+e,this._[0]*c+this._[1]*d+f)},i.prototype.rotateInv=function(a){return this.set((this._[0]-a.o._[0])*a.cosA+(this._[1]-a.o._[1])*a.sinA+a.o._[0],-(this._[0]-a.o._[0])*a.sinA+(this._[1]-a.o._[1])*a.cosA+a.o._[1])},i.prototype.translate=function(a){return this.vadd(a.v)},i.prototype.translateInv=function(a){return this.vsub(a.v)},i.prototype.clone=function(a){return a?a._?(this.recalc=a.recalc,a.recalc||(this._[3]=a._[3],this._[4]=a._[4]),this._[0]=a._[0],this._[1]=a._[1],this):this.set(a.x,a.y):new i(this)},i.prototype.swap=function(a){var b=this._;return this._=a._,a._=b,b=this.recalc,this.recalc=a.recalc,a.recalc=b,this},i.prototype.values=function(){return{x:this._[0],y:this._[1]}},i.prototype.zero=function(){return this._[3]=0,this._[4]=0,this._[0]=0,this._[1]=0,this},i.prototype.negate=function(a){return void 0!==a?(this._[a]=-this._[a],this):(this._[0]=-this._[0],this._[1]=-this._[1],this)},i.prototype.clamp=function(a,b){return this._[0]=d(e(this._[0],a.x),b.x),this._[1]=d(e(this._[1],a.y),b.y),this.recalc=!0,this},i.prototype.toString=function(){return"("+this._[0]+", "+this._[1]+")"},i.prototype.equals=function(a){return this._[0]===a._[0]&&this._[1]===a._[1]&&this._[2]===a._[2]},i.axis=[new i(1,0),new i(0,1)],i.zero=new i(0,0),c.vector=i}(this),function(a){var b=a.Physics;c.noConflict=function(){return a.Physics===c&&(a.Physics=b),c}}(this);var d=c.util.decorator=function(a,b){var d={},e={},f=function(a,b){var d,e;for(e in b)d=Object.getOwnPropertyDescriptor(b,e),d.get||d.set?Object.defineProperty(a,e,d):c.util.isFunction(d.value)&&(a[e]=d.value);return a},g=Object.getPrototypeOf;"function"!=typeof g&&(g="object"==typeof"test".__proto__?function(a){return a.__proto__}:function(a){return a.constructor.prototype});var h=Object.create;"function"!=typeof h&&(h=function(a){function b(){}return b.prototype=a,new b});var i=function(b,d){return"object"==typeof b?(e=f(e,b),e.type=a,void 0):("type"!==b&&c.util.isFunction(d)&&(e[b]=d),void 0)};i(b);var j=function(b,c,i,j){var k,l=e;if("string"!=typeof c)j=i,i=c;else{if(l=d[c],!l)throw'Error: "'+c+'" '+a+" not defined";l=l.prototype}if("function"==typeof i)k=d[b],k?k.prototype=f(k.prototype,i(g(k.prototype))):(k=d[b]=function(a){this.init&&this.init(a)},k.prototype=h(l),k.prototype=f(k.prototype,i(l,k.prototype))),k.prototype.type=a,k.prototype.name=b;else if(j=i||{},k=d[b],!k)throw'Error: "'+b+'" '+a+" not defined";return j?new k(j):void 0};return j.mixin=i,j};return c.util.indexOf=function(a,b){for(var c=0,d=a.length;d>c;){if(d--,a[c]===b)return c;if(a[d]===b)return d;c++}return-1},c.util.throttle=function(a,b,c){var d,e,f=!1,g=function(){clearTimeout(d),f?(f=!1,d=setTimeout(g,b),a.apply(c,e)):d=!1};return c=c||null,function(){f=!0,e=arguments,d||g()}},c.util.options=function(a,b){var d,e={},f=[];return d=function(a){c.util.extend(b,a,null);for(var d=0,e=f.length;e>d;++d)f[d](b);return b},d.defaults=function(a){return c.util.extend(e,a),c.util.defaults(b,e),e},d.onChange=function(a){f.push(a)},b=b||d,d.defaults(a),d},c.util.pairHash=function(a,b){return a=0|a,b=0|b,(0|a)===(0|b)?-1:0|((0|a)>(0|b)?a<<16|65535&b:b<<16|65535&a)},c.util.bind=Function.prototype.bind?function(a,b,c){return c=Array.prototype.slice.call(arguments,1),Function.prototype.bind.apply(a,c)}:function(a,b,c){return c=Array.prototype.slice.call(arguments,2),function(){return a.apply(b,c.concat(Array.prototype.slice.call(arguments)))}},c.util.find=function(a,b){var c,d,e=a.length;for(c=0;e>c;c++)if(d=a[c],b(d,c,a))return d},c.util.filter=function(a,b){var c,d,e=a.length,f=[];for(c=0;e>c;c++)d=a[c],b(d,c,a)&&f.push(d);return f},function(){function a(a){a.length=0,v.length-1?0:-1:a?0:-1}function g(a){var b=this.cache,c=typeof a;if("boolean"===c||null==a)b[a]=!0;else{"number"!==c&&"string"!==c&&(c="object");var d="number"===c?a:y+a,e=b[c]||(b[c]={});"object"===c?(e[d]||(e[d]=[])).push(a):e[d]=!0}}function h(a){var b=-1,c=a.length,e=a[0],f=a[0|c/2],h=a[c-1];if(e&&"object"==typeof e&&f&&"object"==typeof f&&h&&"object"==typeof h)return!1;var i=d();i["false"]=i["null"]=i["true"]=i.undefined=!1;var j=d();for(j.array=a,j.cache=i,j.push=g;++b=u&&k===c.util.indexOf,o=i||n?e():m;if(n){var p=h(o);k=f,o=p}for(;++jb;b++)d=a[b],e=i(0,++f),h[f]=h[e],h[e]=d;return h},c.util.isObject=function(a){return!(!a||!n[typeof a])},c.util.isFunction=j,c.util.isArray=Array.isArray||function(a){return a&&"object"==typeof a&&"number"==typeof a.length&&s.call(a)===p||!1};var C=RegExp("^"+String(s).replace(/[.*+?^${}()|[\]\\]/g,"\\$&").replace(/toString| for [^\]]+/g,".*?")+"$");c.util.isPlainObject=Object.getPrototypeOf?function(a){if(!a||s.call(a)!==q)return!1;var b=a.valueOf,c=k(b)&&(c=Object.getPrototypeOf(b))&&Object.getPrototypeOf(c);return c?a===c||Object.getPrototypeOf(a)===c:l(a)}:l,c.util.uniq=function(a,b,c){return"boolean"!=typeof b&&null!=b&&(c=b,b=!1),m(a,b,c)};var D=function(a,b,c){var d,e=a,f=e;if(!e)return f;var g,h=arguments,i=0,j="number"==typeof c?2:h.length;for(j>2&&"function"==typeof h[j-1]&&(g=h[--j]);++id;){var f=d+e>>>1;c(a[f])=b.maxScratches)throw f},a.prototype={done:function(a){this._active=!1;for(var b=0;j>b;++b)this[b]=0;return h.push(this),a}},b=function k(b){if(b)return k.fn(b);var c=h.pop()||new a;return c._active=!0,c},b.maxScratches=100,b.maxIndex=20,b.fn=function(b){for(var c=[],d=0,e=b.length;e>d;d++)c.push(d);c="a"+c.join(",a");var f=new Function("fn, scratches, Scratch","return function("+c+"){ "+"var scratch = scratches.pop() || new Scratch( scratches );"+"scratch._active = true;"+"return scratch.done( fn(scratch, "+c+") );"+"};");return f(b,h,a)},b.register=function(c,f,h){var i=a.prototype,k=j++,l="_"+c+"Stack",m=h&&h.useFactory;if(c in i)throw g;i[c]=function(){var a=this[l]||(this[l]=[]),c=0|this[k];if(this[k]=c+1,!this._active)throw d;if(c>=b.maxIndex)throw e;return a[c]||(a[c]=m?f():new f)}},b.register("vector",c.vector),b.register("transform",c.transform),b}(),function(){function a(a){return a._priority_}c.scratchpad.register("event",function(){return{}},{useFactory:!0});var b=function d(){return this instanceof d?void 0:new d};b.prototype={on:function(b,d,e,f){var g,h,i;if(this._topics=this._topics||(this._topics={}),c.util.isObject(b)){for(var j in b)this.on(j,b[j],d,e);return this}return g=this._topics[b]||(this._topics[b]=[]),h=d,c.util.isObject(e)?(d=c.util.bind(d,e),d._bindfn_=h,d._one_=h._one_):f||(f=e),d._priority_=f,i=c.util.sortedIndex(g,d,a),g.splice(i,0,d),this},off:function(a,b){var d,e;if(!this._topics)return this;if(a===!0)return this._topics={},this;if(c.util.isObject(a)){for(var f in a)this.off(f,a[f]);return this}if(d=this._topics[a],!d)return this;if(b===!0)return this._topics[a]=[],this;for(var g=0,h=d.length;h>g;g++)if(e=d[g],e._bindfn_===b||e===b){d.splice(g,1);break}return this},emit:function(a,b){if(!this._topics)return this;var d,e,f=this._topics[a],g=f&&f.length,h=c.scratchpad();if(!g)return h.done(this);for(e=h.event(),e.topic=a,e.handler=d;g--;)d=f[g],d(b,e),d._one_&&f.splice(g,1);return h.done(this)},one:function(a,b,d){if(c.util.isObject(a)){for(var e in a)this.one(e,a[e],b,d);return this}return b._one_=!0,this.on(a,b,d),this}},c.util.pubsub=b}(),function(a){function b(){return l&&l.now?l.now()+l.timing.navigationStart:Date.now()}function d(){var c;j&&(c=b(),c&&(a.requestAnimationFrame(d),k.emit("tick",c)))}function e(){return j=!0,d(),this}function f(){return j=!1,this}function g(a){return k.on("tick",a),this}function h(a){return k.off("tick",a),this}function i(){return!!j}var j=!1,k=c.util.pubsub(),l=a.performance;c.util.ticker={now:b,start:e,stop:f,on:g,off:h,isActive:i}}(this),function(){var a=function(){return!0},b=c.util.indexOf,d=function(a,b){return function(c){return a(c[b])}},e=function(a,d){return function(e){e=d?e[d]:e;var f,g=0;if(c.util.isArray(e)){if(c.util.isArray(a)){if(f=e.length,f!==a.length)return!1;for(;f>g;){if(f--,-1===b(a,e[g])||-1===b(a,e[f]))return!1;g++}return!0}return b(e,a)>-1}return e===a}},f=function(a,b){var c=e(a,b);return function(a){return!c(a)}},g=function(a,d){return function(e){e=d?e[d]:e;var f,g=0;if(c.util.isArray(e)){for(f=e.length;f>g;){if(f--,b(a,e[g])>-1||b(a,e[f])>-1)return!0;g++}return!1}return b(a,e)>-1}},h=function(a,b){var c=g(a,b);return function(a){return!c(a)}},i=function(a){return a=c.vector(a),function(b){var d=b.aabb();return c.aabb.contains(d,a)}},j=function(a){return a.next?function(b){for(var c=a;c;){if(!c(b))return!1;c=c.next}return!0}:a},k=function(a){return a.next?function(b){for(var c=a;c;){if(c(b))return!0;c=c.next}return!1}:a},l={$eq:e,$ne:f,$in:g,$nin:h,$at:i},m=function n(b,f){var g,h,i,m,o,p;if(f){if("$or"===f||"$and"===f){for(g=0,h=b.length;h>g;++g)p=n(b[g]),o=o?o.next=p:m=p;return"$or"===f?k(m):j(m)}if(g=l[f])return g(b);throw"Unknown query operation: "+f}for(g in b)i=b[g],p="$"===g[0]?n(i,g):c.util.isPlainObject(i)?d(n(i),g):e(i,g),o=o?o.next=p:m=p;return j(m||a)};c.query=m}(this),function(){var a={priority:0};c.behavior=d("behavior",{init:function(b){this.options=c.util.options(a),this.options(b)},applyTo:function(a){return this._targets=a===!0?null:c.util.uniq(a),this},getTargets:function(){return this._targets||(this._world?this._world._bodies:[])},setWorld:function(a){return this.disconnect&&this._world&&this.disconnect(this._world),this._world=a,this.connect&&a&&this.connect(a),this},connect:function(a){this.behave&&a.on("integrate:positions",this.behave,this,this.options.priority)},disconnect:function(a){this.behave&&a.off("integrate:positions",this.behave)},behave:null})}(),function(){var a={hidden:!1,treatment:"dynamic",mass:1,restitution:1,cof:.8,view:null},b=1;c.body=d("body",{init:function(d){var e=c.vector;if(this.options=c.util.options(a,this),this.options(d),this.state={pos:e(this.x,this.y),vel:e(this.vx,this.vy),acc:e(),angular:{pos:this.angle||0,vel:this.angularVelocity||0,acc:0},old:{pos:e(),vel:e(),acc:e(),angular:{pos:0,vel:0,acc:0}}},delete this.x,delete this.y,delete this.vx,delete this.vy,delete this.angle,delete this.angularVelocity,0===this.mass)throw"Error: Bodies must have non-zero mass";this.uid=b++,this.geometry=c.geometry("point")},setWorld:function(a){return this.disconnect&&this._world&&this.disconnect(this._world),this._world=a,this.connect&&a&&this.connect(a),this},accelerate:function(a){return"dynamic"===this.treatment&&this.state.acc.vadd(a),this},applyForce:function(a,b){if("dynamic"!==this.treatment)return this;var d,e=c.scratchpad(),f=e.vector();return b&&this.moi&&(d=this.state,f.clone(b),this.state.angular.acc-=f.cross(a)/this.moi),this.accelerate(f.clone(a).mult(1/this.mass)),e.done(),this},aabb:function(){var a=this.state.angular.pos,b=this.geometry.aabb(a);return b.x+=this.state.pos.x,b.y+=this.state.pos.y,b},recalc:function(){return this}})}(),function(){c.geometry=d("geometry",{init:function(a){this.options=c.util.options(),this.options(a),this._aabb=new c.aabb},aabb:function(){return c.aabb.clone(this._aabb)},getFarthestHullPoint:function(a,b){return b=b||c.vector(),b.set(0,0)},getFarthestCorePoint:function(a,b){return b=b||c.vector(),b.set(0,0)}})}(),c.geometry.isPolygonConvex=function(a){var b=c.scratchpad(),d=b.vector(),e=b.vector(),f=b.vector(),g=!0,h=!1,i=a.length;if(!a||!i)return!1;if(3>i)return b.done(),g;d.clone(a[0]).vsub(f.clone(a[i-1]));for(var j=1;i>=j;++j){if(e.clone(a[j%i]).vsub(f.clone(a[(j-1)%i])),h===!1)h=d.cross(e);else if(h>0^d.cross(e)>0){g=!1;break}e.swap(d)}return b.done(),g},c.geometry.getPolygonMOI=function(a){var b,d=c.scratchpad(),e=d.vector(),f=d.vector(),g=0,h=0,i=a.length;if(2>i)return d.done(),0;if(2===i)return b=f.clone(a[1]).distSq(e.clone(a[0])),d.done(),b/12;e.clone(a[0]);for(var j=1;i>j;++j)f.clone(a[j]),b=Math.abs(f.cross(e)),g+=b*(f.normSq()+f.dot(e)+e.normSq()),h+=b,e.swap(f);return d.done(),g/(6*h)},c.geometry.isPointInPolygon=function(a,b){var d=c.scratchpad(),e=d.vector().clone(a),f=d.vector(),g=d.vector(),h=0,i=b.length;if(2>i)return h=e.equals(f.clone(b[0])),d.done(),h;if(2===i)return h=e.angle(f.clone(b[0])),h+=e.angle(f.clone(b[1])),d.done(),Math.abs(h)===Math.PI;f.clone(b[0]).vsub(e);for(var j=1;i>=j;++j)g.clone(b[j%i]).vsub(e),h+=g.angle(f),f.swap(g);return d.done(),Math.abs(h)>1e-6},c.geometry.getPolygonArea=function(a){var b=c.scratchpad(),d=b.vector(),e=b.vector(),f=0,g=a.length;if(3>g)return b.done(),0;d.clone(a[g-1]);for(var h=0;g>h;++h)e.clone(a[h]),f+=d.cross(e),d.swap(e);return b.done(),f/2},c.geometry.getPolygonCentroid=function(a){var b,d=c.scratchpad(),e=d.vector(),f=d.vector(),g=c.vector(),h=a.length;if(2>h)return d.done(),c.vector(a[0]);if(2===h)return d.done(),c.vector((a[1].x+a[0].x)/2,(a[1].y+a[0].y)/2);e.clone(a[h-1]);for(var i=0;h>i;++i)f.clone(a[i]),b=e.cross(f),e.vadd(f).mult(b),g.vadd(e),e.swap(f);return b=1/(6*c.geometry.getPolygonArea(a)),d.done(),g.mult(b)},c.geometry.nearestPointOnLine=function(a,b,d){var e,f,g=c.scratchpad(),h=g.vector().clone(a),i=g.vector().clone(b).vsub(h),j=g.vector().clone(d).vsub(h).vsub(i);return j.equals(c.vector.zero)?(g.done(),c.vector(b)):(e=-j.dot(i)/j.normSq(),f=1-e,0>=f?(g.done(),c.vector(d)):0>=e?(g.done(),c.vector(b)):(h=c.vector(d).mult(e).vadd(i.clone(b).mult(f)),g.done(),h))},function(){var a={drag:0};c.integrator=d("integrator",{init:function(){this.options=c.util.options(a)},setWorld:function(a){return this.disconnect&&this._world&&this.disconnect(this._world),this._world=a,this.connect&&a&&this.connect(a),this},integrate:function(a,b){var c=this._world;return this.integrateVelocities(a,b),c&&c.emit("integrate:velocities",{bodies:a,dt:b}),this.integratePositions(a,b),c&&c.emit("integrate:positions",{bodies:a,dt:b}),this},connect:null,disconnect:null,integrateVelocities:function(){throw"The integrator.integrateVelocities() method must be overriden"},integratePositions:function(){throw"The integrator.integratePositions() method must be overriden"}})}(),function(){var a={meta:!1,metaRefresh:200,width:600,height:600};c.renderer=d("renderer",{init:function(d){var e="string"==typeof d.el?b.getElementById(d.el):d.el;this.options=c.util.extend({},a,d),this.el=e?e:b.body,this.drawMeta=c.util.throttle(c.util.bind(this.drawMeta,this),this.options.metaRefresh)},setWorld:function(a){return this.disconnect&&this._world&&this.disconnect(this._world),this._world=a,this.connect&&a&&this.connect(a),this},render:function(a,b){var c,d;this.beforeRender&&this.beforeRender(),this._world.emit("beforeRender",{renderer:this,bodies:a,meta:b}),this.options.meta&&this.drawMeta(b),this._interpolateTime=b.interpolateTime;for(var e=0,f=a.length;f>e;++e)c=a[e],d=c.view||(c.view=this.createView(c.geometry,c.styles)),c.hidden||this.drawBody(c,d);return this},createView:function(){throw"You must override the renderer.createView() method."},drawMeta:function(){throw"You must override the renderer.drawMeta() method."},drawBody:function(){throw"You must override the renderer.drawBody() method."}})}(),function(){var a=function e(a,b,c){for(var d,f,g=function(){return e(a,b,c)};d=a.shift();)if(f=d.apply(b,c),f&&f.then)return f.then(g)},b={timestep:1e3/120,maxIPF:16,webworker:!1,integrator:"verlet"},d=function f(a,b){return this instanceof f?(this.init(a,b),void 0):new f(a,b)};d.prototype=c.util.extend({},c.util.pubsub.prototype,{init:function(d,e){var f=this;(c.util.isFunction(d)||c.util.isArray(d))&&(e=d,d={}),this._meta={fps:0,ipf:0},this._bodies=[],this._behaviors=[],this._integrator=null,this._renderer=null,this._paused=!1,this._warp=1,this._time=0,this.options=c.util.options(b),this.options.onChange(function(a){f.timestep(a.timestep)}),this.options(d),this.add(c.integrator(this.options.integrator)),c.util.isFunction(e)?a([e],this,[this,c]):c.util.isArray(e)&&a(e,this,[this,c])},options:null,add:function(a){var b=0,c=a&&a.length||0,d=c?a[0]:a;if(!d)return this;do switch(d.type){case"behavior":this.addBehavior(d);break;case"integrator":this.integrator(d);break;case"renderer":this.renderer(d);break;case"body":this.addBody(d);break;default:throw'Error: failed to add item of unknown type "'+d.type+'" to world'}while(++b-1},integrator:function(a){return void 0===a?this._integrator:this._integrator===a?this:(this._integrator&&(this._integrator.setWorld(null),this.emit("remove:integrator",{integrator:this._integrator})),a&&(this._integrator=a,this._integrator.setWorld(this),this.emit("add:integrator",{integrator:this._integrator})),this)},renderer:function(a){return void 0===a?this._renderer:this._renderer===a?this:(this._renderer&&(this._renderer.setWorld(null),this.emit("remove:renderer",{renderer:this._renderer})),a&&(this._renderer=a,this._renderer.setWorld(this),this.emit("add:renderer",{renderer:this._renderer})),this)},timestep:function(a){return a?(this._dt=a,this._maxJump=a*this.options.maxIPF,this):this._dt},addBehavior:function(a){return this.has(a)?this:(a.setWorld(this),this._behaviors.push(a),this.emit("add:behavior",{behavior:a}),this)},getBehaviors:function(){return[].concat(this._behaviors)},removeBehavior:function(a){var b=this._behaviors;if(a)for(var c=0,d=b.length;d>c;++c)if(a===b[c]){b.splice(c,1),a.setWorld(null),this.emit("remove:behavior",{behavior:a});break}return this},addBody:function(a){return this.has(a)?this:(a.setWorld(this),this._bodies.push(a),this.emit("add:body",{body:a}),this)},getBodies:function(){return[].concat(this._bodies)},removeBody:function(a){var b=this._bodies;if(a)for(var c=0,d=b.length;d>c;++c)if(a===b[c]){b.splice(c,1),a.setWorld(null),this.emit("remove:body",{body:a});break}return this},findOne:function(a){var b=this,d="function"==typeof a?a:c.query(a);return c.util.find(b._bodies,d)||!1},find:function(a){var b=this,d="function"==typeof a?a:c.query(a);return c.util.filter(b._bodies,d)},iterate:function(a){this._integrator.integrate(this._bodies,a)},step:function(a){var b,d,e,f=this._time,g=this._warp,h=1/g,i=this._dt,j=i*h,k=this._maxJump*h,l=this._meta;if(this._paused||void 0===this._animTime)return this._animTime=a||this._animTime||c.util.ticker.now(),this._paused||this.emit("step",l),this;if(a=a||this._animTime+j,b=a-this._animTime,b>k&&(this._animTime=a-k,b=k),d=b*g,e=f+d-i,e>=f)for(;e>=f;)f+=i,this._animTime+=j,this._time=f,this.iterate(i);return l.fps=1e3/(a-this._lastTime),l.ipf=(d/i).toFixed(2),l.interpolateTime=i+e-f,this._lastTime=a,this.emit("step",l),this},warp:function(a){return void 0===a?this._warp:(this._warp=a||1,this)},render:function(){if(!this._renderer)throw"No renderer added to world";return this._renderer.render(this._bodies,this._meta),this.emit("render",{bodies:this._bodies,meta:this._meta,renderer:this._renderer}),this},pause:function(){return this._paused=!0,this.emit("pause"),this},unpause:function(){return this._paused=!1,this.emit("unpause"),this},isPaused:function(){return!!this._paused},destroy:function(){var a=this;a.pause(),this.emit("destroy"),a.off(!0),a.remove(a.getBodies()),a.remove(a.getBehaviors()),a.integrator(null),a.renderer(null)}}),c.world=d}(),c.integrator("verlet",function(a){return c.body.mixin({started:function(a){return void 0!==a&&(this._started=!0),!!this._started}}),{init:function(b){a.init.call(this,b)},integrateVelocities:function(a,b){for(var c,d=b*b,e=1-this.options.drag,f=null,g=0,h=a.length;h>g;++g)f=a[g],c=f.state,"static"!==f.treatment?(c.vel.equals(c.old.vel)&&f.started()?c.vel.clone(c.pos).vsub(c.old.pos):(c.old.pos.clone(c.pos).vsub(c.vel),c.vel.mult(b)),e&&c.vel.mult(e),c.vel.vadd(c.acc.mult(d)),c.vel.mult(1/b),c.old.vel.clone(c.vel),c.acc.zero(),c.angular.vel===c.old.angular.vel&&f.started()?c.angular.vel=c.angular.pos-c.old.angular.pos:(c.old.angular.pos=c.angular.pos-c.angular.vel,c.angular.vel*=b),c.angular.vel+=c.angular.acc*d,c.angular.vel/=b,c.old.angular.vel=c.angular.vel,c.angular.acc=0,f.started(!0)):(c.vel.zero(),c.acc.zero(),c.angular.vel=0,c.angular.acc=0)},integratePositions:function(a,b){for(var c,d=null,e=0,f=a.length;f>e;++e)d=a[e],c=d.state,"static"!==d.treatment&&(c.vel.mult(b),c.old.pos.clone(c.pos),c.pos.vadd(c.vel),c.vel.mult(1/b),c.old.vel.clone(c.vel),c.angular.vel*=b,c.old.angular.pos=c.angular.pos,c.angular.pos+=c.angular.vel,c.angular.vel/=b,c.old.angular.vel=c.angular.vel)}}}),c.geometry("point",function(){}),c.body("point",function(a){return{init:function(b){a.init.call(this,b),this.moi=0}}}),c.geometry("circle",function(a){var b={radius:1};return{init:function(d){a.init.call(this,d),this.options.defaults(b),this.options.onChange(function(a){this.radius=a.radius}),this.options(d),this._aabb=c.aabb(),this.radius=this.options.radius},aabb:function(){var a=this.radius;return this._aabb.hw!==a&&(this._aabb=c.aabb(-a,-a,a,a)),c.aabb.clone(this._aabb)},getFarthestHullPoint:function(a,b){return b=b||c.vector(),b.clone(a).normalize().mult(this.radius)},getFarthestCorePoint:function(a,b,d){return b=b||c.vector(),b.clone(a).normalize().mult(this.radius-d)}}}),c.geometry("convex-polygon",function(a){var b="Error: The vertices specified do not match that of a _convex_ polygon.",d={};return{init:function(b){var c=this;a.init.call(this,b),this.options.defaults(d),this.options.onChange(function(a){c.setVertices(a.vertices||[])}),this.options(b),c.setVertices(this.options.vertices||[])},setVertices:function(a){var d=c.scratchpad(),e=d.transform(),f=this.vertices=[];if(!c.geometry.isPolygonConvex(a))throw b;e.setRotation(0),e.setTranslation(c.geometry.getPolygonCentroid(a).negate());for(var g=0,h=a.length;h>g;++g)f.push(c.vector(a[g]).translate(e));return this._area=c.geometry.getPolygonArea(f),this._aabb=!1,d.done(),this},aabb:function(a){if(!a&&this._aabb)return c.aabb.clone(this._aabb);var b,d=c.scratchpad(),e=d.vector(),f=d.transform().setRotation(a||0),g=d.vector().set(1,0).rotateInv(f),h=d.vector().set(0,1).rotateInv(f),i=this.getFarthestHullPoint(g,e).proj(g),j=-this.getFarthestHullPoint(g.negate(),e).proj(g),k=this.getFarthestHullPoint(h,e).proj(h),l=-this.getFarthestHullPoint(h.negate(),e).proj(h);return b=c.aabb(j,l,i,k),a||(this._aabb=c.aabb.clone(b)),d.done(),b},getFarthestHullPoint:function(a,b,d){var e,f,g,h=this.vertices,i=h.length,j=2;if(b=b||c.vector(),2>i)return d&&(d.idx=0),b.clone(h[0]);if(f=h[0].dot(a),e=h[1].dot(a),2===i)return g=e>=f?1:0,d&&(d.idx=g),b.clone(h[g]);if(e>=f){for(;i>j&&e>=f;)f=e,e=h[j].dot(a),j++;return e>=f&&j++,g=j-2,d&&(d.idx=j-2),b.clone(h[g])}for(j=i;j>1&&f>=e;)j--,e=f,f=h[j].dot(a);return g=(j+1)%i,d&&(d.idx=g),b.clone(h[g])},getFarthestCorePoint:function(a,b,d){var e,f=c.scratchpad(),g=f.vector(),h=f.vector(),i=this.vertices,j=i.length,k=this._area>0,l={};return b=this.getFarthestHullPoint(a,b,l),g.clone(i[(l.idx+1)%j]).vsub(b).normalize().perp(k),h.clone(i[(l.idx-1+j)%j]).vsub(b).normalize().perp(!k),e=d/(1+g.dot(h)),b.vadd(g.vadd(h).mult(e)),f.done(),b -}}}),c.geometry("rectangle",function(a){var b={};return{init:function(c){var d=this;a.init.call(this,c),this.options.defaults(b),this.options.onChange(function(){d.width=d.options.width||1,d.height=d.options.height||1}),this.options(c)},aabb:function(a){if(!a)return c.aabb(this.width,this.height);var b=c.scratchpad(),d=b.vector(),e=b.transform().setRotation(a||0),f=b.vector().set(1,0).rotateInv(e),g=b.vector().set(0,1).rotateInv(e),h=this.getFarthestHullPoint(f,d).proj(f),i=-this.getFarthestHullPoint(f.negate(),d).proj(f),j=this.getFarthestHullPoint(g,d).proj(g),k=-this.getFarthestHullPoint(g.negate(),d).proj(g);return b.done(),c.aabb(i,k,h,j)},getFarthestHullPoint:function(a,b){b=b||new c.vector;var d=a.x,e=a.y;return d=0===d?0:0>d?.5*-this.width:.5*this.width,e=0===e?0:0>e?.5*-this.height:.5*this.height,b.set(d,e)},getFarthestCorePoint:function(a,b,c){var d,e;return b=this.getFarthestHullPoint(a,b),d=b.x,e=b.y,b.x=0===d?0:0>d?d+c:d-c,b.y=0===e?0:0>e?e+c:e-c,b}}}),c.body("circle",function(a){var b={radius:1};return{init:function(d){a.init.call(this,d),d=c.util.extend({},b,d),this.geometry=c.geometry("circle",{radius:d.radius}),this.recalc()},recalc:function(){a.recalc.call(this),this.moi=this.mass*this.geometry.radius*this.geometry.radius/2}}}),c.body("convex-polygon",function(a){var b={};return{init:function(d){a.init.call(this,d),d=c.util.extend({},b,d),this.geometry=c.geometry("convex-polygon",{vertices:d.vertices}),this.recalc()},recalc:function(){a.recalc.call(this),this.moi=c.geometry.getPolygonMOI(this.geometry.vertices)}}}),c.body("rectangle",function(a){var b={};return{init:function(d){a.init.call(this,d),d=c.util.extend({},b,d),this.geometry=c.geometry("rectangle",{width:d.width,height:d.height}),this.recalc()},recalc:function(){var b=this.geometry.width,c=this.geometry.height;a.recalc.call(this),this.moi=(b*b+c*c)*this.mass/12}}}),c.behavior("attractor",function(a){var b={pos:null,strength:1,order:2,max:!1,min:!1};return{init:function(d){var e=this;this._pos=new c.vector,a.init.call(this),this.options.defaults(b),this.options.onChange(function(a){e._maxDist=a.max===!1?1/0:a.max,e._minDist=a.min?a.min:10,e.position(a.pos)}),this.options(d)},position:function(a){var b=this;return a?(this._pos.clone(a),b):this._pos.values()},behave:function(){for(var a,b,d,e=this.getTargets(),f=this.options.order,g=this.options.strength,h=this._minDist,i=this._maxDist,j=c.scratchpad(),k=j.vector(),l=0,m=e.length;m>l;l++)a=e[l],k.clone(this._pos),k.vsub(a.state.pos),b=k.norm(),b>h&&i>b&&(d=g/Math.pow(b,f),a.accelerate(k.normalize().mult(d)));j.done()}}}),c.behavior("body-collision-detection",function(a){var b=[],d=function(a,d){var e=c.util.pairHash(a.uid,d.uid),f=b[e];return f||(f=b[e]=function(b){var e=c.scratchpad(),g=f.tA,h=f.tB,i=e.vector(),j=e.vector(),k=f.marginA,l=f.marginB;return f.useCore?(i=a.geometry.getFarthestCorePoint(b.rotateInv(g),i,k).transform(g),j=d.geometry.getFarthestCorePoint(b.rotate(g).rotateInv(h).negate(),j,l).transform(h)):(i=a.geometry.getFarthestHullPoint(b.rotateInv(g),i).transform(g),j=d.geometry.getFarthestHullPoint(b.rotate(g).rotateInv(h).negate(),j).transform(h)),b.negate().rotate(h),e.done({a:i.values(),b:j.values(),pt:i.vsub(j).values()})},f.tA=c.transform(),f.tB=c.transform()),f.useCore=!1,f.margin=0,f.tA.setTranslation(a.state.pos).setRotation(a.state.angular.pos),f.tB.setTranslation(d.state.pos).setRotation(d.state.angular.pos),f.bodyA=a,f.bodyB=d,f},e=function(a,b){var e,f,g,h=c.scratchpad(),i=h.vector(),j=h.vector(),k=!1,l=a.aabb(),m=Math.min(l.hw,l.hh),n=b.aabb(),o=Math.min(n.hw,n.hh);if(g=d(a,b),i.clone(a.state.pos).vsub(b.state.pos),f=c.gjk(g,i,!0),f.overlap){for(k={bodyA:a,bodyB:b},g.useCore=!0,g.marginA=0,g.marginB=0;f.overlap&&(g.marginA=d&&(g={bodyA:a,bodyB:b,norm:f.normalize().values(),mtv:f.mult(-d).values(),pos:f.normalize().mult(a.geometry.radius).values(),overlap:-d}),e.done(g)},g=function(a,b){return"static"!==a.treatment&&"kinematic"!==a.treatment||"static"!==b.treatment&&"kinematic"!==b.treatment?"circle"===a.geometry.name&&"circle"===b.geometry.name?f(a,b):e(a,b):!1},h={check:"collisions:candidates",channel:"collisions:detected"};return{init:function(b){a.init.call(this),this.options.defaults(h),this.options(b)},connect:function(a){this.options.check===!0?a.on("integrate:velocities",this.checkAll,this):a.on(this.options.check,this.check,this)},disconnect:function(a){this.options.check===!0?a.off("integrate:velocities",this.checkAll):a.off(this.options.check,this.check)},check:function(a){for(var b,d,e=a.candidates,f=this.getTargets(),h=[],i=0,j=e.length;j>i;++i)b=e[i],(f===this._world._bodies||c.util.indexOf(f,b.bodyA)>-1&&c.util.indexOf(f,b.bodyB)>-1)&&(d=g(b.bodyA,b.bodyB),d&&h.push(d));h.length&&this._world.emit(this.options.channel,{collisions:h})},checkAll:function(a){for(var b,c,d,e=this.getTargets(),f=(a.dt,[]),h=0,i=e.length;i>h;h++){b=e[h];for(var j=h+1;i>j;j++)c=e[j],d=g(b,c),d&&f.push(d)}f.length&&this._world.emit(this.options.channel,{collisions:f})}}}),c.behavior("body-impulse-response",function(a){var b={check:"collisions:detected"};return{init:function(c){a.init.call(this),this.options.defaults(b),this.options(c)},applyTo:!1,connect:function(a){a.on(this.options.check,this.respond,this)},disconnect:function(a){a.off(this.options.check,this.respond)},collideBodies:function(a,b,d,e,f,g){var h="static"===a.treatment||"kinematic"===a.treatment,i="static"===b.treatment||"kinematic"===b.treatment,j=c.scratchpad(),k=j.vector().clone(f);if(h&&i)return j.done(),void 0;h?b.state.pos.vadd(k):i?a.state.pos.vsub(k):(k.mult(.5),a.state.pos.vsub(k),b.state.pos.vadd(k));var l,m,n,o=h?0:1/a.moi,p=i?0:1/b.moi,q=h?0:1/a.mass,r=i?0:1/b.mass,s=g?0:a.restitution*b.restitution,t=a.cof*b.cof,u=j.vector().clone(d),v=j.vector().clone(u).perp(),w=j.vector().clone(e),x=j.vector().clone(e).vadd(a.state.pos).vsub(b.state.pos),y=j.vector(),z=a.state.angular.vel,A=b.state.angular.vel,B=j.vector().clone(b.state.vel).vadd(y.clone(x).perp().mult(A)).vsub(a.state.vel).vsub(y.clone(w).perp().mult(z)),C=w.proj(u),D=w.proj(v),E=x.proj(u),F=x.proj(v),G=B.proj(u),H=B.proj(v),I=!1;return G>=0?(j.done(),void 0):(o=1/0===o?0:o,p=1/0===p?0:p,l=-((1+s)*G)/(q+r+o*D*D+p*F*F),h?(b.state.vel.vadd(u.mult(l*r)),b.state.angular.vel-=l*p*F):i?(a.state.vel.vsub(u.mult(l*q)),a.state.angular.vel+=l*o*D):(b.state.vel.vadd(u.mult(l*r)),b.state.angular.vel-=l*p*F,a.state.vel.vsub(u.mult(q*b.mass)),a.state.angular.vel+=l*o*D),t&&H&&(n=H/(q+r+o*C*C+p*E*E),I?l=n:(m=0>H?-1:1,l*=m*t,l=1===m?Math.min(l,n):Math.max(l,n)),h?(b.state.vel.vsub(v.mult(l*r)),b.state.angular.vel-=l*p*E):i?(a.state.vel.vadd(v.mult(l*q)),a.state.angular.vel+=l*o*C):(b.state.vel.vsub(v.mult(l*r)),b.state.angular.vel-=l*p*E,a.state.vel.vadd(v.mult(q*b.mass)),a.state.angular.vel+=l*o*C)),j.done(),void 0)},respond:function(a){for(var b,d=this,e=c.util.shuffle(a.collisions),f=0,g=e.length;g>f;++f)b=e[f],d.collideBodies(b.bodyA,b.bodyB,b.norm,b.pos,b.mtv)}}}),c.behavior("constant-acceleration",function(a){var b={acc:{x:0,y:4e-4}};return{init:function(d){a.init.call(this),this.options.defaults(b),this.options(d),this._acc=c.vector(),this.setAcceleration(this.options.acc),delete this.options.acc},setAcceleration:function(a){return this._acc.clone(a),this},behave:function(){for(var a=this.getTargets(),b=0,c=a.length;c>b;++b)a[b].accelerate(this._acc)}}}),c.behavior("edge-collision-detection",function(a){var b=function(a,b,d){var e,f=a.aabb(),g=c.scratchpad(),h=g.transform(),i=g.vector(),j=g.vector(),k=!1,l=[];return e=f.x+f.hw-b.max.x,e>=0&&(i.set(1,0).rotateInv(h.setRotation(a.state.angular.pos)),k={bodyA:a,bodyB:d,overlap:e,norm:{x:1,y:0},mtv:{x:e,y:0},pos:a.geometry.getFarthestHullPoint(i,j).rotate(h).values()},l.push(k)),e=f.y+f.hh-b.max.y,e>=0&&(i.set(0,1).rotateInv(h.setRotation(a.state.angular.pos)),k={bodyA:a,bodyB:d,overlap:e,norm:{x:0,y:1},mtv:{x:0,y:e},pos:a.geometry.getFarthestHullPoint(i,j).rotate(h).values()},l.push(k)),e=b.min.x-(f.x-f.hw),e>=0&&(i.set(-1,0).rotateInv(h.setRotation(a.state.angular.pos)),k={bodyA:a,bodyB:d,overlap:e,norm:{x:-1,y:0},mtv:{x:-e,y:0},pos:a.geometry.getFarthestHullPoint(i,j).rotate(h).values()},l.push(k)),e=b.min.y-(f.y-f.hh),e>=0&&(i.set(0,-1).rotateInv(h.setRotation(a.state.angular.pos)),k={bodyA:a,bodyB:d,overlap:e,norm:{x:0,y:-1},mtv:{x:0,y:-e},pos:a.geometry.getFarthestHullPoint(i,j).rotate(h).values()},l.push(k)),g.done(),l},d=function(a,c,d){return b(a,c,d)},e={aabb:null,restitution:.99,cof:1,channel:"collisions:detected"};return{init:function(b){a.init.call(this),this.options.defaults(e),this.options(b),this.setAABB(this.options.aabb),this.restitution=this.options.restitution,this.body=c.body("point",{treatment:"static",restitution:this.options.restitution,cof:this.options.cof})},setAABB:function(a){if(!a)throw"Error: aabb not set";return this._edges={min:{x:a.x-a.hw,y:a.y-a.hh},max:{x:a.x+a.hw,y:a.y+a.hh}},this},connect:function(a){a.on("integrate:velocities",this.checkAll,this)},disconnect:function(a){a.off("integrate:velocities",this.checkAll)},checkAll:function(a){for(var b,c,e=this.getTargets(),f=(a.dt,[]),g=this._edges,h=this.body,i=0,j=e.length;j>i;i++)b=e[i],"dynamic"===b.treatment&&(c=d(b,g,h),c&&f.push.apply(f,c));f.length&&this._world.emit(this.options.channel,{collisions:f})}}}),c.behavior("interactive",function(a){if(!b)return{};var d={el:null,moveThrottle:10,minVel:{x:-5,y:-5},maxVel:{x:5,y:5}},e=function(a){var b=0,c=0;if(a.offsetParent)do b+=a.offsetLeft,c+=a.offsetTop;while(a=a.offsetParent);return{left:b,top:c}},f=function(a){var b=e(a.target),c=a.changedTouches&&a.changedTouches[0]||a,d=c.pageX-b.left,f=c.pageY-b.top;return{x:d,y:f}};return{init:function(e){var g,h,i=this;if(a.init.call(this),this.options.defaults(d),this.options(e),this.mousePos=new c.vector,this.mousePosOld=new c.vector,this.offset=new c.vector,this.el="string"==typeof this.options.el?b.getElementById(this.options.el):this.options.el,!this.el)throw"No DOM element specified";var j=function(a){var b,d=f(a);i._world&&(b=i._world.findOne({$at:new c.vector(d.x,d.y)}),b?(g=b.treatment,b.treatment="kinematic",b.state.vel.zero(),b.state.angular.vel=0,i.body=b,i.mousePos.clone(d),i.offset.clone(d).vsub(b.state.pos),d.body=b,i._world.emit("interact:grab",d)):i._world.emit("interact:poke",d))},k=c.util.throttle(function(a){var b=f(a);i.body&&(h=c.util.ticker.now(),i.mousePosOld.clone(i.mousePos),i.mousePos.set(b.x,b.y),b.body=i.body),i._world.emit("interact:move",b)},i.options.moveThrottle),l=function(a){var b=f(a),d=Math.max(c.util.ticker.now()-h,i.options.moveThrottle);i.mousePos.set(b.x,b.y),i.body&&(i.body.treatment=g,i.body.state.vel.clone(i.mousePos).vsub(i.mousePosOld).mult(1/d),i.body.state.vel.clamp(i.options.minVel,i.options.maxVel),i.body=!1),i._world&&i._world.emit("interact:release",b)};this.el.addEventListener("mousedown",j),this.el.addEventListener("touchstart",j),this.el.addEventListener("mousemove",k),this.el.addEventListener("touchmove",k),this.el.addEventListener("mouseup",l),this.el.addEventListener("touchend",l)},connect:function(a){a.on("integrate:positions",this.behave,this)},disconnect:function(a){a.off("integrate:positions",this.behave)},behave:function(a){var b,c=this,d=Math.max(a.dt,c.options.moveThrottle);c.body&&(b=c.body.state,b.vel.clone(c.mousePos).vsub(c.offset).vsub(b.pos).mult(1/d))}}}),c.behavior("newtonian",function(a){var b={strength:1,max:!1,min:!1};return{init:function(c){var d=this;a.init.call(this),this.options.defaults(b),this.options.onChange(function(a){d._maxDistSq=a.max===!1?1/0:a.max*a.max,d._minDistSq=a.min?a.min*a.min:100*a.strength}),this.options(c)},behave:function(){for(var a,b,d,e,f=this.getTargets(),g=this.options.strength,h=this._minDistSq,i=this._maxDistSq,j=c.scratchpad(),k=j.vector(),l=0,m=f.length;m>l;l++){a=f[l];for(var n=l+1;m>n;n++)b=f[n],k.clone(b.state.pos),k.vsub(a.state.pos),d=k.normSq(),d>h&&i>d&&(e=g/d,a.accelerate(k.normalize().mult(e*b.mass)),b.accelerate(k.mult(a.mass/b.mass).negate()))}j.done()}}}),c.behavior("sweep-prune",function(a){var b=1,d=function(){return b++},e={x:0,y:1},f=2,g=c.util.pairHash;return{init:function(b){a.init.call(this),this.options.defaults({channel:"collisions:candidates"}),this.options(b),this.encounters=[],this.candidates=[],this.clear()},clear:function(){this.tracked=[],this.pairs=[],this.intervalLists=[];for(var a=0;f>a;++a)this.intervalLists[a]=[]},connect:function(a){a.on("add:body",this.trackBody,this),a.on("remove:body",this.untrackBody,this),a.on("integrate:velocities",this.sweep,this);for(var b=a.getBodies(),c=0,d=b.length;d>c;++c)this.trackBody({body:b[c]})},disconnect:function(a){a.off("add:body",this.trackBody),a.off("remove:body",this.untrackBody),a.off("integrate:velocities",this.sweep),this.clear()},broadPhase:function(){return this.updateIntervals(),this.sortIntervalLists(),this.checkOverlaps()},sortIntervalLists:function(){for(var a,b,c,d,e,g,h,i,j,k=0;f>k;++k)for(a=this.intervalLists[k],c=0,b=a.length,j=k;++c0&&(i>g||i===g&&h.type&&!e.type);)a[d]=h,d--,h=a[d-1],i=h&&h.val.get(j);a[d]=e}},getPair:function(a,b,c){var d=g(a.id,b.id);if(d===!1)return null;var e=this.pairs[d];if(!e){if(!c)return null;e=this.pairs[d]={bodyA:a.body,bodyB:b.body,flag:1}}return e},checkOverlaps:function(){var a,b,c,d,g,h,i,j,k,l=1<p;++p)for(a=0===p,g=this.intervalLists[p],i=0,h=g.length;h>i;i++)if(d=g[i],b=d.tracker,d.type)for(j=n,j=n-1;j>=0;j--)c=m[j],c===b?(n-1>j?m[j]=m.pop():m.pop(),n--):(k=this.getPair(b,c,a),k&&(k.flag>l&&(k.flag=1),k.flag=k.flag<=0;)a=h[i],b=a.interval,f.clone(a.body.state.pos),d=a.body.aabb(),g.set(d.hw,d.hh),b.min.val.clone(f).vsub(g),b.max.val.clone(f).vadd(g);e.done()},trackBody:function(a){var b=a.body,e={id:d(),body:b},g={min:{type:!1,val:c.vector(),tracker:e},max:{type:!0,val:c.vector(),tracker:e}};e.interval=g,this.tracked.push(e);for(var h=0;f>h;++h)this.intervalLists[h].push(g.min,g.max)},untrackBody:function(a){for(var b,c,d,e,g=a.body,h=this.tracked,i=0,j=h.length;j>i;++i)if(d=h[i],d.body===g){h.splice(i,1);for(var k=0;f>k;++k){e=0,b=this.intervalLists[k];for(var l=0,m=b.length;m>l;++l)if(c=b[l],c===d.interval.min||c===d.interval.max){if(b.splice(l,1),l--,j--,e>0)break;e++}}break}},sweep:function(){var a,b=this;a=b.broadPhase(),a.length&&this._world.emit(this.options.channel,{candidates:a})}}}),c.behavior("verlet-constraints",function(a){var b=2*Math.PI,d={iterations:2};return{init:function(b){a.init.call(this),this.options.defaults(d),this.options(b),this._distanceConstraints=[],this._angleConstraints=[]},connect:function(a){var b=a.integrator();if(b&&b.name.indexOf("verlet")<0)throw'The rigid constraint manager needs a world with a "verlet" compatible integrator.';a.on("integrate:positions",this.resolve,this)},disconnect:function(a){a.off("integrate:positions",this.resolve)},drop:function(){return this._distanceConstraints=[],this._angleConstraints=[],this},distanceConstraint:function(a,b,d,e){var f;return a&&b?(f={id:c.util.uniqueId("dis-constraint"),type:"dis",bodyA:a,bodyB:b,stiffness:d||.5,targetLength:e||b.state.pos.dist(a.state.pos)},f.targetLengthSq=f.targetLength*f.targetLength,this._distanceConstraints.push(f),f):!1},angleConstraint:function(a,b,d,e,f){var g;return a&&b?(g={id:c.util.uniqueId("ang-constraint"),type:"ang",bodyA:a,bodyB:b,bodyC:d,stiffness:e||.5,targetAngle:f||b.state.pos.angle2(a.state.pos,d.state.pos)},this._angleConstraints.push(g),g):!1},remove:function(a){var b,d,e,f,g;if(e=c.util.isObject(a),d=e?a.type:a.substr(0,3),b="ang"===d?this._angleConstraints:this._distanceConstraints,e){for(f=0,g=b.length;g>f;++f)if(b[f]===a)return b.splice(f,1),this}else for(f=0,g=b.length;g>f;++f)if(b[f].id===a)return b.splice(f,1),this;return this},resolveAngleConstraints:function(a){for(var d,e,f,g,h=this._angleConstraints,i=c.scratchpad(),j=i.transform(),k=0,l=h.length;l>k;++k)d=h[k],e=d.bodyB.state.pos.angle2(d.bodyA.state.pos,d.bodyC.state.pos),f=e-d.targetAngle,f&&(f<=-Math.PI?f+=b:f>=Math.PI&&(f-=b),j.setTranslation(d.bodyB.state.pos),f*=-a*d.stiffness,"dynamic"===d.bodyA.treatment&&"dynamic"===d.bodyB.treatment&&"dynamic"===d.bodyC.treatment&&(g=1/(d.bodyA.mass+d.bodyB.mass+d.bodyC.mass)),"dynamic"===d.bodyA.treatment&&(e="dynamic"===d.bodyB.treatment&&"dynamic"===d.bodyC.treatment?f*(d.bodyB.mass+d.bodyC.mass)*g:"dynamic"!==d.bodyB.treatment?f*d.bodyC.mass/(d.bodyC.mass+d.bodyA.mass):f*d.bodyB.mass/(d.bodyB.mass+d.bodyA.mass),j.setRotation(e),d.bodyA.state.pos.translateInv(j),d.bodyA.state.pos.rotate(j),d.bodyA.state.pos.translate(j)),"dynamic"===d.bodyC.treatment&&(e="dynamic"===d.bodyA.treatment&&"dynamic"===d.bodyB.treatment?-f*(d.bodyB.mass+d.bodyA.mass)*g:"dynamic"!==d.bodyB.treatment?-f*d.bodyA.mass/(d.bodyC.mass+d.bodyA.mass):-f*d.bodyB.mass/(d.bodyB.mass+d.bodyC.mass),j.setRotation(e),d.bodyC.state.pos.translateInv(j),d.bodyC.state.pos.rotate(j),d.bodyC.state.pos.translate(j)),"dynamic"===d.bodyB.treatment&&(e="dynamic"===d.bodyA.treatment&&"dynamic"===d.bodyC.treatment?f*(d.bodyA.mass+d.bodyC.mass)*g:"dynamic"!==d.bodyA.treatment?f*d.bodyC.mass/(d.bodyC.mass+d.bodyB.mass):f*d.bodyA.mass/(d.bodyA.mass+d.bodyC.mass),j.setRotation(e).setTranslation(d.bodyA.state.pos),d.bodyB.state.pos.translateInv(j),d.bodyB.state.pos.rotate(j),d.bodyB.state.pos.translate(j),j.setTranslation(d.bodyC.state.pos),d.bodyB.state.pos.translateInv(j),d.bodyB.state.pos.rotateInv(j),d.bodyB.state.pos.translate(j)));i.done()},resolveDistanceConstraints:function(a){for(var b,d,e,f,g=this._distanceConstraints,h=c.scratchpad(),i=h.vector(),j=0,k=g.length;k>j;++j)b=g[j],i.clone(b.bodyB.state.pos).vsub(b.bodyA.state.pos),d=i.normSq()||1e-4*Math.random(),e=a*b.stiffness*(d-b.targetLengthSq)/d,i.mult(e),f="dynamic"!==b.bodyA.treatment||"dynamic"!==b.bodyB.treatment?1:b.bodyB.mass/(b.bodyA.mass+b.bodyB.mass),"dynamic"===b.bodyA.treatment&&("dynamic"===b.bodyB.treatment&&i.mult(f),b.bodyA.state.pos.vadd(i),"dynamic"===b.bodyB.treatment&&i.mult(1/f)),"dynamic"===b.bodyB.treatment&&("dynamic"===b.bodyA.treatment&&i.mult(1-f),b.bodyB.state.pos.vsub(i));h.done()},shuffleConstraints:function(){this._distanceConstraints=c.util.shuffle(this._distanceConstraints),this._angleConstraints=c.util.shuffle(this._angleConstraints)},resolve:function(){for(var a=this.options.iterations,b=1/a,c=0;a>c;c++)this.resolveDistanceConstraints(b),this.resolveAngleConstraints(b)},getConstraints:function(){return{distanceConstraints:[].concat(this._distanceConstraints),angleConstraints:[].concat(this._angleConstraints)}}}}),c.integrator("improved-euler",function(a){return{init:function(b){a.init.call(this,b)},integrateVelocities:function(a,b){for(var c,d=1-this.options.drag,e=null,f=0,g=a.length;g>f;++f)e=a[f],c=e.state,"static"!==e.treatment?(c.old.vel.clone(c.vel),c.old.acc.clone(c.acc),c.vel.vadd(c.acc.mult(b)),d&&c.vel.mult(d),c.acc.zero(),c.old.angular.vel=c.angular.vel,c.angular.vel+=c.angular.acc*b,c.angular.acc=0):(c.vel.zero(),c.acc.zero(),c.angular.vel=0,c.angular.acc=0)},integratePositions:function(a,b){for(var d,e=.5*b*b,f=null,g=c.scratchpad(),h=g.vector(),i=0,j=a.length;j>i;++i)f=a[i],d=f.state,"static"!==f.treatment&&(d.old.pos.clone(d.pos),h.clone(d.old.vel),d.pos.vadd(h.mult(b)).vadd(d.old.acc.mult(e)),d.old.acc.zero(),d.old.angular.pos=d.angular.pos,d.angular.pos+=d.old.angular.vel*b+d.old.angular.acc*e,d.old.angular.acc=0);g.done()}}}),c.renderer("canvas",function(a){if(!b)return{};var d=2*Math.PI,e=function(a,c){var d=b.createElement(a||"div");return c&&(d.innerHTML=c),d},f={white:"#fff",violet:"#542437",blue:"#53777A"},g={debug:!1,metaEl:null,styles:{circle:{strokeStyle:f.blue,lineWidth:1,fillStyle:f.blue,angleIndicator:f.white},"convex-polygon":{strokeStyle:f.violet,lineWidth:1,fillStyle:f.violet,angleIndicator:f.white}},offset:{x:0,y:0}},h=function(a,b){return c.util.isPlainObject(b)?c.util.extend({},a,b,h):void 0!==b?b:a};return{init:function(d){if(a.init.call(this,d),this.options=c.util.extend({},g,this.options,h),this.options.offset=c.vector(this.options.offset),this.hiddenCanvas=b.createElement("canvas"),this.hiddenCanvas.width=this.hiddenCanvas.height=100,!this.hiddenCanvas.getContext)throw"Canvas not supported";this.hiddenCtx=this.hiddenCanvas.getContext("2d");var f=this.el;if("CANVAS"!==f.nodeName.toUpperCase()&&(f=b.createElement("canvas"),this.el.appendChild(f),"string"==typeof this.options.el&&this.el===b.body&&(f.id=this.options.el),this.el=f),this.ctx=f.getContext("2d"),this.els={},this.options.meta){var i=this.options.metaEl||e();i.className="pjs-meta",this.els.fps=e("span"),this.els.ipf=e("span"),i.appendChild(e("span","fps: ")),i.appendChild(this.els.fps),i.appendChild(e("br")),i.appendChild(e("span","ipf: ")),i.appendChild(this.els.ipf),f.parentNode.insertBefore(i,f)}this._layers={},this.addLayer("main",this.el),this.resize(this.options.width,this.options.height)},layer:function(a){return a in this._layers?this._layers[a]:null},addLayer:function(a,d,e){var f=this,g=[],h=c.util.extend({},this.options.styles),i={id:a,el:d||b.createElement("canvas"),options:c.util.options({width:this.el.width,height:this.el.height,manual:!1,autoResize:!0,follow:null,offset:null,scale:1,zIndex:1})(e)};if(a in this._layers)throw'Layer "'+a+'" already added.';return this.el.parentNode.insertBefore(i.el,this.el),i.el.style.position="absolute",i.el.style.zIndex=i.options.zIndex,i.el.className+=" pjs-layer-"+i.id,i.ctx=i.el.getContext("2d"),i.ctx.scale(1,1),i.el.width=i.options.width,i.el.height=i.options.height,i.bodies=g,i.reset=function(a){return g=a||[],i},i.addToStack=function(a){return c.util.isArray(a)?g.push.apply(g,a):g.push(a),i},i.removeFromStack=function(a){var b,d;if(c.util.isArray(a))for(b=0,d=a.length;d>b;++b)i.removeFromStack(a[b]);else b=c.util.indexOf(g,a),b>-1&&g.splice(b,1);return i},i.render=function(a){var b,d,e,j=c.scratchpad(),k=j.vector().set(0,0),l=i.options.scale,m=g.length,n=m||"main"!==i.id?g:f._world._bodies;if(i.options.manual)return j.done(),i;for(i.options.offset&&("center"===i.options.offset?k.add(.5*i.el.width,.5*i.el.height).mult(1/l):k.vadd(i.options.offset).mult(1/l)),i.options.follow&&k.vsub(i.options.follow.state.pos),a!==!1&&i.ctx.clearRect(0,0,i.el.width,i.el.height),1!==l&&(i.ctx.save(),i.ctx.scale(l,l)),e=0,m=n.length;m>e;++e)b=n[e],b.hidden||(d=b.view||(b.view=f.createView(b.geometry,b.styles||h[b.geometry.name])),f.drawBody(b,b.view,i.ctx,k));return 1!==l&&i.ctx.restore(),j.done(),i},this._layers[a]=i,i},removeLayer:function(a){var b=a.id?a.id:a,c=this._layers[b].el;return c!==this.el&&c.parentNode.removeChild(c),delete this._layers[b],this},resize:function(a,b){var c;for(var d in this._layers)c=this._layers[d],c.options.autoResize&&(c.el.width=a,c.el.height=b);return this},setStyle:function(a,b){b=b||this.ctx,c.util.isObject(a)?(a.strokeStyle=a.lineWidth?a.strokeStyle:"rgba(0,0,0,0)",c.util.extend(b,a)):(b.fillStyle=b.strokeStyle=a,b.lineWidth=1)},drawCircle:function(a,b,c,e,f){f=f||this.ctx,f.beginPath(),this.setStyle(e,f),f.arc(a,b,c,0,d,!1),f.closePath(),f.stroke(),f.fill()},drawPolygon:function(a,b,c){var d=a[0],e=d.x,f=d.y,g=a.length;c=c||this.ctx,c.beginPath(),this.setStyle(b,c),c.moveTo(e,f);for(var h=1;g>h;++h)d=a[h],e=d.x,f=d.y,c.lineTo(e,f);g>2&&c.closePath(),c.stroke(),c.fill()},drawRect:function(a,b,c,d,e,f){var g=.5*c,h=.5*d;f=f||this.ctx,this.setStyle(e,f),f.beginPath(),f.rect(a-g,b-h,c,d),f.closePath(),f.stroke(),f.fill()},drawLine:function(a,b,c,d){var e=a.x,f=a.y;d=d||this.ctx,d.beginPath(),this.setStyle(c,d),d.moveTo(e,f),e=b.x,f=b.y,d.lineTo(e,f),d.stroke(),d.fill()},createView:function(a,b){var c,d=a.aabb(),e=d.hw+Math.abs(d.x),f=d.hh+Math.abs(d.y),g=e+1,h=f+1,i=this.hiddenCtx,j=this.hiddenCanvas,k=a.name;return b=b||this.options.styles[k]||{},b.src?(c=new Image,c.src=b.src,b.width&&(c.width=b.width),b.height&&(c.height=b.height),c):(g+=0|b.lineWidth,h+=0|b.lineWidth,j.width=2*e+2+(0|2*b.lineWidth),j.height=2*f+2+(0|2*b.lineWidth),i.save(),i.translate(g,h),"circle"===k?this.drawCircle(0,0,a.radius,b,i):"convex-polygon"===k?this.drawPolygon(a.vertices,b,i):"rectangle"===k&&this.drawRect(0,0,a.width,a.height,b,i),b.angleIndicator&&(i.beginPath(),this.setStyle(b.angleIndicator,i),i.moveTo(0,0),i.lineTo(e,0),i.closePath(),i.stroke()),i.restore(),c=new Image(j.width,j.height),c.src=j.toDataURL("image/png"),c)},drawMeta:function(a){this.els.fps.innerHTML=a.fps.toFixed(2),this.els.ipf.innerHTML=a.ipf},drawBody:function(a,b,c,d){var e,f,g,h,i=a.state.pos,j=a.state.vel,k=this._interpolateTime||0;d=d||this.options.offset,c=c||this.ctx,e=i.x+d.x+j.x*k,f=i.y+d.y+j.y*k,g=a.state.angular.pos+a.state.angular.vel*k,c.save(),c.translate(e,f),c.rotate(g),c.drawImage(b,-b.width/2,-b.height/2),c.restore(),this.options.debug&&(h=a.aabb(),this.drawRect(h.x,h.y,2*h.hw,2*h.hh,"rgba(0, 0, 255, 0.3)"),a._debugView=a._debugView||this.createView(a.geometry,"rgba(255, 0, 0, 0.5)"),c.save(),c.translate(i.x+d.x,i.y+d.y),c.rotate(a.state.angular.pos),c.drawImage(a._debugView,.5*-a._debugView.width,.5*-a._debugView.height),c.restore())},render:function(a,b){this._world.emit("beforeRender",{renderer:this,meta:b}),this.options.meta&&this.drawMeta(b),this._interpolateTime=b.interpolateTime;for(var c in this._layers)this._layers[c].render();return this}}}),c.renderer("dom",function(a){if(!b)return{};var c={},d=b.createElement("div"),e=function(a){return a.replace(/(?:^|\s)\w/g,function(a){return a.toUpperCase()})},f=function(a){if(c[a])return c[a];for(var b,f=["Webkit","Moz","Ms","O"],g=0,h=f.length;h>g;++g)if(b=f[g]+e(a),b in d.style)return c[a]=b;return b in d.style?c[a]=a:!1},g="pjs-",h="px",i=f("transform"),j=function(a,c){var d=b.createElement(a||"div");return c&&(d.innerHTML=c),d};return{init:function(b){a.init.call(this,b);var c=this.el;if(c.style.position="relative",c.style.overflow="hidden",c.style[i]="translateZ(0)",c.style.width=this.options.width+h,c.style.height=this.options.height+h,this.els={},b.meta){var d=j();d.className="pjs-meta",this.els.fps=j("span"),this.els.ipf=j("span"),d.appendChild(j("span","fps: ")),d.appendChild(this.els.fps),d.appendChild(j("br")),d.appendChild(j("span","ipf: ")),d.appendChild(this.els.ipf),c.appendChild(d)}},circleProperties:function(a,b){var c=b.aabb();a.style.width=2*c.hw+h,a.style.height=2*c.hh+h,a.style.marginLeft=-c.hw+h,a.style.marginTop=-c.hh+h},rectangleProperties:function(a,b){var c=b.aabb();a.style.width=2*c.hw+h,a.style.height=2*c.hh+h,a.style.marginLeft=-c.hw+h,a.style.marginTop=-c.hh+h},createView:function(a){var b=j(),c=a.name+"Properties";return b.className=g+a.name,b.style.position="absolute",b.style.top="0px",b.style.left="0px",this[c]&&this[c](b,a),this.el.appendChild(b),b},connect:function(a){a.on("add:body",this.attach,this),a.on("remove:body",this.detach,this)},disconnect:function(a){a.off("add:body",this.attach),a.off("remove:body",this.detach)},detach:function(a){var b=a.nodeType&&a||a.body&&a.body.view,c=b&&b.parentNode;return b&&c&&c.removeChild(b),this},attach:function(a){var b=a.nodeType&&a||a.body&&a.body.view;return b&&this.el.appendChild(b),this},drawMeta:function(a){this.els.fps.innerHTML=a.fps.toFixed(2),this.els.ipf.innerHTML=a.ipf},drawBody:function(a,b){var c,d,e,f=a.state.pos,g=a.state.vel,h=this._interpolateTime;c=f.x+g.x*h,d=f.y+g.y*h,e=a.state.angular.pos+a.state.angular.vel*h,b.style[i]="translate("+c+"px,"+d+"px) rotate("+e+"rad)"}}}),c.renderer("pixi",function(a){if(!b)return{};2*Math.PI;var d={debug:!1,metaEl:null,offset:{x:0,y:0},styles:{color:"0x66FF99",point:"0xE8900C",circle:{strokeStyle:"0xE8900C",lineWidth:3,fillStyle:"0xD5DE4C",angleIndicator:"0xE8900C"},"convex-polygon":{strokeStyle:"0xE8900C",lineWidth:3,fillStyle:"0xD5DE4C",angleIndicator:"0xE8900C"}}},e=function(a,b){return c.util.isPlainObject(b)?c.util.extend({},a,b,e):void 0!==b?b:a};return{init:function(f){if("undefined"==typeof PIXI)throw"PIXI obj not present - cannot continue ";a.init.call(this,f),this.options=c.util.extend({},d,this.options,e),this.options.offset=c.vector(this.options.offset),this.stage=new PIXI.Stage(this.options.styles.color),this.renderer=new PIXI.autoDetectRenderer(this.options.width,this.options.height),this.meta={},"CANVAS"===this.el.nodeName?this.renderer=new PIXI.autoDetectRenderer(this.options.width,this.options.height,this.el):(this.renderer=new PIXI.autoDetectRenderer(this.options.width,this.options.height),null!==this.el?this.el.appendChild(this.renderer.view):b.body.appendChild(this.renderer.view))},loadSpriteSheets:function(a,b){if(!c.util.isArray(a))throw"Spritesheets must be defined in arrays";var d=this,e=new PIXI.AssetLoader(a);return e.load(),e.on("onComplete",function(){d.assetsLoaded=!0,b()}),d},drawBody:function(a,b){var c,d,e,f=a.state.pos,g=a.state.vel,h=this._interpolateTime||0;c=f.x+g.x*h,d=f.y+g.y*h,e=a.state.angular.pos+a.state.angular.vel*h,b.position.x=c,b.position.y=d,b.rotation=e},render:function(b,c){a.render.call(this,b,c),this.renderer.render(this.stage)},createCircle:function(a,b,c,d){var e=new PIXI.Graphics;return e.beginFill(d.fillStyle),e.lineStyle(d.lineWidth,d.strokeStyle),e.drawCircle(a,b,c),e.pivot.x=a/2+c/2,e.pivot.y=b/2+c/2,e},createPolygon:function(a,b){var c=a[0],d=c.x,e=c.y,f=a.length,g={x:d,y:e},h=new PIXI.Graphics;h.beginFill(b.fillStyle),h.lineStyle(b.lineWidth,b.strokeStyle),h.moveTo(d,e);for(var i=1;f>i;++i)c=a[i],d=c.x,e=c.y,h.lineTo(d,e);return f>2&&h.lineTo(g.x,g.y),h.endFill(),h},createLine:function(a,b,c){var d=a.x,e=a.y,f=new PIXI.Graphics;return f.beginFill(c.fillStyle),f.lineStyle(c.lineWidth,c.strokeStyle),f.moveTo(d,e),d=b.x,e=b.y,f.lineTo(d,e),f.endFill(),f},createView:function(a){var b=null,c=a.aabb(),d=c.hw+Math.abs(c.x),e=c.hh+Math.abs(c.y),f=d+1,g=e+1,h=a.name,i=i||this.options.styles[h];if(f+=0|i.lineWidth,g+=0|i.lineWidth,"circle"===h?b=this.createCircle(f,g,a.radius,i):"convex-polygon"===h&&(b=this.createPolygon(a.vertices,i)),i.angleIndicator&&(b.beginFill(i.angleIndicator),b.moveTo(f/2,5+i.lineWidth),b.lineTo(f/2+a.radius/2,a.radius),b.endFill()),b)return this.stage.addChild(b),b;throw"Invalid view name passed."},drawMeta:function(a){if(this.meta.loaded)this.meta.fps.setText("FPS: "+a.fps.toFixed(2)),this.meta.ipf.setText("IPF: "+a.ipf);else{var b={font:"18px Snippet",fill:"white",align:"left"};this.meta.fps=new PIXI.Text("FPS: "+a.fps.toFixed(2),b),this.meta.fps.position.x=15,this.meta.fps.position.y=5,this.meta.ipf=new PIXI.Text("IPF: "+a.ipf,b),this.meta.ipf.position.x=15,this.meta.ipf.position.y=30,this.stage.addChild(this.meta.fps),this.stage.addChild(this.meta.ipf),this.meta.loaded=!0}},createDisplay:function(a,b){var c=null,d=null;switch(a){case"sprite":return d=PIXI.Texture.fromImage(b.texture),c=new PIXI.Sprite(d),b.anchor&&(c.anchor.x=b.anchor.x,c.anchor.y=b.anchor.y),b.container?b.container.addChild(c):this.stage.addChild(c),c;case"movieclip":if(!this.assetsLoaded)throw"No assets have been loaded. Use loadSpritesheet() first";var e=[],f=0;for(f;f Object + * - aabb1 (Object): The first aabb (returned if modify is `true`) + * - aabb2 (Object): The second aabb + * + (Object): The union of two aabbs. If modify is `true`, then the first aabb will be modified and returned. + * + * Get the union of two aabbs. + **/ + Physics.aabb.union = function( aabb1, aabb2, modify ){ + + var ret = modify === true ? aabb1 : {} + ,maxX = Math.max( aabb1.x + aabb1.hw, aabb2.x + aabb2.hw ) + ,maxY = Math.max( aabb1.y + aabb1.hh, aabb2.y + aabb2.hh ) + ,minX = Math.min( aabb1.x - aabb1.hw, aabb2.x - aabb2.hw ) + ,minY = Math.min( aabb1.y - aabb1.hh, aabb2.y - aabb2.hh ) + ; + + ret.hw = Math.abs(maxX - minX) * 0.5; + ret.hh = Math.abs(maxY - minY) * 0.5; + ret.x = (maxX + minX) * 0.5; + ret.y = (maxY + minY) * 0.5; + + return ret; + }; + + /** * Physics.aabb.overlap( aabb1, aabb2 ) -> Boolean * - aabb1 (Object): The first aabb @@ -547,6 +573,73 @@ Physics.util = {}; })(); +// --- +// inside: src/math/statistics.js + +(function(){ + + Physics.statistics = { + /** + * Physics.statistics.pushRunningAvg( v, k, m, s ) -> Array + * - v (Number): is value to push + * - k (Number): is num elements + * - m (Number): is current mean + * - s (Number): is current s value + * + (Array): Returns a 2 element array containing the next mean, and s value + * + * Push a value to a running average calculation. + * see [http://www.johndcook.com/blog/standard_deviation] + * + * Note: variance can be calculated from the "s" value by multiplying it by `1/(k-1)` + **/ + pushRunningAvg: function( v, k, m, s ){ + + var x = v - m; + + // Mk = Mk-1+ (xk – Mk-1)/k + // Sk = Sk-1 + (xk – Mk-1)*(xk – Mk). + m += x / k; + s += x * (v - m); + return [m, s]; + }, + + /** + * Physics.statistics.pushRunningVectorAvg( v, k, m[, s] ) + * - v (Physics.vector): is vector to push + * - k (Number): is num elements + * - m (Physics.vector): is current mean + * - s (Physics.vector): is current s value + * + * Push a vector to a running vector average calculation. + * see [http://www.johndcook.com/blog/standard_deviation] + * + * Calculations are done in place. The `m` and `s` parameters are altered. + * + * Note: variance can be calculated from the "s" vector by multiplying it by `1/(k-1)` + * + * If s value is ommitted it won't be used. + **/ + pushRunningVectorAvg: function( v, k, m, s ){ + var invK = 1/k + ,x = v.get(0) - m.get(0) + ,y = v.get(1) - m.get(1) + ; + + // Mk = Mk-1+ (xk – Mk-1)/k + // Sk = Sk-1 + (xk – Mk-1)*(xk – Mk). + m.add( x * invK, y * invK ); + + if ( s ){ + x *= v.get(0) - m.get(0); + y *= v.get(1) - m.get(1); + + s.add( x, y ); + } + } + }; +})(); + + // --- // inside: src/math/transform.js @@ -574,8 +667,8 @@ Physics.util = {}; return new Transform( vect, angle ); } - this.v = Physics.vector(); - this.o = Physics.vector(); // origin of rotation + this.v = new Physics.vector(); + this.o = new Physics.vector(); // origin of rotation if ( vect instanceof Transform ){ @@ -668,9 +761,9 @@ Physics.util = {}; ,typedArrays = !!window.Float64Array ; - /** + /** * class Physics.vector - * + * * The vector class and factory function. * * Call `Physics.vector` with the same arguments as @@ -708,7 +801,7 @@ Physics.util = {}; * - x (Number): The x coordinate * - y (Number): The y coordinate * - vect (Vectorish): A vector-like object to clone - * + * * Vector Constructor. **/ var Vector = function Vector( x, y ) { @@ -750,9 +843,9 @@ Physics.util = {}; }; Object.defineProperties( Vector.prototype, { - /** + /** * Physics.vector#x - * + * * Getter/setter property for the x coordinate. **/ x: { @@ -765,9 +858,9 @@ Physics.util = {}; this._[0] = x; } }, - /** + /** * Physics.vector#y - * + * * Getter/setter property for the y coordinate. **/ y: { @@ -782,15 +875,15 @@ Physics.util = {}; } }); - // + // // Methods - // + // /** * Physics.vector#set( x, y ) -> this * - x (Number): x coordinate * - y (Number): y coordinate - * + * * Sets the x and y components of this vector. **/ Vector.prototype.set = function( x, y ) { @@ -805,7 +898,7 @@ Physics.util = {}; /** deprecated: 0.6.0..1.0.0 * Physics.vector#get( idx ) -> Number * - idx (Number): The coordinate index (0 or 1) - * + * * Get the x or y component by index. **/ Vector.prototype.get = function( n ){ @@ -816,7 +909,7 @@ Physics.util = {}; /** * Physics.vector#vadd( v ) -> this * - v (Physics.vector): vector to add - * + * * Add a [[Physics.vector]] to `this`. **/ Vector.prototype.vadd = function( v ) { @@ -831,7 +924,7 @@ Physics.util = {}; /** * Physics.vector#vsub( v ) -> this * - v (Physics.vector): vector to subtract - * + * * Subtract a [[Physics.vector]] from `this`. **/ Vector.prototype.vsub = function( v ) { @@ -847,11 +940,11 @@ Physics.util = {}; * Physics.vector#add( x, y ) -> this * - x (Number): amount to add to the x coordinate * - y (Number): amount to add to the y coordinate - * + * * Add scalars [[Physics.vector]] to the coordinates. **/ Vector.prototype.add = function( x, y ){ - + this.recalc = true; this._[0] += +x || 0; @@ -863,11 +956,11 @@ Physics.util = {}; * Physics.vector#sub( x, y ) -> this * - x (Number): amount to subtract from the x coordinate * - y (Number): amount to subtract from the y coordinate - * + * * Subtract scalars [[Physics.vector]] from the coordinates. **/ Vector.prototype.sub = function( x, y ){ - + this.recalc = true; this._[0] -= x; @@ -878,13 +971,13 @@ Physics.util = {}; /** * Physics.vector#mult( m ) -> this * - m (Number): amount to multiply this vector by - * + * * Multiply this by a scalar quantity. * * Same as scaling the vector by an amount `m`. **/ Vector.prototype.mult = function( m ) { - + if ( !this.recalc ){ this._[4] *= m * m; @@ -896,10 +989,10 @@ Physics.util = {}; return this; }; - /** + /** * Physics.vector#dot( v ) -> Number * - v (Physics.vector): The other vector - * + * * Compute the dot product of this vector with `v`. **/ Vector.prototype.dot = function( v ) { @@ -907,10 +1000,10 @@ Physics.util = {}; return (this._[0] * v._[0]) + (this._[1] * v._[1]); }; - /** + /** * Physics.vector#cross( v ) -> Number * - v (Physics.vector): The other vector - * + * * Compute the (left-handed) cross product of this vector with `v`. **/ Vector.prototype.cross = function( v ) { @@ -921,7 +1014,7 @@ Physics.util = {}; /** * Physics.vector#proj( v ) -> Number * - v (Physics.vector): The other vector - * + * * Compute the [scalar projection](http://en.wikipedia.org/wiki/Vector_projection#Scalar_projection_2) of this along `v`. **/ Vector.prototype.proj = function( v ){ @@ -933,7 +1026,7 @@ Physics.util = {}; /** * Physics.vector#vproj( v ) -> this * - v (Physics.vector): The other vector - * + * * Compute the [vector projection](http://en.wikipedia.org/wiki/Vector_projection#Vector_projection_2) of this along `v` and copy the result into this vector. **/ Vector.prototype.vproj = function( v ){ @@ -946,7 +1039,7 @@ Physics.util = {}; * Physics.vector#angle( [v] ) -> Number * - v (Physics.vector): The other vector * + (Number): The angle in radians between this vector and the x-axis OR `v` if specified - * + * * Compute the angle between `this` and vector `v` or this and x axis. **/ Vector.prototype.angle = function( v ){ @@ -954,7 +1047,7 @@ Physics.util = {}; var ang; if ( this.equals( Vector.zero ) ){ - + if ( v ){ return v.angle(); } else { @@ -966,10 +1059,10 @@ Physics.util = {}; if ( v && !v.equals( Vector.zero ) ){ ang = atan2( this._[1] * v._[0] - this._[0] * v._[1], this._[0] * v._[0] + this._[1] * v._[1]); } else { - ang = atan2( this._[ 1 ], this._[ 0 ] ); + ang = atan2( this._[ 1 ], this._[ 0 ] ); } } - + while (ang > Math.PI){ ang -= TWOPI; } @@ -985,7 +1078,7 @@ Physics.util = {}; * Physics.vector#angle2( left, right ) -> Number * - left (Physics.vector): The position on the left * - right (Physics.vector): The position on the right - * + * * Compute the angle created between three points; left -> this -> right. **/ Vector.prototype.angle2 = function( left, right ){ @@ -1010,7 +1103,7 @@ Physics.util = {}; /** * Physics.vector#norm() -> Number - * + * * Compute the norm (length) of this vector. **/ Vector.prototype.norm = function() { @@ -1020,13 +1113,13 @@ Physics.util = {}; this._[4] = (this._[0] * this._[0] + this._[1] * this._[1]); this._[3] = sqrt( this._[4] ); } - + return this._[3]; }; /** * Physics.vector#normSq() -> Number - * + * * Compute the norm (length) squared of this vector. **/ Vector.prototype.normSq = function() { @@ -1043,14 +1136,14 @@ Physics.util = {}; /** * Physics.vector#dist( v ) -> Number * - v (Physics.vector): The other vector - * + * * Compute the distance from this vector to another vector `v`. **/ Vector.prototype.dist = function( v ) { - + var dx, dy; return sqrt( - (dx = (v._[0] - this._[0])) * dx + + (dx = (v._[0] - this._[0])) * dx + (dy = (v._[1] - this._[1])) * dy ); }; @@ -1058,14 +1151,14 @@ Physics.util = {}; /** * Physics.vector#distSq( v ) -> Number * - v (Physics.vector): The other vector - * + * * Compute the distance squared from this vector to another vector `v`. **/ Vector.prototype.distSq = function( v ) { var dx, dy; return ( - (dx = (v._[0] - this._[0])) * dx + + (dx = (v._[0] - this._[0])) * dx + (dy = (v._[1] - this._[1])) * dy ); }; @@ -1073,7 +1166,7 @@ Physics.util = {}; /** * Physics.vector#perp( [ccw] ) -> this * - ccw (Boolean): flag to indicate that we should rotate counterclockwise - * + * * Change this vector into a vector that will be perpendicular. * * In other words, rotate by (+-) 90 degrees. @@ -1103,7 +1196,7 @@ Physics.util = {}; /** * Physics.vector#normalize() -> this - * + * * Normalise this vector, making it a unit vector. **/ Vector.prototype.normalize = function() { @@ -1129,7 +1222,7 @@ Physics.util = {}; /** * Physics.vector#transform( t ) -> this * - t (Physics.transform): The transformation to apply - * + * * Apply a [[Physics.transform]] to this vector. **/ Vector.prototype.transform = function( t ){ @@ -1145,7 +1238,7 @@ Physics.util = {}; // rotate about origin "o" then translate return this.set( - this._[ 0 ] * cosA - this._[ 1 ] * sinA + x + t.v._[ 0 ], + this._[ 0 ] * cosA - this._[ 1 ] * sinA + x + t.v._[ 0 ], this._[ 0 ] * sinA + this._[ 1 ] * cosA + y + t.v._[ 1 ] ); }; @@ -1153,7 +1246,7 @@ Physics.util = {}; /** * Physics.vector#transformInv( t ) -> this * - t (Physics.transform): The transformation to apply the inverse of - * + * * Apply an inverse [[Physics.transform]] to this vector. **/ Vector.prototype.transformInv = function( t ){ @@ -1169,7 +1262,7 @@ Physics.util = {}; // inverse translate then inverse rotate about origin "o" return this.set( - this._[ 0 ] * cosA + this._[ 1 ] * sinA + x, + this._[ 0 ] * cosA + this._[ 1 ] * sinA + x, - this._[ 0 ] * sinA + this._[ 1 ] * cosA + y ); }; @@ -1180,10 +1273,10 @@ Physics.util = {}; * - t (Physics.transform): The transformation to apply the rotational part of * - ang (Number): The angle (in radians), to rotate by * - o (Vectorish): The point of origin of the rotation - * + * * Rotate this vector. - * - * An angle and rotation origin can be specified, + * + * An angle and rotation origin can be specified, * or a transform can be specified and only the rotation * portion of that transform will be applied **/ @@ -1200,22 +1293,22 @@ Physics.util = {}; cosA = Math.cos( t ); if ( o ){ - x = (o.x || o._[ 0 ]) | 0; - y = (o.y || o._[ 1 ]) | 0; + x = o.x; + y = o.y; } } else { sinA = t.sinA; cosA = t.cosA; - + x = t.o._[ 0 ]; y = t.o._[ 1 ]; } - + this._[ 0 ] -= x; this._[ 1 ] -= y; return this.set( - this._[ 0 ] * cosA - this._[ 1 ] * sinA + x, + this._[ 0 ] * cosA - this._[ 1 ] * sinA + x, this._[ 0 ] * sinA + this._[ 1 ] * cosA + y ); }; @@ -1223,16 +1316,16 @@ Physics.util = {}; /** * Physics.vector#rotateInv( t ) -> this * - t (Physics.transform): The transformation to apply the inverse rotational part of - * + * * Apply the inverse rotation of a transform. - * - * Only the inverse rotation portion of + * + * Only the inverse rotation portion of * that transform will be applied. **/ Vector.prototype.rotateInv = function( t ){ return this.set( - (this._[ 0 ] - t.o._[ 0 ]) * t.cosA + (this._[ 1 ] - t.o._[ 1 ]) * t.sinA + t.o._[ 0 ], + (this._[ 0 ] - t.o._[ 0 ]) * t.cosA + (this._[ 1 ] - t.o._[ 1 ]) * t.sinA + t.o._[ 0 ], -(this._[ 0 ] - t.o._[ 0 ]) * t.sinA + (this._[ 1 ] - t.o._[ 1 ]) * t.cosA + t.o._[ 1 ] ); }; @@ -1240,10 +1333,10 @@ Physics.util = {}; /** * Physics.vector#translate( t ) -> this * - t (Physics.transform): The transformation to apply the translational part of - * + * * Apply the translation of a transform. - * - * Only the translation portion of + * + * Only the translation portion of * that transform will be applied. **/ Vector.prototype.translate = function( t ){ @@ -1254,10 +1347,10 @@ Physics.util = {}; /** * Physics.vector#translateInv( t ) -> this * - t (Physics.transform): The transformation to apply the inverse translational part of - * + * * Apply the inverse translation of a transform. - * - * Only the inverse translation portion of + * + * Only the inverse translation portion of * that transform will be applied. **/ Vector.prototype.translateInv = function( t ){ @@ -1271,10 +1364,10 @@ Physics.util = {}; * - v (Vectorish): The vector-like object to clone * + (this): If `v` is specified as an argument * + (Physics.vector): A new vector instance that clones this vector, if no argument is specified - * + * * Create a clone of this vector, or clone another vector into this instance. * - * This is especially useful in vector algorithms + * This is especially useful in vector algorithms * that use temporary vectors (which most should). * You can create temporary vectors and then do things like... * ``` @@ -1285,7 +1378,7 @@ Physics.util = {}; * ``` **/ Vector.prototype.clone = function( v ) { - + // http://jsperf.com/vector-storage-test if ( v ){ @@ -1294,7 +1387,7 @@ Physics.util = {}; return this.set( v.x, v.y ); } - + this.recalc = v.recalc; if (!v.recalc){ @@ -1314,7 +1407,7 @@ Physics.util = {}; /** * Physics.vector#swap( v ) -> this * - v (Physics.vector): The other vector - * + * * Swap values with other vector. **/ Vector.prototype.swap = function( v ){ @@ -1331,7 +1424,7 @@ Physics.util = {}; /** * Physics.vector#values() -> Object - * + * * Get the coordinate values as an object literal. **/ Vector.prototype.values = function(){ @@ -1345,7 +1438,7 @@ Physics.util = {}; /** * Physics.vector#zero() -> this - * + * * Set the coordinates of this vector to zero. **/ Vector.prototype.zero = function() { @@ -1360,7 +1453,7 @@ Physics.util = {}; /** * Physics.vector#negate() -> this - * + * * Flip this vector in the opposite direction. **/ Vector.prototype.negate = function( component ){ @@ -1380,9 +1473,9 @@ Physics.util = {}; * Physics.vector#clamp( minV, maxV ) -> this * - minV (Vectorish): The minimum vector * - maxV (Vectorish): The maximum vector - * + * * Constrain vector components to minima and maxima. - * + * * The vector analog of [scalar clamping](http://en.wikipedia.org/wiki/Clamping_(graphics)). **/ Vector.prototype.clamp = function( minV, maxV ){ @@ -1395,7 +1488,7 @@ Physics.util = {}; /** * Physics.vector#toString() -> String - * + * * Get a formatted string of this vector's coordinates. **/ Vector.prototype.toString = function(){ @@ -1407,7 +1500,7 @@ Physics.util = {}; /** * Physics.vector#equals( v ) -> Boolean * - v (Physics.vector): The other vector - * + * * Determine if this vector equals another. **/ Vector.prototype.equals = function( v ){ @@ -1419,7 +1512,7 @@ Physics.util = {}; /** * Physics.vector.axis = Array - * + * * Read-only axis vectors for general reference. * * Example: @@ -1436,7 +1529,7 @@ Physics.util = {}; /** * Physics.vector.zero = zeroVector - * + * * Read-only zero vector for reference **/ Vector.zero = new Vector(0, 0); @@ -1704,6 +1797,23 @@ Physics.util.indexOf = function indexOf(arr, value) { return -1; }; + +// http://jsperf.com/array-destroy/87 +/** + * Physics.util.clearArray( arr ) -> Array + * - arr (Array): The array to clear + * + (Array): The array passed in + * + * Quickly clear an array. + **/ +Physics.util.clearArray = function clearArray(arr){ + var l = arr.length; + while( l-- ){ + arr.pop(); + } + return arr; +}; + /** * Physics.util.throttle( fn, delay ) -> Function * - fn (Function): The function to throttle @@ -1726,7 +1836,7 @@ Physics.util.throttle = function throttle( fn, delay, scope ){ } } ; - + scope = scope || null; return function(){ @@ -1765,6 +1875,16 @@ Physics.util.throttle = function throttle( fn, delay, scope ){ * }); * ``` **/ +// deep copy callback to extend deeper into options +var deepCopyFn = function( a, b ){ + + if ( Physics.util.isPlainObject( b ) ){ + + return Physics.util.extend({}, a, b, deepCopyFn ); + } + + return b !== undefined ? b : a; +}; Physics.util.options = function( def, target ){ var _def = {} @@ -1773,9 +1893,9 @@ Physics.util.options = function( def, target ){ ; // set options - fn = function fn( options ){ + fn = function fn( options, deep ){ - Physics.util.extend(target, options, null); + Physics.util.extend(target, options, deep ? deepCopyFn : null); for ( var i = 0, l = callbacks.length; i < l; ++i ){ callbacks[ i ]( target ); } @@ -1783,9 +1903,9 @@ Physics.util.options = function( def, target ){ }; // add defaults - fn.defaults = function defaults( def ){ - Physics.util.extend( _def, def ); - Physics.util.defaults( target, _def ); + fn.defaults = function defaults( def, deep ){ + Physics.util.extend( _def, def, deep ? deepCopyFn : null ); + Physics.util.defaults( target, _def, deep ? deepCopyFn : null ); return _def; }; @@ -1943,7 +2063,7 @@ var maxPoolSize = 40; var keyPrefix = +new Date() + ''; function releaseArray(array) { - array.length = 0; + Physics.util.clearArray( array ); if (arrayPool.length < maxPoolSize) { arrayPool.push(array); } @@ -2378,7 +2498,7 @@ Physics.scratchpad = (function(){ var scratches = []; var numScratches = 0; var Scratch, Scratchpad; - + var regIndex = 0; @@ -2386,7 +2506,7 @@ Physics.scratchpad = (function(){ * class Scratch * * A scratchpad session. - * + * * This class keeps track of temporary objects used * in this session and releases them when finished (call to `.done()`). * @@ -2401,7 +2521,7 @@ Physics.scratchpad = (function(){ // private variables this._active = false; this._indexArr = []; - + if (++numScratches >= Scratchpad.maxScratches){ throw SCRATCH_MAX_REACHED; } @@ -2414,9 +2534,9 @@ Physics.scratchpad = (function(){ * - val (Mixed): No effect on this method, just passed on to the return value so you can do things like: return scratch.done( myReturnVal ); * + (Mixed): Whatever you specified as `val` - * + * * Declare that your work is finished. - * + * * Release temp objects for use elsewhere. Must be called when immediate work is done. * * You can wrap the return value in scratch.done() so that you don't forget to call it. @@ -2432,10 +2552,10 @@ Physics.scratchpad = (function(){ this._active = false; var s; for ( var i = 0; i < regIndex; ++i ){ - + this[ i ] = 0; } - + // add it back to the scratch stack for future use scratches.push( this ); return val; @@ -2450,13 +2570,13 @@ Physics.scratchpad = (function(){ * - fn (Function): Some function you'd like to wrap in a scratch session. First argument is the scratch instance. * + (Function): The wrapped function (if `fn` arg specified) that can be reused like the original minus the first (scratch) parameter. * + (Scratch): The scratch session. - * + * * Get a new scratch session to work from or wrap a function in a scratch session. - * + * * Call `.done()` on it when finished. * * Example: - * + * * ```javascript * // get a scratch session manually * var myAlg = function( scratch, arg1, arg2, ... ){ @@ -2473,7 +2593,7 @@ Physics.scratchpad = (function(){ * ``` * * Example: - * + * * ```javascript * // wrap a function in a scratch session * var myAlg = Physics.scratchpad(function( scratch, arg1, arg2, ... ){ @@ -2506,13 +2626,13 @@ Physics.scratchpad = (function(){ * Physics.scratchpad.fn( fn ) -> Function * - fn (Function): Some function you'd like to wrap in a scratch session. First argument is the scratch instance. See [[Physics.scratchpad]]. * + (Function): The wrapped function that can be reused like the original minus the first (scratch) parameter. - * + * * Wrap a function in a scratch session. * * Same as calling `Physics.scratchpad( fn )` with a function specified. **/ Scratchpad.fn = function( fn ){ - + var args = []; for ( var i = 0, l = fn.length; i < l; i++ ){ args.push( i ); @@ -2535,7 +2655,7 @@ Physics.scratchpad = (function(){ * Physics.scratchpad.register( name, constructor ) * - name (String): Name of the object class * - constructor (Function): The object constructor - * + * * Register a new object to be included in scratchpads. * * Example: @@ -2558,7 +2678,7 @@ Physics.scratchpad = (function(){ } // create a new function on the prototype - proto[ name ] = function(){ + Scratch.prototype[ name ] = function(){ // get the stack (or initialize it) var stack = this[ stackname ] || (this[ stackname ] = []) @@ -2581,7 +2701,7 @@ Physics.scratchpad = (function(){ } // return or create new instance - return stack[ stackIndex ] || + return stack[ stackIndex ] || (stack[ stackIndex ] = useFactory ? constructor() : new constructor() ); }; @@ -2595,11 +2715,14 @@ Physics.scratchpad = (function(){ })(); + // --- // inside: src/util/pubsub.js (function(){ + var defaultPriority = 1; + function getPriority( val ){ return val._priority_; } @@ -2666,13 +2789,14 @@ Physics.scratchpad = (function(){ fn = Physics.util.bind( fn, scope ); fn._bindfn_ = orig; fn._one_ = orig._one_; + fn._scope_ = scope; - } else if (!priority) { + } else if ( priority === undefined ) { priority = scope; } - fn._priority_ = priority; + fn._priority_ = priority === undefined ? defaultPriority : priority; idx = Physics.util.sortedIndex( listeners, fn, getPriority ); @@ -2681,15 +2805,16 @@ Physics.scratchpad = (function(){ }, /** - * Physics.util.pubsub#off( topic, fn ) -> this + * Physics.util.pubsub#off( topic, fn[, scope] ) -> this * Physics.util.pubsub#off( topicCfg ) -> this * - topic (String): topic The topic name. Specify `true` to remove all listeners for all topics * - topicCfg (Object): A config with key/value pairs of `{ topic: callbackFn, ... }` * - fn (Function): The original callback function. Specify `true` to remove all listeners for specified topic + * - scope (Object): The scope the callback was bound to. This is important if you are binding methods that come from object prototypes. * * Unsubscribe callback(s) from topic(s). **/ - off: function( topic, fn ){ + off: function( topic, fn, scope ){ var listeners ,listn @@ -2734,7 +2859,10 @@ Physics.scratchpad = (function(){ listn = listeners[ i ]; - if ( listn._bindfn_ === fn || listn === fn ){ + if ( + (listn._bindfn_ === fn || listn === fn) && + ( (!scope) || listn._scope_ === scope) // check the scope too if specified + ){ listeners.splice( i, 1 ); break; } @@ -2839,7 +2967,7 @@ Physics.scratchpad = (function(){ **/ (function(window){ - var active = false + var active = true ,ps = Physics.util.pubsub() ,perf = window.performance ; @@ -2861,6 +2989,8 @@ Physics.scratchpad = (function(){ var time; + window.requestAnimationFrame( step ); + if (!active){ return; } @@ -2871,10 +3001,16 @@ Physics.scratchpad = (function(){ return; } - window.requestAnimationFrame( step ); ps.emit( 'tick', time ); } + // start stepping if we can + if ( window.requestAnimationFrame ){ + step(); + } else { + active = false; + } + /** * Physics.util.ticker.start() -> this * @@ -2883,7 +3019,6 @@ Physics.scratchpad = (function(){ function start(){ active = true; - step(); return this; } @@ -3098,7 +3233,7 @@ Physics.scratchpad = (function(){ * Get a test function to match any body who's aabb intersects point **/ var $at = function $at( point ){ - point = Physics.vector( point ); + point = new Physics.vector( point ); return function( body ){ var aabb = body.aabb(); return Physics.aabb.contains( aabb, point ); @@ -3416,7 +3551,7 @@ Physics.scratchpad = (function(){ disconnect: function( world ){ if (this.behave){ - world.off('integrate:positions', this.behave); + world.off('integrate:positions', this.behave, this); } }, @@ -3458,6 +3593,11 @@ Physics.scratchpad = (function(){ var uidGen = 1; + var Pi2 = Math.PI * 2; + function cycleAngle( ang ){ + return ((ang % Pi2) + Pi2) % Pi2; + } + /** related to: Physics.util.decorator * Physics.body( name[, options] ) -> Body * - name (String): The name of the body to create @@ -3478,7 +3618,9 @@ Physics.scratchpad = (function(){ // what is its coefficient of friction with another surface with COF = 1? cof: 0.8, // what is the view object (mixed) that should be used when rendering? - view: null + view: null, + // the vector offsetting the geometry from its center of mass + offset: Physics.vector(0,0) } ``` * @@ -3503,6 +3645,7 @@ Physics.scratchpad = (function(){ **/ init: function( options ){ + var self = this; var vector = Physics.vector; /** related to: Physics.util.options @@ -3523,6 +3666,9 @@ Physics.scratchpad = (function(){ **/ // all options get copied onto the body. this.options = Physics.util.options( defaults, this ); + this.options.onChange(function( opts ){ + self.offset = new vector( opts.offset ); + }); this.options( options ); /** @@ -3543,18 +3689,18 @@ Physics.scratchpad = (function(){ * ``` **/ this.state = { - pos: vector( this.x, this.y ), - vel: vector( this.vx, this.vy ), - acc: vector(), + pos: new vector( this.x, this.y ), + vel: new vector( this.vx, this.vy ), + acc: new vector(), angular: { pos: this.angle || 0.0, vel: this.angularVelocity || 0.0, acc: 0.0 }, old: { - pos: vector(), - vel: vector(), - acc: vector(), + pos: new vector(), + vel: new vector(), + acc: new vector(), angular: { pos: 0.0, vel: 0.0, @@ -3563,6 +3709,13 @@ Physics.scratchpad = (function(){ } }; + // private storage for sleeping + this._sleepAngPosMean = 0; + this._sleepAngPosVariance = 0; + this._sleepPosMean = new vector(); + this._sleepPosVariance = new vector(); + this._sleepMeanK = 0; + // cleanup delete this.x; delete this.y; @@ -3597,6 +3750,12 @@ Physics.scratchpad = (function(){ * The mass. **/ + /** + * Body#offset + * + * The vector offsetting the body's shape from its center of mass. + **/ + /** * Body#restitution = 1.0 * @@ -3666,7 +3825,7 @@ Physics.scratchpad = (function(){ **/ /** related to: Physics.renderer - * Body#style + * Body#styles * * The styles the renderer should use for creating the view. * @@ -3674,6 +3833,110 @@ Physics.scratchpad = (function(){ **/ }, + /** + * Body#sleep( [dt] ) -> Boolean + * - dt (Number): Time to advance the idle time + * - dt (Boolean): If `true`, the body will be forced to sleep. If `false`, the body will be forced to awake. + * + * Get and/or set whether the body is asleep. + * + * If called with a time (in ms), the time will be added to the idle time and sleep conditions will be checked. + **/ + sleep: function( dt ){ + + if ( dt === true ){ + // force sleep + this.asleep = true; + + } else if ( dt === false ){ + // force wakup + this.asleep = false; + this._sleepMeanK = 0; + this._sleepAngPosMean = 0; + this._sleepAngPosVariance = 0; + this._sleepPosMean.zero(); + this._sleepPosVariance.zero(); + this.sleepIdleTime = 0; + + } else if ( dt && !this.asleep ) { + + this.sleepCheck( dt ); + } + + return this.asleep; + }, + + /** + * Body#sleepCheck( [dt] ) + * - dt (Number): Time to advance the idle time + * + * Check if the body should be sleeping. + * + * Call with no arguments if some event could possibly wake up the body. This will force the body to recheck. + **/ + sleepCheck: function( dt ){ + + var opts = this._world && this._world.options; + + // if sleeping disabled. stop. + if ( this.sleepDisabled || (opts && opts.sleepDisabled) ){ + return; + } + + var limit + ,v + ,d + ,r + ,aabb + ,scratch = Physics.scratchpad() + ,diff = scratch.vector() + ,diff2 = scratch.vector() + ,kfac + ,stats + ; + + dt = dt || 0; + aabb = this.geometry.aabb(); + r = Math.max(aabb.hw, aabb.hh); + + if ( this.asleep ){ + // check velocity + v = this.state.vel.norm() + Math.abs(r * this.state.angular.vel); + limit = this.sleepSpeedLimit || (opts && opts.sleepSpeedLimit) || 0; + + if ( v >= limit ){ + this.sleep( false ); + return scratch.done(); + } + } + + this._sleepMeanK++; + kfac = this._sleepMeanK > 1 ? 1/(this._sleepMeanK - 1) : 0; + Physics.statistics.pushRunningVectorAvg( this.state.pos, this._sleepMeanK, this._sleepPosMean, this._sleepPosVariance ); + // we take the sin because that maps the discontinuous angle to a continuous value + // then the statistics calculations work better + stats = Physics.statistics.pushRunningAvg( Math.sin(this.state.angular.pos), this._sleepMeanK, this._sleepAngPosMean, this._sleepAngPosVariance ); + this._sleepAngPosMean = stats[0]; + this._sleepAngPosVariance = stats[1]; + v = this._sleepPosVariance.norm() + Math.abs(r * Math.asin(stats[1])); + v *= kfac; + limit = this.sleepVarianceLimit || (opts && opts.sleepVarianceLimit) || 0; + // console.log(v, limit, kfac, this._sleepPosVariance.norm(), stats[1]) + if ( v <= limit ){ + // check idle time + limit = this.sleepTimeLimit || (opts && opts.sleepTimeLimit) || 0; + this.sleepIdleTime = (this.sleepIdleTime || 0) + dt; + + if ( this.sleepIdleTime > limit ){ + this.asleep = true; + } + } else { + this.sleep( false ); + } + + scratch.done(); + }, + /** * Body#setWorld( world ) -> this * - world (Object): The world (or null) @@ -3732,7 +3995,7 @@ Physics.scratchpad = (function(){ // if no point at which to apply the force... apply at center of mass if ( p && this.moi ){ - + // apply torques state = this.state; r.clone( p ); @@ -3746,22 +4009,62 @@ Physics.scratchpad = (function(){ return this; }, - /** related to: Physics.aabb - * Body#aabb() -> Object - * + (Object): The aabb of this body + /** related to: Body#offset + * Body#getGlobalOffset( [out] ) -> Physics.vector + * - out (Physics.vector): A vector to use to put the result into. One is created if `out` isn't specified. + * + (Physics.vector): The offset in global coordinates + * + * Get the body offset vector (from the center of mass) for the body's shape in global coordinates. + **/ + getGlobalOffset: function( out ){ + + out = out || new Physics.vector(); + out.clone( this.offset ).rotate( this.state.angular.pos ); + return out; + }, + + /** related to: Physics.aabb + * Body#aabb() -> Object + * + (Object): The aabb of this body * * Get the Axis aligned bounding box for the body in its current position and rotation **/ aabb: function(){ var angle = this.state.angular.pos + ,scratch = Physics.scratchpad() + ,v = scratch.vector() ,aabb = this.geometry.aabb( angle ) ; - aabb.x += this.state.pos.x; - aabb.y += this.state.pos.y; + this.getGlobalOffset( v ); - return aabb; + aabb.x += this.state.pos._[0] + v._[0]; + aabb.y += this.state.pos._[1] + v._[1]; + + return scratch.done( aabb ); + }, + + /** + * Body#toBodyCoords( v ) -> Physics.vector + * - v (Physics.vector): The vector to transform + * + (Physics.vector): The transformed vector + * + * Transform a vector into coordinates relative to this body. + **/ + toBodyCoords: function( v ){ + return v.vsub( this.state.pos ).rotate( -this.state.angular.pos ); + }, + + /** + * Body#toWorldCoords( v ) -> Physics.vector + * - v (Physics.vector): The vector to transform + * + (Physics.vector): The transformed vector + * + * Transform a vector from body coordinates into world coordinates. + **/ + toWorldCoords: function( v ){ + return v.rotate( this.state.angular.pos ).vadd( this.state.pos ); }, /** @@ -3777,6 +4080,47 @@ Physics.scratchpad = (function(){ } }); + /** + * Body.getCOM( bodies[, com] ) -> Physics.vector + * - bodies (Array): The list of bodies + * - com (Physics.vector): The vector to put result into. A new vector will be created if not provided. + * + (Physics.vector): The center of mass position + * + * Get center of mass position from list of bodies. + **/ + Physics.body.getCOM = function( bodies, com ){ + // @TODO add a test for this fn + var b + ,pos + ,i + ,l = bodies && bodies.length + ,M = 0 + ; + + com = com || new Physics.vector(); + + if ( !l ){ + return com.zero(); + } + + if ( l === 1 ){ + return com.clone( bodies[0].state.pos ); + } + + com.zero(); + + for ( i = 0; i < l; i++ ){ + b = bodies[ i ]; + pos = b.state.pos; + com.add( pos._[0] * b.mass, pos._[1] * b.mass ); + M += b.mass; + } + + com.mult( 1 / M ); + + return com; + }; + }()); @@ -3866,7 +4210,7 @@ Physics.scratchpad = (function(){ **/ getFarthestHullPoint: function( dir, result ){ - result = result || Physics.vector(); + result = result || new Physics.vector(); // not implemented. return result.set( 0, 0 ); @@ -3888,7 +4232,7 @@ Physics.scratchpad = (function(){ **/ getFarthestCorePoint: function( dir, result, margin ){ - result = result || Physics.vector(); + result = result || new Physics.vector(); // not implemented. return result.set( 0, 0 ); @@ -3904,11 +4248,38 @@ Physics.scratchpad = (function(){ * Geometry helper functions */ +/** + * Physics.geometry.regularPolygonVertices( sides, radius ) -> Array + * - sides (Number): Number of sides the polygon has + * - radius (Number): Size from center to a vertex + * + (Array): A list of [[Vectorish]] objects representing the vertices + * + * Generate a list of vertices for a regular polygon of any number of sides. + **/ +Physics.geometry.regularPolygonVertices = function( sides, radius ){ + var verts = [] + ,angle = Math.PI * 2 / sides + ,a = 0 + ,i + ; + + for ( i = 0; i < sides; i++ ){ + verts.push({ + x: radius * Math.cos( a ) + ,y: radius * Math.sin( a ) + }); + + a += angle; + } + + return verts; +}; + /** * Physics.geometry.isPolygonConvex( hull ) -> Boolean * - hull (Array): Array of ([[Vectorish]]) vertices * + (Boolean): `true` if the polygon is convex. `false` otherwise. - * + * * Determine if polygon hull is convex **/ Physics.geometry.isPolygonConvex = function( hull ){ @@ -3939,7 +4310,7 @@ Physics.geometry.isPolygonConvex = function( hull ){ // edge and retain the last edge // add two to the length to do a full cycle for ( var i = 1; i <= l; ++i ){ - + next.clone( hull[ i % l ] ).vsub( tmp.clone( hull[ (i - 1) % l ] ) ); if ( sign === false ){ @@ -3948,7 +4319,7 @@ Physics.geometry.isPolygonConvex = function( hull ){ sign = prev.cross( next ); } else if ( (sign > 0) ^ (prev.cross( next ) > 0) ){ - + // if the cross products are different signs it's not convex ret = false; break; @@ -3966,13 +4337,13 @@ Physics.geometry.isPolygonConvex = function( hull ){ * Physics.geometry.getPolygonMOI( hull ) -> Number * - hull (Array): Array of ([[Vectorish]]) vertices * + (Number): The polygon's moment of inertia - * + * * Gets the moment of inertia of a convex polygon * * See [List of moments of inertia](http://en.wikipedia.org/wiki/List_of_moments_of_inertia) * for more information. - * - * _Note_: we make the following assumpations: + * + * _Note_: we make the following assumpations: * * mass is unitary (== 1) * * axis of rotation is the origin **/ @@ -4005,7 +4376,7 @@ Physics.geometry.getPolygonMOI = function( hull ){ prev.clone( hull[ 0 ] ); for ( var i = 1; i < l; ++i ){ - + next.clone( hull[ i ] ); tmp = Math.abs( next.cross( prev ) ); @@ -4024,7 +4395,7 @@ Physics.geometry.getPolygonMOI = function( hull ){ * - pt (Vectorish): The point to test * - hull (Array): Array of ([[Vectorish]]) vertices * + (Boolean): `true` if point `pt` is inside the polygon - * + * * Check if point is inside polygon hull. **/ Physics.geometry.isPointInPolygon = function( pt, hull ){ @@ -4057,7 +4428,7 @@ Physics.geometry.isPointInPolygon = function( pt, hull ){ // calculate the sum of angles between vector pairs // from point to vertices for ( var i = 1; i <= l; ++i ){ - + next.clone( hull[ i % l ] ).vsub( point ); ang += next.angle( prev ); prev.swap( next ); @@ -4071,7 +4442,7 @@ Physics.geometry.isPointInPolygon = function( pt, hull ){ * Physics.geometry.getPolygonArea( hull ) -> Number * - hull (Array): Array of ([[Vectorish]]) vertices * + (Number): The area (positive for clockwise ordering) - * + * * Get the signed area of the polygon. **/ Physics.geometry.getPolygonArea = function getPolygonArea( hull ){ @@ -4093,7 +4464,7 @@ Physics.geometry.getPolygonArea = function getPolygonArea( hull ){ prev.clone( hull[ l - 1 ] ); for ( var i = 0; i < l; ++i ){ - + next.clone( hull[ i ] ); ret += prev.cross( next ); @@ -4109,7 +4480,7 @@ Physics.geometry.getPolygonArea = function getPolygonArea( hull ){ * Physics.geometry.getPolygonCentroid( hull ) -> Physics.vector * - hull (Array): Array of ([[Vectorish]]) vertices * + (Physics.vector): The centroid - * + * * Get the coordinates of the centroid. **/ Physics.geometry.getPolygonCentroid = function getPolygonCentroid( hull ){ @@ -4117,7 +4488,7 @@ Physics.geometry.getPolygonCentroid = function getPolygonCentroid( hull ){ var scratch = Physics.scratchpad() ,prev = scratch.vector() ,next = scratch.vector() - ,ret = Physics.vector() + ,ret = new Physics.vector() ,tmp ,l = hull.length ; @@ -4125,20 +4496,20 @@ Physics.geometry.getPolygonCentroid = function getPolygonCentroid( hull ){ if ( l < 2 ){ // it must be a point scratch.done(); - return Physics.vector( hull[0] ); + return new Physics.vector( hull[0] ); } if ( l === 2 ){ // it's a line // get the midpoint scratch.done(); - return Physics.vector((hull[ 1 ].x + hull[ 0 ].x)/2, (hull[ 1 ].y + hull[ 0 ].y)/2 ); + return new Physics.vector((hull[ 1 ].x + hull[ 0 ].x)/2, (hull[ 1 ].y + hull[ 0 ].y)/2 ); } prev.clone( hull[ l - 1 ] ); for ( var i = 0; i < l; ++i ){ - + next.clone( hull[ i ] ); tmp = prev.cross( next ); @@ -4160,7 +4531,7 @@ Physics.geometry.getPolygonCentroid = function getPolygonCentroid( hull ){ * - linePt1 (Vectorish): The first endpoint of the line * - linePt2 (Vectorish): The second endpoint of the line * + (Vector): The closest point - * + * * Get the closest point on a discrete line to specified point. **/ Physics.geometry.nearestPointOnLine = function nearestPointOnLine( pt, linePt1, linePt2 ){ @@ -4177,7 +4548,7 @@ Physics.geometry.nearestPointOnLine = function nearestPointOnLine( pt, linePt1, // oh.. it's a zero vector. So A and B are both the closest. // just use one of them scratch.done(); - return Physics.vector( linePt1 ); + return new Physics.vector( linePt1 ); } lambdaB = - L.dot( A ) / L.normSq(); @@ -4187,21 +4558,20 @@ Physics.geometry.nearestPointOnLine = function nearestPointOnLine( pt, linePt1, // woops.. that means the closest simplex point // isn't on the line it's point B itself scratch.done(); - return Physics.vector( linePt2 ); + return new Physics.vector( linePt2 ); } else if ( lambdaB <= 0 ){ // vice versa scratch.done(); - return Physics.vector( linePt1 ); + return new Physics.vector( linePt1 ); } // guess we'd better do the math now... - p = Physics.vector( linePt2 ).mult( lambdaB ).vadd( A.clone( linePt1 ).mult( lambdaA ) ); + p = new Physics.vector( linePt2 ).mult( lambdaB ).vadd( A.clone( linePt1 ).mult( lambdaA ) ); scratch.done(); return p; }; - // --- // inside: src/core/integrator.js @@ -4220,7 +4590,7 @@ Physics.geometry.nearestPointOnLine = function nearestPointOnLine( pt, linePt1, * - name (String): The name of the integrator to create * - options (Object): The configuration for that integrator ( depends on integrator ). Available options and defaults: - + ```javascript { // drag applied during integration @@ -4246,28 +4616,29 @@ Physics.geometry.nearestPointOnLine = function nearestPointOnLine( pt, linePt1, /** internal * Integrator#init( options ) * - options (Object): The configuration options passed by the factory - * + * * Initialization. Internal use. **/ init: function( options ){ - + /** related to: Physics.util.options * Integrator#options( options ) -> Object * - options (Object): The options to set as an object * + (Object): The options - * - * Set options on this instance. - * + * + * Set options on this instance. + * * Access options directly from the options object. - * + * * Example: * * ```javascript * this.options.someOption; * ``` - * + * **/ this.options = Physics.util.options( defaults ); + this.options( options ); }, /** @@ -4297,7 +4668,7 @@ Physics.geometry.nearestPointOnLine = function nearestPointOnLine( pt, linePt1, * Integrator#integrate( bodies, dt ) -> this * - bodies (Array): List of bodies to integrate * - dt (Number): Timestep size - * + * * Integrate bodies by timestep. * * Will emit `integrate:velocities` and `integrate:positions` @@ -4308,7 +4679,7 @@ Physics.geometry.nearestPointOnLine = function nearestPointOnLine( pt, linePt1, var world = this._world; this.integrateVelocities( bodies, dt ); - + if ( world ){ world.emit('integrate:velocities', { bodies: bodies, @@ -4317,7 +4688,7 @@ Physics.geometry.nearestPointOnLine = function nearestPointOnLine( pt, linePt1, } this.integratePositions( bodies, dt ); - + if ( world ){ world.emit('integrate:positions', { bodies: bodies, @@ -4331,7 +4702,7 @@ Physics.geometry.nearestPointOnLine = function nearestPointOnLine( pt, linePt1, /** * Integrator#connect( world ) * - world (Physics.world): The world to connect to - * + * * Connect to a world. * * Extend this when creating integrators if you need to specify pubsub management. @@ -4342,7 +4713,7 @@ Physics.geometry.nearestPointOnLine = function nearestPointOnLine( pt, linePt1, /** * Integrator#disconnect( world ) * - world (Physics.world): The world to disconnect from - * + * * Disconnect from a world. * * Extend this when creating integrators if you need to specify pubsub management. @@ -4354,7 +4725,7 @@ Physics.geometry.nearestPointOnLine = function nearestPointOnLine( pt, linePt1, * Integrator#integrateVelocities( bodies, dt ) * - bodies (Array): List of bodies to integrate * - dt (Number): Timestep size - * + * * Just integrate the velocities. * * Should be overridden when creating integrators. @@ -4368,11 +4739,11 @@ Physics.geometry.nearestPointOnLine = function nearestPointOnLine( pt, linePt1, * Integrator#integratePositions( bodies, dt ) * - bodies (Array): List of bodies to integrate * - dt (Number): Timestep size - * + * * Just integrate the positions. * * Called after [[Integrator#integrateVelocities]]. - * + * * Should be overridden when creating integrators. **/ integratePositions: function( bodies, dt ){ @@ -4383,6 +4754,7 @@ Physics.geometry.nearestPointOnLine = function nearestPointOnLine( pt, linePt1, }()); + // --- // inside: src/core/renderer.js @@ -4397,7 +4769,9 @@ Physics.geometry.nearestPointOnLine = function nearestPointOnLine( pt, linePt1, // width of viewport width: 600, // height of viewport - height: 600 + height: 600, + // automatically resize the renderer + autoResize: true }; /** related to: Physics.util.decorator @@ -4417,6 +4791,8 @@ Physics.geometry.nearestPointOnLine = function nearestPointOnLine( pt, linePt1, width: 600, // height of viewport height: 600 + // automatically resize the renderer + autoResize: true } ``` * @@ -4441,13 +4817,43 @@ Physics.geometry.nearestPointOnLine = function nearestPointOnLine( pt, linePt1, **/ init: function( options ){ - var el = typeof options.el === 'string' ? document.getElementById(options.el) : options.el + var self = this + ,el = typeof options.el === 'string' ? document.getElementById(options.el) : options.el ; - this.options = Physics.util.extend({}, defaults, options); + this.options = Physics.util.options(defaults); + this.options( options ); this.el = el ? el : document.body; + this.container = el && el.parentNode ? el.parentNode : document.body; this.drawMeta = Physics.util.throttle( Physics.util.bind(this.drawMeta, this), this.options.metaRefresh ); + + window.addEventListener('resize', Physics.util.throttle(function(){ + if ( self.options.autoResize ){ + self.resize(); + } + }), 100); + }, + + /** + * Renderer#resize( [width, height] ) -> this + * - width (Number): The width in px + * - height (Number): The height in px + * + * Set the dimensions of the renderer. + * + * If no dimensions are specified it will auto resize. + **/ + resize: function( width, height ){ + + if ( width === undefined && height === undefined ){ + width = this.container.offsetWidth; + height = this.container.offsetHeight; + } + + this.width = width || 0; + this.height = height || 0; + // should be implemented in renderers }, /** @@ -4625,13 +5031,22 @@ Physics.geometry.nearestPointOnLine = function nearestPointOnLine( pt, linePt1, var defaults = { // default timestep - timestep: 1000.0 / 120, + timestep: 6, // maximum number of iterations per step - maxIPF: 16, + maxIPF: 4, webworker: false, // NOT YET IMPLEMENTED // default integrator - integrator: 'verlet' + integrator: 'verlet', + + // is sleeping disabled? + sleepDisabled: false, + // speed at which bodies wake up + sleepSpeedLimit: 0.05, + // variance in position below which bodies fall asleep + sleepVarianceLimit: 0.02, + // time (ms) before sleepy bodies fall asleep + sleepTimeLimit: 500 }; // begin world definitions @@ -4651,12 +5066,22 @@ Physics.geometry.nearestPointOnLine = function nearestPointOnLine( pt, linePt1, * * ```javascript * { - * // default timestep - * timestep: 1000.0 / 120, - * // maximum number of iterations per step - * maxIPF: 16, - * // default integrator - * integrator: 'verlet' + * // default timestep + * timestep: 6, + * // maximum number of iterations per step + * maxIPF: 4, + * + * // default integrator + * integrator: 'verlet', + * + * // is sleeping disabled? + * sleepDisabled: false, + * // speed at which bodies wake up + * sleepSpeedLimit: 0.1, + * // variance in position below which bodies fall asleep + * sleepVarianceLimit: 2, + * // time (ms) before sleepy bodies fall asleep + * sleepTimeLimit: 500 * } * ``` * @@ -4782,7 +5207,7 @@ Physics.geometry.nearestPointOnLine = function nearestPointOnLine( pt, linePt1, var i = 0 ,len = arg && arg.length || 0 - ,thing = len ? arg[ 0 ] : arg + ,thing = Physics.util.isArray( arg ) ? arg[ 0 ] : arg ; if ( !thing ){ @@ -4830,7 +5255,7 @@ Physics.geometry.nearestPointOnLine = function nearestPointOnLine( pt, linePt1, var i = 0 ,len = arg && arg.length || 0 - ,thing = len ? arg[ 0 ] : arg + ,thing = Physics.util.isArray( arg ) ? arg[ 0 ] : arg ; if ( !thing ){ @@ -5009,7 +5434,7 @@ Physics.geometry.nearestPointOnLine = function nearestPointOnLine( pt, linePt1, if ( dt ){ - this._dt = dt; + this._dt = +dt.toPrecision(4); // only keep 4 decimal places of precision otherwise we get rounding errors // calculate the maximum jump in time over which to do iterations this._maxJump = dt * this.options.maxIPF; @@ -5019,6 +5444,22 @@ Physics.geometry.nearestPointOnLine = function nearestPointOnLine( pt, linePt1, return this._dt; }, + /** chainable + * Physics.world#wakeUpAll() -> this + * + (this): for chaining + * + * Wake up all bodies in world. + **/ + wakeUpAll: function(){ + var i = 0 + ,l = this._bodies.length + ; + + for ( i = 0; i < l; i++ ){ + this._bodies[ i ].sleep( false ); + } + }, + /** chainable * Physics.world#addBehavior( behavior ) -> this * - behavior (Behavior): The behavior to add @@ -5250,6 +5691,8 @@ Physics.geometry.nearestPointOnLine = function nearestPointOnLine( pt, linePt1, // the target time for the world time to step to target = time + worldDiff - dt; + this.emit('beforeStep'); + if ( time <= target ){ while ( time <= target ){ @@ -5404,12 +5847,12 @@ Physics.integrator('verlet', function( parent ){ return { - /** + /** * class Verlet < Integrator * * `Physics.integrator('verlet')`. * - * The improved euler integrator. + * The verlet integrator. **/ // extended @@ -5427,6 +5870,8 @@ Physics.integrator('verlet', function( parent ){ ,drag = 1 - this.options.drag ,body = null ,state + ,prevDt = this.prevDt || dt + ,dtMul = (dtdt + dt * prevDt) * 0.5 ; for ( var i = 0, l = bodies.length; i < l; ++i ){ @@ -5435,24 +5880,24 @@ Physics.integrator('verlet', function( parent ){ state = body.state; // only integrate if the body isn't static - if ( body.treatment !== 'static' ){ + if ( body.treatment !== 'static' && !body.sleep( dt ) ){ // Inspired from https://github.com/soulwire/Coffee-Physics // @licence MIT - // + // // v = x - ox // x = x + (v + a * dt * dt) // use the velocity in vel if the velocity has been changed manually if (state.vel.equals( state.old.vel ) && body.started()){ - + // Get velocity by subtracting old position from curr position state.vel.clone( state.pos ).vsub( state.old.pos ); } else { state.old.pos.clone( state.pos ).vsub( state.vel ); - // so we need to scale the value by dt so it + // so we need to scale the value by dt so it // complies with other integration methods state.vel.mult( dt ); } @@ -5465,9 +5910,9 @@ Physics.integrator('verlet', function( parent ){ // Apply acceleration // v += a * dt * dt - state.vel.vadd( state.acc.mult( dtdt ) ); + state.vel.vadd( state.acc.mult( dtMul ) ); - // normalize velocity + // restore velocity state.vel.mult( 1/dt ); // store calculated velocity @@ -5478,7 +5923,7 @@ Physics.integrator('verlet', function( parent ){ // // Angular components - // + // if (state.angular.vel === state.old.angular.vel && body.started()){ @@ -5490,7 +5935,7 @@ Physics.integrator('verlet', function( parent ){ state.angular.vel *= dt; } - state.angular.vel += state.angular.acc * dtdt; + state.angular.vel += state.angular.acc * dtMul; state.angular.vel /= dt; state.old.angular.vel = state.angular.vel; state.angular.acc = 0; @@ -5514,6 +5959,8 @@ Physics.integrator('verlet', function( parent ){ var dtdt = dt * dt ,body = null ,state + ,prevDt = this.prevDt || dt + ,dtcorr = dt/prevDt ; for ( var i = 0, l = bodies.length; i < l; ++i ){ @@ -5522,44 +5969,45 @@ Physics.integrator('verlet', function( parent ){ state = body.state; // only integrate if the body isn't static - if ( body.treatment !== 'static' ){ + if ( body.treatment !== 'static' && !body.sleep() ){ - // so we need to scale the value by dt so it + // so we need to scale the value by dt so it // complies with other integration methods - state.vel.mult( dt ); - + state.vel.mult( dt * dtcorr ); + // Store old position. // xold = x state.old.pos.clone( state.pos ); state.pos.vadd( state.vel ); - // normalize velocity - state.vel.mult( 1/dt ); + // restore velocity + state.vel.mult( 1 / (dt * dtcorr) ); // store calculated velocity state.old.vel.clone( state.vel ); // // Angular components - // + // + + + state.angular.vel *= dt * dtcorr; - - state.angular.vel *= dt; - state.old.angular.pos = state.angular.pos; state.angular.pos += state.angular.vel; - state.angular.vel /= dt; + state.angular.vel /= dt * dtcorr; state.old.angular.vel = state.angular.vel; } } + + this.prevDt = dt; } }; }); - // --- // inside: src/geometries/point.js @@ -5660,7 +6108,7 @@ Physics.geometry('circle', function( parent ){ // extended getFarthestHullPoint: function( dir, result ){ - result = result || Physics.vector(); + result = result || new Physics.vector(); return result.clone( dir ).normalize().mult( this.radius ); }, @@ -5668,7 +6116,7 @@ Physics.geometry('circle', function( parent ){ // extended getFarthestCorePoint: function( dir, result, margin ){ - result = result || Physics.vector(); + result = result || new Physics.vector(); // we can use the center of the circle as the core object // because we can project a point to the hull in any direction @@ -5681,6 +6129,184 @@ Physics.geometry('circle', function( parent ){ }); +// --- +// inside: src/geometries/compound.js + +/** + * class CompoundGeometry < Geometry + * + * Physics.geometry('compound') + * + * Geometry for compound shapes. + * + * Example: + * + * ```javascript + * var thing = Physics.geometry('compound'); + * thing.addChild( child, pos, rotation ); + * ``` + **/ +Physics.geometry('compound', function( parent ){ + + var defaults = { + + }; + + return { + + // extended + init: function( options ){ + + var self = this; + + // call parent init method + parent.init.call(this, options); + + this.options.defaults( defaults ); + this.options( options ); + + this.children = []; + }, + + /** + * CompoundGeometry#addChild( geometry, pos ) -> this + * - geometry (Geometry): The child to add. + * - pos (Physics.vector): The position to add the child at. + * - angle (Number): The rotation angle + * + * Add a child at relative position. + **/ + addChild: function( geometry, pos, angle ){ + + this._aabb = null; + this.children.push({ + g: geometry + ,pos: new Physics.vector( pos ) + ,angle: angle + }); + + return this; + }, + + /** + * CompoundGeometry#clear() -> this + * + * Remove all children. + **/ + clear: function(){ + + this._aabb = null; + this.children = []; + + return this; + }, + + // extended + aabb: function( angle ){ + + if (!angle && this._aabb){ + return Physics.aabb.clone( this._aabb ); + } + + var b + ,aabb + ,ch + ,ret + ,scratch = Physics.scratchpad() + ,pos = Physics.vector() + ; + + angle = angle || 0; + + for ( var i = 0, l = this.children.length; i < l; i++ ) { + ch = this.children[ i ]; + // the aabb rotated by overall angle and the child rotation + aabb = ch.g.aabb( angle + ch.angle ); + pos.clone( ch.pos ); + if ( angle ){ + // get the child's position rotated if needed + pos.rotate( angle ); + } + // move the aabb to the child's position + aabb.x += pos._[0]; + aabb.y += pos._[1]; + ret = ret ? Physics.aabb.union(ret, aabb, true) : aabb; + } + + if ( !angle ){ + // if we don't have an angle specified (or it's zero) + // then we can cache this result + this._aabb = Physics.aabb.clone( ret ); + } + + return scratch.done( ret ); + }, + + // extended + // NOTE: unlike other geometries this can't be used in the + // GJK algorithm because the shape isn't garanteed to be convex + getFarthestHullPoint: function( dir, result ){ + + var ch + ,i + ,l = this.children.length + ,scratch = Physics.scratchpad() + ,v = scratch.vector() + ,len = 0 + ,maxlen = 0 + ; + + result = result || new Physics.vector(); + + // find the one with the largest projection along dir + for ( i = 0; i < l; i++ ) { + ch = this.children[ i ]; + ch.g.getFarthestHullPoint( dir.rotate(-ch.angle), v ); + len = v.rotate(ch.angle).vadd( ch.pos ).proj( dir.rotate(ch.angle) ); + + if ( len > maxlen ){ + maxlen = len; + result.swap( v ); + } + } + + return scratch.done( result ); + }, + + // extended + // NOTE: unlike other geometries this can't be used in the + // GJK algorithm because the shape isn't garanteed to be convex + getFarthestCorePoint: function( dir, result, margin ){ + + var ch + ,i + ,l = this.children.length + ,scratch = Physics.scratchpad() + ,v = scratch.vector() + ,len = 0 + ,maxlen = 0 + ; + + result = result || new Physics.vector(); + + // find the one with the largest projection along dir + for ( i = 0; i < l; i++ ) { + ch = this.children[ i ]; + ch.g.getFarthestCorePoint(dir.rotate(-ch.angle), v, margin ); + len = v.rotate(ch.angle).vadd( ch.pos ).proj( dir.rotate(ch.angle) ); + + if ( len > maxlen ){ + maxlen = len; + result.swap( v ); + } + } + + return scratch.done( result ); + } + }; +}); + + // --- // inside: src/geometries/convex-polygon.js @@ -5762,14 +6388,12 @@ Physics.geometry('convex-polygon', function( parent ){ // then add the vertex as a vector to this.vertices for ( var i = 0, l = hull.length; i < l; ++i ){ - verts.push( Physics.vector( hull[ i ] ).translate( transl ) ); + verts.push( new Physics.vector( hull[ i ] ).translate( transl ) ); } this._area = Physics.geometry.getPolygonArea( verts ); - this._aabb = false; - scratch.done(); - return this; + return scratch.done(this); }, // extended @@ -5814,7 +6438,7 @@ Physics.geometry('convex-polygon', function( parent ){ ,idx ; - result = result || Physics.vector(); + result = result || new Physics.vector(); if ( l < 2 ){ if ( data ){ @@ -6077,13 +6701,209 @@ Physics.body('circle', function( parent ){ }); +// --- +// inside: src/bodies/compound.js + +/* + * @requires geometries/compound + */ + /** + * class CompoundBody < Body + * + * Physics.body('compound') + * + * Not a body in itself. It's a container to group other bodies. The position of the body is the center of mass. + * It must have at least one child before being added to the world. + * + * Additional config options: + * + * - children: Array of [[Body]] objects. + * + * Example: + * + * ```javascript + * var thing = Physics.body('compound', { + * // place the center of mass at (300, 200) + * x: 300, + * y: 200, + * // the center of mass is automatically calculated and used to position the shape + * children: [ + * body1, + * body2, + * // ... + * ] + * }); + * ``` + **/ +Physics.body('compound', function( parent ){ + + var defaults = { + + }; + + return { + + // extended + init: function( options ){ + + // call parent init method + parent.init.call(this, options); + + this.mass = 0; + this.moi = 0; + + this.children = []; + this.geometry = Physics.geometry('compound'); + this.addChildren( options.children ); + }, + + // extended + connect: function( world ){ + // sanity check + if ( this.mass <= 0 ){ + throw 'Can not add empty compound body to world.'; + } + }, + + /** + * CompoundBody#addChild( body ) -> this + * - body (Body): The child to add + * + * Add a body as a child. + **/ + addChild: function( body ){ + + this.addChildren([ body ]); + return this; + }, + + /** + * CompoundBody#addChildren( bodies ) -> this + * - bodies (Array): The list of children to add + * + * Add an array of children to the compound. + **/ + addChildren: function( bodies ){ + + var self = this + ,scratch = Physics.scratchpad() + ,com = scratch.vector().zero() + ,b + ,pos + ,i + ,l = bodies && bodies.length + ,M = 0 + ; + + if ( !l ){ + return scratch.done( this ); + } + + for ( i = 0; i < l; i++ ){ + b = bodies[ i ]; + // remove body from world if applicable + if ( b._world ){ + b._world.remove( b ); + } + // add child + this.children.push( b ); + // add child to geometry + this.geometry.addChild( + b.geometry, + new Physics.vector(b.offset) + .rotate(b.state.angular.pos) + .vadd(b.state.pos), + b.state.angular.pos + ); + // calc com contribution + pos = b.state.pos; + com.add( pos._[0] * b.mass, pos._[1] * b.mass ); + M += b.mass; + } + + // add mass + this.mass += M; + // com adjustment (assuming com is currently at (0,0) body coords) + com.mult( 1 / this.mass ); + + // shift the center of mass + this.offset.vsub( com ); + + // refresh view on next render + if ( this._world ){ + this._world.one('render', function(){ + self.view = null; + }); + } + this.recalc(); + + return scratch.done( this ); + }, + + /** + * CompoundBody#clear() -> this + * + * Remove all children. + **/ + clear: function(){ + + this._aabb = null; + this.moi = 0; + this.mass = 0; + this.offset.zero(); + this.children = []; + this.geometry.clear(); + + return this; + }, + + /** + * CompoundBody#refreshGeometry() -> this + * + * If the children's positions change, `refreshGeometry()` should be called to fix the shape. + **/ + refreshGeometry: function(){ + + this.geometry.clear(); + + for ( var i = 0, b, l = this.children.length; i < l; i++ ) { + b = this.children[ i ]; + this.geometry.addChild( b.geometry, new Physics.vector(b.state.pos).vadd(b.offset), b.state.angular.pos ); + } + + return this; + }, + + // extended + recalc: function(){ + + parent.recalc.call(this); + // moment of inertia + var b + ,moi = 0 + ; + + for ( var i = 0, l = this.children.length; i < l; i++ ) { + b = this.children[ i ]; + b.recalc(); + // parallel axis theorem + moi += b.moi + b.mass * b.state.pos.normSq(); + } + + this.moi = moi; + return this; + } + }; +}); + + // --- // inside: src/bodies/convex-polygon.js /* * @requires geometries/convex-polygon */ - /** + /** * class ConvexPolygonBody < Body * * Physics.body('convex-polygon') @@ -6091,7 +6911,7 @@ Physics.body('circle', function( parent ){ * Body for convex polygons. The position of the body is the centroid of the polygon. * * Additional config options: - * + * * - vertices: Array of [[Vectorish]] objects representing the polygon vertices in clockwise (or counterclockwise) order. * * Example: @@ -6115,7 +6935,7 @@ Physics.body('circle', function( parent ){ Physics.body('convex-polygon', function( parent ){ var defaults = { - + }; return { @@ -6340,7 +7160,7 @@ Physics.behavior('attractor', function( parent ){ * bodyB: // the second body * norm: // the normal vector (Vectorish) * mtv: // the minimum transit vector. (the direction and length needed to extract bodyB from bodyA) - * pos: // the collision point + * pos: // the collision point relative to bodyA * overlap: // the amount bodyA overlaps bodyB * } * ``` @@ -6368,42 +7188,46 @@ Physics.behavior('body-collision-detection', function( parent ){ ; if ( !fn ){ - fn = supportFnStack[ hash ] = function( searchDir ){ + fn = supportFnStack[ hash ] = function pairSupportFunction( searchDir ){ - var scratch = Physics.scratchpad() - ,tA = fn.tA + var tA = fn.tA ,tB = fn.tB - ,vA = scratch.vector() - ,vB = scratch.vector() - ,marginA = fn.marginA - ,marginB = fn.marginB + ,vA = fn.tmpv1 + ,vB = fn.tmpv2 ; if ( fn.useCore ){ - vA = bodyA.geometry.getFarthestCorePoint( searchDir.rotateInv( tA ), vA, marginA ).transform( tA ); - vB = bodyB.geometry.getFarthestCorePoint( searchDir.rotate( tA ).rotateInv( tB ).negate(), vB, marginB ).transform( tB ); + vA = bodyA.geometry.getFarthestCorePoint( searchDir.rotateInv( tA ), vA, fn.marginA ); + vB = bodyB.geometry.getFarthestCorePoint( searchDir.rotate( tA ).rotateInv( tB ).negate(), vB, fn.marginB ); } else { - vA = bodyA.geometry.getFarthestHullPoint( searchDir.rotateInv( tA ), vA ).transform( tA ); - vB = bodyB.geometry.getFarthestHullPoint( searchDir.rotate( tA ).rotateInv( tB ).negate(), vB ).transform( tB ); + vA = bodyA.geometry.getFarthestHullPoint( searchDir.rotateInv( tA ), vA ); + vB = bodyB.geometry.getFarthestHullPoint( searchDir.rotate( tA ).rotateInv( tB ).negate(), vB ); } + vA.vadd( bodyA.offset ).transform( tA ); + vB.vadd( bodyB.offset ).transform( tB ); searchDir.negate().rotate( tB ); - return scratch.done({ + return { a: vA.values(), b: vB.values(), pt: vA.vsub( vB ).values() - }); + }; }; - fn.tA = Physics.transform(); - fn.tB = Physics.transform(); + // transforms for coordinate transformations + fn.tA = new Physics.transform(); + fn.tB = new Physics.transform(); + + // temp vectors (used too frequently to justify scratchpad) + fn.tmpv1 = new Physics.vector(); + fn.tmpv2 = new Physics.vector(); } fn.useCore = false; fn.margin = 0; - fn.tA.setTranslation( bodyA.state.pos ).setRotation( bodyA.state.angular.pos ); - fn.tB.setTranslation( bodyB.state.pos ).setRotation( bodyB.state.angular.pos ); + fn.tA.setRotation( bodyA.state.angular.pos ).setTranslation( bodyA.state.pos ); + fn.tB.setRotation( bodyB.state.angular.pos ).setTranslation( bodyB.state.pos ); fn.bodyA = bodyA; fn.bodyB = bodyB; @@ -6423,9 +7247,11 @@ Physics.behavior('body-collision-detection', function( parent ){ var scratch = Physics.scratchpad() ,d = scratch.vector() ,tmp = scratch.vector() + ,os = scratch.vector() ,overlap ,result ,support + ,inc ,collision = false ,aabbA = bodyA.aabb() ,dimA = Math.min( aabbA.hw, aabbA.hh ) @@ -6435,7 +7261,11 @@ Physics.behavior('body-collision-detection', function( parent ){ // just check the overlap first support = getSupportFn( bodyA, bodyB ); - d.clone( bodyA.state.pos ).vsub( bodyB.state.pos ); + d.clone( bodyA.state.pos ) + .vadd( bodyA.getGlobalOffset( os ) ) + .vsub( bodyB.state.pos ) + .vsub( bodyB.getGlobalOffset( os ) ) + ; result = Physics.gjk(support, d, true); if ( result.overlap ){ @@ -6446,17 +7276,23 @@ Physics.behavior('body-collision-detection', function( parent ){ bodyB: bodyB }; + // inc by 1% of the smallest dim. + inc = 1e-2 * Math.min(dimA || 1, dimB || 1); + // first get the min distance of between core objects support.useCore = true; support.marginA = 0; support.marginB = 0; - while ( result.overlap && (support.marginA < dimA || support.marginB < dimB) ){ + // while there's still an overlap (or we don't have a positive distance) + // and the support margins aren't bigger than the shapes... + // search for the distance data + while ( (result.overlap || result.distance === 0) && (support.marginA < dimA || support.marginB < dimB) ){ if ( support.marginA < dimA ){ - support.marginA += 1; + support.marginA += inc; } if ( support.marginB < dimB ){ - support.marginB += 1; + support.marginB += inc; } result = Physics.gjk(support, d); @@ -6468,13 +7304,18 @@ Physics.behavior('body-collision-detection', function( parent ){ } // calc overlap - overlap = Math.max(0, (support.marginA + support.marginB) - result.distance); + overlap = (support.marginA + support.marginB) - result.distance; + + if ( overlap <= 0 ){ + return scratch.done(false); + } + collision.overlap = overlap; // @TODO: for now, just let the normal be the mtv collision.norm = d.clone( result.closest.b ).vsub( tmp.clone( result.closest.a ) ).normalize().values(); collision.mtv = d.mult( overlap ).values(); // get a corresponding hull point for one of the core points.. relative to body A - collision.pos = d.clone( collision.norm ).mult( support.margin ).vadd( tmp.clone( result.closest.a ) ).vsub( bodyA.state.pos ).values(); + collision.pos = d.clone( collision.norm ).mult( support.marginA ).vadd( tmp.clone( result.closest.a ) ).vsub( bodyA.state.pos ).values(); } return scratch.done( collision ); @@ -6497,7 +7338,11 @@ Physics.behavior('body-collision-detection', function( parent ){ ,collision = false ; - d.clone( bodyB.state.pos ).vsub( bodyA.state.pos ); + d.clone( bodyB.state.pos ) + .vadd( bodyB.getGlobalOffset( tmp ) ) + .vsub( bodyA.state.pos ) + .vsub( bodyA.getGlobalOffset( tmp ) ) // save offset for later + ; overlap = d.norm() - (bodyA.geometry.radius + bodyB.geometry.radius); // hmm... they overlap exactly... choose a direction @@ -6506,12 +7351,6 @@ Physics.behavior('body-collision-detection', function( parent ){ d.set( 1, 0 ); } - // if ( overlap > 0 ){ - // // check the future - // d.vadd( tmp.clone(bodyB.state.vel).mult( dt ) ).vsub( tmp.clone(bodyA.state.vel).mult( dt ) ); - // overlap = d.norm() - (bodyA.geometry.radius + bodyB.geometry.radius); - // } - if ( overlap <= 0 ){ collision = { @@ -6519,7 +7358,7 @@ Physics.behavior('body-collision-detection', function( parent ){ bodyB: bodyB, norm: d.normalize().values(), mtv: d.mult( -overlap ).values(), - pos: d.normalize().mult( bodyA.geometry.radius ).values(), + pos: d.mult( -bodyA.geometry.radius/overlap ).vadd( tmp ).values(), overlap: -overlap }; } @@ -6528,7 +7367,7 @@ Physics.behavior('body-collision-detection', function( parent ){ }; /* - * checkPair( bodyA, bodyB ) -> Object + * checkPair( bodyA, bodyB[, disp] ) -> Object * - bodyA (Object): First body * - bodyB (Object): Second body * + (Object): Collision result @@ -6549,6 +7388,68 @@ Physics.behavior('body-collision-detection', function( parent ){ return checkCircles( bodyA, bodyB ); + } else if ( bodyA.geometry.name === 'compound' || bodyB.geometry.name === 'compound' ){ + // compound bodies are special. We can't use gjk because + // they could have concavities. so we do the pieces individually + var test = (bodyA.geometry.name === 'compound') + ,compound = test ? bodyA : bodyB + ,other = test ? bodyB : bodyA + ,cols + ,ch + ,ret = [] + ,scratch = Physics.scratchpad() + ,vec = scratch.vector() + ,oldPos = scratch.vector() + ,otherAABB = other.aabb() + ,i + ,l + ; + + for ( i = 0, l = compound.children.length; i < l; i++ ){ + + ch = compound.children[ i ]; + // move body to fake position + oldPos.clone( ch.state.pos ); + ch.offset.vadd( oldPos.vadd( compound.offset ).rotate( -ch.state.angular.pos ) ); + ch.state.pos.clone( compound.state.pos ); + ch.state.angular.pos += compound.state.angular.pos; + + // check it if the aabbs overlap + if ( Physics.aabb.overlap(otherAABB, ch.aabb()) ){ + + cols = checkPair( other, ch ); + + if ( cols instanceof Array ){ + for ( var j = 0, c, ll = cols.length; j < ll; j++ ){ + c = cols[j]; + // set body to be the compound body + if ( c.bodyA === ch ){ + c.bodyA = compound; + } else { + c.bodyB = compound; + } + ret.push( c ); + } + + } else if ( cols ) { + // set body to be the compound body + if ( cols.bodyA === ch ){ + cols.bodyA = compound; + } else { + cols.bodyB = compound; + } + ret.push( cols ); + } + } + + // transform it back + ch.state.angular.pos -= compound.state.angular.pos; + ch.offset.vsub( oldPos ); + ch.state.pos.clone( oldPos.rotate( ch.state.angular.pos ).vsub( compound.offset ) ); + } + + return scratch.done( ret ); + } else { return checkGJK( bodyA, bodyB ); @@ -6593,11 +7494,11 @@ Physics.behavior('body-collision-detection', function( parent ){ if ( this.options.check === true ){ - world.off( 'integrate:velocities', this.checkAll ); + world.off( 'integrate:velocities', this.checkAll, this ); } else { - world.off( this.options.check, this.check ); + world.off( this.options.check, this.check, this ); } }, @@ -6614,6 +7515,10 @@ Physics.behavior('body-collision-detection', function( parent ){ ,targets = this.getTargets() ,collisions = [] ,ret + ,prevContacts = this.prevContacts || {} + ,contactList = {} + ,pairHash = Physics.util.pairHash + ,hash ; for ( var i = 0, l = candidates.length; i < l; ++i ){ @@ -6627,12 +7532,30 @@ Physics.behavior('body-collision-detection', function( parent ){ ){ ret = checkPair( pair.bodyA, pair.bodyB ); - if ( ret ){ + if ( ret instanceof Array ){ + + for ( var j = 0, r, ll = ret.length; j < ll; j++ ){ + r = ret[j]; + if ( r ){ + hash = pairHash( pair.bodyA.uid, pair.bodyB.uid ); + contactList[ hash ] = true; + r.collidedPreviously = prevContacts[ hash ]; + collisions.push( r ); + } + } + + } else if ( ret ){ + hash = pairHash( pair.bodyA.uid, pair.bodyB.uid ); + contactList[ hash ] = true; + ret.collidedPreviously = prevContacts[ hash ]; + collisions.push( ret ); } } } + this.prevContacts = contactList; + if ( collisions.length ){ this._world.emit( this.options.channel, { @@ -6655,6 +7578,10 @@ Physics.behavior('body-collision-detection', function( parent ){ ,bodyB ,collisions = [] ,ret + ,prevContacts = this.prevContacts || {} + ,contactList = {} + ,pairHash = Physics.util.pairHash + ,hash ; for ( var j = 0, l = bodies.length; j < l; j++ ){ @@ -6667,12 +7594,30 @@ Physics.behavior('body-collision-detection', function( parent ){ ret = checkPair( bodyA, bodyB ); - if ( ret ){ + if ( ret instanceof Array ){ + + for ( var k = 0, r, ll = ret.length; k < ll; k++ ){ + r = ret[k]; + if ( r ){ + hash = pairHash( bodyA.uid, bodyB.uid ); + contactList[ hash ] = true; + r.collidedPreviously = prevContacts[ hash ]; + collisions.push( r ); + } + } + + } else if ( ret ){ + hash = pairHash( bodyA.uid, bodyB.uid ); + contactList[ hash ] = true; + ret.collidedPreviously = prevContacts[ hash ]; + collisions.push( ret ); } } } + this.prevContacts = contactList; + if ( collisions.length ){ this._world.emit( this.options.channel, { @@ -6697,14 +7642,46 @@ Physics.behavior('body-collision-detection', function( parent ){ * * Additional options include: * - check: channel to listen to for collisions (default: `collisions:detected`). + * - mtvThreshold: apply partial extraction of bodies if the minimum transit vector is less than this value ( default: `1`) + * this will depend on your simulation characteristic length scale + * - bodyExtractDropoff: every body overlap correction (underneith mtvThreshold) will only extract by this fraction (0..1). Helps with stablizing contacts. (default: `0.5`) + * - forceWakeupAboveOverlapThreshold: force bodies to wake up if the overlap is above mtvThreshold ( default: `true` ) **/ Physics.behavior('body-impulse-response', function( parent ){ var defaults = { // channel to listen to for collisions check: 'collisions:detected' + // apply partial extraction of bodies if the minimum transit vector is less than this value + // this will depend on your simulation characteristic length scale + ,mtvThreshold: 1 + // every body overlap correction (underneith mtvThreshold) will only extract by this fraction (0..1) + // helps with stablizing contacts. + ,bodyExtractDropoff: 0.5 + // force bodies to wake up if the overlap is above mtvThreshold + ,forceWakeupAboveOverlapThreshold: true }; + function getUid( b ){ + return b.uid; + } + + function clampMTV( totalV, mtv, into ){ + + var m, n; + n = mtv.norm(); + m = n - totalV.proj( mtv ); + m = Math.max( 0, Math.min( n, m ) ); + + if ( n === 0 ){ + into.zero(); + } else { + into.clone( mtv ).mult( m/n ); + } + + return into; + } + return { // extended @@ -6713,6 +7690,8 @@ Physics.behavior('body-impulse-response', function( parent ){ parent.init.call( this ); this.options.defaults( defaults ); this.options( options ); + + this._bodyList = []; }, // no applyTo method @@ -6727,7 +7706,7 @@ Physics.behavior('body-impulse-response', function( parent ){ // extended disconnect: function( world ){ - world.off( this.options.check, this.respond ); + world.off( this.options.check, this.respond, this ); }, /** internal @@ -6756,24 +7735,6 @@ Physics.behavior('body-impulse-response', function( parent ){ return; } - if ( fixedA ){ - - // extract bodies - bodyB.state.pos.vadd( mtv ); - - } else if ( fixedB ){ - - // extract bodies - bodyA.state.pos.vsub( mtv ); - - } else { - - // extract bodies - mtv.mult( 0.5 ); - bodyA.state.pos.vsub( mtv ); - bodyB.state.pos.vadd( mtv ); - } - // inverse masses and moments of inertia. // give fixed bodies infinite mass and moi var invMoiA = fixedA ? 0 : 1 / bodyA.moi @@ -6781,18 +7742,20 @@ Physics.behavior('body-impulse-response', function( parent ){ ,invMassA = fixedA ? 0 : 1 / bodyA.mass ,invMassB = fixedB ? 0 : 1 / bodyB.mass // coefficient of restitution between bodies - ,cor = contact ? 0 : bodyA.restitution * bodyB.restitution + ,cor = bodyA.restitution * bodyB.restitution // coefficient of friction between bodies ,cof = bodyA.cof * bodyB.cof // normal vector ,n = scratch.vector().clone( normal ) // vector perpendicular to n ,perp = scratch.vector().clone( n ).perp() + ,tmp = scratch.vector() // collision point from A's center ,rA = scratch.vector().clone( point ) // collision point from B's center - ,rB = scratch.vector().clone( point ).vadd( bodyA.state.pos ).vsub( bodyB.state.pos ) - ,tmp = scratch.vector() + ,rB = scratch.vector().clone( point ) + .vadd( bodyA.state.pos ) + .vsub( bodyB.state.pos ) ,angVelA = bodyA.state.angular.vel ,angVelB = bodyB.state.angular.vel // relative velocity towards B at collision point @@ -6810,9 +7773,37 @@ Physics.behavior('body-impulse-response', function( parent ){ ,impulse ,sign ,max - ,inContact = false + ,ratio + ,inContact = contact ; + if ( contact ){ + + if ( fixedA ){ + + clampMTV( bodyB._mtvTotal, mtv, tmp ); + bodyB._mtvTotal.vadd( tmp ); + + } else if ( fixedB ){ + + clampMTV( bodyA._mtvTotal, mtv.negate(), tmp ); + bodyA._mtvTotal.vadd( tmp ); + mtv.negate(); + + } else { + + ratio = 0.5; //bodyA.mass / ( bodyA.mass + bodyB.mass ); + mtv.mult( ratio ); + clampMTV( bodyB._mtvTotal, mtv, tmp ); + bodyB._mtvTotal.vadd( tmp ); + + mtv.clone( mtrans ).mult( ratio - 1 ); + clampMTV( bodyA._mtvTotal, mtv, tmp ); + bodyA._mtvTotal.vadd( tmp ); + + } + } + // if moving away from each other... don't bother. if (vproj >= 0){ scratch.done(); @@ -6865,21 +7856,15 @@ Physics.behavior('body-impulse-response', function( parent ){ // allowed amount // maximum impulse allowed by kinetic friction - max = vreg / ( invMassA + invMassB + (invMoiA * rAproj * rAproj) + (invMoiB * rBproj * rBproj) ); - - if (!inContact){ - // the sign of vreg ( plus or minus 1 ) - sign = vreg < 0 ? -1 : 1; + max = Math.abs(vreg) / ( invMassA + invMassB + (invMoiA * rAproj * rAproj) + (invMoiB * rBproj * rBproj) ); + // the sign of vreg ( plus or minus 1 ) + sign = vreg < 0 ? -1 : 1; - // get impulse due to friction - impulse *= sign * cof; - // make sure the impulse isn't giving the system energy - impulse = (sign === 1) ? Math.min( impulse, max ) : Math.max( impulse, max ); - - } else { - - impulse = max; - } + // get impulse due to friction + impulse = cof * Math.abs( impulse ); + // constrain the impulse within the "friction cone" ( max < mu * impulse) + impulse = Math.min( impulse, max ); + impulse *= sign; if ( fixedA ){ @@ -6903,9 +7888,25 @@ Physics.behavior('body-impulse-response', function( parent ){ } } + // wake up bodies if necessary + if ( bodyA.sleep() ){ + bodyA.sleepCheck(); + } + if ( bodyB.sleep() ){ + bodyB.sleepCheck(); + } + scratch.done(); }, + // internal + _pushUniq: function( body ){ + var idx = Physics.util.sortedIndex( this._bodyList, body, getUid ); + if ( this._bodyList[ idx ] !== body ){ + this._bodyList.splice( idx, 0, body ); + } + }, + /** internal * BodyImpulseResponseBehavior#respond( data ) * - data (Object): event data @@ -6916,20 +7917,49 @@ Physics.behavior('body-impulse-response', function( parent ){ var self = this ,col - ,collisions = Physics.util.shuffle(data.collisions) + ,collisions = data.collisions// Physics.util.shuffle(data.collisions) + ,i,l,b ; - for ( var i = 0, l = collisions.length; i < l; ++i ){ + for ( i = 0, l = collisions.length; i < l; ++i ){ col = collisions[ i ]; + // add bodies to list for later + this._pushUniq( col.bodyA ); + this._pushUniq( col.bodyB ); + // ensure they have mtv stat vectors + col.bodyA._mtvTotal = col.bodyA._mtvTotal || new Physics.vector(); + col.bodyB._mtvTotal = col.bodyB._mtvTotal || new Physics.vector(); + col.bodyA._oldmtvTotal = col.bodyA._oldmtvTotal || new Physics.vector(); + col.bodyB._oldmtvTotal = col.bodyB._oldmtvTotal || new Physics.vector(); + self.collideBodies( col.bodyA, col.bodyB, col.norm, col.pos, - col.mtv + col.mtv, + col.collidedPreviously ); } + + // apply mtv vectors from the average mtv vector + for ( i = 0, l = this._bodyList.length; i < l; ++i ){ + b = this._bodyList.pop(); + // clampMTV( b._oldmtvTotal, b._mtvTotal, b._mtvTotal ); + + if ( b._mtvTotal.normSq() < this.options.mtvThreshold ){ + b._mtvTotal.mult( this.options.bodyExtractDropoff ); + } else if ( this.options.forceWakeupAboveOverlapThreshold ) { + // wake up bodies if necessary + b.sleep( false ); + } + + b.state.pos.vadd( b._mtvTotal ); + b.state.old.pos.vadd( b._mtvTotal ); + b._oldmtvTotal.swap( b._mtvTotal ); + b._mtvTotal.zero(); + } } }; }); @@ -6967,7 +7997,7 @@ Physics.behavior('constant-acceleration', function( parent ){ this.options( options ); // extend options - this._acc = Physics.vector(); + this._acc = new Physics.vector(); this.setAcceleration( this.options.acc ); delete this.options.acc; }, @@ -7000,7 +8030,7 @@ Physics.behavior('constant-acceleration', function( parent ){ // --- // inside: src/behaviors/edge-collision-detection.js -/** +/** * class EdgeCollisionDetectionBehavior < Behavior * * `Physics.behavior('edge-collision-detection')`. @@ -7021,7 +8051,7 @@ Physics.behavior('edge-collision-detection', function( parent ){ * - bounds (Physics.aabb): The boundary * - dummy: (Body): The dummy body to publish as the static other body it collides with * + (Array): The collision data - * + * * Check if a body collides with the boundary */ var checkGeneral = function checkGeneral( body, bounds, dummy ){ @@ -7029,6 +8059,7 @@ Physics.behavior('edge-collision-detection', function( parent ){ var overlap ,aabb = body.aabb() ,scratch = Physics.scratchpad() + ,offset = body.getGlobalOffset( scratch.vector() ) ,trans = scratch.transform() ,dir = scratch.vector() ,result = scratch.vector() @@ -7055,7 +8086,7 @@ Physics.behavior('edge-collision-detection', function( parent ){ x: overlap, y: 0 }, - pos: body.geometry.getFarthestHullPoint( dir, result ).rotate( trans ).values() + pos: body.geometry.getFarthestHullPoint( dir, result ).rotate( trans ).vadd( offset ).values() }; collisions.push(collision); @@ -7080,7 +8111,7 @@ Physics.behavior('edge-collision-detection', function( parent ){ x: 0, y: overlap }, - pos: body.geometry.getFarthestHullPoint( dir, result ).rotate( trans ).values() + pos: body.geometry.getFarthestHullPoint( dir, result ).rotate( trans ).vadd( offset ).values() }; collisions.push(collision); @@ -7105,7 +8136,7 @@ Physics.behavior('edge-collision-detection', function( parent ){ x: -overlap, y: 0 }, - pos: body.geometry.getFarthestHullPoint( dir, result ).rotate( trans ).values() + pos: body.geometry.getFarthestHullPoint( dir, result ).rotate( trans ).vadd( offset ).values() }; collisions.push(collision); @@ -7130,7 +8161,7 @@ Physics.behavior('edge-collision-detection', function( parent ){ x: 0, y: -overlap }, - pos: body.geometry.getFarthestHullPoint( dir, result ).rotate( trans ).values() + pos: body.geometry.getFarthestHullPoint( dir, result ).rotate( trans ).vadd( offset ).values() }; collisions.push(collision); @@ -7146,7 +8177,7 @@ Physics.behavior('edge-collision-detection', function( parent ){ * - bounds (Physics.aabb): The boundary * - dummy: (Body): The dummy body to publish as the static other body it collides with * + (Array): The collision data - * + * * Check if a body collides with the boundary */ var checkEdgeCollide = function checkEdgeCollide( body, bounds, dummy ){ @@ -7173,8 +8204,8 @@ Physics.behavior('edge-collision-detection', function( parent ){ this.setAABB( this.options.aabb ); this.restitution = this.options.restitution; - - this.body = Physics.body('point', { + + this.body = Physics.body('point', { treatment: 'static', restitution: this.options.restitution, cof: this.options.cof @@ -7184,7 +8215,7 @@ Physics.behavior('edge-collision-detection', function( parent ){ /** * EdgeCollisionDetectionBehavior#setAABB( aabb ) -> this * - aabb (Physics.aabb): The aabb to use as the boundary - * + * * Set the boundaries of the edge. **/ setAABB: function( aabb ){ @@ -7200,7 +8231,7 @@ Physics.behavior('edge-collision-detection', function( parent ){ }, max: { x: (aabb.x + aabb.hw), - y: (aabb.y + aabb.hh) + y: (aabb.y + aabb.hh) } }; @@ -7210,23 +8241,23 @@ Physics.behavior('edge-collision-detection', function( parent ){ // extended connect: function( world ){ - world.on( 'integrate:velocities', this.checkAll, this ); + world.on( 'integrate:positions', this.checkAll, this, 2 ); }, // extended disconnect: function( world ){ - world.off( 'integrate:velocities', this.checkAll ); + world.off( 'integrate:positions', this.checkAll, this, 2 ); }, /** internal * EdgeCollisionDetectionBehavior#checkAll( data ) * - data (Object): Event data - * + * * Event callback to check all bodies for collisions with the edge **/ checkAll: function( data ){ - + var bodies = this.getTargets() ,dt = data.dt ,body @@ -7234,6 +8265,10 @@ Physics.behavior('edge-collision-detection', function( parent ){ ,ret ,bounds = this._edges ,dummy = this.body + ,prevContacts = this.prevContacts || {} + ,contactList = {} + ,pairHash = Physics.util.pairHash + ,hash ; for ( var i = 0, l = bodies.length; i < l; i++ ){ @@ -7242,15 +8277,24 @@ Physics.behavior('edge-collision-detection', function( parent ){ // only detect dynamic bodies if ( body.treatment === 'dynamic' ){ - + ret = checkEdgeCollide( body, bounds, dummy ); if ( ret ){ + hash = pairHash( body.uid, dummy.uid ); + + for ( var j = 0, ll = ret.length; j < ll; j++ ){ + contactList[ hash ] = true; + ret[ j ].collidedPreviously = prevContacts[ hash ]; + } + collisions.push.apply( collisions, ret ); } } } + this.prevContacts = contactList; + if ( collisions.length ){ this._world.emit( this.options.channel, { @@ -7262,6 +8306,7 @@ Physics.behavior('edge-collision-detection', function( parent ){ }); + // --- // inside: src/behaviors/interactive.js @@ -7272,7 +8317,7 @@ Physics.behavior('edge-collision-detection', function( parent ){ * * User interaction helper. * - * Used to get mouse/touch events and add a mouse grab interaction. + * Used to get mouse/touch events and add grab interactions. * * Additional options include: * - el: The element of the renderer. What you input as the `el` for the renderer. @@ -7293,17 +8338,21 @@ Physics.behavior('edge-collision-detection', function( parent ){ * data.x; // the x coord * data.y; // the y coord * }); + * // when a mouse or pointer moves * world.on('interact:move', function( data ){ * data.x; // the x coord * data.y; // the y coord - * data.body; // the body that was grabbed (if applicable) + * data.body; // the grabbed body that was moved (if applicable) * }); * // when the viewport is released (mouseup, touchend) * world.on('interact:release', function( data ){ * data.x; // the x coord * data.y; // the y coord + * data.body; // the body that was grabbed (if applicable) * }); * ``` + * + * The behavior also sets body.isGrabbed = true for any grabbed bodies while they are grabbed. **/ Physics.behavior('interactive', function( parent ){ @@ -7336,28 +8385,13 @@ Physics.behavior('interactive', function( parent ){ return { left: curleft, top: curtop }; } - ,getCoords = function( e ){ - var offset = getElementOffset( e.target ) - ,obj = ( e.changedTouches && e.changedTouches[0] ) || e - ,x = obj.pageX - offset.left - ,y = obj.pageY - offset.top - ; - - return { - x: x - ,y: y - }; - } ; return { // extended init: function( options ){ - var self = this - ,prevTreatment - ,time - ; + var self = this; // call parent init method parent.init.call( this ); @@ -7365,9 +8399,8 @@ Physics.behavior('interactive', function( parent ){ this.options( options ); // vars - this.mousePos = new Physics.vector(); - this.mousePosOld = new Physics.vector(); - this.offset = new Physics.vector(); + this.bodyData = {}; + this.bodyDataByUID = {}; this.el = typeof this.options.el === 'string' ? document.getElementById(this.options.el) : this.options.el; @@ -7376,90 +8409,183 @@ Physics.behavior('interactive', function( parent ){ } // init events - var grab = function grab( e ){ - var pos = getCoords( e ) + // when there are multiple touchdowns, grab is usually called separately for each, + // but we loop through e.changedTouches just in case + self.grab = function grab( e ){ + var pos ,body + ,touchId + ,touch + ,offset + ,data + ,touchIndex + ,l ; if ( self._world ){ - body = self._world.findOne({ $at: new Physics.vector( pos.x, pos.y ) }); - - if ( body ){ - // we're trying to grab a body - - // fix the body in place - prevTreatment = body.treatment; - body.treatment = 'kinematic'; - body.state.vel.zero(); - body.state.angular.vel = 0; - // remember the currently grabbed body - self.body = body; - // remember the mouse offset - self.mousePos.clone( pos ); - self.offset.clone( pos ).vsub( body.state.pos ); - - pos.body = body; - self._world.emit('interact:grab', pos); - } else { + // Adjust for PointerEvent and older browsers + if ( !e.changedTouches ) { + e.changedTouches = [ e ]; + } - self._world.emit('interact:poke', pos); + offset = getElementOffset( e.target ); + + for ( touchIndex = 0, l = e.changedTouches.length; touchIndex < l; touchIndex++) { + touch = e.changedTouches[touchIndex]; + touchId = touch.identifier || touch.pointerId || "mouse"; + pos = { idx: touchId, x: touch.pageX - offset.left, y: touch.pageY - offset.top }; + body = self._world.findOne({ $at: new Physics.vector( pos ), $in: self.getTargets() }); + + if ( body ){ + // we're trying to grab a body + + // fix the body in place + body.state.vel.zero(); + body.state.angular.vel = 0; + body.isGrabbed = true; + // remember the currently grabbed bodies + data = self.bodyData[touchId] || {}; + data.body = body; + // wake the body up + body.sleep( false ); + data.time = Physics.util.ticker.now(); + + // if we're grabbing the same body twice we don't want to remember the wrong treatment. + data.treatment = self.bodyDataByUID[ body.uid ] ? self.bodyDataByUID[ body.uid ].treatment : body.treatment; + // change its treatment but remember its old treatment + body.treatment = 'kinematic'; + // remember the click/touch offset + data.pos = data.pos || new Physics.vector(); + data.pos.clone( pos ); + + data.offset = data.offset || new Physics.vector(); + data.offset.clone( pos ).vsub( body.state.pos ); + // init touchPointsOld here, too, so we don't have to do it in "move" + data.oldPos = data.oldPos || new Physics.vector(); + data.oldPos.clone( pos ); + + pos.body = body; + self.bodyData[touchId] = data; + self.bodyDataByUID[ body.uid ] = data; + self._world.emit('interact:grab', pos); + + } else { + + self._world.emit('interact:poke', pos); + } } } }; - var move = Physics.util.throttle(function move( e ){ - var pos = getCoords( e ) + // when there are multiple touchdowns, move is called once + // and e.changedTouches will have one or more touches in it + self.move = Physics.util.throttle(function move( e ){ + var pos ,state + ,body + ,touchId + ,touch + ,offset + ,data + ,touchIndex + ,l ; - if ( self.body ){ - time = Physics.util.ticker.now(); + if ( self._world ){ + + // Adjust for PointerEvent and older browsers + if ( !e.changedTouches ) { + e.changedTouches = [ e ]; + } - self.mousePosOld.clone( self.mousePos ); - // get new mouse position - self.mousePos.set(pos.x, pos.y); + offset = getElementOffset( self.el ); - pos.body = self.body; - } + for ( touchIndex = 0, l = e.changedTouches.length; touchIndex < l; touchIndex++) { + touch = e.changedTouches[touchIndex]; + touchId = touch.identifier || touch.pointerId || "mouse"; + pos = { idx: touchId, x: touch.pageX - offset.left, y: touch.pageY - offset.top }; + data = self.bodyData[touchId]; + + if ( data ){ + body = data.body; + + // wake the body up + body.sleep( false ); + data.time = Physics.util.ticker.now(); + + // set old mouse position + data.oldPos.clone( data.pos ); + // get new mouse position + data.pos.clone( pos ); + + pos.body = body; + } - self._world.emit('interact:move', pos); + self._world.emit('interact:move', pos); + } + } }, self.options.moveThrottle); - var release = function release( e ){ - var pos = getCoords( e ) + // when there are multiple touchups, release is called once + // and e.changedTouches will have one or more touches in it + self.release = function release( e ){ + var pos ,body - ,dt = Math.max(Physics.util.ticker.now() - time, self.options.moveThrottle) + ,touchId + ,touch + ,offset + ,data + ,dt + ,touchIndex + ,l ; - // get new mouse position - self.mousePos.set(pos.x, pos.y); - - // release the body - if (self.body){ - self.body.treatment = prevTreatment; - // calculate the release velocity - self.body.state.vel.clone( self.mousePos ).vsub( self.mousePosOld ).mult( 1 / dt ); - // make sure it's not too big - self.body.state.vel.clamp( self.options.minVel, self.options.maxVel ); - self.body = false; - } - if ( self._world ){ - self._world.emit('interact:release', pos); - } - }; + // Adjust for PointerEvent and older browsers + if ( !e.changedTouches ) { + e.changedTouches = [ e ]; + } - this.el.addEventListener('mousedown', grab); - this.el.addEventListener('touchstart', grab); + for ( touchIndex = 0, l = e.changedTouches.length; touchIndex < l; touchIndex++) { + offset = getElementOffset( self.el ); + touch = e.changedTouches[touchIndex]; + touchId = touch.identifier || touch.pointerId || "mouse"; + pos = { idx: touchId, x: touch.pageX - offset.left, y: touch.pageY - offset.top }; + data = self.bodyData[touchId]; + + // release the body + if ( data ){ + body = data.body; + // wake the body up + body.sleep( false ); + // get new mouse position + data.pos.clone( pos ); + + dt = Math.max(Physics.util.ticker.now() - data.time, self.options.moveThrottle); + body.treatment = data.treatment; + // calculate the release velocity + body.state.vel.clone( data.pos ).vsub( data.oldPos ).mult( 1 / dt ); + // make sure it's not too big + body.state.vel.clamp( self.options.minVel, self.options.maxVel ); + + body.isGrabbed = false; + pos.body = body; + + delete body.isGrabbed; + } - this.el.addEventListener('mousemove', move); - this.el.addEventListener('touchmove', move); + // emit before we delete the vars in case + // the listeners need the body + self._world.emit('interact:release', pos); - this.el.addEventListener('mouseup', release); - this.el.addEventListener('touchend', release); + // remove vars + delete self.bodyData[touchId]; + } + } + }; }, // extended @@ -7467,13 +8593,51 @@ Physics.behavior('interactive', function( parent ){ // subscribe the .behave() method to the position integration step world.on('integrate:positions', this.behave, this); + + if ( window.PointerEvent ) { + + this.el.addEventListener('pointerdown', this.grab); + window.addEventListener('pointermove', this.move); + window.addEventListener('pointerup', this.release); + + } else { + + this.el.addEventListener('mousedown', this.grab); + this.el.addEventListener('touchstart', this.grab); + + window.addEventListener('mousemove', this.move); + window.addEventListener('touchmove', this.move); + + window.addEventListener('mouseup', this.release); + window.addEventListener('touchend', this.release); + + } }, // extended disconnect: function( world ){ // unsubscribe when disconnected - world.off('integrate:positions', this.behave); + world.off('integrate:positions', this.behave, this); + + if ( window.PointerEvent ) { + + this.el.removeEventListener('pointerdown', this.grab); + window.removeEventListener('pointermove', this.move); + window.removeEventListener('pointerup', this.release); + + } else { + + this.el.removeEventListener('mousedown', this.grab); + this.el.removeEventListener('touchstart', this.grab); + + window.removeEventListener('mousemove', this.move); + window.removeEventListener('touchmove', this.move); + + window.removeEventListener('mouseup', this.release); + window.removeEventListener('touchend', this.release); + + } }, // extended @@ -7482,14 +8646,17 @@ Physics.behavior('interactive', function( parent ){ var self = this ,state ,dt = Math.max(data.dt, self.options.moveThrottle) + ,body + ,d ; - if ( self.body ){ - - // if we have a body, we need to move it the the new mouse position. - // we'll do this by adjusting the velocity so it gets there at the next step - state = self.body.state; - state.vel.clone( self.mousePos ).vsub( self.offset ).vsub( state.pos ).mult( 1 / dt ); + // if we have one or more bodies grabbed, we need to move them to the new mouse/finger positions. + // we'll do this by adjusting the velocity so they get there at the next step + for ( var touchId in self.bodyData ) { + d = self.bodyData[touchId]; + body = d.body; + state = body.state; + state.vel.clone( d.pos ).vsub( d.offset ).vsub( state.pos ).mult( 1 / dt ); } } }; @@ -7499,7 +8666,7 @@ Physics.behavior('interactive', function( parent ){ // --- // inside: src/behaviors/newtonian.js -/** +/** * class NewtonianBehavior < Behavior * * `Physics.behavior('newtonian')`. @@ -7537,42 +8704,95 @@ Physics.behavior('newtonian', function( parent ){ }); this.options( options ); }, - + + calcPotential: function( posA, posB, out ){ + + var strength = this.options.strength + ,minDistSq = this._minDistSq + ,maxDistSq = this._maxDistSq + ,normsq + ,g + ,pos + ; + + pos = out || new Physics.vector(); + + // clone the position + pos.clone( posB ).vsub( posA ); + // get the square distance + normsq = pos.normSq(); + + if (normsq > minDistSq && normsq < maxDistSq){ + + g = strength / normsq; + return pos.normalize().mult( g ); + } + + return pos.zero(); + }, + // extended behave: function( data ){ var bodies = this.getTargets() ,body ,other - ,strength = this.options.strength - ,minDistSq = this._minDistSq - ,maxDistSq = this._maxDistSq ,scratch = Physics.scratchpad() - ,pos = scratch.vector() - ,normsq - ,g + ,potential = scratch.vector() + ,comp + ,bodyA + ,bodyB + ,posA = scratch.vector() + ,posB = scratch.vector() + ,i, j, k, m, l, ll, lll ; - for ( var j = 0, l = bodies.length; j < l; j++ ){ - + for ( j = 0, l = bodies.length; j < l; j++ ){ + body = bodies[ j ]; - for ( var i = j + 1; i < l; i++ ){ - + for ( i = j + 1; i < l; i++ ){ + other = bodies[ i ]; - // clone the position - pos.clone( other.state.pos ); - pos.vsub( body.state.pos ); - // get the square distance - normsq = pos.normSq(); - if (normsq > minDistSq && normsq < maxDistSq){ + if ( body.name === 'compound' ){ + comp = body; + } else if ( other.name === 'compound' ){ + comp = other; + other = body; + } + + if ( comp ){ + if ( other.name === 'compound' ){ + for ( k = 0, ll = comp.children.length; k < ll; k++ ){ + bodyA = comp.children[ k ]; + comp.toWorldCoords( posA.clone( bodyA.state.pos ).vadd( comp.offset ) ); + for ( m = 0, lll = other.children.length; m < lll; m++ ){ + bodyB = other.children[ m ]; + other.toWorldCoords( posB.clone( bodyB.state.pos ).vadd( other.offset ) ); + this.calcPotential( posA, posB, potential ); + comp.accelerate( potential.mult( bodyB.mass ) ); + other.accelerate( potential.mult( bodyA.mass/bodyB.mass ).negate() ); + } + } + } else { + for ( k = 0, ll = comp.children.length; k < ll; k++ ){ + bodyA = comp.children[ k ]; + comp.toWorldCoords( posA.clone( bodyA.state.pos ).vadd( comp.offset ) ); + this.calcPotential( posA, other.state.pos, potential ); + comp.accelerate( potential.mult( other.mass ) ); + other.accelerate( potential.mult( bodyA.mass/other.mass ).negate() ); + } + } - g = strength / normsq; + } else { - body.accelerate( pos.normalize().mult( g * other.mass ) ); - other.accelerate( pos.mult( body.mass/other.mass ).negate() ); + this.calcPotential( body.state.pos, other.state.pos, potential ); + body.accelerate( potential.mult( other.mass ) ); + other.accelerate( potential.mult( body.mass/other.mass ).negate() ); } + + comp = null; } } @@ -7654,7 +8874,7 @@ Physics.behavior('sweep-prune', function( parent ){ world.on( 'add:body', this.trackBody, this ); world.on( 'remove:body', this.untrackBody, this ); - world.on( 'integrate:velocities', this.sweep, this ); + world.on( 'integrate:positions', this.sweep, this, 1 ); // add current bodies var bodies = world.getBodies(); @@ -7667,9 +8887,9 @@ Physics.behavior('sweep-prune', function( parent ){ // extended disconnect: function( world ){ - world.off( 'add:body', this.trackBody ); - world.off( 'remove:body', this.untrackBody ); - world.off( 'integrate:velocities', this.sweep ); + world.off( 'add:body', this.trackBody, this ); + world.off( 'remove:body', this.untrackBody, this ); + world.off( 'integrate:positions', this.sweep, this, 1 ); this.clear(); }, @@ -7683,6 +8903,11 @@ Physics.behavior('sweep-prune', function( parent ){ this.updateIntervals(); this.sortIntervalLists(); + + if ( this._world ){ + this._world.emit('sweep-prune:intervals', this.intervalLists); + } + return this.checkOverlaps(); }, @@ -7783,6 +9008,10 @@ Physics.behavior('sweep-prune', function( parent ){ }; } + if ( doCreate){ + c.flag = 1; + } + return c; }, @@ -7851,7 +9080,8 @@ Physics.behavior('sweep-prune', function( parent ){ ,candidates = this.candidates ; - encounters.length = candidates.length = 0; + Physics.util.clearArray( encounters ); + Physics.util.clearArray( candidates ); for ( var xyz = 0; xyz < maxDof; ++xyz ){ @@ -7899,11 +9129,7 @@ Physics.behavior('sweep-prune', function( parent ){ // if it's the x axis, create a pair c = this.getPair( tr1, tr2, isX ); - if ( c ){ - - if ( c.flag > collisionFlag ){ - c.flag = 1; - } + if ( c && c.flag < collisionFlag ){ // if it's greater than the axis index, set the flag // to = 0. @@ -7946,10 +9172,7 @@ Physics.behavior('sweep-prune', function( parent ){ var tr ,intr - ,scratch = Physics.scratchpad() - ,pos = scratch.vector() ,aabb - ,span = scratch.vector() ,list = this.tracked ,i = list.length ; @@ -7959,17 +9182,13 @@ Physics.behavior('sweep-prune', function( parent ){ tr = list[ i ]; intr = tr.interval; - pos.clone( tr.body.state.pos ); aabb = tr.body.aabb(); - span.set( aabb.hw, aabb.hh ); // copy the position (plus or minus) the aabb half-dimensions // into the min/max intervals - intr.min.val.clone( pos ).vsub( span ); - intr.max.val.clone( pos ).vadd( span ); + intr.min.val.clone( aabb ).sub( aabb.hw, aabb.hh ); + intr.max.val.clone( aabb ).add( aabb.hw, aabb.hh ); } - - scratch.done(); }, /** internal @@ -7990,13 +9209,13 @@ Physics.behavior('sweep-prune', function( parent ){ min: { type: false, //min - val: Physics.vector(), + val: new Physics.vector(), tracker: tracker }, max: { type: true, //max - val: Physics.vector(), + val: new Physics.vector(), tracker: tracker } } @@ -8094,7 +9313,7 @@ Physics.behavior('sweep-prune', function( parent ){ // --- // inside: src/behaviors/verlet-constraints.js -/** +/** * class VerletConstraintsBehavior < Behavior * * `Physics.behavior('verlet-constraints')`. @@ -8145,12 +9364,12 @@ Physics.behavior('verlet-constraints', function( parent ){ // extended disconnect: function( world ){ - world.off('integrate:positions', this.resolve); + world.off('integrate:positions', this.resolve, this); }, /** * VerletConstraintsBehavior#drop() -> this - * + * * Remove all constraints **/ drop: function(){ @@ -8168,7 +9387,7 @@ Physics.behavior('verlet-constraints', function( parent ){ * - stiffness (Number): A number between 0 and 1 that represents the stiffness of the constraint. Defaults to: `0.5` * - targetLength (Number): Target length. defaults to current distance between the bodies * + (Object): The constraint data object - * + * * Constrain two bodies to a target relative distance. * * Returns constraint data that can be used to remove the constraint later. @@ -8211,7 +9430,7 @@ Physics.behavior('verlet-constraints', function( parent ){ * - stiffness (Number): A number between 0 and 1 that represents the stiffness of the constraint. Defaults to: `0.5` * - targetAngle (Number): Target angle. Defaults to the current angle between bodies * + (Object): The constraint data object - * + * * Constrain three bodies to a target relative angle * * Returns constraint data that can be used to remove the constraint later. @@ -8250,7 +9469,7 @@ Physics.behavior('verlet-constraints', function( parent ){ * VerletConstraintsBehavior#remove( constraintId ) -> this * - constraintData (Object): The constraint data returned when creating a constraint * - constraintId (String): The constraint id - * + * * Remove a constraint **/ remove: function( cstrOrId ){ @@ -8270,7 +9489,7 @@ Physics.behavior('verlet-constraints', function( parent ){ if ( isObj ){ for ( i = 0, l = constraints.length; i < l; ++i ){ - + if ( constraints[ i ] === cstrOrId ){ constraints.splice( i, 1 ); @@ -8280,7 +9499,7 @@ Physics.behavior('verlet-constraints', function( parent ){ } else { for ( i = 0, l = constraints.length; i < l; ++i ){ - + if ( constraints[ i ].id === cstrOrId ){ constraints.splice( i, 1 ); @@ -8295,7 +9514,7 @@ Physics.behavior('verlet-constraints', function( parent ){ /** internal * VerletConstraintsBehavior#resolveAngleConstraints( coef ) * - coef (Number): Coefficient for this resolution phase - * + * * Resolve angle constraints. **/ resolveAngleConstraints: function( coef ){ @@ -8311,7 +9530,7 @@ Physics.behavior('verlet-constraints', function( parent ){ ; for ( var i = 0, l = constraints.length; i < l; ++i ){ - + con = constraints[ i ]; ang = con.bodyB.state.pos.angle2( con.bodyA.state.pos, con.bodyC.state.pos ); @@ -8322,11 +9541,11 @@ Physics.behavior('verlet-constraints', function( parent ){ continue; } else if (corr <= -Math.PI){ - + corr += TWOPI; } else if (corr >= Math.PI){ - + corr -= TWOPI; } @@ -8341,7 +9560,7 @@ Physics.behavior('verlet-constraints', function( parent ){ if ( con.bodyA.treatment === 'dynamic' ){ if ( con.bodyB.treatment === 'dynamic' && con.bodyC.treatment === 'dynamic' ){ - + ang = corr * (con.bodyB.mass + con.bodyC.mass) * invMassSum; } else if ( con.bodyB.treatment !== 'dynamic' ){ @@ -8353,7 +9572,6 @@ Physics.behavior('verlet-constraints', function( parent ){ ang = corr * con.bodyB.mass / ( con.bodyB.mass + con.bodyA.mass ); } - // ang = corr; trans.setRotation( ang ); con.bodyA.state.pos.translateInv( trans ); @@ -8364,20 +9582,18 @@ Physics.behavior('verlet-constraints', function( parent ){ if ( con.bodyC.treatment === 'dynamic' ){ if ( con.bodyA.treatment === 'dynamic' && con.bodyB.treatment === 'dynamic' ){ - + ang = -corr * (con.bodyB.mass + con.bodyA.mass) * invMassSum; } else if ( con.bodyB.treatment !== 'dynamic' ){ ang = -corr * con.bodyA.mass / ( con.bodyC.mass + con.bodyA.mass ); - + } else { ang = -corr * con.bodyB.mass / ( con.bodyB.mass + con.bodyC.mass ); } - // ang = -corr; - trans.setRotation( ang ); con.bodyC.state.pos.translateInv( trans ); con.bodyC.state.pos.rotate( trans ); @@ -8387,13 +9603,13 @@ Physics.behavior('verlet-constraints', function( parent ){ if ( con.bodyB.treatment === 'dynamic' ){ if ( con.bodyA.treatment === 'dynamic' && con.bodyC.treatment === 'dynamic' ){ - + ang = corr * (con.bodyA.mass + con.bodyC.mass) * invMassSum; } else if ( con.bodyA.treatment !== 'dynamic' ){ ang = corr * con.bodyC.mass / ( con.bodyC.mass + con.bodyB.mass ); - + } else { ang = corr * con.bodyA.mass / ( con.bodyA.mass + con.bodyC.mass ); @@ -8411,6 +9627,10 @@ Physics.behavior('verlet-constraints', function( parent ){ con.bodyB.state.pos.rotateInv( trans ); con.bodyB.state.pos.translate( trans ); } + + con.bodyA.sleepCheck(); + con.bodyB.sleepCheck(); + con.bodyC.sleepCheck(); } scratch.done(); @@ -8419,7 +9639,7 @@ Physics.behavior('verlet-constraints', function( parent ){ /** internal * VerletConstraintsBehavior#resolveDistanceConstraints( coef ) * - coef (Number): Coefficient for this resolution phase - * + * * Resolve distance constraints. **/ resolveDistanceConstraints: function( coef ){ @@ -8434,7 +9654,7 @@ Physics.behavior('verlet-constraints', function( parent ){ ; for ( var i = 0, l = constraints.length; i < l; ++i ){ - + con = constraints[ i ]; // move constrained bodies to target length based on their @@ -8442,7 +9662,7 @@ Physics.behavior('verlet-constraints', function( parent ){ BA.clone( con.bodyB.state.pos ).vsub( con.bodyA.state.pos ); len = BA.normSq() || Math.random() * 0.0001; corr = coef * con.stiffness * ( len - con.targetLengthSq ) / len; - + BA.mult( corr ); proportion = (con.bodyA.treatment !== 'dynamic' || con.bodyB.treatment !== 'dynamic') ? 1 : con.bodyB.mass / (con.bodyA.mass + con.bodyB.mass); @@ -8467,6 +9687,9 @@ Physics.behavior('verlet-constraints', function( parent ){ con.bodyB.state.pos.vsub( BA ); } + + con.bodyA.sleepCheck(); + con.bodyB.sleepCheck(); } scratch.done(); @@ -8474,7 +9697,7 @@ Physics.behavior('verlet-constraints', function( parent ){ /** internal * VerletConstraintsBehavior#shuffleConstraints() - * + * * Mix up the constraints. **/ shuffleConstraints: function(){ @@ -8485,7 +9708,7 @@ Physics.behavior('verlet-constraints', function( parent ){ /** internal * VerletConstraintsBehavior#resolve() - * + * * Resolve all constraints. **/ resolve: function(){ @@ -8505,7 +9728,7 @@ Physics.behavior('verlet-constraints', function( parent ){ /** * VerletConstraintsBehavior#getConstraints() -> Object * + (Object): The object containing copied arrays of the constraints - * + * * Get all constraints. **/ getConstraints: function(){ @@ -8525,7 +9748,7 @@ Physics.behavior('verlet-constraints', function( parent ){ Physics.integrator('improved-euler', function( parent ){ return { - /** + /** * class ImprovedEuler < Integrator * * `Physics.integrator('improved-euler')`. @@ -8539,7 +9762,7 @@ Physics.integrator('improved-euler', function( parent ){ // call parent init parent.init.call(this, options); }, - + // extended integrateVelocities: function( bodies, dt ){ @@ -8555,15 +9778,15 @@ Physics.integrator('improved-euler', function( parent ){ state = body.state; // only integrate if the body isn't fixed - if ( body.treatment !== 'static' ){ + if ( body.treatment !== 'static' && !body.sleep( dt ) ){ // Inspired from https://github.com/soulwire/Coffee-Physics // @licence MIT - // + // // x += (v * dt) + (a * 0.5 * dt * dt) // v += a * dt - + // Scale force to mass. // state.acc.mult( body.massInv ); @@ -8589,7 +9812,7 @@ Physics.integrator('improved-euler', function( parent ){ // // Angular components - // + // state.old.angular.vel = state.angular.vel; state.angular.vel += state.angular.acc * dt; @@ -8625,7 +9848,7 @@ Physics.integrator('improved-euler', function( parent ){ state = body.state; // only integrate if the body isn't fixed - if ( body.treatment !== 'static' ){ + if ( body.treatment !== 'static' && !body.sleep() ){ // Store previous location. @@ -8643,7 +9866,7 @@ Physics.integrator('improved-euler', function( parent ){ // // Angular components - // + // state.old.angular.pos = state.angular.pos; state.angular.pos += state.old.angular.vel * dt + state.old.angular.acc * halfdtdt; @@ -8658,124 +9881,501 @@ Physics.integrator('improved-euler', function( parent ){ }); - // --- -// inside: src/renderers/canvas.js - -/** - * class CanvasRenderer < Renderer - * - * Physics.renderer('canvas') - * - * Renderer that uses HTMLCanvas to render the world bodies. - * - * Additional config options: - * - * - debug: Draw debug shapes and bounding boxes. (default: `false`) - * - metaEl: HTMLElement to write meta information like FPS and IPF into. (default: autogenerated) - * - offset: Offset the shapes by this amount. (default: `{ x: 0, y: 0 }`) - * - styles: Styles to use to draw the shapes. (see below) - * - * The styles property should contain _default_ styles for each shape you want to draw. - * - * Example: - * - * ```javascript - * styles: { - * - * 'circle' : { - * strokeStyle: '#542437', - * lineWidth: 1, - * fillStyle: '#542437', - * angleIndicator: 'white' - * }, - * - * 'convex-polygon' : { - * strokeStyle: '#542437', - * lineWidth: 1, - * fillStyle: '#542437', - * angleIndicator: 'white' - * } - * } - * ``` - **/ -Physics.renderer('canvas', function( proto ){ - - if ( !document ){ - // must be in node environment - return {}; - } - - var Pi2 = Math.PI * 2 - // helper to create new dom elements - ,newEl = function( node, content ){ - var el = document.createElement(node || 'div'); - if (content){ - el.innerHTML = content; - } - return el; - } - ,colors = { - white: '#fff' - ,violet: '#542437' - ,blue: '#53777A' - } - ; +// inside: src/integrators/velocity-verlet-alt.js - var defaults = { +Physics.integrator('velocity-verlet-alt', function( parent ){ - // draw aabbs of bodies for debugging - debug: false, - // the element to place meta data into - metaEl: null, - // default styles of drawn objects - styles: { + // for this integrator we need to know if the object has been integrated before + // so let's add a mixin to bodies - 'circle' : { - strokeStyle: colors.blue, - lineWidth: 1, - fillStyle: colors.blue, - angleIndicator: colors.white - }, + Physics.body.mixin({ - 'convex-polygon' : { - strokeStyle: colors.violet, - lineWidth: 1, - fillStyle: colors.violet, - angleIndicator: colors.white + started: function( val ){ + if ( val !== undefined ){ + this._started = true; } - }, - offset: { x: 0, y: 0 } - }; - - // deep copy callback to extend deeper into options - var deep = function( a, b ){ - if ( Physics.util.isPlainObject( b ) ){ - - return Physics.util.extend({}, a, b, deep ); + return !!this._started; } + }); - return b !== undefined ? b : a; - }; return { + /** + * class VelocityVerlet < Integrator + * + * `Physics.integrator('velocity-verlet')`. + * + * The velocity-verlet integrator. + **/ // extended init: function( options ){ - var self = this; + // call parent init + parent.init.call(this, options); + }, - // call proto init - proto.init.call(this, options); + // extended + integrateVelocities: function( bodies, dt ){ - // further options - this.options = Physics.util.extend({}, defaults, this.options, deep); - this.options.offset = Physics.vector( this.options.offset ); + // half the timestep + var dtdt = dt * dt + ,drag = 1 - this.options.drag + ,body = null + ,state + ; + for ( var i = 0, l = bodies.length; i < l; ++i ){ - // hidden canvas - this.hiddenCanvas = document.createElement('canvas'); + body = bodies[ i ]; + state = body.state; + + // only integrate if the body isn't static + if ( body.treatment !== 'static' ){ + + // v = v_prev + 0.5 * (a_prev + a) * dt + // x = x_prev + v_prev * dt + 0.5 * a_prev * dt * dt + + // use the velocity in vel if the velocity has been changed manually + if ( !body.started() ){ + + // Set old vals on first integration + state.old.acc.clone( state.acc ); + state.old.acc.mult( dt ); + state.old.vel.clone( state.vel ).vsub( state.old.acc ); + state.old.acc.mult( 1/dt ); + } + + // Apply "air resistance". + if ( drag ){ + + state.vel.mult( drag ); + } + + // Apply acceleration + // v += 0.5 * (a_prev + a) * dt + state.vel.vadd( state.old.acc.vadd( state.acc ).mult( 0.5 * dt ) ); + + // Reset accel + // state.acc.zero(); + + // + // Angular components + // + + if ( !body.started() ){ + + // Set old vals on first integration + state.old.angular.acc = state.angular.acc; + state.old.angular.vel = state.angular.vel - state.old.angular.acc * dt; + } + + state.angular.vel += 0.5 * (state.angular.acc + state.old.angular.acc) * dt; + state.angular.acc = 0; + + body.started( true ); + + } else { + // set the velocity and acceleration to zero! + state.vel.zero(); + state.acc.zero(); + state.angular.vel = 0; + state.angular.acc = 0; + } + } + }, + + // extended + integratePositions: function( bodies, dt ){ + + // half the timestep + var dtdt = dt * dt + ,body = null + ,state + ; + + for ( var i = 0, l = bodies.length; i < l; ++i ){ + + body = bodies[ i ]; + state = body.state; + + // only integrate if the body isn't static + if ( body.treatment !== 'static' ){ + + // x = x_prev + v_prev * dt + 0.5 * a_prev * dt * dt + + // Store old position. + // xold = x + state.old.pos.clone( state.pos ); + + state.old.vel.mult( dt ); + state.old.acc.mult( 0.5 * dtdt ); + state.pos.vadd( state.old.vel ).vadd( state.old.acc ); + + // store calculated velocity + state.old.vel.clone( state.vel ); + + // store old acc + state.old.acc.clone( state.acc ); + + // Reset accel + state.acc.zero(); + + // + // Angular components + // + state.old.angular.pos = state.angular.pos; + + state.angular.pos += state.angular.vel * dt + 0.5 * state.old.angular.acc * dtdt; + state.old.angular.vel = state.angular.vel; + state.old.angular.acc = state.angular.acc; + state.angular.acc = 0; + } + } + } + }; +}); + + +// --- +// inside: src/integrators/velocity-verlet.js + +Physics.integrator('velocity-verlet', function( parent ){ + + // for this integrator we need to know if the object has been integrated before + // so let's add a mixin to bodies + + Physics.body.mixin({ + + started: function( val ){ + if ( val !== undefined ){ + this._started = true; + } + + return !!this._started; + } + }); + + + return { + /** + * class VelocityVerlet < Integrator + * + * `Physics.integrator('velocity-verlet')`. + * + * The velocity-verlet integrator. + **/ + + // extended + init: function( options ){ + + // call parent init + parent.init.call(this, options); + }, + + /** + * Integrator#integrate( bodies, dt ) -> this + * - bodies (Array): List of bodies to integrate + * - dt (Number): Timestep size + * + * Integrate bodies by timestep. + * + * Will emit `integrate:velocities` and `integrate:positions` + * events on the world. + **/ + integrate: function( bodies, dt ){ + + var world = this._world; + + this.integratePositions( bodies, dt ); + + if ( world ){ + world.emit('integrate:positions', { + bodies: bodies, + dt: dt + }); + } + + this.integrateVelocities( bodies, dt ); + + if ( world ){ + world.emit('integrate:velocities', { + bodies: bodies, + dt: dt + }); + } + + return this; + }, + + // extended + integrateVelocities: function( bodies, dt ){ + + // half the timestep + var dtdt = dt * dt + ,drag = 1 - this.options.drag + ,body = null + ,state + ; + + for ( var i = 0, l = bodies.length; i < l; ++i ){ + + body = bodies[ i ]; + state = body.state; + + // only integrate if the body isn't static + if ( body.treatment !== 'static' && !body.sleep() ){ + + // v = v_prev + 0.5 * (a_prev + a) * dt + // x = x_prev + v_prev * dt + 0.5 * a_prev * dt * dt + + // Apply "air resistance". + if ( drag ){ + + state.vel.mult( drag ); + } + + // Apply acceleration + // v += 0.5 * (a_prev + a) * dt + state.old.vel.clone( state.vel ); + state.vel.vadd( state.old.acc.vadd( state.acc ).mult( 0.5 * dt ) ); + + // Reset accel + state.old.acc.clone( state.acc ); + state.acc.zero(); + + // + // Angular components + // + + state.old.angular.vel = state.angular.vel; + state.old.angular.acc = state.angular.acc; + + state.angular.vel += 0.5 * (state.angular.acc + state.old.angular.acc) * dt; + + state.angular.acc = 0; + + body.started( true ); + + } else { + // set the velocity and acceleration to zero! + state.vel.zero(); + state.acc.zero(); + state.angular.vel = 0; + state.angular.acc = 0; + } + } + }, + + // extended + integratePositions: function( bodies, dt ){ + + // half the timestep + var dtdt = dt * dt + ,body = null + ,state + ; + + for ( var i = 0, l = bodies.length; i < l; ++i ){ + + body = bodies[ i ]; + state = body.state; + + // only integrate if the body isn't static + if ( body.treatment !== 'static' && !body.sleep( dt ) ){ + + // x = x_prev + v_prev * dt + 0.5 * a_prev * dt * dt + + // use the velocity in vel if the velocity has been changed manually + if ( !body.started() ){ + + // Set old vals on first integration + state.old.acc.clone( state.acc ); + state.old.acc.mult( dt ); + state.old.vel.clone( state.vel ).vsub( state.old.acc ); + state.old.acc.mult( 1/dt ); + } + + // Store old position. + // xold = x + state.old.pos.clone( state.pos ); + + state.old.vel.mult( dt ); + state.old.acc.mult( 0.5 * dtdt ); + state.pos.vadd( state.old.vel ).vadd( state.old.acc ); + + // revert + state.old.vel.mult( 1/dt ); + state.old.acc.mult( 2 / dtdt ); + + // + // Angular components + // + + if ( !body.started() ){ + + // Set old vals on first integration + state.old.angular.acc = state.angular.acc; + state.old.angular.vel = state.angular.vel - state.old.angular.acc * dt; + } + + state.old.angular.pos = state.angular.pos; + + state.angular.pos += state.angular.vel * dt + 0.5 * state.old.angular.acc * dtdt; + } + } + } + }; +}); + + +// --- +// inside: src/renderers/canvas.js + +/** + * class CanvasRenderer < Renderer + * + * Physics.renderer('canvas') + * + * Renderer that uses HTMLCanvas to render the world bodies. + * + * Additional config options: + * + * - metaEl: HTMLElement to write meta information like FPS and IPF into. (default: autogenerated) + * - offset: Offset the shapes by this amount. (default: `{ x: 0, y: 0 }`) + * - styles: Styles to use to draw the shapes. (see below) + * + * The styles property should contain _default_ styles for each shape you want to draw. + * + * Example: + * + * ```javascript + * styles: { + * + * 'circle' : { + * strokeStyle: '#542437', + * lineWidth: 1, + * fillStyle: '#542437', + * angleIndicator: 'white' + * }, + * + * 'convex-polygon' : { + * strokeStyle: '#542437', + * lineWidth: 1, + * fillStyle: '#542437', + * angleIndicator: 'white' + * } + * } + * ``` + * + * Styles can also be defined on a per-body basis. Use the "styles" property for a body: + * + * Example: + * + * ```javascript + * Physics.body('circle', { + * // ... + * styles: { + * strokeStyle: '#542437', + * lineWidth: 1, + * fillStyle: '#542437', + * angleIndicator: 'white' + * } + * }); + * ``` + * + * You can also define an image to use for a body: + * + * Example: + * + * ```javascript + * Physics.body('circle', { + * // ... + * styles: { + * src: 'path/to/image.jpg', + * width: 40, + * height: 50 + * } + * }); + * ``` + **/ +Physics.renderer('canvas', function( proto ){ + + if ( !document ){ + // must be in node environment + return {}; + } + + var Pi2 = Math.PI * 2 + // helper to create new dom elements + ,newEl = function( node, content ){ + var el = document.createElement(node || 'div'); + if (content){ + el.innerHTML = content; + } + return el; + } + ,colors = { + white: '#fff' + ,violet: '#542437' + ,blue: '#53777A' + } + ; + + var defaults = { + + // the element to place meta data into + metaEl: null, + // default styles of drawn objects + styles: { + + 'point': colors.blue, + + 'circle' : { + strokeStyle: colors.blue, + lineWidth: 1, + fillStyle: colors.blue, + angleIndicator: colors.white + }, + + 'rectangle' : { + strokeStyle: colors.violet, + lineWidth: 1, + fillStyle: colors.violet, + angleIndicator: colors.white + }, + + 'convex-polygon' : { + strokeStyle: colors.violet, + lineWidth: 1, + fillStyle: colors.violet, + angleIndicator: colors.white + } + }, + offset: { x: 0, y: 0 } + }; + + return { + + // extended + init: function( options ){ + + var self = this; + + // call proto init + proto.init.call(this, options); + + // further options + this.options.defaults( defaults, true ); + this.options.onChange(function(){ + self.options.offset = new Physics.vector( self.options.offset ); + }); + this.options( options, true ); + + // hidden canvas + this.hiddenCanvas = document.createElement('canvas'); this.hiddenCanvas.width = this.hiddenCanvas.height = 100; if (!this.hiddenCanvas.getContext){ @@ -8796,6 +10396,7 @@ Physics.renderer('canvas', function( proto ){ this.el = viewport; } + this.container = this.el.parentNode; this.ctx = viewport.getContext('2d'); this.els = {}; @@ -8816,7 +10417,12 @@ Physics.renderer('canvas', function( proto ){ this._layers = {}; this.addLayer( 'main', this.el ); - this.resize( this.options.width, this.options.height ); + + if ( this.options.autoResize ){ + this.resize(); + } else { + this.resize( this.options.width, this.options.height ); + } }, /** @@ -9009,6 +10615,7 @@ Physics.renderer('canvas', function( proto ){ ,view ,i ,l = bodies.length + ,t = self._interpolateTime ,stack = (l || layer.id !== 'main') ? bodies : self._world._bodies ; @@ -9027,6 +10634,7 @@ Physics.renderer('canvas', function( proto ){ if ( layer.options.follow ){ offset.vsub( layer.options.follow.state.pos ); + offset.sub( layer.options.follow.state.vel.get(0)*t, layer.options.follow.state.vel.get(1)*t ); } if ( clear !== false ){ @@ -9092,13 +10700,14 @@ Physics.renderer('canvas', function( proto ){ resize: function( width, height ){ var layer; + proto.resize.call( this, width, height ); for ( var id in this._layers ){ layer = this._layers[ id ]; if ( layer.options.autoResize ){ - layer.el.width = width; - layer.el.height = height; + layer.el.width = this.width; + layer.el.height = this.height; } } @@ -9245,6 +10854,80 @@ Physics.renderer('canvas', function( proto ){ ctx.fill(); }, + /** + * CanvasRenderer#draw( geometry[, styles, ctx, offset] ) -> this + * - geometry (Geometry): The shape to draw + * - styles (Object): The styles configuration + * - ctx (Canvas2DContext): The canvas context + * - offset (Vectorish): The offset from center + * + * Draw a geometry to a context. + **/ + draw: function( geometry, styles, ctx, offset ){ + + var name = geometry.name + ,x = +(offset && offset.x) + ,y = +(offset && offset.y) + ,w = geometry.aabb().hw + ; + + ctx = ctx || this.ctx; + styles = styles || this.options.styles[ name ] || this.options.styles.circle || {}; + + ctx.save(); + ctx.translate(x, y); + + if (name === 'circle'){ + + this.drawCircle(0, 0, geometry.radius, styles, ctx); + + } else if (name === 'convex-polygon'){ + + this.drawPolygon(geometry.vertices, styles, ctx); + + } else if (name === 'rectangle'){ + + this.drawRect(0, 0, geometry.width, geometry.height, styles, ctx); + + } else if (name === 'compound'){ + + for ( var i = 0, l = geometry.children.length, ch; i < l; i++ ){ + ch = geometry.children[ i ]; + + // translate + ctx.translate(ch.pos.x, ch.pos.y); + // rotate + ctx.rotate(ch.angle); + + this.draw( ch.g, styles, ctx ); + + // unrotate + ctx.rotate(-ch.angle); + // untranslate + ctx.translate(-ch.pos.x, -ch.pos.y); + } + + } else { + + // assume it's a point + this.drawCircle(0, 0, 1, styles, ctx); + } + + if (name !== 'compound' && styles.angleIndicator){ + + ctx.beginPath(); + this.setStyle( styles.angleIndicator, ctx ); + ctx.moveTo(0, 0); + ctx.lineTo(w, 0); + ctx.closePath(); + ctx.stroke(); + } + + ctx.restore(); + + return this; + }, + // extended createView: function( geometry, styles ){ @@ -9252,14 +10935,12 @@ Physics.renderer('canvas', function( proto ){ ,aabb = geometry.aabb() ,hw = aabb.hw + Math.abs(aabb.x) ,hh = aabb.hh + Math.abs(aabb.y) - ,x = hw + 1 - ,y = hh + 1 + ,offset = { x: hw + 1, y: hh + 1 } ,hiddenCtx = this.hiddenCtx ,hiddenCanvas = this.hiddenCanvas - ,name = geometry.name ; - styles = styles || this.options.styles[ name ] || {}; + styles = styles || this.options.styles[ name ] || this.options.styles.circle || {}; // must want an image if ( styles.src ){ @@ -9274,40 +10955,14 @@ Physics.renderer('canvas', function( proto ){ return view; } - x += styles.lineWidth | 0; - y += styles.lineWidth | 0; + offset.x += styles.lineWidth | 0; + offset.y += styles.lineWidth | 0; - // clear + // clear and resize hiddenCanvas.width = 2 * hw + 2 + (2 * styles.lineWidth|0); hiddenCanvas.height = 2 * hh + 2 + (2 * styles.lineWidth|0); - hiddenCtx.save(); - hiddenCtx.translate(x, y); - - if (name === 'circle'){ - - this.drawCircle(0, 0, geometry.radius, styles, hiddenCtx); - - } else if (name === 'convex-polygon'){ - - this.drawPolygon(geometry.vertices, styles, hiddenCtx); - - } else if (name === 'rectangle'){ - - this.drawRect(0, 0, geometry.width, geometry.height, styles, hiddenCtx); - } - - if (styles.angleIndicator){ - - hiddenCtx.beginPath(); - this.setStyle( styles.angleIndicator, hiddenCtx ); - hiddenCtx.moveTo(0, 0); - hiddenCtx.lineTo(hw, 0); - hiddenCtx.closePath(); - hiddenCtx.stroke(); - } - - hiddenCtx.restore(); + this.draw( geometry, styles, hiddenCtx, offset ); view = new Image( hiddenCanvas.width, hiddenCanvas.height ); view.src = hiddenCanvas.toDataURL('image/png'); @@ -9325,6 +10980,7 @@ Physics.renderer('canvas', function( proto ){ drawBody: function( body, view, ctx, offset ){ var pos = body.state.pos + ,os = body.offset ,v = body.state.vel ,t = this._interpolateTime || 0 ,x @@ -9337,29 +10993,16 @@ Physics.renderer('canvas', function( proto ){ ctx = ctx || this.ctx; // interpolate positions - x = pos.x + offset.x + v.x * t; - y = pos.y + offset.y + v.y * t; + x = pos._[0] + offset.x + v._[0] * t; + y = pos._[1] + offset.y + v._[1] * t; ang = body.state.angular.pos + body.state.angular.vel * t; ctx.save(); ctx.translate( x, y ); ctx.rotate( ang ); - ctx.drawImage(view, -view.width/2, -view.height/2); + ctx.translate( os._[0], os._[1] ); + ctx.drawImage(view, -view.width/2, -view.height/2, view.width, view.height); ctx.restore(); - - if ( this.options.debug ){ - aabb = body.aabb(); - // draw bounding boxes - this.drawRect( aabb.x, aabb.y, 2 * aabb.hw, 2 * aabb.hh, 'rgba(0, 0, 255, 0.3)' ); - - // draw also paths - body._debugView = body._debugView || this.createView(body.geometry, 'rgba(255, 0, 0, 0.5)'); - ctx.save(); - ctx.translate(pos.x + offset.x, pos.y + offset.y); - ctx.rotate(body.state.angular.pos); - ctx.drawImage(body._debugView, -body._debugView.width * 0.5, -body._debugView.height * 0.5); - ctx.restore(); - } }, // extended @@ -9448,6 +11091,7 @@ Physics.renderer('dom', function( proto ){ var classpfx = 'pjs-' ,px = 'px' ,cssTransform = pfx('transform') + ,borderRadius = pfx('borderRadius') ; var newEl = function( node, content ){ @@ -9490,6 +11134,36 @@ Physics.renderer('dom', function( proto ){ viewport.appendChild(stats); } + + if ( this.options.autoResize ){ + this.resize(); + } else { + this.resize( this.options.width, this.options.height ); + } + }, + + // extended + resize: function( width, height ){ + + proto.resize.call( this, width, height ); + this.el.style.width = this.width + px; + this.el.style.height = this.height + px; + }, + + /** internal + * DomRenderer#pointProperties( el, geometry ) + * - el (HTMLElement): The element + * - geometry (Geometry): The body's geometry + * + * Set dom element style properties for a point. + **/ + pointProperties: function( el, geometry ){ + + el.style.width = '2px'; + el.style.height = '2px'; + el.style.marginLeft = '-1px'; + el.style.marginTop = '-1px'; + el.style[ borderRadius ] = '50%'; }, /** internal @@ -9507,6 +11181,7 @@ Physics.renderer('dom', function( proto ){ el.style.height = (aabb.hh * 2) + px; el.style.marginLeft = (-aabb.hw) + px; el.style.marginTop = (-aabb.hh) + px; + el.style[ borderRadius ] = '50%'; }, /** internal @@ -9530,6 +11205,7 @@ Physics.renderer('dom', function( proto ){ createView: function( geometry ){ var el = newEl() + ,chel ,fn = geometry.name + 'Properties' ; @@ -9538,7 +11214,23 @@ Physics.renderer('dom', function( proto ){ el.style.top = '0px'; el.style.left = '0px'; - if (this[ fn ]){ + if ( geometry.name === 'compound' ){ + + for ( var i = 0, l = geometry.children.length, ch; i < l; i++ ){ + ch = geometry.children[ i ]; + chel = newEl(); + chel.className = classpfx + geometry.name + ' ' + classpfx + 'child'; + chel.style.position = 'absolute'; + chel.style.top = '0px'; + chel.style.left = '0px'; + if ( this[ ch.g.name + 'Properties' ] ){ + this[ ch.g.name + 'Properties' ](chel, ch.g); + } + chel.style[cssTransform] = 'translate('+ch.pos._[0]+'px,'+ch.pos._[1]+'px) rotate('+ ch.angle +'rad)'; + el.appendChild( chel ); + } + + } else if ( this[ fn ] ){ this[ fn ](el, geometry); } @@ -9556,8 +11248,8 @@ Physics.renderer('dom', function( proto ){ // extended disconnect: function( world ){ - world.off( 'add:body', this.attach ); - world.off( 'remove:body', this.detach ); + world.off( 'add:body', this.attach, this ); + world.off( 'remove:body', this.detach, this ); }, /** @@ -9613,6 +11305,7 @@ Physics.renderer('dom', function( proto ){ var pos = body.state.pos ,v = body.state.vel + ,os = body.offset ,x ,y ,ang @@ -9620,10 +11313,10 @@ Physics.renderer('dom', function( proto ){ ; // interpolate positions - x = pos.x + v.x * t; - y = pos.y + v.y * t; + x = pos._[0] + v._[0] * t; + y = pos._[1] + v._[1] * t; ang = body.state.angular.pos + body.state.angular.vel * t; - view.style[cssTransform] = 'translate('+x+'px,'+y+'px) rotate('+ ang +'rad)'; + view.style[cssTransform] = 'translate('+x+'px,'+y+'px) rotate('+ ang +'rad) translate('+os._[0]+'px,'+os._[1]+'px)'; } }; }); @@ -9632,6 +11325,9 @@ Physics.renderer('dom', function( proto ){ // --- // inside: src/renderers/pixi-renderer.js +/* + * @requires pixi.js + */ /** * class PixiRenderer < Renderer * @@ -9641,7 +11337,6 @@ Physics.renderer('dom', function( proto ){ * * Additional config options: * - * - debug: Draw debug shapes and bounding boxes. (default: `false`) * - metaEl: HTMLElement to write meta information like FPS and IPF into. (default: autogenerated) * - offset: Offset the shapes by this amount. (default: `{ x: 0, y: 0 }`) * - styles: Styles to use to draw the shapes. (see below) @@ -9659,7 +11354,9 @@ Physics.renderer('dom', function( proto ){ * strokeStyle: '0xE8900C', * lineWidth: 3, * fillStyle: '0xD5DE4C', - * angleIndicator: '0xE8900C' + * angleIndicator: '0xE8900C', + * strokeAlpha: 1, + * fillAlpha: 1 * }, * * 'convex-polygon' : { @@ -9670,6 +11367,38 @@ Physics.renderer('dom', function( proto ){ * } * } * ``` + * + * Styles can also be defined on a per-body basis. Use the "styles" property for a body: + * + * Example: + * + * ```javascript + * Physics.body('circle', { + * // ... + * styles: { + * strokeStyle: '0x542437', + * lineWidth: 1, + * fillStyle: '0x542437', + * angleIndicator: '0xFFFFFF' + * } + * }); + * ``` + * + * You can also define an image to use for a body: + * + * Example: + * + * ```javascript + * Physics.body('circle', { + * // ... + * styles: { + * src: 'path/to/image.jpg', + * width: 40, + * height: 50, + * anchor: { x: 0.5, y: 0.5 } + * } + * }); + * ``` **/ /* global PIXI */ Physics.renderer('pixi', function( parent ){ @@ -9679,84 +11408,172 @@ Physics.renderer('pixi', function( parent ){ return {}; } - var Pi2 = Math.PI * 2; - - var defaults = { - - // draw aabbs of bodies for debugging - debug: false, - // the element to place meta data into - metaEl: null, - offset: { x: 0, y: 0 }, - // Provide some default colours - styles: { - // Defines the default canvas colour - 'color': '0x66FF99', - - 'point' : '0xE8900C', - - 'circle' : { - strokeStyle: '0xE8900C', - lineWidth: 3, - fillStyle: '0xD5DE4C', - angleIndicator: '0xE8900C' - }, - - 'convex-polygon' : { - strokeStyle: '0xE8900C', - lineWidth: 3, - fillStyle: '0xD5DE4C', - angleIndicator: '0xE8900C' - } + var Pi2 = Math.PI * 2 + ,colors = { + white: '0xFFFFFF' + ,violet: '0x542437' + ,blue: '0x53777A' + } + ,fontStyles = { + font: "18px monospace", + fill: "black", + align: "left" } - }; - // deep copy callback to extend deeper into options - var deep = function( a, b ){ + ,defaults = { + + // the element to place meta data into + metaEl: null, + offset: { x: 0, y: 0 }, + // Provide some default colours + styles: { + // Defines the default canvas colour + 'color': false, + + 'point': colors.blue, + + 'circle' : { + strokeStyle: colors.blue, + lineWidth: 1, + fillStyle: colors.blue, + angleIndicator: colors.white, + fillAlpha: 1, + strokeAlpha: 1, + alpha: 1 + }, - if ( Physics.util.isPlainObject( b ) ){ + 'rectangle' : { + strokeStyle: colors.violet, + lineWidth: 1, + fillStyle: colors.violet, + angleIndicator: colors.white, + fillAlpha: 1, + strokeAlpha: 1, + alpha: 1 + }, - return Physics.util.extend({}, a, b, deep ); + 'convex-polygon' : { + strokeStyle: colors.violet, + lineWidth: 1, + fillStyle: colors.violet, + angleIndicator: colors.white, + fillAlpha: 1, + strokeAlpha: 1, + alpha: 1 + } + } } - - return b !== undefined ? b : a; - }; + ; return { // extended init: function( options ){ + var self = this + ,el + ,isTransparent + ; + if (typeof PIXI === 'undefined') { - throw "PIXI obj not present - cannot continue "; + throw "PIXI not present - cannot continue"; } // call parent init parent.init.call(this, options); // further options - this.options = Physics.util.extend({}, defaults, this.options, deep); - this.options.offset = Physics.vector( this.options.offset ); + this.options.defaults( defaults, true ); + this.options.onChange(function(){ + self.options.offset = new Physics.vector( self.options.offset ); + }); + this.options( options, true ); + isTransparent = (!this.options.styles.color || this.options.styles.color === 'transparent'); // Hook in PIXI stage here this.stage = new PIXI.Stage(this.options.styles.color); - this.renderer = new PIXI.autoDetectRenderer(this.options.width, this.options.height); // Create empty meta object for use later this.meta = {}; + el = (this.el && this.el.nodeName === 'CANVAS') ? el : null; // add the renderer view element to the DOM according to its type - if ( this.el.nodeName === 'CANVAS' ){ - this.renderer = new PIXI.autoDetectRenderer(this.options.width, this.options.height, this.el); + this.renderer = new PIXI.autoDetectRenderer(this.options.width, this.options.height, { + view: el, + transparent: isTransparent, + resolution: window.devicePixelRatio || 1 + }); + + if ( !el ){ + this.el = this.el || document.body; + // add to passed in element + this.el.appendChild( this.renderer.view ); + } + + if ( this.options.autoResize ){ + this.resize(); } else { - this.renderer = new PIXI.autoDetectRenderer(this.options.width, this.options.height); + this.resize( this.options.width, this.options.height ); + } + }, - if ( this.el !== null ) { - this.el.appendChild(this.renderer.view); - } else { - document.body.appendChild(this.renderer.view); - } + // extended + resize: function( width, height ){ + + parent.resize.call( this, width, height ); + this.renderer.resize( this.width, this.height ); + }, + + // extended + connect: function( world ){ + + world.on( 'add:body', this.attach, this ); + world.on( 'remove:body', this.detach, this ); + }, + + // extended + disconnect: function( world ){ + + world.off( 'add:body', this.attach, this ); + world.off( 'remove:body', this.detach, this ); + }, + + /** + * PixiRenderer#detach( data ) -> this + * - data (PIXI.Graphics|Object): Graphics object or event data (`data.body`) + * + * Event callback to detach a child from the stage + **/ + detach: function( data ){ + + // interpred data as either dom node or event data + var el = (data instanceof PIXI.Graphics && data) || (data.body && data.body.view); + + if ( el ){ + // remove view from dom + this.stage.removeChild( el ); } + + return this; + }, + + /** + * PixiRenderer#attach( data ) -> this + * - data (PIXI.Graphics|Object): Graphics object or event data (`data.body`) + * + * Event callback to attach a child to the stage + **/ + attach: function( data ){ + + // interpred data as either dom node or event data + var el = (data instanceof PIXI.Graphics && data) || (data.body && data.body.view); + + if ( el ){ + // attach to viewport + this.stage.addChild( el ); + } + + return this; }, /** @@ -9797,6 +11614,7 @@ Physics.renderer('pixi', function( parent ){ drawBody: function( body, view ){ var pos = body.state.pos ,v = body.state.vel + ,os = body.offset ,t = this._interpolateTime || 0 ,x ,y @@ -9804,12 +11622,12 @@ Physics.renderer('pixi', function( parent ){ ; // interpolate positions - x = pos.x + v.x * t; - y = pos.y + v.y * t; + x = pos._[0] + v._[0] * t; + y = pos._[1] + v._[1] * t; ang = body.state.angular.pos + body.state.angular.vel * t; - view.position.x = x; - view.position.y = y; + view.position.set( x, y ); + view.pivot.set( -os._[0], -os._[1] ); view.rotation = ang; }, @@ -9821,31 +11639,86 @@ Physics.renderer('pixi', function( parent ){ }, /** - * PixiRenderer#createCircle( x, y, r, style ) -> PIXI.Graphics + * PixiRenderer#setStyles( graphics, styles ) -> PIXI.Graphics + * - graphics (PIXI.Graphics): The graphics object to set styles on + * - styles (Object): The styles configuration + * + (PIXI.Graphics): A graphic object + * + * Set styles on pixi graphics object + **/ + setStyles: function( graphics, styles ){ + + if ( Physics.util.isObject(styles) ){ + + if ( styles.fillStyle && styles.fillStyle !== 'transparent' ){ + graphics.beginFill( styles.fillStyle ); + graphics.fillAlpha = styles.fillAlpha !== undefined ? styles.fillAlpha : 1; + } else { + graphics.beginFill(); + graphics.fillAlpha = 0; + } + + graphics.lineStyle( styles.lineWidth || 0, styles.strokeStyle, styles.strokeAlpha !== undefined ? styles.strokeAlpha : 1 ); + graphics.alpha = styles.alpha !== undefined ? styles.alpha : 1; + + } else { + + if ( styles && styles !== 'transparent' ){ + graphics.beginFill( styles ); + } else { + graphics.beginFill(); + graphics.fillAlpha = 0; + } + + graphics.lineStyle( 0 ); + } + + return graphics; + }, + + /** + * PixiRenderer#createCircle( x, y, r, styles ) -> PIXI.Graphics * - x (Number): The x coord * - y (Number): The y coord * - r (Number): The circle radius - * - style (Object): The styles configuration + * - styles (Object): The styles configuration * + (PIXI.Graphics): A graphic object representing a circle. * * Create a circle for use in PIXI stage **/ - createCircle: function( x, y, r, style ){ + createCircle: function( x, y, r, styles ){ + + var graphics = new PIXI.Graphics(); + this.setStyles( graphics, styles ); + graphics.drawCircle( x, y, r ); + graphics.endFill(); + return graphics; + }, + + /** + * PixiRenderer#createRect( x, y, r, styles ) -> PIXI.Graphics + * - x (Number): The x coord + * - y (Number): The y coord + * - width (Number): The rectangle width + * - height (Number): The rectangle height + * - styles (Object): The styles configuration + * + (PIXI.Graphics): A graphic object representing a circle. + * + * Create a rectangle for use in PIXI stage + **/ + createRect: function( x, y, width, height, styles ){ var graphics = new PIXI.Graphics(); - graphics.beginFill(style.fillStyle); - graphics.lineStyle(style.lineWidth, style.strokeStyle); - graphics.drawCircle(x, y, r); - // Center the graphics to the circle - graphics.pivot.x = (x / 2) + (r / 2); - graphics.pivot.y = (y / 2) + (r / 2); + this.setStyles( graphics, styles ); + graphics.drawRect( x, y, width, height ); + graphics.endFill(); return graphics; }, /** - * PixiRenderer#createPolygon( verts, style ) -> PIXI.Graphics + * PixiRenderer#createPolygon( verts, styles ) -> PIXI.Graphics * - verts (Array): Array of [[Vectorish]] vertices - * - style (Object): The styles configuration + * - styles (Object): The styles configuration * + (PIXI.Graphics): A graphic object representing a polygon. * * Create a polygon for use in PIXI stage @@ -9863,8 +11736,7 @@ Physics.renderer('pixi', function( parent ){ ,graphics = new PIXI.Graphics() ; - graphics.beginFill(styles.fillStyle); - graphics.lineStyle(styles.lineWidth, styles.strokeStyle); + this.setStyles( graphics, styles ); graphics.moveTo(x, y); @@ -9885,10 +11757,10 @@ Physics.renderer('pixi', function( parent ){ }, /** - * PixiRenderer#createLine( from, to, style ) -> PIXI.Graphics + * PixiRenderer#createLine( from, to, styles ) -> PIXI.Graphics * - from (Vectorish): Starting point * - to (Vectorish): Ending point - * - style (Object): The styles configuration + * - styles (Object): The styles configuration * + (PIXI.Graphics): A graphic object representing a polygon. * * Create a line for use in PIXI stage @@ -9900,8 +11772,7 @@ Physics.renderer('pixi', function( parent ){ ; var graphics = new PIXI.Graphics(); - graphics.beginFill(styles.fillStyle); - graphics.lineStyle(styles.lineWidth, styles.strokeStyle); + this.setStyles( graphics, styles ); graphics.moveTo(x, y); @@ -9915,58 +11786,82 @@ Physics.renderer('pixi', function( parent ){ }, // extended - createView: function( geometry ){ + createView: function( geometry, styles, parent ){ var view = null ,aabb = geometry.aabb() ,hw = aabb.hw + Math.abs(aabb.x) ,hh = aabb.hh + Math.abs(aabb.y) - ,x = hw + 1 - ,y = hh + 1 ,name = geometry.name ; - var styles = styles || this.options.styles[ name ]; + parent = parent || this.stage; + styles = styles || this.options.styles[ name ] || this.options.styles.circle || {}; - x += styles.lineWidth | 0; - y += styles.lineWidth | 0; + // must want an image + if ( styles.src ){ + view = PIXI.Sprite.fromImage( styles.src ); + view.anchor.set( 0.5, 0.5 ); + if ( styles.anchor ) { + view.anchor.x = styles.anchor.x; + view.anchor.y = styles.anchor.y; + } + if ( styles.width ){ + view.width = styles.width; + } + if ( styles.height ){ + view.height = styles.height; + } + parent.addChild(view); + return view; + } if (name === 'circle'){ - view = this.createCircle(x, y, geometry.radius, styles); + view = this.createCircle(0, 0, geometry.radius, styles); } else if (name === 'convex-polygon'){ view = this.createPolygon(geometry.vertices, styles); - } - if (styles.angleIndicator){ + } else if (name === 'rectangle'){ + + view = this.createRect(-geometry.width/2, -geometry.height/2, geometry.width, geometry.height, styles); + } else if (name === 'compound'){ - view.beginFill(styles.angleIndicator); - view.moveTo((x / 2), (5 + styles.lineWidth)); - view.lineTo((x / 2) + (geometry.radius / 2), geometry.radius); - // Center the graphics to the circle - view.endFill(); + view = new PIXI.Graphics(); - } - if (view) { - this.stage.addChild(view); - return view; + for ( var i = 0, l = geometry.children.length, ch, chview; i < l; i++ ){ + ch = geometry.children[ i ]; + chview = this.createView( ch.g, styles, view ); + chview.position.set( ch.pos.x, ch.pos.y ); + chview.rotation = ch.angle; + } } else { - throw "Invalid view name passed."; + + // assume it's a point + view = this.createCircle(0, 0, 1, styles); + } + + if ( name !== 'compound' && styles.angleIndicator && styles.angleIndicator !== 'transparent' ){ + + view.lineStyle( styles.lineWidth, styles.angleIndicator ); + view.moveTo( 0, 0 ); + view.lineTo( hw, 0 ); } + if ( name !== 'compound' ){ + view.cacheAsBitmap = true; + } + + parent.addChild(view); + return view; }, // extended drawMeta: function( meta ){ if (!this.meta.loaded){ - // define the font style here - var fontStyles = { - font: "18px Snippet", - fill: "white", - align: "left" - }; + // define the font styles here this.meta.fps = new PIXI.Text('FPS: ' + meta.fps.toFixed(2), fontStyles); this.meta.fps.position.x = 15; this.meta.fps.position.y = 5; diff --git a/dist/physicsjs-full.min.js b/dist/physicsjs-full.min.js new file mode 100644 index 00000000..ae662f39 --- /dev/null +++ b/dist/physicsjs-full.min.js @@ -0,0 +1,11 @@ +/** + * PhysicsJS v0.7.0 - 2014-12-08 + * A modular, extendable, and easy-to-use physics engine for javascript + * http://wellcaffeinated.net/PhysicsJS + * + * Copyright (c) 2014 Jasper Palfree + * Licensed MIT + */ +!function(a,b){"object"==typeof exports?module.exports=b.call(a):"function"==typeof define&&define.amd?define(function(){return b.call(a)}):a.Physics=b.call(a)}("undefined"!=typeof window?window:this,function(){"use strict";var a=this,b=a.document,c=function f(){return f.world.apply(f,arguments)};c.util={},function(){c.aabb=function(a,b,c,d){var e={x:0,y:0,hw:0,hh:0};return void 0===a?e:(a&&void 0!==a.x&&(c=b.x,d=b.y,b=a.y,a=a.x),void 0===d&&void 0!==a&&void 0!==b?(e.hw=.5*a,e.hh=.5*b,c&&void 0!==c.x&&(e.x=c.x,e.y=c.y),e):(e.hw=.5*Math.abs(c-a),e.hh=.5*Math.abs(d-b),e.x=.5*(c+a),e.y=.5*(d+b),e))},c.aabb.contains=function(a,b){return b.x>a.x-a.hw&&b.xa.y-a.hh&&b.y=d&&f>=e||f>=c&&e>=f?(c=a.y-a.hh,d=b.y-b.hh,e=a.y+a.hh,f=b.y+b.hh,e>=d&&f>=e||f>=c&&e>=f):!1}}(),function(){var a=1e-4,b=100,d=function(a,b,c){var d=b.normSq()-b.dot(a),e=b.dot(a)-a.normSq();return 0>d?c.clone(b).negate():e>0?c.clone(a).negate():(c.clone(b).vsub(a),c.perp(a.cross(c)>0))},e=function(a){var b,d,e=a.length,f=a[e-2],g=a[e-3],h=c.scratchpad(),i=h.vector().clone(f.pt),j=h.vector().clone(g.pt).vsub(i);return j.equals(c.vector.zero)?h.done({a:f.a,b:f.b}):(b=-j.dot(i)/j.normSq(),d=1-b,h.done(0>=d?{a:g.a,b:g.b}:0>=b?{a:f.a,b:f.b}:{a:i.clone(f.a).mult(d).vadd(j.clone(g.a).mult(b)).values(),b:i.clone(f.b).mult(d).vadd(j.clone(g.b).mult(b)).values()}))},f=function(f,g,h,i){var j,k,l,m,n=!1,o=!1,p=!1,q=[],r=1,s=c.scratchpad(),t=s.vector().clone(g||c.vector.axis[0]),u=s.vector(),v=s.vector(),w=s.vector(),x=s.vector(),y=0;for(m=f(t),r=q.push(m),u.clone(m.pt),t.negate();++y;){if(u.swap(v),m=f(t),r=q.push(m),u.clone(m.pt),i&&i(q),u.equals(c.vector.zero)){n=!0;break}if(!o&&u.dot(t)<=0){if(h)break;o=!0}if(2===r)t=d(u,v,t);else if(o){if(t.normalize(),m=v.dot(t),Math.abs(m-u.dot(t))0,l^u.cross(j)>0)q.shift(),j.perp(!l),t.swap(j);else{if(!(l^k.cross(u)>0)){n=!0;break}q.splice(1,1),k.perp(l),t.swap(j)}if(y>b)return s.done(),{simplex:q,iterations:y,distance:0,maxIterationsReached:!0}}return s.done(),m={overlap:n,simplex:q,iterations:y},p!==!1&&(m.distance=p,m.closest=e(q)),m};c.gjk=f}(),function(){c.statistics={pushRunningAvg:function(a,b,c,d){var e=a-c;return c+=e/b,d+=e*(a-c),[c,d]},pushRunningVectorAvg:function(a,b,c,d){var e=1/b,f=a.get(0)-c.get(0),g=a.get(1)-c.get(1);c.add(f*e,g*e),d&&(f*=a.get(0)-c.get(0),g*=a.get(1)-c.get(1),d.add(f,g))}}}(),function(){var a=function b(a,d,e){return this instanceof b?(this.v=new c.vector,this.o=new c.vector,a instanceof b?void this.clone(a):(a&&this.setTranslation(a),void this.setRotation(d||0,e))):new b(a,d)};a.prototype.setTranslation=function(a){return this.v.clone(a),this},a.prototype.setRotation=function(a,b){return this.cosA=Math.cos(a),this.sinA=Math.sin(a),b?this.o.clone(b):this.o.zero(),this},a.prototype.clone=function(b){return b?(this.setTranslation(b.v),this.cosA=b.cosA,this.sinA=b.sinA,this.o.clone(b.o),this):new a(this)},c.transform=a}(),function(a){var b=Math.sqrt,d=Math.min,e=Math.max,f=(Math.acos,Math.atan2),g=2*Math.PI,h=!!a.Float64Array,i=function j(a,b){return this instanceof j?(this._=h?new Float64Array(5):[],void(a&&(void 0!==a.x||a._&&a._.length)?this.clone(a):(this.recalc=!0,this.set(a,b)))):new j(a,b)};Object.defineProperties(i.prototype,{x:{get:function(){return+this._[0]},set:function(a){a=+a||0,this.recalc=a===this._[0],this._[0]=a}},y:{get:function(){return+this._[1]},set:function(a){a=+a||0,this.recalc=a===this._[1],this._[1]=a}}}),i.prototype.set=function(a,b){return this.recalc=!0,this._[0]=+a||0,this._[1]=+b||0,this},i.prototype.get=function(a){return this._[a]},i.prototype.vadd=function(a){return this.recalc=!0,this._[0]+=a._[0],this._[1]+=a._[1],this},i.prototype.vsub=function(a){return this.recalc=!0,this._[0]-=a._[0],this._[1]-=a._[1],this},i.prototype.add=function(a,b){return this.recalc=!0,this._[0]+=+a||0,this._[1]+=+b||0,this},i.prototype.sub=function(a,b){return this.recalc=!0,this._[0]-=a,this._[1]-=void 0===b?0:b,this},i.prototype.mult=function(a){return this.recalc||(this._[4]*=a*a,this._[3]*=a),this._[0]*=a,this._[1]*=a,this},i.prototype.dot=function(a){return this._[0]*a._[0]+this._[1]*a._[1]},i.prototype.cross=function(a){return-this._[0]*a._[1]+this._[1]*a._[0]},i.prototype.proj=function(a){return this.dot(a)/a.norm()},i.prototype.vproj=function(a){var b=this.dot(a)/a.normSq();return this.clone(a).mult(b)},i.prototype.angle=function(a){var b;if(this.equals(i.zero))return a?a.angle():0/0;for(b=a&&!a.equals(i.zero)?f(this._[1]*a._[0]-this._[0]*a._[1],this._[0]*a._[0]+this._[1]*a._[1]):f(this._[1],this._[0]);b>Math.PI;)b-=g;for(;b<-Math.PI;)b+=g;return b},i.prototype.angle2=function(a,b){for(var c=a._[0]-this._[0],d=a._[1]-this._[1],e=b._[0]-this._[0],h=b._[1]-this._[1],i=f(d*e-c*h,c*e+d*h);i>Math.PI;)i-=g;for(;i<-Math.PI;)i+=g;return i},i.prototype.norm=function(){return this.recalc&&(this.recalc=!1,this._[4]=this._[0]*this._[0]+this._[1]*this._[1],this._[3]=b(this._[4])),this._[3]},i.prototype.normSq=function(){return this.recalc&&(this.recalc=!1,this._[4]=this._[0]*this._[0]+this._[1]*this._[1],this._[3]=b(this._[4])),this._[4]},i.prototype.dist=function(a){var c,d;return b((c=a._[0]-this._[0])*c+(d=a._[1]-this._[1])*d)},i.prototype.distSq=function(a){var b,c;return(b=a._[0]-this._[0])*b+(c=a._[1]-this._[1])*c},i.prototype.perp=function(a){var b=this._[0];return a?(this._[0]=this._[1],this._[1]=-b):(this._[0]=-this._[1],this._[1]=b),this},i.prototype.normalize=function(){var a=this.norm();return 0===a?this:(a=1/a,this._[0]*=a,this._[1]*=a,this._[3]=1,this._[4]=1,this)},i.prototype.transform=function(a){var b=a.sinA,c=a.cosA,d=a.o._[0],e=a.o._[1];return this._[0]-=d,this._[1]-=e,this.set(this._[0]*c-this._[1]*b+d+a.v._[0],this._[0]*b+this._[1]*c+e+a.v._[1])},i.prototype.transformInv=function(a){var b=a.sinA,c=a.cosA,d=a.o._[0],e=a.o._[1];return this._[0]-=d+a.v._[0],this._[1]-=e+a.v._[1],this.set(this._[0]*c+this._[1]*b+d,-this._[0]*b+this._[1]*c+e)},i.prototype.rotate=function(a,b){var c,d,e=0,f=0;return"number"==typeof a?(c=Math.sin(a),d=Math.cos(a),b&&(e=b.x,f=b.y)):(c=a.sinA,d=a.cosA,e=a.o._[0],f=a.o._[1]),this._[0]-=e,this._[1]-=f,this.set(this._[0]*d-this._[1]*c+e,this._[0]*c+this._[1]*d+f)},i.prototype.rotateInv=function(a){return this.set((this._[0]-a.o._[0])*a.cosA+(this._[1]-a.o._[1])*a.sinA+a.o._[0],-(this._[0]-a.o._[0])*a.sinA+(this._[1]-a.o._[1])*a.cosA+a.o._[1])},i.prototype.translate=function(a){return this.vadd(a.v)},i.prototype.translateInv=function(a){return this.vsub(a.v)},i.prototype.clone=function(a){return a?a._?(this.recalc=a.recalc,a.recalc||(this._[3]=a._[3],this._[4]=a._[4]),this._[0]=a._[0],this._[1]=a._[1],this):this.set(a.x,a.y):new i(this)},i.prototype.swap=function(a){var b=this._;return this._=a._,a._=b,b=this.recalc,this.recalc=a.recalc,a.recalc=b,this},i.prototype.values=function(){return{x:this._[0],y:this._[1]}},i.prototype.zero=function(){return this._[3]=0,this._[4]=0,this._[0]=0,this._[1]=0,this},i.prototype.negate=function(a){return void 0!==a?(this._[a]=-this._[a],this):(this._[0]=-this._[0],this._[1]=-this._[1],this)},i.prototype.clamp=function(a,b){return this._[0]=d(e(this._[0],a.x),b.x),this._[1]=d(e(this._[1],a.y),b.y),this.recalc=!0,this},i.prototype.toString=function(){return"("+this._[0]+", "+this._[1]+")"},i.prototype.equals=function(a){return this._[0]===a._[0]&&this._[1]===a._[1]&&this._[2]===a._[2]},i.axis=[new i(1,0),new i(0,1)],i.zero=new i(0,0),c.vector=i}(this),function(a){var b=a.Physics;c.noConflict=function(){return a.Physics===c&&(a.Physics=b),c}}(this);var d=c.util.decorator=function(a,b){var d={},e={},f=function(a,b){var d,e;for(e in b)d=Object.getOwnPropertyDescriptor(b,e),d.get||d.set?Object.defineProperty(a,e,d):c.util.isFunction(d.value)&&(a[e]=d.value);return a},g=Object.getPrototypeOf;"function"!=typeof g&&(g="object"==typeof"test".__proto__?function(a){return a.__proto__}:function(a){return a.constructor.prototype});var h=Object.create;"function"!=typeof h&&(h=function(a){function b(){}return b.prototype=a,new b});var i=function(b,d){return"object"==typeof b?(e=f(e,b),void(e.type=a)):void("type"!==b&&c.util.isFunction(d)&&(e[b]=d))};i(b);var j=function(b,c,i,j){var k,l=e;if("string"!=typeof c)j=i,i=c;else{if(l=d[c],!l)throw'Error: "'+c+'" '+a+" not defined";l=l.prototype}if("function"==typeof i)k=d[b],k?k.prototype=f(k.prototype,i(g(k.prototype))):(k=d[b]=function(a){this.init&&this.init(a)},k.prototype=h(l),k.prototype=f(k.prototype,i(l,k.prototype))),k.prototype.type=a,k.prototype.name=b;else if(j=i||{},k=d[b],!k)throw'Error: "'+b+'" '+a+" not defined";return j?new k(j):void 0};return j.mixin=i,j};c.util.indexOf=function(a,b){for(var c=0,d=a.length;d>c;){if(d--,a[c]===b)return c;if(a[d]===b)return d;c++}return-1},c.util.clearArray=function(a){for(var b=a.length;b--;)a.pop();return a},c.util.throttle=function(a,b,c){var d,e,f=!1,g=function(){clearTimeout(d),f?(f=!1,d=setTimeout(g,b),a.apply(c,e)):d=!1};return c=c||null,function(){f=!0,e=arguments,d||g()}};var e=function(a,b){return c.util.isPlainObject(b)?c.util.extend({},a,b,e):void 0!==b?b:a};return c.util.options=function(a,b){var d,f={},g=[];return d=function(a,d){c.util.extend(b,a,d?e:null);for(var f=0,h=g.length;h>f;++f)g[f](b);return b},d.defaults=function(a,d){return c.util.extend(f,a,d?e:null),c.util.defaults(b,f,d?e:null),f},d.onChange=function(a){g.push(a)},b=b||d,d.defaults(a),d},c.util.pairHash=function(a,b){return a=0|a,b=0|b,(0|a)===(0|b)?-1:0|((0|a)>(0|b)?a<<16|65535&b:b<<16|65535&a)},c.util.bind=Function.prototype.bind?function(a,b,c){return c=Array.prototype.slice.call(arguments,1),Function.prototype.bind.apply(a,c)}:function(a,b,c){return c=Array.prototype.slice.call(arguments,2),function(){return a.apply(b,c.concat(Array.prototype.slice.call(arguments)))}},c.util.find=function(a,b){var c,d,e=a.length;for(c=0;e>c;c++)if(d=a[c],b(d,c,a))return d},c.util.filter=function(a,b){var c,d,e=a.length,f=[];for(c=0;e>c;c++)d=a[c],b(d,c,a)&&f.push(d);return f},function(){function a(a){c.util.clearArray(a),v.length-1?0:-1:a?0:-1}function g(a){var b=this.cache,c=typeof a;if("boolean"===c||null==a)b[a]=!0;else{"number"!==c&&"string"!==c&&(c="object");var d="number"===c?a:y+a,e=b[c]||(b[c]={});"object"===c?(e[d]||(e[d]=[])).push(a):e[d]=!0}}function h(a){var b=-1,c=a.length,e=a[0],f=a[c/2|0],h=a[c-1];if(e&&"object"==typeof e&&f&&"object"==typeof f&&h&&"object"==typeof h)return!1;var i=d();i["false"]=i["null"]=i["true"]=i.undefined=!1;var j=d();for(j.array=a,j.cache=i,j.push=g;++b=u&&k===c.util.indexOf,o=i||n?e():m;if(n){var p=h(o);k=f,o=p}for(;++jb;b++)d=a[b],e=i(0,++f),h[f]=h[e],h[e]=d;return h},c.util.isObject=function(a){return!(!a||!n[typeof a])},c.util.isFunction=j,c.util.isArray=Array.isArray||function(a){return a&&"object"==typeof a&&"number"==typeof a.length&&s.call(a)===p||!1};var C=RegExp("^"+String(s).replace(/[.*+?^${}()|[\]\\]/g,"\\$&").replace(/toString| for [^\]]+/g,".*?")+"$");c.util.isPlainObject=Object.getPrototypeOf?function(a){if(!a||s.call(a)!==q)return!1;var b=a.valueOf,c=k(b)&&(c=Object.getPrototypeOf(b))&&Object.getPrototypeOf(c);return c?a===c||Object.getPrototypeOf(a)===c:l(a)}:l,c.util.uniq=function(a,b,c){return"boolean"!=typeof b&&null!=b&&(c=b,b=!1),m(a,b,c)};var D=function(a,b,c){var d,e=a,f=e;if(!e)return f;var g,h=arguments,i=0,j="number"==typeof c?2:h.length;for(j>2&&"function"==typeof h[j-1]&&(g=h[--j]);++id;){var f=d+e>>>1;c(a[f])=b.maxScratches)throw f},a.prototype={done:function(a){this._active=!1;for(var b=0;j>b;++b)this[b]=0;return h.push(this),a}},b=function k(b){if(b)return k.fn(b);var c=h.pop()||new a;return c._active=!0,c},b.maxScratches=100,b.maxIndex=20,b.fn=function(b){for(var c=[],d=0,e=b.length;e>d;d++)c.push(d);c="a"+c.join(",a");var f=new Function("fn, scratches, Scratch","return function("+c+"){ var scratch = scratches.pop() || new Scratch( scratches );scratch._active = true;return scratch.done( fn(scratch, "+c+") );};");return f(b,h,a)},b.register=function(c,f,h){var i=a.prototype,k=j++,l="_"+c+"Stack",m=h&&h.useFactory;if(c in i)throw g;a.prototype[c]=function(){var a=this[l]||(this[l]=[]),c=0|this[k];if(this[k]=c+1,!this._active)throw d;if(c>=b.maxIndex)throw e;return a[c]||(a[c]=m?f():new f)}},b.register("vector",c.vector),b.register("transform",c.transform),b}(),function(){function a(a){return a._priority_}var b=1;c.scratchpad.register("event",function(){return{}},{useFactory:!0});var d=function e(){return this instanceof e?void 0:new e};d.prototype={on:function(d,e,f,g){var h,i,j;if(this._topics=this._topics||(this._topics={}),c.util.isObject(d)){for(var k in d)this.on(k,d[k],e,f);return this}return h=this._topics[d]||(this._topics[d]=[]),i=e,c.util.isObject(f)?(e=c.util.bind(e,f),e._bindfn_=i,e._one_=i._one_,e._scope_=f):void 0===g&&(g=f),e._priority_=void 0===g?b:g,j=c.util.sortedIndex(h,e,a),h.splice(j,0,e),this},off:function(a,b,d){var e,f;if(!this._topics)return this;if(a===!0)return this._topics={},this;if(c.util.isObject(a)){for(var g in a)this.off(g,a[g]);return this}if(e=this._topics[a],!e)return this;if(b===!0)return this._topics[a]=[],this;for(var h=0,i=e.length;i>h;h++)if(f=e[h],!(f._bindfn_!==b&&f!==b||d&&f._scope_!==d)){e.splice(h,1);break}return this},emit:function(a,b){if(!this._topics)return this;var d,e,f=this._topics[a],g=f&&f.length,h=c.scratchpad();if(!g)return h.done(this);for(e=h.event(),e.topic=a,e.handler=d;g--;)d=f[g],d(b,e),d._one_&&f.splice(g,1);return h.done(this)},one:function(a,b,d){if(c.util.isObject(a)){for(var e in a)this.one(e,a[e],b,d);return this}return b._one_=!0,this.on(a,b,d),this}},c.util.pubsub=d}(),function(a){function b(){return l&&l.now?l.now()+l.timing.navigationStart:Date.now()}function d(){var c;a.requestAnimationFrame(d),j&&(c=b(),c&&k.emit("tick",c))}function e(){return j=!0,this}function f(){return j=!1,this}function g(a){return k.on("tick",a),this}function h(a){return k.off("tick",a),this}function i(){return!!j}var j=!0,k=c.util.pubsub(),l=a.performance;a.requestAnimationFrame?d():j=!1,c.util.ticker={now:b,start:e,stop:f,on:g,off:h,isActive:i}}(this),function(){var a=function(){return!0},b=c.util.indexOf,d=function(a,b){return function(c){return a(c[b])}},e=function(a,d){return function(e){e=d?e[d]:e;var f,g=0;if(c.util.isArray(e)){if(c.util.isArray(a)){if(f=e.length,f!==a.length)return!1;for(;f>g;){if(f--,-1===b(a,e[g])||-1===b(a,e[f]))return!1;g++}return!0}return b(e,a)>-1}return e===a}},f=function(a,b){var c=e(a,b);return function(a){return!c(a)}},g=function(a,d){return function(e){e=d?e[d]:e;var f,g=0;if(c.util.isArray(e)){for(f=e.length;f>g;){if(f--,b(a,e[g])>-1||b(a,e[f])>-1)return!0;g++}return!1}return b(a,e)>-1}},h=function(a,b){var c=g(a,b);return function(a){return!c(a)}},i=function(a){return a=new c.vector(a),function(b){var d=b.aabb();return c.aabb.contains(d,a)}},j=function(a){return a.next?function(b){for(var c=a;c;){if(!c(b))return!1;c=c.next}return!0}:a},k=function(a){return a.next?function(b){for(var c=a;c;){if(c(b))return!0;c=c.next}return!1}:a},l={$eq:e,$ne:f,$in:g,$nin:h,$at:i},m=function n(b,f){var g,h,i,m,o,p;if(f){if("$or"===f||"$and"===f){for(g=0,h=b.length;h>g;++g)p=n(b[g]),o=o?o.next=p:m=p;return"$or"===f?k(m):j(m)}if(g=l[f])return g(b);throw"Unknown query operation: "+f}for(g in b)i=b[g],p="$"===g[0]?n(i,g):c.util.isPlainObject(i)?d(n(i),g):e(i,g),o=o?o.next=p:m=p;return j(m||a)};c.query=m}(this),function(){var a={priority:0};c.behavior=d("behavior",{init:function(b){this.options=c.util.options(a),this.options(b)},applyTo:function(a){return this._targets=a===!0?null:c.util.uniq(a),this},getTargets:function(){return this._targets||(this._world?this._world._bodies:[])},setWorld:function(a){return this.disconnect&&this._world&&this.disconnect(this._world),this._world=a,this.connect&&a&&this.connect(a),this},connect:function(a){this.behave&&a.on("integrate:positions",this.behave,this,this.options.priority)},disconnect:function(a){this.behave&&a.off("integrate:positions",this.behave,this)},behave:null})}(),function(){{var a={hidden:!1,treatment:"dynamic",mass:1,restitution:1,cof:.8,view:null},b=1;2*Math.PI}c.body=d("body",{init:function(d){var e=this,f=c.vector;if(this.options=c.util.options(a,this),this.options.onChange(function(a){e.offset=new f(a.offset)}),this.options(d),this.state={pos:new f(this.x,this.y),vel:new f(this.vx,this.vy),acc:new f,angular:{pos:this.angle||0,vel:this.angularVelocity||0,acc:0},old:{pos:new f,vel:new f,acc:new f,angular:{pos:0,vel:0,acc:0}}},this._sleepAngPosMean=0,this._sleepAngPosVariance=0,this._sleepPosMean=new f,this._sleepPosVariance=new f,this._sleepMeanK=0,delete this.x,delete this.y,delete this.vx,delete this.vy,delete this.angle,delete this.angularVelocity,0===this.mass)throw"Error: Bodies must have non-zero mass";this.uid=b++,this.geometry=c.geometry("point")},sleep:function(a){return a===!0?this.asleep=!0:a===!1?(this.asleep=!1,this._sleepMeanK=0,this._sleepAngPosMean=0,this._sleepAngPosVariance=0,this._sleepPosMean.zero(),this._sleepPosVariance.zero(),this.sleepIdleTime=0):a&&!this.asleep&&this.sleepCheck(a),this.asleep},sleepCheck:function(a){var b=this._world&&this._world.options;if(!(this.sleepDisabled||b&&b.sleepDisabled)){{var d,e,f,g,h,i,j=c.scratchpad();j.vector(),j.vector()}if(a=a||0,g=this.geometry.aabb(),f=Math.max(g.hw,g.hh),this.asleep&&(e=this.state.vel.norm()+Math.abs(f*this.state.angular.vel),d=this.sleepSpeedLimit||b&&b.sleepSpeedLimit||0,e>=d))return this.sleep(!1),j.done();this._sleepMeanK++,h=this._sleepMeanK>1?1/(this._sleepMeanK-1):0,c.statistics.pushRunningVectorAvg(this.state.pos,this._sleepMeanK,this._sleepPosMean,this._sleepPosVariance),i=c.statistics.pushRunningAvg(Math.sin(this.state.angular.pos),this._sleepMeanK,this._sleepAngPosMean,this._sleepAngPosVariance),this._sleepAngPosMean=i[0],this._sleepAngPosVariance=i[1],e=this._sleepPosVariance.norm()+Math.abs(f*Math.asin(i[1])),e*=h,d=this.sleepVarianceLimit||b&&b.sleepVarianceLimit||0,d>=e?(d=this.sleepTimeLimit||b&&b.sleepTimeLimit||0,this.sleepIdleTime=(this.sleepIdleTime||0)+a,this.sleepIdleTime>d&&(this.asleep=!0)):this.sleep(!1),j.done()}},setWorld:function(a){return this.disconnect&&this._world&&this.disconnect(this._world),this._world=a,this.connect&&a&&this.connect(a),this},accelerate:function(a){return"dynamic"===this.treatment&&this.state.acc.vadd(a),this},applyForce:function(a,b){if("dynamic"!==this.treatment)return this;var d,e=c.scratchpad(),f=e.vector();return b&&this.moi&&(d=this.state,f.clone(b),this.state.angular.acc-=f.cross(a)/this.moi),this.accelerate(f.clone(a).mult(1/this.mass)),e.done(),this},getGlobalOffset:function(a){return a=a||new c.vector,a.clone(this.offset).rotate(this.state.angular.pos),a},aabb:function(){var a=this.state.angular.pos,b=c.scratchpad(),d=b.vector(),e=this.geometry.aabb(a);return this.getGlobalOffset(d),e.x+=this.state.pos._[0]+d._[0],e.y+=this.state.pos._[1]+d._[1],b.done(e)},toBodyCoords:function(a){return a.vsub(this.state.pos).rotate(-this.state.angular.pos)},toWorldCoords:function(a){return a.rotate(this.state.angular.pos).vadd(this.state.pos)},recalc:function(){return this}}),c.body.getCOM=function(a,b){var d,e,f,g=a&&a.length,h=0;if(b=b||new c.vector,!g)return b.zero();if(1===g)return b.clone(a[0].state.pos);for(b.zero(),f=0;g>f;f++)d=a[f],e=d.state.pos,b.add(e._[0]*d.mass,e._[1]*d.mass),h+=d.mass;return b.mult(1/h),b}}(),function(){c.geometry=d("geometry",{init:function(a){this.options=c.util.options(),this.options(a),this._aabb=new c.aabb},aabb:function(){return c.aabb.clone(this._aabb)},getFarthestHullPoint:function(a,b){return b=b||new c.vector,b.set(0,0)},getFarthestCorePoint:function(a,b){return b=b||new c.vector,b.set(0,0)}})}(),c.geometry.regularPolygonVertices=function(a,b){var c,d=[],e=2*Math.PI/a,f=0;for(c=0;a>c;c++)d.push({x:b*Math.cos(f),y:b*Math.sin(f)}),f+=e;return d},c.geometry.isPolygonConvex=function(a){var b=c.scratchpad(),d=b.vector(),e=b.vector(),f=b.vector(),g=!0,h=!1,i=a.length;if(!a||!i)return!1;if(3>i)return b.done(),g;d.clone(a[0]).vsub(f.clone(a[i-1]));for(var j=1;i>=j;++j){if(e.clone(a[j%i]).vsub(f.clone(a[(j-1)%i])),h===!1)h=d.cross(e);else if(h>0^d.cross(e)>0){g=!1;break}e.swap(d)}return b.done(),g},c.geometry.getPolygonMOI=function(a){var b,d=c.scratchpad(),e=d.vector(),f=d.vector(),g=0,h=0,i=a.length;if(2>i)return d.done(),0;if(2===i)return b=f.clone(a[1]).distSq(e.clone(a[0])),d.done(),b/12;e.clone(a[0]);for(var j=1;i>j;++j)f.clone(a[j]),b=Math.abs(f.cross(e)),g+=b*(f.normSq()+f.dot(e)+e.normSq()),h+=b,e.swap(f);return d.done(),g/(6*h)},c.geometry.isPointInPolygon=function(a,b){var d=c.scratchpad(),e=d.vector().clone(a),f=d.vector(),g=d.vector(),h=0,i=b.length;if(2>i)return h=e.equals(f.clone(b[0])),d.done(),h;if(2===i)return h=e.angle(f.clone(b[0])),h+=e.angle(f.clone(b[1])),d.done(),Math.abs(h)===Math.PI;f.clone(b[0]).vsub(e);for(var j=1;i>=j;++j)g.clone(b[j%i]).vsub(e),h+=g.angle(f),f.swap(g);return d.done(),Math.abs(h)>1e-6},c.geometry.getPolygonArea=function(a){var b=c.scratchpad(),d=b.vector(),e=b.vector(),f=0,g=a.length;if(3>g)return b.done(),0;d.clone(a[g-1]);for(var h=0;g>h;++h)e.clone(a[h]),f+=d.cross(e),d.swap(e);return b.done(),f/2},c.geometry.getPolygonCentroid=function(a){var b,d=c.scratchpad(),e=d.vector(),f=d.vector(),g=new c.vector,h=a.length;if(2>h)return d.done(),new c.vector(a[0]);if(2===h)return d.done(),new c.vector((a[1].x+a[0].x)/2,(a[1].y+a[0].y)/2);e.clone(a[h-1]);for(var i=0;h>i;++i)f.clone(a[i]),b=e.cross(f),e.vadd(f).mult(b),g.vadd(e),e.swap(f);return b=1/(6*c.geometry.getPolygonArea(a)),d.done(),g.mult(b)},c.geometry.nearestPointOnLine=function(a,b,d){var e,f,g=c.scratchpad(),h=g.vector().clone(a),i=g.vector().clone(b).vsub(h),j=g.vector().clone(d).vsub(h).vsub(i);return j.equals(c.vector.zero)?(g.done(),new c.vector(b)):(e=-j.dot(i)/j.normSq(),f=1-e,0>=f?(g.done(),new c.vector(d)):0>=e?(g.done(),new c.vector(b)):(h=new c.vector(d).mult(e).vadd(i.clone(b).mult(f)),g.done(),h))},function(){var a={drag:0};c.integrator=d("integrator",{init:function(b){this.options=c.util.options(a),this.options(b)},setWorld:function(a){return this.disconnect&&this._world&&this.disconnect(this._world),this._world=a,this.connect&&a&&this.connect(a),this},integrate:function(a,b){var c=this._world;return this.integrateVelocities(a,b),c&&c.emit("integrate:velocities",{bodies:a,dt:b}),this.integratePositions(a,b),c&&c.emit("integrate:positions",{bodies:a,dt:b}),this},connect:null,disconnect:null,integrateVelocities:function(){throw"The integrator.integrateVelocities() method must be overriden"},integratePositions:function(){throw"The integrator.integratePositions() method must be overriden"}})}(),function(){var e={meta:!1,metaRefresh:200,width:600,height:600,autoResize:!0};c.renderer=d("renderer",{init:function(d){var f=this,g="string"==typeof d.el?b.getElementById(d.el):d.el;this.options=c.util.options(e),this.options(d),this.el=g?g:b.body,this.container=g&&g.parentNode?g.parentNode:b.body,this.drawMeta=c.util.throttle(c.util.bind(this.drawMeta,this),this.options.metaRefresh),a.addEventListener("resize",c.util.throttle(function(){f.options.autoResize&&f.resize()}),100)},resize:function(a,b){void 0===a&&void 0===b&&(a=this.container.offsetWidth,b=this.container.offsetHeight),this.width=a||0,this.height=b||0},setWorld:function(a){return this.disconnect&&this._world&&this.disconnect(this._world),this._world=a,this.connect&&a&&this.connect(a),this},render:function(a,b){var c,d;this.beforeRender&&this.beforeRender(),this._world.emit("beforeRender",{renderer:this,bodies:a,meta:b}),this.options.meta&&this.drawMeta(b),this._interpolateTime=b.interpolateTime;for(var e=0,f=a.length;f>e;++e)c=a[e],d=c.view||(c.view=this.createView(c.geometry,c.styles)),c.hidden||this.drawBody(c,d);return this},createView:function(){throw"You must override the renderer.createView() method."},drawMeta:function(){throw"You must override the renderer.drawMeta() method."},drawBody:function(){throw"You must override the renderer.drawBody() method."}})}(),function(){var a=function e(a,b,c){for(var d,f,g=function(){return e(a,b,c)};d=a.shift();)if(f=d.apply(b,c),f&&f.then)return f.then(g)},b={timestep:6,maxIPF:4,webworker:!1,integrator:"verlet",sleepDisabled:!1,sleepSpeedLimit:.05,sleepVarianceLimit:.02,sleepTimeLimit:500},d=function f(a,b){return this instanceof f?void this.init(a,b):new f(a,b)};d.prototype=c.util.extend({},c.util.pubsub.prototype,{init:function(d,e){var f=this;(c.util.isFunction(d)||c.util.isArray(d))&&(e=d,d={}),this._meta={fps:0,ipf:0},this._bodies=[],this._behaviors=[],this._integrator=null,this._renderer=null,this._paused=!1,this._warp=1,this._time=0,this.options=c.util.options(b),this.options.onChange(function(a){f.timestep(a.timestep)}),this.options(d),this.add(c.integrator(this.options.integrator)),c.util.isFunction(e)?a([e],this,[this,c]):c.util.isArray(e)&&a(e,this,[this,c])},options:null,add:function(a){var b=0,d=a&&a.length||0,e=c.util.isArray(a)?a[0]:a;if(!e)return this;do switch(e.type){case"behavior":this.addBehavior(e);break;case"integrator":this.integrator(e);break;case"renderer":this.renderer(e);break;case"body":this.addBody(e);break;default:throw'Error: failed to add item of unknown type "'+e.type+'" to world'}while(++b-1},integrator:function(a){return void 0===a?this._integrator:this._integrator===a?this:(this._integrator&&(this._integrator.setWorld(null),this.emit("remove:integrator",{integrator:this._integrator})),a&&(this._integrator=a,this._integrator.setWorld(this),this.emit("add:integrator",{integrator:this._integrator})),this)},renderer:function(a){return void 0===a?this._renderer:this._renderer===a?this:(this._renderer&&(this._renderer.setWorld(null),this.emit("remove:renderer",{renderer:this._renderer})),a&&(this._renderer=a,this._renderer.setWorld(this),this.emit("add:renderer",{renderer:this._renderer})),this)},timestep:function(a){return a?(this._dt=+a.toPrecision(4),this._maxJump=a*this.options.maxIPF,this):this._dt},wakeUpAll:function(){var a=0,b=this._bodies.length;for(a=0;b>a;a++)this._bodies[a].sleep(!1)},addBehavior:function(a){return this.has(a)?this:(a.setWorld(this),this._behaviors.push(a),this.emit("add:behavior",{behavior:a}),this)},getBehaviors:function(){return[].concat(this._behaviors)},removeBehavior:function(a){var b=this._behaviors;if(a)for(var c=0,d=b.length;d>c;++c)if(a===b[c]){b.splice(c,1),a.setWorld(null),this.emit("remove:behavior",{behavior:a});break}return this},addBody:function(a){return this.has(a)?this:(a.setWorld(this),this._bodies.push(a),this.emit("add:body",{body:a}),this)},getBodies:function(){return[].concat(this._bodies)},removeBody:function(a){var b=this._bodies;if(a)for(var c=0,d=b.length;d>c;++c)if(a===b[c]){b.splice(c,1),a.setWorld(null),this.emit("remove:body",{body:a});break}return this},findOne:function(a){var b=this,d="function"==typeof a?a:c.query(a);return c.util.find(b._bodies,d)||!1},find:function(a){var b=this,d="function"==typeof a?a:c.query(a);return c.util.filter(b._bodies,d)},iterate:function(a){this._integrator.integrate(this._bodies,a)},step:function(a){var b,d,e,f=this._time,g=this._warp,h=1/g,i=this._dt,j=i*h,k=this._maxJump*h,l=this._meta;if(this._paused||void 0===this._animTime)return this._animTime=a||this._animTime||c.util.ticker.now(),this._paused||this.emit("step",l),this;if(a=a||this._animTime+j,b=a-this._animTime,b>k&&(this._animTime=a-k,b=k),d=b*g,e=f+d-i,this.emit("beforeStep"),e>=f)for(;e>=f;)f+=i,this._animTime+=j,this._time=f,this.iterate(i);return l.fps=1e3/(a-this._lastTime),l.ipf=(d/i).toFixed(2),l.interpolateTime=i+e-f,this._lastTime=a,this.emit("step",l),this},warp:function(a){return void 0===a?this._warp:(this._warp=a||1,this)},render:function(){if(!this._renderer)throw"No renderer added to world";return this._renderer.render(this._bodies,this._meta),this.emit("render",{bodies:this._bodies,meta:this._meta,renderer:this._renderer}),this},pause:function(){return this._paused=!0,this.emit("pause"),this},unpause:function(){return this._paused=!1,this.emit("unpause"),this},isPaused:function(){return!!this._paused},destroy:function(){var a=this;a.pause(),this.emit("destroy"),a.off(!0),a.remove(a.getBodies()),a.remove(a.getBehaviors()),a.integrator(null),a.renderer(null) +}}),c.world=d}(),c.integrator("verlet",function(a){return c.body.mixin({started:function(a){return void 0!==a&&(this._started=!0),!!this._started}}),{init:function(b){a.init.call(this,b)},integrateVelocities:function(a,b){for(var c,d=b*b,e=1-this.options.drag,f=null,g=this.prevDt||b,h=.5*(d+b*g),i=0,j=a.length;j>i;++i)f=a[i],c=f.state,"static"===f.treatment||f.sleep(b)?(c.vel.zero(),c.acc.zero(),c.angular.vel=0,c.angular.acc=0):(c.vel.equals(c.old.vel)&&f.started()?c.vel.clone(c.pos).vsub(c.old.pos):(c.old.pos.clone(c.pos).vsub(c.vel),c.vel.mult(b)),e&&c.vel.mult(e),c.vel.vadd(c.acc.mult(h)),c.vel.mult(1/b),c.old.vel.clone(c.vel),c.acc.zero(),c.angular.vel===c.old.angular.vel&&f.started()?c.angular.vel=c.angular.pos-c.old.angular.pos:(c.old.angular.pos=c.angular.pos-c.angular.vel,c.angular.vel*=b),c.angular.vel+=c.angular.acc*h,c.angular.vel/=b,c.old.angular.vel=c.angular.vel,c.angular.acc=0,f.started(!0))},integratePositions:function(a,b){for(var c,d=null,e=this.prevDt||b,f=b/e,g=0,h=a.length;h>g;++g)d=a[g],c=d.state,"static"===d.treatment||d.sleep()||(c.vel.mult(b*f),c.old.pos.clone(c.pos),c.pos.vadd(c.vel),c.vel.mult(1/(b*f)),c.old.vel.clone(c.vel),c.angular.vel*=b*f,c.old.angular.pos=c.angular.pos,c.angular.pos+=c.angular.vel,c.angular.vel/=b*f,c.old.angular.vel=c.angular.vel);this.prevDt=b}}}),c.geometry("point",function(){}),c.body("point",function(a){return{init:function(b){a.init.call(this,b),this.moi=0}}}),c.geometry("circle",function(a){var b={radius:1};return{init:function(d){a.init.call(this,d),this.options.defaults(b),this.options.onChange(function(a){this.radius=a.radius}),this.options(d),this._aabb=c.aabb(),this.radius=this.options.radius},aabb:function(){var a=this.radius;return this._aabb.hw!==a&&(this._aabb=c.aabb(-a,-a,a,a)),c.aabb.clone(this._aabb)},getFarthestHullPoint:function(a,b){return b=b||new c.vector,b.clone(a).normalize().mult(this.radius)},getFarthestCorePoint:function(a,b,d){return b=b||new c.vector,b.clone(a).normalize().mult(this.radius-d)}}}),c.geometry("compound",function(a){var b={};return{init:function(c){a.init.call(this,c),this.options.defaults(b),this.options(c),this.children=[]},addChild:function(a,b,d){return this._aabb=null,this.children.push({g:a,pos:new c.vector(b),angle:d}),this},clear:function(){return this._aabb=null,this.children=[],this},aabb:function(a){if(!a&&this._aabb)return c.aabb.clone(this._aabb);var b,d,e,f=c.scratchpad(),g=c.vector();a=a||0;for(var h=0,i=this.children.length;i>h;h++)d=this.children[h],b=d.g.aabb(a+d.angle),g.clone(d.pos),a&&g.rotate(a),b.x+=g._[0],b.y+=g._[1],e=e?c.aabb.union(e,b,!0):b;return a||(this._aabb=c.aabb.clone(e)),f.done(e)},getFarthestHullPoint:function(a,b){var d,e,f=this.children.length,g=c.scratchpad(),h=g.vector(),i=0,j=0;for(b=b||new c.vector,e=0;f>e;e++)d=this.children[e],d.g.getFarthestHullPoint(a.rotate(-d.angle),h),i=h.rotate(d.angle).vadd(d.pos).proj(a.rotate(d.angle)),i>j&&(j=i,b.swap(h));return g.done(b)},getFarthestCorePoint:function(a,b,d){var e,f,g=this.children.length,h=c.scratchpad(),i=h.vector(),j=0,k=0;for(b=b||new c.vector,f=0;g>f;f++)e=this.children[f],e.g.getFarthestCorePoint(a.rotate(-e.angle),i,d),j=i.rotate(e.angle).vadd(e.pos).proj(a.rotate(e.angle)),j>k&&(k=j,b.swap(i));return h.done(b)}}}),c.geometry("convex-polygon",function(a){var b="Error: The vertices specified do not match that of a _convex_ polygon.",d={};return{init:function(b){var c=this;a.init.call(this,b),this.options.defaults(d),this.options.onChange(function(a){c.setVertices(a.vertices||[])}),this.options(b),c.setVertices(this.options.vertices||[])},setVertices:function(a){var d=c.scratchpad(),e=d.transform(),f=this.vertices=[];if(!c.geometry.isPolygonConvex(a))throw b;e.setRotation(0),e.setTranslation(c.geometry.getPolygonCentroid(a).negate());for(var g=0,h=a.length;h>g;++g)f.push(new c.vector(a[g]).translate(e));return this._area=c.geometry.getPolygonArea(f),this._aabb=!1,d.done(this)},aabb:function(a){if(!a&&this._aabb)return c.aabb.clone(this._aabb);var b,d=c.scratchpad(),e=d.vector(),f=d.transform().setRotation(a||0),g=d.vector().set(1,0).rotateInv(f),h=d.vector().set(0,1).rotateInv(f),i=this.getFarthestHullPoint(g,e).proj(g),j=-this.getFarthestHullPoint(g.negate(),e).proj(g),k=this.getFarthestHullPoint(h,e).proj(h),l=-this.getFarthestHullPoint(h.negate(),e).proj(h);return b=c.aabb(j,l,i,k),a||(this._aabb=c.aabb.clone(b)),d.done(),b},getFarthestHullPoint:function(a,b,d){var e,f,g,h=this.vertices,i=h.length,j=2;if(b=b||new c.vector,2>i)return d&&(d.idx=0),b.clone(h[0]);if(f=h[0].dot(a),e=h[1].dot(a),2===i)return g=e>=f?1:0,d&&(d.idx=g),b.clone(h[g]);if(e>=f){for(;i>j&&e>=f;)f=e,e=h[j].dot(a),j++;return e>=f&&j++,g=j-2,d&&(d.idx=j-2),b.clone(h[g])}for(j=i;j>1&&f>=e;)j--,e=f,f=h[j].dot(a);return g=(j+1)%i,d&&(d.idx=g),b.clone(h[g])},getFarthestCorePoint:function(a,b,d){var e,f=c.scratchpad(),g=f.vector(),h=f.vector(),i=this.vertices,j=i.length,k=this._area>0,l={};return b=this.getFarthestHullPoint(a,b,l),g.clone(i[(l.idx+1)%j]).vsub(b).normalize().perp(k),h.clone(i[(l.idx-1+j)%j]).vsub(b).normalize().perp(!k),e=d/(1+g.dot(h)),b.vadd(g.vadd(h).mult(e)),f.done(),b}}}),c.geometry("rectangle",function(a){var b={};return{init:function(c){var d=this;a.init.call(this,c),this.options.defaults(b),this.options.onChange(function(){d.width=d.options.width||1,d.height=d.options.height||1}),this.options(c)},aabb:function(a){if(!a)return c.aabb(this.width,this.height);var b=c.scratchpad(),d=b.vector(),e=b.transform().setRotation(a||0),f=b.vector().set(1,0).rotateInv(e),g=b.vector().set(0,1).rotateInv(e),h=this.getFarthestHullPoint(f,d).proj(f),i=-this.getFarthestHullPoint(f.negate(),d).proj(f),j=this.getFarthestHullPoint(g,d).proj(g),k=-this.getFarthestHullPoint(g.negate(),d).proj(g);return b.done(),c.aabb(i,k,h,j)},getFarthestHullPoint:function(a,b){b=b||new c.vector;var d=a.x,e=a.y;return d=0===d?0:0>d?.5*-this.width:.5*this.width,e=0===e?0:0>e?.5*-this.height:.5*this.height,b.set(d,e)},getFarthestCorePoint:function(a,b,c){var d,e;return b=this.getFarthestHullPoint(a,b),d=b.x,e=b.y,b.x=0===d?0:0>d?d+c:d-c,b.y=0===e?0:0>e?e+c:e-c,b}}}),c.body("circle",function(a){var b={radius:1};return{init:function(d){a.init.call(this,d),d=c.util.extend({},b,d),this.geometry=c.geometry("circle",{radius:d.radius}),this.recalc()},recalc:function(){a.recalc.call(this),this.moi=this.mass*this.geometry.radius*this.geometry.radius/2}}}),c.body("compound",function(a){return{init:function(b){a.init.call(this,b),this.mass=0,this.moi=0,this.children=[],this.geometry=c.geometry("compound"),this.addChildren(b.children)},connect:function(){if(this.mass<=0)throw"Can not add empty compound body to world."},addChild:function(a){return this.addChildren([a]),this},addChildren:function(a){var b,d,e,f=this,g=c.scratchpad(),h=g.vector().zero(),i=a&&a.length,j=0;if(!i)return g.done(this);for(e=0;i>e;e++)b=a[e],b._world&&b._world.remove(b),this.children.push(b),this.geometry.addChild(b.geometry,new c.vector(b.offset).rotate(b.state.angular.pos).vadd(b.state.pos),b.state.angular.pos),d=b.state.pos,h.add(d._[0]*b.mass,d._[1]*b.mass),j+=b.mass;return this.mass+=j,h.mult(1/this.mass),this.offset.vsub(h),this._world&&this._world.one("render",function(){f.view=null}),this.recalc(),g.done(this)},clear:function(){return this._aabb=null,this.moi=0,this.mass=0,this.offset.zero(),this.children=[],this.geometry.clear(),this},refreshGeometry:function(){this.geometry.clear();for(var a,b=0,d=this.children.length;d>b;b++)a=this.children[b],this.geometry.addChild(a.geometry,new c.vector(a.state.pos).vadd(a.offset),a.state.angular.pos);return this},recalc:function(){a.recalc.call(this);for(var b,c=0,d=0,e=this.children.length;e>d;d++)b=this.children[d],b.recalc(),c+=b.moi+b.mass*b.state.pos.normSq();return this.moi=c,this}}}),c.body("convex-polygon",function(a){var b={};return{init:function(d){a.init.call(this,d),d=c.util.extend({},b,d),this.geometry=c.geometry("convex-polygon",{vertices:d.vertices}),this.recalc()},recalc:function(){a.recalc.call(this),this.moi=c.geometry.getPolygonMOI(this.geometry.vertices)}}}),c.body("rectangle",function(a){var b={};return{init:function(d){a.init.call(this,d),d=c.util.extend({},b,d),this.geometry=c.geometry("rectangle",{width:d.width,height:d.height}),this.recalc()},recalc:function(){var b=this.geometry.width,c=this.geometry.height;a.recalc.call(this),this.moi=(b*b+c*c)*this.mass/12}}}),c.behavior("attractor",function(a){var b={pos:null,strength:1,order:2,max:!1,min:!1};return{init:function(d){var e=this;this._pos=new c.vector,a.init.call(this),this.options.defaults(b),this.options.onChange(function(a){e._maxDist=a.max===!1?1/0:a.max,e._minDist=a.min?a.min:10,e.position(a.pos)}),this.options(d)},position:function(a){var b=this;return a?(this._pos.clone(a),b):this._pos.values()},behave:function(){for(var a,b,d,e=this.getTargets(),f=this.options.order,g=this.options.strength,h=this._minDist,i=this._maxDist,j=c.scratchpad(),k=j.vector(),l=0,m=e.length;m>l;l++)a=e[l],k.clone(this._pos),k.vsub(a.state.pos),b=k.norm(),b>h&&i>b&&(d=g/Math.pow(b,f),a.accelerate(k.normalize().mult(d)));j.done()}}}),c.behavior("body-collision-detection",function(a){var b=[],d=function(a,d){var e=c.util.pairHash(a.uid,d.uid),f=b[e];return f||(f=b[e]=function(b){var c=f.tA,e=f.tB,g=f.tmpv1,h=f.tmpv2;return f.useCore?(g=a.geometry.getFarthestCorePoint(b.rotateInv(c),g,f.marginA),h=d.geometry.getFarthestCorePoint(b.rotate(c).rotateInv(e).negate(),h,f.marginB)):(g=a.geometry.getFarthestHullPoint(b.rotateInv(c),g),h=d.geometry.getFarthestHullPoint(b.rotate(c).rotateInv(e).negate(),h)),g.vadd(a.offset).transform(c),h.vadd(d.offset).transform(e),b.negate().rotate(e),{a:g.values(),b:h.values(),pt:g.vsub(h).values()}},f.tA=new c.transform,f.tB=new c.transform,f.tmpv1=new c.vector,f.tmpv2=new c.vector),f.useCore=!1,f.margin=0,f.tA.setRotation(a.state.angular.pos).setTranslation(a.state.pos),f.tB.setRotation(d.state.angular.pos).setTranslation(d.state.pos),f.bodyA=a,f.bodyB=d,f},e=function(a,b){var e,f,g,h,i=c.scratchpad(),j=i.vector(),k=i.vector(),l=i.vector(),m=!1,n=a.aabb(),o=Math.min(n.hw,n.hh),p=b.aabb(),q=Math.min(p.hw,p.hh);if(g=d(a,b),j.clone(a.state.pos).vadd(a.getGlobalOffset(l)).vsub(b.state.pos).vsub(b.getGlobalOffset(l)),f=c.gjk(g,j,!0),f.overlap){for(m={bodyA:a,bodyB:b},h=.01*Math.min(o||1,q||1),g.useCore=!0,g.marginA=0,g.marginB=0;(f.overlap||0===f.distance)&&(g.marginA=e)return i.done(!1);m.overlap=e,m.norm=j.clone(f.closest.b).vsub(k.clone(f.closest.a)).normalize().values(),m.mtv=j.mult(e).values(),m.pos=j.clone(m.norm).mult(g.marginA).vadd(k.clone(f.closest.a)).vsub(a.state.pos).values()}return i.done(m)},f=function(a,b){var d,e=c.scratchpad(),f=e.vector(),g=e.vector(),h=!1;return f.clone(b.state.pos).vadd(b.getGlobalOffset(g)).vsub(a.state.pos).vsub(a.getGlobalOffset(g)),d=f.norm()-(a.geometry.radius+b.geometry.radius),f.equals(c.vector.zero)&&f.set(1,0),0>=d&&(h={bodyA:a,bodyB:b,norm:f.normalize().values(),mtv:f.mult(-d).values(),pos:f.mult(-a.geometry.radius/d).vadd(g).values(),overlap:-d}),e.done(h)},g=function i(a,b){if(!("static"!==a.treatment&&"kinematic"!==a.treatment||"static"!==b.treatment&&"kinematic"!==b.treatment))return!1;if("circle"===a.geometry.name&&"circle"===b.geometry.name)return f(a,b);if("compound"===a.geometry.name||"compound"===b.geometry.name){var d,g,h,j,k="compound"===a.geometry.name,l=k?a:b,m=k?b:a,n=[],o=c.scratchpad(),p=(o.vector(),o.vector()),q=m.aabb();for(h=0,j=l.children.length;j>h;h++){if(g=l.children[h],p.clone(g.state.pos),g.offset.vadd(p.vadd(l.offset).rotate(-g.state.angular.pos)),g.state.pos.clone(l.state.pos),g.state.angular.pos+=l.state.angular.pos,c.aabb.overlap(q,g.aabb()))if(d=i(m,g),d instanceof Array)for(var r,s=0,t=d.length;t>s;s++)r=d[s],r.bodyA===g?r.bodyA=l:r.bodyB=l,n.push(r);else d&&(d.bodyA===g?d.bodyA=l:d.bodyB=l,n.push(d));g.state.angular.pos-=l.state.angular.pos,g.offset.vsub(p),g.state.pos.clone(p.rotate(g.state.angular.pos).vsub(l.offset))}return o.done(n)}return e(a,b)},h={check:"collisions:candidates",channel:"collisions:detected"};return{init:function(b){a.init.call(this),this.options.defaults(h),this.options(b)},connect:function(a){this.options.check===!0?a.on("integrate:velocities",this.checkAll,this):a.on(this.options.check,this.check,this)},disconnect:function(a){this.options.check===!0?a.off("integrate:velocities",this.checkAll,this):a.off(this.options.check,this.check,this)},check:function(a){for(var b,d,e,f=a.candidates,h=this.getTargets(),i=[],j=this.prevContacts||{},k={},l=c.util.pairHash,m=0,n=f.length;n>m;++m)if(b=f[m],h===this._world._bodies||c.util.indexOf(h,b.bodyA)>-1&&c.util.indexOf(h,b.bodyB)>-1)if(d=g(b.bodyA,b.bodyB),d instanceof Array)for(var o,p=0,q=d.length;q>p;p++)o=d[p],o&&(e=l(b.bodyA.uid,b.bodyB.uid),k[e]=!0,o.collidedPreviously=j[e],i.push(o));else d&&(e=l(b.bodyA.uid,b.bodyB.uid),k[e]=!0,d.collidedPreviously=j[e],i.push(d));this.prevContacts=k,i.length&&this._world.emit(this.options.channel,{collisions:i})},checkAll:function(a){for(var b,d,e,f,h=this.getTargets(),i=(a.dt,[]),j=this.prevContacts||{},k={},l=c.util.pairHash,m=0,n=h.length;n>m;m++){b=h[m];for(var o=m+1;n>o;o++)if(d=h[o],e=g(b,d),e instanceof Array)for(var p,q=0,r=e.length;r>q;q++)p=e[q],p&&(f=l(b.uid,d.uid),k[f]=!0,p.collidedPreviously=j[f],i.push(p));else e&&(f=l(b.uid,d.uid),k[f]=!0,e.collidedPreviously=j[f],i.push(e))}this.prevContacts=k,i.length&&this._world.emit(this.options.channel,{collisions:i})}}}),c.behavior("body-impulse-response",function(a){function b(a){return a.uid}function d(a,b,c){var d,e;return e=b.norm(),d=e-a.proj(b),d=Math.max(0,Math.min(e,d)),0===e?c.zero():c.clone(b).mult(d/e),c}var e={check:"collisions:detected",mtvThreshold:1,bodyExtractDropoff:.5,forceWakeupAboveOverlapThreshold:!0};return{init:function(b){a.init.call(this),this.options.defaults(e),this.options(b),this._bodyList=[]},applyTo:!1,connect:function(a){a.on(this.options.check,this.respond,this)},disconnect:function(a){a.off(this.options.check,this.respond,this)},collideBodies:function(a,b,e,f,g,h){var i="static"===a.treatment||"kinematic"===a.treatment,j="static"===b.treatment||"kinematic"===b.treatment,k=c.scratchpad(),l=k.vector().clone(g);if(i&&j)return void k.done();var m,n,o,p,q=i?0:1/a.moi,r=j?0:1/b.moi,s=i?0:1/a.mass,t=j?0:1/b.mass,u=a.restitution*b.restitution,v=a.cof*b.cof,w=k.vector().clone(e),x=k.vector().clone(w).perp(),y=k.vector(),z=k.vector().clone(f),A=k.vector().clone(f).vadd(a.state.pos).vsub(b.state.pos),B=a.state.angular.vel,C=b.state.angular.vel,D=k.vector().clone(b.state.vel).vadd(y.clone(A).perp().mult(C)).vsub(a.state.vel).vsub(y.clone(z).perp().mult(B)),E=z.proj(w),F=z.proj(x),G=A.proj(w),H=A.proj(x),I=D.proj(w),J=D.proj(x);return h&&(i?(d(b._mtvTotal,l,y),b._mtvTotal.vadd(y)):j?(d(a._mtvTotal,l.negate(),y),a._mtvTotal.vadd(y),l.negate()):(p=.5,l.mult(p),d(b._mtvTotal,l,y),b._mtvTotal.vadd(y),l.clone(g).mult(p-1),d(a._mtvTotal,l,y),a._mtvTotal.vadd(y))),I>=0?void k.done():(q=1/0===q?0:q,r=1/0===r?0:r,m=-((1+u)*I)/(s+t+q*F*F+r*H*H),i?(b.state.vel.vadd(w.mult(m*t)),b.state.angular.vel-=m*r*H):j?(a.state.vel.vsub(w.mult(m*s)),a.state.angular.vel+=m*q*F):(b.state.vel.vadd(w.mult(m*t)),b.state.angular.vel-=m*r*H,a.state.vel.vsub(w.mult(s*b.mass)),a.state.angular.vel+=m*q*F),v&&J&&(o=Math.abs(J)/(s+t+q*E*E+r*G*G),n=0>J?-1:1,m=v*Math.abs(m),m=Math.min(m,o),m*=n,i?(b.state.vel.vsub(x.mult(m*t)),b.state.angular.vel-=m*r*G):j?(a.state.vel.vadd(x.mult(m*s)),a.state.angular.vel+=m*q*E):(b.state.vel.vsub(x.mult(m*t)),b.state.angular.vel-=m*r*G,a.state.vel.vadd(x.mult(s*b.mass)),a.state.angular.vel+=m*q*E)),a.sleep()&&a.sleepCheck(),b.sleep()&&b.sleepCheck(),void k.done())},_pushUniq:function(a){var d=c.util.sortedIndex(this._bodyList,a,b);this._bodyList[d]!==a&&this._bodyList.splice(d,0,a)},respond:function(a){var b,d,e,f,g=this,h=a.collisions;for(d=0,e=h.length;e>d;++d)b=h[d],this._pushUniq(b.bodyA),this._pushUniq(b.bodyB),b.bodyA._mtvTotal=b.bodyA._mtvTotal||new c.vector,b.bodyB._mtvTotal=b.bodyB._mtvTotal||new c.vector,b.bodyA._oldmtvTotal=b.bodyA._oldmtvTotal||new c.vector,b.bodyB._oldmtvTotal=b.bodyB._oldmtvTotal||new c.vector,g.collideBodies(b.bodyA,b.bodyB,b.norm,b.pos,b.mtv,b.collidedPreviously);for(d=0,e=this._bodyList.length;e>d;++d)f=this._bodyList.pop(),f._mtvTotal.normSq()b;++b)a[b].accelerate(this._acc)}}}),c.behavior("edge-collision-detection",function(a){var b=function(a,b,d){var e,f=a.aabb(),g=c.scratchpad(),h=a.getGlobalOffset(g.vector()),i=g.transform(),j=g.vector(),k=g.vector(),l=!1,m=[];return e=f.x+f.hw-b.max.x,e>=0&&(j.set(1,0).rotateInv(i.setRotation(a.state.angular.pos)),l={bodyA:a,bodyB:d,overlap:e,norm:{x:1,y:0},mtv:{x:e,y:0},pos:a.geometry.getFarthestHullPoint(j,k).rotate(i).vadd(h).values()},m.push(l)),e=f.y+f.hh-b.max.y,e>=0&&(j.set(0,1).rotateInv(i.setRotation(a.state.angular.pos)),l={bodyA:a,bodyB:d,overlap:e,norm:{x:0,y:1},mtv:{x:0,y:e},pos:a.geometry.getFarthestHullPoint(j,k).rotate(i).vadd(h).values()},m.push(l)),e=b.min.x-(f.x-f.hw),e>=0&&(j.set(-1,0).rotateInv(i.setRotation(a.state.angular.pos)),l={bodyA:a,bodyB:d,overlap:e,norm:{x:-1,y:0},mtv:{x:-e,y:0},pos:a.geometry.getFarthestHullPoint(j,k).rotate(i).vadd(h).values()},m.push(l)),e=b.min.y-(f.y-f.hh),e>=0&&(j.set(0,-1).rotateInv(i.setRotation(a.state.angular.pos)),l={bodyA:a,bodyB:d,overlap:e,norm:{x:0,y:-1},mtv:{x:0,y:-e},pos:a.geometry.getFarthestHullPoint(j,k).rotate(i).vadd(h).values()},m.push(l)),g.done(),m},d=function(a,c,d){return b(a,c,d)},e={aabb:null,restitution:.99,cof:1,channel:"collisions:detected"};return{init:function(b){a.init.call(this),this.options.defaults(e),this.options(b),this.setAABB(this.options.aabb),this.restitution=this.options.restitution,this.body=c.body("point",{treatment:"static",restitution:this.options.restitution,cof:this.options.cof})},setAABB:function(a){if(!a)throw"Error: aabb not set";return this._edges={min:{x:a.x-a.hw,y:a.y-a.hh},max:{x:a.x+a.hw,y:a.y+a.hh}},this},connect:function(a){a.on("integrate:positions",this.checkAll,this,2)},disconnect:function(a){a.off("integrate:positions",this.checkAll,this,2)},checkAll:function(a){for(var b,e,f,g=this.getTargets(),h=(a.dt,[]),i=this._edges,j=this.body,k=this.prevContacts||{},l={},m=c.util.pairHash,n=0,o=g.length;o>n;n++)if(b=g[n],"dynamic"===b.treatment&&(e=d(b,i,j))){f=m(b.uid,j.uid);for(var p=0,q=e.length;q>p;p++)l[f]=!0,e[p].collidedPreviously=k[f];h.push.apply(h,e)}this.prevContacts=l,h.length&&this._world.emit(this.options.channel,{collisions:h})}}}),c.behavior("interactive",function(d){if(!b)return{};var e={el:null,moveThrottle:10,minVel:{x:-5,y:-5},maxVel:{x:5,y:5}},f=function(a){var b=0,c=0;if(a.offsetParent)do b+=a.offsetLeft,c+=a.offsetTop;while(a=a.offsetParent);return{left:b,top:c}};return{init:function(a){var g=this;if(d.init.call(this),this.options.defaults(e),this.options(a),this.bodyData={},this.bodyDataByUID={},this.el="string"==typeof this.options.el?b.getElementById(this.options.el):this.options.el,!this.el)throw"No DOM element specified";g.grab=function(a){var b,d,e,h,i,j,k,l;if(g._world)for(a.changedTouches||(a.changedTouches=[a]),i=f(a.target),k=0,l=a.changedTouches.length;l>k;k++)h=a.changedTouches[k],e=h.identifier||h.pointerId||"mouse",b={idx:e,x:h.pageX-i.left,y:h.pageY-i.top},d=g._world.findOne({$at:new c.vector(b),$in:g.getTargets()}),d?(d.state.vel.zero(),d.state.angular.vel=0,d.isGrabbed=!0,j=g.bodyData[e]||{},j.body=d,d.sleep(!1),j.time=c.util.ticker.now(),j.treatment=g.bodyDataByUID[d.uid]?g.bodyDataByUID[d.uid].treatment:d.treatment,d.treatment="kinematic",j.pos=j.pos||new c.vector,j.pos.clone(b),j.offset=j.offset||new c.vector,j.offset.clone(b).vsub(d.state.pos),j.oldPos=j.oldPos||new c.vector,j.oldPos.clone(b),b.body=d,g.bodyData[e]=j,g.bodyDataByUID[d.uid]=j,g._world.emit("interact:grab",b)):g._world.emit("interact:poke",b)},g.move=c.util.throttle(function(a){var b,d,e,h,i,j,k,l;if(g._world)for(a.changedTouches||(a.changedTouches=[a]),i=f(g.el),k=0,l=a.changedTouches.length;l>k;k++)h=a.changedTouches[k],e=h.identifier||h.pointerId||"mouse",b={idx:e,x:h.pageX-i.left,y:h.pageY-i.top},j=g.bodyData[e],j&&(d=j.body,d.sleep(!1),j.time=c.util.ticker.now(),j.oldPos.clone(j.pos),j.pos.clone(b),b.body=d),g._world.emit("interact:move",b)},g.options.moveThrottle),g.release=function(a){var b,d,e,h,i,j,k,l,m;if(g._world)for(a.changedTouches||(a.changedTouches=[a]),l=0,m=a.changedTouches.length;m>l;l++)i=f(g.el),h=a.changedTouches[l],e=h.identifier||h.pointerId||"mouse",b={idx:e,x:h.pageX-i.left,y:h.pageY-i.top},j=g.bodyData[e],j&&(d=j.body,d.sleep(!1),j.pos.clone(b),k=Math.max(c.util.ticker.now()-j.time,g.options.moveThrottle),d.treatment=j.treatment,d.state.vel.clone(j.pos).vsub(j.oldPos).mult(1/k),d.state.vel.clamp(g.options.minVel,g.options.maxVel),d.isGrabbed=!1,b.body=d,delete d.isGrabbed),g._world.emit("interact:release",b),delete g.bodyData[e]}},connect:function(b){b.on("integrate:positions",this.behave,this),a.PointerEvent?(this.el.addEventListener("pointerdown",this.grab),a.addEventListener("pointermove",this.move),a.addEventListener("pointerup",this.release)):(this.el.addEventListener("mousedown",this.grab),this.el.addEventListener("touchstart",this.grab),a.addEventListener("mousemove",this.move),a.addEventListener("touchmove",this.move),a.addEventListener("mouseup",this.release),a.addEventListener("touchend",this.release))},disconnect:function(b){b.off("integrate:positions",this.behave,this),a.PointerEvent?(this.el.removeEventListener("pointerdown",this.grab),a.removeEventListener("pointermove",this.move),a.removeEventListener("pointerup",this.release)):(this.el.removeEventListener("mousedown",this.grab),this.el.removeEventListener("touchstart",this.grab),a.removeEventListener("mousemove",this.move),a.removeEventListener("touchmove",this.move),a.removeEventListener("mouseup",this.release),a.removeEventListener("touchend",this.release))},behave:function(a){var b,c,d,e=this,f=Math.max(a.dt,e.options.moveThrottle);for(var g in e.bodyData)d=e.bodyData[g],c=d.body,b=c.state,b.vel.clone(d.pos).vsub(d.offset).vsub(b.pos).mult(1/f)}}}),c.behavior("newtonian",function(a){var b={strength:1,max:!1,min:!1};return{init:function(c){var d=this;a.init.call(this),this.options.defaults(b),this.options.onChange(function(a){d._maxDistSq=a.max===!1?1/0:a.max*a.max,d._minDistSq=a.min?a.min*a.min:100*a.strength}),this.options(c)},calcPotential:function(a,b,d){var e,f,g,h=this.options.strength,i=this._minDistSq,j=this._maxDistSq;return g=d||new c.vector,g.clone(b).vsub(a),e=g.normSq(),e>i&&j>e?(f=h/e,g.normalize().mult(f)):g.zero()},behave:function(){var a,b,d,e,f,g,h,i,j,k,l,m,n=this.getTargets(),o=c.scratchpad(),p=o.vector(),q=o.vector(),r=o.vector();for(h=0,k=n.length;k>h;h++)for(a=n[h],g=h+1;k>g;g++){if(b=n[g],"compound"===a.name?d=a:"compound"===b.name&&(d=b,b=a),d)if("compound"===b.name)for(i=0,l=d.children.length;l>i;i++)for(e=d.children[i],d.toWorldCoords(q.clone(e.state.pos).vadd(d.offset)),j=0,m=b.children.length;m>j;j++)f=b.children[j],b.toWorldCoords(r.clone(f.state.pos).vadd(b.offset)),this.calcPotential(q,r,p),d.accelerate(p.mult(f.mass)),b.accelerate(p.mult(e.mass/f.mass).negate());else for(i=0,l=d.children.length;l>i;i++)e=d.children[i],d.toWorldCoords(q.clone(e.state.pos).vadd(d.offset)),this.calcPotential(q,b.state.pos,p),d.accelerate(p.mult(b.mass)),b.accelerate(p.mult(e.mass/b.mass).negate());else this.calcPotential(a.state.pos,b.state.pos,p),a.accelerate(p.mult(b.mass)),b.accelerate(p.mult(a.mass/b.mass).negate());d=null}o.done()}}}),c.behavior("sweep-prune",function(a){var b=1,d=function(){return b++},e={x:0,y:1},f=2,g=c.util.pairHash;return{init:function(b){a.init.call(this),this.options.defaults({channel:"collisions:candidates"}),this.options(b),this.encounters=[],this.candidates=[],this.clear()},clear:function(){this.tracked=[],this.pairs=[],this.intervalLists=[];for(var a=0;f>a;++a)this.intervalLists[a]=[]},connect:function(a){a.on("add:body",this.trackBody,this),a.on("remove:body",this.untrackBody,this),a.on("integrate:positions",this.sweep,this,1);for(var b=a.getBodies(),c=0,d=b.length;d>c;++c)this.trackBody({body:b[c]})},disconnect:function(a){a.off("add:body",this.trackBody,this),a.off("remove:body",this.untrackBody,this),a.off("integrate:positions",this.sweep,this,1),this.clear()},broadPhase:function(){return this.updateIntervals(),this.sortIntervalLists(),this._world&&this._world.emit("sweep-prune:intervals",this.intervalLists),this.checkOverlaps()},sortIntervalLists:function(){for(var a,b,c,d,e,g,h,i,j,k=0;f>k;++k)for(a=this.intervalLists[k],c=0,b=a.length,j=k;++c0&&(i>g||i===g&&h.type&&!e.type);)a[d]=h,d--,h=a[d-1],i=h&&h.val.get(j);a[d]=e}},getPair:function(a,b,c){var d=g(a.id,b.id);if(d===!1)return null;var e=this.pairs[d];if(!e){if(!c)return null;e=this.pairs[d]={bodyA:a.body,bodyB:b.body,flag:1}}return c&&(e.flag=1),e},checkOverlaps:function(){var a,b,d,g,h,i,j,k,l,m=1<q;++q)for(a=0===q,h=this.intervalLists[q],j=0,i=h.length;i>j;j++)if(g=h[j],b=g.tracker,g.type)for(k=o,k=o-1;k>=0;k--)d=n[k],d===b?(o-1>k?n[k]=n.pop():n.pop(),o--):(l=this.getPair(b,d,a),l&&l.flag=0;)a=d[e],b=a.interval,c=a.body.aabb(),b.min.val.clone(c).sub(c.hw,c.hh),b.max.val.clone(c).add(c.hw,c.hh)},trackBody:function(a){var b=a.body,e={id:d(),body:b},g={min:{type:!1,val:new c.vector,tracker:e},max:{type:!0,val:new c.vector,tracker:e}};e.interval=g,this.tracked.push(e);for(var h=0;f>h;++h)this.intervalLists[h].push(g.min,g.max)},untrackBody:function(a){for(var b,c,d,e,g=a.body,h=this.tracked,i=0,j=h.length;j>i;++i)if(d=h[i],d.body===g){h.splice(i,1);for(var k=0;f>k;++k){e=0,b=this.intervalLists[k];for(var l=0,m=b.length;m>l;++l)if(c=b[l],c===d.interval.min||c===d.interval.max){if(b.splice(l,1),l--,j--,e>0)break;e++}}break}},sweep:function(){var a,b=this;a=b.broadPhase(),a.length&&this._world.emit(this.options.channel,{candidates:a})}}}),c.behavior("verlet-constraints",function(a){var b=2*Math.PI,d={iterations:2};return{init:function(b){a.init.call(this),this.options.defaults(d),this.options(b),this._distanceConstraints=[],this._angleConstraints=[]},connect:function(a){var b=a.integrator();if(b&&b.name.indexOf("verlet")<0)throw'The rigid constraint manager needs a world with a "verlet" compatible integrator.';a.on("integrate:positions",this.resolve,this)},disconnect:function(a){a.off("integrate:positions",this.resolve,this)},drop:function(){return this._distanceConstraints=[],this._angleConstraints=[],this},distanceConstraint:function(a,b,d,e){var f;return a&&b?(f={id:c.util.uniqueId("dis-constraint"),type:"dis",bodyA:a,bodyB:b,stiffness:d||.5,targetLength:e||b.state.pos.dist(a.state.pos)},f.targetLengthSq=f.targetLength*f.targetLength,this._distanceConstraints.push(f),f):!1},angleConstraint:function(a,b,d,e,f){var g;return a&&b?(g={id:c.util.uniqueId("ang-constraint"),type:"ang",bodyA:a,bodyB:b,bodyC:d,stiffness:e||.5,targetAngle:f||b.state.pos.angle2(a.state.pos,d.state.pos)},this._angleConstraints.push(g),g):!1},remove:function(a){var b,d,e,f,g;if(e=c.util.isObject(a),d=e?a.type:a.substr(0,3),b="ang"===d?this._angleConstraints:this._distanceConstraints,e){for(f=0,g=b.length;g>f;++f)if(b[f]===a)return b.splice(f,1),this}else for(f=0,g=b.length;g>f;++f)if(b[f].id===a)return b.splice(f,1),this;return this},resolveAngleConstraints:function(a){for(var d,e,f,g,h=this._angleConstraints,i=c.scratchpad(),j=i.transform(),k=0,l=h.length;l>k;++k)d=h[k],e=d.bodyB.state.pos.angle2(d.bodyA.state.pos,d.bodyC.state.pos),f=e-d.targetAngle,f&&(f<=-Math.PI?f+=b:f>=Math.PI&&(f-=b),j.setTranslation(d.bodyB.state.pos),f*=-a*d.stiffness,"dynamic"===d.bodyA.treatment&&"dynamic"===d.bodyB.treatment&&"dynamic"===d.bodyC.treatment&&(g=1/(d.bodyA.mass+d.bodyB.mass+d.bodyC.mass)),"dynamic"===d.bodyA.treatment&&(e="dynamic"===d.bodyB.treatment&&"dynamic"===d.bodyC.treatment?f*(d.bodyB.mass+d.bodyC.mass)*g:"dynamic"!==d.bodyB.treatment?f*d.bodyC.mass/(d.bodyC.mass+d.bodyA.mass):f*d.bodyB.mass/(d.bodyB.mass+d.bodyA.mass),j.setRotation(e),d.bodyA.state.pos.translateInv(j),d.bodyA.state.pos.rotate(j),d.bodyA.state.pos.translate(j)),"dynamic"===d.bodyC.treatment&&(e="dynamic"===d.bodyA.treatment&&"dynamic"===d.bodyB.treatment?-f*(d.bodyB.mass+d.bodyA.mass)*g:"dynamic"!==d.bodyB.treatment?-f*d.bodyA.mass/(d.bodyC.mass+d.bodyA.mass):-f*d.bodyB.mass/(d.bodyB.mass+d.bodyC.mass),j.setRotation(e),d.bodyC.state.pos.translateInv(j),d.bodyC.state.pos.rotate(j),d.bodyC.state.pos.translate(j)),"dynamic"===d.bodyB.treatment&&(e="dynamic"===d.bodyA.treatment&&"dynamic"===d.bodyC.treatment?f*(d.bodyA.mass+d.bodyC.mass)*g:"dynamic"!==d.bodyA.treatment?f*d.bodyC.mass/(d.bodyC.mass+d.bodyB.mass):f*d.bodyA.mass/(d.bodyA.mass+d.bodyC.mass),j.setRotation(e).setTranslation(d.bodyA.state.pos),d.bodyB.state.pos.translateInv(j),d.bodyB.state.pos.rotate(j),d.bodyB.state.pos.translate(j),j.setTranslation(d.bodyC.state.pos),d.bodyB.state.pos.translateInv(j),d.bodyB.state.pos.rotateInv(j),d.bodyB.state.pos.translate(j)),d.bodyA.sleepCheck(),d.bodyB.sleepCheck(),d.bodyC.sleepCheck());i.done()},resolveDistanceConstraints:function(a){for(var b,d,e,f,g=this._distanceConstraints,h=c.scratchpad(),i=h.vector(),j=0,k=g.length;k>j;++j)b=g[j],i.clone(b.bodyB.state.pos).vsub(b.bodyA.state.pos),d=i.normSq()||1e-4*Math.random(),e=a*b.stiffness*(d-b.targetLengthSq)/d,i.mult(e),f="dynamic"!==b.bodyA.treatment||"dynamic"!==b.bodyB.treatment?1:b.bodyB.mass/(b.bodyA.mass+b.bodyB.mass),"dynamic"===b.bodyA.treatment&&("dynamic"===b.bodyB.treatment&&i.mult(f),b.bodyA.state.pos.vadd(i),"dynamic"===b.bodyB.treatment&&i.mult(1/f)),"dynamic"===b.bodyB.treatment&&("dynamic"===b.bodyA.treatment&&i.mult(1-f),b.bodyB.state.pos.vsub(i)),b.bodyA.sleepCheck(),b.bodyB.sleepCheck();h.done()},shuffleConstraints:function(){this._distanceConstraints=c.util.shuffle(this._distanceConstraints),this._angleConstraints=c.util.shuffle(this._angleConstraints)},resolve:function(){for(var a=this.options.iterations,b=1/a,c=0;a>c;c++)this.resolveDistanceConstraints(b),this.resolveAngleConstraints(b)},getConstraints:function(){return{distanceConstraints:[].concat(this._distanceConstraints),angleConstraints:[].concat(this._angleConstraints)}}}}),c.integrator("improved-euler",function(a){return{init:function(b){a.init.call(this,b)},integrateVelocities:function(a,b){for(var c,d=1-this.options.drag,e=null,f=0,g=a.length;g>f;++f)e=a[f],c=e.state,"static"===e.treatment||e.sleep(b)?(c.vel.zero(),c.acc.zero(),c.angular.vel=0,c.angular.acc=0):(c.old.vel.clone(c.vel),c.old.acc.clone(c.acc),c.vel.vadd(c.acc.mult(b)),d&&c.vel.mult(d),c.acc.zero(),c.old.angular.vel=c.angular.vel,c.angular.vel+=c.angular.acc*b,c.angular.acc=0)},integratePositions:function(a,b){for(var d,e=.5*b*b,f=null,g=c.scratchpad(),h=g.vector(),i=0,j=a.length;j>i;++i)f=a[i],d=f.state,"static"===f.treatment||f.sleep()||(d.old.pos.clone(d.pos),h.clone(d.old.vel),d.pos.vadd(h.mult(b)).vadd(d.old.acc.mult(e)),d.old.acc.zero(),d.old.angular.pos=d.angular.pos,d.angular.pos+=d.old.angular.vel*b+d.old.angular.acc*e,d.old.angular.acc=0); +g.done()}}}),c.integrator("velocity-verlet-alt",function(a){return c.body.mixin({started:function(a){return void 0!==a&&(this._started=!0),!!this._started}}),{init:function(b){a.init.call(this,b)},integrateVelocities:function(a,b){for(var c,d=1-this.options.drag,e=null,f=0,g=a.length;g>f;++f)e=a[f],c=e.state,"static"!==e.treatment?(e.started()||(c.old.acc.clone(c.acc),c.old.acc.mult(b),c.old.vel.clone(c.vel).vsub(c.old.acc),c.old.acc.mult(1/b)),d&&c.vel.mult(d),c.vel.vadd(c.old.acc.vadd(c.acc).mult(.5*b)),e.started()||(c.old.angular.acc=c.angular.acc,c.old.angular.vel=c.angular.vel-c.old.angular.acc*b),c.angular.vel+=.5*(c.angular.acc+c.old.angular.acc)*b,c.angular.acc=0,e.started(!0)):(c.vel.zero(),c.acc.zero(),c.angular.vel=0,c.angular.acc=0)},integratePositions:function(a,b){for(var c,d=b*b,e=null,f=0,g=a.length;g>f;++f)e=a[f],c=e.state,"static"!==e.treatment&&(c.old.pos.clone(c.pos),c.old.vel.mult(b),c.old.acc.mult(.5*d),c.pos.vadd(c.old.vel).vadd(c.old.acc),c.old.vel.clone(c.vel),c.old.acc.clone(c.acc),c.acc.zero(),c.old.angular.pos=c.angular.pos,c.angular.pos+=c.angular.vel*b+.5*c.old.angular.acc*d,c.old.angular.vel=c.angular.vel,c.old.angular.acc=c.angular.acc,c.angular.acc=0)}}}),c.integrator("velocity-verlet",function(a){return c.body.mixin({started:function(a){return void 0!==a&&(this._started=!0),!!this._started}}),{init:function(b){a.init.call(this,b)},integrate:function(a,b){var c=this._world;return this.integratePositions(a,b),c&&c.emit("integrate:positions",{bodies:a,dt:b}),this.integrateVelocities(a,b),c&&c.emit("integrate:velocities",{bodies:a,dt:b}),this},integrateVelocities:function(a,b){for(var c,d=1-this.options.drag,e=null,f=0,g=a.length;g>f;++f)e=a[f],c=e.state,"static"===e.treatment||e.sleep()?(c.vel.zero(),c.acc.zero(),c.angular.vel=0,c.angular.acc=0):(d&&c.vel.mult(d),c.old.vel.clone(c.vel),c.vel.vadd(c.old.acc.vadd(c.acc).mult(.5*b)),c.old.acc.clone(c.acc),c.acc.zero(),c.old.angular.vel=c.angular.vel,c.old.angular.acc=c.angular.acc,c.angular.vel+=.5*(c.angular.acc+c.old.angular.acc)*b,c.angular.acc=0,e.started(!0))},integratePositions:function(a,b){for(var c,d=b*b,e=null,f=0,g=a.length;g>f;++f)e=a[f],c=e.state,"static"===e.treatment||e.sleep(b)||(e.started()||(c.old.acc.clone(c.acc),c.old.acc.mult(b),c.old.vel.clone(c.vel).vsub(c.old.acc),c.old.acc.mult(1/b)),c.old.pos.clone(c.pos),c.old.vel.mult(b),c.old.acc.mult(.5*d),c.pos.vadd(c.old.vel).vadd(c.old.acc),c.old.vel.mult(1/b),c.old.acc.mult(2/d),e.started()||(c.old.angular.acc=c.angular.acc,c.old.angular.vel=c.angular.vel-c.old.angular.acc*b),c.old.angular.pos=c.angular.pos,c.angular.pos+=c.angular.vel*b+.5*c.old.angular.acc*d)}}}),c.renderer("canvas",function(a){if(!b)return{};var d=2*Math.PI,e=function(a,c){var d=b.createElement(a||"div");return c&&(d.innerHTML=c),d},f={white:"#fff",violet:"#542437",blue:"#53777A"},g={metaEl:null,styles:{point:f.blue,circle:{strokeStyle:f.blue,lineWidth:1,fillStyle:f.blue,angleIndicator:f.white},rectangle:{strokeStyle:f.violet,lineWidth:1,fillStyle:f.violet,angleIndicator:f.white},"convex-polygon":{strokeStyle:f.violet,lineWidth:1,fillStyle:f.violet,angleIndicator:f.white}},offset:{x:0,y:0}};return{init:function(d){var f=this;if(a.init.call(this,d),this.options.defaults(g,!0),this.options.onChange(function(){f.options.offset=new c.vector(f.options.offset)}),this.options(d,!0),this.hiddenCanvas=b.createElement("canvas"),this.hiddenCanvas.width=this.hiddenCanvas.height=100,!this.hiddenCanvas.getContext)throw"Canvas not supported";this.hiddenCtx=this.hiddenCanvas.getContext("2d");var h=this.el;if("CANVAS"!==h.nodeName.toUpperCase()&&(h=b.createElement("canvas"),this.el.appendChild(h),"string"==typeof this.options.el&&this.el===b.body&&(h.id=this.options.el),this.el=h),this.container=this.el.parentNode,this.ctx=h.getContext("2d"),this.els={},this.options.meta){var i=this.options.metaEl||e();i.className="pjs-meta",this.els.fps=e("span"),this.els.ipf=e("span"),i.appendChild(e("span","fps: ")),i.appendChild(this.els.fps),i.appendChild(e("br")),i.appendChild(e("span","ipf: ")),i.appendChild(this.els.ipf),h.parentNode.insertBefore(i,h)}this._layers={},this.addLayer("main",this.el),this.options.autoResize?this.resize():this.resize(this.options.width,this.options.height)},layer:function(a){return a in this._layers?this._layers[a]:null},addLayer:function(a,d,e){var f=this,g=[],h=c.util.extend({},this.options.styles),i={id:a,el:d||b.createElement("canvas"),options:c.util.options({width:this.el.width,height:this.el.height,manual:!1,autoResize:!0,follow:null,offset:null,scale:1,zIndex:1})(e)};if(a in this._layers)throw'Layer "'+a+'" already added.';return this.el.parentNode.insertBefore(i.el,this.el),i.el.style.position="absolute",i.el.style.zIndex=i.options.zIndex,i.el.className+=" pjs-layer-"+i.id,i.ctx=i.el.getContext("2d"),i.ctx.scale(1,1),i.el.width=i.options.width,i.el.height=i.options.height,i.bodies=g,i.reset=function(a){return g=a||[],i},i.addToStack=function(a){return c.util.isArray(a)?g.push.apply(g,a):g.push(a),i},i.removeFromStack=function(a){var b,d;if(c.util.isArray(a))for(b=0,d=a.length;d>b;++b)i.removeFromStack(a[b]);else b=c.util.indexOf(g,a),b>-1&&g.splice(b,1);return i},i.render=function(a){var b,d,e,j=c.scratchpad(),k=j.vector().set(0,0),l=i.options.scale,m=g.length,n=f._interpolateTime,o=m||"main"!==i.id?g:f._world._bodies;if(i.options.manual)return j.done(),i;for(i.options.offset&&("center"===i.options.offset?k.add(.5*i.el.width,.5*i.el.height).mult(1/l):k.vadd(i.options.offset).mult(1/l)),i.options.follow&&(k.vsub(i.options.follow.state.pos),k.sub(i.options.follow.state.vel.get(0)*n,i.options.follow.state.vel.get(1)*n)),a!==!1&&i.ctx.clearRect(0,0,i.el.width,i.el.height),1!==l&&(i.ctx.save(),i.ctx.scale(l,l)),e=0,m=o.length;m>e;++e)b=o[e],b.hidden||(d=b.view||(b.view=f.createView(b.geometry,b.styles||h[b.geometry.name])),f.drawBody(b,b.view,i.ctx,k));return 1!==l&&i.ctx.restore(),j.done(),i},this._layers[a]=i,i},removeLayer:function(a){var b=a.id?a.id:a,c=this._layers[b].el;return c!==this.el&&c.parentNode.removeChild(c),delete this._layers[b],this},resize:function(b,c){var d;a.resize.call(this,b,c);for(var e in this._layers)d=this._layers[e],d.options.autoResize&&(d.el.width=this.width,d.el.height=this.height);return this},setStyle:function(a,b){b=b||this.ctx,c.util.isObject(a)?(a.strokeStyle=a.lineWidth?a.strokeStyle:"rgba(0,0,0,0)",c.util.extend(b,a)):(b.fillStyle=b.strokeStyle=a,b.lineWidth=1)},drawCircle:function(a,b,c,e,f){f=f||this.ctx,f.beginPath(),this.setStyle(e,f),f.arc(a,b,c,0,d,!1),f.closePath(),f.stroke(),f.fill()},drawPolygon:function(a,b,c){var d=a[0],e=d.x,f=d.y,g=a.length;c=c||this.ctx,c.beginPath(),this.setStyle(b,c),c.moveTo(e,f);for(var h=1;g>h;++h)d=a[h],e=d.x,f=d.y,c.lineTo(e,f);g>2&&c.closePath(),c.stroke(),c.fill()},drawRect:function(a,b,c,d,e,f){var g=.5*c,h=.5*d;f=f||this.ctx,this.setStyle(e,f),f.beginPath(),f.rect(a-g,b-h,c,d),f.closePath(),f.stroke(),f.fill()},drawLine:function(a,b,c,d){var e=a.x,f=a.y;d=d||this.ctx,d.beginPath(),this.setStyle(c,d),d.moveTo(e,f),e=b.x,f=b.y,d.lineTo(e,f),d.stroke(),d.fill()},draw:function(a,b,c,d){var e=a.name,f=+(d&&d.x),g=+(d&&d.y),h=a.aabb().hw;if(c=c||this.ctx,b=b||this.options.styles[e]||this.options.styles.circle||{},c.save(),c.translate(f,g),"circle"===e)this.drawCircle(0,0,a.radius,b,c);else if("convex-polygon"===e)this.drawPolygon(a.vertices,b,c);else if("rectangle"===e)this.drawRect(0,0,a.width,a.height,b,c);else if("compound"===e)for(var i,j=0,k=a.children.length;k>j;j++)i=a.children[j],c.translate(i.pos.x,i.pos.y),c.rotate(i.angle),this.draw(i.g,b,c),c.rotate(-i.angle),c.translate(-i.pos.x,-i.pos.y);else this.drawCircle(0,0,1,b,c);return"compound"!==e&&b.angleIndicator&&(c.beginPath(),this.setStyle(b.angleIndicator,c),c.moveTo(0,0),c.lineTo(h,0),c.closePath(),c.stroke()),c.restore(),this},createView:function(a,b){var c,d=a.aabb(),e=d.hw+Math.abs(d.x),f=d.hh+Math.abs(d.y),g={x:e+1,y:f+1},h=this.hiddenCtx,i=this.hiddenCanvas;return b=b||this.options.styles[name]||this.options.styles.circle||{},b.src?(c=new Image,c.src=b.src,b.width&&(c.width=b.width),b.height&&(c.height=b.height),c):(g.x+=0|b.lineWidth,g.y+=0|b.lineWidth,i.width=2*e+2+(2*b.lineWidth|0),i.height=2*f+2+(2*b.lineWidth|0),this.draw(a,b,h,g),c=new Image(i.width,i.height),c.src=i.toDataURL("image/png"),c)},drawMeta:function(a){this.els.fps.innerHTML=a.fps.toFixed(2),this.els.ipf.innerHTML=a.ipf},drawBody:function(a,b,c,d){var e,f,g,h=a.state.pos,i=a.offset,j=a.state.vel,k=this._interpolateTime||0;d=d||this.options.offset,c=c||this.ctx,e=h._[0]+d.x+j._[0]*k,f=h._[1]+d.y+j._[1]*k,g=a.state.angular.pos+a.state.angular.vel*k,c.save(),c.translate(e,f),c.rotate(g),c.translate(i._[0],i._[1]),c.drawImage(b,-b.width/2,-b.height/2,b.width,b.height),c.restore()},render:function(a,b){this._world.emit("beforeRender",{renderer:this,meta:b}),this.options.meta&&this.drawMeta(b),this._interpolateTime=b.interpolateTime;for(var c in this._layers)this._layers[c].render();return this}}}),c.renderer("dom",function(a){if(!b)return{};var c={},d=b.createElement("div"),e=function(a){return a.replace(/(?:^|\s)\w/g,function(a){return a.toUpperCase()})},f=function(a){if(c[a])return c[a];for(var b,f=["Webkit","Moz","Ms","O"],g=0,h=f.length;h>g;++g)if(b=f[g]+e(a),b in d.style)return c[a]=b;return b in d.style?c[a]=a:!1},g="pjs-",h="px",i=f("transform"),j=f("borderRadius"),k=function(a,c){var d=b.createElement(a||"div");return c&&(d.innerHTML=c),d};return{init:function(b){a.init.call(this,b);var c=this.el;if(c.style.position="relative",c.style.overflow="hidden",c.style[i]="translateZ(0)",c.style.width=this.options.width+h,c.style.height=this.options.height+h,this.els={},b.meta){var d=k();d.className="pjs-meta",this.els.fps=k("span"),this.els.ipf=k("span"),d.appendChild(k("span","fps: ")),d.appendChild(this.els.fps),d.appendChild(k("br")),d.appendChild(k("span","ipf: ")),d.appendChild(this.els.ipf),c.appendChild(d)}this.options.autoResize?this.resize():this.resize(this.options.width,this.options.height)},resize:function(b,c){a.resize.call(this,b,c),this.el.style.width=this.width+h,this.el.style.height=this.height+h},pointProperties:function(a){a.style.width="2px",a.style.height="2px",a.style.marginLeft="-1px",a.style.marginTop="-1px",a.style[j]="50%"},circleProperties:function(a,b){var c=b.aabb();a.style.width=2*c.hw+h,a.style.height=2*c.hh+h,a.style.marginLeft=-c.hw+h,a.style.marginTop=-c.hh+h,a.style[j]="50%"},rectangleProperties:function(a,b){var c=b.aabb();a.style.width=2*c.hw+h,a.style.height=2*c.hh+h,a.style.marginLeft=-c.hw+h,a.style.marginTop=-c.hh+h},createView:function(a){var b,c=k(),d=a.name+"Properties";if(c.className=g+a.name,c.style.position="absolute",c.style.top="0px",c.style.left="0px","compound"===a.name)for(var e,f=0,h=a.children.length;h>f;f++)e=a.children[f],b=k(),b.className=g+a.name+" "+g+"child",b.style.position="absolute",b.style.top="0px",b.style.left="0px",this[e.g.name+"Properties"]&&this[e.g.name+"Properties"](b,e.g),b.style[i]="translate("+e.pos._[0]+"px,"+e.pos._[1]+"px) rotate("+e.angle+"rad)",c.appendChild(b);else this[d]&&this[d](c,a);return this.el.appendChild(c),c},connect:function(a){a.on("add:body",this.attach,this),a.on("remove:body",this.detach,this)},disconnect:function(a){a.off("add:body",this.attach,this),a.off("remove:body",this.detach,this)},detach:function(a){var b=a.nodeType&&a||a.body&&a.body.view,c=b&&b.parentNode;return b&&c&&c.removeChild(b),this},attach:function(a){var b=a.nodeType&&a||a.body&&a.body.view;return b&&this.el.appendChild(b),this},drawMeta:function(a){this.els.fps.innerHTML=a.fps.toFixed(2),this.els.ipf.innerHTML=a.ipf},drawBody:function(a,b){var c,d,e,f=a.state.pos,g=a.state.vel,h=a.offset,j=this._interpolateTime;c=f._[0]+g._[0]*j,d=f._[1]+g._[1]*j,e=a.state.angular.pos+a.state.angular.vel*j,b.style[i]="translate("+c+"px,"+d+"px) rotate("+e+"rad) translate("+h._[0]+"px,"+h._[1]+"px)"}}}),c.renderer("pixi",function(d){if(!b)return{};var e=(2*Math.PI,{white:"0xFFFFFF",violet:"0x542437",blue:"0x53777A"}),f={font:"18px monospace",fill:"black",align:"left"},g={metaEl:null,offset:{x:0,y:0},styles:{color:!1,point:e.blue,circle:{strokeStyle:e.blue,lineWidth:1,fillStyle:e.blue,angleIndicator:e.white,fillAlpha:1,strokeAlpha:1,alpha:1},rectangle:{strokeStyle:e.violet,lineWidth:1,fillStyle:e.violet,angleIndicator:e.white,fillAlpha:1,strokeAlpha:1,alpha:1},"convex-polygon":{strokeStyle:e.violet,lineWidth:1,fillStyle:e.violet,angleIndicator:e.white,fillAlpha:1,strokeAlpha:1,alpha:1}}};return{init:function(e){var f,h,i=this;if("undefined"==typeof PIXI)throw"PIXI not present - cannot continue";d.init.call(this,e),this.options.defaults(g,!0),this.options.onChange(function(){i.options.offset=new c.vector(i.options.offset)}),this.options(e,!0),h=!this.options.styles.color||"transparent"===this.options.styles.color,this.stage=new PIXI.Stage(this.options.styles.color),this.meta={},f=this.el&&"CANVAS"===this.el.nodeName?f:null,this.renderer=new PIXI.autoDetectRenderer(this.options.width,this.options.height,{view:f,transparent:h,resolution:a.devicePixelRatio||1}),f||(this.el=this.el||b.body,this.el.appendChild(this.renderer.view)),this.options.autoResize?this.resize():this.resize(this.options.width,this.options.height)},resize:function(a,b){d.resize.call(this,a,b),this.renderer.resize(this.width,this.height)},connect:function(a){a.on("add:body",this.attach,this),a.on("remove:body",this.detach,this)},disconnect:function(a){a.off("add:body",this.attach,this),a.off("remove:body",this.detach,this)},detach:function(a){var b=a instanceof PIXI.Graphics&&a||a.body&&a.body.view;return b&&this.stage.removeChild(b),this},attach:function(a){var b=a instanceof PIXI.Graphics&&a||a.body&&a.body.view;return b&&this.stage.addChild(b),this},loadSpriteSheets:function(a,b){if(!c.util.isArray(a))throw"Spritesheets must be defined in arrays";var d=this,e=new PIXI.AssetLoader(a);return e.load(),e.on("onComplete",function(){d.assetsLoaded=!0,b()}),d},drawBody:function(a,b){var c,d,e,f=a.state.pos,g=a.state.vel,h=a.offset,i=this._interpolateTime||0;c=f._[0]+g._[0]*i,d=f._[1]+g._[1]*i,e=a.state.angular.pos+a.state.angular.vel*i,b.position.set(c,d),b.pivot.set(-h._[0],-h._[1]),b.rotation=e},render:function(a,b){d.render.call(this,a,b),this.renderer.render(this.stage)},setStyles:function(a,b){return c.util.isObject(b)?(b.fillStyle&&"transparent"!==b.fillStyle?(a.beginFill(b.fillStyle),a.fillAlpha=void 0!==b.fillAlpha?b.fillAlpha:1):(a.beginFill(),a.fillAlpha=0),a.lineStyle(b.lineWidth||0,b.strokeStyle,void 0!==b.strokeAlpha?b.strokeAlpha:1),a.alpha=void 0!==b.alpha?b.alpha:1):(b&&"transparent"!==b?a.beginFill(b):(a.beginFill(),a.fillAlpha=0),a.lineStyle(0)),a},createCircle:function(a,b,c,d){var e=new PIXI.Graphics;return this.setStyles(e,d),e.drawCircle(a,b,c),e.endFill(),e},createRect:function(a,b,c,d,e){var f=new PIXI.Graphics;return this.setStyles(f,e),f.drawRect(a,b,c,d),f.endFill(),f},createPolygon:function(a,b){var c=a[0],d=c.x,e=c.y,f=a.length,g={x:d,y:e},h=new PIXI.Graphics;this.setStyles(h,b),h.moveTo(d,e);for(var i=1;f>i;++i)c=a[i],d=c.x,e=c.y,h.lineTo(d,e);return f>2&&h.lineTo(g.x,g.y),h.endFill(),h},createLine:function(a,b,c){var d=a.x,e=a.y,f=new PIXI.Graphics;return this.setStyles(f,c),f.moveTo(d,e),d=b.x,e=b.y,f.lineTo(d,e),f.endFill(),f},createView:function(a,b,c){var d=null,e=a.aabb(),f=e.hw+Math.abs(e.x),g=(e.hh+Math.abs(e.y),a.name);if(c=c||this.stage,b=b||this.options.styles[g]||this.options.styles.circle||{},b.src)return d=PIXI.Sprite.fromImage(b.src),d.anchor.set(.5,.5),b.anchor&&(d.anchor.x=b.anchor.x,d.anchor.y=b.anchor.y),b.width&&(d.width=b.width),b.height&&(d.height=b.height),c.addChild(d),d;if("circle"===g)d=this.createCircle(0,0,a.radius,b);else if("convex-polygon"===g)d=this.createPolygon(a.vertices,b);else if("rectangle"===g)d=this.createRect(-a.width/2,-a.height/2,a.width,a.height,b);else if("compound"===g){d=new PIXI.Graphics;for(var h,i,j=0,k=a.children.length;k>j;j++)h=a.children[j],i=this.createView(h.g,b,d),i.position.set(h.pos.x,h.pos.y),i.rotation=h.angle}else d=this.createCircle(0,0,1,b);return"compound"!==g&&b.angleIndicator&&"transparent"!==b.angleIndicator&&(d.lineStyle(b.lineWidth,b.angleIndicator),d.moveTo(0,0),d.lineTo(f,0)),"compound"!==g&&(d.cacheAsBitmap=!0),c.addChild(d),d},drawMeta:function(a){this.meta.loaded?(this.meta.fps.setText("FPS: "+a.fps.toFixed(2)),this.meta.ipf.setText("IPF: "+a.ipf)):(this.meta.fps=new PIXI.Text("FPS: "+a.fps.toFixed(2),f),this.meta.fps.position.x=15,this.meta.fps.position.y=5,this.meta.ipf=new PIXI.Text("IPF: "+a.ipf,f),this.meta.ipf.position.x=15,this.meta.ipf.position.y=30,this.stage.addChild(this.meta.fps),this.stage.addChild(this.meta.ipf),this.meta.loaded=!0)},createDisplay:function(a,b){var c=null,d=null;switch(a){case"sprite":return d=PIXI.Texture.fromImage(b.texture),c=new PIXI.Sprite(d),b.anchor&&(c.anchor.x=b.anchor.x,c.anchor.y=b.anchor.y),b.container?b.container.addChild(c):this.stage.addChild(c),c;case"movieclip":if(!this.assetsLoaded)throw"No assets have been loaded. Use loadSpritesheet() first";var e=[],f=0;for(f;f Object + * - aabb1 (Object): The first aabb (returned if modify is `true`) + * - aabb2 (Object): The second aabb + * + (Object): The union of two aabbs. If modify is `true`, then the first aabb will be modified and returned. + * + * Get the union of two aabbs. + **/ + Physics.aabb.union = function( aabb1, aabb2, modify ){ + + var ret = modify === true ? aabb1 : {} + ,maxX = Math.max( aabb1.x + aabb1.hw, aabb2.x + aabb2.hw ) + ,maxY = Math.max( aabb1.y + aabb1.hh, aabb2.y + aabb2.hh ) + ,minX = Math.min( aabb1.x - aabb1.hw, aabb2.x - aabb2.hw ) + ,minY = Math.min( aabb1.y - aabb1.hh, aabb2.y - aabb2.hh ) + ; + + ret.hw = Math.abs(maxX - minX) * 0.5; + ret.hh = Math.abs(maxY - minY) * 0.5; + ret.x = (maxX + minX) * 0.5; + ret.y = (maxY + minY) * 0.5; + + return ret; + }; + + /** * Physics.aabb.overlap( aabb1, aabb2 ) -> Boolean * - aabb1 (Object): The first aabb @@ -547,6 +573,73 @@ Physics.util = {}; })(); +// --- +// inside: src/math/statistics.js + +(function(){ + + Physics.statistics = { + /** + * Physics.statistics.pushRunningAvg( v, k, m, s ) -> Array + * - v (Number): is value to push + * - k (Number): is num elements + * - m (Number): is current mean + * - s (Number): is current s value + * + (Array): Returns a 2 element array containing the next mean, and s value + * + * Push a value to a running average calculation. + * see [http://www.johndcook.com/blog/standard_deviation] + * + * Note: variance can be calculated from the "s" value by multiplying it by `1/(k-1)` + **/ + pushRunningAvg: function( v, k, m, s ){ + + var x = v - m; + + // Mk = Mk-1+ (xk – Mk-1)/k + // Sk = Sk-1 + (xk – Mk-1)*(xk – Mk). + m += x / k; + s += x * (v - m); + return [m, s]; + }, + + /** + * Physics.statistics.pushRunningVectorAvg( v, k, m[, s] ) + * - v (Physics.vector): is vector to push + * - k (Number): is num elements + * - m (Physics.vector): is current mean + * - s (Physics.vector): is current s value + * + * Push a vector to a running vector average calculation. + * see [http://www.johndcook.com/blog/standard_deviation] + * + * Calculations are done in place. The `m` and `s` parameters are altered. + * + * Note: variance can be calculated from the "s" vector by multiplying it by `1/(k-1)` + * + * If s value is ommitted it won't be used. + **/ + pushRunningVectorAvg: function( v, k, m, s ){ + var invK = 1/k + ,x = v.get(0) - m.get(0) + ,y = v.get(1) - m.get(1) + ; + + // Mk = Mk-1+ (xk – Mk-1)/k + // Sk = Sk-1 + (xk – Mk-1)*(xk – Mk). + m.add( x * invK, y * invK ); + + if ( s ){ + x *= v.get(0) - m.get(0); + y *= v.get(1) - m.get(1); + + s.add( x, y ); + } + } + }; +})(); + + // --- // inside: src/math/transform.js @@ -574,8 +667,8 @@ Physics.util = {}; return new Transform( vect, angle ); } - this.v = Physics.vector(); - this.o = Physics.vector(); // origin of rotation + this.v = new Physics.vector(); + this.o = new Physics.vector(); // origin of rotation if ( vect instanceof Transform ){ @@ -668,9 +761,9 @@ Physics.util = {}; ,typedArrays = !!window.Float64Array ; - /** + /** * class Physics.vector - * + * * The vector class and factory function. * * Call `Physics.vector` with the same arguments as @@ -708,7 +801,7 @@ Physics.util = {}; * - x (Number): The x coordinate * - y (Number): The y coordinate * - vect (Vectorish): A vector-like object to clone - * + * * Vector Constructor. **/ var Vector = function Vector( x, y ) { @@ -750,9 +843,9 @@ Physics.util = {}; }; Object.defineProperties( Vector.prototype, { - /** + /** * Physics.vector#x - * + * * Getter/setter property for the x coordinate. **/ x: { @@ -765,9 +858,9 @@ Physics.util = {}; this._[0] = x; } }, - /** + /** * Physics.vector#y - * + * * Getter/setter property for the y coordinate. **/ y: { @@ -782,15 +875,15 @@ Physics.util = {}; } }); - // + // // Methods - // + // /** * Physics.vector#set( x, y ) -> this * - x (Number): x coordinate * - y (Number): y coordinate - * + * * Sets the x and y components of this vector. **/ Vector.prototype.set = function( x, y ) { @@ -805,7 +898,7 @@ Physics.util = {}; /** deprecated: 0.6.0..1.0.0 * Physics.vector#get( idx ) -> Number * - idx (Number): The coordinate index (0 or 1) - * + * * Get the x or y component by index. **/ Vector.prototype.get = function( n ){ @@ -816,7 +909,7 @@ Physics.util = {}; /** * Physics.vector#vadd( v ) -> this * - v (Physics.vector): vector to add - * + * * Add a [[Physics.vector]] to `this`. **/ Vector.prototype.vadd = function( v ) { @@ -831,7 +924,7 @@ Physics.util = {}; /** * Physics.vector#vsub( v ) -> this * - v (Physics.vector): vector to subtract - * + * * Subtract a [[Physics.vector]] from `this`. **/ Vector.prototype.vsub = function( v ) { @@ -847,11 +940,11 @@ Physics.util = {}; * Physics.vector#add( x, y ) -> this * - x (Number): amount to add to the x coordinate * - y (Number): amount to add to the y coordinate - * + * * Add scalars [[Physics.vector]] to the coordinates. **/ Vector.prototype.add = function( x, y ){ - + this.recalc = true; this._[0] += +x || 0; @@ -863,11 +956,11 @@ Physics.util = {}; * Physics.vector#sub( x, y ) -> this * - x (Number): amount to subtract from the x coordinate * - y (Number): amount to subtract from the y coordinate - * + * * Subtract scalars [[Physics.vector]] from the coordinates. **/ Vector.prototype.sub = function( x, y ){ - + this.recalc = true; this._[0] -= x; @@ -878,13 +971,13 @@ Physics.util = {}; /** * Physics.vector#mult( m ) -> this * - m (Number): amount to multiply this vector by - * + * * Multiply this by a scalar quantity. * * Same as scaling the vector by an amount `m`. **/ Vector.prototype.mult = function( m ) { - + if ( !this.recalc ){ this._[4] *= m * m; @@ -896,10 +989,10 @@ Physics.util = {}; return this; }; - /** + /** * Physics.vector#dot( v ) -> Number * - v (Physics.vector): The other vector - * + * * Compute the dot product of this vector with `v`. **/ Vector.prototype.dot = function( v ) { @@ -907,10 +1000,10 @@ Physics.util = {}; return (this._[0] * v._[0]) + (this._[1] * v._[1]); }; - /** + /** * Physics.vector#cross( v ) -> Number * - v (Physics.vector): The other vector - * + * * Compute the (left-handed) cross product of this vector with `v`. **/ Vector.prototype.cross = function( v ) { @@ -921,7 +1014,7 @@ Physics.util = {}; /** * Physics.vector#proj( v ) -> Number * - v (Physics.vector): The other vector - * + * * Compute the [scalar projection](http://en.wikipedia.org/wiki/Vector_projection#Scalar_projection_2) of this along `v`. **/ Vector.prototype.proj = function( v ){ @@ -933,7 +1026,7 @@ Physics.util = {}; /** * Physics.vector#vproj( v ) -> this * - v (Physics.vector): The other vector - * + * * Compute the [vector projection](http://en.wikipedia.org/wiki/Vector_projection#Vector_projection_2) of this along `v` and copy the result into this vector. **/ Vector.prototype.vproj = function( v ){ @@ -946,7 +1039,7 @@ Physics.util = {}; * Physics.vector#angle( [v] ) -> Number * - v (Physics.vector): The other vector * + (Number): The angle in radians between this vector and the x-axis OR `v` if specified - * + * * Compute the angle between `this` and vector `v` or this and x axis. **/ Vector.prototype.angle = function( v ){ @@ -954,7 +1047,7 @@ Physics.util = {}; var ang; if ( this.equals( Vector.zero ) ){ - + if ( v ){ return v.angle(); } else { @@ -966,10 +1059,10 @@ Physics.util = {}; if ( v && !v.equals( Vector.zero ) ){ ang = atan2( this._[1] * v._[0] - this._[0] * v._[1], this._[0] * v._[0] + this._[1] * v._[1]); } else { - ang = atan2( this._[ 1 ], this._[ 0 ] ); + ang = atan2( this._[ 1 ], this._[ 0 ] ); } } - + while (ang > Math.PI){ ang -= TWOPI; } @@ -985,7 +1078,7 @@ Physics.util = {}; * Physics.vector#angle2( left, right ) -> Number * - left (Physics.vector): The position on the left * - right (Physics.vector): The position on the right - * + * * Compute the angle created between three points; left -> this -> right. **/ Vector.prototype.angle2 = function( left, right ){ @@ -1010,7 +1103,7 @@ Physics.util = {}; /** * Physics.vector#norm() -> Number - * + * * Compute the norm (length) of this vector. **/ Vector.prototype.norm = function() { @@ -1020,13 +1113,13 @@ Physics.util = {}; this._[4] = (this._[0] * this._[0] + this._[1] * this._[1]); this._[3] = sqrt( this._[4] ); } - + return this._[3]; }; /** * Physics.vector#normSq() -> Number - * + * * Compute the norm (length) squared of this vector. **/ Vector.prototype.normSq = function() { @@ -1043,14 +1136,14 @@ Physics.util = {}; /** * Physics.vector#dist( v ) -> Number * - v (Physics.vector): The other vector - * + * * Compute the distance from this vector to another vector `v`. **/ Vector.prototype.dist = function( v ) { - + var dx, dy; return sqrt( - (dx = (v._[0] - this._[0])) * dx + + (dx = (v._[0] - this._[0])) * dx + (dy = (v._[1] - this._[1])) * dy ); }; @@ -1058,14 +1151,14 @@ Physics.util = {}; /** * Physics.vector#distSq( v ) -> Number * - v (Physics.vector): The other vector - * + * * Compute the distance squared from this vector to another vector `v`. **/ Vector.prototype.distSq = function( v ) { var dx, dy; return ( - (dx = (v._[0] - this._[0])) * dx + + (dx = (v._[0] - this._[0])) * dx + (dy = (v._[1] - this._[1])) * dy ); }; @@ -1073,7 +1166,7 @@ Physics.util = {}; /** * Physics.vector#perp( [ccw] ) -> this * - ccw (Boolean): flag to indicate that we should rotate counterclockwise - * + * * Change this vector into a vector that will be perpendicular. * * In other words, rotate by (+-) 90 degrees. @@ -1103,7 +1196,7 @@ Physics.util = {}; /** * Physics.vector#normalize() -> this - * + * * Normalise this vector, making it a unit vector. **/ Vector.prototype.normalize = function() { @@ -1129,7 +1222,7 @@ Physics.util = {}; /** * Physics.vector#transform( t ) -> this * - t (Physics.transform): The transformation to apply - * + * * Apply a [[Physics.transform]] to this vector. **/ Vector.prototype.transform = function( t ){ @@ -1145,7 +1238,7 @@ Physics.util = {}; // rotate about origin "o" then translate return this.set( - this._[ 0 ] * cosA - this._[ 1 ] * sinA + x + t.v._[ 0 ], + this._[ 0 ] * cosA - this._[ 1 ] * sinA + x + t.v._[ 0 ], this._[ 0 ] * sinA + this._[ 1 ] * cosA + y + t.v._[ 1 ] ); }; @@ -1153,7 +1246,7 @@ Physics.util = {}; /** * Physics.vector#transformInv( t ) -> this * - t (Physics.transform): The transformation to apply the inverse of - * + * * Apply an inverse [[Physics.transform]] to this vector. **/ Vector.prototype.transformInv = function( t ){ @@ -1169,7 +1262,7 @@ Physics.util = {}; // inverse translate then inverse rotate about origin "o" return this.set( - this._[ 0 ] * cosA + this._[ 1 ] * sinA + x, + this._[ 0 ] * cosA + this._[ 1 ] * sinA + x, - this._[ 0 ] * sinA + this._[ 1 ] * cosA + y ); }; @@ -1180,10 +1273,10 @@ Physics.util = {}; * - t (Physics.transform): The transformation to apply the rotational part of * - ang (Number): The angle (in radians), to rotate by * - o (Vectorish): The point of origin of the rotation - * + * * Rotate this vector. - * - * An angle and rotation origin can be specified, + * + * An angle and rotation origin can be specified, * or a transform can be specified and only the rotation * portion of that transform will be applied **/ @@ -1200,22 +1293,22 @@ Physics.util = {}; cosA = Math.cos( t ); if ( o ){ - x = (o.x || o._[ 0 ]) | 0; - y = (o.y || o._[ 1 ]) | 0; + x = o.x; + y = o.y; } } else { sinA = t.sinA; cosA = t.cosA; - + x = t.o._[ 0 ]; y = t.o._[ 1 ]; } - + this._[ 0 ] -= x; this._[ 1 ] -= y; return this.set( - this._[ 0 ] * cosA - this._[ 1 ] * sinA + x, + this._[ 0 ] * cosA - this._[ 1 ] * sinA + x, this._[ 0 ] * sinA + this._[ 1 ] * cosA + y ); }; @@ -1223,16 +1316,16 @@ Physics.util = {}; /** * Physics.vector#rotateInv( t ) -> this * - t (Physics.transform): The transformation to apply the inverse rotational part of - * + * * Apply the inverse rotation of a transform. - * - * Only the inverse rotation portion of + * + * Only the inverse rotation portion of * that transform will be applied. **/ Vector.prototype.rotateInv = function( t ){ return this.set( - (this._[ 0 ] - t.o._[ 0 ]) * t.cosA + (this._[ 1 ] - t.o._[ 1 ]) * t.sinA + t.o._[ 0 ], + (this._[ 0 ] - t.o._[ 0 ]) * t.cosA + (this._[ 1 ] - t.o._[ 1 ]) * t.sinA + t.o._[ 0 ], -(this._[ 0 ] - t.o._[ 0 ]) * t.sinA + (this._[ 1 ] - t.o._[ 1 ]) * t.cosA + t.o._[ 1 ] ); }; @@ -1240,10 +1333,10 @@ Physics.util = {}; /** * Physics.vector#translate( t ) -> this * - t (Physics.transform): The transformation to apply the translational part of - * + * * Apply the translation of a transform. - * - * Only the translation portion of + * + * Only the translation portion of * that transform will be applied. **/ Vector.prototype.translate = function( t ){ @@ -1254,10 +1347,10 @@ Physics.util = {}; /** * Physics.vector#translateInv( t ) -> this * - t (Physics.transform): The transformation to apply the inverse translational part of - * + * * Apply the inverse translation of a transform. - * - * Only the inverse translation portion of + * + * Only the inverse translation portion of * that transform will be applied. **/ Vector.prototype.translateInv = function( t ){ @@ -1271,10 +1364,10 @@ Physics.util = {}; * - v (Vectorish): The vector-like object to clone * + (this): If `v` is specified as an argument * + (Physics.vector): A new vector instance that clones this vector, if no argument is specified - * + * * Create a clone of this vector, or clone another vector into this instance. * - * This is especially useful in vector algorithms + * This is especially useful in vector algorithms * that use temporary vectors (which most should). * You can create temporary vectors and then do things like... * ``` @@ -1285,7 +1378,7 @@ Physics.util = {}; * ``` **/ Vector.prototype.clone = function( v ) { - + // http://jsperf.com/vector-storage-test if ( v ){ @@ -1294,7 +1387,7 @@ Physics.util = {}; return this.set( v.x, v.y ); } - + this.recalc = v.recalc; if (!v.recalc){ @@ -1314,7 +1407,7 @@ Physics.util = {}; /** * Physics.vector#swap( v ) -> this * - v (Physics.vector): The other vector - * + * * Swap values with other vector. **/ Vector.prototype.swap = function( v ){ @@ -1331,7 +1424,7 @@ Physics.util = {}; /** * Physics.vector#values() -> Object - * + * * Get the coordinate values as an object literal. **/ Vector.prototype.values = function(){ @@ -1345,7 +1438,7 @@ Physics.util = {}; /** * Physics.vector#zero() -> this - * + * * Set the coordinates of this vector to zero. **/ Vector.prototype.zero = function() { @@ -1360,7 +1453,7 @@ Physics.util = {}; /** * Physics.vector#negate() -> this - * + * * Flip this vector in the opposite direction. **/ Vector.prototype.negate = function( component ){ @@ -1380,9 +1473,9 @@ Physics.util = {}; * Physics.vector#clamp( minV, maxV ) -> this * - minV (Vectorish): The minimum vector * - maxV (Vectorish): The maximum vector - * + * * Constrain vector components to minima and maxima. - * + * * The vector analog of [scalar clamping](http://en.wikipedia.org/wiki/Clamping_(graphics)). **/ Vector.prototype.clamp = function( minV, maxV ){ @@ -1395,7 +1488,7 @@ Physics.util = {}; /** * Physics.vector#toString() -> String - * + * * Get a formatted string of this vector's coordinates. **/ Vector.prototype.toString = function(){ @@ -1407,7 +1500,7 @@ Physics.util = {}; /** * Physics.vector#equals( v ) -> Boolean * - v (Physics.vector): The other vector - * + * * Determine if this vector equals another. **/ Vector.prototype.equals = function( v ){ @@ -1419,7 +1512,7 @@ Physics.util = {}; /** * Physics.vector.axis = Array - * + * * Read-only axis vectors for general reference. * * Example: @@ -1436,7 +1529,7 @@ Physics.util = {}; /** * Physics.vector.zero = zeroVector - * + * * Read-only zero vector for reference **/ Vector.zero = new Vector(0, 0); @@ -1704,6 +1797,23 @@ Physics.util.indexOf = function indexOf(arr, value) { return -1; }; + +// http://jsperf.com/array-destroy/87 +/** + * Physics.util.clearArray( arr ) -> Array + * - arr (Array): The array to clear + * + (Array): The array passed in + * + * Quickly clear an array. + **/ +Physics.util.clearArray = function clearArray(arr){ + var l = arr.length; + while( l-- ){ + arr.pop(); + } + return arr; +}; + /** * Physics.util.throttle( fn, delay ) -> Function * - fn (Function): The function to throttle @@ -1726,7 +1836,7 @@ Physics.util.throttle = function throttle( fn, delay, scope ){ } } ; - + scope = scope || null; return function(){ @@ -1765,6 +1875,16 @@ Physics.util.throttle = function throttle( fn, delay, scope ){ * }); * ``` **/ +// deep copy callback to extend deeper into options +var deepCopyFn = function( a, b ){ + + if ( Physics.util.isPlainObject( b ) ){ + + return Physics.util.extend({}, a, b, deepCopyFn ); + } + + return b !== undefined ? b : a; +}; Physics.util.options = function( def, target ){ var _def = {} @@ -1773,9 +1893,9 @@ Physics.util.options = function( def, target ){ ; // set options - fn = function fn( options ){ + fn = function fn( options, deep ){ - Physics.util.extend(target, options, null); + Physics.util.extend(target, options, deep ? deepCopyFn : null); for ( var i = 0, l = callbacks.length; i < l; ++i ){ callbacks[ i ]( target ); } @@ -1783,9 +1903,9 @@ Physics.util.options = function( def, target ){ }; // add defaults - fn.defaults = function defaults( def ){ - Physics.util.extend( _def, def ); - Physics.util.defaults( target, _def ); + fn.defaults = function defaults( def, deep ){ + Physics.util.extend( _def, def, deep ? deepCopyFn : null ); + Physics.util.defaults( target, _def, deep ? deepCopyFn : null ); return _def; }; @@ -1943,7 +2063,7 @@ var maxPoolSize = 40; var keyPrefix = +new Date() + ''; function releaseArray(array) { - array.length = 0; + Physics.util.clearArray( array ); if (arrayPool.length < maxPoolSize) { arrayPool.push(array); } @@ -2378,7 +2498,7 @@ Physics.scratchpad = (function(){ var scratches = []; var numScratches = 0; var Scratch, Scratchpad; - + var regIndex = 0; @@ -2386,7 +2506,7 @@ Physics.scratchpad = (function(){ * class Scratch * * A scratchpad session. - * + * * This class keeps track of temporary objects used * in this session and releases them when finished (call to `.done()`). * @@ -2401,7 +2521,7 @@ Physics.scratchpad = (function(){ // private variables this._active = false; this._indexArr = []; - + if (++numScratches >= Scratchpad.maxScratches){ throw SCRATCH_MAX_REACHED; } @@ -2414,9 +2534,9 @@ Physics.scratchpad = (function(){ * - val (Mixed): No effect on this method, just passed on to the return value so you can do things like: return scratch.done( myReturnVal ); * + (Mixed): Whatever you specified as `val` - * + * * Declare that your work is finished. - * + * * Release temp objects for use elsewhere. Must be called when immediate work is done. * * You can wrap the return value in scratch.done() so that you don't forget to call it. @@ -2432,10 +2552,10 @@ Physics.scratchpad = (function(){ this._active = false; var s; for ( var i = 0; i < regIndex; ++i ){ - + this[ i ] = 0; } - + // add it back to the scratch stack for future use scratches.push( this ); return val; @@ -2450,13 +2570,13 @@ Physics.scratchpad = (function(){ * - fn (Function): Some function you'd like to wrap in a scratch session. First argument is the scratch instance. * + (Function): The wrapped function (if `fn` arg specified) that can be reused like the original minus the first (scratch) parameter. * + (Scratch): The scratch session. - * + * * Get a new scratch session to work from or wrap a function in a scratch session. - * + * * Call `.done()` on it when finished. * * Example: - * + * * ```javascript * // get a scratch session manually * var myAlg = function( scratch, arg1, arg2, ... ){ @@ -2473,7 +2593,7 @@ Physics.scratchpad = (function(){ * ``` * * Example: - * + * * ```javascript * // wrap a function in a scratch session * var myAlg = Physics.scratchpad(function( scratch, arg1, arg2, ... ){ @@ -2506,13 +2626,13 @@ Physics.scratchpad = (function(){ * Physics.scratchpad.fn( fn ) -> Function * - fn (Function): Some function you'd like to wrap in a scratch session. First argument is the scratch instance. See [[Physics.scratchpad]]. * + (Function): The wrapped function that can be reused like the original minus the first (scratch) parameter. - * + * * Wrap a function in a scratch session. * * Same as calling `Physics.scratchpad( fn )` with a function specified. **/ Scratchpad.fn = function( fn ){ - + var args = []; for ( var i = 0, l = fn.length; i < l; i++ ){ args.push( i ); @@ -2535,7 +2655,7 @@ Physics.scratchpad = (function(){ * Physics.scratchpad.register( name, constructor ) * - name (String): Name of the object class * - constructor (Function): The object constructor - * + * * Register a new object to be included in scratchpads. * * Example: @@ -2558,7 +2678,7 @@ Physics.scratchpad = (function(){ } // create a new function on the prototype - proto[ name ] = function(){ + Scratch.prototype[ name ] = function(){ // get the stack (or initialize it) var stack = this[ stackname ] || (this[ stackname ] = []) @@ -2581,7 +2701,7 @@ Physics.scratchpad = (function(){ } // return or create new instance - return stack[ stackIndex ] || + return stack[ stackIndex ] || (stack[ stackIndex ] = useFactory ? constructor() : new constructor() ); }; @@ -2595,11 +2715,14 @@ Physics.scratchpad = (function(){ })(); + // --- // inside: src/util/pubsub.js (function(){ + var defaultPriority = 1; + function getPriority( val ){ return val._priority_; } @@ -2666,13 +2789,14 @@ Physics.scratchpad = (function(){ fn = Physics.util.bind( fn, scope ); fn._bindfn_ = orig; fn._one_ = orig._one_; + fn._scope_ = scope; - } else if (!priority) { + } else if ( priority === undefined ) { priority = scope; } - fn._priority_ = priority; + fn._priority_ = priority === undefined ? defaultPriority : priority; idx = Physics.util.sortedIndex( listeners, fn, getPriority ); @@ -2681,15 +2805,16 @@ Physics.scratchpad = (function(){ }, /** - * Physics.util.pubsub#off( topic, fn ) -> this + * Physics.util.pubsub#off( topic, fn[, scope] ) -> this * Physics.util.pubsub#off( topicCfg ) -> this * - topic (String): topic The topic name. Specify `true` to remove all listeners for all topics * - topicCfg (Object): A config with key/value pairs of `{ topic: callbackFn, ... }` * - fn (Function): The original callback function. Specify `true` to remove all listeners for specified topic + * - scope (Object): The scope the callback was bound to. This is important if you are binding methods that come from object prototypes. * * Unsubscribe callback(s) from topic(s). **/ - off: function( topic, fn ){ + off: function( topic, fn, scope ){ var listeners ,listn @@ -2734,7 +2859,10 @@ Physics.scratchpad = (function(){ listn = listeners[ i ]; - if ( listn._bindfn_ === fn || listn === fn ){ + if ( + (listn._bindfn_ === fn || listn === fn) && + ( (!scope) || listn._scope_ === scope) // check the scope too if specified + ){ listeners.splice( i, 1 ); break; } @@ -2839,7 +2967,7 @@ Physics.scratchpad = (function(){ **/ (function(window){ - var active = false + var active = true ,ps = Physics.util.pubsub() ,perf = window.performance ; @@ -2861,6 +2989,8 @@ Physics.scratchpad = (function(){ var time; + window.requestAnimationFrame( step ); + if (!active){ return; } @@ -2871,10 +3001,16 @@ Physics.scratchpad = (function(){ return; } - window.requestAnimationFrame( step ); ps.emit( 'tick', time ); } + // start stepping if we can + if ( window.requestAnimationFrame ){ + step(); + } else { + active = false; + } + /** * Physics.util.ticker.start() -> this * @@ -2883,7 +3019,6 @@ Physics.scratchpad = (function(){ function start(){ active = true; - step(); return this; } @@ -3098,7 +3233,7 @@ Physics.scratchpad = (function(){ * Get a test function to match any body who's aabb intersects point **/ var $at = function $at( point ){ - point = Physics.vector( point ); + point = new Physics.vector( point ); return function( body ){ var aabb = body.aabb(); return Physics.aabb.contains( aabb, point ); @@ -3416,7 +3551,7 @@ Physics.scratchpad = (function(){ disconnect: function( world ){ if (this.behave){ - world.off('integrate:positions', this.behave); + world.off('integrate:positions', this.behave, this); } }, @@ -3458,6 +3593,11 @@ Physics.scratchpad = (function(){ var uidGen = 1; + var Pi2 = Math.PI * 2; + function cycleAngle( ang ){ + return ((ang % Pi2) + Pi2) % Pi2; + } + /** related to: Physics.util.decorator * Physics.body( name[, options] ) -> Body * - name (String): The name of the body to create @@ -3478,7 +3618,9 @@ Physics.scratchpad = (function(){ // what is its coefficient of friction with another surface with COF = 1? cof: 0.8, // what is the view object (mixed) that should be used when rendering? - view: null + view: null, + // the vector offsetting the geometry from its center of mass + offset: Physics.vector(0,0) } ``` * @@ -3503,6 +3645,7 @@ Physics.scratchpad = (function(){ **/ init: function( options ){ + var self = this; var vector = Physics.vector; /** related to: Physics.util.options @@ -3523,6 +3666,9 @@ Physics.scratchpad = (function(){ **/ // all options get copied onto the body. this.options = Physics.util.options( defaults, this ); + this.options.onChange(function( opts ){ + self.offset = new vector( opts.offset ); + }); this.options( options ); /** @@ -3543,18 +3689,18 @@ Physics.scratchpad = (function(){ * ``` **/ this.state = { - pos: vector( this.x, this.y ), - vel: vector( this.vx, this.vy ), - acc: vector(), + pos: new vector( this.x, this.y ), + vel: new vector( this.vx, this.vy ), + acc: new vector(), angular: { pos: this.angle || 0.0, vel: this.angularVelocity || 0.0, acc: 0.0 }, old: { - pos: vector(), - vel: vector(), - acc: vector(), + pos: new vector(), + vel: new vector(), + acc: new vector(), angular: { pos: 0.0, vel: 0.0, @@ -3563,6 +3709,13 @@ Physics.scratchpad = (function(){ } }; + // private storage for sleeping + this._sleepAngPosMean = 0; + this._sleepAngPosVariance = 0; + this._sleepPosMean = new vector(); + this._sleepPosVariance = new vector(); + this._sleepMeanK = 0; + // cleanup delete this.x; delete this.y; @@ -3597,6 +3750,12 @@ Physics.scratchpad = (function(){ * The mass. **/ + /** + * Body#offset + * + * The vector offsetting the body's shape from its center of mass. + **/ + /** * Body#restitution = 1.0 * @@ -3666,7 +3825,7 @@ Physics.scratchpad = (function(){ **/ /** related to: Physics.renderer - * Body#style + * Body#styles * * The styles the renderer should use for creating the view. * @@ -3674,6 +3833,110 @@ Physics.scratchpad = (function(){ **/ }, + /** + * Body#sleep( [dt] ) -> Boolean + * - dt (Number): Time to advance the idle time + * - dt (Boolean): If `true`, the body will be forced to sleep. If `false`, the body will be forced to awake. + * + * Get and/or set whether the body is asleep. + * + * If called with a time (in ms), the time will be added to the idle time and sleep conditions will be checked. + **/ + sleep: function( dt ){ + + if ( dt === true ){ + // force sleep + this.asleep = true; + + } else if ( dt === false ){ + // force wakup + this.asleep = false; + this._sleepMeanK = 0; + this._sleepAngPosMean = 0; + this._sleepAngPosVariance = 0; + this._sleepPosMean.zero(); + this._sleepPosVariance.zero(); + this.sleepIdleTime = 0; + + } else if ( dt && !this.asleep ) { + + this.sleepCheck( dt ); + } + + return this.asleep; + }, + + /** + * Body#sleepCheck( [dt] ) + * - dt (Number): Time to advance the idle time + * + * Check if the body should be sleeping. + * + * Call with no arguments if some event could possibly wake up the body. This will force the body to recheck. + **/ + sleepCheck: function( dt ){ + + var opts = this._world && this._world.options; + + // if sleeping disabled. stop. + if ( this.sleepDisabled || (opts && opts.sleepDisabled) ){ + return; + } + + var limit + ,v + ,d + ,r + ,aabb + ,scratch = Physics.scratchpad() + ,diff = scratch.vector() + ,diff2 = scratch.vector() + ,kfac + ,stats + ; + + dt = dt || 0; + aabb = this.geometry.aabb(); + r = Math.max(aabb.hw, aabb.hh); + + if ( this.asleep ){ + // check velocity + v = this.state.vel.norm() + Math.abs(r * this.state.angular.vel); + limit = this.sleepSpeedLimit || (opts && opts.sleepSpeedLimit) || 0; + + if ( v >= limit ){ + this.sleep( false ); + return scratch.done(); + } + } + + this._sleepMeanK++; + kfac = this._sleepMeanK > 1 ? 1/(this._sleepMeanK - 1) : 0; + Physics.statistics.pushRunningVectorAvg( this.state.pos, this._sleepMeanK, this._sleepPosMean, this._sleepPosVariance ); + // we take the sin because that maps the discontinuous angle to a continuous value + // then the statistics calculations work better + stats = Physics.statistics.pushRunningAvg( Math.sin(this.state.angular.pos), this._sleepMeanK, this._sleepAngPosMean, this._sleepAngPosVariance ); + this._sleepAngPosMean = stats[0]; + this._sleepAngPosVariance = stats[1]; + v = this._sleepPosVariance.norm() + Math.abs(r * Math.asin(stats[1])); + v *= kfac; + limit = this.sleepVarianceLimit || (opts && opts.sleepVarianceLimit) || 0; + // console.log(v, limit, kfac, this._sleepPosVariance.norm(), stats[1]) + if ( v <= limit ){ + // check idle time + limit = this.sleepTimeLimit || (opts && opts.sleepTimeLimit) || 0; + this.sleepIdleTime = (this.sleepIdleTime || 0) + dt; + + if ( this.sleepIdleTime > limit ){ + this.asleep = true; + } + } else { + this.sleep( false ); + } + + scratch.done(); + }, + /** * Body#setWorld( world ) -> this * - world (Object): The world (or null) @@ -3732,7 +3995,7 @@ Physics.scratchpad = (function(){ // if no point at which to apply the force... apply at center of mass if ( p && this.moi ){ - + // apply torques state = this.state; r.clone( p ); @@ -3746,6 +4009,20 @@ Physics.scratchpad = (function(){ return this; }, + /** related to: Body#offset + * Body#getGlobalOffset( [out] ) -> Physics.vector + * - out (Physics.vector): A vector to use to put the result into. One is created if `out` isn't specified. + * + (Physics.vector): The offset in global coordinates + * + * Get the body offset vector (from the center of mass) for the body's shape in global coordinates. + **/ + getGlobalOffset: function( out ){ + + out = out || new Physics.vector(); + out.clone( this.offset ).rotate( this.state.angular.pos ); + return out; + }, + /** related to: Physics.aabb * Body#aabb() -> Object * + (Object): The aabb of this body @@ -3755,13 +4032,39 @@ Physics.scratchpad = (function(){ aabb: function(){ var angle = this.state.angular.pos + ,scratch = Physics.scratchpad() + ,v = scratch.vector() ,aabb = this.geometry.aabb( angle ) ; - aabb.x += this.state.pos.x; - aabb.y += this.state.pos.y; + this.getGlobalOffset( v ); - return aabb; + aabb.x += this.state.pos._[0] + v._[0]; + aabb.y += this.state.pos._[1] + v._[1]; + + return scratch.done( aabb ); + }, + + /** + * Body#toBodyCoords( v ) -> Physics.vector + * - v (Physics.vector): The vector to transform + * + (Physics.vector): The transformed vector + * + * Transform a vector into coordinates relative to this body. + **/ + toBodyCoords: function( v ){ + return v.vsub( this.state.pos ).rotate( -this.state.angular.pos ); + }, + + /** + * Body#toWorldCoords( v ) -> Physics.vector + * - v (Physics.vector): The vector to transform + * + (Physics.vector): The transformed vector + * + * Transform a vector from body coordinates into world coordinates. + **/ + toWorldCoords: function( v ){ + return v.rotate( this.state.angular.pos ).vadd( this.state.pos ); }, /** @@ -3777,6 +4080,47 @@ Physics.scratchpad = (function(){ } }); + /** + * Body.getCOM( bodies[, com] ) -> Physics.vector + * - bodies (Array): The list of bodies + * - com (Physics.vector): The vector to put result into. A new vector will be created if not provided. + * + (Physics.vector): The center of mass position + * + * Get center of mass position from list of bodies. + **/ + Physics.body.getCOM = function( bodies, com ){ + // @TODO add a test for this fn + var b + ,pos + ,i + ,l = bodies && bodies.length + ,M = 0 + ; + + com = com || new Physics.vector(); + + if ( !l ){ + return com.zero(); + } + + if ( l === 1 ){ + return com.clone( bodies[0].state.pos ); + } + + com.zero(); + + for ( i = 0; i < l; i++ ){ + b = bodies[ i ]; + pos = b.state.pos; + com.add( pos._[0] * b.mass, pos._[1] * b.mass ); + M += b.mass; + } + + com.mult( 1 / M ); + + return com; + }; + }()); @@ -3866,7 +4210,7 @@ Physics.scratchpad = (function(){ **/ getFarthestHullPoint: function( dir, result ){ - result = result || Physics.vector(); + result = result || new Physics.vector(); // not implemented. return result.set( 0, 0 ); @@ -3888,7 +4232,7 @@ Physics.scratchpad = (function(){ **/ getFarthestCorePoint: function( dir, result, margin ){ - result = result || Physics.vector(); + result = result || new Physics.vector(); // not implemented. return result.set( 0, 0 ); @@ -3904,11 +4248,38 @@ Physics.scratchpad = (function(){ * Geometry helper functions */ +/** + * Physics.geometry.regularPolygonVertices( sides, radius ) -> Array + * - sides (Number): Number of sides the polygon has + * - radius (Number): Size from center to a vertex + * + (Array): A list of [[Vectorish]] objects representing the vertices + * + * Generate a list of vertices for a regular polygon of any number of sides. + **/ +Physics.geometry.regularPolygonVertices = function( sides, radius ){ + var verts = [] + ,angle = Math.PI * 2 / sides + ,a = 0 + ,i + ; + + for ( i = 0; i < sides; i++ ){ + verts.push({ + x: radius * Math.cos( a ) + ,y: radius * Math.sin( a ) + }); + + a += angle; + } + + return verts; +}; + /** * Physics.geometry.isPolygonConvex( hull ) -> Boolean * - hull (Array): Array of ([[Vectorish]]) vertices * + (Boolean): `true` if the polygon is convex. `false` otherwise. - * + * * Determine if polygon hull is convex **/ Physics.geometry.isPolygonConvex = function( hull ){ @@ -3939,7 +4310,7 @@ Physics.geometry.isPolygonConvex = function( hull ){ // edge and retain the last edge // add two to the length to do a full cycle for ( var i = 1; i <= l; ++i ){ - + next.clone( hull[ i % l ] ).vsub( tmp.clone( hull[ (i - 1) % l ] ) ); if ( sign === false ){ @@ -3948,7 +4319,7 @@ Physics.geometry.isPolygonConvex = function( hull ){ sign = prev.cross( next ); } else if ( (sign > 0) ^ (prev.cross( next ) > 0) ){ - + // if the cross products are different signs it's not convex ret = false; break; @@ -3966,13 +4337,13 @@ Physics.geometry.isPolygonConvex = function( hull ){ * Physics.geometry.getPolygonMOI( hull ) -> Number * - hull (Array): Array of ([[Vectorish]]) vertices * + (Number): The polygon's moment of inertia - * + * * Gets the moment of inertia of a convex polygon * * See [List of moments of inertia](http://en.wikipedia.org/wiki/List_of_moments_of_inertia) * for more information. - * - * _Note_: we make the following assumpations: + * + * _Note_: we make the following assumpations: * * mass is unitary (== 1) * * axis of rotation is the origin **/ @@ -4005,7 +4376,7 @@ Physics.geometry.getPolygonMOI = function( hull ){ prev.clone( hull[ 0 ] ); for ( var i = 1; i < l; ++i ){ - + next.clone( hull[ i ] ); tmp = Math.abs( next.cross( prev ) ); @@ -4024,7 +4395,7 @@ Physics.geometry.getPolygonMOI = function( hull ){ * - pt (Vectorish): The point to test * - hull (Array): Array of ([[Vectorish]]) vertices * + (Boolean): `true` if point `pt` is inside the polygon - * + * * Check if point is inside polygon hull. **/ Physics.geometry.isPointInPolygon = function( pt, hull ){ @@ -4057,7 +4428,7 @@ Physics.geometry.isPointInPolygon = function( pt, hull ){ // calculate the sum of angles between vector pairs // from point to vertices for ( var i = 1; i <= l; ++i ){ - + next.clone( hull[ i % l ] ).vsub( point ); ang += next.angle( prev ); prev.swap( next ); @@ -4071,7 +4442,7 @@ Physics.geometry.isPointInPolygon = function( pt, hull ){ * Physics.geometry.getPolygonArea( hull ) -> Number * - hull (Array): Array of ([[Vectorish]]) vertices * + (Number): The area (positive for clockwise ordering) - * + * * Get the signed area of the polygon. **/ Physics.geometry.getPolygonArea = function getPolygonArea( hull ){ @@ -4093,7 +4464,7 @@ Physics.geometry.getPolygonArea = function getPolygonArea( hull ){ prev.clone( hull[ l - 1 ] ); for ( var i = 0; i < l; ++i ){ - + next.clone( hull[ i ] ); ret += prev.cross( next ); @@ -4109,7 +4480,7 @@ Physics.geometry.getPolygonArea = function getPolygonArea( hull ){ * Physics.geometry.getPolygonCentroid( hull ) -> Physics.vector * - hull (Array): Array of ([[Vectorish]]) vertices * + (Physics.vector): The centroid - * + * * Get the coordinates of the centroid. **/ Physics.geometry.getPolygonCentroid = function getPolygonCentroid( hull ){ @@ -4117,7 +4488,7 @@ Physics.geometry.getPolygonCentroid = function getPolygonCentroid( hull ){ var scratch = Physics.scratchpad() ,prev = scratch.vector() ,next = scratch.vector() - ,ret = Physics.vector() + ,ret = new Physics.vector() ,tmp ,l = hull.length ; @@ -4125,20 +4496,20 @@ Physics.geometry.getPolygonCentroid = function getPolygonCentroid( hull ){ if ( l < 2 ){ // it must be a point scratch.done(); - return Physics.vector( hull[0] ); + return new Physics.vector( hull[0] ); } if ( l === 2 ){ // it's a line // get the midpoint scratch.done(); - return Physics.vector((hull[ 1 ].x + hull[ 0 ].x)/2, (hull[ 1 ].y + hull[ 0 ].y)/2 ); + return new Physics.vector((hull[ 1 ].x + hull[ 0 ].x)/2, (hull[ 1 ].y + hull[ 0 ].y)/2 ); } prev.clone( hull[ l - 1 ] ); for ( var i = 0; i < l; ++i ){ - + next.clone( hull[ i ] ); tmp = prev.cross( next ); @@ -4160,7 +4531,7 @@ Physics.geometry.getPolygonCentroid = function getPolygonCentroid( hull ){ * - linePt1 (Vectorish): The first endpoint of the line * - linePt2 (Vectorish): The second endpoint of the line * + (Vector): The closest point - * + * * Get the closest point on a discrete line to specified point. **/ Physics.geometry.nearestPointOnLine = function nearestPointOnLine( pt, linePt1, linePt2 ){ @@ -4177,7 +4548,7 @@ Physics.geometry.nearestPointOnLine = function nearestPointOnLine( pt, linePt1, // oh.. it's a zero vector. So A and B are both the closest. // just use one of them scratch.done(); - return Physics.vector( linePt1 ); + return new Physics.vector( linePt1 ); } lambdaB = - L.dot( A ) / L.normSq(); @@ -4187,21 +4558,20 @@ Physics.geometry.nearestPointOnLine = function nearestPointOnLine( pt, linePt1, // woops.. that means the closest simplex point // isn't on the line it's point B itself scratch.done(); - return Physics.vector( linePt2 ); + return new Physics.vector( linePt2 ); } else if ( lambdaB <= 0 ){ // vice versa scratch.done(); - return Physics.vector( linePt1 ); + return new Physics.vector( linePt1 ); } // guess we'd better do the math now... - p = Physics.vector( linePt2 ).mult( lambdaB ).vadd( A.clone( linePt1 ).mult( lambdaA ) ); + p = new Physics.vector( linePt2 ).mult( lambdaB ).vadd( A.clone( linePt1 ).mult( lambdaA ) ); scratch.done(); return p; }; - // --- // inside: src/core/integrator.js @@ -4220,7 +4590,7 @@ Physics.geometry.nearestPointOnLine = function nearestPointOnLine( pt, linePt1, * - name (String): The name of the integrator to create * - options (Object): The configuration for that integrator ( depends on integrator ). Available options and defaults: - + ```javascript { // drag applied during integration @@ -4246,28 +4616,29 @@ Physics.geometry.nearestPointOnLine = function nearestPointOnLine( pt, linePt1, /** internal * Integrator#init( options ) * - options (Object): The configuration options passed by the factory - * + * * Initialization. Internal use. **/ init: function( options ){ - + /** related to: Physics.util.options * Integrator#options( options ) -> Object * - options (Object): The options to set as an object * + (Object): The options - * - * Set options on this instance. - * + * + * Set options on this instance. + * * Access options directly from the options object. - * + * * Example: * * ```javascript * this.options.someOption; * ``` - * + * **/ this.options = Physics.util.options( defaults ); + this.options( options ); }, /** @@ -4297,7 +4668,7 @@ Physics.geometry.nearestPointOnLine = function nearestPointOnLine( pt, linePt1, * Integrator#integrate( bodies, dt ) -> this * - bodies (Array): List of bodies to integrate * - dt (Number): Timestep size - * + * * Integrate bodies by timestep. * * Will emit `integrate:velocities` and `integrate:positions` @@ -4308,7 +4679,7 @@ Physics.geometry.nearestPointOnLine = function nearestPointOnLine( pt, linePt1, var world = this._world; this.integrateVelocities( bodies, dt ); - + if ( world ){ world.emit('integrate:velocities', { bodies: bodies, @@ -4317,7 +4688,7 @@ Physics.geometry.nearestPointOnLine = function nearestPointOnLine( pt, linePt1, } this.integratePositions( bodies, dt ); - + if ( world ){ world.emit('integrate:positions', { bodies: bodies, @@ -4331,7 +4702,7 @@ Physics.geometry.nearestPointOnLine = function nearestPointOnLine( pt, linePt1, /** * Integrator#connect( world ) * - world (Physics.world): The world to connect to - * + * * Connect to a world. * * Extend this when creating integrators if you need to specify pubsub management. @@ -4342,7 +4713,7 @@ Physics.geometry.nearestPointOnLine = function nearestPointOnLine( pt, linePt1, /** * Integrator#disconnect( world ) * - world (Physics.world): The world to disconnect from - * + * * Disconnect from a world. * * Extend this when creating integrators if you need to specify pubsub management. @@ -4354,7 +4725,7 @@ Physics.geometry.nearestPointOnLine = function nearestPointOnLine( pt, linePt1, * Integrator#integrateVelocities( bodies, dt ) * - bodies (Array): List of bodies to integrate * - dt (Number): Timestep size - * + * * Just integrate the velocities. * * Should be overridden when creating integrators. @@ -4368,11 +4739,11 @@ Physics.geometry.nearestPointOnLine = function nearestPointOnLine( pt, linePt1, * Integrator#integratePositions( bodies, dt ) * - bodies (Array): List of bodies to integrate * - dt (Number): Timestep size - * + * * Just integrate the positions. * * Called after [[Integrator#integrateVelocities]]. - * + * * Should be overridden when creating integrators. **/ integratePositions: function( bodies, dt ){ @@ -4383,6 +4754,7 @@ Physics.geometry.nearestPointOnLine = function nearestPointOnLine( pt, linePt1, }()); + // --- // inside: src/core/renderer.js @@ -4397,7 +4769,9 @@ Physics.geometry.nearestPointOnLine = function nearestPointOnLine( pt, linePt1, // width of viewport width: 600, // height of viewport - height: 600 + height: 600, + // automatically resize the renderer + autoResize: true }; /** related to: Physics.util.decorator @@ -4417,6 +4791,8 @@ Physics.geometry.nearestPointOnLine = function nearestPointOnLine( pt, linePt1, width: 600, // height of viewport height: 600 + // automatically resize the renderer + autoResize: true } ``` * @@ -4441,13 +4817,43 @@ Physics.geometry.nearestPointOnLine = function nearestPointOnLine( pt, linePt1, **/ init: function( options ){ - var el = typeof options.el === 'string' ? document.getElementById(options.el) : options.el + var self = this + ,el = typeof options.el === 'string' ? document.getElementById(options.el) : options.el ; - this.options = Physics.util.extend({}, defaults, options); + this.options = Physics.util.options(defaults); + this.options( options ); this.el = el ? el : document.body; + this.container = el && el.parentNode ? el.parentNode : document.body; this.drawMeta = Physics.util.throttle( Physics.util.bind(this.drawMeta, this), this.options.metaRefresh ); + + window.addEventListener('resize', Physics.util.throttle(function(){ + if ( self.options.autoResize ){ + self.resize(); + } + }), 100); + }, + + /** + * Renderer#resize( [width, height] ) -> this + * - width (Number): The width in px + * - height (Number): The height in px + * + * Set the dimensions of the renderer. + * + * If no dimensions are specified it will auto resize. + **/ + resize: function( width, height ){ + + if ( width === undefined && height === undefined ){ + width = this.container.offsetWidth; + height = this.container.offsetHeight; + } + + this.width = width || 0; + this.height = height || 0; + // should be implemented in renderers }, /** @@ -4625,13 +5031,22 @@ Physics.geometry.nearestPointOnLine = function nearestPointOnLine( pt, linePt1, var defaults = { // default timestep - timestep: 1000.0 / 120, + timestep: 6, // maximum number of iterations per step - maxIPF: 16, + maxIPF: 4, webworker: false, // NOT YET IMPLEMENTED // default integrator - integrator: 'verlet' + integrator: 'verlet', + + // is sleeping disabled? + sleepDisabled: false, + // speed at which bodies wake up + sleepSpeedLimit: 0.05, + // variance in position below which bodies fall asleep + sleepVarianceLimit: 0.02, + // time (ms) before sleepy bodies fall asleep + sleepTimeLimit: 500 }; // begin world definitions @@ -4651,12 +5066,22 @@ Physics.geometry.nearestPointOnLine = function nearestPointOnLine( pt, linePt1, * * ```javascript * { - * // default timestep - * timestep: 1000.0 / 120, - * // maximum number of iterations per step - * maxIPF: 16, - * // default integrator - * integrator: 'verlet' + * // default timestep + * timestep: 6, + * // maximum number of iterations per step + * maxIPF: 4, + * + * // default integrator + * integrator: 'verlet', + * + * // is sleeping disabled? + * sleepDisabled: false, + * // speed at which bodies wake up + * sleepSpeedLimit: 0.1, + * // variance in position below which bodies fall asleep + * sleepVarianceLimit: 2, + * // time (ms) before sleepy bodies fall asleep + * sleepTimeLimit: 500 * } * ``` * @@ -4782,7 +5207,7 @@ Physics.geometry.nearestPointOnLine = function nearestPointOnLine( pt, linePt1, var i = 0 ,len = arg && arg.length || 0 - ,thing = len ? arg[ 0 ] : arg + ,thing = Physics.util.isArray( arg ) ? arg[ 0 ] : arg ; if ( !thing ){ @@ -4830,7 +5255,7 @@ Physics.geometry.nearestPointOnLine = function nearestPointOnLine( pt, linePt1, var i = 0 ,len = arg && arg.length || 0 - ,thing = len ? arg[ 0 ] : arg + ,thing = Physics.util.isArray( arg ) ? arg[ 0 ] : arg ; if ( !thing ){ @@ -5009,7 +5434,7 @@ Physics.geometry.nearestPointOnLine = function nearestPointOnLine( pt, linePt1, if ( dt ){ - this._dt = dt; + this._dt = +dt.toPrecision(4); // only keep 4 decimal places of precision otherwise we get rounding errors // calculate the maximum jump in time over which to do iterations this._maxJump = dt * this.options.maxIPF; @@ -5019,6 +5444,22 @@ Physics.geometry.nearestPointOnLine = function nearestPointOnLine( pt, linePt1, return this._dt; }, + /** chainable + * Physics.world#wakeUpAll() -> this + * + (this): for chaining + * + * Wake up all bodies in world. + **/ + wakeUpAll: function(){ + var i = 0 + ,l = this._bodies.length + ; + + for ( i = 0; i < l; i++ ){ + this._bodies[ i ].sleep( false ); + } + }, + /** chainable * Physics.world#addBehavior( behavior ) -> this * - behavior (Behavior): The behavior to add @@ -5250,6 +5691,8 @@ Physics.geometry.nearestPointOnLine = function nearestPointOnLine( pt, linePt1, // the target time for the world time to step to target = time + worldDiff - dt; + this.emit('beforeStep'); + if ( time <= target ){ while ( time <= target ){ @@ -5404,12 +5847,12 @@ Physics.integrator('verlet', function( parent ){ return { - /** + /** * class Verlet < Integrator * * `Physics.integrator('verlet')`. * - * The improved euler integrator. + * The verlet integrator. **/ // extended @@ -5427,6 +5870,8 @@ Physics.integrator('verlet', function( parent ){ ,drag = 1 - this.options.drag ,body = null ,state + ,prevDt = this.prevDt || dt + ,dtMul = (dtdt + dt * prevDt) * 0.5 ; for ( var i = 0, l = bodies.length; i < l; ++i ){ @@ -5435,24 +5880,24 @@ Physics.integrator('verlet', function( parent ){ state = body.state; // only integrate if the body isn't static - if ( body.treatment !== 'static' ){ + if ( body.treatment !== 'static' && !body.sleep( dt ) ){ // Inspired from https://github.com/soulwire/Coffee-Physics // @licence MIT - // + // // v = x - ox // x = x + (v + a * dt * dt) // use the velocity in vel if the velocity has been changed manually if (state.vel.equals( state.old.vel ) && body.started()){ - + // Get velocity by subtracting old position from curr position state.vel.clone( state.pos ).vsub( state.old.pos ); } else { state.old.pos.clone( state.pos ).vsub( state.vel ); - // so we need to scale the value by dt so it + // so we need to scale the value by dt so it // complies with other integration methods state.vel.mult( dt ); } @@ -5465,9 +5910,9 @@ Physics.integrator('verlet', function( parent ){ // Apply acceleration // v += a * dt * dt - state.vel.vadd( state.acc.mult( dtdt ) ); + state.vel.vadd( state.acc.mult( dtMul ) ); - // normalize velocity + // restore velocity state.vel.mult( 1/dt ); // store calculated velocity @@ -5478,7 +5923,7 @@ Physics.integrator('verlet', function( parent ){ // // Angular components - // + // if (state.angular.vel === state.old.angular.vel && body.started()){ @@ -5490,7 +5935,7 @@ Physics.integrator('verlet', function( parent ){ state.angular.vel *= dt; } - state.angular.vel += state.angular.acc * dtdt; + state.angular.vel += state.angular.acc * dtMul; state.angular.vel /= dt; state.old.angular.vel = state.angular.vel; state.angular.acc = 0; @@ -5514,6 +5959,8 @@ Physics.integrator('verlet', function( parent ){ var dtdt = dt * dt ,body = null ,state + ,prevDt = this.prevDt || dt + ,dtcorr = dt/prevDt ; for ( var i = 0, l = bodies.length; i < l; ++i ){ @@ -5522,44 +5969,45 @@ Physics.integrator('verlet', function( parent ){ state = body.state; // only integrate if the body isn't static - if ( body.treatment !== 'static' ){ + if ( body.treatment !== 'static' && !body.sleep() ){ - // so we need to scale the value by dt so it + // so we need to scale the value by dt so it // complies with other integration methods - state.vel.mult( dt ); - + state.vel.mult( dt * dtcorr ); + // Store old position. // xold = x state.old.pos.clone( state.pos ); state.pos.vadd( state.vel ); - // normalize velocity - state.vel.mult( 1/dt ); + // restore velocity + state.vel.mult( 1 / (dt * dtcorr) ); // store calculated velocity state.old.vel.clone( state.vel ); // // Angular components - // + // + + + state.angular.vel *= dt * dtcorr; - - state.angular.vel *= dt; - state.old.angular.pos = state.angular.pos; state.angular.pos += state.angular.vel; - state.angular.vel /= dt; + state.angular.vel /= dt * dtcorr; state.old.angular.vel = state.angular.vel; } } + + this.prevDt = dt; } }; }); - // --- // inside: src/geometries/point.js diff --git a/dist/physicsjs.min.js b/dist/physicsjs.min.js new file mode 100644 index 00000000..08d5e52a --- /dev/null +++ b/dist/physicsjs.min.js @@ -0,0 +1,10 @@ +/** + * PhysicsJS v0.7.0 - 2014-12-08 + * A modular, extendable, and easy-to-use physics engine for javascript + * http://wellcaffeinated.net/PhysicsJS + * + * Copyright (c) 2014 Jasper Palfree + * Licensed MIT + */ +!function(a,b){"object"==typeof exports?module.exports=b.call(a):"function"==typeof define&&define.amd?define(function(){return b.call(a)}):a.Physics=b.call(a)}("undefined"!=typeof window?window:this,function(){"use strict";var a=this,b=a.document,c=function f(){return f.world.apply(f,arguments)};c.util={},function(){c.aabb=function(a,b,c,d){var e={x:0,y:0,hw:0,hh:0};return void 0===a?e:(a&&void 0!==a.x&&(c=b.x,d=b.y,b=a.y,a=a.x),void 0===d&&void 0!==a&&void 0!==b?(e.hw=.5*a,e.hh=.5*b,c&&void 0!==c.x&&(e.x=c.x,e.y=c.y),e):(e.hw=.5*Math.abs(c-a),e.hh=.5*Math.abs(d-b),e.x=.5*(c+a),e.y=.5*(d+b),e))},c.aabb.contains=function(a,b){return b.x>a.x-a.hw&&b.xa.y-a.hh&&b.y=d&&f>=e||f>=c&&e>=f?(c=a.y-a.hh,d=b.y-b.hh,e=a.y+a.hh,f=b.y+b.hh,e>=d&&f>=e||f>=c&&e>=f):!1}}(),function(){var a=1e-4,b=100,d=function(a,b,c){var d=b.normSq()-b.dot(a),e=b.dot(a)-a.normSq();return 0>d?c.clone(b).negate():e>0?c.clone(a).negate():(c.clone(b).vsub(a),c.perp(a.cross(c)>0))},e=function(a){var b,d,e=a.length,f=a[e-2],g=a[e-3],h=c.scratchpad(),i=h.vector().clone(f.pt),j=h.vector().clone(g.pt).vsub(i);return j.equals(c.vector.zero)?h.done({a:f.a,b:f.b}):(b=-j.dot(i)/j.normSq(),d=1-b,h.done(0>=d?{a:g.a,b:g.b}:0>=b?{a:f.a,b:f.b}:{a:i.clone(f.a).mult(d).vadd(j.clone(g.a).mult(b)).values(),b:i.clone(f.b).mult(d).vadd(j.clone(g.b).mult(b)).values()}))},f=function(f,g,h,i){var j,k,l,m,n=!1,o=!1,p=!1,q=[],r=1,s=c.scratchpad(),t=s.vector().clone(g||c.vector.axis[0]),u=s.vector(),v=s.vector(),w=s.vector(),x=s.vector(),y=0;for(m=f(t),r=q.push(m),u.clone(m.pt),t.negate();++y;){if(u.swap(v),m=f(t),r=q.push(m),u.clone(m.pt),i&&i(q),u.equals(c.vector.zero)){n=!0;break}if(!o&&u.dot(t)<=0){if(h)break;o=!0}if(2===r)t=d(u,v,t);else if(o){if(t.normalize(),m=v.dot(t),Math.abs(m-u.dot(t))0,l^u.cross(j)>0)q.shift(),j.perp(!l),t.swap(j);else{if(!(l^k.cross(u)>0)){n=!0;break}q.splice(1,1),k.perp(l),t.swap(j)}if(y>b)return s.done(),{simplex:q,iterations:y,distance:0,maxIterationsReached:!0}}return s.done(),m={overlap:n,simplex:q,iterations:y},p!==!1&&(m.distance=p,m.closest=e(q)),m};c.gjk=f}(),function(){c.statistics={pushRunningAvg:function(a,b,c,d){var e=a-c;return c+=e/b,d+=e*(a-c),[c,d]},pushRunningVectorAvg:function(a,b,c,d){var e=1/b,f=a.get(0)-c.get(0),g=a.get(1)-c.get(1);c.add(f*e,g*e),d&&(f*=a.get(0)-c.get(0),g*=a.get(1)-c.get(1),d.add(f,g))}}}(),function(){var a=function b(a,d,e){return this instanceof b?(this.v=new c.vector,this.o=new c.vector,a instanceof b?void this.clone(a):(a&&this.setTranslation(a),void this.setRotation(d||0,e))):new b(a,d)};a.prototype.setTranslation=function(a){return this.v.clone(a),this},a.prototype.setRotation=function(a,b){return this.cosA=Math.cos(a),this.sinA=Math.sin(a),b?this.o.clone(b):this.o.zero(),this},a.prototype.clone=function(b){return b?(this.setTranslation(b.v),this.cosA=b.cosA,this.sinA=b.sinA,this.o.clone(b.o),this):new a(this)},c.transform=a}(),function(a){var b=Math.sqrt,d=Math.min,e=Math.max,f=(Math.acos,Math.atan2),g=2*Math.PI,h=!!a.Float64Array,i=function j(a,b){return this instanceof j?(this._=h?new Float64Array(5):[],void(a&&(void 0!==a.x||a._&&a._.length)?this.clone(a):(this.recalc=!0,this.set(a,b)))):new j(a,b)};Object.defineProperties(i.prototype,{x:{get:function(){return+this._[0]},set:function(a){a=+a||0,this.recalc=a===this._[0],this._[0]=a}},y:{get:function(){return+this._[1]},set:function(a){a=+a||0,this.recalc=a===this._[1],this._[1]=a}}}),i.prototype.set=function(a,b){return this.recalc=!0,this._[0]=+a||0,this._[1]=+b||0,this},i.prototype.get=function(a){return this._[a]},i.prototype.vadd=function(a){return this.recalc=!0,this._[0]+=a._[0],this._[1]+=a._[1],this},i.prototype.vsub=function(a){return this.recalc=!0,this._[0]-=a._[0],this._[1]-=a._[1],this},i.prototype.add=function(a,b){return this.recalc=!0,this._[0]+=+a||0,this._[1]+=+b||0,this},i.prototype.sub=function(a,b){return this.recalc=!0,this._[0]-=a,this._[1]-=void 0===b?0:b,this},i.prototype.mult=function(a){return this.recalc||(this._[4]*=a*a,this._[3]*=a),this._[0]*=a,this._[1]*=a,this},i.prototype.dot=function(a){return this._[0]*a._[0]+this._[1]*a._[1]},i.prototype.cross=function(a){return-this._[0]*a._[1]+this._[1]*a._[0]},i.prototype.proj=function(a){return this.dot(a)/a.norm()},i.prototype.vproj=function(a){var b=this.dot(a)/a.normSq();return this.clone(a).mult(b)},i.prototype.angle=function(a){var b;if(this.equals(i.zero))return a?a.angle():0/0;for(b=a&&!a.equals(i.zero)?f(this._[1]*a._[0]-this._[0]*a._[1],this._[0]*a._[0]+this._[1]*a._[1]):f(this._[1],this._[0]);b>Math.PI;)b-=g;for(;b<-Math.PI;)b+=g;return b},i.prototype.angle2=function(a,b){for(var c=a._[0]-this._[0],d=a._[1]-this._[1],e=b._[0]-this._[0],h=b._[1]-this._[1],i=f(d*e-c*h,c*e+d*h);i>Math.PI;)i-=g;for(;i<-Math.PI;)i+=g;return i},i.prototype.norm=function(){return this.recalc&&(this.recalc=!1,this._[4]=this._[0]*this._[0]+this._[1]*this._[1],this._[3]=b(this._[4])),this._[3]},i.prototype.normSq=function(){return this.recalc&&(this.recalc=!1,this._[4]=this._[0]*this._[0]+this._[1]*this._[1],this._[3]=b(this._[4])),this._[4]},i.prototype.dist=function(a){var c,d;return b((c=a._[0]-this._[0])*c+(d=a._[1]-this._[1])*d)},i.prototype.distSq=function(a){var b,c;return(b=a._[0]-this._[0])*b+(c=a._[1]-this._[1])*c},i.prototype.perp=function(a){var b=this._[0];return a?(this._[0]=this._[1],this._[1]=-b):(this._[0]=-this._[1],this._[1]=b),this},i.prototype.normalize=function(){var a=this.norm();return 0===a?this:(a=1/a,this._[0]*=a,this._[1]*=a,this._[3]=1,this._[4]=1,this)},i.prototype.transform=function(a){var b=a.sinA,c=a.cosA,d=a.o._[0],e=a.o._[1];return this._[0]-=d,this._[1]-=e,this.set(this._[0]*c-this._[1]*b+d+a.v._[0],this._[0]*b+this._[1]*c+e+a.v._[1])},i.prototype.transformInv=function(a){var b=a.sinA,c=a.cosA,d=a.o._[0],e=a.o._[1];return this._[0]-=d+a.v._[0],this._[1]-=e+a.v._[1],this.set(this._[0]*c+this._[1]*b+d,-this._[0]*b+this._[1]*c+e)},i.prototype.rotate=function(a,b){var c,d,e=0,f=0;return"number"==typeof a?(c=Math.sin(a),d=Math.cos(a),b&&(e=b.x,f=b.y)):(c=a.sinA,d=a.cosA,e=a.o._[0],f=a.o._[1]),this._[0]-=e,this._[1]-=f,this.set(this._[0]*d-this._[1]*c+e,this._[0]*c+this._[1]*d+f)},i.prototype.rotateInv=function(a){return this.set((this._[0]-a.o._[0])*a.cosA+(this._[1]-a.o._[1])*a.sinA+a.o._[0],-(this._[0]-a.o._[0])*a.sinA+(this._[1]-a.o._[1])*a.cosA+a.o._[1])},i.prototype.translate=function(a){return this.vadd(a.v)},i.prototype.translateInv=function(a){return this.vsub(a.v)},i.prototype.clone=function(a){return a?a._?(this.recalc=a.recalc,a.recalc||(this._[3]=a._[3],this._[4]=a._[4]),this._[0]=a._[0],this._[1]=a._[1],this):this.set(a.x,a.y):new i(this)},i.prototype.swap=function(a){var b=this._;return this._=a._,a._=b,b=this.recalc,this.recalc=a.recalc,a.recalc=b,this},i.prototype.values=function(){return{x:this._[0],y:this._[1]}},i.prototype.zero=function(){return this._[3]=0,this._[4]=0,this._[0]=0,this._[1]=0,this},i.prototype.negate=function(a){return void 0!==a?(this._[a]=-this._[a],this):(this._[0]=-this._[0],this._[1]=-this._[1],this)},i.prototype.clamp=function(a,b){return this._[0]=d(e(this._[0],a.x),b.x),this._[1]=d(e(this._[1],a.y),b.y),this.recalc=!0,this},i.prototype.toString=function(){return"("+this._[0]+", "+this._[1]+")"},i.prototype.equals=function(a){return this._[0]===a._[0]&&this._[1]===a._[1]&&this._[2]===a._[2]},i.axis=[new i(1,0),new i(0,1)],i.zero=new i(0,0),c.vector=i}(this),function(a){var b=a.Physics;c.noConflict=function(){return a.Physics===c&&(a.Physics=b),c}}(this);var d=c.util.decorator=function(a,b){var d={},e={},f=function(a,b){var d,e;for(e in b)d=Object.getOwnPropertyDescriptor(b,e),d.get||d.set?Object.defineProperty(a,e,d):c.util.isFunction(d.value)&&(a[e]=d.value);return a},g=Object.getPrototypeOf;"function"!=typeof g&&(g="object"==typeof"test".__proto__?function(a){return a.__proto__}:function(a){return a.constructor.prototype});var h=Object.create;"function"!=typeof h&&(h=function(a){function b(){}return b.prototype=a,new b});var i=function(b,d){return"object"==typeof b?(e=f(e,b),void(e.type=a)):void("type"!==b&&c.util.isFunction(d)&&(e[b]=d))};i(b);var j=function(b,c,i,j){var k,l=e;if("string"!=typeof c)j=i,i=c;else{if(l=d[c],!l)throw'Error: "'+c+'" '+a+" not defined";l=l.prototype}if("function"==typeof i)k=d[b],k?k.prototype=f(k.prototype,i(g(k.prototype))):(k=d[b]=function(a){this.init&&this.init(a)},k.prototype=h(l),k.prototype=f(k.prototype,i(l,k.prototype))),k.prototype.type=a,k.prototype.name=b;else if(j=i||{},k=d[b],!k)throw'Error: "'+b+'" '+a+" not defined";return j?new k(j):void 0};return j.mixin=i,j};c.util.indexOf=function(a,b){for(var c=0,d=a.length;d>c;){if(d--,a[c]===b)return c;if(a[d]===b)return d;c++}return-1},c.util.clearArray=function(a){for(var b=a.length;b--;)a.pop();return a},c.util.throttle=function(a,b,c){var d,e,f=!1,g=function(){clearTimeout(d),f?(f=!1,d=setTimeout(g,b),a.apply(c,e)):d=!1};return c=c||null,function(){f=!0,e=arguments,d||g()}};var e=function(a,b){return c.util.isPlainObject(b)?c.util.extend({},a,b,e):void 0!==b?b:a};return c.util.options=function(a,b){var d,f={},g=[];return d=function(a,d){c.util.extend(b,a,d?e:null);for(var f=0,h=g.length;h>f;++f)g[f](b);return b},d.defaults=function(a,d){return c.util.extend(f,a,d?e:null),c.util.defaults(b,f,d?e:null),f},d.onChange=function(a){g.push(a)},b=b||d,d.defaults(a),d},c.util.pairHash=function(a,b){return a=0|a,b=0|b,(0|a)===(0|b)?-1:0|((0|a)>(0|b)?a<<16|65535&b:b<<16|65535&a)},c.util.bind=Function.prototype.bind?function(a,b,c){return c=Array.prototype.slice.call(arguments,1),Function.prototype.bind.apply(a,c)}:function(a,b,c){return c=Array.prototype.slice.call(arguments,2),function(){return a.apply(b,c.concat(Array.prototype.slice.call(arguments)))}},c.util.find=function(a,b){var c,d,e=a.length;for(c=0;e>c;c++)if(d=a[c],b(d,c,a))return d},c.util.filter=function(a,b){var c,d,e=a.length,f=[];for(c=0;e>c;c++)d=a[c],b(d,c,a)&&f.push(d);return f},function(){function a(a){c.util.clearArray(a),v.length-1?0:-1:a?0:-1}function g(a){var b=this.cache,c=typeof a;if("boolean"===c||null==a)b[a]=!0;else{"number"!==c&&"string"!==c&&(c="object");var d="number"===c?a:y+a,e=b[c]||(b[c]={});"object"===c?(e[d]||(e[d]=[])).push(a):e[d]=!0}}function h(a){var b=-1,c=a.length,e=a[0],f=a[c/2|0],h=a[c-1];if(e&&"object"==typeof e&&f&&"object"==typeof f&&h&&"object"==typeof h)return!1;var i=d();i["false"]=i["null"]=i["true"]=i.undefined=!1;var j=d();for(j.array=a,j.cache=i,j.push=g;++b=u&&k===c.util.indexOf,o=i||n?e():m;if(n){var p=h(o);k=f,o=p}for(;++jb;b++)d=a[b],e=i(0,++f),h[f]=h[e],h[e]=d;return h},c.util.isObject=function(a){return!(!a||!n[typeof a])},c.util.isFunction=j,c.util.isArray=Array.isArray||function(a){return a&&"object"==typeof a&&"number"==typeof a.length&&s.call(a)===p||!1};var C=RegExp("^"+String(s).replace(/[.*+?^${}()|[\]\\]/g,"\\$&").replace(/toString| for [^\]]+/g,".*?")+"$");c.util.isPlainObject=Object.getPrototypeOf?function(a){if(!a||s.call(a)!==q)return!1;var b=a.valueOf,c=k(b)&&(c=Object.getPrototypeOf(b))&&Object.getPrototypeOf(c);return c?a===c||Object.getPrototypeOf(a)===c:l(a)}:l,c.util.uniq=function(a,b,c){return"boolean"!=typeof b&&null!=b&&(c=b,b=!1),m(a,b,c)};var D=function(a,b,c){var d,e=a,f=e;if(!e)return f;var g,h=arguments,i=0,j="number"==typeof c?2:h.length;for(j>2&&"function"==typeof h[j-1]&&(g=h[--j]);++id;){var f=d+e>>>1;c(a[f])=b.maxScratches)throw f},a.prototype={done:function(a){this._active=!1;for(var b=0;j>b;++b)this[b]=0;return h.push(this),a}},b=function k(b){if(b)return k.fn(b);var c=h.pop()||new a;return c._active=!0,c},b.maxScratches=100,b.maxIndex=20,b.fn=function(b){for(var c=[],d=0,e=b.length;e>d;d++)c.push(d);c="a"+c.join(",a");var f=new Function("fn, scratches, Scratch","return function("+c+"){ var scratch = scratches.pop() || new Scratch( scratches );scratch._active = true;return scratch.done( fn(scratch, "+c+") );};");return f(b,h,a)},b.register=function(c,f,h){var i=a.prototype,k=j++,l="_"+c+"Stack",m=h&&h.useFactory;if(c in i)throw g;a.prototype[c]=function(){var a=this[l]||(this[l]=[]),c=0|this[k];if(this[k]=c+1,!this._active)throw d;if(c>=b.maxIndex)throw e;return a[c]||(a[c]=m?f():new f)}},b.register("vector",c.vector),b.register("transform",c.transform),b}(),function(){function a(a){return a._priority_}var b=1;c.scratchpad.register("event",function(){return{}},{useFactory:!0});var d=function e(){return this instanceof e?void 0:new e};d.prototype={on:function(d,e,f,g){var h,i,j;if(this._topics=this._topics||(this._topics={}),c.util.isObject(d)){for(var k in d)this.on(k,d[k],e,f);return this}return h=this._topics[d]||(this._topics[d]=[]),i=e,c.util.isObject(f)?(e=c.util.bind(e,f),e._bindfn_=i,e._one_=i._one_,e._scope_=f):void 0===g&&(g=f),e._priority_=void 0===g?b:g,j=c.util.sortedIndex(h,e,a),h.splice(j,0,e),this},off:function(a,b,d){var e,f;if(!this._topics)return this;if(a===!0)return this._topics={},this;if(c.util.isObject(a)){for(var g in a)this.off(g,a[g]);return this}if(e=this._topics[a],!e)return this;if(b===!0)return this._topics[a]=[],this;for(var h=0,i=e.length;i>h;h++)if(f=e[h],!(f._bindfn_!==b&&f!==b||d&&f._scope_!==d)){e.splice(h,1);break}return this},emit:function(a,b){if(!this._topics)return this;var d,e,f=this._topics[a],g=f&&f.length,h=c.scratchpad();if(!g)return h.done(this);for(e=h.event(),e.topic=a,e.handler=d;g--;)d=f[g],d(b,e),d._one_&&f.splice(g,1);return h.done(this)},one:function(a,b,d){if(c.util.isObject(a)){for(var e in a)this.one(e,a[e],b,d);return this}return b._one_=!0,this.on(a,b,d),this}},c.util.pubsub=d}(),function(a){function b(){return l&&l.now?l.now()+l.timing.navigationStart:Date.now()}function d(){var c;a.requestAnimationFrame(d),j&&(c=b(),c&&k.emit("tick",c))}function e(){return j=!0,this}function f(){return j=!1,this}function g(a){return k.on("tick",a),this}function h(a){return k.off("tick",a),this}function i(){return!!j}var j=!0,k=c.util.pubsub(),l=a.performance;a.requestAnimationFrame?d():j=!1,c.util.ticker={now:b,start:e,stop:f,on:g,off:h,isActive:i}}(this),function(){var a=function(){return!0},b=c.util.indexOf,d=function(a,b){return function(c){return a(c[b])}},e=function(a,d){return function(e){e=d?e[d]:e;var f,g=0;if(c.util.isArray(e)){if(c.util.isArray(a)){if(f=e.length,f!==a.length)return!1;for(;f>g;){if(f--,-1===b(a,e[g])||-1===b(a,e[f]))return!1;g++}return!0}return b(e,a)>-1}return e===a}},f=function(a,b){var c=e(a,b);return function(a){return!c(a)}},g=function(a,d){return function(e){e=d?e[d]:e;var f,g=0;if(c.util.isArray(e)){for(f=e.length;f>g;){if(f--,b(a,e[g])>-1||b(a,e[f])>-1)return!0;g++}return!1}return b(a,e)>-1}},h=function(a,b){var c=g(a,b);return function(a){return!c(a)}},i=function(a){return a=new c.vector(a),function(b){var d=b.aabb();return c.aabb.contains(d,a)}},j=function(a){return a.next?function(b){for(var c=a;c;){if(!c(b))return!1;c=c.next}return!0}:a},k=function(a){return a.next?function(b){for(var c=a;c;){if(c(b))return!0;c=c.next}return!1}:a},l={$eq:e,$ne:f,$in:g,$nin:h,$at:i},m=function n(b,f){var g,h,i,m,o,p;if(f){if("$or"===f||"$and"===f){for(g=0,h=b.length;h>g;++g)p=n(b[g]),o=o?o.next=p:m=p;return"$or"===f?k(m):j(m)}if(g=l[f])return g(b);throw"Unknown query operation: "+f}for(g in b)i=b[g],p="$"===g[0]?n(i,g):c.util.isPlainObject(i)?d(n(i),g):e(i,g),o=o?o.next=p:m=p;return j(m||a)};c.query=m}(this),function(){var a={priority:0};c.behavior=d("behavior",{init:function(b){this.options=c.util.options(a),this.options(b)},applyTo:function(a){return this._targets=a===!0?null:c.util.uniq(a),this},getTargets:function(){return this._targets||(this._world?this._world._bodies:[])},setWorld:function(a){return this.disconnect&&this._world&&this.disconnect(this._world),this._world=a,this.connect&&a&&this.connect(a),this},connect:function(a){this.behave&&a.on("integrate:positions",this.behave,this,this.options.priority)},disconnect:function(a){this.behave&&a.off("integrate:positions",this.behave,this)},behave:null})}(),function(){{var a={hidden:!1,treatment:"dynamic",mass:1,restitution:1,cof:.8,view:null},b=1;2*Math.PI}c.body=d("body",{init:function(d){var e=this,f=c.vector;if(this.options=c.util.options(a,this),this.options.onChange(function(a){e.offset=new f(a.offset)}),this.options(d),this.state={pos:new f(this.x,this.y),vel:new f(this.vx,this.vy),acc:new f,angular:{pos:this.angle||0,vel:this.angularVelocity||0,acc:0},old:{pos:new f,vel:new f,acc:new f,angular:{pos:0,vel:0,acc:0}}},this._sleepAngPosMean=0,this._sleepAngPosVariance=0,this._sleepPosMean=new f,this._sleepPosVariance=new f,this._sleepMeanK=0,delete this.x,delete this.y,delete this.vx,delete this.vy,delete this.angle,delete this.angularVelocity,0===this.mass)throw"Error: Bodies must have non-zero mass";this.uid=b++,this.geometry=c.geometry("point")},sleep:function(a){return a===!0?this.asleep=!0:a===!1?(this.asleep=!1,this._sleepMeanK=0,this._sleepAngPosMean=0,this._sleepAngPosVariance=0,this._sleepPosMean.zero(),this._sleepPosVariance.zero(),this.sleepIdleTime=0):a&&!this.asleep&&this.sleepCheck(a),this.asleep},sleepCheck:function(a){var b=this._world&&this._world.options;if(!(this.sleepDisabled||b&&b.sleepDisabled)){{var d,e,f,g,h,i,j=c.scratchpad();j.vector(),j.vector()}if(a=a||0,g=this.geometry.aabb(),f=Math.max(g.hw,g.hh),this.asleep&&(e=this.state.vel.norm()+Math.abs(f*this.state.angular.vel),d=this.sleepSpeedLimit||b&&b.sleepSpeedLimit||0,e>=d))return this.sleep(!1),j.done();this._sleepMeanK++,h=this._sleepMeanK>1?1/(this._sleepMeanK-1):0,c.statistics.pushRunningVectorAvg(this.state.pos,this._sleepMeanK,this._sleepPosMean,this._sleepPosVariance),i=c.statistics.pushRunningAvg(Math.sin(this.state.angular.pos),this._sleepMeanK,this._sleepAngPosMean,this._sleepAngPosVariance),this._sleepAngPosMean=i[0],this._sleepAngPosVariance=i[1],e=this._sleepPosVariance.norm()+Math.abs(f*Math.asin(i[1])),e*=h,d=this.sleepVarianceLimit||b&&b.sleepVarianceLimit||0,d>=e?(d=this.sleepTimeLimit||b&&b.sleepTimeLimit||0,this.sleepIdleTime=(this.sleepIdleTime||0)+a,this.sleepIdleTime>d&&(this.asleep=!0)):this.sleep(!1),j.done()}},setWorld:function(a){return this.disconnect&&this._world&&this.disconnect(this._world),this._world=a,this.connect&&a&&this.connect(a),this},accelerate:function(a){return"dynamic"===this.treatment&&this.state.acc.vadd(a),this},applyForce:function(a,b){if("dynamic"!==this.treatment)return this;var d,e=c.scratchpad(),f=e.vector();return b&&this.moi&&(d=this.state,f.clone(b),this.state.angular.acc-=f.cross(a)/this.moi),this.accelerate(f.clone(a).mult(1/this.mass)),e.done(),this},getGlobalOffset:function(a){return a=a||new c.vector,a.clone(this.offset).rotate(this.state.angular.pos),a},aabb:function(){var a=this.state.angular.pos,b=c.scratchpad(),d=b.vector(),e=this.geometry.aabb(a);return this.getGlobalOffset(d),e.x+=this.state.pos._[0]+d._[0],e.y+=this.state.pos._[1]+d._[1],b.done(e)},toBodyCoords:function(a){return a.vsub(this.state.pos).rotate(-this.state.angular.pos)},toWorldCoords:function(a){return a.rotate(this.state.angular.pos).vadd(this.state.pos)},recalc:function(){return this}}),c.body.getCOM=function(a,b){var d,e,f,g=a&&a.length,h=0;if(b=b||new c.vector,!g)return b.zero();if(1===g)return b.clone(a[0].state.pos);for(b.zero(),f=0;g>f;f++)d=a[f],e=d.state.pos,b.add(e._[0]*d.mass,e._[1]*d.mass),h+=d.mass;return b.mult(1/h),b}}(),function(){c.geometry=d("geometry",{init:function(a){this.options=c.util.options(),this.options(a),this._aabb=new c.aabb},aabb:function(){return c.aabb.clone(this._aabb)},getFarthestHullPoint:function(a,b){return b=b||new c.vector,b.set(0,0)},getFarthestCorePoint:function(a,b){return b=b||new c.vector,b.set(0,0)}})}(),c.geometry.regularPolygonVertices=function(a,b){var c,d=[],e=2*Math.PI/a,f=0;for(c=0;a>c;c++)d.push({x:b*Math.cos(f),y:b*Math.sin(f)}),f+=e;return d},c.geometry.isPolygonConvex=function(a){var b=c.scratchpad(),d=b.vector(),e=b.vector(),f=b.vector(),g=!0,h=!1,i=a.length;if(!a||!i)return!1;if(3>i)return b.done(),g;d.clone(a[0]).vsub(f.clone(a[i-1]));for(var j=1;i>=j;++j){if(e.clone(a[j%i]).vsub(f.clone(a[(j-1)%i])),h===!1)h=d.cross(e);else if(h>0^d.cross(e)>0){g=!1;break}e.swap(d)}return b.done(),g},c.geometry.getPolygonMOI=function(a){var b,d=c.scratchpad(),e=d.vector(),f=d.vector(),g=0,h=0,i=a.length;if(2>i)return d.done(),0;if(2===i)return b=f.clone(a[1]).distSq(e.clone(a[0])),d.done(),b/12;e.clone(a[0]);for(var j=1;i>j;++j)f.clone(a[j]),b=Math.abs(f.cross(e)),g+=b*(f.normSq()+f.dot(e)+e.normSq()),h+=b,e.swap(f);return d.done(),g/(6*h)},c.geometry.isPointInPolygon=function(a,b){var d=c.scratchpad(),e=d.vector().clone(a),f=d.vector(),g=d.vector(),h=0,i=b.length;if(2>i)return h=e.equals(f.clone(b[0])),d.done(),h;if(2===i)return h=e.angle(f.clone(b[0])),h+=e.angle(f.clone(b[1])),d.done(),Math.abs(h)===Math.PI;f.clone(b[0]).vsub(e);for(var j=1;i>=j;++j)g.clone(b[j%i]).vsub(e),h+=g.angle(f),f.swap(g);return d.done(),Math.abs(h)>1e-6},c.geometry.getPolygonArea=function(a){var b=c.scratchpad(),d=b.vector(),e=b.vector(),f=0,g=a.length;if(3>g)return b.done(),0;d.clone(a[g-1]);for(var h=0;g>h;++h)e.clone(a[h]),f+=d.cross(e),d.swap(e);return b.done(),f/2},c.geometry.getPolygonCentroid=function(a){var b,d=c.scratchpad(),e=d.vector(),f=d.vector(),g=new c.vector,h=a.length;if(2>h)return d.done(),new c.vector(a[0]);if(2===h)return d.done(),new c.vector((a[1].x+a[0].x)/2,(a[1].y+a[0].y)/2);e.clone(a[h-1]);for(var i=0;h>i;++i)f.clone(a[i]),b=e.cross(f),e.vadd(f).mult(b),g.vadd(e),e.swap(f);return b=1/(6*c.geometry.getPolygonArea(a)),d.done(),g.mult(b)},c.geometry.nearestPointOnLine=function(a,b,d){var e,f,g=c.scratchpad(),h=g.vector().clone(a),i=g.vector().clone(b).vsub(h),j=g.vector().clone(d).vsub(h).vsub(i);return j.equals(c.vector.zero)?(g.done(),new c.vector(b)):(e=-j.dot(i)/j.normSq(),f=1-e,0>=f?(g.done(),new c.vector(d)):0>=e?(g.done(),new c.vector(b)):(h=new c.vector(d).mult(e).vadd(i.clone(b).mult(f)),g.done(),h))},function(){var a={drag:0};c.integrator=d("integrator",{init:function(b){this.options=c.util.options(a),this.options(b)},setWorld:function(a){return this.disconnect&&this._world&&this.disconnect(this._world),this._world=a,this.connect&&a&&this.connect(a),this},integrate:function(a,b){var c=this._world;return this.integrateVelocities(a,b),c&&c.emit("integrate:velocities",{bodies:a,dt:b}),this.integratePositions(a,b),c&&c.emit("integrate:positions",{bodies:a,dt:b}),this},connect:null,disconnect:null,integrateVelocities:function(){throw"The integrator.integrateVelocities() method must be overriden"},integratePositions:function(){throw"The integrator.integratePositions() method must be overriden"}})}(),function(){var e={meta:!1,metaRefresh:200,width:600,height:600,autoResize:!0};c.renderer=d("renderer",{init:function(d){var f=this,g="string"==typeof d.el?b.getElementById(d.el):d.el;this.options=c.util.options(e),this.options(d),this.el=g?g:b.body,this.container=g&&g.parentNode?g.parentNode:b.body,this.drawMeta=c.util.throttle(c.util.bind(this.drawMeta,this),this.options.metaRefresh),a.addEventListener("resize",c.util.throttle(function(){f.options.autoResize&&f.resize()}),100)},resize:function(a,b){void 0===a&&void 0===b&&(a=this.container.offsetWidth,b=this.container.offsetHeight),this.width=a||0,this.height=b||0},setWorld:function(a){return this.disconnect&&this._world&&this.disconnect(this._world),this._world=a,this.connect&&a&&this.connect(a),this},render:function(a,b){var c,d;this.beforeRender&&this.beforeRender(),this._world.emit("beforeRender",{renderer:this,bodies:a,meta:b}),this.options.meta&&this.drawMeta(b),this._interpolateTime=b.interpolateTime;for(var e=0,f=a.length;f>e;++e)c=a[e],d=c.view||(c.view=this.createView(c.geometry,c.styles)),c.hidden||this.drawBody(c,d);return this},createView:function(){throw"You must override the renderer.createView() method."},drawMeta:function(){throw"You must override the renderer.drawMeta() method."},drawBody:function(){throw"You must override the renderer.drawBody() method."}})}(),function(){var a=function e(a,b,c){for(var d,f,g=function(){return e(a,b,c)};d=a.shift();)if(f=d.apply(b,c),f&&f.then)return f.then(g)},b={timestep:6,maxIPF:4,webworker:!1,integrator:"verlet",sleepDisabled:!1,sleepSpeedLimit:.05,sleepVarianceLimit:.02,sleepTimeLimit:500},d=function f(a,b){return this instanceof f?void this.init(a,b):new f(a,b)};d.prototype=c.util.extend({},c.util.pubsub.prototype,{init:function(d,e){var f=this;(c.util.isFunction(d)||c.util.isArray(d))&&(e=d,d={}),this._meta={fps:0,ipf:0},this._bodies=[],this._behaviors=[],this._integrator=null,this._renderer=null,this._paused=!1,this._warp=1,this._time=0,this.options=c.util.options(b),this.options.onChange(function(a){f.timestep(a.timestep)}),this.options(d),this.add(c.integrator(this.options.integrator)),c.util.isFunction(e)?a([e],this,[this,c]):c.util.isArray(e)&&a(e,this,[this,c])},options:null,add:function(a){var b=0,d=a&&a.length||0,e=c.util.isArray(a)?a[0]:a;if(!e)return this;do switch(e.type){case"behavior":this.addBehavior(e);break;case"integrator":this.integrator(e);break;case"renderer":this.renderer(e);break;case"body":this.addBody(e);break;default:throw'Error: failed to add item of unknown type "'+e.type+'" to world'}while(++b-1},integrator:function(a){return void 0===a?this._integrator:this._integrator===a?this:(this._integrator&&(this._integrator.setWorld(null),this.emit("remove:integrator",{integrator:this._integrator})),a&&(this._integrator=a,this._integrator.setWorld(this),this.emit("add:integrator",{integrator:this._integrator})),this)},renderer:function(a){return void 0===a?this._renderer:this._renderer===a?this:(this._renderer&&(this._renderer.setWorld(null),this.emit("remove:renderer",{renderer:this._renderer})),a&&(this._renderer=a,this._renderer.setWorld(this),this.emit("add:renderer",{renderer:this._renderer})),this)},timestep:function(a){return a?(this._dt=+a.toPrecision(4),this._maxJump=a*this.options.maxIPF,this):this._dt},wakeUpAll:function(){var a=0,b=this._bodies.length;for(a=0;b>a;a++)this._bodies[a].sleep(!1)},addBehavior:function(a){return this.has(a)?this:(a.setWorld(this),this._behaviors.push(a),this.emit("add:behavior",{behavior:a}),this)},getBehaviors:function(){return[].concat(this._behaviors)},removeBehavior:function(a){var b=this._behaviors;if(a)for(var c=0,d=b.length;d>c;++c)if(a===b[c]){b.splice(c,1),a.setWorld(null),this.emit("remove:behavior",{behavior:a});break}return this},addBody:function(a){return this.has(a)?this:(a.setWorld(this),this._bodies.push(a),this.emit("add:body",{body:a}),this)},getBodies:function(){return[].concat(this._bodies)},removeBody:function(a){var b=this._bodies;if(a)for(var c=0,d=b.length;d>c;++c)if(a===b[c]){b.splice(c,1),a.setWorld(null),this.emit("remove:body",{body:a});break}return this},findOne:function(a){var b=this,d="function"==typeof a?a:c.query(a);return c.util.find(b._bodies,d)||!1},find:function(a){var b=this,d="function"==typeof a?a:c.query(a);return c.util.filter(b._bodies,d)},iterate:function(a){this._integrator.integrate(this._bodies,a)},step:function(a){var b,d,e,f=this._time,g=this._warp,h=1/g,i=this._dt,j=i*h,k=this._maxJump*h,l=this._meta;if(this._paused||void 0===this._animTime)return this._animTime=a||this._animTime||c.util.ticker.now(),this._paused||this.emit("step",l),this;if(a=a||this._animTime+j,b=a-this._animTime,b>k&&(this._animTime=a-k,b=k),d=b*g,e=f+d-i,this.emit("beforeStep"),e>=f)for(;e>=f;)f+=i,this._animTime+=j,this._time=f,this.iterate(i);return l.fps=1e3/(a-this._lastTime),l.ipf=(d/i).toFixed(2),l.interpolateTime=i+e-f,this._lastTime=a,this.emit("step",l),this},warp:function(a){return void 0===a?this._warp:(this._warp=a||1,this)},render:function(){if(!this._renderer)throw"No renderer added to world";return this._renderer.render(this._bodies,this._meta),this.emit("render",{bodies:this._bodies,meta:this._meta,renderer:this._renderer}),this},pause:function(){return this._paused=!0,this.emit("pause"),this},unpause:function(){return this._paused=!1,this.emit("unpause"),this},isPaused:function(){return!!this._paused},destroy:function(){var a=this;a.pause(),this.emit("destroy"),a.off(!0),a.remove(a.getBodies()),a.remove(a.getBehaviors()),a.integrator(null),a.renderer(null) +}}),c.world=d}(),c.integrator("verlet",function(a){return c.body.mixin({started:function(a){return void 0!==a&&(this._started=!0),!!this._started}}),{init:function(b){a.init.call(this,b)},integrateVelocities:function(a,b){for(var c,d=b*b,e=1-this.options.drag,f=null,g=this.prevDt||b,h=.5*(d+b*g),i=0,j=a.length;j>i;++i)f=a[i],c=f.state,"static"===f.treatment||f.sleep(b)?(c.vel.zero(),c.acc.zero(),c.angular.vel=0,c.angular.acc=0):(c.vel.equals(c.old.vel)&&f.started()?c.vel.clone(c.pos).vsub(c.old.pos):(c.old.pos.clone(c.pos).vsub(c.vel),c.vel.mult(b)),e&&c.vel.mult(e),c.vel.vadd(c.acc.mult(h)),c.vel.mult(1/b),c.old.vel.clone(c.vel),c.acc.zero(),c.angular.vel===c.old.angular.vel&&f.started()?c.angular.vel=c.angular.pos-c.old.angular.pos:(c.old.angular.pos=c.angular.pos-c.angular.vel,c.angular.vel*=b),c.angular.vel+=c.angular.acc*h,c.angular.vel/=b,c.old.angular.vel=c.angular.vel,c.angular.acc=0,f.started(!0))},integratePositions:function(a,b){for(var c,d=null,e=this.prevDt||b,f=b/e,g=0,h=a.length;h>g;++g)d=a[g],c=d.state,"static"===d.treatment||d.sleep()||(c.vel.mult(b*f),c.old.pos.clone(c.pos),c.pos.vadd(c.vel),c.vel.mult(1/(b*f)),c.old.vel.clone(c.vel),c.angular.vel*=b*f,c.old.angular.pos=c.angular.pos,c.angular.pos+=c.angular.vel,c.angular.vel/=b*f,c.old.angular.vel=c.angular.vel);this.prevDt=b}}}),c.geometry("point",function(){}),c.body("point",function(a){return{init:function(b){a.init.call(this,b),this.moi=0}}}),c}); \ No newline at end of file diff --git a/dist/renderers/canvas.js b/dist/renderers/canvas.js index 7a83ca9f..d2143702 100644 --- a/dist/renderers/canvas.js +++ b/dist/renderers/canvas.js @@ -1,5 +1,5 @@ /** - * PhysicsJS v0.6.0 - 2014-04-22 + * PhysicsJS v0.7.0 - 2014-12-08 * A modular, extendable, and easy-to-use physics engine for javascript * http://wellcaffeinated.net/PhysicsJS * @@ -25,7 +25,6 @@ * * Additional config options: * - * - debug: Draw debug shapes and bounding boxes. (default: `false`) * - metaEl: HTMLElement to write meta information like FPS and IPF into. (default: autogenerated) * - offset: Offset the shapes by this amount. (default: `{ x: 0, y: 0 }`) * - styles: Styles to use to draw the shapes. (see below) @@ -52,6 +51,37 @@ * } * } * ``` + * + * Styles can also be defined on a per-body basis. Use the "styles" property for a body: + * + * Example: + * + * ```javascript + * Physics.body('circle', { + * // ... + * styles: { + * strokeStyle: '#542437', + * lineWidth: 1, + * fillStyle: '#542437', + * angleIndicator: 'white' + * } + * }); + * ``` + * + * You can also define an image to use for a body: + * + * Example: + * + * ```javascript + * Physics.body('circle', { + * // ... + * styles: { + * src: 'path/to/image.jpg', + * width: 40, + * height: 50 + * } + * }); + * ``` **/ Physics.renderer('canvas', function( proto ){ @@ -78,13 +108,13 @@ var defaults = { - // draw aabbs of bodies for debugging - debug: false, // the element to place meta data into metaEl: null, // default styles of drawn objects styles: { + 'point': colors.blue, + 'circle' : { strokeStyle: colors.blue, lineWidth: 1, @@ -92,6 +122,13 @@ angleIndicator: colors.white }, + 'rectangle' : { + strokeStyle: colors.violet, + lineWidth: 1, + fillStyle: colors.violet, + angleIndicator: colors.white + }, + 'convex-polygon' : { strokeStyle: colors.violet, lineWidth: 1, @@ -102,17 +139,6 @@ offset: { x: 0, y: 0 } }; - // deep copy callback to extend deeper into options - var deep = function( a, b ){ - - if ( Physics.util.isPlainObject( b ) ){ - - return Physics.util.extend({}, a, b, deep ); - } - - return b !== undefined ? b : a; - }; - return { // extended @@ -124,9 +150,11 @@ proto.init.call(this, options); // further options - this.options = Physics.util.extend({}, defaults, this.options, deep); - this.options.offset = Physics.vector( this.options.offset ); - + this.options.defaults( defaults, true ); + this.options.onChange(function(){ + self.options.offset = new Physics.vector( self.options.offset ); + }); + this.options( options, true ); // hidden canvas this.hiddenCanvas = document.createElement('canvas'); @@ -150,6 +178,7 @@ this.el = viewport; } + this.container = this.el.parentNode; this.ctx = viewport.getContext('2d'); this.els = {}; @@ -170,7 +199,12 @@ this._layers = {}; this.addLayer( 'main', this.el ); - this.resize( this.options.width, this.options.height ); + + if ( this.options.autoResize ){ + this.resize(); + } else { + this.resize( this.options.width, this.options.height ); + } }, /** @@ -363,6 +397,7 @@ ,view ,i ,l = bodies.length + ,t = self._interpolateTime ,stack = (l || layer.id !== 'main') ? bodies : self._world._bodies ; @@ -381,6 +416,7 @@ if ( layer.options.follow ){ offset.vsub( layer.options.follow.state.pos ); + offset.sub( layer.options.follow.state.vel.get(0)*t, layer.options.follow.state.vel.get(1)*t ); } if ( clear !== false ){ @@ -446,13 +482,14 @@ resize: function( width, height ){ var layer; + proto.resize.call( this, width, height ); for ( var id in this._layers ){ layer = this._layers[ id ]; if ( layer.options.autoResize ){ - layer.el.width = width; - layer.el.height = height; + layer.el.width = this.width; + layer.el.height = this.height; } } @@ -599,6 +636,80 @@ ctx.fill(); }, + /** + * CanvasRenderer#draw( geometry[, styles, ctx, offset] ) -> this + * - geometry (Geometry): The shape to draw + * - styles (Object): The styles configuration + * - ctx (Canvas2DContext): The canvas context + * - offset (Vectorish): The offset from center + * + * Draw a geometry to a context. + **/ + draw: function( geometry, styles, ctx, offset ){ + + var name = geometry.name + ,x = +(offset && offset.x) + ,y = +(offset && offset.y) + ,w = geometry.aabb().hw + ; + + ctx = ctx || this.ctx; + styles = styles || this.options.styles[ name ] || this.options.styles.circle || {}; + + ctx.save(); + ctx.translate(x, y); + + if (name === 'circle'){ + + this.drawCircle(0, 0, geometry.radius, styles, ctx); + + } else if (name === 'convex-polygon'){ + + this.drawPolygon(geometry.vertices, styles, ctx); + + } else if (name === 'rectangle'){ + + this.drawRect(0, 0, geometry.width, geometry.height, styles, ctx); + + } else if (name === 'compound'){ + + for ( var i = 0, l = geometry.children.length, ch; i < l; i++ ){ + ch = geometry.children[ i ]; + + // translate + ctx.translate(ch.pos.x, ch.pos.y); + // rotate + ctx.rotate(ch.angle); + + this.draw( ch.g, styles, ctx ); + + // unrotate + ctx.rotate(-ch.angle); + // untranslate + ctx.translate(-ch.pos.x, -ch.pos.y); + } + + } else { + + // assume it's a point + this.drawCircle(0, 0, 1, styles, ctx); + } + + if (name !== 'compound' && styles.angleIndicator){ + + ctx.beginPath(); + this.setStyle( styles.angleIndicator, ctx ); + ctx.moveTo(0, 0); + ctx.lineTo(w, 0); + ctx.closePath(); + ctx.stroke(); + } + + ctx.restore(); + + return this; + }, + // extended createView: function( geometry, styles ){ @@ -606,14 +717,12 @@ ,aabb = geometry.aabb() ,hw = aabb.hw + Math.abs(aabb.x) ,hh = aabb.hh + Math.abs(aabb.y) - ,x = hw + 1 - ,y = hh + 1 + ,offset = { x: hw + 1, y: hh + 1 } ,hiddenCtx = this.hiddenCtx ,hiddenCanvas = this.hiddenCanvas - ,name = geometry.name ; - styles = styles || this.options.styles[ name ] || {}; + styles = styles || this.options.styles[ name ] || this.options.styles.circle || {}; // must want an image if ( styles.src ){ @@ -628,40 +737,14 @@ return view; } - x += styles.lineWidth | 0; - y += styles.lineWidth | 0; + offset.x += styles.lineWidth | 0; + offset.y += styles.lineWidth | 0; - // clear + // clear and resize hiddenCanvas.width = 2 * hw + 2 + (2 * styles.lineWidth|0); hiddenCanvas.height = 2 * hh + 2 + (2 * styles.lineWidth|0); - hiddenCtx.save(); - hiddenCtx.translate(x, y); - - if (name === 'circle'){ - - this.drawCircle(0, 0, geometry.radius, styles, hiddenCtx); - - } else if (name === 'convex-polygon'){ - - this.drawPolygon(geometry.vertices, styles, hiddenCtx); - - } else if (name === 'rectangle'){ - - this.drawRect(0, 0, geometry.width, geometry.height, styles, hiddenCtx); - } - - if (styles.angleIndicator){ - - hiddenCtx.beginPath(); - this.setStyle( styles.angleIndicator, hiddenCtx ); - hiddenCtx.moveTo(0, 0); - hiddenCtx.lineTo(hw, 0); - hiddenCtx.closePath(); - hiddenCtx.stroke(); - } - - hiddenCtx.restore(); + this.draw( geometry, styles, hiddenCtx, offset ); view = new Image( hiddenCanvas.width, hiddenCanvas.height ); view.src = hiddenCanvas.toDataURL('image/png'); @@ -679,6 +762,7 @@ drawBody: function( body, view, ctx, offset ){ var pos = body.state.pos + ,os = body.offset ,v = body.state.vel ,t = this._interpolateTime || 0 ,x @@ -691,29 +775,16 @@ ctx = ctx || this.ctx; // interpolate positions - x = pos.x + offset.x + v.x * t; - y = pos.y + offset.y + v.y * t; + x = pos._[0] + offset.x + v._[0] * t; + y = pos._[1] + offset.y + v._[1] * t; ang = body.state.angular.pos + body.state.angular.vel * t; ctx.save(); ctx.translate( x, y ); ctx.rotate( ang ); - ctx.drawImage(view, -view.width/2, -view.height/2); + ctx.translate( os._[0], os._[1] ); + ctx.drawImage(view, -view.width/2, -view.height/2, view.width, view.height); ctx.restore(); - - if ( this.options.debug ){ - aabb = body.aabb(); - // draw bounding boxes - this.drawRect( aabb.x, aabb.y, 2 * aabb.hw, 2 * aabb.hh, 'rgba(0, 0, 255, 0.3)' ); - - // draw also paths - body._debugView = body._debugView || this.createView(body.geometry, 'rgba(255, 0, 0, 0.5)'); - ctx.save(); - ctx.translate(pos.x + offset.x, pos.y + offset.y); - ctx.rotate(body.state.angular.pos); - ctx.drawImage(body._debugView, -body._debugView.width * 0.5, -body._debugView.height * 0.5); - ctx.restore(); - } }, // extended diff --git a/dist/renderers/debug.js b/dist/renderers/debug.js new file mode 100644 index 00000000..e0b49de6 --- /dev/null +++ b/dist/renderers/debug.js @@ -0,0 +1,437 @@ +/** + * PhysicsJS v0.7.0 - 2014-12-08 + * A modular, extendable, and easy-to-use physics engine for javascript + * http://wellcaffeinated.net/PhysicsJS + * + * Copyright (c) 2014 Jasper Palfree + * Licensed MIT + */ +(function (root, factory) { + if (typeof define === 'function' && define.amd) { + define(['physicsjs','../renderers/canvas'], factory); + } else if (typeof exports === 'object') { + module.exports = factory.apply(root, ['physicsjs','../renderers/canvas'].map(require)); + } else { + factory.call(root, root.Physics); + } +}(this, function (Physics) { + 'use strict'; + /* + * @requires renderers/canvas + */ + /** + * class DebugRenderer < Renderer + * + * Physics.renderer('debug') + * + * Extends canvas renderer with special debugging functionality. + * + * Additional config options: + * + * - metaEl: HTMLElement to write meta information like FPS and IPF into. (default: autogenerated) + * - offset: Offset the shapes by this amount. (default: `{ x: 0, y: 0 }`) + * - styles: Styles to use to draw the shapes. (see below) + * - drawAABB: whether or not to draw bounding boxes. (default: `true`) + * - drawRealPosition: whether or not to draw the non-interpolated position of bodies. (default: `false`) + * - drawIntervals: whether or not to draw the broadphase (sweep-prune) intervals. (default: `false`) + * - drawContacts: whether or not to draw contact points. (default: `false`) + * - drawSleepState: whether or not to highlight sleeping bodies. (default: `false`) + * - drawBodyState: whether or not to show body position and velocity. (default: `false`) + * - aabbColor: the color of AABBs + * - realBodyStyle: styles used to draw the image of the body at its true non-interpolated position + * - intervalMinColor: color of interval minima + * - intervalMaxColor: color of interval maxima + * - mtvColor: color of minimum transit vector for contacts (overlaps) + * - contactColor: color of contact points + * + * The styles property should contain _default_ styles for each shape you want to draw. + * + * Example: + * + * ```javascript + * styles: { + * + * 'circle' : { + * strokeStyle: '#542437', + * lineWidth: 1, + * fillStyle: '#542437', + * angleIndicator: 'white' + * }, + * + * 'convex-polygon' : { + * strokeStyle: '#542437', + * lineWidth: 1, + * fillStyle: '#542437', + * angleIndicator: 'white' + * } + * } + * ``` + **/ + Physics.renderer('debug', 'canvas', function( parent, proto ){ + + if ( !document ){ + // must be in node environment + return {}; + } + + function format( num ){ + return (num >= 0 ? ' ' : '') + num.toPrecision(2); + } + + var defaults = { + + // the element to place meta data into + metaEl: null, + offset: { x: 0, y: 0 }, + // draw aabbs of bodies for debugging + drawAABB: true, + drawRealPosition: false, + drawIntervals: false, + drawContacts: false, + drawSleepState: false, + drawBodyState: false, + + // *** colors + // color of the aabbs + aabbColor: 'rgba(0, 0, 255, 0.1)', + // styles used to draw the image of the body at its true non-interpolated position + realBodyStyle: 'rgba(255, 0, 0, 0.5)', + // colors for intervals + intervalMinColor: 'rgba( 0, 0, 200, 0.1 )', + intervalMaxColor: 'rgba( 200, 0, 0, 0.1 )', + + // contacts + mtvColor: '#c00', + contactColor: '#0c0' + }; + + return { + + // extended + init: function( options ){ + + var self = this; + + // call parent init + parent.init.call(this, options); + + this.options.defaults( defaults, true ); + this.options( options, true ); + + // init debug layer + this.addLayer('debug', null, { + zIndex: 2 + }); + + this.layer('debug').render = function(){ + + var intr, i, l, cols, xy; + + this.el.width = this.el.width; + + if ( self.options.drawIntervals && self._intervals ){ + for ( xy = 0; xy < 2; xy++ ){ + intr = self._intervals[ xy ]; + for ( i = 0, l = intr.length; i < l; i++ ){ + + self.drawInterval( intr[ i ], this.ctx ); + } + } + } + + if ( self.options.drawContacts && self._collisions ){ + cols = self._collisions; + for ( i = 0, l = cols.length; i < l; i++ ){ + self.drawContact( cols[ i ], this.ctx ); + } + } + }; + + if ( window.dat ){ + this.initGui(); + } + }, + + // extended + connect: function( world ){ + + world.on('sweep-prune:intervals', this.storeIntervals, this ); + world.on('collisions:detected', this.storeCollisions, this ); + + this.updateGui(); + }, + + // extended + disconnect: function( world ){ + + world.off('sweep-prune:intervals', this.storeIntervals, this ); + world.off('collisions:detected', this.storeCollisions, this ); + + }, + + storeIntervals: function( intervals ){ + + this._intervals = intervals; + }, + + storeCollisions: function( data ){ + + this._collisions = data.collisions; + }, + + reset: function(){ + + this._intervals = false; + this._collisions = false; + }, + + drawInterval: function( intr, ctx ){ + + var scratch = Physics.scratchpad() + ,opts = this.options + ,x = intr.tracker.body.state.pos.x + intr.tracker.body.offset.x + ,y = intr.tracker.body.state.pos.y + intr.tracker.body.offset.y + ,from = scratch.vector().set( intr.val.x, 0 ) + ,to = scratch.vector().set( intr.val.x, y ) + ; + + this.drawLine( from, to, opts[ intr.type ? 'intervalMaxColor' : 'intervalMinColor' ], ctx ); + this.drawCircle( from.x, from.y, 4, opts[ intr.type ? 'intervalMaxColor' : 'intervalMinColor' ], ctx ); + + from.set( 0, intr.val.y ); + to.set( x, intr.val.y ); + + this.drawLine( from, to, opts[ intr.type ? 'intervalMaxColor' : 'intervalMinColor' ], ctx ); + this.drawCircle( from.x, from.y, 4, opts[ intr.type ? 'intervalMaxColor' : 'intervalMinColor' ], ctx ); + + scratch.done(); + }, + + drawContact: function( c, ctx ){ + + var scratch = Physics.scratchpad() + ,from = scratch.vector().clone( c.pos ).vadd( c.bodyA.state.pos ) + ,to = scratch.vector().clone( from ).vsub( scratch.vector().clone( c.mtv ) ) + ,opts = this.options + ; + + this.drawLine( from, to, opts.mtvColor, ctx ); + this.drawCircle( from.x, from.y, 2, opts.contactColor, ctx ); + + scratch.done(); + }, + + // init the dat.gui settings + initGui: function(){ + + var self = this + ,gui = this.gui = new window.dat.GUI({ autoPlace: false }) + ,op = this.options + ,getset + ,f + ; + + getset = { + get timestep(){ + return self._world ? self._world.timestep() : 6; + } + ,set timestep( dt ){ + if ( self._world ) { + self._world.timestep( dt ); + } + } + ,get warp(){ + return self._world ? self._world.warp() : 1; + } + ,set warp( w ){ + if ( self._world ) { + self._world.warp( w ); + } + } + ,get maxIPF(){ + return self._world ? self._world.options.maxIPF : 16; + } + ,set maxIPF( m ){ + if ( self._world ){ + self._world.options({ maxIPF: m }); + } + } + ,get sleepDisabled(){ + return self._world ? self._world.options.sleepDisabled : false; + } + ,set sleepDisabled( t ){ + if ( self._world ){ + self._world.options.sleepDisabled = t; + if ( t ){ + self._world.wakeUpAll(); + } + } + } + ,get sleepTimeLimit(){ + return self._world ? self._world.options.sleepTimeLimit : 500; + } + ,set sleepTimeLimit( t ){ + if ( self._world ){ + self._world.options.sleepTimeLimit = t; + } + } + ,get sleepSpeedLimit(){ + return self._world ? self._world.options.sleepSpeedLimit : 0.005; + } + ,set sleepSpeedLimit( t ){ + if ( self._world ){ + self._world.options.sleepSpeedLimit = t; + } + } + ,get sleepVarianceLimit(){ + return self._world ? self._world.options.sleepVarianceLimit : 0.2; + } + ,set sleepVarianceLimit( t ){ + if ( self._world ){ + self._world.options.sleepVarianceLimit = t; + } + } + ,get integrator(){ + return self._world ? self._world.integrator().name : 'verlet'; + } + ,set integrator( t ){ + var intr; + if ( self._world ){ + intr = self._world.integrator(); + self._world.integrator( Physics.integrator(t, Physics.util.extend({}, intr, null)) ); + } + } + }; + + function pauseWorld(){ + if ( self._world ){ + if ( self._world.isPaused() ){ + self._world.unpause(); + } else { + self._world.pause(); + } + } + } + + f = gui.addFolder( 'General' ); + f.add( getset, 'integrator', [ 'improved-euler', 'verlet', 'velocity-verlet' ]); + f.add( getset, 'timestep', 1, 20).step( 1 ); + f.add( getset, 'maxIPF', 1, 100).step( 1 ); + f.add( getset, 'warp', 0.01, 2); + f.add( getset, 'sleepDisabled'); + f.add( getset, 'sleepTimeLimit', 1, 10000).step( 10 ); + f.add( getset, 'sleepSpeedLimit', 0.001, 1); + f.add( getset, 'sleepVarianceLimit', 0.01, 1); + f.add( { pause: pauseWorld }, 'pause'); + f.open(); + + f = gui.addFolder( 'Draw Options' ); + f.add( op, 'drawAABB' ); + f.add( op, 'drawRealPosition' ); + f.add( op, 'drawIntervals' ); + f.add( op, 'drawContacts' ); + f.add( op, 'drawSleepState' ); + f.add( op, 'drawBodyState' ); + f.open(); + + f = gui.addFolder( 'Colors' ); + f.addColor( op, 'aabbColor' ); + f.addColor( op, 'realBodyStyle' ); + f.addColor( op, 'intervalMinColor' ); + f.addColor( op, 'intervalMaxColor' ); + f.addColor( op, 'mtvColor' ); + f.addColor( op, 'contactColor' ); + + gui.domElement.style.zIndex = '100'; + gui.domElement.style.position = 'absolute'; + gui.domElement.style.top = '0'; + gui.domElement.style.left = '0'; + this.el.parentNode.appendChild( gui.domElement ); + }, + + // update the dat.gui parameters + updateGui: function(){ + var gui = this.gui; + // Iterate over all controllers + for (var i in gui.__controllers) { + gui.__controllers[i].updateDisplay(); + } + }, + + // extended + drawBody: function( body, view, ctx, offset ){ + + var pos = body.state.pos + ,os = body.offset + ,v = body.state.vel + ,t = this._interpolateTime || 0 + ,x + ,y + ,ang + ,aabb + ; + + offset = offset || this.options.offset; + ctx = ctx || this.ctx; + + // interpolate positions + x = pos._[0] + offset.x + v._[0] * t; + y = pos._[1] + offset.y + v._[1] * t; + ang = body.state.angular.pos + body.state.angular.vel * t; + + ctx.save(); + ctx.translate( x, y ); + ctx.rotate( ang ); + this.drawCircle( 0, 0, 2, 'red' ); + ctx.translate( os.x, os.y ); + ctx.drawImage(view, -view.width/2, -view.height/2, view.width, view.height); + ctx.restore(); + + if ( this.options.drawAABB ){ + aabb = body.aabb(); + // draw bounding boxes + this.drawRect( aabb.x, aabb.y, 2 * aabb.hw, 2 * aabb.hh, this.options.aabbColor ); + } + + if ( this.options.drawRealPosition ){ + // draw the non-interpolated body position + body._debugView = body._debugView || this.createView(body.geometry, this.options.realBodyStyle); + ctx.save(); + ctx.translate(pos.x + offset.x, pos.y + offset.y); + ctx.rotate(body.state.angular.pos); + ctx.translate( os.x, os.y ); + ctx.drawImage(body._debugView, -body._debugView.width * 0.5, -body._debugView.height * 0.5); + ctx.restore(); + } + + if ( this.options.drawSleepState && body.sleep() ){ + aabb = aabb || body.aabb(); + body._sleepView = body._sleepView || this.createView(body.geometry, 'rgba(100,100,100,0.3)'); + ctx.save(); + ctx.globalCompositeOperation = 'color'; + ctx.translate( x, y ); + ctx.rotate( ang ); + ctx.translate( os.x, os.y ); + ctx.drawImage(body._sleepView, -view.width/2, -view.height/2, view.width, view.height); + // ctx.globalCompositeOperation = ''; + ctx.restore(); + } + + if ( this.options.drawBodyState ){ + ctx.strokeStyle = 'black'; + ctx.shadowColor = '#fff'; + ctx.shadowBlur = 4; + ctx.font = '12px monospace'; + ctx.strokeText('r: ('+x.toFixed(0)+', '+y.toFixed(0)+')', x, y-8); + ctx.strokeText('v: ('+format(v.x)+', '+format(v.y)+')', x, y+12); + ctx.strokeText('o: ('+format(os.x)+', '+format(os.y)+')', x, y+26); + ctx.shadowBlur = 0; + ctx.shadowColor = ''; + } + } + }; + }); + + // end module: renderers/debug.js + return Physics; +}));// UMD \ No newline at end of file diff --git a/dist/renderers/dom.js b/dist/renderers/dom.js index 95a12ff7..12d36033 100644 --- a/dist/renderers/dom.js +++ b/dist/renderers/dom.js @@ -1,5 +1,5 @@ /** - * PhysicsJS v0.6.0 - 2014-04-22 + * PhysicsJS v0.7.0 - 2014-12-08 * A modular, extendable, and easy-to-use physics engine for javascript * http://wellcaffeinated.net/PhysicsJS * @@ -69,6 +69,7 @@ var classpfx = 'pjs-' ,px = 'px' ,cssTransform = pfx('transform') + ,borderRadius = pfx('borderRadius') ; var newEl = function( node, content ){ @@ -111,6 +112,36 @@ viewport.appendChild(stats); } + + if ( this.options.autoResize ){ + this.resize(); + } else { + this.resize( this.options.width, this.options.height ); + } + }, + + // extended + resize: function( width, height ){ + + proto.resize.call( this, width, height ); + this.el.style.width = this.width + px; + this.el.style.height = this.height + px; + }, + + /** internal + * DomRenderer#pointProperties( el, geometry ) + * - el (HTMLElement): The element + * - geometry (Geometry): The body's geometry + * + * Set dom element style properties for a point. + **/ + pointProperties: function( el, geometry ){ + + el.style.width = '2px'; + el.style.height = '2px'; + el.style.marginLeft = '-1px'; + el.style.marginTop = '-1px'; + el.style[ borderRadius ] = '50%'; }, /** internal @@ -128,6 +159,7 @@ el.style.height = (aabb.hh * 2) + px; el.style.marginLeft = (-aabb.hw) + px; el.style.marginTop = (-aabb.hh) + px; + el.style[ borderRadius ] = '50%'; }, /** internal @@ -151,6 +183,7 @@ createView: function( geometry ){ var el = newEl() + ,chel ,fn = geometry.name + 'Properties' ; @@ -159,7 +192,23 @@ el.style.top = '0px'; el.style.left = '0px'; - if (this[ fn ]){ + if ( geometry.name === 'compound' ){ + + for ( var i = 0, l = geometry.children.length, ch; i < l; i++ ){ + ch = geometry.children[ i ]; + chel = newEl(); + chel.className = classpfx + geometry.name + ' ' + classpfx + 'child'; + chel.style.position = 'absolute'; + chel.style.top = '0px'; + chel.style.left = '0px'; + if ( this[ ch.g.name + 'Properties' ] ){ + this[ ch.g.name + 'Properties' ](chel, ch.g); + } + chel.style[cssTransform] = 'translate('+ch.pos._[0]+'px,'+ch.pos._[1]+'px) rotate('+ ch.angle +'rad)'; + el.appendChild( chel ); + } + + } else if ( this[ fn ] ){ this[ fn ](el, geometry); } @@ -177,8 +226,8 @@ // extended disconnect: function( world ){ - world.off( 'add:body', this.attach ); - world.off( 'remove:body', this.detach ); + world.off( 'add:body', this.attach, this ); + world.off( 'remove:body', this.detach, this ); }, /** @@ -234,6 +283,7 @@ var pos = body.state.pos ,v = body.state.vel + ,os = body.offset ,x ,y ,ang @@ -241,10 +291,10 @@ ; // interpolate positions - x = pos.x + v.x * t; - y = pos.y + v.y * t; + x = pos._[0] + v._[0] * t; + y = pos._[1] + v._[1] * t; ang = body.state.angular.pos + body.state.angular.vel * t; - view.style[cssTransform] = 'translate('+x+'px,'+y+'px) rotate('+ ang +'rad)'; + view.style[cssTransform] = 'translate('+x+'px,'+y+'px) rotate('+ ang +'rad) translate('+os._[0]+'px,'+os._[1]+'px)'; } }; }); diff --git a/dist/renderers/pixi-renderer.js b/dist/renderers/pixi-renderer.js index 6ea8223e..f795a44a 100644 --- a/dist/renderers/pixi-renderer.js +++ b/dist/renderers/pixi-renderer.js @@ -1,5 +1,5 @@ /** - * PhysicsJS v0.6.0 - 2014-04-22 + * PhysicsJS v0.7.0 - 2014-12-08 * A modular, extendable, and easy-to-use physics engine for javascript * http://wellcaffeinated.net/PhysicsJS * @@ -8,14 +8,17 @@ */ (function (root, factory) { if (typeof define === 'function' && define.amd) { - define(['physicsjs'], factory); + define(['physicsjs','pixi'], factory); } else if (typeof exports === 'object') { - module.exports = factory.apply(root, ['physicsjs'].map(require)); + module.exports = factory.apply(root, ['physicsjs','pixi'].map(require)); } else { factory.call(root, root.Physics); } }(this, function (Physics) { 'use strict'; + /* + * @requires pixi.js + */ /** * class PixiRenderer < Renderer * @@ -25,7 +28,6 @@ * * Additional config options: * - * - debug: Draw debug shapes and bounding boxes. (default: `false`) * - metaEl: HTMLElement to write meta information like FPS and IPF into. (default: autogenerated) * - offset: Offset the shapes by this amount. (default: `{ x: 0, y: 0 }`) * - styles: Styles to use to draw the shapes. (see below) @@ -43,7 +45,9 @@ * strokeStyle: '0xE8900C', * lineWidth: 3, * fillStyle: '0xD5DE4C', - * angleIndicator: '0xE8900C' + * angleIndicator: '0xE8900C', + * strokeAlpha: 1, + * fillAlpha: 1 * }, * * 'convex-polygon' : { @@ -54,6 +58,38 @@ * } * } * ``` + * + * Styles can also be defined on a per-body basis. Use the "styles" property for a body: + * + * Example: + * + * ```javascript + * Physics.body('circle', { + * // ... + * styles: { + * strokeStyle: '0x542437', + * lineWidth: 1, + * fillStyle: '0x542437', + * angleIndicator: '0xFFFFFF' + * } + * }); + * ``` + * + * You can also define an image to use for a body: + * + * Example: + * + * ```javascript + * Physics.body('circle', { + * // ... + * styles: { + * src: 'path/to/image.jpg', + * width: 40, + * height: 50, + * anchor: { x: 0.5, y: 0.5 } + * } + * }); + * ``` **/ /* global PIXI */ Physics.renderer('pixi', function( parent ){ @@ -63,84 +99,172 @@ return {}; } - var Pi2 = Math.PI * 2; - - var defaults = { - - // draw aabbs of bodies for debugging - debug: false, - // the element to place meta data into - metaEl: null, - offset: { x: 0, y: 0 }, - // Provide some default colours - styles: { - // Defines the default canvas colour - 'color': '0x66FF99', - - 'point' : '0xE8900C', - - 'circle' : { - strokeStyle: '0xE8900C', - lineWidth: 3, - fillStyle: '0xD5DE4C', - angleIndicator: '0xE8900C' - }, - - 'convex-polygon' : { - strokeStyle: '0xE8900C', - lineWidth: 3, - fillStyle: '0xD5DE4C', - angleIndicator: '0xE8900C' - } + var Pi2 = Math.PI * 2 + ,colors = { + white: '0xFFFFFF' + ,violet: '0x542437' + ,blue: '0x53777A' } - }; - - // deep copy callback to extend deeper into options - var deep = function( a, b ){ - - if ( Physics.util.isPlainObject( b ) ){ - - return Physics.util.extend({}, a, b, deep ); + ,fontStyles = { + font: "18px monospace", + fill: "black", + align: "left" } - return b !== undefined ? b : a; - }; + ,defaults = { + + // the element to place meta data into + metaEl: null, + offset: { x: 0, y: 0 }, + // Provide some default colours + styles: { + // Defines the default canvas colour + 'color': false, + + 'point': colors.blue, + + 'circle' : { + strokeStyle: colors.blue, + lineWidth: 1, + fillStyle: colors.blue, + angleIndicator: colors.white, + fillAlpha: 1, + strokeAlpha: 1, + alpha: 1 + }, + + 'rectangle' : { + strokeStyle: colors.violet, + lineWidth: 1, + fillStyle: colors.violet, + angleIndicator: colors.white, + fillAlpha: 1, + strokeAlpha: 1, + alpha: 1 + }, + + 'convex-polygon' : { + strokeStyle: colors.violet, + lineWidth: 1, + fillStyle: colors.violet, + angleIndicator: colors.white, + fillAlpha: 1, + strokeAlpha: 1, + alpha: 1 + } + } + } + ; return { // extended init: function( options ){ + var self = this + ,el + ,isTransparent + ; + if (typeof PIXI === 'undefined') { - throw "PIXI obj not present - cannot continue "; + throw "PIXI not present - cannot continue"; } // call parent init parent.init.call(this, options); // further options - this.options = Physics.util.extend({}, defaults, this.options, deep); - this.options.offset = Physics.vector( this.options.offset ); + this.options.defaults( defaults, true ); + this.options.onChange(function(){ + self.options.offset = new Physics.vector( self.options.offset ); + }); + this.options( options, true ); + isTransparent = (!this.options.styles.color || this.options.styles.color === 'transparent'); // Hook in PIXI stage here this.stage = new PIXI.Stage(this.options.styles.color); - this.renderer = new PIXI.autoDetectRenderer(this.options.width, this.options.height); // Create empty meta object for use later this.meta = {}; + el = (this.el && this.el.nodeName === 'CANVAS') ? el : null; // add the renderer view element to the DOM according to its type - if ( this.el.nodeName === 'CANVAS' ){ - this.renderer = new PIXI.autoDetectRenderer(this.options.width, this.options.height, this.el); + this.renderer = new PIXI.autoDetectRenderer(this.options.width, this.options.height, { + view: el, + transparent: isTransparent, + resolution: window.devicePixelRatio || 1 + }); + + if ( !el ){ + this.el = this.el || document.body; + // add to passed in element + this.el.appendChild( this.renderer.view ); + } + + if ( this.options.autoResize ){ + this.resize(); } else { - this.renderer = new PIXI.autoDetectRenderer(this.options.width, this.options.height); + this.resize( this.options.width, this.options.height ); + } + }, - if ( this.el !== null ) { - this.el.appendChild(this.renderer.view); - } else { - document.body.appendChild(this.renderer.view); - } + // extended + resize: function( width, height ){ + + parent.resize.call( this, width, height ); + this.renderer.resize( this.width, this.height ); + }, + + // extended + connect: function( world ){ + + world.on( 'add:body', this.attach, this ); + world.on( 'remove:body', this.detach, this ); + }, + + // extended + disconnect: function( world ){ + + world.off( 'add:body', this.attach, this ); + world.off( 'remove:body', this.detach, this ); + }, + + /** + * PixiRenderer#detach( data ) -> this + * - data (PIXI.Graphics|Object): Graphics object or event data (`data.body`) + * + * Event callback to detach a child from the stage + **/ + detach: function( data ){ + + // interpred data as either dom node or event data + var el = (data instanceof PIXI.Graphics && data) || (data.body && data.body.view); + + if ( el ){ + // remove view from dom + this.stage.removeChild( el ); } + + return this; + }, + + /** + * PixiRenderer#attach( data ) -> this + * - data (PIXI.Graphics|Object): Graphics object or event data (`data.body`) + * + * Event callback to attach a child to the stage + **/ + attach: function( data ){ + + // interpred data as either dom node or event data + var el = (data instanceof PIXI.Graphics && data) || (data.body && data.body.view); + + if ( el ){ + // attach to viewport + this.stage.addChild( el ); + } + + return this; }, /** @@ -181,6 +305,7 @@ drawBody: function( body, view ){ var pos = body.state.pos ,v = body.state.vel + ,os = body.offset ,t = this._interpolateTime || 0 ,x ,y @@ -188,12 +313,12 @@ ; // interpolate positions - x = pos.x + v.x * t; - y = pos.y + v.y * t; + x = pos._[0] + v._[0] * t; + y = pos._[1] + v._[1] * t; ang = body.state.angular.pos + body.state.angular.vel * t; - view.position.x = x; - view.position.y = y; + view.position.set( x, y ); + view.pivot.set( -os._[0], -os._[1] ); view.rotation = ang; }, @@ -205,31 +330,86 @@ }, /** - * PixiRenderer#createCircle( x, y, r, style ) -> PIXI.Graphics + * PixiRenderer#setStyles( graphics, styles ) -> PIXI.Graphics + * - graphics (PIXI.Graphics): The graphics object to set styles on + * - styles (Object): The styles configuration + * + (PIXI.Graphics): A graphic object + * + * Set styles on pixi graphics object + **/ + setStyles: function( graphics, styles ){ + + if ( Physics.util.isObject(styles) ){ + + if ( styles.fillStyle && styles.fillStyle !== 'transparent' ){ + graphics.beginFill( styles.fillStyle ); + graphics.fillAlpha = styles.fillAlpha !== undefined ? styles.fillAlpha : 1; + } else { + graphics.beginFill(); + graphics.fillAlpha = 0; + } + + graphics.lineStyle( styles.lineWidth || 0, styles.strokeStyle, styles.strokeAlpha !== undefined ? styles.strokeAlpha : 1 ); + graphics.alpha = styles.alpha !== undefined ? styles.alpha : 1; + + } else { + + if ( styles && styles !== 'transparent' ){ + graphics.beginFill( styles ); + } else { + graphics.beginFill(); + graphics.fillAlpha = 0; + } + + graphics.lineStyle( 0 ); + } + + return graphics; + }, + + /** + * PixiRenderer#createCircle( x, y, r, styles ) -> PIXI.Graphics * - x (Number): The x coord * - y (Number): The y coord * - r (Number): The circle radius - * - style (Object): The styles configuration + * - styles (Object): The styles configuration * + (PIXI.Graphics): A graphic object representing a circle. * * Create a circle for use in PIXI stage **/ - createCircle: function( x, y, r, style ){ + createCircle: function( x, y, r, styles ){ var graphics = new PIXI.Graphics(); - graphics.beginFill(style.fillStyle); - graphics.lineStyle(style.lineWidth, style.strokeStyle); - graphics.drawCircle(x, y, r); - // Center the graphics to the circle - graphics.pivot.x = (x / 2) + (r / 2); - graphics.pivot.y = (y / 2) + (r / 2); + this.setStyles( graphics, styles ); + graphics.drawCircle( x, y, r ); + graphics.endFill(); return graphics; }, /** - * PixiRenderer#createPolygon( verts, style ) -> PIXI.Graphics + * PixiRenderer#createRect( x, y, r, styles ) -> PIXI.Graphics + * - x (Number): The x coord + * - y (Number): The y coord + * - width (Number): The rectangle width + * - height (Number): The rectangle height + * - styles (Object): The styles configuration + * + (PIXI.Graphics): A graphic object representing a circle. + * + * Create a rectangle for use in PIXI stage + **/ + createRect: function( x, y, width, height, styles ){ + + var graphics = new PIXI.Graphics(); + this.setStyles( graphics, styles ); + graphics.drawRect( x, y, width, height ); + graphics.endFill(); + return graphics; + }, + + /** + * PixiRenderer#createPolygon( verts, styles ) -> PIXI.Graphics * - verts (Array): Array of [[Vectorish]] vertices - * - style (Object): The styles configuration + * - styles (Object): The styles configuration * + (PIXI.Graphics): A graphic object representing a polygon. * * Create a polygon for use in PIXI stage @@ -247,8 +427,7 @@ ,graphics = new PIXI.Graphics() ; - graphics.beginFill(styles.fillStyle); - graphics.lineStyle(styles.lineWidth, styles.strokeStyle); + this.setStyles( graphics, styles ); graphics.moveTo(x, y); @@ -269,10 +448,10 @@ }, /** - * PixiRenderer#createLine( from, to, style ) -> PIXI.Graphics + * PixiRenderer#createLine( from, to, styles ) -> PIXI.Graphics * - from (Vectorish): Starting point * - to (Vectorish): Ending point - * - style (Object): The styles configuration + * - styles (Object): The styles configuration * + (PIXI.Graphics): A graphic object representing a polygon. * * Create a line for use in PIXI stage @@ -284,8 +463,7 @@ ; var graphics = new PIXI.Graphics(); - graphics.beginFill(styles.fillStyle); - graphics.lineStyle(styles.lineWidth, styles.strokeStyle); + this.setStyles( graphics, styles ); graphics.moveTo(x, y); @@ -299,58 +477,82 @@ }, // extended - createView: function( geometry ){ + createView: function( geometry, styles, parent ){ var view = null ,aabb = geometry.aabb() ,hw = aabb.hw + Math.abs(aabb.x) ,hh = aabb.hh + Math.abs(aabb.y) - ,x = hw + 1 - ,y = hh + 1 ,name = geometry.name ; - var styles = styles || this.options.styles[ name ]; + parent = parent || this.stage; + styles = styles || this.options.styles[ name ] || this.options.styles.circle || {}; - x += styles.lineWidth | 0; - y += styles.lineWidth | 0; + // must want an image + if ( styles.src ){ + view = PIXI.Sprite.fromImage( styles.src ); + view.anchor.set( 0.5, 0.5 ); + if ( styles.anchor ) { + view.anchor.x = styles.anchor.x; + view.anchor.y = styles.anchor.y; + } + if ( styles.width ){ + view.width = styles.width; + } + if ( styles.height ){ + view.height = styles.height; + } + parent.addChild(view); + return view; + } if (name === 'circle'){ - view = this.createCircle(x, y, geometry.radius, styles); + view = this.createCircle(0, 0, geometry.radius, styles); } else if (name === 'convex-polygon'){ view = this.createPolygon(geometry.vertices, styles); - } - if (styles.angleIndicator){ + } else if (name === 'rectangle'){ - view.beginFill(styles.angleIndicator); - view.moveTo((x / 2), (5 + styles.lineWidth)); - view.lineTo((x / 2) + (geometry.radius / 2), geometry.radius); - // Center the graphics to the circle - view.endFill(); + view = this.createRect(-geometry.width/2, -geometry.height/2, geometry.width, geometry.height, styles); + } else if (name === 'compound'){ - } - if (view) { - this.stage.addChild(view); - return view; + view = new PIXI.Graphics(); + + for ( var i = 0, l = geometry.children.length, ch, chview; i < l; i++ ){ + ch = geometry.children[ i ]; + chview = this.createView( ch.g, styles, view ); + chview.position.set( ch.pos.x, ch.pos.y ); + chview.rotation = ch.angle; + } } else { - throw "Invalid view name passed."; + + // assume it's a point + view = this.createCircle(0, 0, 1, styles); + } + + if ( name !== 'compound' && styles.angleIndicator && styles.angleIndicator !== 'transparent' ){ + + view.lineStyle( styles.lineWidth, styles.angleIndicator ); + view.moveTo( 0, 0 ); + view.lineTo( hw, 0 ); + } + + if ( name !== 'compound' ){ + view.cacheAsBitmap = true; } + parent.addChild(view); + return view; }, // extended drawMeta: function( meta ){ if (!this.meta.loaded){ - // define the font style here - var fontStyles = { - font: "18px Snippet", - fill: "white", - align: "left" - }; + // define the font styles here this.meta.fps = new PIXI.Text('FPS: ' + meta.fps.toFixed(2), fontStyles); this.meta.fps.position.x = 15; this.meta.fps.position.y = 5; diff --git a/docs/debug.json b/docs/debug.json index 0b5723f4..26d1132a 100644 --- a/docs/debug.json +++ b/docs/debug.json @@ -10,6 +10,7 @@ "src/behaviors/sweep-prune.js", "src/behaviors/verlet-constraints.js", "src/bodies/circle.js", + "src/bodies/compound.js", "src/bodies/convex-polygon.js", "src/bodies/point.js", "src/bodies/rectangle.js", @@ -22,18 +23,23 @@ "src/core/renderer.js", "src/core/world.js", "src/geometries/circle.js", + "src/geometries/compound.js", "src/geometries/convex-polygon.js", "src/geometries/point.js", "src/geometries/rectangle.js", "src/integrators/improved-euler.js", + "src/integrators/velocity-verlet-alt.js", + "src/integrators/velocity-verlet.js", "src/integrators/verlet.js", "src/intro.js", "src/math/aabb.js", "src/math/gjk.js", + "src/math/statistics.js", "src/math/transform.js", "src/math/vector.js", "src/outro.js", "src/renderers/canvas.js", + "src/renderers/debug.js", "src/renderers/dom.js", "src/renderers/pixi-renderer.js", "src/util/decorator.js", @@ -178,7 +184,7 @@ "id": "BodyCollisionDetectionBehavior", "type": "class", "superclass": "Behavior", - "description": "`Physics.behavior('body-collision-detection')`.\n\nDetect collisions of bodies.\n\nPublishes collision events to the world as a group of detected collisions per iteration.\n\nThe event data will have a `.collisions` property that is an array of collisions of the form:\n\n```javascript\n{\n bodyA: // the first body\n bodyB: // the second body\n norm: // the normal vector (Vectorish)\n mtv: // the minimum transit vector. (the direction and length needed to extract bodyB from bodyA)\n pos: // the collision point\n overlap: // the amount bodyA overlaps bodyB\n}\n```\n\nAdditional options include:\n- check: channel to listen to for collision candidates (default: `collisions:candidates`). set to `true` to force check every pair of bodies in the world\n- channel: channel to publish events to (default: `collisions:detected`)", + "description": "`Physics.behavior('body-collision-detection')`.\n\nDetect collisions of bodies.\n\nPublishes collision events to the world as a group of detected collisions per iteration.\n\nThe event data will have a `.collisions` property that is an array of collisions of the form:\n\n```javascript\n{\n bodyA: // the first body\n bodyB: // the second body\n norm: // the normal vector (Vectorish)\n mtv: // the minimum transit vector. (the direction and length needed to extract bodyB from bodyA)\n pos: // the collision point relative to bodyA\n overlap: // the amount bodyA overlaps bodyB\n}\n```\n\nAdditional options include:\n- check: channel to listen to for collision candidates (default: `collisions:candidates`). set to `true` to force check every pair of bodies in the world\n- channel: channel to publish events to (default: `collisions:detected`)", "short_description": "`Physics.behavior('body-collision-detection')`.\n", "line": 27, "aliases": [], @@ -211,7 +217,7 @@ "internal": true, "description": "Event callback to check pairs of objects that have been flagged by broad phase for possible collisions.", "short_description": "Event callback to check pairs of objects that have been flagged by broad phase for possible collisions.", - "line": 285, + "line": 366, "aliases": [], "children": [], "file": "src/behaviors/body-collision-detection.js", @@ -248,7 +254,7 @@ "internal": true, "description": "Event callback to check all pairs of objects in the list for collisions", "short_description": "Event callback to check all pairs of objects in the list for collisions", - "line": 325, + "line": 428, "aliases": [], "children": [], "file": "src/behaviors/body-collision-detection.js", @@ -292,7 +298,7 @@ "internal": true, "description": "Event callback to check pairs of objects that have been flagged by broad phase for possible collisions.", "short_description": "Event callback to check pairs of objects that have been flagged by broad phase for possible collisions.", - "line": 285, + "line": 366, "aliases": [], "children": [], "file": "src/behaviors/body-collision-detection.js", @@ -329,7 +335,7 @@ "internal": true, "description": "Event callback to check all pairs of objects in the list for collisions", "short_description": "Event callback to check all pairs of objects in the list for collisions", - "line": 325, + "line": 428, "aliases": [], "children": [], "file": "src/behaviors/body-collision-detection.js", @@ -342,9 +348,9 @@ "id": "BodyImpulseResponseBehavior", "type": "class", "superclass": "Behavior", - "description": "`Physics.behavior('body-impulse-response')`.\n\nResponds to collisions by applying impulses.\n\nAdditional options include:\n- check: channel to listen to for collisions (default: `collisions:detected`).", + "description": "`Physics.behavior('body-impulse-response')`.\n\nResponds to collisions by applying impulses.\n\nAdditional options include:\n- check: channel to listen to for collisions (default: `collisions:detected`).\n- mtvThreshold: apply partial extraction of bodies if the minimum transit vector is less than this value ( default: `1`)\n this will depend on your simulation characteristic length scale\n- bodyExtractDropoff: every body overlap correction (underneith mtvThreshold) will only extract by this fraction (0..1). Helps with stablizing contacts. (default: `0.5`)\n- forceWakeupAboveOverlapThreshold: force bodies to wake up if the overlap is above mtvThreshold ( default: `true` )", "short_description": "`Physics.behavior('body-impulse-response')`.\n", - "line": 11, + "line": 15, "aliases": [], "children": [ { @@ -445,7 +451,7 @@ "internal": true, "description": "Collide two bodies by modifying their positions and velocities to conserve momentum", "short_description": "Collide two bodies by modifying their positions and velocities to conserve momentum", - "line": 54, + "line": 88, "aliases": [], "children": [], "file": "src/behaviors/body-impulse-response.js", @@ -482,7 +488,7 @@ "internal": true, "description": "Event callback to respond to collision data.", "short_description": "Event callback to respond to collision data.", - "line": 225, + "line": 281, "aliases": [], "children": [], "file": "src/behaviors/body-impulse-response.js", @@ -596,7 +602,7 @@ "internal": true, "description": "Collide two bodies by modifying their positions and velocities to conserve momentum", "short_description": "Collide two bodies by modifying their positions and velocities to conserve momentum", - "line": 54, + "line": 88, "aliases": [], "children": [], "file": "src/behaviors/body-impulse-response.js", @@ -633,7 +639,7 @@ "internal": true, "description": "Event callback to respond to collision data.", "short_description": "Event callback to respond to collision data.", - "line": 225, + "line": 281, "aliases": [], "children": [], "file": "src/behaviors/body-impulse-response.js", @@ -777,7 +783,7 @@ "internal": true, "description": "Event callback to check all bodies for collisions with the edge", "short_description": "Event callback to check all bodies for collisions with the edge", - "line": 226, + "line": 227, "aliases": [], "children": [], "file": "src/behaviors/edge-collision-detection.js", @@ -818,7 +824,7 @@ ], "description": "Set the boundaries of the edge.", "short_description": "Set the boundaries of the edge.", - "line": 188, + "line": 189, "aliases": [], "children": [], "file": "src/behaviors/edge-collision-detection.js", @@ -866,7 +872,7 @@ ], "description": "Set the boundaries of the edge.", "short_description": "Set the boundaries of the edge.", - "line": 188, + "line": 189, "aliases": [], "children": [], "file": "src/behaviors/edge-collision-detection.js", @@ -903,7 +909,7 @@ "internal": true, "description": "Event callback to check all bodies for collisions with the edge", "short_description": "Event callback to check all bodies for collisions with the edge", - "line": 226, + "line": 227, "aliases": [], "children": [], "file": "src/behaviors/edge-collision-detection.js", @@ -916,9 +922,9 @@ "id": "InteractiveBehavior", "type": "class", "superclass": "Behavior", - "description": "`Physics.behavior('interactive')`.\n\nUser interaction helper.\n\nUsed to get mouse/touch events and add a mouse grab interaction.\n\nAdditional options include:\n- el: The element of the renderer. What you input as the `el` for the renderer.\n- moveThrottle: The min time between move events (default: `10`).\n- minVel: The minimum velocity clamp [[Vectorish]] (default: { x: -5, y: -5 }) to restrict velocity a user can give to a body\n- maxVel: The maximum velocity clamp [[Vectorish]] (default: { x: 5, y: 5 }) to restrict velocity a user can give to a body\n\nThe behavior also triggers the following events on the world:\n```javascript\n// a body has been grabbed\nworld.on('interact:grab', function( data ){\n data.x; // the x coord\n data.y; // the y coord\n data.body; // the body that was grabbed\n});\n// no body was grabbed, but the renderer area was clicked, or touched\nworld.on('interact:poke', function( data ){\n data.x; // the x coord\n data.y; // the y coord\n});\nworld.on('interact:move', function( data ){\n data.x; // the x coord\n data.y; // the y coord\n data.body; // the body that was grabbed (if applicable)\n});\n// when the viewport is released (mouseup, touchend)\nworld.on('interact:release', function( data ){\n data.x; // the x coord\n data.y; // the y coord\n});\n```", + "description": "`Physics.behavior('interactive')`.\n\nUser interaction helper.\n\nUsed to get mouse/touch events and add grab interactions.\n\nAdditional options include:\n- el: The element of the renderer. What you input as the `el` for the renderer.\n- moveThrottle: The min time between move events (default: `10`).\n- minVel: The minimum velocity clamp [[Vectorish]] (default: { x: -5, y: -5 }) to restrict velocity a user can give to a body\n- maxVel: The maximum velocity clamp [[Vectorish]] (default: { x: 5, y: 5 }) to restrict velocity a user can give to a body\n\nThe behavior also triggers the following events on the world:\n```javascript\n// a body has been grabbed\nworld.on('interact:grab', function( data ){\n data.x; // the x coord\n data.y; // the y coord\n data.body; // the body that was grabbed\n});\n// no body was grabbed, but the renderer area was clicked, or touched\nworld.on('interact:poke', function( data ){\n data.x; // the x coord\n data.y; // the y coord\n});\n// when a mouse or pointer moves\nworld.on('interact:move', function( data ){\n data.x; // the x coord\n data.y; // the y coord\n data.body; // the grabbed body that was moved (if applicable)\n});\n// when the viewport is released (mouseup, touchend)\nworld.on('interact:release', function( data ){\n data.x; // the x coord\n data.y; // the y coord\n data.body; // the body that was grabbed (if applicable)\n});\n```\n\nThe behavior also sets body.isGrabbed = true for any grabbed bodies while they are grabbed.", "short_description": "`Physics.behavior('interactive')`.\n", - "line": 41, + "line": 45, "aliases": [], "children": [], "file": "src/behaviors/interactive.js", @@ -1004,7 +1010,7 @@ "internal": true, "description": "Check each axis for overlaps of bodies AABBs", "short_description": "Check each axis for overlaps of bodies AABBs", - "line": 248, + "line": 257, "aliases": [], "children": [], "file": "src/behaviors/sweep-prune.js", @@ -1100,7 +1106,7 @@ "internal": true, "description": "Get a pair object for the tracker objects", "short_description": "Get a pair object for the tracker objects", - "line": 176, + "line": 181, "aliases": [], "children": [], "file": "src/behaviors/sweep-prune.js", @@ -1120,7 +1126,7 @@ "internal": true, "description": "Simple insertion sort for each axis", "short_description": "Simple insertion sort for each axis", - "line": 107, + "line": 112, "aliases": [], "children": [], "file": "src/behaviors/sweep-prune.js", @@ -1157,7 +1163,7 @@ "internal": true, "description": "Event callback to sweep and publish event if any candidate collisions are found", "short_description": "Event callback to sweep and publish event if any candidate collisions are found", - "line": 488, + "line": 487, "aliases": [], "children": [], "file": "src/behaviors/sweep-prune.js", @@ -1194,7 +1200,7 @@ "internal": true, "description": "Event callback to add body to list of those tracked by sweep and prune", "short_description": "Event callback to add body to list of those tracked by sweep and prune", - "line": 394, + "line": 393, "aliases": [], "children": [], "file": "src/behaviors/sweep-prune.js", @@ -1231,7 +1237,7 @@ "internal": true, "description": "Event callback to remove body from list of those tracked", "short_description": "Event callback to remove body from list of those tracked", - "line": 433, + "line": 432, "aliases": [], "children": [], "file": "src/behaviors/sweep-prune.js", @@ -1251,7 +1257,7 @@ "internal": true, "description": "Update position intervals on each axis", "short_description": "Update position intervals on each axis", - "line": 358, + "line": 364, "aliases": [], "children": [], "file": "src/behaviors/sweep-prune.js", @@ -1328,7 +1334,7 @@ "internal": true, "description": "Simple insertion sort for each axis", "short_description": "Simple insertion sort for each axis", - "line": 107, + "line": 112, "aliases": [], "children": [], "file": "src/behaviors/sweep-prune.js", @@ -1405,7 +1411,7 @@ "internal": true, "description": "Get a pair object for the tracker objects", "short_description": "Get a pair object for the tracker objects", - "line": 176, + "line": 181, "aliases": [], "children": [], "file": "src/behaviors/sweep-prune.js", @@ -1436,7 +1442,7 @@ "internal": true, "description": "Check each axis for overlaps of bodies AABBs", "short_description": "Check each axis for overlaps of bodies AABBs", - "line": 248, + "line": 257, "aliases": [], "children": [], "file": "src/behaviors/sweep-prune.js", @@ -1456,7 +1462,7 @@ "internal": true, "description": "Update position intervals on each axis", "short_description": "Update position intervals on each axis", - "line": 358, + "line": 364, "aliases": [], "children": [], "file": "src/behaviors/sweep-prune.js", @@ -1493,7 +1499,7 @@ "internal": true, "description": "Event callback to add body to list of those tracked by sweep and prune", "short_description": "Event callback to add body to list of those tracked by sweep and prune", - "line": 394, + "line": 393, "aliases": [], "children": [], "file": "src/behaviors/sweep-prune.js", @@ -1530,7 +1536,7 @@ "internal": true, "description": "Event callback to remove body from list of those tracked", "short_description": "Event callback to remove body from list of those tracked", - "line": 433, + "line": 432, "aliases": [], "children": [], "file": "src/behaviors/sweep-prune.js", @@ -1567,7 +1573,7 @@ "internal": true, "description": "Event callback to sweep and publish event if any candidate collisions are found", "short_description": "Event callback to sweep and publish event if any candidate collisions are found", - "line": 488, + "line": 487, "aliases": [], "children": [], "file": "src/behaviors/sweep-prune.js", @@ -1828,7 +1834,7 @@ ], "description": "Get all constraints.", "short_description": "Get all constraints.", - "line": 415, + "line": 419, "aliases": [], "children": [], "file": "src/behaviors/verlet-constraints.js", @@ -1912,7 +1918,7 @@ "internal": true, "description": "Resolve all constraints.", "short_description": "Resolve all constraints.", - "line": 395, + "line": 399, "aliases": [], "children": [], "file": "src/behaviors/verlet-constraints.js", @@ -1986,7 +1992,7 @@ "internal": true, "description": "Resolve distance constraints.", "short_description": "Resolve distance constraints.", - "line": 329, + "line": 330, "aliases": [], "children": [], "file": "src/behaviors/verlet-constraints.js", @@ -2006,7 +2012,7 @@ "internal": true, "description": "Mix up the constraints.", "short_description": "Mix up the constraints.", - "line": 384, + "line": 388, "aliases": [], "children": [], "file": "src/behaviors/verlet-constraints.js", @@ -2373,7 +2379,7 @@ "internal": true, "description": "Resolve distance constraints.", "short_description": "Resolve distance constraints.", - "line": 329, + "line": 330, "aliases": [], "children": [], "file": "src/behaviors/verlet-constraints.js", @@ -2393,7 +2399,7 @@ "internal": true, "description": "Mix up the constraints.", "short_description": "Mix up the constraints.", - "line": 384, + "line": 388, "aliases": [], "children": [], "file": "src/behaviors/verlet-constraints.js", @@ -2413,7 +2419,7 @@ "internal": true, "description": "Resolve all constraints.", "short_description": "Resolve all constraints.", - "line": 395, + "line": 399, "aliases": [], "children": [], "file": "src/behaviors/verlet-constraints.js", @@ -2443,7 +2449,7 @@ ], "description": "Get all constraints.", "short_description": "Get all constraints.", - "line": 415, + "line": 419, "aliases": [], "children": [], "file": "src/behaviors/verlet-constraints.js", @@ -2467,6 +2473,282 @@ "path": "CircleBody", "outFile": "circle.html" }, + "CompoundBody": { + "id": "CompoundBody", + "type": "class", + "superclass": "Body", + "description": "Physics.body('compound')\n\nNot a body in itself. It's a container to group other bodies. The position of the body is the center of mass.\nIt must have at least one child before being added to the world.\n\nAdditional config options:\n\n- children: Array of [[Body]] objects.\n\nExample:\n\n```javascript\nvar thing = Physics.body('compound', {\n // place the center of mass at (300, 200)\n x: 300,\n y: 200,\n // the center of mass is automatically calculated and used to position the shape\n children: [\n body1,\n body2,\n // ...\n ]\n});\n```", + "short_description": "Physics.body('compound')\n", + "line": 32, + "aliases": [], + "children": [ + { + "id": "CompoundBody#addChild", + "type": "instance method", + "signatures": [ + { + "arguments": [ + { + "name": "body", + "types": [ + "Body" + ], + "description": " The child to add\n\n " + } + ], + "returns": [ + { + "type": "this" + } + ] + } + ], + "arguments": [ + { + "name": "body", + "types": [ + "Body" + ], + "description": " The child to add\n\n " + } + ], + "description": "Add a body as a child.", + "short_description": "Add a body as a child.", + "line": 68, + "aliases": [], + "children": [], + "file": "src/bodies/compound.js", + "name": "addChild", + "name_prefix": "CompoundBody#", + "path": "CompoundBody.prototype.addChild", + "outFile": "compound.html#CompoundBody.prototype.addChild" + }, + { + "id": "CompoundBody#addChildren", + "type": "instance method", + "signatures": [ + { + "arguments": [ + { + "name": "bodies", + "types": [ + "Array" + ], + "description": " The list of children to add\n\n " + } + ], + "returns": [ + { + "type": "this" + } + ] + } + ], + "arguments": [ + { + "name": "bodies", + "types": [ + "Array" + ], + "description": " The list of children to add\n\n " + } + ], + "description": "Add an array of children to the compound.", + "short_description": "Add an array of children to the compound.", + "line": 80, + "aliases": [], + "children": [], + "file": "src/bodies/compound.js", + "name": "addChildren", + "name_prefix": "CompoundBody#", + "path": "CompoundBody.prototype.addChildren", + "outFile": "compound.html#CompoundBody.prototype.addChildren" + }, + { + "id": "CompoundBody#clear", + "type": "instance method", + "signatures": [ + { + "arguments": [], + "returns": [ + { + "type": "this" + } + ] + } + ], + "description": "Remove all children.", + "short_description": "Remove all children.", + "line": 142, + "aliases": [], + "children": [], + "file": "src/bodies/compound.js", + "name": "clear", + "name_prefix": "CompoundBody#", + "path": "CompoundBody.prototype.clear", + "outFile": "compound.html#CompoundBody.prototype.clear" + }, + { + "id": "CompoundBody#refreshGeometry", + "type": "instance method", + "signatures": [ + { + "arguments": [], + "returns": [ + { + "type": "this" + } + ] + } + ], + "description": "If the children's positions change, `refreshGeometry()` should be called to fix the shape.", + "short_description": "If the children's positions change, `refreshGeometry()` should be called to fix the shape.", + "line": 159, + "aliases": [], + "children": [], + "file": "src/bodies/compound.js", + "name": "refreshGeometry", + "name_prefix": "CompoundBody#", + "path": "CompoundBody.prototype.refreshGeometry", + "outFile": "compound.html#CompoundBody.prototype.refreshGeometry" + } + ], + "file": "src/bodies/compound.js", + "subclasses": [], + "name": "CompoundBody", + "path": "CompoundBody", + "outFile": "compound.html" + }, + "CompoundBody#addChild": { + "id": "CompoundBody#addChild", + "type": "instance method", + "signatures": [ + { + "arguments": [ + { + "name": "body", + "types": [ + "Body" + ], + "description": " The child to add\n\n " + } + ], + "returns": [ + { + "type": "this" + } + ] + } + ], + "arguments": [ + { + "name": "body", + "types": [ + "Body" + ], + "description": " The child to add\n\n " + } + ], + "description": "Add a body as a child.", + "short_description": "Add a body as a child.", + "line": 68, + "aliases": [], + "children": [], + "file": "src/bodies/compound.js", + "name": "addChild", + "name_prefix": "CompoundBody#", + "path": "CompoundBody.prototype.addChild", + "outFile": "compound.html#CompoundBody.prototype.addChild" + }, + "CompoundBody#addChildren": { + "id": "CompoundBody#addChildren", + "type": "instance method", + "signatures": [ + { + "arguments": [ + { + "name": "bodies", + "types": [ + "Array" + ], + "description": " The list of children to add\n\n " + } + ], + "returns": [ + { + "type": "this" + } + ] + } + ], + "arguments": [ + { + "name": "bodies", + "types": [ + "Array" + ], + "description": " The list of children to add\n\n " + } + ], + "description": "Add an array of children to the compound.", + "short_description": "Add an array of children to the compound.", + "line": 80, + "aliases": [], + "children": [], + "file": "src/bodies/compound.js", + "name": "addChildren", + "name_prefix": "CompoundBody#", + "path": "CompoundBody.prototype.addChildren", + "outFile": "compound.html#CompoundBody.prototype.addChildren" + }, + "CompoundBody#clear": { + "id": "CompoundBody#clear", + "type": "instance method", + "signatures": [ + { + "arguments": [], + "returns": [ + { + "type": "this" + } + ] + } + ], + "description": "Remove all children.", + "short_description": "Remove all children.", + "line": 142, + "aliases": [], + "children": [], + "file": "src/bodies/compound.js", + "name": "clear", + "name_prefix": "CompoundBody#", + "path": "CompoundBody.prototype.clear", + "outFile": "compound.html#CompoundBody.prototype.clear" + }, + "CompoundBody#refreshGeometry": { + "id": "CompoundBody#refreshGeometry", + "type": "instance method", + "signatures": [ + { + "arguments": [], + "returns": [ + { + "type": "this" + } + ] + } + ], + "description": "If the children's positions change, `refreshGeometry()` should be called to fix the shape.", + "short_description": "If the children's positions change, `refreshGeometry()` should be called to fix the shape.", + "line": 159, + "aliases": [], + "children": [], + "file": "src/bodies/compound.js", + "name": "refreshGeometry", + "name_prefix": "CompoundBody#", + "path": "CompoundBody.prototype.refreshGeometry", + "outFile": "compound.html#CompoundBody.prototype.refreshGeometry" + }, "ConvexPolygonBody": { "id": "ConvexPolygonBody", "type": "class", @@ -2956,7 +3238,7 @@ "id": "BodyCollisionDetectionBehavior", "type": "class", "superclass": "Behavior", - "description": "`Physics.behavior('body-collision-detection')`.\n\nDetect collisions of bodies.\n\nPublishes collision events to the world as a group of detected collisions per iteration.\n\nThe event data will have a `.collisions` property that is an array of collisions of the form:\n\n```javascript\n{\n bodyA: // the first body\n bodyB: // the second body\n norm: // the normal vector (Vectorish)\n mtv: // the minimum transit vector. (the direction and length needed to extract bodyB from bodyA)\n pos: // the collision point\n overlap: // the amount bodyA overlaps bodyB\n}\n```\n\nAdditional options include:\n- check: channel to listen to for collision candidates (default: `collisions:candidates`). set to `true` to force check every pair of bodies in the world\n- channel: channel to publish events to (default: `collisions:detected`)", + "description": "`Physics.behavior('body-collision-detection')`.\n\nDetect collisions of bodies.\n\nPublishes collision events to the world as a group of detected collisions per iteration.\n\nThe event data will have a `.collisions` property that is an array of collisions of the form:\n\n```javascript\n{\n bodyA: // the first body\n bodyB: // the second body\n norm: // the normal vector (Vectorish)\n mtv: // the minimum transit vector. (the direction and length needed to extract bodyB from bodyA)\n pos: // the collision point relative to bodyA\n overlap: // the amount bodyA overlaps bodyB\n}\n```\n\nAdditional options include:\n- check: channel to listen to for collision candidates (default: `collisions:candidates`). set to `true` to force check every pair of bodies in the world\n- channel: channel to publish events to (default: `collisions:detected`)", "short_description": "`Physics.behavior('body-collision-detection')`.\n", "line": 27, "aliases": [], @@ -2989,7 +3271,7 @@ "internal": true, "description": "Event callback to check pairs of objects that have been flagged by broad phase for possible collisions.", "short_description": "Event callback to check pairs of objects that have been flagged by broad phase for possible collisions.", - "line": 285, + "line": 366, "aliases": [], "children": [], "file": "src/behaviors/body-collision-detection.js", @@ -3026,7 +3308,7 @@ "internal": true, "description": "Event callback to check all pairs of objects in the list for collisions", "short_description": "Event callback to check all pairs of objects in the list for collisions", - "line": 325, + "line": 428, "aliases": [], "children": [], "file": "src/behaviors/body-collision-detection.js", @@ -3046,9 +3328,9 @@ "id": "BodyImpulseResponseBehavior", "type": "class", "superclass": "Behavior", - "description": "`Physics.behavior('body-impulse-response')`.\n\nResponds to collisions by applying impulses.\n\nAdditional options include:\n- check: channel to listen to for collisions (default: `collisions:detected`).", + "description": "`Physics.behavior('body-impulse-response')`.\n\nResponds to collisions by applying impulses.\n\nAdditional options include:\n- check: channel to listen to for collisions (default: `collisions:detected`).\n- mtvThreshold: apply partial extraction of bodies if the minimum transit vector is less than this value ( default: `1`)\n this will depend on your simulation characteristic length scale\n- bodyExtractDropoff: every body overlap correction (underneith mtvThreshold) will only extract by this fraction (0..1). Helps with stablizing contacts. (default: `0.5`)\n- forceWakeupAboveOverlapThreshold: force bodies to wake up if the overlap is above mtvThreshold ( default: `true` )", "short_description": "`Physics.behavior('body-impulse-response')`.\n", - "line": 11, + "line": 15, "aliases": [], "children": [ { @@ -3149,7 +3431,7 @@ "internal": true, "description": "Collide two bodies by modifying their positions and velocities to conserve momentum", "short_description": "Collide two bodies by modifying their positions and velocities to conserve momentum", - "line": 54, + "line": 88, "aliases": [], "children": [], "file": "src/behaviors/body-impulse-response.js", @@ -3186,7 +3468,7 @@ "internal": true, "description": "Event callback to respond to collision data.", "short_description": "Event callback to respond to collision data.", - "line": 225, + "line": 281, "aliases": [], "children": [], "file": "src/behaviors/body-impulse-response.js", @@ -3296,7 +3578,7 @@ "internal": true, "description": "Event callback to check all bodies for collisions with the edge", "short_description": "Event callback to check all bodies for collisions with the edge", - "line": 226, + "line": 227, "aliases": [], "children": [], "file": "src/behaviors/edge-collision-detection.js", @@ -3337,7 +3619,7 @@ ], "description": "Set the boundaries of the edge.", "short_description": "Set the boundaries of the edge.", - "line": 188, + "line": 189, "aliases": [], "children": [], "file": "src/behaviors/edge-collision-detection.js", @@ -3357,9 +3639,9 @@ "id": "InteractiveBehavior", "type": "class", "superclass": "Behavior", - "description": "`Physics.behavior('interactive')`.\n\nUser interaction helper.\n\nUsed to get mouse/touch events and add a mouse grab interaction.\n\nAdditional options include:\n- el: The element of the renderer. What you input as the `el` for the renderer.\n- moveThrottle: The min time between move events (default: `10`).\n- minVel: The minimum velocity clamp [[Vectorish]] (default: { x: -5, y: -5 }) to restrict velocity a user can give to a body\n- maxVel: The maximum velocity clamp [[Vectorish]] (default: { x: 5, y: 5 }) to restrict velocity a user can give to a body\n\nThe behavior also triggers the following events on the world:\n```javascript\n// a body has been grabbed\nworld.on('interact:grab', function( data ){\n data.x; // the x coord\n data.y; // the y coord\n data.body; // the body that was grabbed\n});\n// no body was grabbed, but the renderer area was clicked, or touched\nworld.on('interact:poke', function( data ){\n data.x; // the x coord\n data.y; // the y coord\n});\nworld.on('interact:move', function( data ){\n data.x; // the x coord\n data.y; // the y coord\n data.body; // the body that was grabbed (if applicable)\n});\n// when the viewport is released (mouseup, touchend)\nworld.on('interact:release', function( data ){\n data.x; // the x coord\n data.y; // the y coord\n});\n```", + "description": "`Physics.behavior('interactive')`.\n\nUser interaction helper.\n\nUsed to get mouse/touch events and add grab interactions.\n\nAdditional options include:\n- el: The element of the renderer. What you input as the `el` for the renderer.\n- moveThrottle: The min time between move events (default: `10`).\n- minVel: The minimum velocity clamp [[Vectorish]] (default: { x: -5, y: -5 }) to restrict velocity a user can give to a body\n- maxVel: The maximum velocity clamp [[Vectorish]] (default: { x: 5, y: 5 }) to restrict velocity a user can give to a body\n\nThe behavior also triggers the following events on the world:\n```javascript\n// a body has been grabbed\nworld.on('interact:grab', function( data ){\n data.x; // the x coord\n data.y; // the y coord\n data.body; // the body that was grabbed\n});\n// no body was grabbed, but the renderer area was clicked, or touched\nworld.on('interact:poke', function( data ){\n data.x; // the x coord\n data.y; // the y coord\n});\n// when a mouse or pointer moves\nworld.on('interact:move', function( data ){\n data.x; // the x coord\n data.y; // the y coord\n data.body; // the grabbed body that was moved (if applicable)\n});\n// when the viewport is released (mouseup, touchend)\nworld.on('interact:release', function( data ){\n data.x; // the x coord\n data.y; // the y coord\n data.body; // the body that was grabbed (if applicable)\n});\n```\n\nThe behavior also sets body.isGrabbed = true for any grabbed bodies while they are grabbed.", "short_description": "`Physics.behavior('interactive')`.\n", - "line": 41, + "line": 45, "aliases": [], "children": [], "file": "src/behaviors/interactive.js", @@ -3445,7 +3727,7 @@ "internal": true, "description": "Check each axis for overlaps of bodies AABBs", "short_description": "Check each axis for overlaps of bodies AABBs", - "line": 248, + "line": 257, "aliases": [], "children": [], "file": "src/behaviors/sweep-prune.js", @@ -3541,7 +3823,7 @@ "internal": true, "description": "Get a pair object for the tracker objects", "short_description": "Get a pair object for the tracker objects", - "line": 176, + "line": 181, "aliases": [], "children": [], "file": "src/behaviors/sweep-prune.js", @@ -3561,7 +3843,7 @@ "internal": true, "description": "Simple insertion sort for each axis", "short_description": "Simple insertion sort for each axis", - "line": 107, + "line": 112, "aliases": [], "children": [], "file": "src/behaviors/sweep-prune.js", @@ -3598,7 +3880,7 @@ "internal": true, "description": "Event callback to sweep and publish event if any candidate collisions are found", "short_description": "Event callback to sweep and publish event if any candidate collisions are found", - "line": 488, + "line": 487, "aliases": [], "children": [], "file": "src/behaviors/sweep-prune.js", @@ -3635,7 +3917,7 @@ "internal": true, "description": "Event callback to add body to list of those tracked by sweep and prune", "short_description": "Event callback to add body to list of those tracked by sweep and prune", - "line": 394, + "line": 393, "aliases": [], "children": [], "file": "src/behaviors/sweep-prune.js", @@ -3672,7 +3954,7 @@ "internal": true, "description": "Event callback to remove body from list of those tracked", "short_description": "Event callback to remove body from list of those tracked", - "line": 433, + "line": 432, "aliases": [], "children": [], "file": "src/behaviors/sweep-prune.js", @@ -3692,7 +3974,7 @@ "internal": true, "description": "Update position intervals on each axis", "short_description": "Update position intervals on each axis", - "line": 358, + "line": 364, "aliases": [], "children": [], "file": "src/behaviors/sweep-prune.js", @@ -3960,7 +4242,7 @@ ], "description": "Get all constraints.", "short_description": "Get all constraints.", - "line": 415, + "line": 419, "aliases": [], "children": [], "file": "src/behaviors/verlet-constraints.js", @@ -4044,7 +4326,7 @@ "internal": true, "description": "Resolve all constraints.", "short_description": "Resolve all constraints.", - "line": 395, + "line": 399, "aliases": [], "children": [], "file": "src/behaviors/verlet-constraints.js", @@ -4118,7 +4400,7 @@ "internal": true, "description": "Resolve distance constraints.", "short_description": "Resolve distance constraints.", - "line": 329, + "line": 330, "aliases": [], "children": [], "file": "src/behaviors/verlet-constraints.js", @@ -4138,7 +4420,7 @@ "internal": true, "description": "Mix up the constraints.", "short_description": "Mix up the constraints.", - "line": 384, + "line": 388, "aliases": [], "children": [], "file": "src/behaviors/verlet-constraints.js", @@ -4570,7 +4852,7 @@ "id": "BodyCollisionDetectionBehavior", "type": "class", "superclass": "Behavior", - "description": "`Physics.behavior('body-collision-detection')`.\n\nDetect collisions of bodies.\n\nPublishes collision events to the world as a group of detected collisions per iteration.\n\nThe event data will have a `.collisions` property that is an array of collisions of the form:\n\n```javascript\n{\n bodyA: // the first body\n bodyB: // the second body\n norm: // the normal vector (Vectorish)\n mtv: // the minimum transit vector. (the direction and length needed to extract bodyB from bodyA)\n pos: // the collision point\n overlap: // the amount bodyA overlaps bodyB\n}\n```\n\nAdditional options include:\n- check: channel to listen to for collision candidates (default: `collisions:candidates`). set to `true` to force check every pair of bodies in the world\n- channel: channel to publish events to (default: `collisions:detected`)", + "description": "`Physics.behavior('body-collision-detection')`.\n\nDetect collisions of bodies.\n\nPublishes collision events to the world as a group of detected collisions per iteration.\n\nThe event data will have a `.collisions` property that is an array of collisions of the form:\n\n```javascript\n{\n bodyA: // the first body\n bodyB: // the second body\n norm: // the normal vector (Vectorish)\n mtv: // the minimum transit vector. (the direction and length needed to extract bodyB from bodyA)\n pos: // the collision point relative to bodyA\n overlap: // the amount bodyA overlaps bodyB\n}\n```\n\nAdditional options include:\n- check: channel to listen to for collision candidates (default: `collisions:candidates`). set to `true` to force check every pair of bodies in the world\n- channel: channel to publish events to (default: `collisions:detected`)", "short_description": "`Physics.behavior('body-collision-detection')`.\n", "line": 27, "aliases": [], @@ -4603,7 +4885,7 @@ "internal": true, "description": "Event callback to check pairs of objects that have been flagged by broad phase for possible collisions.", "short_description": "Event callback to check pairs of objects that have been flagged by broad phase for possible collisions.", - "line": 285, + "line": 366, "aliases": [], "children": [], "file": "src/behaviors/body-collision-detection.js", @@ -4640,7 +4922,7 @@ "internal": true, "description": "Event callback to check all pairs of objects in the list for collisions", "short_description": "Event callback to check all pairs of objects in the list for collisions", - "line": 325, + "line": 428, "aliases": [], "children": [], "file": "src/behaviors/body-collision-detection.js", @@ -4660,9 +4942,9 @@ "id": "BodyImpulseResponseBehavior", "type": "class", "superclass": "Behavior", - "description": "`Physics.behavior('body-impulse-response')`.\n\nResponds to collisions by applying impulses.\n\nAdditional options include:\n- check: channel to listen to for collisions (default: `collisions:detected`).", + "description": "`Physics.behavior('body-impulse-response')`.\n\nResponds to collisions by applying impulses.\n\nAdditional options include:\n- check: channel to listen to for collisions (default: `collisions:detected`).\n- mtvThreshold: apply partial extraction of bodies if the minimum transit vector is less than this value ( default: `1`)\n this will depend on your simulation characteristic length scale\n- bodyExtractDropoff: every body overlap correction (underneith mtvThreshold) will only extract by this fraction (0..1). Helps with stablizing contacts. (default: `0.5`)\n- forceWakeupAboveOverlapThreshold: force bodies to wake up if the overlap is above mtvThreshold ( default: `true` )", "short_description": "`Physics.behavior('body-impulse-response')`.\n", - "line": 11, + "line": 15, "aliases": [], "children": [ { @@ -4763,7 +5045,7 @@ "internal": true, "description": "Collide two bodies by modifying their positions and velocities to conserve momentum", "short_description": "Collide two bodies by modifying their positions and velocities to conserve momentum", - "line": 54, + "line": 88, "aliases": [], "children": [], "file": "src/behaviors/body-impulse-response.js", @@ -4800,7 +5082,7 @@ "internal": true, "description": "Event callback to respond to collision data.", "short_description": "Event callback to respond to collision data.", - "line": 225, + "line": 281, "aliases": [], "children": [], "file": "src/behaviors/body-impulse-response.js", @@ -4910,7 +5192,7 @@ "internal": true, "description": "Event callback to check all bodies for collisions with the edge", "short_description": "Event callback to check all bodies for collisions with the edge", - "line": 226, + "line": 227, "aliases": [], "children": [], "file": "src/behaviors/edge-collision-detection.js", @@ -4951,7 +5233,7 @@ ], "description": "Set the boundaries of the edge.", "short_description": "Set the boundaries of the edge.", - "line": 188, + "line": 189, "aliases": [], "children": [], "file": "src/behaviors/edge-collision-detection.js", @@ -4971,9 +5253,9 @@ "id": "InteractiveBehavior", "type": "class", "superclass": "Behavior", - "description": "`Physics.behavior('interactive')`.\n\nUser interaction helper.\n\nUsed to get mouse/touch events and add a mouse grab interaction.\n\nAdditional options include:\n- el: The element of the renderer. What you input as the `el` for the renderer.\n- moveThrottle: The min time between move events (default: `10`).\n- minVel: The minimum velocity clamp [[Vectorish]] (default: { x: -5, y: -5 }) to restrict velocity a user can give to a body\n- maxVel: The maximum velocity clamp [[Vectorish]] (default: { x: 5, y: 5 }) to restrict velocity a user can give to a body\n\nThe behavior also triggers the following events on the world:\n```javascript\n// a body has been grabbed\nworld.on('interact:grab', function( data ){\n data.x; // the x coord\n data.y; // the y coord\n data.body; // the body that was grabbed\n});\n// no body was grabbed, but the renderer area was clicked, or touched\nworld.on('interact:poke', function( data ){\n data.x; // the x coord\n data.y; // the y coord\n});\nworld.on('interact:move', function( data ){\n data.x; // the x coord\n data.y; // the y coord\n data.body; // the body that was grabbed (if applicable)\n});\n// when the viewport is released (mouseup, touchend)\nworld.on('interact:release', function( data ){\n data.x; // the x coord\n data.y; // the y coord\n});\n```", + "description": "`Physics.behavior('interactive')`.\n\nUser interaction helper.\n\nUsed to get mouse/touch events and add grab interactions.\n\nAdditional options include:\n- el: The element of the renderer. What you input as the `el` for the renderer.\n- moveThrottle: The min time between move events (default: `10`).\n- minVel: The minimum velocity clamp [[Vectorish]] (default: { x: -5, y: -5 }) to restrict velocity a user can give to a body\n- maxVel: The maximum velocity clamp [[Vectorish]] (default: { x: 5, y: 5 }) to restrict velocity a user can give to a body\n\nThe behavior also triggers the following events on the world:\n```javascript\n// a body has been grabbed\nworld.on('interact:grab', function( data ){\n data.x; // the x coord\n data.y; // the y coord\n data.body; // the body that was grabbed\n});\n// no body was grabbed, but the renderer area was clicked, or touched\nworld.on('interact:poke', function( data ){\n data.x; // the x coord\n data.y; // the y coord\n});\n// when a mouse or pointer moves\nworld.on('interact:move', function( data ){\n data.x; // the x coord\n data.y; // the y coord\n data.body; // the grabbed body that was moved (if applicable)\n});\n// when the viewport is released (mouseup, touchend)\nworld.on('interact:release', function( data ){\n data.x; // the x coord\n data.y; // the y coord\n data.body; // the body that was grabbed (if applicable)\n});\n```\n\nThe behavior also sets body.isGrabbed = true for any grabbed bodies while they are grabbed.", "short_description": "`Physics.behavior('interactive')`.\n", - "line": 41, + "line": 45, "aliases": [], "children": [], "file": "src/behaviors/interactive.js", @@ -5059,7 +5341,7 @@ "internal": true, "description": "Check each axis for overlaps of bodies AABBs", "short_description": "Check each axis for overlaps of bodies AABBs", - "line": 248, + "line": 257, "aliases": [], "children": [], "file": "src/behaviors/sweep-prune.js", @@ -5155,7 +5437,7 @@ "internal": true, "description": "Get a pair object for the tracker objects", "short_description": "Get a pair object for the tracker objects", - "line": 176, + "line": 181, "aliases": [], "children": [], "file": "src/behaviors/sweep-prune.js", @@ -5175,7 +5457,7 @@ "internal": true, "description": "Simple insertion sort for each axis", "short_description": "Simple insertion sort for each axis", - "line": 107, + "line": 112, "aliases": [], "children": [], "file": "src/behaviors/sweep-prune.js", @@ -5212,7 +5494,7 @@ "internal": true, "description": "Event callback to sweep and publish event if any candidate collisions are found", "short_description": "Event callback to sweep and publish event if any candidate collisions are found", - "line": 488, + "line": 487, "aliases": [], "children": [], "file": "src/behaviors/sweep-prune.js", @@ -5249,7 +5531,7 @@ "internal": true, "description": "Event callback to add body to list of those tracked by sweep and prune", "short_description": "Event callback to add body to list of those tracked by sweep and prune", - "line": 394, + "line": 393, "aliases": [], "children": [], "file": "src/behaviors/sweep-prune.js", @@ -5286,7 +5568,7 @@ "internal": true, "description": "Event callback to remove body from list of those tracked", "short_description": "Event callback to remove body from list of those tracked", - "line": 433, + "line": 432, "aliases": [], "children": [], "file": "src/behaviors/sweep-prune.js", @@ -5306,7 +5588,7 @@ "internal": true, "description": "Update position intervals on each axis", "short_description": "Update position intervals on each axis", - "line": 358, + "line": 364, "aliases": [], "children": [], "file": "src/behaviors/sweep-prune.js", @@ -5574,7 +5856,7 @@ ], "description": "Get all constraints.", "short_description": "Get all constraints.", - "line": 415, + "line": 419, "aliases": [], "children": [], "file": "src/behaviors/verlet-constraints.js", @@ -5658,7 +5940,7 @@ "internal": true, "description": "Resolve all constraints.", "short_description": "Resolve all constraints.", - "line": 395, + "line": 399, "aliases": [], "children": [], "file": "src/behaviors/verlet-constraints.js", @@ -5732,7 +6014,7 @@ "internal": true, "description": "Resolve distance constraints.", "short_description": "Resolve distance constraints.", - "line": 329, + "line": 330, "aliases": [], "children": [], "file": "src/behaviors/verlet-constraints.js", @@ -5752,7 +6034,7 @@ "internal": true, "description": "Mix up the constraints.", "short_description": "Mix up the constraints.", - "line": 384, + "line": 388, "aliases": [], "children": [], "file": "src/behaviors/verlet-constraints.js", @@ -6110,7 +6392,7 @@ "types": [ "Object" ], - "description": " The configuration for that body ( depends on body ).\n\n Available options and defaults:\n\n\n\n ```javascript\n\n {\n\n // is the body hidden (not to be rendered)?\n\n hidden: false,\n\n // is the body `dynamic`, `kinematic` or `static`?\n\n // http://www.box2d.org/manual.html#_Toc258082973\n\n treatment: 'dynamic',\n\n // body mass\n\n mass: 1.0,\n\n // body restitution. How \"bouncy\" is it?\n\n restitution: 1.0,\n\n // what is its coefficient of friction with another surface with COF = 1?\n\n cof: 0.8,\n\n // what is the view object (mixed) that should be used when rendering?\n\n view: null\n\n }\n\n ```\n\n " + "description": " The configuration for that body ( depends on body ).\n\n Available options and defaults:\n\n\n\n ```javascript\n\n {\n\n // is the body hidden (not to be rendered)?\n\n hidden: false,\n\n // is the body `dynamic`, `kinematic` or `static`?\n\n // http://www.box2d.org/manual.html#_Toc258082973\n\n treatment: 'dynamic',\n\n // body mass\n\n mass: 1.0,\n\n // body restitution. How \"bouncy\" is it?\n\n restitution: 1.0,\n\n // what is its coefficient of friction with another surface with COF = 1?\n\n cof: 0.8,\n\n // what is the view object (mixed) that should be used when rendering?\n\n view: null,\n\n // the vector offsetting the geometry from its center of mass\n\n offset: Physics.vector(0,0)\n\n }\n\n ```\n\n " } ], "returns": [ @@ -6133,13 +6415,13 @@ "types": [ "Object" ], - "description": " The configuration for that body ( depends on body ).\n\n Available options and defaults:\n\n\n\n ```javascript\n\n {\n\n // is the body hidden (not to be rendered)?\n\n hidden: false,\n\n // is the body `dynamic`, `kinematic` or `static`?\n\n // http://www.box2d.org/manual.html#_Toc258082973\n\n treatment: 'dynamic',\n\n // body mass\n\n mass: 1.0,\n\n // body restitution. How \"bouncy\" is it?\n\n restitution: 1.0,\n\n // what is its coefficient of friction with another surface with COF = 1?\n\n cof: 0.8,\n\n // what is the view object (mixed) that should be used when rendering?\n\n view: null\n\n }\n\n ```\n\n " + "description": " The configuration for that body ( depends on body ).\n\n Available options and defaults:\n\n\n\n ```javascript\n\n {\n\n // is the body hidden (not to be rendered)?\n\n hidden: false,\n\n // is the body `dynamic`, `kinematic` or `static`?\n\n // http://www.box2d.org/manual.html#_Toc258082973\n\n treatment: 'dynamic',\n\n // body mass\n\n mass: 1.0,\n\n // body restitution. How \"bouncy\" is it?\n\n restitution: 1.0,\n\n // what is its coefficient of friction with another surface with COF = 1?\n\n cof: 0.8,\n\n // what is the view object (mixed) that should be used when rendering?\n\n view: null,\n\n // the vector offsetting the geometry from its center of mass\n\n offset: Physics.vector(0,0)\n\n }\n\n ```\n\n " } ], "related_to": "Physics.util.decorator", "description": "Factory function for creating Bodies.\n\nVisit [the PhysicsJS wiki on Bodies](https://github.com/wellcaffeinated/PhysicsJS/wiki/Bodies)\nfor usage documentation.", "short_description": "Factory function for creating Bodies.\n", - "line": 51, + "line": 58, "aliases": [], "children": [ { @@ -6148,7 +6430,7 @@ "belongs_to": "Physics.body", "description": "The base class for bodies created by [[Physics.body]] factory function.", "short_description": "The base class for bodies created by [[Physics.body]] factory function.", - "line": 58, + "line": 65, "aliases": [ "PointBody" ], @@ -6175,7 +6457,7 @@ "related_to": "Physics.aabb", "description": "Get the Axis aligned bounding box for the body in its current position and rotation", "short_description": "Get the Axis aligned bounding box for the body in its current position and rotation", - "line": 316, + "line": 458, "aliases": [], "children": [], "file": "src/core/body.js", @@ -6216,7 +6498,7 @@ ], "description": "Accelerate the body by adding supplied vector to its current acceleration", "short_description": "Accelerate the body by adding supplied vector to its current acceleration", - "line": 267, + "line": 395, "aliases": [], "children": [], "file": "src/core/body.js", @@ -6272,7 +6554,7 @@ ], "description": "Apply a force at center of mass, or at point `p` relative to the center of mass", "short_description": "Apply a force at center of mass, or at point `p` relative to the center of mass", - "line": 283, + "line": 411, "aliases": [], "children": [], "file": "src/core/body.js", @@ -6295,7 +6577,7 @@ ], "description": "The coefficient of friction of the body.\n\nIt's how much \"slide\" it has during collisions.\n\nA `cof` of `0` will really slidy.\n\nA `cof` of `1` has no slide.\n\nThis is a very simplistic implementation at the moment.\nWhat would be better is to have both static and kinetic\nfriction. But that's not done yet.", "short_description": "The coefficient of friction of the body.\n", - "line": 194, + "line": 218, "aliases": [], "children": [], "file": "src/core/body.js", @@ -6310,7 +6592,7 @@ "related_to": "Physics.geometry", "description": "The geometry for this body.\n\nBy default it is a `point` geometry which gets overridden.", "short_description": "The geometry for this body.\n", - "line": 153, + "line": 171, "aliases": [], "children": [], "file": "src/core/body.js", @@ -6319,6 +6601,56 @@ "path": "Body.prototype.geometry", "outFile": "body.html#Body.prototype.geometry" }, + { + "id": "Body#getGlobalOffset", + "type": "instance method", + "signatures": [ + { + "arguments": [ + { + "name": "out", + "optional": true, + "types": [ + "Physics.vector" + ], + "description": " A vector to use to put the result into. One is created if `out` isn't specified.\n\n " + } + ], + "returns": [ + { + "type": "Physics.vector", + "description": " The offset in global coordinates\n\n " + } + ] + } + ], + "arguments": [ + { + "name": "out", + "types": [ + "Physics.vector" + ], + "description": " A vector to use to put the result into. One is created if `out` isn't specified.\n\n " + } + ], + "returns": [ + { + "type": "Physics.vector", + "description": " The offset in global coordinates\n\n " + } + ], + "related_to": "Body#offset", + "description": "Get the body offset vector (from the center of mass) for the body's shape in global coordinates.", + "short_description": "Get the body offset vector (from the center of mass) for the body's shape in global coordinates.", + "line": 445, + "aliases": [], + "children": [], + "file": "src/core/body.js", + "name": "getGlobalOffset", + "name_prefix": "Body#", + "path": "Body.prototype.getGlobalOffset", + "outFile": "body.html#Body.prototype.getGlobalOffset" + }, { "id": "Body#hidden", "type": "constant", @@ -6333,7 +6665,7 @@ ], "description": "Determines whether the body should be hidden by the renderer.", "short_description": "Determines whether the body should be hidden by the renderer.", - "line": 213, + "line": 237, "aliases": [], "children": [], "file": "src/core/body.js", @@ -6370,7 +6702,7 @@ "internal": true, "description": "Initialization. Internal use.", "short_description": "Initialization. Internal use.", - "line": 65, + "line": 72, "aliases": [], "children": [], "file": "src/core/body.js", @@ -6393,7 +6725,7 @@ ], "description": "The mass.", "short_description": "The mass.", - "line": 160, + "line": 178, "aliases": [], "children": [], "file": "src/core/body.js", @@ -6402,6 +6734,20 @@ "path": "Body.prototype.mass", "outFile": "body.html#Body.prototype.mass" }, + { + "id": "Body#offset", + "type": "namespace", + "description": "The vector offsetting the body's shape from its center of mass.", + "short_description": "The vector offsetting the body's shape from its center of mass.", + "line": 184, + "aliases": [], + "children": [], + "file": "src/core/body.js", + "name": "offset", + "name_prefix": "Body#", + "path": "Body.prototype.offset", + "outFile": "body.html#Body.prototype.offset" + }, { "id": "Body#options", "type": "instance method", @@ -6442,7 +6788,7 @@ "related_to": "Physics.util.options", "description": "Set options on this instance.\n\nAccess options directly from the options object.\n\nExample:\n\n```javascript\nthis.options.someOption;\n```", "short_description": "Set options on this instance.\n", - "line": 85, + "line": 93, "aliases": [], "children": [], "file": "src/core/body.js", @@ -6466,7 +6812,7 @@ ], "description": "Recalculate properties.\n\nIntended to be overridden by subclasses. Call when body physical properties are changed.", "short_description": "Recalculate properties.\n", - "line": 335, + "line": 503, "aliases": [], "children": [], "file": "src/core/body.js", @@ -6489,7 +6835,7 @@ ], "description": "The restitution.\n\nThis is the \"bounciness\" of the body.\nIt's a number between `0` and `1`.\n\nA restitution of 1 is the bounciest.\n\nA restitution of 0 is not bouncy.\n\nWhen colliding the restitutions of bodies are\nmultiplied together to get the restitution between two\nbodies.", "short_description": "The restitution.\n", - "line": 178, + "line": 202, "aliases": [], "children": [], "file": "src/core/body.js", @@ -6530,7 +6876,7 @@ ], "description": "Set which world to apply to.\n\nUsually this is called internally. Shouldn't be a need to call this yourself usually.", "short_description": "Set which world to apply to.\n", - "line": 246, + "line": 374, "aliases": [], "children": [], "file": "src/core/body.js", @@ -6539,12 +6885,98 @@ "path": "Body.prototype.setWorld", "outFile": "body.html#Body.prototype.setWorld" }, + { + "id": "Body#sleep", + "type": "instance method", + "signatures": [ + { + "arguments": [ + { + "name": "dt", + "optional": true, + "types": [ + "Number" + ], + "description": " Time to advance the idle time\n\n " + } + ], + "returns": [ + { + "type": "Boolean" + } + ] + } + ], + "arguments": [ + { + "name": "dt", + "types": [ + "Number" + ], + "description": " Time to advance the idle time\n\n " + }, + { + "name": "dt", + "types": [ + "Boolean" + ], + "description": " If `true`, the body will be forced to sleep. If `false`, the body will be forced to awake.\n\n " + } + ], + "description": "Get and/or set whether the body is asleep.\n\nIf called with a time (in ms), the time will be added to the idle time and sleep conditions will be checked.", + "short_description": "Get and/or set whether the body is asleep.\n", + "line": 271, + "aliases": [], + "children": [], + "file": "src/core/body.js", + "name": "sleep", + "name_prefix": "Body#", + "path": "Body.prototype.sleep", + "outFile": "body.html#Body.prototype.sleep" + }, + { + "id": "Body#sleepCheck", + "type": "instance method", + "signatures": [ + { + "arguments": [ + { + "name": "dt", + "optional": true, + "types": [ + "Number" + ], + "description": " Time to advance the idle time\n\n " + } + ] + } + ], + "arguments": [ + { + "name": "dt", + "types": [ + "Number" + ], + "description": " Time to advance the idle time\n\n " + } + ], + "description": "Check if the body should be sleeping.\n\nCall with no arguments if some event could possibly wake up the body. This will force the body to recheck.", + "short_description": "Check if the body should be sleeping.\n", + "line": 303, + "aliases": [], + "children": [], + "file": "src/core/body.js", + "name": "sleepCheck", + "name_prefix": "Body#", + "path": "Body.prototype.sleepCheck", + "outFile": "body.html#Body.prototype.sleepCheck" + }, { "id": "Body#state", "type": "namespace", "description": "The physical state container.\n\n- ``this.state.pos`` ([[Physics.vector]]) The position vector.\n- ``this.state.vel`` ([[Physics.vector]]) The velocity vector.\n- ``this.state.acc`` ([[Physics.vector]]) The acceleration vector.\n- ``this.state.angular.pos`` ([[Number]]) The angular position (in radians, positive is clockwise starting along the x axis)\n- ``this.state.angular.vel`` ([[Number]]) The angular velocity\n- ``this.state.angular.acc`` ([[Number]]) The angular acceleration\n\nProperties from the previous timestep are stored in:\n```javascript\nthis.state.old; // .pos, .vel, ...\n```", "short_description": "The physical state container.\n", - "line": 106, + "line": 117, "aliases": [], "children": [], "file": "src/core/body.js", @@ -6554,19 +6986,115 @@ "outFile": "body.html#Body.prototype.state" }, { - "id": "Body#style", + "id": "Body#styles", "type": "namespace", "related_to": "Physics.renderer", "description": "The styles the renderer should use for creating the view.\n\nThe styles depend on the renderer. See [[Renderer#createView]] for style options.", "short_description": "The styles the renderer should use for creating the view.\n", - "line": 236, + "line": 260, + "aliases": [], + "children": [], + "file": "src/core/body.js", + "name": "styles", + "name_prefix": "Body#", + "path": "Body.prototype.styles", + "outFile": "body.html#Body.prototype.styles" + }, + { + "id": "Body#toBodyCoords", + "type": "instance method", + "signatures": [ + { + "arguments": [ + { + "name": "v", + "types": [ + "Physics.vector" + ], + "description": " The vector to transform\n\n " + } + ], + "returns": [ + { + "type": "Physics.vector", + "description": " The transformed vector\n\n " + } + ] + } + ], + "arguments": [ + { + "name": "v", + "types": [ + "Physics.vector" + ], + "description": " The vector to transform\n\n " + } + ], + "returns": [ + { + "type": "Physics.vector", + "description": " The transformed vector\n\n " + } + ], + "description": "Transform a vector into coordinates relative to this body.", + "short_description": "Transform a vector into coordinates relative to this body.", + "line": 481, "aliases": [], "children": [], "file": "src/core/body.js", - "name": "style", + "name": "toBodyCoords", "name_prefix": "Body#", - "path": "Body.prototype.style", - "outFile": "body.html#Body.prototype.style" + "path": "Body.prototype.toBodyCoords", + "outFile": "body.html#Body.prototype.toBodyCoords" + }, + { + "id": "Body#toWorldCoords", + "type": "instance method", + "signatures": [ + { + "arguments": [ + { + "name": "v", + "types": [ + "Physics.vector" + ], + "description": " The vector to transform\n\n " + } + ], + "returns": [ + { + "type": "Physics.vector", + "description": " The transformed vector\n\n " + } + ] + } + ], + "arguments": [ + { + "name": "v", + "types": [ + "Physics.vector" + ], + "description": " The vector to transform\n\n " + } + ], + "returns": [ + { + "type": "Physics.vector", + "description": " The transformed vector\n\n " + } + ], + "description": "Transform a vector from body coordinates into world coordinates.", + "short_description": "Transform a vector from body coordinates into world coordinates.", + "line": 492, + "aliases": [], + "children": [], + "file": "src/core/body.js", + "name": "toWorldCoords", + "name_prefix": "Body#", + "path": "Body.prototype.toWorldCoords", + "outFile": "body.html#Body.prototype.toWorldCoords" }, { "id": "Body#treatment", @@ -6582,7 +7110,7 @@ ], "description": "How the body is treated by the simulation.\n\nThe body can be `dynamic`, `kinematic` or `static` as\ndescribed by the [analogous box2d docs](http://www.box2d.org/manual.html#_Toc258082973).\n\n* _dynamic_ bodies are treated \"normally\". They are integrated, and collide, and all that.\n* _kinematic_ bodies are bodies that move at a specified velocity. Other bodies collide with them, but they don't bounce off of other bodies.\n* _static_ bodies just stand still. They are like obstacles.", "short_description": "How the body is treated by the simulation.\n", - "line": 207, + "line": 231, "aliases": [], "children": [], "file": "src/core/body.js", @@ -6605,7 +7133,7 @@ ], "description": "The unique id for the body", "short_description": "The unique id for the body", - "line": 144, + "line": 162, "aliases": [], "children": [], "file": "src/core/body.js", @@ -6629,7 +7157,7 @@ "related_to": "Physics.renderer", "description": "Storage for use by the renderer.\n\nThe type of renderer will put different things in the view property.\nBasically, this is how the body \"looks\". It could be a HTMLElement, or\nan Image, etc...\n\nIf your body changes appearance (shape), you should modify this somehow\notherwise the renderer will keep using this same view. If you're letting\nthe renderer create the view for you, just set this to `undefined` if the\nbody gets modified in shape during the simulation.", "short_description": "Storage for use by the renderer.\n", - "line": 228, + "line": 252, "aliases": [], "children": [], "file": "src/core/body.js", @@ -6638,6 +7166,69 @@ "path": "Body.prototype.view", "outFile": "body.html#Body.prototype.view" }, + { + "id": "Body.getCOM", + "type": "class method", + "signatures": [ + { + "arguments": [ + { + "name": "bodies", + "types": [ + "Array" + ], + "description": " The list of bodies\n\n " + }, + { + "name": "com", + "optional": true, + "types": [ + "Physics.vector" + ], + "description": " The vector to put result into. A new vector will be created if not provided.\n\n " + } + ], + "returns": [ + { + "type": "Physics.vector", + "description": " The center of mass position\n\n " + } + ] + } + ], + "arguments": [ + { + "name": "bodies", + "types": [ + "Array" + ], + "description": " The list of bodies\n\n " + }, + { + "name": "com", + "types": [ + "Physics.vector" + ], + "description": " The vector to put result into. A new vector will be created if not provided.\n\n " + } + ], + "returns": [ + { + "type": "Physics.vector", + "description": " The center of mass position\n\n " + } + ], + "description": "Get center of mass position from list of bodies.", + "short_description": "Get center of mass position from list of bodies.", + "line": 517, + "aliases": [], + "children": [], + "file": "src/core/body.js", + "name": "getCOM", + "name_prefix": "Body.", + "path": "Body.getCOM", + "outFile": "body.html#Body.getCOM" + }, { "id": "CircleBody", "type": "class", @@ -6653,6 +7244,152 @@ "path": "CircleBody", "outFile": "circle.html" }, + { + "id": "CompoundBody", + "type": "class", + "superclass": "Body", + "description": "Physics.body('compound')\n\nNot a body in itself. It's a container to group other bodies. The position of the body is the center of mass.\nIt must have at least one child before being added to the world.\n\nAdditional config options:\n\n- children: Array of [[Body]] objects.\n\nExample:\n\n```javascript\nvar thing = Physics.body('compound', {\n // place the center of mass at (300, 200)\n x: 300,\n y: 200,\n // the center of mass is automatically calculated and used to position the shape\n children: [\n body1,\n body2,\n // ...\n ]\n});\n```", + "short_description": "Physics.body('compound')\n", + "line": 32, + "aliases": [], + "children": [ + { + "id": "CompoundBody#addChild", + "type": "instance method", + "signatures": [ + { + "arguments": [ + { + "name": "body", + "types": [ + "Body" + ], + "description": " The child to add\n\n " + } + ], + "returns": [ + { + "type": "this" + } + ] + } + ], + "arguments": [ + { + "name": "body", + "types": [ + "Body" + ], + "description": " The child to add\n\n " + } + ], + "description": "Add a body as a child.", + "short_description": "Add a body as a child.", + "line": 68, + "aliases": [], + "children": [], + "file": "src/bodies/compound.js", + "name": "addChild", + "name_prefix": "CompoundBody#", + "path": "CompoundBody.prototype.addChild", + "outFile": "compound.html#CompoundBody.prototype.addChild" + }, + { + "id": "CompoundBody#addChildren", + "type": "instance method", + "signatures": [ + { + "arguments": [ + { + "name": "bodies", + "types": [ + "Array" + ], + "description": " The list of children to add\n\n " + } + ], + "returns": [ + { + "type": "this" + } + ] + } + ], + "arguments": [ + { + "name": "bodies", + "types": [ + "Array" + ], + "description": " The list of children to add\n\n " + } + ], + "description": "Add an array of children to the compound.", + "short_description": "Add an array of children to the compound.", + "line": 80, + "aliases": [], + "children": [], + "file": "src/bodies/compound.js", + "name": "addChildren", + "name_prefix": "CompoundBody#", + "path": "CompoundBody.prototype.addChildren", + "outFile": "compound.html#CompoundBody.prototype.addChildren" + }, + { + "id": "CompoundBody#clear", + "type": "instance method", + "signatures": [ + { + "arguments": [], + "returns": [ + { + "type": "this" + } + ] + } + ], + "description": "Remove all children.", + "short_description": "Remove all children.", + "line": 142, + "aliases": [], + "children": [], + "file": "src/bodies/compound.js", + "name": "clear", + "name_prefix": "CompoundBody#", + "path": "CompoundBody.prototype.clear", + "outFile": "compound.html#CompoundBody.prototype.clear" + }, + { + "id": "CompoundBody#refreshGeometry", + "type": "instance method", + "signatures": [ + { + "arguments": [], + "returns": [ + { + "type": "this" + } + ] + } + ], + "description": "If the children's positions change, `refreshGeometry()` should be called to fix the shape.", + "short_description": "If the children's positions change, `refreshGeometry()` should be called to fix the shape.", + "line": 159, + "aliases": [], + "children": [], + "file": "src/bodies/compound.js", + "name": "refreshGeometry", + "name_prefix": "CompoundBody#", + "path": "CompoundBody.prototype.refreshGeometry", + "outFile": "compound.html#CompoundBody.prototype.refreshGeometry" + } + ], + "file": "src/bodies/compound.js", + "subclasses": [], + "name": "CompoundBody", + "path": "CompoundBody", + "outFile": "compound.html" + }, { "id": "ConvexPolygonBody", "type": "class", @@ -6703,6 +7440,7 @@ "file": "src/core/body.js", "subclasses": [ "CircleBody", + "CompoundBody", "ConvexPolygonBody", "PointBody", "RectangleBody" @@ -6724,7 +7462,7 @@ "belongs_to": "Physics.body", "description": "The base class for bodies created by [[Physics.body]] factory function.", "short_description": "The base class for bodies created by [[Physics.body]] factory function.", - "line": 58, + "line": 65, "aliases": [ "PointBody" ], @@ -6751,7 +7489,7 @@ "related_to": "Physics.aabb", "description": "Get the Axis aligned bounding box for the body in its current position and rotation", "short_description": "Get the Axis aligned bounding box for the body in its current position and rotation", - "line": 316, + "line": 458, "aliases": [], "children": [], "file": "src/core/body.js", @@ -6792,7 +7530,7 @@ ], "description": "Accelerate the body by adding supplied vector to its current acceleration", "short_description": "Accelerate the body by adding supplied vector to its current acceleration", - "line": 267, + "line": 395, "aliases": [], "children": [], "file": "src/core/body.js", @@ -6848,7 +7586,7 @@ ], "description": "Apply a force at center of mass, or at point `p` relative to the center of mass", "short_description": "Apply a force at center of mass, or at point `p` relative to the center of mass", - "line": 283, + "line": 411, "aliases": [], "children": [], "file": "src/core/body.js", @@ -6871,7 +7609,7 @@ ], "description": "The coefficient of friction of the body.\n\nIt's how much \"slide\" it has during collisions.\n\nA `cof` of `0` will really slidy.\n\nA `cof` of `1` has no slide.\n\nThis is a very simplistic implementation at the moment.\nWhat would be better is to have both static and kinetic\nfriction. But that's not done yet.", "short_description": "The coefficient of friction of the body.\n", - "line": 194, + "line": 218, "aliases": [], "children": [], "file": "src/core/body.js", @@ -6886,7 +7624,7 @@ "related_to": "Physics.geometry", "description": "The geometry for this body.\n\nBy default it is a `point` geometry which gets overridden.", "short_description": "The geometry for this body.\n", - "line": 153, + "line": 171, "aliases": [], "children": [], "file": "src/core/body.js", @@ -6895,6 +7633,56 @@ "path": "Body.prototype.geometry", "outFile": "body.html#Body.prototype.geometry" }, + { + "id": "Body#getGlobalOffset", + "type": "instance method", + "signatures": [ + { + "arguments": [ + { + "name": "out", + "optional": true, + "types": [ + "Physics.vector" + ], + "description": " A vector to use to put the result into. One is created if `out` isn't specified.\n\n " + } + ], + "returns": [ + { + "type": "Physics.vector", + "description": " The offset in global coordinates\n\n " + } + ] + } + ], + "arguments": [ + { + "name": "out", + "types": [ + "Physics.vector" + ], + "description": " A vector to use to put the result into. One is created if `out` isn't specified.\n\n " + } + ], + "returns": [ + { + "type": "Physics.vector", + "description": " The offset in global coordinates\n\n " + } + ], + "related_to": "Body#offset", + "description": "Get the body offset vector (from the center of mass) for the body's shape in global coordinates.", + "short_description": "Get the body offset vector (from the center of mass) for the body's shape in global coordinates.", + "line": 445, + "aliases": [], + "children": [], + "file": "src/core/body.js", + "name": "getGlobalOffset", + "name_prefix": "Body#", + "path": "Body.prototype.getGlobalOffset", + "outFile": "body.html#Body.prototype.getGlobalOffset" + }, { "id": "Body#hidden", "type": "constant", @@ -6909,7 +7697,7 @@ ], "description": "Determines whether the body should be hidden by the renderer.", "short_description": "Determines whether the body should be hidden by the renderer.", - "line": 213, + "line": 237, "aliases": [], "children": [], "file": "src/core/body.js", @@ -6946,7 +7734,7 @@ "internal": true, "description": "Initialization. Internal use.", "short_description": "Initialization. Internal use.", - "line": 65, + "line": 72, "aliases": [], "children": [], "file": "src/core/body.js", @@ -6969,7 +7757,7 @@ ], "description": "The mass.", "short_description": "The mass.", - "line": 160, + "line": 178, "aliases": [], "children": [], "file": "src/core/body.js", @@ -6978,6 +7766,20 @@ "path": "Body.prototype.mass", "outFile": "body.html#Body.prototype.mass" }, + { + "id": "Body#offset", + "type": "namespace", + "description": "The vector offsetting the body's shape from its center of mass.", + "short_description": "The vector offsetting the body's shape from its center of mass.", + "line": 184, + "aliases": [], + "children": [], + "file": "src/core/body.js", + "name": "offset", + "name_prefix": "Body#", + "path": "Body.prototype.offset", + "outFile": "body.html#Body.prototype.offset" + }, { "id": "Body#options", "type": "instance method", @@ -7018,7 +7820,7 @@ "related_to": "Physics.util.options", "description": "Set options on this instance.\n\nAccess options directly from the options object.\n\nExample:\n\n```javascript\nthis.options.someOption;\n```", "short_description": "Set options on this instance.\n", - "line": 85, + "line": 93, "aliases": [], "children": [], "file": "src/core/body.js", @@ -7042,7 +7844,7 @@ ], "description": "Recalculate properties.\n\nIntended to be overridden by subclasses. Call when body physical properties are changed.", "short_description": "Recalculate properties.\n", - "line": 335, + "line": 503, "aliases": [], "children": [], "file": "src/core/body.js", @@ -7065,7 +7867,7 @@ ], "description": "The restitution.\n\nThis is the \"bounciness\" of the body.\nIt's a number between `0` and `1`.\n\nA restitution of 1 is the bounciest.\n\nA restitution of 0 is not bouncy.\n\nWhen colliding the restitutions of bodies are\nmultiplied together to get the restitution between two\nbodies.", "short_description": "The restitution.\n", - "line": 178, + "line": 202, "aliases": [], "children": [], "file": "src/core/body.js", @@ -7106,7 +7908,7 @@ ], "description": "Set which world to apply to.\n\nUsually this is called internally. Shouldn't be a need to call this yourself usually.", "short_description": "Set which world to apply to.\n", - "line": 246, + "line": 374, "aliases": [], "children": [], "file": "src/core/body.js", @@ -7115,12 +7917,98 @@ "path": "Body.prototype.setWorld", "outFile": "body.html#Body.prototype.setWorld" }, + { + "id": "Body#sleep", + "type": "instance method", + "signatures": [ + { + "arguments": [ + { + "name": "dt", + "optional": true, + "types": [ + "Number" + ], + "description": " Time to advance the idle time\n\n " + } + ], + "returns": [ + { + "type": "Boolean" + } + ] + } + ], + "arguments": [ + { + "name": "dt", + "types": [ + "Number" + ], + "description": " Time to advance the idle time\n\n " + }, + { + "name": "dt", + "types": [ + "Boolean" + ], + "description": " If `true`, the body will be forced to sleep. If `false`, the body will be forced to awake.\n\n " + } + ], + "description": "Get and/or set whether the body is asleep.\n\nIf called with a time (in ms), the time will be added to the idle time and sleep conditions will be checked.", + "short_description": "Get and/or set whether the body is asleep.\n", + "line": 271, + "aliases": [], + "children": [], + "file": "src/core/body.js", + "name": "sleep", + "name_prefix": "Body#", + "path": "Body.prototype.sleep", + "outFile": "body.html#Body.prototype.sleep" + }, + { + "id": "Body#sleepCheck", + "type": "instance method", + "signatures": [ + { + "arguments": [ + { + "name": "dt", + "optional": true, + "types": [ + "Number" + ], + "description": " Time to advance the idle time\n\n " + } + ] + } + ], + "arguments": [ + { + "name": "dt", + "types": [ + "Number" + ], + "description": " Time to advance the idle time\n\n " + } + ], + "description": "Check if the body should be sleeping.\n\nCall with no arguments if some event could possibly wake up the body. This will force the body to recheck.", + "short_description": "Check if the body should be sleeping.\n", + "line": 303, + "aliases": [], + "children": [], + "file": "src/core/body.js", + "name": "sleepCheck", + "name_prefix": "Body#", + "path": "Body.prototype.sleepCheck", + "outFile": "body.html#Body.prototype.sleepCheck" + }, { "id": "Body#state", "type": "namespace", "description": "The physical state container.\n\n- ``this.state.pos`` ([[Physics.vector]]) The position vector.\n- ``this.state.vel`` ([[Physics.vector]]) The velocity vector.\n- ``this.state.acc`` ([[Physics.vector]]) The acceleration vector.\n- ``this.state.angular.pos`` ([[Number]]) The angular position (in radians, positive is clockwise starting along the x axis)\n- ``this.state.angular.vel`` ([[Number]]) The angular velocity\n- ``this.state.angular.acc`` ([[Number]]) The angular acceleration\n\nProperties from the previous timestep are stored in:\n```javascript\nthis.state.old; // .pos, .vel, ...\n```", "short_description": "The physical state container.\n", - "line": 106, + "line": 117, "aliases": [], "children": [], "file": "src/core/body.js", @@ -7130,19 +8018,115 @@ "outFile": "body.html#Body.prototype.state" }, { - "id": "Body#style", + "id": "Body#styles", "type": "namespace", "related_to": "Physics.renderer", "description": "The styles the renderer should use for creating the view.\n\nThe styles depend on the renderer. See [[Renderer#createView]] for style options.", "short_description": "The styles the renderer should use for creating the view.\n", - "line": 236, + "line": 260, + "aliases": [], + "children": [], + "file": "src/core/body.js", + "name": "styles", + "name_prefix": "Body#", + "path": "Body.prototype.styles", + "outFile": "body.html#Body.prototype.styles" + }, + { + "id": "Body#toBodyCoords", + "type": "instance method", + "signatures": [ + { + "arguments": [ + { + "name": "v", + "types": [ + "Physics.vector" + ], + "description": " The vector to transform\n\n " + } + ], + "returns": [ + { + "type": "Physics.vector", + "description": " The transformed vector\n\n " + } + ] + } + ], + "arguments": [ + { + "name": "v", + "types": [ + "Physics.vector" + ], + "description": " The vector to transform\n\n " + } + ], + "returns": [ + { + "type": "Physics.vector", + "description": " The transformed vector\n\n " + } + ], + "description": "Transform a vector into coordinates relative to this body.", + "short_description": "Transform a vector into coordinates relative to this body.", + "line": 481, "aliases": [], "children": [], "file": "src/core/body.js", - "name": "style", + "name": "toBodyCoords", "name_prefix": "Body#", - "path": "Body.prototype.style", - "outFile": "body.html#Body.prototype.style" + "path": "Body.prototype.toBodyCoords", + "outFile": "body.html#Body.prototype.toBodyCoords" + }, + { + "id": "Body#toWorldCoords", + "type": "instance method", + "signatures": [ + { + "arguments": [ + { + "name": "v", + "types": [ + "Physics.vector" + ], + "description": " The vector to transform\n\n " + } + ], + "returns": [ + { + "type": "Physics.vector", + "description": " The transformed vector\n\n " + } + ] + } + ], + "arguments": [ + { + "name": "v", + "types": [ + "Physics.vector" + ], + "description": " The vector to transform\n\n " + } + ], + "returns": [ + { + "type": "Physics.vector", + "description": " The transformed vector\n\n " + } + ], + "description": "Transform a vector from body coordinates into world coordinates.", + "short_description": "Transform a vector from body coordinates into world coordinates.", + "line": 492, + "aliases": [], + "children": [], + "file": "src/core/body.js", + "name": "toWorldCoords", + "name_prefix": "Body#", + "path": "Body.prototype.toWorldCoords", + "outFile": "body.html#Body.prototype.toWorldCoords" }, { "id": "Body#treatment", @@ -7158,7 +8142,7 @@ ], "description": "How the body is treated by the simulation.\n\nThe body can be `dynamic`, `kinematic` or `static` as\ndescribed by the [analogous box2d docs](http://www.box2d.org/manual.html#_Toc258082973).\n\n* _dynamic_ bodies are treated \"normally\". They are integrated, and collide, and all that.\n* _kinematic_ bodies are bodies that move at a specified velocity. Other bodies collide with them, but they don't bounce off of other bodies.\n* _static_ bodies just stand still. They are like obstacles.", "short_description": "How the body is treated by the simulation.\n", - "line": 207, + "line": 231, "aliases": [], "children": [], "file": "src/core/body.js", @@ -7181,7 +8165,7 @@ ], "description": "The unique id for the body", "short_description": "The unique id for the body", - "line": 144, + "line": 162, "aliases": [], "children": [], "file": "src/core/body.js", @@ -7205,7 +8189,7 @@ "related_to": "Physics.renderer", "description": "Storage for use by the renderer.\n\nThe type of renderer will put different things in the view property.\nBasically, this is how the body \"looks\". It could be a HTMLElement, or\nan Image, etc...\n\nIf your body changes appearance (shape), you should modify this somehow\notherwise the renderer will keep using this same view. If you're letting\nthe renderer create the view for you, just set this to `undefined` if the\nbody gets modified in shape during the simulation.", "short_description": "Storage for use by the renderer.\n", - "line": 228, + "line": 252, "aliases": [], "children": [], "file": "src/core/body.js", @@ -7214,6 +8198,69 @@ "path": "Body.prototype.view", "outFile": "body.html#Body.prototype.view" }, + { + "id": "Body.getCOM", + "type": "class method", + "signatures": [ + { + "arguments": [ + { + "name": "bodies", + "types": [ + "Array" + ], + "description": " The list of bodies\n\n " + }, + { + "name": "com", + "optional": true, + "types": [ + "Physics.vector" + ], + "description": " The vector to put result into. A new vector will be created if not provided.\n\n " + } + ], + "returns": [ + { + "type": "Physics.vector", + "description": " The center of mass position\n\n " + } + ] + } + ], + "arguments": [ + { + "name": "bodies", + "types": [ + "Array" + ], + "description": " The list of bodies\n\n " + }, + { + "name": "com", + "types": [ + "Physics.vector" + ], + "description": " The vector to put result into. A new vector will be created if not provided.\n\n " + } + ], + "returns": [ + { + "type": "Physics.vector", + "description": " The center of mass position\n\n " + } + ], + "description": "Get center of mass position from list of bodies.", + "short_description": "Get center of mass position from list of bodies.", + "line": 517, + "aliases": [], + "children": [], + "file": "src/core/body.js", + "name": "getCOM", + "name_prefix": "Body.", + "path": "Body.getCOM", + "outFile": "body.html#Body.getCOM" + }, { "id": "CircleBody", "type": "class", @@ -7229,6 +8276,152 @@ "path": "CircleBody", "outFile": "circle.html" }, + { + "id": "CompoundBody", + "type": "class", + "superclass": "Body", + "description": "Physics.body('compound')\n\nNot a body in itself. It's a container to group other bodies. The position of the body is the center of mass.\nIt must have at least one child before being added to the world.\n\nAdditional config options:\n\n- children: Array of [[Body]] objects.\n\nExample:\n\n```javascript\nvar thing = Physics.body('compound', {\n // place the center of mass at (300, 200)\n x: 300,\n y: 200,\n // the center of mass is automatically calculated and used to position the shape\n children: [\n body1,\n body2,\n // ...\n ]\n});\n```", + "short_description": "Physics.body('compound')\n", + "line": 32, + "aliases": [], + "children": [ + { + "id": "CompoundBody#addChild", + "type": "instance method", + "signatures": [ + { + "arguments": [ + { + "name": "body", + "types": [ + "Body" + ], + "description": " The child to add\n\n " + } + ], + "returns": [ + { + "type": "this" + } + ] + } + ], + "arguments": [ + { + "name": "body", + "types": [ + "Body" + ], + "description": " The child to add\n\n " + } + ], + "description": "Add a body as a child.", + "short_description": "Add a body as a child.", + "line": 68, + "aliases": [], + "children": [], + "file": "src/bodies/compound.js", + "name": "addChild", + "name_prefix": "CompoundBody#", + "path": "CompoundBody.prototype.addChild", + "outFile": "compound.html#CompoundBody.prototype.addChild" + }, + { + "id": "CompoundBody#addChildren", + "type": "instance method", + "signatures": [ + { + "arguments": [ + { + "name": "bodies", + "types": [ + "Array" + ], + "description": " The list of children to add\n\n " + } + ], + "returns": [ + { + "type": "this" + } + ] + } + ], + "arguments": [ + { + "name": "bodies", + "types": [ + "Array" + ], + "description": " The list of children to add\n\n " + } + ], + "description": "Add an array of children to the compound.", + "short_description": "Add an array of children to the compound.", + "line": 80, + "aliases": [], + "children": [], + "file": "src/bodies/compound.js", + "name": "addChildren", + "name_prefix": "CompoundBody#", + "path": "CompoundBody.prototype.addChildren", + "outFile": "compound.html#CompoundBody.prototype.addChildren" + }, + { + "id": "CompoundBody#clear", + "type": "instance method", + "signatures": [ + { + "arguments": [], + "returns": [ + { + "type": "this" + } + ] + } + ], + "description": "Remove all children.", + "short_description": "Remove all children.", + "line": 142, + "aliases": [], + "children": [], + "file": "src/bodies/compound.js", + "name": "clear", + "name_prefix": "CompoundBody#", + "path": "CompoundBody.prototype.clear", + "outFile": "compound.html#CompoundBody.prototype.clear" + }, + { + "id": "CompoundBody#refreshGeometry", + "type": "instance method", + "signatures": [ + { + "arguments": [], + "returns": [ + { + "type": "this" + } + ] + } + ], + "description": "If the children's positions change, `refreshGeometry()` should be called to fix the shape.", + "short_description": "If the children's positions change, `refreshGeometry()` should be called to fix the shape.", + "line": 159, + "aliases": [], + "children": [], + "file": "src/bodies/compound.js", + "name": "refreshGeometry", + "name_prefix": "CompoundBody#", + "path": "CompoundBody.prototype.refreshGeometry", + "outFile": "compound.html#CompoundBody.prototype.refreshGeometry" + } + ], + "file": "src/bodies/compound.js", + "subclasses": [], + "name": "CompoundBody", + "path": "CompoundBody", + "outFile": "compound.html" + }, { "id": "ConvexPolygonBody", "type": "class", @@ -7279,6 +8472,7 @@ "file": "src/core/body.js", "subclasses": [ "CircleBody", + "CompoundBody", "ConvexPolygonBody", "PointBody", "RectangleBody" @@ -7315,7 +8509,7 @@ "internal": true, "description": "Initialization. Internal use.", "short_description": "Initialization. Internal use.", - "line": 65, + "line": 72, "aliases": [], "children": [], "file": "src/core/body.js", @@ -7364,7 +8558,7 @@ "related_to": "Physics.util.options", "description": "Set options on this instance.\n\nAccess options directly from the options object.\n\nExample:\n\n```javascript\nthis.options.someOption;\n```", "short_description": "Set options on this instance.\n", - "line": 85, + "line": 93, "aliases": [], "children": [], "file": "src/core/body.js", @@ -7378,7 +8572,7 @@ "type": "namespace", "description": "The physical state container.\n\n- ``this.state.pos`` ([[Physics.vector]]) The position vector.\n- ``this.state.vel`` ([[Physics.vector]]) The velocity vector.\n- ``this.state.acc`` ([[Physics.vector]]) The acceleration vector.\n- ``this.state.angular.pos`` ([[Number]]) The angular position (in radians, positive is clockwise starting along the x axis)\n- ``this.state.angular.vel`` ([[Number]]) The angular velocity\n- ``this.state.angular.acc`` ([[Number]]) The angular acceleration\n\nProperties from the previous timestep are stored in:\n```javascript\nthis.state.old; // .pos, .vel, ...\n```", "short_description": "The physical state container.\n", - "line": 106, + "line": 117, "aliases": [], "children": [], "file": "src/core/body.js", @@ -7401,7 +8595,7 @@ ], "description": "The unique id for the body", "short_description": "The unique id for the body", - "line": 144, + "line": 162, "aliases": [], "children": [], "file": "src/core/body.js", @@ -7416,7 +8610,7 @@ "related_to": "Physics.geometry", "description": "The geometry for this body.\n\nBy default it is a `point` geometry which gets overridden.", "short_description": "The geometry for this body.\n", - "line": 153, + "line": 171, "aliases": [], "children": [], "file": "src/core/body.js", @@ -7439,7 +8633,7 @@ ], "description": "The mass.", "short_description": "The mass.", - "line": 160, + "line": 178, "aliases": [], "children": [], "file": "src/core/body.js", @@ -7448,6 +8642,20 @@ "path": "Body.prototype.mass", "outFile": "body.html#Body.prototype.mass" }, + "Body#offset": { + "id": "Body#offset", + "type": "namespace", + "description": "The vector offsetting the body's shape from its center of mass.", + "short_description": "The vector offsetting the body's shape from its center of mass.", + "line": 184, + "aliases": [], + "children": [], + "file": "src/core/body.js", + "name": "offset", + "name_prefix": "Body#", + "path": "Body.prototype.offset", + "outFile": "body.html#Body.prototype.offset" + }, "Body#restitution": { "id": "Body#restitution", "type": "constant", @@ -7462,7 +8670,7 @@ ], "description": "The restitution.\n\nThis is the \"bounciness\" of the body.\nIt's a number between `0` and `1`.\n\nA restitution of 1 is the bounciest.\n\nA restitution of 0 is not bouncy.\n\nWhen colliding the restitutions of bodies are\nmultiplied together to get the restitution between two\nbodies.", "short_description": "The restitution.\n", - "line": 178, + "line": 202, "aliases": [], "children": [], "file": "src/core/body.js", @@ -7485,7 +8693,7 @@ ], "description": "The coefficient of friction of the body.\n\nIt's how much \"slide\" it has during collisions.\n\nA `cof` of `0` will really slidy.\n\nA `cof` of `1` has no slide.\n\nThis is a very simplistic implementation at the moment.\nWhat would be better is to have both static and kinetic\nfriction. But that's not done yet.", "short_description": "The coefficient of friction of the body.\n", - "line": 194, + "line": 218, "aliases": [], "children": [], "file": "src/core/body.js", @@ -7508,7 +8716,7 @@ ], "description": "How the body is treated by the simulation.\n\nThe body can be `dynamic`, `kinematic` or `static` as\ndescribed by the [analogous box2d docs](http://www.box2d.org/manual.html#_Toc258082973).\n\n* _dynamic_ bodies are treated \"normally\". They are integrated, and collide, and all that.\n* _kinematic_ bodies are bodies that move at a specified velocity. Other bodies collide with them, but they don't bounce off of other bodies.\n* _static_ bodies just stand still. They are like obstacles.", "short_description": "How the body is treated by the simulation.\n", - "line": 207, + "line": 231, "aliases": [], "children": [], "file": "src/core/body.js", @@ -7531,7 +8739,7 @@ ], "description": "Determines whether the body should be hidden by the renderer.", "short_description": "Determines whether the body should be hidden by the renderer.", - "line": 213, + "line": 237, "aliases": [], "children": [], "file": "src/core/body.js", @@ -7555,7 +8763,7 @@ "related_to": "Physics.renderer", "description": "Storage for use by the renderer.\n\nThe type of renderer will put different things in the view property.\nBasically, this is how the body \"looks\". It could be a HTMLElement, or\nan Image, etc...\n\nIf your body changes appearance (shape), you should modify this somehow\notherwise the renderer will keep using this same view. If you're letting\nthe renderer create the view for you, just set this to `undefined` if the\nbody gets modified in shape during the simulation.", "short_description": "Storage for use by the renderer.\n", - "line": 228, + "line": 252, "aliases": [], "children": [], "file": "src/core/body.js", @@ -7564,20 +8772,106 @@ "path": "Body.prototype.view", "outFile": "body.html#Body.prototype.view" }, - "Body#style": { - "id": "Body#style", + "Body#styles": { + "id": "Body#styles", "type": "namespace", "related_to": "Physics.renderer", "description": "The styles the renderer should use for creating the view.\n\nThe styles depend on the renderer. See [[Renderer#createView]] for style options.", "short_description": "The styles the renderer should use for creating the view.\n", - "line": 236, + "line": 260, "aliases": [], "children": [], "file": "src/core/body.js", - "name": "style", + "name": "styles", "name_prefix": "Body#", - "path": "Body.prototype.style", - "outFile": "body.html#Body.prototype.style" + "path": "Body.prototype.styles", + "outFile": "body.html#Body.prototype.styles" + }, + "Body#sleep": { + "id": "Body#sleep", + "type": "instance method", + "signatures": [ + { + "arguments": [ + { + "name": "dt", + "optional": true, + "types": [ + "Number" + ], + "description": " Time to advance the idle time\n\n " + } + ], + "returns": [ + { + "type": "Boolean" + } + ] + } + ], + "arguments": [ + { + "name": "dt", + "types": [ + "Number" + ], + "description": " Time to advance the idle time\n\n " + }, + { + "name": "dt", + "types": [ + "Boolean" + ], + "description": " If `true`, the body will be forced to sleep. If `false`, the body will be forced to awake.\n\n " + } + ], + "description": "Get and/or set whether the body is asleep.\n\nIf called with a time (in ms), the time will be added to the idle time and sleep conditions will be checked.", + "short_description": "Get and/or set whether the body is asleep.\n", + "line": 271, + "aliases": [], + "children": [], + "file": "src/core/body.js", + "name": "sleep", + "name_prefix": "Body#", + "path": "Body.prototype.sleep", + "outFile": "body.html#Body.prototype.sleep" + }, + "Body#sleepCheck": { + "id": "Body#sleepCheck", + "type": "instance method", + "signatures": [ + { + "arguments": [ + { + "name": "dt", + "optional": true, + "types": [ + "Number" + ], + "description": " Time to advance the idle time\n\n " + } + ] + } + ], + "arguments": [ + { + "name": "dt", + "types": [ + "Number" + ], + "description": " Time to advance the idle time\n\n " + } + ], + "description": "Check if the body should be sleeping.\n\nCall with no arguments if some event could possibly wake up the body. This will force the body to recheck.", + "short_description": "Check if the body should be sleeping.\n", + "line": 303, + "aliases": [], + "children": [], + "file": "src/core/body.js", + "name": "sleepCheck", + "name_prefix": "Body#", + "path": "Body.prototype.sleepCheck", + "outFile": "body.html#Body.prototype.sleepCheck" }, "Body#setWorld": { "id": "Body#setWorld", @@ -7611,7 +8905,7 @@ ], "description": "Set which world to apply to.\n\nUsually this is called internally. Shouldn't be a need to call this yourself usually.", "short_description": "Set which world to apply to.\n", - "line": 246, + "line": 374, "aliases": [], "children": [], "file": "src/core/body.js", @@ -7652,7 +8946,7 @@ ], "description": "Accelerate the body by adding supplied vector to its current acceleration", "short_description": "Accelerate the body by adding supplied vector to its current acceleration", - "line": 267, + "line": 395, "aliases": [], "children": [], "file": "src/core/body.js", @@ -7708,7 +9002,7 @@ ], "description": "Apply a force at center of mass, or at point `p` relative to the center of mass", "short_description": "Apply a force at center of mass, or at point `p` relative to the center of mass", - "line": 283, + "line": 411, "aliases": [], "children": [], "file": "src/core/body.js", @@ -7717,6 +9011,56 @@ "path": "Body.prototype.applyForce", "outFile": "body.html#Body.prototype.applyForce" }, + "Body#getGlobalOffset": { + "id": "Body#getGlobalOffset", + "type": "instance method", + "signatures": [ + { + "arguments": [ + { + "name": "out", + "optional": true, + "types": [ + "Physics.vector" + ], + "description": " A vector to use to put the result into. One is created if `out` isn't specified.\n\n " + } + ], + "returns": [ + { + "type": "Physics.vector", + "description": " The offset in global coordinates\n\n " + } + ] + } + ], + "arguments": [ + { + "name": "out", + "types": [ + "Physics.vector" + ], + "description": " A vector to use to put the result into. One is created if `out` isn't specified.\n\n " + } + ], + "returns": [ + { + "type": "Physics.vector", + "description": " The offset in global coordinates\n\n " + } + ], + "related_to": "Body#offset", + "description": "Get the body offset vector (from the center of mass) for the body's shape in global coordinates.", + "short_description": "Get the body offset vector (from the center of mass) for the body's shape in global coordinates.", + "line": 445, + "aliases": [], + "children": [], + "file": "src/core/body.js", + "name": "getGlobalOffset", + "name_prefix": "Body#", + "path": "Body.prototype.getGlobalOffset", + "outFile": "body.html#Body.prototype.getGlobalOffset" + }, "Body#aabb": { "id": "Body#aabb", "type": "instance method", @@ -7739,7 +9083,7 @@ "related_to": "Physics.aabb", "description": "Get the Axis aligned bounding box for the body in its current position and rotation", "short_description": "Get the Axis aligned bounding box for the body in its current position and rotation", - "line": 316, + "line": 458, "aliases": [], "children": [], "file": "src/core/body.js", @@ -7748,6 +9092,102 @@ "path": "Body.prototype.aabb", "outFile": "body.html#Body.prototype.aabb" }, + "Body#toBodyCoords": { + "id": "Body#toBodyCoords", + "type": "instance method", + "signatures": [ + { + "arguments": [ + { + "name": "v", + "types": [ + "Physics.vector" + ], + "description": " The vector to transform\n\n " + } + ], + "returns": [ + { + "type": "Physics.vector", + "description": " The transformed vector\n\n " + } + ] + } + ], + "arguments": [ + { + "name": "v", + "types": [ + "Physics.vector" + ], + "description": " The vector to transform\n\n " + } + ], + "returns": [ + { + "type": "Physics.vector", + "description": " The transformed vector\n\n " + } + ], + "description": "Transform a vector into coordinates relative to this body.", + "short_description": "Transform a vector into coordinates relative to this body.", + "line": 481, + "aliases": [], + "children": [], + "file": "src/core/body.js", + "name": "toBodyCoords", + "name_prefix": "Body#", + "path": "Body.prototype.toBodyCoords", + "outFile": "body.html#Body.prototype.toBodyCoords" + }, + "Body#toWorldCoords": { + "id": "Body#toWorldCoords", + "type": "instance method", + "signatures": [ + { + "arguments": [ + { + "name": "v", + "types": [ + "Physics.vector" + ], + "description": " The vector to transform\n\n " + } + ], + "returns": [ + { + "type": "Physics.vector", + "description": " The transformed vector\n\n " + } + ] + } + ], + "arguments": [ + { + "name": "v", + "types": [ + "Physics.vector" + ], + "description": " The vector to transform\n\n " + } + ], + "returns": [ + { + "type": "Physics.vector", + "description": " The transformed vector\n\n " + } + ], + "description": "Transform a vector from body coordinates into world coordinates.", + "short_description": "Transform a vector from body coordinates into world coordinates.", + "line": 492, + "aliases": [], + "children": [], + "file": "src/core/body.js", + "name": "toWorldCoords", + "name_prefix": "Body#", + "path": "Body.prototype.toWorldCoords", + "outFile": "body.html#Body.prototype.toWorldCoords" + }, "Body#recalc": { "id": "Body#recalc", "type": "instance method", @@ -7763,7 +9203,7 @@ ], "description": "Recalculate properties.\n\nIntended to be overridden by subclasses. Call when body physical properties are changed.", "short_description": "Recalculate properties.\n", - "line": 335, + "line": 503, "aliases": [], "children": [], "file": "src/core/body.js", @@ -7772,6 +9212,131 @@ "path": "Body.prototype.recalc", "outFile": "body.html#Body.prototype.recalc" }, + "Body.getCOM": { + "id": "Body.getCOM", + "type": "class method", + "signatures": [ + { + "arguments": [ + { + "name": "bodies", + "types": [ + "Array" + ], + "description": " The list of bodies\n\n " + }, + { + "name": "com", + "optional": true, + "types": [ + "Physics.vector" + ], + "description": " The vector to put result into. A new vector will be created if not provided.\n\n " + } + ], + "returns": [ + { + "type": "Physics.vector", + "description": " The center of mass position\n\n " + } + ] + } + ], + "arguments": [ + { + "name": "bodies", + "types": [ + "Array" + ], + "description": " The list of bodies\n\n " + }, + { + "name": "com", + "types": [ + "Physics.vector" + ], + "description": " The vector to put result into. A new vector will be created if not provided.\n\n " + } + ], + "returns": [ + { + "type": "Physics.vector", + "description": " The center of mass position\n\n " + } + ], + "description": "Get center of mass position from list of bodies.", + "short_description": "Get center of mass position from list of bodies.", + "line": 517, + "aliases": [], + "children": [], + "file": "src/core/body.js", + "name": "getCOM", + "name_prefix": "Body.", + "path": "Body.getCOM", + "outFile": "body.html#Body.getCOM" + }, + "Physics.geometry.regularPolygonVertices": { + "id": "Physics.geometry.regularPolygonVertices", + "type": "class method", + "signatures": [ + { + "arguments": [ + { + "name": "sides", + "types": [ + "Number" + ], + "description": " Number of sides the polygon has\n\n " + }, + { + "name": "radius", + "types": [ + "Number" + ], + "description": " Size from center to a vertex\n\n " + } + ], + "returns": [ + { + "type": "Array", + "description": " A list of [[Vectorish]] objects representing the vertices\n\n " + } + ] + } + ], + "arguments": [ + { + "name": "sides", + "types": [ + "Number" + ], + "description": " Number of sides the polygon has\n\n " + }, + { + "name": "radius", + "types": [ + "Number" + ], + "description": " Size from center to a vertex\n\n " + } + ], + "returns": [ + { + "type": "Array", + "description": " A list of [[Vectorish]] objects representing the vertices\n\n " + } + ], + "description": "Generate a list of vertices for a regular polygon of any number of sides.", + "short_description": "Generate a list of vertices for a regular polygon of any number of sides.", + "line": 13, + "aliases": [], + "children": [], + "file": "src/core/geometry-helpers.js", + "name": "regularPolygonVertices", + "name_prefix": "Physics.geometry.", + "path": "Physics.geometry.regularPolygonVertices", + "outFile": "geometry-helpers.html#Physics.geometry.regularPolygonVertices" + }, "Physics.geometry.isPolygonConvex": { "id": "Physics.geometry.isPolygonConvex", "type": "class method", @@ -7811,7 +9376,7 @@ ], "description": "Determine if polygon hull is convex", "short_description": "Determine if polygon hull is convex", - "line": 12, + "line": 39, "aliases": [], "children": [], "file": "src/core/geometry-helpers.js", @@ -7859,7 +9424,7 @@ ], "description": "Gets the moment of inertia of a convex polygon\n\nSee [List of moments of inertia](http://en.wikipedia.org/wiki/List_of_moments_of_inertia)\nfor more information.\n\n_Note_: we make the following assumpations:\n* mass is unitary (== 1)\n* axis of rotation is the origin", "short_description": "Gets the moment of inertia of a convex polygon\n", - "line": 77, + "line": 104, "aliases": [], "children": [], "file": "src/core/geometry-helpers.js", @@ -7921,7 +9486,7 @@ ], "description": "Check if point is inside polygon hull.", "short_description": "Check if point is inside polygon hull.", - "line": 128, + "line": 155, "aliases": [], "children": [], "file": "src/core/geometry-helpers.js", @@ -7969,7 +9534,7 @@ ], "description": "Get the signed area of the polygon.", "short_description": "Get the signed area of the polygon.", - "line": 175, + "line": 202, "aliases": [], "children": [], "file": "src/core/geometry-helpers.js", @@ -8017,7 +9582,7 @@ ], "description": "Get the coordinates of the centroid.", "short_description": "Get the coordinates of the centroid.", - "line": 213, + "line": 240, "aliases": [], "children": [], "file": "src/core/geometry-helpers.js", @@ -8093,7 +9658,7 @@ ], "description": "Get the closest point on a discrete line to specified point.", "short_description": "Get the closest point on a discrete line to specified point.", - "line": 264, + "line": 291, "aliases": [], "children": [], "file": "src/core/geometry-helpers.js", @@ -8192,7 +9757,7 @@ ], "description": "Get the signed area of the polygon.", "short_description": "Get the signed area of the polygon.", - "line": 175, + "line": 202, "aliases": [], "children": [], "file": "src/core/geometry-helpers.js", @@ -8240,7 +9805,7 @@ ], "description": "Get the coordinates of the centroid.", "short_description": "Get the coordinates of the centroid.", - "line": 213, + "line": 240, "aliases": [], "children": [], "file": "src/core/geometry-helpers.js", @@ -8288,7 +9853,7 @@ ], "description": "Gets the moment of inertia of a convex polygon\n\nSee [List of moments of inertia](http://en.wikipedia.org/wiki/List_of_moments_of_inertia)\nfor more information.\n\n_Note_: we make the following assumpations:\n* mass is unitary (== 1)\n* axis of rotation is the origin", "short_description": "Gets the moment of inertia of a convex polygon\n", - "line": 77, + "line": 104, "aliases": [], "children": [], "file": "src/core/geometry-helpers.js", @@ -8350,7 +9915,7 @@ ], "description": "Check if point is inside polygon hull.", "short_description": "Check if point is inside polygon hull.", - "line": 128, + "line": 155, "aliases": [], "children": [], "file": "src/core/geometry-helpers.js", @@ -8398,7 +9963,7 @@ ], "description": "Determine if polygon hull is convex", "short_description": "Determine if polygon hull is convex", - "line": 12, + "line": 39, "aliases": [], "children": [], "file": "src/core/geometry-helpers.js", @@ -8474,7 +10039,7 @@ ], "description": "Get the closest point on a discrete line to specified point.", "short_description": "Get the closest point on a discrete line to specified point.", - "line": 264, + "line": 291, "aliases": [], "children": [], "file": "src/core/geometry-helpers.js", @@ -8483,6 +10048,68 @@ "path": "Physics.geometry.nearestPointOnLine", "outFile": "geometry-helpers.html#Physics.geometry.nearestPointOnLine" }, + { + "id": "Physics.geometry.regularPolygonVertices", + "type": "class method", + "signatures": [ + { + "arguments": [ + { + "name": "sides", + "types": [ + "Number" + ], + "description": " Number of sides the polygon has\n\n " + }, + { + "name": "radius", + "types": [ + "Number" + ], + "description": " Size from center to a vertex\n\n " + } + ], + "returns": [ + { + "type": "Array", + "description": " A list of [[Vectorish]] objects representing the vertices\n\n " + } + ] + } + ], + "arguments": [ + { + "name": "sides", + "types": [ + "Number" + ], + "description": " Number of sides the polygon has\n\n " + }, + { + "name": "radius", + "types": [ + "Number" + ], + "description": " Size from center to a vertex\n\n " + } + ], + "returns": [ + { + "type": "Array", + "description": " A list of [[Vectorish]] objects representing the vertices\n\n " + } + ], + "description": "Generate a list of vertices for a regular polygon of any number of sides.", + "short_description": "Generate a list of vertices for a regular polygon of any number of sides.", + "line": 13, + "aliases": [], + "children": [], + "file": "src/core/geometry-helpers.js", + "name": "regularPolygonVertices", + "name_prefix": "Physics.geometry.", + "path": "Physics.geometry.regularPolygonVertices", + "outFile": "geometry-helpers.html#Physics.geometry.regularPolygonVertices" + }, { "id": "Geometry", "type": "class", @@ -8771,6 +10398,108 @@ "path": "CircleGeometry", "outFile": "circle.html" }, + { + "id": "CompoundGeometry", + "type": "class", + "superclass": "Geometry", + "description": "Physics.geometry('compound')\n\nGeometry for compound shapes.\n\nExample:\n\n```javascript\nvar thing = Physics.geometry('compound');\nthing.addChild( child, pos, rotation );\n```", + "short_description": "Physics.geometry('compound')\n", + "line": 15, + "aliases": [], + "children": [ + { + "id": "CompoundGeometry#addChild", + "type": "instance method", + "signatures": [ + { + "arguments": [ + { + "name": "geometry", + "types": [ + "Geometry" + ], + "description": " The child to add.\n\n " + }, + { + "name": "pos", + "types": [ + "Physics.vector" + ], + "description": " The position to add the child at.\n\n " + } + ], + "returns": [ + { + "type": "this" + } + ] + } + ], + "arguments": [ + { + "name": "geometry", + "types": [ + "Geometry" + ], + "description": " The child to add.\n\n " + }, + { + "name": "pos", + "types": [ + "Physics.vector" + ], + "description": " The position to add the child at.\n\n " + }, + { + "name": "angle", + "types": [ + "Number" + ], + "description": " The rotation angle\n\n " + } + ], + "description": "Add a child at relative position.", + "short_description": "Add a child at relative position.", + "line": 45, + "aliases": [], + "children": [], + "file": "src/geometries/compound.js", + "name": "addChild", + "name_prefix": "CompoundGeometry#", + "path": "CompoundGeometry.prototype.addChild", + "outFile": "compound.html#CompoundGeometry.prototype.addChild" + }, + { + "id": "CompoundGeometry#clear", + "type": "instance method", + "signatures": [ + { + "arguments": [], + "returns": [ + { + "type": "this" + } + ] + } + ], + "description": "Remove all children.", + "short_description": "Remove all children.", + "line": 62, + "aliases": [], + "children": [], + "file": "src/geometries/compound.js", + "name": "clear", + "name_prefix": "CompoundGeometry#", + "path": "CompoundGeometry.prototype.clear", + "outFile": "compound.html#CompoundGeometry.prototype.clear" + } + ], + "file": "src/geometries/compound.js", + "subclasses": [], + "name": "CompoundGeometry", + "path": "CompoundGeometry", + "outFile": "compound.html" + }, { "id": "ConvexPolygonGeometry", "type": "class", @@ -8910,6 +10639,7 @@ "file": "src/core/geometry.js", "subclasses": [ "CircleGeometry", + "CompoundGeometry", "ConvexPolygonGeometry", "PointGeometry", "RectangleGeometry" @@ -9213,6 +10943,108 @@ "path": "CircleGeometry", "outFile": "circle.html" }, + { + "id": "CompoundGeometry", + "type": "class", + "superclass": "Geometry", + "description": "Physics.geometry('compound')\n\nGeometry for compound shapes.\n\nExample:\n\n```javascript\nvar thing = Physics.geometry('compound');\nthing.addChild( child, pos, rotation );\n```", + "short_description": "Physics.geometry('compound')\n", + "line": 15, + "aliases": [], + "children": [ + { + "id": "CompoundGeometry#addChild", + "type": "instance method", + "signatures": [ + { + "arguments": [ + { + "name": "geometry", + "types": [ + "Geometry" + ], + "description": " The child to add.\n\n " + }, + { + "name": "pos", + "types": [ + "Physics.vector" + ], + "description": " The position to add the child at.\n\n " + } + ], + "returns": [ + { + "type": "this" + } + ] + } + ], + "arguments": [ + { + "name": "geometry", + "types": [ + "Geometry" + ], + "description": " The child to add.\n\n " + }, + { + "name": "pos", + "types": [ + "Physics.vector" + ], + "description": " The position to add the child at.\n\n " + }, + { + "name": "angle", + "types": [ + "Number" + ], + "description": " The rotation angle\n\n " + } + ], + "description": "Add a child at relative position.", + "short_description": "Add a child at relative position.", + "line": 45, + "aliases": [], + "children": [], + "file": "src/geometries/compound.js", + "name": "addChild", + "name_prefix": "CompoundGeometry#", + "path": "CompoundGeometry.prototype.addChild", + "outFile": "compound.html#CompoundGeometry.prototype.addChild" + }, + { + "id": "CompoundGeometry#clear", + "type": "instance method", + "signatures": [ + { + "arguments": [], + "returns": [ + { + "type": "this" + } + ] + } + ], + "description": "Remove all children.", + "short_description": "Remove all children.", + "line": 62, + "aliases": [], + "children": [], + "file": "src/geometries/compound.js", + "name": "clear", + "name_prefix": "CompoundGeometry#", + "path": "CompoundGeometry.prototype.clear", + "outFile": "compound.html#CompoundGeometry.prototype.clear" + } + ], + "file": "src/geometries/compound.js", + "subclasses": [], + "name": "CompoundGeometry", + "path": "CompoundGeometry", + "outFile": "compound.html" + }, { "id": "ConvexPolygonGeometry", "type": "class", @@ -9352,6 +11184,7 @@ "file": "src/core/geometry.js", "subclasses": [ "CircleGeometry", + "CompoundGeometry", "ConvexPolygonGeometry", "PointGeometry", "RectangleGeometry" @@ -9641,7 +11474,7 @@ "types": [ "Object" ], - "description": " The configuration for that integrator ( depends on integrator ).\n\n Available options and defaults:\n\n \n\n ```javascript\n\n {\n\n // drag applied during integration\n\n // 0 means vacuum\n\n // 0.9 means molasses\n\n drag: 0\n\n }\n\n ```\n\n " + "description": " The configuration for that integrator ( depends on integrator ).\n\n Available options and defaults:\n\n\n\n ```javascript\n\n {\n\n // drag applied during integration\n\n // 0 means vacuum\n\n // 0.9 means molasses\n\n drag: 0\n\n }\n\n ```\n\n " } ], "returns": [ @@ -9664,7 +11497,7 @@ "types": [ "Object" ], - "description": " The configuration for that integrator ( depends on integrator ).\n\n Available options and defaults:\n\n \n\n ```javascript\n\n {\n\n // drag applied during integration\n\n // 0 means vacuum\n\n // 0.9 means molasses\n\n drag: 0\n\n }\n\n ```\n\n " + "description": " The configuration for that integrator ( depends on integrator ).\n\n Available options and defaults:\n\n\n\n ```javascript\n\n {\n\n // drag applied during integration\n\n // 0 means vacuum\n\n // 0.9 means molasses\n\n drag: 0\n\n }\n\n ```\n\n " } ], "related_to": "Physics.util.decorator", @@ -9709,7 +11542,7 @@ ], "description": "Connect to a world.\n\nExtend this when creating integrators if you need to specify pubsub management.\nAutomatically called when added to world by the [[Integrator#setWorld]] method.", "short_description": "Connect to a world.\n", - "line": 133, + "line": 134, "aliases": [], "children": [], "file": "src/core/integrator.js", @@ -9745,7 +11578,7 @@ ], "description": "Disconnect from a world.\n\nExtend this when creating integrators if you need to specify pubsub management.\nAutomatically called when added to world by the [[Integrator#setWorld]] method.", "short_description": "Disconnect from a world.\n", - "line": 144, + "line": 145, "aliases": [], "children": [], "file": "src/core/integrator.js", @@ -9837,14 +11670,14 @@ ], "description": "Integrate bodies by timestep.\n\nWill emit `integrate:velocities` and `integrate:positions`\nevents on the world.", "short_description": "Integrate bodies by timestep.\n", - "line": 99, + "line": 44, "aliases": [], "children": [], - "file": "src/core/integrator.js", + "file": "src/integrators/velocity-verlet.js", "name": "integrate", "name_prefix": "Integrator#", "path": "Integrator.prototype.integrate", - "outFile": "integrator.html#Integrator.prototype.integrate" + "outFile": "velocity-verlet.html#Integrator.prototype.integrate" }, { "id": "Integrator#integratePositions", @@ -9887,7 +11720,7 @@ ], "description": "Just integrate the positions.\n\nCalled after [[Integrator#integrateVelocities]].\n\nShould be overridden when creating integrators.", "short_description": "Just integrate the positions.\n", - "line": 171, + "line": 172, "aliases": [], "children": [], "file": "src/core/integrator.js", @@ -9937,7 +11770,7 @@ ], "description": "Just integrate the velocities.\n\nShould be overridden when creating integrators.", "short_description": "Just integrate the velocities.\n", - "line": 155, + "line": 156, "aliases": [], "children": [], "file": "src/core/integrator.js", @@ -10027,7 +11860,7 @@ ], "description": "Set which world to apply to.\n\nUsually this is called internally. Shouldn't be a need to call this yourself usually.", "short_description": "Set which world to apply to.\n", - "line": 74, + "line": 75, "aliases": [], "children": [], "file": "src/core/integrator.js", @@ -10051,11 +11884,26 @@ "path": "ImprovedEuler", "outFile": "improved-euler.html" }, + { + "id": "VelocityVerlet", + "type": "class", + "superclass": "Integrator", + "description": "`Physics.integrator('velocity-verlet')`.\n\nThe velocity-verlet integrator.", + "short_description": "`Physics.integrator('velocity-verlet')`.\n", + "line": 26, + "aliases": [], + "children": [], + "file": "src/integrators/velocity-verlet.js", + "subclasses": [], + "name": "VelocityVerlet", + "path": "VelocityVerlet", + "outFile": "velocity-verlet.html" + }, { "id": "Verlet", "type": "class", "superclass": "Integrator", - "description": "`Physics.integrator('verlet')`.\n\nThe improved euler integrator.", + "description": "`Physics.integrator('verlet')`.\n\nThe verlet integrator.", "short_description": "`Physics.integrator('verlet')`.\n", "line": 26, "aliases": [], @@ -10070,6 +11918,7 @@ "file": "src/core/integrator.js", "subclasses": [ "ImprovedEuler", + "VelocityVerlet", "Verlet" ], "name": "Integrator", @@ -10119,7 +11968,7 @@ ], "description": "Connect to a world.\n\nExtend this when creating integrators if you need to specify pubsub management.\nAutomatically called when added to world by the [[Integrator#setWorld]] method.", "short_description": "Connect to a world.\n", - "line": 133, + "line": 134, "aliases": [], "children": [], "file": "src/core/integrator.js", @@ -10155,7 +12004,7 @@ ], "description": "Disconnect from a world.\n\nExtend this when creating integrators if you need to specify pubsub management.\nAutomatically called when added to world by the [[Integrator#setWorld]] method.", "short_description": "Disconnect from a world.\n", - "line": 144, + "line": 145, "aliases": [], "children": [], "file": "src/core/integrator.js", @@ -10247,14 +12096,14 @@ ], "description": "Integrate bodies by timestep.\n\nWill emit `integrate:velocities` and `integrate:positions`\nevents on the world.", "short_description": "Integrate bodies by timestep.\n", - "line": 99, + "line": 44, "aliases": [], "children": [], - "file": "src/core/integrator.js", + "file": "src/integrators/velocity-verlet.js", "name": "integrate", "name_prefix": "Integrator#", "path": "Integrator.prototype.integrate", - "outFile": "integrator.html#Integrator.prototype.integrate" + "outFile": "velocity-verlet.html#Integrator.prototype.integrate" }, { "id": "Integrator#integratePositions", @@ -10297,7 +12146,7 @@ ], "description": "Just integrate the positions.\n\nCalled after [[Integrator#integrateVelocities]].\n\nShould be overridden when creating integrators.", "short_description": "Just integrate the positions.\n", - "line": 171, + "line": 172, "aliases": [], "children": [], "file": "src/core/integrator.js", @@ -10347,7 +12196,7 @@ ], "description": "Just integrate the velocities.\n\nShould be overridden when creating integrators.", "short_description": "Just integrate the velocities.\n", - "line": 155, + "line": 156, "aliases": [], "children": [], "file": "src/core/integrator.js", @@ -10437,7 +12286,7 @@ ], "description": "Set which world to apply to.\n\nUsually this is called internally. Shouldn't be a need to call this yourself usually.", "short_description": "Set which world to apply to.\n", - "line": 74, + "line": 75, "aliases": [], "children": [], "file": "src/core/integrator.js", @@ -10461,11 +12310,26 @@ "path": "ImprovedEuler", "outFile": "improved-euler.html" }, + { + "id": "VelocityVerlet", + "type": "class", + "superclass": "Integrator", + "description": "`Physics.integrator('velocity-verlet')`.\n\nThe velocity-verlet integrator.", + "short_description": "`Physics.integrator('velocity-verlet')`.\n", + "line": 26, + "aliases": [], + "children": [], + "file": "src/integrators/velocity-verlet.js", + "subclasses": [], + "name": "VelocityVerlet", + "path": "VelocityVerlet", + "outFile": "velocity-verlet.html" + }, { "id": "Verlet", "type": "class", "superclass": "Integrator", - "description": "`Physics.integrator('verlet')`.\n\nThe improved euler integrator.", + "description": "`Physics.integrator('verlet')`.\n\nThe verlet integrator.", "short_description": "`Physics.integrator('verlet')`.\n", "line": 26, "aliases": [], @@ -10480,6 +12344,7 @@ "file": "src/core/integrator.js", "subclasses": [ "ImprovedEuler", + "VelocityVerlet", "Verlet" ], "name": "Integrator", @@ -10604,7 +12469,7 @@ ], "description": "Set which world to apply to.\n\nUsually this is called internally. Shouldn't be a need to call this yourself usually.", "short_description": "Set which world to apply to.\n", - "line": 74, + "line": 75, "aliases": [], "children": [], "file": "src/core/integrator.js", @@ -10659,14 +12524,14 @@ ], "description": "Integrate bodies by timestep.\n\nWill emit `integrate:velocities` and `integrate:positions`\nevents on the world.", "short_description": "Integrate bodies by timestep.\n", - "line": 99, + "line": 44, "aliases": [], "children": [], - "file": "src/core/integrator.js", + "file": "src/integrators/velocity-verlet.js", "name": "integrate", "name_prefix": "Integrator#", "path": "Integrator.prototype.integrate", - "outFile": "integrator.html#Integrator.prototype.integrate" + "outFile": "velocity-verlet.html#Integrator.prototype.integrate" }, "Integrator#connect": { "id": "Integrator#connect", @@ -10695,7 +12560,7 @@ ], "description": "Connect to a world.\n\nExtend this when creating integrators if you need to specify pubsub management.\nAutomatically called when added to world by the [[Integrator#setWorld]] method.", "short_description": "Connect to a world.\n", - "line": 133, + "line": 134, "aliases": [], "children": [], "file": "src/core/integrator.js", @@ -10731,7 +12596,7 @@ ], "description": "Disconnect from a world.\n\nExtend this when creating integrators if you need to specify pubsub management.\nAutomatically called when added to world by the [[Integrator#setWorld]] method.", "short_description": "Disconnect from a world.\n", - "line": 144, + "line": 145, "aliases": [], "children": [], "file": "src/core/integrator.js", @@ -10781,7 +12646,7 @@ ], "description": "Just integrate the velocities.\n\nShould be overridden when creating integrators.", "short_description": "Just integrate the velocities.\n", - "line": 155, + "line": 156, "aliases": [], "children": [], "file": "src/core/integrator.js", @@ -10831,7 +12696,7 @@ ], "description": "Just integrate the positions.\n\nCalled after [[Integrator#integrateVelocities]].\n\nShould be overridden when creating integrators.", "short_description": "Just integrate the positions.\n", - "line": 171, + "line": 172, "aliases": [], "children": [], "file": "src/core/integrator.js", @@ -11323,7 +13188,7 @@ "types": [ "Object" ], - "description": " The configuration for that renderer ( depends on renderer ).\n\n Available options and defaults:\n\n\n\n ```javascript\n\n {\n\n // draw meta data (fps, steps, etc)\n\n meta: false,\n\n // refresh rate of meta info\n\n metaRefresh: 200,\n\n\n\n // width of viewport\n\n width: 600,\n\n // height of viewport\n\n height: 600\n\n }\n\n ```\n\n " + "description": " The configuration for that renderer ( depends on renderer ).\n\n Available options and defaults:\n\n\n\n ```javascript\n\n {\n\n // draw meta data (fps, steps, etc)\n\n meta: false,\n\n // refresh rate of meta info\n\n metaRefresh: 200,\n\n\n\n // width of viewport\n\n width: 600,\n\n // height of viewport\n\n height: 600\n\n // automatically resize the renderer\n\n autoResize: true\n\n }\n\n ```\n\n " } ], "returns": [ @@ -11346,13 +13211,13 @@ "types": [ "Object" ], - "description": " The configuration for that renderer ( depends on renderer ).\n\n Available options and defaults:\n\n\n\n ```javascript\n\n {\n\n // draw meta data (fps, steps, etc)\n\n meta: false,\n\n // refresh rate of meta info\n\n metaRefresh: 200,\n\n\n\n // width of viewport\n\n width: 600,\n\n // height of viewport\n\n height: 600\n\n }\n\n ```\n\n " + "description": " The configuration for that renderer ( depends on renderer ).\n\n Available options and defaults:\n\n\n\n ```javascript\n\n {\n\n // draw meta data (fps, steps, etc)\n\n meta: false,\n\n // refresh rate of meta info\n\n metaRefresh: 200,\n\n\n\n // width of viewport\n\n width: 600,\n\n // height of viewport\n\n height: 600\n\n // automatically resize the renderer\n\n autoResize: true\n\n }\n\n ```\n\n " } ], "related_to": "Physics.util.decorator", "description": "Factory function for creating Renderers.\n\nVisit [the PhysicsJS wiki on Renderers](https://github.com/wellcaffeinated/PhysicsJS/wiki/Renderers)\nfor usage documentation.", "short_description": "Factory function for creating Renderers.\n", - "line": 40, + "line": 44, "aliases": [], "children": [ { @@ -11361,7 +13226,7 @@ "belongs_to": "Physics.renderer", "description": "The base class for renderers created by [[Physics.renderer]] factory function.", "short_description": "The base class for renderers created by [[Physics.renderer]] factory function.", - "line": 47, + "line": 51, "aliases": [], "children": [ { @@ -11419,7 +13284,7 @@ ], "description": "Create a view for the specified geometry.\n\nThe view is used to render the body. It is a cached version\nof the body that gets moved and rotated according to the simulation.\n\nThe styles are used to modify the appearance of the view.\nThey depend on the renderer.\n\nOverride this when creating renderers.", "short_description": "Create a view for the specified geometry.\n", - "line": 148, + "line": 182, "aliases": [], "children": [], "file": "src/core/renderer.js", @@ -11469,7 +13334,7 @@ ], "description": "Draw specified body using specified view.\n\nOverride this when creating renderers.", "short_description": "Draw specified body using specified view.\n", - "line": 192, + "line": 226, "aliases": [], "children": [], "file": "src/core/renderer.js", @@ -11505,7 +13370,7 @@ ], "description": "Draw the meta data.\n\nThe meta data will look like this:\n\n```javascript\nmeta = {\n fps: 60, // the frames per second\n ipf: 4 // the number of iterations per frame\n};\n```\n\nOverride this when creating renderers.", "short_description": "Draw the meta data.\n", - "line": 175, + "line": 209, "aliases": [], "children": [], "file": "src/core/renderer.js", @@ -11542,7 +13407,7 @@ "internal": true, "description": "Initialization. Internal use.", "short_description": "Initialization. Internal use.", - "line": 54, + "line": 58, "aliases": [], "children": [], "file": "src/core/renderer.js", @@ -11597,7 +13462,7 @@ ], "description": "Render the world bodies and meta. Called by world.render()", "short_description": "Render the world bodies and meta. Called by world.render()", - "line": 95, + "line": 129, "aliases": [], "children": [], "file": "src/core/renderer.js", @@ -11606,6 +13471,63 @@ "path": "Renderer.prototype.render", "outFile": "renderer.html#Renderer.prototype.render" }, + { + "id": "Renderer#resize", + "type": "instance method", + "signatures": [ + { + "arguments": [ + { + "name": "width", + "optional": true, + "types": [ + "Number" + ], + "description": " The width in px\n\n " + }, + { + "name": "height", + "optional": true, + "types": [ + "Number" + ], + "description": " The height in px\n\n " + } + ], + "returns": [ + { + "type": "this" + } + ] + } + ], + "arguments": [ + { + "name": "width", + "types": [ + "Number" + ], + "description": " The width in px\n\n " + }, + { + "name": "height", + "types": [ + "Number" + ], + "description": " The height in px\n\n " + } + ], + "description": "Set the dimensions of the renderer.\n\nIf no dimensions are specified it will auto resize.", + "short_description": "Set the dimensions of the renderer.\n", + "line": 87, + "aliases": [], + "children": [], + "file": "src/core/renderer.js", + "name": "resize", + "name_prefix": "Renderer#", + "path": "Renderer.prototype.resize", + "outFile": "renderer.html#Renderer.prototype.resize" + }, { "id": "Renderer#setWorld", "type": "instance method", @@ -11638,7 +13560,7 @@ ], "description": "Set which world to apply to.\n\nUsually this is called internally. Shouldn't be a need to call this yourself usually.", "short_description": "Set which world to apply to.\n", - "line": 73, + "line": 107, "aliases": [], "children": [], "file": "src/core/renderer.js", @@ -11651,9 +13573,9 @@ "id": "CanvasRenderer", "type": "class", "superclass": "Renderer", - "description": "Physics.renderer('canvas')\n\nRenderer that uses HTMLCanvas to render the world bodies.\n\nAdditional config options:\n\n- debug: Draw debug shapes and bounding boxes. (default: `false`)\n- metaEl: HTMLElement to write meta information like FPS and IPF into. (default: autogenerated)\n- offset: Offset the shapes by this amount. (default: `{ x: 0, y: 0 }`)\n- styles: Styles to use to draw the shapes. (see below)\n\nThe styles property should contain _default_ styles for each shape you want to draw.\n\nExample:\n\n```javascript\nstyles: {\n\n 'circle' : {\n strokeStyle: '#542437',\n lineWidth: 1,\n fillStyle: '#542437',\n angleIndicator: 'white'\n },\n\n 'convex-polygon' : {\n strokeStyle: '#542437',\n lineWidth: 1,\n fillStyle: '#542437',\n angleIndicator: 'white'\n }\n}\n```", + "description": "Physics.renderer('canvas')\n\nRenderer that uses HTMLCanvas to render the world bodies.\n\nAdditional config options:\n\n- metaEl: HTMLElement to write meta information like FPS and IPF into. (default: autogenerated)\n- offset: Offset the shapes by this amount. (default: `{ x: 0, y: 0 }`)\n- styles: Styles to use to draw the shapes. (see below)\n\nThe styles property should contain _default_ styles for each shape you want to draw.\n\nExample:\n\n```javascript\nstyles: {\n\n 'circle' : {\n strokeStyle: '#542437',\n lineWidth: 1,\n fillStyle: '#542437',\n angleIndicator: 'white'\n },\n\n 'convex-polygon' : {\n strokeStyle: '#542437',\n lineWidth: 1,\n fillStyle: '#542437',\n angleIndicator: 'white'\n }\n}\n```\n\nStyles can also be defined on a per-body basis. Use the \"styles\" property for a body:\n\nExample:\n\n```javascript\nPhysics.body('circle', {\n // ...\n styles: {\n strokeStyle: '#542437',\n lineWidth: 1,\n fillStyle: '#542437',\n angleIndicator: 'white'\n }\n});\n```\n\nYou can also define an image to use for a body:\n\nExample:\n\n```javascript\nPhysics.body('circle', {\n // ...\n styles: {\n src: 'path/to/image.jpg',\n width: 40,\n height: 50\n }\n});\n```", "short_description": "Physics.renderer('canvas')\n", - "line": 38, + "line": 68, "aliases": [], "children": [ { @@ -11718,7 +13640,7 @@ ], "description": "Create a new layer.\n\nLayers can have the following options:\n\n- width: The width\n- height: The height\n- manual: Draw manually (default: `false`)\n- autoResize: Automatically resize the layer when the renderer's [[CanvasRenderer#resize]] method is called. (default: `true`)\n- follow: A [[Body]]. Offset this layer's rendering to follow a body's position. (default: `null`)\n- offset: The offset [[Vectorish]] for this layer. (default: `null`)\n- scale: Scale the layer by this amount. (default: `1`)\n- zIndex: The zIndex for the layer's HTMLElement. (default: `1`)", "short_description": "Create a new layer.\n", - "line": 192, + "line": 226, "aliases": [], "children": [], "file": "src/renderers/canvas.js", @@ -11727,6 +13649,92 @@ "path": "CanvasRenderer.prototype.addLayer", "outFile": "canvas.html#CanvasRenderer.prototype.addLayer" }, + { + "id": "CanvasRenderer#draw", + "type": "instance method", + "signatures": [ + { + "arguments": [ + { + "name": "geometry", + "types": [ + "Geometry" + ], + "description": " The shape to draw\n\n " + }, + { + "name": "styles", + "optional": true, + "types": [ + "Object" + ], + "description": " The styles configuration\n\n " + }, + { + "name": "ctx", + "optional": true, + "types": [ + "Canvas2DContext" + ], + "description": " The canvas context\n\n " + }, + { + "name": "offset", + "optional": true, + "types": [ + "Vectorish" + ], + "description": " The offset from center\n\n " + } + ], + "returns": [ + { + "type": "this" + } + ] + } + ], + "arguments": [ + { + "name": "geometry", + "types": [ + "Geometry" + ], + "description": " The shape to draw\n\n " + }, + { + "name": "styles", + "types": [ + "Object" + ], + "description": " The styles configuration\n\n " + }, + { + "name": "ctx", + "types": [ + "Canvas2DContext" + ], + "description": " The canvas context\n\n " + }, + { + "name": "offset", + "types": [ + "Vectorish" + ], + "description": " The offset from center\n\n " + } + ], + "description": "Draw a geometry to a context.", + "short_description": "Draw a geometry to a context.", + "line": 630, + "aliases": [], + "children": [], + "file": "src/renderers/canvas.js", + "name": "draw", + "name_prefix": "CanvasRenderer#", + "path": "CanvasRenderer.prototype.draw", + "outFile": "canvas.html#CanvasRenderer.prototype.draw" + }, { "id": "CanvasRenderer#drawCircle", "type": "instance method", @@ -11811,7 +13819,7 @@ ], "description": "Draw a circle to specified canvas context.", "short_description": "Draw a circle to specified canvas context.", - "line": 477, + "line": 514, "aliases": [], "children": [], "file": "src/renderers/canvas.js", @@ -11890,7 +13898,7 @@ ], "description": "Draw a line onto specified canvas context.", "short_description": "Draw a line onto specified canvas context.", - "line": 562, + "line": 599, "aliases": [], "children": [], "file": "src/renderers/canvas.js", @@ -11955,7 +13963,7 @@ ], "description": "Draw a polygon to specified canvas context.", "short_description": "Draw a polygon to specified canvas context.", - "line": 497, + "line": 534, "aliases": [], "children": [], "file": "src/renderers/canvas.js", @@ -12062,7 +14070,7 @@ ], "description": "Draw a rectangle to specified canvas context.", "short_description": "Draw a rectangle to specified canvas context.", - "line": 538, + "line": 575, "aliases": [], "children": [], "file": "src/renderers/canvas.js", @@ -12103,7 +14111,7 @@ ], "description": "Get the layer by id.", "short_description": "Get the layer by id.", - "line": 164, + "line": 198, "aliases": [], "children": [], "file": "src/renderers/canvas.js", @@ -12167,7 +14175,7 @@ ], "description": "Remove a layer.", "short_description": "Remove a layer.", - "line": 408, + "line": 444, "aliases": [], "children": [], "file": "src/renderers/canvas.js", @@ -12222,7 +14230,7 @@ ], "description": "Resize all layer canvases that have the `autoResize` option set to `true`.", "short_description": "Resize all layer canvases that have the `autoResize` option set to `true`.", - "line": 428, + "line": 464, "aliases": [], "children": [], "file": "src/renderers/canvas.js", @@ -12275,7 +14283,7 @@ ], "description": "Set styles on the specified canvas context (or main context).", "short_description": "Set styles on the specified canvas context (or main context).", - "line": 451, + "line": 488, "aliases": [], "children": [], "file": "src/renderers/canvas.js", @@ -12290,7 +14298,7 @@ "belongs_to": "CanvasRenderer", "description": "A rendering layer for the canvas renderer.\n\nCreate by calling [[CanvasRenderer#addLayer]].", "short_description": "A rendering layer for the canvas renderer.\n", - "line": 201, + "line": 235, "aliases": [], "children": [ { @@ -12348,7 +14356,7 @@ ], "description": "Add body (bodies) to the rendering stack for this layer.\n\nBodies must be added to the stack in order to be rendered by this layer UNLESS it is the \"main\" layer.", "short_description": "Add body (bodies) to the rendering stack for this layer.\n", - "line": 290, + "line": 324, "aliases": [], "children": [], "file": "src/renderers/canvas.js", @@ -12371,7 +14379,7 @@ ], "description": "The Bodies this layer is rendering.\n\nThe \"main\" layer will render all world bodies if it's empty.", "short_description": "The Bodies this layer is rendering.\n", - "line": 266, + "line": 300, "aliases": [], "children": [], "file": "src/renderers/canvas.js", @@ -12394,7 +14402,7 @@ ], "description": "The layer's Canvas", "short_description": "The layer's Canvas", - "line": 217, + "line": 251, "aliases": [], "children": [], "file": "src/renderers/canvas.js", @@ -12417,7 +14425,7 @@ ], "description": "The layer's ID", "short_description": "The layer's ID", - "line": 211, + "line": 245, "aliases": [], "children": [], "file": "src/renderers/canvas.js", @@ -12466,7 +14474,7 @@ "related_to": "Physics.util.options", "description": "Set options on this layer.\n\nAccess options directly from the options object.\n\nExample:\n\n```javascript\nthis.options.someOption;\n```", "short_description": "Set options on this layer.\n", - "line": 233, + "line": 267, "aliases": [], "children": [], "file": "src/renderers/canvas.js", @@ -12530,7 +14538,7 @@ ], "description": "Remove body (bodies) from the rendering stack for this layer.", "short_description": "Remove body (bodies) from the rendering stack for this layer.", - "line": 308, + "line": 342, "aliases": [], "children": [], "file": "src/renderers/canvas.js", @@ -12572,7 +14580,7 @@ ], "description": "Render the bodies in this layer's stack.\n\nIf you want you can replace this function with your own to do custom rendering.\n\nExample:\n\n```javascript\nlayer.render = myCustomRenderFn;\n```", "short_description": "Render the bodies in this layer's stack.\n", - "line": 339, + "line": 373, "aliases": [], "children": [], "file": "src/renderers/canvas.js", @@ -12614,7 +14622,7 @@ ], "description": "Reset the stack.", "short_description": "Reset the stack.", - "line": 274, + "line": 308, "aliases": [], "children": [], "file": "src/renderers/canvas.js", @@ -12637,6 +14645,21 @@ "path": "CanvasRenderer", "outFile": "canvas.html" }, + { + "id": "DebugRenderer", + "type": "class", + "superclass": "Renderer", + "description": "Physics.renderer('debug')\n\nExtends canvas renderer with special debugging functionality.\n\nAdditional config options:\n\n- metaEl: HTMLElement to write meta information like FPS and IPF into. (default: autogenerated)\n- offset: Offset the shapes by this amount. (default: `{ x: 0, y: 0 }`)\n- styles: Styles to use to draw the shapes. (see below)\n- drawAABB: whether or not to draw bounding boxes. (default: `true`)\n- drawRealPosition: whether or not to draw the non-interpolated position of bodies. (default: `false`)\n- drawIntervals: whether or not to draw the broadphase (sweep-prune) intervals. (default: `false`)\n- drawContacts: whether or not to draw contact points. (default: `false`)\n- drawSleepState: whether or not to highlight sleeping bodies. (default: `false`)\n- drawBodyState: whether or not to show body position and velocity. (default: `false`)\n- aabbColor: the color of AABBs\n- realBodyStyle: styles used to draw the image of the body at its true non-interpolated position\n- intervalMinColor: color of interval minima\n- intervalMaxColor: color of interval maxima\n- mtvColor: color of minimum transit vector for contacts (overlaps)\n- contactColor: color of contact points\n\nThe styles property should contain _default_ styles for each shape you want to draw.\n\nExample:\n\n```javascript\nstyles: {\n\n 'circle' : {\n strokeStyle: '#542437',\n lineWidth: 1,\n fillStyle: '#542437',\n angleIndicator: 'white'\n },\n\n 'convex-polygon' : {\n strokeStyle: '#542437',\n lineWidth: 1,\n fillStyle: '#542437',\n angleIndicator: 'white'\n }\n}\n```", + "short_description": "Physics.renderer('debug')\n", + "line": 52, + "aliases": [], + "children": [], + "file": "src/renderers/debug.js", + "subclasses": [], + "name": "DebugRenderer", + "path": "DebugRenderer", + "outFile": "debug.html" + }, { "id": "DomRenderer", "type": "class", @@ -12680,7 +14703,7 @@ ], "description": "Event callback to attach a node to the viewport", "short_description": "Event callback to attach a node to the viewport", - "line": 193, + "line": 242, "aliases": [], "children": [], "file": "src/renderers/dom.js", @@ -12731,7 +14754,7 @@ "internal": true, "description": "Set dom element style properties for a circle.", "short_description": "Set dom element style properties for a circle.", - "line": 105, + "line": 136, "aliases": [], "children": [], "file": "src/renderers/dom.js", @@ -12774,7 +14797,7 @@ ], "description": "Event callback to detach a node from the DOM", "short_description": "Event callback to detach a node from the DOM", - "line": 172, + "line": 221, "aliases": [], "children": [], "file": "src/renderers/dom.js", @@ -12783,6 +14806,57 @@ "path": "DomRenderer.prototype.detach", "outFile": "dom.html#DomRenderer.prototype.detach" }, + { + "id": "DomRenderer#pointProperties", + "type": "instance method", + "signatures": [ + { + "arguments": [ + { + "name": "el", + "types": [ + "HTMLElement" + ], + "description": " The element\n\n " + }, + { + "name": "geometry", + "types": [ + "Geometry" + ], + "description": " The body's geometry\n\n " + } + ] + } + ], + "arguments": [ + { + "name": "el", + "types": [ + "HTMLElement" + ], + "description": " The element\n\n " + }, + { + "name": "geometry", + "types": [ + "Geometry" + ], + "description": " The body's geometry\n\n " + } + ], + "internal": true, + "description": "Set dom element style properties for a point.", + "short_description": "Set dom element style properties for a point.", + "line": 120, + "aliases": [], + "children": [], + "file": "src/renderers/dom.js", + "name": "pointProperties", + "name_prefix": "DomRenderer#", + "path": "DomRenderer.prototype.pointProperties", + "outFile": "dom.html#DomRenderer.prototype.pointProperties" + }, { "id": "DomRenderer#rectangleProperties", "type": "instance method", @@ -12825,7 +14899,7 @@ "internal": true, "description": "Set dom element style properties for a rectangle.", "short_description": "Set dom element style properties for a rectangle.", - "line": 122, + "line": 154, "aliases": [], "children": [], "file": "src/renderers/dom.js", @@ -12845,11 +14919,54 @@ "id": "PixiRenderer", "type": "class", "superclass": "Renderer", - "description": "Physics.renderer('pixi')\n\nRenderer that uses the PIXI.js library. [Documentation can be found here](https://github.com/wellcaffeinated/PhysicsJS/wiki/PIXI-Renderer).\n\nAdditional config options:\n\n- debug: Draw debug shapes and bounding boxes. (default: `false`)\n- metaEl: HTMLElement to write meta information like FPS and IPF into. (default: autogenerated)\n- offset: Offset the shapes by this amount. (default: `{ x: 0, y: 0 }`)\n- styles: Styles to use to draw the shapes. (see below)\n\nThe styles property should contain _default_ styles for each shape you want to draw.\n\nExample:\n\n```javascript\nstyles: {\n // Defines the default canvas colour\n 'color': '0x66FF99',\n\n 'circle' : {\n strokeStyle: '0xE8900C',\n lineWidth: 3,\n fillStyle: '0xD5DE4C',\n angleIndicator: '0xE8900C'\n },\n\n 'convex-polygon' : {\n strokeStyle: '0xE8900C',\n lineWidth: 3,\n fillStyle: '0xD5DE4C',\n angleIndicator: '0xE8900C'\n }\n}\n```", + "description": "Physics.renderer('pixi')\n\nRenderer that uses the PIXI.js library. [Documentation can be found here](https://github.com/wellcaffeinated/PhysicsJS/wiki/PIXI-Renderer).\n\nAdditional config options:\n\n- metaEl: HTMLElement to write meta information like FPS and IPF into. (default: autogenerated)\n- offset: Offset the shapes by this amount. (default: `{ x: 0, y: 0 }`)\n- styles: Styles to use to draw the shapes. (see below)\n\nThe styles property should contain _default_ styles for each shape you want to draw.\n\nExample:\n\n```javascript\nstyles: {\n // Defines the default canvas colour\n 'color': '0x66FF99',\n\n 'circle' : {\n strokeStyle: '0xE8900C',\n lineWidth: 3,\n fillStyle: '0xD5DE4C',\n angleIndicator: '0xE8900C',\n strokeAlpha: 1,\n fillAlpha: 1\n },\n\n 'convex-polygon' : {\n strokeStyle: '0xE8900C',\n lineWidth: 3,\n fillStyle: '0xD5DE4C',\n angleIndicator: '0xE8900C'\n }\n}\n```\n\nStyles can also be defined on a per-body basis. Use the \"styles\" property for a body:\n\nExample:\n\n```javascript\nPhysics.body('circle', {\n // ...\n styles: {\n strokeStyle: '0x542437',\n lineWidth: 1,\n fillStyle: '0x542437',\n angleIndicator: '0xFFFFFF'\n }\n});\n```\n\nYou can also define an image to use for a body:\n\nExample:\n\n```javascript\nPhysics.body('circle', {\n // ...\n styles: {\n src: 'path/to/image.jpg',\n width: 40,\n height: 50,\n anchor: { x: 0.5, y: 0.5 }\n }\n});\n```", "short_description": "Physics.renderer('pixi')\n", - "line": 40, + "line": 76, "aliases": [], "children": [ + { + "id": "PixiRenderer#attach", + "type": "instance method", + "signatures": [ + { + "arguments": [ + { + "name": "data", + "types": [ + "PIXI.Graphics", + "Object" + ], + "description": " Graphics object or event data (`data.body`)\n\n " + } + ], + "returns": [ + { + "type": "this" + } + ] + } + ], + "arguments": [ + { + "name": "data", + "types": [ + "PIXI.Graphics", + "Object" + ], + "description": " Graphics object or event data (`data.body`)\n\n " + } + ], + "description": "Event callback to attach a child to the stage", + "short_description": "Event callback to attach a child to the stage", + "line": 239, + "aliases": [], + "children": [], + "file": "src/renderers/pixi-renderer.js", + "name": "attach", + "name_prefix": "PixiRenderer#", + "path": "PixiRenderer.prototype.attach", + "outFile": "pixi-renderer.html#PixiRenderer.prototype.attach" + }, { "id": "PixiRenderer#centerAnchor", "type": "instance method", @@ -12877,7 +14994,7 @@ ], "description": "Centers the anchor to {x: 0.5, y: 0.5} of a view", "short_description": "Centers the anchor to {x: 0.5, y: 0.5} of a view", - "line": 412, + "line": 622, "aliases": [], "children": [], "file": "src/renderers/pixi-renderer.js", @@ -12914,7 +15031,7 @@ "description": " The circle radius\n\n " }, { - "name": "style", + "name": "styles", "types": [ "Object" ], @@ -12952,7 +15069,7 @@ "description": " The circle radius\n\n " }, { - "name": "style", + "name": "styles", "types": [ "Object" ], @@ -12967,7 +15084,7 @@ ], "description": "Create a circle for use in PIXI stage", "short_description": "Create a circle for use in PIXI stage", - "line": 191, + "line": 362, "aliases": [], "children": [], "file": "src/renderers/pixi-renderer.js", @@ -13029,7 +15146,7 @@ ], "description": "Create a PIXI sprite or movie clip.", "short_description": "Create a PIXI sprite or movie clip.", - "line": 353, + "line": 563, "aliases": [], "children": [], "file": "src/renderers/pixi-renderer.js", @@ -13059,7 +15176,7 @@ "description": " Ending point\n\n " }, { - "name": "style", + "name": "styles", "types": [ "Object" ], @@ -13090,7 +15207,7 @@ "description": " Ending point\n\n " }, { - "name": "style", + "name": "styles", "types": [ "Object" ], @@ -13105,7 +15222,7 @@ ], "description": "Create a line for use in PIXI stage", "short_description": "Create a line for use in PIXI stage", - "line": 254, + "line": 441, "aliases": [], "children": [], "file": "src/renderers/pixi-renderer.js", @@ -13128,7 +15245,7 @@ "description": " Array of [[Vectorish]] vertices\n\n " }, { - "name": "style", + "name": "styles", "types": [ "Object" ], @@ -13152,7 +15269,7 @@ "description": " Array of [[Vectorish]] vertices\n\n " }, { - "name": "style", + "name": "styles", "types": [ "Object" ], @@ -13167,7 +15284,7 @@ ], "description": "Create a polygon for use in PIXI stage", "short_description": "Create a polygon for use in PIXI stage", - "line": 211, + "line": 399, "aliases": [], "children": [], "file": "src/renderers/pixi-renderer.js", @@ -13176,6 +15293,146 @@ "path": "PixiRenderer.prototype.createPolygon", "outFile": "pixi-renderer.html#PixiRenderer.prototype.createPolygon" }, + { + "id": "PixiRenderer#createRect", + "type": "instance method", + "signatures": [ + { + "arguments": [ + { + "name": "x", + "types": [ + "Number" + ], + "description": " The x coord\n\n " + }, + { + "name": "y", + "types": [ + "Number" + ], + "description": " The y coord\n\n " + }, + { + "name": "r", + "types": [ + "Number" + ], + "description": " The rectangle width\n\n " + }, + { + "name": "styles", + "types": [ + "Number" + ], + "description": " The rectangle height\n\n " + } + ], + "returns": [ + { + "type": "PIXI.Graphics", + "description": " A graphic object representing a circle.\n\n " + } + ] + } + ], + "arguments": [ + { + "name": "x", + "types": [ + "Number" + ], + "description": " The x coord\n\n " + }, + { + "name": "y", + "types": [ + "Number" + ], + "description": " The y coord\n\n " + }, + { + "name": "width", + "types": [ + "Number" + ], + "description": " The rectangle width\n\n " + }, + { + "name": "height", + "types": [ + "Number" + ], + "description": " The rectangle height\n\n " + }, + { + "name": "styles", + "types": [ + "Object" + ], + "description": " The styles configuration\n\n " + } + ], + "returns": [ + { + "type": "PIXI.Graphics", + "description": " A graphic object representing a circle.\n\n " + } + ], + "description": "Create a rectangle for use in PIXI stage", + "short_description": "Create a rectangle for use in PIXI stage", + "line": 382, + "aliases": [], + "children": [], + "file": "src/renderers/pixi-renderer.js", + "name": "createRect", + "name_prefix": "PixiRenderer#", + "path": "PixiRenderer.prototype.createRect", + "outFile": "pixi-renderer.html#PixiRenderer.prototype.createRect" + }, + { + "id": "PixiRenderer#detach", + "type": "instance method", + "signatures": [ + { + "arguments": [ + { + "name": "data", + "types": [ + "PIXI.Graphics", + "Object" + ], + "description": " Graphics object or event data (`data.body`)\n\n " + } + ], + "returns": [ + { + "type": "this" + } + ] + } + ], + "arguments": [ + { + "name": "data", + "types": [ + "PIXI.Graphics", + "Object" + ], + "description": " Graphics object or event data (`data.body`)\n\n " + } + ], + "description": "Event callback to detach a child from the stage", + "short_description": "Event callback to detach a child from the stage", + "line": 220, + "aliases": [], + "children": [], + "file": "src/renderers/pixi-renderer.js", + "name": "detach", + "name_prefix": "PixiRenderer#", + "path": "PixiRenderer.prototype.detach", + "outFile": "pixi-renderer.html#PixiRenderer.prototype.detach" + }, { "id": "PixiRenderer#drawBody", "type": "instance method", @@ -13217,7 +15474,7 @@ ], "description": "Draw a PIXI.DisplayObject to the stage.", "short_description": "Draw a PIXI.DisplayObject to the stage.", - "line": 163, + "line": 287, "aliases": [], "children": [], "file": "src/renderers/pixi-renderer.js", @@ -13272,7 +15529,7 @@ ], "description": "Loads textures defined in a spritesheet", "short_description": "Loads textures defined in a spritesheet", - "line": 135, + "line": 259, "aliases": [], "children": [], "file": "src/renderers/pixi-renderer.js", @@ -13280,6 +15537,68 @@ "name_prefix": "PixiRenderer#", "path": "PixiRenderer.prototype.loadSpriteSheets", "outFile": "pixi-renderer.html#PixiRenderer.prototype.loadSpriteSheets" + }, + { + "id": "PixiRenderer#setStyles", + "type": "instance method", + "signatures": [ + { + "arguments": [ + { + "name": "graphics", + "types": [ + "PIXI.Graphics" + ], + "description": " The graphics object to set styles on\n\n " + }, + { + "name": "styles", + "types": [ + "Object" + ], + "description": " The styles configuration\n\n " + } + ], + "returns": [ + { + "type": "PIXI.Graphics", + "description": " A graphic object\n\n " + } + ] + } + ], + "arguments": [ + { + "name": "graphics", + "types": [ + "PIXI.Graphics" + ], + "description": " The graphics object to set styles on\n\n " + }, + { + "name": "styles", + "types": [ + "Object" + ], + "description": " The styles configuration\n\n " + } + ], + "returns": [ + { + "type": "PIXI.Graphics", + "description": " A graphic object\n\n " + } + ], + "description": "Set styles on pixi graphics object", + "short_description": "Set styles on pixi graphics object", + "line": 322, + "aliases": [], + "children": [], + "file": "src/renderers/pixi-renderer.js", + "name": "setStyles", + "name_prefix": "PixiRenderer#", + "path": "PixiRenderer.prototype.setStyles", + "outFile": "pixi-renderer.html#PixiRenderer.prototype.setStyles" } ], "file": "src/renderers/pixi-renderer.js", @@ -13292,6 +15611,7 @@ "file": "src/core/renderer.js", "subclasses": [ "CanvasRenderer", + "DebugRenderer", "DomRenderer", "PixiRenderer" ], @@ -13312,7 +15632,7 @@ "belongs_to": "Physics.renderer", "description": "The base class for renderers created by [[Physics.renderer]] factory function.", "short_description": "The base class for renderers created by [[Physics.renderer]] factory function.", - "line": 47, + "line": 51, "aliases": [], "children": [ { @@ -13370,7 +15690,7 @@ ], "description": "Create a view for the specified geometry.\n\nThe view is used to render the body. It is a cached version\nof the body that gets moved and rotated according to the simulation.\n\nThe styles are used to modify the appearance of the view.\nThey depend on the renderer.\n\nOverride this when creating renderers.", "short_description": "Create a view for the specified geometry.\n", - "line": 148, + "line": 182, "aliases": [], "children": [], "file": "src/core/renderer.js", @@ -13420,7 +15740,7 @@ ], "description": "Draw specified body using specified view.\n\nOverride this when creating renderers.", "short_description": "Draw specified body using specified view.\n", - "line": 192, + "line": 226, "aliases": [], "children": [], "file": "src/core/renderer.js", @@ -13456,7 +15776,7 @@ ], "description": "Draw the meta data.\n\nThe meta data will look like this:\n\n```javascript\nmeta = {\n fps: 60, // the frames per second\n ipf: 4 // the number of iterations per frame\n};\n```\n\nOverride this when creating renderers.", "short_description": "Draw the meta data.\n", - "line": 175, + "line": 209, "aliases": [], "children": [], "file": "src/core/renderer.js", @@ -13493,7 +15813,7 @@ "internal": true, "description": "Initialization. Internal use.", "short_description": "Initialization. Internal use.", - "line": 54, + "line": 58, "aliases": [], "children": [], "file": "src/core/renderer.js", @@ -13548,7 +15868,7 @@ ], "description": "Render the world bodies and meta. Called by world.render()", "short_description": "Render the world bodies and meta. Called by world.render()", - "line": 95, + "line": 129, "aliases": [], "children": [], "file": "src/core/renderer.js", @@ -13557,6 +15877,63 @@ "path": "Renderer.prototype.render", "outFile": "renderer.html#Renderer.prototype.render" }, + { + "id": "Renderer#resize", + "type": "instance method", + "signatures": [ + { + "arguments": [ + { + "name": "width", + "optional": true, + "types": [ + "Number" + ], + "description": " The width in px\n\n " + }, + { + "name": "height", + "optional": true, + "types": [ + "Number" + ], + "description": " The height in px\n\n " + } + ], + "returns": [ + { + "type": "this" + } + ] + } + ], + "arguments": [ + { + "name": "width", + "types": [ + "Number" + ], + "description": " The width in px\n\n " + }, + { + "name": "height", + "types": [ + "Number" + ], + "description": " The height in px\n\n " + } + ], + "description": "Set the dimensions of the renderer.\n\nIf no dimensions are specified it will auto resize.", + "short_description": "Set the dimensions of the renderer.\n", + "line": 87, + "aliases": [], + "children": [], + "file": "src/core/renderer.js", + "name": "resize", + "name_prefix": "Renderer#", + "path": "Renderer.prototype.resize", + "outFile": "renderer.html#Renderer.prototype.resize" + }, { "id": "Renderer#setWorld", "type": "instance method", @@ -13589,7 +15966,7 @@ ], "description": "Set which world to apply to.\n\nUsually this is called internally. Shouldn't be a need to call this yourself usually.", "short_description": "Set which world to apply to.\n", - "line": 73, + "line": 107, "aliases": [], "children": [], "file": "src/core/renderer.js", @@ -13602,9 +15979,9 @@ "id": "CanvasRenderer", "type": "class", "superclass": "Renderer", - "description": "Physics.renderer('canvas')\n\nRenderer that uses HTMLCanvas to render the world bodies.\n\nAdditional config options:\n\n- debug: Draw debug shapes and bounding boxes. (default: `false`)\n- metaEl: HTMLElement to write meta information like FPS and IPF into. (default: autogenerated)\n- offset: Offset the shapes by this amount. (default: `{ x: 0, y: 0 }`)\n- styles: Styles to use to draw the shapes. (see below)\n\nThe styles property should contain _default_ styles for each shape you want to draw.\n\nExample:\n\n```javascript\nstyles: {\n\n 'circle' : {\n strokeStyle: '#542437',\n lineWidth: 1,\n fillStyle: '#542437',\n angleIndicator: 'white'\n },\n\n 'convex-polygon' : {\n strokeStyle: '#542437',\n lineWidth: 1,\n fillStyle: '#542437',\n angleIndicator: 'white'\n }\n}\n```", + "description": "Physics.renderer('canvas')\n\nRenderer that uses HTMLCanvas to render the world bodies.\n\nAdditional config options:\n\n- metaEl: HTMLElement to write meta information like FPS and IPF into. (default: autogenerated)\n- offset: Offset the shapes by this amount. (default: `{ x: 0, y: 0 }`)\n- styles: Styles to use to draw the shapes. (see below)\n\nThe styles property should contain _default_ styles for each shape you want to draw.\n\nExample:\n\n```javascript\nstyles: {\n\n 'circle' : {\n strokeStyle: '#542437',\n lineWidth: 1,\n fillStyle: '#542437',\n angleIndicator: 'white'\n },\n\n 'convex-polygon' : {\n strokeStyle: '#542437',\n lineWidth: 1,\n fillStyle: '#542437',\n angleIndicator: 'white'\n }\n}\n```\n\nStyles can also be defined on a per-body basis. Use the \"styles\" property for a body:\n\nExample:\n\n```javascript\nPhysics.body('circle', {\n // ...\n styles: {\n strokeStyle: '#542437',\n lineWidth: 1,\n fillStyle: '#542437',\n angleIndicator: 'white'\n }\n});\n```\n\nYou can also define an image to use for a body:\n\nExample:\n\n```javascript\nPhysics.body('circle', {\n // ...\n styles: {\n src: 'path/to/image.jpg',\n width: 40,\n height: 50\n }\n});\n```", "short_description": "Physics.renderer('canvas')\n", - "line": 38, + "line": 68, "aliases": [], "children": [ { @@ -13669,7 +16046,7 @@ ], "description": "Create a new layer.\n\nLayers can have the following options:\n\n- width: The width\n- height: The height\n- manual: Draw manually (default: `false`)\n- autoResize: Automatically resize the layer when the renderer's [[CanvasRenderer#resize]] method is called. (default: `true`)\n- follow: A [[Body]]. Offset this layer's rendering to follow a body's position. (default: `null`)\n- offset: The offset [[Vectorish]] for this layer. (default: `null`)\n- scale: Scale the layer by this amount. (default: `1`)\n- zIndex: The zIndex for the layer's HTMLElement. (default: `1`)", "short_description": "Create a new layer.\n", - "line": 192, + "line": 226, "aliases": [], "children": [], "file": "src/renderers/canvas.js", @@ -13678,6 +16055,92 @@ "path": "CanvasRenderer.prototype.addLayer", "outFile": "canvas.html#CanvasRenderer.prototype.addLayer" }, + { + "id": "CanvasRenderer#draw", + "type": "instance method", + "signatures": [ + { + "arguments": [ + { + "name": "geometry", + "types": [ + "Geometry" + ], + "description": " The shape to draw\n\n " + }, + { + "name": "styles", + "optional": true, + "types": [ + "Object" + ], + "description": " The styles configuration\n\n " + }, + { + "name": "ctx", + "optional": true, + "types": [ + "Canvas2DContext" + ], + "description": " The canvas context\n\n " + }, + { + "name": "offset", + "optional": true, + "types": [ + "Vectorish" + ], + "description": " The offset from center\n\n " + } + ], + "returns": [ + { + "type": "this" + } + ] + } + ], + "arguments": [ + { + "name": "geometry", + "types": [ + "Geometry" + ], + "description": " The shape to draw\n\n " + }, + { + "name": "styles", + "types": [ + "Object" + ], + "description": " The styles configuration\n\n " + }, + { + "name": "ctx", + "types": [ + "Canvas2DContext" + ], + "description": " The canvas context\n\n " + }, + { + "name": "offset", + "types": [ + "Vectorish" + ], + "description": " The offset from center\n\n " + } + ], + "description": "Draw a geometry to a context.", + "short_description": "Draw a geometry to a context.", + "line": 630, + "aliases": [], + "children": [], + "file": "src/renderers/canvas.js", + "name": "draw", + "name_prefix": "CanvasRenderer#", + "path": "CanvasRenderer.prototype.draw", + "outFile": "canvas.html#CanvasRenderer.prototype.draw" + }, { "id": "CanvasRenderer#drawCircle", "type": "instance method", @@ -13762,7 +16225,7 @@ ], "description": "Draw a circle to specified canvas context.", "short_description": "Draw a circle to specified canvas context.", - "line": 477, + "line": 514, "aliases": [], "children": [], "file": "src/renderers/canvas.js", @@ -13841,7 +16304,7 @@ ], "description": "Draw a line onto specified canvas context.", "short_description": "Draw a line onto specified canvas context.", - "line": 562, + "line": 599, "aliases": [], "children": [], "file": "src/renderers/canvas.js", @@ -13906,7 +16369,7 @@ ], "description": "Draw a polygon to specified canvas context.", "short_description": "Draw a polygon to specified canvas context.", - "line": 497, + "line": 534, "aliases": [], "children": [], "file": "src/renderers/canvas.js", @@ -14013,7 +16476,7 @@ ], "description": "Draw a rectangle to specified canvas context.", "short_description": "Draw a rectangle to specified canvas context.", - "line": 538, + "line": 575, "aliases": [], "children": [], "file": "src/renderers/canvas.js", @@ -14054,7 +16517,7 @@ ], "description": "Get the layer by id.", "short_description": "Get the layer by id.", - "line": 164, + "line": 198, "aliases": [], "children": [], "file": "src/renderers/canvas.js", @@ -14118,7 +16581,7 @@ ], "description": "Remove a layer.", "short_description": "Remove a layer.", - "line": 408, + "line": 444, "aliases": [], "children": [], "file": "src/renderers/canvas.js", @@ -14173,7 +16636,7 @@ ], "description": "Resize all layer canvases that have the `autoResize` option set to `true`.", "short_description": "Resize all layer canvases that have the `autoResize` option set to `true`.", - "line": 428, + "line": 464, "aliases": [], "children": [], "file": "src/renderers/canvas.js", @@ -14226,7 +16689,7 @@ ], "description": "Set styles on the specified canvas context (or main context).", "short_description": "Set styles on the specified canvas context (or main context).", - "line": 451, + "line": 488, "aliases": [], "children": [], "file": "src/renderers/canvas.js", @@ -14241,7 +16704,7 @@ "belongs_to": "CanvasRenderer", "description": "A rendering layer for the canvas renderer.\n\nCreate by calling [[CanvasRenderer#addLayer]].", "short_description": "A rendering layer for the canvas renderer.\n", - "line": 201, + "line": 235, "aliases": [], "children": [ { @@ -14299,7 +16762,7 @@ ], "description": "Add body (bodies) to the rendering stack for this layer.\n\nBodies must be added to the stack in order to be rendered by this layer UNLESS it is the \"main\" layer.", "short_description": "Add body (bodies) to the rendering stack for this layer.\n", - "line": 290, + "line": 324, "aliases": [], "children": [], "file": "src/renderers/canvas.js", @@ -14322,7 +16785,7 @@ ], "description": "The Bodies this layer is rendering.\n\nThe \"main\" layer will render all world bodies if it's empty.", "short_description": "The Bodies this layer is rendering.\n", - "line": 266, + "line": 300, "aliases": [], "children": [], "file": "src/renderers/canvas.js", @@ -14345,7 +16808,7 @@ ], "description": "The layer's Canvas", "short_description": "The layer's Canvas", - "line": 217, + "line": 251, "aliases": [], "children": [], "file": "src/renderers/canvas.js", @@ -14368,7 +16831,7 @@ ], "description": "The layer's ID", "short_description": "The layer's ID", - "line": 211, + "line": 245, "aliases": [], "children": [], "file": "src/renderers/canvas.js", @@ -14417,7 +16880,7 @@ "related_to": "Physics.util.options", "description": "Set options on this layer.\n\nAccess options directly from the options object.\n\nExample:\n\n```javascript\nthis.options.someOption;\n```", "short_description": "Set options on this layer.\n", - "line": 233, + "line": 267, "aliases": [], "children": [], "file": "src/renderers/canvas.js", @@ -14481,7 +16944,7 @@ ], "description": "Remove body (bodies) from the rendering stack for this layer.", "short_description": "Remove body (bodies) from the rendering stack for this layer.", - "line": 308, + "line": 342, "aliases": [], "children": [], "file": "src/renderers/canvas.js", @@ -14523,7 +16986,7 @@ ], "description": "Render the bodies in this layer's stack.\n\nIf you want you can replace this function with your own to do custom rendering.\n\nExample:\n\n```javascript\nlayer.render = myCustomRenderFn;\n```", "short_description": "Render the bodies in this layer's stack.\n", - "line": 339, + "line": 373, "aliases": [], "children": [], "file": "src/renderers/canvas.js", @@ -14565,7 +17028,7 @@ ], "description": "Reset the stack.", "short_description": "Reset the stack.", - "line": 274, + "line": 308, "aliases": [], "children": [], "file": "src/renderers/canvas.js", @@ -14588,6 +17051,21 @@ "path": "CanvasRenderer", "outFile": "canvas.html" }, + { + "id": "DebugRenderer", + "type": "class", + "superclass": "Renderer", + "description": "Physics.renderer('debug')\n\nExtends canvas renderer with special debugging functionality.\n\nAdditional config options:\n\n- metaEl: HTMLElement to write meta information like FPS and IPF into. (default: autogenerated)\n- offset: Offset the shapes by this amount. (default: `{ x: 0, y: 0 }`)\n- styles: Styles to use to draw the shapes. (see below)\n- drawAABB: whether or not to draw bounding boxes. (default: `true`)\n- drawRealPosition: whether or not to draw the non-interpolated position of bodies. (default: `false`)\n- drawIntervals: whether or not to draw the broadphase (sweep-prune) intervals. (default: `false`)\n- drawContacts: whether or not to draw contact points. (default: `false`)\n- drawSleepState: whether or not to highlight sleeping bodies. (default: `false`)\n- drawBodyState: whether or not to show body position and velocity. (default: `false`)\n- aabbColor: the color of AABBs\n- realBodyStyle: styles used to draw the image of the body at its true non-interpolated position\n- intervalMinColor: color of interval minima\n- intervalMaxColor: color of interval maxima\n- mtvColor: color of minimum transit vector for contacts (overlaps)\n- contactColor: color of contact points\n\nThe styles property should contain _default_ styles for each shape you want to draw.\n\nExample:\n\n```javascript\nstyles: {\n\n 'circle' : {\n strokeStyle: '#542437',\n lineWidth: 1,\n fillStyle: '#542437',\n angleIndicator: 'white'\n },\n\n 'convex-polygon' : {\n strokeStyle: '#542437',\n lineWidth: 1,\n fillStyle: '#542437',\n angleIndicator: 'white'\n }\n}\n```", + "short_description": "Physics.renderer('debug')\n", + "line": 52, + "aliases": [], + "children": [], + "file": "src/renderers/debug.js", + "subclasses": [], + "name": "DebugRenderer", + "path": "DebugRenderer", + "outFile": "debug.html" + }, { "id": "DomRenderer", "type": "class", @@ -14631,7 +17109,7 @@ ], "description": "Event callback to attach a node to the viewport", "short_description": "Event callback to attach a node to the viewport", - "line": 193, + "line": 242, "aliases": [], "children": [], "file": "src/renderers/dom.js", @@ -14682,7 +17160,7 @@ "internal": true, "description": "Set dom element style properties for a circle.", "short_description": "Set dom element style properties for a circle.", - "line": 105, + "line": 136, "aliases": [], "children": [], "file": "src/renderers/dom.js", @@ -14725,7 +17203,7 @@ ], "description": "Event callback to detach a node from the DOM", "short_description": "Event callback to detach a node from the DOM", - "line": 172, + "line": 221, "aliases": [], "children": [], "file": "src/renderers/dom.js", @@ -14734,6 +17212,57 @@ "path": "DomRenderer.prototype.detach", "outFile": "dom.html#DomRenderer.prototype.detach" }, + { + "id": "DomRenderer#pointProperties", + "type": "instance method", + "signatures": [ + { + "arguments": [ + { + "name": "el", + "types": [ + "HTMLElement" + ], + "description": " The element\n\n " + }, + { + "name": "geometry", + "types": [ + "Geometry" + ], + "description": " The body's geometry\n\n " + } + ] + } + ], + "arguments": [ + { + "name": "el", + "types": [ + "HTMLElement" + ], + "description": " The element\n\n " + }, + { + "name": "geometry", + "types": [ + "Geometry" + ], + "description": " The body's geometry\n\n " + } + ], + "internal": true, + "description": "Set dom element style properties for a point.", + "short_description": "Set dom element style properties for a point.", + "line": 120, + "aliases": [], + "children": [], + "file": "src/renderers/dom.js", + "name": "pointProperties", + "name_prefix": "DomRenderer#", + "path": "DomRenderer.prototype.pointProperties", + "outFile": "dom.html#DomRenderer.prototype.pointProperties" + }, { "id": "DomRenderer#rectangleProperties", "type": "instance method", @@ -14776,7 +17305,7 @@ "internal": true, "description": "Set dom element style properties for a rectangle.", "short_description": "Set dom element style properties for a rectangle.", - "line": 122, + "line": 154, "aliases": [], "children": [], "file": "src/renderers/dom.js", @@ -14796,11 +17325,54 @@ "id": "PixiRenderer", "type": "class", "superclass": "Renderer", - "description": "Physics.renderer('pixi')\n\nRenderer that uses the PIXI.js library. [Documentation can be found here](https://github.com/wellcaffeinated/PhysicsJS/wiki/PIXI-Renderer).\n\nAdditional config options:\n\n- debug: Draw debug shapes and bounding boxes. (default: `false`)\n- metaEl: HTMLElement to write meta information like FPS and IPF into. (default: autogenerated)\n- offset: Offset the shapes by this amount. (default: `{ x: 0, y: 0 }`)\n- styles: Styles to use to draw the shapes. (see below)\n\nThe styles property should contain _default_ styles for each shape you want to draw.\n\nExample:\n\n```javascript\nstyles: {\n // Defines the default canvas colour\n 'color': '0x66FF99',\n\n 'circle' : {\n strokeStyle: '0xE8900C',\n lineWidth: 3,\n fillStyle: '0xD5DE4C',\n angleIndicator: '0xE8900C'\n },\n\n 'convex-polygon' : {\n strokeStyle: '0xE8900C',\n lineWidth: 3,\n fillStyle: '0xD5DE4C',\n angleIndicator: '0xE8900C'\n }\n}\n```", + "description": "Physics.renderer('pixi')\n\nRenderer that uses the PIXI.js library. [Documentation can be found here](https://github.com/wellcaffeinated/PhysicsJS/wiki/PIXI-Renderer).\n\nAdditional config options:\n\n- metaEl: HTMLElement to write meta information like FPS and IPF into. (default: autogenerated)\n- offset: Offset the shapes by this amount. (default: `{ x: 0, y: 0 }`)\n- styles: Styles to use to draw the shapes. (see below)\n\nThe styles property should contain _default_ styles for each shape you want to draw.\n\nExample:\n\n```javascript\nstyles: {\n // Defines the default canvas colour\n 'color': '0x66FF99',\n\n 'circle' : {\n strokeStyle: '0xE8900C',\n lineWidth: 3,\n fillStyle: '0xD5DE4C',\n angleIndicator: '0xE8900C',\n strokeAlpha: 1,\n fillAlpha: 1\n },\n\n 'convex-polygon' : {\n strokeStyle: '0xE8900C',\n lineWidth: 3,\n fillStyle: '0xD5DE4C',\n angleIndicator: '0xE8900C'\n }\n}\n```\n\nStyles can also be defined on a per-body basis. Use the \"styles\" property for a body:\n\nExample:\n\n```javascript\nPhysics.body('circle', {\n // ...\n styles: {\n strokeStyle: '0x542437',\n lineWidth: 1,\n fillStyle: '0x542437',\n angleIndicator: '0xFFFFFF'\n }\n});\n```\n\nYou can also define an image to use for a body:\n\nExample:\n\n```javascript\nPhysics.body('circle', {\n // ...\n styles: {\n src: 'path/to/image.jpg',\n width: 40,\n height: 50,\n anchor: { x: 0.5, y: 0.5 }\n }\n});\n```", "short_description": "Physics.renderer('pixi')\n", - "line": 40, + "line": 76, "aliases": [], "children": [ + { + "id": "PixiRenderer#attach", + "type": "instance method", + "signatures": [ + { + "arguments": [ + { + "name": "data", + "types": [ + "PIXI.Graphics", + "Object" + ], + "description": " Graphics object or event data (`data.body`)\n\n " + } + ], + "returns": [ + { + "type": "this" + } + ] + } + ], + "arguments": [ + { + "name": "data", + "types": [ + "PIXI.Graphics", + "Object" + ], + "description": " Graphics object or event data (`data.body`)\n\n " + } + ], + "description": "Event callback to attach a child to the stage", + "short_description": "Event callback to attach a child to the stage", + "line": 239, + "aliases": [], + "children": [], + "file": "src/renderers/pixi-renderer.js", + "name": "attach", + "name_prefix": "PixiRenderer#", + "path": "PixiRenderer.prototype.attach", + "outFile": "pixi-renderer.html#PixiRenderer.prototype.attach" + }, { "id": "PixiRenderer#centerAnchor", "type": "instance method", @@ -14828,7 +17400,7 @@ ], "description": "Centers the anchor to {x: 0.5, y: 0.5} of a view", "short_description": "Centers the anchor to {x: 0.5, y: 0.5} of a view", - "line": 412, + "line": 622, "aliases": [], "children": [], "file": "src/renderers/pixi-renderer.js", @@ -14865,7 +17437,7 @@ "description": " The circle radius\n\n " }, { - "name": "style", + "name": "styles", "types": [ "Object" ], @@ -14903,7 +17475,7 @@ "description": " The circle radius\n\n " }, { - "name": "style", + "name": "styles", "types": [ "Object" ], @@ -14918,7 +17490,7 @@ ], "description": "Create a circle for use in PIXI stage", "short_description": "Create a circle for use in PIXI stage", - "line": 191, + "line": 362, "aliases": [], "children": [], "file": "src/renderers/pixi-renderer.js", @@ -14980,7 +17552,7 @@ ], "description": "Create a PIXI sprite or movie clip.", "short_description": "Create a PIXI sprite or movie clip.", - "line": 353, + "line": 563, "aliases": [], "children": [], "file": "src/renderers/pixi-renderer.js", @@ -15010,7 +17582,7 @@ "description": " Ending point\n\n " }, { - "name": "style", + "name": "styles", "types": [ "Object" ], @@ -15041,7 +17613,7 @@ "description": " Ending point\n\n " }, { - "name": "style", + "name": "styles", "types": [ "Object" ], @@ -15056,7 +17628,7 @@ ], "description": "Create a line for use in PIXI stage", "short_description": "Create a line for use in PIXI stage", - "line": 254, + "line": 441, "aliases": [], "children": [], "file": "src/renderers/pixi-renderer.js", @@ -15079,7 +17651,7 @@ "description": " Array of [[Vectorish]] vertices\n\n " }, { - "name": "style", + "name": "styles", "types": [ "Object" ], @@ -15103,7 +17675,7 @@ "description": " Array of [[Vectorish]] vertices\n\n " }, { - "name": "style", + "name": "styles", "types": [ "Object" ], @@ -15118,7 +17690,7 @@ ], "description": "Create a polygon for use in PIXI stage", "short_description": "Create a polygon for use in PIXI stage", - "line": 211, + "line": 399, "aliases": [], "children": [], "file": "src/renderers/pixi-renderer.js", @@ -15127,6 +17699,146 @@ "path": "PixiRenderer.prototype.createPolygon", "outFile": "pixi-renderer.html#PixiRenderer.prototype.createPolygon" }, + { + "id": "PixiRenderer#createRect", + "type": "instance method", + "signatures": [ + { + "arguments": [ + { + "name": "x", + "types": [ + "Number" + ], + "description": " The x coord\n\n " + }, + { + "name": "y", + "types": [ + "Number" + ], + "description": " The y coord\n\n " + }, + { + "name": "r", + "types": [ + "Number" + ], + "description": " The rectangle width\n\n " + }, + { + "name": "styles", + "types": [ + "Number" + ], + "description": " The rectangle height\n\n " + } + ], + "returns": [ + { + "type": "PIXI.Graphics", + "description": " A graphic object representing a circle.\n\n " + } + ] + } + ], + "arguments": [ + { + "name": "x", + "types": [ + "Number" + ], + "description": " The x coord\n\n " + }, + { + "name": "y", + "types": [ + "Number" + ], + "description": " The y coord\n\n " + }, + { + "name": "width", + "types": [ + "Number" + ], + "description": " The rectangle width\n\n " + }, + { + "name": "height", + "types": [ + "Number" + ], + "description": " The rectangle height\n\n " + }, + { + "name": "styles", + "types": [ + "Object" + ], + "description": " The styles configuration\n\n " + } + ], + "returns": [ + { + "type": "PIXI.Graphics", + "description": " A graphic object representing a circle.\n\n " + } + ], + "description": "Create a rectangle for use in PIXI stage", + "short_description": "Create a rectangle for use in PIXI stage", + "line": 382, + "aliases": [], + "children": [], + "file": "src/renderers/pixi-renderer.js", + "name": "createRect", + "name_prefix": "PixiRenderer#", + "path": "PixiRenderer.prototype.createRect", + "outFile": "pixi-renderer.html#PixiRenderer.prototype.createRect" + }, + { + "id": "PixiRenderer#detach", + "type": "instance method", + "signatures": [ + { + "arguments": [ + { + "name": "data", + "types": [ + "PIXI.Graphics", + "Object" + ], + "description": " Graphics object or event data (`data.body`)\n\n " + } + ], + "returns": [ + { + "type": "this" + } + ] + } + ], + "arguments": [ + { + "name": "data", + "types": [ + "PIXI.Graphics", + "Object" + ], + "description": " Graphics object or event data (`data.body`)\n\n " + } + ], + "description": "Event callback to detach a child from the stage", + "short_description": "Event callback to detach a child from the stage", + "line": 220, + "aliases": [], + "children": [], + "file": "src/renderers/pixi-renderer.js", + "name": "detach", + "name_prefix": "PixiRenderer#", + "path": "PixiRenderer.prototype.detach", + "outFile": "pixi-renderer.html#PixiRenderer.prototype.detach" + }, { "id": "PixiRenderer#drawBody", "type": "instance method", @@ -15168,7 +17880,7 @@ ], "description": "Draw a PIXI.DisplayObject to the stage.", "short_description": "Draw a PIXI.DisplayObject to the stage.", - "line": 163, + "line": 287, "aliases": [], "children": [], "file": "src/renderers/pixi-renderer.js", @@ -15223,7 +17935,7 @@ ], "description": "Loads textures defined in a spritesheet", "short_description": "Loads textures defined in a spritesheet", - "line": 135, + "line": 259, "aliases": [], "children": [], "file": "src/renderers/pixi-renderer.js", @@ -15231,6 +17943,68 @@ "name_prefix": "PixiRenderer#", "path": "PixiRenderer.prototype.loadSpriteSheets", "outFile": "pixi-renderer.html#PixiRenderer.prototype.loadSpriteSheets" + }, + { + "id": "PixiRenderer#setStyles", + "type": "instance method", + "signatures": [ + { + "arguments": [ + { + "name": "graphics", + "types": [ + "PIXI.Graphics" + ], + "description": " The graphics object to set styles on\n\n " + }, + { + "name": "styles", + "types": [ + "Object" + ], + "description": " The styles configuration\n\n " + } + ], + "returns": [ + { + "type": "PIXI.Graphics", + "description": " A graphic object\n\n " + } + ] + } + ], + "arguments": [ + { + "name": "graphics", + "types": [ + "PIXI.Graphics" + ], + "description": " The graphics object to set styles on\n\n " + }, + { + "name": "styles", + "types": [ + "Object" + ], + "description": " The styles configuration\n\n " + } + ], + "returns": [ + { + "type": "PIXI.Graphics", + "description": " A graphic object\n\n " + } + ], + "description": "Set styles on pixi graphics object", + "short_description": "Set styles on pixi graphics object", + "line": 322, + "aliases": [], + "children": [], + "file": "src/renderers/pixi-renderer.js", + "name": "setStyles", + "name_prefix": "PixiRenderer#", + "path": "PixiRenderer.prototype.setStyles", + "outFile": "pixi-renderer.html#PixiRenderer.prototype.setStyles" } ], "file": "src/renderers/pixi-renderer.js", @@ -15243,6 +18017,7 @@ "file": "src/core/renderer.js", "subclasses": [ "CanvasRenderer", + "DebugRenderer", "DomRenderer", "PixiRenderer" ], @@ -15278,7 +18053,7 @@ "internal": true, "description": "Initialization. Internal use.", "short_description": "Initialization. Internal use.", - "line": 54, + "line": 58, "aliases": [], "children": [], "file": "src/core/renderer.js", @@ -15287,6 +18062,63 @@ "path": "Renderer.prototype.init", "outFile": "renderer.html#Renderer.prototype.init" }, + "Renderer#resize": { + "id": "Renderer#resize", + "type": "instance method", + "signatures": [ + { + "arguments": [ + { + "name": "width", + "optional": true, + "types": [ + "Number" + ], + "description": " The width in px\n\n " + }, + { + "name": "height", + "optional": true, + "types": [ + "Number" + ], + "description": " The height in px\n\n " + } + ], + "returns": [ + { + "type": "this" + } + ] + } + ], + "arguments": [ + { + "name": "width", + "types": [ + "Number" + ], + "description": " The width in px\n\n " + }, + { + "name": "height", + "types": [ + "Number" + ], + "description": " The height in px\n\n " + } + ], + "description": "Set the dimensions of the renderer.\n\nIf no dimensions are specified it will auto resize.", + "short_description": "Set the dimensions of the renderer.\n", + "line": 87, + "aliases": [], + "children": [], + "file": "src/core/renderer.js", + "name": "resize", + "name_prefix": "Renderer#", + "path": "Renderer.prototype.resize", + "outFile": "renderer.html#Renderer.prototype.resize" + }, "Renderer#setWorld": { "id": "Renderer#setWorld", "type": "instance method", @@ -15319,7 +18151,7 @@ ], "description": "Set which world to apply to.\n\nUsually this is called internally. Shouldn't be a need to call this yourself usually.", "short_description": "Set which world to apply to.\n", - "line": 73, + "line": 107, "aliases": [], "children": [], "file": "src/core/renderer.js", @@ -15374,7 +18206,7 @@ ], "description": "Render the world bodies and meta. Called by world.render()", "short_description": "Render the world bodies and meta. Called by world.render()", - "line": 95, + "line": 129, "aliases": [], "children": [], "file": "src/core/renderer.js", @@ -15438,7 +18270,7 @@ ], "description": "Create a view for the specified geometry.\n\nThe view is used to render the body. It is a cached version\nof the body that gets moved and rotated according to the simulation.\n\nThe styles are used to modify the appearance of the view.\nThey depend on the renderer.\n\nOverride this when creating renderers.", "short_description": "Create a view for the specified geometry.\n", - "line": 148, + "line": 182, "aliases": [], "children": [], "file": "src/core/renderer.js", @@ -15474,7 +18306,7 @@ ], "description": "Draw the meta data.\n\nThe meta data will look like this:\n\n```javascript\nmeta = {\n fps: 60, // the frames per second\n ipf: 4 // the number of iterations per frame\n};\n```\n\nOverride this when creating renderers.", "short_description": "Draw the meta data.\n", - "line": 175, + "line": 209, "aliases": [], "children": [], "file": "src/core/renderer.js", @@ -15524,7 +18356,7 @@ ], "description": "Draw specified body using specified view.\n\nOverride this when creating renderers.", "short_description": "Draw specified body using specified view.\n", - "line": 192, + "line": 226, "aliases": [], "children": [], "file": "src/core/renderer.js", @@ -15577,7 +18409,7 @@ "chainable": true, "description": "Multipurpose add method. Add one or many bodies, behaviors, integrators, renderers...", "short_description": "Multipurpose add method. Add one or many bodies, behaviors, integrators, renderers...", - "line": 185, + "line": 204, "aliases": [], "children": [], "file": "src/core/world.js", @@ -15619,7 +18451,7 @@ "chainable": true, "description": "Add a behavior to the world", "short_description": "Add a behavior to the world", - "line": 432, + "line": 467, "aliases": [], "children": [], "file": "src/core/world.js", @@ -15661,7 +18493,7 @@ "chainable": true, "description": "Add a body to the world", "short_description": "Add a body to the world", - "line": 500, + "line": 535, "aliases": [], "children": [], "file": "src/core/world.js", @@ -15680,7 +18512,7 @@ ], "description": "Destroy the world.\n(Bwahahahahaha!)", "short_description": "Destroy the world.\n(Bwahahahahaha!)", - "line": 766, + "line": 803, "aliases": [], "children": [], "file": "src/core/world.js", @@ -15762,7 +18594,7 @@ "related_to": "Physics.query", "description": "Find all matching bodies based on query rules.", "short_description": "Find all matching bodies based on query rules.", - "line": 589, + "line": 624, "aliases": [], "children": [], "file": "src/core/world.js", @@ -15850,7 +18682,7 @@ "related_to": "Physics.query", "description": "Find first matching body based on query rules.", "short_description": "Find first matching body based on query rules.", - "line": 571, + "line": 606, "aliases": [], "children": [], "file": "src/core/world.js", @@ -15880,7 +18712,7 @@ ], "description": "Get copied list of behaviors in the world", "short_description": "Get copied list of behaviors in the world", - "line": 457, + "line": 492, "aliases": [], "children": [], "file": "src/core/world.js", @@ -15910,7 +18742,7 @@ ], "description": "Get copied list of bodies in the world", "short_description": "Get copied list of bodies in the world", - "line": 525, + "line": 560, "aliases": [], "children": [], "file": "src/core/world.js", @@ -15959,7 +18791,7 @@ "chainable": true, "description": "Determine if a thing has been added to world.", "short_description": "Determine if a thing has been added to world.", - "line": 286, + "line": 305, "aliases": [], "children": [], "file": "src/core/world.js", @@ -16024,7 +18856,7 @@ "related_to": "new Physics.world", "description": "Initialization", "short_description": "Initialization", - "line": 125, + "line": 144, "aliases": [], "children": [], "file": "src/core/world.js", @@ -16082,7 +18914,7 @@ "chainable": true, "description": "Get or Set the integrator", "short_description": "Get or Set the integrator", - "line": 332, + "line": 351, "aliases": [], "children": [], "file": "src/core/world.js", @@ -16112,7 +18944,7 @@ ], "description": "Determine if world is paused.", "short_description": "Determine if world is paused.", - "line": 755, + "line": 792, "aliases": [], "children": [], "file": "src/core/world.js", @@ -16149,7 +18981,7 @@ "internal": true, "description": "Do a single iteration.", "short_description": "Do a single iteration.", - "line": 604, + "line": 639, "aliases": [], "children": [], "file": "src/core/world.js", @@ -16197,7 +19029,7 @@ ], "description": "Set config options. Also access options by `.options.