three-mesh-bvh
Version:
A BVH implementation to speed up raycasting against three.js meshes.
351 lines (236 loc) • 7.7 kB
JavaScript
import { Vector3, Vector2, Ray, Matrix4, FrontSide, BackSide, Triangle, REVISION } from 'three';
import { GeometryBVH, ExtendedTriangle, INTERSECTED, NOT_INTERSECTED, SKIP_GENERATION } from 'three-mesh-bvh';
const _v0 = /* @__PURE__ */ new Vector3();
const _v1 = /* @__PURE__ */ new Vector3();
const _v2 = /* @__PURE__ */ new Vector3();
const _ray = /* @__PURE__ */ new Ray();
const _inverseMatrix = /* @__PURE__ */ new Matrix4();
const _localPoint = /* @__PURE__ */ new Vector3();
const _axes = [ 'x', 'y', 'z' ];
const IS_GT_REVISION_169 = parseInt( REVISION ) >= 169;
const IS_LT_REVISION_161 = parseInt( REVISION ) <= 161;
const _uvA = /* @__PURE__ */ new Vector2();
const _uvB = /* @__PURE__ */ new Vector2();
const _uvC = /* @__PURE__ */ new Vector2();
const _normalA = /* @__PURE__ */ new Vector3();
const _normalB = /* @__PURE__ */ new Vector3();
const _normalC = /* @__PURE__ */ new Vector3();
export class SkinnedMeshBVH extends GeometryBVH {
get primitiveStride() {
return 3;
}
constructor( mesh, options = {} ) {
if ( ! mesh.isMesh ) {
throw new Error( 'SkinnedMeshBVH: First argument must be a Mesh.' );
}
// skip generation initially so we can add our local fields
// TODO: is there a more clean way to handle this? Update all subclasses to be
// responsible for calling "init" themselves?
super( mesh.geometry, {
...options,
[ SKIP_GENERATION ]: true,
} );
this.mesh = mesh;
if ( ! options[ SKIP_GENERATION ] ) {
this.init( options );
}
}
writePrimitiveBounds( i, targetBuffer, baseIndex ) {
const { mesh, geometry } = this;
const indirectBuffer = this._indirectBuffer;
const index = geometry.index ? geometry.index.array : null;
const tri = indirectBuffer ? indirectBuffer[ i ] : i;
const tri3 = tri * 3;
let ai = tri3 + 0;
let bi = tri3 + 1;
let ci = tri3 + 2;
if ( index ) {
ai = index[ ai ];
bi = index[ bi ];
ci = index[ ci ];
}
// Get skinned vertex positions
mesh.getVertexPosition( ai, _v0 );
mesh.getVertexPosition( bi, _v1 );
mesh.getVertexPosition( ci, _v2 );
// Compute bounds for each axis
for ( let el = 0; el < 3; el ++ ) {
const axis = _axes[ el ];
const a = _v0[ axis ];
const b = _v1[ axis ];
const c = _v2[ axis ];
let min = a;
if ( b < min ) min = b;
if ( c < min ) min = c;
let max = a;
if ( b > max ) max = b;
if ( c > max ) max = c;
// 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 triangle = new ExtendedTriangle();
return super.shapecast(
{
...callbacks,
intersectsPrimitive: callbacks.intersectsTriangle,
scratchPrimitive: triangle,
iterate: iterateOverTriangles,
},
);
}
raycastObject3D( object, raycaster, intersects = [] ) {
const { material } = object;
if ( material === undefined ) {
return;
}
const { matrixWorld } = object;
const { firstHitOnly } = raycaster;
_inverseMatrix.copy( matrixWorld ).invert();
_ray.copy( raycaster.ray ).applyMatrix4( _inverseMatrix );
let closestHit = null;
let closestDistance = Infinity;
this.shapecast( {
boundsTraverseOrder: box => {
return box.distanceToPoint( _ray.origin );
},
intersectsBounds: box => {
return _ray.intersectsBox( box ) ? INTERSECTED : NOT_INTERSECTED;
},
intersectsTriangle: ( tri, triIndex ) => {
// get the intersection
let point = null;
if ( material.side === FrontSide ) {
point = _ray.intersectTriangle( tri.a, tri.b, tri.c, true, _localPoint );
} else if ( material.side === BackSide ) {
point = _ray.intersectTriangle( tri.c, tri.b, tri.a, true, _localPoint );
} else {
point = _ray.intersectTriangle( tri.a, tri.b, tri.c, false, _localPoint );
}
if ( ! point ) {
return;
}
// transform it into world space
point = point.clone().applyMatrix4( matrixWorld );
// check distance to ray
const dist = raycaster.ray.origin.distanceTo( point );
if ( dist >= raycaster.near && dist <= raycaster.far ) {
if ( firstHitOnly && dist >= closestDistance ) {
return;
}
// get the vertex indices
const { geometry } = this;
const { index } = geometry;
const actualTri = this.resolvePrimitiveIndex( triIndex );
const triOffset = actualTri * 3;
let ai = triOffset + 0;
let bi = triOffset + 1;
let ci = triOffset + 2;
if ( index ) {
ai = index.array[ ai ];
bi = index.array[ bi ];
ci = index.array[ ci ];
}
// build the intersection result
const hit = {
distance: dist,
point: point.clone(),
object,
uv: null,
uv1: null,
normal: null,
face: {
a: ai,
b: bi,
c: ci,
normal: Triangle.getNormal( tri.a, tri.b, tri.c, new Vector3() ),
materialIndex: 0
},
faceIndex: actualTri,
};
if ( IS_GT_REVISION_169 ) {
const barycoord = new Vector3();
Triangle.getBarycoord( _localPoint, tri.a, tri.b, tri.c, barycoord );
hit.barycoord = barycoord;
}
// add attribute fields if available
const uv = geometry.attributes.uv;
const uv1 = geometry.attributes.uv1;
const normal = geometry.attributes.normal;
if ( uv ) {
_uvA.fromBufferAttribute( uv, ai );
_uvB.fromBufferAttribute( uv, bi );
_uvC.fromBufferAttribute( uv, ci );
hit.uv = new Vector2();
const resUv = Triangle.getInterpolation( _localPoint, tri.a, tri.b, tri.c, _uvA, _uvB, _uvC, hit.uv );
if ( ! IS_GT_REVISION_169 ) hit.uv = resUv;
}
if ( uv1 ) {
_uvA.fromBufferAttribute( uv1, ai );
_uvB.fromBufferAttribute( uv1, bi );
_uvC.fromBufferAttribute( uv1, ci );
hit.uv1 = new Vector2();
const resUv1 = Triangle.getInterpolation( _localPoint, tri.a, tri.b, tri.c, _uvA, _uvB, _uvC, hit.uv1 );
if ( ! IS_GT_REVISION_169 ) hit.uv1 = resUv1;
if ( IS_LT_REVISION_161 ) hit.uv2 = hit.uv1;
}
if ( normal ) {
_normalA.fromBufferAttribute( normal, ai );
_normalB.fromBufferAttribute( normal, bi );
_normalC.fromBufferAttribute( normal, ci );
hit.normal = new Vector3();
const resNormal = Triangle.getInterpolation( _localPoint, tri.a, tri.b, tri.c, _normalA, _normalB, _normalC, hit.normal );
if ( hit.normal.dot( _ray.direction ) > 0 ) {
hit.normal.multiplyScalar( - 1 );
}
if ( ! IS_GT_REVISION_169 ) hit.normal = resNormal;
}
// first hit only settings
closestDistance = hit.distance;
closestHit = hit;
if ( ! firstHitOnly ) {
intersects.push( hit );
}
}
}
} );
if ( firstHitOnly && closestHit ) {
intersects.push( closestHit );
}
return intersects;
}
}
function iterateOverTriangles(
offset,
count,
bvh,
intersectsTriangleFunc,
contained,
depth,
triangle
) {
const { mesh, geometry } = bvh;
const index = geometry.index ? geometry.index.array : null;
for ( let i = offset, l = count + offset; i < l; i ++ ) {
const tri = bvh.resolvePrimitiveIndex( i );
let i0 = 3 * tri + 0;
let i1 = 3 * tri + 1;
let i2 = 3 * tri + 2;
if ( index ) {
i0 = index[ i0 ];
i1 = index[ i1 ];
i2 = index[ i2 ];
}
mesh.getVertexPosition( i0, triangle.a );
mesh.getVertexPosition( i1, triangle.b );
mesh.getVertexPosition( i2, triangle.c );
triangle.needsUpdate = true;
if ( intersectsTriangleFunc( triangle, i, contained, depth ) ) {
return true;
}
}
return false;
}