UNPKG

three-mesh-bvh

Version:

A BVH implementation to speed up raycasting against three.js meshes.

226 lines (149 loc) 5.13 kB
import { Matrix4, Line3, Vector3, Ray, Box3 } from 'three'; import { PrimitivePool } from '../utils/PrimitivePool.js'; import { INTERSECTED, NOT_INTERSECTED } from './Constants.js'; import { GeometryBVH } from './GeometryBVH.js'; const _inverseMatrix = /* @__PURE__ */ new Matrix4(); const _ray = /* @__PURE__ */ new Ray(); const _linePool = /* @__PURE__ */ new PrimitivePool( () => new Line3() ); const _intersectPointOnRay = /*@__PURE__*/ new Vector3(); const _intersectPointOnSegment = /*@__PURE__*/ new Vector3(); const _box = /* @__PURE__ */ new Box3(); const _getters = [ 'getX', 'getY', 'getZ' ]; export class LineSegmentsBVH extends GeometryBVH { get primitiveStride() { return 2; } writePrimitiveBounds( i, targetBuffer, baseIndex ) { const indirectBuffer = this._indirectBuffer; const { geometry, primitiveStride } = this; const posAttr = geometry.attributes.position; const indexAttr = geometry.index; // TODO: this may not be right for a LineLoop with a limited draw range / groups const vertCount = indexAttr ? indexAttr.count : posAttr.count; const prim = indirectBuffer ? indirectBuffer[ i ] : i; let i0 = prim * primitiveStride; let i1 = ( i0 + 1 ) % vertCount; if ( indexAttr ) { i0 = indexAttr.getX( i0 ); i1 = indexAttr.getX( i1 ); } for ( let el = 0; el < 3; el ++ ) { const v0 = posAttr[ _getters[ el ] ]( i0 ); const v1 = posAttr[ _getters[ el ] ]( i1 ); const min = v0 < v1 ? v0 : v1; const max = v0 > v1 ? v0 : v1; // Write in min/max format [minx, miny, minz, maxx, maxy, maxz] targetBuffer[ baseIndex + el ] = min; targetBuffer[ baseIndex + el + 3 ] = max; } return targetBuffer; } shapecast( callbacks ) { const line = _linePool.getPrimitive(); const result = super.shapecast( { ...callbacks, intersectsPrimitive: callbacks.intersectsLine, scratchPrimitive: line, iterate: iterateOverLines, } ); _linePool.releasePrimitive( line ); return result; } raycastObject3D( object, raycaster, intersects = [] ) { const { matrixWorld } = object; const { firstHitOnly } = raycaster; _inverseMatrix.copy( matrixWorld ).invert(); _ray.copy( raycaster.ray ).applyMatrix4( _inverseMatrix ); const threshold = raycaster.params.Line.threshold; const localThreshold = threshold / ( ( object.scale.x + object.scale.y + object.scale.z ) / 3 ); const localThresholdSq = localThreshold * localThreshold; let closestHit = null; let closestDistance = Infinity; this.shapecast( { boundsTraverseOrder: box => { return box.distanceToPoint( _ray.origin ); }, intersectsBounds: box => { // TODO: for some reason trying to early-out here is causing firstHitOnly tests to fail _box.copy( box ).expandByScalar( Math.abs( localThreshold ) ); return _ray.intersectsBox( _box ) ? INTERSECTED : NOT_INTERSECTED; }, intersectsLine: ( line, index ) => { const distSq = _ray.distanceSqToSegment( line.start, line.end, _intersectPointOnRay, _intersectPointOnSegment ); if ( distSq > localThresholdSq ) return; _intersectPointOnRay.applyMatrix4( object.matrixWorld ); const distance = raycaster.ray.origin.distanceTo( _intersectPointOnRay ); if ( distance < raycaster.near || distance > raycaster.far ) return; if ( firstHitOnly && distance >= closestDistance ) return; closestDistance = distance; index = this.resolvePrimitiveIndex( index ); closestHit = { distance, point: _intersectPointOnSegment.clone().applyMatrix4( matrixWorld ), index: index * this.primitiveStride, face: null, faceIndex: null, barycoord: null, object, }; if ( ! firstHitOnly ) { intersects.push( closestHit ); } }, } ); if ( firstHitOnly && closestHit ) { intersects.push( closestHit ); } return intersects; } } export class LineLoopBVH extends LineSegmentsBVH { get primitiveStride() { return 1; } constructor( geometry, options = {} ) { // "Line" and "LineLoop" BVH must be indirect since we cannot rearrange the index // buffer without breaking the lines options = { ...options, indirect: true, }; super( geometry, options ); } } export class LineBVH extends LineLoopBVH { getRootRanges( ...args ) { const res = super.getRootRanges( ...args ); res.forEach( group => group.count -- ); return res; } } function iterateOverLines( offset, count, bvh, intersectsPointFunc, contained, depth, line ) { const { geometry, primitiveStride } = bvh; const { index } = geometry; const posAttr = geometry.attributes.position; const vertCount = index ? index.count : posAttr.count; for ( let i = offset, l = count + offset; i < l; i ++ ) { const prim = bvh.resolvePrimitiveIndex( i ); let i0 = prim * primitiveStride; let i1 = ( i0 + 1 ) % vertCount; if ( index ) { i0 = index.getX( i0 ); i1 = index.getX( i1 ); } line.start.fromBufferAttribute( posAttr, i0 ); line.end.fromBufferAttribute( posAttr, i1 ); if ( intersectsPointFunc( line, i, contained, depth ) ) { return true; } } return false; }