contact
FreeBodyEngine.core.physics.contact
#
Narrow-phase contact manifold generation for the rigid-body physics system.
core.collider's CollisionShape.collide() only answers "do these
overlap?" - a real physics solver needs to know how much (the
penetration depth, to push bodies apart by the right amount) and where
(one or two world-space contact points, so a resting box doesn't slowly
rotate/tip the way it would with only a single averaged contact) and
which way (the separating normal, to resolve velocity along the right
axis). The functions here produce that richer Manifold instead, reusing
the same shape classes and SAT building blocks core.collider already
has (_project_points, _convex_polygons_overlap and friends) rather
than duplicating shape geometry.
Manifold(normal, penetration, points=list())
dataclass
#
A narrow-phase collision result: normal points from shape A
toward shape B, penetration is how far they overlap along it, and
points holds 1-2 world-space contact points (2 for a stable
polygon-vs-polygon face contact, 1 for anything involving a circle,
which only ever touches at a single point).
circle_vs_circle(a, b)
#
Circle-circle is the simplest possible case: overlap iff the center distance is less than the summed radii, contact normal is along the line between centers, and the single contact point sits halfway into the overlap.
circle_vs_polygon(circle, polygon)
#
Circle-vs-(Rectangle|Polygon): finds the polygon face the circle's center is most separated from (or, if the center is inside the polygon, the least deeply contained face, i.e. the one the circle should be pushed out through). If that separation is >= the circle's radius on any face, they don't overlap; otherwise the contact normal is that face's normal and the single contact point is the circle's surface point along it.
generate_manifold(shape_a, shape_b)
#
Dispatches to the right narrow-phase function for shape_a/
shape_b's concrete types, normalizing the result so normal always
points from shape_a toward shape_b regardless of which order the
underlying function needed them in. circle_vs_polygon(circle,
polygon) always returns a normal pointing away from the polygon's
face, toward the circle - i.e. from its polygon argument to its
circle argument - so that needs negating exactly when the circle is
shape_a (polygon->circle is then shape_b->shape_a, the reverse of
what's wanted), and needs no change when the circle is shape_b
(polygon->circle is already shape_a->shape_b).
polygon_vs_polygon(shape_a, shape_b)
#
SAT collision between any two convex polygon-like shapes (Rectangle
and/or Polygon, in any combination - both expose the same
_get_corners()/_get_axes() interface) with reference/incident
face clipping for up to 2 stable contact points, following the
standard box2d-lite Collide() algorithm: find each polygon's best
(least-penetrating) separating face, pick the reference face as
whichever polygon is less penetrated (for numerical stability, with a
small bias toward keeping A as the reference to avoid flip-flopping
when separations are nearly equal), find the incident polygon's most
anti-parallel face, then clip that incident edge against the
reference face's two side planes and keep whatever's left that's
still behind the reference face.