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