Docs
/

@leumas/body-3d

Procedurally composed 3D bodies: the entity-kit silhouette vocabulary, blueprint part lists, and the resolver that lets a game draw either without owning a single GLB.

@leumas/body-3d

Procedurally composed 3D bodies and props. three and data — no React, no DOM, no router.

import { bodyParts, partsToObject3D, blueprintParts } from '@leumas/body-3d';
import { createProceduralResolver } from '@leumas/body-3d/resolver';

const { root, limbs } = partsToObject3D({ body: 'bot', color: '#7fd1c0' });
scene.add(root);            // limbs: [{role: 'leg-l', hinge}, …] — what an animator drives

Why it exists

The silhouette vocabulary lived in @leumas/features/nav-views/world, which is a React package: importing it drags React, the router and a stylesheet. @leumas/game-3d needs the same bodies and has exactly one runtime dependency. So the geometry moved down to a package both can import; features/nav-views/world/bodies3d.js is now a one-line re-export shim, and every existing call site kept working.

What it holds

ExportWhat it is
bodyParts(id) · BODY3D_IDSthe entity-kit silhouette vocabulary, as shared cached geometry
bodyMaterials(color, emissive)the four material roles — body, light, dark, glow
`partsToObject3D({body \parts, color, scale})`parts list → Object3D, with limb hinges
blueprintParts(blueprint, seed)a forge catalogue record → a stand-in prop of the right size
createProceduralResolver({manifest, lookup})the resolveUrl / loadModel pair for the asset registry

The two things to know

Geometry is shared and you must not dispose it. Every camera in a room points at one tripod. A teardown that walks the tree calling geometry.dispose() blanks every other instance of that species, with no error, and the next mount renders nothing. Assembled meshes are tagged userData.sharedGeometry so a teardown can tell what it owns; disposeBodyCache() and disposeBlueprintCache() are the only things allowed to free them, and both are process-wide.

The resolver is a fallback, and the order matters. A source listed in the manifest resolves to its real URL and the registry's own GLTF loader fetches it, DRACO and KTX2 included. Only a source with no file becomes proc:<source> and is composed here. That is what makes a generated-asset pass an upgrade — same source strings, same call, better mesh when a file lands — rather than a migration out of something that replaced it.

Limbs

A part may name a role (leg-l, leg-r, arm-l, arm-r, head) and a pivot, the height its joint sits at. partsToObject3D hangs those parts off a hinge group at the pivot and offsets the mesh back to where it rests, so rotating the hinge swings the limb about the joint. Rotating the mesh instead swings it about its own centre, which reads as a limb being stirred rather than a body walking. Several parts may share a role and travel as one limb.

Bodies with no roles return limbs: [], and an animation driver over them is a no-op rather than an error — most silhouettes (a crate, a rack, a camera) have no joints.

@leumas/game-3d/anim drives these hinges from replicated velocity and grounded state.

What it exports

12 exports: 4 components, 0 hooks, 8 helpers. Import from @leumas/body-3d.

BLUEPRINT_SHAPESBODY_RESTBODY3D_IDSGLOW_RESTblueprintPartsbodyMaterialsbodyPartscreateProceduralResolverdisposeBlueprintCachedisposeBodyCachemanifestFromPackpartsToObject3D
Source shared/packages/body-3d/README.md (no-git)markdownjson
Generated from the Leumas repository. Every page cites the file it came from.leumas.techllms.txt