From many particles to one rigid body
Imagine a body as particles with masses , world positions , and velocities . Its total mass and center of mass are:
Linear momentum compresses all translational motion into one vector:
Angular momentum about the center of mass records rotational motion:
The last equality is frame-sensitive: and must be expressed in the same frame. With no external force, is constant. With no external torque, world-frame is constant—even though its body-frame components may dance.
A force changes translation; its lever arm changes rotation
Newton's translational and rotational laws have matching shapes:
A force applied at an offset from the center of mass produces torque:
Only the perpendicular component contributes. In the reference lab, and yield . Point the force along the lever arm and the torque becomes zero.
offset = np.array([0.3, 0.0, 0.0])
force = np.array([0.0, 5.0, 0.0])
torque = np.cross(offset, force)
assert np.allclose(torque, [0.0, 0.0, 1.5])Experiment 1: equal torque, unequal response
Mass measures resistance to translational acceleration. The inertia tensor measures resistance to angular acceleration and also depends on how mass is distributed.
For a box of full side lengths aligned with its principal body axes:
At rest, Euler's rotation equation reduces to . Away from rest, the coupling term matters:
Push a cube and a rod with the same torque
If mass and torque match, how much does shape alone change angular acceleration?
A 0.2 m cube and a 0.8 × 0.2 × 0.2 m rod each have mass 1 kg. LavenderSim applies the same 0.02 N·m world-z torque. The rod's z inertia is 8.5 times larger, so the cube's angular acceleration is 8.5 times larger.

Failure modes to try: increase the timestep until integration error is visible; add damping and notice that the velocity ratio is no longer exactly constant; rotate a non-symmetric body and remember that diagonal body inertia is not diagonal in world coordinates.
- Expected result
- The command exits successfully and reproduces the numerical or visual relationship described in this card.
- Failure modes
- Non-finite values, a reversed trend, a failed assertion, or a materially different plot means the assumptions, seed, timestep, build, or backend should be inspected before continuing.
For body-to-world rotation , the same physical inertia written in world coordinates is:
apply_inv_inertia() rotates a world vector into the body frame, applies diagonal inverse inertia, then rotates the result back. This is the compact equivalent of multiplying by .Orientation is not three independent numbers
A rotation matrix has nine entries but only three degrees of freedom. It must satisfy and . Axis-angle stores a unit axis and one angle . A unit quaternion stores the same rotation as:
LavenderSim's public convention is (x, y, z, w), with the scalar component last. Quaternions and describe the same orientation. They avoid Euler-angle singularities, but their unit-length constraint must still be maintained numerically.
Experiment 2: Euler-angle addition versus quaternions
Integrate a constant world angular velocity
What breaks if we treat angular velocity as three Euler-angle derivatives?
For a world-frame angular velocity, quaternion kinematics are:
The lab integrates this equation while a deliberately incorrect method adds directly to XYZ Euler angles. It also integrates a second quaternion without normalization.

Failure modes to try: use a single-axis rotation, which can hide the Euler-angle bug; enlarge dt, which increases quaternion truncation error; swap world- and body-frame multiplication order.
- Expected result
- The command exits successfully and reproduces the numerical or visual relationship described in this card.
- Failure modes
- Non-finite values, a reversed trend, a failed assertion, or a materially different plot means the assumptions, seed, timestep, build, or backend should be inspected before continuing.
If is expressed in the body frame instead, multiplication moves to the other side:
integrate_orientation() uses the world-frame form, takes an explicit quaternion step, and normalizes after every substep.Experiment 3: steady spin and tumbling
In principal body axes, torque-free motion follows Euler's equations:
Spin exactly around a principal axis and the other components stay zero. Start with components on several axes and the body-frame angular velocity evolves, even though world angular momentum and rotational kinetic energy remain constant:
Integrate torque-free Euler equations
How can angular velocity change when no external torque acts?

Failure modes to try: replace RK4 with explicit Euler and plot energy drift; forget whether is in the body or world frame; test rotation near the intermediate principal axis.
- Expected result
- The command exits successfully and reproduces the numerical or visual relationship described in this card.
- Failure modes
- Non-finite values, a reversed trend, a failed assertion, or a materially different plot means the assumptions, seed, timestep, build, or backend should be inspected before continuing.
Experiment 4: trade angular momentum internally
A reaction wheel motor exerts equal and opposite torques on its rotor and housing. In an ideal isolated system:
Command ReactionWheelCube-v0
Can an internal actuator rotate a body without pushing on the outside world?
A positive x-wheel command makes every recorded wheel sample positive and every cube sample negative.

Failure modes to try: saturate the actuator, reverse the command, or command two wheels simultaneously and inspect cross-axis coupling.
- Expected result
- The command exits successfully and reproduces the numerical or visual relationship described in this card.
- Failure modes
- Non-finite values, a reversed trend, a failed assertion, or a materially different plot means the assumptions, seed, timestep, build, or backend should be inspected before continuing.
Let Python choose what the browser explains
Live frames now accept Python-defined body axes and vectors. That makes the visualization part of an experiment rather than a hard-coded renderer feature.
viewer.publish_frame(
sim,
overlays={
"axes": [{"body": "cube", "scale": 0.34}],
"vectors": [
{
"label": "angular velocity",
"origin": cube_position.tolist(),
"vector": cube_omega.tolist(),
"scale": 0.12,
"color": [0.51, 0.87, 0.76, 0.95],
},
{
"label": "torque",
"origin": cube_position.tolist(),
"vector": commanded_torque.tolist(),
"scale": 3.0,
"color": [0.95, 0.66, 0.77, 0.95],
},
],
},
)The browser rotates the three body axes using authoritative body transforms and draws vector arrows in world coordinates. The same payload path can display angular momentum or any diagnostic vector computed by a Python controller.
Paw-check: reason in the right frame
- Double every mass in the center-of-mass example. Which quantities change, and which remain fixed?
- Move the force application point from 0.3 m to −0.3 m. Predict the torque before running the code.
- Rotate the rod 90° about y, apply world-z torque, and compute the relevant world inertia.
- Modify the orientation lab to use body-frame angular velocity and the corresponding quaternion multiplication order.
- Start torque-free motion almost—but not exactly—on each principal axis. Which axis is least forgiving?
- Reverse the reaction-wheel command and assert that both signs reverse.
What should I be able to say now?
A rigid body is summarized by center-of-mass translation and orientation. Forces change linear momentum; torques change angular momentum. Inertia depends on mass distribution and coordinate frame. Angular velocity is not an Euler-angle derivative, while unit quaternions provide a nonsingular orientation representation that must be integrated with the correct frame convention and renormalized. Torque-free body-frame motion can tumble even while world angular momentum is conserved.