From 06e5cc5266b1df35185d735196a4e11692d597c9 Mon Sep 17 00:00:00 2001 From: ANDREA GARGARO Date: Tue, 27 Aug 2024 20:21:47 +0200 Subject: [PATCH] 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 38c3e9c33..f232fd569 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 2cb598d03..82500dc7e 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 54c0abae0..4370de6b0 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 000000000..724bd93c0 --- /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 000000000..8bed44683 --- /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 000000000..7b9e8ef7d --- /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; + +}