Documentation
¶
Overview ¶
Package epa implements the Expanding Polytope Algorithm for computing penetration depth.
EPA is run after GJK detects a collision to determine:
- Penetration depth (how far shapes overlap)
- Contact normal (direction to separate shapes)
- Contact points (where shapes touch)
The algorithm expands a polytope (starting from GJK's final simplex) toward the origin in the Minkowski difference space, finding the closest face which gives us the Minimum Translation Vector (MTV) to separate the shapes.
For detailed algorithm explanation with pseudocode and visual examples, see: ALGORITHMS.md - "EPA Algorithm" section
References:
- Van den Bergen: "Proximity Queries and Penetration Depth Computation on 3D Game Objects" (2001)
Index ¶
- Constants
- func EPA(a, b *actor.RigidBody, simplex *gjk.Simplex) (constraint.ContactConstraint, error)
- func GenerateManifold(bodyA, bodyB *actor.RigidBody, normal mgl64.Vec3, depth float64) []constraint.ContactPoint
- type Edge
- type EdgeEntry
- type Face
- type ManifoldBuilder
- type PolytopeBuilder
- func (b *PolytopeBuilder) AddPointAndRebuildFaces(support mgl64.Vec3, closestIndex int) error
- func (b *PolytopeBuilder) BuildInitialFaces(simplex *gjk.Simplex) error
- func (b *PolytopeBuilder) FindClosestFaceIndex() int
- func (b *PolytopeBuilder) GetClosestFace() *Face
- func (b *PolytopeBuilder) Reset()
Constants ¶
const ( // EPAMaxIterations limits polytope expansion to prevent infinite loops. // Typical convergence: 5-15 iterations for simple shapes. // If this limit is reached, EPA returns an error. EPAMaxIterations = 32 // EPAConvergenceTolerance defines when EPA has converged. // If the distance to a new support point improves by less than this threshold, // we've found the closest face to the origin. // Lower values = more precision but slower convergence. EPAConvergenceTolerance = 0.001 // EPAMinFaceDistance is the minimum face distance before we skip it. // Faces very close to or behind the origin are likely degenerate. EPAMinFaceDistance = 0.0001 // NormalSnapThreshold is used to clamp nearly-zero normal components to exactly zero. // This helps with numerical stability and axis-aligned collisions. NormalSnapThreshold = 1e-8 // DegeneratePenetrationEstimate is a fallback penetration depth for degenerate cases // where we have insufficient simplex points to compute accurate depth. DegeneratePenetrationEstimate = 0.01 )
Variables ¶
This section is empty.
Functions ¶
func EPA ¶
func EPA(a, b *actor.RigidBody, simplex *gjk.Simplex) (constraint.ContactConstraint, error)
EPA computes penetration depth and contact information for overlapping convex shapes.
Algorithm overview:
- Start with simplex from GJK (tetrahedron containing origin)
- Build initial polytope faces from simplex
- Find face closest to origin
- Get support point in face normal direction
- If converged (new point doesn't improve distance) → done
- Otherwise, expand polytope by adding support point
- Repeat from step 3
Parameters:
- a, b: The two colliding rigid bodies
- simplex: Final simplex from GJK (typically 4 points forming tetrahedron)
Returns:
- ContactConstraint: Contains contact normal, penetration depth, contact points
- error: Non-nil if EPA failed to converge or encountered degenerate case
The contact normal points from body A toward body B (separation direction). Penetration depth is always positive (how far to move B away from A).
func GenerateManifold ¶
func GenerateManifold(bodyA, bodyB *actor.RigidBody, normal mgl64.Vec3, depth float64) []constraint.ContactPoint
GenerateManifold is the main entry point
Types ¶
type Edge ¶
Edge represents an edge between two vertices. Kept for backward compatibility, but no longer used with PolytopeBuilder. PolytopeBuilder uses EdgeEntry instead with occurrence counting.
type EdgeEntry ¶
type EdgeEntry struct {
A, B mgl64.Vec3 // Edge vertices (normalized: A < B)
Count int // Occurrence count (1 = boundary edge, 2+ = internal edge)
}
EdgeEntry represents an edge with occurrence counting for boundary detection. An edge is a boundary edge if it appears exactly once (count == 1). Edges are normalized so A < B lexicographically for consistent deduplication.
type Face ¶
type Face struct {
Points [3]mgl64.Vec3 // The 3 vertices of the triangle
Normal mgl64.Vec3 // Outward-pointing normal
Distance float64 // Distance from origin to the face plane
}
Face represents a triangular face of the polytope in EPA. Each face has 3 vertices, an outward-pointing normal, and distance to origin.
This struct is now used by PolytopeBuilder for zero-allocation EPA. The old pointer-based approach with facePool has been replaced.
type ManifoldBuilder ¶
type ManifoldBuilder struct {
// contains filtered or unexported fields
}
ManifoldBuilder contains all working buffers with fixed-size arrays to avoid allocations.
func (*ManifoldBuilder) Generate ¶
func (b *ManifoldBuilder) Generate(bodyA, bodyB *actor.RigidBody, normal mgl64.Vec3, depth float64) []constraint.ContactPoint
Generate generates the manifold using internal buffers
func (*ManifoldBuilder) Reset ¶
func (b *ManifoldBuilder) Reset()
Reset prepares the builder for a new use
type PolytopeBuilder ¶
type PolytopeBuilder struct {
// contains filtered or unexported fields
}
PolytopeBuilder manages polytope expansion with dynamic buffers and initial capacity.
func (*PolytopeBuilder) AddPointAndRebuildFaces ¶
func (b *PolytopeBuilder) AddPointAndRebuildFaces(support mgl64.Vec3, closestIndex int) error
AddPointAndRebuildFaces expands the polytope by adding a support point. This is the main EPA expansion step that:
- Finds visible faces from the support point
- Identifies boundary edges of the visible region
- Removes visible faces
- Creates new faces connecting boundary edges to the support point
All operations use fixed buffers for zero allocations.
func (*PolytopeBuilder) BuildInitialFaces ¶
func (b *PolytopeBuilder) BuildInitialFaces(simplex *gjk.Simplex) error
BuildInitialFaces creates the initial polytope from a GJK tetrahedron simplex. Creates 4 triangular faces from the 4 simplex points, filtering degenerate faces.
Returns error if simplex is invalid (count != 4).
func (*PolytopeBuilder) FindClosestFaceIndex ¶
func (b *PolytopeBuilder) FindClosestFaceIndex() int
FindClosestFaceIndex returns the index of the face closest to the origin. Returns -1 if no faces exist.
func (*PolytopeBuilder) GetClosestFace ¶
func (b *PolytopeBuilder) GetClosestFace() *Face
GetClosestFace returns a pointer to the closest face for EPA result. Returns nil if no faces exist.
func (*PolytopeBuilder) Reset ¶
func (b *PolytopeBuilder) Reset()
Reset prepares the builder for reuse by clearing all slices. This allows the builder to be reused from the pool without reallocation.