Git Product home page Git Product logo

gkjohnson / three-mesh-bvh Goto Github PK

View Code? Open in Web Editor NEW
2.3K 42.0 236.0 152.78 MB

A BVH implementation to speed up raycasting and enable spatial queries against three.js meshes.

Home Page: https://gkjohnson.github.io/three-mesh-bvh/example/bundle/raycast.html

License: MIT License

HTML 3.30% JavaScript 96.62% TypeScript 0.08%
graphics raycast tree bounds threejs three-js bounds-hierarchy performance geometry mesh

three-mesh-bvh's Introduction

three-mesh-bvh

npm version build github twitter sponsors

A Bounding Volume Hierarchy (BVH) implementation to speed up raycasting and enable spatial queries against three.js meshes. See the associated Wikipedia article for more information on bounding volume hierarchies and how they work.

screenshot

Casting 500 rays against an 80,000 polygon model at 60fps!

Examples

Raycasting

Skinned geometry

Point cloud interesection

Shape intersection

Geometry edge intersection

SDF generation

WebWorker generation

BVH options inspector

Tools

Sculpting

Distance comparison

Triangle painting

Lasso selection

Clipped edges

Geometry voxelization

Games

Sphere physics collision

Player movement

Path Tracing

Simple GPU Path Tracing

Lambert GPU Path Tracing

CPU Path Tracing

Gem Refraction Path Tracing

External Projects

three-gpu-pathtracer

three-bvh-csg

three-edge-projection

Use

Using pre-made functions

// Import via ES6 modules
import * as THREE from 'three';
import { computeBoundsTree, disposeBoundsTree, acceleratedRaycast } from 'three-mesh-bvh';

// Or UMD
const { computeBoundsTree, disposeBoundsTree, acceleratedRaycast } = window.MeshBVHLib;


// Add the extension functions
THREE.BufferGeometry.prototype.computeBoundsTree = computeBoundsTree;
THREE.BufferGeometry.prototype.disposeBoundsTree = disposeBoundsTree;
THREE.Mesh.prototype.raycast = acceleratedRaycast;

// Generate geometry and associated BVH
const geom = new THREE.TorusKnotBufferGeometry( 10, 3, 400, 100 );
const mesh = new THREE.Mesh( geom, material );
geom.computeBoundsTree();

Or manually building the BVH

// Import via ES6 modules
import * as THREE from 'three';
import { MeshBVH, acceleratedRaycast } from 'three-mesh-bvh';

// Or UMD
const { MeshBVH, acceleratedRaycast } = window.MeshBVHLib;


// Add the raycast function. Assumes the BVH is available on
// the `boundsTree` variable
THREE.Mesh.prototype.raycast = acceleratedRaycast;

// ...

// Generate the BVH and use the newly generated index
geom.boundsTree = new MeshBVH( geom );

And then raycasting

// Setting "firstHitOnly" to true means the Mesh.raycast function will use the
// bvh "raycastFirst" function to return a result more quickly.
const raycaster = new THREE.Raycaster();
raycaster.firstHitOnly = true;
raycaster.intersectObjects( [ mesh ] );

Querying the BVH Directly

import * as THREE from 'three';
import { MeshBVH, acceleratedRaycast } from 'three-mesh-bvh';

let mesh, geometry;
const invMat = new THREE.Matrix4();

// instantiate the geometry

// ...

const bvh = new MeshBVH( geometry );
invMat.copy( mesh.matrixWorld ).invert();

// raycasting
// ensure the ray is in the local space of the geometry being cast against
raycaster.ray.applyMatrix4( invMat );
const hit = bvh.raycastFirst( raycaster.ray );

// results are returned in local spac, as well, so they must be transformed into
// world space if needed.
hit.point.applyMatrixWorld( mesh.matrixWorld );

// spherecasting
// ensure the sphere is in the local space of the geometry being cast against
sphere.applyMatrix4( invMat );
const intersects = bvh.intersectsSphere( sphere );

With Skinned and Morph Target Meshes

import { StaticGeometryGenerator } from 'three-mesh-bvh';

const generator = new StaticGeometryGenerator( [ ...skinnedMeshes ] );
const geometry = generator.generate();
geometry.computeBoundsTree();

// ...

// update the geometry in place
generator.generate( geometry );
geometry.boundsTree.refit();

Serialization and Deserialization

const geometry = new KnotBufferGeometry( 1, 0.5, 40, 10 );
const bvh = new MeshBVH( geometry );
const serialized = MeshBVH.serialize( bvh );

// ...

const deserializedBVH = MeshBVH.deserialize( serialized, geometry );
geometry.boundsTree = deserializedBVH;

Asynchronous Generation

NOTE WebWorker syntax is inconsistently supported across bundlers and sometimes not supported at all so the GenerateMeshBVHWorker class is not exported from the package root. If needed the code from src/worker can be copied and modified to accommodate a particular build process.

import { GenerateMeshBVHWorker } from 'three-mesh-bvh/src/workers/GenerateMeshBVHWorker.js';

// ...

const geometry = new KnotBufferGeometry( 1, 0.5, 40, 10 );
const worker = new GenerateMeshBVHWorker();
worker.generate( geometry ).then( bvh => {

    geometry.boundsTree = bvh;

} );

Parallel BVH generation is also supported using "ParallelMeshBVHWorker", which requires support for SharedArrayBuffer. If SharedArrayBuffer is not available it falls back to "GenerateMeshBVHWorker". It is recommended that geometry passed to this function have position and index with SharedArrayBuffer arrays, otherwise buffer copies must be made.

import { ParallelMeshBVHWorker } from 'three-mesh-bvh/src/workers/ParallelMeshBVHWorker.js';

// ...

const geometry = new KnotBufferGeometry( 1, 0.5, 40, 10 );
const worker = new ParallelMeshBVHWorker();
worker.generate( geometry ).then( bvh => {

    geometry.boundsTree = bvh;

} );

BVH Queries in a Shader

See the shader implementation in the simple GPU Path Tracing example for an example on how to perform BVH ray queries in a shader. Or the GPU SDF Generation example for how to perform distance and closest point to point queries in a shader.

Exports

Split Strategy Constants

CENTER

Option for splitting each BVH node down the center of the longest axis of the bounds.

This is the fastest construction option and will yield a good, performant bounds.

AVERAGE

Option for splitting each BVH node at the average point along the longest axis for all triangle centroids in the bounds.

This strategy may be better than CENTER with some geometry.

SAH

Option to use a Surface Area Heuristic to split the bounds more optimally. This SAH implementation tests 32 discrete splits in each node along each axis to determine which split is the lowest cost.

This is the slowest construction option but will yield the best bounds of the three options and use the least memory.

Shapecast Intersection Constants

NOT_INTERSECTED

Indicates the shape did not intersect the given bounding box.

INTERSECTED

Indicates the shape did intersect the given bounding box.

CONTAINED

Indicate the shape entirely contains the given bounding box.

MeshBVH

The MeshBVH generation process modifies the geometry's index bufferAttribute in place to save memory. The BVH construction will use the geometry's boundingBox if it exists or set it if it does not. The BVH will no longer work correctly if the index buffer is modified.

Note that all query functions expect arguments in local space of the BVH and return results in local space, as well. If world space results are needed they must be transformed into world space using object.matrixWorld.

static .serialize

static serialize( bvh : MeshBVH, options : Object = null ) : SerializedBVH

Generates a representation of the complete bounds tree and the geometry index buffer which can be used to recreate a bounds tree using the deserialize function. The serialize and deserialize functions can be used to generate a MeshBVH asynchronously in a background web worker to prevent the main thread from stuttering. The BVH roots buffer stored in the serialized representation are the same as the ones used by the original BVH so they should not be modified. If SharedArrayBuffers are used then the same BVH memory can be used for multiple BVH in multiple WebWorkers.

bvh is the MeshBVH to be serialized. The options object can have the following fields:

{

	// if true then a clone of the `geometry.index.array` and MeshBVH buffers are made which slightly slower but
	// ensures modifications do not affect the serialized content. Can be set to "false" if it is guaranteed that
	// no modifications will be made, to save memory, or transfer and share them across WebWorkers if SharedArrayBuffers
	// are being used.
	cloneBuffers: true

}

static .deserialize

static deserialize( data : SerializedBVH, geometry : BufferGeometry, options : Object = null ) : MeshBVH

Returns a new MeshBVH instance from the serialized data. geometry is the geometry used to generate the original BVH data was derived from. The root buffers stored in data are set directly on the new BVH so the memory is shared.

The options object can have the following fields:

{
	// If true then the buffer for the `geometry.index` attribute is set from the serialized
	// data attribute or created if an index does not exist.
	setIndex: true,

}

NOTE: In order for the bounds tree to be used for casts the geometry index attribute must be replaced by the data in the SeralizedMeshBVH object.

.constructor

constructor( geometry : BufferGeometry, options : Object )

Constructs the bounds tree for the given geometry and produces a new index attribute buffer. A reference to the passed geometry is retained. The available options are

{
    // Which split strategy to use when constructing the BVH.
    strategy: CENTER,

    // The maximum depth to allow the tree to build to.
    // Setting this to a smaller trades raycast speed for better construction
    // time and less memory allocation.
    maxDepth: 40,

    // The number of triangles to aim for in a leaf node. Setting this to a lower
    // number can improve raycast performance but increase construction time and
    // memory footprint.
    maxLeafTris: 10,

    // If true then the bounding box for the geometry is set once the BVH
    // has been constructed.
    setBoundingBox: true,

    // If true then the MeshBVH will use SharedArrayBuffer rather than ArrayBuffer when
    // initializing the BVH buffers. Geometry index data will be created as a
    // SharedArrayBuffer only if it needs to be created. Otherwise it is used as-is.
    useSharedArrayBuffer: false,

    // An optional function that takes a "progress" argument in the range [0, 1]
    // indicating the progress along BVH generation. Useful primarily when generating
    // the BVH asynchronously with the GenerateMeshBVHWorker class.
    onProgress: null,

    // If false then an index buffer is created if it does not exist and is rearranged
    // to hold the bvh structure. If false then a separate buffer is created to store the
    // structure and the index buffer (or lack thereof) is retained. This can be used
    // when the existing index layout is important or groups are being used so a
    // single BVH hierarchy can be created to improve performance.
    // Note: This setting is experimental
    indirect: false,

    // Print out warnings encountered during tree construction.
    verbose: true,

}

NOTE: The geometry's index attribute array is modified in order to build the bounds tree unless indirect is set to true. If the geometry has no index then one is added if indirect is set to false.

.resolveTriangleIndex

resolveTriangleIndex( index : Number ) : Number

Helper function for use when indirect is set to true. This function takes a triangle index in the BVH layout and returns the associated triangle index in the geometry index buffer or position attribute.

.raycast

raycast( ray : Ray, side : FrontSide | BackSide | DoubleSide = FrontSide ) : Array<RaycastHit>
raycast( ray : Ray, material : Array<Material> | Material ) : Array<RaycastHit>

Returns all raycast triangle hits in unsorted order. It is expected that ray is in the frame of the BVH already. Likewise the returned results are also provided in the local frame of the BVH. The side identifier is used to determine the side to check when raycasting or a material with the given side field can be passed. If an array of materials is provided then it is expected that the geometry has groups and the appropriate material side is used per group.

Note that unlike three.js' Raycaster results the points and distances in the intersections returned from this function are relative to the local frame of the MeshBVH. When using the acceleratedRaycast function as an override for Mesh.raycast they are transformed into world space to be consistent with three's results.

.raycastFirst

raycastFirst( ray : Ray, side : FrontSide | BackSide | DoubleSide = FrontSide ) : RaycastHit
raycastFirst( ray : Ray, material : Array<Material> | Material ) : RaycastHit

Returns the first raycast hit in the model. This is typically much faster than returning all hits. See raycast for information on the side and material options as well as the frame of the returned intersections.

.intersectsSphere

intersectsSphere( sphere : Sphere ) : Boolean

Returns whether or not the mesh intersects the given sphere.

.intersectsBox

intersectsBox( box : Box3, boxToBvh : Matrix4 ) : Boolean

Returns whether or not the mesh intersects the given box.

The boxToBvh parameter is the transform of the box in the meshes frame.

.intersectsGeometry

intersectsGeometry( geometry : BufferGeometry, geometryToBvh : Matrix4 ) : Boolean

Returns whether or not the mesh intersects the given geometry.

The geometryToBvh parameter is the transform of the geometry in the BVH's local frame.

Performance improves considerably if the provided geometry also has a boundsTree.

.closestPointToPoint

closestPointToPoint(
	point : Vector3,
	target : Object = {},
	minThreshold : Number = 0,
	maxThreshold : Number = Infinity
) : target

Computes the closest distance from the point to the mesh and gives additional information in target. The target can be left undefined to default to a new object which is ultimately returned by the function.

If a point is found that is closer than minThreshold then the function will return that result early. Any triangles or points outside of maxThreshold are ignored. If no point is found within the min / max thresholds then null is returned and the target object is not modified.

target : {
	point : Vector3,
	distance : Number,
	faceIndex : Number
}

The returned faceIndex can be used with the standalone function getTriangleHitPointInfo to obtain more information like UV coordinates, triangle normal and materialIndex.

.closestPointToGeometry

closestPointToGeometry(
	geometry : BufferGeometry,
	geometryToBvh : Matrix4,
	target1 : Object = {},
	target2 : Object = {},
	minThreshold : Number = 0,
	maxThreshold : Number = Infinity
) : target1

Computes the closest distance from the geometry to the mesh and puts the closest point on the mesh in target1 (in the frame of the BVH) and the closest point on the other geometry in target2 (in the geometry frame). If target1 is not provided a new Object is created and returned from the function.

The geometryToBvh parameter is the transform of the geometry in the BVH's local frame.

If a point is found that is closer than minThreshold then the function will return that result early. Any triangles or points outside of maxThreshold are ignored. If no point is found within the min / max thresholds then null is returned and the target objects are not modified.

target1 and target2 are optional objects that similar to the target parameter in closestPointPoint and set with the same fields as that function.

The returned in target1 and target2 can be used with the standalone function getTriangleHitPointInfo to obtain more information like UV coordinates, triangle normal and materialIndex.

Note that this function can be very slow if geometry does not have a geometry.boundsTree computed.

.shapecast

shapecast(
	callbacks : {

		boundsTraverseOrder : (
			box: Box3
		) => Number = null,

		intersectsBounds : (
			box : Box3,
			isLeaf : Boolean,
			score : Number | undefined,
			depth : Number,
			nodeIndex : Number
		) => NOT_INTERSECTED | INTERSECTED | CONTAINED,

		intersectsRange : (
			triangleOffset : Number,
			triangleCount : Number
			contained : Boolean,
			depth : Number,
			nodeIndex : Number,
			box: Box3
		) => Boolean = null,

		intersectsTriangle : (
			triangle : ExtendedTriangle,
			triangleIndex : Number,
			contained : Boolean,
			depth : Number
		) => Boolean = null,

	}

) : Boolean

A generalized cast function that can be used to implement intersection logic for custom shapes. This is used internally for intersectsBox, intersectsSphere, and more. The function returns as soon as a triangle has been reported as intersected and returns true if a triangle has been intersected. The bounds are traversed in depth first order calling boundsTraverseOrder, intersectsBoundsFunc, intersectsRange, and intersectsTriangle for each node and using the results to determine when to end traversal. The depth value passed to callbacks indicates the depth of the bounds the provided box or triangle range belongs to unless the triangles are indicated to be CONTAINED, in which case depth is the depth of the parent bounds that were contained. The depth field can be used to precompute, cache to an array, and then read information about a parent bound to improve performance while traversing because nodes are traversed in a dpeth first order. The triangleIndex parameter specifies the index of the triangle in the index buffer. The three vertex indices can be computed as triangleIndex * 3 + 0, triangleIndex * 3 + 1, triangleIndex * 3 + 2.

boundsTraverseOrder takes as an argument the axis aligned bounding box representing an internal node local to the BVH and returns a score (often distance) used to determine whether the left or right node should be traversed first. The shape with the lowest score is traversed first.

intersectsBounds takes the axis aligned bounding box representing an internal node local to the bvh, whether or not the node is a leaf, the score calculated by boundsTraverseOrder, the node depth, and the node index (for use with the refit function) and returns a constant indicating whether or not the bounds is intersected or contained meaning traversal should continue. If CONTAINED is returned (meaning the bounds is entirely encapsulated by the shape) then an optimization is triggered allowing the range and / or triangle intersection callbacks to be run immediately rather than traversing the rest of the child bounds.

intersectsRange takes a triangle offset and count representing the number of triangles to be iterated over. 1 triangle from this range represents 3 values in the geometry's index buffer. If this function returns true then traversal is stopped and intersectsTriangle is not called if provided.

NOTE The triangle range provided in intersectsRange is for the indirect bvh storage buffer if the option has been set so it is necessary to transform to geometry triangle indices using resolveTriangleIndex.

intersectsTriangle takes a triangle and the triangle index and returns whether or not the triangle has been intersected. If the triangle is reported to be intersected the traversal ends and the shapecast function completes. If multiple triangles need to be collected or intersected return false here and push results onto an array. contained is set to true if one of the parent bounds was marked as entirely contained (returned CONTAINED) in the intersectsBoundsFunc function.

.refit

refit( nodeIndices : Array<Number> | Set<Number> = null ) : void

Refit the node bounds to the current triangle positions. This is quicker than regenerating a new BVH but will not be optimal after significant changes to the vertices. nodeIndices is a set of node indices (provided by the shapecast function, see example snippet below) that need to be refit including all internal nodes. If one of a nodes children is also included in the set of node indices then only the included child bounds are traversed. If neither child index is included in the nodeIndices set, though, then it is assumed that every child below that node needs to be updated.

Here's how to get the set of indices that need to be refit:

const nodeIndices = new Set();
bvh.shapecast(

	{

		intersectsBounds: ( box, isLeaf, score, depth, nodeIndex ) => {

			if ( /* intersects shape */ ) {

				nodeIndices.add( nodeIndex );
				return INTERSECTED;

			}

			return NOT_INTERSECTED;

		},

		intersectsRange: ( offset, count, contained, depth, nodeIndex ) => {

			/* collect triangles / vertices to move */

			// the nodeIndex here will have always already been added to the set in the
			// `intersectsBounds` callback.
			nodeIndices.add( nodeIndex );

		}

	}

);

/* update the positions of the triangle vertices */

// update the BVH bounds of just the bounds that need to be updated
bvh.refit( nodeIndices );

.getBoundingBox

getBoundingBox( target : Box3 ) : Box3

Get the bounding box of the geometry computed from the root node bounds of the BVH. Significantly faster than BufferGeometry.computeBoundingBox.

SerializedBVH

.roots

roots : Array<ArrayBuffer>

.index

index : TypedArray

MeshBVHHelper

extends THREE.Group

Displays a view of the bounds tree up to the given depth of the tree. Update() must be called after any fields that affect visualization geometry are changed.

Note: The visualizer is expected to be a sibling of the mesh being visualized.

.depth

depth : Number

The depth to traverse and visualize the tree to.

.color

color = 0x00FF88 : THREE.Color

The color to render the bounding volume with.

.opacity

opacity = 0.3 : Number

The opacity to render the bounding volume with.

.displayParents

displayParents = false : Boolean

Whether or not to display the parent bounds.

.displayEdges

displayEdges = true : Boolean

If true displays the bounds as edges other displays the bounds as solid meshes.

.edgeMaterial

edgeMaterial : LineBasicMaterial

The material to use when rendering edges.

.meshMaterial

meshMaterial : MeshBasicMaterial

The material to use when rendering as a sold meshes.

.constructor

constructor(
	meshOrBvh: THREE.Mesh | MeshBVH,
	depth = 10 : Number
)

constructor(
	mesh = null : THREE.Mesh,
	bvh = null : MeshBVH,
	depth = 10 : Number
)

Instantiates the helper to visualize a MeshBVH.

If a mesh and no bvh is provided then the mesh.geometry.boundsTree is displayed. Otherwise the provided bvh is displayed. Addtionally, if mesh is provided then the helper world transform is automatically synchronized with the Mesh. Otherwise if not mesh is provided then the user can manage the transform.

.update

update() : void

Updates the display of the bounds tree in the case that the bounds tree has changed or the depth parameter has changed.

.dispose

dispose() : void

Disposes of the material used.

ExtendedTriangle

extends THREE.Triangle

An extended version of three.js' Triangle class. A variety of derivative values are cached on the object to accelerate the intersection functions. .needsUpdate must be set to true when modifying the triangle parameters.

.needsUpdate

needsUpdate : Boolean

Indicates that the triangle fields have changed so cached variables to accelerate other function execution can be updated. Must be set to true after modifying the triangle a, b, c fields.

.intersectsTriangle

intersectsTriangle( other : Triangle, target? : Line3  ) : Boolean;

Returns whether the triangles intersect. target is set to the line segment representing the intersection.

.intersectsSphere

intersectsSphere( sphere : Sphere ) : Boolean

Returns whether the triangle intersects the given sphere.

.closestPointToSegment

closestPointToSegment( segment : Line3, target1? : Vector3, target2? : Vector3 ) : Number

Returns the distance to the provided line segment. target1 and target2 are set to the closest points on the triangle and segment respectively.

.distanceToPoint

distanceToPoint( point : Vector3 ) : Number

Returns the distance to the provided point.

.distanceToTriangle

distanceToTriangle( tri : Triangle ) : Number

Returns the distance to the provided triangle.

OrientedBox

extends THREE.Box3

An oriented version of three.js' Box3 class. A variety of derivative values are cached on the object to accelerate the intersection functions. .needsUpdate must be set to true when modifying the box parameters.

.matrix

matrix : Matrix4

Matrix transformation applied to the box.

.needsUpdate

updateUpdate : Boolean

Indicates that the bounding box fields have changed so cached variables to accelerate other function execution can be updated. Must be set to true after modifying the oriented box min, max, matrix fields.

.set

set( min : Vector3, max : Vector3, matrix : Matrix4 ) : this

Sets the oriented box parameters.

.intersectsBox

intersectsBox( box : Box3 ) : Boolean

Returns true if intersecting with the provided box.

.intersectsTriangle

intersectsTriangle( tri : Triangle ) : Boolean

Returns true if intersecting with the provided triangle.

.closestPointToPoint

closestPointToPoint( point : Vector3, target = null : Vector3 ) : Number

Returns the distance to the provided point. Sets target to the closest point on the surface of the box if provided.

.distanceToPoint

distanceToPoint( point : Vector3 ) : Number

Returns the distance to the provided point.

.distanceToBox

distanceToBox( box : Box3, threshold = 0 : Number, target1 = null : Vector3, target2 = null : Vector3 ) : Number

Returns the distance to the provided box. threshold is an optional distance to return early if the distance is found to be within it. target1 and target2 are set to the points on the surface of this box and the box argument respectively.

Extensions

Raycaster.firstHitOnly

firstHitOnly = false : Boolean

If the Raycaster member firstHitOnly is set to true then the .acceleratedRaycast function will call the .raycastFirst function to retrieve hits which is generally faster.

.computeBoundsTree

computeBoundsTree( options : Object ) : void

A pre-made BufferGeometry extension function that builds a new BVH, assigns it to boundsTree, and applies the new index buffer to the geometry. Comparable to computeBoundingBox and computeBoundingSphere.

THREE.BufferGeometry.prototype.computeBoundsTree = computeBoundsTree;

.disposeBoundsTree

disposeBoundsTree() : void

A BufferGeometry extension function that disposes of the BVH.

THREE.BufferGeometry.prototype.disposeBoundsTree = disposeBoundsTree;

.acceleratedRaycast

acceleratedRaycast( ... )

An accelerated raycast function with the same signature as THREE.Mesh.raycast. Uses the BVH for raycasting if it's available otherwise it falls back to the built-in approach. The results of the function are designed to be identical to the results of the conventional THREE.Mesh.raycast results.

If the raycaster object being used has a property firstHitOnly set to true, then the raycasting will terminate as soon as it finds the closest intersection to the ray's origin and return only that intersection. This is typically several times faster than searching for all intersections.

THREE.Mesh.prototype.raycast = acceleratedRaycast;

StaticGeometryGenerator

A utility class for taking a set of SkinnedMeshes or morph target geometry and baking it into a single, static geometry that a BVH can be generated for.

.useGroups

useGroups = true : Boolean

If true then groups are used to support an array of materials on the mesh.

.attributes

attributes = [ 'position', 'normal', 'tangent', 'uv', 'uv2' ] : Array<String>

The set of attributes to copy onto the static geometry.

.applyWorldTransforms

applyWorldTransforms = true : Boolean

Whether to transform the vertices of the geometry by the world transforms of each mesh when generating.

constructor

constructor( object : Array<Object3D> )

Takes an array of object hierarchies to bake into a single static geometry.

.getMaterials

getMaterials() : Array<Material>

Returns an array of materials for the meshes to be merged. These can be used alongside the generated geometry when creating a mesh: new Mesh( geometry, generator.getMaterials() ).

.generate

generate( target = new BufferGeometry() : BufferGeometry ) : BufferGeometry

Generates a single, static geometry for the passed meshes. When generating for the first time an empty target geometry is expected. The same generated geometry can be passed into the function on subsequent calls to update the geometry in place to save memory. An error will be thrown if the attributes or geometry on the meshes to bake has been changed and are incompatible lengths, types, etc.

On subsequent calls the "index" buffer will not be modified so any BVH generated for the geometry is unaffected. Once the geometry is updated the MeshBVH.refit function can be called to update the BVH.

GenerateMeshBVHWorker

Helper class for generating a MeshBVH for a given geometry in asynchronously in a worker. The geometry position and index buffer attribute ArrayBuffers are transferred to the Worker while the BVH is being generated meaning the geometry will be unavailable to use while the BVH is being processed unless SharedArrayBuffers are used. They will be automatically replaced when the MeshBVH is finished generating.

NOTE It's best to reuse a single instance of this class to avoid the overhead of instantiating a new Worker.

See note in Asyncronous Generation use snippet.

.running

running : Boolean;

Flag indicating whether or not a BVH is already being generated in the worker.

.generate

generate( geometry : BufferGeometry, options : Object ) : Promise< MeshBVH >;

Generates a MeshBVH instance for the given geometry with the given options in a WebWorker. Returns a promise that resolves with the generated MeshBVH. This function will throw an error if it is already running.

.dispose

dispose() : void;

Terminates the worker.

Debug Functions

estimateMemoryInBytes

estimateMemoryInBytes( bvh : MeshBVH ) : Number

Roughly estimates the amount of memory in bytes a BVH is using.

getBVHExtremes

getBVHExtremes( bvh : MeshBVH ) : Array< Object >

Measures the min and max extremes of the tree including node depth, leaf triangle count, and number of splits on different axes to show how well a tree is structured. Returns an array of extremes for each group root for the bvh. The objects are structured like so:

{
	// The total number of nodes in the tree including leaf nodes.
	nodeCount: Number,

	// The total number of leaf nodes in the tree.
	leafNodeCount: Number,

	// A total tree score based on the surface area heuristic score
	// useful for comparing the quality and performance capability
	// of the bounds tree. Lower score is better and based on the surface
	// area of bounds and how many triangles are stored within.
	surfaceAreaScore: Number,

	// The min and max of leaf nodes in the tree.
	depth: { min: Number, max: Number },

	// The min and max number of triangles contained within the
	// bounds the leaf nodes.
	tris: { min: Number, max: Number },

	// The number of splits on any given axis.
	splits: [ Number, Number, Number ]
}

NOTE The when using the refit function the surfaceAreaScore can be used to check how significantly the structure of the BVH has degraded and rebuild it if it has changed beyond some threshold ratio.

Individual Functions

Functions exported individually not part of a class.

getTriangleHitPointInfo

getTriangleHitPointInfo(
	point: Vector3,
	geometry : BufferGeometry,
	triangleIndex: Number
	target: Object
) : Object

This function returns information of a point related to a geometry. It returns the target object or a new one if passed undefined:

target : {
	face: {
		a: Number,
		b: Number,
		c: Number,
		materialIndex: Number,
		normal: Vector3
	},
	uv: Vector2
}
  • a, b, c: Triangle indices
  • materialIndex: Face material index or 0 if not available.
  • normal: Face normal
  • uv: UV coordinates.

This function can be used after a call to closestPointPoint or closestPointToGeometry to retrieve more detailed result information.

Shader and Texture Packing API

In addition to queries in Javascript the BVH can be packed into a series of textures so raycast queries can be performed in a shader using provided WebGL shader functions. See the shader implementation in the simple GPU Path Tracing example for an example on how to use the functionality.

*VertexAttributeTexture

FloatVertexAttributeTexture

UIntVertexAttributeTexture

IntVertexAttributeTexture

extends THREE.DataTexture

Float, Uint, and Int VertexAttributeTexture implementations are designed to simplify the efficient packing of a three.js BufferAttribute into a texture. An instance can be treated as a texture and when passing as a uniform to a shader they should be used as a sampler2d, usampler2d, and isampler2d when using the Float, Uint, and Int texture types respectively.

.overrideItemSize

overrideItemSize : Number = null

Treats BufferAttribute.itemSize as though it were set to this value when packing the buffer attribute texture. Throws an error if the value does not divide evenly into the length of the BufferAttribute buffer (count * itemSize % overrideItemSize).

Specifically used to pack geometry indices into an RGB texture rather than an Red texture.

.updateFrom

updateFrom( attribute : THREE.BufferAttribute ) : void

Updates the texture to have the data contained in the passed BufferAttribute using the BufferAttribute itemSize field, normalized field, and TypedArray layout to determine the appropriate texture layout, format, and type. The texture dimensions will always be square. Because these are intended to be sampled as 1D arrays the width of the texture msut be taken into account to derive a sampling uv. See texelFetch1D in shaderFunctions.

MeshBVHUniformStruct

A shader uniform object corresponding to the BVH shader struct defined in shaderStructs. The object contains four textures containing information about the BVH and geometry so it can be queried in a shader using the bvh intersection functions defined in shaderFunctions. This object is intended to be used as a shader uniform and read in the shader as a BVH struct.

.updateFrom

updateFrom( bvh : MeshBVH ) : void

Updates the object and associated textures with data from the provided BVH.

.dispose

dispose() : void

Dispose of the associated textures.

Shader Function and Struct Exports

shaderStructs

shaderStructs : string

Set of shaders structs and defined constants used for interacting with the packed BVH in a shader. See src/gpu/shaderFunctions.js for full implementations and declarations.

shaderFunctions

shaderFunctions : string

Set of shader functions used for interacting with the packed BVH in a shader and sampling VertexAttributeTextures. See src/gpu/shaderFunctions.js for full implementations and declarations.

Gotchas

  • When querying the MeshBVH directly all shapes and geometry are expected to be specified in the local frame of the BVH. When using three.js' built in raycasting system all results are implicitly transformed into world coordinates.
  • A bounds tree can be generated for either an indexed or non-indexed BufferGeometry, but an index will be produced and retained as a side effect of the construction unless the "indirect" option is used.
  • The bounds hierarchy is not dynamic, so geometry that uses morph targets or skinning cannot be used. Though if vertex positions are modified directly the refit function can be used to adjust the bounds tree.
  • If the geometry is changed then a new bounds tree will need to be generated or refit.
  • InterleavedBufferAttributes are not supported with the geometry index buffer attribute.
  • A separate bounds tree is generated for each geometry group, which could result in less than optimal raycast performance on geometry with lots of groups.
  • Due to errors related to floating point precision it is recommended that geometry be centered using BufferGeometry.center() before creating the BVH if the geometry is sufficiently large or off center so bounds tightly contain the geometry as much as possible.

Running Examples Locally

To run the examples locally:

  • Run npm start
  • Then visit localhost:1234/<demo-name>.html

Where <demo-name> is the name of the HTML file from example folder.

Used and Supported by

...and more!

three-mesh-bvh's People

Contributors

agargaro avatar arpu avatar bekboris avatar bergden-resonai avatar cpind avatar crabmusket avatar deepansh96 avatar dependabot[bot] avatar dmliao avatar dohard2023 avatar frading avatar github4jiawen avatar gkjohnson avatar gonnavis avatar gsimone avatar innovgame avatar krispya avatar lgtm-migrator avatar mattni avatar mikkoh avatar minitoine avatar moritzhertler avatar mqp avatar obrhaberer avatar onekonw avatar robertlong avatar thuijzer avatar vegarringdal avatar vegarringdalaibel avatar yomboprime avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

three-mesh-bvh's Issues

Reduce THREE code duplication

Some of the code in IntersectionUtils and GeometryUtils is copied from THREE js because its not otherwise exposed. If possible this copied code should be removed or at least reduced.

TODO: Document which functions are copied

Consider storing axis of separation in nodes when splitting

Physically Based Rendering had this great suggestion for improving raycasting performance. Ordinarily, if you want to find only the closest intersection for your ray, you need to test your ray against both bounding volumes at each level of the tree hierarchy, and take whichever has the intersection with the closest distance, because it could intersect both. But if you use an SAH strategy and you remember at each node which axis was used for splitting, you can look at where the ray is coming from with respect to the split plane and you know right away which child bounding volume must have the closest intersection, if it intersects that bounding volume at all. You may not have to even look at the other one.

Of course this will cost a little memory but it sounds like it might improve raycasting firstHitOnly performance very substantially.

Provide API for serializing and deserializing the trees

Provide an api for serializing and dserializing the tree so the cost of BVH creation can happen offline and be deserialized and instantiated on page load.

const serialized = geometry.boundsTree.serialize();

// ...

geometry.boundsTree = MeshBVH.deserialize(serialized);

// OR

geometry.computeBoundsTree(Strategy.SAH, serialized);

Update to THREE r96

The intersectTri function seems to have changed so it needs to be recopied.

Remove unnecessary fields from the MeshBVHNode to save memory

Tons of nodes get created so any reduction we can make per object (even empty dictionary keys) may be worthwhile.

  • _mesh does not necessarily need to be a pointer on every node.
  • children can be removed if the node is a leaf node (or only be added if it's an internal one)
  • tris can be removed if the node is an internal node.

Tree construction process could create less garbage

When I profile tree construction, one main bottleneck I see is that multiple GCs have to happen due to the amount of garbage that gets created. Here are a few categories of things that we construct:

  1. MeshBVHNodes. Of course, we need nodes in our tree.
  2. Spheres, Box3s, and their underlying Vector3s. It seems like we mostly need to store different bounding boxes and spheres for all of our nodes, so this is hard to avoid.
  3. For each node, the array of triangle indices in that node. This one is interesting. We need that list at each level when we are constructing child nodes, but we only need to store it permanently for leaf nodes; the arrays of triangles for intermediate nodes are discarded. It could be possible to rework the construction algorithm to not construct so many intermediate arrays.
  4. For each node, lambdas referring to that node and its split volumes, to put into the creation queue. This one is also interesting. It seems like if we reworked the construction algorithm, the processing queue could just be a queue of nodes, where the meaning of each entry is "this node needs to be split." That would save us creating the lambdas.

3 and 4 could be productive avenues for optimizing tree construction.

Add option to store offset and count on inner nodes

So that ranges of triangles can be quickly rolled up if a full bvh node is contained in a shape. The value range should encapsulate all the triangles contained by all the children.

This will mean that we should determine if a node is a leaf using node.left instead of node.count.

The shapecast function should be updated to include the bvh node itself in callbacks so these values can be used.

Possible option names are includeTrianglesOnInternalNodes, storeInternalTriangles, storeInternalIndices

BVH Visualizer does not reflect world rotation

It's documented that the visualizer needs to be a sibling but it might be nice if instead it just worked no matter where the visualizer was added. Could just override "updateMatrixWorld" to copy the target meshes matrixWorld value.

Publish to NPM

  • Move all source files into src directory
  • Decide on approach for initializing the extension to the THREE.Mesh.prototype & THREE.Geometry.prototype (call function? automatically include?)
  • Add name, version, etc to package.json
  • Update docs
  • Produce a UMD variant
  • Use three js named imports
  • #47
  • #37
  • #51
  • Rename rollup MeshBVH global variable to MeshBVHLib (#64)

Update Documentation

Add sections for

  • Describing how the raycasting approach works (BVH, KD Tree, SAH)
  • Making the most out of the bounds tree (can't handle animated meshes, merge mesh sub parts to avoid overlapping bounds trees, intersecting leaf nodes)
  • Memory issues when dealing with lots of MeshBVHNodes

Path to v0.1.0

  • Update Changelog
    • Fix the bounds tree not respecting groups
    • An index buffer is created on geometry if it does not exist
  • Document caveat & throw an error if the index is an InterleavedBufferAttribute.
  • Document that the index will be automatically created and changed on geometry
  • Remove need to set index in README

Implement maximum depth handling better

Right now there are two fishy-looking things about the maximum depth cutoff:

  • If the maximum depth is hit, the leaf nodes will be left without an offset and count, so I don't think raycasting against them will work.
  • When the splitting algorithm reaches the maximum depth, it will do a final split and then proceed to ignore the results. It should return early prior to splitting if it's at the maximum depth, just like it returns early if it meets the cutoff for leaf triangle count.

The former is kind of embarrassing, there probably ought to be a test for this.

Add Construction Options

To computeBoundsTree() function and BoundsTree constructor

{
  // how to split the tree nodes
  splitStrategy,
  
  // the max depth to split the tree at
  maxDepth,

  // the max amount of error to allow when using the SAH strategy
  maxError,

  // whether or not to just intersect the leaf nodes for collision and forgo triangle intersections
  intersectLeafNodes
}

Related to #13
Related to #16
Related to #17

Reorganize Utils Scripts

The difference between BoundsUtilities, GeometryUtilities, and IntersectionUtilities isn't super clear.

Reorganize into something like the following:

  • BoxArrayUtilities.js: Utilities related to reading and creating the bounding box to Float32Array.
  • ThreeIntersectionUtilities.js: The functions copied and slightly modified from the THREE source.
  • IntersectionUtilities: Functions related to intersecting and colliding rays, triangles, and bounding shapes.

Remove box diagonals

Wireframe boxes in threejs show with diagonals on each face, with makes me sad :(

image

BVH construction : Improve memory consumption

I think you could improve BVH construction time and memory consumption with two tricks :

  • For inner nodes, instead of creating each time left and right primitives id collections before calling the recursive function for their two children (huge memory consumption), you can mutate origTris when the split occurs, and only provide to the two recursive functions the range to work with (basically just a start and end index).
    The final origTris -which is now properly ordered by primitive id of "successive leaves"- can then be a private property of the BVH object, and each leaf only stores start index and the number of primitives (+ things like min/max of the bound of course, and eventually split axis), so that it can retrieve its primitives.
    This approach is valid because, in a BVH, each primitive is associated to only one leaf, even if the primitive overlaps a neighbour leaf.

  • After recursive construction, tranforming the BVH nodes to a compact representation (linearization in an Array, Float32Array or Float64Array?) can save a lot of memory (and improve raycast performance, in C++ it's significant, I'm not sure in javascript).

If you are looking for implementation details, the book "Physically Based Rendering" is a great ressource. Their BVH implementation relies on two algorithms of C++ standard library (partition and nth_element, used to reorder primitives collection in two groups, depending of split method), which can be easily implemented in javascript.
They also store each node information in 32 Bytes (memory alignment), but I can't see how to achieve this kind of optimization in javascript.

Interleaved Attribute Buffers not supported

The MeshBVH construction relies on accessing the index array directly, meaning that Interleaved Buffer Attributes are not supported as an index attribute. This limitation should be communicated (documented / error thrown) or code updated so it's supported.

BVH construction doesn't work when geometries have groups

Today I learned that three.js BufferGeometry can have groups, which are ranges of the index that are meant to be drawn with a different material. For example, you can set the first half of the index to be drawn with one material, and the second half to be drawn with another.

This presents a problem for us because if we reorder the index, we are invalidating the group ranges, so the resulting geometry will be drawn with the wrong materials on different parts of the model.

There are maybe three reasonable things we could do to address this:

  1. Don't construct BVHs for geometries with groups. Perhaps we could offer functionality to "ungroup" grouped geometries.
  2. Add a layer of indirection like our tris which we retain in memory permanently, so that we leave the index alone and store ranges of tris in our tree nodes.
  3. Create one BVH "root" for each group which only stores and reorders indices in that group. That way, we will still scramble up the index, but the integrity of the groups will remain. When we raycast, raycast down all the roots (minding bounding boxes.)

I think solution number 3 sounds pretty easy and has no real downsides (raycasting might be very slightly more expensive, but that was also true for solution 2, and if someone doesn't like that, they can ungroup their geometries), and we should implement it.

Benchmark does not reflect size in browser

The benchmark estimates the size of an object based on primitive, array, and dictionary key size calculations, but the javascript engine likely overhead associated with an object.

When computing a bounding box for a TorusKnotBufferGeometry with the arguments TorusKnotBufferGeometry(1, 0.4, 400, 100) the benchmark reports 2476.31 kb (including index buffer) used while the browser reports 6617.08 kb used.

image

Refactor functions to encapsulate private cache variables

A lot of functions depend on cached THREE objects:

const v1 = new Vector3();
function doSomething( triangle ) {

  v1.copy( triangle.a );
  // ... do stuff ...

}

which runs a risk of functions overwriting other functions variables mid-use, etc. It might be best to refactor to use the pattern that THREE.js uses:

const doSomething = ( function() {

  const v1 = new Vector3();
  return function doSomething ( triangle ) {
    // ...
  }

} )();

Or for class members:

MeshBVHNode.prototype.doSomething = ( function() {

  const v1 = new Vector3();
  return function doSomething ( triangle ) {
    // ...
  }

} )();

Update: See MeshBVH.js for the functions that need to be updated.

Speed up rendering?

Thanks for a great library!

I was thinking, could this approach also be used to speed up rendering of large meshes? IIRC Three doesn't render meshes whose bounds don't intersect the frustum, so if a large mesh was partitioned as it was here it would also lead to improvements in rendering speed. You might even get the fast raycasting behavior for free if you turn a Mesh into an Object3D hierarchy.

Add dynamic and scene support

The fast raycasting only works static meshes and doesn't account for co-location, grouping, or blocking of scene geometry.

Some approaches include generalizing the bvh, making it dynamic, or using another data structure (like threeocttree) to make scene-wide raycasts as fast as possible.

Provide API for progressive tree building

Provide an API for progressive tree building, which can be slow for large meshes at the moment. Some ideas:

// uses requestIdleTick or requestAnimationFrame to build the tree and
// provides a promise and variable for whether or not the tree is done
const perFrameTime = 1;
geometry.computeBoundsTree(Strategy.SAH, perFrameTime).then(...);
console.log(geometry.boundsTree.ready); // false
// provides function for running the next tick of the building
geometry.computeBoundsTreeProgressively();

// every frame until finished
if (!geometry.boundsTree.ready) geometry.boundsTree.build(perFrameTime);

this would be easy to do with a generator

Selectively updated OBB, Triangle caches

Some of the cached information in the OBB and SeparatingAxisTriangle classes isn't always needed and can cause a small performance hit to create when performing shape casts. Would be better to selectively update parts of the object as needed.

Check maxNodeTris after split

The geometry in the benchmark with the default options winds up having the following min and max triangle counts:

tris: { min: 1, max: 76 }

despite the maxNodeTris option being set to 10. This could be solved by checking to make sure that both children counts will be above the tris threshold before splitting the nodes into separate children.

Add Shapecast function

Add a shapecast function that takes an object to collide with and functions to do collision checking with the bounding boxes and triangles.

// thisMesh
// - mesh that represents the bounding tree

// intersectsBounds( box3 )
// - callback that returns whether or not an inner node has been intersected
// with and to continue to traverse

// intersectsTri( triangle, indices )
// - callback that returns whether or not a triangle has been intersected with.
// If true then the function will stop and return immediately. If needed triangles
// can be pushed into an array here if all intersections are needed.
MeshBVHNode.shapecast( thisMesh, intersectsBounds, intersectsTri );

Use consistent code transformation

Babel is used to run the benchmarks and parcel is used to build the example page. Technically parcel should be using the babel config to transform the code but it could still be resulting in different enough code to be worth investigating.

Add index option to specify which index array to modify

Provide index option for the MeshBVH.

  • If an index is provided then that array is modified in place
  • If the index option is null then a new index array is generated
  • Defaults to null

From #62

index option

An option to provide an already existing index to modify to the BVH. If it's null then the BVH creates a new index array to use internally. If you wanted the BVH to modify the geometry index in place you might do this:

geometry.boundsTree = new MeshBVH( geometry, { index: geometry.index.array } );

or set it after the fact

geometry.boundsTree = new MeshBVH( geometry );
geometry.setIndex( new THREE.BufferAttribute( geometry.boundsTree.index, 1, false ) );

I guess there are actually three cases:

  1. The geometry has no index. BVH generation should create one and then you can assign it.
  2. The geometry has an existing index, but you care about it, so you want the BVH to create a new one and then you will assign it, maybe after retaining the existing one.
  3. The geometry has an existing index, but you are happy to mutate it.

If we implemented your first suggestion (the "index option"), cases 1 and 2 could both be handled by the "set it after the fact" pattern and case 3 would be handled by the "pass it in" pattern. That seems OK.

Hmm, actually, now I'm not sure how I want the option to work. The thing is that IMO the common case is that the code should handle cases 1 and 3 I listed above. I don't really want to make users type something like

if (geometry.index) {
  geometry.boundsTree = new MeshBVH( geometry, { index: geometry.index.array } );
} else {
  geometry.boundsTree = new MeshBVH( geometry ); // it will create an index
  geometry.setIndex( new THREE.BufferAttribute( geometry.boundsTree.index, 1, false ) );
}

I don't really mind that. It's not really necessary to set the index of the geometry if one doesn't already exist because it should render the same either way and the memory impact should be identical, as well. In fact it might be best to not set it because then the index memory can be released by just removing the boundsTree from the geometry.

This should work for all cases, right?

geometry.boundsTree = new MeshBVH( geometry, { index: geometry.index && geometry.index.array } );

It could be cleaner if we instead took a BufferAttribute but there's no real reason to do so (if we ignore the InterleavedBufferAttribute case, which would mean other code has to change, as well).

Assign default maximum depth < โˆž

I'm not sure what a good maximum depth is, but it seems to me a maximum depth of infinity means that fishy models can kind of DoS non-SaH trees by e.g. making sure that exactly one triangle gets partitioned off every time, causing construction to take forever, overflow the stack, run out of memory, make raycasting very slow, or all of the above. I think some conservative default of, say, 20-30 would be wiser. Assuming reasonable splits are being created that should suffice for models up to millions of tris.

Rename shapecast functions

To "intersectsSphere", etc to be more in line with the THREE.js convention.

Also geometrycast can be moved to use the shapecast function.

Improve Triangle shape intersection performance

See implementations in this repo:

  • Triangle intersects triangle: here
  • Triangle intersects Sphere: here
  • Triangle closest Point: here
  • OBB / AABB Box intersection?
  • OBB / AABB Distance?

OrientedBox and SeparatingAxisTriangle cache update seems to be a bit of a bottleneck, as well.

Write tests

Verify that casts works as expected and match the values that the built in raycast give.
Make sure that ray casting functions work if the ray is cast from inside the geometry.

Some nodes have a negative triangle count

@mquander

I added a function to the benchmark utils to interrogate the structure of the tree a bit more called getExtremes. It returns the min and max depth and triangle count per node. When running it against the geometry in the benchmark script I'm seeing that the "minimum" number of triangles in a node is negative:

const geometry = new THREE.TorusBufferGeometry( 5, 5, 700, 300 );
geometry.computeBoundsTree();

console.log( getExtremes( geometry.boundsTree ) );

// { depth: { min: 11, max: 22 }, tris: { min: -293, max: 304 } }

Maybe you have some thoughts?

Use a better split strategy

Right now, the BVH nodes are being split at the average point of all face vertices along the longest edge of the overall bounding box.

This allows for less-than optimal bounding boxes where there's a large gap between triangles instead of just containing the clusters of polygons.

Consider precomputing bounding boxes for triangles

In tree construction, after a split has been decided, we need to establish a minimal bounding volume for the new child nodes. We do so by looking at each triangle in the child node and finding the smallest possible bounding box for all of them. This entails looking up each vertex for each triangle separately in the geometry and seeing whether that vertex needs to expand our box.

Since in the process of constructing the tree we consider each triangle many times for this purpose (at least once for each level of the tree) we could potentially gain by preparing a data structure beforehand that has a single bounding box for each triangle, which we then consult each time we need to do the process above. That would decrease the amount of random reads and cut the work per triangle by two thirds. It seems likely that this would be faster for trees with a lot of levels, although it's hard to say how much.

Pool visualization boxes for BVH Viz

When the depth to render updates, it tosses out all the boxes and creates new ones for every bounds, which is slow. Instead, it should create new boxes only as needed and remove the unused boxes once the depth has been updated

Consider faster approach to early out raycastFirst after first hit

#36 (comment)

The raycast already seems really performant so it's not a big deal but I believe this check can be faster if you're interested.

This intersectRay function for the box will check all six faces of the box when really you only need to check a single component of one of the planes of the box. So if the split axis is z then you should only have to check if the z component of the split plane is greater than or less than the z component of the intersection (depending on the direction of the ray). That should also let you get rid of the call to distanceToSquared below.

Non buffer geometry is slow to compute

After generalizing the BVH construction and functions for generally accessing triangles between Geometry and BufferGeometry, the Geometry building process is significantly slower than it was before. See this function:

https://github.com/gkjohnson/threejs-raycast-updates/blob/7332865cc44e4bcd4dee64cac7695b29cc526050/lib/GeometryUtilities.js#L15-L19

It's possible that the added dictionary accesses would be causing slowdowns. Previously, this

const index= faces[tri]['a']; // dictionary access
const vert =  vertices[index];
const x = vert.x, y = vert.y, z= vert.z;
// ... do stuff ...

into

const x = vertices[faces[tri]['a']].x; // dictionary access
const y = vertices[faces[tri]['a']].y; // dictionary access
const z = vertices[faces[tri]['a']].z; // dictionary access

1 vs 3 dictionary lookups.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.