├── .gitignore ├── images ├── book.png ├── pen.png ├── pin.png ├── ruby.png ├── blocky.png ├── bomby.png ├── bubble.png ├── coiny.png ├── donut.png ├── eraser.png ├── firey.png ├── flower.png ├── fries.png ├── leafy.png ├── match.png ├── needle.png ├── nickel.png ├── pencil.png ├── rocky.png ├── spongy.png ├── woody.png ├── announcer.png ├── gelatin.png ├── golf-ball.png ├── ice-cube.png ├── puffball.png ├── snowball.png ├── teardrop.png ├── flower-body.png ├── tennis-ball.png └── yellow-face.png ├── README.md ├── js ├── TwoWayMap.js ├── PropManager.js ├── turbulenz │ ├── LICENSE │ ├── debug.js │ └── boxtree.js ├── StateManager.js ├── Animator.js ├── BodyBuilder.js ├── props │ ├── Belt.js │ └── SimpleProp.js ├── bfdi-physics.js └── msgpack.js ├── LICENSE ├── index.html └── characters.json /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | -------------------------------------------------------------------------------- /images/book.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fernozzle/bfdi-physics/HEAD/images/book.png -------------------------------------------------------------------------------- /images/pen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fernozzle/bfdi-physics/HEAD/images/pen.png -------------------------------------------------------------------------------- /images/pin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fernozzle/bfdi-physics/HEAD/images/pin.png -------------------------------------------------------------------------------- /images/ruby.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fernozzle/bfdi-physics/HEAD/images/ruby.png -------------------------------------------------------------------------------- /images/blocky.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fernozzle/bfdi-physics/HEAD/images/blocky.png -------------------------------------------------------------------------------- /images/bomby.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fernozzle/bfdi-physics/HEAD/images/bomby.png -------------------------------------------------------------------------------- /images/bubble.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fernozzle/bfdi-physics/HEAD/images/bubble.png -------------------------------------------------------------------------------- /images/coiny.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fernozzle/bfdi-physics/HEAD/images/coiny.png -------------------------------------------------------------------------------- /images/donut.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fernozzle/bfdi-physics/HEAD/images/donut.png -------------------------------------------------------------------------------- /images/eraser.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fernozzle/bfdi-physics/HEAD/images/eraser.png -------------------------------------------------------------------------------- /images/firey.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fernozzle/bfdi-physics/HEAD/images/firey.png -------------------------------------------------------------------------------- /images/flower.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fernozzle/bfdi-physics/HEAD/images/flower.png -------------------------------------------------------------------------------- /images/fries.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fernozzle/bfdi-physics/HEAD/images/fries.png -------------------------------------------------------------------------------- /images/leafy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fernozzle/bfdi-physics/HEAD/images/leafy.png -------------------------------------------------------------------------------- /images/match.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fernozzle/bfdi-physics/HEAD/images/match.png -------------------------------------------------------------------------------- /images/needle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fernozzle/bfdi-physics/HEAD/images/needle.png -------------------------------------------------------------------------------- /images/nickel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fernozzle/bfdi-physics/HEAD/images/nickel.png -------------------------------------------------------------------------------- /images/pencil.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fernozzle/bfdi-physics/HEAD/images/pencil.png -------------------------------------------------------------------------------- /images/rocky.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fernozzle/bfdi-physics/HEAD/images/rocky.png -------------------------------------------------------------------------------- /images/spongy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fernozzle/bfdi-physics/HEAD/images/spongy.png -------------------------------------------------------------------------------- /images/woody.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fernozzle/bfdi-physics/HEAD/images/woody.png -------------------------------------------------------------------------------- /images/announcer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fernozzle/bfdi-physics/HEAD/images/announcer.png -------------------------------------------------------------------------------- /images/gelatin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fernozzle/bfdi-physics/HEAD/images/gelatin.png -------------------------------------------------------------------------------- /images/golf-ball.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fernozzle/bfdi-physics/HEAD/images/golf-ball.png -------------------------------------------------------------------------------- /images/ice-cube.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fernozzle/bfdi-physics/HEAD/images/ice-cube.png -------------------------------------------------------------------------------- /images/puffball.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fernozzle/bfdi-physics/HEAD/images/puffball.png -------------------------------------------------------------------------------- /images/snowball.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fernozzle/bfdi-physics/HEAD/images/snowball.png -------------------------------------------------------------------------------- /images/teardrop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fernozzle/bfdi-physics/HEAD/images/teardrop.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | BFDI physics toy 2 | ================ 3 | 4 | A web toy featuring BFDI characters. 5 | 6 | -------------------------------------------------------------------------------- /images/flower-body.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fernozzle/bfdi-physics/HEAD/images/flower-body.png -------------------------------------------------------------------------------- /images/tennis-ball.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fernozzle/bfdi-physics/HEAD/images/tennis-ball.png -------------------------------------------------------------------------------- /images/yellow-face.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fernozzle/bfdi-physics/HEAD/images/yellow-face.png -------------------------------------------------------------------------------- /js/TwoWayMap.js: -------------------------------------------------------------------------------- 1 | function TwoWayMap(map){ 2 | this.map = map; 3 | this.reverseMap = {}; 4 | for(var key in map){ 5 | var value = map[key]; 6 | this.reverseMap[value] = key; 7 | } 8 | } 9 | TwoWayMap.prototype.get = function(key){ return this.map[key]; }; 10 | TwoWayMap.prototype.revGet = function(key){ return this.reverseMap[key]; }; 11 | -------------------------------------------------------------------------------- /js/PropManager.js: -------------------------------------------------------------------------------- 1 | var PropManager = { 2 | managers: {}, 3 | init: function(prop, stage, phys2D, world) { 4 | var manager = this._getManager(prop.name); 5 | Object.defineProperty(prop, 'manager', {value: manager, enumerable: false}); 6 | manager.init(prop, stage, phys2D, world); 7 | }, 8 | _getManager: function(name) { 9 | return this.managers[name] || this.managers.simpleProp; 10 | }, 11 | update: function(prop) { 12 | prop.manager.update(prop); 13 | }, 14 | destruct: function(prop, stage, phys2D, world) { 15 | prop.manager.destruct(prop, stage, phys2D, world); 16 | } 17 | }; 18 | -------------------------------------------------------------------------------- /js/turbulenz/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2009-2014 Turbulenz Limited 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /js/StateManager.js: -------------------------------------------------------------------------------- 1 | var StateManager = (function() { 2 | var keyMap = new TwoWayMap({ 3 | 'p': 'props', 4 | 'n': 'name', 5 | 'b': 'bodies', 6 | 'p': 'position', 7 | 'r': 'rotation', 8 | 'v': 'velocity', 9 | 'a': 'angularVelocity' 10 | }); 11 | function renameKeys(object, mapFunction) { 12 | // Array 13 | if (Object.prototype.toString.call(object) === '[object Array]') { 14 | return object.map(function(element) { 15 | return renameKeys(element, mapFunction); 16 | }); 17 | } 18 | // Object 19 | if (Object.prototype.toString.call(object) === '[object Object]') { 20 | var result = {}; 21 | for (var origKey in object) { 22 | var newKey = mapFunction(origKey) || origKey; 23 | result[newKey] = renameKeys(object[origKey], mapFunction); 24 | } 25 | return result; 26 | } 27 | // Other 28 | return object; 29 | } 30 | return { 31 | serializeState: function(state) { 32 | var stateShortKeys = renameKeys(state, keyMap.revGet.bind(keyMap)); 33 | return msgpack.pack(stateShortKeys, true); 34 | }, 35 | deserializeState: function(data) { 36 | var stateShortKeys = msgpack.unpack(data); 37 | return renameKeys(state, keyMap.get.bind(keyMap)); 38 | } 39 | }; 40 | })(); 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Michael Huang 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | 22 | ---------------------------------------------------------------------- 23 | 24 | TwoWayMap by Edgar Villegas Alvarado (http://stackoverflow.com/users/722331/edgar-villegas-alvarado) 25 | http://stackoverflow.com/a/21070876/938853 26 | 27 | Licensed under the Creative Commons Attribution-ShareAlike 3.0 Unported license 28 | http://creativecommons.org/licenses/by-sa/3.0/ 29 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | BFDI Physics Toy 4 | 55 | 56 | 57 |
58 |
59 |
60 |

61 | 
62 | 		
63 | 		
64 | 		
65 | 		
66 | 		
67 | 
68 | 		
69 | 		
70 | 		
71 | 		
72 | 
73 | 		
74 | 		
75 | 		
76 | 		
77 | 
78 | 		
79 | 		
80 | 		
81 | 	
82 | 
83 | 


--------------------------------------------------------------------------------
/js/Animator.js:
--------------------------------------------------------------------------------
 1 | // Not the best name. This module calls `getAnimationFrame` and handles
 2 | // events such as visibility changes.
 3 | 
 4 | var Animator = function() {
 5 | 	this._wasHidden = false;
 6 | 	var that = this;
 7 | 	document.addEventListener(Animator.changeIdentifier, function() {
 8 | 		// Only enable `_wasHidden`: it must be disabled
 9 | 		// after one non-hidden frame
10 | 		if (document[Animator.hiddenIdentifier]) {
11 | 			that._wasHidden = true;
12 | 		}
13 | 	}, false);
14 | }
15 | 
16 | Animator.requestAnimationFrame = (
17 | 	window.requestAnimationFrame       ||
18 | 	window.webkitRequestAnimationFrame ||
19 | 	window.mozRequestAnimationFrame    ||
20 | 	window.oRequestAnimationFrame      ||
21 | 	window.msRequestAnimationFrame     ||
22 | 	function(callback, element) {
23 | 		window.setTimeout(callback, 1000 / 60);
24 | 	}
25 | );
26 | if (typeof document.hidden !== 'undefined') {
27 | 	Animator.hiddenIdentifier = 'hidden';
28 | 	Animator.changeIdentifier = 'visibilitychange';
29 | } else if (typeof document.mozHidden !== 'undefined') {
30 | 	Animator.hiddenIdentifier = 'mozHidden';
31 | 	Animator.changeIdentifier = 'mozvisibilitychange';
32 | } else if (typeof document.msHidden !== 'undefined') {
33 | 	Animator.hiddenIdentifier = 'msHidden';
34 | 	Animator.changeIdentifier = 'msvisibilitychange';
35 | } else if (typeof document.webkitHidden !== 'undefined') {
36 | 	Animator.hiddenIdentifier = 'webkitHidden';
37 | 	Animator.changeIdentifier = 'webkitvisibilitychange';
38 | }
39 | 
40 | Animator.prototype.start = function(callback) {
41 | 	this._callback = callback;
42 | 	var that = this;
43 | 	this._animate = function() {
44 | 		var currentTime = Date.now();
45 | 		var delta = (currentTime - that._previousTime) / 1000;
46 | 
47 | 		if (!document[Animator.hiddenIdentifier] && delta > 0) {
48 | 			// If previously hidden, wait a frame in order to
49 | 			// record a more reasonable time delta
50 | 			if (that._wasHidden) {
51 | 				that._wasHidden = false;
52 | 			} else {
53 | 				that._callback(Math.min(delta, config.maxTimestep), delta);
54 | 			}
55 | 		}
56 | 		that._previousTime = currentTime;
57 | 		that._request();
58 | 	}
59 | 	this._request();
60 | }
61 | Animator.prototype._request = function() {
62 | 	Animator.requestAnimationFrame.call(window, this._animate);
63 | }
64 | 


--------------------------------------------------------------------------------
/js/BodyBuilder.js:
--------------------------------------------------------------------------------
 1 | var BodyBuilder = (function() {
 2 | 	return {
 3 | 		createBody: function(bodyDef, phys2D) {
 4 | 			// Mirror polygonal shape defs
 5 | 			var shapeDefs = bodyDef.shapes.map(function(shapeDef){
 6 | 				// Circles are untouched
 7 | 				if (shapeDefIsCircle(shapeDef)) return shapeDef;
 8 | 				return mirrorPolygon(shapeDef, bodyDef);
 9 | 			});
10 | 
11 | 			// Create shapes
12 | 			var shapes = shapeDefs.map(function(shapeDef) {
13 | 				return createShape(shapeDef, bodyDef, phys2D);
14 | 			});
15 | 
16 | 			// Create body
17 | 			var body = phys2D.createRigidBody({shapes: shapes});
18 | 			body.alignWithOrigin();
19 | 			
20 | 			// Set `totalImageOffset` and `topLeft` in body def
21 | 			var bounds = body.computeWorldBounds();
22 | 			var imageOffset = bodyDef.imageOffset || [0, 0];
23 | 			var topLeft     = [bounds[0], bounds[1]];
24 | 			bodyDef.totalImageOffset = VMath.v2Add(topLeft, imageOffset);
25 | 			bodyDef.topLeft          = topLeft;
26 | 
27 | 			return body;
28 | 		}
29 | 	};
30 | 
31 | 	function createShape(shapeDef, bodyDef, phys2D) {
32 | 		var material = phys2D.createMaterial({
33 | 			elasticity:      bodyDef.restitution,
34 | 			dynamicFriction: bodyDef.friction,
35 | 			density:         bodyDef.density,
36 | 		});
37 | 
38 | 		if(shapeDefIsCircle(shapeDef)) {
39 | 			return phys2D.createCircleShape({
40 | 				radius:   shapeDef.radius,
41 | 				origin:   shapeDef.origin,
42 | 				material: material,
43 | 				group:    bodyDef.group,
44 | 				mask:     bodyDef.mask
45 | 			});
46 | 		} else {
47 | 			return phys2D.createPolygonShape({
48 | 				vertices: shapeDef,
49 | 				material: material,
50 | 				group:    bodyDef.group,
51 | 				mask:     bodyDef.mask
52 | 			});
53 | 		}
54 | 	}
55 | 	function mirrorPolygon(verts, bodyDef) {
56 | 		var newVerts = [];
57 | 		var reversedVerts = verts.slice().reverse();
58 | 		if (true) {
59 | 			verts.forEach(function(vert) {
60 | 				newVerts.push([vert[0], vert[1]]);
61 | 			});
62 | 		}
63 | 		if (bodyDef.mirrorX) {
64 | 			reversedVerts.forEach(function(vert) {
65 | 				if(vert[0] === 0) return;
66 | 				newVerts.push([-vert[0], vert[1]]);
67 | 			});
68 | 		}
69 | 		if (bodyDef.mirrorX && bodyDef.mirrorY) {
70 | 			verts.forEach(function(vert) {
71 | 				if(vert[0] === 0 || vert[1] === 0) return;
72 | 				newVerts.push([-vert[0], -vert[1]]);
73 | 			});
74 | 		}
75 | 		if (bodyDef.mirrorY) {
76 | 			reversedVerts.forEach(function(vert) {
77 | 				if(vert[1] === 0) return;
78 | 				newVerts.push([vert[0], -vert[1]]);
79 | 			});
80 | 		}
81 | 		return newVerts;
82 | 	}
83 | 
84 | 	function shapeDefIsCircle(shape) {
85 | 		return shape.radius !== undefined;
86 | 	}
87 | })();
88 | 


--------------------------------------------------------------------------------
/js/props/Belt.js:
--------------------------------------------------------------------------------
  1 | PropManager.managers.belt = {
  2 | 	init: function(prop, stageElement, phys2D, world) {
  3 | 		this._createPropPhysics(prop, phys2D, world);
  4 | 		this._createPropElement(prop, stageElement);
  5 | 	},
  6 | 	update: function(prop) {
  7 | 	},
  8 | 	destruct: function(prop, stageElement, phys2D, world) {
  9 | 		this._removePropPhysics(prop, world);
 10 | 		this._removePropElement(prop, stageElement);
 11 | 	},
 12 | 
 13 | 	// Physics -------------------------------------------------------------
 14 | 	_createPropPhysics: function(prop, phys2D, world) {
 15 | 		var normal = VMath.v2Build(
 16 | 			prop.end[1] - prop.start[1],
 17 | 			prop.start[0] - prop.end[0]
 18 | 		);
 19 | 		var length = VMath.v2Length(normal);
 20 | 		VMath.v2ScalarMul(normal, prop.radius / length, normal);
 21 | 
 22 | 		var shapes = [
 23 | 			phys2D.createPolygonShape({
 24 | 				vertices: [
 25 | 					VMath.v2Add(prop.start, normal),
 26 | 					VMath.v2Add(prop.end,   normal),
 27 | 					VMath.v2Sub(prop.end,   normal),
 28 | 					VMath.v2Sub(prop.start, normal)
 29 | 				],
 30 | 				material: conveyorBeltMaterial
 31 | 			}),
 32 | 			phys2D.createCircleShape({
 33 | 				radius: prop.radius,
 34 | 				origin: prop.start,
 35 | 				material: conveyorBeltMaterial
 36 | 			}),
 37 | 			phys2D.createCircleShape({
 38 | 				radius: prop.radius,
 39 | 				origin: prop.end,
 40 | 				material: conveyorBeltMaterial
 41 | 			})
 42 | 		];
 43 | 		var physicsBody = phys2D.createRigidBody({
 44 | 			type: 'static',
 45 | 			surfaceVelocity: [prop.speed, 0],
 46 | 			shapes: shapes,
 47 | 			userData: {prop: prop}
 48 | 		});
 49 | 		world.addRigidBody(physicsBody);
 50 | 
 51 | 		Object.defineProperty(prop, 'physicsBody', {
 52 | 			value: physicsBody
 53 | 		});
 54 | 	},
 55 | 
 56 | 	_removePropPhysics: function(prop, world) {
 57 | 		world.removeRigidBody(prop.physicsBody);
 58 | 	},
 59 | 
 60 | 	// Elements ------------------------------------------------------------
 61 | 	_createPropElement: function(prop, stageElement) {
 62 | 		var normal = VMath.v2Build(
 63 | 			prop.end[1] - prop.start[1],
 64 | 			prop.start[0] - prop.end[0]
 65 | 		);
 66 | 		var length = VMath.v2Length(normal);
 67 | 		VMath.v2ScalarMul(normal, prop.radius / length, normal);
 68 | 
 69 | 		var element = document.createElement('div');
 70 | 		element.className = 'belt';
 71 | 
 72 | 		element.style.width  = ((length + 2 * prop.radius) * config.elementScale) + 'px';
 73 | 		element.style.height = ((         2 * prop.radius) * config.elementScale) + 'px';
 74 | 		var transformString =
 75 | 			'translate(' +
 76 | 				((prop.start[0] + normal[0] + normal[1]) * config.elementScale) + 'px,' +
 77 | 				((prop.start[1] + normal[1] - normal[0]) * config.elementScale) + 'px)' +
 78 | 			'rotate(' +
 79 | 				(this._angleBetween(prop.start, prop.end) * degreesPerRadian) + 'deg)';
 80 | 		element.style.webkitTransform = transformString;
 81 | 		element.style.mozTransform    = transformString;
 82 | 		element.style.transform       = transformString;
 83 | 
 84 | 		stage.appendChild(element);
 85 | 		Object.defineProperty(prop, 'element', {
 86 | 			value: element
 87 | 		});
 88 | 	},
 89 | 	_angleBetween: function(v1, v2) {
 90 | 		return Math.atan2(
 91 | 			v2[1] - v1[1],
 92 | 			v2[0] - v1[0]
 93 | 		);
 94 | 	},
 95 | 
 96 | 	_removePropElement: function(prop, stageElement) {
 97 | 		stageElement.removeChild(prop.element);
 98 | 	}
 99 | }
100 | 


--------------------------------------------------------------------------------
/js/turbulenz/debug.js:
--------------------------------------------------------------------------------
  1 | // Copyright (c) 2012-2014 Turbulenz Limited
  2 | var debug = {
  3 |     // Override this to change the behaviour when asserts are
  4 |     // triggered.  Default logs the message to the console and then
  5 |     // throws an exception.
  6 |     reportAssert: function debugReportAssertFn(msg) {
  7 |         var fnName;
  8 |         var stackTrace;
  9 | 
 10 |         if ('undefined' !== typeof Error && ((Error).captureStackTrace)) {
 11 |             var getStackTrace = function debugReportAssertGetStackTraceFn() {
 12 |                 var obj = {};
 13 |                 (Error).captureStackTrace(obj, getStackTrace);
 14 |                 stackTrace = (obj).stack;
 15 | 
 16 |                 // Attempt to get the name of the function in which
 17 |                 // debug.assert was called.
 18 |                 var fnFrame = stackTrace.split("\n")[3];
 19 |                 fnName = fnFrame.substr(fnFrame.indexOf("at ") + 3);
 20 |             };
 21 |             getStackTrace();
 22 |         }
 23 | 
 24 |         if (fnName) {
 25 |             msg = "ASSERT at " + fnName + ": " + msg;
 26 |         } else {
 27 |             msg = "ASSERT: " + msg;
 28 |         }
 29 | 
 30 |         // plugin does not have a "console" object
 31 |         // web workers do not have a "window" object
 32 |         var consoleObj;
 33 | 
 34 |         if (typeof console !== 'undefined') {
 35 |             consoleObj = console;
 36 |         }
 37 |         if (typeof window !== 'undefined') {
 38 |             consoleObj = window.console;
 39 |         }
 40 | 
 41 |         if (consoleObj) {
 42 |             consoleObj.log(msg);
 43 | 
 44 |             if (stackTrace) {
 45 |                 consoleObj.log(stackTrace);
 46 |             }
 47 |         }
 48 | 
 49 |         throw msg;
 50 |     },
 51 |     abort: function debugAbortFn(msg) {
 52 |         debug.reportAssert(msg);
 53 |     },
 54 |     // Basic assertion that a condition is true.
 55 |     assert: function debugAssertFn(condition, msg) {
 56 |         if (!condition) {
 57 |             if (!msg) {
 58 |                 msg = "Unlabelled assert";
 59 |             }
 60 | 
 61 |             // TODO : Grab information about the caller?
 62 |             debug.reportAssert(msg);
 63 |         }
 64 |     },
 65 |     log: function debugAssertLogFn(msg) {
 66 |         window.console.log(msg);
 67 |     },
 68 |     evaluate: function debugEvaluateFn(fn) {
 69 |         fn();
 70 |     },
 71 |     isNumber: function debugIsNumber(s) {
 72 |         return "number" === typeof s;
 73 |     },
 74 |     isMathType: function isMathTypeFn(v) {
 75 |         if (v instanceof VMathArrayConstructor) {
 76 |             return true;
 77 |         }
 78 | 
 79 |         if (TurbulenzEngine.onperformancewarning) {
 80 |             TurbulenzEngine.onperformancewarning("Object is not of type " + VMathArrayConstructor.toString() + ".  If this message appears frequently, performance of your" + " game may be affected.");
 81 |         }
 82 | 
 83 |         return true;
 84 |     },
 85 |     isVec2: function debugIsVec2Fn(v) {
 86 |         return (2 === v.length);
 87 |     },
 88 |     isVec3: function debugIsVec3Fn(v) {
 89 |         return (3 === v.length);
 90 |     },
 91 |     isVec4: function debugIsVec4Fn(v) {
 92 |         return (4 === v.length);
 93 |     },
 94 |     isAABB: function debugIsAABBFn(v) {
 95 |         return (6 === v.length);
 96 |     },
 97 |     isQuat: function debugIsQuatFn(v) {
 98 |         return (4 === v.length);
 99 |     },
100 |     isMtx33: function debugIsMtx33Fn(v) {
101 |         return (9 === v.length);
102 |     },
103 |     isMtx43: function debugIsMtx43Fn(v) {
104 |         return (12 === v.length);
105 |     },
106 |     isMtx34: function debugIsMtx34Fn(v) {
107 |         return (12 === v.length);
108 |     },
109 |     isMtx44: function debugIsMtx44Fn(v) {
110 |         return (16 === v.length);
111 |     },
112 |     isQuatPos: function debugIsQuatPos(v) {
113 |         return (7 === v.length);
114 |     }
115 | };
116 | 


--------------------------------------------------------------------------------
/js/props/SimpleProp.js:
--------------------------------------------------------------------------------
  1 | PropManager.managers.simpleProp = {
  2 | 	init: function(prop, stageElement, phys2D, world) {
  3 | 		this._createPropPhysics (prop, phys2D, world);
  4 | 		this._createPropElements(prop, stageElement);
  5 | 	},
  6 | 	update: function(prop) {
  7 | 		this._syncPropPhysics (prop);
  8 | 		this._syncPropElements(prop);
  9 | 	},
 10 | 	destruct: function(prop, stageElement, phys2D, world) {
 11 | 		this._removePropPhysics (prop, world);
 12 | 		this._removePropElements(prop, stageElement);
 13 | 	},
 14 | 	propDefs: null,
 15 | 
 16 | 	// Physics -------------------------------------------------------------
 17 | 	_createPropPhysics: function(prop, phys2D, world) {
 18 | 		var propDef = this.propDefs[prop.name];
 19 | 		prop.bodies.forEach(function(body, bodyIndex) {
 20 | 			var bodyDef = propDef.bodies[bodyIndex];
 21 | 
 22 | 			var physicsBody = BodyBuilder.createBody(bodyDef, phys2D);
 23 | 			physicsBody.setPosition(body.position);
 24 | 			physicsBody.setRotation(body.rotation);
 25 | 			physicsBody.setVelocity(body.velocity);
 26 | 			physicsBody.setAngularVelocity(body.angularVelocity);
 27 | 			physicsBody.userData = {prop: prop};
 28 | 			
 29 | 			world.addRigidBody(physicsBody);
 30 | 			Object.defineProperty(body, 'physicsBody', {
 31 | 				value: physicsBody
 32 | 			});
 33 | 		});
 34 | 		if (propDef.constraints) {
 35 | 			propDef.constraints.forEach(function(constraintDef) {
 36 | 				var bodyA = prop.bodies[constraintDef.bodyA];
 37 | 				var bodyB = prop.bodies[constraintDef.bodyB];
 38 | 				var bodyDefA = propDef.bodies[constraintDef.bodyA];
 39 | 				var bodyDefB = propDef.bodies[constraintDef.bodyB];
 40 | 				world.addConstraint(phys2D.createPointConstraint({
 41 | 					bodyA: bodyA.physicsBody,
 42 | 					bodyB: bodyB.physicsBody,
 43 | 					anchorA: VMath.v2Add(constraintDef.anchorA, bodyDefA.topLeft),
 44 | 					anchorB: VMath.v2Add(constraintDef.anchorB, bodyDefB.topLeft),
 45 | 					stiff: constraintDef.stiff
 46 | 				}));
 47 | 			});
 48 | 		}
 49 | 	},
 50 | 
 51 | 	_syncPropPhysics: function(prop) {
 52 | 		prop.bodies.forEach(this._syncBodyPhysics);
 53 | 	},
 54 | 	_syncBodyPhysics: function(body) {
 55 | 		var physicsBody = body.physicsBody;
 56 | 		
 57 | 		physicsBody.getPosition(body.position);
 58 | 		body.rotation = physicsBody.getRotation();
 59 | 		physicsBody.getVelocity(body.velocity);
 60 | 		body.angularVelocity = physicsBody.getAngularVelocity();
 61 | 	},
 62 | 
 63 | 	_removePropPhysics: function(prop, world) {
 64 | 		prop.bodies.forEach(function(body) {
 65 | 			world.removeRigidBody(body.physicsBody);
 66 | 		});
 67 | 	},
 68 | 
 69 | 	// Elements ------------------------------------------------------------
 70 | 	_createPropElements: function(prop, stageElement) {
 71 | 		var propDef = this.propDefs[prop.name];
 72 | 		prop.bodies.forEach(function(body, bodyIndex) {
 73 | 			var bodyDef = propDef.bodies[bodyIndex];
 74 | 			var bodyName = bodyDef.name || prop.name;
 75 | 
 76 | 			// Create element
 77 | 			var element = document.createElement('div');
 78 | 			element.className = 'body';
 79 | 
 80 | 			// Create 
 81 | 			var image = document.createElement('img');
 82 | 			var imageOffset = bodyDef.totalImageOffset;
 83 | 			image.style.left = (imageOffset[0] * config.elementScale) + 'px';
 84 | 			image.style.top  = (imageOffset[1] * config.elementScale) + 'px';
 85 | 
 86 | 			// Scale 
 87 | 			var transformString = 'scale(' + (config.elementScale / config.imageScale) + ')';
 88 | 			image.style.webkitTransform = transformString;
 89 | 			image.style.mozTransform    = transformString;
 90 | 			image.style.transform       = transformString;
 91 | 			image.src = 'images/' + bodyName + '.png';
 92 | 
 93 | 			element.appendChild(image);
 94 | 			stageElement.appendChild(element);
 95 | 			Object.defineProperty(body, 'element', {
 96 | 				value: element
 97 | 			});
 98 | 		});
 99 | 	},
100 | 
101 | 	_syncPropElements: function(prop) {
102 | 		prop.bodies.forEach(this._syncBodyElement);
103 | 	},
104 | 	_syncBodyElement: function(body) {
105 | 		transformString =
106 | 			'translate3d(' +
107 | 				(body.position[0] * config.elementScale) + 'px,' +
108 | 				(body.position[1] * config.elementScale) + 'px,0)' +
109 | 			'rotate(' + (degreesPerRadian * body.rotation) + 'deg)';
110 | 		body.element.style.webkitTransform = transformString;
111 | 		body.element.style.mozTransform    = transformString;
112 | 		body.element.style.transform       = transformString;
113 | 	},
114 | 
115 | 	_removePropElements: function(prop, stageElement) {
116 | 		prop.bodies.forEach(function(body) {
117 | 			stageElement.removeChild(body.element);
118 | 		});
119 | 	}
120 | };
121 | 


--------------------------------------------------------------------------------
/js/bfdi-physics.js:
--------------------------------------------------------------------------------
  1 | var state = {
  2 | 	gravity: [0, 20],
  3 | 	size: [30, 22],
  4 | 	props: [],
  5 | 	camera: {
  6 | 		position: [0, 0],
  7 | 		zoom: 0.6
  8 | 	}
  9 | };
 10 | 
 11 | var config = {
 12 | 	imageScale: 50, // Size of images compared to defined shapes
 13 | 	elementScale: 50, // Size to create/place elements
 14 | 	maxTimestep: 0.5
 15 | };
 16 | var renderFramerate = 24;
 17 | var renderDuration  = 60; // in seconds
 18 | 
 19 | // Graphics setup ----------
 20 | 
 21 | var container = document.getElementById('container');
 22 | var stage     = document.getElementById('stage');
 23 | stage.style.width  = state.size[0] * config.elementScale + 'px';
 24 | stage.style.height = state.size[1] * config.elementScale + 'px';
 25 | updateStageTransform();
 26 | function setCamera(position, zoom) {
 27 | 	state.camera.position = position;
 28 | 	state.camera.zoom     = zoom;
 29 | 	updateStageTransform();
 30 | }
 31 | function updateStageTransform() {
 32 | 	var position = state.camera.position;
 33 | 	var zoom     = state.camera.zoom;
 34 | 	stage.style.transform =
 35 | 		'scale(' +
 36 | 			state.camera.zoom + ')' +
 37 | 		'translate3d(' +
 38 | 			(-position[0] * config.elementScale) + 'px,' +
 39 | 			(-position[1] * config.elementScale) + 'px,0)';
 40 | }
 41 | var degreesPerRadian = 57.2957795;
 42 | 
 43 | var animator = new Animator();
 44 | 
 45 | // Physics setup ----------
 46 | 
 47 | var phys2D = Physics2DDevice.create();
 48 | var world = phys2D.createWorld({gravity: state.gravity});
 49 | 
 50 | var conveyorBeltMaterial = phys2D.createMaterial({
 51 | 	elasticity: 0,
 52 | 	staticFriction: 10,
 53 | 	dynamicFriction: 8,
 54 | 	rollingFriction: 0.1
 55 | });
 56 | 
 57 | var handReferenceBody = phys2D.createRigidBody({type: 'static'});
 58 | world.addRigidBody(handReferenceBody);
 59 | var handConstraint = null;
 60 | 
 61 | // FPS counter setup ----------
 62 | 
 63 | var previousTime = Date.now();
 64 | var fpsElement = document.getElementById('fps');
 65 | 
 66 | function init() {
 67 | 	world.clear();
 68 | 	handConstraint = null;
 69 | 
 70 | 	var thickness = 0.01;
 71 | 	var border = phys2D.createRigidBody({
 72 | 		type: 'static',
 73 | 		shapes: [
 74 | 			phys2D.createPolygonShape({
 75 | 				vertices: phys2D.createRectangleVertices(0, 0, thickness, state.size[1])
 76 | 			}),
 77 | 			phys2D.createPolygonShape({
 78 | 				vertices: phys2D.createRectangleVertices(0, 0, state.size[0], thickness)
 79 | 			}),
 80 | 			phys2D.createPolygonShape({
 81 | 				vertices: phys2D.createRectangleVertices((state.size[0] - thickness), 0, state.size[0], state.size[1])
 82 | 			}),
 83 | 			phys2D.createPolygonShape({
 84 | 				vertices: phys2D.createRectangleVertices(0, (state.size[1] - thickness), state.size[0], state.size[1])
 85 | 			})
 86 | 		]
 87 | 	});
 88 | 	world.addRigidBody(border);
 89 | 
 90 | 	function addBelt(start, end, radius, speed) {
 91 | 		var prop = {
 92 | 			name:   'belt',
 93 | 			start:  start,
 94 | 			end:    end,
 95 | 			radius: radius,
 96 | 			speed:  speed
 97 | 		};
 98 | 		state.props.push(prop);
 99 | 	}
100 | 	addBelt([ 0, 11], [ 7, 14], 0.5,  2);
101 | 	addBelt([ 7, 14], [12, 12], 0.5,  2);
102 | 	addBelt([12, 18], [20, 15], 0.5, 12);
103 | 	addBelt([ 0, 22], [21, 22], 0.5,  2);
104 | 	addBelt([20, 10], [10,  5], 0.5, -2);
105 | 	addBelt([10,  5], [ 5,  5], 0.5, -2);
106 | 	addBelt([21, 22], [30, 10], 0.5, 10);
107 | 
108 | 	state.props.forEach(function(prop) {
109 | 		PropManager.init(prop, stage, phys2D, world);
110 | 	});
111 | 
112 | 	function getMousePosition(event, element) {
113 | 		var elementBounds = element.getBoundingClientRect();
114 | 		var elementX = elementBounds.left;
115 | 		var elementY = elementBounds.top;
116 | 		var mouseX = event.clientX;
117 | 		var mouseY = event.clientY;
118 | 		return [mouseX - elementX, mouseY - elementY];
119 | 	}
120 | 	function getWorldPosition(displayPosition) {
121 | 		var p = VMath.v2ScalarMul(
122 | 			displayPosition,
123 | 			1 / config.elementScale / state.camera.zoom
124 | 		);
125 | 		return VMath.v2Add(p, state.camera.position, p);
126 | 	}
127 | 	var mouseDown = function(event) {
128 | 		if (handConstraint) return;
129 | 
130 | 		var point = getWorldPosition(getMousePosition(event, container));
131 | 
132 | 		var bodies = [];
133 | 		world.bodyPointQuery(point, bodies);
134 | 		if (bodies[0]) {
135 | 			handConstraint = phys2D.createPointConstraint({
136 | 				bodyA: handReferenceBody,
137 | 				bodyB: bodies[0],
138 | 				anchorA: point,
139 | 				anchorB: bodies[0].transformWorldPointToLocal(point),
140 | 				stiff: false,
141 | 				maxForce: 1e6
142 | 			});
143 | 			world.addConstraint(handConstraint);
144 | 			console.log(bodies[0].userData.prop);
145 | 		}
146 | 		event.preventDefault();
147 | 		event.stopPropagation();
148 | 	}
149 | 	var mouseMove = function(event) {
150 | 		if (!handConstraint) return;
151 | 
152 | 		var point = getWorldPosition(getMousePosition(event, container));
153 | 
154 | 		handConstraint.setAnchorA(point);
155 | 	}
156 | 	var mouseUp = function(event) {
157 | 		if (handConstraint) {
158 | 			world.removeConstraint(handConstraint);
159 | 			handConstraint = null;
160 | 		}
161 | 	}
162 | 	container.addEventListener('mousedown', mouseDown, false);
163 | 	container.addEventListener('mousemove', mouseMove, false);
164 | 	container.addEventListener('mouseup',   mouseUp,   false);
165 | 	container.addEventListener('touchstart', function(event) {
166 | 		mouseDown(event.touches[0]);
167 | 	}, false);
168 | 	container.addEventListener('touchmove',  function(event) {
169 | 		mouseMove(event.touches[0]);
170 | 	}, false);
171 | 	container.addEventListener('touchend',   function(event) {
172 | 		mouseUp(event.touches[0]);
173 | 	}, false);
174 | }
175 | 
176 | var update = function(delta, realDelta) {
177 | 	if (handConstraint) {
178 | 		var body = handConstraint.bodyB;
179 | 		body.setAngularVelocity(body.getAngularVelocity() * 0.9);
180 | 	}
181 | 	world.step(delta);
182 | 
183 | 	state.props.forEach(function(prop) {
184 | 		PropManager.update(prop);
185 | 	});
186 | 
187 | 	var fps = 1 / realDelta;
188 | 	var renderSeconds = delta * renderDuration * renderFramerate;
189 | 	fpsElement.innerHTML =
190 | 		('   ' + fps.toFixed(1)).substr(-5, 5) + 'fps | ' +
191 | 		renderDuration + 's @ ' + renderFramerate + 'fps → ' +
192 | 		(renderSeconds / 60 / 60).toFixed(2) + ' hours (' +
193 | 		(renderSeconds / 60).toFixed(1) + ' minutes)';
194 | }
195 | 
196 | init();
197 | animator.start(update);
198 | 
199 | // Load prop definitions and add props once loaded
200 | // -----------------------------------------------
201 | function addProp(propDef) {
202 | 	var bodies = [];
203 | 	propDef.bodies.forEach(function() {
204 | 		bodies.push({
205 | 			position: [10, 10],
206 | 			rotation: 0,
207 | 			velocity: [0, 0],
208 | 			angularVelocity: 0
209 | 		});
210 | 	});
211 | 	var prop = {
212 | 		name:   name,
213 | 		bodies: bodies,
214 | 	};
215 | 	PropManager.init(prop, stage, phys2D, world);
216 | 	state.props.push(prop);
217 | }
218 | var request = new XMLHttpRequest();
219 | request.open("GET", "characters.json");
220 | request.onload = function() {
221 | 	PropManager.managers.simpleProp.propDefs = JSON.parse(request.responseText);
222 | 
223 | 	for (name in PropManager.managers.simpleProp.propDefs) {
224 | 		addProp(PropManager.managers.simpleProp.propDefs[name]);
225 | 	}
226 | }
227 | request.send();
228 | 


--------------------------------------------------------------------------------
/characters.json:
--------------------------------------------------------------------------------
  1 | {
  2 | "announcer": {
  3 | 	"bodies": [{
  4 | 		"density": 8,
  5 | 		"friction": 0.1,
  6 | 		"restitution": 0.2,
  7 | 		"shapes": [[
  8 | 			[0.44, 0.63]
  9 | 		]],
 10 | 		"mirrorX": true,
 11 | 		"mirrorY": true
 12 | 	}]
 13 | },
 14 | "blocky": {
 15 | 	"bodies": [{
 16 | 		"density": 6,
 17 | 		"friction": 0.2,
 18 | 		"restitution": 0.6,
 19 | 		"shapes": [[
 20 | 			[0.00, 0.03],
 21 | 			[1.40, 0.00],
 22 | 			[1.40, 1.23],
 23 | 			[1.26, 1.40],
 24 | 			[0.00, 1.37]
 25 | 		]],
 26 | 		"imageOffset": [-0.25, 0]
 27 | 	}]
 28 | },
 29 | "bomby": {
 30 | 	"bodies": [{
 31 | 		"density": 8,
 32 | 		"friction": 0.3,
 33 | 		"restitution": 0.7,
 34 | 		"shapes": [
 35 | 			{
 36 | 				"radius": 0.95,
 37 | 				"origin": [0.95, 1.21]
 38 | 			},
 39 | 			[
 40 | 				[1.46, 0.00],
 41 | 				[1.78, 0.22],
 42 | 				[1.61, 0.55],
 43 | 				[1.20, 0.30]
 44 | 			]
 45 | 		],
 46 | 		"imageOffset": [-0.26, -0.08]
 47 | 	}]
 48 | },
 49 | "book": {
 50 | 	"bodies": [{
 51 | 		"density": 7,
 52 | 		"friction": 0.4,
 53 | 		"restitution": 0.3,
 54 | 		"shapes": [[
 55 | 			[0.71, 0.79]
 56 | 		]],
 57 | 		"mirrorX": true,
 58 | 		"mirrorY": true,
 59 | 		"imageOffset": [-0.15, 0]
 60 | 	}]
 61 | },
 62 | "bubble": {
 63 | 	"bodies": [{
 64 | 		"density": 0.1,
 65 | 		"friction": 0.01,
 66 | 		"restitution": 0.7,
 67 | 		"shapes": [{
 68 | 			"radius": 0.98,
 69 | 			"origin": [0, 0]
 70 | 		}]
 71 | 	}]
 72 | },
 73 | "coiny": {
 74 | 	"bodies": [{
 75 | 		"density": 3,
 76 | 		"friction": 0.1,
 77 | 		"restitution": 0.7,
 78 | 		"shapes": [{
 79 | 			"radius": 0.57,
 80 | 			"origin": [0, 0]
 81 | 		}],
 82 | 		"imageOffset": [-0.17, 0.03]
 83 | 	}]
 84 | },
 85 | "donut": {
 86 | 	"bodies": [{
 87 | 		"density": 5,
 88 | 		"friction": 0.6,
 89 | 		"restitution": 0.3,
 90 | 		"shapes": [{
 91 | 			"radius": 0.84,
 92 | 			"origin": [0, 0]
 93 | 		}]
 94 | 	}]
 95 | },
 96 | "eraser": {
 97 | 	"bodies": [{
 98 | 		"density": 4,
 99 | 		"friction": 0.9,
100 | 		"restitution": 0.5,
101 | 		"shapes": [[
102 | 			[0.66, -0.02],
103 | 			[1.87,  0.10],
104 | 			[1.21,  1.81],
105 | 			[0.00,  1.70]
106 | 		]],
107 | 		"imageOffset": [-0.07, 0.02]
108 | 	}]
109 | },
110 | "firey": {
111 | 	"bodies": [{
112 | 		"density": 4,
113 | 		"friction": 0.2,
114 | 		"restitution": 0.4,
115 | 		"shapes": [
116 | 			[
117 | 				[1.04, 0.31],
118 | 				[1.54, 0.94],
119 | 				[1.55, 1.48],
120 | 				[1.16, 1.78],
121 | 				[0.62, 1.83],
122 | 				[0.19, 1.67],
123 | 				[0.00, 1.34]
124 | 			],
125 | 			[
126 | 				[1.43, 0.00],
127 | 				[1.54, 0.94],
128 | 				[1.22, 0.58]
129 | 			],
130 | 			[
131 | 				[0.64, 0.26],
132 | 				[0.96, 0.96],
133 | 				[0.26, 0.77]
134 | 			],
135 | 			[
136 | 				[0.15, 0.09],
137 | 				[0.63, 0.95],
138 | 				[0.00, 1.34]
139 | 			]
140 | 		],
141 | 		"imageOffset": [-0.22, 0]
142 | 	}]
143 | },
144 | "flower": {
145 | 	"bodies": [
146 | 		{
147 | 			"name": "flower-body",
148 | 			"density": 3,
149 | 			"friction": 0.5,
150 | 			"restitution": 0.2,
151 | 			"shapes": [[
152 | 				[0.00, 0.00],
153 | 				[0.25, 1.42]
154 | 			]],
155 | 			"mirrorX": true,
156 | 			"imageOffset": [0.00, -0.03],
157 | 			"group": 4,
158 | 			"mask": 5
159 | 		},
160 | 		{
161 | 			"density": 2,
162 | 			"friction": 0.6,
163 | 			"restitution": 0.2,
164 | 			"shapes": [
165 | 				[
166 | 					[0.95, 0.00],
167 | 					[1.30, 0.13],
168 | 					[1.27, 1.07],
169 | 					[0.64, 1.07],
170 | 					[0.61, 0.13]
171 | 				],
172 | 				[
173 | 					[1.91, 0.70],
174 | 					[1.90, 1.06],
175 | 					[0.99, 1.32],
176 | 					[0.79, 0.73],
177 | 					[1.68, 0.40]
178 | 				],
179 | 				[
180 | 					[1.55, 1.81],
181 | 					[1.19, 1.92],
182 | 					[0.66, 1.13],
183 | 					[1.17, 0.76],
184 | 					[1.75, 1.51]
185 | 				],
186 | 				[
187 | 					[0.37, 1.82],
188 | 					[0.16, 1.51],
189 | 					[0.74, 0.76],
190 | 					[1.25, 1.13],
191 | 					[0.72, 1.92]
192 | 				],
193 | 				[
194 | 					[0.00, 0.70],
195 | 					[0.23, 0.40],
196 | 					[1.12, 0.73],
197 | 					[0.93, 1.32],
198 | 					[0.01, 1.06]
199 | 				]
200 | 			],
201 | 			"imageOffset": [-0.04, 0],
202 | 			"group": 2,
203 | 			"mask": 3
204 | 		}
205 | 	],
206 | 	"constraints": [
207 | 		{
208 | 			"bodyA": 0,
209 | 			"bodyB": 1,
210 | 			"anchorA": [0.10, -0.60],
211 | 			"anchorB": [0.79,  1.00]
212 | 		},
213 | 		{
214 | 			"bodyA": 0,
215 | 			"bodyB": 1,
216 | 			"anchorA": [0.10, 0.00],
217 | 			"anchorB": [0.79, 1.60],
218 | 			"stiff": false
219 | 		}
220 | 	]
221 | },
222 | "fries": {
223 | 	"bodies": [{
224 | 		"density": 3,
225 | 		"friction": 0.5,
226 | 		"restitution": 0.2,
227 | 		"shapes": [[
228 | 			[0.32, 0.09],
229 | 			[0.79, 0.60],
230 | 			[0.51, 2.12]
231 | 		]],
232 | 		"mirrorX": true,
233 | 		"imageOffset": [-0.14, -0.09]
234 | 	}]
235 | },
236 | "gelatin": {
237 | 	"bodies": [{
238 | 		"density": 3,
239 | 		"friction": 0.05,
240 | 		"restitution": 0.2,
241 | 		"shapes": [[
242 | 			[0.00, 0.00],
243 | 			[0.45, 0.10],
244 | 			[0.67, 0.72],
245 | 			[0.65, 1.25],
246 | 			[0.00, 1.33]
247 | 		]],
248 | 		"mirrorX": true,
249 | 		"imageOffset": [-0.16, 0]
250 | 	}]
251 | },
252 | "golf-ball": {
253 | 	"bodies": [{
254 | 		"density": 8,
255 | 		"friction": 0.2,
256 | 		"restitution": 0.4,
257 | 		"shapes": [{
258 | 			"radius": 0.62,
259 | 			"origin": [0, 0]
260 | 		}]
261 | 	}]
262 | },
263 | "ice-cube": {
264 | 	"bodies": [{
265 | 		"density": 9,
266 | 		"friction": 0.01,
267 | 		"restitution": 0.1,
268 | 		"shapes": [[
269 | 			[0.46, 0.02],
270 | 			[1.41, 0.02],
271 | 			[1.41, 1.13],
272 | 			[1.16, 1.45],
273 | 			[0.01, 1.45],
274 | 			[0.01, 0.24]
275 | 		]],
276 | 		"imageOffset": [-0.01, -0.02]
277 | 	}]
278 | },
279 | "leafy": {
280 | 	"bodies": [{
281 | 		"density": 1,
282 | 		"friction": 0.2,
283 | 		"restitution": 0.5,
284 | 		"shapes": [[
285 | 			[0.55, 0.00],
286 | 			[0.47, 0.39],
287 | 			[0.00, 0.92]
288 | 		]],
289 | 		"mirrorX": true,
290 | 		"mirrorY": true,
291 | 		"imageOffset": [-0.17, -0.02]
292 | 	}]
293 | },
294 | "match": {
295 | 	"bodies": [{
296 | 		"density": 3,
297 | 		"friction": 0.5,
298 | 		"restitution": 0.8,
299 | 		"shapes": [
300 | 			{
301 | 				"radius": 0.30,
302 | 				"origin": [0, 0.30]
303 | 			},
304 | 			[
305 | 				[0.20, 0.40],
306 | 				[0.20, 2.48]
307 | 			]
308 | 		],
309 | 		"mirrorX": true,
310 | 		"imageOffset": [-0.28, -0.10]
311 | 	}]
312 | },
313 | "needle": {
314 | 	"bodies": [{
315 | 		"density": 9,
316 | 		"friction": 0.05,
317 | 		"restitution": 0.85,
318 | 		"shapes": [
319 | 			{
320 | 				"radius": 0.22,
321 | 				"origin": [0, 0.12]
322 | 			},
323 | 			[
324 | 				[0.22, 0.12],
325 | 				[0.00, 2.54]
326 | 			]
327 | 		],
328 | 		"mirrorX": true,
329 | 		"imageOffset": [-0.04, 0]
330 | 	}]
331 | },
332 | "nickel": {
333 | 	"bodies": [{
334 | 		"density": 3,
335 | 		"friction": 0.1,
336 | 		"restitution": 0.7,
337 | 		"shapes": [{
338 | 			"radius": 0.57,
339 | 			"origin": [0, 0]
340 | 		}],
341 | 		"imageOffset": [-0.02, 0.03]
342 | 	}]
343 | },
344 | "pen": {
345 | 	"bodies": [{
346 | 		"density": 2,
347 | 		"friction": 0.2,
348 | 		"restitution": 0.8,
349 | 		"shapes": [
350 | 			[
351 | 				[0.05, 0.00],
352 | 				[0.45, 0.00],
353 | 				[0.56, 0.56],
354 | 				[0.56, 1.53],
355 | 				[0.45, 2.46],
356 | 				[0.05, 2.46]
357 | 			]
358 | 		],
359 | 		"imageOffset": [-0.22, 0]
360 | 	}]
361 | },
362 | "pencil": {
363 | 	"bodies": [{
364 | 		"density": 3,
365 | 		"friction": 0.3,
366 | 		"restitution": 0.7,
367 | 		"shapes": [[
368 | 			[0.22, 0.00],
369 | 			[0.22, 2.12],
370 | 			[0.00, 2.51]
371 | 		]],
372 | 		"mirrorX": true,
373 | 		"imageOffset": [-0.23, 0]
374 | 	}]
375 | },
376 | "pin": {
377 | 	"bodies": [{
378 | 		"density": 7,
379 | 		"friction": 0.2,
380 | 		"restitution": 0.6,
381 | 		"shapes": [
382 | 			[
383 | 				[0.64, 0.63],
384 | 				[0.00, 1.97]
385 | 			],
386 | 			[
387 | 				[0.00, 0.00],
388 | 				[0.64, 1.97]
389 | 			]
390 | 		],
391 | 		"mirrorX": true,
392 | 		"imageOffset": [-0.04, 0]
393 | 	}]
394 | },
395 | "puffball": {
396 | 	"bodies": [{
397 | 		"density": 5,
398 | 		"friction": 0.8,
399 | 		"restitution": 0.2,
400 | 		"shapes": [{
401 | 			"radius": 0.66,
402 | 			"origin": [0, 0]
403 | 		}],
404 | 		"imageOffset": [-0.05, -0.05]
405 | 	}]
406 | },
407 | "rocky": {
408 | 	"bodies": [{
409 | 		"density": 8,
410 | 		"friction": 0.7,
411 | 		"restitution": 0.5,
412 | 		"shapes": [[
413 | 			[0.31, 0.11],
414 | 			[1.06, 0.01],
415 | 			[1.52, 0.73],
416 | 			[0.76, 0.89],
417 | 			[0.00, 0.73]
418 | 		]],
419 | 		"imageOffset": [0, -0.01]
420 | 	}]
421 | },
422 | "ruby": {
423 | 	"bodies": [{
424 | 		"density": 1.4,
425 | 		"friction": 0.05,
426 | 		"restitution": 0.5,
427 | 		"shapes": [[
428 | 			[0.55, 0.00],
429 | 			[1.01, 0.33],
430 | 			[0.00, 1.18]
431 | 		]],
432 | 		"mirrorX": true
433 | 	}]
434 | },
435 | "snowball": {
436 | 	"bodies": [{
437 | 		"density": 7,
438 | 		"friction": 0.3,
439 | 		"restitution": 0.05,
440 | 		"shapes": [{
441 | 			"radius": 1.03,
442 | 			"origin": [0, 0]
443 | 		}]
444 | 	}]
445 | },
446 | "spongy": {
447 | 	"bodies": [{
448 | 		"density": 3,
449 | 		"friction": 0.9,
450 | 		"restitution": 0.8,
451 | 		"shapes": [[
452 | 			[0.10, 0.06],
453 | 			[3.91, 0.03],
454 | 			[4.00, 2.25],
455 | 			[3.61, 2.40],
456 | 			[0.03, 2.40]
457 | 		]]
458 | 	}]
459 | },
460 | "teardrop": {
461 | 	"bodies": [{
462 | 		"density": 7,
463 | 		"friction": 0.01,
464 | 		"restitution": 0.7,
465 | 		"shapes": [
466 | 			{
467 | 				"radius": 0.47,
468 | 				"origin": [0.47, 1.04]
469 | 			},
470 | 			[
471 | 				[0.22, 0.00],
472 | 				[0.69, 0.33],
473 | 				[0.93, 0.98],
474 | 				[0.29, 0.67]
475 | 			]
476 | 		],
477 | 		"imageOffset": [-0.24, 0]
478 | 	}]
479 | },
480 | "tennis-ball": {
481 | 	"bodies": [{
482 | 		"density": 5,
483 | 		"friction": 0.4,
484 | 		"restitution": 0.9,
485 | 		"shapes": [{
486 | 			"radius": 0.98,
487 | 			"origin": [0, 0]
488 | 		}]
489 | 	}]
490 | },
491 | "woody": {
492 | 	"bodies": [{
493 | 		"density": 2,
494 | 		"friction": 0.4,
495 | 		"restitution": 0.7,
496 | 		"shapes": [[
497 | 			[0.13, 0.11],
498 | 			[0.71, 0.00],
499 | 			[1.57, 0.15],
500 | 			[1.53, 1.53],
501 | 			[0.18, 1.64],
502 | 			[0.00, 1.21]
503 | 		]]
504 | 	}]
505 | },
506 | "yellow-face": {
507 | 	"bodies": [{
508 | 		"density": 3,
509 | 		"friction": 0.3,
510 | 		"restitution": 0.8,
511 | 		"shapes": [[
512 | 			[ 0.80, -0.02],
513 | 			[ 1.87,  0.59],
514 | 			[ 2.05,  1.33],
515 | 			[ 1.63,  1.99],
516 | 			[ 1.21,  2.11],
517 | 			[ 0.40,  1.76],
518 | 			[-0.02,  0.95],
519 | 			[ 0.28,  0.22]
520 | 		]],
521 | 		"imageOffset": [0.02, 0.02]
522 | 	}]
523 | }
524 | }
525 | 


--------------------------------------------------------------------------------
/js/msgpack.js:
--------------------------------------------------------------------------------
  1 | /*
  2 | Copyright (c) 2010 uupaa.js@gmail.com
  3 | 
  4 | Permission is hereby granted, free of charge, to any person obtaining
  5 | a copy of this software and associated documentation files (the
  6 | "Software"), to deal in the Software without restriction, including
  7 | without limitation the rights to use, copy, modify, merge, publish,
  8 | distribute, sublicense, and/or sell copies of the Software, and to
  9 | permit persons to whom the Software is furnished to do so, subject to
 10 | the following conditions:
 11 | 
 12 | The above copyright notice and this permission notice shall be
 13 | included in all copies or substantial portions of the Software.
 14 | 
 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 19 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 20 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 21 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 22 | */
 23 | 
 24 | /*!{id:msgpack.codec.js,ver:1.05,license:"MIT",author:"uupaa.js@gmail.com"}*/
 25 | 
 26 | // === msgpack ===
 27 | // MessagePack -> http://msgpack.sourceforge.net/
 28 | 
 29 | this.msgpack || (function(globalScope) {
 30 | 
 31 | globalScope.msgpack = {
 32 |     pack:       msgpackpack,    // msgpack.pack(data:Mix,
 33 |                                 //              toString:Boolean = false):ByteArray/ByteString/false
 34 |                                 //  [1][mix to String]    msgpack.pack({}, true) -> "..."
 35 |                                 //  [2][mix to ByteArray] msgpack.pack({})       -> [...]
 36 |     unpack:     msgpackunpack   // msgpack.unpack(data:BinaryString/ByteArray):Mix
 37 |                                 //  [1][String to mix]    msgpack.unpack("...") -> {}
 38 |                                 //  [2][ByteArray to mix] msgpack.unpack([...]) -> {}
 39 | };
 40 | 
 41 | var _bin2num    = {}, // BinaryStringToNumber   { "\00": 0, ... "\ff": 255 }
 42 |     _num2bin    = {}, // NumberToBinaryString   { 0: "\00", ... 255: "\ff" }
 43 |     _buf        = [], // decode buffer
 44 |     _idx        = 0,  // decode buffer[index]
 45 |     _error      = 0,  // msgpack.pack() error code. 1 = CYCLIC_REFERENCE_ERROR
 46 |     _isArray    = Array.isArray || (function(mix) {
 47 |                     return Object.prototype.toString.call(mix) === "[object Array]";
 48 |                   }),
 49 |     _toString   = String.fromCharCode, // CharCode/ByteArray to String
 50 |     _MAX_DEPTH  = 512;
 51 | 
 52 | // msgpack.pack
 53 | function msgpackpack(data,       // @param Mix:
 54 |                      toString) { // @param Boolean(= false):
 55 |                                  // @return ByteArray/BinaryString/false:
 56 |                                  //     false is error return
 57 |     //  [1][mix to String]    msgpack.pack({}, true) -> "..."
 58 |     //  [2][mix to ByteArray] msgpack.pack({})       -> [...]
 59 | 
 60 |     _error = 0;
 61 | 
 62 |     var byteArray = encode([], data, 0);
 63 | 
 64 |     return _error ? false
 65 |                   : toString ? byteArrayToByteString(byteArray)
 66 |                              : byteArray;
 67 | }
 68 | 
 69 | // msgpack.unpack
 70 | function msgpackunpack(data) { // @param BinaryString/ByteArray:
 71 |                                // @return Mix/undefined:
 72 |                                //       undefined is error return
 73 |     //  [1][String to mix]    msgpack.unpack("...") -> {}
 74 |     //  [2][ByteArray to mix] msgpack.unpack([...]) -> {}
 75 | 
 76 |     _buf = typeof data === "string" ? toByteArray(data) : data;
 77 |     _idx = -1;
 78 |     return decode(); // mix or undefined
 79 | }
 80 | 
 81 | // inner - encoder
 82 | function encode(rv,      // @param ByteArray: result
 83 |                 mix,     // @param Mix: source data
 84 |                 depth) { // @param Number: depth
 85 |     var size, i, iz, c, pos,        // for UTF8.encode, Array.encode, Hash.encode
 86 |         high, low, sign, exp, frac; // for IEEE754
 87 | 
 88 |     if (mix == null) { // null or undefined -> 0xc0 ( null )
 89 |         rv.push(0xc0);
 90 |     } else if (mix === false) { // false -> 0xc2 ( false )
 91 |         rv.push(0xc2);
 92 |     } else if (mix === true) {  // true  -> 0xc3 ( true  )
 93 |         rv.push(0xc3);
 94 |     } else {
 95 |         switch (typeof mix) {
 96 |         case "number":
 97 |             if (mix !== mix) { // isNaN
 98 |                 rv.push(0xcb, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff); // quiet NaN
 99 |             } else if (mix === Infinity) {
100 |                 rv.push(0xcb, 0x7f, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00); // positive infinity
101 |             } else if (Math.floor(mix) === mix) { // int or uint
102 |                 if (mix < 0) {
103 |                     // int
104 |                     if (mix >= -32) { // negative fixnum
105 |                         rv.push(0xe0 + mix + 32);
106 |                     } else if (mix > -0x80) {
107 |                         rv.push(0xd0, mix + 0x100);
108 |                     } else if (mix > -0x8000) {
109 |                         mix += 0x10000;
110 |                         rv.push(0xd1, mix >> 8, mix & 0xff);
111 |                     } else if (mix > -0x80000000) {
112 |                         mix += 0x100000000;
113 |                         rv.push(0xd2, mix >>> 24, (mix >> 16) & 0xff,
114 |                                                   (mix >>  8) & 0xff, mix & 0xff);
115 |                     } else {
116 |                         high = Math.floor(mix / 0x100000000);
117 |                         low  = mix & 0xffffffff;
118 |                         rv.push(0xd3, (high >> 24) & 0xff, (high >> 16) & 0xff,
119 |                                       (high >>  8) & 0xff,         high & 0xff,
120 |                                       (low  >> 24) & 0xff, (low  >> 16) & 0xff,
121 |                                       (low  >>  8) & 0xff,          low & 0xff);
122 |                     }
123 |                 } else {
124 |                     // uint
125 |                     if (mix < 0x80) {
126 |                         rv.push(mix); // positive fixnum
127 |                     } else if (mix < 0x100) { // uint 8
128 |                         rv.push(0xcc, mix);
129 |                     } else if (mix < 0x10000) { // uint 16
130 |                         rv.push(0xcd, mix >> 8, mix & 0xff);
131 |                     } else if (mix < 0x100000000) { // uint 32
132 |                         rv.push(0xce, mix >>> 24, (mix >> 16) & 0xff,
133 |                                                   (mix >>  8) & 0xff, mix & 0xff);
134 |                     } else {
135 |                         high = Math.floor(mix / 0x100000000);
136 |                         low  = mix & 0xffffffff;
137 |                         rv.push(0xcf, (high >> 24) & 0xff, (high >> 16) & 0xff,
138 |                                       (high >>  8) & 0xff,         high & 0xff,
139 |                                       (low  >> 24) & 0xff, (low  >> 16) & 0xff,
140 |                                       (low  >>  8) & 0xff,          low & 0xff);
141 |                     }
142 |                 }
143 |             } else { // double
144 |                 // THX!! @edvakf
145 |                 // http://javascript.g.hatena.ne.jp/edvakf/20101128/1291000731
146 |                 sign = mix < 0;
147 |                 sign && (mix *= -1);
148 | 
149 |                 // add offset 1023 to ensure positive
150 |                 // 0.6931471805599453 = Math.LN2;
151 |                 exp  = ((Math.log(mix) / 0.6931471805599453) + 1023) | 0;
152 | 
153 |                 // shift 52 - (exp - 1023) bits to make integer part exactly 53 bits,
154 |                 // then throw away trash less than decimal point
155 |                 frac = mix * Math.pow(2, 52 + 1023 - exp);
156 | 
157 |                 //  S+-Exp(11)--++-----------------Fraction(52bits)-----------------------+
158 |                 //  ||          ||                                                        |
159 |                 //  v+----------++--------------------------------------------------------+
160 |                 //  00000000|00000000|00000000|00000000|00000000|00000000|00000000|00000000
161 |                 //  6      5    55  4        4        3        2        1        8        0
162 |                 //  3      6    21  8        0        2        4        6
163 |                 //
164 |                 //  +----------high(32bits)-----------+ +----------low(32bits)------------+
165 |                 //  |                                 | |                                 |
166 |                 //  +---------------------------------+ +---------------------------------+
167 |                 //  3      2    21  1        8        0
168 |                 //  1      4    09  6
169 |                 low  = frac & 0xffffffff;
170 |                 sign && (exp |= 0x800);
171 |                 high = ((frac / 0x100000000) & 0xfffff) | (exp << 20);
172 | 
173 |                 rv.push(0xcb, (high >> 24) & 0xff, (high >> 16) & 0xff,
174 |                               (high >>  8) & 0xff,  high        & 0xff,
175 |                               (low  >> 24) & 0xff, (low  >> 16) & 0xff,
176 |                               (low  >>  8) & 0xff,  low         & 0xff);
177 |             }
178 |             break;
179 |         case "string":
180 |             // http://d.hatena.ne.jp/uupaa/20101128
181 |             iz = mix.length;
182 |             pos = rv.length; // keep rewrite position
183 | 
184 |             rv.push(0); // placeholder
185 | 
186 |             // utf8.encode
187 |             for (i = 0; i < iz; ++i) {
188 |                 c = mix.charCodeAt(i);
189 |                 if (c < 0x80) { // ASCII(0x00 ~ 0x7f)
190 |                     rv.push(c & 0x7f);
191 |                 } else if (c < 0x0800) {
192 |                     rv.push(((c >>>  6) & 0x1f) | 0xc0, (c & 0x3f) | 0x80);
193 |                 } else if (c < 0x10000) {
194 |                     rv.push(((c >>> 12) & 0x0f) | 0xe0,
195 |                             ((c >>>  6) & 0x3f) | 0x80, (c & 0x3f) | 0x80);
196 |                 }
197 |             }
198 |             size = rv.length - pos - 1;
199 | 
200 |             if (size < 32) {
201 |                 rv[pos] = 0xa0 + size; // rewrite
202 |             } else if (size < 0x10000) { // 16
203 |                 rv.splice(pos, 1, 0xda, size >> 8, size & 0xff);
204 |             } else if (size < 0x100000000) { // 32
205 |                 rv.splice(pos, 1, 0xdb,
206 |                           size >>> 24, (size >> 16) & 0xff,
207 |                                        (size >>  8) & 0xff, size & 0xff);
208 |             }
209 |             break;
210 |         default: // array or hash
211 |             if (++depth >= _MAX_DEPTH) {
212 |                 _error = 1; // CYCLIC_REFERENCE_ERROR
213 |                 return rv = []; // clear
214 |             }
215 |             if (_isArray(mix)) {
216 |                 size = mix.length;
217 |                 if (size < 16) {
218 |                     rv.push(0x90 + size);
219 |                 } else if (size < 0x10000) { // 16
220 |                     rv.push(0xdc, size >> 8, size & 0xff);
221 |                 } else if (size < 0x100000000) { // 32
222 |                     rv.push(0xdd, size >>> 24, (size >> 16) & 0xff,
223 |                                                (size >>  8) & 0xff, size & 0xff);
224 |                 }
225 |                 for (i = 0; i < size; ++i) {
226 |                     encode(rv, mix[i], depth);
227 |                 }
228 |             } else { // hash
229 |                 // http://d.hatena.ne.jp/uupaa/20101129
230 |                 pos = rv.length; // keep rewrite position
231 |                 rv.push(0); // placeholder
232 |                 size = 0;
233 |                 for (i in mix) {
234 |                     ++size;
235 |                     encode(rv, i,      depth);
236 |                     encode(rv, mix[i], depth);
237 |                 }
238 |                 if (size < 16) {
239 |                     rv[pos] = 0x80 + size; // rewrite
240 |                 } else if (size < 0x10000) { // 16
241 |                     rv.splice(pos, 1, 0xde, size >> 8, size & 0xff);
242 |                 } else if (size < 0x100000000) { // 32
243 |                     rv.splice(pos, 1, 0xdf,
244 |                               size >>> 24, (size >> 16) & 0xff,
245 |                                            (size >>  8) & 0xff, size & 0xff);
246 |                 }
247 |             }
248 |         }
249 |     }
250 |     return rv;
251 | }
252 | 
253 | // inner - decoder
254 | function decode() { // @return Mix:
255 |     var size, i, iz, c, num = 0,
256 |         sign, exp, frac, ary, hash,
257 |         buf = _buf, type = buf[++_idx];
258 | 
259 |     if (type >= 0xe0) {             // Negative FixNum (111x xxxx) (-32 ~ -1)
260 |         return type - 0x100;
261 |     }
262 |     if (type < 0xc0) {
263 |         if (type < 0x80) {          // Positive FixNum (0xxx xxxx) (0 ~ 127)
264 |             return type;
265 |         }
266 |         if (type < 0x90) {          // FixMap (1000 xxxx)
267 |             num  = type - 0x80;
268 |             type = 0x80;
269 |         } else if (type < 0xa0) {   // FixArray (1001 xxxx)
270 |             num  = type - 0x90;
271 |             type = 0x90;
272 |         } else { // if (type < 0xc0) {   // FixRaw (101x xxxx)
273 |             num  = type - 0xa0;
274 |             type = 0xa0;
275 |         }
276 |     }
277 |     switch (type) {
278 |     case 0xc0:  return null;
279 |     case 0xc2:  return false;
280 |     case 0xc3:  return true;
281 |     case 0xca:  // float
282 |                 num = buf[++_idx] * 0x1000000 + (buf[++_idx] << 16) +
283 |                                                 (buf[++_idx] <<  8) + buf[++_idx];
284 |                 sign =  num & 0x80000000;    //  1bit
285 |                 exp  = (num >> 23) & 0xff;   //  8bits
286 |                 frac =  num & 0x7fffff;      // 23bits
287 |                 if (!num || num === 0x80000000) { // 0.0 or -0.0
288 |                     return 0;
289 |                 }
290 |                 if (exp === 0xff) { // NaN or Infinity
291 |                     return frac ? NaN : Infinity;
292 |                 }
293 |                 return (sign ? -1 : 1) *
294 |                             (frac | 0x800000) * Math.pow(2, exp - 127 - 23); // 127: bias
295 |     case 0xcb:  // double
296 |                 num = buf[++_idx] * 0x1000000 + (buf[++_idx] << 16) +
297 |                                                 (buf[++_idx] <<  8) + buf[++_idx];
298 |                 sign =  num & 0x80000000;    //  1bit
299 |                 exp  = (num >> 20) & 0x7ff;  // 11bits
300 |                 frac =  num & 0xfffff;       // 52bits - 32bits (high word)
301 |                 if (!num || num === 0x80000000) { // 0.0 or -0.0
302 |                     _idx += 4;
303 |                     return 0;
304 |                 }
305 |                 if (exp === 0x7ff) { // NaN or Infinity
306 |                     _idx += 4;
307 |                     return frac ? NaN : Infinity;
308 |                 }
309 |                 num = buf[++_idx] * 0x1000000 + (buf[++_idx] << 16) +
310 |                                                 (buf[++_idx] <<  8) + buf[++_idx];
311 |                 return (sign ? -1 : 1) *
312 |                             ((frac | 0x100000) * Math.pow(2, exp - 1023 - 20) // 1023: bias
313 |                              + num * Math.pow(2, exp - 1023 - 52));
314 |     // 0xcf: uint64, 0xce: uint32, 0xcd: uint16
315 |     case 0xcf:  num =  buf[++_idx] * 0x1000000 + (buf[++_idx] << 16) +
316 |                                                  (buf[++_idx] <<  8) + buf[++_idx];
317 |                 return num * 0x100000000 +
318 |                        buf[++_idx] * 0x1000000 + (buf[++_idx] << 16) +
319 |                                                  (buf[++_idx] <<  8) + buf[++_idx];
320 |     case 0xce:  num += buf[++_idx] * 0x1000000 + (buf[++_idx] << 16);
321 |     case 0xcd:  num += buf[++_idx] << 8;
322 |     case 0xcc:  return num + buf[++_idx];
323 |     // 0xd3: int64, 0xd2: int32, 0xd1: int16, 0xd0: int8
324 |     case 0xd3:  num = buf[++_idx];
325 |                 if (num & 0x80) { // sign -> avoid overflow
326 |                     return ((num         ^ 0xff) * 0x100000000000000 +
327 |                             (buf[++_idx] ^ 0xff) *   0x1000000000000 +
328 |                             (buf[++_idx] ^ 0xff) *     0x10000000000 +
329 |                             (buf[++_idx] ^ 0xff) *       0x100000000 +
330 |                             (buf[++_idx] ^ 0xff) *         0x1000000 +
331 |                             (buf[++_idx] ^ 0xff) *           0x10000 +
332 |                             (buf[++_idx] ^ 0xff) *             0x100 +
333 |                             (buf[++_idx] ^ 0xff) + 1) * -1;
334 |                 }
335 |                 return num         * 0x100000000000000 +
336 |                        buf[++_idx] *   0x1000000000000 +
337 |                        buf[++_idx] *     0x10000000000 +
338 |                        buf[++_idx] *       0x100000000 +
339 |                        buf[++_idx] *         0x1000000 +
340 |                        buf[++_idx] *           0x10000 +
341 |                        buf[++_idx] *             0x100 +
342 |                        buf[++_idx];
343 |     case 0xd2:  num  =  buf[++_idx] * 0x1000000 + (buf[++_idx] << 16) +
344 |                        (buf[++_idx] << 8) + buf[++_idx];
345 |                 return num < 0x80000000 ? num : num - 0x100000000; // 0x80000000 * 2
346 |     case 0xd1:  num  = (buf[++_idx] << 8) + buf[++_idx];
347 |                 return num < 0x8000 ? num : num - 0x10000; // 0x8000 * 2
348 |     case 0xd0:  num  =  buf[++_idx];
349 |                 return num < 0x80 ? num : num - 0x100; // 0x80 * 2
350 |     // 0xdb: raw32, 0xda: raw16, 0xa0: raw ( string )
351 |     case 0xdb:  num +=  buf[++_idx] * 0x1000000 + (buf[++_idx] << 16);
352 |     case 0xda:  num += (buf[++_idx] << 8)       +  buf[++_idx];
353 |     case 0xa0:  // utf8.decode
354 |                 for (ary = [], i = _idx, iz = i + num; i < iz; ) {
355 |                     c = buf[++i]; // lead byte
356 |                     ary.push(c < 0x80 ? c : // ASCII(0x00 ~ 0x7f)
357 |                              c < 0xe0 ? ((c & 0x1f) <<  6 | (buf[++i] & 0x3f)) :
358 |                                         ((c & 0x0f) << 12 | (buf[++i] & 0x3f) << 6
359 |                                                           | (buf[++i] & 0x3f)));
360 |                 }
361 |                 _idx = i;
362 |                 return ary.length < 10240 ? _toString.apply(null, ary)
363 |                                           : byteArrayToByteString(ary);
364 |     // 0xdf: map32, 0xde: map16, 0x80: map
365 |     case 0xdf:  num +=  buf[++_idx] * 0x1000000 + (buf[++_idx] << 16);
366 |     case 0xde:  num += (buf[++_idx] << 8)       +  buf[++_idx];
367 |     case 0x80:  hash = {};
368 |                 while (num--) {
369 |                     // make key/value pair
370 |                     size = buf[++_idx] - 0xa0;
371 | 
372 |                     for (ary = [], i = _idx, iz = i + size; i < iz; ) {
373 |                         c = buf[++i]; // lead byte
374 |                         ary.push(c < 0x80 ? c : // ASCII(0x00 ~ 0x7f)
375 |                                  c < 0xe0 ? ((c & 0x1f) <<  6 | (buf[++i] & 0x3f)) :
376 |                                             ((c & 0x0f) << 12 | (buf[++i] & 0x3f) << 6
377 |                                                               | (buf[++i] & 0x3f)));
378 |                     }
379 |                     _idx = i;
380 |                     hash[_toString.apply(null, ary)] = decode();
381 |                 }
382 |                 return hash;
383 |     // 0xdd: array32, 0xdc: array16, 0x90: array
384 |     case 0xdd:  num +=  buf[++_idx] * 0x1000000 + (buf[++_idx] << 16);
385 |     case 0xdc:  num += (buf[++_idx] << 8)       +  buf[++_idx];
386 |     case 0x90:  ary = [];
387 |                 while (num--) {
388 |                     ary.push(decode());
389 |                 }
390 |                 return ary;
391 |     }
392 |     return;
393 | }
394 | 
395 | // inner - byteArray To ByteString
396 | function byteArrayToByteString(byteArray) { // @param ByteArray
397 |                                             // @return String
398 |     // http://d.hatena.ne.jp/uupaa/20101128
399 |     try {
400 |         return _toString.apply(this, byteArray); // toString
401 |     } catch(err) {
402 |         ; // avoid "Maximum call stack size exceeded"
403 |     }
404 |     var rv = [], i = 0, iz = byteArray.length, num2bin = _num2bin;
405 | 
406 |     for (; i < iz; ++i) {
407 |         rv[i] = num2bin[byteArray[i]];
408 |     }
409 |     return rv.join("");
410 | }
411 | 
412 | // inner - BinaryString To ByteArray
413 | function toByteArray(data) { // @param BinaryString: "\00\01"
414 |                              // @return ByteArray: [0x00, 0x01]
415 |     var rv = [], bin2num = _bin2num, remain,
416 |         ary = data.split(""),
417 |         i = -1, iz;
418 | 
419 |     iz = ary.length;
420 |     remain = iz % 8;
421 | 
422 |     while (remain--) {
423 |         ++i;
424 |         rv[i] = bin2num[ary[i]];
425 |     }
426 |     remain = iz >> 3;
427 |     while (remain--) {
428 |         rv.push(bin2num[ary[++i]], bin2num[ary[++i]],
429 |                 bin2num[ary[++i]], bin2num[ary[++i]],
430 |                 bin2num[ary[++i]], bin2num[ary[++i]],
431 |                 bin2num[ary[++i]], bin2num[ary[++i]]);
432 |     }
433 |     return rv;
434 | }
435 | 
436 | // --- init ---
437 | (function() {
438 |     var i = 0, v;
439 | 
440 |     for (; i < 0x100; ++i) {
441 |         v = _toString(i);
442 |         _bin2num[v] = i; // "\00" -> 0x00
443 |         _num2bin[i] = v; //     0 -> "\00"
444 |     }
445 |     // http://twitter.com/edvakf/statuses/15576483807
446 |     for (i = 0x80; i < 0x100; ++i) { // [Webkit][Gecko]
447 |         _bin2num[_toString(0xf700 + i)] = i; // "\f780" -> 0x80
448 |     }
449 | })();
450 | 
451 | })(this);
452 | 


--------------------------------------------------------------------------------
/js/turbulenz/boxtree.js:
--------------------------------------------------------------------------------
   1 | // Copyright (c) 2012-2014 Turbulenz Limited
   2 | /*global Float32Array: false*/
   3 | ;
   4 | 
   5 | ;
   6 | 
   7 | ;
   8 | 
   9 | //
  10 | // BoxTreeNode
  11 | //
  12 | var BoxTreeNode = (function () {
  13 |     function BoxTreeNode(extents, escapeNodeOffset, externalNode) {
  14 |         this.escapeNodeOffset = escapeNodeOffset;
  15 |         this.externalNode = externalNode;
  16 |         this.extents = extents;
  17 |     }
  18 |     BoxTreeNode.prototype.isLeaf = function () {
  19 |         return !!this.externalNode;
  20 |     };
  21 | 
  22 |     BoxTreeNode.prototype.reset = function (minX, minY, maxX, maxY, escapeNodeOffset, externalNode) {
  23 |         this.escapeNodeOffset = escapeNodeOffset;
  24 |         this.externalNode = externalNode;
  25 |         var oldExtents = this.extents;
  26 |         oldExtents[0] = minX;
  27 |         oldExtents[1] = minY;
  28 |         oldExtents[2] = maxX;
  29 |         oldExtents[3] = maxY;
  30 |     };
  31 | 
  32 |     BoxTreeNode.prototype.clear = function () {
  33 |         this.escapeNodeOffset = 1;
  34 |         this.externalNode = undefined;
  35 |         var oldExtents = this.extents;
  36 |         var maxNumber = Number.MAX_VALUE;
  37 |         oldExtents[0] = maxNumber;
  38 |         oldExtents[1] = maxNumber;
  39 |         oldExtents[2] = -maxNumber;
  40 |         oldExtents[3] = -maxNumber;
  41 |     };
  42 | 
  43 |     BoxTreeNode.create = // Constructor function
  44 |     function (extents, escapeNodeOffset, externalNode) {
  45 |         return new BoxTreeNode(extents, escapeNodeOffset, externalNode);
  46 |     };
  47 |     BoxTreeNode.version = 1;
  48 |     return BoxTreeNode;
  49 | })();
  50 | 
  51 | //
  52 | // BoxTree
  53 | //
  54 | var BoxTree = (function () {
  55 |     function BoxTree(highQuality) {
  56 |         this.numNodesLeaf = 4;
  57 |         this.nodes = [];
  58 |         this.endNode = 0;
  59 |         this.needsRebuild = false;
  60 |         this.needsRebound = false;
  61 |         this.numAdds = 0;
  62 |         this.numUpdates = 0;
  63 |         this.numExternalNodes = 0;
  64 |         this.startUpdate = 0x7FFFFFFF;
  65 |         this.endUpdate = -0x7FFFFFFF;
  66 |         this.highQuality = highQuality;
  67 |     }
  68 |     BoxTree.prototype.add = function (externalNode, extents) {
  69 |         var endNode = this.endNode;
  70 |         externalNode.boxTreeIndex = endNode;
  71 |         var copyExtents = new this.arrayConstructor(4);
  72 |         copyExtents[0] = extents[0];
  73 |         copyExtents[1] = extents[1];
  74 |         copyExtents[2] = extents[2];
  75 |         copyExtents[3] = extents[3];
  76 |         this.nodes[endNode] = BoxTreeNode.create(copyExtents, 1, externalNode);
  77 |         this.endNode = (endNode + 1);
  78 |         this.needsRebuild = true;
  79 |         this.numAdds += 1;
  80 |         this.numExternalNodes += 1;
  81 |     };
  82 | 
  83 |     BoxTree.prototype.remove = function (externalNode) {
  84 |         var index = externalNode.boxTreeIndex;
  85 |         if (index !== undefined) {
  86 |             if (this.numExternalNodes > 1) {
  87 |                 var nodes = this.nodes;
  88 | 
  89 |                 nodes[index].clear();
  90 | 
  91 |                 var endNode = this.endNode;
  92 |                 if ((index + 1) >= endNode) {
  93 |                     while (!nodes[endNode - 1].externalNode) {
  94 |                         endNode -= 1;
  95 |                     }
  96 |                     this.endNode = endNode;
  97 |                 } else {
  98 |                     this.needsRebuild = true;
  99 |                 }
 100 |                 this.numExternalNodes -= 1;
 101 |             } else {
 102 |                 this.clear();
 103 |             }
 104 | 
 105 |             externalNode.boxTreeIndex = undefined;
 106 |         }
 107 |     };
 108 | 
 109 |     BoxTree.prototype.findParent = function (nodeIndex) {
 110 |         var nodes = this.nodes;
 111 |         var parentIndex = nodeIndex;
 112 |         var nodeDist = 0;
 113 |         var parent;
 114 |         do {
 115 |             parentIndex -= 1;
 116 |             nodeDist += 1;
 117 |             parent = nodes[parentIndex];
 118 |         } while(parent.escapeNodeOffset <= nodeDist);
 119 |         return parent;
 120 |     };
 121 | 
 122 |     BoxTree.prototype.update = function (externalNode, extents) {
 123 |         var index = externalNode.boxTreeIndex;
 124 |         if (index !== undefined) {
 125 |             var min0 = extents[0];
 126 |             var min1 = extents[1];
 127 |             var max0 = extents[2];
 128 |             var max1 = extents[3];
 129 | 
 130 |             var needsRebuild = this.needsRebuild;
 131 |             var needsRebound = this.needsRebound;
 132 |             var nodes = this.nodes;
 133 |             var node = nodes[index];
 134 |             var nodeExtents = node.extents;
 135 | 
 136 |             var doUpdate = (needsRebuild || needsRebound || nodeExtents[0] > min0 || nodeExtents[1] > min1 || nodeExtents[2] < max0 || nodeExtents[3] < max1);
 137 | 
 138 |             nodeExtents[0] = min0;
 139 |             nodeExtents[1] = min1;
 140 |             nodeExtents[2] = max0;
 141 |             nodeExtents[3] = max1;
 142 | 
 143 |             if (doUpdate) {
 144 |                 if (!needsRebuild && 1 < nodes.length) {
 145 |                     this.numUpdates += 1;
 146 |                     if (this.startUpdate > index) {
 147 |                         this.startUpdate = index;
 148 |                     }
 149 |                     if (this.endUpdate < index) {
 150 |                         this.endUpdate = index;
 151 |                     }
 152 |                     if (!needsRebound) {
 153 |                         if ((2 * this.numUpdates) > this.numExternalNodes) {
 154 |                             this.needsRebound = true;
 155 |                         } else {
 156 |                             var parent = this.findParent(index);
 157 |                             var parentExtents = parent.extents;
 158 |                             if (parentExtents[0] > min0 || parentExtents[1] > min1 || parentExtents[2] < max0 || parentExtents[3] < max1) {
 159 |                                 this.needsRebound = true;
 160 |                             }
 161 |                         }
 162 |                     } else {
 163 |                         if (this.numUpdates > (3 * this.numExternalNodes)) {
 164 |                             this.needsRebuild = true;
 165 |                             this.numAdds = this.numUpdates;
 166 |                         }
 167 |                     }
 168 |                 }
 169 |             }
 170 |         } else {
 171 |             this.add(externalNode, extents);
 172 |         }
 173 |     };
 174 | 
 175 |     BoxTree.prototype.needsFinalize = function () {
 176 |         return (this.needsRebuild || this.needsRebound);
 177 |     };
 178 | 
 179 |     BoxTree.prototype.finalize = function () {
 180 |         if (this.needsRebuild) {
 181 |             this.rebuild();
 182 |         } else if (this.needsRebound) {
 183 |             this.rebound();
 184 |         }
 185 |     };
 186 | 
 187 |     BoxTree.prototype.rebound = function () {
 188 |         var nodes = this.nodes;
 189 |         if (nodes.length > 1) {
 190 |             var startUpdateNodeIndex = this.startUpdate;
 191 |             var endUpdateNodeIndex = this.endUpdate;
 192 | 
 193 |             var nodesStack = [];
 194 |             var numNodesStack = 0;
 195 |             var topNodeIndex = 0;
 196 |             for (; ;) {
 197 |                 var topNode = nodes[topNodeIndex];
 198 |                 var currentNodeIndex = topNodeIndex;
 199 |                 var currentEscapeNodeIndex = (topNodeIndex + topNode.escapeNodeOffset);
 200 |                 var nodeIndex = (topNodeIndex + 1);
 201 |                 var node;
 202 |                 do {
 203 |                     node = nodes[nodeIndex];
 204 |                     var escapeNodeIndex = (nodeIndex + node.escapeNodeOffset);
 205 |                     if (nodeIndex < endUpdateNodeIndex) {
 206 |                         if (!node.externalNode) {
 207 |                             if (escapeNodeIndex > startUpdateNodeIndex) {
 208 |                                 nodesStack[numNodesStack] = topNodeIndex;
 209 |                                 numNodesStack += 1;
 210 |                                 topNodeIndex = nodeIndex;
 211 |                             }
 212 |                         }
 213 |                     } else {
 214 |                         break;
 215 |                     }
 216 |                     nodeIndex = escapeNodeIndex;
 217 |                 } while(nodeIndex < currentEscapeNodeIndex);
 218 | 
 219 |                 if (topNodeIndex === currentNodeIndex) {
 220 |                     nodeIndex = (topNodeIndex + 1);
 221 |                     node = nodes[nodeIndex];
 222 | 
 223 |                     var extents = node.extents;
 224 |                     var minX = extents[0];
 225 |                     var minY = extents[1];
 226 |                     var maxX = extents[2];
 227 |                     var maxY = extents[3];
 228 | 
 229 |                     nodeIndex = (nodeIndex + node.escapeNodeOffset);
 230 |                     while (nodeIndex < currentEscapeNodeIndex) {
 231 |                         node = nodes[nodeIndex];
 232 |                         extents = node.extents;
 233 |                         if (minX > extents[0]) {
 234 |                             minX = extents[0];
 235 |                         }
 236 |                         if (minY > extents[1]) {
 237 |                             minY = extents[1];
 238 |                         }
 239 |                         if (maxX < extents[2]) {
 240 |                             maxX = extents[2];
 241 |                         }
 242 |                         if (maxY < extents[3]) {
 243 |                             maxY = extents[3];
 244 |                         }
 245 |                         nodeIndex = (nodeIndex + node.escapeNodeOffset);
 246 |                     }
 247 | 
 248 |                     extents = topNode.extents;
 249 |                     extents[0] = minX;
 250 |                     extents[1] = minY;
 251 |                     extents[2] = maxX;
 252 |                     extents[3] = maxY;
 253 | 
 254 |                     endUpdateNodeIndex = topNodeIndex;
 255 | 
 256 |                     if (0 < numNodesStack) {
 257 |                         numNodesStack -= 1;
 258 |                         topNodeIndex = nodesStack[numNodesStack];
 259 |                     } else {
 260 |                         break;
 261 |                     }
 262 |                 }
 263 |             }
 264 |         }
 265 | 
 266 |         this.needsRebuild = false;
 267 |         this.needsRebound = false;
 268 |         this.numAdds = 0;
 269 | 
 270 |         //this.numUpdates = 0;
 271 |         this.startUpdate = 0x7FFFFFFF;
 272 |         this.endUpdate = -0x7FFFFFFF;
 273 |     };
 274 | 
 275 |     BoxTree.prototype.rebuild = function () {
 276 |         if (this.numExternalNodes > 0) {
 277 |             var nodes = this.nodes;
 278 | 
 279 |             var buildNodes, numBuildNodes, endNodeIndex;
 280 | 
 281 |             if (this.numExternalNodes === nodes.length) {
 282 |                 buildNodes = nodes;
 283 |                 numBuildNodes = nodes.length;
 284 |                 nodes = [];
 285 |                 this.nodes = nodes;
 286 |             } else {
 287 |                 buildNodes = [];
 288 |                 buildNodes.length = this.numExternalNodes;
 289 |                 numBuildNodes = 0;
 290 |                 endNodeIndex = this.endNode;
 291 |                 for (var n = 0; n < endNodeIndex; n += 1) {
 292 |                     var currentNode = nodes[n];
 293 |                     if (currentNode.externalNode) {
 294 |                         nodes[n] = undefined;
 295 |                         buildNodes[numBuildNodes] = currentNode;
 296 |                         numBuildNodes += 1;
 297 |                     }
 298 |                 }
 299 |                 if (buildNodes.length > numBuildNodes) {
 300 |                     buildNodes.length = numBuildNodes;
 301 |                 }
 302 |             }
 303 | 
 304 |             if (numBuildNodes > 1) {
 305 |                 if (numBuildNodes > this.numNodesLeaf && this.numAdds > 0) {
 306 |                     if (this.highQuality) {
 307 |                         this.sortNodesHighQuality(buildNodes);
 308 |                     } else {
 309 |                         this.sortNodes(buildNodes);
 310 |                     }
 311 |                 }
 312 | 
 313 |                 this.recursiveBuild(buildNodes, 0, numBuildNodes, 0);
 314 | 
 315 |                 endNodeIndex = nodes[0].escapeNodeOffset;
 316 |                 if (nodes.length > endNodeIndex) {
 317 |                     nodes.length = endNodeIndex;
 318 |                 }
 319 |                 this.endNode = endNodeIndex;
 320 |             } else {
 321 |                 var rootNode = buildNodes[0];
 322 |                 rootNode.externalNode.boxTreeIndex = 0;
 323 |                 nodes.length = 1;
 324 |                 nodes[0] = rootNode;
 325 |                 this.endNode = 1;
 326 |             }
 327 |             buildNodes = null;
 328 |         }
 329 | 
 330 |         this.needsRebuild = false;
 331 |         this.needsRebound = false;
 332 |         this.numAdds = 0;
 333 |         this.numUpdates = 0;
 334 |         this.startUpdate = 0x7FFFFFFF;
 335 |         this.endUpdate = -0x7FFFFFFF;
 336 |     };
 337 | 
 338 |     BoxTree.prototype.sortNodes = function (nodes) {
 339 |         var numNodesLeaf = this.numNodesLeaf;
 340 |         var numNodes = nodes.length;
 341 | 
 342 |         var getkeyXfn = function getkeyXfnFn(node) {
 343 |             var extents = node.extents;
 344 |             return (extents[0] + extents[2]);
 345 |         };
 346 | 
 347 |         var getkeyYfn = function getkeyYfnFn(node) {
 348 |             var extents = node.extents;
 349 |             return (extents[1] + extents[3]);
 350 |         };
 351 | 
 352 |         var getreversekeyXfn = function getreversekeyXfnFn(node) {
 353 |             var extents = node.extents;
 354 |             return -(extents[0] + extents[2]);
 355 |         };
 356 | 
 357 |         var getreversekeyYfn = function getreversekeyYfnFn(node) {
 358 |             var extents = node.extents;
 359 |             return -(extents[1] + extents[3]);
 360 |         };
 361 | 
 362 |         var nthElement = this.nthElement;
 363 |         var reverse = false;
 364 |         var axis = 0;
 365 | 
 366 |         var sortNodesRecursive = function sortNodesRecursiveFn(nodes, startIndex, endIndex) {
 367 |             /* tslint:disable:no-bitwise */
 368 |             var splitNodeIndex = ((startIndex + endIndex) >> 1);
 369 | 
 370 |             if (axis === 0) {
 371 |                 if (reverse) {
 372 |                     nthElement(nodes, startIndex, splitNodeIndex, endIndex, getreversekeyXfn);
 373 |                 } else {
 374 |                     nthElement(nodes, startIndex, splitNodeIndex, endIndex, getkeyXfn);
 375 |                 }
 376 |             } else {
 377 |                 if (reverse) {
 378 |                     nthElement(nodes, startIndex, splitNodeIndex, endIndex, getreversekeyYfn);
 379 |                 } else {
 380 |                     nthElement(nodes, startIndex, splitNodeIndex, endIndex, getkeyYfn);
 381 |                 }
 382 |             }
 383 | 
 384 |             if (axis === 0) {
 385 |                 axis = 2;
 386 |             } else if (axis === 2) {
 387 |                 axis = 1;
 388 |             } else {
 389 |                 axis = 0;
 390 |             }
 391 | 
 392 |             reverse = !reverse;
 393 | 
 394 |             if ((startIndex + numNodesLeaf) < splitNodeIndex) {
 395 |                 sortNodesRecursive(nodes, startIndex, splitNodeIndex);
 396 |             }
 397 | 
 398 |             if ((splitNodeIndex + numNodesLeaf) < endIndex) {
 399 |                 sortNodesRecursive(nodes, splitNodeIndex, endIndex);
 400 |             }
 401 |         };
 402 | 
 403 |         sortNodesRecursive(nodes, 0, numNodes);
 404 |     };
 405 | 
 406 |     BoxTree.prototype.sortNodesHighQuality = function (nodes) {
 407 |         var numNodesLeaf = this.numNodesLeaf;
 408 |         var numNodes = nodes.length;
 409 | 
 410 |         var getkeyXfn = function getkeyXfnFn(node) {
 411 |             var extents = node.extents;
 412 |             return (extents[0] + extents[2]);
 413 |         };
 414 | 
 415 |         var getkeyYfn = function getkeyYfnFn(node) {
 416 |             var extents = node.extents;
 417 |             return (extents[1] + extents[3]);
 418 |         };
 419 | 
 420 |         var getkeyXYfn = function getkeyXYfnFn(node) {
 421 |             var extents = node.extents;
 422 |             return (extents[0] + extents[1] + extents[2] + extents[3]);
 423 |         };
 424 | 
 425 |         var getkeyYXfn = function getkeyYXfnFn(node) {
 426 |             var extents = node.extents;
 427 |             return (extents[0] - extents[1] + extents[2] - extents[3]);
 428 |         };
 429 | 
 430 |         var getreversekeyXfn = function getreversekeyXfnFn(node) {
 431 |             var extents = node.extents;
 432 |             return -(extents[0] + extents[2]);
 433 |         };
 434 | 
 435 |         var getreversekeyYfn = function getreversekeyYfnFn(node) {
 436 |             var extents = node.extents;
 437 |             return -(extents[1] + extents[3]);
 438 |         };
 439 | 
 440 |         var getreversekeyXYfn = function getreversekeyXYfnFn(node) {
 441 |             var extents = node.extents;
 442 |             return -(extents[0] + extents[1] + extents[2] + extents[3]);
 443 |         };
 444 | 
 445 |         var getreversekeyYXfn = function getreversekeyYXfnFn(node) {
 446 |             var extents = node.extents;
 447 |             return -(extents[0] - extents[1] + extents[2] - extents[3]);
 448 |         };
 449 | 
 450 |         var nthElement = this.nthElement;
 451 |         var calculateSAH = this.calculateSAH;
 452 |         var reverse = false;
 453 | 
 454 |         var sortNodesHighQualityRecursive = function sortNodesHighQualityRecursiveFn(nodes, startIndex, endIndex) {
 455 |             /* tslint:disable:no-bitwise */
 456 |             var splitNodeIndex = ((startIndex + endIndex) >> 1);
 457 | 
 458 |             /* tslint:enable:no-bitwise */
 459 |             nthElement(nodes, startIndex, splitNodeIndex, endIndex, getkeyXfn);
 460 |             var sahX = (calculateSAH(nodes, startIndex, splitNodeIndex) + calculateSAH(nodes, splitNodeIndex, endIndex));
 461 | 
 462 |             nthElement(nodes, startIndex, splitNodeIndex, endIndex, getkeyYfn);
 463 |             var sahY = (calculateSAH(nodes, startIndex, splitNodeIndex) + calculateSAH(nodes, splitNodeIndex, endIndex));
 464 | 
 465 |             nthElement(nodes, startIndex, splitNodeIndex, endIndex, getkeyXYfn);
 466 |             var sahXY = (calculateSAH(nodes, startIndex, splitNodeIndex) + calculateSAH(nodes, splitNodeIndex, endIndex));
 467 | 
 468 |             nthElement(nodes, startIndex, splitNodeIndex, endIndex, getkeyYXfn);
 469 |             var sahYX = (calculateSAH(nodes, startIndex, splitNodeIndex) + calculateSAH(nodes, splitNodeIndex, endIndex));
 470 | 
 471 |             if (sahX <= sahY && sahX <= sahXY && sahX <= sahYX) {
 472 |                 if (reverse) {
 473 |                     nthElement(nodes, startIndex, splitNodeIndex, endIndex, getreversekeyXfn);
 474 |                 } else {
 475 |                     nthElement(nodes, startIndex, splitNodeIndex, endIndex, getkeyXfn);
 476 |                 }
 477 |             } else if (sahY <= sahXY && sahY <= sahYX) {
 478 |                 if (reverse) {
 479 |                     nthElement(nodes, startIndex, splitNodeIndex, endIndex, getreversekeyYfn);
 480 |                 } else {
 481 |                     nthElement(nodes, startIndex, splitNodeIndex, endIndex, getkeyYfn);
 482 |                 }
 483 |             } else if (sahXY <= sahYX) {
 484 |                 if (reverse) {
 485 |                     nthElement(nodes, startIndex, splitNodeIndex, endIndex, getreversekeyXYfn);
 486 |                 } else {
 487 |                     nthElement(nodes, startIndex, splitNodeIndex, endIndex, getkeyXYfn);
 488 |                 }
 489 |             } else {
 490 |                 if (reverse) {
 491 |                     nthElement(nodes, startIndex, splitNodeIndex, endIndex, getreversekeyYXfn);
 492 |                 } else {
 493 |                     nthElement(nodes, startIndex, splitNodeIndex, endIndex, getkeyYXfn);
 494 |                 }
 495 |             }
 496 | 
 497 |             reverse = !reverse;
 498 | 
 499 |             if ((startIndex + numNodesLeaf) < splitNodeIndex) {
 500 |                 sortNodesHighQualityRecursive(nodes, startIndex, splitNodeIndex);
 501 |             }
 502 | 
 503 |             if ((splitNodeIndex + numNodesLeaf) < endIndex) {
 504 |                 sortNodesHighQualityRecursive(nodes, splitNodeIndex, endIndex);
 505 |             }
 506 |         };
 507 | 
 508 |         sortNodesHighQualityRecursive(nodes, 0, numNodes);
 509 |     };
 510 | 
 511 |     BoxTree.prototype.calculateSAH = function (buildNodes, startIndex, endIndex) {
 512 |         var buildNode, extents, minX, minY, maxX, maxY;
 513 | 
 514 |         buildNode = buildNodes[startIndex];
 515 |         extents = buildNode.extents;
 516 |         minX = extents[0];
 517 |         minY = extents[1];
 518 |         maxX = extents[2];
 519 |         maxY = extents[3];
 520 | 
 521 |         for (var n = (startIndex + 1); n < endIndex; n += 1) {
 522 |             buildNode = buildNodes[n];
 523 |             extents = buildNode.extents;
 524 |             if (minX > extents[0]) {
 525 |                 minX = extents[0];
 526 |             }
 527 |             if (minY > extents[1]) {
 528 |                 minY = extents[1];
 529 |             }
 530 |             if (maxX < extents[2]) {
 531 |                 maxX = extents[2];
 532 |             }
 533 |             if (maxY < extents[3]) {
 534 |                 maxY = extents[3];
 535 |             }
 536 |         }
 537 | 
 538 |         return ((maxX - minX) + (maxY - minY));
 539 |     };
 540 | 
 541 |     BoxTree.prototype.nthElement = function (nodes, first, nth, last, getkey) {
 542 |         function medianFn(a, b, c) {
 543 |             if (a < b) {
 544 |                 if (b < c) {
 545 |                     return b;
 546 |                 } else if (a < c) {
 547 |                     return c;
 548 |                 } else {
 549 |                     return a;
 550 |                 }
 551 |             } else if (a < c) {
 552 |                 return a;
 553 |             } else if (b < c) {
 554 |                 return c;
 555 |             }
 556 |             return b;
 557 |         }
 558 | 
 559 |         function insertionSortFn(nodes, first, last, getkey) {
 560 |             var sorted = (first + 1);
 561 |             while (sorted !== last) {
 562 |                 var tempNode = nodes[sorted];
 563 |                 var tempKey = getkey(tempNode);
 564 | 
 565 |                 var next = sorted;
 566 |                 var current = (sorted - 1);
 567 | 
 568 |                 while (next !== first && tempKey < getkey(nodes[current])) {
 569 |                     nodes[next] = nodes[current];
 570 |                     next -= 1;
 571 |                     current -= 1;
 572 |                 }
 573 | 
 574 |                 if (next !== sorted) {
 575 |                     nodes[next] = tempNode;
 576 |                 }
 577 | 
 578 |                 sorted += 1;
 579 |             }
 580 |         }
 581 | 
 582 |         while ((last - first) > 8) {
 583 |             /* tslint:disable:no-bitwise */
 584 |             var midValue = medianFn(getkey(nodes[first]), getkey(nodes[first + ((last - first) >> 1)]), getkey(nodes[last - 1]));
 585 | 
 586 |             /* tslint:enable:no-bitwise */
 587 |             var firstPos = first;
 588 |             var lastPos = last;
 589 |             var midPos;
 590 |             for (; ; firstPos += 1) {
 591 |                 while (getkey(nodes[firstPos]) < midValue) {
 592 |                     firstPos += 1;
 593 |                 }
 594 | 
 595 |                 do {
 596 |                     lastPos -= 1;
 597 |                 } while(midValue < getkey(nodes[lastPos]));
 598 | 
 599 |                 if (firstPos >= lastPos) {
 600 |                     midPos = firstPos;
 601 |                     break;
 602 |                 } else {
 603 |                     var temp = nodes[firstPos];
 604 |                     nodes[firstPos] = nodes[lastPos];
 605 |                     nodes[lastPos] = temp;
 606 |                 }
 607 |             }
 608 | 
 609 |             if (midPos <= nth) {
 610 |                 first = midPos;
 611 |             } else {
 612 |                 last = midPos;
 613 |             }
 614 |         }
 615 | 
 616 |         insertionSortFn(nodes, first, last, getkey);
 617 |     };
 618 | 
 619 |     BoxTree.prototype.recursiveBuild = function (buildNodes, startIndex, endIndex, lastNodeIndex) {
 620 |         var nodes = this.nodes;
 621 |         var nodeIndex = lastNodeIndex;
 622 |         lastNodeIndex += 1;
 623 | 
 624 |         var minX, minY, maxX, maxY, extents;
 625 |         var buildNode, lastNode;
 626 | 
 627 |         if ((startIndex + this.numNodesLeaf) >= endIndex) {
 628 |             buildNode = buildNodes[startIndex];
 629 |             extents = buildNode.extents;
 630 |             minX = extents[0];
 631 |             minY = extents[1];
 632 |             maxX = extents[2];
 633 |             maxY = extents[3];
 634 | 
 635 |             buildNode.externalNode.boxTreeIndex = lastNodeIndex;
 636 |             nodes[lastNodeIndex] = buildNode;
 637 | 
 638 |             for (var n = (startIndex + 1); n < endIndex; n += 1) {
 639 |                 buildNode = buildNodes[n];
 640 |                 extents = buildNode.extents;
 641 |                 if (minX > extents[0]) {
 642 |                     minX = extents[0];
 643 |                 }
 644 |                 if (minY > extents[1]) {
 645 |                     minY = extents[1];
 646 |                 }
 647 |                 if (maxX < extents[2]) {
 648 |                     maxX = extents[2];
 649 |                 }
 650 |                 if (maxY < extents[3]) {
 651 |                     maxY = extents[3];
 652 |                 }
 653 |                 lastNodeIndex += 1;
 654 |                 buildNode.externalNode.boxTreeIndex = lastNodeIndex;
 655 |                 nodes[lastNodeIndex] = buildNode;
 656 |             }
 657 | 
 658 |             lastNode = nodes[lastNodeIndex];
 659 |         } else {
 660 |             /* tslint:disable:no-bitwise */
 661 |             var splitPosIndex = ((startIndex + endIndex) >> 1);
 662 | 
 663 |             if ((startIndex + 1) >= splitPosIndex) {
 664 |                 buildNode = buildNodes[startIndex];
 665 |                 buildNode.externalNode.boxTreeIndex = lastNodeIndex;
 666 |                 nodes[lastNodeIndex] = buildNode;
 667 |             } else {
 668 |                 this.recursiveBuild(buildNodes, startIndex, splitPosIndex, lastNodeIndex);
 669 |             }
 670 | 
 671 |             lastNode = nodes[lastNodeIndex];
 672 |             extents = lastNode.extents;
 673 |             minX = extents[0];
 674 |             minY = extents[1];
 675 |             maxX = extents[2];
 676 |             maxY = extents[3];
 677 | 
 678 |             lastNodeIndex = (lastNodeIndex + lastNode.escapeNodeOffset);
 679 | 
 680 |             if ((splitPosIndex + 1) >= endIndex) {
 681 |                 buildNode = buildNodes[splitPosIndex];
 682 |                 buildNode.externalNode.boxTreeIndex = lastNodeIndex;
 683 |                 nodes[lastNodeIndex] = buildNode;
 684 |             } else {
 685 |                 this.recursiveBuild(buildNodes, splitPosIndex, endIndex, lastNodeIndex);
 686 |             }
 687 | 
 688 |             lastNode = nodes[lastNodeIndex];
 689 |             extents = lastNode.extents;
 690 |             if (minX > extents[0]) {
 691 |                 minX = extents[0];
 692 |             }
 693 |             if (minY > extents[1]) {
 694 |                 minY = extents[1];
 695 |             }
 696 |             if (maxX < extents[2]) {
 697 |                 maxX = extents[2];
 698 |             }
 699 |             if (maxY < extents[3]) {
 700 |                 maxY = extents[3];
 701 |             }
 702 |         }
 703 | 
 704 |         var node = nodes[nodeIndex];
 705 |         if (node !== undefined) {
 706 |             node.reset(minX, minY, maxX, maxY, (lastNodeIndex + lastNode.escapeNodeOffset - nodeIndex));
 707 |         } else {
 708 |             var parentExtents = new this.arrayConstructor(4);
 709 |             parentExtents[0] = minX;
 710 |             parentExtents[1] = minY;
 711 |             parentExtents[2] = maxX;
 712 |             parentExtents[3] = maxY;
 713 | 
 714 |             nodes[nodeIndex] = BoxTreeNode.create(parentExtents, (lastNodeIndex + lastNode.escapeNodeOffset - nodeIndex));
 715 |         }
 716 |     };
 717 | 
 718 |     BoxTree.prototype.getVisibleNodes = function (planes, visibleNodes) {
 719 |         if (this.numExternalNodes > 0) {
 720 |             var nodes = this.nodes;
 721 |             var endNodeIndex = this.endNode;
 722 |             var numPlanes = planes.length;
 723 |             var numVisibleNodes = visibleNodes.length;
 724 |             var node, extents, endChildren;
 725 |             var n0, n1, p0, p1;
 726 |             var isInside, n, plane, d0, d1;
 727 |             var nodeIndex = 0;
 728 | 
 729 |             for (; ;) {
 730 |                 node = nodes[nodeIndex];
 731 |                 extents = node.extents;
 732 |                 n0 = extents[0];
 733 |                 n1 = extents[1];
 734 |                 p0 = extents[2];
 735 |                 p1 = extents[3];
 736 | 
 737 |                 //isInsidePlanesBox
 738 |                 isInside = true;
 739 |                 n = 0;
 740 |                 do {
 741 |                     plane = planes[n];
 742 |                     d0 = plane[0];
 743 |                     d1 = plane[1];
 744 |                     if ((d0 * (d0 < 0 ? n0 : p0) + d1 * (d1 < 0 ? n1 : p1)) < plane[2]) {
 745 |                         isInside = false;
 746 |                         break;
 747 |                     }
 748 |                     n += 1;
 749 |                 } while(n < numPlanes);
 750 |                 if (isInside) {
 751 |                     if (node.externalNode) {
 752 |                         visibleNodes[numVisibleNodes] = node.externalNode;
 753 |                         numVisibleNodes += 1;
 754 |                         nodeIndex += 1;
 755 |                         if (nodeIndex >= endNodeIndex) {
 756 |                             break;
 757 |                         }
 758 |                     } else {
 759 |                         //isFullyInsidePlanesBox
 760 |                         isInside = true;
 761 |                         n = 0;
 762 |                         do {
 763 |                             plane = planes[n];
 764 |                             d0 = plane[0];
 765 |                             d1 = plane[1];
 766 |                             if ((d0 * (d0 > 0 ? n0 : p0) + d1 * (d1 > 0 ? n1 : p1)) < plane[2]) {
 767 |                                 isInside = false;
 768 |                                 break;
 769 |                             }
 770 |                             n += 1;
 771 |                         } while(n < numPlanes);
 772 |                         if (isInside) {
 773 |                             endChildren = (nodeIndex + node.escapeNodeOffset);
 774 |                             nodeIndex += 1;
 775 |                             do {
 776 |                                 node = nodes[nodeIndex];
 777 |                                 if (node.externalNode) {
 778 |                                     visibleNodes[numVisibleNodes] = node.externalNode;
 779 |                                     numVisibleNodes += 1;
 780 |                                 }
 781 |                                 nodeIndex += 1;
 782 |                             } while(nodeIndex < endChildren);
 783 |                             if (nodeIndex >= endNodeIndex) {
 784 |                                 break;
 785 |                             }
 786 |                         } else {
 787 |                             nodeIndex += 1;
 788 |                         }
 789 |                     }
 790 |                 } else {
 791 |                     nodeIndex += node.escapeNodeOffset;
 792 |                     if (nodeIndex >= endNodeIndex) {
 793 |                         break;
 794 |                     }
 795 |                 }
 796 |             }
 797 |         }
 798 |     };
 799 | 
 800 |     BoxTree.prototype.getOverlappingNodes = function (queryExtents, overlappingNodes, startIndex) {
 801 |         if (this.numExternalNodes > 0) {
 802 |             var queryMinX = queryExtents[0];
 803 |             var queryMinY = queryExtents[1];
 804 |             var queryMaxX = queryExtents[2];
 805 |             var queryMaxY = queryExtents[3];
 806 |             var nodes = this.nodes;
 807 |             var endNodeIndex = this.endNode;
 808 |             var node, extents, endChildren;
 809 |             var numOverlappingNodes = 0;
 810 |             var storageIndex = (startIndex === undefined) ? overlappingNodes.length : startIndex;
 811 |             var nodeIndex = 0;
 812 |             for (; ;) {
 813 |                 node = nodes[nodeIndex];
 814 |                 extents = node.extents;
 815 |                 var minX = extents[0];
 816 |                 var minY = extents[1];
 817 |                 var maxX = extents[2];
 818 |                 var maxY = extents[3];
 819 |                 if (queryMinX <= maxX && queryMinY <= maxY && queryMaxX >= minX && queryMaxY >= minY) {
 820 |                     if (node.externalNode) {
 821 |                         overlappingNodes[storageIndex] = node.externalNode;
 822 |                         storageIndex += 1;
 823 |                         numOverlappingNodes += 1;
 824 |                         nodeIndex += 1;
 825 |                         if (nodeIndex >= endNodeIndex) {
 826 |                             break;
 827 |                         }
 828 |                     } else {
 829 |                         if (queryMaxX >= maxX && queryMaxY >= maxY && queryMinX <= minX && queryMinY <= minY) {
 830 |                             endChildren = (nodeIndex + node.escapeNodeOffset);
 831 |                             nodeIndex += 1;
 832 |                             do {
 833 |                                 node = nodes[nodeIndex];
 834 |                                 if (node.externalNode) {
 835 |                                     overlappingNodes[storageIndex] = node.externalNode;
 836 |                                     storageIndex += 1;
 837 |                                     numOverlappingNodes += 1;
 838 |                                 }
 839 |                                 nodeIndex += 1;
 840 |                             } while(nodeIndex < endChildren);
 841 |                             if (nodeIndex >= endNodeIndex) {
 842 |                                 break;
 843 |                             }
 844 |                         } else {
 845 |                             nodeIndex += 1;
 846 |                         }
 847 |                     }
 848 |                 } else {
 849 |                     nodeIndex += node.escapeNodeOffset;
 850 |                     if (nodeIndex >= endNodeIndex) {
 851 |                         break;
 852 |                     }
 853 |                 }
 854 |             }
 855 |             return numOverlappingNodes;
 856 |         } else {
 857 |             return 0;
 858 |         }
 859 |     };
 860 | 
 861 |     BoxTree.prototype.getCircleOverlappingNodes = function (center, radius, overlappingNodes) {
 862 |         if (this.numExternalNodes > 0) {
 863 |             var radiusSquared = (radius * radius);
 864 |             var centerX = center[0];
 865 |             var centerY = center[1];
 866 |             var nodes = this.nodes;
 867 |             var endNodeIndex = this.endNode;
 868 |             var node, extents;
 869 |             var numOverlappingNodes = overlappingNodes.length;
 870 |             var nodeIndex = 0;
 871 |             for (; ;) {
 872 |                 node = nodes[nodeIndex];
 873 |                 extents = node.extents;
 874 |                 var minX = extents[0];
 875 |                 var minY = extents[1];
 876 |                 var maxX = extents[2];
 877 |                 var maxY = extents[3];
 878 |                 var totalDistance = 0, sideDistance;
 879 |                 if (centerX < minX) {
 880 |                     sideDistance = (minX - centerX);
 881 |                     totalDistance += (sideDistance * sideDistance);
 882 |                 } else if (centerX > maxX) {
 883 |                     sideDistance = (centerX - maxX);
 884 |                     totalDistance += (sideDistance * sideDistance);
 885 |                 }
 886 |                 if (centerY < minY) {
 887 |                     sideDistance = (minY - centerY);
 888 |                     totalDistance += (sideDistance * sideDistance);
 889 |                 } else if (centerY > maxY) {
 890 |                     sideDistance = (centerY - maxY);
 891 |                     totalDistance += (sideDistance * sideDistance);
 892 |                 }
 893 |                 if (totalDistance <= radiusSquared) {
 894 |                     nodeIndex += 1;
 895 |                     if (node.externalNode) {
 896 |                         overlappingNodes[numOverlappingNodes] = node.externalNode;
 897 |                         numOverlappingNodes += 1;
 898 |                         if (nodeIndex >= endNodeIndex) {
 899 |                             break;
 900 |                         }
 901 |                     }
 902 |                 } else {
 903 |                     nodeIndex += node.escapeNodeOffset;
 904 |                     if (nodeIndex >= endNodeIndex) {
 905 |                         break;
 906 |                     }
 907 |                 }
 908 |             }
 909 |         }
 910 |     };
 911 | 
 912 |     BoxTree.prototype.getOverlappingPairs = function (overlappingPairs, startIndex) {
 913 |         if (this.numExternalNodes > 0) {
 914 |             var nodes = this.nodes;
 915 |             var endNodeIndex = this.endNode;
 916 |             var currentNode, currentExternalNode, node, extents;
 917 |             var numInsertions = 0;
 918 |             var storageIndex = (startIndex === undefined) ? overlappingPairs.length : startIndex;
 919 |             var currentNodeIndex = 0, nodeIndex;
 920 |             for (; ;) {
 921 |                 currentNode = nodes[currentNodeIndex];
 922 |                 while (!currentNode.externalNode) {
 923 |                     currentNodeIndex += 1;
 924 |                     currentNode = nodes[currentNodeIndex];
 925 |                 }
 926 | 
 927 |                 currentNodeIndex += 1;
 928 |                 if (currentNodeIndex < endNodeIndex) {
 929 |                     currentExternalNode = currentNode.externalNode;
 930 |                     extents = currentNode.extents;
 931 |                     var minX = extents[0];
 932 |                     var minY = extents[1];
 933 |                     var maxX = extents[2];
 934 |                     var maxY = extents[3];
 935 | 
 936 |                     nodeIndex = currentNodeIndex;
 937 |                     for (; ;) {
 938 |                         node = nodes[nodeIndex];
 939 |                         extents = node.extents;
 940 |                         if (minX <= extents[2] && minY <= extents[3] && maxX >= extents[0] && maxY >= extents[1]) {
 941 |                             nodeIndex += 1;
 942 |                             if (node.externalNode) {
 943 |                                 overlappingPairs[storageIndex] = currentExternalNode;
 944 |                                 overlappingPairs[storageIndex + 1] = node.externalNode;
 945 |                                 storageIndex += 2;
 946 |                                 numInsertions += 2;
 947 |                                 if (nodeIndex >= endNodeIndex) {
 948 |                                     break;
 949 |                                 }
 950 |                             }
 951 |                         } else {
 952 |                             nodeIndex += node.escapeNodeOffset;
 953 |                             if (nodeIndex >= endNodeIndex) {
 954 |                                 break;
 955 |                             }
 956 |                         }
 957 |                     }
 958 |                 } else {
 959 |                     break;
 960 |                 }
 961 |             }
 962 |             return numInsertions;
 963 |         } else {
 964 |             return 0;
 965 |         }
 966 |     };
 967 | 
 968 |     BoxTree.prototype.getRootNode = function () {
 969 |         return this.nodes[0];
 970 |     };
 971 | 
 972 |     BoxTree.prototype.getNodes = function () {
 973 |         return this.nodes;
 974 |     };
 975 | 
 976 |     BoxTree.prototype.getEndNodeIndex = function () {
 977 |         return this.endNode;
 978 |     };
 979 | 
 980 |     BoxTree.prototype.clear = function () {
 981 |         this.nodes = [];
 982 |         this.endNode = 0;
 983 |         this.needsRebuild = false;
 984 |         this.needsRebound = false;
 985 |         this.numAdds = 0;
 986 |         this.numUpdates = 0;
 987 |         this.numExternalNodes = 0;
 988 |         this.startUpdate = 0x7FFFFFFF;
 989 |         this.endUpdate = -0x7FFFFFFF;
 990 |     };
 991 | 
 992 |     BoxTree.rayTest = function (trees, ray, callback) {
 993 |         // convert ray to parametric form
 994 |         var origin = ray.origin;
 995 |         var direction = ray.direction;
 996 | 
 997 |         // values used throughout calculations.
 998 |         var o0 = origin[0];
 999 |         var o1 = origin[1];
1000 |         var d0 = direction[0];
1001 |         var d1 = direction[1];
1002 |         var id0 = 1 / d0;
1003 |         var id1 = 1 / d1;
1004 | 
1005 |         // evaluate distance factor to a node's extents from ray origin, along direction
1006 |         // use this to induce an ordering on which nodes to check.
1007 |         var distanceExtents = function distanceExtentsFn(extents, upperBound) {
1008 |             var min0 = extents[0];
1009 |             var min1 = extents[1];
1010 |             var max0 = extents[2];
1011 |             var max1 = extents[3];
1012 | 
1013 |             if (min0 <= o0 && o0 <= max0 && min1 <= o1 && o1 <= max1) {
1014 |                 return 0.0;
1015 |             }
1016 | 
1017 |             var tmin, tmax;
1018 |             var tymin, tymax;
1019 |             var del;
1020 |             if (d0 >= 0) {
1021 |                 // Deal with cases where d0 == 0
1022 |                 del = (min0 - o0);
1023 |                 tmin = ((del === 0) ? 0 : (del * id0));
1024 |                 del = (max0 - o0);
1025 |                 tmax = ((del === 0) ? 0 : (del * id0));
1026 |             } else {
1027 |                 tmin = ((max0 - o0) * id0);
1028 |                 tmax = ((min0 - o0) * id0);
1029 |             }
1030 | 
1031 |             if (d1 >= 0) {
1032 |                 // Deal with cases where d1 == 0
1033 |                 del = (min1 - o1);
1034 |                 tymin = ((del === 0) ? 0 : (del * id1));
1035 |                 del = (max1 - o1);
1036 |                 tymax = ((del === 0) ? 0 : (del * id1));
1037 |             } else {
1038 |                 tymin = ((max1 - o1) * id1);
1039 |                 tymax = ((min1 - o1) * id1);
1040 |             }
1041 | 
1042 |             if ((tmin > tymax) || (tymin > tmax)) {
1043 |                 return undefined;
1044 |             }
1045 | 
1046 |             if (tymin > tmin) {
1047 |                 tmin = tymin;
1048 |             }
1049 | 
1050 |             if (tymax < tmax) {
1051 |                 tmax = tymax;
1052 |             }
1053 | 
1054 |             if (tmin < 0) {
1055 |                 tmin = tmax;
1056 |             }
1057 | 
1058 |             return (0 <= tmin && tmin < upperBound) ? tmin : undefined;
1059 |         };
1060 | 
1061 |         // we traverse both trees at once
1062 |         // keeping a priority list of nodes to check next.
1063 |         // TODO: possibly implement priority list more effeciently?
1064 |         //       binary heap probably too much overhead in typical case.
1065 |         var priorityList = [];
1066 | 
1067 |         //current upperBound on distance to first intersection
1068 |         //and current closest object properties
1069 |         var minimumResult = null;
1070 | 
1071 |         //if node is a leaf, intersect ray with shape
1072 |         // otherwise insert node into priority list.
1073 |         var processNode = function processNodeFn(tree, nodeIndex, upperBound) {
1074 |             var nodes = tree.getNodes();
1075 |             var node = nodes[nodeIndex];
1076 |             var distance = distanceExtents(node.extents, upperBound);
1077 |             if (distance === undefined) {
1078 |                 return upperBound;
1079 |             }
1080 | 
1081 |             if (node.externalNode) {
1082 |                 var result = callback(tree, node.externalNode, ray, distance, upperBound);
1083 |                 if (result) {
1084 |                     minimumResult = result;
1085 |                     upperBound = result.factor;
1086 |                 }
1087 |             } else {
1088 |                 // TODO: change to binary search?
1089 |                 var length = priorityList.length;
1090 |                 var i;
1091 |                 for (i = 0; i < length; i += 1) {
1092 |                     var curObj = priorityList[i];
1093 |                     if (distance > curObj.distance) {
1094 |                         break;
1095 |                     }
1096 |                 }
1097 | 
1098 |                 //insert node at index i
1099 |                 priorityList.splice(i - 1, 0, {
1100 |                     tree: tree,
1101 |                     nodeIndex: nodeIndex,
1102 |                     distance: distance
1103 |                 });
1104 |             }
1105 | 
1106 |             return upperBound;
1107 |         };
1108 | 
1109 |         var upperBound = ray.maxFactor;
1110 | 
1111 |         var tree;
1112 |         var i;
1113 |         for (i = 0; i < trees.length; i += 1) {
1114 |             tree = trees[i];
1115 |             if (tree.endNode !== 0) {
1116 |                 upperBound = processNode(tree, 0, upperBound);
1117 |             }
1118 |         }
1119 | 
1120 |         while (priorityList.length !== 0) {
1121 |             var nodeObj = priorityList.pop();
1122 | 
1123 |             if (nodeObj.distance >= upperBound) {
1124 |                 continue;
1125 |             }
1126 | 
1127 |             var nodeIndex = nodeObj.nodeIndex;
1128 |             tree = nodeObj.tree;
1129 |             var nodes = tree.getNodes();
1130 | 
1131 |             var node = nodes[nodeIndex];
1132 |             var maxIndex = nodeIndex + node.escapeNodeOffset;
1133 | 
1134 |             var childIndex = nodeIndex + 1;
1135 |             do {
1136 |                 upperBound = processNode(tree, childIndex, upperBound);
1137 |                 childIndex += nodes[childIndex].escapeNodeOffset;
1138 |             } while(childIndex < maxIndex);
1139 |         }
1140 | 
1141 |         return minimumResult;
1142 |     };
1143 | 
1144 |     BoxTree.create = // Constructor function
1145 |     function (highQuality) {
1146 |         return new BoxTree(highQuality);
1147 |     };
1148 |     BoxTree.version = 1;
1149 |     return BoxTree;
1150 | })();
1151 | 
1152 | // Detect correct typed arrays
1153 | ((function () {
1154 |     BoxTree.prototype.arrayConstructor = Array;
1155 |     if (typeof Float32Array !== "undefined") {
1156 |         var testArray = new Float32Array(4);
1157 |         var textDescriptor = Object.prototype.toString.call(testArray);
1158 |         if (textDescriptor === '[object Float32Array]') {
1159 |             BoxTree.prototype.arrayConstructor = Float32Array;
1160 |         }
1161 |     }
1162 | })());
1163 | 


--------------------------------------------------------------------------------