Skip to content
Concepts

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 .glb or .gltf files;
  • PROVENANCE.json, the pack’s receipt: creator, licence, the archive each model came from and the sha256 of every model as shipped;
  • License.txt and CREDITS.txt, the CC0 1.0 notice and attribution (the storage-b pack carries Interior-License.txt and Survival-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.

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:

TS
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:

TS
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 URL

    A 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:

TS
import { modelLoader } from "@fungi.computer/art-library/three";
const { scene } = await modelLoader().loadAsync(modelUrl);

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:

TS
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 more

A 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:

  • heightMetres or lengthMetres (longest axis) is a size the art’s owners declared; sizeSource is declared.

  • typicalLengthMetres with a typicalNote is a reference size for the thing (a baseball bat is about 0.84 m) for models no one declared a size for; sizeSource is typical and sizeNote says why.

  • typicalHeightMetres (with a typicalNote) 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, so sizeSource is declared; the village and buildings packs estimate theirs from a door and a house (sharedMetresPerUnitNote), so those rows say typical.

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.

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.