three-mesh-bvh
Version:
A BVH implementation to speed up raycasting against three.js meshes.
132 lines (86 loc) • 3.54 kB
JavaScript
import { BufferAttribute } from 'three';
export function getVertexCount( geo ) {
return geo.index ? geo.index.count : geo.attributes.position.count;
}
export function getTriCount( geo ) {
return getVertexCount( geo ) / 3;
}
export function getIndexArray( vertexCount, BufferConstructor = ArrayBuffer ) {
if ( vertexCount > 65535 ) {
return new Uint32Array( new BufferConstructor( 4 * vertexCount ) );
} else {
return new Uint16Array( new BufferConstructor( 2 * vertexCount ) );
}
}
// ensures that an index is present on the geometry
export function ensureIndex( geo, options ) {
if ( ! geo.index ) {
const vertexCount = geo.attributes.position.count;
const BufferConstructor = options.useSharedArrayBuffer ? SharedArrayBuffer : ArrayBuffer;
const index = getIndexArray( vertexCount, BufferConstructor );
geo.setIndex( new BufferAttribute( index, 1 ) );
for ( let i = 0; i < vertexCount; i ++ ) {
index[ i ] = i;
}
}
}
// Computes the set of { offset, count } ranges which need independent BVH roots. Each
// region in the geometry index that belongs to a different set of material groups requires
// a separate BVH root, so that triangles indices belonging to one group never get swapped
// with triangle indices belongs to another group. For example, if the groups were like this:
//
// [-------------------------------------------------------------]
// |__________________|
// g0 = [0, 20] |______________________||_____________________|
// g1 = [16, 40] g2 = [41, 60]
//
// we would need four BVH roots: [0, 15], [16, 20], [21, 40], [41, 60].
export function getFullGeometryRange( geo, range ) {
const triCount = getTriCount( geo );
const drawRange = range ? range : geo.drawRange;
const start = drawRange.start / 3;
const end = ( drawRange.start + drawRange.count ) / 3;
const offset = Math.max( 0, start );
const count = Math.min( triCount, end ) - offset;
return [ {
offset: Math.floor( offset ),
count: Math.floor( count ),
} ];
}
export function getRootIndexRanges( geo, range ) {
if ( ! geo.groups || ! geo.groups.length ) {
return getFullGeometryRange( geo, range );
}
const ranges = [];
const rangeBoundaries = new Set();
const drawRange = range ? range : geo.drawRange;
const drawRangeStart = drawRange.start / 3;
const drawRangeEnd = ( drawRange.start + drawRange.count ) / 3;
for ( const group of geo.groups ) {
const groupStart = group.start / 3;
const groupEnd = ( group.start + group.count ) / 3;
rangeBoundaries.add( Math.max( drawRangeStart, groupStart ) );
rangeBoundaries.add( Math.min( drawRangeEnd, groupEnd ) );
}
// note that if you don't pass in a comparator, it sorts them lexicographically as strings :-(
const sortedBoundaries = Array.from( rangeBoundaries.values() ).sort( ( a, b ) => a - b );
for ( let i = 0; i < sortedBoundaries.length - 1; i ++ ) {
const start = sortedBoundaries[ i ];
const end = sortedBoundaries[ i + 1 ];
ranges.push( {
offset: Math.floor( start ),
count: Math.floor( end - start ),
} );
}
return ranges;
}
export function hasGroupGaps( geometry, range ) {
const vertexCount = getTriCount( geometry );
const groups = getRootIndexRanges( geometry, range )
.sort( ( a, b ) => a.offset - b.offset );
const finalGroup = groups[ groups.length - 1 ];
finalGroup.count = Math.min( vertexCount - finalGroup.offset, finalGroup.count );
let total = 0;
groups.forEach( ( { count } ) => total += count );
return vertexCount !== total;
}