From b4ad02fcdf9da46e2d2309a00129200d5d2b9684 Mon Sep 17 00:00:00 2001 From: ANDREA GARGARO Date: Mon, 26 Aug 2024 11:31:21 +0200 Subject: [PATCH 1/8] closestPointToPoint --- src/core/MeshBVH.js | 30 ++-- src/core/cast/closestPointToPoint.js | 78 ---------- src/core/cast/closestPointToPoint.template.js | 133 ++++++++++++++++++ 3 files changed, 155 insertions(+), 86 deletions(-) delete mode 100644 src/core/cast/closestPointToPoint.js create mode 100644 src/core/cast/closestPointToPoint.template.js diff --git a/src/core/MeshBVH.js b/src/core/MeshBVH.js index 95ac647c..d75ea4fc 100644 --- a/src/core/MeshBVH.js +++ b/src/core/MeshBVH.js @@ -5,7 +5,8 @@ import { OrientedBox } from '../math/OrientedBox.js'; import { arrayToBox } from '../utils/ArrayBoxUtilities.js'; import { ExtendedTrianglePool } from '../utils/ExtendedTrianglePool.js'; import { shapecast } from './cast/shapecast.js'; -import { closestPointToPoint } from './cast/closestPointToPoint.js'; +import { closestPointToPoint } from './cast/closestPointToPoint.generated.js'; +import { closestPointToPoint_indirect } from './cast/closestPointToPoint_indirect.generated.js'; import { iterateOverTriangles } from './utils/iterationUtils.generated.js'; import { refit } from './cast/refit.generated.js'; @@ -519,13 +520,26 @@ export class MeshBVH { closestPointToPoint( point, target = { }, minThreshold = 0, maxThreshold = Infinity ) { - return closestPointToPoint( - this, - point, - target, - minThreshold, - maxThreshold, - ); + const closestPointToPointFunc = this.indirect ? closestPointToPoint_indirect : closestPointToPoint; + const roots = this._roots; + let result = null; + + for ( let i = 0, l = roots.length; i < l; i ++ ) { + + result = closestPointToPointFunc( + this, + i, + point, + target, + minThreshold, + maxThreshold, + ); + + if ( result && result.distance <= minThreshold ) break; + + } + + return result; } diff --git a/src/core/cast/closestPointToPoint.js b/src/core/cast/closestPointToPoint.js deleted file mode 100644 index c5ae1a46..00000000 --- a/src/core/cast/closestPointToPoint.js +++ /dev/null @@ -1,78 +0,0 @@ -import { Vector3 } from 'three'; - -const temp = /* @__PURE__ */ new Vector3(); -const temp1 = /* @__PURE__ */ new Vector3(); - -export function closestPointToPoint( - bvh, - point, - target = { }, - minThreshold = 0, - maxThreshold = Infinity, -) { - - // early out if under minThreshold - // skip checking if over maxThreshold - // set minThreshold = maxThreshold to quickly check if a point is within a threshold - // returns Infinity if no value found - const minThresholdSq = minThreshold * minThreshold; - const maxThresholdSq = maxThreshold * maxThreshold; - let closestDistanceSq = Infinity; - let closestDistanceTriIndex = null; - bvh.shapecast( - - { - - boundsTraverseOrder: box => { - - temp.copy( point ).clamp( box.min, box.max ); - return temp.distanceToSquared( point ); - - }, - - intersectsBounds: ( box, isLeaf, score ) => { - - return score < closestDistanceSq && score < maxThresholdSq; - - }, - - intersectsTriangle: ( tri, triIndex ) => { - - tri.closestPointToPoint( point, temp ); - const distSq = point.distanceToSquared( temp ); - if ( distSq < closestDistanceSq ) { - - temp1.copy( temp ); - closestDistanceSq = distSq; - closestDistanceTriIndex = triIndex; - - } - - if ( distSq < minThresholdSq ) { - - return true; - - } else { - - return false; - - } - - }, - - } - - ); - - if ( closestDistanceSq === Infinity ) return null; - - const closestDistance = Math.sqrt( closestDistanceSq ); - - if ( ! target.point ) target.point = temp1.clone(); - else target.point.copy( temp1 ); - target.distance = closestDistance, - target.faceIndex = closestDistanceTriIndex; - - return target; - -} diff --git a/src/core/cast/closestPointToPoint.template.js b/src/core/cast/closestPointToPoint.template.js new file mode 100644 index 00000000..a3f1b97d --- /dev/null +++ b/src/core/cast/closestPointToPoint.template.js @@ -0,0 +1,133 @@ +import { Vector3 } from 'three'; +import { COUNT, OFFSET, LEFT_NODE, RIGHT_NODE, IS_LEAF } from '../utils/nodeBufferUtils.js'; +import { BufferStack } from '../utils/BufferStack.js'; +import { ExtendedTrianglePool } from '../../utils/ExtendedTrianglePool.js'; +import { setTriangle } from '../../utils/TriangleUtilities.js'; + +const temp = /* @__PURE__ */ new Vector3(); +const temp1 = /* @__PURE__ */ new Vector3(); + +// ADD INDIRECT SUPPORT + +export function closestPointToPoint/* @echo INDIRECT_STRING */( + bvh, + root, + point, + target = { }, + minThreshold = 0, + maxThreshold = Infinity +) { + + const minThresholdSq = minThreshold * minThreshold; + const maxThresholdSq = maxThreshold * maxThreshold; + let closestDistanceSq = Infinity; + let closestDistanceTriIndex = null; + + const { geometry } = bvh; + const { index } = geometry; + const pos = geometry.attributes.position; + const triangle = ExtendedTrianglePool.getPrimitive(); + + BufferStack.setBuffer( bvh._roots[ root ] ); + const { float32Array, uint16Array, uint32Array } = BufferStack; // moved try bench + _closestPointToPoint( root ); + BufferStack.clearBuffer(); + + if ( closestDistanceSq === Infinity ) return null; + + const closestDistance = Math.sqrt( closestDistanceSq ); + + if ( ! target.point ) target.point = temp1.clone(); + else target.point.copy( temp1 ); + target.distance = closestDistance; + target.faceIndex = closestDistanceTriIndex; + + return target; + + + function _closestPointToPoint( nodeIndex32 ) { + + const nodeIndex16 = nodeIndex32 * 2; + const isLeaf = IS_LEAF( nodeIndex16, uint16Array ); + if ( isLeaf ) { + + const offset = OFFSET( nodeIndex32, uint32Array ); + const count = COUNT( nodeIndex16, uint16Array ); + + for ( let i = offset, l = count + offset; i < l; i ++ ) { + + /* @if INDIRECT */ + + const ti = bvh.resolveTriangleIndex( i ); + setTriangle( triangle, 3 * ti, index, pos ); + + /* @else */ + + setTriangle( triangle, i * 3, index, pos ); + + /* @endif */ + + triangle.needsUpdate = true; + + triangle.closestPointToPoint( point, temp ); + const distSq = point.distanceToSquared( temp ); + if ( distSq < closestDistanceSq ) { + + temp1.copy( temp ); + closestDistanceSq = distSq; + closestDistanceTriIndex = i; + + if ( distSq < minThresholdSq ) return true; + + } + + } + + return; + + } + + const leftIndex = LEFT_NODE( nodeIndex32 ); + const rightIndex = RIGHT_NODE( nodeIndex32, uint32Array ); + + const leftDistance = distanceSquaredPointToBox( leftIndex, float32Array, point ); + const rightDistance = distanceSquaredPointToBox( rightIndex, float32Array, point ); + + if ( leftDistance < rightDistance ) { + + if ( leftDistance < closestDistanceSq && leftDistance < maxThresholdSq ) { + + if ( _closestPointToPoint( leftIndex ) ) return true; + if ( rightDistance < closestDistanceSq ) return _closestPointToPoint( rightIndex ); + + } + + } else if ( rightDistance < closestDistanceSq && rightDistance < maxThresholdSq ) { + + if ( _closestPointToPoint( rightIndex ) ) return true; + if ( leftDistance < closestDistanceSq ) return _closestPointToPoint( leftIndex ); + + } + + } + +} + +// TODO move it +export function distanceSquaredPointToBox( nodeIndex32, array, point ) { + + const xMin = array[ nodeIndex32 + 0 ] - point.x; + const xMax = point.x - array[ nodeIndex32 + 3 ]; + const dx = xMin > xMax ? ( xMin > 0 ? xMin : 0 ) : ( xMax > 0 ? xMax : 0 ); + + const yMin = array[ nodeIndex32 + 1 ] - point.y; + const yMax = point.y - array[ nodeIndex32 + 4 ]; + const dy = yMin > yMax ? ( yMin > 0 ? yMin : 0 ) : ( yMax > 0 ? yMax : 0 ); + + const zMin = array[ nodeIndex32 + 2 ] - point.z; + const zMax = point.z - array[ nodeIndex32 + 5 ]; + const dz = zMin > zMax ? ( zMin > 0 ? zMin : 0 ) : ( zMax > 0 ? zMax : 0 ); + + return dx * dx + dy * dy + dz * dz; + +} From dd2cec2b425f718974ef87e22050294372017f71 Mon Sep 17 00:00:00 2001 From: ANDREA GARGARO Date: Mon, 26 Aug 2024 11:45:35 +0200 Subject: [PATCH 2/8] removed comment --- src/core/cast/closestPointToPoint.template.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/core/cast/closestPointToPoint.template.js b/src/core/cast/closestPointToPoint.template.js index a3f1b97d..95387ca6 100644 --- a/src/core/cast/closestPointToPoint.template.js +++ b/src/core/cast/closestPointToPoint.template.js @@ -7,8 +7,6 @@ import { setTriangle } from '../../utils/TriangleUtilities.js'; const temp = /* @__PURE__ */ new Vector3(); const temp1 = /* @__PURE__ */ new Vector3(); -// ADD INDIRECT SUPPORT - export function closestPointToPoint/* @echo INDIRECT_STRING */( bvh, root, From 3c5b46a5e900c9385f6b37c6984689beae6183d5 Mon Sep 17 00:00:00 2001 From: ANDREA GARGARO Date: Mon, 26 Aug 2024 18:00:50 +0200 Subject: [PATCH 3/8] example --- example/testToRemove.html | 23 ++++++ example/testToRemove.js | 75 +++++++++++++++++ src/core/MeshBVH.js | 14 ++++ src/core/cast/closestPointToPoint.js | 80 +++++++++++++++++++ src/core/cast/closestPointToPoint.template.js | 4 + 5 files changed, 196 insertions(+) create mode 100644 example/testToRemove.html create mode 100644 example/testToRemove.js create mode 100644 src/core/cast/closestPointToPoint.js diff --git a/example/testToRemove.html b/example/testToRemove.html new file mode 100644 index 00000000..49a724dd --- /dev/null +++ b/example/testToRemove.html @@ -0,0 +1,23 @@ + + + + three-mesh-bvh - Complex Geometry Raycasting + + + + + + + + diff --git a/example/testToRemove.js b/example/testToRemove.js new file mode 100644 index 00000000..1ebf00d1 --- /dev/null +++ b/example/testToRemove.js @@ -0,0 +1,75 @@ +import * as THREE from 'three'; +import { computeBoundsTree, SAH } from '../src'; + +THREE.BufferGeometry.prototype.computeBoundsTree = computeBoundsTree; + +// geometry setup +const radius = 5; +const tube = 0.4; +const tubularSegments = 200; +const radialSegments = 50; +const geometry = new THREE.TorusKnotGeometry( radius, tube, tubularSegments, radialSegments ); + +geometry.computeBoundsTree( { + maxLeafTris: 5, + strategy: SAH, +} ); + +const bvh = geometry.boundsTree; +const target = new THREE.Vector3(); +// const target2 = new THREE.Vector3(); + +// // TEST EQUALS RESULTS + +// for ( let i = 0; i < 100000; i ++ ) { + +// p.random().multiplyScalar( 10 ).subScalar( 5 ); +// bvh.closestPointToPoint( p, target ); +// bvh.closestPointToPointOld( p, target2 ); + +// if ( target.distance !== target2.distance ) console.error( "different result" ); + +// } + +// TEST PERFORMANCE + +const count = 50000; + +const points = new Array( count ); +for ( let i = 0; i < count; i ++ ) { + + points[ i ] = new THREE.Vector3().random().multiplyScalar( 5 ).subScalar( 2.5 ); + +} + +console.log( "TESTING NEW FUNCTION: " ); + +for ( let j = 0; j < 5; j ++ ) { + + console.time( count + " tries" ); + + for ( let i = 0; i < count; i ++ ) { + + bvh.closestPointToPoint( points[ i ], target ); + + } + + console.timeEnd( count + " tries" ); + +} + +console.log( "TESTING OLD FUNCTION: " ); + +for ( let j = 0; j < 5; j ++ ) { + + console.time( count + " tries" ); + + for ( let i = 0; i < count; i ++ ) { + + bvh.closestPointToPointOld( points[ i ], target ); + + } + + console.timeEnd( count + " tries" ); + +} diff --git a/src/core/MeshBVH.js b/src/core/MeshBVH.js index d75ea4fc..2cb598d0 100644 --- a/src/core/MeshBVH.js +++ b/src/core/MeshBVH.js @@ -7,6 +7,7 @@ import { ExtendedTrianglePool } from '../utils/ExtendedTrianglePool.js'; import { shapecast } from './cast/shapecast.js'; import { closestPointToPoint } from './cast/closestPointToPoint.generated.js'; import { closestPointToPoint_indirect } from './cast/closestPointToPoint_indirect.generated.js'; +import { closestPointToPointOld } from './cast/closestPointToPoint.js'; // REMOVE AFTER TEST import { iterateOverTriangles } from './utils/iterationUtils.generated.js'; import { refit } from './cast/refit.generated.js'; @@ -543,6 +544,19 @@ export class MeshBVH { } + // REMOVE AFTER TEST + closestPointToPointOld( point, target = { }, minThreshold = 0, maxThreshold = Infinity ) { + + return closestPointToPointOld( + this, + point, + target, + minThreshold, + maxThreshold, + ); + + } + getBoundingBox( target ) { target.makeEmpty(); diff --git a/src/core/cast/closestPointToPoint.js b/src/core/cast/closestPointToPoint.js new file mode 100644 index 00000000..dd039d8f --- /dev/null +++ b/src/core/cast/closestPointToPoint.js @@ -0,0 +1,80 @@ +import { Vector3 } from 'three'; + +// DELETE THIS AFTER TEST + +const temp = /* @__PURE__ */ new Vector3(); +const temp1 = /* @__PURE__ */ new Vector3(); + +export function closestPointToPointOld( + bvh, + point, + target = { }, + minThreshold = 0, + maxThreshold = Infinity, +) { + + // early out if under minThreshold + // skip checking if over maxThreshold + // set minThreshold = maxThreshold to quickly check if a point is within a threshold + // returns Infinity if no value found + const minThresholdSq = minThreshold * minThreshold; + const maxThresholdSq = maxThreshold * maxThreshold; + let closestDistanceSq = Infinity; + let closestDistanceTriIndex = null; + bvh.shapecast( + + { + + boundsTraverseOrder: box => { + + temp.copy( point ).clamp( box.min, box.max ); + return temp.distanceToSquared( point ); + + }, + + intersectsBounds: ( box, isLeaf, score ) => { + + return score < closestDistanceSq && score < maxThresholdSq; + + }, + + intersectsTriangle: ( tri, triIndex ) => { + + tri.closestPointToPoint( point, temp ); + const distSq = point.distanceToSquared( temp ); + if ( distSq < closestDistanceSq ) { + + temp1.copy( temp ); + closestDistanceSq = distSq; + closestDistanceTriIndex = triIndex; + + } + + if ( distSq < minThresholdSq ) { + + return true; + + } else { + + return false; + + } + + }, + + } + + ); + + if ( closestDistanceSq === Infinity ) return null; + + const closestDistance = Math.sqrt( closestDistanceSq ); + + if ( ! target.point ) target.point = temp1.clone(); + else target.point.copy( temp1 ); + target.distance = closestDistance, + target.faceIndex = closestDistanceTriIndex; + + return target; + +} diff --git a/src/core/cast/closestPointToPoint.template.js b/src/core/cast/closestPointToPoint.template.js index 95387ca6..025d2c9e 100644 --- a/src/core/cast/closestPointToPoint.template.js +++ b/src/core/cast/closestPointToPoint.template.js @@ -43,6 +43,10 @@ export function closestPointToPoint/* @echo INDIRECT_STRING */( return target; + // early out if under minThreshold + // skip checking if over maxThreshold + // set minThreshold = maxThreshold to quickly check if a point is within a threshold + // returns Infinity if no value found function _closestPointToPoint( nodeIndex32 ) { const nodeIndex16 = nodeIndex32 * 2; From 781dedef04e182cd62fc0e9082c2e00b72175760 Mon Sep 17 00:00:00 2001 From: ANDREA GARGARO Date: Tue, 27 Aug 2024 18:10:27 +0200 Subject: [PATCH 4/8] test edited --- example/testToRemove.js | 87 ++++++++++++------- src/core/cast/closestPointToPoint.template.js | 2 +- 2 files changed, 59 insertions(+), 30 deletions(-) diff --git a/example/testToRemove.js b/example/testToRemove.js index 1ebf00d1..38c3e9c3 100644 --- a/example/testToRemove.js +++ b/example/testToRemove.js @@ -3,11 +3,11 @@ import { computeBoundsTree, SAH } from '../src'; THREE.BufferGeometry.prototype.computeBoundsTree = computeBoundsTree; -// geometry setup -const radius = 5; -const tube = 0.4; -const tubularSegments = 200; -const radialSegments = 50; +const radius = 100; +const tube = 0.5; +// const tube = 0.5 * radius; +const tubularSegments = 800; +const radialSegments = 200; const geometry = new THREE.TorusKnotGeometry( radius, tube, tubularSegments, radialSegments ); geometry.computeBoundsTree( { @@ -15,54 +15,67 @@ geometry.computeBoundsTree( { strategy: SAH, } ); -const bvh = geometry.boundsTree; -const target = new THREE.Vector3(); -// const target2 = new THREE.Vector3(); +export class PRNG { -// // TEST EQUALS RESULTS + constructor( seed ) { -// for ( let i = 0; i < 100000; i ++ ) { + this._seed = seed; -// p.random().multiplyScalar( 10 ).subScalar( 5 ); -// bvh.closestPointToPoint( p, target ); -// bvh.closestPointToPointOld( p, target2 ); + } -// if ( target.distance !== target2.distance ) console.error( "different result" ); + next() { -// } + let t = ( this._seed += 0x6d2b79f5 ); + t = Math.imul( t ^ ( t >>> 15 ), t | 1 ); + t ^= t + Math.imul( t ^ ( t >>> 7 ), t | 61 ); + return ( ( t ^ ( t >>> 14 ) ) >>> 0 ) / 4294967296; -// TEST PERFORMANCE + } + + range( min, max ) { + + return min + ( max - min ) * this.next(); + + } + +} + + +const bvh = geometry.boundsTree; +const target = new THREE.Vector3(); +const target2 = new THREE.Vector3(); -const count = 50000; +const count = 10000; +const r = new PRNG( 10000 ); const points = new Array( count ); for ( let i = 0; i < count; i ++ ) { - points[ i ] = new THREE.Vector3().random().multiplyScalar( 5 ).subScalar( 2.5 ); + points[ i ] = new THREE.Vector3( r.next(), r.next(), r.next() ).multiplyScalar( 5 ).subScalar( 2.5 ); } -console.log( "TESTING NEW FUNCTION: " ); +// TEST EQUALS RESULTS -for ( let j = 0; j < 5; j ++ ) { +for ( let i = 0; i < count; i ++ ) { - console.time( count + " tries" ); + bvh.closestPointToPoint( points[ i ], target ); + bvh.closestPointToPointOld( points[ i ], target2 ); - for ( let i = 0; i < count; i ++ ) { + if ( target.distance !== target2.distance ) { - bvh.closestPointToPoint( points[ i ], target ); + const diff = target.distance - target2.distance; + console.error( "error: " + ( diff / target2.distance * 100 ) + "%" ); } - console.timeEnd( count + " tries" ); - } -console.log( "TESTING OLD FUNCTION: " ); +// TEST PERFORMANCE -for ( let j = 0; j < 5; j ++ ) { +function benchmark() { - console.time( count + " tries" ); + const startOld = performance.now(); for ( let i = 0; i < count; i ++ ) { @@ -70,6 +83,22 @@ for ( let j = 0; j < 5; j ++ ) { } - console.timeEnd( count + " tries" ); + const endOld = performance.now() - startOld; + const startNew = performance.now(); + + for ( let i = 0; i < count; i ++ ) { + + bvh.closestPointToPoint( points[ i ], target ); + + } + + const endNew = performance.now() - startNew; + + console.log( `New: ${endNew.toFixed( 1 )} / Old: ${endOld.toFixed( 1 )} / Diff: ${( ( 1 - ( endOld / endNew ) ) * 100).toFixed( 2 )} %` ); + + console.log( "..." ); } + +benchmark(); +setInterval( () => benchmark(), 1000 ); diff --git a/src/core/cast/closestPointToPoint.template.js b/src/core/cast/closestPointToPoint.template.js index 025d2c9e..54c0abae 100644 --- a/src/core/cast/closestPointToPoint.template.js +++ b/src/core/cast/closestPointToPoint.template.js @@ -95,7 +95,7 @@ export function closestPointToPoint/* @echo INDIRECT_STRING */( const leftDistance = distanceSquaredPointToBox( leftIndex, float32Array, point ); const rightDistance = distanceSquaredPointToBox( rightIndex, float32Array, point ); - if ( leftDistance < rightDistance ) { + if ( leftDistance <= rightDistance ) { if ( leftDistance < closestDistanceSq && leftDistance < maxThresholdSq ) { From 06e5cc5266b1df35185d735196a4e11692d597c9 Mon Sep 17 00:00:00 2001 From: ANDREA GARGARO Date: Tue, 27 Aug 2024 20:21:47 +0200 Subject: [PATCH 5/8] Added sort --- example/testToRemove.js | 62 +++++---- src/core/MeshBVH.js | 27 ++++ src/core/cast/closestPointToPoint.template.js | 26 +--- .../cast/closestPointToPointSort.template.js | 119 ++++++++++++++++++ src/core/utils/SortedListDesc.js | 53 ++++++++ src/core/utils/distanceUtils.js | 20 +++ 6 files changed, 259 insertions(+), 48 deletions(-) create mode 100644 src/core/cast/closestPointToPointSort.template.js create mode 100644 src/core/utils/SortedListDesc.js create mode 100644 src/core/utils/distanceUtils.js diff --git a/example/testToRemove.js b/example/testToRemove.js index 38c3e9c3..f232fd56 100644 --- a/example/testToRemove.js +++ b/example/testToRemove.js @@ -3,17 +3,19 @@ import { computeBoundsTree, SAH } from '../src'; THREE.BufferGeometry.prototype.computeBoundsTree = computeBoundsTree; -const radius = 100; -const tube = 0.5; -// const tube = 0.5 * radius; -const tubularSegments = 800; -const radialSegments = 200; -const geometry = new THREE.TorusKnotGeometry( radius, tube, tubularSegments, radialSegments ); - -geometry.computeBoundsTree( { - maxLeafTris: 5, - strategy: SAH, -} ); +const spawnPointRadius = 10; +const radius = 100; // if radius 100 and tube 0.1, sort works really good. +const tube = 0.1; +const segmentsMultiplier = 8; +const maxLeafTris = 5; +const strategy = SAH; + +const seed = 20000; + +// const geometry = new THREE.SphereGeometry( radius, 8 * segmentsMultiplier, 4 * segmentsMultiplier ); +const geometry = new THREE.TorusKnotGeometry( radius, tube, 64 * segmentsMultiplier, 8 * segmentsMultiplier ); + +geometry.computeBoundsTree( { maxLeafTris, strategy } ); export class PRNG { @@ -40,36 +42,35 @@ export class PRNG { } - const bvh = geometry.boundsTree; const target = new THREE.Vector3(); const target2 = new THREE.Vector3(); -const count = 10000; -const r = new PRNG( 10000 ); +const count = 5000; +const r = new PRNG( seed ); const points = new Array( count ); for ( let i = 0; i < count; i ++ ) { - points[ i ] = new THREE.Vector3( r.next(), r.next(), r.next() ).multiplyScalar( 5 ).subScalar( 2.5 ); + points[ i ] = new THREE.Vector3( r.range( - spawnPointRadius, spawnPointRadius ), r.range( - spawnPointRadius, spawnPointRadius ), r.range( - spawnPointRadius, spawnPointRadius ) ); } -// TEST EQUALS RESULTS +// // TEST EQUALS RESULTS -for ( let i = 0; i < count; i ++ ) { +// for ( let i = 0; i < count; i ++ ) { - bvh.closestPointToPoint( points[ i ], target ); - bvh.closestPointToPointOld( points[ i ], target2 ); +// bvh.closestPointToPoint( points[ i ], target ); +// bvh.closestPointToPointOld( points[ i ], target2 ); - if ( target.distance !== target2.distance ) { +// if ( target.distance !== target2.distance ) { - const diff = target.distance - target2.distance; - console.error( "error: " + ( diff / target2.distance * 100 ) + "%" ); +// const diff = target.distance - target2.distance; +// console.error( "error: " + ( diff / target2.distance * 100 ) + "%" ); - } +// } -} +// } // TEST PERFORMANCE @@ -93,10 +94,19 @@ function benchmark() { } const endNew = performance.now() - startNew; + const startSort = performance.now(); + + for ( let i = 0; i < count; i ++ ) { + + bvh.closestPointToPointSort( points[ i ], target ); + + } + + const endSort = performance.now() - startSort; - console.log( `New: ${endNew.toFixed( 1 )} / Old: ${endOld.toFixed( 1 )} / Diff: ${( ( 1 - ( endOld / endNew ) ) * 100).toFixed( 2 )} %` ); + const bestEnd = Math.min( endSort, endNew ); - console.log( "..." ); + console.log( `New: ${endNew.toFixed( 1 )} / Sort: ${endSort.toFixed( 1 )} / Old: ${endOld.toFixed( 1 )} / Diff: ${( ( 1 - ( endOld / bestEnd ) ) * 100 ).toFixed( 2 )} %` ); } diff --git a/src/core/MeshBVH.js b/src/core/MeshBVH.js index 2cb598d0..82500dc7 100644 --- a/src/core/MeshBVH.js +++ b/src/core/MeshBVH.js @@ -7,6 +7,8 @@ import { ExtendedTrianglePool } from '../utils/ExtendedTrianglePool.js'; import { shapecast } from './cast/shapecast.js'; import { closestPointToPoint } from './cast/closestPointToPoint.generated.js'; import { closestPointToPoint_indirect } from './cast/closestPointToPoint_indirect.generated.js'; +import { closestPointToPointSort } from './cast/closestPointToPointSort.generated.js'; +import { closestPointToPointSort_indirect } from './cast/closestPointToPointSort_indirect.generated.js'; import { closestPointToPointOld } from './cast/closestPointToPoint.js'; // REMOVE AFTER TEST import { iterateOverTriangles } from './utils/iterationUtils.generated.js'; @@ -544,6 +546,31 @@ export class MeshBVH { } + closestPointToPointSort( point, target = { }, minThreshold = 0, maxThreshold = Infinity ) { + + const closestPointToPointFunc = this.indirect ? closestPointToPointSort_indirect : closestPointToPointSort; + const roots = this._roots; + let result = null; + + for ( let i = 0, l = roots.length; i < l; i ++ ) { + + result = closestPointToPointFunc( + this, + i, + point, + target, + minThreshold, + maxThreshold, + ); + + if ( result && result.distance <= minThreshold ) break; + + } + + return result; + + } + // REMOVE AFTER TEST closestPointToPointOld( point, target = { }, minThreshold = 0, maxThreshold = Infinity ) { diff --git a/src/core/cast/closestPointToPoint.template.js b/src/core/cast/closestPointToPoint.template.js index 54c0abae..4370de6b 100644 --- a/src/core/cast/closestPointToPoint.template.js +++ b/src/core/cast/closestPointToPoint.template.js @@ -3,6 +3,7 @@ import { COUNT, OFFSET, LEFT_NODE, RIGHT_NODE, IS_LEAF } from '../utils/nodeBuff import { BufferStack } from '../utils/BufferStack.js'; import { ExtendedTrianglePool } from '../../utils/ExtendedTrianglePool.js'; import { setTriangle } from '../../utils/TriangleUtilities.js'; +import { closestDistanceSquaredPointToBox } from '../utils/distanceUtils.js'; const temp = /* @__PURE__ */ new Vector3(); const temp1 = /* @__PURE__ */ new Vector3(); @@ -27,7 +28,7 @@ export function closestPointToPoint/* @echo INDIRECT_STRING */( const triangle = ExtendedTrianglePool.getPrimitive(); BufferStack.setBuffer( bvh._roots[ root ] ); - const { float32Array, uint16Array, uint32Array } = BufferStack; // moved try bench + const { float32Array, uint16Array, uint32Array } = BufferStack; _closestPointToPoint( root ); BufferStack.clearBuffer(); @@ -92,8 +93,8 @@ export function closestPointToPoint/* @echo INDIRECT_STRING */( const leftIndex = LEFT_NODE( nodeIndex32 ); const rightIndex = RIGHT_NODE( nodeIndex32, uint32Array ); - const leftDistance = distanceSquaredPointToBox( leftIndex, float32Array, point ); - const rightDistance = distanceSquaredPointToBox( rightIndex, float32Array, point ); + const leftDistance = closestDistanceSquaredPointToBox( leftIndex, float32Array, point ); + const rightDistance = closestDistanceSquaredPointToBox( rightIndex, float32Array, point ); if ( leftDistance <= rightDistance ) { @@ -114,22 +115,3 @@ export function closestPointToPoint/* @echo INDIRECT_STRING */( } } - -// TODO move it -export function distanceSquaredPointToBox( nodeIndex32, array, point ) { - - const xMin = array[ nodeIndex32 + 0 ] - point.x; - const xMax = point.x - array[ nodeIndex32 + 3 ]; - const dx = xMin > xMax ? ( xMin > 0 ? xMin : 0 ) : ( xMax > 0 ? xMax : 0 ); - - const yMin = array[ nodeIndex32 + 1 ] - point.y; - const yMax = point.y - array[ nodeIndex32 + 4 ]; - const dy = yMin > yMax ? ( yMin > 0 ? yMin : 0 ) : ( yMax > 0 ? yMax : 0 ); - - const zMin = array[ nodeIndex32 + 2 ] - point.z; - const zMax = point.z - array[ nodeIndex32 + 5 ]; - const dz = zMin > zMax ? ( zMin > 0 ? zMin : 0 ) : ( zMax > 0 ? zMax : 0 ); - - return dx * dx + dy * dy + dz * dz; - -} diff --git a/src/core/cast/closestPointToPointSort.template.js b/src/core/cast/closestPointToPointSort.template.js new file mode 100644 index 00000000..724bd93c --- /dev/null +++ b/src/core/cast/closestPointToPointSort.template.js @@ -0,0 +1,119 @@ +import { Vector3 } from 'three'; +import { COUNT, OFFSET, LEFT_NODE, RIGHT_NODE, IS_LEAF } from '../utils/nodeBufferUtils.js'; +import { BufferStack } from '../utils/BufferStack.js'; +import { ExtendedTrianglePool } from '../../utils/ExtendedTrianglePool.js'; +import { setTriangle } from '../../utils/TriangleUtilities.js'; +import { closestDistanceSquaredPointToBox } from '../utils/distanceUtils.js'; +import { SortedListDesc } from '../utils/SortedListDesc.js'; + +const temp = /* @__PURE__ */ new Vector3(); +const temp1 = /* @__PURE__ */ new Vector3(); +const sortedList = new SortedListDesc(); + +export function closestPointToPointSort/* @echo INDIRECT_STRING */( + bvh, + root, + point, + target = { }, + minThreshold = 0, + maxThreshold = Infinity +) { + + const minThresholdSq = minThreshold * minThreshold; + const maxThresholdSq = maxThreshold * maxThreshold; + let closestDistanceSq = Infinity; + let closestDistanceTriIndex = null; + + const { geometry } = bvh; + const { index } = geometry; + const pos = geometry.attributes.position; + const triangle = ExtendedTrianglePool.getPrimitive(); + + sortedList.clear(); + BufferStack.setBuffer( bvh._roots[ root ] ); + const { float32Array, uint16Array, uint32Array } = BufferStack; + + let node = { nodeIndex32: 0, distance: closestDistanceSquaredPointToBox( 0, float32Array, point ) }; + + do { + + const { distance } = node; + + if ( distance >= closestDistanceSq ) return; + + const { nodeIndex32 } = node; + + const nodeIndex16 = nodeIndex32 * 2; + const isLeaf = IS_LEAF( nodeIndex16, uint16Array ); + if ( isLeaf ) { + + const offset = OFFSET( nodeIndex32, uint32Array ); + const count = COUNT( nodeIndex16, uint16Array ); + + for ( let i = offset, l = count + offset; i < l; i ++ ) { + + /* @if INDIRECT */ + + const ti = bvh.resolveTriangleIndex( i ); + setTriangle( triangle, 3 * ti, index, pos ); + + /* @else */ + + setTriangle( triangle, i * 3, index, pos ); + + /* @endif */ + + triangle.needsUpdate = true; + + triangle.closestPointToPoint( point, temp ); + const distSq = point.distanceToSquared( temp ); + if ( distSq < closestDistanceSq ) { + + temp1.copy( temp ); + closestDistanceSq = distSq; + closestDistanceTriIndex = i; + + if ( distSq < minThresholdSq ) return true; + + } + + } + + continue; + + } + + const leftIndex = LEFT_NODE( nodeIndex32 ); + const rightIndex = RIGHT_NODE( nodeIndex32, uint32Array ); + + const leftDistance = closestDistanceSquaredPointToBox( leftIndex, float32Array, point ); + const rightDistance = closestDistanceSquaredPointToBox( rightIndex, float32Array, point ); + + if ( leftDistance < closestDistanceSq && leftDistance < maxThresholdSq ) { + + sortedList.push( { nodeIndex32: leftIndex, distance: leftDistance } ); + + } + + if ( rightDistance < closestDistanceSq && rightDistance < maxThresholdSq ) { + + sortedList.push( { nodeIndex32: rightIndex, distance: rightDistance } ); + + } + + } while ( node = sortedList.pop() ); + + BufferStack.clearBuffer(); + + if ( closestDistanceSq === Infinity ) return null; + + const closestDistance = Math.sqrt( closestDistanceSq ); + + if ( ! target.point ) target.point = temp1.clone(); + else target.point.copy( temp1 ); + target.distance = closestDistance; + target.faceIndex = closestDistanceTriIndex; + + return target; + +} diff --git a/src/core/utils/SortedListDesc.js b/src/core/utils/SortedListDesc.js new file mode 100644 index 00000000..8bed4468 --- /dev/null +++ b/src/core/utils/SortedListDesc.js @@ -0,0 +1,53 @@ +export class SortedListDesc { + + constructor() { + + this.array = []; + + } + + clear() { + + this.array = []; + + } + + + push( node ) { + + const index = this.binarySearch( node.distance ); + this.array.splice( index, 0, node ); + + } + + pop() { + + return this.array.pop(); + + } + + binarySearch( value ) { + + const array = this.array; + + let start = 0; + let end = array.length - 1; + let index = 0; + + while ( start <= end ) { + + index = Math.ceil( ( start + end ) / 2 ); + + if ( index === 0 ) break; + if ( array[ index ].distance <= value && array[ index - 1 ].distance >= value ) return index; + + if ( value > array[ index ].distance ) end = index - 1; + else start = index + 1; + + } + + return value < array[ index ]?.distance ? index + 1 : index; + + } + +} diff --git a/src/core/utils/distanceUtils.js b/src/core/utils/distanceUtils.js new file mode 100644 index 00000000..7b9e8ef7 --- /dev/null +++ b/src/core/utils/distanceUtils.js @@ -0,0 +1,20 @@ +export function closestDistanceSquaredPointToBox( nodeIndex32, array, point ) { + + const xMin = array[ nodeIndex32 + 0 ] - point.x; + const xMax = point.x - array[ nodeIndex32 + 3 ]; + let dx = xMin > xMax ? xMin : xMax; + dx = dx > 0 ? dx : 0; + + const yMin = array[ nodeIndex32 + 1 ] - point.y; + const yMax = point.y - array[ nodeIndex32 + 4 ]; + let dy = yMin > yMax ? yMin : yMax; + dy = dy > 0 ? dy : 0; + + const zMin = array[ nodeIndex32 + 2 ] - point.z; + const zMax = point.z - array[ nodeIndex32 + 5 ]; + let dz = zMin > zMax ? zMin : zMax; + dz = dz > 0 ? dz : 0; + + return dx * dx + dy * dy + dz * dz; + +} From c75e9b2e0015ac3c7cff2b4dbb4ba83b18799553 Mon Sep 17 00:00:00 2001 From: ANDREA GARGARO Date: Tue, 27 Aug 2024 22:15:05 +0200 Subject: [PATCH 6/8] add Hybrid --- example/testToRemove.js | 32 ++- src/core/MeshBVH.js | 28 +++ src/core/cast/closestPointToPoint.template.js | 6 +- .../closestPointToPointHybrid.template.js | 202 ++++++++++++++++++ .../cast/closestPointToPointSort.template.js | 6 +- 5 files changed, 257 insertions(+), 17 deletions(-) create mode 100644 src/core/cast/closestPointToPointHybrid.template.js diff --git a/example/testToRemove.js b/example/testToRemove.js index f232fd56..110cb3f8 100644 --- a/example/testToRemove.js +++ b/example/testToRemove.js @@ -3,13 +3,14 @@ import { computeBoundsTree, SAH } from '../src'; THREE.BufferGeometry.prototype.computeBoundsTree = computeBoundsTree; -const spawnPointRadius = 10; +const spawnPointRadius = 0.1; const radius = 100; // if radius 100 and tube 0.1, sort works really good. const tube = 0.1; -const segmentsMultiplier = 8; +const segmentsMultiplier = 32; const maxLeafTris = 5; const strategy = SAH; +const tries = 1000; const seed = 20000; // const geometry = new THREE.SphereGeometry( radius, 8 * segmentsMultiplier, 4 * segmentsMultiplier ); @@ -44,13 +45,12 @@ export class PRNG { const bvh = geometry.boundsTree; const target = new THREE.Vector3(); -const target2 = new THREE.Vector3(); +// const target2 = new THREE.Vector3(); -const count = 5000; const r = new PRNG( seed ); -const points = new Array( count ); -for ( let i = 0; i < count; i ++ ) { +const points = new Array( tries ); +for ( let i = 0; i < tries; i ++ ) { points[ i ] = new THREE.Vector3( r.range( - spawnPointRadius, spawnPointRadius ), r.range( - spawnPointRadius, spawnPointRadius ), r.range( - spawnPointRadius, spawnPointRadius ) ); @@ -78,7 +78,7 @@ function benchmark() { const startOld = performance.now(); - for ( let i = 0; i < count; i ++ ) { + for ( let i = 0; i < tries; i ++ ) { bvh.closestPointToPointOld( points[ i ], target ); @@ -87,7 +87,7 @@ function benchmark() { const endOld = performance.now() - startOld; const startNew = performance.now(); - for ( let i = 0; i < count; i ++ ) { + for ( let i = 0; i < tries; i ++ ) { bvh.closestPointToPoint( points[ i ], target ); @@ -96,17 +96,27 @@ function benchmark() { const endNew = performance.now() - startNew; const startSort = performance.now(); - for ( let i = 0; i < count; i ++ ) { + for ( let i = 0; i < tries; i ++ ) { bvh.closestPointToPointSort( points[ i ], target ); } const endSort = performance.now() - startSort; + const startHybrid = performance.now(); - const bestEnd = Math.min( endSort, endNew ); + for ( let i = 0; i < tries; i ++ ) { - console.log( `New: ${endNew.toFixed( 1 )} / Sort: ${endSort.toFixed( 1 )} / Old: ${endOld.toFixed( 1 )} / Diff: ${( ( 1 - ( endOld / bestEnd ) ) * 100 ).toFixed( 2 )} %` ); + bvh.closestPointToPointHybrid( points[ i ], target ); + + } + + const endHybrid = performance.now() - startHybrid; + + const bestEnd = Math.min( endSort, endNew, endHybrid ); + const best = bestEnd === endSort ? "Sorted" : ( bestEnd === endNew ? "New" : "Hybrid" ); + + console.log( `New: ${endNew.toFixed( 1 )}ms / Sorted: ${endSort.toFixed( 1 )}ms / Hybrid: ${endHybrid.toFixed( 1 )}ms / Old: ${endOld.toFixed( 1 )}ms / Diff: ${( ( 1 - ( endOld / bestEnd ) ) * 100 ).toFixed( 2 )} % / Best: ${best}` ); } diff --git a/src/core/MeshBVH.js b/src/core/MeshBVH.js index 82500dc7..da33bc88 100644 --- a/src/core/MeshBVH.js +++ b/src/core/MeshBVH.js @@ -9,6 +9,8 @@ import { closestPointToPoint } from './cast/closestPointToPoint.generated.js'; import { closestPointToPoint_indirect } from './cast/closestPointToPoint_indirect.generated.js'; import { closestPointToPointSort } from './cast/closestPointToPointSort.generated.js'; import { closestPointToPointSort_indirect } from './cast/closestPointToPointSort_indirect.generated.js'; +import { closestPointToPointHybrid } from './cast/closestPointToPointHybrid.generated.js'; +import { closestPointToPointHybrid_indirect } from './cast/closestPointToPointHybrid_indirect.generated.js'; import { closestPointToPointOld } from './cast/closestPointToPoint.js'; // REMOVE AFTER TEST import { iterateOverTriangles } from './utils/iterationUtils.generated.js'; @@ -571,6 +573,32 @@ export class MeshBVH { } + closestPointToPointHybrid( point, target = { }, sortedListMaxCount = 16, minThreshold = 0, maxThreshold = Infinity ) { + + const closestPointToPointFunc = this.indirect ? closestPointToPointHybrid_indirect : closestPointToPointHybrid; + const roots = this._roots; + let result = null; + + for ( let i = 0, l = roots.length; i < l; i ++ ) { + + result = closestPointToPointFunc( + this, + i, + point, + target, + sortedListMaxCount, + minThreshold, + maxThreshold, + ); + + if ( result && result.distance <= minThreshold ) break; + + } + + return result; + + } + // REMOVE AFTER TEST closestPointToPointOld( point, target = { }, minThreshold = 0, maxThreshold = Infinity ) { diff --git a/src/core/cast/closestPointToPoint.template.js b/src/core/cast/closestPointToPoint.template.js index 4370de6b..7fb935f6 100644 --- a/src/core/cast/closestPointToPoint.template.js +++ b/src/core/cast/closestPointToPoint.template.js @@ -12,9 +12,9 @@ export function closestPointToPoint/* @echo INDIRECT_STRING */( bvh, root, point, - target = { }, - minThreshold = 0, - maxThreshold = Infinity + target, + minThreshold, + maxThreshold ) { const minThresholdSq = minThreshold * minThreshold; diff --git a/src/core/cast/closestPointToPointHybrid.template.js b/src/core/cast/closestPointToPointHybrid.template.js new file mode 100644 index 00000000..32ecc262 --- /dev/null +++ b/src/core/cast/closestPointToPointHybrid.template.js @@ -0,0 +1,202 @@ +import { Vector3 } from 'three'; +import { COUNT, OFFSET, LEFT_NODE, RIGHT_NODE, IS_LEAF } from '../utils/nodeBufferUtils.js'; +import { BufferStack } from '../utils/BufferStack.js'; +import { ExtendedTrianglePool } from '../../utils/ExtendedTrianglePool.js'; +import { setTriangle } from '../../utils/TriangleUtilities.js'; +import { closestDistanceSquaredPointToBox } from '../utils/distanceUtils.js'; +import { SortedListDesc } from '../utils/SortedListDesc.js'; + +const temp = /* @__PURE__ */ new Vector3(); +const temp1 = /* @__PURE__ */ new Vector3(); +const sortedList = new SortedListDesc(); + +export function closestPointToPointHybrid/* @echo INDIRECT_STRING */( + bvh, + root, + point, + target, + sortedListMaxCount, + minThreshold, + maxThreshold +) { + + const minThresholdSq = minThreshold * minThreshold; + const maxThresholdSq = maxThreshold * maxThreshold; + let closestDistanceSq = Infinity; + let closestDistanceTriIndex = null; + + const { geometry } = bvh; + const { index } = geometry; + const pos = geometry.attributes.position; + const triangle = ExtendedTrianglePool.getPrimitive(); + + BufferStack.setBuffer( bvh._roots[ root ] ); + const { float32Array, uint16Array, uint32Array } = BufferStack; + + if ( sortedListMaxCount === 0 ) { + + _closestPointToPoint( 0 ); + + } else { + + sortedList.clear(); + let count = 0; + let node = { nodeIndex32: 0, distance: closestDistanceSquaredPointToBox( 0, float32Array, point ) }; + + do { + + const { distance, nodeIndex32 } = node; + + if ( distance >= closestDistanceSq ) return; + + if ( count >= sortedListMaxCount ) { + + if ( _closestPointToPoint( nodeIndex32 ) ) return true; + continue; + + } + + count ++; + + const nodeIndex16 = nodeIndex32 * 2; + const isLeaf = IS_LEAF( nodeIndex16, uint16Array ); + if ( isLeaf ) { + + const offset = OFFSET( nodeIndex32, uint32Array ); + const count = COUNT( nodeIndex16, uint16Array ); + + for ( let i = offset, l = count + offset; i < l; i ++ ) { + + /* @if INDIRECT */ + + const ti = bvh.resolveTriangleIndex( i ); + setTriangle( triangle, 3 * ti, index, pos ); + + /* @else */ + + setTriangle( triangle, i * 3, index, pos ); + + /* @endif */ + + triangle.needsUpdate = true; + + triangle.closestPointToPoint( point, temp ); + const distSq = point.distanceToSquared( temp ); + if ( distSq < closestDistanceSq ) { + + temp1.copy( temp ); + closestDistanceSq = distSq; + closestDistanceTriIndex = i; + + if ( distSq < minThresholdSq ) return true; + + } + + } + + continue; + + } + + const leftIndex = LEFT_NODE( nodeIndex32 ); + const rightIndex = RIGHT_NODE( nodeIndex32, uint32Array ); + + const leftDistance = closestDistanceSquaredPointToBox( leftIndex, float32Array, point ); + const rightDistance = closestDistanceSquaredPointToBox( rightIndex, float32Array, point ); + + if ( leftDistance < closestDistanceSq && leftDistance < maxThresholdSq ) { + + sortedList.push( { nodeIndex32: leftIndex, distance: leftDistance } ); + + } + + if ( rightDistance < closestDistanceSq && rightDistance < maxThresholdSq ) { + + sortedList.push( { nodeIndex32: rightIndex, distance: rightDistance } ); + + } + + } while ( node = sortedList.pop() ); + + } + + BufferStack.clearBuffer(); + + if ( closestDistanceSq === Infinity ) return null; + + const closestDistance = Math.sqrt( closestDistanceSq ); + + if ( ! target.point ) target.point = temp1.clone(); + else target.point.copy( temp1 ); + target.distance = closestDistance; + target.faceIndex = closestDistanceTriIndex; + + return target; + + function _closestPointToPoint( nodeIndex32 ) { + + const nodeIndex16 = nodeIndex32 * 2; + const isLeaf = IS_LEAF( nodeIndex16, uint16Array ); + if ( isLeaf ) { + + const offset = OFFSET( nodeIndex32, uint32Array ); + const count = COUNT( nodeIndex16, uint16Array ); + + for ( let i = offset, l = count + offset; i < l; i ++ ) { + + /* @if INDIRECT */ + + const ti = bvh.resolveTriangleIndex( i ); + setTriangle( triangle, 3 * ti, index, pos ); + + /* @else */ + + setTriangle( triangle, i * 3, index, pos ); + + /* @endif */ + + triangle.needsUpdate = true; + + triangle.closestPointToPoint( point, temp ); + const distSq = point.distanceToSquared( temp ); + if ( distSq < closestDistanceSq ) { + + temp1.copy( temp ); + closestDistanceSq = distSq; + closestDistanceTriIndex = i; + + if ( distSq < minThresholdSq ) return true; + + } + + } + + return; + + } + + const leftIndex = LEFT_NODE( nodeIndex32 ); + const rightIndex = RIGHT_NODE( nodeIndex32, uint32Array ); + + const leftDistance = closestDistanceSquaredPointToBox( leftIndex, float32Array, point ); + const rightDistance = closestDistanceSquaredPointToBox( rightIndex, float32Array, point ); + + if ( leftDistance <= rightDistance ) { + + if ( leftDistance < closestDistanceSq && leftDistance < maxThresholdSq ) { + + if ( _closestPointToPoint( leftIndex ) ) return true; + if ( rightDistance < closestDistanceSq ) return _closestPointToPoint( rightIndex ); + + } + + } else if ( rightDistance < closestDistanceSq && rightDistance < maxThresholdSq ) { + + if ( _closestPointToPoint( rightIndex ) ) return true; + if ( leftDistance < closestDistanceSq ) return _closestPointToPoint( leftIndex ); + + } + + } + +} diff --git a/src/core/cast/closestPointToPointSort.template.js b/src/core/cast/closestPointToPointSort.template.js index 724bd93c..fce59c53 100644 --- a/src/core/cast/closestPointToPointSort.template.js +++ b/src/core/cast/closestPointToPointSort.template.js @@ -14,9 +14,9 @@ export function closestPointToPointSort/* @echo INDIRECT_STRING */( bvh, root, point, - target = { }, - minThreshold = 0, - maxThreshold = Infinity + target, + minThreshold, + maxThreshold ) { const minThresholdSq = minThreshold * minThreshold; From 1a7fbcc7573968828ebe7ef0d9b6cd724041ffc6 Mon Sep 17 00:00:00 2001 From: ANDREA GARGARO Date: Tue, 27 Aug 2024 23:41:26 +0200 Subject: [PATCH 7/8] Add example config sortedListMaxCount --- example/testToRemove.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/example/testToRemove.js b/example/testToRemove.js index 110cb3f8..28a50a73 100644 --- a/example/testToRemove.js +++ b/example/testToRemove.js @@ -3,10 +3,11 @@ import { computeBoundsTree, SAH } from '../src'; THREE.BufferGeometry.prototype.computeBoundsTree = computeBoundsTree; -const spawnPointRadius = 0.1; -const radius = 100; // if radius 100 and tube 0.1, sort works really good. -const tube = 0.1; +const spawnPointRadius = 10; +const radius = 100; // if radius 100 and tube 0.1 and spawnRadiusu 10, sort works really good. +const tube = 1; const segmentsMultiplier = 32; +const sortedListMaxCount = 32; const maxLeafTris = 5; const strategy = SAH; @@ -107,7 +108,7 @@ function benchmark() { for ( let i = 0; i < tries; i ++ ) { - bvh.closestPointToPointHybrid( points[ i ], target ); + bvh.closestPointToPointHybrid( points[ i ], target, sortedListMaxCount ); } From 14a7c327cc46c6d63ece0d283fe0acc901cf2543 Mon Sep 17 00:00:00 2001 From: ANDREA GARGARO Date: Wed, 28 Aug 2024 21:29:41 +0200 Subject: [PATCH 8/8] Improvements --- example/testToRemove.js | 30 +++-- src/core/MeshBVH.js | 6 + .../closestPointToPointHybrid.template.js | 39 +++--- .../cast/closestPointToPointSort.template.js | 114 +++++++++--------- src/core/utils/SortedListDesc.js | 18 +-- 5 files changed, 114 insertions(+), 93 deletions(-) diff --git a/example/testToRemove.js b/example/testToRemove.js index 28a50a73..afd63414 100644 --- a/example/testToRemove.js +++ b/example/testToRemove.js @@ -3,12 +3,12 @@ import { computeBoundsTree, SAH } from '../src'; THREE.BufferGeometry.prototype.computeBoundsTree = computeBoundsTree; -const spawnPointRadius = 10; -const radius = 100; // if radius 100 and tube 0.1 and spawnRadiusu 10, sort works really good. -const tube = 1; +const spawnPointRadius = 100; +const radius = 10; // if radius 100 and tube 0.1 and spawnRadius 100, sort works really good. +const tube = 0.1; const segmentsMultiplier = 32; const sortedListMaxCount = 32; -const maxLeafTris = 5; +const maxLeafTris = 10; const strategy = SAH; const tries = 1000; @@ -45,24 +45,30 @@ export class PRNG { } const bvh = geometry.boundsTree; -const target = new THREE.Vector3(); -// const target2 = new THREE.Vector3(); +const target = {}; const r = new PRNG( seed ); - const points = new Array( tries ); -for ( let i = 0; i < tries; i ++ ) { - points[ i ] = new THREE.Vector3( r.range( - spawnPointRadius, spawnPointRadius ), r.range( - spawnPointRadius, spawnPointRadius ), r.range( - spawnPointRadius, spawnPointRadius ) ); +function generatePoints() { + + for ( let i = 0; i < tries; i ++ ) { + + points[ i ] = new THREE.Vector3( r.range( - spawnPointRadius, spawnPointRadius ), r.range( - spawnPointRadius, spawnPointRadius ), r.range( - spawnPointRadius, spawnPointRadius ) ); + + } } + // // TEST EQUALS RESULTS -// for ( let i = 0; i < count; i ++ ) { +// generatePoints(); +// const target2 = {}; +// for ( let i = 0; i < tries; i ++ ) { // bvh.closestPointToPoint( points[ i ], target ); -// bvh.closestPointToPointOld( points[ i ], target2 ); +// bvh.closestPointToPointHybrid( points[ i ], target2 ); // if ( target.distance !== target2.distance ) { @@ -77,6 +83,8 @@ for ( let i = 0; i < tries; i ++ ) { function benchmark() { + generatePoints(); + const startOld = performance.now(); for ( let i = 0; i < tries; i ++ ) { diff --git a/src/core/MeshBVH.js b/src/core/MeshBVH.js index da33bc88..14bab17b 100644 --- a/src/core/MeshBVH.js +++ b/src/core/MeshBVH.js @@ -540,6 +540,8 @@ export class MeshBVH { maxThreshold, ); + // fix here, check old result and new + if ( result && result.distance <= minThreshold ) break; } @@ -565,6 +567,8 @@ export class MeshBVH { maxThreshold, ); + // fix here, check old result and new + if ( result && result.distance <= minThreshold ) break; } @@ -591,6 +595,8 @@ export class MeshBVH { maxThreshold, ); + // fix here, check old result and new + if ( result && result.distance <= minThreshold ) break; } diff --git a/src/core/cast/closestPointToPointHybrid.template.js b/src/core/cast/closestPointToPointHybrid.template.js index 32ecc262..8606d1bb 100644 --- a/src/core/cast/closestPointToPointHybrid.template.js +++ b/src/core/cast/closestPointToPointHybrid.template.js @@ -24,13 +24,13 @@ export function closestPointToPointHybrid/* @echo INDIRECT_STRING */( const maxThresholdSq = maxThreshold * maxThreshold; let closestDistanceSq = Infinity; let closestDistanceTriIndex = null; + BufferStack.setBuffer( bvh._roots[ root ] ); const { geometry } = bvh; const { index } = geometry; const pos = geometry.attributes.position; const triangle = ExtendedTrianglePool.getPrimitive(); - BufferStack.setBuffer( bvh._roots[ root ] ); const { float32Array, uint16Array, uint32Array } = BufferStack; if ( sortedListMaxCount === 0 ) { @@ -39,6 +39,26 @@ export function closestPointToPointHybrid/* @echo INDIRECT_STRING */( } else { + _closestPointToPointSorted(); + + } + + BufferStack.clearBuffer(); + + if ( closestDistanceSq === Infinity ) return null; + + const closestDistance = Math.sqrt( closestDistanceSq ); + + if ( ! target.point ) target.point = temp1.clone(); + else target.point.copy( temp1 ); + target.distance = closestDistance; + target.faceIndex = closestDistanceTriIndex; + + return target; + + + function _closestPointToPointSorted() { + sortedList.clear(); let count = 0; let node = { nodeIndex32: 0, distance: closestDistanceSquaredPointToBox( 0, float32Array, point ) }; @@ -51,7 +71,8 @@ export function closestPointToPointHybrid/* @echo INDIRECT_STRING */( if ( count >= sortedListMaxCount ) { - if ( _closestPointToPoint( nodeIndex32 ) ) return true; + if ( _closestPointToPoint( nodeIndex32 ) ) return; + continue; } @@ -88,7 +109,7 @@ export function closestPointToPointHybrid/* @echo INDIRECT_STRING */( closestDistanceSq = distSq; closestDistanceTriIndex = i; - if ( distSq < minThresholdSq ) return true; + if ( distSq < minThresholdSq ) return; } @@ -120,18 +141,6 @@ export function closestPointToPointHybrid/* @echo INDIRECT_STRING */( } - BufferStack.clearBuffer(); - - if ( closestDistanceSq === Infinity ) return null; - - const closestDistance = Math.sqrt( closestDistanceSq ); - - if ( ! target.point ) target.point = temp1.clone(); - else target.point.copy( temp1 ); - target.distance = closestDistance; - target.faceIndex = closestDistanceTriIndex; - - return target; function _closestPointToPoint( nodeIndex32 ) { diff --git a/src/core/cast/closestPointToPointSort.template.js b/src/core/cast/closestPointToPointSort.template.js index fce59c53..69ca73bc 100644 --- a/src/core/cast/closestPointToPointSort.template.js +++ b/src/core/cast/closestPointToPointSort.template.js @@ -23,97 +23,101 @@ export function closestPointToPointSort/* @echo INDIRECT_STRING */( const maxThresholdSq = maxThreshold * maxThreshold; let closestDistanceSq = Infinity; let closestDistanceTriIndex = null; + BufferStack.setBuffer( bvh._roots[ root ] ); - const { geometry } = bvh; - const { index } = geometry; - const pos = geometry.attributes.position; - const triangle = ExtendedTrianglePool.getPrimitive(); + _closestPointToPoint(); - sortedList.clear(); - BufferStack.setBuffer( bvh._roots[ root ] ); - const { float32Array, uint16Array, uint32Array } = BufferStack; + BufferStack.clearBuffer(); - let node = { nodeIndex32: 0, distance: closestDistanceSquaredPointToBox( 0, float32Array, point ) }; + if ( closestDistanceSq === Infinity ) return null; - do { + const closestDistance = Math.sqrt( closestDistanceSq ); - const { distance } = node; + if ( ! target.point ) target.point = temp1.clone(); + else target.point.copy( temp1 ); + target.distance = closestDistance; + target.faceIndex = closestDistanceTriIndex; + + return target; - if ( distance >= closestDistanceSq ) return; - const { nodeIndex32 } = node; + function _closestPointToPoint() { - const nodeIndex16 = nodeIndex32 * 2; - const isLeaf = IS_LEAF( nodeIndex16, uint16Array ); - if ( isLeaf ) { + const { geometry } = bvh; + const { index } = geometry; + const pos = geometry.attributes.position; + const triangle = ExtendedTrianglePool.getPrimitive(); + const { float32Array, uint16Array, uint32Array } = BufferStack; + sortedList.clear(); - const offset = OFFSET( nodeIndex32, uint32Array ); - const count = COUNT( nodeIndex16, uint16Array ); + let node = { nodeIndex32: 0, distance: closestDistanceSquaredPointToBox( 0, float32Array, point ) }; - for ( let i = offset, l = count + offset; i < l; i ++ ) { + do { - /* @if INDIRECT */ + const { distance, nodeIndex32 } = node; - const ti = bvh.resolveTriangleIndex( i ); - setTriangle( triangle, 3 * ti, index, pos ); + if ( distance >= closestDistanceSq ) return; - /* @else */ + const nodeIndex16 = nodeIndex32 * 2; + const isLeaf = IS_LEAF( nodeIndex16, uint16Array ); + if ( isLeaf ) { - setTriangle( triangle, i * 3, index, pos ); + const offset = OFFSET( nodeIndex32, uint32Array ); + const count = COUNT( nodeIndex16, uint16Array ); - /* @endif */ + for ( let i = offset, l = count + offset; i < l; i ++ ) { - triangle.needsUpdate = true; + /* @if INDIRECT */ - triangle.closestPointToPoint( point, temp ); - const distSq = point.distanceToSquared( temp ); - if ( distSq < closestDistanceSq ) { + const ti = bvh.resolveTriangleIndex( i ); + setTriangle( triangle, 3 * ti, index, pos ); - temp1.copy( temp ); - closestDistanceSq = distSq; - closestDistanceTriIndex = i; + /* @else */ - if ( distSq < minThresholdSq ) return true; + setTriangle( triangle, i * 3, index, pos ); - } + /* @endif */ - } + triangle.needsUpdate = true; - continue; + triangle.closestPointToPoint( point, temp ); + const distSq = point.distanceToSquared( temp ); + if ( distSq < closestDistanceSq ) { - } + temp1.copy( temp ); + closestDistanceSq = distSq; + closestDistanceTriIndex = i; - const leftIndex = LEFT_NODE( nodeIndex32 ); - const rightIndex = RIGHT_NODE( nodeIndex32, uint32Array ); + if ( distSq < minThresholdSq ) return; - const leftDistance = closestDistanceSquaredPointToBox( leftIndex, float32Array, point ); - const rightDistance = closestDistanceSquaredPointToBox( rightIndex, float32Array, point ); + } - if ( leftDistance < closestDistanceSq && leftDistance < maxThresholdSq ) { + } - sortedList.push( { nodeIndex32: leftIndex, distance: leftDistance } ); + continue; - } + } - if ( rightDistance < closestDistanceSq && rightDistance < maxThresholdSq ) { + const leftIndex = LEFT_NODE( nodeIndex32 ); + const rightIndex = RIGHT_NODE( nodeIndex32, uint32Array ); - sortedList.push( { nodeIndex32: rightIndex, distance: rightDistance } ); + const leftDistance = closestDistanceSquaredPointToBox( leftIndex, float32Array, point ); + const rightDistance = closestDistanceSquaredPointToBox( rightIndex, float32Array, point ); - } + if ( leftDistance < closestDistanceSq && leftDistance < maxThresholdSq ) { - } while ( node = sortedList.pop() ); + sortedList.push( { nodeIndex32: leftIndex, distance: leftDistance } ); - BufferStack.clearBuffer(); + } - if ( closestDistanceSq === Infinity ) return null; + if ( rightDistance < closestDistanceSq && rightDistance < maxThresholdSq ) { - const closestDistance = Math.sqrt( closestDistanceSq ); + sortedList.push( { nodeIndex32: rightIndex, distance: rightDistance } ); - if ( ! target.point ) target.point = temp1.clone(); - else target.point.copy( temp1 ); - target.distance = closestDistance; - target.faceIndex = closestDistanceTriIndex; + } - return target; + } while ( node = sortedList.pop() ); + + } } diff --git a/src/core/utils/SortedListDesc.js b/src/core/utils/SortedListDesc.js index 8bed4468..3e4d4e3a 100644 --- a/src/core/utils/SortedListDesc.js +++ b/src/core/utils/SortedListDesc.js @@ -30,23 +30,17 @@ export class SortedListDesc { const array = this.array; - let start = 0; - let end = array.length - 1; - let index = 0; + let low = 0, high = array.length; - while ( start <= end ) { + while ( low < high ) { - index = Math.ceil( ( start + end ) / 2 ); - - if ( index === 0 ) break; - if ( array[ index ].distance <= value && array[ index - 1 ].distance >= value ) return index; - - if ( value > array[ index ].distance ) end = index - 1; - else start = index + 1; + const mid = ( low + high ) >>> 1; + if ( array[ mid ].distance > value ) low = mid + 1; + else high = mid; } - return value < array[ index ]?.distance ? index + 1 : index; + return low; }