Skip to content

3D physics engine

StableTested and in active use. Breaking changes will be called out. Newton is the only solver that is a MuJoCo port; PGS and CG are our own algorithms. Flex, SDF geoms and several actuator types are not modelled.

noeira.physics3d simulates articulated rigid bodies in generalized coordinates — joint space rather than world space — following MuJoCo’s design routine by routine. Models are MJCF XML, the format MuJoCo reads, and the reference it is checked against is the MuJoCo 3.12.0 runtime pixi installs.

Working in joint space means constraints like “this hinge has one degree of freedom” are structural rather than enforced numerically. A hinge joint is one number.

Ant27 obs · 8 actuators · contact-rich
One stepThe tables further down name the algorithms; this is where each of them runs.forwardkinematicsqpos → xpos, xquatcollisiondetectionSAP broadphaseprimitives · GJKconstraintbuildingcontact JacobiansCRBA · RNEconstraintsolvingNewton · CG · PGSpyramidal | ellipticintegrationeuler · implicitrk4The whole pipeline runs batched on GPU, one environment per block for the contact solve.That block kernel is why the hazards cluster on one stage: it will not survive CUDA-graphcapture, it hard-crashes Metal, and its elliptic friction cone cannot be combined with RK4.
straightforward on GPUwhere the GPU caveats live

State lives in Data (qpos, qvel, xpos, xquat, sensordata, …); the model lives in Model. Both are per-field tensors in physics3d/fields/, so each kernel binds exactly the arrays it reads. The stage order is MuJoCo’s mj_step: kinematics and position-stage sensors, velocities and velocity-stage sensors, CRBA and factorization, RNE, passive and actuator forces, collision, the constraint solve, acceleration-stage sensors, integration.

PackageContents
kinematics/forward kinematics, quaternion math, site and camera frames, mocap bodies
dynamics/CRBA, RNE, LDL and LU, tendons and wrapping, actuation, gravity compensation, fluid forces, OSC and site IK
collision/broadphase, analytic pairs, GJK/EPA, multi-contact, hulls, heightfields — see Collision
constraints/contact, limit, friction, equality and tendon rows — see Constraints and tendons
solver/Newton (per-env and blocked), CG, island PGS, elliptic cone, noslip, warm start
integrator/Euler, RK4, implicit / implicitfast
sensors/the <sensor> table and its per-type kernels
ray/ray casting against every geom type, mesh BVH
parser/MJCF reader, <attach> expander, compile-time and runtime model builders
studio/the model inspector’s editing, validation and stepping logic
StageImplementation
Mass matrixCRBA, dense or tree-walk (CRBA_TREEWALK), then LDL
Bias forcesRNE — Coriolis, centrifugal, gravity; rne_post fills cacc / cfrc_int / cfrc_ext for the force sensors
Passive forcesjoint springs and dampers, tendon springs, gravity compensation (<body gravcomp>), fluid forces (inertia-box model, <option wind>)
Tendonsfixed tendons, spatial tendons through sites, sphere and cylinder wrapping with side sites (dynamics/tendon.mojo, dynamics/wrap.mojo)
Actuationmotor, position (kp, kv, dampratio), velocity, general affine gain/bias, adhesion, the mujoco.pid plugin; ctrlrange / forcerange / actuatorfrcrange; filter / filterexact / integrator activation dynamics
Transmissionsjoint, fixed tendon, spatial tendon, site (6-vector gear)
Operational-space controlrobosuite 1.4’s OSC_POSE, the controller LIBERO’s demonstrations were recorded with, one arithmetic for host and device (dynamics/osc_pose_gpu.mojo)
Site IKdm_control’s damped least-squares qpos_from_site_pose (dynamics/ik_site.mojo), used by manipulation resets
Velocity derivativesqderiv — actuator and RNE derivatives for the implicit integrators

Spatial tendon wrapping and the site / spatial-tendon transmissions are CPU-only. The compile-time model builder refuses an actuator the GPU actuation kernel cannot drive, so a batched model cannot silently lose one.

IntegratorNotes
eulersemi-implicit with implicit joint damping, honours <flag eulerdamp>; HalfCheetah, Pusher, Sawyer and the dm_control suite use it
rk4four stages, constraint re-solve per stage; the environment trait’s default, so Hopper, Walker2d, Ant, Swimmer and Humanoid use it
implicit / implicitfastM − h·qDeriv, LU-factored; used by the studio for models that ask for it

The choice is a compile-time constant on the environment config, along with any workspace it requires:

comptime INTEGRATOR: StaticString = "rk4"
comptime INTEGRATOR_WS_EXTRA: Int = rk4_extra_workspace_size[NQ, NV]()
SolverStatus
newtonA port of MuJoCo’s Newton: warm start, the 3-phase line search, incremental Hessian updates, elliptic-cone Hessian, <option iterations tolerance ls_*>. The environment default.
Newton, blockedOne environment per GPU block with the matrices in shared memory. Pyramidal and elliptic cones, bit-identical to the per-env kernel on the gated fixtures. NVIDIA only.
cgPolak-Ribière nonlinear CG with a fixed 100-iteration cap; not MuJoCo’s algorithm
pgs, islandacceleration-level projected Gauss-Seidel, and a variant that freezes converged contact islands; not MuJoCo’s algorithms
noslipmj_solNoSlip for both cones, run after Newton when the model sets noslip_iterations

Contacts use a pyramidal or elliptic friction cone at condim 1, 3, 4 or 6. The cone comes from the model’s <option cone>; the solver is a compile-time parameter of the integrator (SOLVER), and the environment facades build Newton. Constraints and tendons covers which row types each solver handles.

<sensor> is parsed into a sensor table, Data carries sensordata, and sensors/eval.mojo runs MuJoCo’s three stage passes — position, velocity, acceleration — at the points mj_forward runs them, with cutoff applied. Served kinds:

jointpos · jointvel · jointactuatorfrc · actuatorpos · tendonpos
framepos · framequat · framexaxis · frameyaxis · framezaxis
framelinvel · frameangvel · subtreecom · subtreelinvel · subtreeangmom
accelerometer · velocimeter · gyro · force · torque · touch
rangefinder · magnetometer

A declared kind that is not served still gets its address, so every later sensor lands at MuJoCo’s offset; its slot reads NaN and looking it up by name raises. The acceleration-stage sensors that read cacc / cfrc_int need RNE_POST=True, which only the Euler integrator wires. tendonpos is CPU-only in a batch.

Two routes into the same parser:

# Compile time — the model's dimensions become part of its type.
from noeira.physics3d.parser import ModelDefFromXML
# Run time — one binary, any file.
from noeira.physics3d.parser import (
parse_model_runtime, dims_from_flat, build_model_runtime,
)
var fmd = parse_model_runtime("path/to/scene.xml")
var dims = dims_from_flat(fmd, max_contacts=20)

Every shipped environment uses the compile-time route; the runtime route is what the studio uses to open any file, and it is CPU-only. The parser covers default-class chains, <include>, <attach> with name prefixing and unit conversion, <frame pos quat>, the three inertia modes, settotalmass, meshes (STL and OBJ), heightfields, keyframes and <pair> / <exclude>. Gymnasium’s MuJoCo files, the dm_control suite and the Menagerie scenes the validation sweeps use are read as published, not edited copies.

A feature MuJoCo has and this engine does not is counted and printed at load, or refused, rather than dropped. The MuJoCo 3.12 audit tallied those prints over 83 Menagerie scenes and every dm_control asset and got zero — see Validation.

The pipeline runs batched on GPU. Most stages run one thread per environment, with cooperative (PARALLEL_GPU) variants of FK, CRBA, the LDL factor and RNE that are bit-exact against the serial kernels.

Two stages have block-per-environment kernels on NVIDIA: the blocked Newton solver, and a block collision kernel that runs a warp’s candidate pairs on the same routine. Metal keeps the per-environment kernels — the blocked solver’s threadgroup memory exceeds Metal’s 32 KB limit on most models — so an Apple run does not exercise the blocked paths, and pixi run -e nvidia test-physics3d-gpu is the gate for them.

Two measured numbers, each only meaningful with its setup:

MeasurementResult
Block collision kernel vs the serial kernel, 1024 sprawled Unitree G1 lanes, RTX 5090 (benchmarks/physics3d_gpu/bench_g1_collision.mojo)107.7 → 10.4 ms per launch, bit-identical output
Single-env CPU step time as a ratio of MuJoCo 3.10.0’s, Apple M1 Pro, interleaved runs (2026-09-06)humanoid 0.66×, hopper 0.67×, walker2d 0.73×, ant 0.78×, dog 0.82×, humanoid_CMU 0.91×, Sawyer 1.06×, a 3-brick reassembly scene 1.14×

A ratio below 1 is faster than MuJoCo. The CPU ratios predate the move to the 3.12 runtime and come from the compile-time path; the runtime-loaded path is slower on the same XML.