/*! * * 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; centroid.addVectors( va, vb ).add( vc ).divideScalar( 3 ); return Math.max( centroidToVert.subVectors( centroid, va ).length(), centroidToVert.subVectors( centroid, vb ).length(), centroidToVert.subVectors( centroid, vc ).length() ); }, usesFaces: function () { return this.faces instanceof THREE.Face3; } }; /*=================================================== 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, opacity: 0.05, transparent: true } ) ); 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 ) );