Skip to content

CollisionSystem

Manages physics simulation and collision detection using the "Flat Solver" architecture.

Description

CollisionSystem implements the engine's physics pipeline. Unlike simple collision checkers, it provides a full rigid body simulation with gravity, spatial partitioning, and iterative resolution.

It supports three physics body types (configured via PhysicsActor):

  • Static (PhysicsBodyType::STATIC): Immovable world geometry (infinite mass).
  • Kinematic (PhysicsBodyType::KINEMATIC): Moved by game logic (platforms, characters).
  • Rigid (PhysicsBodyType::RIGID): Fully simulated physics objects (debris, bouncing props).

Namespace

namespace pixelroot32::physics {
    class CollisionSystem {
        // ...
    };
}

Inheritance

  • Base class: None (standalone class)
  • Used by: Scene (manages collision system instance)

Public Methods

void addEntity(Entity* e)

Adds an entity to the physics system.

Parameters:

  • e (pixelroot32::core::Entity*): Pointer to the entity to add

Notes:

  • Only Actor entities participate in physics.
  • Entities are automatically added when added to Scene.

void removeEntity(Entity* e)

Removes an entity from the physics system.

Parameters:

  • e (pixelroot32::core::Entity*): Pointer to the entity to remove

void update()

Executes the full physics step for the current frame.

Returns:

  • void

Notes:

  • Called automatically by Scene::update().
  • Executes the "Flat Solver" pipeline: Gravity -> Broadphase -> Narrowphase -> Velocity Resolution -> Integration -> Baumgarte.

How It Works (The Flat Solver Pipeline)

The physics step is executed in a sequential pipeline every frame using a fixed timestep (FIXED_DT = 1/60s):

  1. Apply Gravity: Add gravitational velocity to rigid bodies (v = v + g * dt).
  2. Detect Collisions: Identify all overlapping pairs using the Broadphase (Spatial Grid).
  3. Solve Velocity: Apply impulse-based collision response to resolve approaching velocities.
  4. Restitution is calculated as min(restitutionA, restitutionB) only if both actors have bounce=true.
  5. If either actor has bounce=false, restitution is 0.
  6. Impacts below VELOCITY_THRESHOLD (0.5) have 0 restitution to prevent micro-bouncing.
  7. Integrate Positions: Update positions based on velocity (p = p + v * dt).
  8. Solve Penetration: Apply Baumgarte stabilization (position correction) to fix remaining overlaps.
  9. Trigger Callbacks: Notify gameplay code via onCollision().

This order is critical: - Gravity is applied first to ensure correct trajectory. - Velocity is solved before position integration (prevents energy loss). - Position integration happens before penetration correction (allows proper separation). - Callbacks happen last so gameplay sees the final state.

Spatial Partitioning

To avoid $O(N^2)$ checks, the engine uses a Uniform Spatial Grid.

  • Cell Size: Configurable via SPATIAL_GRID_CELL_SIZE (default 32px).
  • Optimization: On ESP32, the grid uses Static Shared Buffers to reclaim ~100KB of DRAM, as the grid is cleared and rebuilt every frame.

Usage Example

#include "physics/CollisionSystem.h"
#include "core/Actor.h"
#include "physics/StaticActor.h"
#include "physics/RigidActor.h"

class GameScene : public pixelroot32::core::Scene {
public:
    void init() override {
        using pixelroot32::math::toScalar;

        // 1. Create a Static Floor
        auto floor = std::make_unique<pixelroot32::physics::StaticActor>(toScalar(0), toScalar(200), 240, 40);
        addEntity(floor.get());
        entities.push_back(std::move(floor));

        // 2. Create a Dynamic Box (RigidActor)
        auto box = std::make_unique<pixelroot32::physics::RigidActor>(toScalar(100), toScalar(50), 20, 20);
        box->setRestitution(toScalar(0.5f)); // Bounciness
        addEntity(box.get());
        entities.push_back(std::move(box));

        // Physics is handled automatically by Scene::update()
    }
};

Performance Considerations

  • Use StaticActors: They are significantly cheaper than dynamic bodies.
  • Limit Rigid Bodies: On ESP32-C3, aim for <20 simultaneous RigidActor objects for stable 60 FPS.
  • Adjust Cell Size: Match SPATIAL_GRID_CELL_SIZE to your average actor size for best broadphase performance.
  • Shape Costs:
  • AABB vs AABB: Cheapest.
  • Circle vs AABB: Slightly more expensive (sqrt operations).

Continuous Collision Detection (CCD)

Automatic CCD for fast-moving circles to prevent tunneling.

When It Activates

CCD is used only when necessary to save performance:

  1. Shape Check: Only for CIRCLE shapes.
  2. Speed Check: Activates when velocity * dt > radius * CCD_THRESHOLD.
// Default CCD_THRESHOLD = 3.0
// Example: Ball with radius 6px
// CCD activates when speed > 1080 px/s (6 * 3 / (1/60))

Algorithm

The system uses a swept test (swept Circle vs AABB) that samples positions along the movement vector to find the exact time of impact.

Configuration

The system is configurable via EngineConfig.h or build flags:

  • PHYSICS_MAX_PAIRS: Max collisions tracked (Default: 128).
  • VELOCITY_ITERATIONS: Impulse solver iterations per frame. Higher values improve stacking stability (Default: 2).
  • SPATIAL_GRID_CELL_SIZE: Grid cell size (Default: 32).
  • CCD_THRESHOLD: Threshold for activating Continuous Collision Detection (Default: 3.0).
  • BIAS: Baumgarte stabilization factor (Default: 0.2).
  • SLOP: Penetration allowance (Default: 0.02).

See Also