three-mesh-bvh
Version:
A BVH implementation to speed up raycasting against three.js meshes.
208 lines (152 loc) • 5.68 kB
JavaScript
// Distance to Point
/**
* Set of shader functions used for interacting with the packed BVH in a shader and sampling
* VertexAttributeTextures. Provides distance query functions. See
* [src/webgl/glsl](https://github.com/gkjohnson/three-mesh-bvh/tree/master/src/webgl/glsl)
* for full implementations and declarations.
*
* Accessed as `BVHShaderGLSL.bvh_distance_functions`.
*
* @section Shader and Texture Packing API
* @type {string}
*/
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
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 pointer = 0;
uint stack[ BVH_STACK_DEPTH ];
stack[ 0 ] = 0u;
float closestDistanceSquared = maxDistance * maxDistance;
bool found = false;
while ( pointer > - 1 && pointer < BVH_STACK_DEPTH ) {
uint currNodeIndex = stack[ pointer ];
pointer --;
// 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 = currNodeIndex + 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
pointer ++;
stack[ pointer ] = c2;
pointer ++;
stack[ pointer ] = c1;
}
}
return sqrt( closestDistanceSquared );
}
`;