Skip to content
Merged
Show file tree
Hide file tree
Changes from 58 commits
Commits
Show all changes
68 commits
Select commit Hold shift + click to select a range
b1b7c06
rename tri to prim
gkjohnson Jan 4, 2026
8233680
Add base class
gkjohnson Jan 4, 2026
63d066b
Add abstract functions
gkjohnson Jan 4, 2026
edbc520
Fix workers
gkjohnson Jan 5, 2026
dd04927
Add PointsBVH
gkjohnson Jan 5, 2026
ed812d1
update demo
gkjohnson Jan 5, 2026
8019656
Simplification
gkjohnson Jan 5, 2026
d768a0c
Simplify PointsBVH
gkjohnson Jan 5, 2026
76ed620
More cleanup
gkjohnson Jan 5, 2026
1675f39
Improve parallel worker path
gkjohnson Jan 5, 2026
b1d0fce
Further simplification
gkjohnson Jan 6, 2026
a45d9dc
Rearrange
gkjohnson Jan 6, 2026
9442a3c
More simplification
gkjohnson Jan 6, 2026
2c5e60e
Fix stride
gkjohnson Jan 6, 2026
7d7db55
Fix points raycasting
gkjohnson Jan 6, 2026
061eeec
Add "type" option to computeBoundsTree
gkjohnson Jan 6, 2026
6c1e5a0
Updates
gkjohnson Jan 6, 2026
21ac2ba
Add shapecast functions
gkjohnson Jan 6, 2026
50c69ff
Cleanup
gkjohnson Jan 6, 2026
e9ae3d3
Clean up, more lineBVH implementation
gkjohnson Jan 6, 2026
dbf4b66
Reuse functions
gkjohnson Jan 6, 2026
47bbd3d
Remove unused import
gkjohnson Jan 6, 2026
0355b9a
fix issues
gkjohnson Jan 6, 2026
c50b5ce
Update point cloud demo
gkjohnson Jan 6, 2026
f36ee42
Fix raycast functions
gkjohnson Jan 6, 2026
9a27ded
update camera position
gkjohnson Jan 6, 2026
f91c4b2
Rename class
gkjohnson Jan 6, 2026
5557adb
Variable renaming, cleanup
gkjohnson Jan 6, 2026
7513ca0
Remove use of geometry
gkjohnson Jan 6, 2026
08871e4
simplify partition logic
gkjohnson Jan 6, 2026
b354c1c
Fix PointsBVH indirect issue
gkjohnson Jan 6, 2026
b4ddff4
lint fix
gkjohnson Jan 6, 2026
48fa8aa
demo update
gkjohnson Jan 6, 2026
c3a1a71
Raycast updates
gkjohnson Jan 6, 2026
8db0c2a
Update docs
gkjohnson Jan 6, 2026
e3a8436
Update docs
gkjohnson Jan 6, 2026
4bbe7c5
Add demo
gkjohnson Jan 6, 2026
e2ee69e
Fix Line intersections
gkjohnson Jan 6, 2026
bdc3352
Update example, fix raycasting
gkjohnson Jan 6, 2026
798f8f4
Fix line issues
gkjohnson Jan 6, 2026
c8922d3
Add tests for PointsBVH
gkjohnson Jan 7, 2026
f2eba5c
LineBVH: fixes
gkjohnson Jan 7, 2026
04835b2
Add tests
gkjohnson Jan 7, 2026
b0131d6
Switch option to "maxLeafSize"
gkjohnson Jan 7, 2026
d9ae654
Update types
gkjohnson Jan 7, 2026
c916db2
Move "computeTriangleBounds" function
gkjohnson Jan 7, 2026
bd8b362
Function renaming
gkjohnson Jan 7, 2026
47e301c
Fix LineBVH tests
gkjohnson Jan 7, 2026
aaa46db
Fix range generation
gkjohnson Jan 7, 2026
e5ade87
Fix test failure
gkjohnson Jan 7, 2026
3a4f31b
Fix parallel worker
gkjohnson Jan 7, 2026
13dce13
Merge branch 'master' into primitive-bvh
gkjohnson Jan 7, 2026
f6ddce4
Remove unused imports
gkjohnson Jan 7, 2026
d2d567e
Pass range into "getRootRanges"
gkjohnson Jan 7, 2026
2037b89
Small cleanup
gkjohnson Jan 7, 2026
e507fb0
Update types
gkjohnson Jan 7, 2026
83a8f71
Fix historical version tests
gkjohnson Jan 7, 2026
d3891bb
Remove "getPrimitiveCount"
gkjohnson Jan 7, 2026
be4e3ae
Fix import
gkjohnson Jan 7, 2026
bcf3d9a
Remove dead code
gkjohnson Jan 7, 2026
52aa166
Update debug functino
gkjohnson Jan 7, 2026
a7157b8
Fix scale
gkjohnson Jan 7, 2026
6dbcb4b
Updates
gkjohnson Jan 7, 2026
34367af
Update background, update tests
gkjohnson Jan 7, 2026
610c83f
skip case
gkjohnson Jan 7, 2026
4b5e8b8
Rename "BVHHelper"
gkjohnson Jan 7, 2026
470f637
Remove default arg
gkjohnson Jan 7, 2026
fef762a
Variable renames
gkjohnson Jan 7, 2026
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
36 changes: 36 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ Casting 500 rays against an 80,000 polygon model at 60fps!

[Point cloud intersection](https://gkjohnson.github.io/three-mesh-bvh/example/bundle/pointCloudIntersection.html)

[Line segments intersection](https://gkjohnson.github.io/three-mesh-bvh/example/bundle/pointCloudIntersection.html)

[Shape intersection](https://gkjohnson.github.io/three-mesh-bvh/example/bundle/shapecast.html)

[Geometry edge intersection](https://gkjohnson.github.io/three-mesh-bvh/example/bundle/edgeIntersect.html)
Expand Down Expand Up @@ -136,6 +138,32 @@ raycaster.firstHitOnly = true;
raycaster.intersectObjects( [ mesh ] );
```

## Other BVH Types

In addition to `MeshBVH` for triangle meshes, the library provides specialized BVH implementations for other primitive types:

- **PointsBVH** - For `THREE.Points` geometries
- **LineBVH** - For `THREE.Line` geometries
- **LineLoopBVH** - For `THREE.LineLoop` geometries
- **LineSegmentsBVH** - For `THREE.LineSegments` geometries

These can be used with the extension functions by passing a `type` option into "computeBoundsTree" or constructing them explicitly:

```js
import { PointsBVH } from 'three-mesh-bvh';

// For point clouds
THREE.Points.prototype.raycast = acceleratedRaycast;

const points = new THREE.Points( geometry, material );
geometry.computeBoundsTree( { type: PointsBVH } );

// Or create directly
geometry.boundsTree = new PointsBVH( geometry );
```

Each BVH type implements a core API including shapecast & raycastObject3D for its specific primitive type. See the [point cloud intersection](https://gkjohnson.github.io/three-mesh-bvh/example/bundle/pointCloudIntersection.html) & [line intersection](https://gkjohnson.github.io/three-mesh-bvh/example/bundle/lineIntersection.html) examples for a working demonstration. Some features like webworker-generation and serialization are not supported at the moment.

## Querying the BVH Directly

```js
Expand Down Expand Up @@ -409,6 +437,14 @@ raycastFirst( ray : Ray, material : Array<Material> | Material, near : Number =

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.

### .raycastObject3D

```js
raycastObject3D( mesh: Mesh, raycaster: Raycaster, intersects = []: Array<RaycastHit> ): Array<RaycastHit>
```

A convenience function for performing a raycast based on a mesh. Results are formed like three.js raycast results in world frame.

### .intersectsSphere

```js
Expand Down
51 changes: 51 additions & 0 deletions example/lineIntersection.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<!DOCTYPE html>
<html>
<head>
<title>three-mesh-bvh - Line 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%;
}

#info {
position: absolute;
top: 0;
width: 100%;
color: white;
font-family: monospace;
text-align: center;
padding: 5px 0;
}

a {
color: #eee;
}

#output {
position: absolute;
bottom: 0;
left: 0;
padding: 5px;
color: white;
opacity: 0.5;
font-family: monospace;
}
</style>
</head>
<body>
<div id="info">
Accelerated raycasting of 1 million lines using LineBVH
</div>
<div id="output"></div>
<script type="module" src="./lineIntersection.js"></script>
</body>
</html>
275 changes: 275 additions & 0 deletions example/lineIntersection.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,275 @@
import * as THREE from 'three';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
import { GUI } from 'three/examples/jsm/libs/lil-gui.module.min.js';
import Stats from 'three/examples/jsm/libs/stats.module.js';
import {
acceleratedRaycast, computeBoundsTree, disposeBoundsTree, LineBVH, MeshBVHHelper,
SAH, CENTER, AVERAGE,
} from 'three-mesh-bvh';

THREE.Line.prototype.raycast = acceleratedRaycast;
THREE.BufferGeometry.prototype.computeBoundsTree = computeBoundsTree;
THREE.BufferGeometry.prototype.disposeBoundsTree = disposeBoundsTree;

const params = {
displayHelper: false,
helperDepth: 10,
displayParents: false,

useBVH: true,
strategy: 0,
indirect: false,
};

let renderer, camera, scene, controls, stats, outputContainer;

Check warning on line 24 in example/lineIntersection.js

View workflow job for this annotation

GitHub Actions / build (24.x, 0.159.0)

'controls' is assigned a value but never used

Check warning on line 24 in example/lineIntersection.js

View workflow job for this annotation

GitHub Actions / build (24.x, 0.168.0)

'controls' is assigned a value but never used

Check warning on line 24 in example/lineIntersection.js

View workflow job for this annotation

GitHub Actions / build (24.x, latest)

'controls' is assigned a value but never used
let line, helper;
let raycaster, mouse;
let sphereCollision;

init();
render();

function init() {

outputContainer = document.getElementById( 'output' );

// renderer setup
renderer = new THREE.WebGLRenderer( { antialias: true } );
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
renderer.setClearColor( 0x263238 );
document.body.appendChild( renderer.domElement );

// scene setup
scene = new THREE.Scene();

// camera setup
camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 50 );
camera.position.set( 2, 1, 2 );
camera.far = 100;
camera.updateProjectionMatrix();

// controls
controls = new OrbitControls( camera, renderer.domElement );

// raycaster
raycaster = new THREE.Raycaster();
raycaster.params.Line.threshold = 0.01;
mouse = new THREE.Vector2();

// collision sphere
sphereCollision = new THREE.Mesh(
new THREE.SphereGeometry( 0.01, 16, 16 ),
new THREE.MeshBasicMaterial( { color: 0xff0000, transparent: true, opacity: 0.75 } )
);
sphereCollision.visible = false;
scene.add( sphereCollision );

// stats
stats = new Stats();
document.body.appendChild( stats.dom );

// generate initial geometry
line = new THREE.Line( generateGeometry(), new THREE.LineBasicMaterial( {
vertexColors: true,
linewidth: 2,
} ) );

helper = new MeshBVHHelper( line, params.helperDepth );

scene.add( line, helper );

updateBVH();

// GUI
const gui = new GUI();

const helperFolder = gui.addFolder( 'helper' );
helperFolder.add( params, 'displayHelper' );
helperFolder.add( params, 'displayParents' ).onChange( v => {

helper.displayParents = v;
helper.update();

} );
helperFolder.add( params, 'helperDepth', 1, 25, 1 ).name( 'depth' ).onChange( v => {

helper.depth = parseInt( v );
helper.update();

} );
helperFolder.open();

const linesFolder = gui.addFolder( 'lines' );
linesFolder.add( params, 'useBVH' ).onChange( updateBVH );
linesFolder.add( params, 'strategy', { CENTER, AVERAGE, SAH } ).onChange( updateBVH );
linesFolder.add( params, 'indirect' ).onChange( updateBVH );
linesFolder.open();

window.addEventListener( 'resize', onWindowResize, false );
window.addEventListener( 'pointermove', onPointerMove, false );

}

function generateCurve( segments ) {

const points = [];
const norm = new THREE.Vector3();
const tangent = new THREE.Vector3();
const v0 = new THREE.Vector3();
const v1 = new THREE.Vector3();

const getSurfacePoint = ( t, target ) => {

// Torus knot parameters
const p = 3; // number of times the knot winds around the torus longitudinally
const q = 10; // number of times the knot winds around the torus meridionally
const R = 1.0; // major radius
const r = 0.4; // minor radius (tube radius)

const theta = t * Math.PI * 2;
const phi = p * theta;
const psi = q * theta;

const x = ( R + r * Math.cos( psi ) ) * Math.cos( phi );
const y = ( R + r * Math.cos( psi ) ) * Math.sin( phi );
const z = r * Math.sin( psi );

target.set( x, y, z );

};

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

const t0 = i / segments;
const t1 = t0 + 1e-4;

getSurfacePoint( t0, v0 );
getSurfacePoint( t1, v1 );

norm.copy( v0 ).normalize();
tangent.subVectors( v1, v0 ).normalize();

norm.applyAxisAngle( tangent, 1000 * t0 * 2 * Math.PI );

v0
// .multiplyScalar( Math.sin( t0 * Math.PI ) )
.addScaledVector( norm, 0.05 * ( Math.sin( 50 * t0 * Math.PI ) + 2 ) );
points.push( v0.clone() );

}

return points;

}

function generateGeometry() {

const positions = [];
const colors = [];

const points = generateCurve( 1e6 );
const color = new THREE.Color();
for ( let i = 0; i < points.length - 1; i ++ ) {

const p1 = points[ i ];
const t = i / ( points.length - 1 );
color.setHSL( t * 3, 1.0, 0.6 );

positions.push( p1.x, p1.y, p1.z );
colors.push( color.r, color.g, color.b );

}


const geometry = new THREE.BufferGeometry();
geometry.setAttribute( 'position', new THREE.Float32BufferAttribute( positions, 3 ) );
geometry.setAttribute( 'color', new THREE.Float32BufferAttribute( colors, 3 ) );

return geometry;

}

function updateBVH() {

if ( params.useBVH ) {

console.time( 'LineBVH' );
line.geometry.computeBoundsTree( {
strategy: parseInt( params.strategy ),
indirect: params.indirect,
type: LineBVH,
maxLeafTris: 1,
} );
console.timeEnd( 'LineBVH' );

} else {

line.geometry.disposeBoundsTree();

}

helper.update();

}

function updateRaycast() {

raycaster.setFromCamera( mouse, camera );
raycaster.firstHitOnly = true;

const startTime = window.performance.now();
const intersects = raycaster.intersectObject( line );
const delta = window.performance.now() - startTime;

const hit = intersects[ 0 ];
if ( hit ) {

sphereCollision.position.copy( hit.point );
sphereCollision.visible = true;

} else {

sphereCollision.visible = false;

}

outputContainer.innerText = `${ delta.toFixed( 2 ) }ms`;

}

function onPointerMove( event ) {

mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1;
mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1;

}

function onWindowResize() {

camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();

renderer.setSize( window.innerWidth, window.innerHeight );

}

function render() {

requestAnimationFrame( render );

if ( helper ) {

helper.visible = params.displayHelper;

}

line.rotation.y = performance.now() * 1e-4;

updateRaycast();

renderer.render( scene, camera );

stats.update();

}
2 changes: 1 addition & 1 deletion example/pointCloudIntersection.html
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
</head>
<body>
<div id="info">
Point cloud intersection by modeling points as degenerate triangles with MeshBVH.
Accelerated raycasting of 2 million points using PointsBVH

<br/>
<br/>
Expand Down
Loading
Loading