Skip to content

forge3d.Body

A handle to a single rigid body in the simulation. Returned by every World.add_* call.


Class reference

Body

Handle to a simulated rigid body.

Returned by world.add_box(), world.add_sphere(), etc. Use properties to read the body's current state and methods to control it.

Examples

box = world.add_box(size=(1, 1, 1), position=(0, 0, 5), name="my_box") box.position array([0., 0., 5.]) box.name 'my_box' box.apply_force((0, 0, 10)) # applied on next world.step()

position property

position: ndarray

World-frame position (3,) in metres.

velocity property

velocity: ndarray

Linear velocity (3,) in m/s.

orientation property

orientation: ndarray

Unit quaternion [w, x, y, z].

angular_velocity property

angular_velocity: ndarray

Angular velocity (3,) in rad/s.

rotation_matrix property

rotation_matrix: ndarray

Body orientation as a 3×3 rotation matrix (world frame).

name property writable

name: str

Human-readable name assigned at creation. Can be changed at runtime.

is_static property

is_static: bool

True if this body does not move under physics forces.

is_sleeping property

is_sleeping: bool

True if this body is below the sleep velocity threshold for ≥ 1 s.

mass property

mass: float

Body mass in kg (0.0 for static bodies).

shape_type property

shape_type: str

Shape kind: 'box', 'sphere', 'capsule', 'mesh', etc.

shape_params property

shape_params: dict

Shape parameters dict (e.g. {'half_extents': array([0.5, 0.5, 0.5])}).

Returns a copy — mutating the dict does not affect the simulation.

friction property writable

friction: float

Coulomb friction coefficient (>= 0). Can be changed at runtime.

restitution property writable

restitution: float

Coefficient of restitution [0, 1]. Can be changed at runtime.

linear_damping property writable

linear_damping: float

Linear velocity damping coefficient (per second, >= 0).

Applied automatically each world.step(). A value of 0.1 removes ~10% of linear velocity per second.

angular_damping property writable

angular_damping: float

Angular velocity damping coefficient (per second, >= 0).

Applied automatically each world.step().

collision_layer property writable

collision_layer: int

Bit-field: which layer(s) this body belongs to.

collision_mask property writable

collision_mask: int

Bit-field: which layers this body detects collisions with.

apply_force

apply_force(force: Any) -> None

Accumulate a world-frame force (N) to apply on the next step.

Forces reset to zero after each :meth:World.step call.

Parameters

force : (3,) force vector in Newtons, world frame.

Notes

For per-frame forces use this instead of apply_impulse::

body.apply_force(np.array([0, 0, 50]))   # 50 N upward thrust
world.step(dt=1/60)                       # force applied here

apply_torque

apply_torque(torque: Any) -> None

Accumulate a world-frame torque (N·m) to apply on the next step.

Torques reset to zero after each :meth:World.step call.

set_position

set_position(position: Any) -> None

Instantly teleport this body to position (keeps orientation).

set_orientation

set_orientation(quat: Any) -> None

Instantly set orientation (keeps position). quat = [w, x, y, z].

set_velocity

set_velocity(vel: Any) -> None

Override linear velocity (m/s).

set_angular_velocity

set_angular_velocity(omega: Any) -> None

Override angular velocity (rad/s).

on_collision_begin

on_collision_begin(fn: Any) -> Any

Register a callback fired when this body first contacts another.

The callback receives (other: Body, event: CollisionEvent).

Can be used as a decorator::

@player.on_collision_begin
def hit(other, event):
    print(f"player hit {other.name}")

on_collision_end

on_collision_end(fn: Any) -> Any

Register a callback fired when this body separates from another.


Usage examples

Reading state

box = world.add_box(size=(1,1,1), position=(0,0,5), mass=2.0)
print(box.position)     # array([0., 0., 5.])
print(box.mass)         # 2.0
print(box.is_static)    # False
print(box.is_sleeping)  # False

Runtime setters (v1.1.0)

# Change physics material at runtime
box.friction    = 0.05   # ice-slippery (default: 0.5)
box.restitution = 0.95   # very bouncy  (default: 0.3)

Velocity damping (v1.1.0)

# Exponential decay — dt-corrected, FPS-independent
box.linear_damping  = 1.0  # removes 63% of speed per second
box.angular_damping = 2.0  # removes 86% of spin per second

# Applied automatically every world.step()
linear_damping Speed remaining after 1 s
0.0 100% (no damping)
0.5 61%
1.0 37%
2.0 14%

Applying forces

box.apply_force((0, 0, 50))    # 50 N upward (reset each step)
box.apply_torque((0, 0, 10))   # 10 N·m yaw torque

world.step(dt=1/60)
print(box.velocity)

Runtime name change

head = world.add_sphere(radius=0.21, position=(0,0,1.8), name="head")
head.name = "player_head"   # settable at any time
print(head.name)             # "player_head"

Shape query

capsule = world.add_capsule(radius=0.3, half_length=0.9, position=(0,0,2))
print(capsule.shape_type)     # "capsule"
print(capsule.shape_params)   # {'radius': 0.3, 'half_length': 0.9}

# Rotation as 3×3 matrix (alternative to .orientation quaternion)
R = capsule.rotation_matrix   # np.ndarray (3, 3)

Per-body collision callbacks

player = world.add_capsule(radius=0.3, half_length=0.9, name="player")
floor  = world.add_ground()

@player.on_collision_begin
def hit(other, event):
    print(f"player hit {other.name}  speed={event.relative_speed:.1f} m/s")

@player.on_collision_end
def left(other, event):
    print(f"player separated from {other.name}")

# Callbacks fire automatically inside world.step()

Collision layers

from forge3d import CollisionLayer

player = world.add_sphere(radius=0.5, position=(0,0,3), mass=1)
player.collision_layer = CollisionLayer.PLAYER
player.collision_mask  = CollisionLayer.mask_for(
    CollisionLayer.TERRAIN,
    CollisionLayer.ENEMY,
)  # collide only with terrain and enemies

ghost = world.add_sphere(radius=1.0, position=(5,0,0), mass=0.1)
ghost.collision_layer = CollisionLayer.NONE   # no collision