three-mesh-bvh
Version:
A BVH implementation to speed up raycasting against three.js meshes.
166 lines (115 loc) • 4.16 kB
JavaScript
/** @import { BufferGeometry } from 'three' */
import { Box3, BufferAttribute } from 'three';
import { MeshBVH } from '../core/MeshBVH.js';
import { WorkerBase } from './utils/WorkerBase.js';
/**
* Helper class for generating a MeshBVH for a given geometry in asynchronously in a worker. The
* geometry position and index buffer attribute `ArrayBuffers` are transferred to the Worker while
* the BVH is being generated meaning the geometry will be unavailable to use while the BVH is
* being processed unless `SharedArrayBuffers` are used. They will be automatically replaced when
* the MeshBVH is finished generating.
*
* _NOTE It's best to reuse a single instance of this class to avoid the overhead of instantiating
* a new Worker._
*
* @extends WorkerBase
*/
export class GenerateMeshBVHWorker extends WorkerBase {
constructor() {
const worker = new Worker( new URL( './generateMeshBVH.worker.js', import.meta.url ), { type: 'module' } );
super( worker );
this.name = 'GenerateMeshBVHWorker';
}
/**
* Flag indicating whether or not a BVH is already being generated in the worker.
* @name running
* @memberof GenerateMeshBVHWorker
* @instance
* @type {boolean}
*/
/**
* Generates a `MeshBVH` instance for the given geometry with the given options in a WebWorker.
* Returns a Promise that resolves with the generated `MeshBVH`. Throws if already running.
*
* @name generate
* @memberof GenerateMeshBVHWorker
* @instance
* @function
* @param {BufferGeometry} geometry
* @param {Object} [options] - Same options accepted by the `MeshBVH` constructor.
* @param {function(number): void} [options.onProgress] - Callback invoked with a `[0, 1]`
* progress value as the BVH is built.
* @returns {Promise<MeshBVH>}
*/
/**
* Terminates the worker.
*
* @name dispose
* @memberof GenerateMeshBVHWorker
* @instance
* @function
*/
runTask( worker, geometry, options = {} ) {
return new Promise( ( resolve, reject ) => {
if (
geometry.getAttribute( 'position' ).isInterleavedBufferAttribute ||
geometry.index && geometry.index.isInterleavedBufferAttribute
) {
throw new Error( 'GenerateMeshBVHWorker: InterleavedBufferAttribute are not supported for the geometry attributes.' );
}
worker.onerror = e => {
reject( new Error( `GenerateMeshBVHWorker: ${ e.message }` ) );
};
worker.onmessage = e => {
const { data } = e;
if ( data.error ) {
reject( new Error( data.error ) );
worker.onmessage = null;
} else if ( data.serialized ) {
const { serialized, position } = data;
const bvh = MeshBVH.deserialize( serialized, geometry, { setIndex: false } );
const boundsOptions = Object.assign( {
setBoundingBox: true,
}, options );
// we need to replace the arrays because they're neutered entirely by the
// webworker transfer.
geometry.attributes.position.array = position;
if ( serialized.index ) {
if ( geometry.index ) {
geometry.index.array = serialized.index;
} else {
const newIndex = new BufferAttribute( serialized.index, 1, false );
geometry.setIndex( newIndex );
}
}
if ( boundsOptions.setBoundingBox ) {
geometry.boundingBox = bvh.getBoundingBox( new Box3() );
}
if ( options.onProgress ) {
options.onProgress( data.progress );
}
resolve( bvh );
worker.onmessage = null;
} else if ( options.onProgress ) {
options.onProgress( data.progress );
}
};
const index = geometry.index ? geometry.index.array : null;
const position = geometry.attributes.position.array;
const transferable = [ position ];
if ( index ) {
transferable.push( index );
}
worker.postMessage( {
index,
position,
options: {
...options,
onProgress: null,
includedProgressCallback: Boolean( options.onProgress ),
groups: [ ... geometry.groups ],
},
}, transferable.map( arr => arr.buffer ).filter( v => ( typeof SharedArrayBuffer === 'undefined' ) || ! ( v instanceof SharedArrayBuffer ) ) );
} );
}
}