manifold-csg
Safe Rust bindings to the manifold3d geometry kernel for constructive solid geometry (CSG).
manifold3d is a fast, robust C++ library for boolean operations on 3D triangle meshes. These bindings make its capabilities accessible from Rust with minimal overhead and without requiring users to manage C pointers or memory. See the upstream documentation for details on the underlying algorithms and behavior.
What's included
manifold-csg-sys provides raw FFI bindings to the manifold3d C API. If
you need direct C-level control, it's there.
manifold-csg wraps the most commonly needed operations in safe Rust:
- 3D solids (
Manifold) — primitives (cube, sphere, cylinder, tetrahedron), boolean operations (union, difference, intersection), transforms, convex hull, decomposition, Minkowski sum/difference, mesh refinement, smoothing, SDF level sets, warp deformation, and OBJ I/O - 2D regions (
CrossSection) — primitives (square, circle, polygons), boolean operations, Clipper2-based geometric offset, convex hull, transforms, warp, and simplification - Mesh data (
MeshGL64/MeshGL) — f64 and f32 mesh types for getting data in and out - Triangulation (
triangulate_polygons) — constrained Delaunay triangulation of 2D polygons - 2D-to-3D — extrude (with optional twist and scale) and revolve cross-sections into solids; slice solids back to cross-sections
See API_COVERAGE.md for a full table mapping every C API function to its safe wrapper (or noting where one doesn't exist yet).
Design choices
- f64 by default. Mesh I/O uses
MeshGL64so you don't lose precision through f32 round-trips. f32 paths (from_mesh_f32,to_mesh_f32,MeshGL) are available when you need them. Send+Sync. All types can be moved across threads and shared for concurrent reads.- Automatic memory management. All C handles are freed via
Drop. No manual cleanup needed. - Operator overloads.
&a + &b(union),&a - &b(difference),&a ^ &b(intersection) work on bothManifoldandCrossSection. - Callback-based APIs wrapped safely.
warp,set_properties,from_sdf, and OBJ I/O all accept closures withcatch_unwindto prevent panics from unwinding through C stack frames. - C API parity. Parameter order and names follow the C API so users transitioning from C/C++ find things where they expect.
Quick start
use ;
// 3D: drill a cylindrical hole through a cube
let cube = cube;
let hole = cylinder;
let result = &cube - &hole;
assert!;
// 2D -> 3D: offset a rectangle and extrude it
let section = square;
let expanded = section.offset;
let solid = expanded.extrude;
See the examples/ directory for more
complete, runnable examples.
Crates
| Crate | Description |
|---|---|
manifold-csg |
Safe Rust wrapper (start here) |
manifold-csg-sys |
Raw FFI bindings to the full C API |
Build requirements
- Rust 1.85+
- git, cmake, a C++ compiler
- First build clones manifold3d and compiles it from source; subsequent builds use the cached copy. Internet access is required for the initial clone.
Tested on Linux, macOS, Windows, wasm32-unknown-emscripten, and
wasm32-unknown-unknown (see below).
Browser / WebAssembly (wasm32-unknown-emscripten)
manifold-csg builds and runs in the browser via Emscripten. The C++ kernel
is compiled with emcmake/emmake; Rust links against it via emcc.
The integration test suite runs under Node (the workspace .cargo/config.toml
sets the runner, so no env var needed):
Notes:
- The
parallelfeature is silently disabled on emscripten — TBB requires pthread support which in turn needs the host page to provideSharedArrayBuffervia COOP/COEP HTTP headers. Use--no-default-featuresto opt out cleanly. - Tests using
std::thread::spawnare marked#[ignore]on this target for the same reason. - For
wasm32-unknown-unknownsupport (the wasm-bindgen-compatible target), see the next section. - End-user crates that produce their own
bin/cdylibfrom a wasm build need to forward the same emscripten link flags. Add a one-linebuild.rsthat re-emitsDEP_MANIFOLD_LINK_ARGS, or set them via.cargo/config.toml. - Existing examples build for wasm too — e.g.
cargo build --example basics --target wasm32-unknown-emscripten -p manifold-csg --no-default-featuresproduces a.wasm+.jsshim you can run withnode target/wasm32-unknown-emscripten/debug/examples/basics.js.
Browser without Emscripten (wasm32-unknown-unknown)
manifold-csg also builds for the bare-wasm target — the same one
wasm-bindgen consumers (Bevy, Leptos, Yew, etc.) target. The C++
runtime gap (wasm32-unknown-unknown ships no libc, no libc++, no
libc++abi) is filled by wasm-cxx-shim,
which is cloned and built automatically by build.rs.
This target is provisional. The build carries patches against upstream
manifold and Clipper2, ships without an exception runtime (implicit STL
throws abort), and disables OBJ I/O. To acknowledge these constraints,
the unstable-wasm-uu cargo feature is required for any build targeting
wasm32-unknown-unknown. Without it, build.rs aborts with an instructive
error.
= { = "...", = false, = ["unstable-wasm-uu"] }
Requirements:
- A wasm-capable LLVM 20+ install:
- macOS:
brew install llvm(then add/opt/homebrew/opt/llvm@20/binto PATH) - Debian:
apt install clang-20 lld-20 libc++-20-dev libc++abi-20-dev
- macOS:
- Rust target:
rustup target add wasm32-unknown-unknown - If LLVM is in a non-standard location, set
WASM_CXX_SHIM_LLVM_BIN_DIR=/path/to/llvm/binin your environment.
A runnable smoke example exercises a real CSG operation under Node:
# wasm32-uu-smoke: smoke_run() = 36 (triangle count, > 0) ✓
For an interactive end-to-end demo see the boolean playground —
a browser app that wires manifold-csg into three.js via a
small C ABI and lets you union/diff/intersect cubes/spheres/cylinders with
transform gizmos. Source under
crates/manifold-csg-playground/ (auto-deployed
to GitHub Pages).
Notes:
- First build is slow (clones manifold + Clipper2 + wasm-cxx-shim, builds three cmake projects in cross-compile). Subsequent builds use the cached artifacts.
- The
parallelfeature is unavailable on this target (no threads in defaultwasm32-unknown-unknown). - OBJ I/O is unavailable on this target (
Manifold::from_obj/to_objandMeshGL64::from_obj/to_objare#[cfg]-elided). Manifold's iostream- based OBJ paths depend on libc++ machinery the freestanding wasm build excludes; use the binaryMeshGL64API to round-trip mesh data instead. - Exceptions abort. Compiled with
-fno-exceptions; implicit STL throws (bad_alloc, etc.) become unrecoverable wasm traps. - See
docs/plans/wasm-unknown-unknown.mdfor the design background and production-readiness checklist. - If the build fails, run
bash crates/manifold-csg-sys/wasm32-uu/diagnose.sh > bugreport.txt 2>&1and attach the output to your issue — it captures the LLVM probe ladder, env vars, and cached build state.
Feature flags
| Feature | Default | Description |
|---|---|---|
parallel |
yes | Enables TBB-based parallelism for boolean operations |
nalgebra |
no | Adds convenience methods that accept nalgebra::Matrix3, Vector3, Point3 |
unstable-wasm-uu |
no | Required when targeting wasm32-unknown-unknown. Acknowledges that target's provisional status (carry-patches, no exceptions, no OBJ I/O). Has no effect on other targets. |
Documentation
- API_COVERAGE.md — maps every manifold3d C function to its safe wrapper, with source links
- docs.rs — generated API docs
- examples/ — runnable code examples
- Upstream docs — manifold3d C++ API documentation (helpful for understanding parameter semantics)
- Migration guide from manifold3d 0.0.6 — for users
of the original
manifold3dcrate line (pre-transfer). Structured for AI-assisted migration.
License
Licensed under either of Apache License, Version 2.0 or MIT license at your option.