Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ town-kit/ Town & Market Kit: 27 pieces — street tiles (
tools/town-kit/ its reproducible build + e2e
arcane-kit/ Arcane Study Kit: 7 wizard's-study hero props with LODs (crystal ball,
alchemist's table, lectern, telescope, armillary, potions, rune rug; CC0, Meshy.ai)
aquarium-kit/ Aquarium Kit: 3 realistic reef fish (centre pivot, nose +Z, thin-film iridescence), a reef rock
arch, staghorn coral and 2 aquatic plants (CC0, Meshy.ai; kit.md, build in tools/aquarium-kit/)
avatars/ Adventurers: 9 rigged, animated characters (KayKit by Kay Lousberg, CC0) — the
app's player avatars, placeable as animated models (Idle loops)
tools/avatars/ their build: merge to one skinned mesh + 1 material, shared clips, thumbnails;
Expand Down
Binary file not shown.
Binary file not shown.
Binary file added aquarium-kit/CoralArch/thumb.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
Binary file added aquarium-kit/CoralBranch/thumb.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
Binary file not shown.
Binary file added aquarium-kit/FishAngel/thumb.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file added aquarium-kit/FishClown/thumb.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added aquarium-kit/FishTang/glTF-Binary/fish-tang.glb
Binary file not shown.
Binary file not shown.
Binary file added aquarium-kit/FishTang/thumb.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
Binary file added aquarium-kit/PlantEelgrass/thumb.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
Binary file added aquarium-kit/PlantSword/thumb.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 11 additions & 0 deletions aquarium-kit/attribution.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<h1><b>Attribution Statement</b></h1>

<p>Aquarium Kit (<code>aquarium-kit</code>) is © theprototype and released under
<a href="https://creativecommons.org/publicdomain/zero/1.0/">CC0 1.0</a> — use it for
anything, no credit required.</p>
<br />
<p>Made with <a href="https://www.meshy.ai">Meshy.ai</a>: every piece (clownfish, regal blue tang,
emperor angelfish, reef rock arch, staghorn coral, Amazon sword plant, eelgrass clump) was
generated with Meshy's text-to-3D API on a paid plan (Meshy Terms of Use §3.2: paid-plan customers
own their output), then post-processed by theprototype (tools/aquarium-kit, tools/kit-build:
real metres, pivots, 1024² textures, a thin-film iridescence on the fish, LOD levels).</p>
Binary file added aquarium-kit/cover.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
114 changes: 114 additions & 0 deletions aquarium-kit/default.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
[
{
"name": "FishClown",
"screenshot": "thumb.webp",
"label": "Clownfish (realistic)",
"variants": {
"glTF-Binary": "fish-clown.glb"
},
"lods": [
{
"file": "fish-clown.lod1.glb",
"ratio": 0.5
},
{
"file": "fish-clown.lod2.glb",
"ratio": 0.39
}
],
"size": [0.132, 0.142, 0.32],
"box": [-0.066, -0.071, -0.16, 0.066, 0.071, 0.16],
"tris": 4000,
"bytes": 610416
},
{
"name": "FishTang",
"screenshot": "thumb.webp",
"label": "Regal blue tang (realistic)",
"variants": {
"glTF-Binary": "fish-tang.glb"
},
"lods": [
{
"file": "fish-tang.lod1.glb",
"ratio": 0.5
}
],
"size": [0.172, 0.218, 0.42],
"box": [-0.086, -0.109, -0.21, 0.086, 0.109, 0.21],
"tris": 3973,
"bytes": 588728
},
{
"name": "FishAngel",
"screenshot": "thumb.webp",
"label": "Emperor angelfish (realistic)",
"variants": {
"glTF-Binary": "fish-angel.glb"
},
"lods": [
{
"file": "fish-angel.lod1.glb",
"ratio": 0.5
}
],
"size": [0.176, 0.238, 0.42],
"box": [-0.088, -0.119, -0.21, 0.088, 0.119, 0.21],
"tris": 3908,
"bytes": 751488
},
{
"name": "CoralArch",
"screenshot": "thumb.webp",
"label": "Reef rock arch with coral (swim-through)",
"variants": {
"glTF-Binary": "coral-arch.glb"
},
"lods": [
{
"file": "coral-arch.lod1.glb",
"ratio": 0.51
}
],
"size": [1, 0.692, 0.544],
"box": [-0.5, 0, -0.272, 0.5, 0.692, 0.272],
"tris": 5209,
"bytes": 780648
},
{
"name": "CoralBranch",
"screenshot": "thumb.webp",
"label": "Staghorn coral on reef rock",
"variants": {
"glTF-Binary": "coral-branch.glb"
},
"size": [0.566, 0.5, 0.538],
"box": [-0.283, 0, -0.269, 0.283, 0.5, 0.269],
"tris": 5234,
"bytes": 875996
},
{
"name": "PlantSword",
"screenshot": "thumb.webp",
"label": "Amazon sword plant",
"variants": {
"glTF-Binary": "plant-sword.glb"
},
"size": [0.568, 0.9, 0.614],
"box": [-0.284, 0, -0.307, 0.284, 0.9, 0.307],
"tris": 4004,
"bytes": 928396
},
{
"name": "PlantEelgrass",
"screenshot": "thumb.webp",
"label": "Eelgrass clump (vallisneria)",
"variants": {
"glTF-Binary": "plant-eelgrass.glb"
},
"size": [0.768, 1.3, 0.772],
"box": [-0.384, 0, -0.386, 0.384, 1.3, 0.386],
"tris": 3509,
"bytes": 1033668
}
]
50 changes: 50 additions & 0 deletions aquarium-kit/kit.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Aquarium Kit — kit guide

Seven pieces for a reef tank, a pond or an underwater scene: three realistic reef fish, a reef
rock arch a fish can swim through, a staghorn coral and two aquatic plants. They were made for the
**Aquarium** example (Templates ▸ Examples), where the fish swim with core's general-purpose motion
nodes (Follow Path, Wander, Orient to Velocity, Body Wave) and wear the **Fish scales** thin-film
look.

## Scale and pivots

**1 unit = 1 metre.** The Aquarium's tank is 4 × 2 × 1.6 m, so the fish are sized for it (a real
clownfish is 0.1 m — scale a piece down for a desk tank).

| Piece | Size x × y × z (m) | Tris | LODs | Pivot | Place it |
|---|---|---|---|---|---|
| `FishClown` | 0.13 × 0.14 × 0.32 | 4000 | 2 | **centre**, nose +Z | free in the water |
| `FishTang` | 0.17 × 0.22 × 0.42 | 3973 | 1 | **centre**, nose +Z | free in the water |
| `FishAngel` | 0.18 × 0.24 × 0.42 | 3908 | 1 | **centre**, nose +Z | free in the water |
| `CoralArch` | 1.0 × 0.69 × 0.54 | 5209 | 1 | bottom centre | on the sand; the opening runs along X |
| `CoralBranch` | 0.57 × 0.5 × 0.54 | 5234 | — | bottom centre | on the sand or a rock |
| `PlantSword` | 0.57 × 0.9 × 0.61 | 4004 | — | bottom centre | on the sand, back of the tank |
| `PlantEelgrass` | 0.77 × 1.3 × 0.77 | 3509 | — | bottom centre | in clumps along the back glass |

(`kit-build dims` writes the exact `size` / `box` of every row into `default.json`.)

The fish face **+Z** and turn about their **middle** (the new `center` pivot rule in
`tools/kit-build`): that is what Follow Path, Orient to Velocity and Body Wave expect (`forward`
+z), and what lets a fish bank and bend about its own body.

## The look

The fish carry **KHR_materials_iridescence** (factor 0.55, IOR 1.45, 140-460 nm): the thin-film
flash of a turning fish. Core draws it at the device's **look tier** — on a desktop and a phone, not
in a headset (40 F16). The plants are double-sided (their ribbon leaves have no back faces).

## Levels of detail

`kit-build lod` built levels for the fish and the arch. The branching coral and both plants have
none: the visual gate refuses every candidate (thin branches and ribbon leaves need impostor
levels, roadmap-33 F2 — listed in `tools/kit-build/allow.json`); core's automatic runtime LOD still
applies.

## How it was made

Meshy.ai, preview-then-refine under the `40-aquarium` cap (310): eleven previews (5 credits each,
`meshy-6-lite`, style none — "realistic"), looked at as a sheet; one rejected (a potted garden plant)
and one angelfish dropped for a spike; seven refined (10 each); the arch (black inside) and the
Amazon sword (lime, dark spots) retextured (10 each) — **145 credits**. Then
`node tools/aquarium-kit/build.mjs` (kit-build's post step + the film + double-sided leaves),
`kit-build lod`, `thumbs`, `dims --write`, `check`.
22 changes: 16 additions & 6 deletions index.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,8 @@
"copyright": "(c) theprototype",
"license": "CC0-1.0",
"source": "https://github.com/theprototype-app/packs"
},
{
},
{
"name": "architecture-kit",
"title": "Modular Architecture Kit",
"value": "architecture-kit/default.json",
Expand All @@ -53,8 +53,8 @@
"license": "CC0-1.0",
"source": "https://github.com/theprototype-app/packs/tree/main/architecture-kit",
"cover": "architecture-kit/cover.webp"
},
{
},
{
"name": "props-kit",
"title": "Props & Interiors",
"value": "props-kit/default.json",
Expand All @@ -63,8 +63,8 @@
"license": "CC0-1.0",
"source": "https://github.com/theprototype-app/packs/tree/main/props-kit",
"cover": "props-kit/cover.webp"
},
{
},
{
"name": "scifi-kit",
"title": "Sci-fi & Modern Kit",
"value": "scifi-kit/default.json",
Expand Down Expand Up @@ -114,6 +114,16 @@
"source": "https://github.com/theprototype-app/packs/tree/main/arcane-kit",
"cover": "arcane-kit/cover.webp"
},
{
"name": "aquarium-kit",
"title": "Aquarium Kit (reef fish, coral, aquatic plants)",
"value": "aquarium-kit/default.json",
"attribution": "aquarium-kit/attribution.html",
"copyright": "(c) theprototype",
"license": "CC0-1.0",
"source": "https://github.com/theprototype-app/packs/tree/main/aquarium-kit",
"cover": "aquarium-kit/cover.webp"
},
{
"name": "avatars",
"title": "Adventurers (rigged characters)",
Expand Down
89 changes: 89 additions & 0 deletions tools/aquarium-kit/build.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// build: kit.json → aquarium-kit/ (the pack), reproducibly, from the staged Meshy outputs (roadmap 40
// F14). Nothing here spends credits: it reads staging/40-aquarium/<job>/<stage>-<n>/raw.glb.
//
// node tools/aquarium-kit/build.mjs [--only Name,Name]
// node tools/kit-build/kit-build.mjs lod aquarium-kit && node tools/kit-build/kit-build.mjs thumbs aquarium-kit
// node tools/kit-build/kit-build.mjs dims aquarium-kit --write && node tools/kit-build/kit-build.mjs check aquarium-kit
//
// Per item: kit-build's post step (meshy-post: weld, decimate to targetTris, real metres, pivot,
// 1024² JPEG; the emissive policy; defight), then two material touches the glTF carries to core:
// film — KHR_materials_iridescence on every material: the thin-film flash of a turning fish
// (core's look tier draws it on desktops and phones and drops it in a headset, 40 F16)
// doubleSided — thin leaves are seen from both sides (a plant's ribbons have no back faces)
// Output: aquarium-kit/<Name>/glTF-Binary/<file> + aquarium-kit/default.json (the model list; the
// lods / size / box / tris / bytes fields are added by kit-build lod + dims).
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { kitPost } from '../kit-build/lib/post.mjs';
import { load, makeIO } from '../kit-build/lib/deps.mjs';

const HERE = path.dirname(fileURLToPath(import.meta.url));
const REPO = path.resolve(HERE, '../..');
const kit = JSON.parse(fs.readFileSync(path.join(HERE, 'kit.json'), 'utf8'));
const STAGING = process.env.MESHY_STAGING || path.join(os.homedir(), '.code/lanes-30/meshy/staging', kit.staging);
const PACK = path.join(REPO, kit.pack);
const { KHRMaterialsIridescence } = await load('@gltf-transform/extensions');
const io = makeIO();

const args = process.argv.slice(2);
const only = args.includes('--only') ? new Set(args[args.indexOf('--only') + 1].split(',')) : null;

/** newest staged raw for a job/stage @param {{job: string, stage: string}} src */
function stagedRaw(src) {
const dir = path.join(STAGING, src.job);
const n = fs
.readdirSync(dir)
.filter((d) => d.startsWith(src.stage + '-') && fs.existsSync(path.join(dir, d, 'raw.glb')))
.map((d) => Number(d.slice(src.stage.length + 1)))
.sort((a, b) => b - a)[0];
if (!n) throw new Error(`no staged ${src.stage} for ${src.job} under ${dir}`);
return path.join(dir, `${src.stage}-${n}`, 'raw.glb');
}

/** the thin film on every material of a file @param {string} file @param {{factor: number, ior: number, thickness: number[]}} film */
async function addFilm(file, film) {
const doc = await io.read(file);
const ext = doc.createExtension(KHRMaterialsIridescence);
for (const mat of doc.getRoot().listMaterials()) {
const irid = ext
.createIridescence()
.setIridescenceFactor(film.factor)
.setIridescenceIOR(film.ior)
.setIridescenceThicknessMinimum(film.thickness[0])
.setIridescenceThicknessMaximum(film.thickness[1]);
mat.setExtension('KHR_materials_iridescence', irid);
}
await io.write(file, doc);
}

/** @param {string} file */
async function setDoubleSided(file) {
const doc = await io.read(file);
for (const mat of doc.getRoot().listMaterials()) mat.setDoubleSided(true);
await io.write(file, doc);
}

const rows = [];
const report = [];
const existing = fs.existsSync(path.join(PACK, 'default.json')) ? JSON.parse(fs.readFileSync(path.join(PACK, 'default.json'), 'utf8')) : [];
for (const it of kit.items) {
const prior = existing.find((/** @type {any} */ r) => r.name === it.name);
if (only && !only.has(it.name)) {
if (prior) rows.push(prior);
continue;
}
const raw = stagedRaw(it.src);
const out = path.join(PACK, it.name, 'glTF-Binary', it.file);
fs.mkdirSync(path.dirname(out), { recursive: true });
const r = await kitPost(raw, out, { id: it.name, ...it.post });
if (it.film) await addFilm(out, kit.film);
if (it.doubleSided) await setDoubleSided(out);
report.push({ name: it.name, raw: path.relative(STAGING, raw), tris: r.trisOut ?? r.tris, bytes: fs.statSync(out).size, film: !!it.film });
// keep what kit-build measured last time (lods, dims) until it measures again
rows.push({ ...(prior ?? {}), name: it.name, screenshot: 'thumb.webp', label: it.label, variants: { 'glTF-Binary': it.file } });
console.log(`${it.name}: ${path.relative(REPO, out)} (${fs.statSync(out).size} bytes)`);
}
fs.writeFileSync(path.join(PACK, 'default.json'), JSON.stringify(rows, null, 1) + '\n');
fs.writeFileSync(path.join(HERE, 'report.json'), JSON.stringify(report, null, 1) + '\n');
21 changes: 21 additions & 0 deletions tools/aquarium-kit/kit.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"pack": "aquarium-kit",
"staging": "40-aquarium",
"items": [
{"name": "FishClown", "file": "fish-clown.glb", "label": "Clownfish (realistic)", "src": {"job": "fish-clown-a", "stage": "refine"},
"post": {"targetTris": 4000, "dims": {"z": 0.32}, "fit": "contain", "pivot": "center"}, "film": true, "category": "prop", "pivotRule": "center"},
{"name": "FishTang", "file": "fish-tang.glb", "label": "Regal blue tang (realistic)", "src": {"job": "fish-tang-a", "stage": "refine"},
"post": {"targetTris": 4000, "dims": {"z": 0.42}, "fit": "contain", "pivot": "center"}, "film": true, "category": "prop", "pivotRule": "center"},
{"name": "FishAngel", "file": "fish-angel.glb", "label": "Emperor angelfish (realistic)", "src": {"job": "fish-angel-a", "stage": "refine"},
"post": {"targetTris": 4000, "dims": {"z": 0.42}, "fit": "contain", "pivot": "center"}, "film": true, "category": "prop", "pivotRule": "center"},
{"name": "CoralArch", "file": "coral-arch.glb", "label": "Reef rock arch with coral (swim-through)", "src": {"job": "coral-arch-b", "stage": "retexture"},
"post": {"targetTris": 5000, "dims": {"x": 1.0}, "fit": "contain", "pivot": "bottom-center"}},
{"name": "CoralBranch", "file": "coral-branch.glb", "label": "Staghorn coral on reef rock", "src": {"job": "coral-branch-a", "stage": "refine"},
"post": {"targetTris": 5000, "dims": {"y": 0.5}, "fit": "contain", "pivot": "bottom-center"}},
{"name": "PlantSword", "file": "plant-sword.glb", "label": "Amazon sword plant", "src": {"job": "plant-sword-b", "stage": "retexture"},
"post": {"targetTris": 4000, "dims": {"y": 0.9}, "fit": "contain", "pivot": "bottom-center"}, "doubleSided": true},
{"name": "PlantEelgrass", "file": "plant-eelgrass.glb", "label": "Eelgrass clump (vallisneria)", "src": {"job": "plant-vallis-a", "stage": "refine"},
"post": {"targetTris": 4000, "dims": {"y": 1.3}, "fit": "contain", "pivot": "bottom-center"}, "doubleSided": true}
],
"film": {"factor": 0.55, "ior": 1.45, "thickness": [140, 460]}
}
Loading
Loading