UNPKG

three-boids

Version:
435 lines (359 loc) 13.4 kB
import * as THREE from 'three' import OctreeNode from "./OctreeNode"; export default class Octree { constructor(worldObjects,minNodeSize) { //create a new box const bounds = this.#setUpBounds(worldObjects) //create rootNode this.rootNode= new OctreeNode(bounds,minNodeSize) //add objects to the tree this.#addObjects(worldObjects) } /**Grows a bounding box to cover all the objects in the array * * @param {*} worldObjects * @returns Box3 */ #setUpBounds(worldObjects) { //create a new box const bounds = new THREE.Box3() //loop through all meshes and grow the box to encapsulate all worldObjects.forEach(mesh => { //grow the bounds bounds.expandByObject(mesh) }); //copy the size of the bounds const size= new THREE.Vector3() bounds.getSize(size) //find the max axis value of the size vector const maxSize= Math.max(...size) //new box size const sizeVector= new THREE.Vector3(maxSize,maxSize,maxSize) //box starts from center, multiply by 0.5 to account for extra length sizeVector.multiplyScalar(0.5) //get the center of the box const boundsCenter=new THREE.Vector3() bounds.getCenter(boundsCenter) bounds.set(boundsCenter.clone().sub(sizeVector),boundsCenter.add(sizeVector)) return bounds } /** * * @param {*} worldObjects */ #addObjects(worldObjects) { worldObjects.forEach(obj => { this.rootNode.addObject(obj) }); } /** * Recursively checks children, returns a list of THREE.JS Meshes that intersect with the provided object or box3 * * @param {*} mesh * @param {*} scene * @returns */ getObjects(obj,scene) { let box =obj; if(obj.isBox3==null||obj.isBox3==false){ box= new THREE.Box3().setFromObject(obj) } // console.log(box) const boundingSphere= new THREE.Sphere() box.getBoundingSphere(boundingSphere) const intersections=this.intersectsObject(this.rootNode,boundingSphere,scene) const uniqueIntersections = [...new Map(intersections.map(item => [item.uuid, item])).values()] return uniqueIntersections } /** * Recursively checks children, returns the layers of the thee.js meshes * @param {*} mesh * @param {*} scene * @returns */ getLayers(mesh,scene) { const box= new THREE.Box3().setFromObject(mesh) const testLayers=this.intersectsLayers(this.rootNode,box,scene) const unique = [...new Map(testLayers.map(item => [item.mask, item])).values()] return unique } /** * * Recursively checks children, returns true if the object is intersecting with any object * @param {*} mesh * @param {*} scene * @returns */ intersects(mesh,scene) { const box= new THREE.Box3().setFromObject(mesh) return this.isIntersecting(this.rootNode,box,scene) } /** * Recursively checks children, returns a list of THREE.JS Meshes that intersect with the provided box3 * @param {OctreeNode} node * @param {Box3} box * @param {Object3d} scene * @returns Three.Mesh[] */ intersectsObject(node,sphere) { const array= [] //check if bounding box intersects if(node.nodeBounds.intersectsSphere(sphere)&&node.containsObject) { //if node is leaf, return with world objects(if there are any) if(node.children==null) { return node.worldObjects } //loop through children for(let i =0; i<8; i++) { //recursivly checks children that fit criteria: // - child bounds must intersect with the box // - node must contain an object if(node.childBounds[i].intersectsSphere(sphere)) { //recurisve call const value= this.intersectsObject(node.children[i],sphere) if(value) { // NOTE: not sure if this is the best option. multiple calls may outweigh the O()time of map // figured map is pro const unique = [...new Map(value.map(item => [item.uuid, item])).values()] array.push(...unique) } } } } return array } /** * Recursively checks children, returns the layers of the thee.js meshes * @param {OctreeNode} node * @param {Box3} box * @param {Object3d} scene * @returns int[] */ intersectsLayers(node,box,scene) { // console.log(node) const array= [] // if(node.isBox3) //check if bounding box intersects if(node.nodeBounds.intersectsBox(box)&&node.containsObject) { //if node is leaf, return with world objects(if there are any) if(node.children==null) { //generates box if there is a scene variable if(scene){ this.debugDraw(node.nodeBounds,"green",scene) this.debugDraw(box,"red",scene) } const temp=[] if(node.worldObjects.length>0) { node.worldObjects.forEach((worldObj)=>{ temp.push(worldObj.layers); }) } return temp } //loop through children for(let i =0; i<8; i++) { //recursivly checks children that fit criteria: // - child bounds must intersect with the box // - node must contain an object if(node.childBounds[i].intersectsBox(box)) { //recurisve call const value= this.intersectsLayers(node.children[i],box,scene) if(value) { // NOTE: not sure if this is the best option. multiple calls may outweigh the O()time of map // figured map is pro // const unique = [...new Map(value.map(item => [item.uuid, item])).values()] array.push(...value) } } } } return array } /** * Recursively checks children, returns true if the object is intersecting with any object * * @param {OctreeNode} node * @param {Box3} box * @param {Object3d} scene * @returns Three.Mesh[] */ isIntersecting(node,box,scene) { let value =false // if(node.isBox3) //check if bounding box intersects if(node.nodeBounds.intersectsBox(box)&&node.containsObject) { //if node is leaf, return with world objects(if there are any) if(node.children==null) { //generates box if there is a scene variable if(scene){ this.debugDraw(node.nodeBounds,"green",scene) this.debugDraw(box,"red",scene) } return true } //loop through children for(let i =0; i<8; i++) { //recursivly checks children that fit criteria: // - child bounds must intersect with the box // - node must contain an object if(node.childBounds[i].intersectsBox(box)) { //recurisve call const holder = this.isIntersecting(node.children[i],box,scene) if(holder){value=true} } } } return value } //debug /** * Recursively adds box visualizations of the octree to the scene * * @param {*} node * @param {*} scene * @param {*} count * @returns */ #drawBox(node,scene,count) { // console.log('drawing box',node) count=(count!=null)?count:1 if(node.children==null) { const center= new THREE.Vector3() const scale= new THREE.Vector3() node.nodeBounds.getCenter(center) node.nodeBounds.getSize(scale) scale.multiplyScalar(0.999) const box=new THREE.Box3().setFromCenterAndSize(center,scale) let color // console.log(`count:${count}`) switch(count) { case 1: color="#ffffff" break; case 2: color="#c8c2ff" break; case 3: color="#7363ff" break; case 4: color="#1a00ff" break; case 5: color="#ff00ec" break; case 6: color="#ff004b" break; case 7: color="#ff0000" break; case 8: color="#ffd000" break; case 9: color="#a4ff00" break; default: color='#00ff87' } const helper = new THREE.Box3Helper( box, color) node.boxHelper=helper scene.add(helper) return } node.children.forEach(child=> { this.#drawBox(child,scene,count+1) } ) } /** * Recursively removes box visualization from the scene * * @param {*} node * @param {*} scene * @returns */ #removeBox(node,scene) { if(node.children==null) { if(node.boxHelper) { scene.remove(node.boxHelper) node.boxHelper.dispose() node.boxHelper=null } return } node.children.forEach(child=> { this.#removeBox(child,scene) } ) } /** * Adds the current octree visualization to the scene * * @param {*} scene */ showOctree(scene) { this.#drawBox(this.rootNode,scene) } /** * Removes the octree visualization from the scene * * @param {*} scene */ hideOctree(scene) { this.#removeBox(this.rootNode,scene) } debug(gui,scene) { const folder= gui.addFolder().title('Enviroment Optimizations') const debug={} debug.showOctree=false console.log(this) folder.add(debug,'showOctree').name('View Octree Structure').onChange(bool=>{ if(bool) { this.showOctree(scene) } else { this.hideOctree(scene) } }) } }