otherplane
v0.1.1
Published
A static site generator for walkable Gaussian-splat museums. Generate a 3D room per section, link rooms with doorways, publish a static walkable site.
Maintainers
Readme
Otherplane
A static site generator for walkable Gaussian-splat museums. Point it at the sections of a personal site and it generates a 3D room for each one, lets you mark spawn points / doorways / artifacts, links the rooms together, and publishes a plain static walkable site — host it on GitHub Pages, Cloudflare R2/Pages, S3, or any folder behind any web server.
A museum is just rooms linked by exits — like web pages linked by hrefs.
There is no manifest and no central "museum" object; the graph is emergent.
This repo is the engine
Otherplane ships the tool, not a museum. You build your own site on top: your
rooms, splats, colliders, music, and per-site config are your content and are
gitignored by default — clone the engine, then create a museum inside it (or in
any folder) and track that content in your own repo. engine/ is the content-free
renderer; the rest is the CLI, schema, and authoring pipeline.
otherplane/
├── engine/ the renderer — a content-free Next.js viewer (Spark + R3F + Rapier)
├── bin/otherplane.mjs the CLI (the npm scripts alias it)
├── schema/ room.json JSON Schema (editor autocomplete + `otherplane check`)
├── scripts/ gen_world.py — generate a room from a prompt (World Labs Marble)
├── skill.md the authoring guide — how rooms get made, marked, linked, shipped
└── otherplane.config.example.json
# created by `otherplane init` / your content — GITIGNORED, build your own site here:
├── otherplane.config.json deploy config (landing room, base path, title, walk speed)
├── rooms/<slug>/room.json the rooms — one folder, one URL (/<slug>/)
├── worlds/ splats + colliders (.spz / .glb) — host big blobs on R2/S3
└── music/ per-room loopable tracksThe CLI mirrors your content into engine/public/ (a gitignored build artifact)
so Next can serve and enumerate it; you never edit there. The renderer is a deep
module: its only input is a room.json (by URL) — it knows nothing about
museums, hosting, or how assets were made. See CLAUDE.md for
renderer internals and skill.md for the authoring pipeline.
Quick start
Use it as an installed CLI in any folder (the engine's deps install themselves on first run):
npx otherplane init # scaffold otherplane.config.json + rooms/ worlds/ music/
npx otherplane edit # open http://localhost:3000 — walk, mark, and wire rooms
npx otherplane build # → ./out (deploy anywhere) · -- --base /museum to sub-path…or clone the repo and use the npm run aliases (npm run init / edit /
build). Either way, build emits a plain static out/ in your project — host
it on GitHub Pages, Cloudflare, S3, or any static host.
Add rooms one of two ways: generate them with otherplane new (World Labs
Marble — needs a key + uv, see below), or bring your own .spz splat +
.glb collider, drop them in worlds/, and point a rooms/<slug>/room.json at
them. Then otherplane edit to place spawns/doors/artifacts (it writes room.json
for you). No key or Python is needed for the bring-your-own path.
Publishing (maintainers):
npm publishships the CLI + the engine source (filesallowlist; build artifacts andnode_modulesare excluded). The engine is heavy (Next + Three.js + Rapier); its deps install on the user's firstdev/edit/build, not at install time. Bump the version andnpm publish.
CLI
Like other static site generators (Hugo, Astro, Eleventy), Otherplane exposes a
small set of verbs through one CLI (bin/otherplane.mjs). Run them as
otherplane <verb> or via the npm run aliases below:
| Command | What it does |
| :----------------- | :-------------------------------------------------------------------------- |
| npm run setup | Install the engine's dependencies (one-time). |
| npm run init | Scaffold a fresh project (config, rooms/, .env.example) in this folder. |
| npm run new | Generate a room from a prompt (World Labs Marble). See below. |
| npm run dev | Dev server with the viewer (http://localhost:3000). |
| npm run edit | Dev server with edit mode on — mark/wire rooms in the viewer; writes room.json for you. |
| npm run check | Validate every rooms/*/room.json against the schema. |
| npm run build | Static export to engine/out — deploy this folder anywhere. -- --base /museum to mount under a sub-path. |
| npm run serve | Serve the built engine/out at http://localhost:8000. |
| npm run preview | build then serve. |
| npm run clean | Remove build output + the synced mirror. |
Generating a room
cp .env.example .env # then paste your WORLD_LABS_KEY
npm run setup
npm run new -- "a cozy wood-paneled library, warm lamplight" --slug library --name "Library"This calls World Labs Marble, downloads the splat (.spz) + collider (.glb)
into worlds/, saves the raw world object under generated/<slug>/, and writes a
room.json skeleton to rooms/<slug>/. Use --model marble-1.0-draft (the
default) while iterating — it's cheap (~$1.26).
Then mark coordinates and wire the room up in the viewer — see below.
Marking & wiring (edit mode)
npm run edit opens the viewer with an authoring panel and writes room.json
for you (no clipboard, no hand-editing). Walk into a room, then:
- C — drop (or move the selected) entryway at your floor-snapped spot.
- B — aim and drop (or move the selected) artifact at what you're looking at.
- F select the marker under your crosshair · Del remove it · Z fly.
Every marker shows as a labeled orb (green entryway, cyan exit, amber artifact).
In the panel you name entryways, wire exits by menu (any room → entryway) or
paste an arbitrary URL (link into someone else's museum), promote an entryway
into a doorway, and click two-way to auto-create the return door in the target
room. Changes save to rooms/<slug>/room.json as you go.
Hosting & repo size
Keep text in git (room.json + the shared bundle) and blobs in object
storage. A .spz is ~5 MB and a collider ~5 MB; for anything you want to keep,
mirror those to R2 (cheap, zero egress, CDN-backed) and point each room.json
URL there. Adding a room then costs the repo a few KB, not 10 MB. (Marble's own
CDN URLs have no documented retention SLA — self-host keepers.)
Mounting on an existing site (e.g. /museum)
To hang the museum off a personal site — an "Enter my museum" button that goes to
example.com/museum/ — build it under that sub-path and drop the output into your
site's tree:
npm run build -- --base /museum # or set "basePath": "/museum" in otherplane.config.json
# then copy engine/out/* into your site's /museum/ directory (or point the host at it)The link on your homepage is just <a href="/museum/">. The museum is plain
static files, so it coexists with any host; the two builds stay separate (the
viewer carries a WebGL toolchain your main site shouldn't inherit) and integrate
at the deploy layer.
License
MIT — see LICENSE. Built on
WorldSplats (the viewer),
Spark.js (splat rendering), and
World Labs Marble (generation).
