Repository: gkjohnson/three-mesh-bvh
Pull request #658 (2024-04-14 17:21:45)

Add `near` and `far` check in intersectRay


Patch:
diff --git a/README.md b/README.md
index 93da3c8..d603b2b 100644
--- a/README.md
+++ b/README.md
@@ -370,10 +370,10 @@ Helper function for use when `indirect` is set to true. This function takes a tr
 ### .raycast
 
 ```js
-raycast( ray : Ray, side : FrontSide | BackSide | DoubleSide = FrontSide ) : Array<RaycastHit>
+raycast( ray : Ray, side : FrontSide | BackSide | DoubleSide = FrontSide, near : Number = 0, far : Number = Infinity ) : Array<RaycastHit>
 ```
 ```js
-raycast( ray : Ray, material : Array<Material> | Material ) : Array<RaycastHit>
+raycast( ray : Ray, material? : Array<Material> | Material, near : Number = 0, far : Number = Infinity ) : Array<RaycastHit>
 ```
 
 Returns all raycast triangle hits in unsorted order. It is expected that `ray` is in the frame of the BVH already. Likewise the returned results are also provided in the local frame of the BVH. The `side` identifier is used to determine the side to check when raycasting or a material with the given side field can be passed. If an array of materials is provided then it is expected that the geometry has groups and the appropriate material side is used per group.
@@ -383,10 +383,10 @@ Note that unlike three.js' Raycaster results the points and distances in the int
 ### .raycastFirst
 
 ```js
-raycastFirst( ray : Ray, side : FrontSide | BackSide | DoubleSide = FrontSide ) : RaycastHit
+raycastFirst( ray : Ray, side : FrontSide | BackSide | DoubleSide = FrontSide, near : Number = 0, far : Number = Infinity ) : RaycastHit
 ```
 ```js
-raycastFirst( ray : Ray, material : Array<Material> | Material ) : RaycastHit
+raycastFirst( ray : Ray, material : Array<Material> | Material, near : Number = 0, far : Number = Infinity ) : RaycastHit
 ```
 
 Returns the first raycast hit in the model. This is typically much faster than returning all hits. See [raycast](#raycast) for information on the side and material options as well as the frame of the returned intersections.
diff --git a/example/raycast.js b/example/raycast.js
index 37e583e..bb870ee 100644
--- a/example/raycast.js
+++ b/example/raycast.js
@@ -30,7 +30,9 @@ let deltaTime = 0;
 const params = {
 	raycasters: {
 		count: 150,
-		speed: 1
+		speed: 1,
+		near: 0,
+		far: pointDist
 	},
 
 	mesh: {
@@ -96,6 +98,8 @@ function init() {
 	const rcFolder = gui.addFolder( 'Raycasters' );
 	rcFolder.add( params.raycasters, 'count' ).min( 1 ).max( 1000 ).step( 1 ).onChange( () => updateFromOptions() );
 	rcFolder.add( params.raycasters, 'speed' ).min( 0 ).max( 20 );
+	rcFolder.add( params.raycasters, 'near' ).min( 0 ).max( pointDist ).onChange( () => updateFromOptions() );
+	rcFolder.add( params.raycasters, 'far' ).min( 0 ).max( pointDist ).onChange( () => updateFromOptions() );
 	rcFolder.open();
 
 	const meshFolder = gui.addFolder( 'Mesh' );
@@ -177,8 +181,10 @@ function addRaycaster() {
 
 			hitMesh.position.set( pointDist - length, 0, 0 );
 
-			cylinderMesh.position.set( pointDist - ( length / 2 ), 0, 0 );
-			cylinderMesh.scale.set( 1, length, 1 );
+			const lineLength = res.length ? length - raycaster.near : length - raycaster.near - ( pointDist - raycaster.far );
+
+			cylinderMesh.position.set( pointDist - raycaster.near - ( lineLength / 2 ), 0, 0 );
+			cylinderMesh.scale.set( 1, lineLength, 1 );
 
 			cylinderMesh.rotation.z = Math.PI / 2;
 
@@ -195,6 +201,9 @@ function addRaycaster() {
 
 function updateFromOptions() {
 
+	raycaster.near = params.raycasters.near;
+	raycaster.far = params.raycasters.far;
+
 	// Update raycaster count
 	while ( rayCasterObjects.length > params.raycasters.count ) {
 
diff --git a/src/core/MeshBVH.js b/src/core/MeshBVH.js
index e0d9e8e..50d1bd5 100644
--- a/src/core/MeshBVH.js
+++ b/src/core/MeshBVH.js
@@ -211,7 +211,7 @@ export class MeshBVH {
 	}
 
 	/* Core Cast Functions */
-	raycast( ray, materialOrSide = FrontSide ) {
+	raycast( ray, materialOrSide = FrontSide, near = 0, far = Infinity ) {
 
 		const roots = this._roots;
 		const geometry = this.geometry;
@@ -227,7 +227,7 @@ export class MeshBVH {
 			const materialSide = isArrayMaterial ? materialOrSide[ groups[ i ].materialIndex ].side : side;
 			const startCount = intersects.length;
 
-			raycastFunc( this, i, materialSide, ray, intersects );
+			raycastFunc( this, i, materialSide, ray, intersects, near, far );
 
 			if ( isArrayMaterial ) {
 
@@ -246,7 +246,7 @@ export class MeshBVH {
 
 	}
 
-	raycastFirst( ray, materialOrSide = FrontSide ) {
+	raycastFirst( ray, materialOrSide = FrontSide, near = 0, far = Infinity ) {
 
 		const roots = this._roots;
 		const geometry = this.geometry;
@@ -261,7 +261,7 @@ export class MeshBVH {
 		for ( let i = 0, l = roots.length; i < l; i ++ ) {
 
 			const materialSide = isArrayMaterial ? materialOrSide[ groups[ i ].materialIndex ].side : side;
-			const result = raycastFirstFunc( this, i, materialSide, ray );
+			const result = raycastFirstFunc( this, i, materialSide, ray, near, far );
 			if ( result != null && ( closestResult == null || result.distance < closestResult.distance ) ) {
 
 				closestResult = result;
diff --git a/src/core/cast/raycast.template.js b/src/core/cast/raycast.template.js
index 1c35f4d..50694e1 100644
--- a/src/core/cast/raycast.template.js
+++ b/src/core/cast/raycast.template.js
@@ -4,15 +4,15 @@ import { BufferStack } from '../utils/BufferStack.js';
 import { intersectTris } from '../utils/iterationUtils.generated.js';
 import { intersectTris_indirect } from '../utils/iterationUtils_indirect.generated.js';
 
-export function raycast/* @echo INDIRECT_STRING */( bvh, root, side, ray, intersects ) {
+export function raycast/* @echo INDIRECT_STRING */( bvh, root, side, ray, intersects, near, far ) {
 
 	BufferStack.setBuffer( bvh._roots[ root ] );
-	_raycast( 0, bvh, side, ray, intersects );
+	_raycast( 0, bvh, side, ray, intersects, near, far );
 	BufferStack.clearBuffer();
 
 }
 
-function _raycast( nodeIndex32, bvh, side, ray, intersects ) {
+function _raycast( nodeIndex32, bvh, side, ray, intersects, near, far ) {
 
 	const { float32Array, uint16Array, uint32Array } = BufferStack;
 	const nodeIndex16 = nodeIndex32 * 2;
@@ -24,27 +24,27 @@ function _raycast( nodeIndex32, bvh, side, ray, intersects ) {
 
 		/* @if INDIRECT */
 
-		intersectTris_indirect( bvh, side, ray, offset, count, intersects );
+		intersectTris_indirect( bvh, side, ray, offset, count, intersects, near, far );
 
 		/* @else */
 
-		intersectTris( bvh, side, ray, offset, count, intersects );
+		intersectTris( bvh, side, ray, offset, count, intersects, near, far );
 
 		/* @endif */
 
 	} else {
 
 		const leftIndex = LEFT_NODE( nodeIndex32 );
-		if ( intersectRay( leftIndex, float32Array, ray ) ) {
+		if ( intersectRay( leftIndex, float32Array, ray, near, far ) ) {
 
-			_raycast( leftIndex, bvh, side, ray, intersects );
+			_raycast( leftIndex, bvh, side, ray, intersects, near, far );
 
 		}
 
 		const rightIndex = RIGHT_NODE( nodeIndex32, uint32Array );
-		if ( intersectRay( rightIndex, float32Array, ray ) ) {
+		if ( intersectRay( rightIndex, float32Array, ray, near, far ) ) {
 
-			_raycast( rightIndex, bvh, side, ray, intersects );
+			_raycast( rightIndex, bvh, side, ray, intersects, near, far );
 
 		}
 
diff --git a/src/core/cast/raycastFirst.template.js b/src/core/cast/raycastFirst.template.js
index 8adf34a..8e84a4b 100644
--- a/src/core/cast/raycastFirst.template.js
+++ b/src/core/cast/raycastFirst.template.js
@@ -6,17 +6,17 @@ import { intersectClosestTri_indirect } from '../utils/iterationUtils_indirect.g
 
 const _xyzFields = [ 'x', 'y', 'z' ];
 
-export function raycastFirst/* @echo INDIRECT_STRING */( bvh, root, side, ray ) {
+export function raycastFirst/* @echo INDIRECT_STRING */( bvh, root, side, ray, near, far ) {
 
 	BufferStack.setBuffer( bvh._roots[ root ] );
-	const result = _raycastFirst( 0, bvh, side, ray );
+	const result = _raycastFirst( 0, bvh, side, ray, near, far );
 	BufferStack.clearBuffer();
 
 	return result;
 
 }
 
-function _raycastFirst( nodeIndex32, bvh, side, ray ) {
+function _raycastFirst( nodeIndex32, bvh, side, ray, near, far ) {
 
 	const { float32Array, uint16Array, uint32Array } = BufferStack;
 	let nodeIndex16 = nodeIndex32 * 2;
@@ -29,11 +29,11 @@ function _raycastFirst( nodeIndex32, bvh, side, ray ) {
 
 		/* @if INDIRECT */
 
-		return intersectClosestTri_indirect( bvh, side, ray, offset, count );
+		return intersectClosestTri_indirect( bvh, side, ray, offset, count, near, far );
 
 		/* @else */
 
-		return intersectClosestTri( bvh, side, ray, offset, count );
+		return intersectClosestTri( bvh, side, ray, offset, count, near, far );
 
 		/* @endif */
 
@@ -60,8 +60,8 @@ function _raycastFirst( nodeIndex32, bvh, side, ray ) {
 
 		}
 
-		const c1Intersection = intersectRay( c1, float32Array, ray );
-		const c1Result = c1Intersection ? _raycastFirst( c1, bvh, side, ray ) : null;
+		const c1Intersection = intersectRay( c1, float32Array, ray, near, far );
+		const c1Result = c1Intersection ? _raycastFirst( c1, bvh, side, ray, near, far ) : 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
@@ -84,8 +84,8 @@ function _raycastFirst( nodeIndex32, bvh, side, ray ) {
 
 		// 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 );
-		const c2Result = c2Intersection ? _raycastFirst( c2, bvh, side, ray ) : null;
+		const c2Intersection = intersectRay( c2, float32Array, ray, near, far );
+		const c2Result = c2Intersection ? _raycastFirst( c2, bvh, side, ray, near, far ) : null;
 
 		if ( c1Result && c2Result ) {
 
diff --git a/src/core/utils/intersectUtils.js b/src/core/utils/intersectUtils.js
index 107b57e..fcd6210 100644
--- a/src/core/utils/intersectUtils.js
+++ b/src/core/utils/intersectUtils.js
@@ -2,7 +2,7 @@
  * This function performs intersection tests similar to Ray.intersectBox in three.js,
  * with the difference that the box values are read from an array to improve performance.
  */
-export function intersectRay( nodeIndex32, array, ray ) {
+export function intersectRay( nodeIndex32, array, ray, near, far ) {
 
 	let tmin, tmax, tymin, tymax, tzmin, tzmax;
 
@@ -67,14 +67,12 @@ export function intersectRay( nodeIndex32, array, ray ) {
 
 	if ( ( tmin > tzmax ) || ( tzmin > tmax ) ) return false;
 
-	// if ( tzmin > tmin || tmin !== tmin ) tmin = tzmin; // Uncomment this line if add the distance check
+	if ( tzmin > tmin || tmin !== tmin ) tmin = tzmin;
 
 	if ( tzmax < tmax || tmax !== tmax ) tmax = tzmax;
 
 	//return point closest to the ray (positive side)
 
-	if ( tmax < 0 ) return false;
-
-	return true;
+	return tmin <= far && tmax >= near;
 
 }
diff --git a/src/core/utils/iterationUtils.template.js b/src/core/utils/iterationUtils.template.js
index 48ce863..ae79825 100644
--- a/src/core/utils/iterationUtils.template.js
+++ b/src/core/utils/iterationUtils.template.js
@@ -2,7 +2,7 @@
 import { intersectTri } from '../../utils/ThreeRayIntersectUtilities.js';
 import { setTriangle } from '../../utils/TriangleUtilities.js';
 
-export function intersectTris/* @echo INDIRECT_STRING */( bvh, side, ray, offset, count, intersections ) {
+export function intersectTris/* @echo INDIRECT_STRING */( bvh, side, ray, offset, count, intersections, near, far ) {
 
 	const { geometry, _indirectBuffer } = bvh;
 	for ( let i = offset, end = offset + count; i < end; i ++ ) {
@@ -10,11 +10,11 @@ export function intersectTris/* @echo INDIRECT_STRING */( bvh, side, ray, offset
 		/* @if INDIRECT */
 
 		let vi = _indirectBuffer ? _indirectBuffer[ i ] : i;
-		intersectTri( geometry, side, ray, vi, intersections );
+		intersectTri( geometry, side, ray, vi, intersections, near, far );
 
 		/* @else */
 
-		intersectTri( geometry, side, ray, i, intersections );
+		intersectTri( geometry, side, ray, i, intersections, near, far );
 
 		/* @endif */
 
@@ -22,7 +22,7 @@ export function intersectTris/* @echo INDIRECT_STRING */( bvh, side, ray, offset
 
 }
 
-export function intersectClosestTri/* @echo INDIRECT_STRING */( bvh, side, ray, offset, count ) {
+export function intersectClosestTri/* @echo INDIRECT_STRING */( bvh, side, ray, offset, count, near, far ) {
 
 	const { geometry, _indirectBuffer } = bvh;
 	let dist = Infinity;
@@ -32,11 +32,11 @@ export function intersectClosestTri/* @echo INDIRECT_STRING */( bvh, side, ray,
 		let intersection;
 		/* @if INDIRECT */
 
-		intersection = intersectTri( geometry, side, ray, _indirectBuffer ? _indirectBuffer[ i ] : i );
+		intersection = intersectTri( geometry, side, ray, _indirectBuffer ? _indirectBuffer[ i ] : i, null, near, far );
 
 		/* @else */
 
-		intersection = intersectTri( geometry, side, ray, i );
+		intersection = intersectTri( geometry, side, ray, i, null, near, far );
 
 		/* @endif */
 
diff --git a/src/index.d.ts b/src/index.d.ts
index 8bd9888..ebffe22 100644
--- a/src/index.d.ts
+++ b/src/index.d.ts
@@ -53,9 +53,9 @@ export class MeshBVH {
 
   constructor( geometry: BufferGeometry, options?: MeshBVHOptions );
 
-  raycast( ray: Ray, materialOrSide: Side | Array<Material> | Material ): Array<Intersection>
+  raycast( ray: Ray, materialOrSide?: Side | Array<Material> | Material, near?: number, far?: number ): Array<Intersection>
 
-  raycastFirst( ray: Ray, materialOrSide: Side | Array<Material> | Material ): Intersection;
+  raycastFirst( ray: Ray, materialOrSide?: Side | Array<Material> | Material, near?: number, far?: number ): Intersection;
 
   intersectsSphere( sphere: Sphere ): boolean;
 
diff --git a/src/utils/ExtensionUtilities.js b/src/utils/ExtensionUtilities.js
index 7c72051..90ae28c 100644
--- a/src/utils/ExtensionUtilities.js
+++ b/src/utils/ExtensionUtilities.js
@@ -1,9 +1,11 @@
-import { Ray, Matrix4, Mesh } from 'three';
+import { Ray, Matrix4, Mesh, Vector3 } from 'three';
 import { convertRaycastIntersect } from './GeometryRayIntersectUtilities.js';
 import { MeshBVH } from '../core/MeshBVH.js';
 
 const ray = /* @__PURE__ */ new Ray();
+const direction = /* @__PURE__ */ new Vector3();
 const tmpInverseMatrix = /* @__PURE__ */ new Matrix4();
+const worldScale = /* @__PURE__ */ new Vector3();
 const origMeshRaycastFunc = Mesh.prototype.raycast;
 
 export function acceleratedRaycast( raycaster, intersects ) {
@@ -15,10 +17,17 @@ export function acceleratedRaycast( raycaster, intersects ) {
 		tmpInverseMatrix.copy( this.matrixWorld ).invert();
 		ray.copy( raycaster.ray ).applyMatrix4( tmpInverseMatrix );
 
+		this.getWorldScale( worldScale );
+		direction.copy( ray.direction ).multiply( worldScale );
+
+		const scaleFactor = direction.length();
+		const near = raycaster.near / scaleFactor;
+		const far = raycaster.far / scaleFactor;
+
 		const bvh = this.geometry.boundsTree;
 		if ( raycaster.firstHitOnly === true ) {
 
-			const hit = convertRaycastIntersect( bvh.raycastFirst( ray, this.material ), this, raycaster );
+			const hit = convertRaycastIntersect( bvh.raycastFirst( ray, this.material, near, far ), this, raycaster );
 			if ( hit ) {
 
 				intersects.push( hit );
@@ -27,7 +36,7 @@ export function acceleratedRaycast( raycaster, intersects ) {
 
 		} else {
 
-			const hits = bvh.raycast( ray, this.material );
+			const hits = bvh.raycast( ray, this.material, near, far );
 			for ( let i = 0, l = hits.length; i < l; i ++ ) {
 
 				const hit = convertRaycastIntersect( hits[ i ], this, raycaster );
diff --git a/src/utils/GeometryRayIntersectUtilities.js b/src/utils/GeometryRayIntersectUtilities.js
index a2aac97..70238ea 100644
--- a/src/utils/GeometryRayIntersectUtilities.js
+++ b/src/utils/GeometryRayIntersectUtilities.js
@@ -12,14 +12,6 @@ export function convertRaycastIntersect( hit, object, raycaster ) {
 	hit.distance = hit.point.distanceTo( raycaster.ray.origin );
 	hit.object = object;
 
-	if ( hit.distance < raycaster.near || hit.distance > raycaster.far ) {
-
-		return null;
-
-	} else {
-
-		return hit;
-
-	}
+	return hit;
 
 }
diff --git a/src/utils/ThreeRayIntersectUtilities.js b/src/utils/ThreeRayIntersectUtilities.js
index 24ccfd6..d34bf22 100644
--- a/src/utils/ThreeRayIntersectUtilities.js
+++ b/src/utils/ThreeRayIntersectUtilities.js
@@ -15,7 +15,7 @@ const _normalB = /* @__PURE__ */ new Vector3();
 const _normalC = /* @__PURE__ */ new Vector3();
 
 const _intersectionPoint = /* @__PURE__ */ new Vector3();
-function checkIntersection( ray, pA, pB, pC, point, side ) {
+function checkIntersection( ray, pA, pB, pC, point, side, near, far ) {
 
 	let intersect;
 	if ( side === BackSide ) {
@@ -32,6 +32,8 @@ function checkIntersection( ray, pA, pB, pC, point, side ) {
 
 	const distance = ray.origin.distanceTo( point );
 
+	if ( distance < near || distance > far ) return null;
+
 	return {
 
 		distance: distance,
@@ -41,13 +43,13 @@ function checkIntersection( ray, pA, pB, pC, point, side ) {
 
 }
 
-function checkBufferGeometryIntersection( ray, position, normal, uv, uv1, a, b, c, side ) {
+function checkBufferGeometryIntersection( ray, position, normal, uv, uv1, a, b, c, side, near, far ) {
 
 	_vA.fromBufferAttribute( position, a );
 	_vB.fromBufferAttribute( position, b );
 	_vC.fromBufferAttribute( position, c );
 
-	const intersection = checkIntersection( ray, _vA, _vB, _vC, _intersectionPoint, side );
+	const intersection = checkIntersection( ray, _vA, _vB, _vC, _intersectionPoint, side, near, far );
 
 	if ( intersection ) {
 
@@ -106,7 +108,7 @@ function checkBufferGeometryIntersection( ray, position, normal, uv, uv1, a, b,
 }
 
 // https://github.com/mrdoob/three.js/blob/0aa87c999fe61e216c1133fba7a95772b503eddf/src/objects/Mesh.js#L258
-function intersectTri( geo, side, ray, tri, intersections ) {
+function intersectTri( geo, side, ray, tri, intersections, near, far ) {
 
 	const triOffset = tri * 3;
 	let a = triOffset + 0;
@@ -123,7 +125,7 @@ function intersectTri( geo, side, ray, tri, intersections ) {
 	}
 
 	const { position, normal, uv, uv1 } = geo.attributes;
-	const intersection = checkBufferGeometryIntersection( ray, position, normal, uv, uv1, a, b, c, side );
+	const intersection = checkBufferGeometryIntersection( ray, position, normal, uv, uv1, a, b, c, side, near, far );
 
 	if ( intersection ) {
 


Test patch:
diff --git a/test/RandomRaycasts.test.js b/test/RandomRaycasts.test.js
index fb23b92..e200872 100644
--- a/test/RandomRaycasts.test.js
+++ b/test/RandomRaycasts.test.js
@@ -34,6 +34,10 @@ describe( 'Random SAH intersections', () => runRandomTests( { strategy: SAH } )
 describe( 'Random Interleaved SAH intersections', () => runRandomTests( { strategy: SAH, interleaved: true } ) );
 describe( 'Random Indirect Buffer SAH intersections', () => runRandomTests( { strategy: SAH, indirect: true } ) );
 
+describe( 'Random CENTER intersections with near', () => runRandomTests( { strategy: CENTER, near: 6 } ) );
+describe( 'Random CENTER intersections with far', () => runRandomTests( { strategy: CENTER, far: 7 } ) );
+describe( 'Random CENTER intersections with near and far', () => runRandomTests( { strategy: CENTER, near: 6, far: 7 } ) );
+
 function runRandomTests( options ) {
 
 	const transformSeed = Math.floor( Math.random() * 1e10 );
@@ -77,6 +81,18 @@ function runRandomTests( options ) {
 			scene = new Scene();
 			raycaster = new Raycaster();
 
+			if ( options.near !== undefined ) {
+
+				raycaster.near = options.near;
+
+			}
+
+			if ( options.far !== undefined ) {
+
+				raycaster.far = options.far;
+
+			}
+
 			setSeed( transformSeed );
 			random(); // call random() to seed with a larger value