mirror of
https://github.com/Doodle3D/Doodle3D-Slicer.git
synced 2024-12-24 20:13:48 +01:00
2114 lines
45 KiB
JavaScript
Executable File
2114 lines
45 KiB
JavaScript
Executable File
/*!
|
|
*
|
|
* threeoctree.js (r60) / 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 ) : [];
|
|
}
|
|
|
|
function indexOfValue( array, value ) {
|
|
|
|
for ( var i = 0, il = array.length; i < il; i ++ ) {
|
|
|
|
if ( array[ i ] === value ) {
|
|
|
|
return i;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
function indexOfPropertyWithValue( array, property, value ) {
|
|
|
|
for ( var i = 0, il = array.length; i < il; i ++ ) {
|
|
|
|
if ( array[ i ][ property ] === value ) {
|
|
|
|
return i;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
/*===================================================
|
|
|
|
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;
|
|
|
|
if ( this.scene ) {
|
|
|
|
this.visualGeometry = new THREE.BoxGeometry( 1, 1, 1 );
|
|
this.visualMaterial = new THREE.MeshBasicMaterial( { color: 0xFF0066, wireframe: true, wireframeLinewidth: 1 } );
|
|
|
|
}
|
|
|
|
// properties
|
|
|
|
this.objects = [];
|
|
this.objectsMap = {};
|
|
this.objectsData = [];
|
|
this.objectsDeferred = [];
|
|
|
|
this.depthMax = isNumber( parameters.depthMax ) ? parameters.depthMax : Infinity;
|
|
this.objectsThreshold = isNumber( parameters.objectsThreshold ) ? parameters.objectsThreshold : 8;
|
|
this.overlapPct = isNumber( parameters.overlapPct ) ? parameters.overlapPct : 0.15;
|
|
this.undeferred = parameters.undeferred || false;
|
|
|
|
this.root = parameters.root instanceof THREE.OctreeNode ? parameters.root : new THREE.OctreeNode( parameters );
|
|
|
|
};
|
|
|
|
THREE.Octree.prototype = {
|
|
|
|
update: function () {
|
|
|
|
// add any deferred objects that were waiting for render cycle
|
|
|
|
if ( this.objectsDeferred.length > 0 ) {
|
|
|
|
for ( var i = 0, il = this.objectsDeferred.length; i < il; i ++ ) {
|
|
|
|
var deferred = this.objectsDeferred[ i ];
|
|
|
|
this.addDeferred( deferred.object, deferred.options );
|
|
|
|
}
|
|
|
|
this.objectsDeferred.length = 0;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
add: function ( object, options ) {
|
|
|
|
// add immediately
|
|
|
|
if ( this.undeferred ) {
|
|
|
|
this.updateObject( object );
|
|
|
|
this.addDeferred( object, options );
|
|
|
|
} else {
|
|
|
|
// defer add until update called
|
|
|
|
this.objectsDeferred.push( { object: object, options: options } );
|
|
|
|
}
|
|
|
|
},
|
|
|
|
addDeferred: function ( object, options ) {
|
|
|
|
var i, l,
|
|
geometry,
|
|
faces,
|
|
useFaces,
|
|
vertices,
|
|
useVertices,
|
|
objectData;
|
|
|
|
// ensure object is not object data
|
|
|
|
if ( object instanceof THREE.OctreeObjectData ) {
|
|
|
|
object = object.object;
|
|
|
|
}
|
|
|
|
// check uuid to avoid duplicates
|
|
|
|
if ( !object.uuid ) {
|
|
|
|
object.uuid = THREE.Math.generateUUID();
|
|
|
|
}
|
|
|
|
if ( !this.objectsMap[ object.uuid ] ) {
|
|
|
|
// store
|
|
|
|
this.objects.push( object );
|
|
this.objectsMap[ object.uuid ] = object;
|
|
|
|
// check options
|
|
|
|
if ( options ) {
|
|
|
|
useFaces = options.useFaces;
|
|
useVertices = options.useVertices;
|
|
|
|
}
|
|
|
|
if ( useVertices === true ) {
|
|
|
|
geometry = object.geometry;
|
|
vertices = geometry.vertices;
|
|
|
|
for ( i = 0, l = vertices.length; i < l; i ++ ) {
|
|
|
|
this.addObjectData( object, vertices[ i ] );
|
|
|
|
}
|
|
|
|
} else if ( useFaces === true ) {
|
|
|
|
geometry = object.geometry;
|
|
faces = geometry.faces;
|
|
|
|
for ( i = 0, l = faces.length; i < l; i ++ ) {
|
|
|
|
this.addObjectData( object, faces[ i ] );
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
this.addObjectData( object );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
addObjectData: function ( object, part ) {
|
|
|
|
var objectData = new THREE.OctreeObjectData( object, part );
|
|
|
|
// 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;
|
|
|
|
}
|
|
|
|
// check uuid
|
|
|
|
if ( this.objectsMap[ object.uuid ] ) {
|
|
|
|
this.objectsMap[ object.uuid ] = undefined;
|
|
|
|
// check and remove from objects, nodes, and data lists
|
|
|
|
index = indexOfValue( this.objects, object );
|
|
|
|
if ( index !== -1 ) {
|
|
|
|
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 = indexOfValue( this.objectsData, objectData );
|
|
|
|
if ( index !== -1 ) {
|
|
|
|
this.objectsData.splice( index, 1 );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if ( this.objectsDeferred.length > 0 ) {
|
|
|
|
// check and remove from deferred
|
|
|
|
index = indexOfPropertyWithValue( this.objectsDeferred, 'object', object );
|
|
|
|
if ( index !== -1 ) {
|
|
|
|
this.objectsDeferred.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, { useFaces: objectData.faces, useVertices: objectData.vertices } );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
rebuild: function () {
|
|
|
|
var i, l,
|
|
node,
|
|
object,
|
|
objectData,
|
|
indexOctant,
|
|
indexOctantLast,
|
|
objectsUpdate = [];
|
|
|
|
// check all object data for changes in position
|
|
// assumes all object matrices are up to date
|
|
|
|
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 );
|
|
|
|
}
|
|
|
|
},
|
|
|
|
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();
|
|
|
|
}
|
|
|
|
},
|
|
|
|
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 ( !( radius > 0 ) ) {
|
|
|
|
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 = indexOfValue( resultsObjectsIndices, object );
|
|
|
|
// if needed, create new result data
|
|
|
|
if ( resultObjectIndex === -1 ) {
|
|
|
|
resultData = {
|
|
object: object,
|
|
faces: [],
|
|
vertices: []
|
|
};
|
|
|
|
results.push( resultData );
|
|
|
|
resultsObjectsIndices.push( object );
|
|
|
|
} else {
|
|
|
|
resultData = results[ resultObjectIndex ];
|
|
|
|
}
|
|
|
|
// object data has faces or vertices, add to list
|
|
|
|
if ( objectData.faces ) {
|
|
|
|
resultData.faces.push( objectData.faces );
|
|
|
|
} else if ( objectData.vertices ) {
|
|
|
|
resultData.vertices.push( objectData.vertices );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
results = objects;
|
|
|
|
}
|
|
|
|
return results;
|
|
|
|
},
|
|
|
|
setRoot: function ( root ) {
|
|
|
|
if ( root instanceof THREE.OctreeNode ) {
|
|
|
|
// store new root
|
|
|
|
this.root = root;
|
|
|
|
// update properties
|
|
|
|
this.root.updateProperties();
|
|
|
|
}
|
|
|
|
},
|
|
|
|
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, part ) {
|
|
|
|
// properties
|
|
|
|
this.object = object;
|
|
|
|
// handle part by type
|
|
|
|
if ( part instanceof THREE.Face3 ) {
|
|
|
|
this.faces = part;
|
|
this.face3 = true;
|
|
this.utilVec31FaceBounds = new THREE.Vector3();
|
|
|
|
} else if ( part instanceof THREE.Vector3 ) {
|
|
|
|
this.vertices = part;
|
|
|
|
}
|
|
|
|
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.face3 ) {
|
|
|
|
this.radius = this.getFace3BoundingRadius( this.object, this.faces );
|
|
this.position.copy( this.faces.centroid ).applyMatrix4( this.object.matrixWorld );
|
|
|
|
} else if ( this.vertices ) {
|
|
|
|
this.radius = this.object.material.size || 1;
|
|
this.position.copy( this.vertices ).applyMatrix4( this.object.matrixWorld );
|
|
|
|
} else {
|
|
|
|
if ( this.object.geometry ) {
|
|
|
|
if ( this.object.geometry.boundingSphere === null ) {
|
|
|
|
this.object.geometry.computeBoundingSphere();
|
|
|
|
}
|
|
|
|
this.radius = this.object.geometry.boundingSphere.radius;
|
|
this.position.copy( this.object.geometry.boundingSphere.center ).applyMatrix4( this.object.matrixWorld );
|
|
|
|
} else {
|
|
|
|
this.radius = this.object.boundRadius;
|
|
this.position.setFromMatrixPosition( this.object.matrixWorld );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
this.radius = this.radius * Math.max( this.object.scale.x, this.object.scale.y, this.object.scale.z );
|
|
|
|
},
|
|
|
|
getFace3BoundingRadius: function ( object, face ) {
|
|
|
|
if ( face.centroid === undefined ) face.centroid = new THREE.Vector3();
|
|
|
|
var geometry = object.geometry || object,
|
|
vertices = geometry.vertices,
|
|
centroid = face.centroid,
|
|
va = vertices[ face.a ], vb = vertices[ face.b ], vc = vertices[ face.c ],
|
|
centroidToVert = this.utilVec31FaceBounds,
|
|
radius;
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
/*===================================================
|
|
|
|
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 = 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( this.tree.visualGeometry, this.tree.visualMaterial );
|
|
this.visual.scale.set( this.radiusOverlap * 2, this.radiusOverlap * 2, this.radiusOverlap * 2 );
|
|
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 ) {
|
|
|
|
node.indexOctant = indexOctant;
|
|
|
|
if ( indexOfValue( this.nodesIndices, indexOctant ) === -1 ) {
|
|
|
|
this.nodesIndices.push( indexOctant );
|
|
|
|
}
|
|
|
|
this.nodesByIndex[ indexOctant ] = node;
|
|
|
|
if ( node.parent !== this ) {
|
|
|
|
node.setParent( this );
|
|
|
|
}
|
|
|
|
},
|
|
|
|
removeNode: function ( indexOctant ) {
|
|
|
|
var index,
|
|
node;
|
|
|
|
index = indexOfValue( this.nodesIndices, 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 );
|
|
|
|
} else if ( indexOctant < -1 && this.parent instanceof THREE.OctreeNode ) {
|
|
|
|
// if object lies outside bounds, add to parent node
|
|
|
|
this.parent.addObject( object );
|
|
|
|
} else {
|
|
|
|
// add to this objects list
|
|
|
|
index = indexOfValue( this.objects, 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 = indexOfValue( this.objects, object );
|
|
|
|
if ( index !== -1 ) {
|
|
|
|
this.objects.splice( index, 1 );
|
|
object.node = undefined;
|
|
|
|
removeData.objectsDataRemoved.push( object );
|
|
|
|
removeData.searchComplete = objectRemoved = true;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// search each object data for object and remove (slow)
|
|
|
|
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 ( !objectData.faces && !objectData.vertices ) {
|
|
|
|
removeData.searchComplete = true;
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// if object data removed and this is not on nodes removed from
|
|
|
|
if ( objectRemoved === true ) {
|
|
|
|
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 );
|
|
|
|
} else if ( indexOctant < -1 ) {
|
|
|
|
// lies outside radius
|
|
|
|
objectsExpand.push( object );
|
|
objectsExpandOctants.push( indexOctant );
|
|
|
|
} else {
|
|
|
|
// lies across bounds between octants
|
|
|
|
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.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 = octants[ i ];
|
|
|
|
// if object contained by octant, branch this tree
|
|
|
|
if ( indexOctant > -1 ) {
|
|
|
|
node = this.branch( indexOctant );
|
|
|
|
node.addObject( object );
|
|
|
|
} 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 ];
|
|
|
|
} 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.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 = octants[ i ] ;
|
|
|
|
// 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 {
|
|
|
|
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
|
|
|
|
if ( objectData instanceof THREE.OctreeObjectData ) {
|
|
|
|
radiusObj = objectData.radius;
|
|
|
|
positionObj = objectData.position;
|
|
|
|
// update object data position last
|
|
|
|
objectData.positionLast.copy( positionObj );
|
|
|
|
} 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
|
|
|
|
if ( deltaX - radiusObj > -overlap ) {
|
|
|
|
// x right
|
|
|
|
indexOctant = indexOctant | 1;
|
|
|
|
} else if ( !( deltaX + radiusObj < overlap ) ) {
|
|
|
|
// x left
|
|
|
|
objectData.indexOctant = this.tree.INDEX_INSIDE_CROSS;
|
|
return objectData.indexOctant;
|
|
|
|
}
|
|
|
|
if ( deltaY - radiusObj > -overlap ) {
|
|
|
|
// y right
|
|
|
|
indexOctant = indexOctant | 2;
|
|
|
|
} else if ( !( deltaY + radiusObj < overlap ) ) {
|
|
|
|
// y left
|
|
|
|
objectData.indexOctant = this.tree.INDEX_INSIDE_CROSS;
|
|
return objectData.indexOctant;
|
|
|
|
}
|
|
|
|
|
|
if ( deltaZ - radiusObj > -overlap ) {
|
|
|
|
// z right
|
|
|
|
indexOctant = indexOctant | 4;
|
|
|
|
} else if ( !( deltaZ + radiusObj < overlap ) ) {
|
|
|
|
// z left
|
|
|
|
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 ) ); |