瀏覽代碼

Added ThreeOctree. See #2148.

Mr.doob 12 年之前
父節點
當前提交
5245e524e9
共有 2 個文件被更改,包括 2295 次插入0 次删除
  1. 2056 0
      examples/js/Octree.js
  2. 239 0
      examples/webgl_octree.html

+ 2056 - 0
examples/js/Octree.js

@@ -0,0 +1,2056 @@
+/*!
+ *
+ * threeoctree.js (r56) / https://github.com/collinhover/threeoctree
+ * (sparse) dynamic 3D spatial representation structure for fast searches.
+ *
+ * @author Collin Hover / http://collinhover.com/
+ * based on Dynamic Octree by Piko3D @ http://www.piko3d.com/ and Octree by Marek Pawlowski @ pawlowski.it
+ *
+ */
+ ( function ( THREE ) { "use strict";
+	
+	/*===================================================
+
+	utility
+
+	=====================================================*/
+	
+	function isNumber ( n ) {
+		return !isNaN( n ) && isFinite( n );
+	}
+	
+	function isArray ( target ) {
+		return Object.prototype.toString.call( target ) === '[object Array]';
+	}
+	
+	function toArray ( target ) {
+		return target ? ( isArray ( target ) !== true ? [ target ] : target ) : [];
+	}
+
+	/*===================================================
+
+	octree
+
+	=====================================================*/
+
+	THREE.Octree = function ( parameters ) {
+		
+		// handle parameters
+		
+		parameters = parameters || {};
+		
+		parameters.tree = this;
+		
+		// static properties ( modification is not recommended )
+		
+		this.nodeCount = 0;
+		
+		this.INDEX_INSIDE_CROSS = -1;
+		this.INDEX_OUTSIDE_OFFSET = 2;
+		
+		this.INDEX_OUTSIDE_POS_X = isNumber( parameters.INDEX_OUTSIDE_POS_X ) ? parameters.INDEX_OUTSIDE_POS_X : 0;
+		this.INDEX_OUTSIDE_NEG_X = isNumber( parameters.INDEX_OUTSIDE_NEG_X ) ? parameters.INDEX_OUTSIDE_NEG_X : 1;
+		this.INDEX_OUTSIDE_POS_Y = isNumber( parameters.INDEX_OUTSIDE_POS_Y ) ? parameters.INDEX_OUTSIDE_POS_Y : 2;
+		this.INDEX_OUTSIDE_NEG_Y = isNumber( parameters.INDEX_OUTSIDE_NEG_Y ) ? parameters.INDEX_OUTSIDE_NEG_Y : 3;
+		this.INDEX_OUTSIDE_POS_Z = isNumber( parameters.INDEX_OUTSIDE_POS_Z ) ? parameters.INDEX_OUTSIDE_POS_Z : 4;
+		this.INDEX_OUTSIDE_NEG_Z = isNumber( parameters.INDEX_OUTSIDE_NEG_Z ) ? parameters.INDEX_OUTSIDE_NEG_Z : 5;
+		
+		this.INDEX_OUTSIDE_MAP = [];
+		this.INDEX_OUTSIDE_MAP[ this.INDEX_OUTSIDE_POS_X ] = { index: this.INDEX_OUTSIDE_POS_X, count: 0, x: 1, y: 0, z: 0 };
+		this.INDEX_OUTSIDE_MAP[ this.INDEX_OUTSIDE_NEG_X ] = { index: this.INDEX_OUTSIDE_NEG_X, count: 0, x: -1, y: 0, z: 0 };
+		this.INDEX_OUTSIDE_MAP[ this.INDEX_OUTSIDE_POS_Y ] = { index: this.INDEX_OUTSIDE_POS_Y, count: 0, x: 0, y: 1, z: 0 };
+		this.INDEX_OUTSIDE_MAP[ this.INDEX_OUTSIDE_NEG_Y ] = { index: this.INDEX_OUTSIDE_NEG_Y, count: 0, x: 0, y: -1, z: 0 };
+		this.INDEX_OUTSIDE_MAP[ this.INDEX_OUTSIDE_POS_Z ] = { index: this.INDEX_OUTSIDE_POS_Z, count: 0, x: 0, y: 0, z: 1 };
+		this.INDEX_OUTSIDE_MAP[ this.INDEX_OUTSIDE_NEG_Z ] = { index: this.INDEX_OUTSIDE_NEG_Z, count: 0, x: 0, y: 0, z: -1 };
+		
+		this.FLAG_POS_X = 1 << ( this.INDEX_OUTSIDE_POS_X + 1 );
+		this.FLAG_NEG_X = 1 << ( this.INDEX_OUTSIDE_NEG_X + 1 );
+		this.FLAG_POS_Y = 1 << ( this.INDEX_OUTSIDE_POS_Y + 1 );
+		this.FLAG_NEG_Y = 1 << ( this.INDEX_OUTSIDE_NEG_Y + 1 );
+		this.FLAG_POS_Z = 1 << ( this.INDEX_OUTSIDE_POS_Z + 1 );
+		this.FLAG_NEG_Z = 1 << ( this.INDEX_OUTSIDE_NEG_Z + 1 );
+		
+		this.utilVec31Search = new THREE.Vector3();
+		this.utilVec32Search = new THREE.Vector3();
+		
+		// pass scene to see octree structure
+		
+		this.scene = parameters.scene;
+		
+		// properties
+		
+		this.objects = [];
+		this.objectsData = [];
+		
+		this.depthMax = isNumber( parameters.depthMax ) ? parameters.depthMax : -1;
+		this.objectsThreshold = isNumber( parameters.objectsThreshold ) ? parameters.objectsThreshold : 8;
+		this.overlapPct = isNumber( parameters.overlapPct ) ? parameters.overlapPct : 0.15;
+		
+		this.root = parameters.root instanceof THREE.OctreeNode ? parameters.root : new THREE.OctreeNode( parameters );
+		
+	};
+
+	THREE.Octree.prototype = {
+		
+		setRoot: function ( root ) { 
+			
+			if ( root instanceof THREE.OctreeNode ) {
+				
+				// store new root
+				
+				this.root = root;
+				
+				// update properties
+				
+				this.root.updateProperties();
+				
+			}
+			
+		},
+		
+		add: function ( object, useFaces ) {
+			
+			var i, l,
+				index,
+				geometry,
+				faces,
+				objectData;
+			
+			// ensure object is not object data
+			
+			if ( object instanceof THREE.OctreeObjectData ) {
+				
+				object = object.object;
+				
+			}
+			
+			// if does not yet contain object
+			
+			index = this.objects.indexOf( object );
+			
+			if ( index === -1 ) {
+				
+				// store
+				
+				this.objects.push( object );
+				
+				// ensure world matrices are updated
+				
+				this.updateObject( object );
+				
+				// if adding faces of object
+				
+				if ( useFaces === true ) {
+					
+					geometry = object.geometry;
+					faces = geometry.faces;
+					
+					for ( i = 0, l = faces.length; i < l; i++ ) {
+						
+						this.addObjectData( object, faces[ i ] );
+						
+					}
+					
+				}
+				// else add object itself
+				else {
+					
+					this.addObjectData( object );
+					
+				}
+				
+			}
+			
+		},
+		
+		addObjectData: function ( object, face ) {
+			
+			var objectData = new THREE.OctreeObjectData( object, face );
+			
+			// add to tree objects data list
+			
+			this.objectsData.push( objectData );
+			
+			// add to nodes
+			
+			this.root.addObject( objectData );
+			
+		},
+		
+		remove: function ( object ) {
+			
+			var i, l,
+				objectData = object,
+				index,
+				objectsDataRemoved;
+			
+			// ensure object is not object data for index search
+			
+			if ( object instanceof THREE.OctreeObjectData ) {
+				
+				object = object.object;
+				
+			}
+			
+			// if contains object
+			
+			index = this.objects.indexOf( object );
+			
+			if ( index !== -1 ) {
+				
+				// remove from objects list
+				
+				this.objects.splice( index, 1 );
+				
+				// remove from nodes
+				
+				objectsDataRemoved = this.root.removeObject( objectData );
+				
+				// remove from objects data list
+				
+				for ( i = 0, l = objectsDataRemoved.length; i < l; i++ ) {
+					
+					objectData = objectsDataRemoved[ i ];
+					
+					index = this.objectsData.indexOf( objectData );
+					
+					if ( index !== -1 ) {
+						
+						this.objectsData.splice( index, 1 );
+						
+					}
+					
+				}
+				
+			}
+			
+		},
+		
+		extend: function ( octree ) {
+			
+			var i, l,
+				objectsData,
+				objectData;
+				
+			if ( octree instanceof THREE.Octree ) {
+				
+				// for each object data
+				
+				objectsData = octree.objectsData;
+				
+				for ( i = 0, l = objectsData.length; i < l; i++ ) {
+					
+					objectData = objectsData[ i ];
+					
+					this.add( objectData, objectData.usesFaces() );
+					
+				}
+				
+			}
+			
+		},
+		
+		update: function () {
+			
+			var i, l,
+				node,
+				object,
+				objectData,
+				indexOctant,
+				indexOctantLast,
+				objectsUpdate = [];
+			
+			// update all objects
+			
+			for ( i = 0, l = this.objects.length; i < l; i++ ) {
+				
+				object = this.objects[ i ];
+				
+				// ensure world matrices are updated
+				
+				this.updateObject( object );
+				
+			}
+			
+			// check all object data for changes in position
+			
+			for ( i = 0, l = this.objectsData.length; i < l; i++ ) {
+				
+				objectData = this.objectsData[ i ];
+				
+				node = objectData.node;
+				
+				// update object
+				
+				objectData.update();
+				
+				// if position has changed since last organization of object in tree
+				
+				if ( node instanceof THREE.OctreeNode && !objectData.positionLast.equals( objectData.position ) ) {
+					
+					// get octant index of object within current node
+					
+					indexOctantLast = objectData.indexOctant;
+					
+					indexOctant = node.getOctantIndex( objectData );
+					
+					// if object octant index has changed
+					
+					if ( indexOctant !== indexOctantLast ) {
+						
+						// add to update list
+						
+						objectsUpdate.push( objectData );
+						
+					}
+					
+				}
+				
+			}
+			
+			// update changed objects
+			
+			for ( i = 0, l = objectsUpdate.length; i < l; i++ ) {
+				
+				objectData = objectsUpdate[ i ];
+				
+				// remove object from current node
+				
+				objectData.node.removeObject( objectData );
+				
+				// add object to tree root
+				
+				this.root.addObject( objectData );
+				
+			}
+			
+		},
+		
+		search: function ( position, radius, organizeByObject, direction ) {
+			
+			var i, l,
+				node,
+				objects,
+				objectData,
+				object,
+				results,
+				resultData,
+				resultsObjectsIndices,
+				resultObjectIndex,
+				directionPct;
+			
+			// add root objects
+			
+			objects = [].concat( this.root.objects );
+			
+			// ensure radius (i.e. distance of ray) is a number
+			
+			if ( isNumber( radius ) !== true ) {
+				
+				radius = Number.MAX_VALUE;
+				
+			}
+			
+			// if direction passed, normalize and find pct
+			
+			if ( direction instanceof THREE.Vector3 ) {
+				
+				direction = this.utilVec31Search.copy( direction ).normalize();
+				directionPct = this.utilVec32Search.set( 1, 1, 1 ).divide( direction );
+				
+			}
+			
+			// search each node of root
+			
+			for ( i = 0, l = this.root.nodesIndices.length; i < l; i++ ) {
+				
+				node = this.root.nodesByIndex[ this.root.nodesIndices[ i ] ];
+				
+				objects = node.search( position, radius, objects, direction, directionPct );
+				
+			}
+			
+			// if should organize results by object
+			
+			if ( organizeByObject === true ) {
+				
+				results = [];
+				resultsObjectsIndices = [];
+				
+				// for each object data found
+				
+				for ( i = 0, l = objects.length; i < l; i++ ) {
+					
+					objectData = objects[ i ];
+					object = objectData.object;
+					
+					resultObjectIndex = resultsObjectsIndices.indexOf( object );
+					
+					// if needed, create new result data
+					
+					if ( resultObjectIndex === -1 ) {
+						
+						resultData = {
+							object: object,
+							faces: []
+						};
+						
+						results.push( resultData );
+						
+						resultsObjectsIndices.push( object );
+						
+					}
+					else {
+						
+						resultData = results[ resultObjectIndex ];
+						
+					}
+					
+					// if object data has face, add to list
+					
+					if ( typeof objectData.faces !== 'undefined' ) {
+						
+						resultData.faces.push( objectData.faces );
+						
+					}
+					
+				}
+				
+			}
+			else {
+				
+				results = objects;
+				
+			}
+			
+			return results;
+			
+		},
+		
+		updateObject: function ( object ) {
+			
+			var i, l,
+				parentCascade = [ object ],
+				parent,
+				parentUpdate;
+			
+			// search all parents between object and root for world matrix update
+			
+			parent = object.parent;
+			
+			while( parent ) {
+				
+				parentCascade.push( parent );
+				parent = parent.parent;
+				
+			}
+			
+			for ( i = 0, l = parentCascade.length; i < l; i++ ) {
+				
+				parent = parentCascade[ i ];
+				
+				if ( parent.matrixWorldNeedsUpdate === true ) {
+					
+					parentUpdate = parent;
+					
+				}
+				
+			}
+			
+			// update world matrix starting at uppermost parent that needs update
+			
+			if ( typeof parentUpdate !== 'undefined' ) {
+				
+				parentUpdate.updateMatrixWorld();
+				
+			}
+			
+		},
+		
+		getDepthEnd: function () {
+			
+			return this.root.getDepthEnd();
+			
+		},
+		
+		getNodeCountEnd: function () {
+			
+			return this.root.getNodeCountEnd();
+			
+		},
+		
+		getObjectCountEnd: function () {
+			
+			return this.root.getObjectCountEnd();
+			
+		},
+		
+		toConsole: function () {
+			
+			this.root.toConsole();
+			
+		}
+		
+	};
+
+	/*===================================================
+
+	object data
+
+	=====================================================*/
+
+	THREE.OctreeObjectData = function ( object, face ) {
+		
+		// utility
+		
+		this.utilVec31FaceBounds = new THREE.Vector3();
+		
+		// properties
+		
+		this.object = object;
+		this.faces = face;
+		
+		this.radius = 0;
+		this.position = new THREE.Vector3();
+			
+		// initial update
+		
+		if ( this.object instanceof THREE.Object3D ) {
+			
+			this.update();
+			
+		}
+		
+		this.positionLast = this.position.clone();
+		
+	};
+
+	THREE.OctreeObjectData.prototype = {
+		
+		update: function () {
+			
+			if ( this.usesFaces() ) {
+				
+				this.radius = this.getFaceBoundingRadius( this.object, this.faces );
+				this.position.copy( this.faces.centroid ).applyMatrix4( this.object.matrixWorld );
+				
+			} else {
+				
+				var geometry = this.object.geometry;
+
+				if ( geometry.boundingSphere === null ) geometry.computeBoundingSphere();
+
+				this.radius = geometry.boundingSphere.radius;
+				this.position.getPositionFromMatrix( this.object.matrixWorld );
+				
+			}
+			
+			this.radius = this.radius * Math.max( this.object.scale.x, this.object.scale.y, this.object.scale.z );
+			
+		},
+		
+		getFaceBoundingRadius: function ( object, face ) {
+			
+			var geometry = object instanceof THREE.Mesh ? object.geometry : object,
+				vertices = geometry.vertices,
+				centroid = face.centroid,
+				va = vertices[ face.a ], vb = vertices[ face.b ], vc = vertices[ face.c ], vd,
+				centroidToVert = this.utilVec31FaceBounds,
+				radius;
+			
+			// handle face type
+			
+			if ( face instanceof THREE.Face4 ) {
+				
+				vd = vertices[ face.d ];
+				
+				centroid.addVectors( va, vb ).add( vc ).add( vd ).divideScalar( 4 );
+				
+				radius = Math.max( centroidToVert.subVectors( centroid, va ).length(), centroidToVert.subVectors( centroid, vb ).length(), centroidToVert.subVectors( centroid, vc ).length(), centroidToVert.subVectors( centroid, vd ).length() );
+				
+			}
+			else {
+				
+				centroid.addVectors( va, vb ).add( vc ).divideScalar( 3 );
+				
+				radius = Math.max( centroidToVert.subVectors( centroid, va ).length(), centroidToVert.subVectors( centroid, vb ).length(), centroidToVert.subVectors( centroid, vc ).length() );
+				
+			}
+			
+			return radius;
+			
+		},
+		
+		usesFaces: function () {
+			
+			return this.faces instanceof THREE.Face3 || this.faces instanceof THREE.Face4;
+			
+		}
+		
+	};
+
+	/*===================================================
+
+	node
+
+	=====================================================*/
+
+	THREE.OctreeNode = function ( parameters ) {
+		
+		// utility
+		
+		this.utilVec31Branch = new THREE.Vector3();
+		this.utilVec31Expand = new THREE.Vector3();
+		this.utilVec31Ray = new THREE.Vector3();
+		
+		// handle parameters
+		
+		parameters = parameters || {};
+		
+		// store or create tree
+		
+		if ( parameters.tree instanceof THREE.Octree ) {
+			
+			this.tree = parameters.tree;
+			
+		}
+		else if ( parameters.parent instanceof THREE.OctreeNode !== true ) {
+			
+			parameters.root = this;
+			
+			this.tree = new THREE.Octree( parameters );
+			
+		}
+		
+		// basic properties
+		
+		this.id = this.tree.nodeCount++;
+		this.position = parameters.position instanceof THREE.Vector3 ? parameters.position : new THREE.Vector3();
+		this.radius = isNumber( parameters.radius ) && parameters.radius > 0 ? parameters.radius : 1;
+		this.indexOctant = parameters.indexOctant;
+		this.depth = 0;
+		
+		// reset and assign parent
+		
+		this.reset();
+		this.setParent( parameters.parent );
+		
+		// additional properties
+		
+		this.overlap = this.radius * this.tree.overlapPct;
+		this.radiusOverlap = this.radius + this.overlap;
+		this.left = this.position.x - this.radiusOverlap;
+		this.right = this.position.x + this.radiusOverlap;
+		this.bottom = this.position.y - this.radiusOverlap;
+		this.top = this.position.y + this.radiusOverlap;
+		this.back = this.position.z - this.radiusOverlap;
+		this.front = this.position.z + this.radiusOverlap;
+		
+		// visual
+		
+		if ( this.tree.scene ) {
+			
+			this.visual = new THREE.Mesh( new THREE.CubeGeometry( this.radiusOverlap * 2, this.radiusOverlap * 2, this.radiusOverlap * 2 ), new THREE.MeshBasicMaterial( { color: 0xFF0000, wireframe: true, wireframeLinewidth: 1 } ) );
+			this.visual.position.copy( this.position );
+			this.tree.scene.add( this.visual );
+			
+		}
+		
+	};
+
+	THREE.OctreeNode.prototype = {
+		
+		setParent: function ( parent ) {
+			
+			// store new parent
+			
+			if ( parent !== this && this.parent !== parent ) {
+				
+				this.parent = parent;
+				
+				// update properties
+				
+				this.updateProperties();
+				
+			}
+			
+		},
+		
+		updateProperties: function () {
+			
+			var i, l;
+			
+			// properties
+			
+			if ( this.parent instanceof THREE.OctreeNode ) {
+				
+				this.tree = this.parent.tree;
+				this.depth = this.parent.depth + 1;
+				
+			}
+			else {
+				
+				this.depth = 0;
+				
+			}
+			
+			// cascade
+			
+			for ( i = 0, l = this.nodesIndices.length; i < l; i++ ) {
+				
+				this.nodesByIndex[ this.nodesIndices[ i ] ].updateProperties();
+				
+			}
+			
+		},
+		
+		reset: function ( cascade, removeVisual ) {
+			
+			var i, l,
+				node,
+				nodesIndices = this.nodesIndices || [],
+				nodesByIndex = this.nodesByIndex;
+			
+			this.objects = [];
+			this.nodesIndices = [];
+			this.nodesByIndex = {};
+			
+			// unset parent in nodes
+			
+			for ( i = 0, l = nodesIndices.length; i < l; i++ ) {
+				
+				node = nodesByIndex[ nodesIndices[ i ] ];
+				
+				node.setParent( undefined );
+				
+				if ( cascade === true ) {
+					
+					node.reset( cascade, removeVisual );
+					
+				}
+				
+			}
+			
+			// visual
+			
+			if ( removeVisual === true && this.visual && this.visual.parent ) {
+				
+				this.visual.parent.remove( this.visual );
+				
+			}
+			
+		},
+		
+		addNode: function ( node, indexOctant ) {
+			
+			indexOctant = node.indexOctant = isNumber( indexOctant ) ? indexOctant : isNumber( node.indexOctant ) ? node.indexOctant : this.getOctantIndex( node );
+			
+			if ( this.nodesIndices.indexOf( indexOctant ) === -1 ) {
+				
+				this.nodesIndices.push( indexOctant );
+				
+			}
+			
+			this.nodesByIndex[ indexOctant ] = node;
+			
+			if ( node.parent !== this ) {
+				
+				node.setParent( this );
+				
+			}
+			
+		},
+		
+		removeNode: function ( identifier ) {
+			
+			var indexOctant = -1,
+				index,
+				node;
+			
+			// if identifier is node
+			if ( identifier instanceof THREE.OctreeNode && this.nodesByIndex[ identifier.indexOctant ] === identifier ) {
+				
+				node = identifier;
+				indexOctant = node.indexOctant;
+				
+			}
+			// if identifier is number
+			else if ( isNumber( identifier ) ) {
+				
+				indexOctant = identifier;
+				
+			}
+			// else search all nodes for identifier (slow)
+			else {
+				
+				for ( index in this.nodesByIndex ) {
+					
+					node = this.nodesByIndex[ index ];
+					
+					if ( node === identifier ) {
+						
+						indexOctant = index;
+						
+						break;
+						
+					}
+					
+				}
+				
+			}
+			
+			// if indexOctant found
+			
+			if ( indexOctant !== -1 ) {
+				
+				index = this.nodesIndices.indexOf( indexOctant );
+				
+				this.nodesIndices.splice( index, 1 );
+				
+				node = node || this.nodesByIndex[ indexOctant ];
+				
+				delete this.nodesByIndex[ indexOctant ];
+				
+				if ( node.parent === this ) {
+					
+					node.setParent( undefined );
+					
+				}
+				
+			}
+			
+		},
+		
+		addObject: function ( object ) {
+			
+			var index,
+				indexOctant,
+				node;
+			
+			// get object octant index
+			
+			indexOctant = this.getOctantIndex( object );
+			
+			// if object fully contained by an octant, add to subtree
+			if ( indexOctant > -1 && this.nodesIndices.length > 0 ) {
+				
+				node = this.branch( indexOctant );
+				
+				node.addObject( object );
+				
+			}
+			// if object lies outside bounds, add to parent node
+			else if ( indexOctant < -1 && this.parent instanceof THREE.OctreeNode ) {
+				
+				this.parent.addObject( object );
+				
+			}
+			// else add to self
+			else {
+				
+				// add to this objects list
+				
+				index = this.objects.indexOf( object );
+				
+				if ( index === -1 ) {
+					
+					this.objects.push( object );
+					
+				}
+				
+				// node reference
+				
+				object.node = this;
+				
+				// check if need to expand, split, or both
+				
+				this.checkGrow();
+				
+			}
+			
+		},
+		
+		addObjectWithoutCheck: function ( objects ) {
+			
+			var i, l,
+				object;
+
+			for ( i = 0, l = objects.length; i < l; i++ ) {
+				
+				object = objects[ i ];
+				
+				this.objects.push( object );
+				
+				object.node = this;
+				
+			}
+			
+		},
+		
+		removeObject: function ( object ) {
+			
+			var i, l,
+				nodesRemovedFrom,
+				removeData;
+			
+			// cascade through tree to find and remove object
+			
+			removeData = this.removeObjectRecursive( object, { searchComplete: false, nodesRemovedFrom: [], objectsDataRemoved: [] } );
+			
+			// if object removed, try to shrink the nodes it was removed from
+			
+			nodesRemovedFrom = removeData.nodesRemovedFrom;
+			
+			if ( nodesRemovedFrom.length > 0 ) {
+				
+				for ( i = 0, l = nodesRemovedFrom.length; i < l; i++ ) {
+					
+					nodesRemovedFrom[ i ].shrink();
+					
+				}
+				
+			}
+			
+			return removeData.objectsDataRemoved;
+			
+		},
+		
+		removeObjectRecursive: function ( object, removeData ) {
+			
+			var i, l,
+				index = -1,
+				objectData,
+				node,
+				objectRemoved;
+			
+			// find index of object in objects list
+			
+			// search and remove object data (fast)
+			if ( object instanceof THREE.OctreeObjectData ) {
+				
+				// remove from this objects list
+				
+				index = this.objects.indexOf( object );
+				
+				if ( index !== -1 ) {
+					
+					this.objects.splice( index, 1 );
+					object.node = undefined;
+					
+					removeData.objectsDataRemoved.push( object );
+					
+					removeData.searchComplete = objectRemoved = true;
+					
+				}
+				
+			}
+			// search each object data for object and remove (slow)
+			else {
+				
+				for ( i = this.objects.length - 1; i >= 0; i-- ) {
+					
+					objectData = this.objects[ i ];
+					
+					if ( objectData.object === object ) {
+						
+						this.objects.splice( i, 1 );
+						objectData.node = undefined;
+						
+						removeData.objectsDataRemoved.push( objectData );
+						
+						objectRemoved = true;
+						
+						if ( typeof objectData.faces === 'undefined' ) {
+							
+							removeData.searchComplete = true;
+							break;
+							
+						}
+						
+					}
+					
+				}
+				
+			}
+			
+			// if object data removed and this is not on nodes removed from
+			
+			if ( objectRemoved === true ) {//&& removeData.nodesRemovedFrom.indexOf( this ) === -1 ) {
+				
+				removeData.nodesRemovedFrom.push( this );
+				
+			}
+			
+			// if search not complete, search nodes
+			
+			if ( removeData.searchComplete !== true ) {
+				
+				for ( i = 0, l = this.nodesIndices.length; i < l; i++ ) {
+					
+					node = this.nodesByIndex[ this.nodesIndices[ i ] ];
+					
+					// try removing object from node
+					
+					removeData = node.removeObjectRecursive( object, removeData );
+					
+					if ( removeData.searchComplete === true ) {
+						
+						break;
+						
+					}
+					
+				}
+				
+			}
+			
+			return removeData;
+			
+		},
+		
+		checkGrow: function () {
+			
+			// if object count above max
+			
+			if ( this.objects.length > this.tree.objectsThreshold && this.tree.objectsThreshold > 0 ) {
+				
+				this.grow();
+				
+			}
+			
+		},
+		
+		grow: function () {
+			
+			var indexOctant,
+				object,
+				objectsExpand = [],
+				objectsExpandOctants = [],
+				objectsSplit = [],
+				objectsSplitOctants = [],
+				objectsRemaining = [],
+				i, l;
+			
+			// for each object
+			
+			for ( i = 0, l = this.objects.length; i < l; i++ ) {
+				
+				object = this.objects[ i ];
+				
+				// get object octant index
+				
+				indexOctant = this.getOctantIndex( object );
+				
+				// if lies within octant
+				if ( indexOctant > -1 ) {
+					
+					objectsSplit.push( object );
+					objectsSplitOctants.push( indexOctant );
+				
+				}
+				// if lies outside radius
+				else if ( indexOctant < -1 ) {
+					
+					objectsExpand.push( object );
+					objectsExpandOctants.push( indexOctant );
+					
+				}
+				// else if lies across bounds between octants
+				else {
+					
+					objectsRemaining.push( object );
+					
+				}
+				
+			}
+			
+			// if has objects to split
+			
+			if ( objectsSplit.length > 0) {
+				
+				objectsRemaining = objectsRemaining.concat( this.split( objectsSplit, objectsSplitOctants ) );
+				
+			}
+			
+			// if has objects to expand
+			
+			if ( objectsExpand.length > 0) {
+				
+				objectsRemaining = objectsRemaining.concat( this.expand( objectsExpand, objectsExpandOctants ) );
+				
+			}
+			
+			// store remaining
+			
+			this.objects = objectsRemaining;
+			
+			// merge check
+			
+			this.checkMerge();
+			
+		},
+		
+		split: function ( objects, octants ) {
+			
+			var i, l,
+				indexOctant,
+				object,
+				node,
+				objectsRemaining;
+			
+			// if not at max depth
+			
+			if ( this.tree.depthMax < 0 || this.depth < this.tree.depthMax ) {
+				
+				objects = objects || this.objects;
+				
+				octants = octants || [];
+				
+				objectsRemaining = [];
+				
+				// for each object
+				
+				for ( i = 0, l = objects.length; i < l; i++ ) {
+					
+					object = objects[ i ];
+					
+					// get object octant index
+					
+					indexOctant = isNumber( octants[ i ] ) ? octants[ i ] : this.getOctantIndex( object );
+					
+					// if object contained by octant, branch this tree
+					
+					if ( indexOctant > -1 ) {
+						
+						node = this.branch( indexOctant );
+						
+						node.addObject( object );
+						
+					}
+					// else add to remaining
+					else {
+						
+						objectsRemaining.push( object );
+						
+					}
+					
+				}
+				
+				// if all objects, set remaining as new objects
+				
+				if ( objects === this.objects ) {
+					
+					this.objects = objectsRemaining;
+					
+				}
+				
+			}
+			else {
+				
+				objectsRemaining = this.objects;
+				
+			}
+			
+			return objectsRemaining;
+			
+		},
+		
+		branch: function ( indexOctant ) {
+			
+			var node,
+				overlap,
+				radius,
+				radiusOffset,
+				offset,
+				position;
+			
+			// node exists
+			
+			if ( this.nodesByIndex[ indexOctant ] instanceof THREE.OctreeNode ) {
+				
+				node = this.nodesByIndex[ indexOctant ];
+				
+			}
+			// create new
+			else {
+				
+				// properties
+				
+				radius = ( this.radiusOverlap ) * 0.5;
+				overlap = radius * this.tree.overlapPct;
+				radiusOffset = radius - overlap;
+				offset = this.utilVec31Branch.set( indexOctant & 1 ? radiusOffset : -radiusOffset, indexOctant & 2 ? radiusOffset : -radiusOffset, indexOctant & 4 ? radiusOffset : -radiusOffset );
+				position = new THREE.Vector3().addVectors( this.position, offset );
+				
+				// node
+				
+				node = new THREE.OctreeNode( {
+					tree: this.tree,
+					parent: this,
+					position: position,
+					radius: radius,
+					indexOctant: indexOctant
+				} );
+				
+				// store
+				
+				this.addNode( node, indexOctant );
+			
+			}
+			
+			return node;
+			
+		},
+		
+		expand: function ( objects, octants ) {
+			
+			var i, l,
+				object,
+				objectsRemaining,
+				objectsExpand,
+				indexOctant,
+				flagsOutside,
+				indexOutside,
+				indexOctantInverse,
+				iom = this.tree.INDEX_OUTSIDE_MAP,
+				indexOutsideCounts,
+				infoIndexOutside1,
+				infoIndexOutside2,
+				infoIndexOutside3,
+				indexOutsideBitwise1,
+				indexOutsideBitwise2,
+				infoPotential1,
+				infoPotential2,
+				infoPotential3,
+				indexPotentialBitwise1,
+				indexPotentialBitwise2,
+				octantX, octantY, octantZ,
+				overlap,
+				radius,
+				radiusOffset,
+				radiusParent,
+				overlapParent,
+				offset = this.utilVec31Expand,
+				position,
+				parent;
+			
+			// handle max depth down tree
+			
+			if ( this.tree.depthMax < 0 || this.tree.root.getDepthEnd() < this.tree.depthMax ) {
+				
+				objects = objects || this.objects;
+				octants = octants || [];
+				
+				objectsRemaining = [];
+				objectsExpand = [];
+				
+				// reset counts
+				
+				for ( i = 0, l = iom.length; i < l; i++ ) {
+					
+					iom[ i ].count = 0;
+					
+				}
+				
+				// for all outside objects, find outside octants containing most objects
+				
+				for ( i = 0, l = objects.length; i < l; i++ ) {
+					
+					object = objects[ i ];
+					
+					// get object octant index
+					
+					indexOctant = isNumber( octants[ i ] ) ? octants[ i ] : this.getOctantIndex( object );
+					
+					// if object outside this, include in calculations
+					
+					if ( indexOctant < -1 ) {
+						
+						// convert octant index to outside flags
+						
+						flagsOutside = -indexOctant - this.tree.INDEX_OUTSIDE_OFFSET;
+						
+						// check against bitwise flags
+						
+						// x
+						
+						if ( flagsOutside & this.tree.FLAG_POS_X ) {
+							
+							iom[ this.tree.INDEX_OUTSIDE_POS_X ].count++;
+							
+						}
+						else if ( flagsOutside & this.tree.FLAG_NEG_X ) {
+							
+							iom[ this.tree.INDEX_OUTSIDE_NEG_X ].count++;
+							
+						}
+						
+						// y
+						
+						if ( flagsOutside & this.tree.FLAG_POS_Y ) {
+							
+							iom[ this.tree.INDEX_OUTSIDE_POS_Y ].count++;
+							
+						}
+						else if ( flagsOutside & this.tree.FLAG_NEG_Y ) {
+							
+							iom[ this.tree.INDEX_OUTSIDE_NEG_Y ].count++;
+							
+						}
+						
+						// z
+						
+						if ( flagsOutside & this.tree.FLAG_POS_Z ) {
+							
+							iom[ this.tree.INDEX_OUTSIDE_POS_Z ].count++;
+							
+						}
+						else if ( flagsOutside & this.tree.FLAG_NEG_Z ) {
+							
+							iom[ this.tree.INDEX_OUTSIDE_NEG_Z ].count++;
+							
+						}
+						
+						// store in expand list
+						
+						objectsExpand.push( object );
+						
+					}
+					// else add to remaining
+					else {
+						
+						objectsRemaining.push( object );
+						
+					}
+					
+				}
+				
+				// if objects to expand
+				
+				if ( objectsExpand.length > 0 ) {
+					
+					// shallow copy index outside map
+					
+					indexOutsideCounts = iom.slice( 0 );
+					
+					// sort outside index count so highest is first
+					
+					indexOutsideCounts.sort( function ( a, b ) {
+						
+						return b.count - a.count;
+						
+					} );
+					
+					// get highest outside indices
+					
+					// first is first
+					infoIndexOutside1 = indexOutsideCounts[ 0 ];
+					indexOutsideBitwise1 = infoIndexOutside1.index | 1;
+					
+					// second is ( one of next two bitwise OR 1 ) that is not opposite of ( first bitwise OR 1 )
+					
+					infoPotential1 = indexOutsideCounts[ 1 ];
+					infoPotential2 = indexOutsideCounts[ 2 ];
+					
+					infoIndexOutside2 = ( infoPotential1.index | 1 ) !== indexOutsideBitwise1 ? infoPotential1 : infoPotential2;
+					indexOutsideBitwise2 = infoIndexOutside2.index | 1;
+					
+					// third is ( one of next three bitwise OR 1 ) that is not opposite of ( first or second bitwise OR 1 )
+					
+					infoPotential1 = indexOutsideCounts[ 2 ];
+					infoPotential2 = indexOutsideCounts[ 3 ];
+					infoPotential3 = indexOutsideCounts[ 4 ];
+					
+					indexPotentialBitwise1 = infoPotential1.index | 1;
+					indexPotentialBitwise2 = infoPotential2.index | 1;
+					
+					infoIndexOutside3 = indexPotentialBitwise1 !== indexOutsideBitwise1 && indexPotentialBitwise1 !== indexOutsideBitwise2 ? infoPotential1 : indexPotentialBitwise2 !== indexOutsideBitwise1 && indexPotentialBitwise2 !== indexOutsideBitwise2 ? infoPotential2 : infoPotential3;
+					
+					// get this octant normal based on outside octant indices
+					
+					octantX = infoIndexOutside1.x + infoIndexOutside2.x + infoIndexOutside3.x;
+					octantY = infoIndexOutside1.y + infoIndexOutside2.y + infoIndexOutside3.y;
+					octantZ = infoIndexOutside1.z + infoIndexOutside2.z + infoIndexOutside3.z;
+					
+					// get this octant indices based on octant normal
+					
+					indexOctant = this.getOctantIndexFromPosition( octantX, octantY, octantZ );
+					indexOctantInverse = this.getOctantIndexFromPosition( -octantX, -octantY, -octantZ );
+					
+					// properties
+					
+					overlap = this.overlap;
+					radius = this.radius;
+					
+					// radius of parent comes from reversing overlap of this, unless overlap percent is 0
+					
+					radiusParent = this.tree.overlapPct > 0 ? overlap / ( ( 0.5 * this.tree.overlapPct ) * ( 1 + this.tree.overlapPct ) ) : radius * 2; 
+					overlapParent = radiusParent * this.tree.overlapPct;
+					
+					// parent offset is difference between radius + overlap of parent and child
+					
+					radiusOffset = ( radiusParent + overlapParent ) - ( radius + overlap );
+					offset.set( indexOctant & 1 ? radiusOffset : -radiusOffset, indexOctant & 2 ? radiusOffset : -radiusOffset, indexOctant & 4 ? radiusOffset : -radiusOffset );
+					position = new THREE.Vector3().addVectors( this.position, offset );
+					
+					// parent
+					
+					parent = new THREE.OctreeNode( {
+						tree: this.tree,
+						position: position,
+						radius: radiusParent
+					} );
+					
+					// set self as node of parent
+					
+					parent.addNode( this, indexOctantInverse );
+					
+					// set parent as root
+					
+					this.tree.setRoot( parent );
+					
+					// add all expand objects to parent
+					
+					for ( i = 0, l = objectsExpand.length; i < l; i++ ) {
+						
+						this.tree.root.addObject( objectsExpand[ i ] );
+						
+					}
+					
+				}
+				
+				// if all objects, set remaining as new objects
+				
+				if ( objects === this.objects ) {
+					
+					this.objects = objectsRemaining;
+					
+				}
+				
+			}
+			else {
+				
+				objectsRemaining = objects;
+				
+			}
+			
+			return objectsRemaining;
+			
+		},
+		
+		shrink: function () {
+			
+			// merge check
+			
+			this.checkMerge();
+			
+			// contract check
+			
+			this.tree.root.checkContract();
+			
+		},
+		
+		checkMerge: function () {
+			
+			var nodeParent = this,
+				nodeMerge;
+			
+			// traverse up tree as long as node + entire subtree's object count is under minimum
+			
+			while ( nodeParent.parent instanceof THREE.OctreeNode && nodeParent.getObjectCountEnd() < this.tree.objectsThreshold ) {
+				
+				nodeMerge = nodeParent;
+				nodeParent = nodeParent.parent;
+				
+			}
+			
+			// if parent node is not this, merge entire subtree into merge node
+			
+			if ( nodeParent !== this ) {
+				
+				nodeParent.merge( nodeMerge );
+				
+			}
+			
+		},
+		
+		merge: function ( nodes ) {
+			
+			var i, l,
+				j, k,
+				node;
+			
+			// handle nodes
+			
+			nodes = toArray( nodes );
+			
+			for ( i = 0, l = nodes.length; i < l; i++ ) {
+				
+				node = nodes[ i ];
+				
+				// gather node + all subtree objects
+				
+				this.addObjectWithoutCheck( node.getObjectsEnd() );
+				
+				// reset node + entire subtree
+				
+				node.reset( true, true );
+				
+				// remove node
+				
+				this.removeNode( node.indexOctant, node );
+				
+			}
+			
+			// merge check
+			
+			this.checkMerge();
+			
+		},
+		
+		checkContract: function () {
+			
+			var i, l,
+				node,
+				nodeObjectsCount,
+				nodeHeaviest,
+				nodeHeaviestObjectsCount,
+				outsideHeaviestObjectsCount;
+			
+			// find node with highest object count
+			
+			if ( this.nodesIndices.length > 0 ) {
+				
+				nodeHeaviestObjectsCount = 0;
+				outsideHeaviestObjectsCount = this.objects.length;
+				
+				for ( i = 0, l = this.nodesIndices.length; i < l; i++ ) {
+					
+					node = this.nodesByIndex[ this.nodesIndices[ i ] ];
+					
+					nodeObjectsCount = node.getObjectCountEnd();
+					outsideHeaviestObjectsCount += nodeObjectsCount;
+					
+					if ( nodeHeaviest instanceof THREE.OctreeNode === false || nodeObjectsCount > nodeHeaviestObjectsCount ) {
+						
+						nodeHeaviest = node;
+						nodeHeaviestObjectsCount = nodeObjectsCount;
+						
+					}
+					
+				}
+				
+				// subtract heaviest count from outside count
+				
+				outsideHeaviestObjectsCount -= nodeHeaviestObjectsCount;
+				
+				// if should contract
+				
+				if ( outsideHeaviestObjectsCount < this.tree.objectsThreshold && nodeHeaviest instanceof THREE.OctreeNode ) {
+					
+					this.contract( nodeHeaviest );
+					
+				}
+				
+			}
+			
+		},
+		
+		contract: function ( nodeRoot ) {
+			
+			var i, l,
+				node;
+			
+			// handle all nodes
+			
+			for ( i = 0, l = this.nodesIndices.length; i < l; i++ ) {
+				
+				node = this.nodesByIndex[ this.nodesIndices[ i ] ];
+				
+				// if node is not new root
+				
+				if ( node !== nodeRoot ) {
+					
+					// add node + all subtree objects to root
+					
+					nodeRoot.addObjectWithoutCheck( node.getObjectsEnd() );
+					
+					// reset node + entire subtree
+					
+					node.reset( true, true );
+					
+				}
+				
+			}
+			
+			// add own objects to root
+			
+			nodeRoot.addObjectWithoutCheck( this.objects );
+			
+			// reset self
+			
+			this.reset( false, true );
+			
+			// set new root
+			
+			this.tree.setRoot( nodeRoot );
+			
+			// contract check on new root
+			
+			nodeRoot.checkContract();
+			
+		},
+		
+		getOctantIndex: function ( objectData ) {
+			
+			var i, l,
+				positionObj,
+				radiusObj,
+				position = this.position,
+				radiusOverlap = this.radiusOverlap,
+				overlap = this.overlap,
+				deltaX, deltaY, deltaZ,
+				distX, distY, distZ, 
+				distance,
+				indexOctant = 0;
+			
+			// handle type
+			
+			// object data
+			if ( objectData instanceof THREE.OctreeObjectData ) {
+				
+				radiusObj = objectData.radius;
+				
+				positionObj = objectData.position;
+				
+				// update object data position last
+				
+				objectData.positionLast.copy( positionObj );
+				
+			}
+			// node
+			else if ( objectData instanceof THREE.OctreeNode ) {
+				
+				positionObj = objectData.position;
+				
+				radiusObj = 0;
+				
+			}
+			
+			// find delta and distance
+			
+			deltaX = positionObj.x - position.x;
+			deltaY = positionObj.y - position.y;
+			deltaZ = positionObj.z - position.z;
+			
+			distX = Math.abs( deltaX );
+			distY = Math.abs( deltaY );
+			distZ = Math.abs( deltaZ );
+			distance = Math.max( distX, distY, distZ );
+			
+			// if outside, use bitwise flags to indicate on which sides object is outside of
+			
+			if ( distance + radiusObj > radiusOverlap ) {
+				
+				// x
+				
+				if ( distX + radiusObj > radiusOverlap ) {
+					
+					indexOctant = indexOctant ^ ( deltaX > 0 ? this.tree.FLAG_POS_X : this.tree.FLAG_NEG_X );
+					
+				}
+				
+				// y
+				
+				if ( distY + radiusObj > radiusOverlap ) {
+					
+					indexOctant = indexOctant ^ ( deltaY > 0 ? this.tree.FLAG_POS_Y : this.tree.FLAG_NEG_Y );
+					
+				}
+				
+				// z
+				
+				if ( distZ + radiusObj > radiusOverlap ) {
+					
+					indexOctant = indexOctant ^ ( deltaZ > 0 ? this.tree.FLAG_POS_Z : this.tree.FLAG_NEG_Z );
+					
+				}
+				
+				objectData.indexOctant = -indexOctant - this.tree.INDEX_OUTSIDE_OFFSET;
+				
+				return objectData.indexOctant;
+				
+			}
+			
+			// return octant index from delta xyz
+			
+			// x right
+			if ( deltaX - radiusObj > -overlap ) {
+				
+				indexOctant = indexOctant | 1;
+				
+			}
+			// x left
+			else if ( !( deltaX + radiusObj < overlap ) ) {
+				
+				objectData.indexOctant = this.tree.INDEX_INSIDE_CROSS;
+				return objectData.indexOctant;
+				
+			}
+			
+			// y right
+			if ( deltaY - radiusObj > -overlap ) {
+				
+				indexOctant = indexOctant | 2;
+				
+			}
+			// y left
+			else if ( !( deltaY + radiusObj < overlap ) ) {
+				
+				objectData.indexOctant = this.tree.INDEX_INSIDE_CROSS;
+				return objectData.indexOctant;
+				
+			}
+			
+			// z right
+			if ( deltaZ - radiusObj > -overlap ) {
+				
+				indexOctant = indexOctant | 4;
+				
+			}
+			// z left
+			else if ( !( deltaZ + radiusObj < overlap ) ) {
+				
+				objectData.indexOctant = this.tree.INDEX_INSIDE_CROSS;
+				return objectData.indexOctant;
+				
+			}
+			
+			objectData.indexOctant = indexOctant;
+			return objectData.indexOctant;
+			
+		},
+		
+		getOctantIndexFromPosition: function ( x, y, z ) {
+			
+			var indexOctant = 0;
+			
+			if ( x > 0 ) {
+				
+				indexOctant = indexOctant | 1;
+				
+			}
+			
+			if ( y > 0 ) {
+				
+				indexOctant = indexOctant | 2;
+				
+			}
+			
+			if ( z > 0 ) {
+				
+				indexOctant = indexOctant | 4;
+				
+			}
+			
+			return indexOctant;
+			
+		},
+		
+		search: function ( position, radius, objects, direction, directionPct ) {
+			
+			var i, l,
+				node,
+				intersects;
+			
+			// test intersects by parameters
+			
+			if ( direction ) {
+				
+				intersects = this.intersectRay( position, direction, radius, directionPct );
+				
+			}
+			else {
+				
+				intersects = this.intersectSphere( position, radius );
+				
+			}
+			
+			// if intersects
+			
+			if ( intersects === true ) {
+				
+				// gather objects
+				
+				objects = objects.concat( this.objects );
+				
+				// search subtree
+				
+				for ( i = 0, l = this.nodesIndices.length; i < l; i++ ) {
+					
+					node = this.nodesByIndex[ this.nodesIndices[ i ] ];
+					
+					objects = node.search( position, radius, objects, direction );
+					
+				}
+				
+			}
+			
+			return objects;
+			
+		},
+		
+		intersectSphere: function ( position, radius ) {
+			
+			var	distance = radius * radius,
+				px = position.x,
+				py = position.y,
+				pz = position.z;
+			
+			if ( px < this.left ) {
+				distance -= Math.pow( px - this.left, 2 );
+			}
+			else if ( px > this.right ) {
+				distance -= Math.pow( px - this.right, 2 );
+			}
+			
+			if ( py < this.bottom ) {
+				distance -= Math.pow( py - this.bottom, 2 );
+			}
+			else if ( py > this.top ) {
+				distance -= Math.pow( py - this.top, 2 );
+			}
+			
+			if ( pz < this.back ) {
+				distance -= Math.pow( pz - this.back, 2 );
+			}
+			else if ( pz > this.front ) {
+				distance -= Math.pow( pz - this.front, 2 );
+			}
+			
+			return distance >= 0;
+			
+		},
+		
+		intersectRay: function ( origin, direction, distance, directionPct ) {
+			
+			if ( typeof directionPct === 'undefined' ) {
+				
+				directionPct = this.utilVec31Ray.set( 1, 1, 1 ).divide( direction );
+				
+			}
+			
+			var t1 = ( this.left - origin.x ) * directionPct.x,
+				t2 = ( this.right - origin.x ) * directionPct.x,
+				t3 = ( this.bottom - origin.y ) * directionPct.y,
+				t4 = ( this.top - origin.y ) * directionPct.y,
+				t5 = ( this.back - origin.z ) * directionPct.z,
+				t6 = ( this.front - origin.z ) * directionPct.z,
+				tmax = Math.min( Math.min( Math.max( t1, t2), Math.max( t3, t4) ), Math.max( t5, t6) ),
+				tmin;
+
+			// ray would intersect in reverse direction, i.e. this is behind ray
+			if (tmax < 0)
+			{
+				return false;
+			}
+			
+			tmin = Math.max( Math.max( Math.min( t1, t2), Math.min( t3, t4)), Math.min( t5, t6));
+			
+			// if tmin > tmax or tmin > ray distance, ray doesn't intersect AABB
+			if( tmin > tmax || tmin > distance ) {
+				return false;
+			}
+			
+			return true;
+			
+		},
+		
+		getDepthEnd: function ( depth ) {
+			
+			var i, l,
+				node;
+
+			if ( this.nodesIndices.length > 0 ) {
+				
+				for ( i = 0, l = this.nodesIndices.length; i < l; i++ ) {
+
+					node = this.nodesByIndex[ this.nodesIndices[ i ] ];
+
+					depth = node.getDepthEnd( depth );
+
+				}
+				
+			}
+			else {
+
+				depth = !depth || this.depth > depth ? this.depth : depth;
+
+			}
+
+			return depth;
+			
+		},
+		
+		getNodeCountEnd: function () {
+			
+			return this.tree.root.getNodeCountRecursive() + 1;
+			
+		},
+		
+		getNodeCountRecursive: function () {
+			
+			var i, l,
+				count = this.nodesIndices.length;
+			
+			for ( i = 0, l = this.nodesIndices.length; i < l; i++ ) {
+				
+				count += this.nodesByIndex[ this.nodesIndices[ i ] ].getNodeCountRecursive();
+				
+			}
+			
+			return count;
+			
+		},
+		
+		getObjectsEnd: function ( objects ) {
+			
+			var i, l,
+				node;
+			
+			objects = ( objects || [] ).concat( this.objects );
+			
+			for ( i = 0, l = this.nodesIndices.length; i < l; i++ ) {
+				
+				node = this.nodesByIndex[ this.nodesIndices[ i ] ];
+				
+				objects = node.getObjectsEnd( objects );
+				
+			}
+			
+			return objects;
+			
+		},
+		
+		getObjectCountEnd: function () {
+			
+			var i, l,
+				count = this.objects.length;
+			
+			for ( i = 0, l = this.nodesIndices.length; i < l; i++ ) {
+				
+				count += this.nodesByIndex[ this.nodesIndices[ i ] ].getObjectCountEnd();
+				
+			}
+			
+			return count;
+			
+		},
+		
+		getObjectCountStart: function () {
+			
+			var count = this.objects.length,
+				parent = this.parent;
+			
+			while( parent instanceof THREE.OctreeNode ) {
+				
+				count += parent.objects.length;
+				parent = parent.parent;
+				
+			}
+			
+			return count;
+			
+		},
+		
+		toConsole: function ( space ) {
+			
+			var i, l,
+				node,
+				spaceAddition = '   ';
+			
+			space = typeof space === 'string' ? space : spaceAddition;
+			
+			console.log( ( this.parent ? space + ' octree NODE > ' : ' octree ROOT > ' ), this, ' // id: ', this.id, ' // indexOctant: ', this.indexOctant, ' // position: ', this.position.x, this.position.y, this.position.z, ' // radius: ', this.radius, ' // depth: ', this.depth );
+			console.log( ( this.parent ? space + ' ' : ' ' ), '+ objects ( ', this.objects.length, ' ) ', this.objects );
+			console.log( ( this.parent ? space + ' ' : ' ' ), '+ children ( ', this.nodesIndices.length, ' )', this.nodesIndices, this.nodesByIndex );
+			
+			for ( i = 0, l = this.nodesIndices.length; i < l; i++ ) {
+				
+				node = this.nodesByIndex[ this.nodesIndices[ i ] ];
+				
+				node.toConsole( space + spaceAddition );
+				
+			}
+			
+		}
+		
+	};
+
+	/*===================================================
+
+	raycaster additional functionality
+
+	=====================================================*/
+	
+	THREE.Raycaster.prototype.intersectOctreeObject = function ( object, recursive ) {
+		
+		var intersects,
+			octreeObject,
+			facesAll,
+			facesSearch;
+		
+		if ( object.object instanceof THREE.Object3D ) {
+			
+			octreeObject = object;
+			object = octreeObject.object;
+			
+			// temporarily replace object geometry's faces with octree object faces
+			
+			facesSearch = octreeObject.faces;
+			facesAll = object.geometry.faces;
+			
+			if ( facesSearch.length > 0 ) {
+				
+				object.geometry.faces = facesSearch;
+				
+			}
+			
+			// intersect
+			
+			intersects = this.intersectObject( object, recursive );
+			
+			// revert object geometry's faces
+			
+			if ( facesSearch.length > 0 ) {
+				
+				object.geometry.faces = facesAll;
+				
+			}
+			
+		}
+		else {
+			
+			intersects = this.intersectObject( object, recursive );
+			
+		}
+		
+		return intersects;
+		
+	};
+	
+	THREE.Raycaster.prototype.intersectOctreeObjects = function ( objects, recursive ) {
+		
+		var i, il,
+			intersects = [];
+		
+		for ( i = 0, il = objects.length; i < il; i++ ) {
+			
+			intersects = intersects.concat( this.intersectOctreeObject( objects[ i ], recursive ) );
+		
+		}
+		
+		return intersects;
+		
+	};
+
+}( THREE ) );

+ 239 - 0
examples/webgl_octree.html

@@ -0,0 +1,239 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<title>three.js webgl - octree</title>
+		<meta charset="utf-8">
+		<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
+		<style>
+			body {
+				background-color: #ffffff;
+				margin: 0px;
+				overflow: hidden;
+			}
+		</style>
+	</head>
+	<body>
+
+		<script type="text/javascript" src="../build/three.min.js"></script>
+		<script type="text/javascript" src="js/Octree.js"></script>
+		<script>
+
+			var camera, 
+				scene, 
+				renderer,
+				octree,
+				geometry, 
+				material, 
+				mesh,
+				meshes = [],
+				meshesSearch = [],
+				meshCountMax = 1000,
+				radius = 500,
+				radiusMax = radius * 10,
+				radiusMaxHalf = radiusMax * 0.5,
+				radiusSearch = 400,
+				searchMesh,
+				baseR = 255, baseG = 0, baseB = 255,
+				foundR = 0, foundG = 255, foundB = 0,
+				adding = true;
+
+			init();
+			animate();
+
+			function init() {
+			
+				// standard three scene, camera, renderer
+
+				scene = new THREE.Scene();
+
+				camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 1, radius * 100 );
+				scene.add( camera );
+
+				renderer = new THREE.WebGLRenderer();
+				renderer.setSize( window.innerWidth, window.innerHeight );
+
+				document.body.appendChild( renderer.domElement );
+			
+				// create octree
+			
+				octree = new THREE.Octree( {
+					scene: scene
+				} );
+			
+				// create object to show search radius and add to scene
+			
+				searchMesh = new THREE.Mesh(
+					new THREE.SphereGeometry( radiusSearch ),
+					new THREE.MeshBasicMaterial( { color: 0x00FF00, transparent: true, opacity: 0.4 } )
+				);
+				scene.add( searchMesh );
+
+			}
+
+			function animate() {
+
+				// note: three.js includes requestAnimationFrame shim
+				requestAnimationFrame( animate );
+			
+				// modify octree structure by adding/removing objects
+			
+				modifyOctree();
+			
+				// search octree at random location
+			
+				searchOctree();
+			
+				// render results
+			
+				render();
+
+			}
+		
+			function modifyOctree() {
+			
+				// if is adding objects to octree
+			
+				if ( adding === true ) {
+				
+					// create new object
+				
+					geometry = new THREE.CubeGeometry( 50, 50, 50 );
+					material = new THREE.MeshBasicMaterial();
+					material.color.setRGB( baseR, baseG, baseB );
+				
+					mesh = new THREE.Mesh( geometry, material );
+				
+					// give new object a random position in radius
+				
+					mesh.position.set(
+						Math.random() * radiusMax - radiusMaxHalf,
+						Math.random() * radiusMax - radiusMaxHalf,
+						Math.random() * radiusMax - radiusMaxHalf
+					);
+				
+					// add new object to octree and scene
+				
+					octree.add( mesh );
+					scene.add( mesh );
+				
+					// store object for later
+				
+					meshes.push( mesh );
+				
+					// if at max, stop adding
+				
+					if ( meshes.length === meshCountMax ) {
+					
+						adding = false;
+					
+					}
+				
+				}
+				// else remove objects from octree
+				else {
+				
+					// get object
+				
+					mesh = meshes.shift();
+				
+					// remove from scene and octree
+				
+					scene.remove( mesh );
+					octree.remove( mesh );
+				
+					// if no more objects, start adding
+				
+					if ( meshes.length === 0 ) {
+					
+						adding = true;
+					
+					}
+				
+				}
+			
+				/*
+			
+				// octree details to console
+			
+				console.log( ' OCTREE: ', octree );
+				console.log( ' ... depth ', octree.depth, ' vs depth end?', octree.depth_end() );
+				console.log( ' ... num nodes: ', octree.node_count_end() );
+				console.log( ' ... total objects: ', octree.object_count_end(), ' vs tree objects length: ', octree.objects.length );
+			
+				// print full octree structure to console
+			
+				octree.to_console();
+			
+				*/
+			
+			}
+		
+			function searchOctree() {
+			
+				var i, il;
+			
+				// revert previous search objects to base color
+			
+				for ( i = 0, il = meshesSearch.length; i < il; i++ ) {
+				
+					meshesSearch[ i ].object.material.color.setRGB( baseR, baseG, baseB );
+				
+				}
+			
+				// new search position
+				searchMesh.position.set(
+					Math.random() * radiusMax - radiusMaxHalf,
+					Math.random() * radiusMax - radiusMaxHalf,
+					Math.random() * radiusMax - radiusMaxHalf
+				);
+			
+				// record start time
+			
+				var timeStart = Date.now();
+			
+				// search octree from search mesh position with search radius
+				// optional third parameter: boolean, if should sort results by object when using faces in octree
+				// optional fourth parameter: vector3, direction of search when using ray (assumes radius is distance/far of ray)
+			
+				var rayCaster = new THREE.Raycaster( new THREE.Vector3().copy( searchMesh.position ), new THREE.Vector3( Math.random() * 2 - 1, Math.random() * 2 - 1, Math.random() * 2 - 1 ).normalize() );
+				meshesSearch = octree.search( rayCaster.ray.origin, radiusSearch, true, rayCaster.ray.direction );
+				var intersections = rayCaster.intersectOctreeObjects( meshesSearch );
+			
+				// record end time
+			
+				var timeEnd = Date.now();
+			
+				// set color of all meshes found in search
+			
+				for ( i = 0, il = meshesSearch.length; i < il; i++ ) {
+				
+					meshesSearch[ i ].object.material.color.setRGB( foundR, foundG, foundB );
+				
+				}
+			
+				/*
+			
+				// results to console
+			
+				console.log( 'OCTREE: ', octree );
+				console.log( '... searched ', meshes.length, ' and found ', meshesSearch.length, ' with intersections ', intersections.length, ' and took ', ( timeEnd - timeStart ), ' ms ' );
+			
+				*/
+			
+			}
+		
+			function render() {
+	
+				var timer = - Date.now() / 5000;
+
+				camera.position.x = Math.cos( timer ) * 10000;
+				camera.position.z = Math.sin( timer ) * 10000;
+				camera.lookAt( scene.position );
+		
+				renderer.render( scene, camera );
+
+			}
+
+		</script>
+	</body>
+</html>