Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Separate cast functions #552

Merged
merged 7 commits into from
Aug 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 14 additions & 25 deletions src/core/MeshBVH.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,16 @@
import { Vector3, BufferAttribute, Box3, FrontSide, Matrix4 } from 'three';
import { CENTER, BYTES_PER_NODE, IS_LEAFNODE_FLAG } from './Constants.js';
import { buildPackedTree } from './buildFunctions.js';
import {
raycast,
raycastFirst,
shapecast,
intersectsGeometry,
setBuffer,
clearBuffer,
} from './castFunctions.js';
import { OrientedBox } from '../math/OrientedBox.js';
import { ExtendedTriangle } from '../math/ExtendedTriangle.js';
import { PrimitivePool } from '../utils/PrimitivePool.js';
import { arrayToBox } from '../utils/ArrayBoxUtilities.js';
import { iterateOverTriangles, setTriangle } from '../utils/TriangleUtilities.js';
import { BufferStack } from './utils/BufferStack.js';

Check warning on line 9 in src/core/MeshBVH.js

View workflow job for this annotation

GitHub Actions / build (16.x)

'BufferStack' is defined but never used

Check notice

Code scanning / CodeQL

Unused variable, import, function or class Note

Unused import BufferStack.
import { raycast } from './cast/raycast.js';
import { raycastFirst } from './cast/raycastFirst.js';
import { shapecast } from './cast/shapecast.js';
import { intersectsGeometry } from './cast/intersectsGeometry.js';

const SKIP_GENERATION = Symbol( 'skip tree generation' );

Expand Down Expand Up @@ -394,9 +391,7 @@
const materialSide = isArrayMaterial ? materialOrSide[ groups[ i ].materialIndex ].side : side;
const startCount = intersects.length;

setBuffer( roots[ i ] );
raycast( 0, geometry, materialSide, ray, intersects );
clearBuffer();
raycast( this, i, materialSide, ray, intersects );

if ( isArrayMaterial ) {

Expand Down Expand Up @@ -429,11 +424,7 @@
for ( let i = 0, l = roots.length; i < l; i ++ ) {

const materialSide = isArrayMaterial ? materialOrSide[ groups[ i ].materialIndex ].side : side;

setBuffer( roots[ i ] );
const result = raycastFirst( 0, geometry, materialSide, ray );
clearBuffer();

const result = raycastFirst( this, i, materialSide, ray );
if ( result != null && ( closestResult == null || result.distance < closestResult.distance ) ) {

closestResult = result;
Expand All @@ -453,13 +444,11 @@

intersectsGeometry( otherGeometry, geomToMesh ) {

const geometry = this.geometry;
let result = false;
for ( const root of this._roots ) {
const roots = this._roots;
for ( let i = 0, l = roots.length; i < l; i ++ ) {

setBuffer( root );
result = intersectsGeometry( 0, geometry, otherGeometry, geomToMesh );
clearBuffer();
result = intersectsGeometry( this, i, otherGeometry, geomToMesh );

if ( result ) {

Expand Down Expand Up @@ -553,11 +542,11 @@

let result = false;
let byteOffset = 0;
for ( const root of this._roots ) {
const roots = this._roots;
for ( let i = 0, l = roots.length; i < l; i ++ ) {

setBuffer( root );
result = shapecast( 0, geometry, intersectsBounds, intersectsRange, boundsTraverseOrder, byteOffset );
clearBuffer();
const root = roots[ i ];
result = shapecast( this, i, intersectsBounds, intersectsRange, boundsTraverseOrder, byteOffset );

if ( result ) {

Expand Down
2 changes: 1 addition & 1 deletion src/core/bvhcast.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { Box3 } from 'three';
import { OrientedBox } from '../math/OrientedBox.js';
import { arrayToBox } from '../utils/ArrayBoxUtilities.js';
import { PrimitivePool } from '../utils/PrimitivePool.js';
import { COUNT, OFFSET, LEFT_NODE, RIGHT_NODE, IS_LEAF, BOUNDING_DATA_INDEX } from './nodeBufferFunctions.js';
import { COUNT, OFFSET, LEFT_NODE, RIGHT_NODE, IS_LEAF, BOUNDING_DATA_INDEX } from './utils/nodeBufferUtils.js';
import { MeshBVH } from './MeshBVH.js';
import { setTriangle } from '../utils/TriangleUtilities.js';
import { ExtendedTriangle } from '../math/ExtendedTriangle.js';
Expand Down
152 changes: 152 additions & 0 deletions src/core/cast/intersectsGeometry.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
import { Box3, Matrix4 } from 'three';
import { OrientedBox } from '../../math/OrientedBox.js';
import { ExtendedTriangle } from '../../math/ExtendedTriangle.js';
import { setTriangle } from '../../utils/TriangleUtilities.js';
import { arrayToBox } from '../../utils/ArrayBoxUtilities.js';
import { COUNT, OFFSET, IS_LEAF, BOUNDING_DATA_INDEX } from '../utils/nodeBufferUtils.js';
import { BufferStack } from '../utils/BufferStack.js';

const boundingBox = new Box3();
const triangle = new ExtendedTriangle();
const triangle2 = new ExtendedTriangle();
const invertedMat = new Matrix4();

const obb = new OrientedBox();
const obb2 = new OrientedBox();

export function intersectsGeometry( bvh, root, otherGeometry, geometryToBvh ) {

BufferStack.setBuffer( bvh._roots[ root ] );
const result = _intersectsGeometry( 0, bvh.geometry, otherGeometry, geometryToBvh );
BufferStack.clearBuffer();

return result;

}

function _intersectsGeometry( nodeIndex32, geometry, otherGeometry, geometryToBvh, cachedObb = null ) {

const { float32Array, uint16Array, uint32Array } = BufferStack;
let nodeIndex16 = nodeIndex32 * 2;

if ( cachedObb === null ) {

if ( ! otherGeometry.boundingBox ) {

otherGeometry.computeBoundingBox();

}

obb.set( otherGeometry.boundingBox.min, otherGeometry.boundingBox.max, geometryToBvh );
cachedObb = obb;

}

const isLeaf = IS_LEAF( nodeIndex16, uint16Array );
if ( isLeaf ) {

const thisGeometry = geometry;
const thisIndex = thisGeometry.index;
const thisPos = thisGeometry.attributes.position;

const index = otherGeometry.index;
const pos = otherGeometry.attributes.position;

const offset = OFFSET( nodeIndex32, uint32Array );
const count = COUNT( nodeIndex16, uint16Array );

// get the inverse of the geometry matrix so we can transform our triangles into the
// geometry space we're trying to test. We assume there are fewer triangles being checked
// here.
invertedMat.copy( geometryToBvh ).invert();

if ( otherGeometry.boundsTree ) {

arrayToBox( BOUNDING_DATA_INDEX( nodeIndex32 ), float32Array, obb2 );
obb2.matrix.copy( invertedMat );
obb2.needsUpdate = true;

const res = otherGeometry.boundsTree.shapecast( {

intersectsBounds: box => obb2.intersectsBox( box ),

intersectsTriangle: tri => {

tri.a.applyMatrix4( geometryToBvh );
tri.b.applyMatrix4( geometryToBvh );
tri.c.applyMatrix4( geometryToBvh );
tri.needsUpdate = true;

for ( let i = offset * 3, l = ( count + offset ) * 3; i < l; i += 3 ) {

// this triangle needs to be transformed into the current BVH coordinate frame
setTriangle( triangle2, i, thisIndex, thisPos );
triangle2.needsUpdate = true;
if ( tri.intersectsTriangle( triangle2 ) ) {

return true;

}

}

return false;

}

} );

return res;

} else {

for ( let i = offset * 3, l = ( count + offset * 3 ); i < l; i += 3 ) {

// this triangle needs to be transformed into the current BVH coordinate frame
setTriangle( triangle, i, thisIndex, thisPos );
triangle.a.applyMatrix4( invertedMat );
triangle.b.applyMatrix4( invertedMat );
triangle.c.applyMatrix4( invertedMat );
triangle.needsUpdate = true;

for ( let i2 = 0, l2 = index.count; i2 < l2; i2 += 3 ) {

setTriangle( triangle2, i2, index, pos );
triangle2.needsUpdate = true;

if ( triangle.intersectsTriangle( triangle2 ) ) {

return true;

}

}

}

}

} else {

const left = nodeIndex32 + 8;
const right = uint32Array[ nodeIndex32 + 6 ];

arrayToBox( BOUNDING_DATA_INDEX( left ), float32Array, boundingBox );
const leftIntersection =
cachedObb.intersectsBox( boundingBox ) &&
_intersectsGeometry( left, geometry, otherGeometry, geometryToBvh, cachedObb );

if ( leftIntersection ) return true;

arrayToBox( BOUNDING_DATA_INDEX( right ), float32Array, boundingBox );
const rightIntersection =
cachedObb.intersectsBox( boundingBox ) &&
_intersectsGeometry( right, geometry, otherGeometry, geometryToBvh, cachedObb );

if ( rightIntersection ) return true;

return false;

}

}
46 changes: 46 additions & 0 deletions src/core/cast/raycast.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { Vector3 } from 'three';
import { intersectTris } from '../../utils/GeometryRayIntersectUtilities.js';
import { intersectRay } from '../utils/intersectUtils.js';
import { COUNT, OFFSET, LEFT_NODE, RIGHT_NODE, IS_LEAF } from '../utils/nodeBufferUtils.js';
import { BufferStack } from '../utils/BufferStack.js';

const _boxIntersection = /* @__PURE__ */ new Vector3();
export function raycast( bvh, root, side, ray, intersects ) {

BufferStack.setBuffer( bvh._roots[ root ] );
_raycast( 0, bvh.geometry, side, ray, intersects );
BufferStack.clearBuffer();

}

function _raycast( nodeIndex32, geometry, side, ray, intersects ) {

const { float32Array, uint16Array, uint32Array } = BufferStack;
const nodeIndex16 = nodeIndex32 * 2;
const isLeaf = IS_LEAF( nodeIndex16, uint16Array );
if ( isLeaf ) {

const offset = OFFSET( nodeIndex32, uint32Array );
const count = COUNT( nodeIndex16, uint16Array );

intersectTris( geometry, side, ray, offset, count, intersects );

} else {

const leftIndex = LEFT_NODE( nodeIndex32 );
if ( intersectRay( leftIndex, float32Array, ray, _boxIntersection ) ) {

_raycast( leftIndex, geometry, side, ray, intersects );

}

const rightIndex = RIGHT_NODE( nodeIndex32, uint32Array );
if ( intersectRay( rightIndex, float32Array, ray, _boxIntersection ) ) {

_raycast( rightIndex, geometry, side, ray, intersects );

}

}

}
93 changes: 93 additions & 0 deletions src/core/cast/raycastFirst.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { Vector3 } from 'three';
import { COUNT, OFFSET, LEFT_NODE, RIGHT_NODE, IS_LEAF, SPLIT_AXIS } from '../utils/nodeBufferUtils.js';
import { BufferStack } from '../utils/BufferStack.js';
import { intersectClosestTri } from '../../utils/GeometryRayIntersectUtilities.js';
import { intersectRay } from '../utils/intersectUtils.js';

const _boxIntersection = /* @__PURE__ */ new Vector3();
const _xyzFields = [ 'x', 'y', 'z' ];
export function raycastFirst( bvh, root, side, ray ) {

BufferStack.setBuffer( bvh._roots[ root ] );
const result = _raycastFirst( 0, bvh.geometry, side, ray );
BufferStack.clearBuffer();

return result;

}

function _raycastFirst( nodeIndex32, geometry, side, ray ) {

const { float32Array, uint16Array, uint32Array } = BufferStack;
let nodeIndex16 = nodeIndex32 * 2;

const isLeaf = IS_LEAF( nodeIndex16, uint16Array );
if ( isLeaf ) {

const offset = OFFSET( nodeIndex32, uint32Array );
const count = COUNT( nodeIndex16, uint16Array );
return intersectClosestTri( geometry, side, ray, offset, count );

} else {

// consider the position of the split plane with respect to the oncoming ray; whichever direction
// the ray is coming from, look for an intersection among that side of the tree first
const splitAxis = SPLIT_AXIS( nodeIndex32, uint32Array );
const xyzAxis = _xyzFields[ splitAxis ];
const rayDir = ray.direction[ xyzAxis ];
const leftToRight = rayDir >= 0;

// c1 is the child to check first
let c1, c2;
if ( leftToRight ) {

c1 = LEFT_NODE( nodeIndex32 );
c2 = RIGHT_NODE( nodeIndex32, uint32Array );

} else {

c1 = RIGHT_NODE( nodeIndex32, uint32Array );
c2 = LEFT_NODE( nodeIndex32 );

}

const c1Intersection = intersectRay( c1, float32Array, ray, _boxIntersection );
const c1Result = c1Intersection ? _raycastFirst( c1, geometry, side, ray ) : null;

// if we got an intersection in the first node and it's closer than the second node's bounding
// box, we don't need to consider the second node because it couldn't possibly be a better result
if ( c1Result ) {

// check if the point is within the second bounds
// "point" is in the local frame of the bvh
const point = c1Result.point[ xyzAxis ];
const isOutside = leftToRight ?
point <= float32Array[ c2 + splitAxis ] : // min bounding data
point >= float32Array[ c2 + splitAxis + 3 ]; // max bounding data

if ( isOutside ) {

return c1Result;

}

}

// either there was no intersection in the first node, or there could still be a closer
// intersection in the second, so check the second node and then take the better of the two
const c2Intersection = intersectRay( c2, float32Array, ray, _boxIntersection );
const c2Result = c2Intersection ? _raycastFirst( c2, geometry, side, ray ) : null;

if ( c1Result && c2Result ) {

return c1Result.distance <= c2Result.distance ? c1Result : c2Result;

} else {

return c1Result || c2Result || null;

}

}

}
Loading
Loading