|
@@ -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 ) );
|