Physics
PhysX Integration
VkEngine uses NVIDIA PhysX 4.x for physics simulation, collision detection, and character control. The physics system is tightly integrated with GameObjects and provides straightforward APIs for physics interactions.
Physics Materials
Creating Materials
PhysicsMaterial* createPhysicsMaterial(
float staticFriction, // Friction when stationary
float dynamicFriction, // Friction when moving
float restitution // Bounciness (0-1, 1 = perfect bounce)
);
Example:
// Slippery ice
PhysicsMaterial* ice = engine->createPhysicsMaterial(0.1f, 0.05f, 0.3f);
// Rough concrete
PhysicsMaterial* concrete = engine->createPhysicsMaterial(0.8f, 0.6f, 0.1f);
// Bouncy rubber
PhysicsMaterial* rubber = engine->createPhysicsMaterial(0.5f, 0.4f, 0.8f);
// Default material
PhysicsMaterial* standard = engine->createPhysicsMaterial(0.5f, 0.4f, 0.2f);
Material Combinations
When two objects collide, their materials are combined:
- Friction = average of both materials
- Restitution = average of both materials
Rigid Bodies in Game Objects
Creating Objects with Physics
Physics bodies are automatically created when creating a GameObject with a mesh:
createGameObject<T>(
transform,
mesh,
texture,
material,
isDynamic // Physics type determined by this flag
)
Static Objects (isDynamic = false)
Mesh* levelMesh = engine->getMesh("level");
PhysicsMaterial* mat = engine->createPhysicsMaterial(0.8f, 0.6f, 0.1f);
auto level = engine->createGameObject<GameObject>(
{{0,0,0}, {0,0,0,1}, {1,1,1}},
levelMesh,
texture,
mat,
false // Static - immovable collision geometry
);
Properties:
- Immovable and affected by no forces
- Uses triangle mesh collision (accurate for complex shapes)
- Good for terrain, buildings, static obstacles
- High performance (no simulation needed)
Dynamic Objects (isDynamic = true)
Mesh* ballMesh = engine->getMesh("ball");
PhysicsMaterial* bouncyMat = engine->createPhysicsMaterial(0.3f, 0.2f, 0.7f);
auto ball = engine->createGameObject<Ball>(
{{0, 2, 0}, {0,0,0,1}, {1,1,1}},
ballMesh,
texture,
bouncyMat,
true // Dynamic - simulated by physics engine
);
Properties:
- Affected by gravity and forces
- Continuous collision detection enabled (prevents tunneling)
- Fixed mass: 10.0 kg
- Angular damping: 1.0
- Linear damping: 0.5
- Uses convex mesh collision
Applying Forces
Impulse (Instantaneous Force)
void applyForce(const Vector3& force);
// Applies force immediately, like an impulse or explosion
Example:
void takeDamage(Vector3 explosionPoint) {
// Knockback from explosion
Vector3 knockback = (transform.position - explosionPoint).normalize() * 50.0f;
applyForce(knockback);
}
Directional Force
void applyForce(Vector3 direction, float power);
// Applies normalized directional force
Example:
void Update(Engine* engine) {
// Wind force pushing object
Vector3 windDirection = {1, 0, 0};
float windStrength = 5.0f;
applyForce(windDirection, windStrength);
}
Querying Physics State
Velocity
Vector3 getVelocity();
// Returns current linear velocity
Example:
void Update(Engine* engine) {
Vector3 vel = getVelocity();
float speed = sqrt(vel.x*vel.x + vel.y*vel.y + vel.z*vel.z);
if (speed > maxSpeed) {
// Moving too fast, apply drag
applyForce(-getVelocity() * 0.5f);
}
}
Physics Types
PhysicsType Enum
enum class PhysicsType {
Static, // Immovable
Kinematic, // Scripted motion (e.g., platforms)
Dynamic // Physics-simulated
};
Changing Physics Type at Runtime
void setPhysicsType(PhysicsType type);
Example: Object falls when triggered
void Update(Engine* engine) {
if (shouldFall) {
setPhysicsType(PhysicsType::Dynamic);
}
}
Collision Detection
Collision Callbacks
std::function<void(GameObject*, float)> onCollision;
Set a callback to be notified of collisions:
void InitScene(Engine* engine) override {
auto obj = engine->createGameObject<MyObject>(...);
obj->onCollision = [this](GameObject* other, float impulse) {
if (other->tag == "projectile") {
takeDamage(impulse);
}
};
}
Parameters:
other: The object this one collided withimpulse: Magnitude of collision force
Collision Layers
Currently no built-in collision layer system. To exclude collisions:
- Use Triggers instead of physics for certain interactions
- Manage collision logic in callbacks
Triggers (Overlap Volumes)
Triggers are non-physical collision volumes that detect overlaps without affecting physics:
Creating Triggers
Trigger* createBoxTrigger(Vector3 position, Vector3 size);
Example:
Trigger* damageZone = engine->createBoxTrigger(
{0, 0, 10}, // Center position
{5, 2, 5} // Size (width, height, depth)
);
damageZone->onTriggerEnter = [this](GameObject* other) {
if (other->tag == "player") {
playerTakeDamage(10); // Continuous damage in zone
}
};
damageZone->onTriggerExit = [this](GameObject* other) {
if (other->tag == "player") {
stopDamage();
}
};
Callbacks
std::function<void(GameObject*)> onTriggerEnter;
std::function<void(GameObject*)> onTriggerExit;
Called when objects enter/exit the trigger volume.
Cleanup
engine->requestDestroyTrigger(trigger);
Physics Queries
Raycasting
RaycastHit raycast(Vector3 origin, Vector3 direction, float distance);
struct RaycastHit {
float distance; // Distance from origin to hit point
GameObject* object; // The object that was hit (null if no hit)
};
Example: Picking objects with mouse
void MyScene::UpdateScene(Engine* engine) {
if (engine->getMouseButton(MouseButton::Left) == PRESS) {
Vector3 rayOrigin, rayDirection;
engine->getMouseRay(rayOrigin, rayDirection);
RaycastHit hit = engine->raycast(rayOrigin, rayDirection, 1000.0f);
if (hit.object) {
selectObject(hit.object);
}
}
}
Ignores:
- Trigger volumes
- CharacterController shapes (only solid objects)
Sweep (AABB Overlap)
SweepHit sweep(Vector3 position, Vector3 size, GameObject* ignore = nullptr);
struct SweepHit {
std::vector<GameObject*> objects; // All objects in volume
};
Example: Find nearby enemies
Vector3 playerPos = player->transform.position;
SweepHit nearby = engine->sweep(playerPos, {10, 10, 10}, player);
for (GameObject* obj : nearby.objects) {
if (obj->tag == "enemy") {
engageEnemy(obj);
}
}
Character Controller
Creation
The CharacterController is specialized for first-person character movement:
ICharacterController* createCharacterController(
float height, // Capsule height (e.g., 1.8f)
float radius, // Capsule radius (e.g., 0.4f)
Vector3 position, // Starting position
PhysicsMaterial* material, // Physics material
bool interactWithActors // true = push rigid bodies, false = pass through
);
Movement
void Move(Vector3 direction, float speed, float dt);
Example: Player movement in UpdateScene
void PlayerScene::UpdateScene(Engine* engine) {
Vector3 moveDir = {};
if (engine->getKey(KeyCode::W) == PRESS) moveDir.z += 1;
if (engine->getKey(KeyCode::S) == PRESS) moveDir.z -= 1;
if (engine->getKey(KeyCode::A) == PRESS) moveDir.x -= 1;
if (engine->getKey(KeyCode::D) == PRESS) moveDir.x += 1;
controller->Move(moveDir, 10.0f, engine->getDeltaTime());
// Update camera to follow controller
Vector3 pos = controller->getPosition();
engine->cameraPosition = pos + Vector3(0, 1.5f, 0); // Eyes at 1.5m height
}
Jumping
void Jump(float force);
Example:
void UpdateScene(Engine* engine) {
if (engine->getKey(KeyCode::Space) == PRESS && isGrounded) {
controller->Jump(15.0f);
}
// Check if still grounded
float vertVel = controller->getVerticalVelocity();
isGrounded = (vertVel == 0.0f); // Grounded when vertical velocity is zero
}
Position
Vector3 getPosition();
void setPosition(Vector3 newPosition);
Example: Teleport or respawn
if (fellOffMap) {
controller->setPosition(spawnPoint);
}
Vertical Velocity
float getVerticalVelocity();
Useful for:
- Detecting if jumping or falling
- Animation state (falling, jumping, landing)
- Knockback recovery timing
Example: Animation state
float vertVel = controller->getVerticalVelocity();
if (vertVel > 0) {
setAnimationState("jumping");
} else if (vertVel < 0) {
setAnimationState("falling");
} else {
setAnimationState("idle");
}
CharacterController Details
- Gravity: -24.0 units/s²
- Shape: Capsule (collision geometry)
- Collision: Stops at obstacles
- Slope walking: Automatically stays on slopes
- Stepping: Climbs small step heights
- Mass: Fixed for consistent feel
Physics Coordinate System
- X-axis: Left/right (positive right)
- Y-axis: Front/back (positive back)
- Z-axis: Up/down (positive up, gravity is -Z)
This is a Z-up coordinate system.
Physics Engine Details
Simulation Accuracy
- Time stepping: Variable timestep per frame (uses engine delta time)
- Solver iterations: Configurable per simulation
- Continuous collision detection: Enabled for fast-moving objects
- Sleeping: Bodies sleep when inactive for performance
Mesh Cooking
When a mesh is created:
- Dynamic objects: Converted to convex mesh (efficient, less accurate)
- Static objects: Converted to triangle mesh (accurate, static)
- Caching: Cooked meshes cached to avoid recomputation
Performance Characteristics
- Dynamic bodies: O(n) where n = number of dynamic objects
- Static bodies: O(1) per-frame (shape is fixed)
- Triggers: O(n) overlap tests
- Raycasts: O(log n) with spatial acceleration
Physics Implementation in LabEscape
LabEscape demonstrates:
- Player controlled by CharacterController
- Static level geometry from mesh
- Triggers for exit zones and puzzles
- Physics-based objects (balls in cup game)
- Collision-based interactions
Tips and Best Practices
- Static for immovable: Use static bodies for terrain, buildings
- Convex shapes optimal: CharacterController and dynamic objects use convex meshes
- Avoid nested meshes: One mesh per object for best performance
- Trigger for detection: Use triggers instead of collision callbacks for non-physics events
- Raycast for picking: Prefer raycasts over overlap tests for precise interaction
- Material tuning: Test friction/restitution for intended feel
- Mass balance: All dynamic objects have mass 10kg; adjust forces for balance
- Gravity direction: Remember -Z is down; adjust camera accordingly
Known Limitations
- No ragdoll physics
- No rope or cable simulation
- No destruction/deformable meshes
- No joint constraints (hinges, springs, etc.)
- No fluid simulation
- Limited collision layer system
- No sleeping optimization configuration