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.
Casting 500 rays against an 80,000 polygon model at 60fps!
Tools
Games
Path Tracing
External Projects
Using pre-made functions
import * as THREE from 'three';
import {
computeBoundsTree, disposeBoundsTree,
computeBatchedBoundsTree, disposeBatchedBoundsTree, acceleratedRaycast,
} from 'three-mesh-bvh';
// Add the extension functions
THREE.BufferGeometry.prototype.computeBoundsTree = computeBoundsTree;
THREE.BufferGeometry.prototype.disposeBoundsTree = disposeBoundsTree;
THREE.Mesh.prototype.raycast = acceleratedRaycast;
THREE.BatchedMesh.prototype.computeBoundsTree = computeBatchedBoundsTree;
THREE.BatchedMesh.prototype.disposeBoundsTree = disposeBatchedBoundsTree;
THREE.BatchedMesh.prototype.raycast = acceleratedRaycast;
// Generate geometry and associated BVH
const geom = new THREE.TorusKnotGeometry( 10, 3, 400, 100 );
const mesh = new THREE.Mesh( geom, material );
geom.computeBoundsTree();
// Or generate BatchedMesh and associated BVHs
const batchedMesh = new THREE.BatchedMesh( ... );
const geomId = batchedMesh.addGeometry( geom );
const instId = batchedMesh.addGeometry( geom );
// Generate bounds tree for sub geometry
batchedMesh.computeBoundsTree( geomId );
Or manually building the BVH
import * as THREE from 'three';
import { MeshBVH, acceleratedRaycast } from 'three-mesh-bvh';
// 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 ] );
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 );
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();
const geometry = new KnotGeometry( 1, 0.5, 40, 10 );
const bvh = new MeshBVH( geometry );
const serialized = MeshBVH.serialize( bvh );
// ...
const deserializedBVH = MeshBVH.deserialize( serialized, geometry );
geometry.boundsTree = deserializedBVH;
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 KnotGeometry( 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 KnotGeometry( 1, 0.5, 40, 10 );
const worker = new ParallelMeshBVHWorker();
worker.generate( geometry ).then( bvh => {
geometry.boundsTree = bvh;
} );
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.
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.
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.
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.
Indicates the shape did not intersect the given bounding box.
Indicates the shape did intersect the given bounding box.
Indicate the shape entirely contains the given bounding box.
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( 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( 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( 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,
// If given, the MeshBVH will be computed for the given range on the geometry.
// If not specified, geometry.drawRange is used.
range: { start: number, count: number }
}
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( 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( ray : Ray, side : FrontSide | BackSide | DoubleSide = FrontSide, near : Number = 0, far : Number = Infinity ) : Array<RaycastHit>
raycast( ray : Ray, material? : Array<Material> | Material, near : Number = 0, far : Number = Infinity ) : 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( ray : Ray, side : FrontSide | BackSide | DoubleSide = FrontSide, near : Number = 0, far : Number = Infinity ) : RaycastHit
raycastFirst( ray : Ray, material : Array<Material> | Material, near : Number = 0, far : Number = Infinity ) : 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( sphere : Sphere ) : Boolean
Returns whether or not the mesh intersects the given sphere.
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( 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(
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(
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(
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( 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( target : Box3 ) : Box3
Get the bounding box of the geometry computed from the root node bounds of the BVH. Significantly faster than BufferGeometry.computeBoundingBox
.
roots : Array<ArrayBuffer>
index : TypedArray
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 : Number
The depth to traverse and visualize the tree to.
color = 0x00FF88 : THREE.Color
The color to render the bounding volume with.
opacity = 0.3 : Number
The opacity to render the bounding volume with.
displayParents = false : Boolean
Whether or not to display the parent bounds.
displayEdges = true : Boolean
If true displays the bounds as edges other displays the bounds as solid meshes.
objectIndex = 0 : Number
When using an InstancedMesh
or a BatchedMesh
this refers to the item index to use for the BVH and / or matrix transformation to use.
edgeMaterial : LineBasicMaterial
The material to use when rendering edges.
meshMaterial : MeshBasicMaterial
The material to use when rendering as a sold meshes.
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() : void
Updates the display of the bounds tree in the case that the bounds tree has changed or the depth parameter has changed.
dispose() : void
Disposes of the material used.
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 : 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( other : Triangle, target? : Line3 ) : Boolean;
Returns whether the triangles intersect. target
is set to the line segment representing the intersection.
intersectsSphere( sphere : Sphere ) : Boolean
Returns whether the triangle intersects the given sphere.
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( point : Vector3 ) : Number
Returns the distance to the provided point.
distanceToTriangle( tri : Triangle ) : Number
Returns the distance to the provided triangle.
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 : Matrix4
Matrix transformation applied to the box.
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( min : Vector3, max : Vector3, matrix : Matrix4 ) : this
Sets the oriented box parameters.
intersectsBox( box : Box3 ) : Boolean
Returns true if intersecting with the provided box.
intersectsTriangle( tri : Triangle ) : Boolean
Returns true if intersecting with the provided triangle.
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( point : Vector3 ) : Number
Returns the distance to the provided point.
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.
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( options? : Object ) : void
A pre-made BufferGeometry extension function that builds a new BVH, assigns it to boundsTree
for BufferGeometry, and applies the new index buffer to the geometry. Comparable to computeBoundingBox
and computeBoundingSphere
.
THREE.BufferGeometry.prototype.computeBoundsTree = computeBoundsTree;
disposeBoundsTree() : void
A BufferGeometry extension function that disposes of the BVH.
THREE.BufferGeometry.prototype.disposeBoundsTree = disposeBoundsTree;
computeBatchedBoundsTree( index = - 1 : Number, options? : Object ) : void
Equivalent of computeBoundsTree
for BatchedMesh. Calling this generates a BatchedMesh.boundsTrees
array if it doesn't exist and assigns the newly generated BVHs. If index
is -1 then BVHs for all available geometry are generated. Otherwise only the BVH for the geometry at the given index is generated.
THREE.BatchedMesh.prototype.computeBoundsTree = computeBatchedBoundsTree;
disposeBatchedBoundsTree( index = - 1 : Number, options? : Object ) : void
Equivalent of disposeBoundsTree
for BatchedMesh. Calling this sets entries in BatchedMesh.boundsTrees
array to null. If index
is -1 then BVHs are disposed. Otherwise only the BVH for the geometry at the given index is disposed.
THREE.BatchedMesh.prototype.disposeBoundsTree = disposeBatchedBoundsTree;
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;
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 = true : Boolean
If true then groups are used to support an array of materials on the mesh.
attributes = [ 'position', 'normal', 'tangent', 'uv', 'uv2' ] : Array<String>
The set of attributes to copy onto the static geometry.
applyWorldTransforms = true : Boolean
Whether to transform the vertices of the geometry by the world transforms of each mesh when generating.
constructor( object : Array<Object3D> )
Takes an array of object hierarchies to bake into a single static geometry.
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( 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.
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 : Boolean;
Flag indicating whether or not a BVH is already being generated in the worker.
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() : void;
Terminates the worker.
estimateMemoryInBytes( bvh : MeshBVH ) : Number
Roughly estimates the amount of memory in bytes a BVH is using.
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.
Functions exported individually not part of a class.
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 indicesmaterialIndex
: Face material index or 0 if not available.normal
: Face normaluv
: UV coordinates.
This function can be used after a call to closestPointPoint or closestPointToGeometry to retrieve more detailed result information.
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.
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 : 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( 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.
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( bvh : MeshBVH ) : void
Updates the object and associated textures with data from the provided BVH.
dispose() : void
Dispose of the associated textures.
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 : 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.
- 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.
To run the examples locally:
- Run
npm start
- Then visit
localhost:5173/<demo-name>.html
Where <demo-name>
is the name of the HTML file from example
folder.
...and more!
three-mesh-bvh's People
Forkers
customorthopaedics mqp jsdelivrbot thomaslow chpatrick lp249839965 ig-88-2 mohitsahunitrr ademola-lou vistone robertlong mf-corey i5anoff github4jiawen cxz leapar thibka lxq vatro arpu sermonis zjlmmm-github mikpim01 felixmariotto otoshimtoshi mystlive dbuck crabmusket gonnavis zhuzhuzhenbang lxlyh yurykonvpalto shafiahmed carstenschwede unitycoder jchabin xtorlab gassepouille grideyes-2010 ketourneau joaquin6 ryanaltair nadineab onuratakan hyprstereo kirill-osipov yichunsung feifeivv yomboprime blueyy amaranthos octopoulos mcculloughrt longde123 minitoine peterzhousz gearshiftstudios threepointsquare fluqz jiaozhu77988 wuzhenzhen1314 harrycollin jaterroso ruthysheffi trybetter stonerao wellsliao sivhma robksawyer jerryzfc pleasedwork outsourcingwarehouse vin-ni w0lramd n8python jeonghopark blaxar outsidev xtfntg cpind vinkovsky denzel1991 lishanlei stopyransky xudamin cuulee vegarringdal frading dennissmolek joewhatkins oneorthomedical mikkoh kumatani-megumi gkucan repalash ajayns xiaojie1992 dohard-ma godeye barusxxxthree-mesh-bvh's Issues
Add option for limiting tree depth
This can be based on "max tree depth" or relative SAH value (only split if there's sufficient empty space between triangles).
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.
Rename and document `maxLeafNodes` option
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);
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
andright
primitives id collections before calling the recursive function for their two children (huge memory consumption), you can mutateorigTris
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 finalorigTris
-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.
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.
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:
MeshBVHNode
s. Of course, we need nodes in our tree.Sphere
s,Box3
s, and their underlyingVector3
s. It seems like we mostly need to store different bounding boxes and spheres for all of our nodes, so this is hard to avoid.- 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.
- 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.
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.
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:
- Don't construct BVHs for geometries with groups. Perhaps we could offer functionality to "ungroup" grouped geometries.
- Add a layer of indirection like our
tris
which we retain in memory permanently, so that we leave the index alone and store ranges oftris
in our tree nodes. - 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.
Update to THREE r96
The intersectTri
function seems to have changed so it needs to be recopied.
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.
Provide other types of lookups
Provide lookups for sphere, capsule, box casting rather than just ray casting against the BVH.
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
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.
Consider faster approach to early out raycastFirst after first hit
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 isz
then you should only have to check if thez
component of the split plane is greater than or less than thez
component of the intersection (depending on the direction of the ray). That should also let you get rid of the call todistanceToSquared
below.
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.
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.
Add a first hit option to speed things up further
Add an option to get the first hit out the bounds tree so the rest of the bounds and triangles don't have to be iterated over.
Right now, all candidate indices are a collected and returned.
Add typescript `.d.ts` bindings
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
Make the MeshBVHVisualizer respect multiple roots
#66 introduced multiple roots to accommodate groups. The bvh visualizer should be updated to display all.
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.
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.
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 toMeshBVHLib
(#64)
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.
Investigate assembly script compilation
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.
Add distance calculation functions
So distance to mesh can be calculated.
- Triangle to Triangle: https://github.com/juj/MathGeoLib/blob/master/src/Geometry/Triangle.cpp#L1751
- Sphere to Triangle: https://github.com/juj/MathGeoLib/blob/master/src/Geometry/Triangle.cpp#L889
- Box to Triangle
- OBB to AABB
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.
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.
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
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.
Clean up existing split strategies and move them into option object
Improve Triangle shape intersection performance
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.
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
}
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
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?
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:
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.
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.
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 );
Enable benchmarks in browser
Upgrade to Babel 7
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
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
optionAn 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:
- The geometry has no index. BVH generation should create one and then you can assign it.
- 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.
- 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 theInterleavedBufferAttribute
case, which would mean other code has to change, as well).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
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
Remove box diagonals
Add option for casting against leaf nodes only
This would skip triangle casts for an efficiency boost and allow for a shallower tree while still support tight raycast bounds.
Recommend Projects
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
Vue.js
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
TensorFlow
An Open Source Machine Learning Framework for Everyone
Django
The Web framework for perfectionists with deadlines.
Laravel
A PHP framework for web artisans
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.
Visualization
Some thing interesting about visualization, use data art
Game
Some thing interesting about game, make everyone happy.
Recommend Org
We are working to build community through open source technology. NB: members must have two-factor auth.
Microsoft
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba
Alibaba Open Source for everyone
D3
Data-Driven Documents codes.
Tencent
China tencent open source team.