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

Improve ClosestPointToPoint #699

Draft
wants to merge 10 commits into
base: master
Choose a base branch
from
Draft
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
23 changes: 23 additions & 0 deletions example/testToRemove.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<!DOCTYPE html>
<html>
<head>
<title>three-mesh-bvh - Complex Geometry Raycasting</title>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">

<style type="text/css">
html, body {
padding: 0;
margin: 0;
overflow: hidden;
}

canvas {
width: 100%;
height: 100%;
}
</style>
</head>
<body>
<script type="module" src="./testToRemove.js"></script>
</body>
</html>
133 changes: 133 additions & 0 deletions example/testToRemove.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import * as THREE from 'three';
import { computeBoundsTree, SAH } from '../src';

THREE.BufferGeometry.prototype.computeBoundsTree = computeBoundsTree;

const spawnPointRadius = 20;
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 maxDepthSorted = 4;
const maxLeafTris = 10;
const strategy = SAH;

const tries = 1000;
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 {

constructor( seed ) {

this._seed = seed;

}

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;

}

range( min, max ) {

return min + ( max - min ) * this.next();

}

}

const bvh = geometry.boundsTree;
const target = {};

const r = new PRNG( seed );
const points = new Array( tries );

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

// generatePoints();
// const target2 = {};
// for ( let i = 0; i < tries; i ++ ) {

// bvh.closestPointToPoint( points[ i ], target );
// bvh.closestPointToPointHybrid( points[ i ], target2 );

// if ( target.distance !== target2.distance ) {

// const diff = target.distance - target2.distance;
// console.error( "error: " + ( diff / target2.distance * 100 ) + "%" );

// }

// }

// TEST PERFORMANCE

function benchmark() {

generatePoints();

const startOld = performance.now();

for ( let i = 0; i < tries; i ++ ) {

bvh.closestPointToPointOld( points[ i ], target );

}

const endOld = performance.now() - startOld;
const startNew = performance.now();

for ( let i = 0; i < tries; i ++ ) {

bvh.closestPointToPoint( points[ i ], target );

}

const endNew = performance.now() - startNew;
const startSort = performance.now();

for ( let i = 0; i < tries; i ++ ) {

bvh.closestPointToPointSort( points[ i ], target );

}

const endSort = performance.now() - startSort;
const startHybrid = performance.now();

for ( let i = 0; i < tries; i ++ ) {

bvh.closestPointToPointHybrid( points[ i ], target, maxDepthSorted );

}

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}` );

}

benchmark();
setInterval( () => benchmark(), 1000 );
93 changes: 91 additions & 2 deletions src/core/MeshBVH.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,13 @@ 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 { 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';
import { refit } from './cast/refit.generated.js';
Expand Down Expand Up @@ -519,7 +525,90 @@ export class MeshBVH {

closestPointToPoint( point, target = { }, minThreshold = 0, maxThreshold = Infinity ) {

return closestPointToPoint(
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,
);

// fix here, check old result and new

if ( result && result.distance <= minThreshold ) break;

}

return result;

}

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,
);

// fix here, check old result and new

if ( result && result.distance <= minThreshold ) break;

}

return result;

}

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,
);

// fix here, check old result and new

if ( result && result.distance <= minThreshold ) break;

}

return result;

}

// REMOVE AFTER TEST
closestPointToPointOld( point, target = { }, minThreshold = 0, maxThreshold = Infinity ) {

return closestPointToPointOld(
this,
point,
target,
Expand Down
4 changes: 3 additions & 1 deletion src/core/cast/closestPointToPoint.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { Vector3 } from 'three';

// DELETE THIS AFTER TEST

const temp = /* @__PURE__ */ new Vector3();
const temp1 = /* @__PURE__ */ new Vector3();

export function closestPointToPoint(
export function closestPointToPointOld(
bvh,
point,
target = { },
Expand Down
117 changes: 117 additions & 0 deletions src/core/cast/closestPointToPoint.template.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
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';

const temp = /* @__PURE__ */ new Vector3();
const temp1 = /* @__PURE__ */ new Vector3();

export function closestPointToPoint/* @echo INDIRECT_STRING */(
bvh,
root,
point,
target,
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;
_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;


// 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;
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 );
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't we want to check distance to maxThresholdSq here, as well?


}

} else if ( rightDistance < closestDistanceSq && rightDistance < maxThresholdSq ) {

if ( _closestPointToPoint( rightIndex ) ) return true;
if ( leftDistance < closestDistanceSq ) return _closestPointToPoint( leftIndex );

}

}

}
Loading
Loading