UNPKG

three-mesh-bvh

Version:

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

422 lines (276 loc) 9.43 kB
import { Vector3, Matrix4, Line3 } from 'three'; import { SeparatingAxisBounds } from './SeparatingAxisBounds.js'; import { ExtendedTriangle } from './ExtendedTriangle.js'; import { closestPointsSegmentToSegment } from './MathUtilities.js'; export class OrientedBox { constructor( min, max, matrix ) { this.isOrientedBox = true; this.min = new Vector3(); this.max = new Vector3(); this.matrix = new Matrix4(); this.invMatrix = new Matrix4(); this.points = new Array( 8 ).fill().map( () => new Vector3() ); this.satAxes = new Array( 3 ).fill().map( () => new Vector3() ); this.satBounds = new Array( 3 ).fill().map( () => new SeparatingAxisBounds() ); this.alignedSatBounds = new Array( 3 ).fill().map( () => new SeparatingAxisBounds() ); this.needsUpdate = false; if ( min ) this.min.copy( min ); if ( max ) this.max.copy( max ); if ( matrix ) this.matrix.copy( matrix ); } set( min, max, matrix ) { this.min.copy( min ); this.max.copy( max ); this.matrix.copy( matrix ); this.needsUpdate = true; } copy( other ) { this.min.copy( other.min ); this.max.copy( other.max ); this.matrix.copy( other.matrix ); this.needsUpdate = true; } } OrientedBox.prototype.update = ( function () { return function update() { const matrix = this.matrix; const min = this.min; const max = this.max; const points = this.points; for ( let x = 0; x <= 1; x ++ ) { for ( let y = 0; y <= 1; y ++ ) { for ( let z = 0; z <= 1; z ++ ) { const i = ( ( 1 << 0 ) * x ) | ( ( 1 << 1 ) * y ) | ( ( 1 << 2 ) * z ); const v = points[ i ]; v.x = x ? max.x : min.x; v.y = y ? max.y : min.y; v.z = z ? max.z : min.z; v.applyMatrix4( matrix ); } } } const satBounds = this.satBounds; const satAxes = this.satAxes; const minVec = points[ 0 ]; for ( let i = 0; i < 3; i ++ ) { const axis = satAxes[ i ]; const sb = satBounds[ i ]; const index = 1 << i; const pi = points[ index ]; axis.subVectors( minVec, pi ); sb.setFromPoints( axis, points ); } const alignedSatBounds = this.alignedSatBounds; alignedSatBounds[ 0 ].setFromPointsField( points, 'x' ); alignedSatBounds[ 1 ].setFromPointsField( points, 'y' ); alignedSatBounds[ 2 ].setFromPointsField( points, 'z' ); this.invMatrix.copy( this.matrix ).invert(); this.needsUpdate = false; }; } )(); OrientedBox.prototype.intersectsBox = ( function () { const aabbBounds = /* @__PURE__ */ new SeparatingAxisBounds(); return function intersectsBox( box ) { // TODO: should this be doing SAT against the AABB? if ( this.needsUpdate ) { this.update(); } const min = box.min; const max = box.max; const satBounds = this.satBounds; const satAxes = this.satAxes; const alignedSatBounds = this.alignedSatBounds; aabbBounds.min = min.x; aabbBounds.max = max.x; if ( alignedSatBounds[ 0 ].isSeparated( aabbBounds ) ) return false; aabbBounds.min = min.y; aabbBounds.max = max.y; if ( alignedSatBounds[ 1 ].isSeparated( aabbBounds ) ) return false; aabbBounds.min = min.z; aabbBounds.max = max.z; if ( alignedSatBounds[ 2 ].isSeparated( aabbBounds ) ) return false; for ( let i = 0; i < 3; i ++ ) { const axis = satAxes[ i ]; const sb = satBounds[ i ]; aabbBounds.setFromBox( axis, box ); if ( sb.isSeparated( aabbBounds ) ) return false; } return true; }; } )(); OrientedBox.prototype.intersectsTriangle = ( function () { const saTri = /* @__PURE__ */ new ExtendedTriangle(); const pointsArr = /* @__PURE__ */ new Array( 3 ); const cachedSatBounds = /* @__PURE__ */ new SeparatingAxisBounds(); const cachedSatBounds2 = /* @__PURE__ */ new SeparatingAxisBounds(); const cachedAxis = /* @__PURE__ */ new Vector3(); return function intersectsTriangle( triangle ) { if ( this.needsUpdate ) { this.update(); } if ( ! triangle.isExtendedTriangle ) { saTri.copy( triangle ); saTri.update(); triangle = saTri; } else if ( triangle.needsUpdate ) { triangle.update(); } const satBounds = this.satBounds; const satAxes = this.satAxes; pointsArr[ 0 ] = triangle.a; pointsArr[ 1 ] = triangle.b; pointsArr[ 2 ] = triangle.c; for ( let i = 0; i < 3; i ++ ) { const sb = satBounds[ i ]; const sa = satAxes[ i ]; cachedSatBounds.setFromPoints( sa, pointsArr ); if ( sb.isSeparated( cachedSatBounds ) ) return false; } const triSatBounds = triangle.satBounds; const triSatAxes = triangle.satAxes; const points = this.points; for ( let i = 0; i < 3; i ++ ) { const sb = triSatBounds[ i ]; const sa = triSatAxes[ i ]; cachedSatBounds.setFromPoints( sa, points ); if ( sb.isSeparated( cachedSatBounds ) ) return false; } // check crossed axes for ( let i = 0; i < 3; i ++ ) { const sa1 = satAxes[ i ]; for ( let i2 = 0; i2 < 4; i2 ++ ) { const sa2 = triSatAxes[ i2 ]; cachedAxis.crossVectors( sa1, sa2 ); cachedSatBounds.setFromPoints( cachedAxis, pointsArr ); cachedSatBounds2.setFromPoints( cachedAxis, points ); if ( cachedSatBounds.isSeparated( cachedSatBounds2 ) ) return false; } } return true; }; } )(); OrientedBox.prototype.closestPointToPoint = ( function () { return function closestPointToPoint( point, target1 ) { if ( this.needsUpdate ) { this.update(); } target1 .copy( point ) .applyMatrix4( this.invMatrix ) .clamp( this.min, this.max ) .applyMatrix4( this.matrix ); return target1; }; } )(); OrientedBox.prototype.distanceToPoint = ( function () { const target = new Vector3(); return function distanceToPoint( point ) { this.closestPointToPoint( point, target ); return point.distanceTo( target ); }; } )(); OrientedBox.prototype.distanceToBox = ( function () { const xyzFields = [ 'x', 'y', 'z' ]; const segments1 = /* @__PURE__ */ new Array( 12 ).fill().map( () => new Line3() ); const segments2 = /* @__PURE__ */ new Array( 12 ).fill().map( () => new Line3() ); const point1 = /* @__PURE__ */ new Vector3(); const point2 = /* @__PURE__ */ new Vector3(); // early out if we find a value below threshold return function distanceToBox( box, threshold = 0, target1 = null, target2 = null ) { if ( this.needsUpdate ) { this.update(); } if ( this.intersectsBox( box ) ) { if ( target1 || target2 ) { box.getCenter( point2 ); this.closestPointToPoint( point2, point1 ); box.closestPointToPoint( point1, point2 ); if ( target1 ) target1.copy( point1 ); if ( target2 ) target2.copy( point2 ); } return 0; } const threshold2 = threshold * threshold; const min = box.min; const max = box.max; const points = this.points; // iterate over every edge and compare distances let closestDistanceSq = Infinity; // check over all these points for ( let i = 0; i < 8; i ++ ) { const p = points[ i ]; point2.copy( p ).clamp( min, max ); const dist = p.distanceToSquared( point2 ); if ( dist < closestDistanceSq ) { closestDistanceSq = dist; if ( target1 ) target1.copy( p ); if ( target2 ) target2.copy( point2 ); if ( dist < threshold2 ) return Math.sqrt( dist ); } } // generate and check all line segment distances let count = 0; for ( let i = 0; i < 3; i ++ ) { for ( let i1 = 0; i1 <= 1; i1 ++ ) { for ( let i2 = 0; i2 <= 1; i2 ++ ) { const nextIndex = ( i + 1 ) % 3; const nextIndex2 = ( i + 2 ) % 3; // get obb line segments const index = i1 << nextIndex | i2 << nextIndex2; const index2 = 1 << i | i1 << nextIndex | i2 << nextIndex2; const p1 = points[ index ]; const p2 = points[ index2 ]; const line1 = segments1[ count ]; line1.set( p1, p2 ); // get aabb line segments const f1 = xyzFields[ i ]; const f2 = xyzFields[ nextIndex ]; const f3 = xyzFields[ nextIndex2 ]; const line2 = segments2[ count ]; const start = line2.start; const end = line2.end; start[ f1 ] = min[ f1 ]; start[ f2 ] = i1 ? min[ f2 ] : max[ f2 ]; start[ f3 ] = i2 ? min[ f3 ] : max[ f2 ]; end[ f1 ] = max[ f1 ]; end[ f2 ] = i1 ? min[ f2 ] : max[ f2 ]; end[ f3 ] = i2 ? min[ f3 ] : max[ f2 ]; count ++; } } } // check all the other boxes point for ( let x = 0; x <= 1; x ++ ) { for ( let y = 0; y <= 1; y ++ ) { for ( let z = 0; z <= 1; z ++ ) { point2.x = x ? max.x : min.x; point2.y = y ? max.y : min.y; point2.z = z ? max.z : min.z; this.closestPointToPoint( point2, point1 ); const dist = point2.distanceToSquared( point1 ); if ( dist < closestDistanceSq ) { closestDistanceSq = dist; if ( target1 ) target1.copy( point1 ); if ( target2 ) target2.copy( point2 ); if ( dist < threshold2 ) return Math.sqrt( dist ); } } } } for ( let i = 0; i < 12; i ++ ) { const l1 = segments1[ i ]; for ( let i2 = 0; i2 < 12; i2 ++ ) { const l2 = segments2[ i2 ]; closestPointsSegmentToSegment( l1, l2, point1, point2 ); const dist = point1.distanceToSquared( point2 ); if ( dist < closestDistanceSq ) { closestDistanceSq = dist; if ( target1 ) target1.copy( point1 ); if ( target2 ) target2.copy( point2 ); if ( dist < threshold2 ) return Math.sqrt( dist ); } } } return Math.sqrt( closestDistanceSq ); }; } )();