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 dispatcherfixed_update(double fixed_dt)- Steps the physics simulation at fixed timesteppre_update(double dt)- Synchronizes TransformComponent changes to PhysX actorsupdate(double dt)- Applies PhysX simulation results back to TransformComponent
Configuration:
- Takes
PhysicsSystemConfigwith 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:
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:
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:
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 materialGlobal 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:
void PhysicsSystem::fixed_update(double fixed_dt) {
if (gSimulate) {
gScene->simulate(fixed_dt);
gScene->fetchResults(true);
}
}Simulation Control:
- F4 key toggles
gSimulateflag 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 rotationEntity Linkage:
- PhysX actors store entity pointers in their
userDatafield - Enables bidirectional updates between ECS and PhysX
Key Features
Rigid Body Dynamics
Add physics simulation to entities:
// 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:
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 boundsMath Conversion Utilities
Convert between GLM and PhysX math types:
// 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:
// 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:
- Start with initial simplex (single point in Minkowski difference)
- Iteratively expand simplex toward origin
- Check if origin is enclosed by simplex
- 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:
// 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:
// 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:
// 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 conversionsRelated Modules
- 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
- Use Fixed Timestep: PhysicsSystem's fixed_update() is called at a fixed interval by the engine
- Populate Mesh Vertices: MeshCollisionComponent requires vertex data for collision detection
- Understand Limitations: Collision shapes are always AABB boxes (no sphere/capsule variants)
- Material Properties: Currently hardcoded; cannot be configured per-object
- Mass Parameter: Note that mass parameter is currently ignored (hardcoded to 10.0f)
- 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:
// 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 shapeCollision Detection Between Entities
Check if two entities with MeshCollisionComponent are colliding:
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.