UNPKG

three-mesh-bvh

Version:

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

214 lines (156 loc) 5.58 kB
import { wgslFn, wgsl } from 'three/tsl'; import { bvhNodeStruct } from './common_functions.wgsl.js'; export const closestPointToPointResultStruct = wgsl( /* wgsl */ ` struct ClosestPointToPointResult { faceIndices: vec4u, faceNormal: vec3f, barycoord: vec3f, point: vec3f, side: f32, distanceSq: f32, found: bool, }; ` ); export const closestPointToTriangleResultStruct = wgsl( /* wgsl */ ` struct ClosestPointToTriangleResult { barycoord: vec3f, point: vec3f, }; ` ); export const closestPointToTriangle = wgslFn( /* wgsl */ ` fn closestPointToTriangle( p: vec3f, v0: vec3f, v1: vec3f, v2: vec3f ) -> ClosestPointToTriangleResult { // https://www.shadertoy.com/view/ttfGWl let v10 = v1 - v0; let v21 = v2 - v1; let v02 = v0 - v2; let p0 = p - v0; let p1 = p - v1; let p2 = p - v2; let nor = cross( v10, v02 ); // method 2, in barycentric space let q = cross( nor, p0 ); let d = 1.0 / dot( nor, nor ); var u = d * dot( q, v02 ); var v = d * dot( q, v10 ); var w = 1.0 - u - v; if( u < 0.0 ) { w = clamp( dot( p2, v02 ) / dot( v02, v02 ), 0.0, 1.0 ); u = 0.0; v = 1.0 - w; } else if( v < 0.0 ) { u = clamp( dot( p0, v10 ) / dot( v10, v10 ), 0.0, 1.0 ); v = 0.0; w = 1.0 - u; } else if( w < 0.0 ) { v = clamp( dot( p1, v21 ) / dot( v21, v21 ), 0.0, 1.0 ); w = 0.0; u = 1.0 - v; } var result: ClosestPointToTriangleResult; result.barycoord = vec3f( u, v, w ); result.point = u * v1 + v * v2 + w * v0; return result; } `, [ closestPointToTriangleResultStruct ] ); export const distanceToTriangles = wgslFn( /* wgsl */ ` fn distanceToTriangles( // geometry info and triangle range bvh_index: ptr<storage, array<vec3u>, read>, bvh_position: ptr<storage, array<vec3f>, read>, offset: u32, count: u32, // point and current result. Cut off range is taken from the struct point: vec3f, ioRes: ptr<function, ClosestPointToPointResult>, ) -> void { for ( var i = offset; i < offset + count; i = i + 1u ) { let indices = bvh_index[ i ]; let a = bvh_position[ indices.x ]; let b = bvh_position[ indices.y ]; let c = bvh_position[ indices.z ]; // get the closest point and barycoord let pointRes = closestPointToTriangle( point, a, b, c ); let delta = point - pointRes.point; let distSq = dot( delta, delta ); if ( distSq < ioRes.distanceSq ) { // set the output results ioRes.distanceSq = distSq; ioRes.faceIndices = vec4u( indices.xyz, i ); ioRes.faceNormal = normalize( cross( a - b, b - c ) ); ioRes.barycoord = pointRes.barycoord; ioRes.point = pointRes.point; ioRes.side = sign( dot( ioRes.faceNormal, delta ) ); } } } `, [ closestPointToTriangle, closestPointToPointResultStruct ] ); export const distanceSqToBounds = wgslFn( /* wgsl */ ` fn distanceSqToBounds( point: vec3f, boundsMin: vec3f, boundsMax: vec3f ) -> f32 { let clampedPoint = clamp( point, boundsMin, boundsMax ); let delta = point - clampedPoint; return dot( delta, delta ); } ` ); export const distanceSqToBVHNodeBoundsPoint = wgslFn( /* wgsl */ ` fn distanceSqToBVHNodeBoundsPoint( point: vec3f, bvh: ptr<storage, array<BVHNode>, read>, currNodeIndex: u32, ) -> f32 { let node = bvh[ currNodeIndex ]; let minBounds = vec3f(node.bounds.min[0], node.bounds.min[1], node.bounds.min[2]); let maxBounds = vec3f(node.bounds.max[0], node.bounds.max[1], node.bounds.max[2]); return distanceSqToBounds( point, minBounds, maxBounds ); } `, [ distanceSqToBounds, bvhNodeStruct ] ); export const closestPointToPoint = wgslFn( /* wgsl */ ` fn bvhClosestPointToPoint( bvh_index: ptr<storage, array<vec3u>, read>, bvh_position: ptr<storage, array<vec3f>, read>, bvh: ptr<storage, array<BVHNode>, read>, point: vec3f, maxDistance: f32 ) -> ClosestPointToPointResult { const BVH_STACK_DEPTH = 64; // 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 var pointer = 0; var stack: array<u32, BVH_STACK_DEPTH>; stack[ 0 ] = 0u; var res: ClosestPointToPointResult; res.distanceSq = maxDistance * maxDistance; while pointer > - 1 && pointer < BVH_STACK_DEPTH { let currNodeIndex = stack[ pointer ]; let node = bvh[ currNodeIndex ]; pointer = pointer - 1; // check if we intersect the current bounds let boundsDistance = distanceSqToBVHNodeBoundsPoint( point, bvh, currNodeIndex ); if ( boundsDistance > res.distanceSq ) { continue; } let boundsInfox = node.splitAxisOrTriangleCount; let boundsInfoy = node.rightChildOrTriangleOffset; let isLeaf = ( boundsInfox & 0xffff0000u ) != 0u; if ( isLeaf ) { let count = boundsInfox & 0x0000ffffu; let offset = boundsInfoy; distanceToTriangles( bvh_index, bvh_position, offset, count, point, &res ); } else { let leftIndex = currNodeIndex + 1u; let splitAxis = boundsInfox & 0x0000ffffu; let rightIndex = currNodeIndex + boundsInfoy; let leftToRight = distanceSqToBVHNodeBoundsPoint( point, bvh, leftIndex ) < distanceSqToBVHNodeBoundsPoint( point, bvh, rightIndex ); let c1 = select( rightIndex, leftIndex, leftToRight ); let c2 = select( leftIndex, rightIndex, leftToRight ); pointer = pointer + 1; stack[ pointer ] = c2; pointer = pointer + 1; stack[ pointer ] = c1; } } return res; } `, [ bvhNodeStruct, closestPointToPointResultStruct, distanceToTriangles, distanceSqToBVHNodeBoundsPoint ] );