Skip to main content

Renderer-agnostic real-time rigid-body physics on the OMI glTF physics model

Project description

omi_physics

A renderer-agnostic, real-time rigid-body physics engine for Python, built natively on the OMI glTF physics data model. State lives in flat NumPy arrays, the whole step pipeline is vectorized, and optional compiled Cython accelerators drop in transparently for the hot paths (the contact solver and box/sphere collision).

  • NumPy is the only hard dependency. No graphics library is required to simulate.
  • PyOpenGL is optional — used solely by the experimental GPU compute backend (omi_physics.glcompute).
  • Deterministic on the CPU backend: the same inputs produce the same trajectory, run to run.
  • Fast: sweep-and-prune broadphase, SAT/GJK narrowphase, an island-parallel sequential-impulse solver, sleeping, and a background-threaded simulation loop that overlaps with a consumer's render/IO thread.

⚠️ This code is largely LLM-written. It has a test suite and the CPU backend is deterministic, but it comes with no guarantees of correctness, accuracy, or fitness for any purpose (see LICENSE, MIT). Review it before relying on it for anything that matters.

Install

pip install omi_physics            # core engine (NumPy only)
pip install "omi_physics[gl]"      # + PyOpenGL for the GPU compute backend

Prebuilt wheels ship the compiled accelerators, so no C compiler is needed. A source install without a compiler still works — the engine falls back to the identical pure-NumPy code paths.

Quick start

import numpy as np
from omi_physics import PhysicsWorld, model

world = PhysicsWorld(gravity=model.Gravity(gravity=9.81, direction=(0, -1, 0)))

# A static ground box and a dynamic box that falls onto it.
ground = world.add_shape(model.Shape.box((20, 1, 20)))
box    = world.add_shape(model.Shape.box((1, 1, 1)))
wood   = world.add_material(model.Material())

world.add_body(model.Motion(type=model.STATIC),
               collider=model.Collider(shape=ground, physicsMaterial=wood),
               position=(0, 0, 0))
falling = world.add_body(model.Motion(type=model.DYNAMIC, mass=1.0),
                         collider=model.Collider(shape=box, physicsMaterial=wood),
                         position=(0, 5, 0))

for _ in range(120):                       # 2 seconds at 60 Hz
    world.step(1 / 60)

print(world.position[falling])             # resting on the ground

The exact add_body/add_shape signatures are the source of truth in world.py; see the tests for worked examples.

Off-thread simulation

ThreadedSimulation steps the world on a daemon thread and publishes an immutable pose snapshot each tick. A renderer reads the latest snapshot every frame without ever blocking on the solver:

from omi_physics.threaded import ThreadedSimulation

sim = ThreadedSimulation(world, sim_hz=120)
sim.start()
# ... each render frame:
snapshot, version = sim.latest()           # (position, axis_angle, awake, dynamic)
# ... on shutdown:
sim.stop()

How it works

One world.step(dt) runs this fixed-timestep pipeline over the world's structure-of-arrays state:

flowchart LR
    A[integrate forces<br/>gravity · damping · drag] --> B[refit AABBs]
    B --> C[broadphase<br/>sweep & prune]
    C --> D[narrowphase<br/>SAT · GJK/EPA]
    D --> E[solver<br/>sequential impulse]
    E --> F[joints]
    F --> G[integrate positions]
    G --> H[sleeping]

The data model is OMI glTF physics (model.Shape, Motion, Collider, Material, Joint, ...), so scenes round-trip to and from glTF documents (omi_physics.omi_gltf). See docs/ for a deep dive:

Working on omi_physics

git clone https://github.com/mcfletch/omi_physics
cd omi_physics
python -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"          # editable install, builds the accelerators
pytest                            # run the test suite

Handy commands:

# Rebuild the accelerators in place after editing a .pyx
python setup.py build_ext --inplace

# Force the pure-Python fallback (delete the compiled modules)
rm -f src/omi_physics/*.so

# Type-check and lint
mypy src/omi_physics
ruff check src tests

The accelerators are a pure speedup: every .pyx has an identical NumPy/ Python implementation the engine uses when the compiled module is absent. Tests must pass in both configurations (with and without the .so files present).

Layout

src/omi_physics/        the engine (importable as omi_physics)
  model.py              OMI glTF physics data model
  world.py              PhysicsWorld — structure-of-arrays state + step()
  backend.py            NumpyBackend / GPU backend selection
  glcompute.py          optional GL 4.3 compute integration backend (needs PyOpenGL)
  broadphase.py         sweep-and-prune + dynamic AABB tree
  collide.py            SAT box-box, sphere tests (vectorized)
  gjk.py                GJK/EPA for convex shapes
  narrowphase.py        contact generation, routes to accelerators
  solver.py             island-parallel sequential-impulse contact solver
  joints.py             point / distance / hinge constraints and motors
  character.py          kinematic character controller
  cookery.py / hull.py  shape cooking (convex hulls, trimeshes)
  omi_gltf.py           read/write OMI physics from glTF documents
  threaded.py           ThreadedSimulation — background-thread stepping
  _solver_native.pyx    Cython contact solver accelerator
  _collide_native.pyx   Cython collision accelerator
tests/                  pytest suite (pure NumPy; GPU-parity tests skip w/o GL)
docs/                   deep-dive documentation (Markdown + Mermaid)

License

MIT — see LICENSE.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

omi_physics-0.1.0.tar.gz (418.9 kB view details)

Uploaded Source

File details

Details for the file omi_physics-0.1.0.tar.gz.

File metadata

  • Download URL: omi_physics-0.1.0.tar.gz
  • Upload date:
  • Size: 418.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.9

File hashes

Hashes for omi_physics-0.1.0.tar.gz
Algorithm Hash digest
SHA256 eae5bc21d08a832c7e5063928d059c8c9d03efaa7b8cf627fb1f1d0ae1800e9a
MD5 1e540a8690f12c484a01e117c45030f0
BLAKE2b-256 21d4e8a502a961361239e84fe4100f090a7cb32484c5c91955ef0631b361d32e

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page