How it’s built · Image to 3D asset
Understory Studio
Language models are poor at writing raw 3D geometry: ask for vertices and you get a melted shape. Understory never asks. The model writes a short design made of named, parametric parts, and trusted code builds the mesh. Then the model sees renders of what it built beside your reference pictures and corrects itself.
- 1–3reference pictures in
- 5levels of detail from one design
- 87animation clips for rigged characters
- 96unit tests on the app’s services
From pictures to an asset
- ReferencesOne to three pictures of the object or character, from any angles you have.
- DesignThe model writes a seeded function that returns materials and named parts: boxes, ellipsoids, tubes, lathes and extrusions.
- BuildA trusted builder validates the design and constructs the mesh and procedural textures.
- Self-reviewFront, side and back renders go back to a vision model beside the references, and it revises the design.
- Rig and exportCharacters get a skeleton, skin weights and animations; everything exports as GLB.
Design decisions
The contract is the product
Because the model writes a declarative design rather than three.js code, its output can be validated, re-seeded and edited by hand. The design function runs in a disposable worker inside an opaque-origin frame with no network, and only plain JSON comes back.
Five LODs, identical placement
All five levels of detail are derived from the same description by scaling each part’s resolution, with an optional last level per part. Parts never drift between levels, because there is only one source.
Originals are never replaced
An optional triangle clean-up (weld, remove hidden faces, then simplify with meshoptimizer) is built beside the original master and LODs, never over them, and export can include both.
Ported, then made smaller
It started as a standalone Vite project with excellent output and a weak UI. The port kept the modelling core verbatim and deleted about 900 lines of provider and settings code that the hub already supplies to every app.
Problems worth solving
- Bounds that ignore skinning.
Box3.setFromObjecton a skinned mesh reads the bind-pose geometry, so a pose test written against bounds passes whether or not the fix works. The tests measure bone world positions instead. - Bent limbs defeat rigid fits. The clean-up removes triangles hidden inside other parts. Fitting a posed arm rigidly across its elbow left a residual larger than the arm, so nothing was removed. Bent parts are now tested by winding number against their posed triangles.
- Test fixtures can lie. An eight-vertex box can only shear under skinning, never bend, so it “proved” bugs that were not there. Fixtures use subdivided geometry.
- Workers and import maps. Module workers do not inherit the page’s import map. The vendored rigging solver’s bare
threeimports had to be rewritten to relative paths, and the only symptom of a wrong path was an error event with an empty message.