three-mesh-bvh
Version:
A BVH implementation to speed up raycasting against three.js meshes.
151 lines (111 loc) • 3.5 kB
JavaScript
/** @import { FunctionNode } from 'three/tsl'; */
import { wgslTagFn } from '../nodes/WGSLTagFnNode.js';
import { rayIntersectionResultStruct, rayStruct } from './structs.js';
/**
* WGSL function node that finds the closest point on a triangle to `p` and returns the barycoord.
* @type {FunctionNode}
* @section TSL Functions
*/
export const closestPointToTriangle = wgslTagFn/* wgsl */`
// fn
fn closestPointToTriangle(
p: vec3f,
v0: vec3f,
v1: vec3f,
v2: vec3f
) -> vec3f {
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 );
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;
}
return vec3f( w, u, v );
}
`;
/**
* WGSL function node that tests a ray against a single triangle and returns an
* {@link rayIntersectionResultStruct} result. Useful when writing a custom `intersectRangeFn`
* for {@link BVHComputeData#getShapecastFn}.
* @type {FunctionNode}
* @section TSL Functions
*/
export const intersectRayTriangle = wgslTagFn/* wgsl */ `
// fn
fn intersectRayTriangle( ray: ${ rayStruct }, a: vec3f, b: vec3f, c: vec3f, threshold: f32 ) -> ${ rayIntersectionResultStruct } {
const DET_EPSILON = 1e-15;
var result: ${ rayIntersectionResultStruct };
result.didHit = false;
let edge1 = b - a;
let edge2 = c - a;
let n = cross( edge1, edge2 );
let det = - dot( ray.direction, n );
if ( abs( det ) < DET_EPSILON ) {
return result;
}
let invdet = 1.0 / det;
let AO = ray.origin - a;
let DAO = cross( AO, ray.direction );
let u = dot( edge2, DAO ) * invdet;
if ( u < 0.0 || u > 1.0 ) {
return result;
}
let v = - dot( edge1, DAO ) * invdet;
if ( v < 0.0 || u + v > 1.0 ) {
return result;
}
let t = dot( AO, n ) * invdet;
let w = 1.0 - u - v;
if ( t < threshold ) {
return result;
}
result.didHit = true;
result.barycoord = vec3f( w, u, v );
result.dist = t;
result.side = sign( det );
result.normal = result.side * normalize( n );
return result;
}
`;
/**
* WGSL function node that builds a camera ray (origin + far-plane direction) from an NDC
* coordinate and an inverse model-view-projection matrix. Works for both perspective and
* orthographic projections. The origin sits on the near plane, the returned direction is
* normalized, and "maxDist" is the world-space distance to the far plane so traversal
* clips there.
* @type {FunctionNode}
* @section TSL Functions
*/
export const ndcToCameraRay = wgslTagFn/* wgsl */`
// fn
fn ndcToCameraRay( ndc: vec2f, inverseModelViewProjection: mat4x4f ) -> ${ rayStruct } {
var homogeneous = vec4f();
var ray: ${ rayStruct };
homogeneous = inverseModelViewProjection * vec4f( ndc, 0.0, 1.0 );
ray.origin = homogeneous.xyz / homogeneous.w;
homogeneous = inverseModelViewProjection * vec4f( ndc, 1.0, 1.0 );
let toFarPlane = ( homogeneous.xyz / homogeneous.w ) - ray.origin;
ray.maxDist = length( toFarPlane );
ray.direction = toFarPlane / ray.maxDist;
return ray;
}
`;