@developerz.ai/ts23d 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +11 -0
- package/CLAUDE.md +1 -1
- package/README.md +10 -5
- package/dist/{chunk-a8fxakcj.js → chunk-6b05mnmx.js} +2 -2
- package/dist/{chunk-kgvbpqqw.js → chunk-7rtfjh9f.js} +6 -5
- package/dist/chunk-7rtfjh9f.js.map +10 -0
- package/dist/{chunk-1ypfs7ga.js → chunk-8j4c28jt.js} +790 -276
- package/dist/chunk-8j4c28jt.js.map +46 -0
- package/dist/{chunk-wtqhq529.js → chunk-d1fv6hrm.js} +6 -6
- package/dist/{chunk-wtqhq529.js.map → chunk-d1fv6hrm.js.map} +1 -1
- package/dist/{chunk-gcyp9b4d.js → chunk-dbq4qf52.js} +362 -215
- package/dist/chunk-dbq4qf52.js.map +25 -0
- package/dist/{chunk-xnt2sdhf.js → chunk-drna2rny.js} +2 -2
- package/dist/{chunk-ravbb06q.js → chunk-dxvypr7y.js} +2 -2
- package/dist/{chunk-11v28977.js → chunk-f6yme003.js} +85 -71
- package/dist/{chunk-11v28977.js.map → chunk-f6yme003.js.map} +6 -6
- package/dist/{chunk-4ddzsqtc.js → chunk-hg7pf34j.js} +4 -3
- package/dist/{chunk-4ddzsqtc.js.map → chunk-hg7pf34j.js.map} +5 -5
- package/dist/{chunk-w1zyhj40.js → chunk-jq4e17tj.js} +2 -2
- package/dist/{chunk-kvcep7ft.js → chunk-y63c6zcv.js} +967 -218
- package/dist/chunk-y63c6zcv.js.map +53 -0
- package/dist/cli/args.d.ts +7 -1
- package/dist/cli/bin.js +66 -25
- package/dist/cli/bin.js.map +6 -5
- package/dist/cli/manifest.d.ts +5 -1
- package/dist/cli/texture-cache.d.ts +4 -0
- package/dist/core/definition.d.ts +7 -0
- package/dist/core/errors.d.ts +1 -1
- package/dist/core/index.js +1 -1
- package/dist/export/credits.d.ts +4 -0
- package/dist/export/glb-options.d.ts +48 -0
- package/dist/export/glb.d.ts +5 -35
- package/dist/export/hierarchy.d.ts +23 -0
- package/dist/export/index.d.ts +2 -1
- package/dist/export/index.js +21 -7
- package/dist/export/index.js.map +1 -1
- package/dist/export/lod-coverage.d.ts +12 -0
- package/dist/export/metadata.d.ts +2 -0
- package/dist/export/version.d.ts +2 -2
- package/dist/grammar/index.js +2 -2
- package/dist/index.js +63 -17
- package/dist/index.js.map +1 -1
- package/dist/kinds/head-armour.d.ts +3 -7
- package/dist/kinds/head-bust.d.ts +3 -2
- package/dist/kinds/head-face.d.ts +3 -1
- package/dist/kinds/head-garb.d.ts +4 -0
- package/dist/kinds/head-mesh.d.ts +4 -0
- package/dist/kinds/head-mouth.d.ts +4 -0
- package/dist/kinds/head-params.d.ts +9 -1
- package/dist/kinds/head-tone.d.ts +10 -0
- package/dist/kinds/head-torso.d.ts +35 -0
- package/dist/kinds/index.js +6 -6
- package/dist/material/cc0.d.ts +4 -0
- package/dist/material/define.d.ts +34 -0
- package/dist/material/generator.d.ts +1 -1
- package/dist/material/index.d.ts +8 -3
- package/dist/material/index.js +39 -7
- package/dist/material/index.js.map +1 -1
- package/dist/material/library.d.ts +7 -0
- package/dist/material/material-def.d.ts +30 -0
- package/dist/material/paint/canvas.d.ts +7 -0
- package/dist/material/paint/default-sheet.d.ts +3 -1
- package/dist/material/paint/fabric.d.ts +4 -0
- package/dist/material/paint/image-strip.d.ts +4 -0
- package/dist/material/paint/kit.d.ts +5 -0
- package/dist/material/paint/metal.d.ts +3 -0
- package/dist/material/paint/orm.d.ts +1 -1
- package/dist/material/paint/strip.d.ts +1 -1
- package/dist/material/paint/types.d.ts +32 -2
- package/dist/material/palette.d.ts +2 -0
- package/dist/material/slots.d.ts +5 -0
- package/dist/material/texture-source.d.ts +34 -0
- package/dist/material/trim-sheet.d.ts +20 -4
- package/dist/neural/index.js +2 -2
- package/dist/passes/index.js +2 -2
- package/dist/primitives/index.js +3 -3
- package/dist/qa/index.js +2 -2
- package/dist/render/beauty.d.ts +1 -1
- package/dist/render/index.js +3 -3
- package/dist/render/lighting.d.ts +7 -0
- package/dist/render/shade.d.ts +1 -1
- package/dist/testing/index.js +5 -5
- package/docs/README.md +4 -0
- package/docs/architecture/gltf-capabilities.md +38 -1
- package/docs/architecture/materials.md +151 -0
- package/docs/idea/10-sculpting.md +166 -0
- package/docs/idea/11-materials-authoring.md +132 -0
- package/docs/idea/12-blender-bridge.md +136 -0
- package/docs/idea/13-asset-pipeline-research.md +89 -0
- package/docs/plans/roadmap.md +12 -0
- package/package.json +2 -1
- package/recipes/boxcar/README.md +7 -0
- package/recipes/boxcar/boxcar.ts +118 -34
- package/recipes/goblin_head/README.md +27 -14
- package/recipes/goblin_head/goblin_head.ts +11 -9
- package/recipes/human_head/README.md +8 -4
- package/recipes/human_head/human_head.ts +4 -3
- package/recipes/material_swatches/material_swatches.ts +11 -5
- package/recipes/orc_head/README.md +32 -20
- package/recipes/orc_head/orc_head.ts +9 -7
- package/src/cli/args.ts +16 -3
- package/src/cli/manifest.ts +6 -0
- package/src/cli/run.ts +33 -9
- package/src/cli/texture-cache.ts +24 -0
- package/src/core/asset.ts +1 -0
- package/src/core/definition.ts +7 -0
- package/src/core/errors.ts +6 -1
- package/src/export/credits.ts +25 -0
- package/src/export/glb-options.ts +53 -0
- package/src/export/glb.ts +100 -113
- package/src/export/hierarchy.ts +122 -0
- package/src/export/index.ts +17 -1
- package/src/export/lod-coverage.ts +71 -0
- package/src/export/metadata.ts +3 -0
- package/src/export/version.ts +1 -1
- package/src/kinds/head-armour.ts +103 -73
- package/src/kinds/head-bust.ts +49 -86
- package/src/kinds/head-face.ts +94 -74
- package/src/kinds/head-garb.ts +181 -0
- package/src/kinds/head-hair.ts +74 -33
- package/src/kinds/head-mesh.ts +43 -5
- package/src/kinds/head-mouth.ts +111 -0
- package/src/kinds/head-params.ts +11 -2
- package/src/kinds/head-presets.ts +30 -27
- package/src/kinds/head-tone.ts +52 -0
- package/src/kinds/head-torso.ts +230 -0
- package/src/kinds/head.ts +8 -3
- package/src/material/cc0.ts +41 -0
- package/src/material/define.ts +130 -0
- package/src/material/generator.ts +15 -8
- package/src/material/index.ts +34 -3
- package/src/material/library.ts +196 -0
- package/src/material/material-def.ts +84 -0
- package/src/material/paint/canvas.ts +11 -1
- package/src/material/paint/compose.ts +1 -1
- package/src/material/paint/decode-png.ts +19 -13
- package/src/material/paint/default-sheet.ts +40 -28
- package/src/material/paint/fabric.ts +63 -0
- package/src/material/paint/image-strip.ts +88 -0
- package/src/material/paint/kit.ts +37 -0
- package/src/material/paint/masks.ts +3 -1
- package/src/material/paint/metal.ts +109 -0
- package/src/material/paint/orm.ts +14 -1
- package/src/material/paint/planks.ts +49 -4
- package/src/material/paint/strip.ts +35 -29
- package/src/material/paint/types.ts +45 -2
- package/src/material/paint/validate.ts +19 -3
- package/src/material/palette.ts +3 -0
- package/src/material/slots.ts +18 -0
- package/src/material/texture-source.ts +86 -0
- package/src/material/trim-sheet.ts +79 -13
- package/src/render/beauty.ts +2 -2
- package/src/render/lighting.ts +26 -0
- package/src/render/shade.ts +10 -5
- package/dist/chunk-1ypfs7ga.js.map +0 -42
- package/dist/chunk-gcyp9b4d.js.map +0 -22
- package/dist/chunk-kgvbpqqw.js.map +0 -10
- package/dist/chunk-kvcep7ft.js.map +0 -43
- /package/dist/{chunk-a8fxakcj.js.map → chunk-6b05mnmx.js.map} +0 -0
- /package/dist/{chunk-xnt2sdhf.js.map → chunk-drna2rny.js.map} +0 -0
- /package/dist/{chunk-ravbb06q.js.map → chunk-dxvypr7y.js.map} +0 -0
- /package/dist/{chunk-w1zyhj40.js.map → chunk-jq4e17tj.js.map} +0 -0
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
# 10 — Sculpting: what artists do, and what it means for SDF edit lists
|
|
2
|
+
|
|
3
|
+
**Artists fight topology so they can push clay; ts23d has no topology until it meshes. Most of the sculpting toolbox maps onto the existing `sculpt` edit list as new edit kinds, not as a mesh editor.** This page surveys how sculptors work (Blender, ZBrush, the SpeedSculpt add-on), maps each technique onto `src/primitives/sculpt*.ts` and `sdf*.ts`, and proposes the next DSL steps. Research as of 2026-11; everything under "Proposed" is **planned, not shipped**.
|
|
4
|
+
|
|
5
|
+
## How sculptors work
|
|
6
|
+
|
|
7
|
+
| Technique | What it does | Source |
|
|
8
|
+
|---|---|---|
|
|
9
|
+
| **Voxel remesh** (Blender Remesh, ZBrush DynaMesh) | Rebuild the whole mesh as an even, manifold grid at one resolution. Blender's Remesh modifier has Blocks / Smooth / Sharp / Voxel modes; Voxel "generate[s] a new manifold mesh from the current geometry" via OpenVDB. Used for **base forms**: merge blobs, keep brush strokes even | [Blender: Remesh](https://docs.blender.org/manual/en/latest/modeling/modifiers/generate/remesh.html), [ZBrush base mesh creation](https://help.maxon.net/zbr/en-us/Content/html/features/main-features/base-mesh-creation/base-mesh-creation.html) |
|
|
10
|
+
| **Dyntopo** (dynamic topology) | Subdivides/collapses under the brush during a stroke: "most brushes will subdivide the mesh during the stroke". Detail size is Relative (screen pixels), Constant (fraction of a unit), Brush or Manual. Used for **local detail** without densifying everything | [Blender: Dyntopo](https://docs.blender.org/manual/en/latest/sculpt_paint/sculpting/tool_settings/dyntopo.html) |
|
|
11
|
+
| **Retopology** (ZRemesher, QuadriFlow, by hand) | After the form is final, a clean low-poly cage with controlled edge flow; community order is "DynaMesh first, ZRemesher last" | [ZBrushCentral thread](https://zbrushcentral.com/t/what-is-the-difference-between-dynamesh-and-zremesher-advantages/324598) (practitioner opinion) |
|
|
12
|
+
| **Skin / ZSpheres** | A vertex-and-edge skeleton with a radius per vertex becomes a mostly-quad tube surface. Blender: one **root** per connected set; **branch smoothing** relaxes joints "with the side effect of shrinking it". ZSpheres: "Adaptive Skin" around a sphere chain | [Blender: Skin](https://docs.blender.org/manual/en/latest/modeling/modifiers/generate/skin.html), [ZBrush ZSpheres](https://help.maxon.net/zbp/en-us/Content/html/4_palettes_tool-3-zsphere-settings.html) |
|
|
13
|
+
| **Lattice deform** | A coarse control cage bends everything inside it; non-destructive, keeps UVs, one lattice drives several objects. Theory: free-form deformation with trivariate Bernstein polynomials (Sederberg & Parry, SIGGRAPH 1986) | [Blender: Lattice modifier](https://docs.blender.org/manual/en/latest/modeling/modifiers/deform/lattice.html), [Sederberg & Parry 1986](https://history.siggraph.org/?p=116034) |
|
|
14
|
+
| **SpeedSculpt** (paid add-on, Pitiwazou) | A workflow wrapper over Blender: build from primitives with auto mirror + parenting, curves, **Draw Skin** (tails, horns), Dyntopo + Remesh, **Geomesh** ("create a final version of your meshes without having to combine them": non-destructive union preview), Booleans, Lattice from a mask, **Quick Pose** (bones for a part), Decimate, masks, cutters. Lathe appears in forum discussion but not on the current product page | [Superhive: SpeedSculpt](https://superhivemarket.com/products/speedsculpt), [Blender Artists thread](https://blenderartists.org/t/speedsculpt-addon/673722) |
|
|
15
|
+
| **Primary → secondary → tertiary** | Block the big planes (skull, cheekbones, sockets, jaw), then features and folds, then surface detail (pores, wrinkles). Stylised work exaggerates primaries and keeps planes crisp | [Polycount discussion](https://polycount.com/discussion/comment/2555887), Gnomon "Sculpting the Planes of the Head" (course; description only) |
|
|
16
|
+
| **Reference planes** | Front and side orthographic images behind the model; today often AI-generated turnarounds. Sculptors check silhouette against them in ortho views | Common practice; no single authoritative source |
|
|
17
|
+
|
|
18
|
+
**The pattern:** remesh, Dyntopo and retopology all exist because a triangle mesh stores the form *and* its sampling in the same data. Push a region and it runs out of triangles; merge two blobs and the topology tangles. An SDF edit list stores only the form. Sampling happens once, at the end, at whatever `resolution` the LOD asks for.
|
|
19
|
+
|
|
20
|
+
## What ts23d already has
|
|
21
|
+
|
|
22
|
+
| Artist concept | ts23d today | Where |
|
|
23
|
+
|---|---|---|
|
|
24
|
+
| Ordered clay edits: add, subtract, intersect, paint | `sculpt({ edits })`, Dreams-style smooth-min fold with `blend` radius; colour rides the same weights | `src/primitives/sculpt.ts`, `sculpt-tree.ts` |
|
|
25
|
+
| Non-destructive booleans (Geomesh) | Edits never collapse; `op: 'subtract' \| 'intersect'`; **groups** fold their own edits first, so a subtract inside a group trims only that group | `sculpt-edit.ts` (`SculptGroup`) |
|
|
26
|
+
| Voxel remesh (base forms) | `sdf()` samples a cubic grid; `resolution` (cell edge, metres) *is* the voxel size and the LOD knob; surface nets (default) or marching cubes; always closed | `sdf.ts`, `sdf-grid.ts`, `surface-nets.ts` |
|
|
27
|
+
| Symmetry (mirror X in Blender) | `mirror: 'y'`, per-edit opt-out | `sculpt.ts` |
|
|
28
|
+
| Primitive library | sphere, ellipsoid, capsule, roundBox, cone, torus + rotation | `sculpt-edit.ts`, `sdf-ops.ts` (`shapes`, `sd`) |
|
|
29
|
+
| Noise displacement (tertiary skin, rock pitting) | `sd.displace(f, valueNoise(rng), amount)`; `sculptField` exposes the field for it | `sdf-ops.ts`, `value-noise.ts` |
|
|
30
|
+
| Primary → secondary → tertiary order | The `head` kind writes skull edits primary → secondary, explicitly "the ZBrush order" | `src/kinds/head-face.ts`, `head-extras.ts` |
|
|
31
|
+
| Hand-painted cavity + top-light tone | `toned()` samples the field: cavity, warm key, cool bounce, into vertex colour | `src/kinds/head-tone.ts` |
|
|
32
|
+
| Smooth normals at low res | Normals from the field gradient, so a coarse LOD2 still shades like the exact surface (vertex-frequency "high→low") | `sdf.ts` |
|
|
33
|
+
| Sparse evaluation | Bounding-sphere skips, 8³ block culling by Lipschitz bound | `sculpt-tree.ts`, `sdf-grid.ts` |
|
|
34
|
+
| Lathe | Polygon `lathe` for hard surface (not an SDF shape) | `lathe.ts` |
|
|
35
|
+
| Lattice | **Name clash:** `lattice` is the truss primitive, not a deformer | `lattice.ts` |
|
|
36
|
+
|
|
37
|
+
Missing: brush strokes along a path, tapered tubes (draw-skin), revolve as an SDF shape, cage/lattice deformation, bend/twist/taper warps, posing a group about a pivot, adaptive resolution, reference-image overlays.
|
|
38
|
+
|
|
39
|
+
## Proposed: brushes as SDF edits (planned)
|
|
40
|
+
|
|
41
|
+
Each brush is a new **edit kind** in the same list, so it composes with groups, mirror, colour and blend for free. Every proposal keeps determinism (pure functions of the edit data) and the zero-dependency rule.
|
|
42
|
+
|
|
43
|
+
### 1. Strokes: `crease`, `inflate`, `flatten`, `pinch` along a path
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
interface SculptStroke {
|
|
47
|
+
readonly brush: 'clay' | 'crease' | 'inflate' | 'flatten' | 'pinch';
|
|
48
|
+
readonly path: readonly Vec3[]; // polyline, metres (a curve stroke)
|
|
49
|
+
readonly radius: number | readonly number[]; // per path point = pressure taper
|
|
50
|
+
readonly strength?: number; // metres of push at the centre, signed
|
|
51
|
+
readonly falloff?: 'smooth' | 'sharp' | 'linear';
|
|
52
|
+
readonly color?: Vec3; readonly mirror?: boolean;
|
|
53
|
+
}
|
|
54
|
+
type SculptEdit = SculptShapeEdit | SculptGroup | SculptStroke; // sculpt-edit.ts
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
| Brush | Field operation | Lipschitz cost |
|
|
58
|
+
|---|---|---|
|
|
59
|
+
| `clay` | smooth-union a flattened tapered capsule chain along the path | 1 (exact-ish) |
|
|
60
|
+
| `crease` | smooth-subtract a thin tapered capsule chain with a small `blend`: a V-groove (eyelid fold, armour seam, knuckle) | 1 |
|
|
61
|
+
| `inflate` | `d − strength · w(p)`, w = falloff of distance to the path | 1 + strength/radius |
|
|
62
|
+
| `flatten` | blend `d` toward the distance to the plane fitted through the path, weight w(p) | ≤ 2 |
|
|
63
|
+
| `pinch` | domain warp: pull `p` toward the path by `strength · w(p)` before evaluating everything earlier | 1/(1 − max warp gradient) |
|
|
64
|
+
|
|
65
|
+
The Lipschitz column matters: `sdf-grid.ts` skips whole 8³ blocks using a Lipschitz bound. Inflate and warps stretch distances, so each stroke must raise the bound it hands to `sampleGrid`, or the grid can cull real surface (a hole, not an error). The compiler sums the bounds; a test fails if a stroke's declared bound is exceeded at sample points. Size: **M** (crease/clay/inflate), **M** more for pinch/flatten.
|
|
66
|
+
|
|
67
|
+
### 2. Draw-skin: `skin()` as a capsule chain with two radii
|
|
68
|
+
|
|
69
|
+
```ts
|
|
70
|
+
interface SkinNode { readonly at: Vec3; readonly r: number }
|
|
71
|
+
function skin(o: {
|
|
72
|
+
nodes: readonly SkinNode[]; edges: readonly (readonly [number, number])[];
|
|
73
|
+
blend?: number; // branch smoothing: smooth-min radius at joints (metres)
|
|
74
|
+
color?: Vec3; op?: 'add' | 'subtract';
|
|
75
|
+
}): SculptGroup; // pure helper: returns edits, no new evaluator
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Needs one new shape: `roundCone` (a capsule with radius `r1` at `at`, `r2` at `to`; Quílez's exact round-cone distance). `blend` *is* Blender's branch smoothing, without the shrink: smooth-min adds material at joints instead of relaxing it away. Tails, horns, tentacles, roots, limbs. The same node graph becomes the bone list for glTF skins later ([gltf-capabilities](../architecture/gltf-capabilities.md#skins-how-ts23d-will-rig-without-auto-rigging)). Size: **S**.
|
|
79
|
+
|
|
80
|
+
### 3. Revolve: lathe as an SDF shape
|
|
81
|
+
|
|
82
|
+
`{ shape: 'revolve', profile: Vec2[], at, rotate? }`: the 2D polygon distance evaluated at `(√(x²+y²), z)`. Vases, helmets, pommels, mushroom caps that must blend into organic edits (the polygon `lathe` cannot smooth-union). Size: **S–M**.
|
|
83
|
+
|
|
84
|
+
### 4. Cage deform (FFD): `ffd()`
|
|
85
|
+
|
|
86
|
+
Named `ffd` because `lattice` is taken by the truss.
|
|
87
|
+
|
|
88
|
+
```ts
|
|
89
|
+
interface Ffd {
|
|
90
|
+
readonly box: readonly [Vec3, Vec3]; // the undeformed cage
|
|
91
|
+
readonly divisions: readonly [number, number, number]; // control points per axis, 2..6
|
|
92
|
+
readonly move: ReadonlyArray<{ readonly index: readonly [number, number, number]; readonly by: Vec3 }>;
|
|
93
|
+
}
|
|
94
|
+
function ffd(mesh: Mesh, cage: Ffd): Mesh; // passes tier: Bernstein FFD on vertices
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Apply it **after** meshing, to vertices (Sederberg–Parry forward map), then recompute normals. Works on any mesh (sculpt, loft, lathe, imported), keeps UVs and colours, is exact and cheap. Inverting FFD to warp an SDF query point needs Newton iteration per sample and breaks the Lipschitz cull, so field-space FFD is not proposed. Size: **S–M**. Use: squash a whole bust into a chibi, lean a tower, bulge a barrel.
|
|
98
|
+
|
|
99
|
+
### 5. Bend, twist, taper on groups
|
|
100
|
+
|
|
101
|
+
```ts
|
|
102
|
+
interface SculptGroup { /* … */ readonly warp?: { twist?: number /* °/m about local Z */;
|
|
103
|
+
bend?: number /* °/m */; taper?: number /* scale/m */; pivot?: Vec3; axis?: Vec3 } }
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Domain warps on the query point before the group's edits, like Quílez's twist/bend operators; each reports its Lipschitz factor (same rule as strokes). Horns that curl, banners, bent pipes in an organic piece. Size: **S**.
|
|
107
|
+
|
|
108
|
+
### 6. Quick pose: a group about a pivot
|
|
109
|
+
|
|
110
|
+
```ts
|
|
111
|
+
interface SculptGroup { /* … */ readonly pose?: { readonly pivot: Vec3; readonly rotate: Vec3 } }
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Rotate a whole group (an arm, the jaw, a tail) about a joint before folding. Sculptors pose to check silhouette; here it also prepares rigging: the posed group *is* the joint. Size: **S**.
|
|
115
|
+
|
|
116
|
+
### 7. Forms as an LOD and review axis
|
|
117
|
+
|
|
118
|
+
```ts
|
|
119
|
+
interface SculptShapeEdit { /* … */ readonly form?: 'primary' | 'secondary' | 'tertiary' }
|
|
120
|
+
sculpt({ edits, resolution, forms: ctx.pick([['primary','secondary','tertiary'], ['primary','secondary'], ['primary']]) })
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Tertiary forms below the cell size cost triangles and alias anyway; dropping them by LOD makes LOD2 cleaner *and* faster to sample. `t3d review` can render a "primary only" view: a stylised character that reads from that view reads at a distance (a classic sculpting check). Size: **S**.
|
|
124
|
+
|
|
125
|
+
### 8. Reference overlays: front/side images in the contact sheet
|
|
126
|
+
|
|
127
|
+
```
|
|
128
|
+
t3d shot recipes/goblin_head/goblin_head.ts --ref front=refs/front.png --ref side=refs/side.png
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
The contact sheet is already orthographic with a metre grid (`src/render/sheet.ts`), so an aligned overlay of a front/side turnaround (AI-generated or drawn) is a blend into the matching tile, plus a silhouette IoU number per view. This is the orthographic cousin of the planned `photoMatch` (`src/render/photo-match.ts`, throws `T3D_NOT_IMPLEMENTED`). Images are inputs only; nothing ships them. Size: **M**.
|
|
132
|
+
|
|
133
|
+
### 9. Adaptive resolution (the Dyntopo analogue)
|
|
134
|
+
|
|
135
|
+
Uniform grids spend cells on flat cranium as on lips. Options: an octree of cells refined where `|d| < h` and the edit tree is dense, meshed by dual contouring (Ju et al., SIGGRAPH 2002) to keep sharp features; or a per-edit `detail` hint that refines only inside that edit's bounds. Deterministic if refinement order is fixed. Size: **L**; only worth it once a recipe hits `MAX_POINTS` (`sdf-grid.ts`) or the 1 s budget.
|
|
136
|
+
|
|
137
|
+
## What not to build
|
|
138
|
+
|
|
139
|
+
| Idea | Why not |
|
|
140
|
+
|---|---|
|
|
141
|
+
| Interactive mesh brushes on triangle meshes | Stroke-by-stroke mesh mutation is what remesh/Dyntopo exist to patch; it is not text an agent can diff. The edit list is the sculpt |
|
|
142
|
+
| Retopology to quads | glTF engines draw triangles; edge flow matters for subdivision and hand-skinning, neither of which ts23d does. Skin weights come from blend weights instead |
|
|
143
|
+
| Realistic anatomy as a goal | Stylised readable forms are the fit ([07](07-what-fits.md)); realism goes through `importGlb` / `/neural` |
|
|
144
|
+
|
|
145
|
+
## Next steps for ts23d (prioritised)
|
|
146
|
+
|
|
147
|
+
1. **`roundCone` shape + `skin()` helper** (draw-skin). S. Unlocks tails, horns, limbs, roots; feeds the `creature` kind (M4).
|
|
148
|
+
2. **`form` tags + `forms` LOD filter** on sculpt edits, and a primary-only review view. S.
|
|
149
|
+
3. **Group `pose` and `warp` (twist/bend/taper)** with declared Lipschitz factors. S.
|
|
150
|
+
4. **`ffd()` cage deform pass** on any mesh (Bernstein, vertices). S–M.
|
|
151
|
+
5. **Strokes: `crease`, `clay`, `inflate`** along a path with a radius taper, Lipschitz-aware culling and a test that catches an under-declared bound. M.
|
|
152
|
+
6. **`--ref front=… side=…` overlays + per-view silhouette IoU** in `t3d shot`. M.
|
|
153
|
+
7. **`revolve` SDF shape.** S–M.
|
|
154
|
+
8. **`pinch` / `flatten` strokes.** M.
|
|
155
|
+
9. **Adaptive (octree / dual contouring) meshing**, only when a recipe needs it. L.
|
|
156
|
+
|
|
157
|
+
## Sources
|
|
158
|
+
|
|
159
|
+
- Blender Manual 5.2 LTS: [Remesh](https://docs.blender.org/manual/en/latest/modeling/modifiers/generate/remesh.html) · [Dyntopo](https://docs.blender.org/manual/en/latest/sculpt_paint/sculpting/tool_settings/dyntopo.html) · [Skin](https://docs.blender.org/manual/en/latest/modeling/modifiers/generate/skin.html) · [Lattice](https://docs.blender.org/manual/en/latest/modeling/modifiers/deform/lattice.html)
|
|
160
|
+
- Maxon ZBrush docs: [Base mesh creation (DynaMesh, ZRemesher, ZSpheres)](https://help.maxon.net/zbr/en-us/Content/html/features/main-features/base-mesh-creation/base-mesh-creation.html)
|
|
161
|
+
- SpeedSculpt: [Superhive product page](https://superhivemarket.com/products/speedsculpt), [Blender Artists thread](https://blenderartists.org/t/speedsculpt-addon/673722)
|
|
162
|
+
- T. Sederberg, S. Parry, "Free-form deformation of solid geometric models", SIGGRAPH 1986, [archive](https://history.siggraph.org/?p=116034)
|
|
163
|
+
- A. Evans, "Learning from failure" (Dreams SDF sculpting), SIGGRAPH 2015, [slides](https://advances.realtimerendering.com/s2015/AlexEvans_SIGGRAPH-2015-sml.pdf)
|
|
164
|
+
- I. Quílez, distance functions and domain operators, iquilezles.org/articles/distfunctions (page unreachable when checked 2026-11-07; formulas as widely reproduced)
|
|
165
|
+
- T. Ju et al., "Dual Contouring of Hermite Data", SIGGRAPH 2002
|
|
166
|
+
- Forms order: [Polycount discussion](https://polycount.com/discussion/comment/2555887) (practitioner convention, not a standard)
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# 11 — Authoring materials: node graphs, edge wear, and the one trim sheet
|
|
2
|
+
|
|
3
|
+
**Substance Designer, Material Maker and ArmorPaint all run the same chain: noise → height → normal / AO / curvature → masks → albedo, roughness, metalness. ts23d's painter already runs that chain in TypeScript; what it lacks is curvature, so edges can't wear yet.** This page maps the material-authoring toolbox onto `src/material/` and proposes where edge wear and roughness variation should live, given that every asset shares one trim sheet ([04](04-materials.md), [ADR 008](../decisions/008-surface-materials.md)). Research as of 2026-11; everything under "Proposed" is **planned, not shipped**.
|
|
4
|
+
|
|
5
|
+
## How material artists work
|
|
6
|
+
|
|
7
|
+
| Tool | Model | Licence / cost | Source |
|
|
8
|
+
|---|---|---|---|
|
|
9
|
+
| Substance 3D Designer (Adobe) | Node graph of generators and filters, output = tileable PBR maps; **mesh-based mask generators** take baked curvature + AO | Commercial subscription | [Edge Damages node](https://helpx.adobe.com/substance-3d-designer/substance-compositing-graphs/nodes-reference-for-substance-compositing-graphs/node-library/mesh-based-generators/mask-generators/edge-damages.html) |
|
|
10
|
+
| Substance 3D Painter | Paint on the mesh; "smart materials" are generator stacks (Metal Edge Wear, …) driven by baked mesh maps | Commercial | [Metal Edge Wear](https://experienceleague.adobe.com/en/docs/substance-3d-painter/using/effects/generators/metal-edge-wear) |
|
|
11
|
+
| Material Maker | Node graph like Designer, also paints models; built on Godot | MIT ("files in this repository are licensed under the MIT license") | [GitHub](https://github.com/RodZill4/material-maker) |
|
|
12
|
+
| ArmorPaint (ArmorTools) | Painter-style 3D texture painting, node materials | Source zlib/libpng; "Distributed binaries are paid" | [GitHub](https://github.com/armory3d/armortools), [licence](https://raw.githubusercontent.com/armory3d/armortools/main/license.md) |
|
|
13
|
+
| Blender shader nodes + bake | Procedural nodes, baked to images with Cycles | GPL software; output is yours | [Blender licence](https://www.blender.org/about/license/) |
|
|
14
|
+
|
|
15
|
+
### The chain, step by step
|
|
16
|
+
|
|
17
|
+
| Step | What artists do | Why |
|
|
18
|
+
|---|---|---|
|
|
19
|
+
| 1. Shapes + noise | Tile generators (bricks, planks), Perlin/cells/fBm, warps | All structure starts as a greyscale field |
|
|
20
|
+
| 2. **Height** | Combine shapes into one height map | The single source of truth: everything else derives from it |
|
|
21
|
+
| 3. Normal | Gradient of height (Sobel / Scharr) at known texel size | Relief that lights correctly |
|
|
22
|
+
| 4. AO / cavity | Blur(height) − height, or ray-marched | Dirt collects, light doesn't reach |
|
|
23
|
+
| 5. **Curvature** | Convex edges (+) vs concave creases (−) from the height Laplacian, or baked from the mesh | Drives edge wear, dust, highlights |
|
|
24
|
+
| 6. Masks | Thresholded noise × curvature × AO; **slope blur in Min mode** to chew a mask edge into chips | Wear goes where it would happen physically |
|
|
25
|
+
| 7. Albedo / roughness / metal | Gradient-map masks into colour, roughness and metalness | Realism lives here: roughness *variation* reads as material more than colour does |
|
|
26
|
+
|
|
27
|
+
Edge damage specifically: Designer's **Edge Damages** "represents damage done to raised, convex edges based on curvature and baked AO" (both inputs required); Adobe's own tutorial chips a height map with slope blur / multi-directional warp in Min mode, then subtracts the damaged height from the original to extract the damaged areas ([tutorial](https://www.adobe.com/learn/substance-3d-designer/web/create-edge-damage)). Practitioners note low-poly hard edges give weak curvature bakes and UV seams break edge masks ([Unreal forum](https://forums.unrealengine.com/t/help-with-turning-a-curvature-map-into-a-mask-for-edge-wear/48349), [Polycount](https://polycount.com/discussion/comment/2571946)).
|
|
28
|
+
|
|
29
|
+
## What ts23d already has
|
|
30
|
+
|
|
31
|
+
| Chain step | ts23d today | Where |
|
|
32
|
+
|---|---|---|
|
|
33
|
+
| Canvas in real metres | Layers `height` (m), `tone`, `rough`, `metal`, `mix` (two-colour blend), `occl` | `src/material/paint/canvas.ts` |
|
|
34
|
+
| Shapes | Running-bond layout shared by bricks, stone, planks, slates, plates; SDF helpers for decal motifs | `paint/courses.ts`, `paint/shapes.ts` |
|
|
35
|
+
| Noise | Seeded, periodic-in-U value noise + fBm (strips tile) | `paint/noise.ts` |
|
|
36
|
+
| Normal | Sobel of height over texel size in metres, glTF tangent space | `paint/normal.ts` |
|
|
37
|
+
| AO / cavity | Blurred height − height, packed into ORM R; mild cavity darkening baked into albedo | `paint/orm.ts` |
|
|
38
|
+
| Masks | `rustStreaks`, `grime` (bottom pooling + rain streaks) | `paint/masks.ts` |
|
|
39
|
+
| Edge chips, texture-space | Paint films with `chips` revealing albedo2 (planks: mostly along board edges; metal; plates: chipped paint on edges) | `paint/planks.ts`, `paint/metal.ts`, `paint/plates.ts` |
|
|
40
|
+
| Measured PBR | 16 library strips with physicallybased.info reflectance, sRGB 30–240 albedo range | `src/material/library.ts` |
|
|
41
|
+
| Project materials | `defineMaterial({ kind, roughness, albedo, albedo2, params, paint })`; a `paint` layer gets a `PaintKit` (tileable noise, hash, smooth, `each`) and is NaN-checked | `define.ts`, `material-def.ts`, `paint/kit.ts` |
|
|
42
|
+
| Image inputs | PNG stamped into cells | `paint/stamp.ts`, `paint/decode-png.ts` |
|
|
43
|
+
| Mesh-space wear | `grime` (vertex colour near ground / zones), `bakeAo` (ray-cast AO into vertex colour), `livery` (paint by rules) | `src/passes/` |
|
|
44
|
+
| Shader-side wear | `ShaderHints.wear`: darkens + roughens **cavities** (1 − AO) and ground grime | `src/material/shader.ts` |
|
|
45
|
+
| Hard-surface realism | `box({ bevel })` chamfers, `weightedNormals` (big faces flat, bevels catch light) | `primitives/bevel-box.ts`, `passes/weighted-normals.ts` |
|
|
46
|
+
|
|
47
|
+
**The structural constraint.** A strip is shared by every asset and tiles along U, so the texture cannot know where *this* mesh's edges are. Substance bakes curvature per asset; ts23d can't, without giving up one material. So wear splits in two: **texture-space** wear (edges *inside* the pattern: brick arrises, plank ends, plate laps), which belongs in the painter, and **mesh-space** wear (the silhouette edges of this hull), which belongs in vertex data and the shader. Today only cavities are handled mesh-side; convex edges are not.
|
|
48
|
+
|
|
49
|
+
## Proposed (planned)
|
|
50
|
+
|
|
51
|
+
### 1. Texture-space curvature and `edgeWear` in the painter
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
// src/material/paint/curvature.ts — Laplacian of height (metres⁻¹), wraps along U for strips
|
|
55
|
+
export function curvature(c: Canvas, radiusMetres: number): Float32Array; // + convex, − concave
|
|
56
|
+
// src/material/paint/masks.ts
|
|
57
|
+
export function edgeWear(c: Canvas, o: {
|
|
58
|
+
amount: number; // 0..1, share of convex edge exposed
|
|
59
|
+
width: number; // metres of edge the wear can reach
|
|
60
|
+
chip: number; // 0..1 noise erosion of the mask edge (slope-blur-min analogue)
|
|
61
|
+
to: 'albedo2' | 'lighter'; // reveal the second colour (bare metal, raw wood) or just lighten
|
|
62
|
+
roughness?: number; metalness?: number; // what is exposed (bare steel: 0.35, 1)
|
|
63
|
+
}, seed: number): void;
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Exposed via `PaintKit.curvature()` and a shared `params.edgeWear` (like today's `params.grime`, `paint/strip.ts`), so every strip kind gets it. Chipping: threshold `curvature × (1 + chip·fbm)`, then a min-filter pass in a noise-warped direction: deterministic, a few lines, no GPU. Size: **S**.
|
|
67
|
+
|
|
68
|
+
### 2. Mesh-space convexity → edge wear in vertex data
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
// src/passes/edge-wear.ts
|
|
72
|
+
export interface EdgeWearOptions {
|
|
73
|
+
readonly angle?: number; // dihedral (°) that counts as an edge; default 30
|
|
74
|
+
readonly width?: number; // metres the wear spreads from the edge (geodesic, capped)
|
|
75
|
+
readonly amount: number; // 0..1
|
|
76
|
+
readonly lighten?: number; // vertex-colour lift on worn edges (scuffed paint)
|
|
77
|
+
readonly attribute?: boolean; // also write a `_WEAR` 0..1 vertex attribute for the shader
|
|
78
|
+
}
|
|
79
|
+
export function edgeWear(mesh: Mesh, o: EdgeWearOptions): Mesh;
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Per-vertex convexity from the signed dihedral angles of incident edges (polygon meshes), or from the field Laplacian for `sdf`/`sculpt` meshes (the SDF Laplacian is the level set's mean curvature up to a convention factor, because |∇d| = 1; [StEik, eq. 14](https://arxiv.org/pdf/2305.18414)). `head-tone.ts` already probes the field for cavity; convexity is the same probe with the other sign. Lightening vertex colour works in every engine with no shader; `_WEAR` follows the `_WIND` precedent and lets `threeMaterialSource` / `godotShaderSource` blend toward bare metal (`mix`, roughness down, metalness up) per pixel. Needs the GLB writer to emit `_WEAR` (an UNOWNED change in `src/export/`). Size: **M**.
|
|
83
|
+
|
|
84
|
+
### 3. An `edge` trim row: the trim-sheet way to wear edges
|
|
85
|
+
|
|
86
|
+
Production trim sheets carry dedicated bevel/edge strips: a chamfer mapped to a strip that is painted worn ([docs/idea/13](13-asset-pipeline-research.md#trim-sheets)). `box({ bevel })` already emits chamfer quads; mapping them to a `worn_edge` strip (bright scuffed metal or paint over primer, `edgeWear` at 100%) gives pixel-sharp wear on every bevelled asset with no new attribute and no new material. Costs one strip row (28 rows today, 4 custom slots: `trim-sheet.ts`). Size: **S–M**.
|
|
87
|
+
|
|
88
|
+
### 4. Roughness variation as a first-class knob
|
|
89
|
+
|
|
90
|
+
Every painter writes `rough`; add `roughnessVariation` (0..1, default from the library) to `MaterialDef`, applied as low-frequency fBm + cavity-driven increase (dirt is rougher) + edge-driven decrease (handled edges polish). Validate that a strip's roughness histogram is not flat: a `material` test fails a strip whose roughness standard deviation is below a floor (a flat roughness map is the first sign of a CG look). Size: **S**.
|
|
91
|
+
|
|
92
|
+
### 5. A node-graph-shaped helper API, as functions
|
|
93
|
+
|
|
94
|
+
Node graphs are hard for agents to diff ([ADR 001](../decisions/001-recipe-compiler-over-ai-meshes.md)); plain composable functions are not. Wrap the painter internals as pure layer ops a `defineMaterial({ paint })` can chain:
|
|
95
|
+
|
|
96
|
+
```ts
|
|
97
|
+
paint(s, kit) {
|
|
98
|
+
const h = kit.layer(); // Float32Array, metres
|
|
99
|
+
kit.cells(h, { size: 0.04, jitter: 0.8 }); // Voronoi stones
|
|
100
|
+
kit.add(h, kit.fbm({ size: 0.3, amp: 0.002 }));
|
|
101
|
+
const wear = kit.mask(kit.curvature(h), { above: 0.6, chip: 0.5 });
|
|
102
|
+
kit.write(s, { height: h, rough: kit.lerp(0.7, 0.4, wear), mix: wear });
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
`PaintKit` (`paint/types.ts`) is the place. Ops: `layer`, `fbm`, `cells` (Voronoi/Worley), `tiles`, `warp`, `slopeBlur`, `blur`, `curvature`, `mask`, `levels`, `lerp`, `write`. Each is a tested pure function over typed arrays, periodic in U. Size: **M**.
|
|
107
|
+
|
|
108
|
+
### 6. Texel-density gate
|
|
109
|
+
|
|
110
|
+
The sheet is 2048² at 64 px per strip row; a strip is 2 m along U (`trim-sheet.ts`), i.e. ~1024 px/m along U at 2048². Industry examples: 10.24 px/cm first person, 5.12 px/cm third person ([Maxon: Texel density](https://help.maxon.net/c4d/en-us/Content/html/Texel_Density.html)). Strips are anisotropic (V spans `scaleMetres`), so a `texel-density` QA gate should report px/m along U and V per part and flag parts below a target, and faces where V is stretched (the `box` known limit in [04](04-materials.md#known-limits)). The roadmap already lists "Gates: … texel density" (M2). Size: **S–M**.
|
|
111
|
+
|
|
112
|
+
### 7. CC0 photo textures as *inputs* to strips (not as maps)
|
|
113
|
+
|
|
114
|
+
ambientCG / Poly Haven / cgbookcase sets are CC0 ([13](13-asset-pipeline-research.md#free-cc0-libraries)). Copying their colour maps into the sheet breaks the greyscale-tint model and the size budget. Better: use them as **calibration** (sample albedo and roughness histograms to set `MaterialDef` numbers, cite the URL in `sources`) and, for a few strips, as a **height source**: a vendored CC0 displacement PNG, resampled to the strip, then run through the same normal/AO/curvature chain so it stays consistent. Vendored with a hash; never fetched at build time. Size: **M**.
|
|
115
|
+
|
|
116
|
+
## Next steps for ts23d (prioritised)
|
|
117
|
+
|
|
118
|
+
1. **`curvature()` + `edgeWear` mask in the painter**, exposed as `params.edgeWear` on every strip and on `PaintKit`. S.
|
|
119
|
+
2. **`edgeWear(mesh)` pass**: dihedral convexity (polygon) / Laplacian (SDF) → vertex-colour lift. S–M. Then `_WEAR` attribute + shader-source support. M.
|
|
120
|
+
3. **`roughnessVariation`** on `MaterialDef` + the flat-roughness test. S.
|
|
121
|
+
4. **`worn_edge` strip** mapped by `box({ bevel })` chamfers. S–M.
|
|
122
|
+
5. **`PaintKit` layer ops** (fbm, cells, slopeBlur, mask, levels, lerp, write). M.
|
|
123
|
+
6. **Texel-density gate** (px/m per part, V-stretch warning). S–M.
|
|
124
|
+
7. **CC0 height sources + calibration workflow** documented in the author-recipe skill. M.
|
|
125
|
+
|
|
126
|
+
## Sources
|
|
127
|
+
|
|
128
|
+
- Adobe: [Edge Damages](https://helpx.adobe.com/substance-3d-designer/substance-compositing-graphs/nodes-reference-for-substance-compositing-graphs/node-library/mesh-based-generators/mask-generators/edge-damages.html), [Leather Wear](https://helpx.adobe.com/substance-3d-designer/substance-compositing-graphs/nodes-reference-for-substance-compositing-graphs/node-library/mesh-based-generators/mask-generators/leather-wear.html), [Create edge damage tutorial](https://www.adobe.com/learn/substance-3d-designer/web/create-edge-damage), [Painter Metal Edge Wear](https://experienceleague.adobe.com/en/docs/substance-3d-painter/using/effects/generators/metal-edge-wear)
|
|
129
|
+
- [Material Maker](https://github.com/RodZill4/material-maker) (MIT, Godot) · [ArmorTools](https://github.com/armory3d/armortools) (zlib source, paid binaries)
|
|
130
|
+
- Curvature of an SDF: Yang et al., "StEik", 2023, [arXiv:2305.18414](https://arxiv.org/pdf/2305.18414)
|
|
131
|
+
- Texel density examples: [Maxon Cinema 4D docs](https://help.maxon.net/c4d/en-us/Content/html/Texel_Density.html)
|
|
132
|
+
- Edge-mask pitfalls: [Unreal forum](https://forums.unrealengine.com/t/help-with-turning-a-curvature-map-into-a-mask-for-edge-wear/48349), [Polycount](https://polycount.com/discussion/comment/2571946) (practitioner reports)
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# 12 — Blender bridge (design + feasibility)
|
|
2
|
+
|
|
3
|
+
**Status: all planned. Nothing here is shipped, and the core library never needs Blender.** An optional adapter could call a user-installed Blender headless (`blender -b --python`) for things ts23d's CPU paths do not do: Cycles path-traced beauty renders under a CC0 HDRI, high→low bakes, voxel remesh / decimate of imported meshes, and GLB round-trips through other formats. Blender output is not byte-deterministic across versions or hardware, so every Blender result is a **cached input**, keyed by content hash, exactly like the `/neural` cache ([08](08-neural-adapters.md)). Research as of 2026-11-07; Blender was not run on the authoring machine, so every command below is unverified on hardware.
|
|
4
|
+
|
|
5
|
+
## Why at all
|
|
6
|
+
|
|
7
|
+
| Need | ts23d today | What Blender adds |
|
|
8
|
+
|---|---|---|
|
|
9
|
+
| Hero image for a README / store page | `beauty()`: zero-GPU rasteriser, key sun + PCF shadows, sky fill, AO, ORM-driven specular (`src/render/beauty.ts`, `shade.ts`) | Path-traced GI, HDRI lighting, depth of field, real glass |
|
|
10
|
+
| Bring in a messy mesh (CC0 base mesh, a sculpt, a neural output) | `importGlb` planned (M5) | Voxel remesh to a manifold, decimate to a budget, clean normals, before ts23d fits it |
|
|
11
|
+
| Per-asset baked maps | Not in the one-sheet model (vertex AO via `bakeAo`) | Selected-to-active normal/AO/curvature bakes, for an opt-in per-asset texture (needs an ADR, see below) |
|
|
12
|
+
| Format reach | GLB only | FBX/USD/OBJ in or out through Blender's importers (out of scope for core; [gltf-capabilities](../architecture/gltf-capabilities.md)) |
|
|
13
|
+
|
|
14
|
+
Not a reason: modelling. Recipes stay TypeScript; [ADR 001](../decisions/001-recipe-compiler-over-ai-meshes.md) rejected DCC node graphs because agents diff them poorly.
|
|
15
|
+
|
|
16
|
+
## Verified facts the design rests on
|
|
17
|
+
|
|
18
|
+
| Fact | Source |
|
|
19
|
+
|---|---|
|
|
20
|
+
| `-b/--background` "Run in background (often used for UI-less rendering)"; `-P/--python <file>`; `--python-expr`; `--python-exit-code <code>` sets the exit code on a Python exception; `--factory-startup` skips the user's startup.blend; `-noaudio`; `--` ends option processing, rest goes to `sys.argv`; `-t/--threads` | [Blender CLI arguments](https://docs.blender.org/manual/en/latest/advanced/command_line/arguments.html) |
|
|
21
|
+
| `--python-use-system-env` is *opt-in*: by default Blender's Python ignores `PYTHONPATH` and user site-packages | same |
|
|
22
|
+
| LTS: 5.2 LTS and 4.5 LTS maintained (both updated 2026-10-22), "critical fixes throughout a 2-year time span", no new features or API changes | [Blender LTS](https://www.blender.org/download/lts/) |
|
|
23
|
+
| Bake settings: `use_selected_to_active`, `cage_extrusion` ("Inflate the active object…"), `cage_object`, `max_ray_distance` (0 = no limit) | [bpy BakeSettings](https://docs.blender.org/api/latest/bpy.types.BakeSettings.html) |
|
|
24
|
+
| glTF importer/exporter (Khronos) bundled since 2.80; `bpy.ops.export_scene.gltf(filepath=…, export_format='GLB')` headless | [glTF-Blender-IO](https://github.com/KhronosGroup/glTF-Blender-IO), [example](https://til.jakelazaroff.com/blender/export-a-blender-file-to-glb-from-the-command-line/) |
|
|
25
|
+
| Voxel remesh uses OpenVDB and makes a manifold mesh | [Remesh modifier](https://docs.blender.org/manual/en/latest/modeling/modifiers/generate/remesh.html) |
|
|
26
|
+
| Cycles is not bit-reproducible run to run in general (a user report: identical only with `-t 1`); CPU and GPU tiles drew different random values | [blender#101726](https://projects.blender.org/blender/blender/issues/101726) (could not open, 403; summarised from search), [T72141](https://developer.blender.org/T72141) — treat as unverified |
|
|
27
|
+
| Licence: "What you create with Blender is your sole property"; scripts using the Python API, if published, must be under a GPL-compliant licence | [Blender licence](https://www.blender.org/about/license/) |
|
|
28
|
+
|
|
29
|
+
## Architecture
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
ts23d (TypeScript, zero deps) user's machine, optional
|
|
33
|
+
───────────────────────────── ────────────────────────
|
|
34
|
+
t3d blender render recipe.ts ─┐
|
|
35
|
+
compile → toGlb (bytes) │ job.json + in.glb + hdri path
|
|
36
|
+
hash(job) → cache lookup ───┼─ miss ─► Bun.spawn(blender, ['-b', '--factory-startup',
|
|
37
|
+
│ '-noaudio', '--python-exit-code', '3',
|
|
38
|
+
cache hit: read out.* ◄────┘ '--python', 'ts23d_job.py', '--', 'job.json'])
|
|
39
|
+
◄─ out.png / out.glb + report.json (stdout last line)
|
|
40
|
+
.ts23d/blender/<sha256>/{out.*, report.json} committed or published like the neural cache
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
| Piece | Where | Notes |
|
|
44
|
+
|---|---|---|
|
|
45
|
+
| Discovery | `blender` on PATH, or `T3D_BLENDER` / `--blender <path>` | Env is read by the **CLI only**, never by `src/` library code (same rule as API keys in [08](08-neural-adapters.md)) |
|
|
46
|
+
| Version pin | `blender --version` parsed; supported list in code (e.g. `['4.5', '5.2']`); others refused | Version (major.minor + build hash) goes into the cache key and `report.json` |
|
|
47
|
+
| Job | A JSON file: `{ op, input, params, seed, ts23dVersion }` | One Python entry script dispatches on `op`; no Python ever imported by TS |
|
|
48
|
+
| Python scripts | `blender/ts23d_job.py` + one file per op, shipped in the npm package as data | GPL-compliant licence required if published (Blender licence page). MIT is generally considered GPL-compatible; **confirm with counsel before publishing** |
|
|
49
|
+
| Module | New `src/blender/` at tier 3 (needs `export` for GLB bytes and `render` types), or `cli`-only | Must stay out of `src/index.ts` main barrel; subpath `@developerz.ai/ts23d/blender`. Needs `scripts/lib/tiers.ts` entry (UNOWNED) |
|
|
50
|
+
| Isolation | `--factory-startup` (no user add-ons, prefs), `--python-use-system-env` **off**, a temp working dir, timeout | User add-ons are the biggest source of "works on my machine" |
|
|
51
|
+
|
|
52
|
+
### Operations
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
// @developerz.ai/ts23d/blender — planned API, all async, all cache-first
|
|
56
|
+
interface BlenderOptions { readonly blender?: string; readonly cacheDir?: string; readonly offline?: boolean }
|
|
57
|
+
|
|
58
|
+
render(asset: CompiledAsset, o: BlenderOptions & {
|
|
59
|
+
hdri?: string; // path to a vendored CC0 .hdr/.exr (Poly Haven), hashed into the key
|
|
60
|
+
camera?: 'hero' | PerspectiveCamera; // default = beauty()'s cameraFor(), so shots line up
|
|
61
|
+
samples?: number; // default 128; denoise: 'oidn' | 'none'
|
|
62
|
+
size?: readonly [number, number];
|
|
63
|
+
}): Promise<Uint8Array>; // PNG
|
|
64
|
+
|
|
65
|
+
remesh(glb: Uint8Array, o: BlenderOptions & { voxel: number; decimate?: number /* target tris */ }): Promise<Uint8Array>; // GLB
|
|
66
|
+
bake(high: Uint8Array, low: Uint8Array, o: BlenderOptions & {
|
|
67
|
+
maps: readonly ('normal' | 'ao' | 'curvature')[]; size: 512 | 1024 | 2048;
|
|
68
|
+
cageExtrusion: number; maxRayDistance?: number;
|
|
69
|
+
}): Promise<Record<string, Uint8Array>>; // PNGs, low's UVs
|
|
70
|
+
convert(bytes: Uint8Array, o: BlenderOptions & { from: 'fbx' | 'obj' | 'usd'; }): Promise<Uint8Array>; // → GLB
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
```
|
|
74
|
+
t3d blender render recipes/t34/t34.ts --hdri refs/studio_small_09_2k.hdr --out renders # → cycles.png
|
|
75
|
+
t3d blender remesh in/horse.glb --voxel 0.01 --decimate 8000 --out in/horse_clean.glb
|
|
76
|
+
t3d blender bake --high in/rock_high.glb --low out/rock/lod2.glb --maps normal,ao --size 1024
|
|
77
|
+
t3d blender doctor # finds Blender, prints version, runs a 1-triangle job, reports timings
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Every command prints the cache key and, with `--json`, a machine-readable report, like the rest of the CLI.
|
|
81
|
+
|
|
82
|
+
## Determinism: how to live with a non-deterministic tool
|
|
83
|
+
|
|
84
|
+
1. **Blender never runs in the gate.** `bun run verify` and CI never call Blender. Committed `hero.png` stays the CPU `beauty()` render.
|
|
85
|
+
2. **Results are inputs, cached by content.** Key = `sha256(op, params, input bytes, hdri bytes, script bytes, blender version+hash)`. A recipe that consumes a Blender result (say, a remeshed imported mesh) reads it from `.ts23d/blender/<key>/`; the compile stays byte-deterministic because the bytes are fixed.
|
|
86
|
+
3. **Cache miss in CI = error, never a silent rebuild:** `T3D_BLENDER_CACHE_MISS` with `fix: run "t3d blender remesh … " locally with Blender 5.2 and commit .ts23d/blender/<key>`. Share the miss logic with the neural cache (one `src/core` or `src/cache` helper; design choice for the implementer).
|
|
87
|
+
4. **Regeneration is explicit.** `--refresh` deletes the entry and reruns. A version bump of Blender changes the key, so old results keep working until someone regenerates.
|
|
88
|
+
5. **Renders are compared perceptually, not by bytes.** If a Cycles image is ever checked in CI-like flows, compare by a downscaled mean-absolute-error threshold, not a hash. Fixed `scene.cycles.seed`, fixed samples, `-t 1` when bit-stability on one machine matters (slow; per the bug report above).
|
|
89
|
+
6. **Geometry from Blender is normalised on the way back**: ts23d's own `optimize` (weld + Forsyth) runs on the imported mesh, so vertex order no longer depends on Blender internals once cached.
|
|
90
|
+
|
|
91
|
+
## Errors (proposed codes)
|
|
92
|
+
|
|
93
|
+
| Code | When | `fix:` |
|
|
94
|
+
|---|---|---|
|
|
95
|
+
| `T3D_BLENDER_NOT_FOUND` | No binary on PATH / flag / `T3D_BLENDER` | Install Blender 5.2 LTS from blender.org, or pass `--blender /path/to/blender` |
|
|
96
|
+
| `T3D_BLENDER_VERSION` | Version not in the supported list | Use 4.5 LTS or 5.2 LTS, or `--allow-blender-version` (key still records it) |
|
|
97
|
+
| `T3D_BLENDER_FAILED` | Non-zero exit (`--python-exit-code`), timeout, missing output | The last 40 lines of Blender's stderr + the job file path to rerun by hand |
|
|
98
|
+
| `T3D_BLENDER_CACHE_MISS` | Offline/CI and no cached result | The exact command that regenerates it |
|
|
99
|
+
|
|
100
|
+
## High→low baking vs the one-material rule
|
|
101
|
+
|
|
102
|
+
A Blender normal bake makes a **per-asset** texture; [ADR 008](../decisions/008-surface-materials.md) says every asset shares the trim sheet. Options, in order of preference:
|
|
103
|
+
|
|
104
|
+
| Option | Keeps one material? | Notes |
|
|
105
|
+
|---|---|---|
|
|
106
|
+
| A. Don't bake normals; SDF meshes already take normals from the exact field at every LOD (`sdf.ts`) | Yes | Covers sculpts today; polygon recipes get `weightedNormals` + bevels |
|
|
107
|
+
| B. Bake AO / curvature into **vertex colour / `_WEAR`** on the low mesh (sample the bake at vertices) | Yes | Cheap; ts23d can do this itself (`bakeAo` exists) without Blender |
|
|
108
|
+
| C. Opt-in `surface: 'baked'` with its own texture set for hero assets | No: a second material per asset | Needs an ADR; only worth it for imported organic hero assets |
|
|
109
|
+
|
|
110
|
+
Recommendation: ship A/B in core first; Blender baking only serves C, so it waits for that ADR.
|
|
111
|
+
|
|
112
|
+
## Feasibility
|
|
113
|
+
|
|
114
|
+
| Item | Feasible? | Effort | Risk |
|
|
115
|
+
|---|---|---|---|
|
|
116
|
+
| `render` (Cycles + HDRI, same camera as `beauty`) | Yes: standard headless pattern | M | Camera/axis conversion (ts23d Z-up +X front → glTF Y-up → Blender Z-up); test against `beauty()` silhouette |
|
|
117
|
+
| `remesh` / `decimate` on imported GLB | Yes | S–M | Vertex colours / UVs lost by voxel remesh; re-colour on the ts23d side (`importGlb` already plans recolouring) |
|
|
118
|
+
| `convert` FBX/OBJ/USD → GLB | Yes | S | Importer quirks per format; GLB stays the only output |
|
|
119
|
+
| `bake` selected-to-active | Yes technically | M–L | Only useful with option C above |
|
|
120
|
+
| CI coverage | Partial | S | Tests mock the spawn (fake `blender` script printing a canned report); a manual `t3d blender doctor` is the real smoke test |
|
|
121
|
+
|
|
122
|
+
## Next steps for ts23d (prioritised, all planned)
|
|
123
|
+
|
|
124
|
+
1. **New ADR "Optional external tools"**: Blender runs only via the CLI/subpath, results are cached inputs, never in the gate; licence note on shipped Python. S.
|
|
125
|
+
2. **Shared content-hash cache helper** for `/neural` and `/blender` (key, lookup, miss error). S–M.
|
|
126
|
+
3. **`t3d blender doctor` + discovery + version pin** with the four error codes. S.
|
|
127
|
+
4. **`t3d blender render`** (Cycles, CC0 HDRI path, `beauty()`'s camera), output `cycles.png` beside `hero.png`, never gated. M.
|
|
128
|
+
5. **`remesh` / `decimate` / `convert`** feeding the planned `importGlb`. M.
|
|
129
|
+
6. **Baking** only after an ADR accepts a per-asset `baked` surface. L.
|
|
130
|
+
|
|
131
|
+
## Sources
|
|
132
|
+
|
|
133
|
+
- [Blender command-line arguments](https://docs.blender.org/manual/en/latest/advanced/command_line/arguments.html) · [LTS](https://www.blender.org/download/lts/) · [Licence](https://www.blender.org/about/license/)
|
|
134
|
+
- [bpy.types.BakeSettings](https://docs.blender.org/api/latest/bpy.types.BakeSettings.html) · [Remesh modifier](https://docs.blender.org/manual/en/latest/modeling/modifiers/generate/remesh.html)
|
|
135
|
+
- [glTF-Blender-IO](https://github.com/KhronosGroup/glTF-Blender-IO) · [headless GLB export example](https://til.jakelazaroff.com/blender/export-a-blender-file-to-glb-from-the-command-line/)
|
|
136
|
+
- Cycles reproducibility reports: [blender#101726](https://projects.blender.org/blender/blender/issues/101726), [T72141](https://developer.blender.org/T72141) (not opened directly; status unverified)
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# 13 — The professional game-asset pipeline, free CC0 libraries, and where ts23d sits
|
|
2
|
+
|
|
3
|
+
**The studio pipeline is blockout → high poly → low poly → UV → bake → texture → present. ts23d collapses most of it into one compile: the recipe is the blockout and the low poly, the trim sheet replaces per-asset UV + bake + texture, and `shot` / `hero` / `review` are the presentation. What it borrows from the pipeline is the taste: bevels, roughness variation, consistent texel density, readable silhouettes.** This page records the pipeline as practitioners describe it, the free CC0 libraries (with exact licence wording) that can feed ts23d as inputs, and the gaps. Research as of 2026-11-07; "Proposed" items are **planned, not shipped**. Neutral positioning: artists, DCC tools and AI generators all produce good assets; ts23d is the free, code-based option.
|
|
4
|
+
|
|
5
|
+
## The pipeline as practitioners describe it
|
|
6
|
+
|
|
7
|
+
| Stage | What happens | Typical tools | ts23d equivalent | Where |
|
|
8
|
+
|---|---|---|---|---|
|
|
9
|
+
| Reference | Photos, drawings, specs, front/side sheets | PureRef, browser | `recipes/<name>/README.md` written first; `real` + `sources` + `measures` | CLAUDE.md recipe loop |
|
|
10
|
+
| **Blockout** | Primitive masses at real scale, read the silhouette in-engine | Any DCC | Step 2 "Masses", `proportions` ±5% / `measures` ±2% gates | [05](05-agent-loop.md) |
|
|
11
|
+
| High poly | Sub-D or sculpt with every bevel and detail, for baking only | ZBrush, Blender, Maya | Not a separate artefact: LOD0 *is* the dense mesh; SDF normals come from the exact field | `src/primitives/sdf.ts` |
|
|
12
|
+
| Low poly | Game mesh around the high poly; hard edges at UV splits | DCC, retopo tools | Same recipe at LOD1/LOD2, gated by `ctx.lod`, never decimated | [02](02-dsl.md), CLAUDE.md |
|
|
13
|
+
| UV | Unwrap, pack, consistent texel density | RizomUV, Blender | Primitives author UVs onto the trim sheet at true metres | `src/material/trim-sheet.ts` |
|
|
14
|
+
| **Bake** | High → low: normal, AO, curvature, ID; cage controls the ray search so hard edges shade soft | Marmoset Toolbag, Substance, xNormal, Blender | Vertex AO (`bakeAo`), weighted normals, field-gradient normals; no per-asset maps by design | `src/passes/bake-ao.ts`, `weighted-normals.ts` |
|
|
15
|
+
| Texture | Smart materials on baked maps: edge wear from curvature, dirt from AO, roughness breakup | Substance Painter, ArmorPaint | Procedural trim sheet + vertex colour + `grime` / `livery`; edge wear planned ([11](11-materials-authoring.md)) | `src/material/paint/`, `src/passes/` |
|
|
16
|
+
| Present | Marmoset turntable, ArtStation renders, in-engine shots | Marmoset, engine | `t3d shot` (six views), `t3d hero` (beauty), `t3d review` (AI review pack) | `src/render/` |
|
|
17
|
+
|
|
18
|
+
Order varies: some artists build the high poly first and the low poly around it, others go blockout → mid → high → low; hard-surface flows often add an in-between mesh ([Polycount thread](https://polycount.com/discussion/comment/1771950), practitioner opinions). The bake cage "allows the baker to search for high-poly detail as if edges were soft, while the actual low-poly retains hard edges" (Gnomon course description, [Creating Props for Games](https://bit.thegnomonworkshop.com/workshops/creating-props-for-games-vol-1)).
|
|
19
|
+
|
|
20
|
+
## The two realism keys
|
|
21
|
+
|
|
22
|
+
**Bevels.** Real objects have no razor edges; a 1–5 mm chamfer catches a highlight and reads as "manufactured" even at a distance. Studios fake bevels in textures to save triangles: Insomniac's "Ultimate Trim" (GDC 2015, Sunset Overdrive) is a sheet of horizontal trim strips with 45° normal-map bevels, standardised so materials swap without re-UVing ([GDC Vault](https://gdcvault.com/play/1022323/The-Ultimate-Trim-Texturing-Techniques), [CG Channel summary](https://www.cgchannel.com/2019/07/download-justen-lazarros-free-ultimate-trim-texturing-tools)). The Last of Us Part I used baked-bevel trims to save texture memory ([80.lv](https://80.lv/articles/the-last-of-us-remake-s-trim-pipeline-in-substance-3d-explained)).
|
|
23
|
+
ts23d today: geometric bevels via `box({ bevel })` + `weightedNormals` (the mid-poly look; `src/primitives/bevel-box.ts`).
|
|
24
|
+
|
|
25
|
+
**Roughness variation.** A flat roughness value reads as plastic CG; smudges, wear and dirt varying the specular response read as a material. ts23d today: every painter writes a `rough` layer with mottling; there is no gate on it. Proposed: `roughnessVariation` + a flat-roughness test ([11](11-materials-authoring.md)).
|
|
26
|
+
|
|
27
|
+
### Trim sheets
|
|
28
|
+
|
|
29
|
+
ts23d's sheet *is* a trim sheet in the studio sense ([ADR 003](../decisions/003-shared-trim-sheet.md) → [ADR 008](../decisions/008-surface-materials.md)): 28 strip rows tiling along U, then decal and foliage cells, one 2048² set (`src/material/trim-sheet.ts`). What studio trims have that ts23d's lacks:
|
|
30
|
+
|
|
31
|
+
| Studio trim feature | ts23d | Proposed |
|
|
32
|
+
|---|---|---|
|
|
33
|
+
| Bevel/edge strips with baked 45° normal + worn edge | No; bevels are geometry | A `worn_edge` strip; map `box({ bevel })` chamfers onto it (S–M) |
|
|
34
|
+
| Strip heights doubling (small to large trims) | Equal 64 px rows | Only if a recipe needs fine trims; layout change = ADR |
|
|
35
|
+
| Decals over trims (bolts, vents, grates) as floating quads | Cells, mapped by `cell()` | `decal()` primitive: a quad offset 1–2 mm along the normal, cutout surface (S) |
|
|
36
|
+
|
|
37
|
+
### Texel density
|
|
38
|
+
|
|
39
|
+
Examples from vendor docs: 10.24 px/cm first person, 5.12 px/cm third person, 2.56 px/cm or less top-down; closer cameras need more ([Maxon](https://help.maxon.net/c4d/en-us/Content/html/Texel_Density.html)). ts23d's strips give 1024 px/m along U at 2048² (a strip spans 2 m), constant across assets by construction; along V it depends on `scaleMetres`. The planned texel-density gate (roadmap M2) should measure both axes.
|
|
40
|
+
|
|
41
|
+
## Free CC0 libraries
|
|
42
|
+
|
|
43
|
+
All five below state CC0 or public-domain terms. CC0 means no attribution required and commercial use and redistribution allowed; it also means **no warranty** (no indemnity for trademarks, likeness or third-party IP depicted). Verified on each site 2026-11-07 unless noted.
|
|
44
|
+
|
|
45
|
+
| Library | What | Licence wording | Exceptions | Fits ts23d as |
|
|
46
|
+
|---|---|---|---|---|
|
|
47
|
+
| [Poly Haven](https://polyhaven.com/license) | HDRIs, PBR textures, models | "Our assets are all licensed as CC0"; "You can redistribute them"; "You do not need to give credit" | Site content other than the assets (logos, renders) is protected | **HDRIs** for the Blender bridge's Cycles renders ([12](12-blender-bridge.md)) and a future image-based-light option in `beauty()`; texture calibration |
|
|
48
|
+
| [ambientCG](https://docs.ambientcg.com/license/) | PBR materials, HDRIs, models | "provided under the Creative Commons CC0 1.0 Universal License"; "You don't need to give credit" | None stated | Calibrating `MaterialDef` roughness/albedo; CC0 **height** maps as strip sources ([11](11-materials-authoring.md)) |
|
|
49
|
+
| [cgbookcase](https://www.cgbookcase.com) | PBR textures (hundreds of sets) | CC0 per the author's announcements ([BlenderNation 2018](https://www.blendernation.com/2018/09/14/250-free-cc0-high-quality-pbr-textures-from-cgbookcase-com/), [CG Channel 2019](https://www.cgchannel.com/2019/03/download-380-free-pbr-texture-sets-from-cgbookcase-com/)) | **Licence page not reachable when checked (404)**; verify on the site before vendoring | Same as ambientCG |
|
|
50
|
+
| [Kenney](https://kenney.nl/support) | Low-poly game kits, UI, audio | "all game assets on the asset pages are public domain licensed (CC0)"; "Attribution is not required" | "Do not use our logo" | `importGlb` (planned, M5) inputs; scale/style references for stylised recipes |
|
|
51
|
+
| [Quaternius](https://quaternius.com/packs/fantasypropsmegakit.html) | Low-poly models, some rigged/animated, glTF included | CC0, "free to use in personal, educational and commercial projects" (pack pages; [OpenGameArt mirrors](https://opengameart.org/content/low-poly-rpg-pack)) | None stated | `importGlb` inputs: rigged characters for what recipes do weakly ([07](07-what-fits.md)) |
|
|
52
|
+
|
|
53
|
+
Other licences to know, **not** CC0: Sketchfab downloads carry per-model CC-BY / CC-BY-NC etc. (check each); Fab/Quixel Megascans have their own EULA; Substance Source is subscription content. None of these should be vendored into the repo.
|
|
54
|
+
|
|
55
|
+
### How CC0 assets enter ts23d without breaking determinism
|
|
56
|
+
|
|
57
|
+
| Rule | Why |
|
|
58
|
+
|---|---|
|
|
59
|
+
| **Vendor, never fetch at build time.** Commit the file (or publish it with the package) with its sha256 | A URL can change or vanish; bytes in git cannot |
|
|
60
|
+
| **Record provenance in `sources`** (URL, licence, date) and in GLB XMP (`KHR_xmp_json_ld`, shipped) | CC0 needs no credit, but provenance is how a reviewer checks it |
|
|
61
|
+
| **Images are inputs, not the only path** ([04](04-materials.md#image-sources)) | The sheet must still paint with no image present |
|
|
62
|
+
| **Imported meshes go through `importGlb` → fit → recolour onto the sheet** | Keeps one material and real units |
|
|
63
|
+
| A `licence` field per vendored input, checked by a script in `bun run verify` (proposed) | "A rule without a script is a suggestion" (CLAUDE.md) |
|
|
64
|
+
|
|
65
|
+
## Where ts23d differs on purpose
|
|
66
|
+
|
|
67
|
+
| Studio practice | ts23d choice | Reason |
|
|
68
|
+
|---|---|---|
|
|
69
|
+
| Per-asset baked normal/AO/curvature maps | One shared sheet; wear in vertex data | One draw call, swap palette by recipe edit ([ADR 008](../decisions/008-surface-materials.md)) |
|
|
70
|
+
| Decimate the high poly for LODs | LOD-gated emission from one source | Cleaner silhouettes, named parts survive every LOD |
|
|
71
|
+
| Hand UVs | UVs by construction at true metres | Agents don't unwrap; consistent density for free |
|
|
72
|
+
| Quad retopology | Triangles, vertex-cache ordered (`optimize`) | Engines draw triangles; ts23d doesn't subdivide |
|
|
73
|
+
|
|
74
|
+
## Next steps for ts23d (prioritised)
|
|
75
|
+
|
|
76
|
+
1. **Texel-density gate** (px/m along U and V per part; V-stretch warning). S–M. Already on roadmap M2.
|
|
77
|
+
2. **`worn_edge` strip + bevel-to-strip mapping** in `box({ bevel })`: the Ultimate-Trim idea with real geometry. S–M.
|
|
78
|
+
3. **`decal()` primitive**: offset quad on a cell, cutout surface, for bolts/vents/grates. S.
|
|
79
|
+
4. **Vendored-input convention + `licence` check** in `bun run verify` (sha256, URL, licence, date per file under `assets/vendor/` or similar). S.
|
|
80
|
+
5. **Roughness-variation knob + flat-roughness test** ([11](11-materials-authoring.md)). S.
|
|
81
|
+
6. **CC0 HDRI support**: Poly Haven `.hdr` loader for an optional IBL term in `beauty()` (zero-dep RGBE decode is ~100 lines) and for the Blender bridge. M.
|
|
82
|
+
7. **Document the calibration workflow** in `.claude/skills/author-recipe/SKILL.md`: sample a CC0 texture's albedo/roughness, put the numbers in `MaterialDef`, cite the URL. S.
|
|
83
|
+
|
|
84
|
+
## Sources
|
|
85
|
+
|
|
86
|
+
- Pipeline: [Polycount threads](https://polycount.com/discussion/comment/1771950), [Gnomon: Creating Props for Games](https://bit.thegnomonworkshop.com/workshops/creating-props-for-games-vol-1) (course description) — practitioner accounts, not a standard
|
|
87
|
+
- Trims: [GDC Vault, "The Ultimate Trim: Texturing Techniques of Sunset Overdrive"](https://gdcvault.com/play/1022323/The-Ultimate-Trim-Texturing-Techniques) (Insomniac, 2015), [CG Channel](https://www.cgchannel.com/2019/07/download-justen-lazarros-free-ultimate-trim-texturing-tools), [80.lv on The Last of Us Part I trims](https://80.lv/articles/the-last-of-us-remake-s-trim-pipeline-in-substance-3d-explained)
|
|
88
|
+
- Texel density: [Maxon Cinema 4D docs](https://help.maxon.net/c4d/en-us/Content/html/Texel_Density.html)
|
|
89
|
+
- Licences: [Poly Haven](https://polyhaven.com/license), [ambientCG](https://docs.ambientcg.com/license/), [Kenney](https://kenney.nl/support), [Quaternius](https://quaternius.com/packs/fantasypropsmegakit.html), cgbookcase via [BlenderNation](https://www.blendernation.com/2018/09/14/250-free-cc0-high-quality-pbr-textures-from-cgbookcase-com/) and [CG Channel](https://www.cgchannel.com/2019/03/download-380-free-pbr-texture-sets-from-cgbookcase-com/)
|
package/docs/plans/roadmap.md
CHANGED
|
@@ -66,6 +66,10 @@ Materials:
|
|
|
66
66
|
- [x] Sheet in the GLB: embedded or shared external PNGs (`--sheet`, `--sheet-size`)
|
|
67
67
|
- [ ] KTX2 profile; fix mip bleed between strips; per-face V mapping on `box`
|
|
68
68
|
- [ ] Image-stamped cells (decals from any `ImageSource`)
|
|
69
|
+
- [ ] Painter curvature + `edgeWear` mask (`params.edgeWear`, `PaintKit.curvature`) ([11](../idea/11-materials-authoring.md))
|
|
70
|
+
- [ ] `edgeWear(mesh)` pass (dihedral / SDF-Laplacian convexity → vertex colour; `_WEAR` attribute for shader sources)
|
|
71
|
+
- [ ] `roughnessVariation` on `MaterialDef` + flat-roughness test
|
|
72
|
+
- [ ] `worn_edge` strip mapped by `box({ bevel })` chamfers; `decal()` offset-quad primitive
|
|
69
73
|
|
|
70
74
|
Optimisation and GLB production (gap ids from [glb-production.md](../architecture/glb-production.md)):
|
|
71
75
|
- [x] `optimize(mesh)`: vertex weld + vertex-cache reorder, reports ACMR (default in export)
|
|
@@ -110,6 +114,12 @@ Exit bar: a 4-4-0 with tender matching [`docs/reference/loco-photo-match.png`](h
|
|
|
110
114
|
- [x] `sdf`: surface nets or marching cubes over a signed-distance field; `sculpt` edit lists (Dreams-style)
|
|
111
115
|
- [ ] `creature`: SDF flesh over biped / quadruped / bird skeletons, bones as named parts
|
|
112
116
|
- [ ] Recipes: stylised critters, rocks
|
|
117
|
+
- [ ] `roundCone` sculpt shape + `skin()` (draw-skin capsule chains) ([10](../idea/10-sculpting.md))
|
|
118
|
+
- [ ] Sculpt `form` tags (primary/secondary/tertiary) as an LOD filter + primary-only review view
|
|
119
|
+
- [ ] Group `pose` (pivot + rotate) and `warp` (twist/bend/taper) with declared Lipschitz factors
|
|
120
|
+
- [ ] `ffd()` cage deform pass (Sederberg–Parry)
|
|
121
|
+
- [ ] Sculpt strokes along a path: `crease`, `clay`, `inflate` (then `pinch`, `flatten`); `revolve` SDF shape
|
|
122
|
+
- [ ] `t3d shot --ref front=… side=…` overlays + per-view silhouette IoU
|
|
113
123
|
|
|
114
124
|
## M5: Neural adapters and import
|
|
115
125
|
|
|
@@ -118,6 +128,8 @@ Exit bar: a 4-4-0 with tender matching [`docs/reference/loco-photo-match.png`](h
|
|
|
118
128
|
- [ ] `importGlb`: any GLB as a part, fitted and recoloured onto the trim sheet
|
|
119
129
|
- [ ] `kubeezImages`: kubeez.com image source for references, decals, texture sources
|
|
120
130
|
- [ ] Recipe: a code-built cart drawn by a generated horse
|
|
131
|
+
- [ ] Optional Blender bridge, never in the gate ([12](../idea/12-blender-bridge.md)): ADR, shared content-hash cache with /neural, `t3d blender doctor | render | remesh | convert`
|
|
132
|
+
- [ ] Vendored-input convention (sha256 + URL + licence) and a `licence` check in `bun run verify`; CC0 HDRI loader for `beauty()` IBL
|
|
121
133
|
|
|
122
134
|
## M6: Reverse direction and reach
|
|
123
135
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@developerz.ai/ts23d",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Free, deterministic 3D asset generation in TypeScript — a DSL your coding agent writes, compiled to game-ready glTF.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -75,6 +75,7 @@
|
|
|
75
75
|
"filesize": "bun run scripts/filesize.ts",
|
|
76
76
|
"recipes": "bun run scripts/recipes.ts",
|
|
77
77
|
"shots": "bun run scripts/shots.ts",
|
|
78
|
+
"textures": "bun run scripts/textures.ts",
|
|
78
79
|
"release": "bun run scripts/release.ts",
|
|
79
80
|
"t3d": "bun run src/cli/bin.ts",
|
|
80
81
|
"site:dev": "bun run --filter @ts23d/site dev",
|
package/recipes/boxcar/README.md
CHANGED
|
@@ -44,3 +44,10 @@ children `*_wheel_l/_r`) · `door_l`, `door_r` (slide one way toward −X, `oneW
|
|
|
44
44
|
- Truck centres are not published in the sources found: 9.37 m is scaled from the photo.
|
|
45
45
|
- Lettering is painted on the boards and post faces; the diagonal braces cross over it (a stroke
|
|
46
46
|
painted on a brace face jumps sideways in parallax and garbles the word at 3/4 views).
|
|
47
|
+
|
|
48
|
+
## Materials
|
|
49
|
+
Painted plank sheathing and door (`wood_painted`: car colour over grey wood, worn at board edges and
|
|
50
|
+
ends), silver-grey running board and saddles (`wood_weathered`), painted steel frame, sills and roof
|
|
51
|
+
(`steel_painted`), sand-cast trucks, wheels and bolsters (`iron_cast`), and rusty hardware: grab
|
|
52
|
+
irons, couplers, brake gear (`iron_rust`). The numbers and sources are in
|
|
53
|
+
`docs/architecture/materials.md`.
|