Skip to content

Merge shape pair contacts into body-pair contacts - #64

Open
ZehMatt wants to merge 3 commits into
erincatto:mainfrom
ZehMatt:contact-reduction
Open

Merge shape pair contacts into body-pair contacts#64
ZehMatt wants to merge 3 commits into
erincatto:mainfrom
ZehMatt:contact-reduction

Conversation

@ZehMatt

@ZehMatt ZehMatt commented Jul 10, 2026

Copy link
Copy Markdown

Source Engine props are convex decompositions, the metal_wheel1 model from the issue is 37 hulls on one body. When two of these stack, every touching hull pair makes its own manifold, so a single body pair ends up with hundreds of near coplanar contact points. The solver has to push all of them through TGS soft and they fight each other: the stack never manages to sleep, restitution can add energy (a slab bounces higher than it was dropped from), and solve dominates the step.

This PR changes the contact to represent a body pair. Broadphase still works on shape pairs, but overlapping pairs between two bodies merge into one contact as sub contacts. The narrowphase collects a local manifold from every sub and
clusters them by normal into a few manifolds with up to 4 points each, warm started by feature id. This is the same clustering the mesh contact path already does per triangle, generalized so a sub collision can be a hull pair or a mesh. Contacts with a single shape pair take exactly the same path as before, the SIMD solver path and the existing determinism hashes are untouched, so scenes without compounds are bit identical.

Benchmarks

benchmark main this PR delta
trees100 144.369 125.910 -12.8%
trees50 220.515 171.001 -22.5%
trees25 491.339 440.437 -10.4%
joint_grid 1114.30 1065.69 -4.4%
junkyard 10599.4 10699.1 +0.9%
large_pyramid 1203.30 1197.58 -0.5%
many_pyramids 1455.76 1465.18 +0.6%
rain 1359.85 1357.94 -0.1%
washer 13900.7 14347.5 +3.2%
large_world 21.9654 20.8164 -5.2%
wheel_stack - 46.772 -

The trees and joint_grid wins come from recycling: one cached transform check per body pair instead of one per shape pair, so a 22 hull tree does 1 check instead of 22. Washer is the one real cost, debris against the 36 hull drum merges onto the scalar solver path and loses the SIMD lanes. The -1, +1 are tolerance, I re-ran it a couple times and it flip-flops.

The new wheel_stack benchmark (30 stacked wheels, 1110 hulls) has no counterpart on main to compare against, on this PR it runs 0.095ms per step with collide at 0.006ms, and the whole stack is 30 contacts, one per body pair. The stack settles and goes to sleep.

There is a new determinism test that hashes the wheel stack across worker counts, baked for float and double builds. Behavior tests cover stacking, restitution, mesh contacts, high mass ratio, rolling, events, curved contacts and sliding friction, plus a benchmark and a sample built from the actual .phy hull data.

Something to note: hit events on a merged contact report the first sub contacts shapes, and pre solve events are not delivered for multi sub contacts (mesh contacts already behave this way). The snapshot image version was bumped, old
recordings dont replay since the sim results changed anyway.

Closes #57

@blueshank-gh

Copy link
Copy Markdown

I've verified this in a real environment, and the contact points is extremely reduced, compared to the maximum of 255.
image

@ZehMatt
ZehMatt marked this pull request as draft July 11, 2026 03:23
@ZehMatt ZehMatt changed the title Contact reduction for multi-convex body pairs Merge shape pair contacts into body-pair contacts Jul 11, 2026
@ZehMatt
ZehMatt force-pushed the contact-reduction branch from d1774b5 to 76cbfb2 Compare July 11, 2026 06:42
@ZehMatt
ZehMatt marked this pull request as ready for review July 11, 2026 06:42
@ZehMatt
ZehMatt force-pushed the contact-reduction branch from 76cbfb2 to 4d3bb5b Compare July 11, 2026 06:48
@erincatto

Copy link
Copy Markdown
Owner

I wrote some simple code to wrap these wheels with a single hull and the stack goes to sleep. In this case the asset should be optimized for the physics engine.

constexpr int N = 512;
b3Vec3 buffer[N];
int bufferCount = 0;
for ( int h = 0; h < s_metalWheel1HullCount; ++h )
{
	const WheelHullSpan& span = s_metalWheel1Hulls[h];
	m_hulls[h] = b3CreateHull( &s_metalWheel1Verts[span.offset], span.count, span.count );

	const b3Vec3* points = b3GetHullPoints( m_hulls[h] );
	for ( int j = 0; j < m_hulls[h]->vertexCount && bufferCount < N; ++j )
	{
		buffer[bufferCount] = points[j];
		++bufferCount;
	}
}

b3HullData* wheelHull = b3CreateHull( buffer, bufferCount, bufferCount );

The stack goes to sleep reasonable quickly.

image

@erincatto erincatto closed this Jul 19, 2026
@erincatto

erincatto commented Jul 20, 2026

Copy link
Copy Markdown
Owner

Sorry, didn't mean to close this. Meant to close the issue. The PR is still interesting.

@erincatto erincatto reopened this Jul 20, 2026
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.

Multi-convex bodies never sleep when stacked, solver-bound by redundant contacts

3 participants