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 asFloat32, for convertingmax_slope_degreesto radians.
struct GroundState #
struct GroundState(Equatable, ImplicitlyCopyable, Movable, Writable)Ground contact state of a CharacterController, mirrored from Jolt's ground state.
Fields:
-
value: UInt8 -
on_groundon_ground() -> GroundState
Standing on a surface no steeper thanmax_slope_degrees. -
on_steep_groundon_steep_ground() -> GroundState
Touching a surface steeper thanmax_slope_degrees;updatetreats it like the air and applies gravity. -
not_supportednot_supported() -> GroundState
Touching something that cannot hold the character (its edge, a falling body). -
in_airin_air() -> GroundState
Touching nothing;updateapplies gravity. -
write_towrite_to(self, mut w: Some[Writer])
struct CharacterDesc #
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 #
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 -
positionposition(self) -> Vec3
World position of the character's feet, in metres. -
set_positionset_position(self, position: Vec3)
Teleports the character so its feet are atposition(metres). -
rotationrotation(self) -> Quat
World rotation of the character. -
set_rotationset_rotation(self, rotation: Quat)
Sets the world rotation of the character. -
velocityvelocity(self) -> Vec3
Linear velocity of the character in metres per second. -
ground_stateground_state(self) -> GroundState
Ground contact state from Jolt's last character update (seeGroundState). -
is_supportedis_supported(self) -> Bool
Whether Jolt considers the character supported by the ground it touches. -
ground_normalground_normal(self) -> Vec3
World-space surface normal of the ground contact. -
ground_velocityground_velocity(self) -> Vec3
Velocity of the ground under the character in metres per second (a moving platform's), refreshed at the start ofupdate. -
ground_bodyground_body(self) -> UInt32
Id of the body under the character's feet;INVALID_BODYwhen Jolt reports none. -
updateupdate( mut self, dt: Float32, desired_velocity: Vec3, jump_speed: Float32 = 0.0 )
Moves the character for one step.desired_velocityis the horizontal velocity the player asks for (its Y is ignored); a positivejump_speedlaunches the character when it stands on the ground. Call beforePhysicsWorld.stepwith the samedt.
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 ofRigidBody.
struct RigidBody #
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_idcomponent_id() -> Int
struct PhysicsSync #
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_bodiespull_bodies( mut self, mut world: World, physics: PhysicsWorld ) raises -> Int
After a physics step: copies each dynamic body's pose into itsRigidBody, keeping the previous pose for interpolation. Returns the number of bodies read.push_kinematicspush_kinematics( mut self, mut world: World, physics: PhysicsWorld, dt: Float32 ) raises -> Int
Before a physics step: kinematic bodies whoseLocalTransformchanged since the last push are moved so they reach the new pose during this step. Returns the count.write_transformswrite_transforms( mut self, mut world: World, alpha: Float32 ) raises -> Int
WritesLocalTransformfor dynamic bodies atalphain [0, 1] between the previous and current poses. Bodies whose id isINVALID_BODYare skipped, as inpull_bodies. Only changed transforms are marked. Returns the count updated.
def pull_bodies #
def pull_bodies(mut world: World, physics: PhysicsWorld) raises -> IntPhysicsSync.pull_bodies through a temporary PhysicsSync, so it allocates query scratch on every call. Returns the number of dynamic bodies read.
def push_kinematics #
def push_kinematics( mut world: World, physics: PhysicsWorld, dt: Float32 ) raises -> IntPhysicsSync.push_kinematics through a temporary PhysicsSync, so it allocates query scratch on every call. Returns the number of kinematic bodies moved.
def write_transforms #
def write_transforms(mut world: World, alpha: Float32) raises -> IntPhysicsSync.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 #
def to_jolt(v: Vec3) -> JPH_Vec3Converts an engine Vec3 to a joltc JPH_Vec3.
def from_jolt #
def from_jolt(v: JPH_Vec3) -> Vec3Converts a joltc JPH_Vec3 to an engine Vec3.
def quat_to_jolt #
def quat_to_jolt(q: Quat) -> JPH_QuatConverts an engine Quat to a joltc JPH_Quat (x, y, z, w).
def quat_from_jolt #
def quat_from_jolt(q: JPH_Quat) -> QuatConverts a joltc JPH_Quat to an engine Quat.
def mat_to_jolt #
def mat_to_jolt(m: Mat4) -> JPH_Mat4Column-major on both sides.
struct LayerConfig #
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) -
collidecollide(mut self, a: Int, b: Int)
Enables collision between layersaandb(symmetric). -
defaultdefault() -> LayerConfig
Two layers: 0 static, 1 moving; moving collides with both.
struct MotionType #
struct MotionType(Equatable, ImplicitlyCopyable, Movable)How a body moves: static, kinematic (driven by the caller) or dynamic (simulated).
Fields:
-
value: UInt8 -
staticstatic() -> MotionType
A body that never moves. -
kinematickinematic() -> MotionType
A body moved by the caller (move_kinematic,set_position_rotation), not by forces or contacts. -
dynamicdynamic() -> MotionType
A body simulated by Jolt: gravity, forces, impulses and contacts move it. -
to_joltto_jolt(self) -> JPH_MotionType
The matching joltcJPH_MotionType; any value other than static or kinematic maps to dynamic.
struct Shape #
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 #
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 #
struct PhysicsEventKind(Equatable, ImplicitlyCopyable, Movable, Writable)Kind of a PhysicsEvent; writes as its snake_case name.
Fields:
-
value: UInt8 -
contact_addedcontact_added() -> PhysicsEventKind
Two bodies started touching. -
contact_persistedcontact_persisted() -> PhysicsEventKind
Two bodies that were touching are still touching. -
contact_removedcontact_removed() -> PhysicsEventKind
Two bodies stopped touching; the event carries only the body ids. -
body_activatedbody_activated() -> PhysicsEventKind
A body woke up; the event carries the body and itsuser_data. -
body_deactivatedbody_deactivated() -> PhysicsEventKind
A body went to sleep; the event carries the body and itsuser_data. -
write_towrite_to(self, mut w: Some[Writer])
struct PhysicsEvent #
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 -
involvesinvolves(self, body: UInt32) -> Bool
Whetherbodyisbody1orbody2. -
pairpair(self, a: UInt32, b: UInt32) -> Bool
Whether the event is between bodiesaandb, in either order.
struct RayHit #
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: UInt32fraction: Float32point: Vec3normal: Vec3
struct ShapeHit #
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: UInt32fraction: Float32point: Vec3normal: Vec3penetration_depth: Float32
struct PhysicsWorld #
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 byCharacterController. -
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_Initis idempotent in joltc, so every world calls it; the process-wide Jolt state is never shut down while a world exists. -
set_gravityset_gravity(self, g: Vec3)
Sets the gravity acceleration in m/s^2 (scaled per body byBodyDesc.gravity_factor). -
optimize_broad_phaseoptimize_broad_phase(self)
Call after creating many static bodies and before the first step. -
live_objectslive_objects(self) -> Int
Shapes, bodies and characters still alive. Should be zero before the world is destroyed; the destructor printsleak_report()when it is not, but does not abort. -
leak_reportleak_report(self) -> Optional[LogRecord]
The ERROR record the destructor prints whenlive_objects()is not zero (categoryphysics, withshapes,bodiesandcharactersfields), or None without leaks. -
error_counterror_count(self) -> Int
Number of ERROR records indiagnostics. -
boxbox( mut self, half_extents: Vec3, convex_radius: Float32 = 0.05 ) raises -> Shape
Creates a box shape withhalf_extentsin metres;convex_radius(metres) rounds its edges. The caller destroys it withdestroy_shape. Raises when Jolt fails to create it. -
spheresphere(mut self, radius: Float32) raises -> Shape
Creates a sphere shape ofradiusmetres. The caller destroys it withdestroy_shape. Raises when Jolt fails to create it. -
capsulecapsule( mut self, half_height: Float32, radius: Float32 ) raises -> Shape
Capsule along Y: a cylinder of2 * half_heightcapped by hemispheres ofradius. -
cylindercylinder( mut self, half_height: Float32, radius: Float32 ) raises -> Shape
Creates a cylinder along Y,2 * half_heighttall withradius(metres). The caller destroys it withdestroy_shape. Raises when Jolt fails to create it. -
convex_hullconvex_hull( mut self, points: List[Vec3], convex_radius: Float32 = 0.05 ) raises -> Shape
Creates the convex hull ofpoints(metres, shape space);convex_radiusrounds it. The caller destroys it withdestroy_shape. Raises with fewer than 4 points or when Jolt cannot build the hull. -
meshmesh( mut self, vertices: List[Vec3], indices: List[UInt32] ) raises -> Shape
Static triangle mesh collider (Jolt mesh shapes only work on static or kinematic bodies). -
scaledscaled(mut self, shape: Shape, scale: Vec3) raises -> Shape
Creates a shape that isshapescaled per axis byscale. It is a new tracked shape: destroy it withdestroy_shapein addition to the original. Raises when Jolt fails to create it. -
destroy_shapedestroy_shape(mut self, shape: Shape)
Releases the caller's reference toshapeand 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 todiagnostics. -
create_bodycreate_body( mut self, desc: BodyDesc, activate: Bool = True ) raises -> UInt32
Creates a body fromdesc, adds it to the world (awake whenactivate) and returns its id; destroy it withdestroy_body. The body keeps its own reference to the shape, so the shape handle may be destroyed right after. A positivedesc.massis 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_bodydestroy_body(mut self, id: UInt32)
Removes bodyidfrom 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 todiagnostics. -
positionposition(self, id: UInt32) -> Vec3
World position of bodyid, in metres. -
rotationrotation(self, id: UInt32) -> Quat
World rotation of bodyid. -
transformtransform(self, id: UInt32) -> Mat4
World matrix of bodyid, with unit scale. -
set_position_rotationset_position_rotation( self, id: UInt32, position: Vec3, rotation: Quat, activate: Bool = True )
Teleports bodyidtoposition(metres) androtation, waking it whenactivate. Kinematic bodies that should carry riders are moved withmove_kinematicinstead. -
move_kinematicmove_kinematic( self, id: UInt32, target_position: Vec3, target_rotation: Quat, dt: Float32, )
Sets a kinematic body's velocity so it reaches the target overdt(next step). -
linear_velocitylinear_velocity(self, id: UInt32) -> Vec3
Linear velocity of bodyid, in metres per second. -
set_linear_velocityset_linear_velocity(self, id: UInt32, velocity: Vec3)
Sets the linear velocity of bodyid, in metres per second. -
angular_velocityangular_velocity(self, id: UInt32) -> Vec3
Angular velocity of bodyid, in radians per second. -
set_angular_velocityset_angular_velocity(self, id: UInt32, velocity: Vec3)
Sets the angular velocity of bodyid, in radians per second. -
add_forceadd_force(self, id: UInt32, force: Vec3)
Addsforce(newtons) to bodyidat its centre of mass. -
add_impulseadd_impulse(self, id: UInt32, impulse: Vec3)
Appliesimpulse(newton seconds) to bodyidat its centre of mass; wakes a sleeping body. -
add_impulse_atadd_impulse_at(self, id: UInt32, impulse: Vec3, point: Vec3)
Appliesimpulse(newton seconds) to bodyidat the world-spacepoint(metres), which can also spin it. -
is_activeis_active(self, id: UInt32) -> Bool
Whether bodyidis awake. -
activateactivate(self, id: UInt32)
Wakes bodyid. -
set_motion_typeset_motion_type(self, id: UInt32, motion: MotionType)
Changes the motion type of bodyidand wakes it. Mesh shapes work only on static or kinematic bodies. -
motion_typemotion_type(self, id: UInt32) -> MotionType
Current motion type of bodyid. -
user_datauser_data(self, id: UInt32) -> UInt64
The 64-bit user value stored on bodyid(BodyDesc.user_dataorset_user_data). -
set_user_dataset_user_data(self, id: UInt32, value: UInt64)
Stores a 64-bit user value on bodyid; activation events report it. -
body_countbody_count(self) -> Int
Number of bodies in the Jolt system. -
stepstep(mut self, dt: Float32, collision_steps: Int = 1) raises
Advances the simulation bydt. Deterministic for a given sequence of calls and inputs (Jolt's deterministic mode is on by default). -
poll_eventspoll_events(mut self) -> List[PhysicsEvent]
Drains the listener queue (call once per step afterstep). -
raycastraycast( mut self, origin: Vec3, direction: Vec3, layer_mask: UInt32 = 0xFFFFFFFF, ignore_body: UInt32 = INVALID_BODY, ) -> Optional[RayHit]
Closest hit alongorigin + t * direction, t in [0, 1] (directioncarries the range).layer_maskselects object layers;ignore_bodyis skipped. -
shape_castshape_cast( mut self, shape: Shape, transform: Mat4, direction: Vec3, layer_mask: UInt32 = 0xFFFFFFFF, ignore_body: UInt32 = INVALID_BODY, ) -> Optional[ShapeHit]
Closest hit ofshapeattransformswept bydirection(t in [0, 1]).