tiramisu/physics
Physics module using Rapier physics engine
Provides declarative physics simulation following the same immutable, diff/patch pattern as the rest of Tiramisu.
Physics bodies are declared alongside scene nodes, and the physics world is managed as part of the game’s Model state.
Types
Axis locks for restricting body movement/rotation
pub type AxisLock {
AxisLock(
lock_translation_x: Bool,
lock_translation_y: Bool,
lock_translation_z: Bool,
lock_rotation_x: Bool,
lock_rotation_y: Bool,
lock_rotation_z: Bool,
)
}
Constructors
-
AxisLock( lock_translation_x: Bool, lock_translation_y: Bool, lock_translation_z: Bool, lock_rotation_x: Bool, lock_rotation_y: Bool, lock_rotation_z: Bool, )
Arguments
- lock_translation_x
-
Lock translation on X axis
- lock_translation_y
-
Lock translation on Y axis
- lock_translation_z
-
Lock translation on Z axis
- lock_rotation_x
-
Lock rotation on X axis
- lock_rotation_y
-
Lock rotation on Y axis
- lock_rotation_z
-
Lock rotation on Z axis
Physics body type
pub type Body {
Dynamic
Kinematic
Fixed
}
Constructors
-
Dynamic
Dynamic bodies are affected by forces and gravity
-
Kinematic
Kinematic bodies can be moved programmatically but don’t respond to forces
-
Fixed
Fixed (static) bodies don’t move
Collider shape
pub type ColliderShape {
Box(
offset: transform.Transform,
width: Float,
height: Float,
depth: Float,
)
Sphere(offset: transform.Transform, radius: Float)
Capsule(
offset: transform.Transform,
half_height: Float,
radius: Float,
)
Cylinder(
offset: transform.Transform,
half_height: Float,
radius: Float,
)
}
Constructors
-
Box( offset: transform.Transform, width: Float, height: Float, depth: Float, )
Box collider with half-extents
-
Sphere(offset: transform.Transform, radius: Float)
Sphere collider with radius
-
Capsule( offset: transform.Transform, half_height: Float, radius: Float, )
Capsule collider (cylinder with rounded caps)
-
Cylinder( offset: transform.Transform, half_height: Float, radius: Float, )
Cylinder collider
Collision events that occurred during the physics step
pub type CollisionEvent {
CollisionStarted(body_a: String, body_b: String)
CollisionEnded(body_a: String, body_b: String)
}
Constructors
-
CollisionStarted(body_a: String, body_b: String)
Two bodies started colliding
-
CollisionEnded(body_a: String, body_b: String)
Two bodies stopped colliding
Collision groups for filtering which objects can collide with each other.
Uses Rapier’s collision groups system based on 32-bit bitmasks:
membership
: What collision layers this body belongs to (0-15)filter
: What collision layers this body can interact with (0-15)
Two bodies can collide only if:
- Body A’s membership overlaps with Body B’s filter, AND
- Body B’s membership overlaps with Body A’s filter
Example
// Player belongs to layer 0, collides with layers 1 (enemies) and 2 (ground)
let player_groups = physics.CollisionGroups(
membership: [0],
filter: [1, 2]
)
// Enemy belongs to layer 1, collides with layers 0 (player) and 3 (projectiles)
let enemy_groups = physics.CollisionGroups(
membership: [1],
filter: [0, 3]
)
// Ground belongs to layer 2, collides with everything
let ground_groups = physics.CollisionGroups(
membership: [2],
filter: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
)
pub type CollisionGroups {
CollisionGroups(membership: List(Int), filter: List(Int))
}
Constructors
-
CollisionGroups(membership: List(Int), filter: List(Int))
Arguments
- membership
-
List of collision layers this body belongs to (0-15)
- filter
-
List of collision layers this body can collide with (0-15)
Opaque handle to the Rapier physics world This is part of your Model and gets updated each frame
pub opaque type PhysicsWorld(id)
Result of a raycast hit
pub type RaycastHit(id) {
RaycastHit(
id: id,
point: vec3.Vec3(Float),
normal: vec3.Vec3(Float),
distance: Float,
)
}
Constructors
-
Arguments
- id
-
ID of the body that was hit
- point
-
Point where the ray intersected the body
- normal
-
Normal vector at the hit point
- distance
-
Distance from ray origin to hit point
Builder for creating rigid bodies with a fluent API
pub opaque type RigidBodyBuilder(a)
pub type WithCollider
pub type WithoutCollider
Values
pub fn apply_force(
world: PhysicsWorld(body),
id: body,
force: vec3.Vec3(Float),
) -> PhysicsWorld(body)
Queue a force to be applied to a rigid body during the next physics step. Returns updated world with the command queued.
Example
let world = physics.apply_force(world, "player", vec3.Vec3(0.0, 100.0, 0.0))
let world = physics.step(world, ctx.delta_time) // Force is applied here
pub fn apply_impulse(
world: PhysicsWorld(body),
id: body,
impulse: vec3.Vec3(Float),
) -> PhysicsWorld(body)
Queue an impulse to be applied to a rigid body during the next physics step. Returns updated world with the command queued.
Example
// Jump
let world = physics.apply_impulse(world, "player", vec3.Vec3(0.0, 10.0, 0.0))
pub fn apply_torque(
world: PhysicsWorld(body),
id: body,
torque: vec3.Vec3(Float),
) -> PhysicsWorld(body)
Queue a torque to be applied to a rigid body during the next physics step. Returns updated world with the command queued.
pub fn apply_torque_impulse(
world: PhysicsWorld(body),
id: body,
impulse: vec3.Vec3(Float),
) -> PhysicsWorld(body)
Queue a torque impulse to be applied to a rigid body during the next physics step. Returns updated world with the command queued.
pub fn build(
builder: RigidBodyBuilder(WithCollider),
) -> RigidBody
Build the final rigid body from the builder
Returns an error if no collider was set.
pub fn get_angular_velocity(
world: PhysicsWorld(body),
id: body,
) -> Result(vec3.Vec3(Float), Nil)
Get the current angular velocity of a rigid body
pub fn get_collision_events(
world: PhysicsWorld(id),
) -> List(CollisionEvent)
Get all collision events that occurred during the last physics step.
Events are automatically collected when step()
is called and stored in the world.
Example
let physics_world = physics.step(physics_world)
let collision_events = physics.get_collision_events(physics_world)
list.each(collision_events, fn(event) {
case event {
physics.CollisionStarted(a, b) ->
io.println(a <> " started colliding with " <> b)
physics.CollisionEnded(a, b) ->
io.println(a <> " ended colliding with " <> b)
}
})
pub fn get_transform(
physics_world: PhysicsWorld(id),
id: id,
) -> Result(transform.Transform, Nil)
Get the current transform of a rigid body.
Queries the physics simulation directly, so it always returns the latest position even for bodies that were just created in the current frame.
Example
let cube_transform = case physics.get_transform(physics_world, Cube1) {
Ok(t) -> t
Error(_) -> transform.at(position: vec3.Vec3(0.0, 10.0, 0.0))
}
pub fn get_velocity(
world: PhysicsWorld(body),
id: body,
) -> Result(vec3.Vec3(Float), Nil)
Get the current velocity of a rigid body
pub fn new_rigid_body(
body_type: Body,
) -> RigidBodyBuilder(WithoutCollider)
Create a new rigid body builder
Example
let body = physics.new_rigid_body(physics.Dynamic)
|> physics.body_collider(physics.Box(2.0, 2.0, 2.0))
|> physics.body_mass(5.0)
|> physics.build_body()
pub fn new_world(config: WorldConfig(body)) -> PhysicsWorld(body)
Create a new physics world (call this in your init function)
pub fn raycast(
world: PhysicsWorld(id),
origin origin: vec3.Vec3(Float),
direction direction: vec3.Vec3(Float),
max_distance max_distance: Float,
) -> Result(RaycastHit(id), Nil)
Cast a ray and return the first hit
Useful for shooting mechanics, line-of-sight checks, and ground detection.
Example
// Cast ray downward from player position
let origin = player_position
let direction = vec3.Vec3(0.0, -1.0, 0.0)
case physics.raycast(world, origin, direction, max_distance: 10.0) {
Ok(hit) -> {
// Found ground at hit.distance units below player
io.println("Hit body with ID")
}
Error(Nil) -> {
// No ground found within 10 units
}
}
pub fn set_angular_velocity(
world: PhysicsWorld(body),
id: body,
velocity: vec3.Vec3(Float),
) -> PhysicsWorld(body)
Queue an angular velocity change for a rigid body during the next physics step. Returns updated world with the command queued.
pub fn set_velocity(
world: PhysicsWorld(body),
id: body,
velocity: vec3.Vec3(Float),
) -> PhysicsWorld(body)
Queue a velocity change for a rigid body during the next physics step. Returns updated world with the command queued.
pub fn step(world: PhysicsWorld(id)) -> PhysicsWorld(id)
Step the physics simulation forward This should be called in your update function each frame Returns updated world with new transforms for all bodies
pub fn with_angular_damping(
builder: RigidBodyBuilder(a),
damping: Float,
) -> RigidBodyBuilder(b)
Set the angular damping for the rigid body
pub fn with_body_ccd_enabled(
builder: RigidBodyBuilder(a),
) -> RigidBodyBuilder(b)
Enable continuous collision detection for the rigid body
pub fn with_collider(
builder: RigidBodyBuilder(a),
collider: ColliderShape,
) -> RigidBodyBuilder(WithCollider)
Set the collider shape for the rigid body
pub fn with_collision_groups(
builder: RigidBodyBuilder(a),
membership membership: List(Int),
can_collide_with filter: List(Int),
) -> RigidBodyBuilder(b)
Set collision groups for filtering which objects can collide
Example
// Player belongs to layer 0, collides with enemies (1) and ground (2)
let body = physics.new_rigid_body(physics.Dynamic)
|> physics.body_collider(physics.Capsule(1.0, 0.5))
|> physics.body_collision_groups(
membership: [0],
filter: [1, 2]
)
|> physics.build_body()
pub fn with_friction(
builder: RigidBodyBuilder(a),
friction: Float,
) -> RigidBodyBuilder(b)
Set the friction for the rigid body
pub fn with_linear_damping(
builder: RigidBodyBuilder(a),
damping: Float,
) -> RigidBodyBuilder(b)
Set the linear damping for the rigid body
pub fn with_lock_rotation_x(
builder: RigidBodyBuilder(a),
) -> RigidBodyBuilder(b)
Lock rotation on the X axis (pitch)
pub fn with_lock_rotation_y(
builder: RigidBodyBuilder(a),
) -> RigidBodyBuilder(b)
Lock rotation on the Y axis (yaw)
pub fn with_lock_rotation_z(
builder: RigidBodyBuilder(a),
) -> RigidBodyBuilder(b)
Lock rotation on the Z axis (roll)
pub fn with_lock_translation_x(
builder: RigidBodyBuilder(a),
) -> RigidBodyBuilder(b)
Lock translation on the X axis
pub fn with_lock_translation_y(
builder: RigidBodyBuilder(a),
) -> RigidBodyBuilder(b)
Lock translation on the Y axis
pub fn with_lock_translation_z(
builder: RigidBodyBuilder(a),
) -> RigidBodyBuilder(b)
Lock translation on the Z axis
pub fn with_mass(
builder: RigidBodyBuilder(a),
mass: Float,
) -> RigidBodyBuilder(b)
Set the mass for the rigid body
pub fn with_restitution(
builder: RigidBodyBuilder(a),
restitution: Float,
) -> RigidBodyBuilder(b)
Set the restitution (bounciness) for the rigid body