Skip to content

Replace O(n^2) broad phase with a spatial hash; fix box collision - #2

Open
erichanwang wants to merge 1 commit into
masterfrom
physics-broadphase-boxcollision
Open

Replace O(n^2) broad phase with a spatial hash; fix box collision#2
erichanwang wants to merge 1 commit into
masterfrom
physics-broadphase-boxcollision

Conversation

@erichanwang

Copy link
Copy Markdown
Owner

Two changes to the physics engine.

Broad phase: the all-pairs loop is replaced with a uniform spatial hash
(src/physics/BroadPhase.h). Every body is dropped into each grid cell its
bounding sphere touches; candidates come from bodies sharing a cell, which
can only ever produce a superset of the pairs that actually overlap.
testBroadPhaseCoversAllCollidingPairs asserts that directly against a
400-body exhaustive scan, since a broad phase that is fast but drops a pair
is a tunnelling bug, not an optimization.

bench/broadphase_bench.cpp measures the hash against the old loop at 100,
1000, and 10000 bodies and bisects for the real crossover rather than
quoting a single number: the loop wins below roughly 340 bodies (pure
arithmetic, no bookkeeping), the hash wins above it, 2.55x at 1000 bodies
and 13.48x at 10000 on this machine.

Box collision: RigidBody now has a Box shape (halfExtents) alongside the
existing Sphere. PhysicsEngine resolves sphere-box (closest point on the
box, so contact normal is the actual face normal rather than a
center-to-center vector) and box-box (least-overlap axis) with proper
contact normals, so a Block settles on a face instead of its bounding
sphere. Ground contact also now uses the box's bottom face.

Found and fixed a real bug along the way: resolveBoxBox built its contact
normal inverted on all three axes, so a box dropped onto a static box fell
straight through it instead of resting. Also gave box-box contacts the same
resting-velocity threshold resolveGround already used, since gravity
re-enters every frame and any restitution at rest just buzzes forever.

Test plan

  • tests/test_math_physics.cpp: all 20 assertions pass, including
    ground settling, elastic-collision momentum conservation to 1e-9,
    box-on-ground, box-on-box, sphere-on-box face normal, and the broad
    phase pair-coverage property
  • Blocks, Parachutes, ControlDemo all build clean under
    -Wall -Wextra -Werror
  • bench/broadphase_bench.cpp run standalone for the timing/crossover
    numbers above

Two things, one of which was a real bug rather than an optimization.

The broad phase was an all-pairs loop. It is now a uniform spatial hash:
every body is dropped into each grid cell its bounding sphere touches, and
candidates come from bodies sharing a cell. That can only ever produce a
superset of the genuinely overlapping pairs, which is the property that
matters - a broad phase that is fast and drops a pair is a tunnelling bug,
so testBroadPhaseCoversAllCollidingPairs asserts every pair the exhaustive
loop finds colliding is present among the candidates.

Measured on this machine, hash versus the loop: 2.55x at 1000 bodies and
13.48x at 10000. The loop is genuinely faster below roughly 340 bodies,
where the hashing overhead exceeds the pairs it saves, and the benchmark
bisects for that crossover rather than quoting a single flattering number.

The bug: resolveBoxBox built its contact normal inverted on all three axes.
applyContact expects the normal to point a -> b, matching what
resolveSphereSphere produces, but the box path emitted -1 where a sat below
b. That is not a missing bounce. The positional correction then pushed the
two boxes into each other, and since a static body has zero inverse mass,
the whole correction landed on the falling one and drove it downward every
frame. The separating-velocity guard compounded it by reading an
approaching pair as already parting, so no impulse ever fired. A box
dropped onto a static box fell straight through it, ending 530 units below
where it should have come to rest.

Box-box contacts also needed the resting threshold resolveGround already
applies: gravity re-enters every frame, so any restitution at a resting
contact leaves a body buzzing forever instead of settling.

18 tests pass; all six targets build clean under -Wall -Wextra -Werror.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant