[Lengthy] Box3D Technical Feedback + Questions For Erin #175
Unanswered
coreysaintpierre
asked this question in
Q&A
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Hi Erin,
We've been doing a fairly deep integration and validation pass with Box3D for a deterministic game simulation. I wanted to share a few observations and questions that came out of that work because they may be useful feedback for Box3D itself.
I'm intentionally keeping the application details abstract, but the relevant simulation shape is straightforward: one fast dynamic sphere interacting with a dense static field of convex/static objects, with an application-owned fixed logical tick and a requirement to identify exact collision boundaries while still allowing Box3D to own the actual contact graph and rigid-body response.
We're currently pinned to Box3D commit:
954cf879e717334eb96da9a5c255788fdc9f5f87We have not modified Box3D source.
What we've liked
The overall experience with Box3D has been very positive.
In particular:
The biggest takeaway from our investigation is that the engine has given us enough control to solve an unusual scheduling requirement through public APIs. We have not found a technical justification for maintaining a Box3D fork.
We can provide minimized standalone repros, measurements or source-level traces for anything below if useful.
1. Is a zero-time world step an intentional public contract?
This is our most important question.
We've experimentally found that:
b3World_Step(world, 0.0f, subStepCount)performs the broad-phase/contact update and narrow-phase refresh without positive-time solver integration.
That behavior is extremely useful for us.
Our application sometimes establishes an additional collision boundary inside one logical gameplay tick. At that boundary we need Box3D to recompute the native contact graph from the body's current transform without advancing simulation time.
A zero-dt step has given us exactly that operation.
Our question is:
Is
b3World_Step(world, 0, ...)intentionally supported as a contact-refresh operation that applications can rely on long-term?If yes, simply documenting that guarantee would be very useful.
If not, what public mechanism would you recommend for:
An explicit operation conceptually similar to:
b3World_RefreshContacts(world)would map very cleanly to our use case.
We would much rather depend on an intentional Box3D contract than on behavior that merely happens to follow from the current implementation.
2. Scoped contact-recycling override during refresh
We found a specific interaction between externally partitioned world steps and contact recycling.
A simplified case is:
The geometry can therefore be valid while the cached contact still reports non-touching.
We isolated this against the
b3_isFastpath and contact-recycle distance.The cleanest public workaround we've measured is:
This forces the boundary refresh to recompute existing cached contacts, after which normal recycling immediately resumes.
We've tested this fairly aggressively:
In the 1,000-sphere release test, the pulse recovered the intentionally stale target while producing zero observed non-target touching/begin/end/hit changes across the other 999 spheres plus five walls.
Total pulse cost on the target machine was:
22.473 us36.353 usFor comparison, one 120 Hz logical tick is approximately
8333 us.The public workaround is therefore both effective and inexpensive for our workload.
Still, conceptually we're changing persistent world state in order to request behavior for one refresh operation.
Would something like a scoped refresh option make sense?
Conceptually:
or an equivalent step option.
That would make the intent clearer and eliminate the need for applications to temporarily mutate and restore the global recycle distance.
3. Targeted refresh/invalidation of existing body contacts
We also looked at the per-body contact-recycling API.
Current documentation correctly notes that changing body contact-recycling state only affects subsequently created contacts; existing contacts keep the recycling state with which they were created.
That makes sense architecturally, but it means it cannot be used to invalidate an already-existing stale contact pair.
A potentially useful advanced API might be something along the lines of:
We do not need this for performance; the world-level refresh is already very inexpensive in our tests, but such an operation could express the application's intent more narrowly.
Character controllers, externally scheduled projectiles, deterministic gameplay simulations and similar systems might conceivably have related use cases.
4. Outer world-step partitioning versus solver substeps
One of the more interesting things we learned is that splitting one logical interval into multiple calls to
b3World_Stepis meaningfully different from keeping one outer step and increasing the Box3D substep count.The outer boundary changes more than integration resolution.
In our experiments it affected:
b3_isFast.For example, a body that would qualify as fast over the complete logical interval can become non-fast when that interval is externally partitioned into a much shorter first world step. That in turn can make an existing non-touching pair eligible for recycling.
This is internally consistent, but it is not necessarily an obvious consequence for users treating substeps and outer partitions as two ways of increasing temporal resolution.
A documentation note along the lines of:
might save advanced users some investigation.
5. Shape-pair differences in speculative contact behavior
Another useful thing we discovered was how materially different primitive pairs can be with respect to speculative contact construction.
For the cases relevant to us:
B3_SPECULATIVE_DISTANCE.That distinction had major consequences for externally scheduled collision boundaries.
We eventually mapped the hull/sphere transition experimentally at approximately the engine's 20 mm speculative distance and confirmed the source behavior.
The general documentation already explains speculative contacts well, but a more explicit indication of which primitive-pair manifold routines create positive-separation speculative contacts versus requiring actual overlap might be useful for people building precise collision scheduling or debugging contact timing.
We're not suggesting those semantics should change; only that their differences are significant enough to be worth surfacing.
6. Contact events during a contact-only refresh
A related contract question:
During the zero-time refresh described above, we observe begin/end contact state transitions without positive-time integration or a collision impulse. Native hit events then occur on the subsequent positive-time solver step when appropriate.
That distinction is actually useful to us:
Is that event behavior under
dt == 0intentional and stable?If zero-time stepping is intended to be supported, documenting its event semantics would be particularly helpful.
Summary
The questions we'd most value guidance on are:
b3World_Stepintentionally supported as a contact-refresh operation?We're treating any application-specific response-law differences as our responsibility rather than as a Box3D change request.
None of these questions are blocking us today; we have a public-API implementation that works extremely well in our measured workload. The main thing we want to avoid is building long-term architecture around an accidental zero-dt stepping contract if that isn't how you intend Box3D to be used.
If any of this is interesting, we're happy to provide small standalone repros rather than application code, including a minimal short-partition stale-contact case, mixed sphere/hull case, corner case, and the 1,000-static-body performance benchmark.
Thanks for building Box3D. We've spent enough time inside it now to appreciate how much difficult machinery it is quietly handling for us.
All reactions