Add BVH acceleration for ray picking#614
Draft
oursland wants to merge 4 commits into
Draft
Conversation
Per-shape BVH (Bounding Volume Hierarchy) cache that accelerates
SoShape::rayPick() from O(N) to O(log N) triangle intersection tests.
On the first pick for each shape, triangle data is collected during the
brute-force generatePrimitives() pass via invokeTriangleCallbacks().
A BVH is then built using SAH-binned construction with a flat
depth-first node layout. All subsequent picks use the cached BVH.
Shapes with <64 triangles remain brute-force. The BVH is invalidated
in SoShape::notify() when geometry changes.
This approach works for all SoShape subclasses — including FreeCAD's
custom SoBrepFaceSet, SoBrepEdgeSet, etc. — because it collects from
the generatePrimitives callback path rather than depending on
SoPrimitiveVertexCache (which is only populated for shapes using
Coin's standard GL render path).
Tracy profiling results (FreeCAD, macOS ARM, complex CAD model):
Before BVH:
Typical pick: 23-35 ms, worst case 417 ms
SoShape::rayPick: 231,816 calls/session, all brute-force
After BVH:
Typical pick: 10-21 ms, worst case 48 ms (excl. first-pick build)
BVH rayPick: 87 calls, 15.8 us mean, 1.4 ms total session
generatePrimitives: 5,190 calls (first-pick only, then cached)
When a SoSeparator has more than 8 children, rayPick now checks each
child SoSeparator's cached bounding box against the ray before
traversing it. Children whose bbox doesn't intersect the ray are
skipped entirely. This is safe because SoSeparator children are
state-isolated (push/pop), so skipping them doesn't affect siblings.
Non-separator children (SoTransform, SoMaterial, SoShape, etc.) are
always traversed to preserve state ordering.
Tracy profiling results (FreeCAD, macOS ARM, complex CAD model):
Before child culling:
SoShape::rayPick: 113,871 calls per session
After child culling:
SoShape::rayPick: 47,691 calls per session (58% reduction)
Add COIN_USE_TRACY cmake option for Tracy frame profiler support. When enabled, adds profiling zones to SoHandleEventAction::doPick, SoHandleEventAction::getPickedPointList, SoShape::rayPick, and SoBVHCache::rayPick. Uses -undefined dynamic_lookup on macOS so Coin shares the host application's TracyClient instance. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Contributor
Author
|
I've converted this back to draft. Experimentation has shown that this approach of small BVHs is a major improvement, but overall the whole scene must be inserted into a single global BVH to get the pick performance expected. I'm working on that change now. |
Three-level BVH hierarchy for ray picking:
1. Scene-level BVH (SoSceneBVH): flat BVH over ALL shapes' world-space
bounding boxes. Built lazily on first pick from shapes that register
during traversal. Subsequent picks query the BVH to produce a
candidate set — shapes not in the set skip immediately in rayPick().
Reduces shapes visited per pick from ~5000 to ~2000.
2. Per-separator child BVH (SoChildBVH): BVH over direct children's
bounding boxes within each SoSeparator. Uses affectsState() to
ensure state nodes (transforms, materials) are always traversed.
Threshold lowered to 4 children.
3. Per-shape triangle BVH (SoBVHCache): existing per-shape BVH over
triangles, threshold lowered from 64 to 8 triangles.
Tracy profiling results (FreeCAD, macOS ARM, complex Assembly):
Before all BVH work:
doPick median: 28.5 ms, mean: 28.8 ms
After scene-level BVH:
doPick median: 11.7 ms, mean: 14.8 ms, p95: 17.5 ms
Scene BVH build: 0.3 ms (once)
Scene BVH query: 14 us/pick
Shapes per pick: 2,017 (down from 5,268)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Per-shape BVH (Bounding Volume Hierarchy) cache that accelerates SoShape::rayPick() from O(N) to O(log N) triangle intersection tests.
On the first pick for each shape, triangle data is collected during the brute-force generatePrimitives() pass via invokeTriangleCallbacks(). A BVH is then built using SAH-binned construction with a flat depth-first node layout. All subsequent picks use the cached BVH. Shapes with <64 triangles remain brute-force. The BVH is invalidated in SoShape::notify() when geometry changes.
This approach works for all SoShape subclasses — including FreeCAD's custom SoBrepFaceSet, SoBrepEdgeSet, etc. — because it collects from the generatePrimitives callback path rather than depending on SoPrimitiveVertexCache (which is only populated for shapes using Coin's standard GL render path).
Tracy profiling results (FreeCAD, macOS ARM, complex CAD model):
Before BVH:
Typical pick: 23-35 ms, worst case 417 ms
SoShape::rayPick: 231,816 calls/session, all brute-force
After BVH:
Typical pick: 10-21 ms, worst case 48 ms (excl. first-pick build)
BVH rayPick: 87 calls, 15.8 us mean, 1.4 ms total session
generatePrimitives: 5,190 calls (first-pick only, then cached)