|
@@ -1,11963 +0,0 @@
|
|
|
-/**
|
|
|
- * 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 <jasper@wellcaffeinated.net>
|
|
|
- * Licensed MIT
|
|
|
- */
|
|
|
-
|
|
|
-// ---
|
|
|
-// inside: src/intro.js
|
|
|
-
|
|
|
-(function (root, factory) {
|
|
|
- if (typeof exports === 'object') {
|
|
|
- // Node.
|
|
|
- module.exports = factory.call(root);
|
|
|
- } else if (typeof define === 'function' && define.amd) {
|
|
|
- // AMD. Register as an anonymous module.
|
|
|
- define(function(){ return factory.call(root) });
|
|
|
- } else {
|
|
|
- // Browser globals (root is window)
|
|
|
- root.Physics = factory.call(root);
|
|
|
- }
|
|
|
-}(typeof window !== 'undefined' ? window : this, function () {
|
|
|
-
|
|
|
-'use strict';
|
|
|
-
|
|
|
-var window = this;
|
|
|
-var document = window.document;
|
|
|
-
|
|
|
-/** related to: Physics.world
|
|
|
- * Physics
|
|
|
- *
|
|
|
- * The top-level namespace. All of PhysicsJS is contained in
|
|
|
- * the `Physics` namespace.
|
|
|
- *
|
|
|
- * It may (and should) be invoked as a function to create a world instance. For all intensive purposes, [[Physics]] and [[Physics.world]] are the same thing.
|
|
|
- *
|
|
|
- * See [[new Physics.world]] for config options and function signature.
|
|
|
- *
|
|
|
- * Example:
|
|
|
- *
|
|
|
- * ```javascript
|
|
|
- * Physics( cfg, function( world ) {
|
|
|
- * // use world
|
|
|
- * }); // -> world
|
|
|
- * ```
|
|
|
- **/
|
|
|
-var Physics = function Physics(){
|
|
|
-
|
|
|
- return Physics.world.apply(Physics, arguments);
|
|
|
-};
|
|
|
-
|
|
|
-/**
|
|
|
- * Physics.util
|
|
|
- *
|
|
|
- * Namespace for utility functions.
|
|
|
- **/
|
|
|
-Physics.util = {};
|
|
|
-
|
|
|
-/**
|
|
|
- * == Special ==
|
|
|
- *
|
|
|
- * This section contains miscellaneous functionality.
|
|
|
- **/
|
|
|
-
|
|
|
-
|
|
|
-// ---
|
|
|
-// inside: src/math/aabb.js
|
|
|
-
|
|
|
-(function(){
|
|
|
-
|
|
|
- /**
|
|
|
- * Physics.aabb( minX, minY, maxX, maxY ) -> Object
|
|
|
- * Physics.aabb( pt1, pt2 ) -> Object
|
|
|
- * Physics.aabb( width, height[, pt] ) -> Object
|
|
|
- * - minX (Number): The x coord of the "top left" point
|
|
|
- * - minY (Number): The y coord of the "top left" point
|
|
|
- * - maxX (Number): The x coord of the "bottom right" point
|
|
|
- * - maxY (Number): The y coord of the "bottom right" point
|
|
|
- * - pt1 (Vectorish): The first corner
|
|
|
- * - pt2 (Vectorish): The opposite corner
|
|
|
- * - width (Number): The width of the bounding box
|
|
|
- * - height (Number): The height of the bounding box
|
|
|
- * - pt (Vectorish): The center point of the bounding box
|
|
|
- *
|
|
|
- * Create an Axis Aligned Bounding Box.
|
|
|
- *
|
|
|
- * Signature:
|
|
|
- *
|
|
|
- * ```javascript
|
|
|
- * {
|
|
|
- * x: Number, // the x coord of the center point
|
|
|
- * y: Number, // the y coord of the center point
|
|
|
- * hw: Number, // the half-width
|
|
|
- * hh: Number, // the half-height
|
|
|
- * }
|
|
|
- * ```
|
|
|
- **/
|
|
|
- Physics.aabb = function( minX, minY, maxX, maxY ){
|
|
|
-
|
|
|
- var aabb = { x: 0, y: 0, hw: 0, hh: 0 };
|
|
|
-
|
|
|
- if ( minX === undefined ){
|
|
|
- return aabb;
|
|
|
- }
|
|
|
-
|
|
|
- if ( minX && minX.x !== undefined ){
|
|
|
- // we have a point specified as first arg
|
|
|
- maxX = minY.x;
|
|
|
- maxY = minY.y;
|
|
|
- minY = minX.y;
|
|
|
- minX = minX.x;
|
|
|
- }
|
|
|
-
|
|
|
- if ( maxY === undefined && minX !== undefined && minY !== undefined ){
|
|
|
-
|
|
|
- aabb.hw = minX * 0.5;
|
|
|
- aabb.hh = minY * 0.5;
|
|
|
-
|
|
|
- if ( maxX && maxX.x !== undefined ){
|
|
|
- // we have a point specified as the third arg
|
|
|
- // so we assume it's the center point
|
|
|
- aabb.x = maxX.x;
|
|
|
- aabb.y = maxX.y;
|
|
|
- }
|
|
|
-
|
|
|
- return aabb;
|
|
|
- }
|
|
|
-
|
|
|
- // here, we should have all the arguments as numbers
|
|
|
- aabb.hw = Math.abs(maxX - minX) * 0.5;
|
|
|
- aabb.hh = Math.abs(maxY - minY) * 0.5;
|
|
|
- aabb.x = (maxX + minX) * 0.5;
|
|
|
- aabb.y = (maxY + minY) * 0.5;
|
|
|
-
|
|
|
- return aabb;
|
|
|
- };
|
|
|
-
|
|
|
- /**
|
|
|
- * Physics.aabb.contains( aabb, pt ) -> Boolean
|
|
|
- * - aabb (Object): The aabb
|
|
|
- * - pt (Vectorish): The point
|
|
|
- * + (Boolean): `true` if `pt` is inside `aabb`, `false` otherwise
|
|
|
- *
|
|
|
- * Check if a point is inside an aabb.
|
|
|
- **/
|
|
|
- Physics.aabb.contains = function contains( aabb, pt ){
|
|
|
-
|
|
|
- return (pt.x > (aabb.x - aabb.hw)) &&
|
|
|
- (pt.x < (aabb.x + aabb.hw)) &&
|
|
|
- (pt.y > (aabb.y - aabb.hh)) &&
|
|
|
- (pt.y < (aabb.y + aabb.hh));
|
|
|
- };
|
|
|
-
|
|
|
- /**
|
|
|
- * Physics.aabb.clone( aabb ) -> Object
|
|
|
- * - aabb (Object): The aabb to clone
|
|
|
- * + (Object): The clone
|
|
|
- *
|
|
|
- * Clone an aabb.
|
|
|
- **/
|
|
|
- Physics.aabb.clone = function( aabb ){
|
|
|
- return {
|
|
|
- x: aabb.x,
|
|
|
- y: aabb.y,
|
|
|
- hw: aabb.hw,
|
|
|
- hh: aabb.hh
|
|
|
- };
|
|
|
- };
|
|
|
-
|
|
|
- /**
|
|
|
- * Physics.aabb.union( aabb1, aabb2[, modify] ) -> 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
|
|
|
- * - aabb2 (Object): The second aabb
|
|
|
- * + (Boolean): `true` if they overlap, `false` otherwise
|
|
|
- *
|
|
|
- * Check if two AABBs overlap.
|
|
|
- **/
|
|
|
- Physics.aabb.overlap = function( aabb1, aabb2 ){
|
|
|
-
|
|
|
- var min1 = aabb1.x - aabb1.hw
|
|
|
- ,min2 = aabb2.x - aabb2.hw
|
|
|
- ,max1 = aabb1.x + aabb1.hw
|
|
|
- ,max2 = aabb2.x + aabb2.hw
|
|
|
- ;
|
|
|
-
|
|
|
- // first check x-axis
|
|
|
-
|
|
|
- if ( (min2 <= max1 && max1 <= max2) || (min1 <= max2 && max2 <= max1) ){
|
|
|
- // overlap in x-axis
|
|
|
- // check y...
|
|
|
- min1 = aabb1.y - aabb1.hh;
|
|
|
- min2 = aabb2.y - aabb2.hh;
|
|
|
- max1 = aabb1.y + aabb1.hh;
|
|
|
- max2 = aabb2.y + aabb2.hh;
|
|
|
-
|
|
|
- return (min2 <= max1 && max1 <= max2) || (min1 <= max2 && max2 <= max1);
|
|
|
- }
|
|
|
-
|
|
|
- // they don't overlap
|
|
|
- return false;
|
|
|
- };
|
|
|
-
|
|
|
-}());
|
|
|
-
|
|
|
-
|
|
|
-// ---
|
|
|
-// inside: src/math/gjk.js
|
|
|
-
|
|
|
-(function(){
|
|
|
-
|
|
|
- // the algorithm doesn't always converge for curved shapes.
|
|
|
- // need these constants to dictate how accurate we want to be.
|
|
|
- var gjkAccuracy = 0.0001;
|
|
|
- var gjkMaxIterations = 100;
|
|
|
-
|
|
|
- // get the next search direction from two simplex points
|
|
|
- var getNextSearchDir = function getNextSearchDir( ptA, ptB, dir ){
|
|
|
-
|
|
|
- var ABdotB = ptB.normSq() - ptB.dot( ptA )
|
|
|
- ,ABdotA = ptB.dot( ptA ) - ptA.normSq()
|
|
|
- ;
|
|
|
-
|
|
|
- // if the origin is farther than either of these points
|
|
|
- // get the direction from one of those points to the origin
|
|
|
- if ( ABdotB < 0 ){
|
|
|
-
|
|
|
- return dir.clone( ptB ).negate();
|
|
|
-
|
|
|
- } else if ( ABdotA > 0 ){
|
|
|
-
|
|
|
- return dir.clone( ptA ).negate();
|
|
|
-
|
|
|
- // otherwise, use the perpendicular direction from the simplex
|
|
|
- } else {
|
|
|
-
|
|
|
- // dir = AB = B - A
|
|
|
- dir.clone( ptB ).vsub( ptA );
|
|
|
- // if (left handed coordinate system)
|
|
|
- // A cross AB < 0 then get perpendicular counterclockwise
|
|
|
- return dir.perp( (ptA.cross( dir ) > 0) );
|
|
|
- }
|
|
|
- };
|
|
|
-
|
|
|
- /** hide
|
|
|
- * getClosestPoints( simplex ) -> Object
|
|
|
- * - simplex (Array): The simplex
|
|
|
- *
|
|
|
- * Figure out the closest points on the original objects
|
|
|
- * from the last two entries of the simplex
|
|
|
- **/
|
|
|
- var getClosestPoints = function getClosestPoints( simplex ){
|
|
|
-
|
|
|
- // see http://www.codezealot.org/archives/153
|
|
|
- // for algorithm details
|
|
|
-
|
|
|
- // we know that the position of the last point
|
|
|
- // is very close to the previous. (by nature of the distance test)
|
|
|
- // this won't give great results for the closest
|
|
|
- // points algorithm, so let's use the previous two
|
|
|
- var len = simplex.length
|
|
|
- ,last = simplex[ len - 2 ]
|
|
|
- ,prev = simplex[ len - 3 ]
|
|
|
- ,scratch = Physics.scratchpad()
|
|
|
- ,A = scratch.vector().clone( last.pt )
|
|
|
- // L = B - A
|
|
|
- ,L = scratch.vector().clone( prev.pt ).vsub( A )
|
|
|
- ,lambdaB
|
|
|
- ,lambdaA
|
|
|
- ;
|
|
|
-
|
|
|
- if ( L.equals(Physics.vector.zero) ){
|
|
|
-
|
|
|
- // oh.. it's a zero vector. So A and B are both the closest.
|
|
|
- // just use one of them
|
|
|
- return scratch.done({
|
|
|
-
|
|
|
- a: last.a,
|
|
|
- b: last.b
|
|
|
- });
|
|
|
- }
|
|
|
-
|
|
|
- lambdaB = - L.dot( A ) / L.normSq();
|
|
|
- lambdaA = 1 - lambdaB;
|
|
|
-
|
|
|
- if ( lambdaA <= 0 ){
|
|
|
- // woops.. that means the closest simplex point
|
|
|
- // isn't on the line it's point B itself
|
|
|
- return scratch.done({
|
|
|
- a: prev.a,
|
|
|
- b: prev.b
|
|
|
- });
|
|
|
- } else if ( lambdaB <= 0 ){
|
|
|
- // vice versa
|
|
|
- return scratch.done({
|
|
|
- a: last.a,
|
|
|
- b: last.b
|
|
|
- });
|
|
|
- }
|
|
|
-
|
|
|
- // guess we'd better do the math now...
|
|
|
- return scratch.done({
|
|
|
- // a closest = lambdaA * Aa + lambdaB * Ba
|
|
|
- a: A.clone( last.a ).mult( lambdaA ).vadd( L.clone( prev.a ).mult( lambdaB ) ).values(),
|
|
|
- // b closest = lambdaA * Ab + lambdaB * Bb
|
|
|
- b: A.clone( last.b ).mult( lambdaA ).vadd( L.clone( prev.b ).mult( lambdaB ) ).values()
|
|
|
- });
|
|
|
- };
|
|
|
-
|
|
|
- /**
|
|
|
- * Physics.gjk( support(axis)[, seed, checkOverlapOnly, debugFn] ) -> Object
|
|
|
- * - support (Function): The support function. Must return an object containing
|
|
|
- the witness points (`.a`, `.b`) and the support point (`.pt`).
|
|
|
- Recommended to use simple objects.
|
|
|
- Eg:
|
|
|
- ```javascript
|
|
|
- return {
|
|
|
- a: { x: 1, y:2 },
|
|
|
- b: { x: 3, y: 4 },
|
|
|
- pt: { x: 2, y: 2 }
|
|
|
- };
|
|
|
- ```
|
|
|
- * - axis (Physics.vector): The axis to search
|
|
|
- * - seed (Physics.vector): The starting direction for the simplex (defaults to x-axis)
|
|
|
- * - checkOverlapOnly (Boolean): only check whether there is an overlap, don't calculate the depth
|
|
|
- * - debugFn (Function): For debugging. Called at every iteration with the current simplex.
|
|
|
- *
|
|
|
- * Implementation agnostic GJK function.
|
|
|
- *
|
|
|
- * Gilbert–Johnson–Keerthi object collison algorithm
|
|
|
- * For general information about GJK see:
|
|
|
- * - [www.codezealot.org/archives/88](http://www.codezealot.org/archives/88)
|
|
|
- * - [mollyrocket.com/849](http://mollyrocket.com/849)
|
|
|
- *
|
|
|
- * The algorithm information returned:
|
|
|
- * ```javascript
|
|
|
- * {
|
|
|
- * overlap: Boolean,
|
|
|
- * simplex: [] // array containing simplex points as simple x/y objects
|
|
|
- * }
|
|
|
- * ```
|
|
|
- **/
|
|
|
- var gjk = function gjk( support, seed, checkOverlapOnly, debugFn ){
|
|
|
-
|
|
|
- var overlap = false
|
|
|
- ,noOverlap = false // if we're sure we're not overlapping
|
|
|
- ,distance = false
|
|
|
- ,simplex = []
|
|
|
- ,simplexLen = 1
|
|
|
- // setup a scratchpad of temporary cheap objects
|
|
|
- ,scratch = Physics.scratchpad()
|
|
|
- // use seed as starting direction or use x axis
|
|
|
- ,dir = scratch.vector().clone(seed || Physics.vector.axis[ 0 ])
|
|
|
- ,last = scratch.vector()
|
|
|
- ,lastlast = scratch.vector()
|
|
|
- // some temp vectors
|
|
|
- ,v1 = scratch.vector()
|
|
|
- ,v2 = scratch.vector()
|
|
|
- ,ab
|
|
|
- ,ac
|
|
|
- ,sign
|
|
|
- ,tmp
|
|
|
- ,iterations = 0
|
|
|
- ;
|
|
|
-
|
|
|
- // get the first Minkowski Difference point
|
|
|
- tmp = support( dir );
|
|
|
- simplexLen = simplex.push( tmp );
|
|
|
- last.clone( tmp.pt );
|
|
|
- // negate d for the next point
|
|
|
- dir.negate();
|
|
|
-
|
|
|
- // start looping
|
|
|
- while ( ++iterations ) {
|
|
|
-
|
|
|
- // swap last and lastlast, to save on memory/speed
|
|
|
- last.swap(lastlast);
|
|
|
- // push a new point to the simplex because we haven't terminated yet
|
|
|
- tmp = support( dir );
|
|
|
- simplexLen = simplex.push( tmp );
|
|
|
- last.clone( tmp.pt );
|
|
|
-
|
|
|
- if ( debugFn ){
|
|
|
- debugFn( simplex );
|
|
|
- }
|
|
|
-
|
|
|
- if ( last.equals(Physics.vector.zero) ){
|
|
|
- // we happened to pick the origin as a support point... lucky.
|
|
|
- overlap = true;
|
|
|
- break;
|
|
|
- }
|
|
|
-
|
|
|
- // check if the last point we added actually passed the origin
|
|
|
- if ( !noOverlap && last.dot( dir ) <= 0.0 ) {
|
|
|
- // if the point added last was not past the origin in the direction of d
|
|
|
- // then the Minkowski difference cannot possibly contain the origin since
|
|
|
- // the last point added is on the edge of the Minkowski Difference
|
|
|
-
|
|
|
- // if we just need the overlap...
|
|
|
- if ( checkOverlapOnly ){
|
|
|
- break;
|
|
|
- }
|
|
|
-
|
|
|
- noOverlap = true;
|
|
|
- }
|
|
|
-
|
|
|
- // if it's a line...
|
|
|
- if ( simplexLen === 2 ){
|
|
|
-
|
|
|
- // otherwise we need to determine if the origin is in
|
|
|
- // the current simplex and act accordingly
|
|
|
-
|
|
|
- dir = getNextSearchDir( last, lastlast, dir );
|
|
|
- // continue...
|
|
|
-
|
|
|
- // if it's a triangle... and we're looking for the distance
|
|
|
- } else if ( noOverlap ){
|
|
|
-
|
|
|
- // if we know there isn't any overlap and
|
|
|
- // we're just trying to find the distance...
|
|
|
- // make sure we're getting closer to the origin
|
|
|
- dir.normalize();
|
|
|
- tmp = lastlast.dot( dir );
|
|
|
- if ( Math.abs(tmp - last.dot( dir )) < gjkAccuracy ){
|
|
|
-
|
|
|
- distance = -tmp;
|
|
|
- break;
|
|
|
- }
|
|
|
-
|
|
|
- // if we are still getting closer then only keep
|
|
|
- // the points in the simplex that are closest to
|
|
|
- // the origin (we already know that last is closer
|
|
|
- // than the previous two)
|
|
|
- // the norm is the same as distance(origin, a)
|
|
|
- // use norm squared to avoid the sqrt operations
|
|
|
- if (lastlast.normSq() < v1.clone(simplex[ 0 ].pt).normSq()) {
|
|
|
-
|
|
|
- simplex.shift();
|
|
|
-
|
|
|
- } else {
|
|
|
-
|
|
|
- simplex.splice(1, 1);
|
|
|
- }
|
|
|
-
|
|
|
- dir = getNextSearchDir( v1.clone(simplex[ 1 ].pt), v2.clone(simplex[ 0 ].pt), dir );
|
|
|
- // continue...
|
|
|
-
|
|
|
- // if it's a triangle
|
|
|
- } else {
|
|
|
-
|
|
|
- // we need to trim the useless point...
|
|
|
-
|
|
|
- ab = ab || scratch.vector();
|
|
|
- ac = ac || scratch.vector();
|
|
|
-
|
|
|
- // get the edges AB and AC
|
|
|
- ab.clone( lastlast ).vsub( last );
|
|
|
- ac.clone( simplex[ 0 ].pt ).vsub( last );
|
|
|
-
|
|
|
- // here normally people think about this as getting outward facing
|
|
|
- // normals and checking dot products. Since we're in 2D
|
|
|
- // we can be clever...
|
|
|
- sign = ab.cross( ac ) > 0;
|
|
|
-
|
|
|
- if ( sign ^ (last.cross( ab ) > 0) ){
|
|
|
-
|
|
|
- // ok... so there's an XOR here... don't freak out
|
|
|
- // remember last = A = -AO
|
|
|
- // if AB cross AC and AO cross AB have the same sign
|
|
|
- // then the origin is along the outward facing normal of AB
|
|
|
- // so if AB cross AC and A cross AB have _different_ (XOR) signs
|
|
|
- // then this is also the case... so we proceed...
|
|
|
-
|
|
|
- // point C is dead to us now...
|
|
|
- simplex.shift();
|
|
|
-
|
|
|
- // if we haven't deduced that we've enclosed the origin
|
|
|
- // then we know which way to look...
|
|
|
- // morph the ab vector into its outward facing normal
|
|
|
- ab.perp( !sign );
|
|
|
-
|
|
|
- // swap
|
|
|
- dir.swap( ab );
|
|
|
-
|
|
|
- // continue...
|
|
|
-
|
|
|
- // if we get to this if, then it means we can continue to look along
|
|
|
- // the other outward normal direction (ACperp)
|
|
|
- // if we don't see the origin... then we must have it enclosed
|
|
|
- } else if ( sign ^ (ac.cross( last ) > 0) ){
|
|
|
- // then the origin is along the outward facing normal
|
|
|
- // of AC; (ACperp)
|
|
|
-
|
|
|
- // point B is dead to us now...
|
|
|
- simplex.splice(1, 1);
|
|
|
-
|
|
|
- ac.perp( sign );
|
|
|
-
|
|
|
- // swap
|
|
|
- dir.swap( ab );
|
|
|
-
|
|
|
- // continue...
|
|
|
-
|
|
|
- } else {
|
|
|
-
|
|
|
- // we have enclosed the origin!
|
|
|
- overlap = true;
|
|
|
- // fewf... take a break
|
|
|
- break;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // woah nelly... that's a lot of iterations.
|
|
|
- // Stop it!
|
|
|
- if (iterations > gjkMaxIterations){
|
|
|
- scratch.done();
|
|
|
- return {
|
|
|
- simplex: simplex,
|
|
|
- iterations: iterations,
|
|
|
- distance: 0,
|
|
|
- maxIterationsReached: true
|
|
|
- };
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // free workspace
|
|
|
- scratch.done();
|
|
|
-
|
|
|
- tmp = {
|
|
|
- overlap: overlap,
|
|
|
- simplex: simplex,
|
|
|
- iterations: iterations
|
|
|
- };
|
|
|
-
|
|
|
- if ( distance !== false ){
|
|
|
-
|
|
|
- tmp.distance = distance;
|
|
|
- tmp.closest = getClosestPoints( simplex );
|
|
|
- }
|
|
|
-
|
|
|
- return tmp;
|
|
|
- };
|
|
|
-
|
|
|
- Physics.gjk = gjk;
|
|
|
-
|
|
|
-})();
|
|
|
-
|
|
|
-
|
|
|
-// ---
|
|
|
-// 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
|
|
|
-
|
|
|
-(function(){
|
|
|
-
|
|
|
- /**
|
|
|
- * class Physics.transform
|
|
|
- *
|
|
|
- * Vector Transformations class for rotating and translating vectors
|
|
|
- **/
|
|
|
-
|
|
|
- /**
|
|
|
- * new Physics.transform( [vect, angle, origin] )
|
|
|
- * new Physics.transform( transform )
|
|
|
- * - vect (Vectorish): Translation vector
|
|
|
- * - transform (Physics.transform): Transform to copy
|
|
|
- * - angle (Number): Angle (radians) to use for rotation
|
|
|
- * - origin (Vectorish): Origin of the rotation
|
|
|
- *
|
|
|
- * Transform Constructor / Factory
|
|
|
- **/
|
|
|
- var Transform = function Transform( vect, angle, origin ) {
|
|
|
-
|
|
|
- if (!(this instanceof Transform)){
|
|
|
- return new Transform( vect, angle );
|
|
|
- }
|
|
|
-
|
|
|
- this.v = new Physics.vector();
|
|
|
- this.o = new Physics.vector(); // origin of rotation
|
|
|
-
|
|
|
- if ( vect instanceof Transform ){
|
|
|
-
|
|
|
- this.clone( vect );
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- if (vect){
|
|
|
- this.setTranslation( vect );
|
|
|
- }
|
|
|
-
|
|
|
- this.setRotation( angle || 0, origin );
|
|
|
- };
|
|
|
-
|
|
|
- /**
|
|
|
- * Physics.transform#setTranslation( vect ) -> this
|
|
|
- * - vect (Vectorish): The translation vector
|
|
|
- *
|
|
|
- * Set the translation portion of the transform.
|
|
|
- **/
|
|
|
- Transform.prototype.setTranslation = function( vect ){
|
|
|
-
|
|
|
- this.v.clone( vect );
|
|
|
- return this;
|
|
|
- };
|
|
|
-
|
|
|
- /**
|
|
|
- * Physics.transform#setRotation( angle[, origin ] ) -> this
|
|
|
- * - angle (Number): Angle (radians) to use for rotation
|
|
|
- * - origin (Vectorish): Origin of the rotation
|
|
|
- *
|
|
|
- * Set the rotation portion of the transform
|
|
|
- **/
|
|
|
- Transform.prototype.setRotation = function( angle, origin ){
|
|
|
-
|
|
|
- this.cosA = Math.cos( angle );
|
|
|
- this.sinA = Math.sin( angle );
|
|
|
-
|
|
|
- if ( origin ){
|
|
|
- this.o.clone( origin );
|
|
|
- } else {
|
|
|
- this.o.zero();
|
|
|
- }
|
|
|
-
|
|
|
- return this;
|
|
|
- };
|
|
|
-
|
|
|
- /**
|
|
|
- * Physics.transform#clone( [transform] ) -> this|Physics.transform
|
|
|
- * - transform (Physics.transform): Transform to copy
|
|
|
- * + (this): For chaining
|
|
|
- * + (Physics.transform): New copy of `this` if none is specified as an argument
|
|
|
- *
|
|
|
- * Clone another transform. Or clone self into new transform.
|
|
|
- **/
|
|
|
- Transform.prototype.clone = function( t ){
|
|
|
-
|
|
|
- if ( t ){
|
|
|
-
|
|
|
- this.setTranslation( t.v );
|
|
|
- this.cosA = t.cosA;
|
|
|
- this.sinA = t.sinA;
|
|
|
- this.o.clone( t.o );
|
|
|
-
|
|
|
- return this;
|
|
|
- }
|
|
|
-
|
|
|
- return new Transform( this );
|
|
|
- };
|
|
|
-
|
|
|
- Physics.transform = Transform;
|
|
|
-
|
|
|
-})();
|
|
|
-
|
|
|
-// ---
|
|
|
-// inside: src/math/vector.js
|
|
|
-
|
|
|
-(function(window){
|
|
|
-
|
|
|
- // http://jsperf.com/vector-storage-test/2
|
|
|
-
|
|
|
- // cached math functions
|
|
|
- // TODO: might be faster not to do this???
|
|
|
- var sqrt = Math.sqrt
|
|
|
- ,min = Math.min
|
|
|
- ,max = Math.max
|
|
|
- ,acos = Math.acos
|
|
|
- ,atan2 = Math.atan2
|
|
|
- ,TWOPI = Math.PI * 2
|
|
|
- ,typedArrays = !!window.Float64Array
|
|
|
- ;
|
|
|
-
|
|
|
- /**
|
|
|
- * class Physics.vector
|
|
|
- *
|
|
|
- * The vector class and factory function.
|
|
|
- *
|
|
|
- * Call `Physics.vector` with the same arguments as
|
|
|
- * [[new Physics.vector]] to create an instance.
|
|
|
- *
|
|
|
- * The vector methods mostly modify the vector instance.
|
|
|
- * This makes computations faster because creating vectors
|
|
|
- * is avoided.
|
|
|
- *
|
|
|
- * Creating vectors is generally an expensive operation
|
|
|
- * so try to avoid doing this in the simulation loop.
|
|
|
- * Instead you can use [[Physics.scratchpad]] to get
|
|
|
- * temporary vectors for use in performance critical
|
|
|
- * code.
|
|
|
- *
|
|
|
- * _Note_: The coordinate system is left-handed, meaning that
|
|
|
- * the clockwise angular direction is positive. This has implications
|
|
|
- * for the cross-product rule.
|
|
|
- **/
|
|
|
-
|
|
|
- /** section: Special
|
|
|
- * class Vectorish
|
|
|
- *
|
|
|
- * Any object with `.x` and `.y` properties.
|
|
|
- *
|
|
|
- * A `Vectorish` isn't really a class. In this documentation, when
|
|
|
- * an argument is specified as a `Vectorish` it means either a true
|
|
|
- * [[Physics.vector]] instance, or an object literal with `.x` and `.y`
|
|
|
- * properties.
|
|
|
- **/
|
|
|
-
|
|
|
- /**
|
|
|
- * new Physics.vector( x, y )
|
|
|
- * new Physics.vector( vect )
|
|
|
- * - 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 ) {
|
|
|
-
|
|
|
- // enforce instantiation
|
|
|
- if ( !(this instanceof Vector) ){
|
|
|
-
|
|
|
- return new Vector( x, y );
|
|
|
- }
|
|
|
-
|
|
|
- // arrays to store values
|
|
|
- // x = _[0]
|
|
|
- // y = _[1]
|
|
|
- // norm = _[3]
|
|
|
- // normsq = _[4]
|
|
|
-
|
|
|
- /** internal
|
|
|
- * Physics.vector#_
|
|
|
- *
|
|
|
- * Private storage array for data.
|
|
|
- *
|
|
|
- * Do not access this directly. Private. Keep out.
|
|
|
- **/
|
|
|
- if (typedArrays){
|
|
|
- this._ = new Float64Array(5);
|
|
|
- } else {
|
|
|
- this._ = [];
|
|
|
- }
|
|
|
-
|
|
|
- if (x && (x.x !== undefined || x._ && x._.length)){
|
|
|
-
|
|
|
- this.clone( x );
|
|
|
-
|
|
|
- } else {
|
|
|
-
|
|
|
- this.recalc = true; //whether or not recalculate norms
|
|
|
- this.set( x, y );
|
|
|
- }
|
|
|
- };
|
|
|
-
|
|
|
- Object.defineProperties( Vector.prototype, {
|
|
|
- /**
|
|
|
- * Physics.vector#x
|
|
|
- *
|
|
|
- * Getter/setter property for the x coordinate.
|
|
|
- **/
|
|
|
- x: {
|
|
|
- get: function(){
|
|
|
- return +this._[0];
|
|
|
- },
|
|
|
- set: function( x ){
|
|
|
- x = +x || 0;
|
|
|
- this.recalc = ( x === this._[0] );
|
|
|
- this._[0] = x;
|
|
|
- }
|
|
|
- },
|
|
|
- /**
|
|
|
- * Physics.vector#y
|
|
|
- *
|
|
|
- * Getter/setter property for the y coordinate.
|
|
|
- **/
|
|
|
- y: {
|
|
|
- get: function(){
|
|
|
- return +this._[1];
|
|
|
- },
|
|
|
- set: function( y ){
|
|
|
- y = +y || 0;
|
|
|
- this.recalc = ( y === this._[1] );
|
|
|
- this._[1] = y;
|
|
|
- }
|
|
|
- }
|
|
|
- });
|
|
|
-
|
|
|
- //
|
|
|
- // 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 ) {
|
|
|
-
|
|
|
- this.recalc = true;
|
|
|
-
|
|
|
- this._[0] = +x || 0;
|
|
|
- this._[1] = +y || 0;
|
|
|
- return this;
|
|
|
- };
|
|
|
-
|
|
|
- /** 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 ){
|
|
|
-
|
|
|
- return this._[ n ];
|
|
|
- };
|
|
|
-
|
|
|
- /**
|
|
|
- * Physics.vector#vadd( v ) -> this
|
|
|
- * - v (Physics.vector): vector to add
|
|
|
- *
|
|
|
- * Add a [[Physics.vector]] to `this`.
|
|
|
- **/
|
|
|
- Vector.prototype.vadd = function( v ) {
|
|
|
-
|
|
|
- this.recalc = true;
|
|
|
-
|
|
|
- this._[0] += v._[0];
|
|
|
- this._[1] += v._[1];
|
|
|
- return this;
|
|
|
- };
|
|
|
-
|
|
|
- /**
|
|
|
- * Physics.vector#vsub( v ) -> this
|
|
|
- * - v (Physics.vector): vector to subtract
|
|
|
- *
|
|
|
- * Subtract a [[Physics.vector]] from `this`.
|
|
|
- **/
|
|
|
- Vector.prototype.vsub = function( v ) {
|
|
|
-
|
|
|
- this.recalc = true;
|
|
|
-
|
|
|
- this._[0] -= v._[0];
|
|
|
- this._[1] -= v._[1];
|
|
|
- return this;
|
|
|
- };
|
|
|
-
|
|
|
- /**
|
|
|
- * 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;
|
|
|
- this._[1] += +y || 0;
|
|
|
- return this;
|
|
|
- };
|
|
|
-
|
|
|
- /**
|
|
|
- * 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;
|
|
|
- this._[1] -= y === undefined? 0 : y;
|
|
|
- return this;
|
|
|
- };
|
|
|
-
|
|
|
- /**
|
|
|
- * 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;
|
|
|
- this._[3] *= m;
|
|
|
- }
|
|
|
-
|
|
|
- this._[0] *= m;
|
|
|
- this._[1] *= m;
|
|
|
- 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 ) {
|
|
|
-
|
|
|
- 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 ) {
|
|
|
-
|
|
|
- return ( - this._[0] * v._[1]) + (this._[1] * v._[0]);
|
|
|
- };
|
|
|
-
|
|
|
- /**
|
|
|
- * 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 ){
|
|
|
-
|
|
|
- return this.dot( v ) / v.norm();
|
|
|
- };
|
|
|
-
|
|
|
-
|
|
|
- /**
|
|
|
- * 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 ){
|
|
|
-
|
|
|
- var m = this.dot( v ) / v.normSq();
|
|
|
- return this.clone( v ).mult( m );
|
|
|
- };
|
|
|
-
|
|
|
- /**
|
|
|
- * 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 ){
|
|
|
-
|
|
|
- var ang;
|
|
|
-
|
|
|
- if ( this.equals( Vector.zero ) ){
|
|
|
-
|
|
|
- if ( v ){
|
|
|
- return v.angle();
|
|
|
- } else {
|
|
|
- return NaN;
|
|
|
- }
|
|
|
-
|
|
|
- } else {
|
|
|
-
|
|
|
- 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 ] );
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- while (ang > Math.PI){
|
|
|
- ang -= TWOPI;
|
|
|
- }
|
|
|
-
|
|
|
- while (ang < -Math.PI){
|
|
|
- ang += TWOPI;
|
|
|
- }
|
|
|
-
|
|
|
- return ang;
|
|
|
- };
|
|
|
-
|
|
|
- /**
|
|
|
- * 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 ){
|
|
|
-
|
|
|
- var x1 = left._[0] - this._[0]
|
|
|
- ,y1 = left._[1] - this._[1]
|
|
|
- ,x2 = right._[0] - this._[0]
|
|
|
- ,y2 = right._[1] - this._[1]
|
|
|
- ,ang = atan2( y1 * x2 - x1 * y2, x1 * x2 + y1 * y2)
|
|
|
- ;
|
|
|
-
|
|
|
- while (ang > Math.PI){
|
|
|
- ang -= TWOPI;
|
|
|
- }
|
|
|
-
|
|
|
- while (ang < -Math.PI){
|
|
|
- ang += TWOPI;
|
|
|
- }
|
|
|
-
|
|
|
- return ang;
|
|
|
- };
|
|
|
-
|
|
|
- /**
|
|
|
- * Physics.vector#norm() -> Number
|
|
|
- *
|
|
|
- * Compute the norm (length) of this vector.
|
|
|
- **/
|
|
|
- Vector.prototype.norm = function() {
|
|
|
-
|
|
|
- if (this.recalc){
|
|
|
- this.recalc = false;
|
|
|
- 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() {
|
|
|
-
|
|
|
- if (this.recalc){
|
|
|
- this.recalc = false;
|
|
|
- this._[4] = (this._[0] * this._[0] + this._[1] * this._[1]);
|
|
|
- this._[3] = sqrt( this._[4] );
|
|
|
- }
|
|
|
-
|
|
|
- return this._[4];
|
|
|
- };
|
|
|
-
|
|
|
- /**
|
|
|
- * 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 +
|
|
|
- (dy = (v._[1] - this._[1])) * dy
|
|
|
- );
|
|
|
- };
|
|
|
-
|
|
|
- /**
|
|
|
- * 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 +
|
|
|
- (dy = (v._[1] - this._[1])) * dy
|
|
|
- );
|
|
|
- };
|
|
|
-
|
|
|
- /**
|
|
|
- * 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.
|
|
|
- **/
|
|
|
- Vector.prototype.perp = function( ccw ) {
|
|
|
-
|
|
|
- var tmp = this._[0]
|
|
|
- ;
|
|
|
-
|
|
|
- if ( ccw ){
|
|
|
-
|
|
|
- // x <-> y
|
|
|
- // negate y
|
|
|
- this._[0] = this._[1];
|
|
|
- this._[1] = -tmp;
|
|
|
-
|
|
|
- } else {
|
|
|
-
|
|
|
- // x <-> y
|
|
|
- // negate x
|
|
|
- this._[0] = -this._[1];
|
|
|
- this._[1] = tmp;
|
|
|
- }
|
|
|
-
|
|
|
- return this;
|
|
|
- };
|
|
|
-
|
|
|
- /**
|
|
|
- * Physics.vector#normalize() -> this
|
|
|
- *
|
|
|
- * Normalise this vector, making it a unit vector.
|
|
|
- **/
|
|
|
- Vector.prototype.normalize = function() {
|
|
|
-
|
|
|
- var m = this.norm();
|
|
|
-
|
|
|
- // means it's a zero Vector
|
|
|
- if ( m === 0 ){
|
|
|
- return this;
|
|
|
- }
|
|
|
-
|
|
|
- m = 1/m;
|
|
|
-
|
|
|
- this._[0] *= m;
|
|
|
- this._[1] *= m;
|
|
|
-
|
|
|
- this._[3] = 1.0;
|
|
|
- this._[4] = 1.0;
|
|
|
-
|
|
|
- return this;
|
|
|
- };
|
|
|
-
|
|
|
- /**
|
|
|
- * Physics.vector#transform( t ) -> this
|
|
|
- * - t (Physics.transform): The transformation to apply
|
|
|
- *
|
|
|
- * Apply a [[Physics.transform]] to this vector.
|
|
|
- **/
|
|
|
- Vector.prototype.transform = function( t ){
|
|
|
-
|
|
|
- var sinA = t.sinA
|
|
|
- ,cosA = t.cosA
|
|
|
- ,x = t.o._[ 0 ]
|
|
|
- ,y = t.o._[ 1 ]
|
|
|
- ;
|
|
|
-
|
|
|
- this._[ 0 ] -= x;
|
|
|
- this._[ 1 ] -= y;
|
|
|
-
|
|
|
- // rotate about origin "o" then translate
|
|
|
- return this.set(
|
|
|
- this._[ 0 ] * cosA - this._[ 1 ] * sinA + x + t.v._[ 0 ],
|
|
|
- this._[ 0 ] * sinA + this._[ 1 ] * cosA + y + t.v._[ 1 ]
|
|
|
- );
|
|
|
- };
|
|
|
-
|
|
|
- /**
|
|
|
- * 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 ){
|
|
|
-
|
|
|
- var sinA = t.sinA
|
|
|
- ,cosA = t.cosA
|
|
|
- ,x = t.o._[ 0 ]
|
|
|
- ,y = t.o._[ 1 ]
|
|
|
- ;
|
|
|
-
|
|
|
- this._[ 0 ] -= x + t.v._[ 0 ];
|
|
|
- this._[ 1 ] -= y + t.v._[ 1 ];
|
|
|
-
|
|
|
- // inverse translate then inverse rotate about origin "o"
|
|
|
- return this.set(
|
|
|
- this._[ 0 ] * cosA + this._[ 1 ] * sinA + x,
|
|
|
- - this._[ 0 ] * sinA + this._[ 1 ] * cosA + y
|
|
|
- );
|
|
|
- };
|
|
|
-
|
|
|
- /**
|
|
|
- * Physics.vector#rotate( t ) -> this
|
|
|
- * Physics.vector#rotate( ang[, o] ) -> this
|
|
|
- * - 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,
|
|
|
- * or a transform can be specified and only the rotation
|
|
|
- * portion of that transform will be applied
|
|
|
- **/
|
|
|
- Vector.prototype.rotate = function( t, o ){
|
|
|
-
|
|
|
- var sinA
|
|
|
- ,cosA
|
|
|
- ,x = 0
|
|
|
- ,y = 0
|
|
|
- ;
|
|
|
-
|
|
|
- if ( typeof t === 'number' ){
|
|
|
- sinA = Math.sin( t );
|
|
|
- cosA = Math.cos( t );
|
|
|
-
|
|
|
- if ( o ){
|
|
|
- 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 ] * sinA + this._[ 1 ] * cosA + y
|
|
|
- );
|
|
|
- };
|
|
|
-
|
|
|
- /**
|
|
|
- * 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
|
|
|
- * 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.sinA + (this._[ 1 ] - t.o._[ 1 ]) * t.cosA + t.o._[ 1 ]
|
|
|
- );
|
|
|
- };
|
|
|
-
|
|
|
- /**
|
|
|
- * 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
|
|
|
- * that transform will be applied.
|
|
|
- **/
|
|
|
- Vector.prototype.translate = function( t ){
|
|
|
-
|
|
|
- return this.vadd( t.v );
|
|
|
- };
|
|
|
-
|
|
|
- /**
|
|
|
- * 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
|
|
|
- * that transform will be applied.
|
|
|
- **/
|
|
|
- Vector.prototype.translateInv = function( t ){
|
|
|
-
|
|
|
- return this.vsub( t.v );
|
|
|
- };
|
|
|
-
|
|
|
-
|
|
|
- /**
|
|
|
- * Physics.vector#clone( [v] ) -> this|Physics.vector
|
|
|
- * - 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
|
|
|
- * that use temporary vectors (which most should).
|
|
|
- * You can create temporary vectors and then do things like...
|
|
|
- * ```
|
|
|
- * temp.clone( otherVector );
|
|
|
- * // compute things with temp...
|
|
|
- * // then save the result
|
|
|
- * result.clone( tmp );
|
|
|
- * ```
|
|
|
- **/
|
|
|
- Vector.prototype.clone = function( v ) {
|
|
|
-
|
|
|
- // http://jsperf.com/vector-storage-test
|
|
|
-
|
|
|
- if ( v ){
|
|
|
-
|
|
|
- if (!v._){
|
|
|
-
|
|
|
- return this.set( v.x, v.y );
|
|
|
- }
|
|
|
-
|
|
|
- this.recalc = v.recalc;
|
|
|
-
|
|
|
- if (!v.recalc){
|
|
|
- this._[3] = v._[3];
|
|
|
- this._[4] = v._[4];
|
|
|
- }
|
|
|
-
|
|
|
- this._[0] = v._[0];
|
|
|
- this._[1] = v._[1];
|
|
|
-
|
|
|
- return this;
|
|
|
- }
|
|
|
-
|
|
|
- return new Vector( this );
|
|
|
- };
|
|
|
-
|
|
|
- /**
|
|
|
- * Physics.vector#swap( v ) -> this
|
|
|
- * - v (Physics.vector): The other vector
|
|
|
- *
|
|
|
- * Swap values with other vector.
|
|
|
- **/
|
|
|
- Vector.prototype.swap = function( v ){
|
|
|
-
|
|
|
- var _ = this._;
|
|
|
- this._ = v._;
|
|
|
- v._ = _;
|
|
|
-
|
|
|
- _ = this.recalc;
|
|
|
- this.recalc = v.recalc;
|
|
|
- v.recalc = _;
|
|
|
- return this;
|
|
|
- };
|
|
|
-
|
|
|
- /**
|
|
|
- * Physics.vector#values() -> Object
|
|
|
- *
|
|
|
- * Get the coordinate values as an object literal.
|
|
|
- **/
|
|
|
- Vector.prototype.values = function(){
|
|
|
-
|
|
|
- return {
|
|
|
- x: this._[0],
|
|
|
- y: this._[1]
|
|
|
- };
|
|
|
- };
|
|
|
-
|
|
|
-
|
|
|
- /**
|
|
|
- * Physics.vector#zero() -> this
|
|
|
- *
|
|
|
- * Set the coordinates of this vector to zero.
|
|
|
- **/
|
|
|
- Vector.prototype.zero = function() {
|
|
|
-
|
|
|
- this._[3] = 0.0;
|
|
|
- this._[4] = 0.0;
|
|
|
-
|
|
|
- this._[0] = 0.0;
|
|
|
- this._[1] = 0.0;
|
|
|
- return this;
|
|
|
- };
|
|
|
-
|
|
|
- /**
|
|
|
- * Physics.vector#negate() -> this
|
|
|
- *
|
|
|
- * Flip this vector in the opposite direction.
|
|
|
- **/
|
|
|
- Vector.prototype.negate = function( component ){
|
|
|
-
|
|
|
- if (component !== undefined){
|
|
|
-
|
|
|
- this._[ component ] = -this._[ component ];
|
|
|
- return this;
|
|
|
- }
|
|
|
-
|
|
|
- this._[0] = -this._[0];
|
|
|
- this._[1] = -this._[1];
|
|
|
- return this;
|
|
|
- };
|
|
|
-
|
|
|
- /**
|
|
|
- * 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 ){
|
|
|
-
|
|
|
- this._[0] = min(max(this._[0], minV.x), maxV.x);
|
|
|
- this._[1] = min(max(this._[1], minV.y), maxV.y);
|
|
|
- this.recalc = true;
|
|
|
- return this;
|
|
|
- };
|
|
|
-
|
|
|
- /**
|
|
|
- * Physics.vector#toString() -> String
|
|
|
- *
|
|
|
- * Get a formatted string of this vector's coordinates.
|
|
|
- **/
|
|
|
- Vector.prototype.toString = function(){
|
|
|
-
|
|
|
- return '('+this._[0] + ', ' + this._[1]+')';
|
|
|
- };
|
|
|
-
|
|
|
-
|
|
|
- /**
|
|
|
- * Physics.vector#equals( v ) -> Boolean
|
|
|
- * - v (Physics.vector): The other vector
|
|
|
- *
|
|
|
- * Determine if this vector equals another.
|
|
|
- **/
|
|
|
- Vector.prototype.equals = function( v ){
|
|
|
-
|
|
|
- return this._[0] === v._[0] &&
|
|
|
- this._[1] === v._[1] &&
|
|
|
- this._[2] === v._[2];
|
|
|
- };
|
|
|
-
|
|
|
- /**
|
|
|
- * Physics.vector.axis = Array
|
|
|
- *
|
|
|
- * Read-only axis vectors for general reference.
|
|
|
- *
|
|
|
- * Example:
|
|
|
- *
|
|
|
- * ```javascript
|
|
|
- * Physics.vector.axis[0]; // The x axis unit vector
|
|
|
- * Physics.vector.axis[1]; // The y axis unit vector
|
|
|
- * ```
|
|
|
- **/
|
|
|
- Vector.axis = [
|
|
|
- new Vector(1.0, 0.0),
|
|
|
- new Vector(0.0, 1.0)
|
|
|
- ];
|
|
|
-
|
|
|
- /**
|
|
|
- * Physics.vector.zero = zeroVector
|
|
|
- *
|
|
|
- * Read-only zero vector for reference
|
|
|
- **/
|
|
|
- Vector.zero = new Vector(0, 0);
|
|
|
-
|
|
|
- // assign
|
|
|
- Physics.vector = Vector;
|
|
|
-
|
|
|
-}(this)); // end Vector class
|
|
|
-
|
|
|
-
|
|
|
-// ---
|
|
|
-// inside: src/util/noconflict.js
|
|
|
-
|
|
|
-(function( window ){
|
|
|
-
|
|
|
- var _Physics = window.Physics;
|
|
|
-
|
|
|
- /**
|
|
|
- * Physics.noConflict() -> Physics
|
|
|
- *
|
|
|
- * Restore the original reference to the global window.Physics variable.
|
|
|
- *
|
|
|
- * Does nothing if PhysicsJS doesn't have a reference in global scope
|
|
|
- **/
|
|
|
- Physics.noConflict = function(){
|
|
|
-
|
|
|
- if ( window.Physics === Physics ) {
|
|
|
- window.Physics = _Physics;
|
|
|
- }
|
|
|
-
|
|
|
- return Physics;
|
|
|
- };
|
|
|
-
|
|
|
-})( this );
|
|
|
-
|
|
|
-// ---
|
|
|
-// inside: src/util/decorator.js
|
|
|
-
|
|
|
-/** related to: factory
|
|
|
- * Physics.util.decorator( type [, protoDef ] ) -> Function
|
|
|
- * - type (String): The name of the factory you are creating
|
|
|
- * - protoDef (Object): The top-level prototype
|
|
|
- * + (Function): The factory function
|
|
|
- *
|
|
|
- * Facilitates creation of decorator factory functions.
|
|
|
- *
|
|
|
- * See the [[factory]] definition for the factory signatures.
|
|
|
- * [For full documentation and examples, please visit the wiki](https://github.com/wellcaffeinated/PhysicsJS/wiki/Fundamentals#the-factory-pattern).
|
|
|
- *
|
|
|
- * Example:
|
|
|
- *
|
|
|
- * ```javascript
|
|
|
- * var factory = Physics.util.decorator('factory', {
|
|
|
- * // prototype methods...
|
|
|
- * method: function( args ){
|
|
|
- * }
|
|
|
- * });
|
|
|
- *
|
|
|
- * // define
|
|
|
- * factory( 'name', 'parent-name', function( parent ){
|
|
|
- *
|
|
|
- * // extend further...
|
|
|
- * return {
|
|
|
- * // overrides
|
|
|
- * init: function( cfg ){
|
|
|
- * parent.init.call(this, cfg);
|
|
|
- * }
|
|
|
- * };
|
|
|
- * });
|
|
|
- *
|
|
|
- * // instantiate
|
|
|
- * var options = { key: 'val' };
|
|
|
- * var instance = factory( 'name', options );
|
|
|
- * ```
|
|
|
- **/
|
|
|
-var Decorator = Physics.util.decorator = function Decorator( type, baseProto ){
|
|
|
-
|
|
|
- var registry = {}
|
|
|
- ,proto = {}
|
|
|
- ;
|
|
|
-
|
|
|
- // extend that supports getters/setters
|
|
|
- // only extends functions
|
|
|
- var extend = function extend( to, from ){
|
|
|
- var desc, key;
|
|
|
- for ( key in from ){
|
|
|
- desc = Object.getOwnPropertyDescriptor( from, key );
|
|
|
- if ( desc.get || desc.set ){
|
|
|
-
|
|
|
- Object.defineProperty( to, key, desc );
|
|
|
-
|
|
|
- } else if ( Physics.util.isFunction( desc.value ) ){
|
|
|
-
|
|
|
- to[ key ] = desc.value;
|
|
|
- }
|
|
|
- }
|
|
|
- return to;
|
|
|
- };
|
|
|
-
|
|
|
- // http://ejohn.org/blog/objectgetprototypeof/
|
|
|
- /* jshint -W103 */
|
|
|
- var getProto = Object.getPrototypeOf;
|
|
|
- if ( typeof getProto !== 'function' ) {
|
|
|
- if ( typeof 'test'.__proto__ === 'object' ) {
|
|
|
- getProto = function(object){
|
|
|
- return object.__proto__;
|
|
|
- };
|
|
|
- } else {
|
|
|
- getProto = function(object){
|
|
|
- // May break if the constructor has been tampered with
|
|
|
- return object.constructor.prototype;
|
|
|
- };
|
|
|
- }
|
|
|
- }
|
|
|
- /* jshint +W103 */
|
|
|
-
|
|
|
- var objectCreate = Object.create;
|
|
|
- if (typeof objectCreate !== 'function') {
|
|
|
- objectCreate = function (o) {
|
|
|
- function F() {}
|
|
|
- F.prototype = o;
|
|
|
- return new F();
|
|
|
- };
|
|
|
- }
|
|
|
-
|
|
|
- /*
|
|
|
- * mixin( key, val )
|
|
|
- * mixin( obj )
|
|
|
- * - key (String): The method name
|
|
|
- * - val (Function): The function to assign
|
|
|
- * - obj (Object): object with many `key: fn` pairs
|
|
|
- *
|
|
|
- * Apply mixin methods to decorator base.
|
|
|
- */
|
|
|
- var mixin = function mixin( key, val ){
|
|
|
-
|
|
|
- if ( typeof key === 'object' ){
|
|
|
- proto = extend(proto, key);
|
|
|
- proto.type = type;
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- if ( key !== 'type' && Physics.util.isFunction( val ) ){
|
|
|
- proto[ key ] = val;
|
|
|
- }
|
|
|
- };
|
|
|
-
|
|
|
- // @TODO: not sure of the best way to make the constructor names
|
|
|
- // transparent and readable in debug consoles...
|
|
|
- mixin( baseProto );
|
|
|
-
|
|
|
- /** belongs to: Physics.util.decorator
|
|
|
- * factory( name[, parentName], decorator[, cfg] )
|
|
|
- * factory( name, cfg ) -> Object
|
|
|
- * - name (String): The class name
|
|
|
- * - parentName (String): The name of parent class to extend
|
|
|
- * - decorator (Function): The decorator function that should define and return methods to extend (decorate) the base class
|
|
|
- * - cfg (Object): The configuration to pass to the class initializer
|
|
|
- *
|
|
|
- * Factory function for definition and instantiation of subclasses.
|
|
|
- *
|
|
|
- * Use the first signature (once) to define it first.
|
|
|
- * If defining without the "cfg" parameter, void will be returned. Otherwise the class instance will be returned.
|
|
|
- *
|
|
|
- * See [[Physics.util.decorator]] for more information.
|
|
|
- **/
|
|
|
- var factory = function factory( name, parentName, decorator, cfg ){
|
|
|
-
|
|
|
- var instance
|
|
|
- ,result
|
|
|
- ,parent = proto
|
|
|
- ,tmp
|
|
|
- ;
|
|
|
-
|
|
|
- // set parent if specified
|
|
|
- if ( typeof parentName !== 'string' ){
|
|
|
-
|
|
|
- // ... otherwise reassign parameters
|
|
|
- cfg = decorator;
|
|
|
- decorator = parentName;
|
|
|
-
|
|
|
- } else {
|
|
|
-
|
|
|
- // extend the specified module
|
|
|
- parent = registry[ parentName ];
|
|
|
-
|
|
|
- if ( !parent ){
|
|
|
-
|
|
|
- throw 'Error: "' + parentName + '" ' + type + ' not defined';
|
|
|
- }
|
|
|
-
|
|
|
- parent = parent.prototype;
|
|
|
- }
|
|
|
-
|
|
|
- if ( typeof decorator === 'function' ){
|
|
|
-
|
|
|
- result = registry[ name ];
|
|
|
-
|
|
|
- if ( result ){
|
|
|
-
|
|
|
- result.prototype = extend(result.prototype, decorator( getProto(result.prototype) ));
|
|
|
-
|
|
|
- } else {
|
|
|
- // newly defined
|
|
|
- // store the new class
|
|
|
- result = registry[ name ] = function constructor( opts ){
|
|
|
- if (this.init){
|
|
|
- this.init( opts );
|
|
|
- }
|
|
|
- };
|
|
|
-
|
|
|
- result.prototype = objectCreate( parent );
|
|
|
- result.prototype = extend(result.prototype, decorator( parent, result.prototype ));
|
|
|
- }
|
|
|
-
|
|
|
- result.prototype.type = type;
|
|
|
- result.prototype.name = name;
|
|
|
-
|
|
|
- } else {
|
|
|
-
|
|
|
- cfg = decorator || {};
|
|
|
- result = registry[ name ];
|
|
|
- if (!result){
|
|
|
-
|
|
|
- throw 'Error: "' + name + '" ' + type + ' not defined';
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- if ( cfg ) {
|
|
|
-
|
|
|
- // create a new instance from the provided decorator
|
|
|
- return new result( cfg );
|
|
|
- }
|
|
|
- };
|
|
|
-
|
|
|
- factory.mixin = mixin;
|
|
|
-
|
|
|
- return factory;
|
|
|
-};
|
|
|
-
|
|
|
-
|
|
|
-// ---
|
|
|
-// inside: src/util/helpers.js
|
|
|
-
|
|
|
-/**
|
|
|
- * Physics.util.indexOf( arr, value ) -> Number
|
|
|
- * - arr (Array): The array to search
|
|
|
- * - value (Mixed): The value to find
|
|
|
- * + (Number): The index of `value` in the array OR `-1` if not found
|
|
|
- *
|
|
|
- * Fast indexOf implementation.
|
|
|
- **/
|
|
|
-Physics.util.indexOf = function indexOf(arr, value) {
|
|
|
- var fr = 0, bk = arr.length;
|
|
|
- while (fr < bk) {
|
|
|
- bk--;
|
|
|
- if (arr[ fr ] === value) {
|
|
|
- return fr;
|
|
|
- }
|
|
|
- if (arr[ bk ] === value) {
|
|
|
- return bk;
|
|
|
- }
|
|
|
- fr++;
|
|
|
- }
|
|
|
- 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
|
|
|
- * - delay (Number): Time in milliseconds
|
|
|
- *
|
|
|
- * Ensure a function is only called once every specified time span.
|
|
|
- **/
|
|
|
-Physics.util.throttle = function throttle( fn, delay, scope ){
|
|
|
- var to
|
|
|
- ,call = false
|
|
|
- ,args
|
|
|
- ,cb = function(){
|
|
|
- clearTimeout( to );
|
|
|
- if ( call ){
|
|
|
- call = false;
|
|
|
- to = setTimeout(cb, delay);
|
|
|
- fn.apply(scope, args);
|
|
|
- } else {
|
|
|
- to = false;
|
|
|
- }
|
|
|
- }
|
|
|
- ;
|
|
|
-
|
|
|
- scope = scope || null;
|
|
|
-
|
|
|
- return function(){
|
|
|
- call = true;
|
|
|
- args = arguments;
|
|
|
- if ( !to ){
|
|
|
- cb();
|
|
|
- }
|
|
|
- };
|
|
|
-};
|
|
|
-
|
|
|
-/**
|
|
|
- * Physics.util.options( def[, target] ) -> Function
|
|
|
- * - def (Object): Default options to set
|
|
|
- * - target (Object): Where to copy the options to. Defaults to the returned function.
|
|
|
- * + (Function): The options function
|
|
|
- *
|
|
|
- * Options helper to keep track of options. Call it with a config object. Access options directly on the function.
|
|
|
- *
|
|
|
- * Example:
|
|
|
- *
|
|
|
- * ```javascript
|
|
|
- * this.options = Physics.util.options({ foo: 'bar', opt: 'def' });
|
|
|
- * this.options({ opt: 'myVal' });
|
|
|
- *
|
|
|
- * this.options.foo; // === 'bar'
|
|
|
- * this.options.def; // === 'myVal'
|
|
|
- *
|
|
|
- * // can also change defaults later
|
|
|
- * this.options.defaults({ foo: 'baz' });
|
|
|
- *
|
|
|
- * // can add a change callback
|
|
|
- * this.options.onChange(function( opts ){
|
|
|
- * // some option changed
|
|
|
- * // opts is the target
|
|
|
- * });
|
|
|
- * ```
|
|
|
- **/
|
|
|
-// 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 = {}
|
|
|
- ,fn
|
|
|
- ,callbacks = []
|
|
|
- ;
|
|
|
-
|
|
|
- // set options
|
|
|
- fn = function fn( options, deep ){
|
|
|
-
|
|
|
- Physics.util.extend(target, options, deep ? deepCopyFn : null);
|
|
|
- for ( var i = 0, l = callbacks.length; i < l; ++i ){
|
|
|
- callbacks[ i ]( target );
|
|
|
- }
|
|
|
- return target;
|
|
|
- };
|
|
|
-
|
|
|
- // add defaults
|
|
|
- fn.defaults = function defaults( def, deep ){
|
|
|
- Physics.util.extend( _def, def, deep ? deepCopyFn : null );
|
|
|
- Physics.util.defaults( target, _def, deep ? deepCopyFn : null );
|
|
|
- return _def;
|
|
|
- };
|
|
|
-
|
|
|
- fn.onChange = function( cb ){
|
|
|
- callbacks.push( cb );
|
|
|
- };
|
|
|
-
|
|
|
- target = target || fn;
|
|
|
-
|
|
|
- fn.defaults( def );
|
|
|
-
|
|
|
- return fn;
|
|
|
-};
|
|
|
-
|
|
|
-/**
|
|
|
- * Physics.util.pairHash( id1, id2 ) -> Number
|
|
|
- * - id1 (Number): The id of the first thing
|
|
|
- * - id2 (Number): The id of the second thing
|
|
|
- * + (Number): A unique numeric hash (valid for values < 2^16)
|
|
|
- *
|
|
|
- * Generate a unique numeric hash from two input IDs.
|
|
|
- *
|
|
|
- * Useful for speedy indexing of pairs.
|
|
|
- **/
|
|
|
-Physics.util.pairHash = function( id1, id2 ){
|
|
|
- id1 = id1|0;
|
|
|
- id2 = id2|0;
|
|
|
-
|
|
|
- if ( (id1|0) === (id2|0) ){
|
|
|
-
|
|
|
- return -1;
|
|
|
- }
|
|
|
-
|
|
|
- // valid for values < 2^16
|
|
|
- return ((id1|0) > (id2|0) ?
|
|
|
- (id1 << 16) | (id2 & 0xFFFF) :
|
|
|
- (id2 << 16) | (id1 & 0xFFFF))|0
|
|
|
- ;
|
|
|
-};
|
|
|
-
|
|
|
-/**
|
|
|
- * Physics.util.bind( fn, scope[, args... ] ) -> Function
|
|
|
- * - fn (Function): The function to bind scope to
|
|
|
- * - scope (Object): The scope to give to `fn`
|
|
|
- * - args (Mixed): Arguments to send to `fn`
|
|
|
- *
|
|
|
- * Bind a scope to a function.
|
|
|
- *
|
|
|
- * Basically the same functionality as [Function.prototype.bind](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind).
|
|
|
- **/
|
|
|
-if ( !Function.prototype.bind ){
|
|
|
- Physics.util.bind = function( fn, scope, args ){
|
|
|
- args = Array.prototype.slice.call( arguments, 2 );
|
|
|
- return function(){
|
|
|
- return fn.apply( scope, args.concat( Array.prototype.slice.call(arguments) ) );
|
|
|
- };
|
|
|
- };
|
|
|
-} else {
|
|
|
- Physics.util.bind = function( fn, scope, args ){
|
|
|
- args = Array.prototype.slice.call( arguments, 1 );
|
|
|
- return Function.prototype.bind.apply( fn, args );
|
|
|
- };
|
|
|
-}
|
|
|
-
|
|
|
-/**
|
|
|
- * Physics.util.find( collection, fn( value, index, collection ) ) -> Mixed
|
|
|
- * - collection (Array): Collection of values to test
|
|
|
- * - fn (Function): The test function
|
|
|
- * - value (Mixed): The value to test
|
|
|
- * - index (Number): The index of value in collection
|
|
|
- * - collection (Array): The input collection
|
|
|
- *
|
|
|
- * Test an array of values against a test function
|
|
|
- * and return the first value for which the function
|
|
|
- * returns true.
|
|
|
- **/
|
|
|
-Physics.util.find = function( collection, fn ){
|
|
|
- var i
|
|
|
- ,l = collection.length
|
|
|
- ,val
|
|
|
- ;
|
|
|
-
|
|
|
- for ( i = 0; i < l; i++ ){
|
|
|
- val = collection[ i ];
|
|
|
- if ( fn( val, i, collection ) ){
|
|
|
- return val;
|
|
|
- }
|
|
|
- }
|
|
|
-};
|
|
|
-
|
|
|
-/**
|
|
|
- * Physics.util.filter( collection, fn( value, index, collection ) ) -> Array
|
|
|
- * - collection (Array): Collection of values to test
|
|
|
- * - fn (Function): The test function
|
|
|
- * - value (Mixed): The value to test
|
|
|
- * - index (Number): The index of value in collection
|
|
|
- * - collection (Array): The input collection
|
|
|
- *
|
|
|
- * Test an array of values against a test function
|
|
|
- * and return another array of values for which
|
|
|
- * the test function returns true.
|
|
|
- **/
|
|
|
-Physics.util.filter = function( collection, fn ){
|
|
|
- var i
|
|
|
- ,l = collection.length
|
|
|
- ,val
|
|
|
- ,matches = []
|
|
|
- ;
|
|
|
-
|
|
|
- for ( i = 0; i < l; i++ ){
|
|
|
- val = collection[ i ];
|
|
|
- if ( fn( val, i, collection ) ){
|
|
|
- matches.push( val );
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- return matches;
|
|
|
-};
|
|
|
-
|
|
|
-// lodash methods
|
|
|
-
|
|
|
-(function(){
|
|
|
-/*
|
|
|
- * @license
|
|
|
- * Modified version of:
|
|
|
- * Lo-Dash 2.4.1 (Custom Build) <http://lodash.com/>
|
|
|
- * Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
|
|
|
- * Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
|
|
|
- * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
|
|
|
- * Available under MIT license <http://lodash.com/license>
|
|
|
- */
|
|
|
-
|
|
|
-/* Used to determine if values are of the language type Object */
|
|
|
-var objectTypes = {
|
|
|
- 'boolean': false,
|
|
|
- 'function': true,
|
|
|
- 'object': true,
|
|
|
- 'number': false,
|
|
|
- 'string': false,
|
|
|
- 'undefined': false
|
|
|
-};
|
|
|
-var identity = function(a){ return a; };
|
|
|
-var arrayClass = '[object Array]';
|
|
|
-var objectClass = '[object Object]';
|
|
|
-var nativeKeys = Object.keys;
|
|
|
-var toString = Object.prototype.toString;
|
|
|
-var hasOwnProperty = Object.prototype.hasOwnProperty;
|
|
|
-/* Used as the size when optimizations are enabled for large arrays */
|
|
|
-var largeArraySize = 75;
|
|
|
-/* Used to pool arrays and objects used internally */
|
|
|
-var arrayPool = [],
|
|
|
- objectPool = [];
|
|
|
-/* Used as the max size of the `arrayPool` and `objectPool` */
|
|
|
-var maxPoolSize = 40;
|
|
|
-var keyPrefix = +new Date() + '';
|
|
|
-
|
|
|
-function releaseArray(array) {
|
|
|
- Physics.util.clearArray( array );
|
|
|
- if (arrayPool.length < maxPoolSize) {
|
|
|
- arrayPool.push(array);
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-function releaseObject(object) {
|
|
|
- var cache = object.cache;
|
|
|
- if (cache) {
|
|
|
- releaseObject(cache);
|
|
|
- }
|
|
|
- object.array = object.cache = object.criteria = object.object = object.number = object.string = object.value = null;
|
|
|
- if (objectPool.length < maxPoolSize) {
|
|
|
- objectPool.push(object);
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-function getObject() {
|
|
|
- return objectPool.pop() || {
|
|
|
- 'array': null,
|
|
|
- 'cache': null,
|
|
|
- 'criteria': null,
|
|
|
- 'false': false,
|
|
|
- 'index': 0,
|
|
|
- 'null': false,
|
|
|
- 'number': null,
|
|
|
- 'object': null,
|
|
|
- 'push': null,
|
|
|
- 'string': null,
|
|
|
- 'true': false,
|
|
|
- 'undefined': false,
|
|
|
- 'value': null
|
|
|
- };
|
|
|
-}
|
|
|
-
|
|
|
-function getArray() {
|
|
|
- return arrayPool.pop() || [];
|
|
|
-}
|
|
|
-
|
|
|
-function cacheIndexOf(cache, value) {
|
|
|
- var type = typeof value;
|
|
|
- cache = cache.cache;
|
|
|
-
|
|
|
- if (type === 'boolean' || value == null) {
|
|
|
- return cache[value] ? 0 : -1;
|
|
|
- }
|
|
|
- if (type !== 'number' && type !== 'string') {
|
|
|
- type = 'object';
|
|
|
- }
|
|
|
- var key = type === 'number' ? value : keyPrefix + value;
|
|
|
- cache = (cache = cache[type]) && cache[key];
|
|
|
-
|
|
|
- return type === 'object' ?
|
|
|
- (cache && Physics.util.indexOf(cache, value) > -1 ? 0 : -1) :
|
|
|
- (cache ? 0 : -1);
|
|
|
-}
|
|
|
-
|
|
|
-function cachePush(value) {
|
|
|
- var cache = this.cache,
|
|
|
- type = typeof value;
|
|
|
-
|
|
|
- if (type === 'boolean' || value == null) {
|
|
|
- cache[value] = true;
|
|
|
- } else {
|
|
|
- if (type !== 'number' && type !== 'string') {
|
|
|
- type = 'object';
|
|
|
- }
|
|
|
- var key = type === 'number' ? value : keyPrefix + value,
|
|
|
- typeCache = cache[type] || (cache[type] = {});
|
|
|
-
|
|
|
- if (type === 'object') {
|
|
|
- (typeCache[key] || (typeCache[key] = [])).push(value);
|
|
|
- } else {
|
|
|
- typeCache[key] = true;
|
|
|
- }
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-function createCache(array) {
|
|
|
- var index = -1,
|
|
|
- length = array.length,
|
|
|
- first = array[0],
|
|
|
- mid = array[(length / 2) | 0],
|
|
|
- last = array[length - 1];
|
|
|
-
|
|
|
- if (first && typeof first === 'object' &&
|
|
|
- mid && typeof mid === 'object' && last && typeof last === 'object') {
|
|
|
- return false;
|
|
|
- }
|
|
|
- var cache = getObject();
|
|
|
- cache['false'] = cache['null'] = cache['true'] = cache['undefined'] = false;
|
|
|
-
|
|
|
- var result = getObject();
|
|
|
- result.array = array;
|
|
|
- result.cache = cache;
|
|
|
- result.push = cachePush;
|
|
|
-
|
|
|
- while (++index < length) {
|
|
|
- result.push(array[index]);
|
|
|
- }
|
|
|
- return result;
|
|
|
-}
|
|
|
-
|
|
|
-var shimKeys = function(object) {
|
|
|
- var index, iterable = object, result = [];
|
|
|
- if (!iterable){ return result; }
|
|
|
- if (!(objectTypes[typeof object])){ return result; }
|
|
|
- for (index in iterable) {
|
|
|
- if (hasOwnProperty.call(iterable, index)) {
|
|
|
- result.push(index);
|
|
|
- }
|
|
|
- }
|
|
|
- return result;
|
|
|
-};
|
|
|
-
|
|
|
-var keys = !nativeKeys ? shimKeys : function(object) {
|
|
|
- if (!Physics.util.isObject(object)) {
|
|
|
- return [];
|
|
|
- }
|
|
|
- return nativeKeys(object);
|
|
|
-};
|
|
|
-
|
|
|
-var idCounter = 0;
|
|
|
-/**
|
|
|
- * Physics.util.uniqueId( [prefix] ) -> String
|
|
|
- * - prefix (String): Prefix to the id
|
|
|
- *
|
|
|
- * Generate a unique id, optionally prefixed.
|
|
|
- **/
|
|
|
-Physics.util.uniqueId = function uniqueId(prefix) {
|
|
|
- var id = ++idCounter;
|
|
|
- return '' + (prefix || '') + id;
|
|
|
-};
|
|
|
-
|
|
|
-/*
|
|
|
- * The base implementation of `_.random` without argument juggling or support
|
|
|
- * for returning floating-point numbers.
|
|
|
- *
|
|
|
- * @private
|
|
|
- * @param {number} min The minimum possible value.
|
|
|
- * @param {number} max The maximum possible value.
|
|
|
- * @returns {number} Returns a random number.
|
|
|
- */
|
|
|
-function baseRandom(min, max) {
|
|
|
- return min + Math.floor(Math.random() * (max - min + 1));
|
|
|
-}
|
|
|
-
|
|
|
-/*
|
|
|
- * Creates an array of shuffled values, using a version of the Fisher-Yates
|
|
|
- * shuffle. See http://en.wikipedia.org/wiki/Fisher-Yates_shuffle.
|
|
|
- *
|
|
|
- * @static
|
|
|
- * @memberOf _
|
|
|
- * @category Collections
|
|
|
- * @param {Array|Object|string} collection The collection to shuffle.
|
|
|
- * @returns {Array} Returns a new shuffled collection.
|
|
|
- * @example
|
|
|
- *
|
|
|
- * _.shuffle([1, 2, 3, 4, 5, 6]);
|
|
|
- * // => [4, 1, 6, 3, 5, 2]
|
|
|
- */
|
|
|
-Physics.util.shuffle = function(collection) {
|
|
|
- var index = -1
|
|
|
- ,length = collection ? collection.length : 0
|
|
|
- ,result = Array(typeof length === 'number' ? length : 0)
|
|
|
- ,i
|
|
|
- ,l
|
|
|
- ,value
|
|
|
- ,rand
|
|
|
- ;
|
|
|
-
|
|
|
- for ( i = 0, l = collection.length; i < l; i++ ){
|
|
|
- value = collection[ i ];
|
|
|
- rand = baseRandom(0, ++index);
|
|
|
- result[index] = result[rand];
|
|
|
- result[rand] = value;
|
|
|
- }
|
|
|
- return result;
|
|
|
-};
|
|
|
-
|
|
|
-/**
|
|
|
- * Physics.util.isObject( val ) -> Boolean
|
|
|
- * - val (Mixed): The value to test
|
|
|
- *
|
|
|
- * Test if a value is an object.
|
|
|
- **/
|
|
|
-Physics.util.isObject = function isObject(value) {
|
|
|
- // check if the value is the ECMAScript language type of Object
|
|
|
- // http://es5.github.io/#x8
|
|
|
- // and avoid a V8 bug
|
|
|
- // http://code.google.com/p/v8/issues/detail?id=2291
|
|
|
- return !!(value && objectTypes[typeof value]);
|
|
|
-};
|
|
|
-
|
|
|
-function isFunction(value) {
|
|
|
- return typeof value === 'function';
|
|
|
-}
|
|
|
-
|
|
|
-/**
|
|
|
- * Physics.util.isFunction( val ) -> Boolean
|
|
|
- * - val (Mixed): The value to test
|
|
|
- *
|
|
|
- * Test if a value is a function.
|
|
|
- **/
|
|
|
-Physics.util.isFunction = isFunction;
|
|
|
-
|
|
|
-/**
|
|
|
- * Physics.util.isArray( val ) -> Boolean
|
|
|
- * - val (Mixed): The value to test
|
|
|
- *
|
|
|
- * Test if a value is an array.
|
|
|
- **/
|
|
|
-Physics.util.isArray = Array.isArray || function(value) {
|
|
|
- return value && typeof value === 'object' && typeof value.length === 'number' &&
|
|
|
- toString.call(value) === arrayClass || false;
|
|
|
-};
|
|
|
-
|
|
|
-var reNative = RegExp('^' +
|
|
|
- String(toString)
|
|
|
- .replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
|
- .replace(/toString| for [^\]]+/g, '.*?') + '$'
|
|
|
-);
|
|
|
-function isNative(value) {
|
|
|
- return typeof value === 'function' && reNative.test(value);
|
|
|
-}
|
|
|
-
|
|
|
-function shimIsPlainObject(value) {
|
|
|
- var ctor,
|
|
|
- result;
|
|
|
-
|
|
|
- // avoid non Object objects, `arguments` objects, and DOM elements
|
|
|
- if (!(value && toString.call(value) === objectClass) ||
|
|
|
- (ctor = value.constructor, isFunction(ctor) && !(ctor instanceof ctor))) {
|
|
|
- return false;
|
|
|
- }
|
|
|
- // In most environments an object's own properties are iterated before
|
|
|
- // its inherited properties. If the last iterated property is an object's
|
|
|
- // own property then there are no inherited enumerable properties.
|
|
|
- for (var key in value){
|
|
|
- result = key;
|
|
|
- }
|
|
|
- return typeof result === 'undefined' || hasOwnProperty.call(value, result);
|
|
|
-}
|
|
|
-
|
|
|
-/**
|
|
|
- * Physics.util.isPlainObject( val ) -> Boolean
|
|
|
- * - val (Mixed): The value to test
|
|
|
- *
|
|
|
- * Test if a value is a plain javascript object.
|
|
|
- **/
|
|
|
-Physics.util.isPlainObject = !Object.getPrototypeOf ? shimIsPlainObject : function(value) {
|
|
|
- if (!(value && toString.call(value) === objectClass)) {
|
|
|
- return false;
|
|
|
- }
|
|
|
- var valueOf = value.valueOf,
|
|
|
- objProto = isNative(valueOf) && (objProto = Object.getPrototypeOf(valueOf)) && Object.getPrototypeOf(objProto);
|
|
|
-
|
|
|
- return objProto ?
|
|
|
- (value === objProto || Object.getPrototypeOf(value) === objProto) :
|
|
|
- shimIsPlainObject(value);
|
|
|
-};
|
|
|
-
|
|
|
-function baseUniq(array, isSorted, callback) {
|
|
|
- var index = -1,
|
|
|
- indexOf = Physics.util.indexOf,
|
|
|
- length = array ? array.length : 0,
|
|
|
- result = [];
|
|
|
-
|
|
|
- var isLarge = !isSorted && length >= largeArraySize && indexOf === Physics.util.indexOf,
|
|
|
- seen = (callback || isLarge) ? getArray() : result;
|
|
|
-
|
|
|
- if (isLarge) {
|
|
|
- var cache = createCache(seen);
|
|
|
- indexOf = cacheIndexOf;
|
|
|
- seen = cache;
|
|
|
- }
|
|
|
- while (++index < length) {
|
|
|
- var value = array[index],
|
|
|
- computed = callback ? callback(value, index, array) : value;
|
|
|
-
|
|
|
- if (isSorted ?
|
|
|
- !index || seen[seen.length - 1] !== computed :
|
|
|
- indexOf(seen, computed) < 0
|
|
|
- ) {
|
|
|
- if (callback || isLarge) {
|
|
|
- seen.push(computed);
|
|
|
- }
|
|
|
- result.push(value);
|
|
|
- }
|
|
|
- }
|
|
|
- if (isLarge) {
|
|
|
- releaseArray(seen.array);
|
|
|
- releaseObject(seen);
|
|
|
- } else if (callback) {
|
|
|
- releaseArray(seen);
|
|
|
- }
|
|
|
- return result;
|
|
|
-}
|
|
|
-
|
|
|
-/**
|
|
|
- * Physics.util.uniq( array, [isSorted, callback] ) -> Array
|
|
|
- * - array (Array): The array
|
|
|
- * - isSorted (Boolean): Flag to indicate the array is sorted
|
|
|
- * - callback (Function): Mapping function
|
|
|
- *
|
|
|
- * Create an array without duplicates.
|
|
|
- **/
|
|
|
-Physics.util.uniq = function uniq(array, isSorted, callback) {
|
|
|
- // juggle arguments
|
|
|
- if (typeof isSorted !== 'boolean' && isSorted != null) {
|
|
|
- callback = isSorted;
|
|
|
- isSorted = false;
|
|
|
- }
|
|
|
- return baseUniq(array, isSorted, callback);
|
|
|
-};
|
|
|
-
|
|
|
-var assign = function(object, source, guard) {
|
|
|
- var index, iterable = object, result = iterable;
|
|
|
- if (!iterable) { return result; }
|
|
|
- var args = arguments,
|
|
|
- argsIndex = 0,
|
|
|
- callback,
|
|
|
- argsLength = typeof guard === 'number' ? 2 : args.length;
|
|
|
- if (argsLength > 2 && typeof args[argsLength - 1] === 'function') {
|
|
|
- callback = args[--argsLength];
|
|
|
- }
|
|
|
- while (++argsIndex < argsLength) {
|
|
|
- iterable = args[argsIndex];
|
|
|
- if (iterable && objectTypes[typeof iterable]) {
|
|
|
- var ownIndex = -1,
|
|
|
- ownProps = objectTypes[typeof iterable] && keys(iterable),
|
|
|
- length = ownProps ? ownProps.length : 0;
|
|
|
-
|
|
|
- while (++ownIndex < length) {
|
|
|
- index = ownProps[ownIndex];
|
|
|
- result[index] = callback ? callback(result[index], iterable[index]) : iterable[index];
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- return result;
|
|
|
-};
|
|
|
-
|
|
|
-/**
|
|
|
- * Physics.util.extend( object, source...[, callback] ) -> Object
|
|
|
- * - object (Object): The destination object
|
|
|
- * - source (Object): The source objects
|
|
|
- * - callback (Function): The function to customize assigning values
|
|
|
- *
|
|
|
- * Implementation of [lodash.extend](http://lodash.com/docs#assign)
|
|
|
- **/
|
|
|
-Physics.util.extend = assign;
|
|
|
-
|
|
|
-/**
|
|
|
- * Physics.util.defaults( object, source...[, callback] ) -> Object
|
|
|
- * - object (Object): The destination object
|
|
|
- * - source (Object): The source objects
|
|
|
- * - callback (Function): The function to customize assigning values
|
|
|
- *
|
|
|
- * Implementation of [lodash.defaults](http://lodash.com/docs#defaults).
|
|
|
- **/
|
|
|
-Physics.util.defaults = function(object, source, guard) {
|
|
|
- var index, iterable = object, result = iterable;
|
|
|
- if (!iterable){ return result; }
|
|
|
- var args = arguments,
|
|
|
- argsIndex = 0,
|
|
|
- argsLength = typeof guard === 'number' ? 2 : args.length;
|
|
|
- while (++argsIndex < argsLength) {
|
|
|
- iterable = args[argsIndex];
|
|
|
- if (iterable && objectTypes[typeof iterable]) {
|
|
|
- var ownIndex = -1,
|
|
|
- ownProps = objectTypes[typeof iterable] && keys(iterable),
|
|
|
- length = ownProps ? ownProps.length : 0;
|
|
|
-
|
|
|
- while (++ownIndex < length) {
|
|
|
- index = ownProps[ownIndex];
|
|
|
- if (typeof result[index] === 'undefined') {
|
|
|
- result[index] = iterable[index];
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- return result;
|
|
|
-};
|
|
|
-
|
|
|
-/**
|
|
|
- * Physics.util.sortedIndex( array, value[, callback] ) -> Number
|
|
|
- * - array (Array): The array to inspect
|
|
|
- * - value (Mixed): The value to evaluate
|
|
|
- * - callback (Function): Function called per iteration
|
|
|
- *
|
|
|
- * Implementation of [lodash.sortedIndex](http://lodash.com/docs#sortedIndex).
|
|
|
- **/
|
|
|
-Physics.util.sortedIndex = function sortedIndex(array, value, callback) {
|
|
|
- var low = 0,
|
|
|
- high = array ? array.length : low;
|
|
|
-
|
|
|
- // explicitly reference `identity` for better inlining in Firefox
|
|
|
- callback = callback || identity;
|
|
|
- value = callback(value);
|
|
|
-
|
|
|
- /* jshint -W030 */
|
|
|
- while (low < high) {
|
|
|
- var mid = (low + high) >>> 1;
|
|
|
- (callback(array[mid]) < value) ?
|
|
|
- low = mid + 1 :
|
|
|
- high = mid;
|
|
|
- }
|
|
|
- /* jshint +W030 */
|
|
|
- return low;
|
|
|
-};
|
|
|
-
|
|
|
-})();
|
|
|
-
|
|
|
-
|
|
|
-// ---
|
|
|
-// inside: src/util/scratchpad.js
|
|
|
-
|
|
|
-/*
|
|
|
- * scratchpad
|
|
|
- * thread-safe management of temporary (voletile)
|
|
|
- * objects for use in calculations
|
|
|
- * https://github.com/wellcaffeinated/scratchpad.js
|
|
|
- */
|
|
|
-Physics.scratchpad = (function(){
|
|
|
-
|
|
|
- // Errors
|
|
|
- var SCRATCH_USAGE_ERROR = 'Error: Scratchpad used after .done() called. (Could it be unintentionally scoped?)';
|
|
|
- var SCRATCH_INDEX_OUT_OF_BOUNDS = 'Error: Scratchpad usage space out of bounds. (Did you forget to call .done()?)';
|
|
|
- var SCRATCH_MAX_REACHED = 'Error: Too many scratchpads created. (Did you forget to call .done()?)';
|
|
|
- var ALREADY_DEFINED_ERROR = 'Error: Object is already registered.';
|
|
|
-
|
|
|
- // cache previously created scratches
|
|
|
- var scratches = [];
|
|
|
- var numScratches = 0;
|
|
|
- var Scratch, Scratchpad;
|
|
|
-
|
|
|
- var regIndex = 0;
|
|
|
-
|
|
|
-
|
|
|
- /** belongs to: Physics.scratchpad
|
|
|
- * class Scratch
|
|
|
- *
|
|
|
- * A scratchpad session.
|
|
|
- *
|
|
|
- * This class keeps track of temporary objects used
|
|
|
- * in this session and releases them when finished (call to `.done()`).
|
|
|
- *
|
|
|
- * Use this to retrieve temporary objects:
|
|
|
- * - `.vector()`: retrieve a temporary [[Physics.vector]]
|
|
|
- * - `.transform()`: retrieve a temporary [[Physics.transform]]
|
|
|
- *
|
|
|
- * See [[Physics.scratchpad]] for more info.
|
|
|
- **/
|
|
|
- Scratch = function Scratch(){
|
|
|
-
|
|
|
- // private variables
|
|
|
- this._active = false;
|
|
|
- this._indexArr = [];
|
|
|
-
|
|
|
- if (++numScratches >= Scratchpad.maxScratches){
|
|
|
- throw SCRATCH_MAX_REACHED;
|
|
|
- }
|
|
|
- };
|
|
|
-
|
|
|
- Scratch.prototype = {
|
|
|
-
|
|
|
- /**
|
|
|
- * Scratch#done( [val] ) -> Mixed
|
|
|
- * - 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.
|
|
|
- *
|
|
|
- * Example:
|
|
|
- *
|
|
|
- * ```javascript
|
|
|
- * return scratch.done( myReturnValue );
|
|
|
- * ```
|
|
|
- **/
|
|
|
- done: function( val ){
|
|
|
-
|
|
|
- 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;
|
|
|
- }
|
|
|
- };
|
|
|
-
|
|
|
-
|
|
|
- // API
|
|
|
-
|
|
|
- /**
|
|
|
- * Physics.scratchpad( [fn] ) -> Scratch|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, ... ){
|
|
|
- * var scratch = Physics.scratchpad()
|
|
|
- * ,vec = scratch.vector().set( 0, 0 ) // need to reinitialize... it's recycled!
|
|
|
- * ;
|
|
|
- * // ...
|
|
|
- * return scratch.done( result );
|
|
|
- * };
|
|
|
- * // later...
|
|
|
- * while( awesome ){
|
|
|
- * myAlg( arg1, arg2, ... );
|
|
|
- * }
|
|
|
- * ```
|
|
|
- *
|
|
|
- * Example:
|
|
|
- *
|
|
|
- * ```javascript
|
|
|
- * // wrap a function in a scratch session
|
|
|
- * var myAlg = Physics.scratchpad(function( scratch, arg1, arg2, ... ){
|
|
|
- * var vec = scratch.vector().set( 0, 0 ); // need to reinitialize... it's recycled!
|
|
|
- * //...
|
|
|
- * return result;
|
|
|
- * });
|
|
|
- * // later...
|
|
|
- * while( awesome ){
|
|
|
- * myAlg( arg1, arg2, ... );
|
|
|
- * }
|
|
|
- * ```
|
|
|
- **/
|
|
|
- Scratchpad = function Scratchpad( fn ){
|
|
|
-
|
|
|
- if ( fn ){
|
|
|
- return Scratchpad.fn( fn );
|
|
|
- }
|
|
|
-
|
|
|
- var scratch = scratches.pop() || new Scratch();
|
|
|
- scratch._active = true;
|
|
|
- return scratch;
|
|
|
- };
|
|
|
-
|
|
|
- // options
|
|
|
- Scratchpad.maxScratches = 100; // maximum number of scratches
|
|
|
- Scratchpad.maxIndex = 20; // maximum number of any type of temp objects
|
|
|
-
|
|
|
- /**
|
|
|
- * 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 );
|
|
|
- }
|
|
|
-
|
|
|
- args = 'a' + args.join(',a');
|
|
|
- /* jshint -W054 */
|
|
|
- var handle = new Function('fn, scratches, Scratch', 'return function('+args+'){ '+
|
|
|
- 'var scratch = scratches.pop() || new Scratch( scratches );'+
|
|
|
- 'scratch._active = true;'+
|
|
|
- 'return scratch.done( fn(scratch, '+args+') );'+
|
|
|
- '};'
|
|
|
- );
|
|
|
- /* jshint +W054 */
|
|
|
-
|
|
|
- return handle(fn, scratches, Scratch);
|
|
|
- };
|
|
|
-
|
|
|
- /**
|
|
|
- * 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:
|
|
|
- *
|
|
|
- * ```javascript
|
|
|
- * // register a hypothetical vector class...
|
|
|
- * Physics.scratchpad.register('vector', Vector);
|
|
|
- * ```
|
|
|
- **/
|
|
|
- Scratchpad.register = function register( name, constructor, options ){
|
|
|
-
|
|
|
- var proto = Scratch.prototype
|
|
|
- ,idx = regIndex++ // increase the scratch type index
|
|
|
- ,stackname = '_' + name + 'Stack' // the name of the array stack
|
|
|
- ,useFactory = options && options.useFactory
|
|
|
- ;
|
|
|
-
|
|
|
- if ( name in proto ) {
|
|
|
- throw ALREADY_DEFINED_ERROR;
|
|
|
- }
|
|
|
-
|
|
|
- // create a new function on the prototype
|
|
|
- Scratch.prototype[ name ] = function(){
|
|
|
-
|
|
|
- // get the stack (or initialize it)
|
|
|
- var stack = this[ stackname ] || (this[ stackname ] = [])
|
|
|
- // we increase this index every time a voletile object is requested
|
|
|
- // seems weird to store it on this as a number (ie: this.0, this.1)...
|
|
|
- // but actually it's faster...
|
|
|
- ,stackIndex = this[ idx ] | 0
|
|
|
- ;
|
|
|
-
|
|
|
- this[ idx ] = stackIndex + 1;
|
|
|
-
|
|
|
- // if used after calling done...
|
|
|
- if (!this._active){
|
|
|
- throw SCRATCH_USAGE_ERROR;
|
|
|
- }
|
|
|
-
|
|
|
- // if too many objects created...
|
|
|
- if (stackIndex >= Scratchpad.maxIndex){
|
|
|
- throw SCRATCH_INDEX_OUT_OF_BOUNDS;
|
|
|
- }
|
|
|
-
|
|
|
- // return or create new instance
|
|
|
- return stack[ stackIndex ] ||
|
|
|
- (stack[ stackIndex ] = useFactory ? constructor() : new constructor() );
|
|
|
- };
|
|
|
-
|
|
|
- };
|
|
|
-
|
|
|
- // register some classes
|
|
|
- Scratchpad.register('vector', Physics.vector);
|
|
|
- Scratchpad.register('transform', Physics.transform);
|
|
|
-
|
|
|
- return Scratchpad;
|
|
|
-
|
|
|
-})();
|
|
|
-
|
|
|
-
|
|
|
-// ---
|
|
|
-// inside: src/util/pubsub.js
|
|
|
-
|
|
|
-(function(){
|
|
|
-
|
|
|
- var defaultPriority = 1;
|
|
|
-
|
|
|
- function getPriority( val ){
|
|
|
- return val._priority_;
|
|
|
- }
|
|
|
-
|
|
|
- // register a new scratch object so we can reuse event data
|
|
|
- Physics.scratchpad.register('event', function(){ return {}; }, { useFactory: true });
|
|
|
-
|
|
|
- /**
|
|
|
- * class Physics.util.pubsub
|
|
|
- *
|
|
|
- * Fast pubsub implementation.
|
|
|
- *
|
|
|
- * Can be mixed into other classes easily.
|
|
|
- **/
|
|
|
- var PubSub = function PubSub(){
|
|
|
-
|
|
|
- if (!(this instanceof PubSub)){
|
|
|
- return new PubSub();
|
|
|
- }
|
|
|
- };
|
|
|
-
|
|
|
- PubSub.prototype = {
|
|
|
-
|
|
|
- /**
|
|
|
- * Physics.util.pubsub#on( topic, fn( data, event )[, scope, priority] ) -> this
|
|
|
- * Physics.util.pubsub#on( topicConfig[, scope, priority] ) -> this
|
|
|
- * - topic (String): The topic name
|
|
|
- * - topicConfig (Object): A config with key/value pairs of `{ topic: callbackFn, ... }`
|
|
|
- * - fn (Function): The callback function (if not using Object as previous argument)
|
|
|
- * - data (Mixed): The data sent from the call to `.emit()`
|
|
|
- * - event (Object): Event data, holding `.topic`, the topic, and `.handler`, the `fn` callback.
|
|
|
- * - scope (Object): The scope to bind callback to
|
|
|
- * - priority (Number): The priority of the callback (higher is earlier)
|
|
|
- *
|
|
|
- * Subscribe callback(s) to a topic(s).
|
|
|
- **/
|
|
|
- on: function( topic, fn, scope, priority ){
|
|
|
-
|
|
|
- var listeners
|
|
|
- ,orig
|
|
|
- ,idx
|
|
|
- ;
|
|
|
-
|
|
|
- // ensure topics hash is initialized
|
|
|
- this._topics = this._topics || (this._topics = {});
|
|
|
-
|
|
|
- // check if we're subscribing to multiple topics
|
|
|
- // with an object
|
|
|
- if ( Physics.util.isObject( topic ) ){
|
|
|
-
|
|
|
- for ( var t in topic ){
|
|
|
-
|
|
|
- this.on( t, topic[ t ], fn, scope );
|
|
|
- }
|
|
|
-
|
|
|
- return this;
|
|
|
- }
|
|
|
-
|
|
|
- listeners = this._topics[ topic ] || (this._topics[ topic ] = []);
|
|
|
- orig = fn;
|
|
|
-
|
|
|
- if ( Physics.util.isObject( scope ) ){
|
|
|
-
|
|
|
- fn = Physics.util.bind( fn, scope );
|
|
|
- fn._bindfn_ = orig;
|
|
|
- fn._one_ = orig._one_;
|
|
|
- fn._scope_ = scope;
|
|
|
-
|
|
|
- } else if ( priority === undefined ) {
|
|
|
-
|
|
|
- priority = scope;
|
|
|
- }
|
|
|
-
|
|
|
- fn._priority_ = priority === undefined ? defaultPriority : priority;
|
|
|
-
|
|
|
- idx = Physics.util.sortedIndex( listeners, fn, getPriority );
|
|
|
-
|
|
|
- listeners.splice( idx, 0, fn );
|
|
|
- return 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, scope ){
|
|
|
-
|
|
|
- var listeners
|
|
|
- ,listn
|
|
|
- ;
|
|
|
-
|
|
|
- if ( !this._topics ){
|
|
|
- // nothing subscribed
|
|
|
- return this;
|
|
|
- }
|
|
|
-
|
|
|
- if ( topic === true ){
|
|
|
- // purge all listeners
|
|
|
- this._topics = {};
|
|
|
- return this;
|
|
|
- }
|
|
|
-
|
|
|
- // check if we're subscribing to multiple topics
|
|
|
- // with an object
|
|
|
- if ( Physics.util.isObject( topic ) ){
|
|
|
-
|
|
|
- for ( var t in topic ){
|
|
|
-
|
|
|
- this.off( t, topic[ t ] );
|
|
|
- }
|
|
|
-
|
|
|
- return this;
|
|
|
- }
|
|
|
-
|
|
|
- listeners = this._topics[ topic ];
|
|
|
-
|
|
|
- if (!listeners){
|
|
|
- return this;
|
|
|
- }
|
|
|
-
|
|
|
- if ( fn === true ){
|
|
|
- // purge all listeners for topic
|
|
|
- this._topics[ topic ] = [];
|
|
|
- return this;
|
|
|
- }
|
|
|
-
|
|
|
- for ( var i = 0, l = listeners.length; i < l; i++ ){
|
|
|
-
|
|
|
- listn = listeners[ i ];
|
|
|
-
|
|
|
- if (
|
|
|
- (listn._bindfn_ === fn || listn === fn) &&
|
|
|
- ( (!scope) || listn._scope_ === scope) // check the scope too if specified
|
|
|
- ){
|
|
|
- listeners.splice( i, 1 );
|
|
|
- break;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- return this;
|
|
|
- },
|
|
|
-
|
|
|
- /**
|
|
|
- * Physics.util.pubsub#emit( topic[, data] ) -> this
|
|
|
- * - topic (String): The topic name
|
|
|
- * - data (Mixed): The data to send
|
|
|
- *
|
|
|
- * Publish data to a topic.
|
|
|
- **/
|
|
|
- emit: function( topic, data ){
|
|
|
-
|
|
|
- if ( !this._topics ){
|
|
|
- // nothing subscribed
|
|
|
- return this;
|
|
|
- }
|
|
|
-
|
|
|
- var listeners = this._topics[ topic ]
|
|
|
- ,l = listeners && listeners.length
|
|
|
- ,handler
|
|
|
- ,e
|
|
|
- ,scratch = Physics.scratchpad()
|
|
|
- ;
|
|
|
-
|
|
|
- if ( !l ){
|
|
|
- return scratch.done(this);
|
|
|
- }
|
|
|
-
|
|
|
- e = scratch.event();
|
|
|
- // event data
|
|
|
- e.topic = topic;
|
|
|
- e.handler = handler;
|
|
|
-
|
|
|
- // reverse iterate so priorities work out correctly
|
|
|
- while ( l-- ){
|
|
|
-
|
|
|
- handler = listeners[ l ];
|
|
|
- handler( data, e );
|
|
|
-
|
|
|
- // if _one_ flag is set, the unsubscribe
|
|
|
- if ( handler._one_ ){
|
|
|
- listeners.splice( l, 1 );
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- return scratch.done(this);
|
|
|
- },
|
|
|
-
|
|
|
- /**
|
|
|
- * Physics.util.pubsub#one( topic, fn( data, event )[, scope, priority] ) -> this
|
|
|
- * Physics.util.pubsub#one( topicConfig[, scope, priority] ) -> this
|
|
|
- * - topic (String): The topic name
|
|
|
- * - topicConfig (Object): A config with key/value pairs of `{ topic: callbackFn, ... }`
|
|
|
- * - fn (Function): The callback function (if not using Object as previous argument)
|
|
|
- * - data (Mixed): The data sent from the call to `.emit()`
|
|
|
- * - event (Object): Event data, holding `.topic`, the topic, and `.handler`, the `fn` callback.
|
|
|
- * - scope (Object): The scope to bind callback to
|
|
|
- * - priority (Number): The priority of the callback (higher is earlier)
|
|
|
- *
|
|
|
- * Subscribe callback(s) to a topic(s), but only ONCE.
|
|
|
- **/
|
|
|
- one: function( topic, fn, scope ){
|
|
|
-
|
|
|
- // check if we're subscribing to multiple topics
|
|
|
- // with an object
|
|
|
- if ( Physics.util.isObject( topic ) ){
|
|
|
-
|
|
|
- for ( var t in topic ){
|
|
|
-
|
|
|
- this.one( t, topic[ t ], fn, scope );
|
|
|
- }
|
|
|
-
|
|
|
- return this;
|
|
|
- }
|
|
|
-
|
|
|
- // set the _one_ flag
|
|
|
- fn._one_ = true;
|
|
|
- this.on( topic, fn, scope );
|
|
|
-
|
|
|
- return this;
|
|
|
- }
|
|
|
- };
|
|
|
-
|
|
|
- Physics.util.pubsub = PubSub;
|
|
|
-})();
|
|
|
-
|
|
|
-
|
|
|
-// ---
|
|
|
-// inside: src/util/ticker.js
|
|
|
-
|
|
|
-/**
|
|
|
- * class Physics.util.ticker
|
|
|
- *
|
|
|
- * The Ticker _singleton_ for easily binding callbacks to animation loops (requestAnimationFrame).
|
|
|
- *
|
|
|
- * Requires window.requestAnimationFrame... so polyfill it if you need to.
|
|
|
- **/
|
|
|
-(function(window){
|
|
|
-
|
|
|
- var active = true
|
|
|
- ,ps = Physics.util.pubsub()
|
|
|
- ,perf = window.performance
|
|
|
- ;
|
|
|
-
|
|
|
- function now(){
|
|
|
- // http://updates.html5rocks.com/2012/05/requestAnimationFrame-API-now-with-sub-millisecond-precision
|
|
|
- return (perf && perf.now) ?
|
|
|
- (perf.now() + perf.timing.navigationStart) :
|
|
|
- Date.now();
|
|
|
- }
|
|
|
-
|
|
|
- /*
|
|
|
- * step( time )
|
|
|
- * - time (Number): The current time
|
|
|
- *
|
|
|
- * Publish a tick to subscribed callbacks
|
|
|
- */
|
|
|
- function step(){
|
|
|
-
|
|
|
- var time;
|
|
|
-
|
|
|
- window.requestAnimationFrame( step );
|
|
|
-
|
|
|
- if (!active){
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- time = now();
|
|
|
-
|
|
|
- if (!time){
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- ps.emit( 'tick', time );
|
|
|
- }
|
|
|
-
|
|
|
- // start stepping if we can
|
|
|
- if ( window.requestAnimationFrame ){
|
|
|
- step();
|
|
|
- } else {
|
|
|
- active = false;
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Physics.util.ticker.start() -> this
|
|
|
- *
|
|
|
- * Start the ticker
|
|
|
- **/
|
|
|
- function start(){
|
|
|
-
|
|
|
- active = true;
|
|
|
- return this;
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Physics.util.ticker.stop() -> this
|
|
|
- *
|
|
|
- * Stop the ticker
|
|
|
- **/
|
|
|
- function stop(){
|
|
|
-
|
|
|
- active = false;
|
|
|
- return this;
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Physics.util.ticker.on( listener( time ) ) -> this
|
|
|
- * - listener (Function): The callback function
|
|
|
- * - time (Number): The current timestamp
|
|
|
- *
|
|
|
- * Subscribe a callback to the ticker.
|
|
|
- **/
|
|
|
- function on( listener ){
|
|
|
-
|
|
|
- ps.on('tick', listener);
|
|
|
- return this;
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Physics.util.ticker.off( listener ) -> this
|
|
|
- * - listener (Function): The callback function previously bound
|
|
|
- *
|
|
|
- * Unsubscribe a callback from the ticker.
|
|
|
- **/
|
|
|
- function off( listener ){
|
|
|
-
|
|
|
- ps.off('tick', listener);
|
|
|
- return this;
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Physics.util.ticker.isActive() -> Boolean
|
|
|
- * + (Boolean): `true` if running, `false` otherwise.
|
|
|
- *
|
|
|
- * Determine if ticker is currently running.
|
|
|
- **/
|
|
|
- function isActive(){
|
|
|
-
|
|
|
- return !!active;
|
|
|
- }
|
|
|
-
|
|
|
- // API
|
|
|
- Physics.util.ticker = {
|
|
|
- now: now,
|
|
|
- start: start,
|
|
|
- stop: stop,
|
|
|
- on: on,
|
|
|
- off: off,
|
|
|
- isActive: isActive
|
|
|
- };
|
|
|
-
|
|
|
-}(this));
|
|
|
-
|
|
|
-
|
|
|
-// ---
|
|
|
-// inside: src/core/query.js
|
|
|
-
|
|
|
-(function (window) {
|
|
|
-
|
|
|
- /*
|
|
|
- * Group helpers
|
|
|
- */
|
|
|
- var fnTrue = function(){ return !0; }; // return true
|
|
|
-
|
|
|
- var indexOf = Physics.util.indexOf;
|
|
|
-
|
|
|
- /** hide
|
|
|
- * wrapRule( fn( propVal ), prop ) -> Function
|
|
|
- * - fn (Function): The test function
|
|
|
- * - prop (String): The property name to test
|
|
|
- * - propVal (Mixed): The property value
|
|
|
- *
|
|
|
- * Get test function to test on sub property.
|
|
|
- **/
|
|
|
- var wrapRule = function wrapRule( fn, prop ){
|
|
|
- return function( thing ){
|
|
|
- return fn( thing[ prop ] );
|
|
|
- };
|
|
|
- };
|
|
|
-
|
|
|
- /** hide
|
|
|
- * $eq( toMatch[, prop] ) -> Function
|
|
|
- * - toMatch (Mixed): The value to match
|
|
|
- * - prop (String): The property name to test
|
|
|
- *
|
|
|
- * Get an equality test function.
|
|
|
- **/
|
|
|
- var $eq = function $eq( toMatch, prop ){
|
|
|
- return function( thing ){
|
|
|
-
|
|
|
- thing = prop ? thing[ prop ] : thing;
|
|
|
-
|
|
|
- var fr = 0
|
|
|
- ,bk
|
|
|
- ;
|
|
|
-
|
|
|
- if ( Physics.util.isArray( thing ) ){
|
|
|
-
|
|
|
- if ( Physics.util.isArray( toMatch ) ){
|
|
|
- // match all
|
|
|
- bk = thing.length;
|
|
|
-
|
|
|
- // check lengths
|
|
|
- if ( bk !== toMatch.length ){
|
|
|
- return false;
|
|
|
- }
|
|
|
-
|
|
|
- while ( fr < bk ){
|
|
|
- bk--;
|
|
|
- if (
|
|
|
- // check front
|
|
|
- (indexOf(toMatch, thing[ fr ]) === -1) ||
|
|
|
- // check back
|
|
|
- (indexOf(toMatch, thing[ bk ]) === -1)
|
|
|
- ) {
|
|
|
- return false;
|
|
|
- }
|
|
|
- fr++;
|
|
|
- }
|
|
|
- return true;
|
|
|
- } else {
|
|
|
- // find in array
|
|
|
- return (indexOf( thing, toMatch ) > -1);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // exact match
|
|
|
- return (thing === toMatch);
|
|
|
- };
|
|
|
- };
|
|
|
-
|
|
|
- /** hide
|
|
|
- * $ne( toMatch[, prop] ) -> Function
|
|
|
- * - toMatch (Mixed): The value to match
|
|
|
- * - prop (String): The property name to test
|
|
|
- *
|
|
|
- * Get a NOT equality test function.
|
|
|
- **/
|
|
|
- var $ne = function $ne( toMatch, prop ){
|
|
|
- var fn = $eq( toMatch, prop );
|
|
|
- return function( thing ){
|
|
|
- return !fn( thing );
|
|
|
- };
|
|
|
- };
|
|
|
-
|
|
|
- /** hide
|
|
|
- * $in( toMatch[, prop] ) -> Function
|
|
|
- * - toMatch (Array): The array to match
|
|
|
- * - prop (String): The property name to test
|
|
|
- *
|
|
|
- * Get a test function for matching ANY in array
|
|
|
- **/
|
|
|
- var $in = function $in( toMatch, prop ){
|
|
|
- return function( thing ){
|
|
|
-
|
|
|
- thing = prop ? thing[ prop ] : thing;
|
|
|
-
|
|
|
- var fr = 0
|
|
|
- ,bk
|
|
|
- ;
|
|
|
-
|
|
|
- if ( Physics.util.isArray( thing ) ){
|
|
|
- bk = thing.length;
|
|
|
-
|
|
|
- while( fr < bk ){
|
|
|
- bk--;
|
|
|
- if (
|
|
|
- // check front
|
|
|
- (indexOf(toMatch, thing[ fr ]) > -1) ||
|
|
|
- // check back
|
|
|
- (indexOf(toMatch, thing[ bk ]) > -1)
|
|
|
- ) {
|
|
|
- return true;
|
|
|
- }
|
|
|
- fr++;
|
|
|
- }
|
|
|
- return false;
|
|
|
- }
|
|
|
-
|
|
|
- // if thing matches any in array
|
|
|
- return (indexOf(toMatch, thing) > -1);
|
|
|
- };
|
|
|
- };
|
|
|
-
|
|
|
- /** hide
|
|
|
- * $nin( toMatch[, prop] ) -> Function
|
|
|
- * - toMatch (Array): The array to match
|
|
|
- * - prop (String): The property name to test
|
|
|
- *
|
|
|
- * Get a test function for matching NOT ANY in array
|
|
|
- **/
|
|
|
- var $nin = function $nin( toMatch, prop ){
|
|
|
- var fn = $in( toMatch, prop );
|
|
|
- return function( thing ){
|
|
|
- return !fn( thing );
|
|
|
- };
|
|
|
- };
|
|
|
-
|
|
|
- /** hide
|
|
|
- * $at( point ) -> Function
|
|
|
- * - point (Vectorish): The point to check
|
|
|
- *
|
|
|
- * Get a test function to match any body who's aabb intersects point
|
|
|
- **/
|
|
|
- var $at = function $at( point ){
|
|
|
- point = new Physics.vector( point );
|
|
|
- return function( body ){
|
|
|
- var aabb = body.aabb();
|
|
|
- return Physics.aabb.contains( aabb, point );
|
|
|
- };
|
|
|
- };
|
|
|
-
|
|
|
- /** hide
|
|
|
- * $and( first ) -> Function
|
|
|
- * - first (Function): First function node. `first.next` should have the next function, and so on.
|
|
|
- *
|
|
|
- * Get an AND test function.
|
|
|
- **/
|
|
|
- var $and = function $and( first ){
|
|
|
- return first.next ? function( thing ){
|
|
|
- var fn = first;
|
|
|
- while ( fn ){
|
|
|
-
|
|
|
- if ( !fn( thing ) ){
|
|
|
- return false;
|
|
|
- }
|
|
|
- fn = fn.next;
|
|
|
- }
|
|
|
- return true;
|
|
|
- } : first;
|
|
|
- };
|
|
|
-
|
|
|
- /** hide
|
|
|
- * $or( first ) -> Function
|
|
|
- * - first (Function): First function node. `first.next` should have the next function, and so on.
|
|
|
- *
|
|
|
- * Get an OR test function.
|
|
|
- **/
|
|
|
- var $or = function $or( first ){
|
|
|
- return first.next ? function( thing ){
|
|
|
- var fn = first;
|
|
|
- while ( fn ){
|
|
|
-
|
|
|
- if ( fn( thing ) ){
|
|
|
- return true;
|
|
|
- }
|
|
|
- fn = fn.next;
|
|
|
- }
|
|
|
- return false;
|
|
|
- } : first;
|
|
|
- };
|
|
|
-
|
|
|
- // operation hash
|
|
|
- var operations = {
|
|
|
- // $and and $or are separate
|
|
|
- $eq: $eq
|
|
|
- ,$ne: $ne
|
|
|
- ,$in: $in
|
|
|
- ,$nin: $nin
|
|
|
- ,$at: $at
|
|
|
- };
|
|
|
-
|
|
|
- /** related to: Physics.world#find
|
|
|
- * Physics.query( rules ) -> Function
|
|
|
- * - rules (Object): The mongodb-like search rules. (See description).
|
|
|
- * + (Function): The test function
|
|
|
- *
|
|
|
- * Creates a function that can be used to perform tests on objects.
|
|
|
- *
|
|
|
- * The test function will return a [[Boolean]]; `true` if the object matches the tests.
|
|
|
- *
|
|
|
- * Query rules are mongodb-like. You can specify a hash of values to match like this:
|
|
|
- *
|
|
|
- * ```javascript
|
|
|
- * {
|
|
|
- * foo: 'bar',
|
|
|
- * baz: 2,
|
|
|
- * some: {
|
|
|
- * nested: 'value'
|
|
|
- * }
|
|
|
- * }
|
|
|
- * ```
|
|
|
- *
|
|
|
- * And they will all need to match (it's an AND rule).
|
|
|
- *
|
|
|
- * You can also use operators for more versatility. The operators you can use include:
|
|
|
- *
|
|
|
- * - $eq: Test if some property is equal to a value (this is done by default, and is thus redundant)
|
|
|
- * - $ne: Test if some property is _NOT_ equal to a value
|
|
|
- * - $in: Test if some value (or array of values) is one of the specified array of values
|
|
|
- * - $nin: Test if some value (or array of values) is _NOT_ one of the specified array of values
|
|
|
- * - $at: Test if a body's [[Physics.aabb]] includes specified point. It's a primative hit-test.
|
|
|
- *
|
|
|
- * Example:
|
|
|
- *
|
|
|
- * ```javascript
|
|
|
- * var wheelsArray = [];
|
|
|
- *
|
|
|
- * var queryFn = Physics.query({
|
|
|
- * name: 'circle', // only circles
|
|
|
- * $nin: wheelsArray, // not in the wheelsArray
|
|
|
- * labels: { $in: [ 'player', 'monster' ] } // that have player OR monster labels
|
|
|
- * });
|
|
|
- *
|
|
|
- * var obj = {
|
|
|
- * name: 'circle',
|
|
|
- * labels: [ 'round' ]
|
|
|
- * };
|
|
|
- *
|
|
|
- * queryFn( obj ); // -> false
|
|
|
- * // give it a player tag
|
|
|
- * obj.labels.push('player');
|
|
|
- * queryFn( obj ); // -> true
|
|
|
- * // put it inside the wheelsArray
|
|
|
- * wheelsArray.push( obj );
|
|
|
- * queryFn( obj ); // -> false
|
|
|
- * ```
|
|
|
- **/
|
|
|
- var Query = function Query( rules, /* internal use */ $op ){
|
|
|
-
|
|
|
- var op
|
|
|
- ,l
|
|
|
- ,rule
|
|
|
- ,first
|
|
|
- ,list
|
|
|
- ,fn
|
|
|
- ;
|
|
|
-
|
|
|
- if ( $op ){
|
|
|
-
|
|
|
- // parse operation choice
|
|
|
- if ( $op === '$or' || $op === '$and' ){
|
|
|
-
|
|
|
- // expect a rules array
|
|
|
- for ( op = 0, l = rules.length; op < l; ++op ){
|
|
|
-
|
|
|
- fn = Query( rules[ op ] );
|
|
|
- // if first hasn't been set yet, set it and start the list there
|
|
|
- // otherwise set the next node of the list
|
|
|
- list = list ? list.next = fn : first = fn;
|
|
|
- }
|
|
|
-
|
|
|
- return ($op === '$or') ? $or( first ) : $and( first );
|
|
|
- } else if ( op = operations[ $op ] ){
|
|
|
-
|
|
|
- return op( rules );
|
|
|
-
|
|
|
- } else {
|
|
|
- // does not compute...
|
|
|
- throw 'Unknown query operation: ' + $op;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // loop through rules
|
|
|
- for ( op in rules ){
|
|
|
- rule = rules[ op ];
|
|
|
-
|
|
|
- if ( op[0] === '$' ){
|
|
|
- // it's an operation rule
|
|
|
- fn = Query( rule, op );
|
|
|
-
|
|
|
- } else if ( Physics.util.isPlainObject( rule ) ) {
|
|
|
- // it's an object so parse subrules
|
|
|
- fn = wrapRule( Query( rule ), op );
|
|
|
- } else {
|
|
|
- // simple equality rule
|
|
|
- fn = $eq( rule, op );
|
|
|
- }
|
|
|
-
|
|
|
- // if first hasn't been set yet, set it and start the list there
|
|
|
- // otherwise set the next node of the list
|
|
|
- list = list ? list.next = fn : first = fn;
|
|
|
- }
|
|
|
-
|
|
|
- // return the rules test
|
|
|
- return $and( first || fnTrue );
|
|
|
- };
|
|
|
-
|
|
|
- Physics.query = Query;
|
|
|
-
|
|
|
-})(this);
|
|
|
-
|
|
|
-
|
|
|
-// ---
|
|
|
-// inside: src/core/behavior.js
|
|
|
-
|
|
|
-(function(){
|
|
|
-
|
|
|
- var defaults = {
|
|
|
- priority: 0
|
|
|
- };
|
|
|
-
|
|
|
- /** related to: Physics.util.decorator
|
|
|
- * Physics.behavior( name[, options] ) -> Behavior
|
|
|
- * - name (String): The name of the behavior to create
|
|
|
- * - options (Object): The configuration for that behavior ( depends on behavior ).
|
|
|
- Available options and defaults:
|
|
|
-
|
|
|
- ```javascript
|
|
|
- {
|
|
|
- priority: 0 // the priority of this body
|
|
|
- }
|
|
|
- ```
|
|
|
- *
|
|
|
- * Factory function for creating Behaviors.
|
|
|
- *
|
|
|
- * Visit [the PhysicsJS wiki on Behaviors](https://github.com/wellcaffeinated/PhysicsJS/wiki/Behaviors)
|
|
|
- * for usage documentation.
|
|
|
- **/
|
|
|
- Physics.behavior = Decorator('behavior', {
|
|
|
-
|
|
|
- /** belongs to: Physics.behavior
|
|
|
- * class Behavior
|
|
|
- *
|
|
|
- * The base class for behaviors created by [[Physics.behavior]] factory function.
|
|
|
- **/
|
|
|
-
|
|
|
- /** internal
|
|
|
- * Behavior#init( options )
|
|
|
- * - options (Object): The configuration options passed by the factory
|
|
|
- *
|
|
|
- * Initialization. Internal use.
|
|
|
- **/
|
|
|
- init: function( options ){
|
|
|
-
|
|
|
- /** related to: Physics.util.options
|
|
|
- * Behavior#options( options ) -> Object
|
|
|
- * - options (Object): The options to set as an object
|
|
|
- * + (Object): The options
|
|
|
- *
|
|
|
- * 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 );
|
|
|
- },
|
|
|
-
|
|
|
- /**
|
|
|
- * Behavior#applyTo( arr ) -> this
|
|
|
- * - arr (Array): Array of bodies to apply this behavior to. Specify `true` for all objects in world.
|
|
|
- *
|
|
|
- * Apply the behavior to a group of bodies.
|
|
|
- **/
|
|
|
- applyTo: function( arr ){
|
|
|
-
|
|
|
- if ( arr === true ){
|
|
|
- this._targets = null;
|
|
|
- } else {
|
|
|
- this._targets = Physics.util.uniq( arr );
|
|
|
- }
|
|
|
- return this;
|
|
|
- },
|
|
|
-
|
|
|
- /**
|
|
|
- * Behavior#getTargets() -> Array
|
|
|
- * + (Array): The array of bodies (by reference!) this behavior is applied to.
|
|
|
- *
|
|
|
- * Get the array of bodies (by reference!) this behavior is applied to.
|
|
|
- **/
|
|
|
- getTargets: function(){
|
|
|
-
|
|
|
- return this._targets || ( this._world ? this._world._bodies : [] );
|
|
|
- },
|
|
|
-
|
|
|
- /**
|
|
|
- * Behavior#setWorld( world ) -> this
|
|
|
- * - world (Object): The world (or null)
|
|
|
- *
|
|
|
- * Set which world to apply to.
|
|
|
- *
|
|
|
- * Usually this is called internally. Shouldn't be a need to call this yourself usually.
|
|
|
- **/
|
|
|
- setWorld: function( world ){
|
|
|
-
|
|
|
- if ( this.disconnect && this._world ){
|
|
|
- this.disconnect( this._world );
|
|
|
- }
|
|
|
-
|
|
|
- this._world = world;
|
|
|
-
|
|
|
- if ( this.connect && world ){
|
|
|
- this.connect( world );
|
|
|
- }
|
|
|
-
|
|
|
- return this;
|
|
|
- },
|
|
|
-
|
|
|
- /**
|
|
|
- * Behavior#connect( world )
|
|
|
- * - world (Physics.world): The world to connect to
|
|
|
- *
|
|
|
- * Connect to a world.
|
|
|
- *
|
|
|
- * Extend this when creating behaviors if you need to specify pubsub management.
|
|
|
- * Automatically called when added to world by the [[Behavior#setWorld]] method.
|
|
|
- **/
|
|
|
- connect: function( world ){
|
|
|
-
|
|
|
- if (this.behave){
|
|
|
- world.on('integrate:positions', this.behave, this, this.options.priority);
|
|
|
- }
|
|
|
- },
|
|
|
-
|
|
|
- /**
|
|
|
- * Behavior#disconnect( world )
|
|
|
- * - world (Physics.world): The world to disconnect from
|
|
|
- *
|
|
|
- * Disconnect from a world.
|
|
|
- *
|
|
|
- * Extend this when creating behaviors if you need to specify pubsub management.
|
|
|
- * Automatically called when added to world by the [[Behavior#setWorld]] method.
|
|
|
- **/
|
|
|
- disconnect: function( world ){
|
|
|
-
|
|
|
- if (this.behave){
|
|
|
- world.off('integrate:positions', this.behave, this);
|
|
|
- }
|
|
|
- },
|
|
|
-
|
|
|
- /**
|
|
|
- * Behavior#behave( data )
|
|
|
- * - data (Object): The pubsub `integrate:positions` event data
|
|
|
- *
|
|
|
- * Default method run on every world integration.
|
|
|
- *
|
|
|
- * You _must_ extend this when creating a behavior,
|
|
|
- * unless you extend the [[Behavior#connect]] and [[Behavior#disconnect]] methods.
|
|
|
- **/
|
|
|
- behave: null
|
|
|
- });
|
|
|
-
|
|
|
-}());
|
|
|
-
|
|
|
-// ---
|
|
|
-// inside: src/core/body.js
|
|
|
-
|
|
|
-(function(){
|
|
|
-
|
|
|
- var defaults = {
|
|
|
-
|
|
|
- // is the body hidden (not to be rendered)?
|
|
|
- hidden: false,
|
|
|
- // is the body `dynamic`, `kinematic` or `static`?
|
|
|
- // http://www.box2d.org/manual.html#_Toc258082973
|
|
|
- treatment: 'dynamic',
|
|
|
- // body mass
|
|
|
- mass: 1.0,
|
|
|
- // body restitution. How "bouncy" is it?
|
|
|
- restitution: 1.0,
|
|
|
- // 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
|
|
|
- };
|
|
|
-
|
|
|
- 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
|
|
|
- * - options (Object): The configuration for that body ( depends on body ).
|
|
|
- Available options and defaults:
|
|
|
-
|
|
|
- ```javascript
|
|
|
- {
|
|
|
- // is the body hidden (not to be rendered)?
|
|
|
- hidden: false,
|
|
|
- // is the body `dynamic`, `kinematic` or `static`?
|
|
|
- // http://www.box2d.org/manual.html#_Toc258082973
|
|
|
- treatment: 'dynamic',
|
|
|
- // body mass
|
|
|
- mass: 1.0,
|
|
|
- // body restitution. How "bouncy" is it?
|
|
|
- restitution: 1.0,
|
|
|
- // 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,
|
|
|
- // the vector offsetting the geometry from its center of mass
|
|
|
- offset: Physics.vector(0,0)
|
|
|
- }
|
|
|
- ```
|
|
|
- *
|
|
|
- * Factory function for creating Bodies.
|
|
|
- *
|
|
|
- * Visit [the PhysicsJS wiki on Bodies](https://github.com/wellcaffeinated/PhysicsJS/wiki/Bodies)
|
|
|
- * for usage documentation.
|
|
|
- **/
|
|
|
- Physics.body = Decorator('body', {
|
|
|
-
|
|
|
- /** belongs to: Physics.body
|
|
|
- * class Body
|
|
|
- *
|
|
|
- * The base class for bodies created by [[Physics.body]] factory function.
|
|
|
- **/
|
|
|
-
|
|
|
- /** internal
|
|
|
- * Body#init( options )
|
|
|
- * - options (Object): The configuration options passed by the factory
|
|
|
- *
|
|
|
- * Initialization. Internal use.
|
|
|
- **/
|
|
|
- init: function( options ){
|
|
|
-
|
|
|
- var self = this;
|
|
|
- var vector = Physics.vector;
|
|
|
-
|
|
|
- /** related to: Physics.util.options
|
|
|
- * Body#options( options ) -> Object
|
|
|
- * - options (Object): The options to set as an object
|
|
|
- * + (Object): The options
|
|
|
- *
|
|
|
- * Set options on this instance.
|
|
|
- *
|
|
|
- * Access options directly from the options object.
|
|
|
- *
|
|
|
- * Example:
|
|
|
- *
|
|
|
- * ```javascript
|
|
|
- * this.options.someOption;
|
|
|
- * ```
|
|
|
- *
|
|
|
- **/
|
|
|
- // 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 );
|
|
|
-
|
|
|
- /**
|
|
|
- * Body#state
|
|
|
- *
|
|
|
- * The physical state container.
|
|
|
- *
|
|
|
- * - ``this.state.pos`` ([[Physics.vector]]) The position vector.
|
|
|
- * - ``this.state.vel`` ([[Physics.vector]]) The velocity vector.
|
|
|
- * - ``this.state.acc`` ([[Physics.vector]]) The acceleration vector.
|
|
|
- * - ``this.state.angular.pos`` ([[Number]]) The angular position (in radians, positive is clockwise starting along the x axis)
|
|
|
- * - ``this.state.angular.vel`` ([[Number]]) The angular velocity
|
|
|
- * - ``this.state.angular.acc`` ([[Number]]) The angular acceleration
|
|
|
- *
|
|
|
- * Properties from the previous timestep are stored in:
|
|
|
- * ```javascript
|
|
|
- * this.state.old; // .pos, .vel, ...
|
|
|
- * ```
|
|
|
- **/
|
|
|
- this.state = {
|
|
|
- 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: new vector(),
|
|
|
- vel: new vector(),
|
|
|
- acc: new vector(),
|
|
|
- angular: {
|
|
|
- pos: 0.0,
|
|
|
- vel: 0.0,
|
|
|
- acc: 0.0
|
|
|
- }
|
|
|
- }
|
|
|
- };
|
|
|
-
|
|
|
- // 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;
|
|
|
- delete this.vx;
|
|
|
- delete this.vy;
|
|
|
- delete this.angle;
|
|
|
- delete this.angularVelocity;
|
|
|
-
|
|
|
- if (this.mass === 0){
|
|
|
- throw "Error: Bodies must have non-zero mass";
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Body#uid = Number
|
|
|
- *
|
|
|
- * The unique id for the body
|
|
|
- **/
|
|
|
- this.uid = uidGen++;
|
|
|
-
|
|
|
- /** related to: Physics.geometry
|
|
|
- * Body#geometry
|
|
|
- *
|
|
|
- * The geometry for this body.
|
|
|
- *
|
|
|
- * By default it is a `point` geometry which gets overridden.
|
|
|
- **/
|
|
|
- this.geometry = Physics.geometry('point');
|
|
|
-
|
|
|
- /**
|
|
|
- * Body#mass = 1.0
|
|
|
- *
|
|
|
- * The mass.
|
|
|
- **/
|
|
|
-
|
|
|
- /**
|
|
|
- * Body#offset
|
|
|
- *
|
|
|
- * The vector offsetting the body's shape from its center of mass.
|
|
|
- **/
|
|
|
-
|
|
|
- /**
|
|
|
- * Body#restitution = 1.0
|
|
|
- *
|
|
|
- * The restitution.
|
|
|
- *
|
|
|
- * This is the "bounciness" of the body.
|
|
|
- * It's a number between `0` and `1`.
|
|
|
- *
|
|
|
- * A restitution of 1 is the bounciest.
|
|
|
- *
|
|
|
- * A restitution of 0 is not bouncy.
|
|
|
- *
|
|
|
- * When colliding the restitutions of bodies are
|
|
|
- * multiplied together to get the restitution between two
|
|
|
- * bodies.
|
|
|
- *
|
|
|
- **/
|
|
|
-
|
|
|
- /**
|
|
|
- * Body#cof = 0.8
|
|
|
- *
|
|
|
- * The coefficient of friction of the body.
|
|
|
- *
|
|
|
- * It's how much "slide" it has during collisions.
|
|
|
- *
|
|
|
- * A `cof` of `0` will really slidy.
|
|
|
- *
|
|
|
- * A `cof` of `1` has no slide.
|
|
|
- *
|
|
|
- * This is a very simplistic implementation at the moment.
|
|
|
- * What would be better is to have both static and kinetic
|
|
|
- * friction. But that's not done yet.
|
|
|
- **/
|
|
|
-
|
|
|
- /**
|
|
|
- * Body#treatment = String
|
|
|
- *
|
|
|
- * How the body is treated by the simulation.
|
|
|
- *
|
|
|
- * The body can be `dynamic`, `kinematic` or `static` as
|
|
|
- * described by the [analogous box2d docs](http://www.box2d.org/manual.html#_Toc258082973).
|
|
|
- *
|
|
|
- * * _dynamic_ bodies are treated "normally". They are integrated, and collide, and all that.
|
|
|
- * * _kinematic_ bodies are bodies that move at a specified velocity. Other bodies collide with them, but they don't bounce off of other bodies.
|
|
|
- * * _static_ bodies just stand still. They are like obstacles.
|
|
|
- **/
|
|
|
-
|
|
|
- /**
|
|
|
- * Body#hidden = false
|
|
|
- *
|
|
|
- * Determines whether the body should be hidden by the renderer.
|
|
|
- **/
|
|
|
-
|
|
|
- /** related to: Physics.renderer
|
|
|
- * Body#view = it_depends
|
|
|
- *
|
|
|
- * Storage for use by the renderer.
|
|
|
- *
|
|
|
- * The type of renderer will put different things in the view property.
|
|
|
- * Basically, this is how the body "looks". It could be a HTMLElement, or
|
|
|
- * an Image, etc...
|
|
|
- *
|
|
|
- * If your body changes appearance (shape), you should modify this somehow
|
|
|
- * otherwise the renderer will keep using this same view. If you're letting
|
|
|
- * the renderer create the view for you, just set this to `undefined` if the
|
|
|
- * body gets modified in shape during the simulation.
|
|
|
- **/
|
|
|
-
|
|
|
- /** related to: Physics.renderer
|
|
|
- * Body#styles
|
|
|
- *
|
|
|
- * The styles the renderer should use for creating the view.
|
|
|
- *
|
|
|
- * The styles depend on the renderer. See [[Renderer#createView]] for style options.
|
|
|
- **/
|
|
|
- },
|
|
|
-
|
|
|
- /**
|
|
|
- * 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)
|
|
|
- *
|
|
|
- * Set which world to apply to.
|
|
|
- *
|
|
|
- * Usually this is called internally. Shouldn't be a need to call this yourself usually.
|
|
|
- **/
|
|
|
- setWorld: function( world ){
|
|
|
-
|
|
|
- if ( this.disconnect && this._world ){
|
|
|
- this.disconnect( this._world );
|
|
|
- }
|
|
|
-
|
|
|
- this._world = world;
|
|
|
-
|
|
|
- if ( this.connect && world ){
|
|
|
- this.connect( world );
|
|
|
- }
|
|
|
-
|
|
|
- return this;
|
|
|
- },
|
|
|
-
|
|
|
- /**
|
|
|
- * Body#accelerate( acc ) -> this
|
|
|
- * - acc (Physics.vector): The acceleration vector
|
|
|
- *
|
|
|
- * Accelerate the body by adding supplied vector to its current acceleration
|
|
|
- **/
|
|
|
- accelerate: function( acc ){
|
|
|
-
|
|
|
- if ( this.treatment === 'dynamic' ){
|
|
|
- this.state.acc.vadd( acc );
|
|
|
- }
|
|
|
-
|
|
|
- return this;
|
|
|
- },
|
|
|
-
|
|
|
- /**
|
|
|
- * Body#applyForce( force[, p] ) -> this
|
|
|
- * - force (Vectorish): The force vector
|
|
|
- * - p (Vectorish): The point vector from the COM at which to apply the force
|
|
|
- *
|
|
|
- * Apply a force at center of mass, or at point `p` relative to the center of mass
|
|
|
- **/
|
|
|
- applyForce: function( force, p ){
|
|
|
-
|
|
|
- if ( this.treatment !== 'dynamic' ){
|
|
|
- return this;
|
|
|
- }
|
|
|
-
|
|
|
- var scratch = Physics.scratchpad()
|
|
|
- ,r = scratch.vector()
|
|
|
- ,state
|
|
|
- ;
|
|
|
-
|
|
|
- // 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 );
|
|
|
- // r cross F
|
|
|
- this.state.angular.acc -= r.cross( force ) / this.moi;
|
|
|
- }
|
|
|
-
|
|
|
- this.accelerate( r.clone( force ).mult( 1/this.mass ) );
|
|
|
-
|
|
|
- scratch.done();
|
|
|
- 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
|
|
|
- *
|
|
|
- * 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 )
|
|
|
- ;
|
|
|
-
|
|
|
- this.getGlobalOffset( v );
|
|
|
-
|
|
|
- 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 );
|
|
|
- },
|
|
|
-
|
|
|
- /**
|
|
|
- * Body#recalc() -> this
|
|
|
- *
|
|
|
- * Recalculate properties.
|
|
|
- *
|
|
|
- * Intended to be overridden by subclasses. Call when body physical properties are changed.
|
|
|
- **/
|
|
|
- recalc: function(){
|
|
|
- // override to recalculate properties
|
|
|
- return this;
|
|
|
- }
|
|
|
- });
|
|
|
-
|
|
|
- /**
|
|
|
- * 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;
|
|
|
- };
|
|
|
-
|
|
|
-}());
|
|
|
-
|
|
|
-
|
|
|
-// ---
|
|
|
-// inside: src/core/geometry.js
|
|
|
-
|
|
|
-(function(){
|
|
|
- /** related to: Physics.util.decorator
|
|
|
- * Physics.geometry( name[, options] ) -> Geometry
|
|
|
- * - name (String): The name of the geometry to create
|
|
|
- * - options (Object): The configuration for that geometry ( depends on geometry ).
|
|
|
- *
|
|
|
- * Factory function for creating Geometries.
|
|
|
- *
|
|
|
- * Visit [the PhysicsJS wiki on Geometries](https://github.com/wellcaffeinated/PhysicsJS/wiki/Geometries)
|
|
|
- * for usage documentation.
|
|
|
- **/
|
|
|
- Physics.geometry = Decorator('geometry', {
|
|
|
-
|
|
|
- /** belongs to: Physics.geometry
|
|
|
- * class Geometry
|
|
|
- *
|
|
|
- * The base class for geometries created by [[Physics.geometry]] factory function.
|
|
|
- **/
|
|
|
-
|
|
|
- /** internal
|
|
|
- * Geometry#init( options )
|
|
|
- * - options (Object): The configuration options passed by the factory
|
|
|
- *
|
|
|
- * Initialization. Internal use.
|
|
|
- **/
|
|
|
- init: function( options ){
|
|
|
-
|
|
|
- /** related to: Physics.util.options
|
|
|
- * Geometry#options( options ) -> Object
|
|
|
- * - options (Object): The options to set as an object
|
|
|
- * + (Object): The options
|
|
|
- *
|
|
|
- * Set options on this instance.
|
|
|
- *
|
|
|
- * Access options directly from the options object.
|
|
|
- *
|
|
|
- * Example:
|
|
|
- *
|
|
|
- * ```javascript
|
|
|
- * this.options.someOption;
|
|
|
- * ```
|
|
|
- *
|
|
|
- **/
|
|
|
- this.options = Physics.util.options();
|
|
|
- this.options( options );
|
|
|
-
|
|
|
- this._aabb = new Physics.aabb();
|
|
|
- },
|
|
|
-
|
|
|
- /** related to: Physics.aabb
|
|
|
- * Geometry#aabb( angle ) -> Object
|
|
|
- * - angle (Number): The angle to rotate the geometry
|
|
|
- * + (Object): Bounding box values
|
|
|
- *
|
|
|
- * Get axis-aligned bounding box for this object (rotated by angle if specified).
|
|
|
- **/
|
|
|
- aabb: function( angle ){
|
|
|
-
|
|
|
- return Physics.aabb.clone(this._aabb);
|
|
|
- },
|
|
|
-
|
|
|
- /**
|
|
|
- * Geometry#getFarthestHullPoint( dir[, result] ) -> Physics.vector
|
|
|
- * - dir (Physics.vector): Direction to look
|
|
|
- * - result (Physics.vector): A vector to write result to. Speeds up calculations.
|
|
|
- * + (Physics.vector): The farthest hull point in local coordinates
|
|
|
- *
|
|
|
- * Get farthest point on the hull of this geometry
|
|
|
- * along the direction vector `dir`
|
|
|
- * returns local coordinates. Replaces result if provided.
|
|
|
- *
|
|
|
- * Assume all coordinates are relative to the geometry
|
|
|
- * centroid (IE: in the body frame).
|
|
|
- *
|
|
|
- * This should take a direction vector then it should
|
|
|
- * calculate the location (in that frame of reference)
|
|
|
- * of the point on the perimeter (hull) if you traveled
|
|
|
- * in a straight line from the centroid in the provided
|
|
|
- * direction. The result should be returned/set just like
|
|
|
- * it is in the other geometries.
|
|
|
- **/
|
|
|
- getFarthestHullPoint: function( dir, result ){
|
|
|
-
|
|
|
- result = result || new Physics.vector();
|
|
|
-
|
|
|
- // not implemented.
|
|
|
- return result.set( 0, 0 );
|
|
|
- },
|
|
|
-
|
|
|
- /** related to: Geometry#getFarthestHullPoint
|
|
|
- * Geometry#getFarthestCorePoint( dir[, result] ) -> Physics.vector
|
|
|
- * - dir (Physics.vector): Direction to look
|
|
|
- * - result (Physics.vector): A vector to write result to. Speeds up calculations.
|
|
|
- * + (Physics.vector): The farthest hull point in local coordinates
|
|
|
- *
|
|
|
- * Get farthest point on the core shape of this geometry
|
|
|
- * along the direction vector `dir`
|
|
|
- * returns local coordinates. Replaces result if provided.
|
|
|
- *
|
|
|
- * This does almost the same thing as [[Geometry#getFarthestHullPoint]]
|
|
|
- * but shrinks the shape by subtracting "margin" from it.
|
|
|
- * Return the position of the point on the "core" shape.
|
|
|
- **/
|
|
|
- getFarthestCorePoint: function( dir, result, margin ){
|
|
|
-
|
|
|
- result = result || new Physics.vector();
|
|
|
-
|
|
|
- // not implemented.
|
|
|
- return result.set( 0, 0 );
|
|
|
- }
|
|
|
- });
|
|
|
-
|
|
|
-}());
|
|
|
-
|
|
|
-// ---
|
|
|
-// inside: src/core/geometry-helpers.js
|
|
|
-
|
|
|
-/*
|
|
|
- * 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 ){
|
|
|
-
|
|
|
- var scratch = Physics.scratchpad()
|
|
|
- ,prev = scratch.vector()
|
|
|
- ,next = scratch.vector()
|
|
|
- ,tmp = scratch.vector()
|
|
|
- ,ret = true
|
|
|
- ,sign = false
|
|
|
- ,l = hull.length
|
|
|
- ;
|
|
|
-
|
|
|
- if ( !hull || !l ){
|
|
|
- return false;
|
|
|
- }
|
|
|
-
|
|
|
- if ( l < 3 ){
|
|
|
- // it must be a point or a line...
|
|
|
- // which are convex
|
|
|
- scratch.done();
|
|
|
- return ret;
|
|
|
- }
|
|
|
-
|
|
|
- prev.clone( hull[ 0 ] ).vsub( tmp.clone( hull[ l - 1 ] ) );
|
|
|
-
|
|
|
- // loop over the edges of the hull and construct vectors of the current
|
|
|
- // 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 ){
|
|
|
-
|
|
|
- // first check the sign of the first cross product
|
|
|
- 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;
|
|
|
- }
|
|
|
-
|
|
|
- // remember the last edge
|
|
|
- next.swap( prev );
|
|
|
- }
|
|
|
-
|
|
|
- scratch.done();
|
|
|
- return ret;
|
|
|
-};
|
|
|
-
|
|
|
-/**
|
|
|
- * 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:
|
|
|
- * * mass is unitary (== 1)
|
|
|
- * * axis of rotation is the origin
|
|
|
- **/
|
|
|
-Physics.geometry.getPolygonMOI = function( hull ){
|
|
|
-
|
|
|
- var scratch = Physics.scratchpad()
|
|
|
- ,prev = scratch.vector()
|
|
|
- ,next = scratch.vector()
|
|
|
- ,num = 0
|
|
|
- ,denom = 0
|
|
|
- ,tmp
|
|
|
- ,l = hull.length
|
|
|
- ;
|
|
|
-
|
|
|
- if ( l < 2 ){
|
|
|
- // it must be a point
|
|
|
- // moi = 0
|
|
|
- scratch.done();
|
|
|
- return 0;
|
|
|
- }
|
|
|
-
|
|
|
- if ( l === 2 ){
|
|
|
- // it's a line
|
|
|
- // get length squared
|
|
|
- tmp = next.clone( hull[ 1 ] ).distSq( prev.clone( hull[ 0 ] ) );
|
|
|
- scratch.done();
|
|
|
- return tmp / 12;
|
|
|
- }
|
|
|
-
|
|
|
- prev.clone( hull[ 0 ] );
|
|
|
-
|
|
|
- for ( var i = 1; i < l; ++i ){
|
|
|
-
|
|
|
- next.clone( hull[ i ] );
|
|
|
-
|
|
|
- tmp = Math.abs( next.cross( prev ) );
|
|
|
- num += tmp * ( next.normSq() + next.dot( prev ) + prev.normSq() );
|
|
|
- denom += tmp;
|
|
|
-
|
|
|
- prev.swap( next );
|
|
|
- }
|
|
|
-
|
|
|
- scratch.done();
|
|
|
- return num / ( 6 * denom );
|
|
|
-};
|
|
|
-
|
|
|
-/**
|
|
|
- * Physics.geometry.isPointInPolygon( pt, hull ) -> Boolean
|
|
|
- * - 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 ){
|
|
|
-
|
|
|
- var scratch = Physics.scratchpad()
|
|
|
- ,point = scratch.vector().clone( pt )
|
|
|
- ,prev = scratch.vector()
|
|
|
- ,next = scratch.vector()
|
|
|
- ,ang = 0
|
|
|
- ,l = hull.length
|
|
|
- ;
|
|
|
-
|
|
|
- if ( l < 2 ){
|
|
|
- // it's a point...
|
|
|
- ang = point.equals( prev.clone( hull[ 0 ] ));
|
|
|
- scratch.done();
|
|
|
- return ang;
|
|
|
- }
|
|
|
-
|
|
|
- if ( l === 2 ){
|
|
|
- // it's a line
|
|
|
- ang = point.angle( prev.clone( hull[ 0 ] ));
|
|
|
- ang += point.angle( prev.clone( hull[ 1 ] ));
|
|
|
- scratch.done();
|
|
|
- return ( Math.abs(ang) === Math.PI );
|
|
|
- }
|
|
|
-
|
|
|
- prev.clone( hull[ 0 ] ).vsub( point );
|
|
|
-
|
|
|
- // 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 );
|
|
|
- }
|
|
|
-
|
|
|
- scratch.done();
|
|
|
- return ( Math.abs(ang) > 1e-6 );
|
|
|
-};
|
|
|
-
|
|
|
-/**
|
|
|
- * 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 ){
|
|
|
-
|
|
|
- var scratch = Physics.scratchpad()
|
|
|
- ,prev = scratch.vector()
|
|
|
- ,next = scratch.vector()
|
|
|
- ,ret = 0
|
|
|
- ,l = hull.length
|
|
|
- ;
|
|
|
-
|
|
|
- if ( l < 3 ){
|
|
|
- // it must be a point or a line
|
|
|
- // area = 0
|
|
|
- scratch.done();
|
|
|
- return 0;
|
|
|
- }
|
|
|
-
|
|
|
- prev.clone( hull[ l - 1 ] );
|
|
|
-
|
|
|
- for ( var i = 0; i < l; ++i ){
|
|
|
-
|
|
|
- next.clone( hull[ i ] );
|
|
|
-
|
|
|
- ret += prev.cross( next );
|
|
|
-
|
|
|
- prev.swap( next );
|
|
|
- }
|
|
|
-
|
|
|
- scratch.done();
|
|
|
- return ret / 2;
|
|
|
-};
|
|
|
-
|
|
|
-/**
|
|
|
- * 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 ){
|
|
|
-
|
|
|
- var scratch = Physics.scratchpad()
|
|
|
- ,prev = scratch.vector()
|
|
|
- ,next = scratch.vector()
|
|
|
- ,ret = new Physics.vector()
|
|
|
- ,tmp
|
|
|
- ,l = hull.length
|
|
|
- ;
|
|
|
-
|
|
|
- if ( l < 2 ){
|
|
|
- // it must be a point
|
|
|
- scratch.done();
|
|
|
- return new Physics.vector( hull[0] );
|
|
|
- }
|
|
|
-
|
|
|
- if ( l === 2 ){
|
|
|
- // it's a line
|
|
|
- // get the midpoint
|
|
|
- scratch.done();
|
|
|
- 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 );
|
|
|
- prev.vadd( next ).mult( tmp );
|
|
|
- ret.vadd( prev );
|
|
|
-
|
|
|
- prev.swap( next );
|
|
|
- }
|
|
|
-
|
|
|
- tmp = 1 / (6 * Physics.geometry.getPolygonArea( hull ));
|
|
|
-
|
|
|
- scratch.done();
|
|
|
- return ret.mult( tmp );
|
|
|
-};
|
|
|
-
|
|
|
-/**
|
|
|
- * Physics.geometry.nearestPointOnLine( pt, linePt1, linePt2 ) -> Physics.vector
|
|
|
- * - pt (Vectorish): The point
|
|
|
- * - 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 ){
|
|
|
-
|
|
|
- var scratch = Physics.scratchpad()
|
|
|
- ,p = scratch.vector().clone( pt )
|
|
|
- ,A = scratch.vector().clone( linePt1 ).vsub( p )
|
|
|
- ,L = scratch.vector().clone( linePt2 ).vsub( p ).vsub( A )
|
|
|
- ,lambdaB
|
|
|
- ,lambdaA
|
|
|
- ;
|
|
|
-
|
|
|
- if ( L.equals(Physics.vector.zero) ){
|
|
|
- // oh.. it's a zero vector. So A and B are both the closest.
|
|
|
- // just use one of them
|
|
|
- scratch.done();
|
|
|
- return new Physics.vector( linePt1 );
|
|
|
- }
|
|
|
-
|
|
|
- lambdaB = - L.dot( A ) / L.normSq();
|
|
|
- lambdaA = 1 - lambdaB;
|
|
|
-
|
|
|
- if ( lambdaA <= 0 ){
|
|
|
- // woops.. that means the closest simplex point
|
|
|
- // isn't on the line it's point B itself
|
|
|
- scratch.done();
|
|
|
- return new Physics.vector( linePt2 );
|
|
|
- } else if ( lambdaB <= 0 ){
|
|
|
- // vice versa
|
|
|
- scratch.done();
|
|
|
- return new Physics.vector( linePt1 );
|
|
|
- }
|
|
|
-
|
|
|
- // guess we'd better do the math now...
|
|
|
- p = new Physics.vector( linePt2 ).mult( lambdaB ).vadd( A.clone( linePt1 ).mult( lambdaA ) );
|
|
|
- scratch.done();
|
|
|
- return p;
|
|
|
-};
|
|
|
-
|
|
|
-
|
|
|
-// ---
|
|
|
-// inside: src/core/integrator.js
|
|
|
-
|
|
|
-(function(){
|
|
|
-
|
|
|
- var defaults = {
|
|
|
-
|
|
|
- // drag applied during integration
|
|
|
- // 0 means vacuum
|
|
|
- // 0.9 means molasses
|
|
|
- drag: 0
|
|
|
- };
|
|
|
-
|
|
|
- /** related to: Physics.util.decorator
|
|
|
- * Physics.integrator( name[, options] ) -> Integrator
|
|
|
- * - 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
|
|
|
- // 0 means vacuum
|
|
|
- // 0.9 means molasses
|
|
|
- drag: 0
|
|
|
- }
|
|
|
- ```
|
|
|
- *
|
|
|
- * Factory function for creating Integrators.
|
|
|
- *
|
|
|
- * Visit [the PhysicsJS wiki on Integrators](https://github.com/wellcaffeinated/PhysicsJS/wiki/Integrators)
|
|
|
- * for usage documentation.
|
|
|
- **/
|
|
|
- Physics.integrator = Decorator('integrator', {
|
|
|
-
|
|
|
- /** belongs to: Physics.integrator
|
|
|
- * class Integrator
|
|
|
- *
|
|
|
- * The base class for integrators created by [[Physics.integrator]] factory function.
|
|
|
- **/
|
|
|
-
|
|
|
- /** 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.
|
|
|
- *
|
|
|
- * Access options directly from the options object.
|
|
|
- *
|
|
|
- * Example:
|
|
|
- *
|
|
|
- * ```javascript
|
|
|
- * this.options.someOption;
|
|
|
- * ```
|
|
|
- *
|
|
|
- **/
|
|
|
- this.options = Physics.util.options( defaults );
|
|
|
- this.options( options );
|
|
|
- },
|
|
|
-
|
|
|
- /**
|
|
|
- * Integrator#setWorld( world ) -> this
|
|
|
- * - world (Object): The world (or null)
|
|
|
- *
|
|
|
- * Set which world to apply to.
|
|
|
- *
|
|
|
- * Usually this is called internally. Shouldn't be a need to call this yourself usually.
|
|
|
- **/
|
|
|
- setWorld: function( world ){
|
|
|
-
|
|
|
- if ( this.disconnect && this._world ){
|
|
|
- this.disconnect( this._world );
|
|
|
- }
|
|
|
-
|
|
|
- this._world = world;
|
|
|
-
|
|
|
- if ( this.connect && world ){
|
|
|
- this.connect( world );
|
|
|
- }
|
|
|
-
|
|
|
- return this;
|
|
|
- },
|
|
|
-
|
|
|
- /**
|
|
|
- * 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.integrateVelocities( bodies, dt );
|
|
|
-
|
|
|
- if ( world ){
|
|
|
- world.emit('integrate:velocities', {
|
|
|
- bodies: bodies,
|
|
|
- dt: dt
|
|
|
- });
|
|
|
- }
|
|
|
-
|
|
|
- this.integratePositions( bodies, dt );
|
|
|
-
|
|
|
- if ( world ){
|
|
|
- world.emit('integrate:positions', {
|
|
|
- bodies: bodies,
|
|
|
- dt: dt
|
|
|
- });
|
|
|
- }
|
|
|
-
|
|
|
- return this;
|
|
|
- },
|
|
|
-
|
|
|
- /**
|
|
|
- * 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.
|
|
|
- * Automatically called when added to world by the [[Integrator#setWorld]] method.
|
|
|
- **/
|
|
|
- connect: null,
|
|
|
-
|
|
|
- /**
|
|
|
- * 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.
|
|
|
- * Automatically called when added to world by the [[Integrator#setWorld]] method.
|
|
|
- **/
|
|
|
- disconnect: null,
|
|
|
-
|
|
|
- /**
|
|
|
- * 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.
|
|
|
- **/
|
|
|
- integrateVelocities: function( bodies, dt ){
|
|
|
-
|
|
|
- throw 'The integrator.integrateVelocities() method must be overriden';
|
|
|
- },
|
|
|
-
|
|
|
- /**
|
|
|
- * 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 ){
|
|
|
-
|
|
|
- throw 'The integrator.integratePositions() method must be overriden';
|
|
|
- }
|
|
|
- });
|
|
|
-
|
|
|
-}());
|
|
|
-
|
|
|
-
|
|
|
-// ---
|
|
|
-// inside: src/core/renderer.js
|
|
|
-
|
|
|
-(function(){
|
|
|
-
|
|
|
- var defaults = {
|
|
|
- // draw meta data (fps, steps, etc)
|
|
|
- meta: false,
|
|
|
- // refresh rate of meta info
|
|
|
- metaRefresh: 200,
|
|
|
-
|
|
|
- // width of viewport
|
|
|
- width: 600,
|
|
|
- // height of viewport
|
|
|
- height: 600,
|
|
|
- // automatically resize the renderer
|
|
|
- autoResize: true
|
|
|
- };
|
|
|
-
|
|
|
- /** related to: Physics.util.decorator
|
|
|
- * Physics.renderer( name[, options] ) -> Renderer
|
|
|
- * - name (String): The name of the renderer to create
|
|
|
- * - options (Object): The configuration for that renderer ( depends on renderer ).
|
|
|
- Available options and defaults:
|
|
|
-
|
|
|
- ```javascript
|
|
|
- {
|
|
|
- // draw meta data (fps, steps, etc)
|
|
|
- meta: false,
|
|
|
- // refresh rate of meta info
|
|
|
- metaRefresh: 200,
|
|
|
-
|
|
|
- // width of viewport
|
|
|
- width: 600,
|
|
|
- // height of viewport
|
|
|
- height: 600
|
|
|
- // automatically resize the renderer
|
|
|
- autoResize: true
|
|
|
- }
|
|
|
- ```
|
|
|
- *
|
|
|
- * Factory function for creating Renderers.
|
|
|
- *
|
|
|
- * Visit [the PhysicsJS wiki on Renderers](https://github.com/wellcaffeinated/PhysicsJS/wiki/Renderers)
|
|
|
- * for usage documentation.
|
|
|
- **/
|
|
|
- Physics.renderer = Decorator('renderer', {
|
|
|
-
|
|
|
- /** belongs to: Physics.renderer
|
|
|
- * class Renderer
|
|
|
- *
|
|
|
- * The base class for renderers created by [[Physics.renderer]] factory function.
|
|
|
- **/
|
|
|
-
|
|
|
- /** internal
|
|
|
- * Renderer#init( options )
|
|
|
- * - options (Object): The configuration options passed by the factory
|
|
|
- *
|
|
|
- * Initialization. Internal use.
|
|
|
- **/
|
|
|
- init: function( options ){
|
|
|
-
|
|
|
- var self = this
|
|
|
- ,el = typeof options.el === 'string' ? document.getElementById(options.el) : options.el
|
|
|
- ;
|
|
|
-
|
|
|
- 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
|
|
|
- },
|
|
|
-
|
|
|
- /**
|
|
|
- * Renderer#setWorld( world ) -> this
|
|
|
- * - world (Object): The world (or null)
|
|
|
- *
|
|
|
- * Set which world to apply to.
|
|
|
- *
|
|
|
- * Usually this is called internally. Shouldn't be a need to call this yourself usually.
|
|
|
- **/
|
|
|
- setWorld: function( world ){
|
|
|
-
|
|
|
- if ( this.disconnect && this._world ){
|
|
|
- this.disconnect( this._world );
|
|
|
- }
|
|
|
-
|
|
|
- this._world = world;
|
|
|
-
|
|
|
- if ( this.connect && world ){
|
|
|
- this.connect( world );
|
|
|
- }
|
|
|
-
|
|
|
- return this;
|
|
|
- },
|
|
|
-
|
|
|
- /**
|
|
|
- * Renderer#render( bodies, meta ) -> this
|
|
|
- * - bodies (Array): Array of bodies in the world (by reference!)
|
|
|
- * - meta (Object): meta information
|
|
|
- *
|
|
|
- * Render the world bodies and meta. Called by world.render()
|
|
|
- **/
|
|
|
- render: function( bodies, meta ){
|
|
|
-
|
|
|
- var body
|
|
|
- ,view
|
|
|
- ,pos
|
|
|
- ;
|
|
|
-
|
|
|
- if (this.beforeRender){
|
|
|
-
|
|
|
- this.beforeRender();
|
|
|
- }
|
|
|
-
|
|
|
- this._world.emit('beforeRender', {
|
|
|
- renderer: this,
|
|
|
- bodies: bodies,
|
|
|
- meta: meta
|
|
|
- });
|
|
|
-
|
|
|
- if (this.options.meta){
|
|
|
- this.drawMeta( meta );
|
|
|
- }
|
|
|
-
|
|
|
- this._interpolateTime = meta.interpolateTime;
|
|
|
-
|
|
|
- for ( var i = 0, l = bodies.length; i < l; ++i ){
|
|
|
-
|
|
|
- body = bodies[ i ];
|
|
|
- view = body.view || ( body.view = this.createView(body.geometry, body.styles) );
|
|
|
-
|
|
|
- if ( !body.hidden ){
|
|
|
- this.drawBody( body, view );
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- return this;
|
|
|
- },
|
|
|
-
|
|
|
- /**
|
|
|
- * Renderer#createView( geometry, styles ) -> Mixed
|
|
|
- * - geometry (Geometry): geometry The geometry
|
|
|
- * - styles (Object|String): The styles configuration
|
|
|
- * + (Mixed): Whatever the renderer needs to render the body.
|
|
|
- *
|
|
|
- * Create a view for the specified geometry.
|
|
|
- *
|
|
|
- * The view is used to render the body. It is a cached version
|
|
|
- * of the body that gets moved and rotated according to the simulation.
|
|
|
- *
|
|
|
- * The styles are used to modify the appearance of the view.
|
|
|
- * They depend on the renderer.
|
|
|
- *
|
|
|
- * Override this when creating renderers.
|
|
|
- **/
|
|
|
- createView: function( geometry, styles ){
|
|
|
-
|
|
|
- // example:
|
|
|
- // var el = document.createElement('div');
|
|
|
- // el.style.height = geometry.height + 'px';
|
|
|
- // el.style.width = geometry.width + 'px';
|
|
|
- // return el;
|
|
|
- throw 'You must override the renderer.createView() method.';
|
|
|
- },
|
|
|
-
|
|
|
- /**
|
|
|
- * Renderer#drawMeta( meta )
|
|
|
- * - meta (Object): The meta data
|
|
|
- *
|
|
|
- * Draw the meta data.
|
|
|
- *
|
|
|
- * The meta data will look like this:
|
|
|
- *
|
|
|
- * ```javascript
|
|
|
- * meta = {
|
|
|
- * fps: 60, // the frames per second
|
|
|
- * ipf: 4 // the number of iterations per frame
|
|
|
- * };
|
|
|
- * ```
|
|
|
- *
|
|
|
- * Override this when creating renderers.
|
|
|
- **/
|
|
|
- drawMeta: function( meta ){
|
|
|
-
|
|
|
- // example:
|
|
|
- // this.els.fps.innerHTML = meta.fps.toFixed(2);
|
|
|
- // this.els.steps.innerHTML = meta.steps;
|
|
|
- throw 'You must override the renderer.drawMeta() method.';
|
|
|
- },
|
|
|
-
|
|
|
- /**
|
|
|
- * Renderer#drawBody( body, view )
|
|
|
- * - body (Object): The body to draw
|
|
|
- * - view (Object): The view for the body
|
|
|
- *
|
|
|
- * Draw specified body using specified view.
|
|
|
- *
|
|
|
- * Override this when creating renderers.
|
|
|
- **/
|
|
|
- drawBody: function( body, view ){
|
|
|
-
|
|
|
- // example (pseudocode):
|
|
|
- // view.angle = body.state.angle
|
|
|
- // view.position = body.state.position
|
|
|
- throw 'You must override the renderer.drawBody() method.';
|
|
|
- }
|
|
|
-
|
|
|
-
|
|
|
- });
|
|
|
-
|
|
|
-}());
|
|
|
-
|
|
|
-
|
|
|
-// ---
|
|
|
-// inside: src/core/world.js
|
|
|
-
|
|
|
-/** related to: Physics
|
|
|
- * class Physics.world
|
|
|
- *
|
|
|
- * The world class and factory function.
|
|
|
- *
|
|
|
- * Use [[Physics]] to create worlds.
|
|
|
- **/
|
|
|
-(function(){
|
|
|
-
|
|
|
- var execCallbacks = function execCallbacks( fns, scope, args ){
|
|
|
-
|
|
|
- var fn
|
|
|
- ,ret
|
|
|
- ,cb = function(){
|
|
|
- return execCallbacks( fns, scope, args );
|
|
|
- }
|
|
|
- ;
|
|
|
-
|
|
|
- while ( fn = fns.shift() ){
|
|
|
-
|
|
|
- ret = fn.apply(scope, args);
|
|
|
-
|
|
|
- if (ret && ret.then){
|
|
|
- return ret.then( cb );
|
|
|
- }
|
|
|
- }
|
|
|
- };
|
|
|
-
|
|
|
- var defaults = {
|
|
|
-
|
|
|
- // default timestep
|
|
|
- timestep: 6,
|
|
|
- // maximum number of iterations per step
|
|
|
- maxIPF: 4,
|
|
|
- webworker: false, // NOT YET IMPLEMENTED
|
|
|
-
|
|
|
- // default integrator
|
|
|
- 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
|
|
|
-
|
|
|
- /** alias of: Physics
|
|
|
- * new Physics.world([options, fn(world, Physics)])
|
|
|
- * - options (Object): configuration options (see description)
|
|
|
- * - fn (Function|Array): Callback function or array of callbacks called with this === world
|
|
|
- * - world (Physics.world): The current world created
|
|
|
- * - Physics (Physics): The Physics namespace
|
|
|
- *
|
|
|
- * World Constructor.
|
|
|
- *
|
|
|
- * Use [[Physics]] to create worlds.
|
|
|
- *
|
|
|
- * Configuration options and defaults:
|
|
|
- *
|
|
|
- * ```javascript
|
|
|
- * {
|
|
|
- * // 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
|
|
|
- * }
|
|
|
- * ```
|
|
|
- *
|
|
|
- * If called with an array of functions, and any functions
|
|
|
- * return a [promise-like object](http://promises-aplus.github.io/promises-spec/),
|
|
|
- * each remaining callback will be called only when that promise is resolved.
|
|
|
- *
|
|
|
- * Example:
|
|
|
- *
|
|
|
- * ```javascript
|
|
|
- * // hypothetical resources need to be loaded...
|
|
|
- * Physics( cfg, [
|
|
|
- * function( world ){
|
|
|
- * var dfd = $.Deferred()
|
|
|
- * ,images = []
|
|
|
- * ,toLoad = myImages.length
|
|
|
- * ,callback = function(){
|
|
|
- * toLoad--;
|
|
|
- * // wait for all images to be loaded
|
|
|
- * if ( toLoad <= 0 ){
|
|
|
- * dfd.resolve();
|
|
|
- * }
|
|
|
- * }
|
|
|
- * ;
|
|
|
- *
|
|
|
- * // load images
|
|
|
- * $.each(myImages, function( src ){
|
|
|
- * var img = new Image();
|
|
|
- * img.onload = callback;
|
|
|
- * img.src = src;
|
|
|
- * });
|
|
|
- *
|
|
|
- * return dfd.promise();
|
|
|
- * },
|
|
|
- * function( world ){
|
|
|
- * // won't be executed until images are loaded
|
|
|
- * // initialize world... etc...
|
|
|
- * }
|
|
|
- * ]);
|
|
|
- * ```
|
|
|
- **/
|
|
|
- var World = function World( cfg, fn ){
|
|
|
-
|
|
|
- // allow creation of world without "new"
|
|
|
- if (!(this instanceof World)){
|
|
|
- return new World( cfg, fn );
|
|
|
- }
|
|
|
-
|
|
|
- this.init( cfg, fn );
|
|
|
- };
|
|
|
-
|
|
|
- // extend pubsub
|
|
|
- World.prototype = Physics.util.extend({}, Physics.util.pubsub.prototype, {
|
|
|
-
|
|
|
- /** internal, see: new Physics.world
|
|
|
- * Physics.world#init( [options, fn(world, Physics)] )
|
|
|
- * - options (Object): configuration options (see constructor)
|
|
|
- * - fn (Function|Array): Callback function or array of callbacks called with this === world
|
|
|
- *
|
|
|
- * Initialization
|
|
|
- **/
|
|
|
- init: function( cfg, fn ){
|
|
|
-
|
|
|
- var self = this;
|
|
|
-
|
|
|
- if ( Physics.util.isFunction( cfg ) || Physics.util.isArray( cfg ) ){
|
|
|
- fn = cfg;
|
|
|
- cfg = {};
|
|
|
- }
|
|
|
-
|
|
|
- this._meta = {
|
|
|
- // statistics (fps, etc)
|
|
|
- fps: 0,
|
|
|
- ipf: 0
|
|
|
- };
|
|
|
- this._bodies = [];
|
|
|
- this._behaviors = [];
|
|
|
- this._integrator = null;
|
|
|
- this._renderer = null;
|
|
|
- this._paused = false;
|
|
|
- this._warp = 1;
|
|
|
- this._time = 0;
|
|
|
-
|
|
|
- // set options
|
|
|
- this.options = Physics.util.options( defaults );
|
|
|
- this.options.onChange(function( opts ){
|
|
|
-
|
|
|
- // set timestep
|
|
|
- self.timestep( opts.timestep );
|
|
|
- });
|
|
|
- this.options( cfg );
|
|
|
-
|
|
|
- // add integrator
|
|
|
- this.add(Physics.integrator( this.options.integrator ));
|
|
|
-
|
|
|
- // apply the callback function
|
|
|
- if ( Physics.util.isFunction( fn ) ){
|
|
|
-
|
|
|
- execCallbacks([ fn ], this, [this, Physics] );
|
|
|
-
|
|
|
- } else if ( Physics.util.isArray( fn ) ){
|
|
|
-
|
|
|
- execCallbacks(fn, this, [this, Physics] );
|
|
|
- }
|
|
|
- },
|
|
|
-
|
|
|
- /**
|
|
|
- * Physics.world#options( cfg ) -> Object
|
|
|
- * - options (Object): configuration options (see constructor)
|
|
|
- * + (Object): Options container
|
|
|
- *
|
|
|
- * Set config options. Also access options by `.options.<option>`.
|
|
|
- **/
|
|
|
- options: null,
|
|
|
-
|
|
|
- /** chainable
|
|
|
- * Physics.world#add( things ) -> this
|
|
|
- * - things (Object|Array): The thing, or array of things (body, behavior, integrator, or renderer) to add.
|
|
|
- *
|
|
|
- * Multipurpose add method. Add one or many bodies, behaviors, integrators, renderers...
|
|
|
- **/
|
|
|
- add: function( arg ){
|
|
|
-
|
|
|
- var i = 0
|
|
|
- ,len = arg && arg.length || 0
|
|
|
- ,thing = Physics.util.isArray( arg ) ? arg[ 0 ] : arg
|
|
|
- ;
|
|
|
-
|
|
|
- if ( !thing ){
|
|
|
- return this;
|
|
|
- }
|
|
|
-
|
|
|
- // we'll either cycle through an array
|
|
|
- // or just run this on the arg itself
|
|
|
- do {
|
|
|
- switch (thing.type){
|
|
|
-
|
|
|
- case 'behavior':
|
|
|
- this.addBehavior(thing);
|
|
|
- break; // end behavior
|
|
|
-
|
|
|
- case 'integrator':
|
|
|
- this.integrator(thing);
|
|
|
- break; // end integrator
|
|
|
-
|
|
|
- case 'renderer':
|
|
|
- this.renderer(thing);
|
|
|
- break; // end renderer
|
|
|
-
|
|
|
- case 'body':
|
|
|
- this.addBody(thing);
|
|
|
- break; // end body
|
|
|
-
|
|
|
- default:
|
|
|
- throw 'Error: failed to add item of unknown type "'+ thing.type +'" to world';
|
|
|
- // end default
|
|
|
- }
|
|
|
-
|
|
|
- } while ( ++i < len && (thing = arg[ i ]) );
|
|
|
-
|
|
|
- return this;
|
|
|
- },
|
|
|
-
|
|
|
- /** chainable
|
|
|
- * Physics.world#remove( things ) -> this
|
|
|
- * - things (Object|Array): The thing, or array of things (body, behavior, integrator, or renderer) to remove.
|
|
|
- *
|
|
|
- * Multipurpose remove method. Remove one or many bodies, behaviors, integrators, renderers...
|
|
|
- **/
|
|
|
- remove: function( arg ){
|
|
|
-
|
|
|
- var i = 0
|
|
|
- ,len = arg && arg.length || 0
|
|
|
- ,thing = Physics.util.isArray( arg ) ? arg[ 0 ] : arg
|
|
|
- ;
|
|
|
-
|
|
|
- if ( !thing ){
|
|
|
- return this;
|
|
|
- }
|
|
|
-
|
|
|
- // we'll either cycle through an array
|
|
|
- // or just run this on the arg itself
|
|
|
- do {
|
|
|
- switch (thing.type){
|
|
|
-
|
|
|
- case 'behavior':
|
|
|
- this.removeBehavior( thing );
|
|
|
- break; // end behavior
|
|
|
-
|
|
|
- case 'integrator':
|
|
|
- if (thing === this._integrator){
|
|
|
- this.integrator( null );
|
|
|
- }
|
|
|
- break; // end integrator
|
|
|
-
|
|
|
- case 'renderer':
|
|
|
- if (thing === this._renderer){
|
|
|
- this.renderer( null );
|
|
|
- }
|
|
|
- break; // end renderer
|
|
|
-
|
|
|
- case 'body':
|
|
|
- this.removeBody( thing );
|
|
|
- break; // end body
|
|
|
-
|
|
|
- default:
|
|
|
- throw 'Error: failed to remove item of unknown type "'+ thing.type +'" from world';
|
|
|
- // end default
|
|
|
- }
|
|
|
-
|
|
|
- } while ( ++i < len && (thing = arg[ i ]) );
|
|
|
-
|
|
|
- return this;
|
|
|
- },
|
|
|
-
|
|
|
- /** chainable
|
|
|
- * Physics.world#has( thing ) -> Boolean
|
|
|
- * - thing (Object): The thing to test
|
|
|
- * + (Boolean): `true` if thing is in the world, `false` otherwise.
|
|
|
- *
|
|
|
- * Determine if a thing has been added to world.
|
|
|
- **/
|
|
|
- has: function( thing ){
|
|
|
-
|
|
|
- var arr
|
|
|
- ,i
|
|
|
- ,l
|
|
|
- ;
|
|
|
-
|
|
|
- if ( !thing ){
|
|
|
- return false;
|
|
|
- }
|
|
|
-
|
|
|
- switch (thing.type){
|
|
|
-
|
|
|
- case 'behavior':
|
|
|
- arr = this._behaviors;
|
|
|
- break; // end behavior
|
|
|
-
|
|
|
- case 'integrator':
|
|
|
- return ( this._integrator === thing );
|
|
|
- // end integrator
|
|
|
-
|
|
|
- case 'renderer':
|
|
|
- return ( this._renderer === thing );
|
|
|
- // end renderer
|
|
|
-
|
|
|
- case 'body':
|
|
|
- arr = this._bodies;
|
|
|
- break; // end body
|
|
|
-
|
|
|
- default:
|
|
|
- throw 'Error: unknown type "'+ thing.type +'"';
|
|
|
- // end default
|
|
|
- }
|
|
|
-
|
|
|
- // check array
|
|
|
- return (Physics.util.indexOf( arr, thing ) > -1);
|
|
|
- },
|
|
|
-
|
|
|
- /** chainable
|
|
|
- * Physics.world#integrator( [integrator] ) -> Integrator|this
|
|
|
- * - integrator (Integrator): The integrator to set on the world
|
|
|
- * + (Integrator): The currently set integrator if `integrator` not specified
|
|
|
- * + (this): for chaining if `integrator` specified
|
|
|
- *
|
|
|
- * Get or Set the integrator
|
|
|
- **/
|
|
|
- integrator: function( integrator ){
|
|
|
-
|
|
|
- if ( integrator === undefined ){
|
|
|
- return this._integrator;
|
|
|
- }
|
|
|
-
|
|
|
- // do nothing if already added
|
|
|
- if ( this._integrator === integrator ){
|
|
|
- return this;
|
|
|
- }
|
|
|
-
|
|
|
- if ( this._integrator ){
|
|
|
-
|
|
|
- this._integrator.setWorld( null );
|
|
|
-
|
|
|
- this.emit( 'remove:integrator', {
|
|
|
- integrator: this._integrator
|
|
|
- });
|
|
|
- }
|
|
|
-
|
|
|
- if ( integrator ){
|
|
|
- this._integrator = integrator;
|
|
|
- this._integrator.setWorld( this );
|
|
|
-
|
|
|
- this.emit( 'add:integrator', {
|
|
|
- integrator: this._integrator
|
|
|
- });
|
|
|
- }
|
|
|
-
|
|
|
- return this;
|
|
|
- },
|
|
|
-
|
|
|
- /** chainable
|
|
|
- * Physics.world#renderer( [renderer] ) -> Renderer|this
|
|
|
- * - renderer (Renderer): The renderer to set on the world
|
|
|
- * + (Renderer): The currently set renderer if `renderer` not specified
|
|
|
- * + (this): for chaining if `renderer` specified
|
|
|
- *
|
|
|
- * Get or Set the renderer
|
|
|
- **/
|
|
|
- renderer: function( renderer ){
|
|
|
-
|
|
|
- if ( renderer === undefined ){
|
|
|
- return this._renderer;
|
|
|
- }
|
|
|
-
|
|
|
- // do nothing if renderer already added
|
|
|
- if ( this._renderer === renderer ){
|
|
|
- return this;
|
|
|
- }
|
|
|
-
|
|
|
- if ( this._renderer ){
|
|
|
-
|
|
|
- this._renderer.setWorld( null );
|
|
|
-
|
|
|
- this.emit( 'remove:renderer', {
|
|
|
- renderer: this._renderer
|
|
|
- });
|
|
|
- }
|
|
|
-
|
|
|
- if ( renderer ){
|
|
|
- this._renderer = renderer;
|
|
|
- this._renderer.setWorld( this );
|
|
|
-
|
|
|
- this.emit( 'add:renderer', {
|
|
|
- renderer: this._renderer
|
|
|
- });
|
|
|
- }
|
|
|
-
|
|
|
- return this;
|
|
|
- },
|
|
|
-
|
|
|
- /** chainable
|
|
|
- * Physics.world#timestep( [dt] ) -> Number|this
|
|
|
- * - dt (Number): The time step for the world
|
|
|
- * + (Number): The currently set time step if `dt` not specified
|
|
|
- * + (this): for chaining if `dt` specified
|
|
|
- *
|
|
|
- * Get or Set the timestep
|
|
|
- **/
|
|
|
- timestep: function( dt ){
|
|
|
-
|
|
|
- if ( 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;
|
|
|
-
|
|
|
- return this;
|
|
|
- }
|
|
|
-
|
|
|
- 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
|
|
|
- *
|
|
|
- * Add a behavior to the world
|
|
|
- **/
|
|
|
- addBehavior: function( behavior ){
|
|
|
-
|
|
|
- var notify;
|
|
|
-
|
|
|
- // don't allow duplicates
|
|
|
- if ( this.has( behavior ) ){
|
|
|
- return this;
|
|
|
- }
|
|
|
-
|
|
|
- behavior.setWorld( this );
|
|
|
- this._behaviors.push( behavior );
|
|
|
-
|
|
|
- this.emit( 'add:behavior', {
|
|
|
- behavior: behavior
|
|
|
- });
|
|
|
-
|
|
|
- return this;
|
|
|
- },
|
|
|
-
|
|
|
- /**
|
|
|
- * Physics.world#getBehaviors() -> Array
|
|
|
- * + (Array): Array of behaviors
|
|
|
- *
|
|
|
- * Get copied list of behaviors in the world
|
|
|
- **/
|
|
|
- getBehaviors: function(){
|
|
|
-
|
|
|
- // return the copied array
|
|
|
- return [].concat(this._behaviors);
|
|
|
- },
|
|
|
-
|
|
|
- /** chainable
|
|
|
- * Physics.world#removeBehavior( behavior ) -> this
|
|
|
- * - behavior (Behavior): The behavior to remove
|
|
|
- *
|
|
|
- * Remove a behavior from the world
|
|
|
- **/
|
|
|
- removeBehavior: function( behavior ){
|
|
|
-
|
|
|
- var behaviors = this._behaviors;
|
|
|
-
|
|
|
- if (behavior){
|
|
|
-
|
|
|
- for ( var i = 0, l = behaviors.length; i < l; ++i ){
|
|
|
-
|
|
|
- if (behavior === behaviors[ i ]){
|
|
|
-
|
|
|
- behaviors.splice( i, 1 );
|
|
|
- behavior.setWorld( null );
|
|
|
-
|
|
|
- this.emit( 'remove:behavior', {
|
|
|
- behavior: behavior
|
|
|
- });
|
|
|
-
|
|
|
- break;
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- return this;
|
|
|
- },
|
|
|
-
|
|
|
- /** chainable
|
|
|
- * Physics.world#addBody( body ) -> this
|
|
|
- * - body (Body): The behavior to add
|
|
|
- *
|
|
|
- * Add a body to the world
|
|
|
- **/
|
|
|
- addBody: function( body ){
|
|
|
-
|
|
|
- var notify;
|
|
|
-
|
|
|
- // don't allow duplicates
|
|
|
- if ( this.has( body ) ){
|
|
|
- return this;
|
|
|
- }
|
|
|
-
|
|
|
- body.setWorld( this );
|
|
|
- this._bodies.push( body );
|
|
|
-
|
|
|
- this.emit( 'add:body', {
|
|
|
- body: body
|
|
|
- });
|
|
|
-
|
|
|
- return this;
|
|
|
- },
|
|
|
-
|
|
|
- /**
|
|
|
- * Physics.world#getBodies() -> Array
|
|
|
- * + (Array): Array of bodies
|
|
|
- *
|
|
|
- * Get copied list of bodies in the world
|
|
|
- **/
|
|
|
- getBodies: function(){
|
|
|
-
|
|
|
- // return the copied array
|
|
|
- return [].concat(this._bodies);
|
|
|
- },
|
|
|
-
|
|
|
- /** chainable
|
|
|
- * Physics.world#removeBody( body ) -> this
|
|
|
- * - body (Body): The body to remove
|
|
|
- *
|
|
|
- * Remove a body from the world
|
|
|
- **/
|
|
|
- removeBody: function( body ){
|
|
|
-
|
|
|
- var bodies = this._bodies;
|
|
|
-
|
|
|
- if (body){
|
|
|
-
|
|
|
- for ( var i = 0, l = bodies.length; i < l; ++i ){
|
|
|
-
|
|
|
- if (body === bodies[ i ]){
|
|
|
-
|
|
|
- bodies.splice( i, 1 );
|
|
|
- body.setWorld( null );
|
|
|
-
|
|
|
- this.emit( 'remove:body', {
|
|
|
- body: body
|
|
|
- });
|
|
|
-
|
|
|
- break;
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- return this;
|
|
|
- },
|
|
|
-
|
|
|
- /** see: Physics.query
|
|
|
- * Physics.world#findOne( rules ) -> Body | false
|
|
|
- * Physics.world#findOne( filter(body) ) -> Body | false
|
|
|
- * - rules (Object): Query rules.
|
|
|
- * - filter (Function): Filter function called to check bodies
|
|
|
- * - body (Body): Each body in the world
|
|
|
- *
|
|
|
- * Find first matching body based on query rules.
|
|
|
- **/
|
|
|
- findOne: function( rules ){
|
|
|
-
|
|
|
- var self = this
|
|
|
- ,fn = (typeof rules === 'function') ? rules : Physics.query( rules )
|
|
|
- ;
|
|
|
-
|
|
|
- return Physics.util.find( self._bodies, fn ) || false;
|
|
|
- },
|
|
|
-
|
|
|
- /** see: Physics.query
|
|
|
- * Physics.world#find( rules ) -> Array
|
|
|
- * Physics.world#find( filter(body) ) -> Array
|
|
|
- * - rules (Object): Query rules
|
|
|
- * - filter (Function): Filter function called to check bodies
|
|
|
- * - body (Body): Each body in the world
|
|
|
- *
|
|
|
- * Find all matching bodies based on query rules.
|
|
|
- **/
|
|
|
- find: function( rules ){
|
|
|
-
|
|
|
- var self = this
|
|
|
- ,fn = (typeof rules === 'function') ? rules : Physics.query( rules )
|
|
|
- ;
|
|
|
-
|
|
|
- return Physics.util.filter( self._bodies, fn );
|
|
|
- },
|
|
|
-
|
|
|
- /** internal
|
|
|
- * Physics.world#iterate( dt )
|
|
|
- * - dt (Number): The timestep
|
|
|
- *
|
|
|
- * Do a single iteration.
|
|
|
- **/
|
|
|
- iterate: function( dt ){
|
|
|
-
|
|
|
- this._integrator.integrate( this._bodies, dt );
|
|
|
- },
|
|
|
-
|
|
|
- /** chainable
|
|
|
- * Physics.world#step( [now] ) -> this
|
|
|
- * - now (Number): Current unix timestamp
|
|
|
- *
|
|
|
- * Step the world up to specified time or do one step if no time is specified.
|
|
|
- **/
|
|
|
- step: function( now ){
|
|
|
-
|
|
|
- var time = this._time
|
|
|
- ,warp = this._warp
|
|
|
- ,invWarp = 1 / warp
|
|
|
- ,dt = this._dt
|
|
|
- ,animDt = dt * invWarp
|
|
|
- ,animMaxJump = this._maxJump * invWarp
|
|
|
- ,animDiff
|
|
|
- ,worldDiff
|
|
|
- ,target
|
|
|
- ,meta = this._meta
|
|
|
- ;
|
|
|
-
|
|
|
- // if it's paused, don't step
|
|
|
- // or if it's the first step...
|
|
|
- if ( this._paused || this._animTime === undefined ){
|
|
|
- this._animTime = now || this._animTime || Physics.util.ticker.now();
|
|
|
-
|
|
|
- if ( !this._paused ){
|
|
|
- this.emit('step', meta);
|
|
|
- }
|
|
|
- return this;
|
|
|
- }
|
|
|
-
|
|
|
- // new time is specified, or just one iteration ahead
|
|
|
- now = now || (this._animTime + animDt);
|
|
|
- // the time between this step and the last
|
|
|
- animDiff = now - this._animTime;
|
|
|
-
|
|
|
- // if the time difference is too big... adjust
|
|
|
- if ( animDiff > animMaxJump ){
|
|
|
- this._animTime = now - animMaxJump;
|
|
|
- animDiff = animMaxJump;
|
|
|
- }
|
|
|
-
|
|
|
- // the "world" time between this step and the last. Adjusts for warp
|
|
|
- worldDiff = animDiff * warp;
|
|
|
-
|
|
|
- // the target time for the world time to step to
|
|
|
- target = time + worldDiff - dt;
|
|
|
-
|
|
|
- this.emit('beforeStep');
|
|
|
-
|
|
|
- if ( time <= target ){
|
|
|
-
|
|
|
- while ( time <= target ){
|
|
|
- // increment world time
|
|
|
- time += dt;
|
|
|
- // increment animation time
|
|
|
- this._animTime += animDt;
|
|
|
- // record the world time
|
|
|
- this._time = time;
|
|
|
- // iterate by one timestep
|
|
|
- this.iterate( dt );
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // set some meta
|
|
|
- meta.fps = 1000 / (now - this._lastTime); // frames per second
|
|
|
- meta.ipf = (worldDiff / dt).toFixed(2); // iterations per frame
|
|
|
- meta.interpolateTime = dt + target - time;
|
|
|
-
|
|
|
- // record the time this was called
|
|
|
- this._lastTime = now;
|
|
|
-
|
|
|
- this.emit('step', meta);
|
|
|
- return this;
|
|
|
- },
|
|
|
-
|
|
|
- /**
|
|
|
- * Physics.world#warp( [warp] ) -> this|Number
|
|
|
- * - warp (Number): The time warp factor
|
|
|
- *
|
|
|
- * Speed up or slow down the iteration by this factor.
|
|
|
- *
|
|
|
- * Example:
|
|
|
- * ```javascript
|
|
|
- * // slow motion... 10x slower
|
|
|
- * world.warp( 0.01 );
|
|
|
- * ```
|
|
|
- **/
|
|
|
- warp: function( warp ){
|
|
|
- if ( warp === undefined ){
|
|
|
- return this._warp;
|
|
|
- }
|
|
|
-
|
|
|
- this._warp = warp || 1;
|
|
|
-
|
|
|
- return this;
|
|
|
- },
|
|
|
-
|
|
|
- /** chainable
|
|
|
- * Physics.world#render() -> this
|
|
|
- *
|
|
|
- * Render current world state using the renderer
|
|
|
- **/
|
|
|
- render: function(){
|
|
|
-
|
|
|
- if ( !this._renderer ){
|
|
|
- throw "No renderer added to world";
|
|
|
- }
|
|
|
-
|
|
|
- this._renderer.render( this._bodies, this._meta );
|
|
|
- this.emit('render', {
|
|
|
- bodies: this._bodies,
|
|
|
- meta: this._meta,
|
|
|
- renderer: this._renderer
|
|
|
- });
|
|
|
- return this;
|
|
|
- },
|
|
|
-
|
|
|
- /** chainable
|
|
|
- * Physics.world#pause() -> this
|
|
|
- *
|
|
|
- * Pause the world (step calls do nothing).
|
|
|
- **/
|
|
|
- pause: function(){
|
|
|
-
|
|
|
- this._paused = true;
|
|
|
- this.emit('pause');
|
|
|
- return this;
|
|
|
- },
|
|
|
-
|
|
|
- /** chainable
|
|
|
- * Physics.world#unpause() -> this
|
|
|
- *
|
|
|
- * Unpause the world (step calls continue as usual).
|
|
|
- **/
|
|
|
- unpause: function(){
|
|
|
-
|
|
|
- this._paused = false;
|
|
|
- this.emit('unpause');
|
|
|
- return this;
|
|
|
- },
|
|
|
-
|
|
|
- /**
|
|
|
- * Physics.world#isPaused() -> Boolean
|
|
|
- * + (Boolean): Returns `true` if world is paused, `false` otherwise.
|
|
|
- *
|
|
|
- * Determine if world is paused.
|
|
|
- **/
|
|
|
- isPaused: function(){
|
|
|
-
|
|
|
- return !!this._paused;
|
|
|
- },
|
|
|
-
|
|
|
- /**
|
|
|
- * Physics.world#destroy()
|
|
|
- *
|
|
|
- * Destroy the world.
|
|
|
- * (Bwahahahahaha!)
|
|
|
- **/
|
|
|
- destroy: function(){
|
|
|
-
|
|
|
- var self = this;
|
|
|
- self.pause();
|
|
|
-
|
|
|
- // notify before
|
|
|
- this.emit('destroy');
|
|
|
-
|
|
|
- // remove all listeners
|
|
|
- self.off( true );
|
|
|
- // remove everything
|
|
|
- self.remove( self.getBodies() );
|
|
|
- self.remove( self.getBehaviors() );
|
|
|
- self.integrator( null );
|
|
|
- self.renderer( null );
|
|
|
- }
|
|
|
-
|
|
|
- });
|
|
|
-
|
|
|
- Physics.world = World;
|
|
|
-
|
|
|
-}());
|
|
|
-
|
|
|
-
|
|
|
-// ---
|
|
|
-// inside: src/integrators/verlet.js
|
|
|
-
|
|
|
-Physics.integrator('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 Verlet < Integrator
|
|
|
- *
|
|
|
- * `Physics.integrator('verlet')`.
|
|
|
- *
|
|
|
- * The 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
|
|
|
- ,prevDt = this.prevDt || dt
|
|
|
- ,dtMul = (dtdt + dt * prevDt) * 0.5
|
|
|
- ;
|
|
|
-
|
|
|
- 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 ) ){
|
|
|
-
|
|
|
- // 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
|
|
|
- // complies with other integration methods
|
|
|
- state.vel.mult( dt );
|
|
|
- }
|
|
|
-
|
|
|
- // Apply "air resistance".
|
|
|
- if ( drag ){
|
|
|
-
|
|
|
- state.vel.mult( drag );
|
|
|
- }
|
|
|
-
|
|
|
- // Apply acceleration
|
|
|
- // v += a * dt * dt
|
|
|
- state.vel.vadd( state.acc.mult( dtMul ) );
|
|
|
-
|
|
|
- // restore velocity
|
|
|
- state.vel.mult( 1/dt );
|
|
|
-
|
|
|
- // store calculated velocity
|
|
|
- state.old.vel.clone( state.vel );
|
|
|
-
|
|
|
- // Reset accel
|
|
|
- state.acc.zero();
|
|
|
-
|
|
|
- //
|
|
|
- // Angular components
|
|
|
- //
|
|
|
-
|
|
|
- if (state.angular.vel === state.old.angular.vel && body.started()){
|
|
|
-
|
|
|
- state.angular.vel = (state.angular.pos - state.old.angular.pos);
|
|
|
-
|
|
|
- } else {
|
|
|
-
|
|
|
- state.old.angular.pos = state.angular.pos - state.angular.vel;
|
|
|
- state.angular.vel *= dt;
|
|
|
- }
|
|
|
-
|
|
|
- state.angular.vel += state.angular.acc * dtMul;
|
|
|
- state.angular.vel /= dt;
|
|
|
- state.old.angular.vel = state.angular.vel;
|
|
|
- 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
|
|
|
- ,prevDt = this.prevDt || dt
|
|
|
- ,dtcorr = dt/prevDt
|
|
|
- ;
|
|
|
-
|
|
|
- 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() ){
|
|
|
-
|
|
|
- // so we need to scale the value by dt so it
|
|
|
- // complies with other integration methods
|
|
|
- state.vel.mult( dt * dtcorr );
|
|
|
-
|
|
|
- // Store old position.
|
|
|
- // xold = x
|
|
|
- state.old.pos.clone( state.pos );
|
|
|
-
|
|
|
- state.pos.vadd( state.vel );
|
|
|
-
|
|
|
- // 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.old.angular.pos = state.angular.pos;
|
|
|
-
|
|
|
- state.angular.pos += state.angular.vel;
|
|
|
- state.angular.vel /= dt * dtcorr;
|
|
|
- state.old.angular.vel = state.angular.vel;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- this.prevDt = dt;
|
|
|
- }
|
|
|
- };
|
|
|
-});
|
|
|
-
|
|
|
-
|
|
|
-// ---
|
|
|
-// inside: src/geometries/point.js
|
|
|
-
|
|
|
-/** alias of: Geometry
|
|
|
- * class PointGeometry < Geometry
|
|
|
- *
|
|
|
- * Physics.geometry('point')
|
|
|
- *
|
|
|
- * The point geometry represents a point.
|
|
|
- **/
|
|
|
-Physics.geometry('point', function( parent ){});
|
|
|
-
|
|
|
-
|
|
|
-// ---
|
|
|
-// inside: src/bodies/point.js
|
|
|
-
|
|
|
-/** alias of: Body
|
|
|
- * class PointBody < Body
|
|
|
- *
|
|
|
- * Physics.body('point')
|
|
|
- *
|
|
|
- * The point body represents a point.
|
|
|
- **/
|
|
|
-Physics.body('point', function( parent ){
|
|
|
- return {
|
|
|
- init: function( opts ){
|
|
|
- parent.init.call( this, opts );
|
|
|
- this.moi = 0;
|
|
|
- }
|
|
|
- };
|
|
|
-});
|
|
|
-
|
|
|
-
|
|
|
-// ---
|
|
|
-// inside: src/geometries/circle.js
|
|
|
-
|
|
|
-/**
|
|
|
- * class CircleGeometry < Geometry
|
|
|
- *
|
|
|
- * Physics.geometry('circle')
|
|
|
- *
|
|
|
- * The circle geometry has a circular shape.
|
|
|
- *
|
|
|
- * Additional options include:
|
|
|
- * - radius: the radius
|
|
|
- *
|
|
|
- * Example:
|
|
|
- *
|
|
|
- * ```javascript
|
|
|
- * var round = Physics.body('circle', {
|
|
|
- * x: 30,
|
|
|
- * y: 20,
|
|
|
- * radius: 5
|
|
|
- * });
|
|
|
- * ```
|
|
|
- **/
|
|
|
-Physics.geometry('circle', function( parent ){
|
|
|
-
|
|
|
- var defaults = {
|
|
|
-
|
|
|
- radius: 1.0
|
|
|
- };
|
|
|
-
|
|
|
- return {
|
|
|
-
|
|
|
- // extended
|
|
|
- init: function( options ){
|
|
|
-
|
|
|
- var self = this;
|
|
|
- // call parent init method
|
|
|
- parent.init.call(this, options);
|
|
|
-
|
|
|
- this.options.defaults( defaults );
|
|
|
- this.options.onChange(function( opts ){
|
|
|
- this.radius = opts.radius;
|
|
|
- });
|
|
|
- this.options( options );
|
|
|
-
|
|
|
- this._aabb = Physics.aabb();
|
|
|
- this.radius = this.options.radius;
|
|
|
- },
|
|
|
-
|
|
|
- // extended
|
|
|
- aabb: function( angle ){
|
|
|
-
|
|
|
- var r = this.radius
|
|
|
- ;
|
|
|
-
|
|
|
- // circles are symetric... so angle has no effect
|
|
|
- if ( this._aabb.hw !== r ){
|
|
|
- // recalculate
|
|
|
- this._aabb = Physics.aabb( -r, -r, r, r );
|
|
|
- }
|
|
|
-
|
|
|
- return Physics.aabb.clone( this._aabb );
|
|
|
- },
|
|
|
-
|
|
|
- // extended
|
|
|
- getFarthestHullPoint: function( dir, result ){
|
|
|
-
|
|
|
- result = result || new Physics.vector();
|
|
|
-
|
|
|
- return result.clone( dir ).normalize().mult( this.radius );
|
|
|
- },
|
|
|
-
|
|
|
- // extended
|
|
|
- getFarthestCorePoint: function( dir, result, margin ){
|
|
|
-
|
|
|
- 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
|
|
|
- // ... yay circles!
|
|
|
- // but since the caller is expecting a certain margin... give it
|
|
|
- // to them
|
|
|
- return result.clone( dir ).normalize().mult( this.radius - margin );
|
|
|
- }
|
|
|
- };
|
|
|
-});
|
|
|
-
|
|
|
-
|
|
|
-// ---
|
|
|
-// 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
|
|
|
-
|
|
|
-/**
|
|
|
- * class ConvexPolygonGeometry < Geometry
|
|
|
- *
|
|
|
- * Physics.geometry('convex-polygon')
|
|
|
- *
|
|
|
- * Geometry for convex polygons.
|
|
|
- *
|
|
|
- * Additional config options:
|
|
|
- *
|
|
|
- * - vertices: Array of [[Vectorish]] objects representing the polygon vertices in clockwise (or counterclockwise) order.
|
|
|
- *
|
|
|
- * Example:
|
|
|
- *
|
|
|
- * ```javascript
|
|
|
- * var pentagon = Physics.geometry('convex-polygon', {
|
|
|
- * // the centroid is automatically calculated and used to position the shape
|
|
|
- * vertices: [
|
|
|
- * { x: 0, y: -30 },
|
|
|
- * { x: -29, y: -9 },
|
|
|
- * { x: -18, y: 24 },
|
|
|
- * { x: 18, y: 24 },
|
|
|
- * { x: 29, y: -9 }
|
|
|
- * ]
|
|
|
- * });
|
|
|
- * ```
|
|
|
- **/
|
|
|
-Physics.geometry('convex-polygon', function( parent ){
|
|
|
-
|
|
|
- var ERROR_NOT_CONVEX = 'Error: The vertices specified do not match that of a _convex_ polygon.';
|
|
|
-
|
|
|
- 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.onChange(function( opts ){
|
|
|
- self.setVertices( opts.vertices || [] );
|
|
|
- });
|
|
|
- this.options( options );
|
|
|
-
|
|
|
- self.setVertices( this.options.vertices || [] );
|
|
|
-
|
|
|
- },
|
|
|
-
|
|
|
- /**
|
|
|
- * ConvexPolygonGeometry#setVertices( hull ) -> this
|
|
|
- * - hull (Array): Vertices represented by an array of [[Vectorish]] objects, in either clockwise or counterclockwise order
|
|
|
- *
|
|
|
- * Set the vertices of this polygon.
|
|
|
- **/
|
|
|
- setVertices: function( hull ){
|
|
|
-
|
|
|
- var scratch = Physics.scratchpad()
|
|
|
- ,transl = scratch.transform()
|
|
|
- ,verts = this.vertices = []
|
|
|
- ;
|
|
|
-
|
|
|
- if ( !Physics.geometry.isPolygonConvex( hull ) ){
|
|
|
- throw ERROR_NOT_CONVEX;
|
|
|
- }
|
|
|
-
|
|
|
- transl.setRotation( 0 );
|
|
|
- transl.setTranslation( Physics.geometry.getPolygonCentroid( hull ).negate() );
|
|
|
-
|
|
|
- // translate each vertex so that the centroid is at the origin
|
|
|
- // then add the vertex as a vector to this.vertices
|
|
|
- for ( var i = 0, l = hull.length; i < l; ++i ){
|
|
|
-
|
|
|
- verts.push( new Physics.vector( hull[ i ] ).translate( transl ) );
|
|
|
- }
|
|
|
-
|
|
|
- this._area = Physics.geometry.getPolygonArea( verts );
|
|
|
- this._aabb = false;
|
|
|
- return scratch.done(this);
|
|
|
- },
|
|
|
-
|
|
|
- // extended
|
|
|
- aabb: function( angle ){
|
|
|
-
|
|
|
- if (!angle && this._aabb){
|
|
|
- return Physics.aabb.clone( this._aabb );
|
|
|
- }
|
|
|
-
|
|
|
- var scratch = Physics.scratchpad()
|
|
|
- ,p = scratch.vector()
|
|
|
- ,trans = scratch.transform().setRotation( angle || 0 )
|
|
|
- ,xaxis = scratch.vector().set( 1, 0 ).rotateInv( trans )
|
|
|
- ,yaxis = scratch.vector().set( 0, 1 ).rotateInv( trans )
|
|
|
- ,xmax = this.getFarthestHullPoint( xaxis, p ).proj( xaxis )
|
|
|
- ,xmin = - this.getFarthestHullPoint( xaxis.negate(), p ).proj( xaxis )
|
|
|
- ,ymax = this.getFarthestHullPoint( yaxis, p ).proj( yaxis )
|
|
|
- ,ymin = - this.getFarthestHullPoint( yaxis.negate(), p ).proj( yaxis )
|
|
|
- ,aabb
|
|
|
- ;
|
|
|
-
|
|
|
- aabb = Physics.aabb( xmin, ymin, xmax, ymax );
|
|
|
-
|
|
|
- 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( aabb );
|
|
|
- }
|
|
|
-
|
|
|
- scratch.done();
|
|
|
- return aabb;
|
|
|
- },
|
|
|
-
|
|
|
- // extended
|
|
|
- getFarthestHullPoint: function( dir, result, data ){
|
|
|
-
|
|
|
- var verts = this.vertices
|
|
|
- ,val
|
|
|
- ,prev
|
|
|
- ,l = verts.length
|
|
|
- ,i = 2
|
|
|
- ,idx
|
|
|
- ;
|
|
|
-
|
|
|
- result = result || new Physics.vector();
|
|
|
-
|
|
|
- if ( l < 2 ){
|
|
|
- if ( data ){
|
|
|
- data.idx = 0;
|
|
|
- }
|
|
|
- return result.clone( verts[0] );
|
|
|
- }
|
|
|
-
|
|
|
- prev = verts[ 0 ].dot( dir );
|
|
|
- val = verts[ 1 ].dot( dir );
|
|
|
-
|
|
|
- if ( l === 2 ){
|
|
|
- idx = (val >= prev) ? 1 : 0;
|
|
|
- if ( data ){
|
|
|
- data.idx = idx;
|
|
|
- }
|
|
|
- return result.clone( verts[ idx ] );
|
|
|
- }
|
|
|
-
|
|
|
- if ( val >= prev ){
|
|
|
- // go up
|
|
|
- // search until the next dot product
|
|
|
- // is less than the previous
|
|
|
- while ( i < l && val >= prev ){
|
|
|
- prev = val;
|
|
|
- val = verts[ i ].dot( dir );
|
|
|
- i++;
|
|
|
- }
|
|
|
-
|
|
|
- if (val >= prev){
|
|
|
- i++;
|
|
|
- }
|
|
|
-
|
|
|
- // return the previous (furthest with largest dot product)
|
|
|
- idx = i - 2;
|
|
|
- if ( data ){
|
|
|
- data.idx = i - 2;
|
|
|
- }
|
|
|
- return result.clone( verts[ idx ] );
|
|
|
-
|
|
|
- } else {
|
|
|
- // go down
|
|
|
-
|
|
|
- i = l;
|
|
|
- while ( i > 1 && prev >= val ){
|
|
|
- i--;
|
|
|
- val = prev;
|
|
|
- prev = verts[ i ].dot( dir );
|
|
|
- }
|
|
|
-
|
|
|
- // return the previous (furthest with largest dot product)
|
|
|
- idx = (i + 1) % l;
|
|
|
- if ( data ){
|
|
|
- data.idx = idx;
|
|
|
- }
|
|
|
- return result.clone( verts[ idx ] );
|
|
|
- }
|
|
|
- },
|
|
|
-
|
|
|
- // extended
|
|
|
- getFarthestCorePoint: function( dir, result, margin ){
|
|
|
-
|
|
|
- var norm
|
|
|
- ,scratch = Physics.scratchpad()
|
|
|
- ,next = scratch.vector()
|
|
|
- ,prev = scratch.vector()
|
|
|
- ,verts = this.vertices
|
|
|
- ,l = verts.length
|
|
|
- ,mag
|
|
|
- ,sign = this._area > 0
|
|
|
- ,data = {}
|
|
|
- ;
|
|
|
-
|
|
|
- result = this.getFarthestHullPoint( dir, result, data );
|
|
|
-
|
|
|
- // get normalized directions to next and previous vertices
|
|
|
- next.clone( verts[ (data.idx + 1) % l ] ).vsub( result ).normalize().perp( sign );
|
|
|
- prev.clone( verts[ (data.idx - 1 + l) % l ] ).vsub( result ).normalize().perp( !sign );
|
|
|
-
|
|
|
- // get the magnitude of a vector from the result vertex
|
|
|
- // that splits down the middle
|
|
|
- // creating a margin of "m" to each edge
|
|
|
- mag = margin / (1 + next.dot(prev));
|
|
|
-
|
|
|
- result.vadd( next.vadd( prev ).mult( mag ) );
|
|
|
- scratch.done();
|
|
|
- return result;
|
|
|
- }
|
|
|
- };
|
|
|
-});
|
|
|
-
|
|
|
-
|
|
|
-// ---
|
|
|
-// inside: src/geometries/rectangle.js
|
|
|
-
|
|
|
-/**
|
|
|
- * class RectangleGeometry < Geometry
|
|
|
- *
|
|
|
- * Physics.geometry('rectangle')
|
|
|
- *
|
|
|
- * Geometry for rectangles.
|
|
|
- *
|
|
|
- * Additional config options:
|
|
|
- *
|
|
|
- * - width: The width
|
|
|
- * - height: The height
|
|
|
- *
|
|
|
- * Example:
|
|
|
- *
|
|
|
- * ```javascript
|
|
|
- * var rectGeo = Physics.geometry('rectangle', {
|
|
|
- * width: 30,
|
|
|
- * height: 40
|
|
|
- * });
|
|
|
- * ```
|
|
|
- **/
|
|
|
-Physics.geometry('rectangle', 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.onChange(function( opts ){
|
|
|
- /**
|
|
|
- * RectangleGeometry#width = Number
|
|
|
- *
|
|
|
- * The width
|
|
|
- **/
|
|
|
- self.width = self.options.width || 1;
|
|
|
- /**
|
|
|
- * RectangleGeometry#height = Number
|
|
|
- *
|
|
|
- * The height
|
|
|
- **/
|
|
|
- self.height = self.options.height || 1;
|
|
|
- });
|
|
|
- this.options( options );
|
|
|
- },
|
|
|
-
|
|
|
- // extended
|
|
|
- aabb: function( angle ){
|
|
|
-
|
|
|
- if (!angle){
|
|
|
- return Physics.aabb( this.width, this.height );
|
|
|
- }
|
|
|
-
|
|
|
- var scratch = Physics.scratchpad()
|
|
|
- ,p = scratch.vector()
|
|
|
- ,trans = scratch.transform().setRotation( angle || 0 )
|
|
|
- ,xaxis = scratch.vector().set( 1, 0 ).rotateInv( trans )
|
|
|
- ,yaxis = scratch.vector().set( 0, 1 ).rotateInv( trans )
|
|
|
- ,xmax = this.getFarthestHullPoint( xaxis, p ).proj( xaxis )
|
|
|
- ,xmin = - this.getFarthestHullPoint( xaxis.negate(), p ).proj( xaxis )
|
|
|
- ,ymax = this.getFarthestHullPoint( yaxis, p ).proj( yaxis )
|
|
|
- ,ymin = - this.getFarthestHullPoint( yaxis.negate(), p ).proj( yaxis )
|
|
|
- ;
|
|
|
-
|
|
|
- scratch.done();
|
|
|
- return Physics.aabb( xmin, ymin, xmax, ymax );
|
|
|
- },
|
|
|
-
|
|
|
- // extended
|
|
|
- getFarthestHullPoint: function( dir, result ){
|
|
|
-
|
|
|
- result = result || new Physics.vector();
|
|
|
-
|
|
|
- var x = dir.x
|
|
|
- ,y = dir.y
|
|
|
- ;
|
|
|
-
|
|
|
- x = x === 0 ? 0 : x < 0 ? -this.width * 0.5 : this.width * 0.5;
|
|
|
- y = y === 0 ? 0 : y < 0 ? -this.height * 0.5 : this.height * 0.5;
|
|
|
-
|
|
|
- return result.set( x, y );
|
|
|
- },
|
|
|
-
|
|
|
- // extended
|
|
|
- getFarthestCorePoint: function( dir, result, margin ){
|
|
|
-
|
|
|
- var x, y;
|
|
|
- result = this.getFarthestHullPoint( dir, result );
|
|
|
- x = result.x;
|
|
|
- y = result.y;
|
|
|
- result.x = x === 0 ? 0 : x < 0 ? x + margin : x - margin;
|
|
|
- result.y = y === 0 ? 0 : y < 0 ? y + margin : y - margin;
|
|
|
-
|
|
|
- return result;
|
|
|
- }
|
|
|
- };
|
|
|
-});
|
|
|
-
|
|
|
-
|
|
|
-// ---
|
|
|
-// inside: src/bodies/circle.js
|
|
|
-
|
|
|
-/*
|
|
|
- * @requires geometries/circle
|
|
|
- */
|
|
|
-/**
|
|
|
- * class CircleBody < Body
|
|
|
- *
|
|
|
- * Physics.body('circle')
|
|
|
- *
|
|
|
- * The circle body has a circular shape.
|
|
|
- *
|
|
|
- * Additional options include:
|
|
|
- * - radius: the radius
|
|
|
- *
|
|
|
- * Example:
|
|
|
- *
|
|
|
- * ```javascript
|
|
|
- * var round = Physics.body('circle', {
|
|
|
- * x: 30,
|
|
|
- * y: 20,
|
|
|
- * radius: 5
|
|
|
- * });
|
|
|
- * ```
|
|
|
- **/
|
|
|
-Physics.body('circle', function( parent ){
|
|
|
-
|
|
|
- var defaults = {
|
|
|
- radius: 1.0
|
|
|
- };
|
|
|
-
|
|
|
- return {
|
|
|
-
|
|
|
- // extended
|
|
|
- init: function( options ){
|
|
|
-
|
|
|
- // call parent init method
|
|
|
- parent.init.call(this, options);
|
|
|
-
|
|
|
- options = Physics.util.extend({}, defaults, options);
|
|
|
-
|
|
|
- this.geometry = Physics.geometry('circle', {
|
|
|
- radius: options.radius
|
|
|
- });
|
|
|
-
|
|
|
- this.recalc();
|
|
|
- },
|
|
|
-
|
|
|
- // extended
|
|
|
- recalc: function(){
|
|
|
- parent.recalc.call(this);
|
|
|
- // moment of inertia
|
|
|
- this.moi = this.mass * this.geometry.radius * this.geometry.radius / 2;
|
|
|
- }
|
|
|
- };
|
|
|
-});
|
|
|
-
|
|
|
-
|
|
|
-// ---
|
|
|
-// 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')
|
|
|
- *
|
|
|
- * 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:
|
|
|
- *
|
|
|
- * ```javascript
|
|
|
- * var pentagon = Physics.body('convex-polygon', {
|
|
|
- * // place the centroid of the polygon at (300, 200)
|
|
|
- * x: 300,
|
|
|
- * y: 200,
|
|
|
- * // the centroid is automatically calculated and used to position the shape
|
|
|
- * vertices: [
|
|
|
- * { x: 0, y: -30 },
|
|
|
- * { x: -29, y: -9 },
|
|
|
- * { x: -18, y: 24 },
|
|
|
- * { x: 18, y: 24 },
|
|
|
- * { x: 29, y: -9 }
|
|
|
- * ]
|
|
|
- * });
|
|
|
- * ```
|
|
|
- **/
|
|
|
-Physics.body('convex-polygon', function( parent ){
|
|
|
-
|
|
|
- var defaults = {
|
|
|
-
|
|
|
- };
|
|
|
-
|
|
|
- return {
|
|
|
-
|
|
|
- // extended
|
|
|
- init: function( options ){
|
|
|
-
|
|
|
- // call parent init method
|
|
|
- parent.init.call(this, options);
|
|
|
-
|
|
|
- options = Physics.util.extend({}, defaults, options);
|
|
|
-
|
|
|
- this.geometry = Physics.geometry('convex-polygon', {
|
|
|
- vertices: options.vertices
|
|
|
- });
|
|
|
-
|
|
|
- this.recalc();
|
|
|
- },
|
|
|
-
|
|
|
- // extended
|
|
|
- recalc: function(){
|
|
|
- parent.recalc.call(this);
|
|
|
- // moment of inertia
|
|
|
- this.moi = Physics.geometry.getPolygonMOI( this.geometry.vertices );
|
|
|
- }
|
|
|
- };
|
|
|
-});
|
|
|
-
|
|
|
-
|
|
|
-// ---
|
|
|
-// inside: src/bodies/rectangle.js
|
|
|
-
|
|
|
-/*
|
|
|
- * @requires geometries/rectangle
|
|
|
- */
|
|
|
- /**
|
|
|
- * class RectangleBody < Body
|
|
|
- *
|
|
|
- * Physics.body('rectangle')
|
|
|
- *
|
|
|
- * Body for rectangles. The position of the body is the centroid of the rectangle.
|
|
|
- *
|
|
|
- * Additional config options:
|
|
|
- *
|
|
|
- * - width: The width
|
|
|
- * - height: The height
|
|
|
- *
|
|
|
- * Example:
|
|
|
- *
|
|
|
- * ```javascript
|
|
|
- * var rect = Physics.body('rectangle', {
|
|
|
- * // place the centroid of the rectangle at (300, 200)
|
|
|
- * x: 300,
|
|
|
- * y: 200,
|
|
|
- * width: 30,
|
|
|
- * height: 40
|
|
|
- * });
|
|
|
- * ```
|
|
|
- **/
|
|
|
-Physics.body('rectangle', function( parent ){
|
|
|
-
|
|
|
- var defaults = {
|
|
|
-
|
|
|
- };
|
|
|
-
|
|
|
- return {
|
|
|
-
|
|
|
- // extended
|
|
|
- init: function( options ){
|
|
|
-
|
|
|
- // call parent init method
|
|
|
- parent.init.call(this, options);
|
|
|
-
|
|
|
- options = Physics.util.extend({}, defaults, options);
|
|
|
-
|
|
|
- this.geometry = Physics.geometry('rectangle', {
|
|
|
- width: options.width,
|
|
|
- height: options.height
|
|
|
- });
|
|
|
-
|
|
|
- this.recalc();
|
|
|
- },
|
|
|
-
|
|
|
- // extended
|
|
|
- recalc: function(){
|
|
|
- var w = this.geometry.width;
|
|
|
- var h = this.geometry.height;
|
|
|
- parent.recalc.call(this);
|
|
|
- // moment of inertia
|
|
|
- this.moi = ( w*w + h*h ) * this.mass / 12;
|
|
|
- }
|
|
|
- };
|
|
|
-});
|
|
|
-
|
|
|
-
|
|
|
-// ---
|
|
|
-// inside: src/behaviors/attractor.js
|
|
|
-
|
|
|
-/**
|
|
|
- * class AttractorBehavior < Behavior
|
|
|
- *
|
|
|
- * `Physics.behavior('attractor')`.
|
|
|
- *
|
|
|
- * Attractor behavior attracts bodies to a specific point.
|
|
|
- *
|
|
|
- * Additional options include:
|
|
|
- * - pos: The position of the attraction point
|
|
|
- * - strength: How strong the attraction is (default: `1`)
|
|
|
- * - order: The power of the inverse distance (default: `2` because that is newtonian gravity... inverse square)
|
|
|
- * - max: The maximum distance in which to apply the attraction (default: Infinity)
|
|
|
- * - min: The minimum distance above which to apply the attraction (default: very small non-zero)
|
|
|
- **/
|
|
|
-Physics.behavior('attractor', function( parent ){
|
|
|
-
|
|
|
- var defaults = {
|
|
|
-
|
|
|
- pos: null, // default to (0, 0)
|
|
|
- // how strong the attraction is
|
|
|
- strength: 1,
|
|
|
- // power of the inverse distance (2 is inverse square)
|
|
|
- order: 2,
|
|
|
- // max distance to apply it to
|
|
|
- max: false, // infinite
|
|
|
- // min distance to apply it to
|
|
|
- min: false // auto calc
|
|
|
- };
|
|
|
-
|
|
|
- return {
|
|
|
-
|
|
|
- // extended
|
|
|
- init: function( options ){
|
|
|
-
|
|
|
- var self = this;
|
|
|
- this._pos = new Physics.vector();
|
|
|
- // call parent init method
|
|
|
- parent.init.call( this );
|
|
|
- this.options.defaults( defaults );
|
|
|
- this.options.onChange(function( opts ){
|
|
|
- self._maxDist = opts.max === false ? Infinity : opts.max;
|
|
|
- self._minDist = opts.min ? opts.min : 10;
|
|
|
- self.position( opts.pos );
|
|
|
- });
|
|
|
- this.options( options );
|
|
|
- },
|
|
|
-
|
|
|
- /**
|
|
|
- * AttractorBehavior#position( [pos] ) -> this|Object
|
|
|
- * - pos (Vectorish): The position to set
|
|
|
- * + (Object): Returns the [[Vectorish]] position if no arguments provided
|
|
|
- * + (this): For chaining
|
|
|
- *
|
|
|
- * Get or set the position of the attractor.
|
|
|
- **/
|
|
|
- position: function( pos ){
|
|
|
-
|
|
|
- var self = this;
|
|
|
-
|
|
|
- if ( pos ){
|
|
|
- this._pos.clone( pos );
|
|
|
- return self;
|
|
|
- }
|
|
|
-
|
|
|
- return this._pos.values();
|
|
|
- },
|
|
|
-
|
|
|
- // extended
|
|
|
- behave: function( data ){
|
|
|
-
|
|
|
- var bodies = this.getTargets()
|
|
|
- ,body
|
|
|
- ,order = this.options.order
|
|
|
- ,strength = this.options.strength
|
|
|
- ,minDist = this._minDist
|
|
|
- ,maxDist = this._maxDist
|
|
|
- ,scratch = Physics.scratchpad()
|
|
|
- ,acc = scratch.vector()
|
|
|
- ,norm
|
|
|
- ,g
|
|
|
- ;
|
|
|
-
|
|
|
- for ( var j = 0, l = bodies.length; j < l; j++ ){
|
|
|
-
|
|
|
- body = bodies[ j ];
|
|
|
-
|
|
|
- // clone the position
|
|
|
- acc.clone( this._pos );
|
|
|
- acc.vsub( body.state.pos );
|
|
|
- // get the distance
|
|
|
- norm = acc.norm();
|
|
|
-
|
|
|
- if (norm > minDist && norm < maxDist){
|
|
|
-
|
|
|
- g = strength / Math.pow(norm, order);
|
|
|
-
|
|
|
- body.accelerate( acc.normalize().mult( g ) );
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- scratch.done();
|
|
|
- }
|
|
|
- };
|
|
|
-});
|
|
|
-
|
|
|
-
|
|
|
-// ---
|
|
|
-// inside: src/behaviors/body-collision-detection.js
|
|
|
-
|
|
|
-/**
|
|
|
- * class BodyCollisionDetectionBehavior < Behavior
|
|
|
- *
|
|
|
- * `Physics.behavior('body-collision-detection')`.
|
|
|
- *
|
|
|
- * Detect collisions of bodies.
|
|
|
- *
|
|
|
- * Publishes collision events to the world as a group of detected collisions per iteration.
|
|
|
- *
|
|
|
- * The event data will have a `.collisions` property that is an array of collisions of the form:
|
|
|
- *
|
|
|
- * ```javascript
|
|
|
- * {
|
|
|
- * bodyA: // the first body
|
|
|
- * 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 relative to bodyA
|
|
|
- * overlap: // the amount bodyA overlaps bodyB
|
|
|
- * }
|
|
|
- * ```
|
|
|
- *
|
|
|
- * Additional options include:
|
|
|
- * - check: channel to listen to for collision candidates (default: `collisions:candidates`). set to `true` to force check every pair of bodies in the world
|
|
|
- * - channel: channel to publish events to (default: `collisions:detected`)
|
|
|
- **/
|
|
|
-Physics.behavior('body-collision-detection', function( parent ){
|
|
|
-
|
|
|
- var supportFnStack = [];
|
|
|
-
|
|
|
- /*
|
|
|
- * getSupportFn( bodyA, bodyB ) -> Function
|
|
|
- * - bodyA (Object): First body
|
|
|
- * - bodyB (Object): Second body
|
|
|
- * + (Function): The support function
|
|
|
- *
|
|
|
- * Get a general support function for use with GJK algorithm
|
|
|
- */
|
|
|
- var getSupportFn = function getSupportFn( bodyA, bodyB ){
|
|
|
-
|
|
|
- var hash = Physics.util.pairHash( bodyA.uid, bodyB.uid )
|
|
|
- ,fn = supportFnStack[ hash ]
|
|
|
- ;
|
|
|
-
|
|
|
- if ( !fn ){
|
|
|
- fn = supportFnStack[ hash ] = function pairSupportFunction( searchDir ){
|
|
|
-
|
|
|
- var tA = fn.tA
|
|
|
- ,tB = fn.tB
|
|
|
- ,vA = fn.tmpv1
|
|
|
- ,vB = fn.tmpv2
|
|
|
- ;
|
|
|
-
|
|
|
- if ( fn.useCore ){
|
|
|
- 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 );
|
|
|
- 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 {
|
|
|
- a: vA.values(),
|
|
|
- b: vB.values(),
|
|
|
- pt: vA.vsub( vB ).values()
|
|
|
- };
|
|
|
- };
|
|
|
-
|
|
|
- // 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.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;
|
|
|
-
|
|
|
- return fn;
|
|
|
- };
|
|
|
-
|
|
|
- /*
|
|
|
- * checkGJK( bodyA, bodyB ) -> Object
|
|
|
- * - bodyA (Object): First body
|
|
|
- * - bodyB (Object): Second body
|
|
|
- * + (Object): Collision result
|
|
|
- *
|
|
|
- * Use GJK algorithm to check arbitrary bodies for collisions
|
|
|
- */
|
|
|
- var checkGJK = function checkGJK( bodyA, bodyB ){
|
|
|
-
|
|
|
- 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 )
|
|
|
- ,aabbB = bodyB.aabb()
|
|
|
- ,dimB = Math.min( aabbB.hw, aabbB.hh )
|
|
|
- ;
|
|
|
-
|
|
|
- // just check the overlap first
|
|
|
- support = getSupportFn( bodyA, bodyB );
|
|
|
- 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 ){
|
|
|
-
|
|
|
- // there is a collision. let's do more work.
|
|
|
- collision = {
|
|
|
- bodyA: bodyA,
|
|
|
- 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 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 += inc;
|
|
|
- }
|
|
|
- if ( support.marginB < dimB ){
|
|
|
- support.marginB += inc;
|
|
|
- }
|
|
|
-
|
|
|
- result = Physics.gjk(support, d);
|
|
|
- }
|
|
|
-
|
|
|
- if ( result.overlap || result.maxIterationsReached ){
|
|
|
- // This implementation can't deal with a core overlap yet
|
|
|
- return scratch.done(false);
|
|
|
- }
|
|
|
-
|
|
|
- // calc overlap
|
|
|
- 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.marginA ).vadd( tmp.clone( result.closest.a ) ).vsub( bodyA.state.pos ).values();
|
|
|
- }
|
|
|
-
|
|
|
- return scratch.done( collision );
|
|
|
- };
|
|
|
-
|
|
|
- /*
|
|
|
- * checkCircles( bodyA, bodyB ) -> Object
|
|
|
- * - bodyA (Object): First body
|
|
|
- * - bodyB (Object): Second body
|
|
|
- * + (Object): Collision result
|
|
|
- *
|
|
|
- * Check two circles for collisions.
|
|
|
- */
|
|
|
- var checkCircles = function checkCircles( bodyA, bodyB ){
|
|
|
-
|
|
|
- var scratch = Physics.scratchpad()
|
|
|
- ,d = scratch.vector()
|
|
|
- ,tmp = scratch.vector()
|
|
|
- ,overlap
|
|
|
- ,collision = false
|
|
|
- ;
|
|
|
-
|
|
|
- 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
|
|
|
- if ( d.equals( Physics.vector.zero ) ){
|
|
|
-
|
|
|
- d.set( 1, 0 );
|
|
|
- }
|
|
|
-
|
|
|
- if ( overlap <= 0 ){
|
|
|
-
|
|
|
- collision = {
|
|
|
- bodyA: bodyA,
|
|
|
- bodyB: bodyB,
|
|
|
- norm: d.normalize().values(),
|
|
|
- mtv: d.mult( -overlap ).values(),
|
|
|
- pos: d.mult( -bodyA.geometry.radius/overlap ).vadd( tmp ).values(),
|
|
|
- overlap: -overlap
|
|
|
- };
|
|
|
- }
|
|
|
-
|
|
|
- return scratch.done( collision );
|
|
|
- };
|
|
|
-
|
|
|
- /*
|
|
|
- * checkPair( bodyA, bodyB[, disp] ) -> Object
|
|
|
- * - bodyA (Object): First body
|
|
|
- * - bodyB (Object): Second body
|
|
|
- * + (Object): Collision result
|
|
|
- *
|
|
|
- * Check a pair for collisions
|
|
|
- */
|
|
|
- var checkPair = function checkPair( bodyA, bodyB ){
|
|
|
-
|
|
|
- // filter out bodies that don't collide with each other
|
|
|
- if (
|
|
|
- ( bodyA.treatment === 'static' || bodyA.treatment === 'kinematic' ) &&
|
|
|
- ( bodyB.treatment === 'static' || bodyB.treatment === 'kinematic' )
|
|
|
- ){
|
|
|
- return false;
|
|
|
- }
|
|
|
-
|
|
|
- if ( bodyA.geometry.name === 'circle' && bodyB.geometry.name === 'circle' ){
|
|
|
-
|
|
|
- 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 );
|
|
|
- }
|
|
|
- };
|
|
|
-
|
|
|
- var defaults = {
|
|
|
-
|
|
|
- // channel to listen to for collision candidates
|
|
|
- // set to "true" to force check every pair of bodies in the world
|
|
|
- check: 'collisions:candidates',
|
|
|
-
|
|
|
- // channel to publish events to
|
|
|
- channel: 'collisions:detected'
|
|
|
- };
|
|
|
-
|
|
|
- return {
|
|
|
-
|
|
|
- // extended
|
|
|
- init: function( options ){
|
|
|
-
|
|
|
- parent.init.call( this );
|
|
|
- this.options.defaults( defaults );
|
|
|
- this.options( options );
|
|
|
- },
|
|
|
-
|
|
|
- // extended
|
|
|
- connect: function( world ){
|
|
|
-
|
|
|
- if ( this.options.check === true ){
|
|
|
-
|
|
|
- world.on( 'integrate:velocities', this.checkAll, this );
|
|
|
-
|
|
|
- } else {
|
|
|
-
|
|
|
- world.on( this.options.check, this.check, this );
|
|
|
- }
|
|
|
- },
|
|
|
-
|
|
|
- // extended
|
|
|
- disconnect: function( world ){
|
|
|
-
|
|
|
- if ( this.options.check === true ){
|
|
|
-
|
|
|
- world.off( 'integrate:velocities', this.checkAll, this );
|
|
|
-
|
|
|
- } else {
|
|
|
-
|
|
|
- world.off( this.options.check, this.check, this );
|
|
|
- }
|
|
|
- },
|
|
|
-
|
|
|
- /** internal
|
|
|
- * BodyCollisionDetectionBehavior#check( data )
|
|
|
- * - data (Object): The event data
|
|
|
- *
|
|
|
- * Event callback to check pairs of objects that have been flagged by broad phase for possible collisions.
|
|
|
- **/
|
|
|
- check: function( data ){
|
|
|
-
|
|
|
- var candidates = data.candidates
|
|
|
- ,pair
|
|
|
- ,targets = this.getTargets()
|
|
|
- ,collisions = []
|
|
|
- ,ret
|
|
|
- ,prevContacts = this.prevContacts || {}
|
|
|
- ,contactList = {}
|
|
|
- ,pairHash = Physics.util.pairHash
|
|
|
- ,hash
|
|
|
- ;
|
|
|
-
|
|
|
- for ( var i = 0, l = candidates.length; i < l; ++i ){
|
|
|
-
|
|
|
- pair = candidates[ i ];
|
|
|
-
|
|
|
- if ( targets === this._world._bodies ||
|
|
|
- // only check if the members are targeted by this behavior
|
|
|
- (Physics.util.indexOf( targets, pair.bodyA ) > -1) &&
|
|
|
- (Physics.util.indexOf( targets, pair.bodyB ) > -1)
|
|
|
- ){
|
|
|
- ret = checkPair( pair.bodyA, pair.bodyB );
|
|
|
-
|
|
|
- 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, {
|
|
|
- collisions: collisions
|
|
|
- });
|
|
|
- }
|
|
|
- },
|
|
|
-
|
|
|
- /** internal
|
|
|
- * BodyCollisionDetectionBehavior#checkAll( data )
|
|
|
- * - data (Object): The event data
|
|
|
- *
|
|
|
- * Event callback to check all pairs of objects in the list for collisions
|
|
|
- **/
|
|
|
- checkAll: function( data ){
|
|
|
-
|
|
|
- var bodies = this.getTargets()
|
|
|
- ,dt = data.dt
|
|
|
- ,bodyA
|
|
|
- ,bodyB
|
|
|
- ,collisions = []
|
|
|
- ,ret
|
|
|
- ,prevContacts = this.prevContacts || {}
|
|
|
- ,contactList = {}
|
|
|
- ,pairHash = Physics.util.pairHash
|
|
|
- ,hash
|
|
|
- ;
|
|
|
-
|
|
|
- for ( var j = 0, l = bodies.length; j < l; j++ ){
|
|
|
-
|
|
|
- bodyA = bodies[ j ];
|
|
|
-
|
|
|
- for ( var i = j + 1; i < l; i++ ){
|
|
|
-
|
|
|
- bodyB = bodies[ i ];
|
|
|
-
|
|
|
- ret = checkPair( bodyA, bodyB );
|
|
|
-
|
|
|
- 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, {
|
|
|
- collisions: collisions
|
|
|
- });
|
|
|
- }
|
|
|
- }
|
|
|
- };
|
|
|
-
|
|
|
-});
|
|
|
-
|
|
|
-
|
|
|
-// ---
|
|
|
-// inside: src/behaviors/body-impulse-response.js
|
|
|
-
|
|
|
-/**
|
|
|
- * class BodyImpulseResponseBehavior < Behavior
|
|
|
- *
|
|
|
- * `Physics.behavior('body-impulse-response')`.
|
|
|
- *
|
|
|
- * Responds to collisions by applying impulses.
|
|
|
- *
|
|
|
- * 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
|
|
|
- init: function( options ){
|
|
|
-
|
|
|
- parent.init.call( this );
|
|
|
- this.options.defaults( defaults );
|
|
|
- this.options( options );
|
|
|
-
|
|
|
- this._bodyList = [];
|
|
|
- },
|
|
|
-
|
|
|
- // no applyTo method
|
|
|
- applyTo: false,
|
|
|
-
|
|
|
- // extended
|
|
|
- connect: function( world ){
|
|
|
-
|
|
|
- world.on( this.options.check, this.respond, this );
|
|
|
- },
|
|
|
-
|
|
|
- // extended
|
|
|
- disconnect: function( world ){
|
|
|
-
|
|
|
- world.off( this.options.check, this.respond, this );
|
|
|
- },
|
|
|
-
|
|
|
- /** internal
|
|
|
- * BodyImpulseResponseBehavior#collideBodes( bodyA, bodyB, normal, point, mtrans, contact )
|
|
|
- * - bodyA (Object): First Body
|
|
|
- * - bodyB (Object): Second body
|
|
|
- * - normal (Vector): Normal vector of the collision surface
|
|
|
- * - point (Vector): Contact point of the collision
|
|
|
- * - mtrans (Vector): Minimum transit vector that is the smallest displacement to separate the bodies
|
|
|
- * - contact (Boolean): Are the bodies in resting contact relative to each other
|
|
|
- *
|
|
|
- * Collide two bodies by modifying their positions and velocities to conserve momentum
|
|
|
- **/
|
|
|
- collideBodies: function(bodyA, bodyB, normal, point, mtrans, contact){
|
|
|
-
|
|
|
- var fixedA = bodyA.treatment === 'static' || bodyA.treatment === 'kinematic'
|
|
|
- ,fixedB = bodyB.treatment === 'static' || bodyB.treatment === 'kinematic'
|
|
|
- ,scratch = Physics.scratchpad()
|
|
|
- // minimum transit vector for each body
|
|
|
- ,mtv = scratch.vector().clone( mtrans )
|
|
|
- ;
|
|
|
-
|
|
|
- // do nothing if both are fixed
|
|
|
- if ( fixedA && fixedB ){
|
|
|
- scratch.done();
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- // inverse masses and moments of inertia.
|
|
|
- // give fixed bodies infinite mass and moi
|
|
|
- var invMoiA = fixedA ? 0 : 1 / bodyA.moi
|
|
|
- ,invMoiB = fixedB ? 0 : 1 / bodyB.moi
|
|
|
- ,invMassA = fixedA ? 0 : 1 / bodyA.mass
|
|
|
- ,invMassB = fixedB ? 0 : 1 / bodyB.mass
|
|
|
- // coefficient of restitution between bodies
|
|
|
- ,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 )
|
|
|
- ,angVelA = bodyA.state.angular.vel
|
|
|
- ,angVelB = bodyB.state.angular.vel
|
|
|
- // relative velocity towards B at collision point
|
|
|
- ,vAB = scratch.vector().clone( bodyB.state.vel )
|
|
|
- .vadd( tmp.clone(rB).perp().mult( angVelB ) )
|
|
|
- .vsub( bodyA.state.vel )
|
|
|
- .vsub( tmp.clone(rA).perp().mult( angVelA ) )
|
|
|
- // break up components along normal and perp-normal directions
|
|
|
- ,rAproj = rA.proj( n )
|
|
|
- ,rAreg = rA.proj( perp )
|
|
|
- ,rBproj = rB.proj( n )
|
|
|
- ,rBreg = rB.proj( perp )
|
|
|
- ,vproj = vAB.proj( n ) // projection of vAB along n
|
|
|
- ,vreg = vAB.proj( perp ) // rejection of vAB along n (perp of proj)
|
|
|
- ,impulse
|
|
|
- ,sign
|
|
|
- ,max
|
|
|
- ,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();
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- invMoiA = invMoiA === Infinity ? 0 : invMoiA;
|
|
|
- invMoiB = invMoiB === Infinity ? 0 : invMoiB;
|
|
|
-
|
|
|
- impulse = - ((1 + cor) * vproj) / ( invMassA + invMassB + (invMoiA * rAreg * rAreg) + (invMoiB * rBreg * rBreg) );
|
|
|
- // vproj += impulse * ( invMass + (invMoi * rreg * rreg) );
|
|
|
- // angVel -= impulse * rreg * invMoi;
|
|
|
-
|
|
|
-
|
|
|
- if ( fixedA ){
|
|
|
-
|
|
|
- // apply impulse
|
|
|
- bodyB.state.vel.vadd( n.mult( impulse * invMassB ) );
|
|
|
- bodyB.state.angular.vel -= impulse * invMoiB * rBreg;
|
|
|
-
|
|
|
- } else if ( fixedB ){
|
|
|
-
|
|
|
- // apply impulse
|
|
|
- bodyA.state.vel.vsub( n.mult( impulse * invMassA ) );
|
|
|
- bodyA.state.angular.vel += impulse * invMoiA * rAreg;
|
|
|
-
|
|
|
- } else {
|
|
|
-
|
|
|
- // apply impulse
|
|
|
- bodyB.state.vel.vadd( n.mult( impulse * invMassB ) );
|
|
|
- bodyB.state.angular.vel -= impulse * invMoiB * rBreg;
|
|
|
- bodyA.state.vel.vsub( n.mult( invMassA * bodyB.mass ) );
|
|
|
- bodyA.state.angular.vel += impulse * invMoiA * rAreg;
|
|
|
- }
|
|
|
-
|
|
|
- // inContact = (impulse < 0.004);
|
|
|
-
|
|
|
- // if we have friction and a relative velocity perpendicular to the normal
|
|
|
- if ( cof && vreg ){
|
|
|
-
|
|
|
-
|
|
|
- // TODO: here, we could first assume static friction applies
|
|
|
- // and that the tangential relative velocity is zero.
|
|
|
- // Then we could calculate the impulse and check if the
|
|
|
- // tangential impulse is less than that allowed by static
|
|
|
- // friction. If not, _then_ apply kinetic friction.
|
|
|
-
|
|
|
- // instead we're just applying kinetic friction and making
|
|
|
- // sure the impulse we apply is less than the maximum
|
|
|
- // allowed amount
|
|
|
-
|
|
|
- // maximum impulse allowed by kinetic friction
|
|
|
- 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 = cof * Math.abs( impulse );
|
|
|
- // constrain the impulse within the "friction cone" ( max < mu * impulse)
|
|
|
- impulse = Math.min( impulse, max );
|
|
|
- impulse *= sign;
|
|
|
-
|
|
|
- if ( fixedA ){
|
|
|
-
|
|
|
- // apply frictional impulse
|
|
|
- bodyB.state.vel.vsub( perp.mult( impulse * invMassB ) );
|
|
|
- bodyB.state.angular.vel -= impulse * invMoiB * rBproj;
|
|
|
-
|
|
|
- } else if ( fixedB ){
|
|
|
-
|
|
|
- // apply frictional impulse
|
|
|
- bodyA.state.vel.vadd( perp.mult( impulse * invMassA ) );
|
|
|
- bodyA.state.angular.vel += impulse * invMoiA * rAproj;
|
|
|
-
|
|
|
- } else {
|
|
|
-
|
|
|
- // apply frictional impulse
|
|
|
- bodyB.state.vel.vsub( perp.mult( impulse * invMassB ) );
|
|
|
- bodyB.state.angular.vel -= impulse * invMoiB * rBproj;
|
|
|
- bodyA.state.vel.vadd( perp.mult( invMassA * bodyB.mass ) );
|
|
|
- bodyA.state.angular.vel += impulse * invMoiA * rAproj;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // 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
|
|
|
- *
|
|
|
- * Event callback to respond to collision data.
|
|
|
- **/
|
|
|
- respond: function( data ){
|
|
|
-
|
|
|
- var self = this
|
|
|
- ,col
|
|
|
- ,collisions = data.collisions// Physics.util.shuffle(data.collisions)
|
|
|
- ,i,l,b
|
|
|
- ;
|
|
|
-
|
|
|
- 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.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();
|
|
|
- }
|
|
|
- }
|
|
|
- };
|
|
|
-});
|
|
|
-
|
|
|
-
|
|
|
-// ---
|
|
|
-// inside: src/behaviors/constant-acceleration.js
|
|
|
-
|
|
|
-/**
|
|
|
- * class ConstantAccelerationBehavior < Behavior
|
|
|
- *
|
|
|
- * `Physics.behavior('constant-acceleration')`.
|
|
|
- *
|
|
|
- * Constant acceleration behavior.
|
|
|
- *
|
|
|
- * Basically the "gravity" behavior. Used to give "earth-like gravity" to the world.
|
|
|
- *
|
|
|
- * Additional options include:
|
|
|
- * - acc: The acceleration vector (Vectorish). (default: `{ x: 0, y: 0.0004 }`)
|
|
|
- **/
|
|
|
-Physics.behavior('constant-acceleration', function( parent ){
|
|
|
-
|
|
|
- var defaults = {
|
|
|
-
|
|
|
- acc: { x : 0, y: 0.0004 }
|
|
|
- };
|
|
|
-
|
|
|
- return {
|
|
|
-
|
|
|
- // extended
|
|
|
- init: function( options ){
|
|
|
-
|
|
|
- parent.init.call( this );
|
|
|
- this.options.defaults( defaults );
|
|
|
- this.options( options );
|
|
|
-
|
|
|
- // extend options
|
|
|
- this._acc = new Physics.vector();
|
|
|
- this.setAcceleration( this.options.acc );
|
|
|
- delete this.options.acc;
|
|
|
- },
|
|
|
-
|
|
|
- /**
|
|
|
- * ConstantAccelerationBehavior#setAcceleration( acc ) -> this
|
|
|
- * - acc (Vectorish): The acceleration vector
|
|
|
- *
|
|
|
- * Set the acceleration of the behavior.
|
|
|
- **/
|
|
|
- setAcceleration: function( acc ){
|
|
|
-
|
|
|
- this._acc.clone( acc );
|
|
|
- return this;
|
|
|
- },
|
|
|
-
|
|
|
- // extended
|
|
|
- behave: function( data ){
|
|
|
-
|
|
|
- var bodies = this.getTargets();
|
|
|
-
|
|
|
- for ( var i = 0, l = bodies.length; i < l; ++i ){
|
|
|
-
|
|
|
- bodies[ i ].accelerate( this._acc );
|
|
|
- }
|
|
|
- }
|
|
|
- };
|
|
|
-});
|
|
|
-
|
|
|
-// ---
|
|
|
-// inside: src/behaviors/edge-collision-detection.js
|
|
|
-
|
|
|
-/**
|
|
|
- * class EdgeCollisionDetectionBehavior < Behavior
|
|
|
- *
|
|
|
- * `Physics.behavior('edge-collision-detection')`.
|
|
|
- *
|
|
|
- * Used to detect collisions with the boundaries of an AABB.
|
|
|
- *
|
|
|
- * Additional options include:
|
|
|
- * - aabb: The [[Physics.aabb]] bounds to use as the constraining boundary
|
|
|
- * - restitution: The restitution of the boundary walls (default: `0.99`)
|
|
|
- * - cof: The coefficient of friction of the boundary walls (default: `1`)
|
|
|
- * - channel: The channel to publish collisions to. (default: 'collisions:detected')
|
|
|
- **/
|
|
|
-Physics.behavior('edge-collision-detection', function( parent ){
|
|
|
-
|
|
|
- /*
|
|
|
- * checkGeneral( body, bounds, dummy ) -> Array
|
|
|
- * - body (Body): The body to check
|
|
|
- * - 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 ){
|
|
|
-
|
|
|
- var overlap
|
|
|
- ,aabb = body.aabb()
|
|
|
- ,scratch = Physics.scratchpad()
|
|
|
- ,offset = body.getGlobalOffset( scratch.vector() )
|
|
|
- ,trans = scratch.transform()
|
|
|
- ,dir = scratch.vector()
|
|
|
- ,result = scratch.vector()
|
|
|
- ,collision = false
|
|
|
- ,collisions = []
|
|
|
- ;
|
|
|
-
|
|
|
- // right
|
|
|
- overlap = (aabb.x + aabb.hw) - bounds.max.x;
|
|
|
-
|
|
|
- if ( overlap >= 0 ){
|
|
|
-
|
|
|
- dir.set( 1, 0 ).rotateInv( trans.setRotation( body.state.angular.pos ) );
|
|
|
-
|
|
|
- collision = {
|
|
|
- bodyA: body,
|
|
|
- bodyB: dummy,
|
|
|
- overlap: overlap,
|
|
|
- norm: {
|
|
|
- x: 1,
|
|
|
- y: 0
|
|
|
- },
|
|
|
- mtv: {
|
|
|
- x: overlap,
|
|
|
- y: 0
|
|
|
- },
|
|
|
- pos: body.geometry.getFarthestHullPoint( dir, result ).rotate( trans ).vadd( offset ).values()
|
|
|
- };
|
|
|
-
|
|
|
- collisions.push(collision);
|
|
|
- }
|
|
|
-
|
|
|
- // bottom
|
|
|
- overlap = (aabb.y + aabb.hh) - bounds.max.y;
|
|
|
-
|
|
|
- if ( overlap >= 0 ){
|
|
|
-
|
|
|
- dir.set( 0, 1 ).rotateInv( trans.setRotation( body.state.angular.pos ) );
|
|
|
-
|
|
|
- collision = {
|
|
|
- bodyA: body,
|
|
|
- bodyB: dummy,
|
|
|
- overlap: overlap,
|
|
|
- norm: {
|
|
|
- x: 0,
|
|
|
- y: 1
|
|
|
- },
|
|
|
- mtv: {
|
|
|
- x: 0,
|
|
|
- y: overlap
|
|
|
- },
|
|
|
- pos: body.geometry.getFarthestHullPoint( dir, result ).rotate( trans ).vadd( offset ).values()
|
|
|
- };
|
|
|
-
|
|
|
- collisions.push(collision);
|
|
|
- }
|
|
|
-
|
|
|
- // left
|
|
|
- overlap = bounds.min.x - (aabb.x - aabb.hw);
|
|
|
-
|
|
|
- if ( overlap >= 0 ){
|
|
|
-
|
|
|
- dir.set( -1, 0 ).rotateInv( trans.setRotation( body.state.angular.pos ) );
|
|
|
-
|
|
|
- collision = {
|
|
|
- bodyA: body,
|
|
|
- bodyB: dummy,
|
|
|
- overlap: overlap,
|
|
|
- norm: {
|
|
|
- x: -1,
|
|
|
- y: 0
|
|
|
- },
|
|
|
- mtv: {
|
|
|
- x: -overlap,
|
|
|
- y: 0
|
|
|
- },
|
|
|
- pos: body.geometry.getFarthestHullPoint( dir, result ).rotate( trans ).vadd( offset ).values()
|
|
|
- };
|
|
|
-
|
|
|
- collisions.push(collision);
|
|
|
- }
|
|
|
-
|
|
|
- // top
|
|
|
- overlap = bounds.min.y - (aabb.y - aabb.hh);
|
|
|
-
|
|
|
- if ( overlap >= 0 ){
|
|
|
-
|
|
|
- dir.set( 0, -1 ).rotateInv( trans.setRotation( body.state.angular.pos ) );
|
|
|
-
|
|
|
- collision = {
|
|
|
- bodyA: body,
|
|
|
- bodyB: dummy,
|
|
|
- overlap: overlap,
|
|
|
- norm: {
|
|
|
- x: 0,
|
|
|
- y: -1
|
|
|
- },
|
|
|
- mtv: {
|
|
|
- x: 0,
|
|
|
- y: -overlap
|
|
|
- },
|
|
|
- pos: body.geometry.getFarthestHullPoint( dir, result ).rotate( trans ).vadd( offset ).values()
|
|
|
- };
|
|
|
-
|
|
|
- collisions.push(collision);
|
|
|
- }
|
|
|
-
|
|
|
- scratch.done();
|
|
|
- return collisions;
|
|
|
- };
|
|
|
-
|
|
|
- /*
|
|
|
- * checkEdgeCollide( body, bounds, dummy ) -> Array
|
|
|
- * - body (Body): The body to check
|
|
|
- * - 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 ){
|
|
|
-
|
|
|
- return checkGeneral( body, bounds, dummy );
|
|
|
- };
|
|
|
-
|
|
|
- var defaults = {
|
|
|
-
|
|
|
- aabb: null,
|
|
|
- restitution: 0.99,
|
|
|
- cof: 1.0,
|
|
|
- channel: 'collisions:detected'
|
|
|
- };
|
|
|
-
|
|
|
- return {
|
|
|
-
|
|
|
- // extended
|
|
|
- init: function( options ){
|
|
|
-
|
|
|
- parent.init.call( this );
|
|
|
- this.options.defaults( defaults );
|
|
|
- this.options( options );
|
|
|
-
|
|
|
- this.setAABB( this.options.aabb );
|
|
|
- this.restitution = this.options.restitution;
|
|
|
-
|
|
|
- this.body = Physics.body('point', {
|
|
|
- treatment: 'static',
|
|
|
- restitution: this.options.restitution,
|
|
|
- cof: this.options.cof
|
|
|
- });
|
|
|
- },
|
|
|
-
|
|
|
- /**
|
|
|
- * EdgeCollisionDetectionBehavior#setAABB( aabb ) -> this
|
|
|
- * - aabb (Physics.aabb): The aabb to use as the boundary
|
|
|
- *
|
|
|
- * Set the boundaries of the edge.
|
|
|
- **/
|
|
|
- setAABB: function( aabb ){
|
|
|
-
|
|
|
- if (!aabb) {
|
|
|
- throw 'Error: aabb not set';
|
|
|
- }
|
|
|
-
|
|
|
- this._edges = {
|
|
|
- min: {
|
|
|
- x: (aabb.x - aabb.hw),
|
|
|
- y: (aabb.y - aabb.hh)
|
|
|
- },
|
|
|
- max: {
|
|
|
- x: (aabb.x + aabb.hw),
|
|
|
- y: (aabb.y + aabb.hh)
|
|
|
- }
|
|
|
- };
|
|
|
-
|
|
|
- return this;
|
|
|
- },
|
|
|
-
|
|
|
- // extended
|
|
|
- connect: function( world ){
|
|
|
-
|
|
|
- world.on( 'integrate:positions', this.checkAll, this, 2 );
|
|
|
- },
|
|
|
-
|
|
|
- // extended
|
|
|
- disconnect: function( world ){
|
|
|
-
|
|
|
- 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
|
|
|
- ,collisions = []
|
|
|
- ,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++ ){
|
|
|
-
|
|
|
- body = bodies[ i ];
|
|
|
-
|
|
|
- // 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, {
|
|
|
- collisions: collisions
|
|
|
- });
|
|
|
- }
|
|
|
- }
|
|
|
- };
|
|
|
-
|
|
|
-});
|
|
|
-
|
|
|
-
|
|
|
-// ---
|
|
|
-// inside: src/behaviors/interactive.js
|
|
|
-
|
|
|
-/**
|
|
|
- * class InteractiveBehavior < Behavior
|
|
|
- *
|
|
|
- * `Physics.behavior('interactive')`.
|
|
|
- *
|
|
|
- * User interaction helper.
|
|
|
- *
|
|
|
- * 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.
|
|
|
- * - moveThrottle: The min time between move events (default: `10`).
|
|
|
- * - minVel: The minimum velocity clamp [[Vectorish]] (default: { x: -5, y: -5 }) to restrict velocity a user can give to a body
|
|
|
- * - maxVel: The maximum velocity clamp [[Vectorish]] (default: { x: 5, y: 5 }) to restrict velocity a user can give to a body
|
|
|
- *
|
|
|
- * The behavior also triggers the following events on the world:
|
|
|
- * ```javascript
|
|
|
- * // a body has been grabbed
|
|
|
- * world.on('interact:grab', function( data ){
|
|
|
- * data.x; // the x coord
|
|
|
- * data.y; // the y coord
|
|
|
- * data.body; // the body that was grabbed
|
|
|
- * });
|
|
|
- * // no body was grabbed, but the renderer area was clicked, or touched
|
|
|
- * world.on('interact:poke', function( data ){
|
|
|
- * 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 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 ){
|
|
|
-
|
|
|
- if ( !document ){
|
|
|
- // must be in node environment
|
|
|
- return {};
|
|
|
- }
|
|
|
-
|
|
|
- var defaults = {
|
|
|
- // the element to monitor
|
|
|
- el: null,
|
|
|
- // time between move events
|
|
|
- moveThrottle: 1000 / 100 | 0,
|
|
|
- // minimum velocity clamp
|
|
|
- minVel: { x: -5, y: -5 },
|
|
|
- // maximum velocity clamp
|
|
|
- maxVel: { x: 5, y: 5 }
|
|
|
- }
|
|
|
- ,getElementOffset = function( el ){
|
|
|
- var curleft = 0
|
|
|
- ,curtop = 0
|
|
|
- ;
|
|
|
-
|
|
|
- if (el.offsetParent) {
|
|
|
- do {
|
|
|
- curleft += el.offsetLeft;
|
|
|
- curtop += el.offsetTop;
|
|
|
- } while (el = el.offsetParent);
|
|
|
- }
|
|
|
-
|
|
|
- return { left: curleft, top: curtop };
|
|
|
- }
|
|
|
- ;
|
|
|
-
|
|
|
- return {
|
|
|
- // extended
|
|
|
- init: function( options ){
|
|
|
-
|
|
|
- var self = this;
|
|
|
-
|
|
|
- // call parent init method
|
|
|
- parent.init.call( this );
|
|
|
- this.options.defaults( defaults );
|
|
|
- this.options( options );
|
|
|
-
|
|
|
- // vars
|
|
|
- this.bodyData = {};
|
|
|
- this.bodyDataByUID = {};
|
|
|
-
|
|
|
- this.el = typeof this.options.el === 'string' ? document.getElementById(this.options.el) : this.options.el;
|
|
|
-
|
|
|
- if ( !this.el ){
|
|
|
- throw "No DOM element specified";
|
|
|
- }
|
|
|
-
|
|
|
- // init events
|
|
|
- // 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 ){
|
|
|
-
|
|
|
- // Adjust for PointerEvent and older browsers
|
|
|
- if ( !e.changedTouches ) {
|
|
|
- e.changedTouches = [ e ];
|
|
|
- }
|
|
|
-
|
|
|
- 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);
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- };
|
|
|
-
|
|
|
- // 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._world ){
|
|
|
-
|
|
|
- // Adjust for PointerEvent and older browsers
|
|
|
- if ( !e.changedTouches ) {
|
|
|
- e.changedTouches = [ e ];
|
|
|
- }
|
|
|
-
|
|
|
- offset = getElementOffset( self.el );
|
|
|
-
|
|
|
- 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.options.moveThrottle);
|
|
|
-
|
|
|
- // 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
|
|
|
- ,touchId
|
|
|
- ,touch
|
|
|
- ,offset
|
|
|
- ,data
|
|
|
- ,dt
|
|
|
- ,touchIndex
|
|
|
- ,l
|
|
|
- ;
|
|
|
-
|
|
|
- if ( self._world ){
|
|
|
-
|
|
|
- // 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];
|
|
|
- }
|
|
|
- }
|
|
|
- };
|
|
|
- },
|
|
|
-
|
|
|
- // extended
|
|
|
- connect: function( world ){
|
|
|
-
|
|
|
- // 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, 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
|
|
|
- behave: function( data ){
|
|
|
-
|
|
|
- var self = this
|
|
|
- ,state
|
|
|
- ,dt = Math.max(data.dt, self.options.moveThrottle)
|
|
|
- ,body
|
|
|
- ,d
|
|
|
- ;
|
|
|
-
|
|
|
- // 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 );
|
|
|
- }
|
|
|
- }
|
|
|
- };
|
|
|
-});
|
|
|
-
|
|
|
-
|
|
|
-// ---
|
|
|
-// inside: src/behaviors/newtonian.js
|
|
|
-
|
|
|
-/**
|
|
|
- * class NewtonianBehavior < Behavior
|
|
|
- *
|
|
|
- * `Physics.behavior('newtonian')`.
|
|
|
- *
|
|
|
- * Newtonian attraction between bodies (inverse square law).
|
|
|
- *
|
|
|
- * Additional options include:
|
|
|
- * - strength: The strength of the interaction between bodies. (default: `1`)
|
|
|
- * - max: The maximum distance between bodies at which to apply the behavior. (default: `false`... infinite)
|
|
|
- * - min: The minimum distance between bodies at which to apply the behavior. (default: `false`... autocalculate)
|
|
|
- **/
|
|
|
-Physics.behavior('newtonian', function( parent ){
|
|
|
-
|
|
|
- var defaults = {
|
|
|
-
|
|
|
- strength: 1,
|
|
|
- // max distance to apply it to
|
|
|
- max: false, // infinite
|
|
|
- // min distance to apply it to
|
|
|
- min: false // auto calc
|
|
|
- };
|
|
|
-
|
|
|
- return {
|
|
|
-
|
|
|
- // extended
|
|
|
- init: function( options ){
|
|
|
-
|
|
|
- var self = this;
|
|
|
- // call parent init method
|
|
|
- parent.init.call( this );
|
|
|
- this.options.defaults( defaults );
|
|
|
- this.options.onChange(function( opts ){
|
|
|
- self._maxDistSq = opts.max === false ? Infinity : opts.max * opts.max;
|
|
|
- self._minDistSq = opts.min ? opts.min * opts.min : 100 * opts.strength;
|
|
|
- });
|
|
|
- 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
|
|
|
- ,scratch = Physics.scratchpad()
|
|
|
- ,potential = scratch.vector()
|
|
|
- ,comp
|
|
|
- ,bodyA
|
|
|
- ,bodyB
|
|
|
- ,posA = scratch.vector()
|
|
|
- ,posB = scratch.vector()
|
|
|
- ,i, j, k, m, l, ll, lll
|
|
|
- ;
|
|
|
-
|
|
|
- for ( j = 0, l = bodies.length; j < l; j++ ){
|
|
|
-
|
|
|
- body = bodies[ j ];
|
|
|
-
|
|
|
- for ( i = j + 1; i < l; i++ ){
|
|
|
-
|
|
|
- other = bodies[ i ];
|
|
|
-
|
|
|
- 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() );
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- } else {
|
|
|
-
|
|
|
- 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;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- scratch.done();
|
|
|
- }
|
|
|
- };
|
|
|
-});
|
|
|
-
|
|
|
-
|
|
|
-// ---
|
|
|
-// inside: src/behaviors/sweep-prune.js
|
|
|
-
|
|
|
-/**
|
|
|
- * class SweepPruneBehavior < Behavior
|
|
|
- *
|
|
|
- * `Physics.behavior('sweep-prune')`.
|
|
|
- *
|
|
|
- * Sweep and Prune implementation for broad phase collision detection.
|
|
|
- *
|
|
|
- * This massively improves the speed of collision detection. It's set up to always be used with [[BodyCollisionDetection]], and [[BodyImpulseResponse]].
|
|
|
- *
|
|
|
- * Additional options include:
|
|
|
- * - channel: The channel to publish collision candidates to. (default: `collisions:candidates`)
|
|
|
- **/
|
|
|
-Physics.behavior('sweep-prune', function( parent ){
|
|
|
-
|
|
|
- var uid = 1;
|
|
|
-
|
|
|
- // Get a unique numeric id for internal use
|
|
|
- var getUniqueId = function getUniqueId(){
|
|
|
-
|
|
|
- return uid++;
|
|
|
- };
|
|
|
-
|
|
|
- // add z: 2 to get this to work in 3D
|
|
|
- var dof = { x: 0, y: 1 }; // degrees of freedom
|
|
|
- // change to "3" to get it to work in 3D
|
|
|
- var maxDof = 2;
|
|
|
-
|
|
|
- var pairHash = Physics.util.pairHash;
|
|
|
-
|
|
|
- return {
|
|
|
-
|
|
|
- // extended
|
|
|
- init: function( options ){
|
|
|
-
|
|
|
- parent.init.call( this );
|
|
|
- this.options.defaults({
|
|
|
- channel: 'collisions:candidates' //default channel
|
|
|
- });
|
|
|
- this.options( options );
|
|
|
-
|
|
|
- this.encounters = [];
|
|
|
- this.candidates = [];
|
|
|
-
|
|
|
- this.clear();
|
|
|
- },
|
|
|
-
|
|
|
- /**
|
|
|
- * SweepPruneBehavior#clear()
|
|
|
- *
|
|
|
- * Refresh tracking data
|
|
|
- **/
|
|
|
- clear: function(){
|
|
|
-
|
|
|
- this.tracked = [];
|
|
|
- this.pairs = []; // pairs selected as candidate collisions by broad phase
|
|
|
- this.intervalLists = []; // stores lists of aabb projection intervals to be sorted
|
|
|
-
|
|
|
- // init intervalLists
|
|
|
- for ( var xyz = 0; xyz < maxDof; ++xyz ){
|
|
|
-
|
|
|
- this.intervalLists[ xyz ] = [];
|
|
|
- }
|
|
|
- },
|
|
|
-
|
|
|
- // extended
|
|
|
- connect: function( world ){
|
|
|
-
|
|
|
- world.on( 'add:body', this.trackBody, this );
|
|
|
- world.on( 'remove:body', this.untrackBody, this );
|
|
|
- world.on( 'integrate:positions', this.sweep, this, 1 );
|
|
|
-
|
|
|
- // add current bodies
|
|
|
- var bodies = world.getBodies();
|
|
|
- for ( var i = 0, l = bodies.length; i < l; ++i ){
|
|
|
-
|
|
|
- this.trackBody({ body: bodies[ i ] });
|
|
|
- }
|
|
|
- },
|
|
|
-
|
|
|
- // extended
|
|
|
- disconnect: function( world ){
|
|
|
-
|
|
|
- world.off( 'add:body', this.trackBody, this );
|
|
|
- world.off( 'remove:body', this.untrackBody, this );
|
|
|
- world.off( 'integrate:positions', this.sweep, this, 1 );
|
|
|
- this.clear();
|
|
|
- },
|
|
|
-
|
|
|
- /** internal
|
|
|
- * SweepPruneBehavior#broadPhase() -> Array
|
|
|
- * + (Array): The candidate data of overlapping aabbs
|
|
|
- *
|
|
|
- * Execute the broad phase and get candidate collisions
|
|
|
- **/
|
|
|
- broadPhase: function(){
|
|
|
-
|
|
|
- this.updateIntervals();
|
|
|
- this.sortIntervalLists();
|
|
|
-
|
|
|
- if ( this._world ){
|
|
|
- this._world.emit('sweep-prune:intervals', this.intervalLists);
|
|
|
- }
|
|
|
-
|
|
|
- return this.checkOverlaps();
|
|
|
- },
|
|
|
-
|
|
|
- /** internal
|
|
|
- * SweepPruneBehavior#sortIntervalLists()
|
|
|
- *
|
|
|
- * Simple insertion sort for each axis
|
|
|
- **/
|
|
|
- sortIntervalLists: function(){
|
|
|
-
|
|
|
- var list
|
|
|
- ,len
|
|
|
- ,i
|
|
|
- ,hole
|
|
|
- ,bound
|
|
|
- ,boundVal
|
|
|
- ,left
|
|
|
- ,leftVal
|
|
|
- ,axis
|
|
|
- ;
|
|
|
-
|
|
|
- // for each axis...
|
|
|
- for ( var xyz = 0; xyz < maxDof; ++xyz ){
|
|
|
-
|
|
|
- // get the intervals for that axis
|
|
|
- list = this.intervalLists[ xyz ];
|
|
|
- i = 0;
|
|
|
- len = list.length;
|
|
|
- axis = xyz;
|
|
|
-
|
|
|
- // for each interval bound...
|
|
|
- while ( (++i) < len ){
|
|
|
-
|
|
|
- // store bound
|
|
|
- bound = list[ i ];
|
|
|
- boundVal = bound.val.get( axis );
|
|
|
- hole = i;
|
|
|
-
|
|
|
- left = list[ hole - 1 ];
|
|
|
- leftVal = left && left.val.get( axis );
|
|
|
-
|
|
|
- // while others are greater than bound...
|
|
|
- while (
|
|
|
- hole > 0 &&
|
|
|
- (
|
|
|
- leftVal > boundVal ||
|
|
|
- // if it's an equality, only move it over if
|
|
|
- // the hole was created by a minimum
|
|
|
- // and the previous is a maximum
|
|
|
- // so that we detect contacts also
|
|
|
- leftVal === boundVal &&
|
|
|
- ( left.type && !bound.type )
|
|
|
- )
|
|
|
- ) {
|
|
|
-
|
|
|
- // move others greater than bound to the right
|
|
|
- list[ hole ] = left;
|
|
|
- hole--;
|
|
|
- left = list[ hole - 1 ];
|
|
|
- leftVal = left && left.val.get( axis );
|
|
|
- }
|
|
|
-
|
|
|
- // insert bound in the hole
|
|
|
- list[ hole ] = bound;
|
|
|
- }
|
|
|
- }
|
|
|
- },
|
|
|
-
|
|
|
- /** internal
|
|
|
- * SweepPruneBehavior#getPair( tr1, tr2, doCreate ) -> Object
|
|
|
- * - tr1 (Object): First tracker
|
|
|
- * - tr2 (Object): Second tracker
|
|
|
- * - doCreate (Boolean): Create if not found
|
|
|
- * + (Object): Pair object or null if not found
|
|
|
- *
|
|
|
- * Get a pair object for the tracker objects
|
|
|
- **/
|
|
|
- getPair: function(tr1, tr2, doCreate){
|
|
|
-
|
|
|
- var hash = pairHash( tr1.id, tr2.id );
|
|
|
-
|
|
|
- if ( hash === false ){
|
|
|
- return null;
|
|
|
- }
|
|
|
-
|
|
|
- var c = this.pairs[ hash ];
|
|
|
-
|
|
|
- if ( !c ){
|
|
|
-
|
|
|
- if ( !doCreate ){
|
|
|
- return null;
|
|
|
- }
|
|
|
-
|
|
|
- c = this.pairs[ hash ] = {
|
|
|
- bodyA: tr1.body,
|
|
|
- bodyB: tr2.body,
|
|
|
- flag: 1
|
|
|
- };
|
|
|
- }
|
|
|
-
|
|
|
- if ( doCreate){
|
|
|
- c.flag = 1;
|
|
|
- }
|
|
|
-
|
|
|
- return c;
|
|
|
- },
|
|
|
-
|
|
|
- // getPair: function(tr1, tr2, doCreate){
|
|
|
-
|
|
|
- // var hash = Math.min(tr1.id, tr2.id) // = pairHash( tr1.id, tr2.id )
|
|
|
- // ,other = Math.max(tr1.id, tr2.id)
|
|
|
- // ,first
|
|
|
- // ,c
|
|
|
- // ;
|
|
|
-
|
|
|
- // if ( hash === false ){
|
|
|
- // return null;
|
|
|
- // }
|
|
|
-
|
|
|
- // first = this.pairs[ hash ];
|
|
|
-
|
|
|
- // if ( !first ){
|
|
|
- // if ( !doCreate ){
|
|
|
- // return null;
|
|
|
- // }
|
|
|
-
|
|
|
- // first = this.pairs[ hash ] = [];
|
|
|
- // }
|
|
|
-
|
|
|
- // c = first[ other ];
|
|
|
-
|
|
|
- // if ( !c ){
|
|
|
-
|
|
|
- // if ( !doCreate ){
|
|
|
- // return null;
|
|
|
- // }
|
|
|
-
|
|
|
- // c = first[ other ] = {
|
|
|
- // bodyA: tr1.body,
|
|
|
- // bodyB: tr2.body,
|
|
|
- // flag: 1
|
|
|
- // };
|
|
|
- // }
|
|
|
-
|
|
|
- // return c;
|
|
|
- // },
|
|
|
-
|
|
|
- /** internal
|
|
|
- * SweepPruneBehavior#checkOverlaps() -> Array
|
|
|
- * + (Array): List of candidate collisions
|
|
|
- *
|
|
|
- * Check each axis for overlaps of bodies AABBs
|
|
|
- **/
|
|
|
- checkOverlaps: function(){
|
|
|
-
|
|
|
- var isX
|
|
|
- ,hash
|
|
|
- ,tr1
|
|
|
- ,tr2
|
|
|
- ,bound
|
|
|
- ,list
|
|
|
- ,len
|
|
|
- ,i
|
|
|
- ,j
|
|
|
- ,c
|
|
|
- // determine which axis is the last we need to check
|
|
|
- ,collisionFlag = 1 << (dof.z + 1) << (dof.y + 1) << (dof.x + 1)
|
|
|
- ,encounters = this.encounters
|
|
|
- ,enclen = 0
|
|
|
- ,candidates = this.candidates
|
|
|
- ;
|
|
|
-
|
|
|
- Physics.util.clearArray( encounters );
|
|
|
- Physics.util.clearArray( candidates );
|
|
|
-
|
|
|
- for ( var xyz = 0; xyz < maxDof; ++xyz ){
|
|
|
-
|
|
|
- // is the x coord
|
|
|
- isX = (xyz === 0);
|
|
|
- // get the interval list for this axis
|
|
|
- list = this.intervalLists[ xyz ];
|
|
|
-
|
|
|
- // for each interval bound
|
|
|
- for ( i = 0, len = list.length; i < len; i++ ){
|
|
|
-
|
|
|
- bound = list[ i ];
|
|
|
- tr1 = bound.tracker;
|
|
|
-
|
|
|
- if ( bound.type ){
|
|
|
-
|
|
|
- // is a max
|
|
|
-
|
|
|
- j = enclen;
|
|
|
-
|
|
|
- for ( j = enclen - 1; j >= 0; j-- ){
|
|
|
-
|
|
|
- tr2 = encounters[ j ];
|
|
|
-
|
|
|
- // if they are the same tracked interval
|
|
|
- if ( tr2 === tr1 ){
|
|
|
-
|
|
|
- // remove the interval from the encounters list
|
|
|
- // faster than .splice()
|
|
|
- if ( j < enclen - 1 ) {
|
|
|
-
|
|
|
- encounters[ j ] = encounters.pop();
|
|
|
-
|
|
|
- } else {
|
|
|
-
|
|
|
- // encountered a max right after a min... no overlap
|
|
|
- encounters.pop();
|
|
|
- }
|
|
|
-
|
|
|
- enclen--;
|
|
|
-
|
|
|
- } else {
|
|
|
-
|
|
|
- // check if we have flagged this pair before
|
|
|
- // if it's the x axis, create a pair
|
|
|
- c = this.getPair( tr1, tr2, isX );
|
|
|
-
|
|
|
- if ( c && c.flag < collisionFlag ){
|
|
|
-
|
|
|
- // if it's greater than the axis index, set the flag
|
|
|
- // to = 0.
|
|
|
- // if not, increment the flag by one.
|
|
|
- c.flag = c.flag << (xyz + 1);
|
|
|
-
|
|
|
- // c.flag will equal collisionFlag
|
|
|
- // if we've incremented the flag
|
|
|
- // enough that all axes are overlapping
|
|
|
- if ( c.flag === collisionFlag ){
|
|
|
-
|
|
|
- // overlaps on all axes.
|
|
|
- // add it to possible collision
|
|
|
- // candidates list for narrow phase
|
|
|
-
|
|
|
- candidates.push( c );
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- } else {
|
|
|
-
|
|
|
- // is a min
|
|
|
- // just add this minimum to the encounters list
|
|
|
- enclen = encounters.push( tr1 );
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- return candidates;
|
|
|
- },
|
|
|
-
|
|
|
- /** internal
|
|
|
- * SweepPruneBehavior#updateIntervals()
|
|
|
- *
|
|
|
- * Update position intervals on each axis
|
|
|
- **/
|
|
|
- updateIntervals: function(){
|
|
|
-
|
|
|
- var tr
|
|
|
- ,intr
|
|
|
- ,aabb
|
|
|
- ,list = this.tracked
|
|
|
- ,i = list.length
|
|
|
- ;
|
|
|
-
|
|
|
- // for all tracked bodies
|
|
|
- while ( (--i) >= 0 ){
|
|
|
-
|
|
|
- tr = list[ i ];
|
|
|
- intr = tr.interval;
|
|
|
- aabb = tr.body.aabb();
|
|
|
-
|
|
|
- // copy the position (plus or minus) the aabb half-dimensions
|
|
|
- // into the min/max intervals
|
|
|
- intr.min.val.clone( aabb ).sub( aabb.hw, aabb.hh );
|
|
|
- intr.max.val.clone( aabb ).add( aabb.hw, aabb.hh );
|
|
|
- }
|
|
|
- },
|
|
|
-
|
|
|
- /** internal
|
|
|
- * SweepPruneBehavior#trackBody( data )
|
|
|
- * - data (Object): Event data
|
|
|
- *
|
|
|
- * Event callback to add body to list of those tracked by sweep and prune
|
|
|
- **/
|
|
|
- trackBody: function( data ){
|
|
|
-
|
|
|
- var body = data.body
|
|
|
- ,tracker = {
|
|
|
-
|
|
|
- id: getUniqueId(),
|
|
|
- body: body
|
|
|
- }
|
|
|
- ,intr = {
|
|
|
-
|
|
|
- min: {
|
|
|
- type: false, //min
|
|
|
- val: new Physics.vector(),
|
|
|
- tracker: tracker
|
|
|
- },
|
|
|
-
|
|
|
- max: {
|
|
|
- type: true, //max
|
|
|
- val: new Physics.vector(),
|
|
|
- tracker: tracker
|
|
|
- }
|
|
|
- }
|
|
|
- ;
|
|
|
-
|
|
|
- tracker.interval = intr;
|
|
|
- this.tracked.push( tracker );
|
|
|
-
|
|
|
- for ( var xyz = 0; xyz < maxDof; ++xyz ){
|
|
|
-
|
|
|
- this.intervalLists[ xyz ].push( intr.min, intr.max );
|
|
|
- }
|
|
|
- },
|
|
|
-
|
|
|
- /** internal
|
|
|
- * SweepPruneBehavior#untrackBody( data )
|
|
|
- * - data (Object): Event data
|
|
|
- *
|
|
|
- * Event callback to remove body from list of those tracked
|
|
|
- **/
|
|
|
- untrackBody: function( data ){
|
|
|
-
|
|
|
- var body = data.body
|
|
|
- ,list
|
|
|
- ,minmax
|
|
|
- ,trackedList = this.tracked
|
|
|
- ,tracker
|
|
|
- ,count
|
|
|
- ;
|
|
|
-
|
|
|
- for ( var i = 0, l = trackedList.length; i < l; ++i ){
|
|
|
-
|
|
|
- tracker = trackedList[ i ];
|
|
|
-
|
|
|
- if ( tracker.body === body ){
|
|
|
-
|
|
|
- // remove the tracker at this index
|
|
|
- trackedList.splice(i, 1);
|
|
|
-
|
|
|
- for ( var xyz = 0; xyz < maxDof; ++xyz ){
|
|
|
-
|
|
|
- count = 0;
|
|
|
- list = this.intervalLists[ xyz ];
|
|
|
-
|
|
|
- for ( var j = 0, m = list.length; j < m; ++j ){
|
|
|
-
|
|
|
- minmax = list[ j ];
|
|
|
-
|
|
|
- if ( minmax === tracker.interval.min || minmax === tracker.interval.max ){
|
|
|
-
|
|
|
- // remove interval from list
|
|
|
- list.splice(j, 1);
|
|
|
- j--;
|
|
|
- l--;
|
|
|
-
|
|
|
- if (count > 0){
|
|
|
- break;
|
|
|
- }
|
|
|
-
|
|
|
- count++;
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- break;
|
|
|
- }
|
|
|
- }
|
|
|
- },
|
|
|
-
|
|
|
- /** internal
|
|
|
- * SweepPruneBehavior#sweep( data )
|
|
|
- * - data (Object): Event data
|
|
|
- *
|
|
|
- * Event callback to sweep and publish event if any candidate collisions are found
|
|
|
- **/
|
|
|
- sweep: function( data ){
|
|
|
-
|
|
|
- var self = this
|
|
|
- ,candidates
|
|
|
- ;
|
|
|
-
|
|
|
- candidates = self.broadPhase();
|
|
|
-
|
|
|
- if ( candidates.length ){
|
|
|
-
|
|
|
- this._world.emit( this.options.channel, {
|
|
|
- candidates: candidates
|
|
|
- });
|
|
|
- }
|
|
|
- }
|
|
|
- };
|
|
|
-});
|
|
|
-
|
|
|
-
|
|
|
-// ---
|
|
|
-// inside: src/behaviors/verlet-constraints.js
|
|
|
-
|
|
|
-/**
|
|
|
- * class VerletConstraintsBehavior < Behavior
|
|
|
- *
|
|
|
- * `Physics.behavior('verlet-constraints')`.
|
|
|
- *
|
|
|
- * Verlet constraints manager.
|
|
|
- *
|
|
|
- * Handles distance constraints, and angle constraints
|
|
|
- *
|
|
|
- * Additional options include:
|
|
|
- * - iterations: The number of iterations to take to relax the constraints. (default: `2`)
|
|
|
- **/
|
|
|
-Physics.behavior('verlet-constraints', function( parent ){
|
|
|
-
|
|
|
- var TWOPI = 2 * Math.PI;
|
|
|
-
|
|
|
- var defaults = {
|
|
|
-
|
|
|
- // number of iterations to resolve constraints
|
|
|
- iterations: 2
|
|
|
- };
|
|
|
-
|
|
|
- return {
|
|
|
-
|
|
|
- // extended
|
|
|
- init: function( options ){
|
|
|
-
|
|
|
- parent.init.call( this );
|
|
|
- this.options.defaults( defaults );
|
|
|
- this.options( options );
|
|
|
-
|
|
|
- this._distanceConstraints = [];
|
|
|
- this._angleConstraints = [];
|
|
|
- },
|
|
|
-
|
|
|
- // extended
|
|
|
- connect: function( world ){
|
|
|
-
|
|
|
- var intg = world.integrator();
|
|
|
-
|
|
|
- if ( intg && intg.name.indexOf('verlet') < 0 ){
|
|
|
-
|
|
|
- throw 'The rigid constraint manager needs a world with a "verlet" compatible integrator.';
|
|
|
- }
|
|
|
-
|
|
|
- world.on('integrate:positions', this.resolve, this);
|
|
|
- },
|
|
|
-
|
|
|
- // extended
|
|
|
- disconnect: function( world ){
|
|
|
-
|
|
|
- world.off('integrate:positions', this.resolve, this);
|
|
|
- },
|
|
|
-
|
|
|
- /**
|
|
|
- * VerletConstraintsBehavior#drop() -> this
|
|
|
- *
|
|
|
- * Remove all constraints
|
|
|
- **/
|
|
|
- drop: function(){
|
|
|
-
|
|
|
- // drop the current constraints
|
|
|
- this._distanceConstraints = [];
|
|
|
- this._angleConstraints = [];
|
|
|
- return this;
|
|
|
- },
|
|
|
-
|
|
|
- /**
|
|
|
- * VerletConstraintsBehavior#distanceConstraint( bodyA, bodyB[, stiffness, targetLength] ) -> Object
|
|
|
- * - bodyA (Body): First body
|
|
|
- * - bodyB (Body): Second body
|
|
|
- * - 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.
|
|
|
- *
|
|
|
- * - `.bodyA` and `.bodyB` are references to the bodies
|
|
|
- * - `.type` is the type of constraint
|
|
|
- * - `.id` is the string ID of the constraint
|
|
|
- * - `.stiffness` is the stiffness
|
|
|
- * - `.targetLength` is the target length
|
|
|
- **/
|
|
|
- distanceConstraint: function( bodyA, bodyB, stiffness, targetLength ){
|
|
|
-
|
|
|
- var cst;
|
|
|
-
|
|
|
- if (!bodyA || !bodyB){
|
|
|
-
|
|
|
- return false;
|
|
|
- }
|
|
|
-
|
|
|
- cst = {
|
|
|
- id: Physics.util.uniqueId('dis-constraint'),
|
|
|
- type: 'dis',
|
|
|
- bodyA: bodyA,
|
|
|
- bodyB: bodyB,
|
|
|
- stiffness: stiffness || 0.5,
|
|
|
- targetLength: targetLength || bodyB.state.pos.dist( bodyA.state.pos )
|
|
|
- };
|
|
|
-
|
|
|
- cst.targetLengthSq = cst.targetLength * cst.targetLength;
|
|
|
-
|
|
|
- this._distanceConstraints.push( cst );
|
|
|
- return cst;
|
|
|
- },
|
|
|
-
|
|
|
- /**
|
|
|
- * VerletConstraintsBehavior#angleConstraint( bodyA, bodyB, bodyC[, stiffness, targetAngle] ) -> Object
|
|
|
- * - bodyA (Body): First body
|
|
|
- * - bodyB (Body): Second body
|
|
|
- * - bodyC (Body): Third body
|
|
|
- * - 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.
|
|
|
- *
|
|
|
- * - `.bodyA`, `.bodyB`, and `.bodyC` are references to the bodies
|
|
|
- * - `.type` is the type of constraint
|
|
|
- * - `.id` is the string ID of the constraint
|
|
|
- * - `.stiffness` is the stiffness
|
|
|
- * - `.targetAngle` is the target angle
|
|
|
- **/
|
|
|
- angleConstraint: function( bodyA, bodyB, bodyC, stiffness, targetAngle ){
|
|
|
-
|
|
|
- var cst;
|
|
|
-
|
|
|
- if (!bodyA || !bodyB){
|
|
|
-
|
|
|
- return false;
|
|
|
- }
|
|
|
-
|
|
|
- cst = {
|
|
|
- id: Physics.util.uniqueId('ang-constraint'),
|
|
|
- type: 'ang',
|
|
|
- bodyA: bodyA,
|
|
|
- bodyB: bodyB,
|
|
|
- bodyC: bodyC,
|
|
|
- stiffness: stiffness || 0.5,
|
|
|
- targetAngle: targetAngle || bodyB.state.pos.angle2( bodyA.state.pos, bodyC.state.pos )
|
|
|
- };
|
|
|
-
|
|
|
- this._angleConstraints.push( cst );
|
|
|
- return cst;
|
|
|
- },
|
|
|
-
|
|
|
- /**
|
|
|
- * VerletConstraintsBehavior#remove( constraintData ) -> this
|
|
|
- * 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 ){
|
|
|
-
|
|
|
- var constraints
|
|
|
- ,type
|
|
|
- ,isObj
|
|
|
- ,i
|
|
|
- ,l
|
|
|
- ;
|
|
|
-
|
|
|
- isObj = Physics.util.isObject( cstrOrId );
|
|
|
-
|
|
|
- type = (isObj) ? cstrOrId.type : cstrOrId.substr(0, 3);
|
|
|
- constraints = ( type === 'ang' ) ? this._angleConstraints : this._distanceConstraints;
|
|
|
-
|
|
|
- if ( isObj ){
|
|
|
-
|
|
|
- for ( i = 0, l = constraints.length; i < l; ++i ){
|
|
|
-
|
|
|
- if ( constraints[ i ] === cstrOrId ){
|
|
|
-
|
|
|
- constraints.splice( i, 1 );
|
|
|
- return this;
|
|
|
- }
|
|
|
- }
|
|
|
- } else {
|
|
|
-
|
|
|
- for ( i = 0, l = constraints.length; i < l; ++i ){
|
|
|
-
|
|
|
- if ( constraints[ i ].id === cstrOrId ){
|
|
|
-
|
|
|
- constraints.splice( i, 1 );
|
|
|
- return this;
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- return this;
|
|
|
- },
|
|
|
-
|
|
|
- /** internal
|
|
|
- * VerletConstraintsBehavior#resolveAngleConstraints( coef )
|
|
|
- * - coef (Number): Coefficient for this resolution phase
|
|
|
- *
|
|
|
- * Resolve angle constraints.
|
|
|
- **/
|
|
|
- resolveAngleConstraints: function( coef ){
|
|
|
-
|
|
|
- var constraints = this._angleConstraints
|
|
|
- ,scratch = Physics.scratchpad()
|
|
|
- ,trans = scratch.transform()
|
|
|
- ,con
|
|
|
- ,ang
|
|
|
- ,corr
|
|
|
- ,proportion
|
|
|
- ,invMassSum
|
|
|
- ;
|
|
|
-
|
|
|
- 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 );
|
|
|
- corr = ang - con.targetAngle;
|
|
|
-
|
|
|
- if (!corr){
|
|
|
-
|
|
|
- continue;
|
|
|
-
|
|
|
- } else if (corr <= -Math.PI){
|
|
|
-
|
|
|
- corr += TWOPI;
|
|
|
-
|
|
|
- } else if (corr >= Math.PI){
|
|
|
-
|
|
|
- corr -= TWOPI;
|
|
|
- }
|
|
|
-
|
|
|
- trans.setTranslation( con.bodyB.state.pos );
|
|
|
-
|
|
|
- corr *= -coef * con.stiffness;
|
|
|
-
|
|
|
- if ( con.bodyA.treatment === 'dynamic' && con.bodyB.treatment === 'dynamic' && con.bodyC.treatment === 'dynamic' ){
|
|
|
- invMassSum = 1 / (con.bodyA.mass + con.bodyB.mass + con.bodyC.mass);
|
|
|
- }
|
|
|
-
|
|
|
- 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' ){
|
|
|
-
|
|
|
- ang = corr * con.bodyC.mass / ( con.bodyC.mass + con.bodyA.mass );
|
|
|
-
|
|
|
- } else {
|
|
|
-
|
|
|
- ang = corr * con.bodyB.mass / ( con.bodyB.mass + con.bodyA.mass );
|
|
|
- }
|
|
|
-
|
|
|
-
|
|
|
- trans.setRotation( ang );
|
|
|
- con.bodyA.state.pos.translateInv( trans );
|
|
|
- con.bodyA.state.pos.rotate( trans );
|
|
|
- con.bodyA.state.pos.translate( trans );
|
|
|
- }
|
|
|
-
|
|
|
- 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 );
|
|
|
- }
|
|
|
-
|
|
|
- trans.setRotation( ang );
|
|
|
- con.bodyC.state.pos.translateInv( trans );
|
|
|
- con.bodyC.state.pos.rotate( trans );
|
|
|
- con.bodyC.state.pos.translate( trans );
|
|
|
- }
|
|
|
-
|
|
|
- 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 );
|
|
|
- }
|
|
|
-
|
|
|
- // ang = corr;
|
|
|
-
|
|
|
- trans.setRotation( ang ).setTranslation( con.bodyA.state.pos );
|
|
|
- con.bodyB.state.pos.translateInv( trans );
|
|
|
- con.bodyB.state.pos.rotate( trans );
|
|
|
- con.bodyB.state.pos.translate( trans );
|
|
|
-
|
|
|
- trans.setTranslation( con.bodyC.state.pos );
|
|
|
- con.bodyB.state.pos.translateInv( trans );
|
|
|
- con.bodyB.state.pos.rotateInv( trans );
|
|
|
- con.bodyB.state.pos.translate( trans );
|
|
|
- }
|
|
|
-
|
|
|
- con.bodyA.sleepCheck();
|
|
|
- con.bodyB.sleepCheck();
|
|
|
- con.bodyC.sleepCheck();
|
|
|
- }
|
|
|
-
|
|
|
- scratch.done();
|
|
|
- },
|
|
|
-
|
|
|
- /** internal
|
|
|
- * VerletConstraintsBehavior#resolveDistanceConstraints( coef )
|
|
|
- * - coef (Number): Coefficient for this resolution phase
|
|
|
- *
|
|
|
- * Resolve distance constraints.
|
|
|
- **/
|
|
|
- resolveDistanceConstraints: function( coef ){
|
|
|
-
|
|
|
- var constraints = this._distanceConstraints
|
|
|
- ,scratch = Physics.scratchpad()
|
|
|
- ,BA = scratch.vector()
|
|
|
- ,con
|
|
|
- ,len
|
|
|
- ,corr
|
|
|
- ,proportion
|
|
|
- ;
|
|
|
-
|
|
|
- for ( var i = 0, l = constraints.length; i < l; ++i ){
|
|
|
-
|
|
|
- con = constraints[ i ];
|
|
|
-
|
|
|
- // move constrained bodies to target length based on their
|
|
|
- // mass proportions
|
|
|
- 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);
|
|
|
-
|
|
|
- if ( con.bodyA.treatment === 'dynamic' ){
|
|
|
-
|
|
|
- if ( con.bodyB.treatment === 'dynamic' ){
|
|
|
- BA.mult( proportion );
|
|
|
- }
|
|
|
-
|
|
|
- con.bodyA.state.pos.vadd( BA );
|
|
|
-
|
|
|
- if ( con.bodyB.treatment === 'dynamic' ){
|
|
|
- BA.mult( 1 / proportion );
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- if ( con.bodyB.treatment === 'dynamic' ){
|
|
|
-
|
|
|
- if ( con.bodyA.treatment === 'dynamic' ){
|
|
|
- BA.mult( 1 - proportion );
|
|
|
- }
|
|
|
-
|
|
|
- con.bodyB.state.pos.vsub( BA );
|
|
|
- }
|
|
|
-
|
|
|
- con.bodyA.sleepCheck();
|
|
|
- con.bodyB.sleepCheck();
|
|
|
- }
|
|
|
-
|
|
|
- scratch.done();
|
|
|
- },
|
|
|
-
|
|
|
- /** internal
|
|
|
- * VerletConstraintsBehavior#shuffleConstraints()
|
|
|
- *
|
|
|
- * Mix up the constraints.
|
|
|
- **/
|
|
|
- shuffleConstraints: function(){
|
|
|
-
|
|
|
- this._distanceConstraints = Physics.util.shuffle( this._distanceConstraints );
|
|
|
- this._angleConstraints = Physics.util.shuffle( this._angleConstraints );
|
|
|
- },
|
|
|
-
|
|
|
- /** internal
|
|
|
- * VerletConstraintsBehavior#resolve()
|
|
|
- *
|
|
|
- * Resolve all constraints.
|
|
|
- **/
|
|
|
- resolve: function(){
|
|
|
-
|
|
|
- var its = this.options.iterations
|
|
|
- ,coef = 1 / its
|
|
|
- ;
|
|
|
-
|
|
|
- for (var i = 0; i < its; i++){
|
|
|
-
|
|
|
- // this.shuffleConstraints();
|
|
|
- this.resolveDistanceConstraints( coef );
|
|
|
- this.resolveAngleConstraints( coef );
|
|
|
- }
|
|
|
- },
|
|
|
-
|
|
|
- /**
|
|
|
- * VerletConstraintsBehavior#getConstraints() -> Object
|
|
|
- * + (Object): The object containing copied arrays of the constraints
|
|
|
- *
|
|
|
- * Get all constraints.
|
|
|
- **/
|
|
|
- getConstraints: function(){
|
|
|
-
|
|
|
- return {
|
|
|
- distanceConstraints: [].concat(this._distanceConstraints),
|
|
|
- angleConstraints: [].concat(this._angleConstraints)
|
|
|
- };
|
|
|
- }
|
|
|
- };
|
|
|
-});
|
|
|
-
|
|
|
-
|
|
|
-// ---
|
|
|
-// inside: src/integrators/improved-euler.js
|
|
|
-
|
|
|
-Physics.integrator('improved-euler', function( parent ){
|
|
|
-
|
|
|
- return {
|
|
|
- /**
|
|
|
- * class ImprovedEuler < Integrator
|
|
|
- *
|
|
|
- * `Physics.integrator('improved-euler')`.
|
|
|
- *
|
|
|
- * The improved euler integrator.
|
|
|
- **/
|
|
|
-
|
|
|
- // extended
|
|
|
- init: function( options ){
|
|
|
-
|
|
|
- // call parent init
|
|
|
- parent.init.call(this, options);
|
|
|
- },
|
|
|
-
|
|
|
- // extended
|
|
|
- integrateVelocities: function( bodies, dt ){
|
|
|
-
|
|
|
- // half the timestep squared
|
|
|
- var 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 fixed
|
|
|
- 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 );
|
|
|
-
|
|
|
- // Remember velocity for future use.
|
|
|
- state.old.vel.clone( state.vel );
|
|
|
-
|
|
|
- // remember original acc
|
|
|
- state.old.acc.clone( state.acc );
|
|
|
-
|
|
|
- // Update velocity first so we can reuse the acc vector.
|
|
|
- // a *= dt
|
|
|
- // v += a ...
|
|
|
- state.vel.vadd( state.acc.mult( dt ) );
|
|
|
-
|
|
|
- // Apply "air resistance".
|
|
|
- if ( drag ){
|
|
|
-
|
|
|
- state.vel.mult( drag );
|
|
|
- }
|
|
|
-
|
|
|
- // Reset accel
|
|
|
- state.acc.zero();
|
|
|
-
|
|
|
- //
|
|
|
- // Angular components
|
|
|
- //
|
|
|
-
|
|
|
- state.old.angular.vel = state.angular.vel;
|
|
|
- state.angular.vel += state.angular.acc * dt;
|
|
|
- state.angular.acc = 0;
|
|
|
-
|
|
|
- } 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 squared
|
|
|
- var halfdtdt = 0.5 * dt * dt
|
|
|
- ,body = null
|
|
|
- ,state
|
|
|
- // use cached vector instances
|
|
|
- // so we don't need to recreate them in a loop
|
|
|
- ,scratch = Physics.scratchpad()
|
|
|
- ,vel = scratch.vector()
|
|
|
- ,angVel
|
|
|
- ;
|
|
|
-
|
|
|
- for ( var i = 0, l = bodies.length; i < l; ++i ){
|
|
|
-
|
|
|
- body = bodies[ i ];
|
|
|
- state = body.state;
|
|
|
-
|
|
|
- // only integrate if the body isn't fixed
|
|
|
- if ( body.treatment !== 'static' && !body.sleep() ){
|
|
|
-
|
|
|
-
|
|
|
- // Store previous location.
|
|
|
- state.old.pos.clone( state.pos );
|
|
|
-
|
|
|
- // Update position.
|
|
|
- // ...
|
|
|
- // oldV *= dt
|
|
|
- // a *= 0.5 * dt
|
|
|
- // x += oldV + a
|
|
|
- vel.clone( state.old.vel );
|
|
|
- state.pos.vadd( vel.mult( dt ) ).vadd( state.old.acc.mult( halfdtdt ) );
|
|
|
-
|
|
|
- state.old.acc.zero();
|
|
|
-
|
|
|
- //
|
|
|
- // Angular components
|
|
|
- //
|
|
|
-
|
|
|
- state.old.angular.pos = state.angular.pos;
|
|
|
- state.angular.pos += state.old.angular.vel * dt + state.old.angular.acc * halfdtdt;
|
|
|
- state.old.angular.acc = 0;
|
|
|
-
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- scratch.done();
|
|
|
- }
|
|
|
- };
|
|
|
-});
|
|
|
-
|
|
|
-
|
|
|
-// ---
|
|
|
-// inside: src/integrators/velocity-verlet-alt.js
|
|
|
-
|
|
|
-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;
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- };
|
|
|
-});
|
|
|
-
|
|
|
-
|
|
|
-// ---
|
|
|
-// 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){
|
|
|
- throw "Canvas not supported";
|
|
|
- }
|
|
|
-
|
|
|
- this.hiddenCtx = this.hiddenCanvas.getContext('2d');
|
|
|
-
|
|
|
- // actual viewport
|
|
|
- var viewport = this.el;
|
|
|
- if (viewport.nodeName.toUpperCase() !== 'CANVAS'){
|
|
|
-
|
|
|
- viewport = document.createElement('canvas');
|
|
|
- this.el.appendChild( viewport );
|
|
|
- if (typeof this.options.el === 'string' && this.el === document.body){
|
|
|
- viewport.id = this.options.el;
|
|
|
- }
|
|
|
- this.el = viewport;
|
|
|
- }
|
|
|
-
|
|
|
- this.container = this.el.parentNode;
|
|
|
- this.ctx = viewport.getContext('2d');
|
|
|
-
|
|
|
- this.els = {};
|
|
|
-
|
|
|
- if (this.options.meta){
|
|
|
- var stats = this.options.metaEl || newEl();
|
|
|
- stats.className = 'pjs-meta';
|
|
|
- this.els.fps = newEl('span');
|
|
|
- this.els.ipf = newEl('span');
|
|
|
- stats.appendChild(newEl('span', 'fps: '));
|
|
|
- stats.appendChild(this.els.fps);
|
|
|
- stats.appendChild(newEl('br'));
|
|
|
- stats.appendChild(newEl('span', 'ipf: '));
|
|
|
- stats.appendChild(this.els.ipf);
|
|
|
-
|
|
|
- viewport.parentNode.insertBefore(stats, viewport);
|
|
|
- }
|
|
|
-
|
|
|
- this._layers = {};
|
|
|
- this.addLayer( 'main', this.el );
|
|
|
-
|
|
|
- if ( this.options.autoResize ){
|
|
|
- this.resize();
|
|
|
- } else {
|
|
|
- this.resize( this.options.width, this.options.height );
|
|
|
- }
|
|
|
- },
|
|
|
-
|
|
|
- /**
|
|
|
- * CanvasRenderer#layer( id ) -> Layer
|
|
|
- * - id (String): The id for the layer
|
|
|
- *
|
|
|
- * Get the layer by id.
|
|
|
- **/
|
|
|
- layer: function( id ){
|
|
|
-
|
|
|
- if ( id in this._layers ){
|
|
|
- return this._layers[ id ];
|
|
|
- }
|
|
|
-
|
|
|
- return null;
|
|
|
- },
|
|
|
-
|
|
|
- /**
|
|
|
- * CanvasRenderer#addLayer( id[, el, opts ] ) -> Layer
|
|
|
- * - id (String): The id for the layer
|
|
|
- * - el (HTMLElement): The canvas element to use for this layer
|
|
|
- * - opts (Object): The options for this layer (see below)
|
|
|
- *
|
|
|
- * Create a new layer.
|
|
|
- *
|
|
|
- * Layers can have the following options:
|
|
|
- *
|
|
|
- * - width: The width
|
|
|
- * - height: The height
|
|
|
- * - manual: Draw manually (default: `false`)
|
|
|
- * - autoResize: Automatically resize the layer when the renderer's [[CanvasRenderer#resize]] method is called. (default: `true`)
|
|
|
- * - follow: A [[Body]]. Offset this layer's rendering to follow a body's position. (default: `null`)
|
|
|
- * - offset: The offset [[Vectorish]] for this layer. (default: `null`)
|
|
|
- * - scale: Scale the layer by this amount. (default: `1`)
|
|
|
- * - zIndex: The zIndex for the layer's HTMLElement. (default: `1`)
|
|
|
- **/
|
|
|
- addLayer: function( id, el, opts ){
|
|
|
-
|
|
|
- /** belongs to: CanvasRenderer
|
|
|
- * class Layer
|
|
|
- *
|
|
|
- * A rendering layer for the canvas renderer.
|
|
|
- *
|
|
|
- * Create by calling [[CanvasRenderer#addLayer]].
|
|
|
- **/
|
|
|
-
|
|
|
- var self = this
|
|
|
- ,bodies = []
|
|
|
- ,styles = Physics.util.extend({}, this.options.styles)
|
|
|
- ,layer = {
|
|
|
- /**
|
|
|
- * Layer#id = String
|
|
|
- *
|
|
|
- * The layer's ID
|
|
|
- **/
|
|
|
- id: id
|
|
|
- /**
|
|
|
- * Layer#el = HTMLElement
|
|
|
- *
|
|
|
- * The layer's Canvas
|
|
|
- **/
|
|
|
- ,el: el || document.createElement('canvas')
|
|
|
- /** related to: Physics.util.options
|
|
|
- * Layer#options( options ) -> Object
|
|
|
- * - options (Object): The options to set as an object
|
|
|
- * + (Object): The options
|
|
|
- *
|
|
|
- * Set options on this layer.
|
|
|
- *
|
|
|
- * Access options directly from the options object.
|
|
|
- *
|
|
|
- * Example:
|
|
|
- *
|
|
|
- * ```javascript
|
|
|
- * this.options.someOption;
|
|
|
- * ```
|
|
|
- **/
|
|
|
- ,options: Physics.util.options({
|
|
|
- width: this.el.width
|
|
|
- ,height: this.el.height
|
|
|
- ,manual: false
|
|
|
- ,autoResize: true
|
|
|
- ,follow: null
|
|
|
- ,offset: null
|
|
|
- ,scale: 1
|
|
|
- ,zIndex: 1
|
|
|
- })( opts )
|
|
|
- }
|
|
|
- ;
|
|
|
-
|
|
|
- if ( id in this._layers ){
|
|
|
- throw 'Layer "' + id + '" already added.';
|
|
|
- }
|
|
|
-
|
|
|
- this.el.parentNode.insertBefore( layer.el, this.el );
|
|
|
- layer.el.style.position = 'absolute';
|
|
|
- layer.el.style.zIndex = layer.options.zIndex;
|
|
|
- layer.el.className += ' pjs-layer-' + layer.id;
|
|
|
- layer.ctx = layer.el.getContext('2d');
|
|
|
- layer.ctx.scale( 1, 1 );
|
|
|
- layer.el.width = layer.options.width;
|
|
|
- layer.el.height = layer.options.height;
|
|
|
-
|
|
|
- /**
|
|
|
- * Layer#bodies = Array
|
|
|
- *
|
|
|
- * The Bodies this layer is rendering.
|
|
|
- *
|
|
|
- * The "main" layer will render all world bodies if it's empty.
|
|
|
- **/
|
|
|
- layer.bodies = bodies;
|
|
|
-
|
|
|
- /**
|
|
|
- * Layer#reset( [arr] ) -> this
|
|
|
- * - arr (Array): Array to replace the current stack of Bodies.
|
|
|
- *
|
|
|
- * Reset the stack.
|
|
|
- **/
|
|
|
- layer.reset = function( arr ){
|
|
|
-
|
|
|
- bodies = arr || [];
|
|
|
- return layer;
|
|
|
- };
|
|
|
-
|
|
|
- /**
|
|
|
- * Layer#addToStack( arr ) -> this
|
|
|
- * Layer#addToStack( body ) -> this
|
|
|
- * - body (Body): Body to add
|
|
|
- * - arr (Array): Array of bodies to add
|
|
|
- *
|
|
|
- * Add body (bodies) to the rendering stack for this layer.
|
|
|
- *
|
|
|
- * Bodies must be added to the stack in order to be rendered by this layer UNLESS it is the "main" layer.
|
|
|
- **/
|
|
|
- layer.addToStack = function( thing ){
|
|
|
-
|
|
|
- if ( Physics.util.isArray( thing ) ){
|
|
|
- bodies.push.apply( bodies, thing );
|
|
|
- } else {
|
|
|
- bodies.push( thing );
|
|
|
- }
|
|
|
- return layer;
|
|
|
- };
|
|
|
-
|
|
|
- /**
|
|
|
- * Layer#removeFromStack( arr ) -> this
|
|
|
- * Layer#removeFromStack( body ) -> this
|
|
|
- * - body (Body): Body to remove
|
|
|
- * - arr (Array): Array of bodies to remove
|
|
|
- *
|
|
|
- * Remove body (bodies) from the rendering stack for this layer.
|
|
|
- **/
|
|
|
- layer.removeFromStack = function( thing ){
|
|
|
-
|
|
|
- var i, l;
|
|
|
-
|
|
|
- if ( Physics.util.isArray( thing ) ){
|
|
|
- for ( i = 0, l = thing.length; i < l; ++i ){
|
|
|
- layer.removeFromStack(thing[ i ]);
|
|
|
- }
|
|
|
- } else {
|
|
|
- i = Physics.util.indexOf( bodies, thing );
|
|
|
- if ( i > -1 ){
|
|
|
- bodies.splice( i, 1 );
|
|
|
- }
|
|
|
- }
|
|
|
- return layer;
|
|
|
- };
|
|
|
-
|
|
|
- /**
|
|
|
- * Layer#render( [clear] ) -> this
|
|
|
- * - clear (Boolean): Clear the canvas (default: `true`)
|
|
|
- *
|
|
|
- * Render the bodies in this layer's stack.
|
|
|
- *
|
|
|
- * If you want you can replace this function with your own to do custom rendering.
|
|
|
- *
|
|
|
- * Example:
|
|
|
- *
|
|
|
- * ```javascript
|
|
|
- * layer.render = myCustomRenderFn;
|
|
|
- * ```
|
|
|
- **/
|
|
|
- layer.render = function( clear ){
|
|
|
-
|
|
|
- var body
|
|
|
- ,scratch = Physics.scratchpad()
|
|
|
- ,offset = scratch.vector().set(0, 0)
|
|
|
- ,scale = layer.options.scale
|
|
|
- ,view
|
|
|
- ,i
|
|
|
- ,l = bodies.length
|
|
|
- ,t = self._interpolateTime
|
|
|
- ,stack = (l || layer.id !== 'main') ? bodies : self._world._bodies
|
|
|
- ;
|
|
|
-
|
|
|
- if ( layer.options.manual ){
|
|
|
- scratch.done();
|
|
|
- return layer;
|
|
|
- }
|
|
|
-
|
|
|
- if ( layer.options.offset ){
|
|
|
- if ( layer.options.offset === 'center' ){
|
|
|
- offset.add( layer.el.width * 0.5, layer.el.height * 0.5 ).mult( 1/scale );
|
|
|
- } else {
|
|
|
- offset.vadd( layer.options.offset ).mult( 1/scale );
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- 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 ){
|
|
|
- layer.ctx.clearRect(0, 0, layer.el.width, layer.el.height);
|
|
|
- }
|
|
|
-
|
|
|
- if ( scale !== 1 ){
|
|
|
- layer.ctx.save();
|
|
|
- layer.ctx.scale( scale, scale );
|
|
|
- }
|
|
|
-
|
|
|
- for ( i = 0, l = stack.length; i < l; ++i ){
|
|
|
-
|
|
|
- body = stack[ i ];
|
|
|
- if ( !body.hidden ){
|
|
|
- view = body.view || ( body.view = self.createView(body.geometry, body.styles || styles[ body.geometry.name ]) );
|
|
|
- self.drawBody( body, body.view, layer.ctx, offset );
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- if ( scale !== 1 ){
|
|
|
- layer.ctx.restore();
|
|
|
- }
|
|
|
-
|
|
|
- scratch.done();
|
|
|
- return layer;
|
|
|
- };
|
|
|
-
|
|
|
- // remember layer
|
|
|
- this._layers[ id ] = layer;
|
|
|
-
|
|
|
- return layer;
|
|
|
- },
|
|
|
-
|
|
|
- /**
|
|
|
- * CanvasRenderer#removeLayer( id ) -> this
|
|
|
- * CanvasRenderer#removeLayer( layer ) -> this
|
|
|
- * - id (String): The id for the layer
|
|
|
- * - layer (Layer): The layer
|
|
|
- *
|
|
|
- * Remove a layer.
|
|
|
- **/
|
|
|
- removeLayer: function( idOrLayer ){
|
|
|
-
|
|
|
- var id = idOrLayer.id ? idOrLayer.id : idOrLayer
|
|
|
- ,el = this._layers[ id ].el
|
|
|
- ;
|
|
|
-
|
|
|
- if ( el !== this.el ){
|
|
|
- el.parentNode.removeChild( el );
|
|
|
- }
|
|
|
- delete this._layers[ id ];
|
|
|
- return this;
|
|
|
- },
|
|
|
-
|
|
|
- /**
|
|
|
- * CanvasRenderer#resize( width, height ) -> this
|
|
|
- * - width (Number): The width
|
|
|
- * - height (Number): The height
|
|
|
- *
|
|
|
- * Resize all layer canvases that have the `autoResize` option set to `true`.
|
|
|
- **/
|
|
|
- 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 = this.width;
|
|
|
- layer.el.height = this.height;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- return this;
|
|
|
- },
|
|
|
-
|
|
|
- /**
|
|
|
- * CanvasRenderer#setStyle( styles[, ctx] )
|
|
|
- * - styles (Object|String): Styles to set on the canvas context
|
|
|
- * - ctx (Canvas2DContext): The canvas context
|
|
|
- *
|
|
|
- * Set styles on the specified canvas context (or main context).
|
|
|
- **/
|
|
|
- setStyle: function( styles, ctx ){
|
|
|
-
|
|
|
- ctx = ctx || this.ctx;
|
|
|
-
|
|
|
- if ( Physics.util.isObject(styles) ){
|
|
|
-
|
|
|
- styles.strokeStyle = styles.lineWidth ? styles.strokeStyle : 'rgba(0,0,0,0)';
|
|
|
- Physics.util.extend(ctx, styles);
|
|
|
-
|
|
|
- } else {
|
|
|
-
|
|
|
- ctx.fillStyle = ctx.strokeStyle = styles;
|
|
|
- ctx.lineWidth = 1;
|
|
|
- }
|
|
|
- },
|
|
|
-
|
|
|
- /**
|
|
|
- * CanvasRenderer#drawCircle( x, y, r, styles[, ctx] )
|
|
|
- * - x (Number): The x coord
|
|
|
- * - y (Number): The y coord
|
|
|
- * - r (Number): The circle radius
|
|
|
- * - styles (Object): The styles configuration
|
|
|
- * - ctx (Canvas2DContext): The canvas context
|
|
|
- *
|
|
|
- * Draw a circle to specified canvas context.
|
|
|
- **/
|
|
|
- drawCircle: function(x, y, r, styles, ctx){
|
|
|
-
|
|
|
- ctx = ctx || this.ctx;
|
|
|
-
|
|
|
- ctx.beginPath();
|
|
|
- this.setStyle( styles, ctx );
|
|
|
- ctx.arc(x, y, r, 0, Pi2, false);
|
|
|
- ctx.closePath();
|
|
|
- ctx.stroke();
|
|
|
- ctx.fill();
|
|
|
- },
|
|
|
-
|
|
|
- /**
|
|
|
- * CanvasRenderer#drawPolygon( verts, styles[, ctx] )
|
|
|
- * - verts (Array): Array of [[Vectorish]] vertices
|
|
|
- * - styles (Object): The styles configuration
|
|
|
- * - ctx (Canvas2DContext): The canvas context
|
|
|
- *
|
|
|
- * Draw a polygon to specified canvas context.
|
|
|
- **/
|
|
|
- drawPolygon: function(verts, styles, ctx){
|
|
|
-
|
|
|
- var vert = verts[0]
|
|
|
- ,x = vert.x
|
|
|
- ,y = vert.y
|
|
|
- ,l = verts.length
|
|
|
- ;
|
|
|
-
|
|
|
- ctx = ctx || this.ctx;
|
|
|
- ctx.beginPath();
|
|
|
- this.setStyle( styles, ctx );
|
|
|
-
|
|
|
- ctx.moveTo(x, y);
|
|
|
-
|
|
|
- for ( var i = 1; i < l; ++i ){
|
|
|
-
|
|
|
- vert = verts[ i ];
|
|
|
- x = vert.x;
|
|
|
- y = vert.y;
|
|
|
- ctx.lineTo(x, y);
|
|
|
- }
|
|
|
-
|
|
|
- if ( l > 2 ){
|
|
|
- ctx.closePath();
|
|
|
- }
|
|
|
-
|
|
|
- ctx.stroke();
|
|
|
- ctx.fill();
|
|
|
- },
|
|
|
-
|
|
|
- /**
|
|
|
- * CanvasRenderer#drawRect( x, y, width, height, styles[, ctx] )
|
|
|
- * - x (Number): The x coord
|
|
|
- * - y (Number): The y coord
|
|
|
- * - width (Number): The width
|
|
|
- * - height (Number): The height
|
|
|
- * - styles (Object): The styles configuration
|
|
|
- * - ctx (Canvas2DContext): The canvas context
|
|
|
- *
|
|
|
- * Draw a rectangle to specified canvas context.
|
|
|
- **/
|
|
|
- drawRect: function(x, y, width, height, styles, ctx){
|
|
|
-
|
|
|
- var hw = width * 0.5
|
|
|
- ,hh = height * 0.5
|
|
|
- ;
|
|
|
-
|
|
|
- ctx = ctx || this.ctx;
|
|
|
- this.setStyle( styles, ctx );
|
|
|
- ctx.beginPath();
|
|
|
- ctx.rect(x - hw, y - hh, width, height);
|
|
|
- ctx.closePath();
|
|
|
- ctx.stroke();
|
|
|
- ctx.fill();
|
|
|
- },
|
|
|
-
|
|
|
- /**
|
|
|
- * CanvasRenderer#drawLine( from, to, styles[, ctx] )
|
|
|
- * - from (Vectorish): The starting pt
|
|
|
- * - to (Vectorish): The ending pt
|
|
|
- * - styles (Object): The styles configuration
|
|
|
- * - ctx (Canvas2DContext): The canvas context
|
|
|
- *
|
|
|
- * Draw a line onto specified canvas context.
|
|
|
- **/
|
|
|
- drawLine: function(from, to, styles, ctx){
|
|
|
-
|
|
|
- var x = from.x
|
|
|
- ,y = from.y
|
|
|
- ;
|
|
|
-
|
|
|
- ctx = ctx || this.ctx;
|
|
|
-
|
|
|
- ctx.beginPath();
|
|
|
- this.setStyle( styles, ctx );
|
|
|
-
|
|
|
- ctx.moveTo(x, y);
|
|
|
-
|
|
|
- x = to.x;
|
|
|
- y = to.y;
|
|
|
-
|
|
|
- ctx.lineTo(x, y);
|
|
|
-
|
|
|
- ctx.stroke();
|
|
|
- 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 ){
|
|
|
-
|
|
|
- var view
|
|
|
- ,aabb = geometry.aabb()
|
|
|
- ,hw = aabb.hw + Math.abs(aabb.x)
|
|
|
- ,hh = aabb.hh + Math.abs(aabb.y)
|
|
|
- ,offset = { x: hw + 1, y: hh + 1 }
|
|
|
- ,hiddenCtx = this.hiddenCtx
|
|
|
- ,hiddenCanvas = this.hiddenCanvas
|
|
|
- ;
|
|
|
-
|
|
|
- styles = styles || this.options.styles[ name ] || this.options.styles.circle || {};
|
|
|
-
|
|
|
- // must want an image
|
|
|
- if ( styles.src ){
|
|
|
- view = new Image();
|
|
|
- view.src = styles.src;
|
|
|
- if ( styles.width ){
|
|
|
- view.width = styles.width;
|
|
|
- }
|
|
|
- if ( styles.height ){
|
|
|
- view.height = styles.height;
|
|
|
- }
|
|
|
- return view;
|
|
|
- }
|
|
|
-
|
|
|
- offset.x += styles.lineWidth | 0;
|
|
|
- offset.y += styles.lineWidth | 0;
|
|
|
-
|
|
|
- // clear and resize
|
|
|
- hiddenCanvas.width = 2 * hw + 2 + (2 * styles.lineWidth|0);
|
|
|
- hiddenCanvas.height = 2 * hh + 2 + (2 * styles.lineWidth|0);
|
|
|
-
|
|
|
- this.draw( geometry, styles, hiddenCtx, offset );
|
|
|
-
|
|
|
- view = new Image( hiddenCanvas.width, hiddenCanvas.height );
|
|
|
- view.src = hiddenCanvas.toDataURL('image/png');
|
|
|
- return view;
|
|
|
- },
|
|
|
-
|
|
|
- // extended
|
|
|
- drawMeta: function( meta ){
|
|
|
-
|
|
|
- this.els.fps.innerHTML = meta.fps.toFixed(2);
|
|
|
- this.els.ipf.innerHTML = meta.ipf;
|
|
|
- },
|
|
|
-
|
|
|
- // 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 );
|
|
|
- ctx.translate( os._[0], os._[1] );
|
|
|
- ctx.drawImage(view, -view.width/2, -view.height/2, view.width, view.height);
|
|
|
- ctx.restore();
|
|
|
- },
|
|
|
-
|
|
|
- // extended
|
|
|
- render: function( bodies, meta ){
|
|
|
-
|
|
|
- var body
|
|
|
- ,view
|
|
|
- ,pos
|
|
|
- ;
|
|
|
-
|
|
|
- this._world.emit('beforeRender', {
|
|
|
- renderer: this,
|
|
|
- meta: meta
|
|
|
- });
|
|
|
-
|
|
|
- if ( this.options.meta ) {
|
|
|
- this.drawMeta( meta );
|
|
|
- }
|
|
|
-
|
|
|
- this._interpolateTime = meta.interpolateTime;
|
|
|
-
|
|
|
- for ( var id in this._layers ){
|
|
|
-
|
|
|
- this._layers[ id ].render();
|
|
|
- }
|
|
|
-
|
|
|
- return this;
|
|
|
- }
|
|
|
- };
|
|
|
-});
|
|
|
-
|
|
|
-
|
|
|
-// ---
|
|
|
-// inside: src/renderers/dom.js
|
|
|
-
|
|
|
-/**
|
|
|
- * class DomRenderer < Renderer
|
|
|
- *
|
|
|
- * Physics.renderer('dom')
|
|
|
- *
|
|
|
- * Renderer that manipulates DOM elements according to the physics simulation. Very primative...
|
|
|
- **/
|
|
|
-Physics.renderer('dom', function( proto ){
|
|
|
-
|
|
|
- if ( !document ){
|
|
|
- // must be in node environment
|
|
|
- return {};
|
|
|
- }
|
|
|
-
|
|
|
- // utility methods
|
|
|
- var thePrefix = {}
|
|
|
- ,tmpdiv = document.createElement("div")
|
|
|
- ,toTitleCase = function toTitleCase(str) {
|
|
|
- return str.replace(/(?:^|\s)\w/g, function(match) {
|
|
|
- return match.toUpperCase();
|
|
|
- });
|
|
|
- }
|
|
|
- // return the prefixed name for the specified css property
|
|
|
- ,pfx = function pfx(prop) {
|
|
|
-
|
|
|
- if (thePrefix[prop]){
|
|
|
- return thePrefix[prop];
|
|
|
- }
|
|
|
-
|
|
|
- var arrayOfPrefixes = ['Webkit', 'Moz', 'Ms', 'O']
|
|
|
- ,name
|
|
|
- ;
|
|
|
-
|
|
|
- for (var i = 0, l = arrayOfPrefixes.length; i < l; ++i) {
|
|
|
-
|
|
|
- name = arrayOfPrefixes[i] + toTitleCase(prop);
|
|
|
-
|
|
|
- if (name in tmpdiv.style){
|
|
|
- return thePrefix[prop] = name;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- if (name in tmpdiv.style){
|
|
|
- return thePrefix[prop] = prop;
|
|
|
- }
|
|
|
-
|
|
|
- return false;
|
|
|
- }
|
|
|
- ;
|
|
|
-
|
|
|
- var classpfx = 'pjs-'
|
|
|
- ,px = 'px'
|
|
|
- ,cssTransform = pfx('transform')
|
|
|
- ,borderRadius = pfx('borderRadius')
|
|
|
- ;
|
|
|
-
|
|
|
- var newEl = function( node, content ){
|
|
|
- var el = document.createElement(node || 'div');
|
|
|
- if (content){
|
|
|
- el.innerHTML = content;
|
|
|
- }
|
|
|
- return el;
|
|
|
- }
|
|
|
- ,drawBody
|
|
|
- ;
|
|
|
-
|
|
|
- return {
|
|
|
-
|
|
|
- // extended
|
|
|
- init: function( options ){
|
|
|
-
|
|
|
- // call proto init
|
|
|
- proto.init.call(this, options);
|
|
|
-
|
|
|
- var viewport = this.el;
|
|
|
- viewport.style.position = 'relative';
|
|
|
- viewport.style.overflow = 'hidden';
|
|
|
- viewport.style[cssTransform] = 'translateZ(0)'; // force GPU accel
|
|
|
- viewport.style.width = this.options.width + px;
|
|
|
- viewport.style.height = this.options.height + px;
|
|
|
-
|
|
|
- this.els = {};
|
|
|
-
|
|
|
- if (options.meta){
|
|
|
- var stats = newEl();
|
|
|
- stats.className = 'pjs-meta';
|
|
|
- this.els.fps = newEl('span');
|
|
|
- this.els.ipf = newEl('span');
|
|
|
- stats.appendChild(newEl('span', 'fps: '));
|
|
|
- stats.appendChild(this.els.fps);
|
|
|
- stats.appendChild(newEl('br'));
|
|
|
- stats.appendChild(newEl('span', 'ipf: '));
|
|
|
- stats.appendChild(this.els.ipf);
|
|
|
-
|
|
|
- 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
|
|
|
- * DomRenderer#circleProperties( el, geometry )
|
|
|
- * - el (HTMLElement): The element
|
|
|
- * - geometry (Geometry): The body's geometry
|
|
|
- *
|
|
|
- * Set dom element style properties for a circle.
|
|
|
- **/
|
|
|
- circleProperties: function( el, geometry ){
|
|
|
-
|
|
|
- var aabb = geometry.aabb();
|
|
|
-
|
|
|
- el.style.width = (aabb.hw * 2) + px;
|
|
|
- el.style.height = (aabb.hh * 2) + px;
|
|
|
- el.style.marginLeft = (-aabb.hw) + px;
|
|
|
- el.style.marginTop = (-aabb.hh) + px;
|
|
|
- el.style[ borderRadius ] = '50%';
|
|
|
- },
|
|
|
-
|
|
|
- /** internal
|
|
|
- * DomRenderer#rectangleProperties( el, geometry )
|
|
|
- * - el (HTMLElement): The element
|
|
|
- * - geometry (Geometry): The body's geometry
|
|
|
- *
|
|
|
- * Set dom element style properties for a rectangle.
|
|
|
- **/
|
|
|
- rectangleProperties: function( el, geometry ){
|
|
|
-
|
|
|
- var aabb = geometry.aabb();
|
|
|
-
|
|
|
- el.style.width = (aabb.hw * 2) + px;
|
|
|
- el.style.height = (aabb.hh * 2) + px;
|
|
|
- el.style.marginLeft = (-aabb.hw) + px;
|
|
|
- el.style.marginTop = (-aabb.hh) + px;
|
|
|
- },
|
|
|
-
|
|
|
- // extended
|
|
|
- createView: function( geometry ){
|
|
|
-
|
|
|
- var el = newEl()
|
|
|
- ,chel
|
|
|
- ,fn = geometry.name + 'Properties'
|
|
|
- ;
|
|
|
-
|
|
|
- el.className = classpfx + geometry.name;
|
|
|
- el.style.position = 'absolute';
|
|
|
- el.style.top = '0px';
|
|
|
- el.style.left = '0px';
|
|
|
-
|
|
|
- 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);
|
|
|
- }
|
|
|
-
|
|
|
- this.el.appendChild( el );
|
|
|
- return el;
|
|
|
- },
|
|
|
-
|
|
|
- // 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 );
|
|
|
- },
|
|
|
-
|
|
|
- /**
|
|
|
- * DomRenderer#detach( data ) -> this
|
|
|
- * - data (HTMLElement|Object): DOM node or event data (`data.body`)
|
|
|
- *
|
|
|
- * Event callback to detach a node from the DOM
|
|
|
- **/
|
|
|
- detach: function( data ){
|
|
|
-
|
|
|
- // interpred data as either dom node or event data
|
|
|
- var el = (data.nodeType && data) || (data.body && data.body.view)
|
|
|
- ,par = el && el.parentNode
|
|
|
- ;
|
|
|
-
|
|
|
- if ( el && par ){
|
|
|
- // remove view from dom
|
|
|
- par.removeChild( el );
|
|
|
- }
|
|
|
-
|
|
|
- return this;
|
|
|
- },
|
|
|
-
|
|
|
- /**
|
|
|
- * DomRenderer#attach( data ) -> this
|
|
|
- * - data (HTMLElement|Object): DOM node or event data (`data.body`)
|
|
|
- *
|
|
|
- * Event callback to attach a node to the viewport
|
|
|
- **/
|
|
|
- attach: function( data ){
|
|
|
-
|
|
|
- // interpred data as either dom node or event data
|
|
|
- var el = (data.nodeType && data) || (data.body && data.body.view)
|
|
|
- ;
|
|
|
-
|
|
|
- if ( el ){
|
|
|
- // attach to viewport
|
|
|
- this.el.appendChild( el );
|
|
|
- }
|
|
|
-
|
|
|
- return this;
|
|
|
- },
|
|
|
-
|
|
|
- // extended
|
|
|
- drawMeta: function( meta ){
|
|
|
-
|
|
|
- this.els.fps.innerHTML = meta.fps.toFixed(2);
|
|
|
- this.els.ipf.innerHTML = meta.ipf;
|
|
|
- },
|
|
|
-
|
|
|
- // extended
|
|
|
- drawBody: function( body, view ){
|
|
|
-
|
|
|
- var pos = body.state.pos
|
|
|
- ,v = body.state.vel
|
|
|
- ,os = body.offset
|
|
|
- ,x
|
|
|
- ,y
|
|
|
- ,ang
|
|
|
- ,t = this._interpolateTime
|
|
|
- ;
|
|
|
-
|
|
|
- // interpolate positions
|
|
|
- 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) translate('+os._[0]+'px,'+os._[1]+'px)';
|
|
|
- }
|
|
|
- };
|
|
|
-});
|
|
|
-
|
|
|
-
|
|
|
-// ---
|
|
|
-// inside: src/renderers/pixi-renderer.js
|
|
|
-
|
|
|
-/*
|
|
|
- * @requires pixi.js
|
|
|
- */
|
|
|
-/**
|
|
|
- * class PixiRenderer < Renderer
|
|
|
- *
|
|
|
- * Physics.renderer('pixi')
|
|
|
- *
|
|
|
- * Renderer that uses the PIXI.js library. [Documentation can be found here](https://github.com/wellcaffeinated/PhysicsJS/wiki/PIXI-Renderer).
|
|
|
- *
|
|
|
- * 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: {
|
|
|
- * // Defines the default canvas colour
|
|
|
- * 'color': '0x66FF99',
|
|
|
- *
|
|
|
- * 'circle' : {
|
|
|
- * strokeStyle: '0xE8900C',
|
|
|
- * lineWidth: 3,
|
|
|
- * fillStyle: '0xD5DE4C',
|
|
|
- * angleIndicator: '0xE8900C',
|
|
|
- * strokeAlpha: 1,
|
|
|
- * fillAlpha: 1
|
|
|
- * },
|
|
|
- *
|
|
|
- * 'convex-polygon' : {
|
|
|
- * strokeStyle: '0xE8900C',
|
|
|
- * lineWidth: 3,
|
|
|
- * fillStyle: '0xD5DE4C',
|
|
|
- * angleIndicator: '0xE8900C'
|
|
|
- * }
|
|
|
- * }
|
|
|
- * ```
|
|
|
- *
|
|
|
- * 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 ){
|
|
|
-
|
|
|
- if ( !document ){
|
|
|
- // must be in node environment
|
|
|
- return {};
|
|
|
- }
|
|
|
-
|
|
|
- var Pi2 = Math.PI * 2
|
|
|
- ,colors = {
|
|
|
- white: '0xFFFFFF'
|
|
|
- ,violet: '0x542437'
|
|
|
- ,blue: '0x53777A'
|
|
|
- }
|
|
|
- ,fontStyles = {
|
|
|
- font: "18px monospace",
|
|
|
- fill: "black",
|
|
|
- align: "left"
|
|
|
- }
|
|
|
-
|
|
|
- ,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 not present - cannot continue";
|
|
|
- }
|
|
|
-
|
|
|
- // call parent init
|
|
|
- parent.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 );
|
|
|
-
|
|
|
- isTransparent = (!this.options.styles.color || this.options.styles.color === 'transparent');
|
|
|
- // Hook in PIXI stage here
|
|
|
- this.stage = new PIXI.Stage(this.options.styles.color);
|
|
|
-
|
|
|
- // 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
|
|
|
- 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.resize( this.options.width, this.options.height );
|
|
|
- }
|
|
|
- },
|
|
|
-
|
|
|
- // 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;
|
|
|
- },
|
|
|
-
|
|
|
- /**
|
|
|
- * PixiRenderer#loadSpriteSheets( assetsToLoad, callback ) -> this
|
|
|
- * - assetsToLoad (Array): Array of spritesheets to load
|
|
|
- * - callback (Function): Function to call when loading is complete
|
|
|
- *
|
|
|
- * Loads textures defined in a spritesheet
|
|
|
- **/
|
|
|
- loadSpriteSheets: function( assetsToLoad, callback ){
|
|
|
-
|
|
|
- if ( !Physics.util.isArray( assetsToLoad ) ) {
|
|
|
- throw 'Spritesheets must be defined in arrays';
|
|
|
- }
|
|
|
-
|
|
|
- var self = this
|
|
|
- ,loader = new PIXI.AssetLoader(assetsToLoad)
|
|
|
- ;
|
|
|
-
|
|
|
- // Start loading resources!
|
|
|
- loader.load();
|
|
|
-
|
|
|
- loader.on('onComplete', function(evt){
|
|
|
- self.assetsLoaded = true;
|
|
|
- callback();
|
|
|
- });
|
|
|
-
|
|
|
- return self;
|
|
|
- },
|
|
|
-
|
|
|
- /**
|
|
|
- * PixiRenderer#drawBody( body, view )
|
|
|
- * - body (Body): The body to draw
|
|
|
- * - view (DisplayObject): The pixi display object
|
|
|
- *
|
|
|
- * Draw a PIXI.DisplayObject to the stage.
|
|
|
- **/
|
|
|
- drawBody: function( body, view ){
|
|
|
- var pos = body.state.pos
|
|
|
- ,v = body.state.vel
|
|
|
- ,os = body.offset
|
|
|
- ,t = this._interpolateTime || 0
|
|
|
- ,x
|
|
|
- ,y
|
|
|
- ,ang
|
|
|
- ;
|
|
|
-
|
|
|
- // interpolate positions
|
|
|
- x = pos._[0] + v._[0] * t;
|
|
|
- y = pos._[1] + v._[1] * t;
|
|
|
- ang = body.state.angular.pos + body.state.angular.vel * t;
|
|
|
-
|
|
|
- view.position.set( x, y );
|
|
|
- view.pivot.set( -os._[0], -os._[1] );
|
|
|
- view.rotation = ang;
|
|
|
- },
|
|
|
-
|
|
|
- // extended
|
|
|
- render: function( bodies, meta ){
|
|
|
-
|
|
|
- parent.render.call(this, bodies, meta);
|
|
|
- this.renderer.render(this.stage);
|
|
|
- },
|
|
|
-
|
|
|
- /**
|
|
|
- * 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
|
|
|
- * - 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, 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();
|
|
|
- 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
|
|
|
- * - styles (Object): The styles configuration
|
|
|
- * + (PIXI.Graphics): A graphic object representing a polygon.
|
|
|
- *
|
|
|
- * Create a polygon for use in PIXI stage
|
|
|
- **/
|
|
|
- createPolygon: function( verts, styles ){
|
|
|
-
|
|
|
- var vert = verts[0]
|
|
|
- ,x = vert.x
|
|
|
- ,y = vert.y
|
|
|
- ,l = verts.length
|
|
|
- ,start = {
|
|
|
- x: x
|
|
|
- ,y: y
|
|
|
- }
|
|
|
- ,graphics = new PIXI.Graphics()
|
|
|
- ;
|
|
|
-
|
|
|
- this.setStyles( graphics, styles );
|
|
|
-
|
|
|
- graphics.moveTo(x, y);
|
|
|
-
|
|
|
- for ( var i = 1; i < l; ++i ){
|
|
|
-
|
|
|
- vert = verts[ i ];
|
|
|
- x = vert.x;
|
|
|
- y = vert.y;
|
|
|
- graphics.lineTo(x, y);
|
|
|
- }
|
|
|
-
|
|
|
- if (l > 2){
|
|
|
- graphics.lineTo(start.x, start.y);
|
|
|
- }
|
|
|
-
|
|
|
- graphics.endFill();
|
|
|
- return graphics;
|
|
|
- },
|
|
|
-
|
|
|
- /**
|
|
|
- * PixiRenderer#createLine( from, to, styles ) -> PIXI.Graphics
|
|
|
- * - from (Vectorish): Starting point
|
|
|
- * - to (Vectorish): Ending point
|
|
|
- * - styles (Object): The styles configuration
|
|
|
- * + (PIXI.Graphics): A graphic object representing a polygon.
|
|
|
- *
|
|
|
- * Create a line for use in PIXI stage
|
|
|
- **/
|
|
|
- createLine: function( from, to, styles ){
|
|
|
-
|
|
|
- var x = from.x
|
|
|
- ,y = from.y
|
|
|
- ;
|
|
|
-
|
|
|
- var graphics = new PIXI.Graphics();
|
|
|
- this.setStyles( graphics, styles );
|
|
|
-
|
|
|
- graphics.moveTo(x, y);
|
|
|
-
|
|
|
- x = to.x;
|
|
|
- y = to.y;
|
|
|
-
|
|
|
- graphics.lineTo(x, y);
|
|
|
-
|
|
|
- graphics.endFill();
|
|
|
- return graphics;
|
|
|
- },
|
|
|
-
|
|
|
- // extended
|
|
|
- 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)
|
|
|
- ,name = geometry.name
|
|
|
- ;
|
|
|
-
|
|
|
- parent = parent || this.stage;
|
|
|
- styles = styles || this.options.styles[ name ] || this.options.styles.circle || {};
|
|
|
-
|
|
|
- // 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(0, 0, geometry.radius, styles);
|
|
|
-
|
|
|
- } else if (name === 'convex-polygon'){
|
|
|
-
|
|
|
- view = this.createPolygon(geometry.vertices, styles);
|
|
|
-
|
|
|
- } else if (name === 'rectangle'){
|
|
|
-
|
|
|
- view = this.createRect(-geometry.width/2, -geometry.height/2, geometry.width, geometry.height, styles);
|
|
|
- } else if (name === 'compound'){
|
|
|
-
|
|
|
- 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 {
|
|
|
-
|
|
|
- // 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 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;
|
|
|
-
|
|
|
- this.meta.ipf = new PIXI.Text('IPF: ' + meta.ipf, fontStyles);
|
|
|
- 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 = true;
|
|
|
- } else {
|
|
|
- this.meta.fps.setText('FPS: ' + meta.fps.toFixed(2));
|
|
|
- this.meta.ipf.setText('IPF: ' + meta.ipf);
|
|
|
- }
|
|
|
- },
|
|
|
-
|
|
|
- /**
|
|
|
- * PixiRenderer#createDisplay( type, options ) -> PIXI.DisplayObject
|
|
|
- * - type (String): The type of PIXI.DisplayObject to make
|
|
|
- * - options (Object): Options to apply to the view.
|
|
|
- * + (PIXI.DisplayObject): An object that is renderable.
|
|
|
- *
|
|
|
- * Create a PIXI sprite or movie clip.
|
|
|
- **/
|
|
|
- createDisplay: function( type, options ){
|
|
|
- var view = null
|
|
|
- ,texture = null
|
|
|
- ;
|
|
|
- switch (type){
|
|
|
- // Create a sprite object
|
|
|
- case 'sprite':
|
|
|
- texture = PIXI.Texture.fromImage(options.texture);
|
|
|
- view = new PIXI.Sprite(texture);
|
|
|
- if (options.anchor ) {
|
|
|
- view.anchor.x = options.anchor.x;
|
|
|
- view.anchor.y = options.anchor.y;
|
|
|
- }
|
|
|
- // If a container is specified, use add to that container
|
|
|
- if (options.container) {
|
|
|
- options.container.addChild(view);
|
|
|
- } else {
|
|
|
- // Otherwise just add the view to the stage
|
|
|
- this.stage.addChild(view);
|
|
|
- }
|
|
|
- return view;
|
|
|
- // Create a movieclip object
|
|
|
- case 'movieclip':
|
|
|
- if (!this.assetsLoaded) {
|
|
|
- throw "No assets have been loaded. Use loadSpritesheet() first";
|
|
|
- }
|
|
|
- var tex = []
|
|
|
- ,i = 0
|
|
|
- ;
|
|
|
- // Populate our movieclip
|
|
|
- for (i; i < options.frames.length; i++) {
|
|
|
- texture = PIXI.Texture.fromFrame(options.frames[i]);
|
|
|
- tex.push(texture);
|
|
|
- }
|
|
|
- view = new PIXI.MovieClip(tex);
|
|
|
- if (options.anchor ) {
|
|
|
- view.anchor.x = options.anchor.x;
|
|
|
- view.anchor.y = options.anchor.y;
|
|
|
- }
|
|
|
- // If a container is specified, use add to that container
|
|
|
- if (options.container) {
|
|
|
- options.container.addChild(view);
|
|
|
- } else {
|
|
|
- // Otherwise just add the view to the stage
|
|
|
- this.stage.addChild(view);
|
|
|
- }
|
|
|
- return view;
|
|
|
- // Create a default case
|
|
|
- default:
|
|
|
- throw 'Invalid PIXI.DisplayObject passed';
|
|
|
- }
|
|
|
- },
|
|
|
-
|
|
|
- /**
|
|
|
- * PixiRenderer#centerAnchor( view )
|
|
|
- * - view (PIXI.DisplayObject): The view to center
|
|
|
- *
|
|
|
- * Centers the anchor to {x: 0.5, y: 0.5} of a view
|
|
|
- **/
|
|
|
- centerAnchor: function( view ) {
|
|
|
- if (view !== null){
|
|
|
- view.anchor.x = 0.5;
|
|
|
- view.anchor.y = 0.5;
|
|
|
- }
|
|
|
- }
|
|
|
- };
|
|
|
-});
|
|
|
-
|
|
|
-
|
|
|
-// ---
|
|
|
-// inside: src/outro.js
|
|
|
-
|
|
|
-return Physics;
|
|
|
-}));
|