diff --git a/README.md b/README.md index a1a0cb0aa..be3f7dd14 100644 --- a/README.md +++ b/README.md @@ -77,19 +77,18 @@ Using pre-made functions ```js // Import via ES6 modules import * as THREE from 'three'; -import { computeBoundsTree, disposeBoundsTree, acceleratedRaycast } from 'three-mesh-bvh'; - -// Or UMD -const { computeBoundsTree, disposeBoundsTree, acceleratedRaycast } = window.MeshBVHLib; - +import { + computeBoundsTree, disposeBoundsTree, + computeBatchedBoundsTree, disposeBatchedBoundsTree, acceleratedRaycast, +} from 'three-mesh-bvh'; // Add the extension functions THREE.BufferGeometry.prototype.computeBoundsTree = computeBoundsTree; THREE.BufferGeometry.prototype.disposeBoundsTree = disposeBoundsTree; THREE.Mesh.prototype.raycast = acceleratedRaycast; -THREE.BatchedMesh.prototype.computeBoundsTree = computeBoundsTree; -THREE.BatchedMesh.prototype.disposeBoundsTree = disposeBoundsTree; +THREE.BatchedMesh.prototype.computeBoundsTree = computeBatchedBoundsTree; +THREE.BatchedMesh.prototype.disposeBoundsTree = disposeBatchedBoundsTree; THREE.BatchedMesh.prototype.raycast = acceleratedRaycast; // Generate geometry and associated BVH @@ -110,10 +109,6 @@ Or manually building the BVH import * as THREE from 'three'; import { MeshBVH, acceleratedRaycast } from 'three-mesh-bvh'; -// Or UMD -const { MeshBVH, acceleratedRaycast } = window.MeshBVHLib; - - // Add the raycast function. Assumes the BVH is available on // the `boundsTree` variable THREE.Mesh.prototype.raycast = acceleratedRaycast; @@ -845,14 +840,13 @@ If the `Raycaster` member `firstHitOnly` is set to true then the [.acceleratedRa ### .computeBoundsTree ```js -computeBoundsTree( options : Object ) : void +computeBoundsTree( options? : Object ) : void ``` -A pre-made BufferGeometry and BatchedMesh extension function that builds a new BVH, assigns it to `boundsTree` for BufferGeometry or `boundsTrees` for BatchedMesh, and applies the new index buffer to the geometry. Comparable to `computeBoundingBox` and `computeBoundingSphere`. +A pre-made BufferGeometry extension function that builds a new BVH, assigns it to `boundsTree` for BufferGeometry, and applies the new index buffer to the geometry. Comparable to `computeBoundingBox` and `computeBoundingSphere`. ```js THREE.BufferGeometry.prototype.computeBoundsTree = computeBoundsTree; -THREE.BatchedMesh.prototype.computeBoundsTree = computeBoundsTree; ``` ### .disposeBoundsTree @@ -861,11 +855,34 @@ THREE.BatchedMesh.prototype.computeBoundsTree = computeBoundsTree; disposeBoundsTree() : void ``` -A BufferGeometry and BatchedMesh extension function that disposes of the BVH. +A BufferGeometry extension function that disposes of the BVH. ```js THREE.BufferGeometry.prototype.disposeBoundsTree = disposeBoundsTree; -THREE.BatchedMesh.prototype.disposeBoundsTree = disposeBoundsTree; +``` + +### .computeBatchedBoundsTree + +```js +computeBatchedBoundsTree( index = - 1 : Number, options? : Object ) : void +``` + +Equivalent of `computeBoundsTree` for BatchedMesh. Calling this generates a `BatchedMesh.boundsTrees` array if it doesn't exist and assigns the newly generated BVHs. If `index` is -1 then BVHs for all available geometry are generated. Otherwise only the BVH for the geometry at the given index is generated. + +```js +THREE.BatchedMesh.prototype.computeBoundsTree = computeBatchedBoundsTree; +``` + +### .disposeBatchedBoundsTree + +```js +disposeBatchedBoundsTree( index = - 1 : Number, options? : Object ) : void +``` + +Equivalent of `disposeBoundsTree` for BatchedMesh. Calling this sets entries in `BatchedMesh.boundsTrees` array to null. If `index` is -1 then BVHs are disposed. Otherwise only the BVH for the geometry at the given index is disposed. + +```js +THREE.BatchedMesh.prototype.disposeBoundsTree = disposeBatchedBoundsTree; ``` ### .acceleratedRaycast diff --git a/example/batchedMesh.js b/example/batchedMesh.js index 285c7f094..df46387ca 100644 --- a/example/batchedMesh.js +++ b/example/batchedMesh.js @@ -3,17 +3,21 @@ import * as dat from 'three/examples/jsm/libs/lil-gui.module.min.js'; import * as THREE from 'three'; import { acceleratedRaycast, computeBoundsTree, disposeBoundsTree, + computeBatchedBoundsTree, disposeBatchedBoundsTree, CENTER, SAH, AVERAGE, } from '..'; THREE.Mesh.prototype.raycast = acceleratedRaycast; THREE.BufferGeometry.prototype.computeBoundsTree = computeBoundsTree; THREE.BufferGeometry.prototype.disposeBoundsTree = disposeBoundsTree; + THREE.BatchedMesh.prototype.raycast = acceleratedRaycast; -THREE.BatchedMesh.prototype.computeBoundsTree = computeBoundsTree; -THREE.BatchedMesh.prototype.disposeBoundsTree = disposeBoundsTree; +THREE.BatchedMesh.prototype.computeBoundsTree = computeBatchedBoundsTree; +THREE.BatchedMesh.prototype.disposeBoundsTree = disposeBatchedBoundsTree; -const bgColor = 0x263238 / 2; +const bgColor = 0xcfd8dc; +const meshColor = 0x263238; +const lineColor = 0xd81b60; let renderer, scene, stats, camera; let material, containerObj, batchedMesh; @@ -61,15 +65,15 @@ function init() { // scene setup scene = new THREE.Scene(); - scene.fog = new THREE.Fog( 0x263238 / 2, 40, 80 ); + scene.fog = new THREE.Fog( bgColor, 40, 100 ); - const light = new THREE.DirectionalLight( 0xffffff, 0.5 ); + const light = new THREE.DirectionalLight( 0xffffff, 1.5 ); light.position.set( 1, 1, 1 ); scene.add( light ); - scene.add( new THREE.AmbientLight( 0xffffff, 0.4 ) ); + scene.add( new THREE.AmbientLight( 0xffffff, 1.2 ) ); containerObj = new THREE.Object3D(); - material = new THREE.MeshPhongMaterial( { color: 0xE91E63 } ); + material = new THREE.MeshPhongMaterial( { color: meshColor } ); containerObj.scale.multiplyScalar( 10 ); containerObj.rotation.x = 10.989999999999943; containerObj.rotation.y = 10.989999999999943; @@ -189,13 +193,13 @@ function addRaycaster() { // Objects const obj = new THREE.Object3D(); - const material = new THREE.MeshBasicMaterial( { color: 0xffffff } ); + const material = new THREE.MeshBasicMaterial( { color: lineColor } ); const origMesh = new THREE.Mesh( sphere, material ); const hitMesh = new THREE.Mesh( sphere, material ); hitMesh.scale.multiplyScalar( 0.25 ); origMesh.scale.multiplyScalar( 0.5 ); - const cylinderMesh = new THREE.Mesh( cylinder, new THREE.MeshBasicMaterial( { color: 0xffffff, transparent: true, opacity: 0.25 } ) ); + const cylinderMesh = new THREE.Mesh( cylinder, new THREE.MeshBasicMaterial( { color: lineColor, transparent: true, opacity: 0.5 } ) ); // Init the rotation root obj.add( cylinderMesh ); @@ -288,7 +292,7 @@ function updateFromOptions() { if ( params.mesh.useBoundsTree && ! batchedMesh.boundsTrees ) { console.time( 'computing bounds tree' ); - batchedMesh.computeBoundsTree( { + batchedMesh.computeBoundsTree( - 1, { maxLeafTris: 5, strategy: parseFloat( params.mesh.splitStrategy ), } ); diff --git a/src/index.d.ts b/src/index.d.ts index 82a2f10bc..3902d41a9 100644 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -199,10 +199,14 @@ export class MeshBVHHelper extends Group { // THREE.js Extensions -export function computeBoundsTree( options?: MeshBVHOptions ): MeshBVH | MeshBVH[]; +export function computeBoundsTree( options?: MeshBVHOptions ): MeshBVH; export function disposeBoundsTree(): void; +export function computeBatchedBoundsTree( index?: Number, options?: MeshBVHOptions ): MeshBVH | MeshBVH[]; + +export function disposeBatchedBoundsTree( index?: Number ): void; + export function acceleratedRaycast( raycaster: Raycaster, intersects: Array @@ -216,9 +220,9 @@ declare module 'three' { } export interface BatchedMesh { - boundsTrees?: MeshBVH[]; - computeBoundsTree: typeof computeBoundsTree; - disposeBoundsTree: typeof disposeBoundsTree; + boundsTrees?: Array; + computeBoundsTree: typeof computeBatchedBoundsTree; + disposeBoundsTree: typeof disposeBatchedBoundsTree; } export interface Raycaster { diff --git a/src/index.js b/src/index.js index 0c32fd761..d6663c59a 100644 --- a/src/index.js +++ b/src/index.js @@ -2,7 +2,7 @@ export { MeshBVH } from './core/MeshBVH.js'; export { MeshBVHHelper } from './objects/MeshBVHHelper.js'; export { CENTER, AVERAGE, SAH, NOT_INTERSECTED, INTERSECTED, CONTAINED } from './core/Constants.js'; export { getBVHExtremes, estimateMemoryInBytes, getJSONStructure, validateBounds } from './debug/Debug.js'; -export { acceleratedRaycast, computeBoundsTree, disposeBoundsTree } from './utils/ExtensionUtilities.js'; +export * from './utils/ExtensionUtilities.js'; export { getTriangleHitPointInfo } from './utils/TriangleUtilities.js'; export * from './math/ExtendedTriangle.js'; export * from './math/OrientedBox.js'; diff --git a/src/utils/ExtensionUtilities.js b/src/utils/ExtensionUtilities.js index c69c2cf46..61b787d22 100644 --- a/src/utils/ExtensionUtilities.js +++ b/src/utils/ExtensionUtilities.js @@ -151,30 +151,53 @@ function acceleratedMeshRaycast( raycaster, intersects ) { export function computeBoundsTree( options = {} ) { - if ( this.isBatchedMesh ) { + this.boundsTree = new MeshBVH( this, options ); + return this.boundsTree; - if ( ! IS_REVISION_166 ) { +} - console.error( 'Three r166+ is required.' ); - return; +export function disposeBoundsTree() { - } + this.boundsTree = null; - if ( options.indirect ) { +} - console.warn( '"Indirect" is set to false because it is not supported for BatchedMesh.' ); +export function computeBatchedBoundsTree( index = - 1, options = {} ) { - } + if ( ! IS_REVISION_166 ) { - options = { - ...options, - indirect: false, - range: null - }; + throw new Error( 'BatchedMesh: Three r166+ is required to compute bounds trees.' ); - const drawRanges = this._drawRanges; - const geometryCount = this._geometryCount; - const boundsTrees = []; + } + + if ( options.indirect ) { + + console.warn( '"Indirect" is set to false because it is not supported for BatchedMesh.' ); + + } + + options = { + ...options, + indirect: false, + range: null + }; + + const drawRanges = this._drawRanges; + const geometryCount = this._geometryCount; + if ( ! this.boundsTrees ) { + + this.boundsTrees = new Array( geometryCount ).fill( null ); + + } + + const boundsTrees = this.boundsTrees; + while ( boundsTrees.length < geometryCount ) { + + boundsTrees.push( null ); + + } + + if ( index < 0 ) { for ( let i = 0; i < geometryCount; i ++ ) { @@ -183,25 +206,36 @@ export function computeBoundsTree( options = {} ) { } - this.boundsTrees = boundsTrees; - return this.boundsTrees; + return boundsTrees; - } + } else { - this.boundsTree = new MeshBVH( this, options ); - return this.boundsTree; + if ( index < drawRanges.length ) { + + options.range = drawRanges[ index ]; + boundsTrees[ index ] = new MeshBVH( this.geometry, options ); + + } + + return boundsTrees[ index ] || null; + + } } -export function disposeBoundsTree() { +export function disposeBatchedBoundsTree( index = - 1 ) { - if ( this.isBatchedMesh ) { + if ( index < 0 ) { - this.boundsTrees = null; + this.boundsTrees.fill( null ); } else { - this.boundsTree = null; + if ( index < this.boundsTree.length ) { + + this.boundsTrees[ index ] = null; + + } } diff --git a/test/RandomRaycasts.test.js b/test/RandomRaycasts.test.js index a2d4293be..981db2424 100644 --- a/test/RandomRaycasts.test.js +++ b/test/RandomRaycasts.test.js @@ -16,6 +16,8 @@ import { acceleratedRaycast, computeBoundsTree, disposeBoundsTree, + computeBatchedBoundsTree, + disposeBatchedBoundsTree, CENTER, SAH, AVERAGE, @@ -26,8 +28,8 @@ Mesh.prototype.raycast = acceleratedRaycast; BufferGeometry.prototype.computeBoundsTree = computeBoundsTree; BufferGeometry.prototype.disposeBoundsTree = disposeBoundsTree; BatchedMesh.prototype.raycast = acceleratedRaycast; -BatchedMesh.prototype.computeBoundsTree = computeBoundsTree; -BatchedMesh.prototype.disposeBoundsTree = disposeBoundsTree; +BatchedMesh.prototype.computeBoundsTree = computeBatchedBoundsTree; +BatchedMesh.prototype.disposeBoundsTree = disposeBatchedBoundsTree; describe( 'Random CENTER intersections', () => runRandomTests( { strategy: CENTER } ) ); describe( 'Random Interleaved CENTER intersections', () => runRandomTests( { strategy: CENTER, interleaved: true } ) ); @@ -144,14 +146,14 @@ function runRandomTests( options ) { const geoId = batchedMesh.addGeometry( geo ); if ( options.onlyOneGeo ) { - batchedMesh.computeBoundsTree( options ); + batchedMesh.computeBoundsTree( - 1, options ); } const geo2Id = batchedMesh.addGeometry( geo2 ); if ( ! options.onlyOneGeo ) { - batchedMesh.computeBoundsTree( options ); + batchedMesh.computeBoundsTree( - 1, options ); } diff --git a/test/TypescriptImportTest.ts b/test/TypescriptImportTest.ts index 7ae6d16b6..9c3d3a617 100644 --- a/test/TypescriptImportTest.ts +++ b/test/TypescriptImportTest.ts @@ -1,11 +1,13 @@ import { BufferGeometry, Mesh, Raycaster, BatchedMesh } from 'three'; -import { acceleratedRaycast, computeBoundsTree, disposeBoundsTree } from '../src/index'; +import { acceleratedRaycast, computeBoundsTree, disposeBoundsTree, computeBatchedBoundsTree, disposeBatchedBoundsTree } from '../src/index'; Mesh.prototype.raycast = acceleratedRaycast; BufferGeometry.prototype.computeBoundsTree = computeBoundsTree; BufferGeometry.prototype.disposeBoundsTree = disposeBoundsTree; -BatchedMesh.prototype.computeBoundsTree = computeBoundsTree; -BatchedMesh.prototype.disposeBoundsTree = disposeBoundsTree; + +BatchedMesh.prototype.raycast = acceleratedRaycast; +BatchedMesh.prototype.computeBoundsTree = computeBatchedBoundsTree; +BatchedMesh.prototype.disposeBoundsTree = disposeBatchedBoundsTree; const mesh = new Mesh(); mesh.geometry.computeBoundsTree();