Physics and Collisions¶
PixelRoot32 features a robust "Flat Solver" physics engine designed for stability and performance on ESP32. It supports gravity, stacking, bouncing, and friction for both box and circle shapes.
Physics Overview¶
The physics system uses a fixed timestep (1/60s) for deterministic simulation and separates velocity and position solving phases for stability.
1. Static Bodies (StaticActor)¶
- Role: World geometry (floors, walls, platforms).
- Behavior: Infinite mass. Never moves. Unaffected by gravity or collisions.
- Performance: Very cheap. Use for all non-moving colliders.
- Usage: Use the
pixelroot32::physics::StaticActorclass.
2. Kinematic Bodies (KinematicActor)¶
- Role: Players, moving platforms, elevators.
- Behavior: Movement is driven by your code (e.g., input), not by forces. Pushes dynamic bodies but isn't pushed by them.
- Usage: Inherit from
pixelroot32::physics::KinematicActor. - Note: Kinematic actors are properly detected by Rigid actors in the broadphase.
Movement & Collision State: Kinematic actors use moveAndSlide() to move while sliding against obstacles. After movement, you can check the collision state: - is_on_floor(): True if standing on a surface. - is_on_ceiling(): True if hit a ceiling. - is_on_wall(): True if hit a wall.
3. Rigid Bodies (RigidActor)¶
- Role: Crates, debris, balls, physics props.
- Behavior: Fully simulated. Responds to gravity, forces, and collisions. Can stack and bounce.
- Performance: Most expensive. Use sparingly on ESP32 (aim for <20).
- Usage: Use the
pixelroot32::physics::RigidActorclass. - Important: Position integration is handled automatically by the system. Do NOT manually update position in
update().
Using Physics Classes¶
Basic Setup (Rigid Body)¶
#include <physics/RigidActor.h>
#include <math/Scalar.h>
using pixelroot32::math::toScalar;
// Create a falling crate
auto crate = std::make_unique<pixelroot32::physics::RigidActor>(toScalar(100), toScalar(50), 16, 16);
crate->setRestitution(toScalar(0.5f)); // Bounciness
crate->setFriction(toScalar(0.2f)); // Friction
scene->addEntity(crate.get());
Continuous Collision Detection (CCD)¶
For fast-moving small objects (like bullets or high-speed balls), standard collision detection might miss collisions (tunneling). The engine now supports CCD for circular shapes.
CCD activates automatically when: velocity * dt > radius * CCD_THRESHOLD (Default threshold is 3.0)
To ensure CCD works: 1. Use CollisionShape::CIRCLE 2. Set the radius correctly using setRadius()
// Enable circle collision with CCD support
actor->setCollisionShape(pixelroot32::core::CollisionShape::CIRCLE);
actor->setRadius(toScalar(6)); // Critical for CCD
actor->setRestitution(toScalar(1.0f)); // Perfect bounce supported
Basic Setup (Static Body)¶
#include <physics/StaticActor.h>
#include <math/Scalar.h>
using pixelroot32::math::toScalar;
// Create a floor
auto floor = std::make_unique<pixelroot32::physics::StaticActor>(toScalar(0), toScalar(200), 240, 20);
scene->addEntity(floor.get());
Physics Properties¶
Velocity¶
For Rigid bodies, velocity is modified by forces and gravity. For Kinematic bodies, you set velocity to move them.
// Set velocity directly (individual scalars or Vector2)
actor->setVelocity(toScalar(100), toScalar(-50));
actor->setVelocity(Vector2(toScalar(0), toScalar(200)));
// Add to current velocity (Jump!)
actor->setVelocity(actor->getVelocityX(), toScalar(-200.0f));
// Or for RigidActor, use Impulses:
rigidActor->applyImpulse(Vector2(toScalar(0), toScalar(-200)));
Restitution (Bounciness)¶
Controls energy conservation in collisions.
0.0: No bounce (mud).1.0: Perfect bounce (superball).
Friction¶
Controls how much velocity is lost when sliding.
0.0: Ice.1.0: Sandpaper.
Collision Shapes¶
The engine supports two primitive shapes:
- AABB (Box): Fastest. Default. Good for tiles, crates, walls.
- Circle: Good for balls, wheels. Slightly more expensive. Supports CCD.
// Enable circle collision
actor->setCollisionShape(pixelroot32::core::CollisionShape::CIRCLE);
actor->setRadius(toScalar(8)); // Required for Circle shape
Simulation Pipeline¶
The physics step executes in a strict order to ensure stability (Flat Solver):
- Detect Collisions: Identify all overlapping pairs.
- Solve Velocity: Apply impulse-based collision response.
- Integrate Positions: Update positions (
p = p + v * dt). - Solve Penetration: Baumgarte stabilization to fix overlaps.
- Trigger Callbacks: Notify gameplay code (
onCollision).
This order ensures that callbacks see the final, resolved state of the world.
Performance Tips for ESP32¶
The "Flat Solver" is optimized, but physics is CPU-intensive. Follow these tips for 60 FPS on ESP32:
- Prefer StaticActors: Use
STATICfor anything that doesn't move. They are nearly free in the solver. - Limit Rigid Bodies: On ESP32-C3, keep simultaneous
RIGIDbodies under 20. - Tune the Grid: Set
SPATIAL_GRID_CELL_SIZEinEngineConfig.hto match your average actor size (e.g., 32px). - Use AABB: Box collisions are faster than Circle collisions. Use Circles only when necessary (e.g., for rolling or CCD).
- Iterations: Increase
VELOCITY_ITERATIONS(default 2) for better stacking, or decrease for performance.
Collision System (Layers & Masks)¶
(This section is unchanged - refer to previous documentation or API reference for layers/masks setup).
The collision system automatically detects when Actors overlap and triggers callbacks.
Collision Layers¶
Use bit flags to organize actors into groups:
// Define layers (typically in GameLayers.h)
namespace Layers {
constexpr uint16_t PLAYER = 0x0001;
constexpr uint16_t ENEMY = 0x0002;
constexpr uint16_t WALL = 0x0008;
}
Setting Up Collisions¶
Handling Collisions¶
Override onCollision to respond to events: