DocsAPI reference

cinder.physics

cinder.physics #

Source: cinder/physics/__init__.mojo

Physics over Jolt: rigid-body world, queries, character controller and ECS synchronisation.

cinder.physics.character #

Source: cinder/physics/character.mojo

Character controller over Jolt's CharacterVirtual: slopes, steps and moving platforms. The character is a capsule whose position is at its feet. update applies the engine's standard velocity rules (stick to the ground and inherit its velocity, otherwise fall under gravity), then runs Jolt's extended update which walks stairs up to step_height and sticks to the floor when walking down. Contacts with dynamic bodies push them with the character's mass.

  • comptime PI_F32: Float32 = 3.14159265358979 — Pi as Float32, for converting max_slope_degrees to radians.

struct GroundState #

mojo
struct GroundState(Equatable, ImplicitlyCopyable, Movable, Writable)

Ground contact state of a CharacterController, mirrored from Jolt's ground state.

Fields:

  • value: UInt8

  • on_ground on_ground() -> GroundState
    Standing on a surface no steeper than max_slope_degrees.

  • on_steep_ground on_steep_ground() -> GroundState
    Touching a surface steeper than max_slope_degrees; update treats it like the air and applies gravity.

  • not_supported not_supported() -> GroundState
    Touching something that cannot hold the character (its edge, a falling body).

  • in_air in_air() -> GroundState
    Touching nothing; update applies gravity.

  • write_to write_to(self, mut w: Some[Writer])

struct CharacterDesc #

mojo
struct CharacterDesc(Copyable, Movable)

Parameters of a CharacterController in metres, kilograms and degrees. radius and height size the capsule (height must exceed 2 * radius + 0.01); max_slope_degrees is the steepest walkable slope and step_height the tallest step walked up; mass and max_strength set how hard dynamic bodies are pushed; layer is the object layer the character collides as; gravity (m/s^2, default -9.81 on Y) applies while it is not standing on ground.

Fields:

  • radius: Float32

  • height: Float32 — Total standing height including both hemispheres.

  • max_slope_degrees: Float32

  • step_height: Float32

  • mass: Float32

  • layer: Int

  • gravity: Vec3

  • max_strength: Float32 — Maximum force (N) the character can push dynamic bodies with.

  • __init__ __init__( out self, radius: Float32 = 0.3, height: Float32 = 1.8, layer: Int = 1, max_slope_degrees: Float32 = 50.0, step_height: Float32 = 0.4, mass: Float32 = 70.0, )

struct CharacterController #

mojo
struct CharacterController(Movable)

A capsule character over Jolt's CharacterVirtual, positioned at its feet. It is not a rigid body: it has no body id. It creates and owns its capsule shape and counts itself in world.live_characters; its destructor destroys the character and the shape and decrements the count. It keeps a raw pointer to the PhysicsWorld, so the world must not move and must outlive the controller; use it from the thread that steps the world. The constructor raises when height does not exceed 2 * radius + 0.01 or Jolt cannot create the character.

Fields:

  • world: Pointer[PhysicsWorld, MutUntrackedOrigin]

  • desc: CharacterDesc

  • shape: Shape

  • character: Optional[Pointer[JPH_CharacterVirtual, MutUntrackedOrigin]]

  • extended: JPH_ExtendedUpdateSettings

  • __init__ __init__( out self, world: Pointer[PhysicsWorld, MutUntrackedOrigin], desc: CharacterDesc, position: Vec3, ) raises

  • position position(self) -> Vec3
    World position of the character's feet, in metres.

  • set_position set_position(self, position: Vec3)
    Teleports the character so its feet are at position (metres).

  • rotation rotation(self) -> Quat
    World rotation of the character.

  • set_rotation set_rotation(self, rotation: Quat)
    Sets the world rotation of the character.

  • velocity velocity(self) -> Vec3
    Linear velocity of the character in metres per second.

  • ground_state ground_state(self) -> GroundState
    Ground contact state from Jolt's last character update (see GroundState).

  • is_supported is_supported(self) -> Bool
    Whether Jolt considers the character supported by the ground it touches.

  • ground_normal ground_normal(self) -> Vec3
    World-space surface normal of the ground contact.

  • ground_velocity ground_velocity(self) -> Vec3
    Velocity of the ground under the character in metres per second (a moving platform's), refreshed at the start of update.

  • ground_body ground_body(self) -> UInt32
    Id of the body under the character's feet; INVALID_BODY when Jolt reports none.

  • update update( mut self, dt: Float32, desired_velocity: Vec3, jump_speed: Float32 = 0.0 )
    Moves the character for one step. desired_velocity is the horizontal velocity the player asks for (its Y is ignored); a positive jump_speed launches the character when it stands on the ground. Call before PhysicsWorld.step with the same dt.

cinder.physics.sync #

Source: cinder/physics/sync.mojo

ECS <-> physics synchronisation with interpolation. RigidBody links an entity to a Jolt body and stores the two most recent simulated poses. The fixed-step loop is: push_kinematics(world, physics, dt) # moved LocalTransforms drive kinematic bodies physics.step(dt) pull_bodies(world, physics) # dynamic bodies: previous <- current <- Jolt ... write_transforms(world, alpha) # every frame: LocalTransform at alpha between the poses Kinematic bodies follow the entity; dynamic bodies drive it; static bodies are left alone.

  • comptime RIGID_BODY_COMPONENT_ID = 7 — ECS component id of RigidBody.

struct RigidBody #

mojo
struct RigidBody(Component)

The physics side of an entity. scale is preserved when the body writes the transform (Jolt bodies carry no scale).

Fields:

  • body: UInt32

  • motion: MotionType

  • interpolate: Bool

  • previous_position: Vec3

  • previous_rotation: Quat

  • position: Vec3

  • rotation: Quat

  • scale: Vec3

  • last_pushed_tick: UInt32 — Change tick of the LocalTransform last pushed to a kinematic body.

  • __init__ __init__( out self, body: UInt32, motion: MotionType, position: Vec3, rotation: Quat, scale: Vec3 = Vec3.one(), interpolate: Bool = True, )

  • component_id component_id() -> Int

struct PhysicsSync #

mojo
struct PhysicsSync(Movable)

Per-frame physics <-> ECS synchronisation with persistent query specs and scratch buffers, so steady-state frames allocate nothing. The free functions below are convenience wrappers that build a temporary instance.

Fields:

  • __init__ __init__(out self)
  • pull_bodies pull_bodies( mut self, mut world: World, physics: PhysicsWorld ) raises -> Int
    After a physics step: copies each dynamic body's pose into its RigidBody, keeping the previous pose for interpolation. Returns the number of bodies read.
  • push_kinematics push_kinematics( mut self, mut world: World, physics: PhysicsWorld, dt: Float32 ) raises -> Int
    Before a physics step: kinematic bodies whose LocalTransform changed since the last push are moved so they reach the new pose during this step. Returns the count.
  • write_transforms write_transforms( mut self, mut world: World, alpha: Float32 ) raises -> Int
    Writes LocalTransform for dynamic bodies at alpha in [0, 1] between the previous and current poses. Bodies whose id is INVALID_BODY are skipped, as in pull_bodies. Only changed transforms are marked. Returns the count updated.

def pull_bodies #

mojo
def pull_bodies(mut world: World, physics: PhysicsWorld) raises -> Int

PhysicsSync.pull_bodies through a temporary PhysicsSync, so it allocates query scratch on every call. Returns the number of dynamic bodies read.

def push_kinematics #

mojo
def push_kinematics( mut world: World, physics: PhysicsWorld, dt: Float32 ) raises -> Int

PhysicsSync.push_kinematics through a temporary PhysicsSync, so it allocates query scratch on every call. Returns the number of kinematic bodies moved.

def write_transforms #

mojo
def write_transforms(mut world: World, alpha: Float32) raises -> Int

PhysicsSync.write_transforms through a temporary PhysicsSync, so it allocates scratch on every call. Returns the number of transforms written.

cinder.physics.world #

Source: cinder/physics/world.mojo

Rigid-body world over Jolt (joltc): layers, shapes, bodies, queries and queued events. Every Jolt object created here is counted; live_objects() must be zero before the world is destroyed. The destructor does not abort on leaks (an abort would hide the real failure of a test that raised): it prints an ERROR record naming the leaked counts (leak_report()), and tests and App.live_objects() assert zero explicitly. Destroying a body or shape the world does not know (twice, or an invalid id) leaves the counts alone and records an ERROR in diagnostics. Jolt runs its own thread pool for the simulation step; its listeners only post to the shim queue (ADR-0003) and poll_events() drains that queue on the calling thread, so Mojo never runs inside a Jolt callback. Units: metres, seconds, kilograms; the coordinate system is the engine's (right-handed, Y up).

  • comptime INVALID_BODY: UInt32 = 0xFFFFFFFF — Jolt's invalid body id.
  • comptime MAX_LAYERS = 32 — Object layers are bits of a 32-bit mask in filters.

def to_jolt #

mojo
def to_jolt(v: Vec3) -> JPH_Vec3

Converts an engine Vec3 to a joltc JPH_Vec3.

def from_jolt #

mojo
def from_jolt(v: JPH_Vec3) -> Vec3

Converts a joltc JPH_Vec3 to an engine Vec3.

def quat_to_jolt #

mojo
def quat_to_jolt(q: Quat) -> JPH_Quat

Converts an engine Quat to a joltc JPH_Quat (x, y, z, w).

def quat_from_jolt #

mojo
def quat_from_jolt(q: JPH_Quat) -> Quat

Converts a joltc JPH_Quat to an engine Quat.

def mat_to_jolt #

mojo
def mat_to_jolt(m: Mat4) -> JPH_Mat4

Column-major on both sides.

struct LayerConfig #

mojo
struct LayerConfig(Copyable, Movable)

Object layers and which pairs collide. Layer i is bit i in query masks. Layers flagged static go to a separate broad-phase layer so static geometry is never tested against itself.

Fields:

  • count: Int

  • static_mask: UInt32

  • pairs: List[Tuple[Int, Int]]

  • __init__ __init__(out self, count: Int, static_mask: UInt32 = 1)

  • collide collide(mut self, a: Int, b: Int)
    Enables collision between layers a and b (symmetric).

  • default default() -> LayerConfig
    Two layers: 0 static, 1 moving; moving collides with both.

struct MotionType #

mojo
struct MotionType(Equatable, ImplicitlyCopyable, Movable)

How a body moves: static, kinematic (driven by the caller) or dynamic (simulated).

Fields:

  • value: UInt8

  • static static() -> MotionType
    A body that never moves.

  • kinematic kinematic() -> MotionType
    A body moved by the caller (move_kinematic, set_position_rotation), not by forces or contacts.

  • dynamic dynamic() -> MotionType
    A body simulated by Jolt: gravity, forces, impulses and contacts move it.

  • to_jolt to_jolt(self) -> JPH_MotionType
    The matching joltc JPH_MotionType; any value other than static or kinematic maps to dynamic.

struct Shape #

mojo
struct Shape(ImplicitlyCopyable, Movable)

A reference-counted Jolt shape that the world tracks; copy freely, destroy once.

Fields:

  • ptr: Optional[Pointer[JPH_Shape, MutUntrackedOrigin]]

struct BodyDesc #

mojo
struct BodyDesc(Copyable, Movable)

Creation parameters for PhysicsWorld.create_body. The constructor takes the shape, position (metres), motion type, object layer and rotation; the other fields keep their defaults until set: friction 0.5, restitution 0, linear and angular damping 0.05, gravity factor 1, not a sensor, discrete motion quality, sleeping allowed, user data 0, zero initial linear velocity (ignored for static bodies) and mass 0. The shape is borrowed: the caller still destroys it with destroy_shape.

Fields:

  • shape: Shape

  • position: Vec3

  • rotation: Quat

  • motion: MotionType

  • layer: Int

  • friction: Float32

  • restitution: Float32

  • linear_damping: Float32

  • angular_damping: Float32

  • gravity_factor: Float32

  • is_sensor: Bool — Sensors report contacts but exert no forces (triggers).

  • continuous: Bool — Linear-cast motion quality for fast small bodies.

  • allow_sleeping: Bool

  • user_data: UInt64

  • linear_velocity: Vec3

  • mass: Float32 — Dynamic bodies: 0 derives the mass from the shape volume and Jolt's default density (1000 kg/m^3); otherwise this mass with the shape's inertia tensor scaled to match.

  • __init__ __init__( out self, shape: Shape, position: Vec3, motion: MotionType, layer: Int, rotation: Quat = Quat.identity(), )

struct PhysicsEventKind #

mojo
struct PhysicsEventKind(Equatable, ImplicitlyCopyable, Movable, Writable)

Kind of a PhysicsEvent; writes as its snake_case name.

Fields:

  • value: UInt8

  • contact_added contact_added() -> PhysicsEventKind
    Two bodies started touching.

  • contact_persisted contact_persisted() -> PhysicsEventKind
    Two bodies that were touching are still touching.

  • contact_removed contact_removed() -> PhysicsEventKind
    Two bodies stopped touching; the event carries only the body ids.

  • body_activated body_activated() -> PhysicsEventKind
    A body woke up; the event carries the body and its user_data.

  • body_deactivated body_deactivated() -> PhysicsEventKind
    A body went to sleep; the event carries the body and its user_data.

  • write_to write_to(self, mut w: Some[Writer])

struct PhysicsEvent #

mojo
struct PhysicsEvent(ImplicitlyCopyable, Movable)

One queued Jolt event. Contacts: body1/body2, world normal (from body 1 to body 2), first contact point on body 1 and the manifold contact_count. Activation: body1 and the body's user_data.

Fields:

  • kind: PhysicsEventKind

  • body1: UInt32

  • body2: UInt32

  • normal: Vec3

  • point: Vec3

  • contact_count: Int

  • user_data: UInt64

  • involves involves(self, body: UInt32) -> Bool
    Whether body is body1 or body2.

  • pair pair(self, a: UInt32, b: UInt32) -> Bool
    Whether the event is between bodies a and b, in either order.

struct RayHit #

mojo
struct RayHit(ImplicitlyCopyable, Movable)

Closest hit of PhysicsWorld.raycast: the body id, fraction of the ray (the hit is at origin + fraction * direction), the world point and the hit surface normal.

Fields:

  • body: UInt32
  • fraction: Float32
  • point: Vec3
  • normal: Vec3

struct ShapeHit #

mojo
struct ShapeHit(ImplicitlyCopyable, Movable)

Closest hit of PhysicsWorld.shape_cast: the body id, fraction of the sweep, the contact point on the hit body, that body's surface normal, and penetration_depth, which is greater than zero when the shape already overlaps at the start.

Fields:

  • body: UInt32
  • fraction: Float32
  • point: Vec3
  • normal: Vec3
  • penetration_depth: Float32

struct PhysicsWorld #

mojo
struct PhysicsWorld(Movable)

One Jolt physics system with its thread pool, layer filters and event queue. Shapes are Shape handles that the caller destroys once with destroy_shape; bodies are UInt32 ids that the caller destroys with destroy_body. Shapes, bodies and characters are counted in live_objects(), which should be zero before the world is destroyed; the destructor prints leak_report() when it is not. Destroying an unknown shape or body is ignored and recorded as an ERROR in diagnostics. The world must not move while a CharacterController points at it, and the NativePtr library must outlive it. Call every method from one thread (the fixed-step thread); Jolt's workers run only inside step. The constructor raises on a layer count outside [1, MAX_LAYERS], a collision pair out of range, or a failed Jolt allocation.

Fields:

  • lib: NativePtr

  • layers: LayerConfig

  • queue: Optional[Pointer[CnEventQueue, MutUntrackedOrigin]]

  • jobs: Optional[Pointer[JPH_JobSystem, MutUntrackedOrigin]]

  • bp_layers: Optional[

  • pair_filter: Optional[

  • bp_filter: Optional[

  • system: Optional[Pointer[JPH_PhysicsSystem, MutUntrackedOrigin]]

  • bodies: Optional[Pointer[JPH_BodyInterface, MutUntrackedOrigin]]

  • contact_listener: Optional[

  • activation_listener: Optional[

  • body_filter: Optional[Pointer[CnPhysicsBodyFilter, MutUntrackedOrigin]] — One reusable body filter for queries that ignore a body.

  • filters: List[_MaskFilter] — Layer-mask filters cached per distinct mask.

  • diagnostics: List[LogRecord] — ERROR records for misuse the world survived (destroying an unknown shape or body).

  • live_shapes: Int

  • live_bodies: Int

  • live_characters: Int — Maintained by CharacterController.

  • steps: Int

  • dropped_events: UInt64

  • __init__ __init__( out self, lib: NativePtr, layers: LayerConfig = LayerConfig.default(), max_bodies: Int = 4096, worker_threads: Int = 2, event_capacity: Int = 1 << 14, ) raises
    Creates the Jolt system. JPH_Init is idempotent in joltc, so every world calls it; the process-wide Jolt state is never shut down while a world exists.

  • set_gravity set_gravity(self, g: Vec3)
    Sets the gravity acceleration in m/s^2 (scaled per body by BodyDesc.gravity_factor).

  • optimize_broad_phase optimize_broad_phase(self)
    Call after creating many static bodies and before the first step.

  • live_objects live_objects(self) -> Int
    Shapes, bodies and characters still alive. Should be zero before the world is destroyed; the destructor prints leak_report() when it is not, but does not abort.

  • leak_report leak_report(self) -> Optional[LogRecord]
    The ERROR record the destructor prints when live_objects() is not zero (category physics, with shapes, bodies and characters fields), or None without leaks.

  • error_count error_count(self) -> Int
    Number of ERROR records in diagnostics.

  • box box( mut self, half_extents: Vec3, convex_radius: Float32 = 0.05 ) raises -> Shape
    Creates a box shape with half_extents in metres; convex_radius (metres) rounds its edges. The caller destroys it with destroy_shape. Raises when Jolt fails to create it.

  • sphere sphere(mut self, radius: Float32) raises -> Shape
    Creates a sphere shape of radius metres. The caller destroys it with destroy_shape. Raises when Jolt fails to create it.

  • capsule capsule( mut self, half_height: Float32, radius: Float32 ) raises -> Shape
    Capsule along Y: a cylinder of 2 * half_height capped by hemispheres of radius.

  • cylinder cylinder( mut self, half_height: Float32, radius: Float32 ) raises -> Shape
    Creates a cylinder along Y, 2 * half_height tall with radius (metres). The caller destroys it with destroy_shape. Raises when Jolt fails to create it.

  • convex_hull convex_hull( mut self, points: List[Vec3], convex_radius: Float32 = 0.05 ) raises -> Shape
    Creates the convex hull of points (metres, shape space); convex_radius rounds it. The caller destroys it with destroy_shape. Raises with fewer than 4 points or when Jolt cannot build the hull.

  • mesh mesh( mut self, vertices: List[Vec3], indices: List[UInt32] ) raises -> Shape
    Static triangle mesh collider (Jolt mesh shapes only work on static or kinematic bodies).

  • scaled scaled(mut self, shape: Shape, scale: Vec3) raises -> Shape
    Creates a shape that is shape scaled per axis by scale. It is a new tracked shape: destroy it with destroy_shape in addition to the original. Raises when Jolt fails to create it.

  • destroy_shape destroy_shape(mut self, shape: Shape)
    Releases the caller's reference to shape and decrements the live count; bodies created from it keep their own reference. Call once per created shape and do not use its copies afterwards; a null shape is ignored. A shape this world did not create, or one already destroyed, is not released again: the count is left alone and an ERROR record is added to diagnostics.

  • create_body create_body( mut self, desc: BodyDesc, activate: Bool = True ) raises -> UInt32
    Creates a body from desc, adds it to the world (awake when activate) and returns its id; destroy it with destroy_body. The body keeps its own reference to the shape, so the shape handle may be destroyed right after. A positive desc.mass is applied to dynamic bodies only. Raises when the layer is out of range, the shape is null, or Jolt's body limit (max_bodies) is reached.

  • destroy_body destroy_body(mut self, id: UInt32)
    Removes body id from the world and destroys it. Jolt may reuse the index, but with a new sequence number, so a stale id never names the new body. An id that is not a live body of this world (INVALID_BODY, never created, already destroyed) is ignored: the live count is left alone and an ERROR record is added to diagnostics.

  • position position(self, id: UInt32) -> Vec3
    World position of body id, in metres.

  • rotation rotation(self, id: UInt32) -> Quat
    World rotation of body id.

  • transform transform(self, id: UInt32) -> Mat4
    World matrix of body id, with unit scale.

  • set_position_rotation set_position_rotation( self, id: UInt32, position: Vec3, rotation: Quat, activate: Bool = True )
    Teleports body id to position (metres) and rotation, waking it when activate. Kinematic bodies that should carry riders are moved with move_kinematic instead.

  • move_kinematic move_kinematic( self, id: UInt32, target_position: Vec3, target_rotation: Quat, dt: Float32, )
    Sets a kinematic body's velocity so it reaches the target over dt (next step).

  • linear_velocity linear_velocity(self, id: UInt32) -> Vec3
    Linear velocity of body id, in metres per second.

  • set_linear_velocity set_linear_velocity(self, id: UInt32, velocity: Vec3)
    Sets the linear velocity of body id, in metres per second.

  • angular_velocity angular_velocity(self, id: UInt32) -> Vec3
    Angular velocity of body id, in radians per second.

  • set_angular_velocity set_angular_velocity(self, id: UInt32, velocity: Vec3)
    Sets the angular velocity of body id, in radians per second.

  • add_force add_force(self, id: UInt32, force: Vec3)
    Adds force (newtons) to body id at its centre of mass.

  • add_impulse add_impulse(self, id: UInt32, impulse: Vec3)
    Applies impulse (newton seconds) to body id at its centre of mass; wakes a sleeping body.

  • add_impulse_at add_impulse_at(self, id: UInt32, impulse: Vec3, point: Vec3)
    Applies impulse (newton seconds) to body id at the world-space point (metres), which can also spin it.

  • is_active is_active(self, id: UInt32) -> Bool
    Whether body id is awake.

  • activate activate(self, id: UInt32)
    Wakes body id.

  • set_motion_type set_motion_type(self, id: UInt32, motion: MotionType)
    Changes the motion type of body id and wakes it. Mesh shapes work only on static or kinematic bodies.

  • motion_type motion_type(self, id: UInt32) -> MotionType
    Current motion type of body id.

  • user_data user_data(self, id: UInt32) -> UInt64
    The 64-bit user value stored on body id (BodyDesc.user_data or set_user_data).

  • set_user_data set_user_data(self, id: UInt32, value: UInt64)
    Stores a 64-bit user value on body id; activation events report it.

  • body_count body_count(self) -> Int
    Number of bodies in the Jolt system.

  • step step(mut self, dt: Float32, collision_steps: Int = 1) raises
    Advances the simulation by dt. Deterministic for a given sequence of calls and inputs (Jolt's deterministic mode is on by default).

  • poll_events poll_events(mut self) -> List[PhysicsEvent]
    Drains the listener queue (call once per step after step).

  • raycast raycast( mut self, origin: Vec3, direction: Vec3, layer_mask: UInt32 = 0xFFFFFFFF, ignore_body: UInt32 = INVALID_BODY, ) -> Optional[RayHit]
    Closest hit along origin + t * direction, t in [0, 1] (direction carries the range). layer_mask selects object layers; ignore_body is skipped.

  • shape_cast shape_cast( mut self, shape: Shape, transform: Mat4, direction: Vec3, layer_mask: UInt32 = 0xFFFFFFFF, ignore_body: UInt32 = INVALID_BODY, ) -> Optional[ShapeHit]
    Closest hit of shape at transform swept by direction (t in [0, 1]).

View as MarkdownSource: docs/api/physics.md