Skip to main content

ManifoldX

PyPI version Python versions License Tests

A real-time 3D rendering engine built on pure wgpu with an Entity Component System (ECS) architecture. Written in Python with numpy for high-performance data handling.

⚠️ Beta / Academic Project — This is an experimental proof-of-concept exploring the extent to which Python can be used for high-performance graphics via wgpu. Not recommended for production use. Expect bugs, breaking changes, and missing features.

Motivation

Domain researchers need to run large-scale simulations (10⁴-10⁶ entities) and visualize results in 3D. Currently they can't do both in pure Python.

Tool Simulation Visualization Language
Matplotlib/Plotly 2D only, slow at scale Pure Python
VisPy OpenGL, steep curve Python + GLSL
PyGfx Excellent rendering Pure Python
Game engines (PyGame) 2D mostly Pure Python
Specialized (OpenMM, SUMO) Data export only Fortran/C++

The gap: No Python tool combines data-driven simulation with accessible 3D visualization in one package.

The Vision

ManifoldX gives researchers both:

  1. Simulation in pure NumPy — vectorized operations over entity arrays, no Python loops in the hot path
  2. 3D visualization without graphics knowledge — spawn entities with meshes/materials, the engine handles GPU rendering
# Write physics in pure numpy (data-driven, not OOP)
@engine.system
def nbody_physics(query, dt):
    # Magic happens here...
    forces = compute_gravity_all_pairs(query[Transform].pos.data)
    velocities += forces * dt
    query[Transform].pos += velocities * dt

# Engine handles GPU buffers, WGSL shaders, instanced draw calls
# You get 3D visualization of your simulation instantly

Target domains: Astrophysics (galaxy formation), molecular dynamics (protein folding), epidemiology (disease spread), crowd science (evacuation flows), traffic engineering (vehicle flow), weather (particle advection).

Works in: Jupyter notebooks, Quarto documents, Streamlit dashboards, standalone scripts.

⚠️ Spoiler: Python is surprisingly capable at real-time 3D. The N-body demo runs 500 bodies with N² gravity — 250,000 force pairs per frame in pure numpy.

Why Not PyGfx?

PyGfx is an excellent rendering engine and has been a key source of inspiration — we've learned a lot from its WGSL patterns, material system, and API design. However, it's fundamentally a scene graph engine (object-oriented hierarchy of transforms), not a data-driven ECS. For large-scale simulations with vectorized physics, the OOP overhead of traversing a scene graph becomes a bottleneck. ManifoldX uses a flat SoA data layout where simulation logic operates directly on numpy arrays.

Technically this is a game engine — the ECS + instanced rendering + PBR pipeline is exactly what you'd use for a game. But that's not the focus. The goal is making 3D visualization accessible to researchers who don't know anything about graphics.

Installation

pip install manifold-gfx           # Base (no rendering backends)
pip install manifold-gfx[desktop]  # + GLFW for desktop window
pip install manifold-gfx[offline]  # + imageio-ffmpeg for video
pip install manifold-gfx[all]      # Everything

# or with uv
uv add manifold-gfx
uv add manifold-gfx[desktop]       # Desktop rendering
uv add manifold-gfx[offline]      # Video rendering

Requirements:

  • Python 3.13+
  • GPU with WebGPU support (via wgpu backend)
    • Vulkan on Linux
    • Metal on macOS
    • D3D12 on Windows

Note: Install with [desktop] for interactive rendering, [offline] for video output, or [all] for both.

Quick Start

import manifoldx as mx
import numpy as np

from manifoldx.components import Transform, Mesh, Material
from manifoldx.resources import StandardMaterial, PointLight, cube, sphere

# Create engine with default settings
engine = mx.Engine("My First Scene")

# Create a cube and sphere
cube_geo = cube(1, 1, 1)
sphere_geo = sphere(0.7, 32)

# Create PBR materials (roughness: 0-1, metallic: 0-1)
red_shiny = StandardMaterial(color="#ff3333", roughness=0.15, metallic=0.9)
blue_dull = StandardMaterial(color="#3366ff", roughness=0.8, metallic=0.0)

# Spawn entities
engine.spawn(
    Mesh(cube_geo),
    Material(red_shiny),
    Transform(pos=(-1.5, 0, 0)),
)

engine.spawn(
    Mesh(sphere_geo),
    Material(blue_dull),
    Transform(pos=(1.5, 0, 0)),
)

# Add an orbiting light
light = PointLight(color="#ffffff", intensity=15.0, position=(5, 5, 5))
engine.set_lights([light])

# Animate
@engine.system
def animate_lights(query: mx.Query[Transform], dt: float):
    t = engine.elapsed
    light.position = (
        5 * np.cos(t * 0.7),
        3 + np.sin(t * 0.5) * 2,
        5 * np.sin(t * 0.7),
    )

# Auto-fit camera to view the scene
engine.camera.fit(radius=5.0, azimuth=30, elevation=35)

# CLI handles run vs render automatically
engine.cli()

Save as my_scene.py and run:

python my_scene.py              # Interactive window
python my_scene.py --render     # Render 60s video → my_scene.mp4
python my_scene.py --render --fps 60 --duration 120 --output movie.mp4

N-Body Simulation

A pure-numpy gravitational simulation running 500 bodies in real-time at a single draw call (instanced rendering).

Physics: All pairwise forces are computed with a single vectorized numpy expression — no Python loops in the hot path. For N bodies this means N² = 250,000 force computations per frame, each a 3-component vector.

@engine.system
def nbody_physics(query: mx.Query[Transform], dt: float):
    global velocities
    pos = query[Transform].pos.data

    # All-pairs position differences (N, N, 3) — one numpy broadcast
    diff = pos[None, :] - pos[:, None]

    # Pairwise distances (N, N)
    dist = np.linalg.norm(diff, axis=2)
    dist = np.maximum(dist, SOFTENING)

    # Gravitational force magnitude for every pair
    force_mag = G * (masses[None, :] * masses[:, None]) / dist**2

    # Net force on each body: sum over all other bodies
    direction = diff / dist[:, :, None]
    forces = force_mag[:, :, None] * direction
    net_force = forces.sum(axis=1)

    # Integrate: F = ma → a = F/m
    velocities += (net_force / masses[:, None]) * dt
    query[Transform].pos += velocities * dt

See examples/nbody.py for the full implementation with velocity damping and speed clamping.

Ideal Gas Simulation

The examples/gas.py demo shows the other side: no gravity, all collisions. 500 particles bounce inside an invisible box with elastic collisions and wall reflections — a kinetic theory simulation in pure numpy.

Collisions find overlapping pairs with a vectorized comparison, filter with np.where(np.triu(...)), then resolve impulse with np.add.at for safe accumulation. Wall reflections are a single vectorized mask: velocities[next_pos < wall] = np.abs(...).

See examples/gas.py for the full implementation.

Boids Flocking Simulation

The examples/boids.py demo shows emergent behavior from simple local rules: 300 boids with separation, alignment, cohesion, plus 4 wandering predators they flee from.

Flocking rules (all vectorized):

  • Separation — boids repel neighbors weighted by 1/dist²
  • Alignment — match average velocity of nearby boids
  • Cohesion — steer toward center of mass of neighbors

Predator avoidance — boids detect predators within 10 units and flee with 20x the force of any flocking rule. Fleeing boids get a speed boost (15 vs 10).

Spatial optimization — uses squared distances to avoid sqrt in the hot path. All three rules computed via masked tensor sums over axis=1 of an (N, N, 3) difference tensor.

See examples/boids.py for the full implementation.

Three Demos, Three Vectorization Patterns

Demo Entities Physics Pattern Operations/Frame
nbody.py 500 All-pairs gravity N² = 250,000 force pairs
gas.py 500 Pair collisions + walls O(N²) pair checks + wall masks
boids.py 300 + 4 Neighbor flocking + predator-flee (N,N,3) tensor sums + (N,P,3)

All three use pure numpy — zero Python loops in the hot path. The ECS overhead is ~microseconds/frame; the bottleneck is GPU fill-rate, not CPU physics.

Example Description
hello_world.py Minimal empty window
cube.py Rotating cube with Phong material
pbr_demo.py 3×2 grid demonstrating PBR materials + 3 orbiting lights
spheres.py Many spheres with physics-like behavior
nbody.py 500-body gravitational simulation with pure-numpy physics
gas.py 500-particle ideal gas with collisions and virtual walls
boids.py 300-agent flocking simulation with emergent swarm behavior

Run an example:

python examples/nbody.py              # Interactive window
python examples/nbody.py --render 60  # Render 60s video to nbody.mp4

Features

ECS Architecture

  • Structure of Arrays (SoA) layout for each component
  • Vectorized numpy operations for batch transforms
  • Free-list for efficient entity reuse
  • Component view with operator overloads (+=, *=, etc.)

Rendering

  • Instanced drawing — single draw call per (geometry, material) batch
  • Material-specific pipelines — each material type compiles its own WGSL shader
  • Transform caching — dirty-flag optimization to avoid recomputing matrices
  • Shared transform buffer — all instance transforms uploaded once per frame

Materials & Lighting

  • BasicMaterial — unlit flat color with simple diffuse
  • StandardMaterial — full PBR with GGX BRDF
    • Roughness/metallic workflow
    • Multiple point lights with inverse-square attenuation
    • Reinhard tonemapping + gamma correction
  • External lights — passed to engine like camera (not in ECS)

Camera

  • Perspective projection (WebGPU NDC)
  • Spherical coordinate orbit controls
  • Fit/fit_bounds for automatic framing

Geometries

  • Cube (with normals)
  • UV Sphere (with normals, CCW winding)
  • Plane (with normals)

Video Rendering

Render simulations to video files for sharing or CI using the built-in CLI:

python examples/nbody.py --render
python examples/nbody.py --render --fps 60 --duration 120 --output movie.mp4

Options:

  • --render — Render to video instead of showing window
  • --fps N — Frames per second (default: 30)
  • --duration N — Video duration in seconds (default: 60)
  • --output FILE — Output filename (default: <script>.mp4)
  • --quality Q — Quality: low, medium, high (default: high)

Programmatic API:

engine.render(
    output="simulation.mp4",
    fps=30,
    duration=60,
    quality="high",
)

Note: Video rendering requires the offline extra: pip install manifold-gfx[offline]

Architecture Highlights

The ECS uses numpy arrays for all component data. When you call query[Transform].pos += velocity * dt, it's a single vectorized numpy operation spanning thousands of entities.

Real-world examples: The N-body demo (examples/nbody.py) simulates 500 bodies with 250,000 pairwise gravitational force computations per frame. The ideal gas demo (examples/gas.py) runs 500 particles with elastic collisions and wall reflections. Both are pure numpy with zero Python loops.

Limitations (Known)

  • ❌ No shadows
  • ❌ No texture support
  • ❌ No environment/IBL mapping
  • ❌ Single material params per draw call (not per-instance)
  • ❌ Only point lights in PBR shader
  • ❌ Limited to ~100k entities

Future Ideas

This is an academic/experimental project. Ideas for future development:

  1. Per-instance material data — Storage buffer for varying roughness/metallic per instance in a single draw
  2. Shadow mapping — Shadow pass + PCF sampling
  3. Texture maps — Diffuse, normal, roughness textures via storage buffers
  4. Spot/Directional lights — Extend PBR shader
  5. Environment mapping — IBL with prefiltered radiance
  6. Skinned animation — Bone transforms in vertex shader
  7. Post-processing — Bloom, DOF, TAA
  8. Deferred rendering — Forward+ / clustered lighting for many lights

Contributing

Contributions welcome! This is an educational project — all skill levels encouraged.

Areas needing work:

  • Bug fixes and stability improvements
  • Additional geometry types (torus, cylinder, etc.)
  • More material types (toon, unlit with texture)
  • Shadow implementation
  • Performance profiling and optimization

Getting started:

# Clone and set up
git clone https://github.com/apiad/manifoldx.git
cd manifoldx
pip install -e ".[dev]"

# Run tests
make test

# Run an example
python -m examples.cube

Testing

# Run all tests (uses offline backend for headless CI)
make test

# Run specific test file
python -m pytest tests/test_ecs.py -v

Current test coverage: 162 tests covering ECS operations, components, materials, rendering, camera, and video output.

License

MIT License — See LICENSE file.

Credits

  • wgpu — Pure Python WebGPU bindings
  • PyGfx — Reference for WGSL shader patterns
  • rendercanvas — Window management

Download files

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

Source Distribution

manifold_gfx-0.3.0.tar.gz (208.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

manifold_gfx-0.3.0-py3-none-any.whl (40.1 kB view details)

Uploaded Python 3

File details

Details for the file manifold_gfx-0.3.0.tar.gz.

File metadata

  • Download URL: manifold_gfx-0.3.0.tar.gz
  • Upload date:
  • Size: 208.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.3 {"installer":{"name":"uv","version":"0.11.3","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for manifold_gfx-0.3.0.tar.gz
Algorithm Hash digest
SHA256 17d6e3a9bece317837a768893743700d1867ac456cd37d1958b41cf75739c65b
MD5 9ab9e6307eb1f1e4429dc56f4c3a9f7d
BLAKE2b-256 bcf48dd18e083392ede20ed156bc379a3786275d95f36bb5f7a459e017c3d666

See more details on using hashes here.

File details

Details for the file manifold_gfx-0.3.0-py3-none-any.whl.

File metadata

  • Download URL: manifold_gfx-0.3.0-py3-none-any.whl
  • Upload date:
  • Size: 40.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.3 {"installer":{"name":"uv","version":"0.11.3","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for manifold_gfx-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 68ce84a8fddb13d15cda771c78d15a6b9a1ced1918970fab6a02e8102a5caba2
MD5 39609c752a7e7fef6c18ef069bd1e03d
BLAKE2b-256 6020b7de321ffabac10c4ae3faca15175e7dd096f1226d7412bf28b7be3c558e

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

0.3.0 This release

2 files

0.2.0

2 files

0.1.0

2 files

Supported by

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