otherplane 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +130 -0
- package/LICENSE +21 -0
- package/README.md +146 -0
- package/bin/otherplane.mjs +489 -0
- package/engine/eslint.config.mjs +25 -0
- package/engine/next.config.ts +43 -0
- package/engine/package-lock.json +6848 -0
- package/engine/package.json +36 -0
- package/engine/postcss.config.mjs +5 -0
- package/engine/src/app/LandingRedirect.tsx +15 -0
- package/engine/src/app/[room]/RoomViewer.tsx +413 -0
- package/engine/src/app/[room]/page.tsx +30 -0
- package/engine/src/app/favicon.ico +0 -0
- package/engine/src/app/layout.tsx +45 -0
- package/engine/src/app/page.tsx +11 -0
- package/engine/src/app/providers.tsx +22 -0
- package/engine/src/components/controls/MobileHud.tsx +25 -0
- package/engine/src/components/controls/PlayerController.tsx +170 -0
- package/engine/src/components/controls/TouchLookController.tsx +93 -0
- package/engine/src/components/controls/VirtualStick.tsx +153 -0
- package/engine/src/components/edit/EditCapture.tsx +182 -0
- package/engine/src/components/edit/EditorPanel.tsx +265 -0
- package/engine/src/components/edit/Markers.tsx +91 -0
- package/engine/src/components/hud/Button.tsx +228 -0
- package/engine/src/components/hud/ClickToPlay.tsx +13 -0
- package/engine/src/components/hud/ContentOverlay.tsx +44 -0
- package/engine/src/components/hud/NavHeader.module.css +24 -0
- package/engine/src/components/scene/Artifacts.tsx +85 -0
- package/engine/src/components/scene/Exits.tsx +92 -0
- package/engine/src/components/scene/PointerLockBridge.tsx +28 -0
- package/engine/src/components/scene/WorldScene.tsx +164 -0
- package/engine/src/components/spark/SparkLayer.tsx +112 -0
- package/engine/src/components/spark/SplatWorld.tsx +156 -0
- package/engine/src/config/audio.ts +11 -0
- package/engine/src/data/editApi.ts +73 -0
- package/engine/src/data/presets.ts +34 -0
- package/engine/src/data/room.ts +100 -0
- package/engine/src/data/site.ts +50 -0
- package/engine/src/data/universeconfig.ts +19 -0
- package/engine/src/icons/ArrowLeft.tsx +20 -0
- package/engine/src/icons/ChevronDown.tsx +23 -0
- package/engine/src/icons/ChevronLeft.tsx +22 -0
- package/engine/src/icons/Home.tsx +22 -0
- package/engine/src/icons/Spinner.module.css +13 -0
- package/engine/src/icons/Spinner.tsx +28 -0
- package/engine/src/icons/VolumeMax.tsx +21 -0
- package/engine/src/icons/VolumeX.tsx +22 -0
- package/engine/src/icons/icons.interface.ts +7 -0
- package/engine/src/icons/index.ts +27 -0
- package/engine/src/physics/RapierProvider.tsx +302 -0
- package/engine/src/physics/index.ts +2 -0
- package/engine/src/physics/types.ts +9 -0
- package/engine/src/providers/audio.tsx +215 -0
- package/engine/src/providers/edit.tsx +357 -0
- package/engine/src/providers/pointerLock.tsx +88 -0
- package/engine/src/styles/globals.css +88 -0
- package/engine/tailwind.config.js +184 -0
- package/engine/tsconfig.json +27 -0
- package/otherplane.config.example.json +6 -0
- package/package.json +56 -0
- package/schema/room.schema.json +77 -0
- package/scripts/gen_world.py +147 -0
- package/skill.md +94 -0
package/CLAUDE.md
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
Guidance for Claude Code working in the **Otherplane** repo.
|
|
4
|
+
|
|
5
|
+
## What this is
|
|
6
|
+
|
|
7
|
+
A static site generator for walkable Gaussian-splat museums. This repo ships the
|
|
8
|
+
**engine/tool** only; a user's museum content is theirs and is gitignored. Two
|
|
9
|
+
layers:
|
|
10
|
+
|
|
11
|
+
- **the project** — a user's content root: `otherplane.config.json` (deploy config:
|
|
12
|
+
landing room, base path, site title, walk speed) plus `rooms/<slug>/room.json`,
|
|
13
|
+
`worlds/` (splats + colliders), `music/`. Gitignored here; it's the source of
|
|
14
|
+
truth for a given site. `otherplane init` scaffolds it.
|
|
15
|
+
- **`engine/`** — the renderer: a content-free Next.js viewer (Spark + R3F +
|
|
16
|
+
Rapier) that statically exports (`output: 'export'` — no server, any static
|
|
17
|
+
host). Its only input is a `room.json` by URL; it knows nothing about museums,
|
|
18
|
+
hosting, or how assets were made. Keep it content-free — no rooms baked in.
|
|
19
|
+
|
|
20
|
+
Plus the tooling: `bin/otherplane.mjs` (the CLI), `schema/` (room.json JSON
|
|
21
|
+
Schema), `scripts/gen_world.py` (Marble room generation), and [`skill.md`](skill.md)
|
|
22
|
+
(the authoring pipeline: generate → mark → wire → publish).
|
|
23
|
+
|
|
24
|
+
The CLI mirrors the project's `rooms/`, `worlds/`, `music/` into `engine/public/`
|
|
25
|
+
(gitignored build artifact) before dev/build, so Next's `generateStaticParams`
|
|
26
|
+
can enumerate `public/rooms/*`. Author at the project root, never in the mirror.
|
|
27
|
+
The project root defaults to the cwd, overridable via `$OTHERPLANE_PROJECT`.
|
|
28
|
+
|
|
29
|
+
## Commands (run from the project root)
|
|
30
|
+
|
|
31
|
+
One CLI, aliased by the npm scripts: `npm run init` (scaffold a project) ·
|
|
32
|
+
`npm run new` (generate a room) · `npm run dev` · `npm run edit` (author in the
|
|
33
|
+
viewer) · `npm run check` (validate rooms) · `npm run build` (→ `engine/out`;
|
|
34
|
+
`-- --base /museum` to mount under a sub-path) · `npm run serve` · `npm run clean`.
|
|
35
|
+
Each aliases `otherplane <verb>` (`bin/otherplane.mjs`), which drives `engine/` and
|
|
36
|
+
`gen_world.py`.
|
|
37
|
+
|
|
38
|
+
## The room primitive (`engine/src/data/room.ts`)
|
|
39
|
+
|
|
40
|
+
A room is `rooms/<slug>/room.json`. Types:
|
|
41
|
+
|
|
42
|
+
- `Entryway { id, pos, yaw }` — a named, addressable spawn spot. The URL fragment
|
|
43
|
+
(`/library/#from-study`) selects it; `id: "default"` is the no-fragment spawn.
|
|
44
|
+
- `Exit { pos, radius?, to }` — walk up + press **E** to follow `to` (a room URL +
|
|
45
|
+
`#entryway`; relative within a site, absolute across). Dead links 404 by design.
|
|
46
|
+
- `Artifact { id?, pos, radius, url }` — walk up + interact to open a web URL
|
|
47
|
+
overlay (no travel).
|
|
48
|
+
- `loadRoom(url)` resolves every asset URL **relative to the room.json's URL**, so
|
|
49
|
+
assets can be colocated, on object storage (R2/S3), or on the World Labs CDN.
|
|
50
|
+
|
|
51
|
+
A museum is just rooms linked by exits (like web pages linked by hrefs) — there is
|
|
52
|
+
no manifest and no global "museum" object; the graph is emergent.
|
|
53
|
+
|
|
54
|
+
## Routing (`engine/src/app`)
|
|
55
|
+
|
|
56
|
+
- `app/[room]/page.tsx` — server wrapper; `generateStaticParams` enumerates
|
|
57
|
+
`public/rooms/*` so each room pre-renders to `/<slug>/index.html` in the export.
|
|
58
|
+
(generateStaticParams can't live in a `'use client'` file — hence the wrapper.)
|
|
59
|
+
- `app/[room]/RoomViewer.tsx` — the client viewer: loads the room, resolves the
|
|
60
|
+
spawn entryway from `location.hash`, renders the scene, handles exits (client
|
|
61
|
+
nav same-origin, full nav cross-origin).
|
|
62
|
+
- `app/page.tsx` — server component that bakes in `LANDING_ROOM` (`src/data/site.ts`,
|
|
63
|
+
read from `otherplane.config.json`) and renders `LandingRedirect.tsx`, a tiny
|
|
64
|
+
client redirect (static export has no server).
|
|
65
|
+
- `app/providers.tsx` — PointerLock / Audio / Edit providers (in the root layout).
|
|
66
|
+
|
|
67
|
+
## Rendering & physics
|
|
68
|
+
|
|
69
|
+
- `components/scene/WorldScene.tsx` — the R3F `<Canvas>` and scene graph.
|
|
70
|
+
- `components/spark/SplatWorld.tsx` — loads the `.spz` via Spark. All Marble splats
|
|
71
|
+
use quaternion `[1,0,0,0]` (180° about X) + the room's `calibration.scale` (see
|
|
72
|
+
`roomToWorldDef` in `presets.ts`); the collider is baked with the same transform
|
|
73
|
+
so walls line up with what you see.
|
|
74
|
+
- `physics/RapierProvider.tsx` — builds a trimesh env collider from the room's
|
|
75
|
+
collider GLB; spawns the player (fixed-size dynamic capsule) at the entryway
|
|
76
|
+
`pos` (body-center) facing `yaw`. Falls back to a floor raycast (`findValidSpawn`)
|
|
77
|
+
only when a room declares no entryways. Rapier is `@dimforge/rapier3d-compat`
|
|
78
|
+
**v0.12** — ray hits use `.toi` (not `.timeOfImpact`).
|
|
79
|
+
- `components/controls/PlayerController.tsx` — WASD + mouse. The player capsule is
|
|
80
|
+
a **fixed real-world size**; `calibration.scale` sizes the *room* to fit it, so a
|
|
81
|
+
too-small scale leaves the player wedged (that's the room-scale footgun). No
|
|
82
|
+
jumping/sprinting; edit-mode specter (Z + arrows) is the only vertical movement.
|
|
83
|
+
|
|
84
|
+
## Edit mode (`engine/src/providers/edit.tsx`)
|
|
85
|
+
|
|
86
|
+
A property of the running **instance** (`NEXT_PUBLIC_EDIT_MODE`, via `npm run
|
|
87
|
+
edit`), not a URL — so published builds have no edit mode and never mount any of
|
|
88
|
+
it. Edit mode **writes** `room.json`: `otherplane edit` runs a tiny local HTTP
|
|
89
|
+
writer alongside `next dev`, and `src/data/editApi.ts` POSTs marks to it; the CLI
|
|
90
|
+
merges only the coordinate arrays into the PROJECT source (never asset URLs, never
|
|
91
|
+
the mirror) and re-syncs. Pieces:
|
|
92
|
+
|
|
93
|
+
- `providers/edit.tsx` — owns the room's draft + mutators (each persists) + undo.
|
|
94
|
+
- `components/edit/EditCapture.tsx` — keys: **C** add/move entryway (floor-snapped),
|
|
95
|
+
**B** add/move artifact (beamed), **F** gaze-select, **Del** remove, **X**
|
|
96
|
+
deselect, **Z** specter fly (↑/↓).
|
|
97
|
+
- `components/edit/Markers.tsx` — labeled orbs in the scene (green entryway / cyan
|
|
98
|
+
exit / amber artifact), edit-only.
|
|
99
|
+
- `components/edit/EditorPanel.tsx` — the single DOM panel: help legend, undo/⌘Z,
|
|
100
|
+
name/wire/delete, exit dropdown-or-arbitrary-URL, promote-to-doorway, two-way
|
|
101
|
+
(reciprocal exit reusing the target's entryway), and two sliders that write
|
|
102
|
+
**shippable** data — room scale → `calibration.scale` in room.json (rescales the
|
|
103
|
+
markers with it), walk speed → `moveSpeed` in `otherplane.config.json` (per-museum,
|
|
104
|
+
ships to every viewer).
|
|
105
|
+
|
|
106
|
+
## Controls (published viewer)
|
|
107
|
+
|
|
108
|
+
Desktop: WASD move, mouse look, **E** to use an exit, mute (top-right), Esc to
|
|
109
|
+
release the mouse. The first click or keypress engages look + audio (browsers
|
|
110
|
+
require a gesture). Mobile: virtual joystick + touch-drag to look.
|
|
111
|
+
|
|
112
|
+
## Conventions
|
|
113
|
+
|
|
114
|
+
- TypeScript, functional components, hooks. Providers live OUTSIDE the R3F Canvas.
|
|
115
|
+
- Keep the renderer content-free: no specific rooms/assets baked in.
|
|
116
|
+
- Memoize the `WorldDef` passed to the scene (stable `position`/`quaternion`
|
|
117
|
+
references) or SplatWorld's load effect re-fires every render.
|
|
118
|
+
- Stable slugs + entryway ids: other rooms link to them; renaming rots links.
|
|
119
|
+
- Big binaries (`.spz`/`.glb`) belong in object storage referenced by URL from
|
|
120
|
+
`room.json`; keep git small.
|
|
121
|
+
- `.env` holds `WORLD_LABS_KEY` and is gitignored — never commit it (`.env.example`
|
|
122
|
+
is the template). Build output (`engine/.next`, `engine/out`, `node_modules`) and
|
|
123
|
+
content (`rooms/`, `worlds/`, `music/`, `generated/`) are gitignored too.
|
|
124
|
+
|
|
125
|
+
## Tech stack
|
|
126
|
+
|
|
127
|
+
Next.js 15 (static export) · React 19 · TypeScript · three.js + @react-three/fiber ·
|
|
128
|
+
@sparkjsdev/spark (splat rendering) · @dimforge/rapier3d-compat (physics) ·
|
|
129
|
+
Tailwind CSS. Built on [WorldSplats](https://github.com/philchacko/worldsplats),
|
|
130
|
+
[Spark.js](https://sparkjs.dev), and [World Labs Marble](https://marble.worldlabs.ai).
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Phil Chacko
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# Otherplane
|
|
2
|
+
|
|
3
|
+
A **static site generator for walkable Gaussian-splat museums.** Point it at the
|
|
4
|
+
sections of a personal site and it generates a 3D **room** for each one, lets you
|
|
5
|
+
mark spawn points / doorways / artifacts, links the rooms together, and publishes
|
|
6
|
+
a plain **static walkable site** — host it on GitHub Pages, Cloudflare R2/Pages,
|
|
7
|
+
S3, or any folder behind any web server.
|
|
8
|
+
|
|
9
|
+
A museum is just **rooms linked by exits** — like web pages linked by `href`s.
|
|
10
|
+
There is no manifest and no central "museum" object; the graph is emergent.
|
|
11
|
+
|
|
12
|
+
## This repo is the engine
|
|
13
|
+
|
|
14
|
+
Otherplane ships the **tool**, not a museum. You build your own site on top: your
|
|
15
|
+
rooms, splats, colliders, music, and per-site config are **your content** and are
|
|
16
|
+
gitignored by default — clone the engine, then create a museum inside it (or in
|
|
17
|
+
any folder) and track that content in your own repo. `engine/` is the content-free
|
|
18
|
+
renderer; the rest is the CLI, schema, and authoring pipeline.
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
otherplane/
|
|
22
|
+
├── engine/ the renderer — a content-free Next.js viewer (Spark + R3F + Rapier)
|
|
23
|
+
├── bin/otherplane.mjs the CLI (the npm scripts alias it)
|
|
24
|
+
├── schema/ room.json JSON Schema (editor autocomplete + `otherplane check`)
|
|
25
|
+
├── scripts/ gen_world.py — generate a room from a prompt (World Labs Marble)
|
|
26
|
+
├── skill.md the authoring guide — how rooms get made, marked, linked, shipped
|
|
27
|
+
└── otherplane.config.example.json
|
|
28
|
+
|
|
29
|
+
# created by `otherplane init` / your content — GITIGNORED, build your own site here:
|
|
30
|
+
├── otherplane.config.json deploy config (landing room, base path, title, walk speed)
|
|
31
|
+
├── rooms/<slug>/room.json the rooms — one folder, one URL (/<slug>/)
|
|
32
|
+
├── worlds/ splats + colliders (.spz / .glb) — host big blobs on R2/S3
|
|
33
|
+
└── music/ per-room loopable tracks
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
The CLI mirrors your content into `engine/public/` (a gitignored build artifact)
|
|
37
|
+
so Next can serve and enumerate it; you never edit there. The renderer is a **deep
|
|
38
|
+
module**: its only input is a `room.json` (by URL) — it knows nothing about
|
|
39
|
+
museums, hosting, or how assets were made. See [`CLAUDE.md`](CLAUDE.md) for
|
|
40
|
+
renderer internals and [`skill.md`](skill.md) for the authoring pipeline.
|
|
41
|
+
|
|
42
|
+
## Quick start
|
|
43
|
+
|
|
44
|
+
Use it as an installed CLI in any folder (the engine's deps install themselves on
|
|
45
|
+
first run):
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
npx otherplane init # scaffold otherplane.config.json + rooms/ worlds/ music/
|
|
49
|
+
npx otherplane edit # open http://localhost:3000 — walk, mark, and wire rooms
|
|
50
|
+
npx otherplane build # → ./out (deploy anywhere) · -- --base /museum to sub-path
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
…or clone the repo and use the `npm run` aliases (`npm run init` / `edit` /
|
|
54
|
+
`build`). Either way, `build` emits a plain static `out/` in your project — host
|
|
55
|
+
it on GitHub Pages, Cloudflare, S3, or any static host.
|
|
56
|
+
|
|
57
|
+
Add rooms one of two ways: **generate** them with `otherplane new` (World Labs
|
|
58
|
+
Marble — needs a key + `uv`, see below), or **bring your own** `.spz` splat +
|
|
59
|
+
`.glb` collider, drop them in `worlds/`, and point a `rooms/<slug>/room.json` at
|
|
60
|
+
them. Then `otherplane edit` to place spawns/doors/artifacts (it writes `room.json`
|
|
61
|
+
for you). No key or Python is needed for the bring-your-own path.
|
|
62
|
+
|
|
63
|
+
> **Publishing (maintainers):** `npm publish` ships the CLI + the engine source
|
|
64
|
+
> (`files` allowlist; build artifacts and `node_modules` are excluded). The engine
|
|
65
|
+
> is heavy (Next + Three.js + Rapier); its deps install on the user's first
|
|
66
|
+
> `dev`/`edit`/`build`, not at install time. Bump the version and `npm publish`.
|
|
67
|
+
|
|
68
|
+
## CLI
|
|
69
|
+
|
|
70
|
+
Like other static site generators (Hugo, Astro, Eleventy), Otherplane exposes a
|
|
71
|
+
small set of verbs through one CLI (`bin/otherplane.mjs`). Run them as
|
|
72
|
+
`otherplane <verb>` or via the `npm run` aliases below:
|
|
73
|
+
|
|
74
|
+
| Command | What it does |
|
|
75
|
+
| :----------------- | :-------------------------------------------------------------------------- |
|
|
76
|
+
| `npm run setup` | Install the engine's dependencies (one-time). |
|
|
77
|
+
| `npm run init` | Scaffold a fresh project (config, `rooms/`, `.env.example`) in this folder. |
|
|
78
|
+
| `npm run new` | Generate a room from a prompt (World Labs Marble). See below. |
|
|
79
|
+
| `npm run dev` | Dev server with the viewer (http://localhost:3000). |
|
|
80
|
+
| `npm run edit` | Dev server with **edit mode** on — mark/wire rooms in the viewer; writes `room.json` for you. |
|
|
81
|
+
| `npm run check` | Validate every `rooms/*/room.json` against the schema. |
|
|
82
|
+
| `npm run build` | Static export to `engine/out` — deploy this folder anywhere. `-- --base /museum` to mount under a sub-path. |
|
|
83
|
+
| `npm run serve` | Serve the built `engine/out` at http://localhost:8000. |
|
|
84
|
+
| `npm run preview` | `build` then `serve`. |
|
|
85
|
+
| `npm run clean` | Remove build output + the synced mirror. |
|
|
86
|
+
|
|
87
|
+
### Generating a room
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
cp .env.example .env # then paste your WORLD_LABS_KEY
|
|
91
|
+
npm run setup
|
|
92
|
+
npm run new -- "a cozy wood-paneled library, warm lamplight" --slug library --name "Library"
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
This calls World Labs Marble, downloads the splat (`.spz`) + collider (`.glb`)
|
|
96
|
+
into `worlds/`, saves the raw world object under `generated/<slug>/`, and writes a
|
|
97
|
+
`room.json` skeleton to `rooms/<slug>/`. Use `--model marble-1.0-draft` (the
|
|
98
|
+
default) while iterating — it's cheap (~$1.26).
|
|
99
|
+
|
|
100
|
+
Then mark coordinates and wire the room up in the viewer — see below.
|
|
101
|
+
|
|
102
|
+
### Marking & wiring (edit mode)
|
|
103
|
+
|
|
104
|
+
`npm run edit` opens the viewer with an authoring panel and writes `room.json`
|
|
105
|
+
for you (no clipboard, no hand-editing). Walk into a room, then:
|
|
106
|
+
|
|
107
|
+
- **C** — drop (or move the selected) **entryway** at your floor-snapped spot.
|
|
108
|
+
- **B** — aim and drop (or move the selected) **artifact** at what you're looking at.
|
|
109
|
+
- **F** select the marker under your crosshair · **Del** remove it · **Z** fly.
|
|
110
|
+
|
|
111
|
+
Every marker shows as a labeled orb (green entryway, cyan exit, amber artifact).
|
|
112
|
+
In the panel you name entryways, wire **exits** by menu (any room → entryway) or
|
|
113
|
+
paste an arbitrary URL (link into someone else's museum), promote an entryway
|
|
114
|
+
into a doorway, and click **two-way** to auto-create the return door in the target
|
|
115
|
+
room. Changes save to `rooms/<slug>/room.json` as you go.
|
|
116
|
+
|
|
117
|
+
## Hosting & repo size
|
|
118
|
+
|
|
119
|
+
Keep **text in git** (`room.json` + the shared bundle) and **blobs in object
|
|
120
|
+
storage**. A `.spz` is ~5 MB and a collider ~5 MB; for anything you want to keep,
|
|
121
|
+
mirror those to R2 (cheap, zero egress, CDN-backed) and point each `room.json`
|
|
122
|
+
URL there. Adding a room then costs the repo a few KB, not 10 MB. (Marble's own
|
|
123
|
+
CDN URLs have no documented retention SLA — self-host keepers.)
|
|
124
|
+
|
|
125
|
+
### Mounting on an existing site (e.g. `/museum`)
|
|
126
|
+
|
|
127
|
+
To hang the museum off a personal site — an "Enter my museum" button that goes to
|
|
128
|
+
`example.com/museum/` — build it under that sub-path and drop the output into your
|
|
129
|
+
site's tree:
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
npm run build -- --base /museum # or set "basePath": "/museum" in otherplane.config.json
|
|
133
|
+
# then copy engine/out/* into your site's /museum/ directory (or point the host at it)
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
The link on your homepage is just `<a href="/museum/">`. The museum is plain
|
|
137
|
+
static files, so it coexists with any host; the two builds stay separate (the
|
|
138
|
+
viewer carries a WebGL toolchain your main site shouldn't inherit) and integrate
|
|
139
|
+
at the deploy layer.
|
|
140
|
+
|
|
141
|
+
## License
|
|
142
|
+
|
|
143
|
+
MIT — see [`LICENSE`](LICENSE). Built on
|
|
144
|
+
[WorldSplats](https://github.com/philchacko/worldsplats) (the viewer),
|
|
145
|
+
[Spark.js](https://sparkjs.dev) (splat rendering), and
|
|
146
|
+
[World Labs Marble](https://marble.worldlabs.ai) (generation).
|