UNPKG

three-mesh-bvh

Version:

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

196 lines (141 loc) 5.2 kB
// Distance to Point export const bvh_distance_functions = /* glsl */` float dot2( vec3 v ) { return dot( v, v ); } // https://www.shadertoy.com/view/ttfGWl vec3 closestPointToTriangle( vec3 p, vec3 v0, vec3 v1, vec3 v2, out vec3 barycoord ) { vec3 v10 = v1 - v0; vec3 v21 = v2 - v1; vec3 v02 = v0 - v2; vec3 p0 = p - v0; vec3 p1 = p - v1; vec3 p2 = p - v2; vec3 nor = cross( v10, v02 ); // method 2, in barycentric space vec3 q = cross( nor, p0 ); float d = 1.0 / dot2( nor ); float u = d * dot( q, v02 ); float v = d * dot( q, v10 ); float w = 1.0 - u - v; if( u < 0.0 ) { w = clamp( dot( p2, v02 ) / dot2( v02 ), 0.0, 1.0 ); u = 0.0; v = 1.0 - w; } else if( v < 0.0 ) { u = clamp( dot( p0, v10 ) / dot2( v10 ), 0.0, 1.0 ); v = 0.0; w = 1.0 - u; } else if( w < 0.0 ) { v = clamp( dot( p1, v21 ) / dot2( v21 ), 0.0, 1.0 ); w = 0.0; u = 1.0-v; } barycoord = vec3( u, v, w ); return u * v1 + v * v2 + w * v0; } float distanceToTriangles( // geometry info and triangle range sampler2D positionAttr, usampler2D indexAttr, uint offset, uint count, // point and cut off range vec3 point, float closestDistanceSquared, // outputs inout uvec4 faceIndices, inout vec3 faceNormal, inout vec3 barycoord, inout float side, inout vec3 outPoint ) { bool found = false; vec3 localBarycoord; for ( uint i = offset, l = offset + count; i < l; i ++ ) { uvec3 indices = uTexelFetch1D( indexAttr, i ).xyz; vec3 a = texelFetch1D( positionAttr, indices.x ).rgb; vec3 b = texelFetch1D( positionAttr, indices.y ).rgb; vec3 c = texelFetch1D( positionAttr, indices.z ).rgb; // get the closest point and barycoord vec3 closestPoint = closestPointToTriangle( point, a, b, c, localBarycoord ); vec3 delta = point - closestPoint; float sqDist = dot2( delta ); if ( sqDist < closestDistanceSquared ) { // set the output results closestDistanceSquared = sqDist; faceIndices = uvec4( indices.xyz, i ); faceNormal = normalize( cross( a - b, b - c ) ); barycoord = localBarycoord; outPoint = closestPoint; side = sign( dot( faceNormal, delta ) ); } } return closestDistanceSquared; } float distanceSqToBounds( vec3 point, vec3 boundsMin, vec3 boundsMax ) { vec3 clampedPoint = clamp( point, boundsMin, boundsMax ); vec3 delta = point - clampedPoint; return dot( delta, delta ); } float distanceSqToBVHNodeBoundsPoint( vec3 point, sampler2D bvhBounds, uint currNodeIndex ) { uint cni2 = currNodeIndex * 2u; vec3 boundsMin = texelFetch1D( bvhBounds, cni2 ).xyz; vec3 boundsMax = texelFetch1D( bvhBounds, cni2 + 1u ).xyz; return distanceSqToBounds( point, boundsMin, boundsMax ); } // use a macro to hide the fact that we need to expand the struct into separate fields #define\ bvhClosestPointToPoint(\ bvh,\ point, maxDistance, faceIndices, faceNormal, barycoord, side, outPoint\ )\ _bvhClosestPointToPoint(\ bvh.position, bvh.index, bvh.bvhBounds, bvh.bvhContents,\ point, maxDistance, faceIndices, faceNormal, barycoord, side, outPoint\ ) float _bvhClosestPointToPoint( // bvh info sampler2D bvh_position, usampler2D bvh_index, sampler2D bvh_bvhBounds, usampler2D bvh_bvhContents, // point to check vec3 point, float maxDistance, // output variables inout uvec4 faceIndices, inout vec3 faceNormal, inout vec3 barycoord, inout float side, inout vec3 outPoint ) { // stack needs to be twice as long as the deepest tree we expect because // we push both the left and right child onto the stack every traversal int ptr = 0; uint stack[ BVH_STACK_DEPTH ]; stack[ 0 ] = 0u; float closestDistanceSquared = maxDistance * maxDistance; bool found = false; while ( ptr > - 1 && ptr < BVH_STACK_DEPTH ) { uint currNodeIndex = stack[ ptr ]; ptr --; // check if we intersect the current bounds float boundsHitDistance = distanceSqToBVHNodeBoundsPoint( point, bvh_bvhBounds, currNodeIndex ); if ( boundsHitDistance > closestDistanceSquared ) { continue; } uvec2 boundsInfo = uTexelFetch1D( bvh_bvhContents, currNodeIndex ).xy; bool isLeaf = bool( boundsInfo.x & 0xffff0000u ); if ( isLeaf ) { uint count = boundsInfo.x & 0x0000ffffu; uint offset = boundsInfo.y; closestDistanceSquared = distanceToTriangles( bvh_position, bvh_index, offset, count, point, closestDistanceSquared, // outputs faceIndices, faceNormal, barycoord, side, outPoint ); } else { uint leftIndex = currNodeIndex + 1u; uint splitAxis = boundsInfo.x & 0x0000ffffu; uint rightIndex = boundsInfo.y; bool leftToRight = distanceSqToBVHNodeBoundsPoint( point, bvh_bvhBounds, leftIndex ) < distanceSqToBVHNodeBoundsPoint( point, bvh_bvhBounds, rightIndex );//rayDirection[ splitAxis ] >= 0.0; uint c1 = leftToRight ? leftIndex : rightIndex; uint c2 = leftToRight ? rightIndex : leftIndex; // set c2 in the stack so we traverse it later. We need to keep track of a pointer in // the stack while we traverse. The second pointer added is the one that will be // traversed first ptr ++; stack[ ptr ] = c2; ptr ++; stack[ ptr ] = c1; } } return sqrt( closestDistanceSquared ); } `;