An interactive playground for Manifold-3d modeling operations, rendered live with Three.js.
A curated set of interactive cases demonstrating core Manifold-3d operations, each with live three-dimensional visualization and a tweakable lil-gui control panel.
| # | Case | What it shows |
|---|---|---|
| 1 | Base Shapes | Built-in primitives (cube, sphere, cylinder, …) via Manifold |
| 2 | Extrude | 2D cross-sections extruded along the Z-axis into 3D solids |
| 3 | Revolve | 2D cross-sections revolved around the Y-axis |
| 4 | 2D Boolean | CrossSection union / intersection / difference (line-rendered) |
| 5 | 3D Boolean | Manifold union / intersection / difference on solid meshes |
| 6 | Smooth | Manifold.smooth + refine for G1-continuous surfaces with optional hard edges |
| 7 | Simplify | asOriginal + Manifold.simplify tolerance-based mesh simplification on refined models |
| 8 | Import / Export | Round-trip models through gltf / glb / 3mf via @gltf-transform |
- Node.js ≥ 18
- pnpm (recommended) or
npm/yarn
pnpm installpnpm dev # start Vite dev server
pnpm build # type-check (tsc --noEmit) + production build
pnpm preview # preview the production build
pnpm typecheck # run TypeScript checks onlyOpen http://localhost:5173 and pick a case from the left sidebar. The URL hash routes to the active case (#/case/<key>) and is shareable.
| Library | Version | Role |
|---|---|---|
manifold-3d |
^3.5.1 | CSG modeling kernel (WASM) |
three |
^0.184.0 | WebGL renderer & scene graph |
@gltf-transform/* |
^4.4.0 | glTF / glb / 3mf I/O |
lil-gui |
^0.21.0 | Per-case parameter panel |
vite |
^8.0.0 | Dev server & bundler |
typescript |
^6.0.0 | Type safety |
src/
├── main.ts # entry: bootstraps SceneApp
├── style.css
├── core/
│ ├── SceneApp.ts # wires Sidebar + Stage + CaseManager, handles routing
│ ├── Sidebar.ts # left-side case picker
│ ├── Stage.ts # Three.js canvas + camera/orbit controls
│ ├── CaseManager.ts # mounts/unmounts the active case
│ ├── registry.ts # central case registration + lazy loaders
│ ├── router.ts # parse / build #/case/<key> hash routes
│ └── types.ts
├── cases/ # one folder per demo (lazy-loaded)
│ ├── index.ts # registerCase(...) entries
│ ├── base-shape/
│ ├── extrude-shape/
│ ├── revolve/
│ ├── 2d-boolean/
│ ├── 3d-boolean/
│ ├── smooth/
│ └── import-export/
├── utils/
│ ├── manifold.ts # wasm init + helpers
│ └── three.ts # three.js helpers
└── assets/
Each demo lives in its own folder and is registered through a single registerCase(...) call in src/cases/index.ts. The shell renders the sidebar before the case module is downloaded; that is why name and description are duplicated there — they must be known eagerly.
The hash format is #/case/<key>. An empty or invalid hash falls back to the first registered case without rewriting the URL.
-
Create
src/cases/<your-case>/index.tsandexport defaultaCaseinstance. -
Register it in
src/cases/index.ts:registerCase({ key: 'your-case', name: 'Your Case', description: 'Short description shown in the sidebar', load: () => import('./your-case').then((m) => m.default), });
That is it — the sidebar entry, the lazy chunk, and the #/case/your-case route are all generated automatically.
| Script | Description |
|---|---|
pnpm dev |
Start the Vite dev server with HMR |
pnpm build |
Type-check and build for production |
pnpm preview |
Serve the production build locally |
pnpm typecheck |
Run tsc --noEmit only |
MIT © 2026 — feel free to learn from, fork, and build on top of it.
