three-mesh-bvh
Version:
A BVH implementation to speed up raycasting against three.js meshes.
148 lines (106 loc) • 4.49 kB
JavaScript
/** @import { BVHComputeData } from '../BVHComputeData.js' */
import { float } from 'three/tsl';
import { wgslTagFn } from '../nodes/WGSLTagFnNode.js';
import { rayStruct, rayIntersectionResultStruct, bvhNodeStruct, bvhNodeBoundsStruct } from '../tsl/structs.js';
import { intersectRayTriangle } from '../tsl/fns.js';
/**
* Builds the `raycastFirstHit` shapecast function: traverses the BVH front to back and returns the
* closest ray / triangle intersection.
*
* @private
* @param {BVHComputeData} bvhData
* @returns {Function} TSL function node for the TLAS traversal.
*/
export function getRaycastFirstHitFn( bvhData ) {
// these are proxy nodes, so they can be referenced before the storage buffers exist
const { index, attributes, transforms } = bvhData.storage;
const scratchRayScalar = float( 1.0 ).toVar( `bvh_rayScalar_${ Math.random().toString( 36 ).substring( 2, 7 ) }` );
return bvhData.getShapecastFn( {
name: 'bvh_RaycastFirstHit',
shapeStruct: rayStruct,
resultStruct: rayIntersectionResultStruct,
boundsOrderFn: wgslTagFn/* wgsl */`
fn getBoundsOrder( ray: ${ rayStruct }, splitAxis: u32, node: ${ bvhNodeStruct } ) -> bool {
return ray.direction[ splitAxis ] >= 0.0;
}
`,
intersectsBoundsFn: wgslTagFn/* wgsl */`
fn rayIntersectsBounds( ray: ${ rayStruct }, bounds: ${ bvhNodeBoundsStruct }, result: ptr<function, ${ rayIntersectionResultStruct }> ) -> u32 {
let boundsMin = vec3( bounds.min[0], bounds.min[1], bounds.min[2] );
let boundsMax = vec3( bounds.max[0], bounds.max[1], bounds.max[2] );
let invDir = 1.0 / ray.direction;
let tMinPlane = ( boundsMin - ray.origin ) * invDir;
let tMaxPlane = ( boundsMax - ray.origin ) * invDir;
let tMinHit = vec3f(
min( tMinPlane.x, tMaxPlane.x ),
min( tMinPlane.y, tMaxPlane.y ),
min( tMinPlane.z, tMaxPlane.z )
);
let tMaxHit = vec3f(
max( tMinPlane.x, tMaxPlane.x ),
max( tMinPlane.y, tMaxPlane.y ),
max( tMinPlane.z, tMaxPlane.z )
);
let t0 = max( max( tMinHit.x, tMinHit.y ), tMinHit.z );
let t1 = min( min( tMaxHit.x, tMaxHit.y ), tMaxHit.z );
let dist = max( t0, 0.0 );
if ( t1 < dist ) {
return 0u;
} else if ( ray.maxDist > 0.0 && dist * ${ scratchRayScalar } >= ray.maxDist ) {
return 0u;
} else if ( result.didHit && dist * ${ scratchRayScalar } >= result.dist ) {
return 0u;
} else {
return 1u;
}
}
`,
intersectRangeFn: wgslTagFn/* wgsl */`
fn intersectRange( ray: ${ rayStruct }, offset: u32, count: u32, result: ptr<function, ${ rayIntersectionResultStruct }> ) -> bool {
var didHit = false;
for ( var ti = offset; ti < offset + count; ti = ti + 1u ) {
let i0 = ${ index }[ ti * 3u ];
let i1 = ${ index }[ ti * 3u + 1u ];
let i2 = ${ index }[ ti * 3u + 2u ];
let a = ${ attributes }[ i0 ].position.xyz;
let b = ${ attributes }[ i1 ].position.xyz;
let c = ${ attributes }[ i2 ].position.xyz;
var triResult = ${ intersectRayTriangle }( ray, a, b, c, 0.0 );
triResult.dist *= ${ scratchRayScalar };
if ( triResult.didHit && ( ray.maxDist <= 0.0 || triResult.dist < ray.maxDist ) && ( ! result.didHit || triResult.dist < result.dist ) ) {
result.didHit = true;
result.dist = triResult.dist;
result.normal = triResult.normal;
result.side = triResult.side;
result.barycoord = triResult.barycoord;
result.indices = vec4u( i0, i1, i2, ti );
didHit = true;
}
}
return didHit;
}
`,
transformShapeFn: wgslTagFn/* wgsl */`
fn transformRay( ray: ptr<function, ${ rayStruct }>, objectIndex: u32 ) -> void {
let toLocal = ${ transforms }[ objectIndex ].inverseMatrixWorld;
ray.origin = ( toLocal * vec4f( ray.origin, 1.0 ) ).xyz;
ray.direction = ( toLocal * vec4f( ray.direction, 0.0 ) ).xyz;
let len = length( ray.direction );
ray.direction /= len;
${ scratchRayScalar } = 1.0 / len;
}
`,
transformResultFn: wgslTagFn/* wgsl */`
fn transformResult( hit: ptr<function, ${ rayIntersectionResultStruct }>, objectIndex: u32 ) -> void {
let toLocal = ${ transforms }[ objectIndex ].inverseMatrixWorld;
hit.normal = normalize( ( transpose( toLocal ) * vec4f( hit.normal, 0.0 ) ).xyz );
hit.objectIndex = objectIndex;
}
`,
resetShapeFn: wgslTagFn/* wgsl */`
fn resetRayScalar( objectIndex: u32 ) -> void {
${ scratchRayScalar } = 1.0;
}
`,
} );
}