Skip to content

Physics Module

The Physics module integrates NVIDIA PhysX to handle rigid body dynamics and collision resolution. It synchronizes simulation results with the ECS layer, allowing entities to interact with physical forces and geometry.

Purpose

The Physics module:

  • Manages the PhysX SDK and scene lifecycle.
  • Handles static and dynamic rigid body dynamics.
  • Detects collisions using the GJK algorithm for mesh-based geometry.
  • Synchronizes transforms between entities and PhysX actors.
  • Provides physics components for the ECS.

Key Components

PhysicsSystem

ECS system that manages the PhysX physics simulation.

Key Methods:

  • start() - Initializes PhysX foundation, physics SDK, scene, and dispatcher
  • fixed_update(double fixed_dt) - Steps the physics simulation at fixed timestep
  • pre_update(double dt) - Synchronizes TransformComponent changes to PhysX actors
  • update(double dt) - Applies PhysX simulation results back to TransformComponent

Configuration:

  • Takes PhysicsSystemConfig with fields: backend, gravity, pvd_host, pvd_port, pvd_timeout
  • Uses global static PhysX objects (gFoundation, gPhysics, gScene, gDispatcher)
  • Simulation can be toggled on/off with F4 key

RigidBodyComponent

Component that adds physics simulation to an entity:

cpp
enum RigidType { Static = 0, Dynamic = 1 };

class RigidBodyComponent : public Component<RigidBodyComponent> {
public:
    RigidBodyComponent(COMPONENT_INIT_PARAMS,
                       RigidType rigid_type = RigidType::Dynamic,
                       float gravity = 0.5f, float velocity = 2.0f,
                       float acceleration = 2.0f, float drag = 0.0f,
                       float mass = 1.0f);

    void start(physx::PxPhysics* physics, physx::PxScene* scene);
    void update(float dt);

private:
    float velocity;
    float gravity;
    float drag;
    float mass;
    float acceleration;
    RigidType m_rigid_type;
};

Key Features:

  • Creates PhysX rigid actors (PxRigidDynamic or PxRigidStatic based on RigidType)
  • Integrates with MeshCollisionComponent to attach collision shapes
  • Stores entity reference in PhysX actor's userData field for bidirectional updates
  • Note: Mass parameter is currently ignored; hardcoded to 10.0f in PhysX actor creation

MeshCollisionComponent

Defines collision detection for physics objects using mesh geometry:

cpp
class MeshCollisionComponent : public Component<MeshCollisionComponent> {
public:
    MeshCollisionComponent(COMPONENT_INIT_PARAMS);

    void start();
    void attach_shape(physx::PxRigidActor* body, glm::mat4 transform_matrix,
                      physx::PxPhysics* physics);

    // Collision detection methods
    bool aabb_intersect(MeshCollisionComponent* other);
    bool has_intersect(MeshCollisionComponent* other);

    // GJK algorithm support methods
    glm::vec3 get_support(MeshCollisionComponent* other_collider, glm::vec3 direction);
    glm::vec3 find_furthest_point(glm::vec3 direction, std::vector<glm::vec3> vertices);
    bool can_check_next_simplex(Simplex& simplex, glm::vec3& direction);
    bool search_line(Simplex& simplex, glm::vec3& direction);
    bool search_triangle(Simplex& simplex, glm::vec3& direction);
    bool search_tetrahedron(Simplex& simplex, glm::vec3& direction);

    std::vector<glm::vec3> vertices;
};

Key Features:

  • Implements GJK (Gilbert-Johnson-Keerthi) collision detection algorithm
  • Attaches box collision shapes to PhysX rigid actors based on mesh bounding box (AABB)
  • Collision shapes are always AABB boxes derived from mesh bounds
  • Material properties are hardcoded: 0.5f static friction, 0.5f dynamic friction, 0.6f restitution
  • No sphere, capsule, or custom mesh geometry variants
  • No trigger support (isTrigger field does not exist)

Simplex

Helper class for GJK collision detection algorithm:

cpp
class Simplex {
public:
    Simplex();

    Simplex& operator=(std::initializer_list<glm::vec3> list);
    glm::vec3& operator[](unsigned int index);

    void push_front(glm::vec3 point);
    auto size() const;
    auto begin() const;
    auto end() const;

    enum SimplexType { line, triangle, tetrahedron };
    SimplexType get_type();

private:
    std::array<glm::vec3, 4> m_points;
    unsigned m_size;
};

Purpose:

  • Stores up to 4 points forming a simplex (line, triangle, or tetrahedron)
  • Used by MeshCollisionComponent's GJK collision detection algorithm

Architecture

PhysX Integration

Astra integrates PhysX for physics simulation:

PhysicsSystem
    └── Global PhysX State
            ├── gFoundation (physx::PxFoundation*)
            ├── gPhysics (physx::PxPhysics*)
            ├── gScene (physx::PxScene*)
            ├── gDispatcher (physx::PxDefaultCpuDispatcher*)
            ├── gPvd (physx::PxPvd*) - Visual debugger connection
            └── gMaterial (physx::PxMaterial*) - Shared material

Global State Pattern:

  • PhysX objects are managed as static globals
  • CPU dispatcher hardcoded to 2 threads
  • Optional GPU support via libPhysXGpu_64.so when ENGINE_USE_PHYSX_GPU is defined

Fixed Timestep

Physics simulation is driven by the fixed_update() method called at a fixed timestep:

cpp
void PhysicsSystem::fixed_update(double fixed_dt) {
    if (gSimulate) {
        gScene->simulate(fixed_dt);
        gScene->fetchResults(true);
    }
}

Simulation Control:

  • F4 key toggles gSimulate flag on/off
  • Simulation is off by default
  • Fixed timestep value comes from PhysicsSystemConfig

Transform Synchronization

Physics updates are synchronized with entity transforms through a two-way process:

pre_update(dt):
    - For entities with dirty TransformComponent:
        → Update PhysX actor pose from TransformComponent
        → Clear dirty flag

fixed_update(fixed_dt):
    - Run PhysX simulation if gSimulate is true

update(dt):
    - For dynamic RigidBodyComponent entities:
        → Read PhysX actor pose
        → Update TransformComponent position and rotation

Entity Linkage:

  • PhysX actors store entity pointers in their userData field
  • Enables bidirectional updates between ECS and PhysX

Key Features

Rigid Body Dynamics

Add physics simulation to entities:

cpp
// Create a dynamic physics object
Entity* box = entity_manager->create_entity();

// Add transform
box->add_component<TransformComponent>();

// Add rigid body (constructor parameters)
box->add_component<RigidBodyComponent>(
    RigidType::Dynamic,  // rigid_type
    0.5f,                // gravity
    2.0f,                // velocity
    2.0f,                // acceleration
    0.0f,                // drag
    1.0f                 // mass (note: currently ignored, uses hardcoded 10.0f)
);

// Add collision shape (requires mesh with vertices)
auto* mesh_collision = box->add_component<MeshCollisionComponent>();
mesh_collision->vertices = { /* vertex data from mesh */ };

Static Colliders

Create static collision geometry:

cpp
Entity* ground = entity_manager->create_entity();

ground->add_component<TransformComponent>();

ground->add_component<RigidBodyComponent>(RigidType::Static);

auto* mesh_collision = ground->add_component<MeshCollisionComponent>();
mesh_collision->vertices = { /* vertex data from mesh */ };
// Collision shape will be AABB box derived from mesh bounds

Math Conversion Utilities

Convert between GLM and PhysX math types:

cpp
// Available conversion utilities in utils/math.hpp
inline physx::PxVec3 GlmVec3ToPxVec3(const glm::vec3& vec);
inline physx::PxQuat GlmQuatToPxQuat(const glm::quat& quat);
inline glm::mat4 PxMat44ToGlmMat4(physx::PxMat44 pxMatrix);
inline physx::PxMat44 GlmMat4ToPxMat44(glm::mat4 glmMatrix);
inline glm::quat PxQuatToGlmQuat(const physx::PxQuat& pxQuat);

Usage: These utilities enable bidirectional conversion when synchronizing transforms between the ECS layer (using GLM) and PhysX (using PxVec3, PxQuat, etc.).

Collision Detection (GJK Algorithm)

MeshCollisionComponent implements custom collision detection:

cpp
// Check AABB intersection (fast broad-phase)
bool aabb_intersect(MeshCollisionComponent* other);

// Precise collision using GJK algorithm (narrow-phase)
bool has_intersect(MeshCollisionComponent* other);

// GJK support function - finds furthest point in Minkowski difference
glm::vec3 get_support(MeshCollisionComponent* other_collider, glm::vec3 direction);

How GJK Works:

  1. Start with initial simplex (single point in Minkowski difference)
  2. Iteratively expand simplex toward origin
  3. Check if origin is enclosed by simplex
  4. Return true if origin enclosed (collision detected)

Limitations:

  • No collision event callbacks (OnCollisionEnter/Exit)
  • No trigger volumes (isTrigger field doesn't exist)
  • AABB intersection logic may have inverted return value (bug)
  • All collision shapes are AABB boxes from mesh bounds

Integration with Other Modules

With Project Module

PhysicsSystem takes configuration from project descriptor:

cpp
// PhysicsSystemConfig is read from project descriptor's systems array
// Expected fields:
struct PhysicsSystemConfig {
    std::string backend;      // "physx"
    glm::vec3 gravity;        // e.g., {0, -9.81f, 0}
    std::string pvd_host;     // PhysX Visual Debugger host
    int pvd_port;             // PhysX Visual Debugger port
    int pvd_timeout;          // Connection timeout
};

// Example in project.astra:
{
    "systems": [
        {
            "type": "physics",
            "backend": "physx",
            "gravity": [0, -9.81, 0],
            "pvd_host": "127.0.0.1",
            "pvd_port": 5425,
            "pvd_timeout": 10
        }
    ]
}

With Renderer Module

MeshCollisionComponent requires mesh vertex data:

cpp
// MeshCollisionComponent needs mesh vertices for collision detection
auto* mesh_collision = entity->add_component<MeshCollisionComponent>();

// Vertices must be populated from mesh data (typically from renderer)
mesh_collision->vertices = mesh->get_vertices();

// attach_shape() creates AABB box from mesh bounds
mesh_collision->attach_shape(physx_actor, transform_matrix, gPhysics);

Physics Configuration

PhysicsSystem is configured via PhysicsSystemConfig from the project descriptor:

cpp
// Configuration structure (inferred from PhysicsSystem constructor)
struct PhysicsSystemConfig {
    std::string backend;       // "physx"
    glm::vec3 gravity;         // World gravity vector
    std::string pvd_host;      // PhysX Visual Debugger host
    int pvd_port;              // PhysX Visual Debugger port
    int pvd_timeout;           // PVD connection timeout in seconds
};

Implementation Details:

  • CPU dispatcher is hardcoded to 2 threads
  • Material properties are hardcoded (0.5f static/dynamic friction, 0.6f restitution)
  • Scene uses default PhysX scene description
  • GPU support is optional (enabled via ENGINE_USE_PHYSX_GPU compile flag)
  • Simulation toggle via F4 key (gSimulate global flag)

File Locations

/src/modules/physics/
├── CMakeLists.txt                                           # Build configuration
├── systems/
│   ├── physics-system.hpp                                   # PhysicsSystem declaration
│   └── physics-system.cpp                                   # PhysX init and simulation
├── components/
│   ├── rigidbody/
│   │   ├── rigidbody-component.hpp                          # RigidBodyComponent
│   │   ├── rigidbody-component.cpp                          # PhysX actor creation
│   │   └── serializers/
│   │       ├── rigidbody-component-serializer.hpp
│   │       └── rigidbody-component-serializer.cpp           # Stub implementation
│   └── mesh-collision/
│       ├── mesh-collision-component.hpp                     # MeshCollisionComponent + Simplex
│       ├── mesh-collision-component.cpp                     # GJK collision detection
│       └── serializers/
│           ├── mesh-collision-component-serializer.hpp
│           └── mesh-collision-component-serializer.cpp      # Stub implementation
└── utils/
    └── math.hpp                                              # GLM↔PhysX conversions
  • Engine Module - Provides ECS base classes (Component, System)
  • Project Module - Provides physics configuration via PhysicsSystemConfig
  • Renderer Module - Provides mesh vertex data for collision detection

Best Practices

  1. Use Fixed Timestep: PhysicsSystem's fixed_update() is called at a fixed interval by the engine
  2. Populate Mesh Vertices: MeshCollisionComponent requires vertex data for collision detection
  3. Understand Limitations: Collision shapes are always AABB boxes (no sphere/capsule variants)
  4. Material Properties: Currently hardcoded; cannot be configured per-object
  5. Mass Parameter: Note that mass parameter is currently ignored (hardcoded to 10.0f)
  6. Toggle Simulation: Press F4 to enable/disable physics simulation (off by default)

Performance Considerations

  • Physics simulation is CPU-intensive (especially GJK algorithm)
  • AABB broad-phase check (aabb_intersect()) before expensive GJK narrow-phase
  • CPU dispatcher uses 2 threads (hardcoded)
  • Optional GPU support via libPhysXGpu_64.so (ENGINE_USE_PHYSX_GPU)
  • Simulation can be disabled (F4 key) when not needed
  • GJK algorithm complexity depends on mesh vertex count

Common Patterns

Creating a Physics Entity

Complete example of adding physics to an entity:

cpp
// Create entity
Entity* box = entity_manager->create_entity();

// Add transform
auto* transform = box->add_component<TransformComponent>();
transform->position = glm::vec3(0, 10, 0);

// Add rigid body (dynamic)
box->add_component<RigidBodyComponent>(
    RigidType::Dynamic,  // Type
    9.81f,               // Gravity (local gravity value)
    0.0f,                // Velocity (scalar)
    0.0f,                // Acceleration (scalar)
    0.05f,               // Drag
    1.0f                 // Mass (note: currently ignored)
);

// Add mesh collision
auto* collision = box->add_component<MeshCollisionComponent>();
// Populate vertices from mesh data
collision->vertices = mesh->get_vertices();

// Components will initialize in start() phase:
// - RigidBodyComponent::start() creates PhysX actor
// - MeshCollisionComponent::start() attaches AABB shape

Collision Detection Between Entities

Check if two entities with MeshCollisionComponent are colliding:

cpp
Entity* entity_a = /* ... */;
Entity* entity_b = /* ... */;

auto* collision_a = entity_a->get_component<MeshCollisionComponent>();
auto* collision_b = entity_b->get_component<MeshCollisionComponent>();

// Fast broad-phase check (AABB)
if (collision_a->aabb_intersect(collision_b)) {
    // Precise narrow-phase check (GJK)
    if (collision_a->has_intersect(collision_b)) {
        // Collision detected!
        HandleCollision(entity_a, entity_b);
    }
}

Note: There are no automatic collision callbacks. You must manually check for collisions in your game logic.