Concepts
The Quaternius CC0 model library with its provenance, shared by Vishnu and Stage
The Quaternius models that Vishnu and Stage share, with their provenance. One copy of each model lives here: Vishnu bakes its art from it and Stage ships the models it shows as split assets.
packs/<pack>/ holds, for each of the 21 packs:
models/, the.glbor.gltffiles;PROVENANCE.json, the pack’s receipt: creator, licence, the archive each model came from and the sha256 of every model as shipped;License.txtandCREDITS.txt, the CC0 1.0 notice and attribution (thestorage-bpack carriesInterior-License.txtandSurvival-License.txt).
The models are CC0 1.0 and stay so; see NOTICE.md. The package code and Botanical’s adaptations are MIT; see LICENSE.
The medieval-village, buildings, farm-animals and animated-animals
packs, and every characters chibi except the three adaptations (Casual_Male,
Zombie_Male, Zombie_Female), ship meshopt-compressed GLB
(EXT_meshopt_compression, quantized attributes); read them with the three
entry’s modelLoader() (below). A receipt that names a compressor records the
gltfpack command and version; each compressed model’s fileLicences basis says
so, and a .blend original’s uncompressed export is recorded as
convertedSha256.
Pack code (builders, dimensions, baked atlases, icons) and the Blender export scripts stay with Vishnu’s art owners. The brute’s derived model and the scale-reference character are not part of this package.
Core concepts
Section titled “Core concepts”Discover and pin a model id with models, searchModels and requireModel
from @fungi.computer/art-library/models first. A selected row contains
metadata, not a loaded scene or a deployed URL. Import its matching single-model
URL module so a bundler can emit that asset without importing a whole pack’s URL
table:
import alpacaUrl from "@fungi.computer/art-library/packs/animated-animals/urls/alpaca.glb.js";import { modelLoader } from "@fungi.computer/art-library/three";
const { scene, animations } = await modelLoader().loadAsync(alpacaUrl.href);console.log(scene, animations);Keep this usage guide here rather than in an emitted module header: Vishnu’s bake manifests pin the exact loader bytes, including preserved comments.
A pack is a folder of related models with one receipt. packFile(pack, path)
locates a file in a pack wherever the package is installed and refuses a path
that leaves the pack. artPacks lists the pack ids.
A bundler that emits assets imports a file directly through the package’s
./packs/* export, so the model becomes its own file in the build instead of
inlined bytes. Set the bundler’s inline limit to zero when models must stay
files:
import apple from "@fungi.computer/art-library/packs/food-a/models/apple.glb?url";Both forms below resolve from the package wherever it is installed, in Node and
in a dev server alike (a literal new URL("../models/<file>", import.meta.url)
inside the package).
-
A caller that needs specific models imports each one, so a bake pins only those:
TS import apple from "@fungi.computer/art-library/packs/food-a/urls/apple.glb.js"; -
A caller that loops over a whole pack imports its table:
TS import { foodAModelUrls } from "@fungi.computer/art-library/packs/food-a/urls.js";const apple = foodAModelUrls["models/apple.glb"]; // a URLA bundler that emits assets emits every model of a pack whose table it imports, so prefer the single-model form for fixed picks.
Run pnpm run generate (then prettier, from the repository root) after adding
or removing a model; the package test fails while the tables disagree with
models/.
Models ship compressed by gltfpack (meshopt compression with mesh quantization).
To read them with three.js, take the loader from the three entry; it sets
three’s meshopt decoder, and a bare GLTFLoader refuses them:
import { modelLoader } from "@fungi.computer/art-library/three";
const { scene } = await modelLoader().loadAsync(modelUrl);The model catalog
Section titled “The model catalog”Browsers and any reader that cannot read packs from disk use the generated
catalog. @fungi.computer/art-library/models exports models (every row, typed
in model-data.ts), searchModels, requireModel and UnknownModelError, and
imports nothing from Node. pnpm run generate rewrites it from the receipts and
model files, and the package test fails while it is stale. The Node
modelCatalog() below is its generator and oracle.
modelCatalog() from @fungi.computer/art-library/catalog (Node) lists the
library’s models as rows. Each row is derived from a pack’s PROVENANCE.json
and its model file, so editing a receipt is the only edit a rename needs and no
second manifest exists:
import { modelCatalog, requireModel, searchModels,} from "@fungi.computer/art-library/catalog";
const catalog = await modelCatalog();searchModels(catalog, "fridge")[0]?.id; // "interiors/kitchen-fridge"requireModel(catalog, "food-a/apple").height; // 0.09 (metres)requireModel(catalog, "food-a/aple"); // throws, listing food-a/apple and 2 moreA row has id (<pack>/<item>), kind, name, tags, description,
width, height and depth (metres, full precision), sizeSource,
sizeNote, file, author, originalLicense (the upstream pack’s) and
fileLicense (the shipped file’s). Packs whose receipts list no items are
absent, and a pack’s reused rows add nothing because the owning pack already
lists the file.
Sizes come from the model’s bounding box, scaled to the real size its receipt gives. The box alone is in the units the art was drawn in (a fridge is 3.3 units tall). The receipts own every real size, and Vishnu reads them from here rather than keeping a copy:
-
heightMetresorlengthMetres(longest axis) is a size the art’s owners declared;sizeSourceisdeclared. -
typicalLengthMetreswith atypicalNoteis a reference size for the thing (a baseball bat is about 0.84 m) for models no one declared a size for;sizeSourceistypicalandsizeNotesays why. -
typicalHeightMetres(with atypicalNote) is the same for a thing known by its height, such as an animal. -
A row with no size of its own uses the pack’s
sharedMetresPerUnit(metres per drawing unit). The characters declare theirs, sosizeSourceisdeclared; the village and buildings packs estimate theirs from a door and a house (sharedMetresPerUnitNote), so those rows saytypical.
An item with two sizes, or with none and no pack scale, is an error, so no row
is in drawing units. A pack names the kind of its models (character, animal,
building, furniture, street, vehicle, food or item), and a row can
override it with its own kind (a cart in the village pack is a vehicle). The
catalog also reads EXT_meshopt_compression models.
Packed consumer readiness
Section titled “Packed consumer readiness”Run pnpm run build, pnpm run pack and pnpm run stranger in this package.
The maintained stranger proof installs only the tarball into a fresh npm
consumer, checks the exact shipped files, runs the public runtime fixture and
compiles the entrypoint with strict NodeNext. This does not publish to npm.