threex
Version:
Game Extensions for three.js http://www.threejsgames.com/extensions/
2,057 lines (1,191 loc) • 44.9 kB
JavaScript
/*!
*
* 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 ) );