├── algorithms ├── oimo │ ├── Pair.js │ ├── bvh.js │ ├── sweep.js │ ├── brute-force.js │ ├── BasicProxy.js │ ├── Proxy.js │ ├── dbvt │ │ ├── DBVTProxy.js │ │ ├── DBVTNode.js │ │ ├── DBVTBroadPhase.js │ │ └── DBVT.js │ ├── ooimo-tester.js │ ├── sap │ │ ├── SAPElement.js │ │ ├── SAPProxy.js │ │ ├── SAPAxis.js │ │ └── SAPBroadPhase.js │ ├── AABB.js │ ├── BruteForceBroadPhase.js │ └── BroadPhase.js ├── box-intersect │ ├── complete.js │ └── bipartite.js ├── rbush │ ├── bulk.js │ ├── incremental.js │ └── bipartite.js ├── rtree │ ├── complete.js │ └── bipartite.js ├── boxtree │ ├── complete.js │ ├── bipartite.js │ └── boxtree.js ├── aabbtree │ ├── complete.js │ ├── bipartite.js │ └── aabbtree.js ├── jsts │ ├── quadtree.js │ ├── quadtree-bipartite.js │ ├── strtree.js │ └── strtree-bipartite.js ├── brute-force │ ├── complete-robust.js │ ├── complete-fast.js │ └── bipartite-fast.js ├── p2 │ ├── sweep.js │ └── grid.js ├── lazykdtree │ └── complete.js ├── simple-quadtree │ ├── complete.js │ └── bipartite.js └── box2d │ └── broadphase.js ├── images ├── skewed.png ├── sphere.png └── uniform.png ├── generators ├── bunny.js ├── sc.js ├── skewed.js ├── sphere.js └── uniform.js ├── .gitignore ├── cases ├── bunny-complete.json ├── sphere3d-complete.json ├── uniform3d-complete.json ├── skewed3d-complete.json ├── skewed2d-large-complete.json ├── uniform2d-large-complete.json ├── boxvsrbush-bipartite.js ├── sphere2d-large-complete.json ├── circle-vs-uniform-2d.json ├── skewed-vs-circle-2d.json ├── skewed-vs-uniform-2d.json ├── skewed2d-small-complete.json ├── sphere2d-small-complete.json ├── uniform2d-medium-complete.json ├── test.json ├── uniform-small-vs-large-2d.json ├── uniform2d-small-complete.json └── uniform2d-tiny-complete.json ├── run.js ├── draw.js ├── LICENSE ├── package.json ├── plot.js ├── README.md └── bench.js /algorithms/oimo/Pair.js: -------------------------------------------------------------------------------- 1 | module.exports = function Pair() { 2 | this.shape1 = this.shape2 = null 3 | } -------------------------------------------------------------------------------- /images/skewed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikolalysenko/box-intersect-benchmark/HEAD/images/skewed.png -------------------------------------------------------------------------------- /images/sphere.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikolalysenko/box-intersect-benchmark/HEAD/images/sphere.png -------------------------------------------------------------------------------- /images/uniform.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikolalysenko/box-intersect-benchmark/HEAD/images/uniform.png -------------------------------------------------------------------------------- /generators/bunny.js: -------------------------------------------------------------------------------- 1 | var bunny = require('bunny') 2 | var sc = require('./sc') 3 | 4 | module.exports = function() { 5 | return sc(bunny.cells, bunny.positions) 6 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | lib-cov 2 | *.seed 3 | *.log 4 | *.csv 5 | *.dat 6 | *.out 7 | *.pid 8 | *.gz 9 | 10 | pids 11 | logs 12 | results 13 | 14 | npm-debug.log 15 | node_modules/* 16 | *.DS_Store 17 | plotly.json -------------------------------------------------------------------------------- /cases/bunny-complete.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Stanford bunny", 3 | "algorithms": [ 4 | "brute-force", 5 | "box-intersect", 6 | "aabbtree", 7 | "oimo-bvh" 8 | ], 9 | "distribution": { 10 | "type": "bunny" 11 | }, 12 | "numIters": 50, 13 | "plot": true 14 | } -------------------------------------------------------------------------------- /algorithms/oimo/bvh.js: -------------------------------------------------------------------------------- 1 | var ooimoTester = require('./ooimo-tester') 2 | var DBVTBroadPhase = require('./dbvt/DBVTBroadPhase') 3 | 4 | exports.name = 'oimo - dbvt' 5 | 6 | exports.prepare = ooimoTester.prepare 7 | 8 | exports.run = function(data) { 9 | return ooimoTester.run(DBVTBroadPhase, data) 10 | } -------------------------------------------------------------------------------- /algorithms/oimo/sweep.js: -------------------------------------------------------------------------------- 1 | var ooimoTester = require('./ooimo-tester') 2 | var SAPBroadPhase = require('./sap/SAPBroadPhase') 3 | 4 | exports.name = 'oimo - sweep' 5 | 6 | exports.prepare = ooimoTester.prepare 7 | 8 | exports.run = function(data) { 9 | return ooimoTester.run(SAPBroadPhase, data) 10 | } -------------------------------------------------------------------------------- /algorithms/box-intersect/complete.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var intersect = require('box-intersect') 4 | 5 | exports.name = 'box-intersect' 6 | 7 | exports.prepare = function(boxes) {return boxes} 8 | 9 | exports.run = function(boxes) { 10 | var count = 0 11 | intersect(boxes, function(i,j) { 12 | count += 1 13 | }) 14 | return count 15 | } -------------------------------------------------------------------------------- /algorithms/oimo/brute-force.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var ooimoTester = require('./ooimo-tester') 4 | var BruteForceBroadPhase = require('./BruteForceBroadPhase') 5 | 6 | exports.name = 'oimo - brute force' 7 | 8 | exports.prepare = ooimoTester.prepare 9 | 10 | exports.run = function(data) { 11 | return ooimoTester.run(BruteForceBroadPhase, data) >>> 1 12 | } -------------------------------------------------------------------------------- /algorithms/box-intersect/bipartite.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var intersect = require('box-intersect') 4 | 5 | exports.name = 'box-intersect' 6 | 7 | exports.prepare = function(boxes) {return boxes} 8 | 9 | exports.run = function(red, blue) { 10 | var count = 0 11 | intersect(red, blue, function(i,j) { 12 | count += 1 13 | }) 14 | return count 15 | } -------------------------------------------------------------------------------- /algorithms/oimo/BasicProxy.js: -------------------------------------------------------------------------------- 1 | var Proxy = require('./Proxy') 2 | 3 | /** 4 | * A basic implementation of proxies. 5 | * @author saharan 6 | */ 7 | var BasicProxy = function(shape){ 8 | Proxy.call( this, shape ); 9 | } 10 | BasicProxy.prototype = Object.create( Proxy.prototype ); 11 | BasicProxy.prototype.update = function () { 12 | } 13 | 14 | module.exports = BasicProxy -------------------------------------------------------------------------------- /cases/sphere3d-complete.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "3D Sphere Complete", 3 | "algorithms": [ 4 | "brute-force", 5 | "box-intersect", 6 | "aabbtree", 7 | "oimo-bvh" 8 | ], 9 | "distribution": { 10 | "d": 3, 11 | "type": "sphere" 12 | }, 13 | "parameters": [[ "n" ]], 14 | "cases": [10], 15 | "ranges": [[[ 500, 2500 ]]], 16 | "numIters": 100, 17 | "plot": true 18 | } -------------------------------------------------------------------------------- /cases/uniform3d-complete.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "3D Uniform Complete", 3 | "algorithms": [ 4 | "brute-force", 5 | "box-intersect", 6 | "aabbtree", 7 | "oimo-bvh" 8 | ], 9 | "distribution": { 10 | "d": 3, 11 | "type": "uniform" 12 | }, 13 | "parameters": [[ "n" ]], 14 | "cases": [10], 15 | "ranges": [[[ 500, 2500 ]]], 16 | "numIters": 100, 17 | "plot": true 18 | } -------------------------------------------------------------------------------- /cases/skewed3d-complete.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "3D High Aspect Complete", 3 | "algorithms": [ 4 | "brute-force", 5 | "box-intersect", 6 | "aabbtree", 7 | "oimo-bvh" 8 | ], 9 | "distribution": { 10 | "d": 3, 11 | "type": "skewed" 12 | }, 13 | "parameters": [[ "n" ]], 14 | "cases": [10], 15 | "ranges": [[[ 500, 2500 ]]], 16 | "numIters": 100, 17 | "plot": true 18 | } -------------------------------------------------------------------------------- /cases/skewed2d-large-complete.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "2D Skewed Complete Intersect (Large)", 3 | "algorithms": [ 4 | "box-intersect", 5 | "boxtree", 6 | "rbush-incremental", 7 | "rbush-bulk" 8 | ], 9 | "distribution": { 10 | "d": 2, 11 | "type": "skewed" 12 | }, 13 | "parameters": [[ "n" ]], 14 | "cases": [10], 15 | "ranges": [[[ 10000, 100000 ]]], 16 | "numIters": 20, 17 | "plot": true 18 | } -------------------------------------------------------------------------------- /cases/uniform2d-large-complete.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "2D Uniform Complete Intersect (Large)", 3 | "algorithms": [ 4 | "boxtree", 5 | "box-intersect", 6 | "rbush-incremental", 7 | "rbush-bulk", 8 | "p2-grid" 9 | ], 10 | "distribution": { 11 | "d": 2, 12 | "type": "uniform" 13 | }, 14 | "parameters": [[ "n" ]], 15 | "cases": [10], 16 | "ranges": [[[ 10000, 250000 ]]], 17 | "numIters": 30, 18 | "plot": true 19 | } -------------------------------------------------------------------------------- /algorithms/rbush/bulk.js: -------------------------------------------------------------------------------- 1 | var rbush = require('rbush') 2 | 3 | exports.name = 'rbush' 4 | 5 | exports.prepare = function(boxes) { return boxes.map(function(b,i) { 6 | return [b[0], b[1], b[2], b[3]] 7 | }) } 8 | 9 | exports.run = function(boxes) { 10 | var tree = rbush(9).load(boxes) //9 seems to be faster on this example 11 | var count = 0 12 | var n = boxes.length 13 | for(var i=0; i>>1 17 | } -------------------------------------------------------------------------------- /cases/boxvsrbush-bipartite.js: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Bipartite box intersect performance", 3 | "algorithms": [ 4 | "boxtree", 5 | "box-intersect" 6 | ], 7 | "distribution": [ 8 | { 9 | "d": 2, 10 | "type": "uniform" 11 | }, 12 | { 13 | "d": 2, 14 | "type": "uniform" 15 | }], 16 | "parameters": [ ["0.n"], ["1.n"] ], 17 | "cases": [15, 15], 18 | "ranges": [ [[50, 100000]], [[50, 100000]] ], 19 | "numIters": 50, 20 | "plot": false 21 | } -------------------------------------------------------------------------------- /cases/sphere2d-large-complete.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "2D Sphere Complete Intersect (Large)", 3 | "algorithms": [ 4 | "box-intersect", 5 | "boxtree", 6 | "rbush-incremental", 7 | "rbush-bulk", 8 | "p2-grid", 9 | "jsts-quadtree", 10 | "jsts-strtree" 11 | ], 12 | "distribution": { 13 | "d": 2, 14 | "type": "sphere" 15 | }, 16 | "parameters": [[ "n" ]], 17 | "cases": [10], 18 | "ranges": [[[ 5000, 50000 ]]], 19 | "numIters": 30, 20 | "plot": true 21 | } -------------------------------------------------------------------------------- /algorithms/rbush/incremental.js: -------------------------------------------------------------------------------- 1 | var rbush = require('rbush') 2 | 3 | exports.name = 'rbush' 4 | 5 | exports.prepare = function(boxes) { return boxes.map(function(b,i) { 6 | return [b[0], b[1], b[2], b[3]] 7 | }) } 8 | 9 | exports.run = function(boxes) { 10 | var tree = rbush(16) //16 seems to give best results 11 | var count = 0 12 | var n = boxes.length 13 | for(var i=0; i blue.length) { 11 | return run(blue, red) 12 | } 13 | var tree = rbush(9).load(blue) //9 seems to be faster on this example 14 | var count = 0 15 | var n = red.length 16 | for(var i=0; i>> 1); 23 | } 24 | 25 | -------------------------------------------------------------------------------- /algorithms/aabbtree/complete.js: -------------------------------------------------------------------------------- 1 | var AABBTree = require('./aabbtree') 2 | 3 | exports.name = 'aabbtree' 4 | 5 | exports.prepare = function(boxes) { 6 | return boxes.map(function(b) { 7 | return { 8 | spatialIndex: -1, 9 | extents: b 10 | }; 11 | }) 12 | } 13 | 14 | exports.run = function(nodes) { 15 | var aabbTree = AABBTree.create(false); // passing 'true' would use a much slower building strategy 16 | var n = nodes.length 17 | for(var i=0; i>> 1); 23 | } 24 | 25 | -------------------------------------------------------------------------------- /algorithms/oimo/dbvt/DBVTNode.js: -------------------------------------------------------------------------------- 1 | var AABB = require('../AABB') 2 | 3 | /** 4 | * A node of the dynamic bounding volume tree. 5 | * @author saharan 6 | */ 7 | var DBVTNode = function(){ 8 | // The first child node of this node. 9 | this.child1=null; 10 | // The second child node of this node. 11 | this.child2=null; 12 | // The parent node of this tree. 13 | this.parent=null; 14 | // The proxy of this node. This has no value if this node is not leaf. 15 | this.proxy=null; 16 | // The maximum distance from leaf nodes. 17 | this.height=0; 18 | // The AABB of this node. 19 | this.aabb=new AABB(); 20 | } 21 | 22 | module.exports = DBVTNode -------------------------------------------------------------------------------- /algorithms/oimo/ooimo-tester.js: -------------------------------------------------------------------------------- 1 | var AABB = require('./AABB') 2 | 3 | exports.prepare = function(boxes) { 4 | return boxes.map(function(b, i) { 5 | return { 6 | aabb: new AABB(b[0], b[3], b[1], b[4], b[2], b[5]), 7 | id: i, 8 | belongsTo: 1, 9 | collidesWith: 1, 10 | parent: { 11 | sleeping: false, 12 | isDynamic: true, 13 | numJoints: 0, 14 | jointLink: null 15 | } 16 | } 17 | }) 18 | } 19 | 20 | exports.run = function(BroadPhase, boxes) { 21 | var bp = new BroadPhase() 22 | for(var i=0; i>>1 14 | var count = 0 15 | for(var k=0; k>>1 31 | } -------------------------------------------------------------------------------- /algorithms/lazykdtree/complete.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var kdtree = require('lazykdtree') 4 | 5 | exports.prepare = function(boxes) { 6 | return boxes.map(function(box) { 7 | var res = new kdtree.BoundingBox() 8 | var d = box.length>>>1 9 | var coords = new Array(d) 10 | for(var i=0; i>>1; ++i) { 11 | coords[i] = [box[i], box[i+d]] 12 | } 13 | res.coords = coords 14 | return res 15 | }) 16 | } 17 | 18 | exports.run = function(boxes) { 19 | var d = boxes[0].coords.length 20 | var index = new kdtree.Index(d) 21 | for(var i=0; i>> 1 30 | } -------------------------------------------------------------------------------- /generators/uniform.js: -------------------------------------------------------------------------------- 1 | function defaultLo(d, n) { 2 | var result = new Array(d) 3 | for(var i=0; i>>1 9 | for(var i=0; i (a1 + b1)) { 32 | continue j_loop 33 | } 34 | } 35 | count += 1 36 | } 37 | } 38 | return count 39 | } -------------------------------------------------------------------------------- /algorithms/simple-quadtree/complete.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var QuadTree = require('simple-quadtree'); 4 | 5 | exports.prepare = function(boxes) { 6 | return boxes.map(function(box, id) { 7 | return { 8 | x: box[0], 9 | y: box[1], 10 | w: box[2] - box[0], 11 | h: box[3] - box[1], 12 | id: id 13 | } 14 | }) 15 | } 16 | 17 | exports.run = function(boxes) { 18 | var minX = Infinity 19 | var minY = Infinity 20 | var maxX = -Infinity 21 | var maxY = -Infinity 22 | for(var i=0; i>>1 9 | for(var i=0; i (a1 + b1)) { 33 | continue j_loop 34 | } 35 | } 36 | count += 1 37 | } 38 | } 39 | return count 40 | } -------------------------------------------------------------------------------- /draw.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | 4 | function drawBoxes(width, height, n, dist, padx, pady, padys) { 5 | var boxes = dist({ 6 | n: n, 7 | d: 2 8 | }) 9 | var canvas = document.createElement('canvas') 10 | canvas.width = (1 + 2*padx)*width 11 | canvas.height = (1 + 2*pady)*height 12 | 13 | var context = canvas.getContext('2d') 14 | context.fillStyle = 'rgba(0,0,0,0.1)' 15 | context.strokeStyle = '#000' 16 | 17 | boxes.forEach(function(box) { 18 | context.beginPath() 19 | context.rect(width*(box[0]+padx), height*(box[1]+pady+padys), width*(box[2]-box[0]), height*(box[3]-box[1])) 20 | context.stroke() 21 | context.fill() 22 | }) 23 | 24 | var img = new Image() 25 | img.src = canvas.toDataURL() 26 | document.body.appendChild(img) 27 | } 28 | 29 | 30 | drawBoxes(512, 512, 500, require('./generators/uniform'), 0.125, 0.125,0) 31 | drawBoxes(512, 512, 80, require('./generators/sphere'), 0.125, 0.125,0) 32 | drawBoxes(512, 512, 80, require('./generators/skewed'), 0.125, 0.25,-0.25) -------------------------------------------------------------------------------- /algorithms/box2d/broadphase.js: -------------------------------------------------------------------------------- 1 | var box2d = require('./box2d') 2 | 3 | exports.name = 'box2d' 4 | 5 | exports.prepare = function(boxes) { 6 | return boxes.map(function(b) { 7 | var aabb = new box2d.AABB() 8 | aabb.lowerBound.x = b[0] 9 | aabb.lowerBound.y = b[1] 10 | aabb.upperBound.x = b[2] 11 | aabb.upperBound.y = b[3] 12 | return aabb 13 | }) 14 | } 15 | 16 | exports.run = function(boxes) { 17 | var broadphase = new box2d.BroadPhase() 18 | var n = boxes.length 19 | for(var i=0; i blue.length) 18 | { 19 | nodes = blue; 20 | queries = red; 21 | } 22 | else 23 | { 24 | nodes = red; 25 | queries = blue; 26 | } 27 | var n = nodes.length; 28 | var i; 29 | for(i=0; i blue.length) 18 | { 19 | nodes = blue; 20 | queries = red; 21 | } 22 | else 23 | { 24 | nodes = red; 25 | queries = blue; 26 | } 27 | var n = nodes.length; 28 | var i; 29 | for(i=0; iaabb2.maxX) ? aabb1.maxX : aabb2.maxX; 34 | this.minY = (aabb1.minYaabb2.maxY) ? aabb1.maxY : aabb2.maxY; 36 | this.minZ = (aabb1.minZaabb2.maxZ) ? aabb1.maxZ : aabb2.maxZ; 38 | /* 39 | var margin=0; 40 | this.minX-=margin; 41 | this.minY-=margin; 42 | this.minZ-=margin; 43 | this.maxX+=margin; 44 | this.maxY+=margin; 45 | this.maxZ+=margin; 46 | */ 47 | }, 48 | /** 49 | * Get the surface area. 50 | * @return 51 | */ 52 | surfaceArea:function(){ 53 | var h=this.maxY-this.minY; 54 | var d=this.maxZ-this.minZ; 55 | return 2*((this.maxX-this.minX)*(h+d)+h*d); 56 | }, 57 | /** 58 | * Get whether the AABB intersects with the point or not. 59 | * @param x 60 | * @param y 61 | * @param z 62 | * @return 63 | */ 64 | intersectsWithPoint:function(x,y,z){ 65 | return x>=this.minX&&x<=this.maxX&&y>=this.minY&&y<=this.maxY&&z>=this.minZ&&z<=this.maxZ; 66 | } 67 | } 68 | 69 | module.exports = AABB -------------------------------------------------------------------------------- /algorithms/oimo/sap/SAPProxy.js: -------------------------------------------------------------------------------- 1 | var Proxy = require('../Proxy') 2 | var SAPElement = require('./SAPElement') 3 | 4 | /** 5 | * A proxy for sweep and prune broad-phase. 6 | * @author saharan 7 | */ 8 | var SAPProxy = function(sap,shape){ 9 | Proxy.call( this, shape); 10 | // Type of the axis to which the proxy belongs to. [0:none, 1:dynamic, 2:static] 11 | this.belongsTo = 0; 12 | // The maximum elements on each axis. 13 | this.max = []; 14 | // The minimum elements on each axis. 15 | this.min = []; 16 | 17 | this.sap=sap; 18 | this.min[0]=new SAPElement(this,false); 19 | this.max[0]=new SAPElement(this,true); 20 | this.min[1]=new SAPElement(this,false); 21 | this.max[1]=new SAPElement(this,true); 22 | this.min[2]=new SAPElement(this,false); 23 | this.max[2]=new SAPElement(this,true); 24 | this.max[0].pair=this.min[0]; 25 | this.max[1].pair=this.min[1]; 26 | this.max[2].pair=this.min[2]; 27 | this.min[0].min1=this.min[1]; 28 | this.min[0].max1=this.max[1]; 29 | this.min[0].min2=this.min[2]; 30 | this.min[0].max2=this.max[2]; 31 | this.min[1].min1=this.min[0]; 32 | this.min[1].max1=this.max[0]; 33 | this.min[1].min2=this.min[2]; 34 | this.min[1].max2=this.max[2]; 35 | this.min[2].min1=this.min[0]; 36 | this.min[2].max1=this.max[0]; 37 | this.min[2].min2=this.min[1]; 38 | this.min[2].max2=this.max[1]; 39 | }; 40 | SAPProxy.prototype = Object.create( Proxy.prototype ); 41 | /** 42 | * Returns whether the proxy is dynamic or not. 43 | * @return 44 | */ 45 | SAPProxy.prototype.isDynamic = function () { 46 | var body=this.shape.parent; 47 | return body.isDynamic&&!body.sleeping; 48 | } 49 | SAPProxy.prototype.update = function () { 50 | this.min[0].value=this.aabb.minX; 51 | this.max[0].value=this.aabb.maxX; 52 | this.min[1].value=this.aabb.minY; 53 | this.max[1].value=this.aabb.maxY; 54 | this.min[2].value=this.aabb.minZ; 55 | this.max[2].value=this.aabb.maxZ; 56 | if(this.belongsTo==1&&!this.isDynamic()||this.belongsTo==2&&this.isDynamic()){ 57 | this.sap.removeProxy(this); 58 | this.sap.addProxy(this); 59 | } 60 | } 61 | 62 | module.exports = SAPProxy -------------------------------------------------------------------------------- /algorithms/oimo/BruteForceBroadPhase.js: -------------------------------------------------------------------------------- 1 | /** 2 | * A broad-phase algorithm with brute-force search. 3 | * This always checks for all possible pairs. 4 | */ 5 | 6 | var BroadPhase = require('./BroadPhase') 7 | var BasicProxy = require('./BasicProxy') 8 | 9 | var BruteForceBroadPhase = function(){ 10 | BroadPhase.call( this); 11 | this.types = 0x1; 12 | this.numProxies=0; 13 | this.maxProxies = 256; 14 | this.proxies = [];// Vector ! 15 | this.proxies.length = 256; 16 | } 17 | 18 | BruteForceBroadPhase.prototype = Object.create( BroadPhase.prototype ); 19 | 20 | BruteForceBroadPhase.prototype.createProxy = function (shape) { 21 | return new BasicProxy(shape); 22 | } 23 | BruteForceBroadPhase.prototype.addProxy = function (proxy) { 24 | if(this.numProxies==this.maxProxies){ 25 | //this.maxProxies<<=1; 26 | this.maxProxies*=2; 27 | var newProxies=[]; 28 | newProxies.length = this.maxProxies; 29 | var i = this.numProxies; 30 | while(i--){ 31 | //for(var i=0, l=this.numProxies;i>1; 51 | //this.numPairChecks=this.numProxies*(this.numProxies-1)*0.5; 52 | var i = this.numProxies; 53 | while(i--){ 54 | //for(var i=0, l=this.numProxies;ib2.maxX|| b1.maxYb2.maxY|| b1.maxZb2.maxZ|| !this.isAvailablePair(s1,s2) ){ 66 | continue; 67 | } 68 | this.addPair(s1,s2); 69 | }} 70 | } 71 | } 72 | 73 | module.exports = BruteForceBroadPhase -------------------------------------------------------------------------------- /algorithms/oimo/BroadPhase.js: -------------------------------------------------------------------------------- 1 | var Pair = require('./Pair') 2 | 3 | /** 4 | * The broad-phase is used for collecting all possible pairs for collision. 5 | */ 6 | 7 | var BroadPhase = function(){ 8 | this.types = 0x0; 9 | this.numPairChecks=0; 10 | this.numPairs=0; 11 | 12 | this.bufferSize=256; 13 | this.pairs=[];// vector 14 | this.pairs.length = this.bufferSize; 15 | var i = this.bufferSize; 16 | while(i--){ 17 | this.pairs[i] = new Pair(); 18 | } 19 | } 20 | 21 | BroadPhase.prototype = { 22 | 23 | constructor: BroadPhase, 24 | /** 25 | * Create a new proxy. 26 | * @param shape 27 | * @return 28 | */ 29 | createProxy:function(shape){ 30 | throw new Error("Inheritance error."); 31 | }, 32 | /** 33 | * Add the proxy into the broad-phase. 34 | * @param proxy 35 | */ 36 | addProxy:function(proxy){ 37 | throw new Error("Inheritance error."); 38 | }, 39 | /** 40 | * Remove the proxy from the broad-phase. 41 | * @param proxy 42 | */ 43 | removeProxy:function(proxy){ 44 | throw new Error("Inheritance error."); 45 | }, 46 | /** 47 | * Returns whether the pair is available or not. 48 | * @param s1 49 | * @param s2 50 | * @return 51 | */ 52 | isAvailablePair:function(s1,s2){ 53 | var b1=s1.parent; 54 | var b2=s2.parent; 55 | if( b1==b2 || // same parents 56 | (!b1.isDynamic&&!b2.isDynamic) || // static or kinematic object 57 | (s1.belongsTo&s2.collidesWith)==0 || 58 | (s2.belongsTo&s1.collidesWith)==0 // collision filtering 59 | ){ return false; } 60 | var js; 61 | if(b1.numJoints0){ 73 | var pair=this.pairs[--this.numPairs]; 74 | pair.shape1=null; 75 | pair.shape2=null; 76 | } 77 | this.numPairChecks=0; 78 | this.collectPairs(); 79 | }, 80 | collectPairs:function(){ 81 | throw new Error("Inheritance error."); 82 | }, 83 | addPair:function(s1,s2){ 84 | if(this.numPairs==this.bufferSize){ // expand pair buffer 85 | var newBufferSize=this.bufferSize<<1; 86 | //var newBufferSize=this.bufferSize*2; 87 | var newPairs=[];// vector 88 | newPairs.length = this.bufferSize; 89 | //var i = this.bufferSize; 90 | //var j; 91 | //while(i--){ 92 | for(var i=0, j=this.bufferSize;i= j){ 98 | for(i=this.bufferSize, j=newBufferSize;ileafB.maxX|| 62 | trueB.minYleafB.maxY|| 63 | trueB.minZleafB.maxZ 64 | ){// the leaf needs correcting 65 | var margin=0.1; 66 | this.tree.deleteLeaf(leaf); 67 | leafB.minX=trueB.minX-margin; 68 | leafB.maxX=trueB.maxX+margin; 69 | leafB.minY=trueB.minY-margin; 70 | leafB.maxY=trueB.maxY+margin; 71 | leafB.minZ=trueB.minZ-margin; 72 | leafB.maxZ=trueB.maxZ+margin; 73 | this.tree.insertLeaf(leaf); 74 | this.collide(leaf,this.tree.root); 75 | } 76 | } 77 | } 78 | DBVTBroadPhase.prototype.collide = function (node1,node2) { 79 | var stackCount=2; 80 | this.stack[0]=node1; 81 | this.stack[1]=node2; 82 | while(stackCount>0){ 83 | var n1=this.stack[--stackCount]; 84 | var n2=this.stack[--stackCount]; 85 | var l1=n1.proxy!=null; 86 | var l2=n2.proxy!=null; 87 | this.numPairChecks++; 88 | if(l1&&l2){ 89 | var s1=n1.proxy.shape; 90 | var s2=n2.proxy.shape; 91 | var b1=s1.aabb; 92 | var b2=s2.aabb; 93 | if( 94 | s1==s2|| 95 | b1.maxXb2.maxX|| 96 | b1.maxYb2.maxY|| 97 | b1.maxZb2.maxZ|| 98 | !this.isAvailablePair(s1,s2) 99 | ){ 100 | continue; 101 | } 102 | this.addPair(s1,s2); 103 | }else{ 104 | b1=n1.aabb; 105 | b2=n2.aabb; 106 | if( 107 | b1.maxXb2.maxX|| 108 | b1.maxYb2.maxY|| 109 | b1.maxZb2.maxZ 110 | ){ 111 | continue; 112 | } 113 | if(stackCount+4>=this.maxStack){// expand the stack 114 | //this.maxStack<<=1; 115 | this.maxStack*=2; 116 | var newStack=[];// vector 117 | newStack.length = this.maxStack; 118 | for(var i=0;in2.aabb.surfaceArea())){ 124 | this.stack[stackCount++]=n1.child1; 125 | this.stack[stackCount++]=n2; 126 | this.stack[stackCount++]=n1.child2; 127 | this.stack[stackCount++]=n2; 128 | }else{ 129 | this.stack[stackCount++]=n1; 130 | this.stack[stackCount++]=n2.child1; 131 | this.stack[stackCount++]=n1; 132 | this.stack[stackCount++]=n2.child2; 133 | } 134 | } 135 | } 136 | } 137 | 138 | module.exports = DBVTBroadPhase -------------------------------------------------------------------------------- /plot.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var hashStr = require('murmurhash-js') 4 | var fs = require('fs') 5 | var path = require('path') 6 | 7 | module.exports = plotBenchmark 8 | 9 | 10 | //TODO: If you are running this locally, create an account with plotly and get an API key 11 | // Store this in a local file called plotly.json with two keys: 12 | // 13 | // username: your plotly user name 14 | // key: your plotly api key 15 | // 16 | 17 | var PLOTLY_CONFIG 18 | try { 19 | PLOTLY_CONFIG = require('./plotly.json') 20 | } catch(e) { 21 | PLOTLY_CONFIG = { 22 | "username": "Node.js-Demo-Account", 23 | "key": "dvlqkmw0zm" 24 | } 25 | } 26 | 27 | var plotly = require('plotly')(PLOTLY_CONFIG.username, PLOTLY_CONFIG.key) 28 | 29 | function plotBenchmark(result) { 30 | if(typeof document !== 'undefined') { 31 | console.log(result) 32 | return 33 | } 34 | switch(result.type) { 35 | case 'barchart': 36 | plotBarChart(result) 37 | break 38 | case 'series': 39 | plotSeries(result) 40 | break 41 | case 'surface': 42 | plotSurface(result) 43 | break 44 | } 45 | } 46 | 47 | function plotName(str) { 48 | return str.replace(/\s/g, '_').replace(/\(\)\.\-/g, '') 49 | } 50 | 51 | function nameToColor(name) { 52 | if(name === 'brute-force') { 53 | return '#f00' 54 | } else if(name === 'box-intersect') { 55 | return '#54e' 56 | } else if(name === 'rbush-incremental') { 57 | return '#2d4' 58 | } else if(name === 'rbush-bulk') { 59 | return '#2bd' 60 | } else if(name === 'p2-grid') { 61 | return '#cc5' 62 | } 63 | 64 | var hash = hashStr(name) & 0xfff 65 | var colorStr = hash.toString(16) 66 | while(colorStr.length < 3) { 67 | colorStr = '0' + colorStr 68 | } 69 | return '#' + colorStr 70 | } 71 | 72 | function makePlot(result, traces, options) { 73 | plotly.plot(traces, options, function(err, msg) { 74 | if(err) { 75 | console.error("error!", err) 76 | console.log("data:", JSON.stringify(result)) 77 | } else { 78 | console.log(result.name+':', msg.url) 79 | } 80 | }) 81 | } 82 | 83 | function plotBarChart(result) { 84 | var series = Object.keys(result.data) 85 | var data = [] 86 | series.forEach(function(name) { 87 | data.push({ 88 | x: [name], 89 | y: [result.data[name][0]], 90 | marker: { 91 | color: nameToColor(name), 92 | }, 93 | type: 'bar', 94 | name: name 95 | }) 96 | }) 97 | var options = { 98 | filename: plotName(result.name), 99 | fileopt: 'overwrite', 100 | layout: { 101 | title: result.name, 102 | autosize: true, 103 | showlegend: false, 104 | yaxis: { 105 | title: "Average time (ms)", 106 | autorange: true 107 | } 108 | } 109 | } 110 | makePlot(result, data, options) 111 | } 112 | 113 | function plotSeries(result) { 114 | var series = Object.keys(result.data) 115 | var traces = series.map(function(name) { 116 | return { 117 | x: result.xaxis, 118 | y: result.data[name], 119 | type: 'scatter', 120 | name: name, 121 | line: { 122 | color: nameToColor(name) 123 | } 124 | } 125 | }) 126 | var options = { 127 | filename: plotName(result.name), 128 | fileopt: "overwrite", 129 | layout: { 130 | title: result.name, 131 | showlegend: true, 132 | autosize: true, 133 | yaxis: { 134 | title: "Average time (ms)", 135 | autorange: true 136 | }, 137 | xaxis: { 138 | title: result.xaxisTitle, 139 | autorange: true 140 | } 141 | } 142 | } 143 | makePlot(result, traces, options) 144 | } 145 | 146 | function plotSurface(result) { 147 | var series = Object.keys(result.data) 148 | var traces = series.map(function(name) { 149 | return { 150 | name: name, 151 | x: result.xaxis, 152 | y: result.yaxis, 153 | z: result.data[name], 154 | type: 'surface' 155 | } 156 | }) 157 | var options = { 158 | filename: plotName(result.name), 159 | fileopt: "overwrite", 160 | layout: { 161 | title: result.name, 162 | scene: { 163 | xaxis: { 164 | title: result.xaxisTitle 165 | }, 166 | yaxis: { 167 | title: result.yaxisTitle 168 | }, 169 | zaxis: { 170 | title: "Average time (ms)" 171 | } 172 | } 173 | } 174 | } 175 | makePlot(result, traces, options) 176 | } -------------------------------------------------------------------------------- /algorithms/oimo/sap/SAPAxis.js: -------------------------------------------------------------------------------- 1 | /** 2 | * A projection axis for sweep and prune broad-phase. 3 | * @author saharan 4 | */ 5 | var SAPAxis = function(){ 6 | this.numElements=0; 7 | this.bufferSize=256; 8 | this.elements=[]; 9 | this.elements.length = this.bufferSize; 10 | this.stack=new Array(64); 11 | }; 12 | 13 | SAPAxis.prototype = { 14 | 15 | constructor: SAPAxis, 16 | 17 | addElements:function(min,max){ 18 | if(this.numElements+2>=this.bufferSize){ 19 | //this.bufferSize<<=1; 20 | this.bufferSize*=2; 21 | var newElements=[]; 22 | var i = this.numElements; 23 | while(i--){ 24 | //for(var i=0, l=this.numElements; i>threshold)!=0)threshold++; 59 | threshold=threshold*this.numElements>>2; 60 | count=0; 61 | var giveup=false; 62 | var elements=this.elements; 63 | for(var i=1, l=this.numElements; ipivot){ 68 | var j=i; 69 | do{ 70 | elements[j]=tmp2; 71 | if(--j==0)break; 72 | tmp2=elements[j-1]; 73 | }while(tmp2.value>pivot); 74 | elements[j]=tmp; 75 | count+=i-j; 76 | if(count>threshold){ 77 | giveup=true; // stop and use quick sort 78 | break; 79 | } 80 | } 81 | } 82 | if(!giveup)return; 83 | count=2;var stack=this.stack; 84 | stack[0]=0; 85 | stack[1]=this.numElements-1; 86 | while(count>0){ 87 | var right=stack[--count]; 88 | var left=stack[--count]; 89 | var diff=right-left; 90 | if(diff>16){ // quick sort 91 | //var mid=left+(diff>>1); 92 | var mid=left+(Math.floor(diff*0.5)); 93 | tmp=elements[mid]; 94 | elements[mid]=elements[right]; 95 | elements[right]=tmp; 96 | pivot=tmp.value; 97 | i=left-1; 98 | j=right; 99 | while(true){ 100 | var ei; 101 | var ej; 102 | do{ ei=elements[++i]; }while(ei.value=j)break; 105 | elements[i]=ej; 106 | elements[j]=ei; 107 | } 108 | 109 | elements[right]=elements[i]; 110 | elements[i]=tmp; 111 | if(i-left>right-i){ 112 | stack[count++]=left; 113 | stack[count++]=i-1; 114 | stack[count++]=i+1; 115 | stack[count++]=right; 116 | }else{ 117 | stack[count++]=i+1; 118 | stack[count++]=right; 119 | stack[count++]=left; 120 | stack[count++]=i-1; 121 | } 122 | }else{ 123 | for(i=left+1;i<=right;i++){ 124 | tmp=elements[i]; 125 | pivot=tmp.value; 126 | tmp2=elements[i-1]; 127 | if(tmp2.value>pivot){ 128 | j=i; 129 | do{ 130 | elements[j]=tmp2; 131 | if(--j==0)break; 132 | tmp2=elements[j-1]; 133 | }while(tmp2.value>pivot); 134 | elements[j]=tmp; 135 | } 136 | } 137 | } 138 | } 139 | }, 140 | calculateTestCount:function(){ 141 | var num=1; 142 | var sum=0; 143 | for(var i=1, l=this.numElements; ie2.max1.value||max1e2.max2.value||max2e2.max1.value||max1e2.max2.value||max2 33 | 34 | #### Tiny (500 boxes) 35 | 36 | [](https://plot.ly/~MikolaLysenko/124) 37 | 38 | #### Small (1500 boxes) 39 | 40 | [](https://plot.ly/~MikolaLysenko/125) 41 | 42 | #### Medium (10000 boxes) 43 | 44 | [](https://plot.ly/~MikolaLysenko/127) 45 | 46 | #### Large (250000 boxes) 47 | 48 | [](https://plot.ly/~MikolaLysenko/129) 49 | 50 | ### Circle 51 | 52 | 53 | 54 | #### Small (10000 boxes) 55 | 56 | [](https://plot.ly/~MikolaLysenko/130) 57 | 58 | #### Large (50000 boxes) 59 | 60 | [](https://plot.ly/~MikolaLysenko/131) 61 | 62 | ### High aspect ratio 63 | 64 | 65 | 66 | #### Small (10000 boxes) 67 | 68 | [](https://plot.ly/~MikolaLysenko/132) 69 | 70 | #### Large (100000 boxes) 71 | 72 | [](https://plot.ly/~MikolaLysenko/139) 73 | 74 | ## 2D - Bipartite 75 | 76 | #### Small uniform (1500) vs Large uniform (50000) 77 | 78 | [](https://plot.ly/~MikolaLysenko/147) 79 | 80 | #### Circle (20000) vs uniform (20000) 81 | 82 | [](https://plot.ly/~MikolaLysenko/148) 83 | 84 | #### High aspect ratio (20000) vs uniform (20000) 85 | 86 | [](https://plot.ly/~MikolaLysenko/150) 87 | 88 | #### Skewed (5000) vs circle (20000) 89 | 90 | [](https://plot.ly/~MikolaLysenko/151) 91 | 92 | ## 3D - Complete 93 | 94 | #### Uniform 95 | 96 | [](https://plot.ly/~MikolaLysenko/152) 97 | 98 | #### Sphere 99 | 100 | [](https://plot.ly/~MikolaLysenko/153) 101 | 102 | #### Skewed 103 | 104 | [](https://plot.ly/~MikolaLysenko/154) 105 | 106 | #### Bunny 107 | 108 | [](https://plot.ly/~MikolaLysenko/155) 109 | 110 | # Running the benchmark 111 | 112 | ## In node.js/iojs 113 | 114 | First, you will need to have [npm](https://www.npmjs.com/) installed and [git](http://git-scm.com/). Clone this repo, go into the directory where it is located and then type: 115 | 116 | ``` 117 | npm install 118 | ``` 119 | 120 | To pull in all the files locally. If you have node.js installed, you can then do, 121 | 122 | ``` 123 | node run 124 | ``` 125 | 126 | Or if you are using iojs, 127 | 128 | ``` 129 | iojs run 130 | ``` 131 | 132 | You can run specific cases by specifying them on the command line. For example, to run the tiny benchmark do: 133 | 134 | ``` 135 | node run cases/uniform2d-tiny-complete.json 136 | ``` 137 | 138 | If you want to run the whole suite at once, you can run one of the following npm scripts: 139 | 140 | ``` 141 | npm run complete2 142 | npm run bipartite2 143 | npm run complete3 144 | npm run bipartite3 145 | ``` 146 | 147 | These take some time to run so be patient! 148 | 149 | ### Setting up [plot.ly](https://plot.ly/) (optional) 150 | 151 | If you want to make charts to go along with your data, you will need to create an account and get an API key with [plot.ly](https://plot.ly/). Once you've done this, save your credentials to the file `plotly.json` in the root directory of the folder (note this is not tracked in git). The contents of the JSON file should look something like: 152 | 153 | ```javascript 154 | { 155 | "username": "Node.js-Demo-Account", 156 | "key": "dvlqkmw0zm" 157 | } 158 | ``` 159 | 160 | # Contributing 161 | 162 | Improvements to these benchmarks are always welcome. 163 | 164 | ## Adding more test data 165 | 166 | To create a new generator, you can create a module in the `generators/` folder and add a reference to it in the distributions object in the `bench.js` file. You can also create new test cases by modifying the JSON configuration files in the `cases/` folder. The cases which are run 167 | 168 | ## Adding an algorithm 169 | 170 | To add a new algorithm to the suite, there are 3 things you need to do: 171 | 172 | 1. Create an adapter for the algorithm in the `algorithms/` folder. At minimum, your adapter must implement two methods: `exports.prepare` and `exports.run`. 173 | a. `prepare` should translate the input boxes into whatever data format your algorithm requires (ie if you have some custom AABB type, then do these conversions here so you aren't penalized by them) 174 | a. `run` should execute your algorithm and return the total number of overlapping rectangles 175 | 2. Add an entry in the `completeAlgs` or `bipartiteAlgs` sets in `bench.js`. If your algorithm only supports one of these modes of entry (for example only complete intersections), then you don't have to add it to both tables. 176 | 3. Add your algorithm to the relevant test cases. 177 | 178 | You can run your test cases using the `run.js` command. 179 | 180 | # License 181 | (c) 2015 Mikola Lysenko. MIT License -------------------------------------------------------------------------------- /bench.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = runBenchmark 4 | 5 | var distributions = { 6 | 'uniform': require('./generators/uniform'), 7 | 'skewed': require('./generators/skewed'), 8 | 'sphere': require('./generators/sphere'), 9 | 'bunny': require('./generators/bunny') 10 | } 11 | 12 | var completeAlgs = { 13 | 'brute-force': require('./algorithms/brute-force/complete-fast'), 14 | 'brute-force-robust': require('./algorithms/brute-force/complete-robust'), 15 | 'box-intersect': require('./algorithms/box-intersect/complete'), 16 | 'rbush-incremental': require('./algorithms/rbush/incremental'), 17 | 'rbush-bulk': require('./algorithms/rbush/bulk'), 18 | 'box2d': require('./algorithms/box2d/broadphase'), 19 | 'p2-grid': require('./algorithms/p2/grid'), 20 | 'p2-sweep': require('./algorithms/p2/sweep'), 21 | 'oimo-brute-force': require('./algorithms/oimo/brute-force'), 22 | 'oimo-bvh': require('./algorithms/oimo/bvh'), 23 | 'simple-quadtree': require('./algorithms/simple-quadtree/complete'), 24 | 'lazykdtree': require('./algorithms/lazykdtree/complete'), 25 | 'rtree': require('./algorithms/rtree/complete'), 26 | 'jsts-quadtree': require('./algorithms/jsts/quadtree'), 27 | 'jsts-strtree': require('./algorithms/jsts/strtree'), 28 | 'boxtree': require('./algorithms/boxtree/complete'), 29 | 'aabbtree': require('./algorithms/aabbtree/complete') 30 | } 31 | 32 | var bipartiteAlgs = { 33 | 'brute-force': require('./algorithms/brute-force/bipartite-fast'), 34 | 'box-intersect': require('./algorithms/box-intersect/bipartite'), 35 | 'rbush': require('./algorithms/rbush/bipartite'), 36 | 'simple-quadtree': require('./algorithms/simple-quadtree/bipartite'), 37 | 'rtree': require('./algorithms/rtree/bipartite'), 38 | 'jsts-quadtree': require('./algorithms/jsts/quadtree-bipartite'), 39 | 'jsts-strtree': require('./algorithms/jsts/strtree-bipartite'), 40 | 'boxtree': require('./algorithms/boxtree/bipartite'), 41 | 'aabbtree': require('./algorithms/aabbtree/bipartite') 42 | } 43 | 44 | var prettyNames = { 45 | 'n': 'Boxes', 46 | '0.n': 'Red boxes', 47 | '1.n': 'Blue boxes' 48 | } 49 | 50 | 51 | function generateBoxes(options) { 52 | var dist = distributions[options.type] 53 | if(dist) { 54 | return dist(options) 55 | } 56 | throw new Error('invalid distribution type: ' + options.type) 57 | } 58 | 59 | function runBenchmark(desc) { 60 | var algorithms = desc.algorithms 61 | var distribution = desc.distribution 62 | var sweepParam = desc.parameters 63 | var sweepValues = desc.ranges 64 | var sweepCases = desc.cases 65 | var numIters = desc.numIters || 5 66 | var bipartite = Array.isArray(distribution) && distribution.length === 2 67 | 68 | if(bipartite) { 69 | console.log('generators:', distribution[0].type, distribution[1].type) 70 | } else { 71 | console.log('generator:', desc.distribution.type) 72 | } 73 | 74 | function parameterSweep() { 75 | var result = { 76 | names: sweepParam[0].map(function(name) { 77 | if(name in prettyNames) { 78 | return prettyNames[name] 79 | } 80 | return name 81 | }), 82 | axis: [], 83 | data: {} 84 | } 85 | for(var i=0; i 1) { 95 | x = x[p.shift()] 96 | } 97 | var v = sweepValues[0][j] 98 | var vv = v[0] + (v[1] - v[0]) / sweepCases[0] * i 99 | x[p[0]] = vv 100 | xparameter.push(vv) 101 | } 102 | result.axis.push(xparameter) 103 | 104 | var boxes, red, blue 105 | 106 | if(bipartite) { 107 | red = generateBoxes(distribution[0]) 108 | blue = generateBoxes(distribution[1]) 109 | } else { 110 | boxes = generateBoxes(distribution) 111 | } 112 | 113 | console.log('case:', i, ' - ', distribution) 114 | algorithms.forEach(function(alg) { 115 | console.log('testing', alg) 116 | 117 | if(bipartite) { 118 | var algorithm = bipartiteAlgs[alg] 119 | var convertedRed = algorithm.prepare(red) 120 | var convertedBlue = algorithm.prepare(blue) 121 | for(var k=0; k<5; ++k) { 122 | algorithm.run(convertedRed, convertedBlue) 123 | } 124 | var tStart = Date.now() 125 | var counter = 0 126 | for(var k=0; k 1) { 203 | x = x[p.shift()] 204 | } 205 | var v = sweepValues[1][j] 206 | var vv = v[0] + (v[1] - v[0]) / sweepCases[1] * i 207 | x[p[0]] = vv 208 | yparameter.push(vv) 209 | } 210 | result.yaxis.push(yparameter[0]) 211 | 212 | var sweep = parameterSweep() 213 | result.xaxisTitle = sweep.names.join(', ') 214 | result.xaxis = sweep.axis.map(function(x) { 215 | return x[0] 216 | }) 217 | for(var j=0; j0){ 86 | newParent=this.freeNodes[--this.numFreeNodes]; 87 | }else{ 88 | newParent=new DBVTNode(); 89 | } 90 | newParent.parent=oldParent; 91 | newParent.child1=leaf; 92 | newParent.child2=sibling; 93 | newParent.aabb.combine(leaf.aabb,sibling.aabb); 94 | newParent.height=sibling.height+1; 95 | sibling.parent=newParent; 96 | leaf.parent=newParent; 97 | if(sibling==this.root){ 98 | // replace root 99 | this.root=newParent; 100 | }else{ 101 | // replace child 102 | if(oldParent.child1==sibling){ 103 | oldParent.child1=newParent; 104 | }else{ 105 | oldParent.child2=newParent; 106 | } 107 | } 108 | // update whole tree 109 | do{ 110 | newParent=this.balance(newParent); 111 | this.fix(newParent); 112 | newParent=newParent.parent; 113 | }while(newParent!=null); 114 | }, 115 | getBalance:function(node){ 116 | if(node.proxy!=null)return 0; 117 | return node.child1.height-node.child2.height; 118 | }, 119 | print:function(node,indent,text){ 120 | var hasChild=node.proxy==null; 121 | if(hasChild)text=this.print(node.child1,indent+1,text); 122 | for(var i=indent*2;i>=0;i--){ 123 | text+=" "; 124 | } 125 | text+=(hasChild?this.getBalance(node):"["+node.proxy.aabb.minX+"]")+"\n"; 126 | if(hasChild)text=this.print(node.child2,indent+1,text); 127 | return text; 128 | }, 129 | /** 130 | * Delete a leaf from the tree. 131 | * @param node 132 | */ 133 | deleteLeaf:function(leaf){ 134 | if(leaf==this.root){ 135 | this.root=null; 136 | return; 137 | } 138 | var parent=leaf.parent; 139 | var sibling; 140 | if(parent.child1==leaf){ 141 | sibling=parent.child2; 142 | }else{ 143 | sibling=parent.child1; 144 | } 145 | if(parent==this.root){ 146 | this.root=sibling; 147 | sibling.parent=null; 148 | return; 149 | } 150 | var grandParent=parent.parent; 151 | sibling.parent=grandParent; 152 | if(grandParent.child1==parent){ 153 | grandParent.child1=sibling; 154 | }else{ 155 | grandParent.child2=sibling; 156 | } 157 | if(this.numFreeNodes<16384){ 158 | this.freeNodes[this.numFreeNodes++]=parent; 159 | } 160 | do{ 161 | grandParent=this.balance(grandParent); 162 | this.fix(grandParent); 163 | grandParent=grandParent.parent; 164 | }while(grandParent!=null); 165 | }, 166 | balance:function(node){ 167 | var nh=node.height; 168 | if(nh<2){ 169 | return node; 170 | } 171 | var p=node.parent; 172 | var l=node.child1; 173 | var r=node.child2; 174 | var lh=l.height; 175 | var rh=r.height; 176 | var balance=lh-rh; 177 | var t;// for bit operation 178 | 179 | // [ N ] 180 | // / \ 181 | // [ L ] [ R ] 182 | // / \ / \ 183 | // [L-L] [L-R] [R-L] [R-R] 184 | 185 | // Is the tree balanced? 186 | if(balance>1){ 187 | var ll=l.child1; 188 | var lr=l.child2; 189 | var llh=ll.height; 190 | var lrh=lr.height; 191 | 192 | // Is L-L higher than L-R? 193 | if(llh>lrh){ 194 | // set N to L-R 195 | l.child2=node; 196 | node.parent=l; 197 | 198 | // [ L ] 199 | // / \ 200 | // [L-L] [ N ] 201 | // / \ / \ 202 | // [...] [...] [ L ] [ R ] 203 | 204 | // set L-R 205 | node.child1=lr; 206 | lr.parent=node; 207 | 208 | // [ L ] 209 | // / \ 210 | // [L-L] [ N ] 211 | // / \ / \ 212 | // [...] [...] [L-R] [ R ] 213 | 214 | // fix bounds and heights 215 | node.aabb.combine(lr.aabb,r.aabb); 216 | t=lrh-rh; 217 | node.height=lrh-(t&t>>31)+1; 218 | l.aabb.combine(ll.aabb,node.aabb); 219 | t=llh-nh; 220 | l.height=llh-(t&t>>31)+1; 221 | }else{ 222 | // set N to L-L 223 | l.child1=node; 224 | node.parent=l; 225 | 226 | // [ L ] 227 | // / \ 228 | // [ N ] [L-R] 229 | // / \ / \ 230 | // [ L ] [ R ] [...] [...] 231 | 232 | // set L-L 233 | node.child1=ll; 234 | ll.parent=node; 235 | 236 | // [ L ] 237 | // / \ 238 | // [ N ] [L-R] 239 | // / \ / \ 240 | // [L-L] [ R ] [...] [...] 241 | 242 | // fix bounds and heights 243 | node.aabb.combine(ll.aabb,r.aabb); 244 | t=llh-rh; 245 | node.height=llh-(t&t>>31)+1; 246 | 247 | l.aabb.combine(node.aabb,lr.aabb); 248 | t=nh-lrh; 249 | l.height=nh-(t&t>>31)+1; 250 | } 251 | // set new parent of L 252 | if(p!=null){ 253 | if(p.child1==node){ 254 | p.child1=l; 255 | }else{ 256 | p.child2=l; 257 | } 258 | }else{ 259 | this.root=l; 260 | } 261 | l.parent=p; 262 | return l; 263 | }else if(balance<-1){ 264 | var rl=r.child1; 265 | var rr=r.child2; 266 | var rlh=rl.height; 267 | var rrh=rr.height; 268 | 269 | // Is R-L higher than R-R? 270 | if(rlh>rrh){ 271 | // set N to R-R 272 | r.child2=node; 273 | node.parent=r; 274 | 275 | // [ R ] 276 | // / \ 277 | // [R-L] [ N ] 278 | // / \ / \ 279 | // [...] [...] [ L ] [ R ] 280 | 281 | // set R-R 282 | node.child2=rr; 283 | rr.parent=node; 284 | 285 | // [ R ] 286 | // / \ 287 | // [R-L] [ N ] 288 | // / \ / \ 289 | // [...] [...] [ L ] [R-R] 290 | 291 | // fix bounds and heights 292 | node.aabb.combine(l.aabb,rr.aabb); 293 | t=lh-rrh; 294 | node.height=lh-(t&t>>31)+1; 295 | r.aabb.combine(rl.aabb,node.aabb); 296 | t=rlh-nh; 297 | r.height=rlh-(t&t>>31)+1; 298 | }else{ 299 | // set N to R-L 300 | r.child1=node; 301 | node.parent=r; 302 | // [ R ] 303 | // / \ 304 | // [ N ] [R-R] 305 | // / \ / \ 306 | // [ L ] [ R ] [...] [...] 307 | 308 | // set R-L 309 | node.child2=rl; 310 | rl.parent=node; 311 | 312 | // [ R ] 313 | // / \ 314 | // [ N ] [R-R] 315 | // / \ / \ 316 | // [ L ] [R-L] [...] [...] 317 | 318 | // fix bounds and heights 319 | node.aabb.combine(l.aabb,rl.aabb); 320 | t=lh-rlh; 321 | node.height=lh-(t&t>>31)+1; 322 | r.aabb.combine(node.aabb,rr.aabb); 323 | t=nh-rrh; 324 | r.height=nh-(t&t>>31)+1; 325 | } 326 | // set new parent of R 327 | if(p!=null){ 328 | if(p.child1==node){ 329 | p.child1=r; 330 | }else{ 331 | p.child2=r; 332 | } 333 | }else{ 334 | this.root=r; 335 | } 336 | r.parent=p; 337 | return r; 338 | } 339 | return node; 340 | }, 341 | fix:function(node){ 342 | var c1=node.child1; 343 | var c2=node.child2; 344 | node.aabb.combine(c1.aabb,c2.aabb); 345 | var h1=c1.height; 346 | var h2=c2.height; 347 | if(h1 1) { 76 | var nodes = this.nodes; 77 | 78 | nodes[index].clear(); 79 | 80 | var endNode = this.endNode; 81 | if ((index + 1) >= endNode) { 82 | while (!nodes[endNode - 1].externalNode) { 83 | endNode -= 1; 84 | } 85 | this.endNode = endNode; 86 | } else { 87 | this.needsRebuild = true; 88 | } 89 | this.numExternalNodes -= 1; 90 | } else { 91 | this.clear(); 92 | } 93 | 94 | externalNode.boxTreeIndex = undefined; 95 | } 96 | }; 97 | 98 | BoxTree.prototype.findParent = function (nodeIndex) { 99 | var nodes = this.nodes; 100 | var parentIndex = nodeIndex; 101 | var nodeDist = 0; 102 | var parent; 103 | do { 104 | parentIndex -= 1; 105 | nodeDist += 1; 106 | parent = nodes[parentIndex]; 107 | } while(parent.escapeNodeOffset <= nodeDist); 108 | return parent; 109 | }; 110 | 111 | BoxTree.prototype.update = function (externalNode, extents) { 112 | var index = externalNode.boxTreeIndex; 113 | if (index !== undefined) { 114 | var min0 = extents[0]; 115 | var min1 = extents[1]; 116 | var max0 = extents[2]; 117 | var max1 = extents[3]; 118 | 119 | var needsRebuild = this.needsRebuild; 120 | var needsRebound = this.needsRebound; 121 | var nodes = this.nodes; 122 | var node = nodes[index]; 123 | 124 | var doUpdate = (needsRebuild || needsRebound || node.minX > min0 || node.minY > min1 || node.maxX < max0 || node.maxY < max1); 125 | 126 | node.minX = min0; 127 | node.minY = min1; 128 | node.maxX = max0; 129 | node.maxY = max1; 130 | 131 | if (doUpdate) { 132 | if (!needsRebuild && 1 < nodes.length) { 133 | this.numUpdates += 1; 134 | if (this.startUpdate > index) { 135 | this.startUpdate = index; 136 | } 137 | if (this.endUpdate < index) { 138 | this.endUpdate = index; 139 | } 140 | if (!needsRebound) { 141 | // force a rebound when things change too much 142 | if ((2 * this.numUpdates) > this.numExternalNodes) { 143 | this.needsRebound = true; 144 | } else { 145 | var parent = this.findParent(index); 146 | if (parent.minX > min0 || parent.minY > min1 || parent.maxX < max0 || parent.maxY < max1) { 147 | this.needsRebound = true; 148 | } 149 | } 150 | } else { 151 | // force a rebuild when things change too much 152 | if (this.numUpdates > (3 * this.numExternalNodes)) { 153 | this.needsRebuild = true; 154 | this.numAdds = this.numUpdates; 155 | } 156 | } 157 | } 158 | } 159 | } else { 160 | this.add(externalNode, extents); 161 | } 162 | }; 163 | 164 | BoxTree.prototype.needsFinalize = function () { 165 | return (this.needsRebuild || this.needsRebound); 166 | }; 167 | 168 | BoxTree.prototype.finalize = function () { 169 | if (this.needsRebuild) { 170 | this.rebuild(); 171 | } else if (this.needsRebound) { 172 | this.rebound(); 173 | } 174 | }; 175 | 176 | BoxTree.prototype.rebound = function () { 177 | var nodes = this.nodes; 178 | if (nodes.length > 1) { 179 | var startUpdateNodeIndex = this.startUpdate; 180 | var endUpdateNodeIndex = this.endUpdate; 181 | 182 | var nodesStack = []; 183 | var numNodesStack = 0; 184 | var topNodeIndex = 0; 185 | for (; ;) { 186 | var topNode = nodes[topNodeIndex]; 187 | var currentNodeIndex = topNodeIndex; 188 | var currentEscapeNodeIndex = (topNodeIndex + topNode.escapeNodeOffset); 189 | var nodeIndex = (topNodeIndex + 1); 190 | var node; 191 | do { 192 | node = nodes[nodeIndex]; 193 | var escapeNodeIndex = (nodeIndex + node.escapeNodeOffset); 194 | if (nodeIndex < endUpdateNodeIndex) { 195 | if (!node.externalNode) { 196 | if (escapeNodeIndex > startUpdateNodeIndex) { 197 | nodesStack[numNodesStack] = topNodeIndex; 198 | numNodesStack += 1; 199 | topNodeIndex = nodeIndex; 200 | } 201 | } 202 | } else { 203 | break; 204 | } 205 | nodeIndex = escapeNodeIndex; 206 | } while(nodeIndex < currentEscapeNodeIndex); 207 | 208 | if (topNodeIndex === currentNodeIndex) { 209 | nodeIndex = (topNodeIndex + 1); // First child 210 | node = nodes[nodeIndex]; 211 | 212 | var minX = node.minX; 213 | var minY = node.minY; 214 | var maxX = node.maxX; 215 | var maxY = node.maxY; 216 | 217 | nodeIndex = (nodeIndex + node.escapeNodeOffset); 218 | while (nodeIndex < currentEscapeNodeIndex) { 219 | node = nodes[nodeIndex]; 220 | if (minX > node.minX) { 221 | minX = node.minX; 222 | } 223 | if (minY > node.minY) { 224 | minY = node.minY; 225 | } 226 | if (maxX < node.maxX) { 227 | maxX = node.maxX; 228 | } 229 | if (maxY < node.maxY) { 230 | maxY = node.maxY; 231 | } 232 | nodeIndex = (nodeIndex + node.escapeNodeOffset); 233 | } 234 | 235 | topNode.minX = minX; 236 | topNode.minY = minY; 237 | topNode.maxX = maxX; 238 | topNode.maxY = maxY; 239 | 240 | endUpdateNodeIndex = topNodeIndex; 241 | 242 | if (0 < numNodesStack) { 243 | numNodesStack -= 1; 244 | topNodeIndex = nodesStack[numNodesStack]; 245 | } else { 246 | break; 247 | } 248 | } 249 | } 250 | } 251 | 252 | this.needsRebuild = false; 253 | this.needsRebound = false; 254 | this.numAdds = 0; 255 | 256 | //this.numUpdates = 0; 257 | this.startUpdate = 0x7FFFFFFF; 258 | this.endUpdate = -0x7FFFFFFF; 259 | }; 260 | 261 | BoxTree.prototype.rebuild = function () { 262 | if (this.numExternalNodes > 0) { 263 | var nodes = this.nodes; 264 | 265 | var buildNodes, numBuildNodes, endNodeIndex; 266 | 267 | if (this.numExternalNodes === nodes.length) { 268 | buildNodes = nodes; 269 | numBuildNodes = nodes.length; 270 | nodes = []; 271 | this.nodes = nodes; 272 | } else { 273 | buildNodes = []; 274 | buildNodes.length = this.numExternalNodes; 275 | numBuildNodes = 0; 276 | endNodeIndex = this.endNode; 277 | for (var n = 0; n < endNodeIndex; n += 1) { 278 | var currentNode = nodes[n]; 279 | if (currentNode.externalNode) { 280 | nodes[n] = undefined; 281 | buildNodes[numBuildNodes] = currentNode; 282 | numBuildNodes += 1; 283 | } 284 | } 285 | if (buildNodes.length > numBuildNodes) { 286 | buildNodes.length = numBuildNodes; 287 | } 288 | } 289 | 290 | if (numBuildNodes > 1) { 291 | if (numBuildNodes > this.numNodesLeaf && this.numAdds > 0) { 292 | if (this.highQuality) { 293 | this.sortNodesHighQuality(buildNodes); 294 | } else { 295 | this.sortNodes(buildNodes); 296 | } 297 | } 298 | 299 | this.recursiveBuild(buildNodes, 0, numBuildNodes, 0); 300 | 301 | endNodeIndex = nodes[0].escapeNodeOffset; 302 | if (nodes.length > endNodeIndex) { 303 | nodes.length = endNodeIndex; 304 | } 305 | this.endNode = endNodeIndex; 306 | } else { 307 | var rootNode = buildNodes[0]; 308 | rootNode.externalNode.boxTreeIndex = 0; 309 | nodes.length = 1; 310 | nodes[0] = rootNode; 311 | this.endNode = 1; 312 | } 313 | buildNodes = null; 314 | } 315 | 316 | this.needsRebuild = false; 317 | this.needsRebound = false; 318 | this.numAdds = 0; 319 | this.numUpdates = 0; 320 | this.startUpdate = 0x7FFFFFFF; 321 | this.endUpdate = -0x7FFFFFFF; 322 | }; 323 | 324 | BoxTree.prototype.sortNodes = function (nodes) { 325 | var numNodesLeaf = this.numNodesLeaf; 326 | var numNodes = nodes.length; 327 | 328 | function getkeyXfn(node) { 329 | return (node.minX + node.maxX); 330 | } 331 | 332 | function getkeyYfn(node) { 333 | return (node.minY + node.maxY); 334 | } 335 | 336 | function getreversekeyXfn(node) { 337 | return -(node.minX + node.maxX); 338 | } 339 | 340 | function getreversekeyYfn(node) { 341 | return -(node.minY + node.maxY); 342 | } 343 | 344 | var nthElement = this.nthElement; 345 | var reverse = false; 346 | 347 | function sortNodesRecursive(nodes, startIndex, endIndex, sortByX) { 348 | /* tslint:disable:no-bitwise */ 349 | var splitNodeIndex = ((startIndex + endIndex) >> 1); 350 | 351 | /* tslint:enable:no-bitwise */ 352 | if (sortByX) { 353 | if (reverse) { 354 | nthElement(nodes, startIndex, splitNodeIndex, endIndex, getreversekeyXfn); 355 | } else { 356 | nthElement(nodes, startIndex, splitNodeIndex, endIndex, getkeyXfn); 357 | } 358 | } else { 359 | if (reverse) { 360 | nthElement(nodes, startIndex, splitNodeIndex, endIndex, getreversekeyYfn); 361 | } else { 362 | nthElement(nodes, startIndex, splitNodeIndex, endIndex, getkeyYfn); 363 | } 364 | } 365 | 366 | reverse = !reverse; 367 | 368 | if ((startIndex + numNodesLeaf) < splitNodeIndex) { 369 | sortNodesRecursive(nodes, startIndex, splitNodeIndex, !sortByX); 370 | } 371 | 372 | if ((splitNodeIndex + numNodesLeaf) < endIndex) { 373 | sortNodesRecursive(nodes, splitNodeIndex, endIndex, !sortByX); 374 | } 375 | } 376 | 377 | sortNodesRecursive(nodes, 0, numNodes, true); 378 | }; 379 | 380 | BoxTree.prototype.sortNodesHighQuality = function (nodes) { 381 | var numNodesLeaf = this.numNodesLeaf; 382 | var numNodes = nodes.length; 383 | 384 | function getkeyXfn(node) { 385 | return (node.minX + node.maxX); 386 | } 387 | 388 | function getkeyYfn(node) { 389 | return (node.minY + node.maxY); 390 | } 391 | 392 | function getkeyXYfn(node) { 393 | return (node.minX + node.minY + node.maxX + node.maxY); 394 | } 395 | 396 | function getkeyYXfn(node) { 397 | return (node.minX - node.minY + node.maxX - node.maxY); 398 | } 399 | 400 | function getreversekeyXfn(node) { 401 | return -(node.minX + node.maxX); 402 | } 403 | 404 | function getreversekeyYfn(node) { 405 | return -(node.minY + node.maxY); 406 | } 407 | 408 | function getreversekeyXYfn(node) { 409 | return -(node.minX + node.minY + node.maxX + node.maxY); 410 | } 411 | 412 | function getreversekeyYXfn(node) { 413 | return -(node.minX - node.minY + node.maxX - node.maxY); 414 | } 415 | 416 | var nthElement = this.nthElement; 417 | var calculateSAH = this.calculateSAH; 418 | var reverse = false; 419 | 420 | function sortNodesHighQualityRecursive(nodes, startIndex, endIndex) { 421 | /* tslint:disable:no-bitwise */ 422 | var splitNodeIndex = ((startIndex + endIndex) >> 1); 423 | 424 | /* tslint:enable:no-bitwise */ 425 | nthElement(nodes, startIndex, splitNodeIndex, endIndex, getkeyXfn); 426 | var sahX = (calculateSAH(nodes, startIndex, splitNodeIndex) + calculateSAH(nodes, splitNodeIndex, endIndex)); 427 | 428 | nthElement(nodes, startIndex, splitNodeIndex, endIndex, getkeyYfn); 429 | var sahY = (calculateSAH(nodes, startIndex, splitNodeIndex) + calculateSAH(nodes, splitNodeIndex, endIndex)); 430 | 431 | nthElement(nodes, startIndex, splitNodeIndex, endIndex, getkeyXYfn); 432 | var sahXY = (calculateSAH(nodes, startIndex, splitNodeIndex) + calculateSAH(nodes, splitNodeIndex, endIndex)); 433 | 434 | nthElement(nodes, startIndex, splitNodeIndex, endIndex, getkeyYXfn); 435 | var sahYX = (calculateSAH(nodes, startIndex, splitNodeIndex) + calculateSAH(nodes, splitNodeIndex, endIndex)); 436 | 437 | if (sahX <= sahY && sahX <= sahXY && sahX <= sahYX) { 438 | if (reverse) { 439 | nthElement(nodes, startIndex, splitNodeIndex, endIndex, getreversekeyXfn); 440 | } else { 441 | nthElement(nodes, startIndex, splitNodeIndex, endIndex, getkeyXfn); 442 | } 443 | } else if (sahY <= sahXY && sahY <= sahYX) { 444 | if (reverse) { 445 | nthElement(nodes, startIndex, splitNodeIndex, endIndex, getreversekeyYfn); 446 | } else { 447 | nthElement(nodes, startIndex, splitNodeIndex, endIndex, getkeyYfn); 448 | } 449 | } else if (sahXY <= sahYX) { 450 | if (reverse) { 451 | nthElement(nodes, startIndex, splitNodeIndex, endIndex, getreversekeyXYfn); 452 | } else { 453 | nthElement(nodes, startIndex, splitNodeIndex, endIndex, getkeyXYfn); 454 | } 455 | } else { 456 | if (reverse) { 457 | nthElement(nodes, startIndex, splitNodeIndex, endIndex, getreversekeyYXfn); 458 | } else { 459 | nthElement(nodes, startIndex, splitNodeIndex, endIndex, getkeyYXfn); 460 | } 461 | } 462 | 463 | reverse = !reverse; 464 | 465 | if ((startIndex + numNodesLeaf) < splitNodeIndex) { 466 | sortNodesHighQualityRecursive(nodes, startIndex, splitNodeIndex); 467 | } 468 | 469 | if ((splitNodeIndex + numNodesLeaf) < endIndex) { 470 | sortNodesHighQualityRecursive(nodes, splitNodeIndex, endIndex); 471 | } 472 | } 473 | 474 | sortNodesHighQualityRecursive(nodes, 0, numNodes); 475 | }; 476 | 477 | BoxTree.prototype.calculateSAH = function (buildNodes, startIndex, endIndex) { 478 | var buildNode, minX, minY, maxX, maxY; 479 | 480 | buildNode = buildNodes[startIndex]; 481 | minX = buildNode.minX; 482 | minY = buildNode.minY; 483 | maxX = buildNode.maxX; 484 | maxY = buildNode.maxY; 485 | 486 | for (var n = (startIndex + 1); n < endIndex; n += 1) { 487 | buildNode = buildNodes[n]; 488 | if (minX > buildNode.minX) { 489 | minX = buildNode.minX; 490 | } 491 | if (minY > buildNode.minY) { 492 | minY = buildNode.minY; 493 | } 494 | if (maxX < buildNode.maxX) { 495 | maxX = buildNode.maxX; 496 | } 497 | if (maxY < buildNode.maxY) { 498 | maxY = buildNode.maxY; 499 | } 500 | } 501 | 502 | return ((maxX - minX) + (maxY - minY)); 503 | }; 504 | 505 | BoxTree.prototype.nthElement = function (nodes, first, nth, last, getkey) { 506 | function medianFn(a, b, c) { 507 | if (a < b) { 508 | if (b < c) { 509 | return b; 510 | } else if (a < c) { 511 | return c; 512 | } else { 513 | return a; 514 | } 515 | } else if (a < c) { 516 | return a; 517 | } else if (b < c) { 518 | return c; 519 | } 520 | return b; 521 | } 522 | 523 | function insertionSort(nodes, first, last, getkey) { 524 | var sorted = (first + 1); 525 | while (sorted !== last) { 526 | var tempNode = nodes[sorted]; 527 | var tempKey = getkey(tempNode); 528 | 529 | var next = sorted; 530 | var current = (sorted - 1); 531 | 532 | while (next !== first && tempKey < getkey(nodes[current])) { 533 | nodes[next] = nodes[current]; 534 | next -= 1; 535 | current -= 1; 536 | } 537 | 538 | if (next !== sorted) { 539 | nodes[next] = tempNode; 540 | } 541 | 542 | sorted += 1; 543 | } 544 | } 545 | 546 | while ((last - first) > 8) { 547 | /* tslint:disable:no-bitwise */ 548 | var midValue = medianFn(getkey(nodes[first]), getkey(nodes[first + ((last - first) >> 1)]), getkey(nodes[last - 1])); 549 | 550 | /* tslint:enable:no-bitwise */ 551 | var firstPos = first; 552 | var lastPos = last; 553 | var midPos; 554 | for (; ; firstPos += 1) { 555 | while (getkey(nodes[firstPos]) < midValue) { 556 | firstPos += 1; 557 | } 558 | 559 | do { 560 | lastPos -= 1; 561 | } while(midValue < getkey(nodes[lastPos])); 562 | 563 | if (firstPos >= lastPos) { 564 | midPos = firstPos; 565 | break; 566 | } else { 567 | var temp = nodes[firstPos]; 568 | nodes[firstPos] = nodes[lastPos]; 569 | nodes[lastPos] = temp; 570 | } 571 | } 572 | 573 | if (midPos <= nth) { 574 | first = midPos; 575 | } else { 576 | last = midPos; 577 | } 578 | } 579 | 580 | insertionSort(nodes, first, last, getkey); 581 | }; 582 | 583 | BoxTree.prototype.recursiveBuild = function (buildNodes, startIndex, endIndex, lastNodeIndex) { 584 | var nodes = this.nodes; 585 | var nodeIndex = lastNodeIndex; 586 | lastNodeIndex += 1; 587 | 588 | var minX, minY, maxX, maxY; 589 | var buildNode, lastNode; 590 | 591 | if ((startIndex + this.numNodesLeaf) >= endIndex) { 592 | buildNode = buildNodes[startIndex]; 593 | minX = buildNode.minX; 594 | minY = buildNode.minY; 595 | maxX = buildNode.maxX; 596 | maxY = buildNode.maxY; 597 | 598 | buildNode.externalNode.boxTreeIndex = lastNodeIndex; 599 | nodes[lastNodeIndex] = buildNode; 600 | 601 | for (var n = (startIndex + 1); n < endIndex; n += 1) { 602 | buildNode = buildNodes[n]; 603 | if (minX > buildNode.minX) { 604 | minX = buildNode.minX; 605 | } 606 | if (minY > buildNode.minY) { 607 | minY = buildNode.minY; 608 | } 609 | if (maxX < buildNode.maxX) { 610 | maxX = buildNode.maxX; 611 | } 612 | if (maxY < buildNode.maxY) { 613 | maxY = buildNode.maxY; 614 | } 615 | lastNodeIndex += 1; 616 | buildNode.externalNode.boxTreeIndex = lastNodeIndex; 617 | nodes[lastNodeIndex] = buildNode; 618 | } 619 | 620 | lastNode = nodes[lastNodeIndex]; 621 | } else { 622 | /* tslint:disable:no-bitwise */ 623 | var splitPosIndex = ((startIndex + endIndex) >> 1); 624 | 625 | /* tslint:enable:no-bitwise */ 626 | if ((startIndex + 1) >= splitPosIndex) { 627 | buildNode = buildNodes[startIndex]; 628 | buildNode.externalNode.boxTreeIndex = lastNodeIndex; 629 | nodes[lastNodeIndex] = buildNode; 630 | } else { 631 | this.recursiveBuild(buildNodes, startIndex, splitPosIndex, lastNodeIndex); 632 | } 633 | 634 | lastNode = nodes[lastNodeIndex]; 635 | minX = lastNode.minX; 636 | minY = lastNode.minY; 637 | maxX = lastNode.maxX; 638 | maxY = lastNode.maxY; 639 | 640 | lastNodeIndex = (lastNodeIndex + lastNode.escapeNodeOffset); 641 | 642 | if ((splitPosIndex + 1) >= endIndex) { 643 | buildNode = buildNodes[splitPosIndex]; 644 | buildNode.externalNode.boxTreeIndex = lastNodeIndex; 645 | nodes[lastNodeIndex] = buildNode; 646 | } else { 647 | this.recursiveBuild(buildNodes, splitPosIndex, endIndex, lastNodeIndex); 648 | } 649 | 650 | lastNode = nodes[lastNodeIndex]; 651 | if (minX > lastNode.minX) { 652 | minX = lastNode.minX; 653 | } 654 | if (minY > lastNode.minY) { 655 | minY = lastNode.minY; 656 | } 657 | if (maxX < lastNode.maxX) { 658 | maxX = lastNode.maxX; 659 | } 660 | if (maxY < lastNode.maxY) { 661 | maxY = lastNode.maxY; 662 | } 663 | } 664 | 665 | var node = nodes[nodeIndex]; 666 | if (node !== undefined) { 667 | node.reset(minX, minY, maxX, maxY, (lastNodeIndex + lastNode.escapeNodeOffset - nodeIndex)); 668 | } else { 669 | nodes[nodeIndex] = BoxTreeNode.create(minX, minY, maxX, maxY, (lastNodeIndex + lastNode.escapeNodeOffset - nodeIndex), undefined); 670 | } 671 | }; 672 | 673 | BoxTree.prototype.getVisibleNodes = function (planes, visibleNodes) { 674 | if (this.numExternalNodes > 0) { 675 | var nodes = this.nodes; 676 | var endNodeIndex = this.endNode; 677 | var numPlanes = planes.length; 678 | var numVisibleNodes = visibleNodes.length; 679 | var node, endChildren; 680 | var n0, n1, p0, p1; 681 | var isInside, n, plane, d0, d1; 682 | var nodeIndex = 0; 683 | 684 | for (; ;) { 685 | node = nodes[nodeIndex]; 686 | n0 = node.minX; 687 | n1 = node.minY; 688 | p0 = node.maxX; 689 | p1 = node.maxY; 690 | 691 | //isInsidePlanesBox 692 | isInside = true; 693 | n = 0; 694 | do { 695 | plane = planes[n]; 696 | d0 = plane[0]; 697 | d1 = plane[1]; 698 | if ((d0 * (d0 < 0 ? n0 : p0) + d1 * (d1 < 0 ? n1 : p1)) < plane[2]) { 699 | isInside = false; 700 | break; 701 | } 702 | n += 1; 703 | } while(n < numPlanes); 704 | if (isInside) { 705 | if (node.externalNode) { 706 | visibleNodes[numVisibleNodes] = node.externalNode; 707 | numVisibleNodes += 1; 708 | nodeIndex += 1; 709 | if (nodeIndex >= endNodeIndex) { 710 | break; 711 | } 712 | } else { 713 | //isFullyInsidePlanesBox 714 | isInside = true; 715 | n = 0; 716 | do { 717 | plane = planes[n]; 718 | d0 = plane[0]; 719 | d1 = plane[1]; 720 | if ((d0 * (d0 > 0 ? n0 : p0) + d1 * (d1 > 0 ? n1 : p1)) < plane[2]) { 721 | isInside = false; 722 | break; 723 | } 724 | n += 1; 725 | } while(n < numPlanes); 726 | if (isInside) { 727 | endChildren = (nodeIndex + node.escapeNodeOffset); 728 | nodeIndex += 1; 729 | do { 730 | node = nodes[nodeIndex]; 731 | if (node.externalNode) { 732 | visibleNodes[numVisibleNodes] = node.externalNode; 733 | numVisibleNodes += 1; 734 | } 735 | nodeIndex += 1; 736 | } while(nodeIndex < endChildren); 737 | if (nodeIndex >= endNodeIndex) { 738 | break; 739 | } 740 | } else { 741 | nodeIndex += 1; 742 | } 743 | } 744 | } else { 745 | nodeIndex += node.escapeNodeOffset; 746 | if (nodeIndex >= endNodeIndex) { 747 | break; 748 | } 749 | } 750 | } 751 | } 752 | }; 753 | 754 | BoxTree.prototype.getOverlappingNodes = function (queryExtents, overlappingNodes, startIndex) { 755 | if (this.numExternalNodes > 0) { 756 | var queryMinX = queryExtents[0]; 757 | var queryMinY = queryExtents[1]; 758 | var queryMaxX = queryExtents[2]; 759 | var queryMaxY = queryExtents[3]; 760 | var nodes = this.nodes; 761 | var endNodeIndex = this.endNode; 762 | var node, endChildren; 763 | var numOverlappingNodes = 0; 764 | var storageIndex = (startIndex === undefined) ? overlappingNodes.length : startIndex; 765 | var nodeIndex = 0; 766 | for (; ;) { 767 | node = nodes[nodeIndex]; 768 | var minX = node.minX; 769 | var minY = node.minY; 770 | var maxX = node.maxX; 771 | var maxY = node.maxY; 772 | if (queryMinX <= maxX && queryMinY <= maxY && queryMaxX >= minX && queryMaxY >= minY) { 773 | if (node.externalNode) { 774 | overlappingNodes[storageIndex] = node.externalNode; 775 | storageIndex += 1; 776 | numOverlappingNodes += 1; 777 | nodeIndex += 1; 778 | if (nodeIndex >= endNodeIndex) { 779 | break; 780 | } 781 | } else { 782 | if (queryMaxX >= maxX && queryMaxY >= maxY && queryMinX <= minX && queryMinY <= minY) { 783 | endChildren = (nodeIndex + node.escapeNodeOffset); 784 | nodeIndex += 1; 785 | do { 786 | node = nodes[nodeIndex]; 787 | if (node.externalNode) { 788 | overlappingNodes[storageIndex] = node.externalNode; 789 | storageIndex += 1; 790 | numOverlappingNodes += 1; 791 | } 792 | nodeIndex += 1; 793 | } while(nodeIndex < endChildren); 794 | if (nodeIndex >= endNodeIndex) { 795 | break; 796 | } 797 | } else { 798 | nodeIndex += 1; 799 | } 800 | } 801 | } else { 802 | nodeIndex += node.escapeNodeOffset; 803 | if (nodeIndex >= endNodeIndex) { 804 | break; 805 | } 806 | } 807 | } 808 | return numOverlappingNodes; 809 | } else { 810 | return 0; 811 | } 812 | }; 813 | 814 | BoxTree.prototype.getCircleOverlappingNodes = function (center, radius, overlappingNodes) { 815 | if (this.numExternalNodes > 0) { 816 | var radiusSquared = (radius * radius); 817 | var centerX = center[0]; 818 | var centerY = center[1]; 819 | var nodes = this.nodes; 820 | var endNodeIndex = this.endNode; 821 | var node; 822 | var numOverlappingNodes = overlappingNodes.length; 823 | var nodeIndex = 0; 824 | for (; ;) { 825 | node = nodes[nodeIndex]; 826 | var minX = node.minX; 827 | var minY = node.minY; 828 | var maxX = node.maxX; 829 | var maxY = node.maxY; 830 | var totalDistance = 0, sideDistance; 831 | if (centerX < minX) { 832 | sideDistance = (minX - centerX); 833 | totalDistance += (sideDistance * sideDistance); 834 | } else if (centerX > maxX) { 835 | sideDistance = (centerX - maxX); 836 | totalDistance += (sideDistance * sideDistance); 837 | } 838 | if (centerY < minY) { 839 | sideDistance = (minY - centerY); 840 | totalDistance += (sideDistance * sideDistance); 841 | } else if (centerY > maxY) { 842 | sideDistance = (centerY - maxY); 843 | totalDistance += (sideDistance * sideDistance); 844 | } 845 | if (totalDistance <= radiusSquared) { 846 | nodeIndex += 1; 847 | if (node.externalNode) { 848 | overlappingNodes[numOverlappingNodes] = node.externalNode; 849 | numOverlappingNodes += 1; 850 | if (nodeIndex >= endNodeIndex) { 851 | break; 852 | } 853 | } 854 | } else { 855 | nodeIndex += node.escapeNodeOffset; 856 | if (nodeIndex >= endNodeIndex) { 857 | break; 858 | } 859 | } 860 | } 861 | } 862 | }; 863 | 864 | BoxTree.prototype.getOverlappingPairs = function (overlappingPairs, startIndex) { 865 | if (this.numExternalNodes > 0) { 866 | var nodes = this.nodes; 867 | var endNodeIndex = this.endNode; 868 | var currentNode, currentExternalNode, node; 869 | var numInsertions = 0; 870 | var storageIndex = (startIndex === undefined) ? overlappingPairs.length : startIndex; 871 | var currentNodeIndex = 0, nodeIndex; 872 | for (; ;) { 873 | currentNode = nodes[currentNodeIndex]; 874 | while (!currentNode.externalNode) { 875 | currentNodeIndex += 1; 876 | currentNode = nodes[currentNodeIndex]; 877 | } 878 | 879 | currentNodeIndex += 1; 880 | if (currentNodeIndex < endNodeIndex) { 881 | currentExternalNode = currentNode.externalNode; 882 | var minX = currentNode.minX; 883 | var minY = currentNode.minY; 884 | var maxX = currentNode.maxX; 885 | var maxY = currentNode.maxY; 886 | 887 | nodeIndex = currentNodeIndex; 888 | for (; ;) { 889 | node = nodes[nodeIndex]; 890 | if (minX <= node.maxX && minY <= node.maxY && maxX >= node.minX && maxY >= node.minY) { 891 | nodeIndex += 1; 892 | if (node.externalNode) { 893 | overlappingPairs[storageIndex] = currentExternalNode; 894 | overlappingPairs[storageIndex + 1] = node.externalNode; 895 | storageIndex += 2; 896 | numInsertions += 2; 897 | if (nodeIndex >= endNodeIndex) { 898 | break; 899 | } 900 | } 901 | } else { 902 | nodeIndex += node.escapeNodeOffset; 903 | if (nodeIndex >= endNodeIndex) { 904 | break; 905 | } 906 | } 907 | } 908 | } else { 909 | break; 910 | } 911 | } 912 | return numInsertions; 913 | } else { 914 | return 0; 915 | } 916 | }; 917 | 918 | BoxTree.prototype.getRootNode = function () { 919 | return this.nodes[0]; 920 | }; 921 | 922 | BoxTree.prototype.getNodes = function () { 923 | return this.nodes; 924 | }; 925 | 926 | BoxTree.prototype.getEndNodeIndex = function () { 927 | return this.endNode; 928 | }; 929 | 930 | BoxTree.prototype.clear = function () { 931 | this.nodes = []; 932 | this.endNode = 0; 933 | this.needsRebuild = false; 934 | this.needsRebound = false; 935 | this.numAdds = 0; 936 | this.numUpdates = 0; 937 | this.numExternalNodes = 0; 938 | this.startUpdate = 0x7FFFFFFF; 939 | this.endUpdate = -0x7FFFFFFF; 940 | }; 941 | 942 | BoxTree.rayTest = function (trees, ray, callback) { 943 | // convert ray to parametric form 944 | var origin = ray.origin; 945 | var direction = ray.direction; 946 | 947 | // values used throughout calculations. 948 | var o0 = origin[0]; 949 | var o1 = origin[1]; 950 | var d0 = direction[0]; 951 | var d1 = direction[1]; 952 | var id0 = 1 / d0; 953 | var id1 = 1 / d1; 954 | 955 | // evaluate distance factor to a node's extents from ray origin, along direction 956 | // use this to induce an ordering on which nodes to check. 957 | function distanceExtents(min0, min1, max0, max1, upperBound) { 958 | // treat origin internal to extents as 0 distance. 959 | if (min0 <= o0 && o0 <= max0 && min1 <= o1 && o1 <= max1) { 960 | return 0.0; 961 | } 962 | 963 | var tmin, tmax; 964 | var tymin, tymax; 965 | var del; 966 | if (d0 >= 0) { 967 | // Deal with cases where d0 == 0 968 | del = (min0 - o0); 969 | tmin = ((del === 0) ? 0 : (del * id0)); 970 | del = (max0 - o0); 971 | tmax = ((del === 0) ? 0 : (del * id0)); 972 | } else { 973 | tmin = ((max0 - o0) * id0); 974 | tmax = ((min0 - o0) * id0); 975 | } 976 | 977 | if (d1 >= 0) { 978 | // Deal with cases where d1 == 0 979 | del = (min1 - o1); 980 | tymin = ((del === 0) ? 0 : (del * id1)); 981 | del = (max1 - o1); 982 | tymax = ((del === 0) ? 0 : (del * id1)); 983 | } else { 984 | tymin = ((max1 - o1) * id1); 985 | tymax = ((min1 - o1) * id1); 986 | } 987 | 988 | if ((tmin > tymax) || (tymin > tmax)) { 989 | return undefined; 990 | } 991 | 992 | if (tymin > tmin) { 993 | tmin = tymin; 994 | } 995 | 996 | if (tymax < tmax) { 997 | tmax = tymax; 998 | } 999 | 1000 | if (tmin < 0) { 1001 | tmin = tmax; 1002 | } 1003 | 1004 | return (0 <= tmin && tmin < upperBound) ? tmin : undefined; 1005 | } 1006 | 1007 | // we traverse both trees at once 1008 | // keeping a priority list of nodes to check next. 1009 | // TODO: possibly implement priority list more effeciently? 1010 | // binary heap probably too much overhead in typical case. 1011 | var priorityList = []; 1012 | 1013 | //current upperBound on distance to first intersection 1014 | //and current closest object properties 1015 | var minimumResult = null; 1016 | 1017 | //if node is a leaf, intersect ray with shape 1018 | // otherwise insert node into priority list. 1019 | function processNode(tree, nodeIndex, upperBound) { 1020 | var nodes = tree.getNodes(); 1021 | var node = nodes[nodeIndex]; 1022 | var distance = distanceExtents(node.minX, node.minY, node.maxX, node.maxY, upperBound); 1023 | if (distance === undefined) { 1024 | return upperBound; 1025 | } 1026 | 1027 | if (node.externalNode) { 1028 | var result = callback(tree, node.externalNode, ray, distance, upperBound); 1029 | if (result) { 1030 | minimumResult = result; 1031 | upperBound = result.factor; 1032 | } 1033 | } else { 1034 | // TODO: change to binary search? 1035 | var length = priorityList.length; 1036 | var i; 1037 | for (i = 0; i < length; i += 1) { 1038 | var curObj = priorityList[i]; 1039 | if (distance > curObj.distance) { 1040 | break; 1041 | } 1042 | } 1043 | 1044 | //insert node at index i 1045 | priorityList.splice(i - 1, 0, { 1046 | tree: tree, 1047 | nodeIndex: nodeIndex, 1048 | distance: distance 1049 | }); 1050 | } 1051 | 1052 | return upperBound; 1053 | } 1054 | 1055 | var upperBound = ray.maxFactor; 1056 | 1057 | var tree; 1058 | var i; 1059 | for (i = 0; i < trees.length; i += 1) { 1060 | tree = trees[i]; 1061 | if (tree.endNode !== 0) { 1062 | upperBound = processNode(tree, 0, upperBound); 1063 | } 1064 | } 1065 | 1066 | while (priorityList.length !== 0) { 1067 | var nodeObj = priorityList.pop(); 1068 | 1069 | // A node inserted into priority list after this one may have 1070 | // moved the upper bound. 1071 | if (nodeObj.distance >= upperBound) { 1072 | continue; 1073 | } 1074 | 1075 | var nodeIndex = nodeObj.nodeIndex; 1076 | tree = nodeObj.tree; 1077 | var nodes = tree.getNodes(); 1078 | 1079 | var node = nodes[nodeIndex]; 1080 | var maxIndex = nodeIndex + node.escapeNodeOffset; 1081 | 1082 | var childIndex = nodeIndex + 1; 1083 | do { 1084 | upperBound = processNode(tree, childIndex, upperBound); 1085 | childIndex += nodes[childIndex].escapeNodeOffset; 1086 | } while(childIndex < maxIndex); 1087 | } 1088 | 1089 | return minimumResult; 1090 | }; 1091 | 1092 | // Constructor function 1093 | BoxTree.create = function (highQuality) { 1094 | return new BoxTree(highQuality); 1095 | }; 1096 | BoxTree.version = 1; 1097 | 1098 | module.exports = BoxTree; 1099 | 1100 | })(); 1101 | -------------------------------------------------------------------------------- /algorithms/aabbtree/aabbtree.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2009-2015 Turbulenz Limited 2 | 3 | (function() 4 | { 5 | "use strict"; 6 | 7 | // 8 | // AABBTreeNode 9 | // 10 | function AABBTreeNode(extents, escapeNodeOffset, externalNode) { 11 | this.escapeNodeOffset = escapeNodeOffset; 12 | this.externalNode = externalNode; 13 | this.extents = extents; 14 | 15 | return this; 16 | } 17 | AABBTreeNode.prototype.isLeaf = function () { 18 | return !!this.externalNode; 19 | }; 20 | 21 | AABBTreeNode.prototype.reset = function (minX, minY, minZ, maxX, maxY, maxZ, escapeNodeOffset, externalNode) { 22 | this.escapeNodeOffset = escapeNodeOffset; 23 | this.externalNode = externalNode; 24 | var oldExtents = this.extents; 25 | oldExtents[0] = minX; 26 | oldExtents[1] = minY; 27 | oldExtents[2] = minZ; 28 | oldExtents[3] = maxX; 29 | oldExtents[4] = maxY; 30 | oldExtents[5] = maxZ; 31 | }; 32 | 33 | AABBTreeNode.prototype.clear = function () { 34 | this.escapeNodeOffset = 1; 35 | this.externalNode = undefined; 36 | var oldExtents = this.extents; 37 | var maxNumber = Number.MAX_VALUE; 38 | oldExtents[0] = maxNumber; 39 | oldExtents[1] = maxNumber; 40 | oldExtents[2] = maxNumber; 41 | oldExtents[3] = -maxNumber; 42 | oldExtents[4] = -maxNumber; 43 | oldExtents[5] = -maxNumber; 44 | }; 45 | 46 | // Constructor function 47 | AABBTreeNode.create = function (extents, escapeNodeOffset, externalNode) { 48 | return new AABBTreeNode(extents, escapeNodeOffset, externalNode); 49 | }; 50 | AABBTreeNode.version = 1; 51 | 52 | // 53 | // AABBTree 54 | // 55 | function AABBTree(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 | this.ignoreY = false; 68 | this.nodesStack = new Array(32); 69 | } 70 | AABBTree.allocateNode = function () { 71 | var nodesPool = this.nodesPool; 72 | if (!nodesPool.length) { 73 | // Allocate a bunch of nodes in one go 74 | var nodesPoolAllocationSize = this.nodesPoolAllocationSize; 75 | var n, extents; 76 | for (n = 0; n < nodesPoolAllocationSize; n += 1) { 77 | extents = [0, 0, 0, 0, 0, 0]; 78 | nodesPool[n] = AABBTreeNode.create(extents, 1, undefined); 79 | } 80 | } 81 | return nodesPool.pop(); 82 | }; 83 | 84 | AABBTree.releaseNode = function (node) { 85 | var nodesPool = this.nodesPool; 86 | if (nodesPool.length < this.nodesPoolAllocationSize) { 87 | node.clear(); 88 | nodesPool.push(node); 89 | } 90 | }; 91 | 92 | AABBTree.recycleNodes = function (nodes, start) { 93 | var numNodes = nodes.length; 94 | var n; 95 | for (n = start; n < numNodes; n += 1) { 96 | var node = nodes[n]; 97 | if (node) { 98 | this.releaseNode(node); 99 | } 100 | } 101 | nodes.length = start; 102 | }; 103 | 104 | AABBTree.prototype.add = function (externalNode, extents) { 105 | var endNode = this.endNode; 106 | externalNode.spatialIndex = endNode; 107 | 108 | var node = AABBTree.allocateNode(); 109 | node.escapeNodeOffset = 1; 110 | node.externalNode = externalNode; 111 | var copyExtents = node.extents; 112 | copyExtents[0] = extents[0]; 113 | copyExtents[1] = extents[1]; 114 | copyExtents[2] = extents[2]; 115 | copyExtents[3] = extents[3]; 116 | copyExtents[4] = extents[4]; 117 | copyExtents[5] = extents[5]; 118 | 119 | this.nodes[endNode] = node; 120 | this.endNode = (endNode + 1); 121 | this.needsRebuild = true; 122 | this.numAdds += 1; 123 | this.numExternalNodes += 1; 124 | }; 125 | 126 | AABBTree.prototype.remove = function (externalNode) { 127 | var index = externalNode.spatialIndex; 128 | if (index !== undefined) { 129 | if (this.numExternalNodes > 1) { 130 | var nodes = this.nodes; 131 | debug.assert(nodes[index].externalNode === externalNode); 132 | 133 | nodes[index].clear(); 134 | 135 | var endNode = this.endNode; 136 | if ((index + 1) >= endNode) { 137 | while (!nodes[endNode - 1].externalNode) { 138 | endNode -= 1; 139 | } 140 | this.endNode = endNode; 141 | } else { 142 | this.needsRebuild = true; 143 | } 144 | this.numExternalNodes -= 1; 145 | } else { 146 | this.clear(); 147 | } 148 | 149 | externalNode.spatialIndex = undefined; 150 | } 151 | }; 152 | 153 | AABBTree.prototype.findParent = function (nodeIndex) { 154 | var nodes = this.nodes; 155 | var parentIndex = nodeIndex; 156 | var nodeDist = 0; 157 | var parent; 158 | do { 159 | parentIndex -= 1; 160 | nodeDist += 1; 161 | parent = nodes[parentIndex]; 162 | } while(parent.escapeNodeOffset <= nodeDist); 163 | return parent; 164 | }; 165 | 166 | AABBTree.prototype.update = function (externalNode, extents) { 167 | var index = externalNode.spatialIndex; 168 | if (index !== undefined) { 169 | var min0 = extents[0]; 170 | var min1 = extents[1]; 171 | var min2 = extents[2]; 172 | var max0 = extents[3]; 173 | var max1 = extents[4]; 174 | var max2 = extents[5]; 175 | 176 | var needsRebuild = this.needsRebuild; 177 | var needsRebound = this.needsRebound; 178 | var nodes = this.nodes; 179 | var node = nodes[index]; 180 | debug.assert(node.externalNode === externalNode); 181 | var nodeExtents = node.extents; 182 | 183 | var doUpdate = (needsRebuild || needsRebound || nodeExtents[0] > min0 || nodeExtents[1] > min1 || nodeExtents[2] > min2 || nodeExtents[3] < max0 || nodeExtents[4] < max1 || nodeExtents[5] < max2); 184 | 185 | nodeExtents[0] = min0; 186 | nodeExtents[1] = min1; 187 | nodeExtents[2] = min2; 188 | nodeExtents[3] = max0; 189 | nodeExtents[4] = max1; 190 | nodeExtents[5] = max2; 191 | 192 | if (doUpdate) { 193 | if (!needsRebuild && 1 < nodes.length) { 194 | this.numUpdates += 1; 195 | if (this.startUpdate > index) { 196 | this.startUpdate = index; 197 | } 198 | if (this.endUpdate < index) { 199 | this.endUpdate = index; 200 | } 201 | if (!needsRebound) { 202 | // force a rebound when things change too much 203 | if ((2 * this.numUpdates) > this.numExternalNodes) { 204 | this.needsRebound = true; 205 | } else { 206 | var parent = this.findParent(index); 207 | var parentExtents = parent.extents; 208 | if (parentExtents[0] > min0 || parentExtents[1] > min1 || parentExtents[2] > min2 || parentExtents[3] < max0 || parentExtents[4] < max1 || parentExtents[5] < max2) { 209 | this.needsRebound = true; 210 | } 211 | } 212 | } else { 213 | // force a rebuild when things change too much 214 | if (this.numUpdates > (3 * this.numExternalNodes)) { 215 | this.needsRebuild = true; 216 | this.numAdds = this.numUpdates; 217 | } 218 | } 219 | } 220 | } 221 | } else { 222 | this.add(externalNode, extents); 223 | } 224 | }; 225 | 226 | AABBTree.prototype.needsFinalize = function () { 227 | return (this.needsRebuild || this.needsRebound); 228 | }; 229 | 230 | AABBTree.prototype.finalize = function () { 231 | if (this.needsRebuild) { 232 | this.rebuild(); 233 | } else if (this.needsRebound) { 234 | this.rebound(); 235 | } 236 | }; 237 | 238 | AABBTree.prototype.rebound = function () { 239 | var nodes = this.nodes; 240 | if (nodes.length > 1) { 241 | var startUpdateNodeIndex = this.startUpdate; 242 | var endUpdateNodeIndex = this.endUpdate; 243 | 244 | var nodesStack = this.nodesStack; 245 | var numNodesStack = 0; 246 | var topNodeIndex = 0; 247 | for (; ;) { 248 | var topNode = nodes[topNodeIndex]; 249 | var currentNodeIndex = topNodeIndex; 250 | var currentEscapeNodeIndex = (topNodeIndex + topNode.escapeNodeOffset); 251 | var nodeIndex = (topNodeIndex + 1); 252 | var node; 253 | do { 254 | node = nodes[nodeIndex]; 255 | var escapeNodeIndex = (nodeIndex + node.escapeNodeOffset); 256 | if (nodeIndex < endUpdateNodeIndex) { 257 | if (!node.externalNode) { 258 | if (escapeNodeIndex > startUpdateNodeIndex) { 259 | nodesStack[numNodesStack] = topNodeIndex; 260 | numNodesStack += 1; 261 | topNodeIndex = nodeIndex; 262 | } 263 | } 264 | } else { 265 | break; 266 | } 267 | nodeIndex = escapeNodeIndex; 268 | } while(nodeIndex < currentEscapeNodeIndex); 269 | 270 | if (topNodeIndex === currentNodeIndex) { 271 | nodeIndex = (topNodeIndex + 1); // First child 272 | node = nodes[nodeIndex]; 273 | 274 | var extents = node.extents; 275 | var minX = extents[0]; 276 | var minY = extents[1]; 277 | var minZ = extents[2]; 278 | var maxX = extents[3]; 279 | var maxY = extents[4]; 280 | var maxZ = extents[5]; 281 | 282 | nodeIndex = (nodeIndex + node.escapeNodeOffset); 283 | while (nodeIndex < currentEscapeNodeIndex) { 284 | node = nodes[nodeIndex]; 285 | extents = node.extents; 286 | 287 | /*jshint white: false*/ 288 | if (minX > extents[0]) { 289 | minX = extents[0]; 290 | } 291 | if (minY > extents[1]) { 292 | minY = extents[1]; 293 | } 294 | if (minZ > extents[2]) { 295 | minZ = extents[2]; 296 | } 297 | if (maxX < extents[3]) { 298 | maxX = extents[3]; 299 | } 300 | if (maxY < extents[4]) { 301 | maxY = extents[4]; 302 | } 303 | if (maxZ < extents[5]) { 304 | maxZ = extents[5]; 305 | } 306 | 307 | /*jshint white: true*/ 308 | nodeIndex = (nodeIndex + node.escapeNodeOffset); 309 | } 310 | 311 | extents = topNode.extents; 312 | extents[0] = minX; 313 | extents[1] = minY; 314 | extents[2] = minZ; 315 | extents[3] = maxX; 316 | extents[4] = maxY; 317 | extents[5] = maxZ; 318 | 319 | endUpdateNodeIndex = topNodeIndex; 320 | 321 | if (0 < numNodesStack) { 322 | numNodesStack -= 1; 323 | topNodeIndex = nodesStack[numNodesStack]; 324 | } else { 325 | break; 326 | } 327 | } 328 | } 329 | } 330 | 331 | this.needsRebuild = false; 332 | this.needsRebound = false; 333 | this.numAdds = 0; 334 | 335 | //this.numUpdates = 0; 336 | this.startUpdate = 0x7FFFFFFF; 337 | this.endUpdate = -0x7FFFFFFF; 338 | }; 339 | 340 | AABBTree.prototype.rebuild = function () { 341 | if (this.numExternalNodes > 0) { 342 | var nodes = this.nodes; 343 | 344 | var n, buildNodes, numBuildNodes, endNodeIndex; 345 | 346 | if (this.numExternalNodes === nodes.length) { 347 | buildNodes = nodes; 348 | numBuildNodes = nodes.length; 349 | nodes = []; 350 | this.nodes = nodes; 351 | } else { 352 | buildNodes = []; 353 | buildNodes.length = this.numExternalNodes; 354 | numBuildNodes = 0; 355 | endNodeIndex = this.endNode; 356 | for (n = 0; n < endNodeIndex; n += 1) { 357 | var currentNode = nodes[n]; 358 | if (currentNode.externalNode) { 359 | nodes[n] = undefined; 360 | buildNodes[numBuildNodes] = currentNode; 361 | numBuildNodes += 1; 362 | } 363 | } 364 | if (buildNodes.length > numBuildNodes) { 365 | buildNodes.length = numBuildNodes; 366 | } 367 | } 368 | 369 | var rootNode; 370 | if (numBuildNodes > 1) { 371 | if (numBuildNodes > this.numNodesLeaf && this.numAdds > 0) { 372 | if (this.highQuality) { 373 | this._sortNodesHighQuality(buildNodes); 374 | } else if (this.ignoreY) { 375 | this._sortNodesNoY(buildNodes); 376 | } else { 377 | this._sortNodes(buildNodes); 378 | } 379 | } 380 | 381 | var predictedNumNodes = this._predictNumNodes(0, numBuildNodes, 0); 382 | if (nodes.length > predictedNumNodes) { 383 | AABBTree.recycleNodes(nodes, predictedNumNodes); 384 | } 385 | 386 | this._recursiveBuild(buildNodes, 0, numBuildNodes, 0); 387 | 388 | endNodeIndex = nodes[0].escapeNodeOffset; 389 | if (nodes.length > endNodeIndex) { 390 | AABBTree.recycleNodes(nodes, endNodeIndex); 391 | } 392 | this.endNode = endNodeIndex; 393 | 394 | // Check if we should take into account the Y coordinate 395 | rootNode = nodes[0]; 396 | var extents = rootNode.extents; 397 | var deltaX = (extents[3] - extents[0]); 398 | var deltaY = (extents[4] - extents[1]); 399 | var deltaZ = (extents[5] - extents[2]); 400 | this.ignoreY = ((4 * deltaY) < (deltaX <= deltaZ ? deltaX : deltaZ)); 401 | } else { 402 | rootNode = buildNodes[0]; 403 | rootNode.externalNode.spatialIndex = 0; 404 | nodes.length = 1; 405 | nodes[0] = rootNode; 406 | this.endNode = 1; 407 | } 408 | buildNodes = null; 409 | } 410 | 411 | this.needsRebuild = false; 412 | this.needsRebound = false; 413 | this.numAdds = 0; 414 | this.numUpdates = 0; 415 | this.startUpdate = 0x7FFFFFFF; 416 | this.endUpdate = -0x7FFFFFFF; 417 | }; 418 | 419 | AABBTree.prototype._sortNodes = function (nodes) { 420 | var numNodesLeaf = this.numNodesLeaf; 421 | var numNodes = nodes.length; 422 | 423 | function getkeyXfn(node) { 424 | var extents = node.extents; 425 | return (extents[0] + extents[3]); 426 | } 427 | 428 | function getkeyYfn(node) { 429 | var extents = node.extents; 430 | return (extents[1] + extents[4]); 431 | } 432 | 433 | function getkeyZfn(node) { 434 | var extents = node.extents; 435 | return (extents[2] + extents[5]); 436 | } 437 | 438 | function getreversekeyXfn(node) { 439 | var extents = node.extents; 440 | return -(extents[0] + extents[3]); 441 | } 442 | 443 | function getreversekeyYfn(node) { 444 | var extents = node.extents; 445 | return -(extents[1] + extents[4]); 446 | } 447 | 448 | function getreversekeyZfn(node) { 449 | var extents = node.extents; 450 | return -(extents[2] + extents[5]); 451 | } 452 | 453 | var nthElement = this._nthElement; 454 | var reverse = false; 455 | 456 | function sortNodesRecursive(nodes, startIndex, endIndex, axis) { 457 | /* tslint:disable:no-bitwise */ 458 | var splitNodeIndex = ((startIndex + endIndex) >> 1); 459 | 460 | /* tslint:enable:no-bitwise */ 461 | if (axis === 0) { 462 | if (reverse) { 463 | nthElement(nodes, startIndex, splitNodeIndex, endIndex, getreversekeyXfn); 464 | } else { 465 | nthElement(nodes, startIndex, splitNodeIndex, endIndex, getkeyXfn); 466 | } 467 | } else if (axis === 2) { 468 | if (reverse) { 469 | nthElement(nodes, startIndex, splitNodeIndex, endIndex, getreversekeyZfn); 470 | } else { 471 | nthElement(nodes, startIndex, splitNodeIndex, endIndex, getkeyZfn); 472 | } 473 | } else { 474 | if (reverse) { 475 | nthElement(nodes, startIndex, splitNodeIndex, endIndex, getreversekeyYfn); 476 | } else { 477 | nthElement(nodes, startIndex, splitNodeIndex, endIndex, getkeyYfn); 478 | } 479 | } 480 | 481 | if (axis === 0) { 482 | axis = 2; 483 | } else if (axis === 2) { 484 | axis = 1; 485 | } else { 486 | axis = 0; 487 | } 488 | 489 | reverse = !reverse; 490 | 491 | if ((startIndex + numNodesLeaf) < splitNodeIndex) { 492 | sortNodesRecursive(nodes, startIndex, splitNodeIndex, axis); 493 | } 494 | 495 | if ((splitNodeIndex + numNodesLeaf) < endIndex) { 496 | sortNodesRecursive(nodes, splitNodeIndex, endIndex, axis); 497 | } 498 | } 499 | 500 | sortNodesRecursive(nodes, 0, numNodes, 0); 501 | }; 502 | 503 | AABBTree.prototype._sortNodesNoY = function (nodes) { 504 | var numNodesLeaf = this.numNodesLeaf; 505 | var numNodes = nodes.length; 506 | 507 | function getkeyXfn(node) { 508 | var extents = node.extents; 509 | return (extents[0] + extents[3]); 510 | } 511 | 512 | function getkeyZfn(node) { 513 | var extents = node.extents; 514 | return (extents[2] + extents[5]); 515 | } 516 | 517 | function getreversekeyXfn(node) { 518 | var extents = node.extents; 519 | return -(extents[0] + extents[3]); 520 | } 521 | 522 | function getreversekeyZfn(node) { 523 | var extents = node.extents; 524 | return -(extents[2] + extents[5]); 525 | } 526 | 527 | var nthElement = this._nthElement; 528 | var reverse = false; 529 | 530 | function sortNodesNoYRecursive(nodes, startIndex, endIndex, axis) { 531 | /* tslint:disable:no-bitwise */ 532 | var splitNodeIndex = ((startIndex + endIndex) >> 1); 533 | 534 | /* tslint:enable:no-bitwise */ 535 | if (axis === 0) { 536 | if (reverse) { 537 | nthElement(nodes, startIndex, splitNodeIndex, endIndex, getreversekeyXfn); 538 | } else { 539 | nthElement(nodes, startIndex, splitNodeIndex, endIndex, getkeyXfn); 540 | } 541 | } else { 542 | if (reverse) { 543 | nthElement(nodes, startIndex, splitNodeIndex, endIndex, getreversekeyZfn); 544 | } else { 545 | nthElement(nodes, startIndex, splitNodeIndex, endIndex, getkeyZfn); 546 | } 547 | } 548 | 549 | if (axis === 0) { 550 | axis = 2; 551 | } else { 552 | axis = 0; 553 | } 554 | 555 | reverse = !reverse; 556 | 557 | if ((startIndex + numNodesLeaf) < splitNodeIndex) { 558 | sortNodesNoYRecursive(nodes, startIndex, splitNodeIndex, axis); 559 | } 560 | 561 | if ((splitNodeIndex + numNodesLeaf) < endIndex) { 562 | sortNodesNoYRecursive(nodes, splitNodeIndex, endIndex, axis); 563 | } 564 | } 565 | 566 | sortNodesNoYRecursive(nodes, 0, numNodes, 0); 567 | }; 568 | 569 | AABBTree.prototype._sortNodesHighQuality = function (nodes) { 570 | var numNodesLeaf = this.numNodesLeaf; 571 | var numNodes = nodes.length; 572 | 573 | function getkeyXfn(node) { 574 | var extents = node.extents; 575 | return (extents[0] + extents[3]); 576 | } 577 | 578 | function getkeyYfn(node) { 579 | var extents = node.extents; 580 | return (extents[1] + extents[4]); 581 | } 582 | 583 | function getkeyZfn(node) { 584 | var extents = node.extents; 585 | return (extents[2] + extents[5]); 586 | } 587 | 588 | function getkeyXZfn(node) { 589 | var extents = node.extents; 590 | return (extents[0] + extents[2] + extents[3] + extents[5]); 591 | } 592 | 593 | function getkeyZXfn(node) { 594 | var extents = node.extents; 595 | return (extents[0] - extents[2] + extents[3] - extents[5]); 596 | } 597 | 598 | function getreversekeyXfn(node) { 599 | var extents = node.extents; 600 | return -(extents[0] + extents[3]); 601 | } 602 | 603 | function getreversekeyYfn(node) { 604 | var extents = node.extents; 605 | return -(extents[1] + extents[4]); 606 | } 607 | 608 | function getreversekeyZfn(node) { 609 | var extents = node.extents; 610 | return -(extents[2] + extents[5]); 611 | } 612 | 613 | function getreversekeyXZfn(node) { 614 | var extents = node.extents; 615 | return -(extents[0] + extents[2] + extents[3] + extents[5]); 616 | } 617 | 618 | function getreversekeyZXfn(node) { 619 | var extents = node.extents; 620 | return -(extents[0] - extents[2] + extents[3] - extents[5]); 621 | } 622 | 623 | var nthElement = this._nthElement; 624 | var calculateSAH = this._calculateSAH; 625 | var reverse = false; 626 | 627 | function sortNodesHighQualityRecursive(nodes, startIndex, endIndex) { 628 | /* tslint:disable:no-bitwise */ 629 | var splitNodeIndex = ((startIndex + endIndex) >> 1); 630 | 631 | /* tslint:enable:no-bitwise */ 632 | nthElement(nodes, startIndex, splitNodeIndex, endIndex, getkeyXfn); 633 | var sahX = (calculateSAH(nodes, startIndex, splitNodeIndex) + calculateSAH(nodes, splitNodeIndex, endIndex)); 634 | 635 | nthElement(nodes, startIndex, splitNodeIndex, endIndex, getkeyYfn); 636 | var sahY = (calculateSAH(nodes, startIndex, splitNodeIndex) + calculateSAH(nodes, splitNodeIndex, endIndex)); 637 | 638 | nthElement(nodes, startIndex, splitNodeIndex, endIndex, getkeyZfn); 639 | var sahZ = (calculateSAH(nodes, startIndex, splitNodeIndex) + calculateSAH(nodes, splitNodeIndex, endIndex)); 640 | 641 | nthElement(nodes, startIndex, splitNodeIndex, endIndex, getkeyXZfn); 642 | var sahXZ = (calculateSAH(nodes, startIndex, splitNodeIndex) + calculateSAH(nodes, splitNodeIndex, endIndex)); 643 | 644 | nthElement(nodes, startIndex, splitNodeIndex, endIndex, getkeyZXfn); 645 | var sahZX = (calculateSAH(nodes, startIndex, splitNodeIndex) + calculateSAH(nodes, splitNodeIndex, endIndex)); 646 | 647 | if (sahX <= sahY && sahX <= sahZ && sahX <= sahXZ && sahX <= sahZX) { 648 | if (reverse) { 649 | nthElement(nodes, startIndex, splitNodeIndex, endIndex, getreversekeyXfn); 650 | } else { 651 | nthElement(nodes, startIndex, splitNodeIndex, endIndex, getkeyXfn); 652 | } 653 | } else if (sahZ <= sahY && sahZ <= sahXZ && sahZ <= sahZX) { 654 | if (reverse) { 655 | nthElement(nodes, startIndex, splitNodeIndex, endIndex, getreversekeyZfn); 656 | } else { 657 | nthElement(nodes, startIndex, splitNodeIndex, endIndex, getkeyZfn); 658 | } 659 | } else if (sahY <= sahXZ && sahY <= sahZX) { 660 | if (reverse) { 661 | nthElement(nodes, startIndex, splitNodeIndex, endIndex, getreversekeyYfn); 662 | } else { 663 | nthElement(nodes, startIndex, splitNodeIndex, endIndex, getkeyYfn); 664 | } 665 | } else if (sahXZ <= sahZX) { 666 | if (reverse) { 667 | nthElement(nodes, startIndex, splitNodeIndex, endIndex, getreversekeyXZfn); 668 | } else { 669 | nthElement(nodes, startIndex, splitNodeIndex, endIndex, getkeyXZfn); 670 | } 671 | } else { 672 | if (reverse) { 673 | nthElement(nodes, startIndex, splitNodeIndex, endIndex, getreversekeyZXfn); 674 | } else { 675 | nthElement(nodes, startIndex, splitNodeIndex, endIndex, getkeyZXfn); 676 | } 677 | } 678 | 679 | reverse = !reverse; 680 | 681 | if ((startIndex + numNodesLeaf) < splitNodeIndex) { 682 | sortNodesHighQualityRecursive(nodes, startIndex, splitNodeIndex); 683 | } 684 | 685 | if ((splitNodeIndex + numNodesLeaf) < endIndex) { 686 | sortNodesHighQualityRecursive(nodes, splitNodeIndex, endIndex); 687 | } 688 | } 689 | 690 | sortNodesHighQualityRecursive(nodes, 0, numNodes); 691 | }; 692 | 693 | AABBTree.prototype._calculateSAH = function (buildNodes, startIndex, endIndex) { 694 | var buildNode, extents, minX, minY, minZ, maxX, maxY, maxZ; 695 | 696 | buildNode = buildNodes[startIndex]; 697 | extents = buildNode.extents; 698 | minX = extents[0]; 699 | minY = extents[1]; 700 | minZ = extents[2]; 701 | maxX = extents[3]; 702 | maxY = extents[4]; 703 | maxZ = extents[5]; 704 | 705 | for (var n = (startIndex + 1); n < endIndex; n += 1) { 706 | buildNode = buildNodes[n]; 707 | extents = buildNode.extents; 708 | 709 | /*jshint white: false*/ 710 | if (minX > extents[0]) { 711 | minX = extents[0]; 712 | } 713 | if (minY > extents[1]) { 714 | minY = extents[1]; 715 | } 716 | if (minZ > extents[2]) { 717 | minZ = extents[2]; 718 | } 719 | if (maxX < extents[3]) { 720 | maxX = extents[3]; 721 | } 722 | if (maxY < extents[4]) { 723 | maxY = extents[4]; 724 | } 725 | if (maxZ < extents[5]) { 726 | maxZ = extents[5]; 727 | } 728 | /*jshint white: true*/ 729 | } 730 | 731 | return ((maxX - minX) + (maxY - minY) + (maxZ - minZ)); 732 | }; 733 | 734 | AABBTree.prototype._nthElement = function (nodes, first, nth, last, getkey) { 735 | function medianFn(a, b, c) { 736 | if (a < b) { 737 | if (b < c) { 738 | return b; 739 | } else if (a < c) { 740 | return c; 741 | } else { 742 | return a; 743 | } 744 | } else if (a < c) { 745 | return a; 746 | } else if (b < c) { 747 | return c; 748 | } 749 | return b; 750 | } 751 | 752 | function insertionSortFn(nodes, first, last, getkey) { 753 | var sorted = (first + 1); 754 | while (sorted !== last) { 755 | var tempNode = nodes[sorted]; 756 | var tempKey = getkey(tempNode); 757 | 758 | var next = sorted; 759 | var current = (sorted - 1); 760 | 761 | while (next !== first && tempKey < getkey(nodes[current])) { 762 | nodes[next] = nodes[current]; 763 | next -= 1; 764 | current -= 1; 765 | } 766 | 767 | if (next !== sorted) { 768 | nodes[next] = tempNode; 769 | } 770 | 771 | sorted += 1; 772 | } 773 | } 774 | 775 | while ((last - first) > 8) { 776 | /* tslint:disable:no-bitwise */ 777 | var midValue = medianFn(getkey(nodes[first]), getkey(nodes[first + ((last - first) >> 1)]), getkey(nodes[last - 1])); 778 | 779 | /* tslint:enable:no-bitwise */ 780 | var firstPos = first; 781 | var lastPos = last; 782 | var midPos; 783 | for (; ; firstPos += 1) { 784 | while (getkey(nodes[firstPos]) < midValue) { 785 | firstPos += 1; 786 | } 787 | 788 | do { 789 | lastPos -= 1; 790 | } while(midValue < getkey(nodes[lastPos])); 791 | 792 | if (firstPos >= lastPos) { 793 | midPos = firstPos; 794 | break; 795 | } else { 796 | var temp = nodes[firstPos]; 797 | nodes[firstPos] = nodes[lastPos]; 798 | nodes[lastPos] = temp; 799 | } 800 | } 801 | 802 | if (midPos <= nth) { 803 | first = midPos; 804 | } else { 805 | last = midPos; 806 | } 807 | } 808 | 809 | insertionSortFn(nodes, first, last, getkey); 810 | }; 811 | 812 | AABBTree.prototype._recursiveBuild = function (buildNodes, startIndex, endIndex, lastNodeIndex) { 813 | var nodes = this.nodes; 814 | var nodeIndex = lastNodeIndex; 815 | lastNodeIndex += 1; 816 | 817 | var minX, minY, minZ, maxX, maxY, maxZ, extents; 818 | var buildNode, lastNode; 819 | 820 | if ((startIndex + this.numNodesLeaf) >= endIndex) { 821 | buildNode = buildNodes[startIndex]; 822 | extents = buildNode.extents; 823 | minX = extents[0]; 824 | minY = extents[1]; 825 | minZ = extents[2]; 826 | maxX = extents[3]; 827 | maxY = extents[4]; 828 | maxZ = extents[5]; 829 | 830 | buildNode.externalNode.spatialIndex = lastNodeIndex; 831 | this._replaceNode(nodes, lastNodeIndex, buildNode); 832 | 833 | for (var n = (startIndex + 1); n < endIndex; n += 1) { 834 | buildNode = buildNodes[n]; 835 | extents = buildNode.extents; 836 | 837 | /*jshint white: false*/ 838 | if (minX > extents[0]) { 839 | minX = extents[0]; 840 | } 841 | if (minY > extents[1]) { 842 | minY = extents[1]; 843 | } 844 | if (minZ > extents[2]) { 845 | minZ = extents[2]; 846 | } 847 | if (maxX < extents[3]) { 848 | maxX = extents[3]; 849 | } 850 | if (maxY < extents[4]) { 851 | maxY = extents[4]; 852 | } 853 | if (maxZ < extents[5]) { 854 | maxZ = extents[5]; 855 | } 856 | 857 | /*jshint white: true*/ 858 | lastNodeIndex += 1; 859 | buildNode.externalNode.spatialIndex = lastNodeIndex; 860 | this._replaceNode(nodes, lastNodeIndex, buildNode); 861 | } 862 | 863 | lastNode = nodes[lastNodeIndex]; 864 | } else { 865 | /* tslint:disable:no-bitwise */ 866 | var splitPosIndex = ((startIndex + endIndex) >> 1); 867 | 868 | /* tslint:enable:no-bitwise */ 869 | if ((startIndex + 1) >= splitPosIndex) { 870 | buildNode = buildNodes[startIndex]; 871 | buildNode.externalNode.spatialIndex = lastNodeIndex; 872 | this._replaceNode(nodes, lastNodeIndex, buildNode); 873 | } else { 874 | this._recursiveBuild(buildNodes, startIndex, splitPosIndex, lastNodeIndex); 875 | } 876 | 877 | lastNode = nodes[lastNodeIndex]; 878 | extents = lastNode.extents; 879 | minX = extents[0]; 880 | minY = extents[1]; 881 | minZ = extents[2]; 882 | maxX = extents[3]; 883 | maxY = extents[4]; 884 | maxZ = extents[5]; 885 | 886 | lastNodeIndex = (lastNodeIndex + lastNode.escapeNodeOffset); 887 | 888 | if ((splitPosIndex + 1) >= endIndex) { 889 | buildNode = buildNodes[splitPosIndex]; 890 | buildNode.externalNode.spatialIndex = lastNodeIndex; 891 | this._replaceNode(nodes, lastNodeIndex, buildNode); 892 | } else { 893 | this._recursiveBuild(buildNodes, splitPosIndex, endIndex, lastNodeIndex); 894 | } 895 | 896 | lastNode = nodes[lastNodeIndex]; 897 | extents = lastNode.extents; 898 | 899 | /*jshint white: false*/ 900 | if (minX > extents[0]) { 901 | minX = extents[0]; 902 | } 903 | if (minY > extents[1]) { 904 | minY = extents[1]; 905 | } 906 | if (minZ > extents[2]) { 907 | minZ = extents[2]; 908 | } 909 | if (maxX < extents[3]) { 910 | maxX = extents[3]; 911 | } 912 | if (maxY < extents[4]) { 913 | maxY = extents[4]; 914 | } 915 | if (maxZ < extents[5]) { 916 | maxZ = extents[5]; 917 | } 918 | /*jshint white: true*/ 919 | } 920 | 921 | var node = nodes[nodeIndex]; 922 | if (node === undefined) { 923 | nodes[nodeIndex] = node = AABBTree.allocateNode(); 924 | } 925 | node.reset(minX, minY, minZ, maxX, maxY, maxZ, (lastNodeIndex + lastNode.escapeNodeOffset - nodeIndex)); 926 | }; 927 | 928 | AABBTree.prototype._replaceNode = function (nodes, nodeIndex, newNode) { 929 | var oldNode = nodes[nodeIndex]; 930 | nodes[nodeIndex] = newNode; 931 | if (oldNode !== undefined) { 932 | AABBTree.releaseNode(oldNode); 933 | } 934 | }; 935 | 936 | AABBTree.prototype._predictNumNodes = function (startIndex, endIndex, lastNodeIndex) { 937 | lastNodeIndex += 1; 938 | 939 | if ((startIndex + this.numNodesLeaf) >= endIndex) { 940 | lastNodeIndex += (endIndex - startIndex); 941 | } else { 942 | /* tslint:disable:no-bitwise */ 943 | var splitPosIndex = ((startIndex + endIndex) >> 1); 944 | 945 | /* tslint:enable:no-bitwise */ 946 | if ((startIndex + 1) >= splitPosIndex) { 947 | lastNodeIndex += 1; 948 | } else { 949 | lastNodeIndex = this._predictNumNodes(startIndex, splitPosIndex, lastNodeIndex); 950 | } 951 | 952 | if ((splitPosIndex + 1) >= endIndex) { 953 | lastNodeIndex += 1; 954 | } else { 955 | lastNodeIndex = this._predictNumNodes(splitPosIndex, endIndex, lastNodeIndex); 956 | } 957 | } 958 | 959 | return lastNodeIndex; 960 | }; 961 | 962 | AABBTree.prototype.getVisibleNodes = function (planes, visibleNodes, startIndex) { 963 | var numVisibleNodes = 0; 964 | if (this.numExternalNodes > 0) { 965 | var nodes = this.nodes; 966 | var endNodeIndex = this.endNode; 967 | var numPlanes = planes.length; 968 | var storageIndex = (startIndex === undefined) ? visibleNodes.length : startIndex; 969 | var node, extents, endChildren; 970 | var n0, n1, n2, p0, p1, p2; 971 | var isInside, n, plane, d0, d1, d2, distance; 972 | var nodeIndex = 0; 973 | 974 | for (; ;) { 975 | node = nodes[nodeIndex]; 976 | extents = node.extents; 977 | n0 = extents[0]; 978 | n1 = extents[1]; 979 | n2 = extents[2]; 980 | p0 = extents[3]; 981 | p1 = extents[4]; 982 | p2 = extents[5]; 983 | 984 | //isInsidePlanesAABB 985 | isInside = true; 986 | n = 0; 987 | do { 988 | plane = planes[n]; 989 | d0 = plane[0]; 990 | d1 = plane[1]; 991 | d2 = plane[2]; 992 | distance = (d0 * (d0 < 0 ? n0 : p0) + d1 * (d1 < 0 ? n1 : p1) + d2 * (d2 < 0 ? n2 : p2)); 993 | if (distance < plane[3]) { 994 | isInside = false; 995 | break; 996 | } 997 | n += 1; 998 | } while(n < numPlanes); 999 | if (isInside) { 1000 | if (node.externalNode) { 1001 | visibleNodes[storageIndex] = node.externalNode; 1002 | storageIndex += 1; 1003 | numVisibleNodes += 1; 1004 | nodeIndex += 1; 1005 | if (nodeIndex >= endNodeIndex) { 1006 | break; 1007 | } 1008 | } else { 1009 | //isFullyInsidePlanesAABB 1010 | isInside = true; 1011 | n = 0; 1012 | do { 1013 | plane = planes[n]; 1014 | d0 = plane[0]; 1015 | d1 = plane[1]; 1016 | d2 = plane[2]; 1017 | distance = (d0 * (d0 > 0 ? n0 : p0) + d1 * (d1 > 0 ? n1 : p1) + d2 * (d2 > 0 ? n2 : p2)); 1018 | if (distance < plane[3]) { 1019 | isInside = false; 1020 | break; 1021 | } 1022 | n += 1; 1023 | } while(n < numPlanes); 1024 | if (isInside) { 1025 | endChildren = (nodeIndex + node.escapeNodeOffset); 1026 | nodeIndex += 1; 1027 | do { 1028 | node = nodes[nodeIndex]; 1029 | if (node.externalNode) { 1030 | visibleNodes[storageIndex] = node.externalNode; 1031 | storageIndex += 1; 1032 | numVisibleNodes += 1; 1033 | } 1034 | nodeIndex += 1; 1035 | } while(nodeIndex < endChildren); 1036 | if (nodeIndex >= endNodeIndex) { 1037 | break; 1038 | } 1039 | } else { 1040 | nodeIndex += 1; 1041 | } 1042 | } 1043 | } else { 1044 | nodeIndex += node.escapeNodeOffset; 1045 | if (nodeIndex >= endNodeIndex) { 1046 | break; 1047 | } 1048 | } 1049 | } 1050 | } 1051 | return numVisibleNodes; 1052 | }; 1053 | 1054 | AABBTree.prototype.getOverlappingNodes = function (queryExtents, overlappingNodes, startIndex) { 1055 | if (this.numExternalNodes > 0) { 1056 | var queryMinX = queryExtents[0]; 1057 | var queryMinY = queryExtents[1]; 1058 | var queryMinZ = queryExtents[2]; 1059 | var queryMaxX = queryExtents[3]; 1060 | var queryMaxY = queryExtents[4]; 1061 | var queryMaxZ = queryExtents[5]; 1062 | var nodes = this.nodes; 1063 | var endNodeIndex = this.endNode; 1064 | var node, extents, endChildren; 1065 | var numOverlappingNodes = 0; 1066 | var storageIndex = (startIndex === undefined) ? overlappingNodes.length : startIndex; 1067 | var nodeIndex = 0; 1068 | for (; ;) { 1069 | node = nodes[nodeIndex]; 1070 | extents = node.extents; 1071 | var minX = extents[0]; 1072 | var minY = extents[1]; 1073 | var minZ = extents[2]; 1074 | var maxX = extents[3]; 1075 | var maxY = extents[4]; 1076 | var maxZ = extents[5]; 1077 | if (queryMinX <= maxX && queryMinY <= maxY && queryMinZ <= maxZ && queryMaxX >= minX && queryMaxY >= minY && queryMaxZ >= minZ) { 1078 | if (node.externalNode) { 1079 | overlappingNodes[storageIndex] = node.externalNode; 1080 | storageIndex += 1; 1081 | numOverlappingNodes += 1; 1082 | nodeIndex += 1; 1083 | if (nodeIndex >= endNodeIndex) { 1084 | break; 1085 | } 1086 | } else { 1087 | if (queryMaxX >= maxX && queryMaxY >= maxY && queryMaxZ >= maxZ && queryMinX <= minX && queryMinY <= minY && queryMinZ <= minZ) { 1088 | endChildren = (nodeIndex + node.escapeNodeOffset); 1089 | nodeIndex += 1; 1090 | do { 1091 | node = nodes[nodeIndex]; 1092 | if (node.externalNode) { 1093 | overlappingNodes[storageIndex] = node.externalNode; 1094 | storageIndex += 1; 1095 | numOverlappingNodes += 1; 1096 | } 1097 | nodeIndex += 1; 1098 | } while(nodeIndex < endChildren); 1099 | if (nodeIndex >= endNodeIndex) { 1100 | break; 1101 | } 1102 | } else { 1103 | nodeIndex += 1; 1104 | } 1105 | } 1106 | } else { 1107 | nodeIndex += node.escapeNodeOffset; 1108 | if (nodeIndex >= endNodeIndex) { 1109 | break; 1110 | } 1111 | } 1112 | } 1113 | return numOverlappingNodes; 1114 | } else { 1115 | return 0; 1116 | } 1117 | }; 1118 | 1119 | AABBTree.prototype.getSphereOverlappingNodes = function (center, radius, overlappingNodes) { 1120 | if (this.numExternalNodes > 0) { 1121 | var radiusSquared = (radius * radius); 1122 | var centerX = center[0]; 1123 | var centerY = center[1]; 1124 | var centerZ = center[2]; 1125 | var nodes = this.nodes; 1126 | var endNodeIndex = this.endNode; 1127 | var node, extents; 1128 | var numOverlappingNodes = overlappingNodes.length; 1129 | var nodeIndex = 0; 1130 | for (; ;) { 1131 | node = nodes[nodeIndex]; 1132 | extents = node.extents; 1133 | var minX = extents[0]; 1134 | var minY = extents[1]; 1135 | var minZ = extents[2]; 1136 | var maxX = extents[3]; 1137 | var maxY = extents[4]; 1138 | var maxZ = extents[5]; 1139 | var totalDistance = 0, sideDistance; 1140 | if (centerX < minX) { 1141 | sideDistance = (minX - centerX); 1142 | totalDistance += (sideDistance * sideDistance); 1143 | } else if (centerX > maxX) { 1144 | sideDistance = (centerX - maxX); 1145 | totalDistance += (sideDistance * sideDistance); 1146 | } 1147 | if (centerY < minY) { 1148 | sideDistance = (minY - centerY); 1149 | totalDistance += (sideDistance * sideDistance); 1150 | } else if (centerY > maxY) { 1151 | sideDistance = (centerY - maxY); 1152 | totalDistance += (sideDistance * sideDistance); 1153 | } 1154 | if (centerZ < minZ) { 1155 | sideDistance = (minZ - centerZ); 1156 | totalDistance += (sideDistance * sideDistance); 1157 | } else if (centerZ > maxZ) { 1158 | sideDistance = (centerZ - maxZ); 1159 | totalDistance += (sideDistance * sideDistance); 1160 | } 1161 | if (totalDistance <= radiusSquared) { 1162 | nodeIndex += 1; 1163 | if (node.externalNode) { 1164 | overlappingNodes[numOverlappingNodes] = node.externalNode; 1165 | numOverlappingNodes += 1; 1166 | if (nodeIndex >= endNodeIndex) { 1167 | break; 1168 | } 1169 | } 1170 | } else { 1171 | nodeIndex += node.escapeNodeOffset; 1172 | if (nodeIndex >= endNodeIndex) { 1173 | break; 1174 | } 1175 | } 1176 | } 1177 | } 1178 | }; 1179 | 1180 | AABBTree.prototype.getOverlappingPairs = function (overlappingPairs, startIndex) { 1181 | if (this.numExternalNodes > 0) { 1182 | var nodes = this.nodes; 1183 | var endNodeIndex = this.endNode; 1184 | var currentNode, currentExternalNode, node, extents; 1185 | var numInsertions = 0; 1186 | var storageIndex = (startIndex === undefined) ? overlappingPairs.length : startIndex; 1187 | var currentNodeIndex = 0, nodeIndex; 1188 | for (; ;) { 1189 | currentNode = nodes[currentNodeIndex]; 1190 | while (!currentNode.externalNode) { 1191 | currentNodeIndex += 1; 1192 | currentNode = nodes[currentNodeIndex]; 1193 | } 1194 | 1195 | currentNodeIndex += 1; 1196 | if (currentNodeIndex < endNodeIndex) { 1197 | currentExternalNode = currentNode.externalNode; 1198 | extents = currentNode.extents; 1199 | var minX = extents[0]; 1200 | var minY = extents[1]; 1201 | var minZ = extents[2]; 1202 | var maxX = extents[3]; 1203 | var maxY = extents[4]; 1204 | var maxZ = extents[5]; 1205 | 1206 | nodeIndex = currentNodeIndex; 1207 | for (; ;) { 1208 | node = nodes[nodeIndex]; 1209 | extents = node.extents; 1210 | if (minX <= extents[3] && minY <= extents[4] && minZ <= extents[5] && maxX >= extents[0] && maxY >= extents[1] && maxZ >= extents[2]) { 1211 | nodeIndex += 1; 1212 | if (node.externalNode) { 1213 | overlappingPairs[storageIndex] = currentExternalNode; 1214 | overlappingPairs[storageIndex + 1] = node.externalNode; 1215 | storageIndex += 2; 1216 | numInsertions += 2; 1217 | if (nodeIndex >= endNodeIndex) { 1218 | break; 1219 | } 1220 | } 1221 | } else { 1222 | nodeIndex += node.escapeNodeOffset; 1223 | if (nodeIndex >= endNodeIndex) { 1224 | break; 1225 | } 1226 | } 1227 | } 1228 | } else { 1229 | break; 1230 | } 1231 | } 1232 | return numInsertions; 1233 | } else { 1234 | return 0; 1235 | } 1236 | }; 1237 | 1238 | AABBTree.prototype.getExtents = function () { 1239 | return (0 < this.nodes.length ? this.nodes[0].extents : null); 1240 | }; 1241 | 1242 | AABBTree.prototype.getRootNode = function () { 1243 | return this.nodes[0]; 1244 | }; 1245 | 1246 | AABBTree.prototype.getNodes = function () { 1247 | return this.nodes; 1248 | }; 1249 | 1250 | AABBTree.prototype.getEndNodeIndex = function () { 1251 | return this.endNode; 1252 | }; 1253 | 1254 | AABBTree.prototype.clear = function () { 1255 | if (this.nodes.length) { 1256 | AABBTree.recycleNodes(this.nodes, 0); 1257 | } 1258 | this.endNode = 0; 1259 | this.needsRebuild = false; 1260 | this.needsRebound = false; 1261 | this.numAdds = 0; 1262 | this.numUpdates = 0; 1263 | this.numExternalNodes = 0; 1264 | this.startUpdate = 0x7FFFFFFF; 1265 | this.endUpdate = -0x7FFFFFFF; 1266 | this.ignoreY = false; 1267 | }; 1268 | 1269 | AABBTree.rayTest = function (trees, ray, callback) { 1270 | // convert ray to parametric form 1271 | var origin = ray.origin; 1272 | var direction = ray.direction; 1273 | 1274 | // values used throughout calculations. 1275 | var o0 = origin[0]; 1276 | var o1 = origin[1]; 1277 | var o2 = origin[2]; 1278 | var d0 = direction[0]; 1279 | var d1 = direction[1]; 1280 | var d2 = direction[2]; 1281 | var id0 = 1 / d0; 1282 | var id1 = 1 / d1; 1283 | var id2 = 1 / d2; 1284 | 1285 | // evaluate distance factor to a node's extents from ray origin, along direction 1286 | // use this to induce an ordering on which nodes to check. 1287 | function distanceExtents(extents, upperBound) { 1288 | var min0 = extents[0]; 1289 | var min1 = extents[1]; 1290 | var min2 = extents[2]; 1291 | var max0 = extents[3]; 1292 | var max1 = extents[4]; 1293 | var max2 = extents[5]; 1294 | 1295 | // treat origin internal to extents as 0 distance. 1296 | if (min0 <= o0 && o0 <= max0 && min1 <= o1 && o1 <= max1 && min2 <= o2 && o2 <= max2) { 1297 | return 0.0; 1298 | } 1299 | 1300 | var tmin, tmax; 1301 | var tymin, tymax; 1302 | var del; 1303 | if (d0 >= 0) { 1304 | // Deal with cases where d0 == 0 1305 | del = (min0 - o0); 1306 | tmin = ((del === 0) ? 0 : (del * id0)); 1307 | del = (max0 - o0); 1308 | tmax = ((del === 0) ? 0 : (del * id0)); 1309 | } else { 1310 | tmin = ((max0 - o0) * id0); 1311 | tmax = ((min0 - o0) * id0); 1312 | } 1313 | 1314 | if (d1 >= 0) { 1315 | // Deal with cases where d1 == 0 1316 | del = (min1 - o1); 1317 | tymin = ((del === 0) ? 0 : (del * id1)); 1318 | del = (max1 - o1); 1319 | tymax = ((del === 0) ? 0 : (del * id1)); 1320 | } else { 1321 | tymin = ((max1 - o1) * id1); 1322 | tymax = ((min1 - o1) * id1); 1323 | } 1324 | 1325 | if ((tmin > tymax) || (tymin > tmax)) { 1326 | return undefined; 1327 | } 1328 | 1329 | if (tymin > tmin) { 1330 | tmin = tymin; 1331 | } 1332 | 1333 | if (tymax < tmax) { 1334 | tmax = tymax; 1335 | } 1336 | 1337 | var tzmin, tzmax; 1338 | if (d2 >= 0) { 1339 | // Deal with cases where d2 == 0 1340 | del = (min2 - o2); 1341 | tzmin = ((del === 0) ? 0 : (del * id2)); 1342 | del = (max2 - o2); 1343 | tzmax = ((del === 0) ? 0 : (del * id2)); 1344 | } else { 1345 | tzmin = ((max2 - o2) * id2); 1346 | tzmax = ((min2 - o2) * id2); 1347 | } 1348 | 1349 | if ((tmin > tzmax) || (tzmin > tmax)) { 1350 | return undefined; 1351 | } 1352 | 1353 | if (tzmin > tmin) { 1354 | tmin = tzmin; 1355 | } 1356 | 1357 | if (tzmax < tmax) { 1358 | tmax = tzmax; 1359 | } 1360 | 1361 | if (tmin < 0) { 1362 | tmin = tmax; 1363 | } 1364 | 1365 | return (0 <= tmin && tmin < upperBound) ? tmin : undefined; 1366 | } 1367 | 1368 | // we traverse both trees at once 1369 | // keeping a priority list of nodes to check next. 1370 | // TODO: possibly implement priority list more effeciently? 1371 | // binary heap probably too much overhead in typical case. 1372 | var priorityList = []; 1373 | 1374 | //current upperBound on distance to first intersection 1375 | //and current closest object properties 1376 | var minimumResult = null; 1377 | 1378 | //if node is a leaf, intersect ray with shape 1379 | // otherwise insert node into priority list. 1380 | function processNode(tree, nodeIndex, upperBound) { 1381 | var nodes = tree.getNodes(); 1382 | var node = nodes[nodeIndex]; 1383 | var distance = distanceExtents(node.extents, upperBound); 1384 | if (distance === undefined) { 1385 | return upperBound; 1386 | } 1387 | 1388 | if (node.externalNode) { 1389 | var result = callback(tree, node.externalNode, ray, distance, upperBound); 1390 | if (result) { 1391 | minimumResult = result; 1392 | upperBound = result.factor; 1393 | } 1394 | } else { 1395 | // TODO: change to binary search? 1396 | var length = priorityList.length; 1397 | var i; 1398 | for (i = 0; i < length; i += 1) { 1399 | var curObj = priorityList[i]; 1400 | if (distance > curObj.distance) { 1401 | break; 1402 | } 1403 | } 1404 | 1405 | //insert node at index i 1406 | priorityList.splice(i - 1, 0, { 1407 | tree: tree, 1408 | nodeIndex: nodeIndex, 1409 | distance: distance 1410 | }); 1411 | } 1412 | 1413 | return upperBound; 1414 | } 1415 | 1416 | var upperBound = ray.maxFactor; 1417 | 1418 | var tree; 1419 | var i; 1420 | for (i = 0; i < trees.length; i += 1) { 1421 | tree = trees[i]; 1422 | if (tree.endNode !== 0) { 1423 | upperBound = processNode(tree, 0, upperBound); 1424 | } 1425 | } 1426 | 1427 | while (priorityList.length !== 0) { 1428 | var nodeObj = priorityList.pop(); 1429 | 1430 | // A node inserted into priority list after this one may have 1431 | // moved the upper bound. 1432 | if (nodeObj.distance >= upperBound) { 1433 | continue; 1434 | } 1435 | 1436 | var nodeIndex = nodeObj.nodeIndex; 1437 | tree = nodeObj.tree; 1438 | var nodes = tree.getNodes(); 1439 | 1440 | var node = nodes[nodeIndex]; 1441 | var maxIndex = nodeIndex + node.escapeNodeOffset; 1442 | 1443 | var childIndex = nodeIndex + 1; 1444 | do { 1445 | upperBound = processNode(tree, childIndex, upperBound); 1446 | childIndex += nodes[childIndex].escapeNodeOffset; 1447 | } while(childIndex < maxIndex); 1448 | } 1449 | 1450 | return minimumResult; 1451 | }; 1452 | 1453 | AABBTree.create = function (highQuality) { 1454 | return new AABBTree(highQuality ? true : false); 1455 | }; 1456 | AABBTree.version = 1; 1457 | 1458 | AABBTree.nodesPoolAllocationSize = 128; 1459 | AABBTree.nodesPool = []; 1460 | 1461 | module.exports = AABBTree; 1462 | 1463 | })(); 1464 | --------------------------------------------------------------------------------