@developerz.ai/ts23d 0.0.0-stage → 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/.claude/agents/bug-hunter.md +21 -0
- package/.claude/agents/docs-author.md +13 -0
- package/.claude/agents/dx-auditor.md +14 -0
- package/.claude/agents/primitive-author.md +22 -0
- package/.claude/agents/recipe-author.md +36 -0
- package/.claude/agents/visual-reviewer.md +23 -0
- package/.claude/commands/feature.md +28 -0
- package/.claude/commands/new-recipe.md +19 -0
- package/.claude/commands/release.md +14 -0
- package/.claude/commands/verify.md +9 -0
- package/.claude/skills/author-recipe/SKILL.md +93 -0
- package/CHANGELOG.md +90 -0
- package/CLAUDE.md +85 -0
- package/LICENSE +21 -0
- package/README.md +385 -2
- package/dist/chunk-6b05mnmx.js +19 -0
- package/dist/chunk-6b05mnmx.js.map +11 -0
- package/dist/chunk-7rtfjh9f.js +45 -0
- package/dist/chunk-7rtfjh9f.js.map +10 -0
- package/dist/chunk-8j4c28jt.js +4085 -0
- package/dist/chunk-8j4c28jt.js.map +46 -0
- package/dist/chunk-d1fv6hrm.js +2532 -0
- package/dist/chunk-d1fv6hrm.js.map +43 -0
- package/dist/chunk-dbq4qf52.js +902 -0
- package/dist/chunk-dbq4qf52.js.map +25 -0
- package/dist/chunk-drna2rny.js +836 -0
- package/dist/chunk-drna2rny.js.map +17 -0
- package/dist/chunk-dxvypr7y.js +89 -0
- package/dist/chunk-dxvypr7y.js.map +11 -0
- package/dist/chunk-f6yme003.js +1589 -0
- package/dist/chunk-f6yme003.js.map +30 -0
- package/dist/chunk-hg7pf34j.js +465 -0
- package/dist/chunk-hg7pf34j.js.map +19 -0
- package/dist/chunk-jq4e17tj.js +1031 -0
- package/dist/chunk-jq4e17tj.js.map +21 -0
- package/dist/chunk-y63c6zcv.js +2668 -0
- package/dist/chunk-y63c6zcv.js.map +53 -0
- package/dist/cli/args.d.ts +30 -0
- package/dist/cli/bin.d.ts +2 -0
- package/dist/cli/bin.js +348 -0
- package/dist/cli/bin.js.map +15 -0
- package/dist/cli/load.d.ts +2 -0
- package/dist/cli/manifest.d.ts +53 -0
- package/dist/cli/render-cmds.d.ts +12 -0
- package/dist/cli/run.d.ts +1 -0
- package/dist/cli/texture-cache.d.ts +4 -0
- package/dist/core/asset.d.ts +10 -0
- package/dist/core/definition.d.ts +155 -0
- package/dist/core/errors.d.ts +17 -0
- package/dist/core/family.d.ts +23 -0
- package/dist/core/index.d.ts +9 -0
- package/dist/core/index.js +69 -0
- package/dist/core/index.js.map +9 -0
- package/dist/core/mesh.d.ts +35 -0
- package/dist/core/part.d.ts +114 -0
- package/dist/core/png.d.ts +1 -0
- package/dist/core/rng.d.ts +12 -0
- package/dist/core/transform.d.ts +20 -0
- package/dist/core/vec.d.ts +11 -0
- package/dist/core/world.d.ts +16 -0
- package/dist/export/bin-writer.d.ts +16 -0
- package/dist/export/container.d.ts +3 -0
- package/dist/export/credits.d.ts +4 -0
- package/dist/export/glb-options.d.ts +48 -0
- package/dist/export/glb.d.ts +8 -0
- package/dist/export/hierarchy.d.ts +23 -0
- package/dist/export/index.d.ts +5 -0
- package/dist/export/index.js +40 -0
- package/dist/export/index.js.map +9 -0
- package/dist/export/lights.d.ts +8 -0
- package/dist/export/lod-coverage.d.ts +12 -0
- package/dist/export/materials.d.ts +23 -0
- package/dist/export/merge-pivots.d.ts +6 -0
- package/dist/export/metadata.d.ts +21 -0
- package/dist/export/motion.d.ts +9 -0
- package/dist/export/optimize-parts.d.ts +26 -0
- package/dist/export/primitive.d.ts +3 -0
- package/dist/export/tangents.d.ts +2 -0
- package/dist/export/textures.d.ts +22 -0
- package/dist/export/version.d.ts +2 -0
- package/dist/grammar/index.d.ts +2 -0
- package/dist/grammar/index.js +16 -0
- package/dist/grammar/index.js.map +9 -0
- package/dist/grammar/split.d.ts +21 -0
- package/dist/grammar/whyte.d.ts +5 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +457 -0
- package/dist/index.js.map +9 -0
- package/dist/kinds/building.d.ts +44 -0
- package/dist/kinds/faces.d.ts +27 -0
- package/dist/kinds/head-armour.d.ts +5 -0
- package/dist/kinds/head-beard.d.ts +6 -0
- package/dist/kinds/head-bust.d.ts +5 -0
- package/dist/kinds/head-extras.d.ts +15 -0
- package/dist/kinds/head-face.d.ts +8 -0
- package/dist/kinds/head-garb.d.ts +4 -0
- package/dist/kinds/head-geom.d.ts +7 -0
- package/dist/kinds/head-hair.d.ts +4 -0
- package/dist/kinds/head-layout.d.ts +29 -0
- package/dist/kinds/head-mesh.d.ts +31 -0
- package/dist/kinds/head-mouth.d.ts +4 -0
- package/dist/kinds/head-params.d.ts +116 -0
- package/dist/kinds/head-presets.d.ts +19 -0
- package/dist/kinds/head-rim.d.ts +10 -0
- package/dist/kinds/head-tone.d.ts +10 -0
- package/dist/kinds/head-torso.d.ts +35 -0
- package/dist/kinds/head.d.ts +39 -0
- package/dist/kinds/hull-lines.d.ts +65 -0
- package/dist/kinds/hull-skin.d.ts +6 -0
- package/dist/kinds/hull.d.ts +14 -0
- package/dist/kinds/index.d.ts +16 -0
- package/dist/kinds/index.js +78 -0
- package/dist/kinds/index.js.map +9 -0
- package/dist/kinds/mass.d.ts +15 -0
- package/dist/kinds/openings.d.ts +30 -0
- package/dist/kinds/planned.d.ts +6 -0
- package/dist/kinds/roof-chimney.d.ts +16 -0
- package/dist/kinds/roof-shape.d.ts +64 -0
- package/dist/kinds/roof.d.ts +31 -0
- package/dist/kinds/vegetation-fronds.d.ts +15 -0
- package/dist/kinds/vegetation-impostor.d.ts +9 -0
- package/dist/kinds/vegetation-leaves.d.ts +15 -0
- package/dist/kinds/vegetation-mesh.d.ts +21 -0
- package/dist/kinds/vegetation-params.d.ts +90 -0
- package/dist/kinds/vegetation-presets.d.ts +20 -0
- package/dist/kinds/vegetation-skeleton.d.ts +20 -0
- package/dist/kinds/vegetation.d.ts +42 -0
- package/dist/kinds/vehicle.d.ts +42 -0
- package/dist/material/cc0.d.ts +4 -0
- package/dist/material/define.d.ts +34 -0
- package/dist/material/generator.d.ts +4 -0
- package/dist/material/index.d.ts +9 -0
- package/dist/material/index.js +70 -0
- package/dist/material/index.js.map +9 -0
- package/dist/material/library.d.ts +7 -0
- package/dist/material/material-def.d.ts +30 -0
- package/dist/material/paint/bond.d.ts +3 -0
- package/dist/material/paint/canvas.d.ts +39 -0
- package/dist/material/paint/cell.d.ts +4 -0
- package/dist/material/paint/cells-round.d.ts +5 -0
- package/dist/material/paint/cells.d.ts +7 -0
- package/dist/material/paint/compose.d.ts +5 -0
- package/dist/material/paint/concrete.d.ts +3 -0
- package/dist/material/paint/courses.d.ts +31 -0
- package/dist/material/paint/decode-png.d.ts +6 -0
- package/dist/material/paint/default-sheet.d.ts +4 -0
- package/dist/material/paint/fabric.d.ts +4 -0
- package/dist/material/paint/foliage-broad.d.ts +5 -0
- package/dist/material/paint/foliage-common.d.ts +35 -0
- package/dist/material/paint/foliage-compound.d.ts +4 -0
- package/dist/material/paint/foliage-conifer.d.ts +5 -0
- package/dist/material/paint/foliage-raster.d.ts +26 -0
- package/dist/material/paint/foliage.d.ts +3 -0
- package/dist/material/paint/image-strip.d.ts +4 -0
- package/dist/material/paint/kit.d.ts +5 -0
- package/dist/material/paint/masks.d.ts +3 -0
- package/dist/material/paint/metal.d.ts +3 -0
- package/dist/material/paint/noise.d.ts +9 -0
- package/dist/material/paint/normal.d.ts +8 -0
- package/dist/material/paint/orm.d.ts +8 -0
- package/dist/material/paint/planks.d.ts +3 -0
- package/dist/material/paint/plates.d.ts +3 -0
- package/dist/material/paint/shapes.d.ts +8 -0
- package/dist/material/paint/slate.d.ts +3 -0
- package/dist/material/paint/stamp.d.ts +3 -0
- package/dist/material/paint/strip.d.ts +3 -0
- package/dist/material/paint/surface.d.ts +5 -0
- package/dist/material/paint/types.d.ts +65 -0
- package/dist/material/paint/validate.d.ts +3 -0
- package/dist/material/palette.d.ts +14 -0
- package/dist/material/shader-godot.d.ts +2 -0
- package/dist/material/shader-three.d.ts +2 -0
- package/dist/material/shader.d.ts +46 -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 +38 -0
- package/dist/neural/generator.d.ts +25 -0
- package/dist/neural/images.d.ts +17 -0
- package/dist/neural/index.d.ts +2 -0
- package/dist/neural/index.js +14 -0
- package/dist/neural/index.js.map +9 -0
- package/dist/passes/bake-ao.d.ts +17 -0
- package/dist/passes/grime.d.ts +15 -0
- package/dist/passes/index.d.ts +6 -0
- package/dist/passes/index.js +22 -0
- package/dist/passes/index.js.map +9 -0
- package/dist/passes/livery.d.ts +19 -0
- package/dist/passes/optimize-cache.d.ts +15 -0
- package/dist/passes/optimize-weld.d.ts +15 -0
- package/dist/passes/optimize.d.ts +28 -0
- package/dist/passes/ray-grid.d.ts +23 -0
- package/dist/passes/weighted-normals.d.ts +8 -0
- package/dist/primitives/beam.d.ts +17 -0
- package/dist/primitives/bevel-box.d.ts +13 -0
- package/dist/primitives/box.d.ts +16 -0
- package/dist/primitives/cell.d.ts +20 -0
- package/dist/primitives/check.d.ts +12 -0
- package/dist/primitives/closed.d.ts +11 -0
- package/dist/primitives/erosion.d.ts +22 -0
- package/dist/primitives/font-5x7.d.ts +3 -0
- package/dist/primitives/heightfield.d.ts +16 -0
- package/dist/primitives/index.d.ts +23 -0
- package/dist/primitives/index.js +81 -0
- package/dist/primitives/index.js.map +9 -0
- package/dist/primitives/lathe-facets.d.ts +8 -0
- package/dist/primitives/lathe.d.ts +26 -0
- package/dist/primitives/lattice.d.ts +30 -0
- package/dist/primitives/loft.d.ts +15 -0
- package/dist/primitives/mc-table.d.ts +2 -0
- package/dist/primitives/member.d.ts +19 -0
- package/dist/primitives/noise.d.ts +34 -0
- package/dist/primitives/planar.d.ts +10 -0
- package/dist/primitives/prism.d.ts +13 -0
- package/dist/primitives/sculpt-edit.d.ts +56 -0
- package/dist/primitives/sculpt-tree.d.ts +23 -0
- package/dist/primitives/sculpt.d.ts +27 -0
- package/dist/primitives/sdf-grid.d.ts +17 -0
- package/dist/primitives/sdf-ops.d.ts +37 -0
- package/dist/primitives/sdf.d.ts +21 -0
- package/dist/primitives/strip-uv.d.ts +11 -0
- package/dist/primitives/surface-nets.d.ts +7 -0
- package/dist/primitives/sweep.d.ts +19 -0
- package/dist/primitives/terrain-color.d.ts +30 -0
- package/dist/primitives/terrain-grid.d.ts +36 -0
- package/dist/primitives/text-layout.d.ts +22 -0
- package/dist/primitives/text.d.ts +27 -0
- package/dist/primitives/tiled.d.ts +22 -0
- package/dist/primitives/transport.d.ts +17 -0
- package/dist/primitives/triangulate.d.ts +2 -0
- package/dist/primitives/value-noise.d.ts +10 -0
- package/dist/qa/checks.d.ts +11 -0
- package/dist/qa/clash.d.ts +2 -0
- package/dist/qa/floating.d.ts +5 -0
- package/dist/qa/gates.d.ts +12 -0
- package/dist/qa/geometry.d.ts +21 -0
- package/dist/qa/index.d.ts +12 -0
- package/dist/qa/index.js +78 -0
- package/dist/qa/index.js.map +9 -0
- package/dist/qa/inside.d.ts +12 -0
- package/dist/qa/inspect.d.ts +18 -0
- package/dist/qa/lods.d.ts +12 -0
- package/dist/qa/measures.d.ts +4 -0
- package/dist/qa/occlusion.d.ts +3 -0
- package/dist/qa/sanity.d.ts +7 -0
- package/dist/qa/types.d.ts +12 -0
- package/dist/render/beauty.d.ts +40 -0
- package/dist/render/camera.d.ts +33 -0
- package/dist/render/depth-map.d.ts +38 -0
- package/dist/render/font.d.ts +1 -0
- package/dist/render/ground.d.ts +13 -0
- package/dist/render/index.d.ts +10 -0
- package/dist/render/index.js +47 -0
- package/dist/render/index.js.map +9 -0
- package/dist/render/lighting.d.ts +27 -0
- package/dist/render/lod-strip.d.ts +5 -0
- package/dist/render/metrics.d.ts +37 -0
- package/dist/render/options.d.ts +28 -0
- package/dist/render/photo-match.d.ts +19 -0
- package/dist/render/raster.d.ts +25 -0
- package/dist/render/resolve.d.ts +11 -0
- package/dist/render/review-prompt.d.ts +2 -0
- package/dist/render/review-warnings.d.ts +34 -0
- package/dist/render/review.d.ts +24 -0
- package/dist/render/scene.d.ts +18 -0
- package/dist/render/shade.d.ts +22 -0
- package/dist/render/sheet.d.ts +15 -0
- package/dist/render/texture.d.ts +15 -0
- package/dist/render/thumbnail.d.ts +8 -0
- package/dist/render/vis-buffer.d.ts +18 -0
- package/dist/testing/assert.d.ts +21 -0
- package/dist/testing/compiled.d.ts +15 -0
- package/dist/testing/hash.d.ts +1 -0
- package/dist/testing/index.d.ts +4 -0
- package/dist/testing/index.js +134 -0
- package/dist/testing/index.js.map +13 -0
- package/dist/testing/snapshot.d.ts +13 -0
- package/docs/README.md +56 -0
- package/docs/architecture/README.md +68 -0
- package/docs/architecture/adding-a-kind.md +44 -0
- package/docs/architecture/adding-a-primitive.md +49 -0
- package/docs/architecture/errors.md +47 -0
- package/docs/architecture/glb-production.md +214 -0
- package/docs/architecture/gltf-capabilities.md +117 -0
- package/docs/architecture/materials.md +151 -0
- package/docs/decisions/001-recipe-compiler-over-ai-meshes.md +27 -0
- package/docs/decisions/002-typescript-not-json.md +22 -0
- package/docs/decisions/003-shared-trim-sheet.md +21 -0
- package/docs/decisions/004-z-up-metres.md +21 -0
- package/docs/decisions/005-zero-runtime-deps.md +21 -0
- package/docs/decisions/006-single-package-subpath-exports.md +20 -0
- package/docs/decisions/007-cpu-contact-sheets.md +21 -0
- package/docs/decisions/008-surface-materials.md +21 -0
- package/docs/decisions/009-gltf-front-axis.md +21 -0
- package/docs/decisions/README.md +15 -0
- package/docs/idea/01-vision.md +117 -0
- package/docs/idea/02-dsl.md +343 -0
- package/docs/idea/03-ir.md +97 -0
- package/docs/idea/04-materials.md +114 -0
- package/docs/idea/05-agent-loop.md +141 -0
- package/docs/idea/06-qa.md +101 -0
- package/docs/idea/07-what-fits.md +45 -0
- package/docs/idea/08-neural-adapters.md +78 -0
- package/docs/idea/09-kinds.md +105 -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/landscape.md +49 -0
- package/docs/plans/roadmap.md +139 -0
- package/examples/build-with-bun.ts +41 -0
- package/llms.txt +47 -0
- package/package.json +100 -4
- package/recipes/README.md +38 -0
- package/recipes/asteroid/README.md +46 -0
- package/recipes/asteroid/asteroid.ts +336 -0
- package/recipes/boulders/README.md +28 -0
- package/recipes/boulders/boulders.ts +117 -0
- package/recipes/boxcar/README.md +53 -0
- package/recipes/boxcar/boxcar.ts +1000 -0
- package/recipes/cargo_container_scifi/README.md +52 -0
- package/recipes/cargo_container_scifi/cargo_container_scifi.ts +461 -0
- package/recipes/dropship/README.md +84 -0
- package/recipes/dropship/dropship.ts +1144 -0
- package/recipes/eiffel_tower/README.md +110 -0
- package/recipes/eiffel_tower/eiffel_tower.ts +781 -0
- package/recipes/goblin_head/README.md +75 -0
- package/recipes/goblin_head/goblin_head.ts +38 -0
- package/recipes/human_head/README.md +68 -0
- package/recipes/human_head/human_head.ts +38 -0
- package/recipes/loco_440/README.md +32 -0
- package/recipes/loco_440/loco_440.ts +1404 -0
- package/recipes/material_swatches/README.md +21 -0
- package/recipes/material_swatches/material_swatches.ts +189 -0
- package/recipes/mountain_range/README.md +45 -0
- package/recipes/mountain_range/mountain_range.ts +128 -0
- package/recipes/oak/README.md +30 -0
- package/recipes/oak/oak.ts +19 -0
- package/recipes/orc_head/README.md +78 -0
- package/recipes/orc_head/orc_head.ts +41 -0
- package/recipes/palm/README.md +30 -0
- package/recipes/palm/palm.ts +19 -0
- package/recipes/pine/README.md +29 -0
- package/recipes/pine/pine.ts +19 -0
- package/recipes/signal_gantry/README.md +36 -0
- package/recipes/signal_gantry/signal_gantry.ts +522 -0
- package/recipes/station_halt/README.md +39 -0
- package/recipes/station_halt/station_halt.ts +722 -0
- package/recipes/t34/README.md +44 -0
- package/recipes/t34/t34.ts +1068 -0
- package/recipes/tiger_1/README.md +75 -0
- package/recipes/tiger_1/tiger_1.ts +1370 -0
- package/recipes/water_tower/README.md +40 -0
- package/recipes/water_tower/water_tower.ts +542 -0
- package/src/cli/args.ts +125 -0
- package/src/cli/bin.ts +7 -0
- package/src/cli/load.ts +17 -0
- package/src/cli/manifest.ts +95 -0
- package/src/cli/render-cmds.ts +66 -0
- package/src/cli/run.ts +192 -0
- package/src/cli/texture-cache.ts +24 -0
- package/src/core/asset.ts +129 -0
- package/src/core/definition.ts +156 -0
- package/src/core/errors.ts +50 -0
- package/src/core/family.ts +50 -0
- package/src/core/index.ts +66 -0
- package/src/core/mesh.ts +114 -0
- package/src/core/part.ts +130 -0
- package/src/core/png.ts +49 -0
- package/src/core/rng.ts +49 -0
- package/src/core/transform.ts +90 -0
- package/src/core/vec.ts +26 -0
- package/src/core/world.ts +80 -0
- package/src/export/bin-writer.ts +79 -0
- package/src/export/container.ts +35 -0
- package/src/export/credits.ts +25 -0
- package/src/export/glb-options.ts +53 -0
- package/src/export/glb.ts +187 -0
- package/src/export/hierarchy.ts +122 -0
- package/src/export/index.ts +34 -0
- package/src/export/lights.ts +49 -0
- package/src/export/lod-coverage.ts +71 -0
- package/src/export/materials.ts +126 -0
- package/src/export/merge-pivots.ts +146 -0
- package/src/export/metadata.ts +62 -0
- package/src/export/motion.ts +70 -0
- package/src/export/optimize-parts.ts +80 -0
- package/src/export/primitive.ts +35 -0
- package/src/export/tangents.ts +75 -0
- package/src/export/textures.ts +88 -0
- package/src/export/version.ts +6 -0
- package/src/grammar/index.ts +4 -0
- package/src/grammar/split.ts +87 -0
- package/src/grammar/whyte.ts +20 -0
- package/src/index.ts +14 -0
- package/src/kinds/building.ts +153 -0
- package/src/kinds/faces.ts +138 -0
- package/src/kinds/head-armour.ts +181 -0
- package/src/kinds/head-beard.ts +133 -0
- package/src/kinds/head-bust.ts +184 -0
- package/src/kinds/head-extras.ts +150 -0
- package/src/kinds/head-face.ts +242 -0
- package/src/kinds/head-garb.ts +181 -0
- package/src/kinds/head-geom.ts +31 -0
- package/src/kinds/head-hair.ts +193 -0
- package/src/kinds/head-layout.ts +63 -0
- package/src/kinds/head-mesh.ts +195 -0
- package/src/kinds/head-mouth.ts +111 -0
- package/src/kinds/head-params.ts +162 -0
- package/src/kinds/head-presets.ts +155 -0
- package/src/kinds/head-rim.ts +128 -0
- package/src/kinds/head-tone.ts +52 -0
- package/src/kinds/head-torso.ts +230 -0
- package/src/kinds/head.ts +176 -0
- package/src/kinds/hull-lines.ts +160 -0
- package/src/kinds/hull-skin.ts +83 -0
- package/src/kinds/hull.ts +209 -0
- package/src/kinds/index.ts +63 -0
- package/src/kinds/mass.ts +110 -0
- package/src/kinds/openings.ts +141 -0
- package/src/kinds/planned.ts +12 -0
- package/src/kinds/roof-chimney.ts +100 -0
- package/src/kinds/roof-shape.ts +230 -0
- package/src/kinds/roof.ts +250 -0
- package/src/kinds/vegetation-fronds.ts +126 -0
- package/src/kinds/vegetation-impostor.ts +118 -0
- package/src/kinds/vegetation-leaves.ts +168 -0
- package/src/kinds/vegetation-mesh.ts +174 -0
- package/src/kinds/vegetation-params.ts +147 -0
- package/src/kinds/vegetation-presets.ts +184 -0
- package/src/kinds/vegetation-skeleton.ts +168 -0
- package/src/kinds/vegetation.ts +199 -0
- package/src/kinds/vehicle.ts +144 -0
- package/src/material/cc0.ts +41 -0
- package/src/material/define.ts +130 -0
- package/src/material/generator.ts +63 -0
- package/src/material/index.ts +60 -0
- package/src/material/library.ts +196 -0
- package/src/material/material-def.ts +84 -0
- package/src/material/paint/bond.ts +60 -0
- package/src/material/paint/canvas.ts +101 -0
- package/src/material/paint/cell.ts +39 -0
- package/src/material/paint/cells-round.ts +119 -0
- package/src/material/paint/cells.ts +111 -0
- package/src/material/paint/compose.ts +53 -0
- package/src/material/paint/concrete.ts +62 -0
- package/src/material/paint/courses.ts +85 -0
- package/src/material/paint/decode-png.ts +106 -0
- package/src/material/paint/default-sheet.ts +73 -0
- package/src/material/paint/fabric.ts +63 -0
- package/src/material/paint/foliage-broad.ts +126 -0
- package/src/material/paint/foliage-common.ts +93 -0
- package/src/material/paint/foliage-compound.ts +135 -0
- package/src/material/paint/foliage-conifer.ts +143 -0
- package/src/material/paint/foliage-raster.ts +196 -0
- package/src/material/paint/foliage.ts +23 -0
- package/src/material/paint/image-strip.ts +88 -0
- package/src/material/paint/kit.ts +37 -0
- package/src/material/paint/masks.ts +46 -0
- package/src/material/paint/metal.ts +109 -0
- package/src/material/paint/noise.ts +52 -0
- package/src/material/paint/normal.ts +52 -0
- package/src/material/paint/orm.ts +108 -0
- package/src/material/paint/planks.ts +101 -0
- package/src/material/paint/plates.ts +72 -0
- package/src/material/paint/shapes.ts +28 -0
- package/src/material/paint/slate.ts +52 -0
- package/src/material/paint/stamp.ts +40 -0
- package/src/material/paint/strip.ts +48 -0
- package/src/material/paint/surface.ts +60 -0
- package/src/material/paint/types.ts +94 -0
- package/src/material/paint/validate.ts +74 -0
- package/src/material/palette.ts +46 -0
- package/src/material/shader-godot.ts +77 -0
- package/src/material/shader-three.ts +103 -0
- package/src/material/shader.ts +100 -0
- package/src/material/slots.ts +18 -0
- package/src/material/texture-source.ts +86 -0
- package/src/material/trim-sheet.ts +166 -0
- package/src/neural/generator.ts +37 -0
- package/src/neural/images.ts +27 -0
- package/src/neural/index.ts +10 -0
- package/src/passes/bake-ao.ts +183 -0
- package/src/passes/grime.ts +37 -0
- package/src/passes/index.ts +8 -0
- package/src/passes/livery.ts +66 -0
- package/src/passes/optimize-cache.ts +175 -0
- package/src/passes/optimize-weld.ts +147 -0
- package/src/passes/optimize.ts +85 -0
- package/src/passes/ray-grid.ts +237 -0
- package/src/passes/weighted-normals.ts +125 -0
- package/src/primitives/beam.ts +60 -0
- package/src/primitives/bevel-box.ts +140 -0
- package/src/primitives/box.ts +90 -0
- package/src/primitives/cell.ts +115 -0
- package/src/primitives/check.ts +49 -0
- package/src/primitives/closed.ts +74 -0
- package/src/primitives/erosion.ts +183 -0
- package/src/primitives/font-5x7.ts +117 -0
- package/src/primitives/heightfield.ts +101 -0
- package/src/primitives/index.ts +56 -0
- package/src/primitives/lathe-facets.ts +45 -0
- package/src/primitives/lathe.ts +174 -0
- package/src/primitives/lattice.ts +131 -0
- package/src/primitives/loft.ts +174 -0
- package/src/primitives/mc-table.ts +41 -0
- package/src/primitives/member.ts +130 -0
- package/src/primitives/noise.ts +157 -0
- package/src/primitives/planar.ts +55 -0
- package/src/primitives/prism.ts +85 -0
- package/src/primitives/sculpt-edit.ts +212 -0
- package/src/primitives/sculpt-tree.ts +226 -0
- package/src/primitives/sculpt.ts +80 -0
- package/src/primitives/sdf-grid.ts +131 -0
- package/src/primitives/sdf-ops.ts +145 -0
- package/src/primitives/sdf.ts +83 -0
- package/src/primitives/strip-uv.ts +26 -0
- package/src/primitives/surface-nets.ts +161 -0
- package/src/primitives/sweep.ts +162 -0
- package/src/primitives/terrain-color.ts +85 -0
- package/src/primitives/terrain-grid.ts +137 -0
- package/src/primitives/text-layout.ts +85 -0
- package/src/primitives/text.ts +197 -0
- package/src/primitives/tiled.ts +93 -0
- package/src/primitives/transport.ts +85 -0
- package/src/primitives/triangulate.ts +42 -0
- package/src/primitives/value-noise.ts +62 -0
- package/src/qa/checks.ts +110 -0
- package/src/qa/clash.ts +177 -0
- package/src/qa/floating.ts +63 -0
- package/src/qa/gates.ts +126 -0
- package/src/qa/geometry.ts +187 -0
- package/src/qa/index.ts +28 -0
- package/src/qa/inside.ts +114 -0
- package/src/qa/inspect.ts +107 -0
- package/src/qa/lods.ts +97 -0
- package/src/qa/measures.ts +67 -0
- package/src/qa/occlusion.ts +166 -0
- package/src/qa/sanity.ts +137 -0
- package/src/qa/types.ts +17 -0
- package/src/render/beauty.ts +115 -0
- package/src/render/camera.ts +142 -0
- package/src/render/depth-map.ts +191 -0
- package/src/render/font.ts +76 -0
- package/src/render/ground.ts +87 -0
- package/src/render/index.ts +34 -0
- package/src/render/lighting.ts +121 -0
- package/src/render/lod-strip.ts +34 -0
- package/src/render/metrics.ts +147 -0
- package/src/render/options.ts +81 -0
- package/src/render/photo-match.ts +28 -0
- package/src/render/raster.ts +143 -0
- package/src/render/resolve.ts +109 -0
- package/src/render/review-prompt.ts +59 -0
- package/src/render/review-warnings.ts +92 -0
- package/src/render/review.ts +103 -0
- package/src/render/scene.ts +119 -0
- package/src/render/shade.ts +204 -0
- package/src/render/sheet.ts +88 -0
- package/src/render/texture.ts +97 -0
- package/src/render/thumbnail.ts +75 -0
- package/src/render/vis-buffer.ts +119 -0
- package/src/testing/assert.ts +115 -0
- package/src/testing/compiled.ts +31 -0
- package/src/testing/hash.ts +14 -0
- package/src/testing/index.ts +14 -0
- package/src/testing/snapshot.ts +37 -0
- package/src/types/gltf-validator.d.ts +13 -0
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# 09 — Kinds
|
|
2
|
+
|
|
3
|
+
**A kind is a library of builders a recipe calls, not a class the recipe fills in.** `facade(ctx, {...})` adds parts to the caller's `ctx`; the recipe stays in charge of layout, naming and LOD. `kind: '…'` on `defineAsset` is a free string for metadata. Source: `src/kinds/`.
|
|
4
|
+
|
|
5
|
+
Status as of 2026-11:
|
|
6
|
+
|
|
7
|
+
| Kind | Builders | Status |
|
|
8
|
+
|---|---|---|
|
|
9
|
+
| Building / tower | `facade`, `setbacks` | Shipped |
|
|
10
|
+
| | `roof` / `roofMesh` (gable, hip, pyramid, mansard, gambrel, shed; chimneys), `opening` (window/door kit), `facade({ kit })`, `mass` (true-scale masonry) | Shipped |
|
|
11
|
+
| Vehicle | `axle`, `trackRun`, `whyteAxles`, `propeller` | Shipped |
|
|
12
|
+
| | lofted bodies, fuselages, wings | Possible now with `loft`, `sweep`; no dedicated builder yet |
|
|
13
|
+
| Vessel | `hull` / `hullMesh` / `hullLines` | Shipped |
|
|
14
|
+
| Vegetation | `tree`, `trees` family, `treeReal`; presets `oak`, `pine`, `palm`, `birch`, `willow` | Shipped |
|
|
15
|
+
| Character | `head` (orc, goblin, human, dwarf, elf via `HEADS`), `headParams`, `headEdits`, `checkHead` | Shipped (stylised busts) |
|
|
16
|
+
| Creature | `creature` (whole bodies, skinned) | Planned, M4 |
|
|
17
|
+
| Scene | `ctx.use` (core), `heightfield` terrain with erosion and `terrainColor` | Shipped |
|
|
18
|
+
| Families | `defineFamily` (core): variants of any kind | Shipped |
|
|
19
|
+
|
|
20
|
+
## Building / tower
|
|
21
|
+
|
|
22
|
+
**Masses, then details.** Block the volumes with `box` or `prism`, then wrap them with `facade`.
|
|
23
|
+
|
|
24
|
+
`facade(ctx, opts)` runs a [split-grammar](#split-grammar) line along each side, per floor. Slots named `win`, `door`, `arch`, `shop` become inset panels; every other symbol stays wall.
|
|
25
|
+
|
|
26
|
+
| Option | Meaning |
|
|
27
|
+
|---|---|
|
|
28
|
+
| `prefix` | Part name prefix → `<prefix>_<side>_<floor>_<slot>` |
|
|
29
|
+
| `at`, `footprint` | Base centre and `[width X, depth Y]` of the mass it wraps |
|
|
30
|
+
| `floors`, `floorHeight` | Storeys, metres per storey |
|
|
31
|
+
| `grammar`, `ground?` | The line for every floor; `ground` overrides floor 0 |
|
|
32
|
+
| `sides` | Any of `front` (+X), `back`, `left` (+Y), `right` |
|
|
33
|
+
| `glass`, `frame?`, `windowRatio?` | Colours; window height as a fraction of the floor (default 0.6) |
|
|
34
|
+
|
|
35
|
+
`setbacks(ctx, { prefix, tiers, floorHeight, color, grammar, glass, detail })` stacks shrinking masses (Empire State, Chrysler-style). Each tier: `{ footprint, floors }`. Returns the top z for the spire. Pass `detail: ctx.lod < 2`.
|
|
36
|
+
|
|
37
|
+
### Split grammar
|
|
38
|
+
|
|
39
|
+
After CGA shape grammar (Müller et al. 2006). One string lays out one strip.
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
"pier:0.9 | {arch:2.5}* | door:3 | pier:~"
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
| Term | Meaning |
|
|
46
|
+
|---|---|
|
|
47
|
+
| `sym:1.2` | Fixed width, metres |
|
|
48
|
+
| `sym:~` / `sym:~2` | Stretch: shares the leftover, by weight (default 1) |
|
|
49
|
+
| `{a:1.4 \| b:0.6}*` | Repeat: as many whole copies as fit. Fixed sizes only; one repeat per line |
|
|
50
|
+
|
|
51
|
+
With no stretch term, repeat copies widen evenly to fill. Fixed terms longer than the strip → `T3D_GRAMMAR_OVERFLOW`. `split(src, length)` returns `{ symbol, start, size }[]`.
|
|
52
|
+
|
|
53
|
+
## Vehicle
|
|
54
|
+
|
|
55
|
+
Named parts follow the engine contract: `axle_*` and `wheel_*` spin about local Y; `prop_*` / `rotor_*` spin about their own axis.
|
|
56
|
+
|
|
57
|
+
| Builder | Adds | Notes |
|
|
58
|
+
|---|---|---|
|
|
59
|
+
| `axle(ctx, { name, at, radius, gauge, width, color, rps })` | Group `name` (spinning about Y) + `name_wheel_l`, `name_wheel_r` | Segments drop per LOD (24 → 12 → 6) |
|
|
60
|
+
| `trackRun(ctx, { side, y, length, wheels, radius, width, color, track })` | `road_wheel_<side>_<i>` (spinning), `track_<side>_top`, `track_<side>_bottom` | Tracked running gear, one side per call |
|
|
61
|
+
| `whyteAxles('4-4-0', { leading, driving, trailing, spacing })` | Nothing; returns `{ role, x, radius }[]` | Whyte notation → axle positions along X, leading end at +X |
|
|
62
|
+
| `propeller(ctx, { name, at, radius, blades, color, parent? })` | Group spinning about X + hub + blades | |
|
|
63
|
+
|
|
64
|
+
Domain-native params are the point: a loco recipe says `'2-6-2'`, not twelve wheel coordinates.
|
|
65
|
+
|
|
66
|
+
## Vessel
|
|
67
|
+
|
|
68
|
+
`hull(ctx, o)` adds `hull` + `hull_deck`; `hullMesh(o, lod)` returns the mesh for `place`/`merge`; `hullLines(o)` exposes the shape functions. Required: `length`, `beam`, `draft`, `freeboard`, `colors: { topsides, bottom, boot?, deck, bulwark? }`. Optional: `sheer: [bow, stern]`, `stern: 'round' | 'transom'`, `transomWidth`, `fullness`, `entrance`, `flare`, `deadriseDeg`, `bulwark`, `camber`, `bootTop`, `fender`, `at` (the waterline). Bow toward +X. Watertight at every LOD; waterline and boot-top bands are crisp.
|
|
69
|
+
|
|
70
|
+
## Roofs and openings
|
|
71
|
+
|
|
72
|
+
Every builder has a pure mesh form (`roofMesh(o, lod)`, `opening(o, lod)`, `mass(o, lod)`) and recipes compose them with `place`/`merge`; `roof(ctx, o)` is the thin ctx wrapper and returns `{ ridge, eave, heightAt(x, y) }`. A roof is the lower envelope of its slope planes, so ridges and hips fall out exactly; the underside sits on the walls' top outer edge, gable and shed ends get infill walls, LOD0/1 add slab thickness, soffit, fascia, ridge/hip caps and chimneys (pots at LOD0), LOD2 is bare slopes (≤ 40 triangles). `opening` is a window or door with a true-scale reveal, sill, lintel, sash and glazing bars; `facade(…, { kit })` places one in every opening slot. `mass` is a wall block whose masonry courses start at the ground line, unlike `box`, which stretches its strip over the face.
|
|
73
|
+
|
|
74
|
+
## Vegetation
|
|
75
|
+
|
|
76
|
+
`tree(ctx, { … })`: Weber–Penn botanical skeleton. Recursive branch levels (length, curve, split, taper per level) swept with `sweep`, leaves as cards on the foliage cells (`cutout` surface).
|
|
77
|
+
|
|
78
|
+
| API | Does |
|
|
79
|
+
|---|---|
|
|
80
|
+
| `tree(ctx, options)` | One tree into the caller's `ctx` |
|
|
81
|
+
| `trees` | A family: `trees.variant({ name, params: { species: 'pine', … } })` |
|
|
82
|
+
| `treeReal(params)` | `real` dimensions for the `proportions` gate |
|
|
83
|
+
| `SPECIES`: `oak`, `pine`, `palm`, `birch`, `willow` | Presets as data (`TreeParams`) |
|
|
84
|
+
|
|
85
|
+
The `oak`, `pine` and `palm` recipes are about 18 lines each. Wind is a crown `sway` for now. Planned: per-vertex wind weights; LOD2 impostors as crossed billboards from the foliage cells (today's are crude).
|
|
86
|
+
|
|
87
|
+
## Character
|
|
88
|
+
|
|
89
|
+
`head(ctx, { race: 'orc' | 'goblin' | 'human' | 'dwarf' | 'elf' | HeadParams, params?, prefix?, at?, jawSway? })` sculpts a stylised head and bust from SDF edits (`sculpt`) and adds `bust` (on z = 0), `head`, `jaw` (pivot at the hinge, optional sway) and `eye_l` / `eye_r` (pivots at the eyeball centres, looking along +X). Races are data (`HEADS`); `params` overrides one level deep; `checkHead` rejects impossible proportions with a fix. Vertex AO is baked at LOD0/1. Recipes: `orc_head`, `goblin_head`, `human_head`.
|
|
90
|
+
|
|
91
|
+
## Creature (planned, M4)
|
|
92
|
+
|
|
93
|
+
`creature(ctx, { skeleton: 'biped' | 'quadruped' | 'bird', height })`: SDF flesh over a skeleton, meshed with marching cubes (`sdf` primitive). **Stylised, not realistic.** Bones become named parts for posing. Realistic animals and people go through [`/neural`](08-neural-adapters.md).
|
|
94
|
+
|
|
95
|
+
## Scene
|
|
96
|
+
|
|
97
|
+
A scene is an asset whose `build` calls `ctx.use(other, { prefix, at, yaw, seed })`. No special kind is needed. Example (a street of varied houses): [02-dsl.md#composition-ctxuse](02-dsl.md#composition-ctxuse). Planned on top: layout helpers (street grids, scatter on a footprint) built on the split grammar.
|
|
98
|
+
|
|
99
|
+
Terrain for scenes and presentation dioramas: `heightfield({ rect: [x0, y0, x1, y1], cells: [nx, ny], height: (x, y) => z, color, strip? })`. `color` may be a function of `(x, y, z)` (ruts darker, slopes greener); `cells` is the LOD knob.
|
|
100
|
+
|
|
101
|
+
## Families and emitters
|
|
102
|
+
|
|
103
|
+
Any kind can be wrapped in a family: `defineFamily({ defaults, build })` → `variant` / `extend` ([02-dsl](02-dsl.md#families-one-build-many-assets)). Vehicles and buildings add `ctx.emitter` nodes for exhaust, steam, chimney smoke and muzzle flash ([02-dsl](02-dsl.md#emitters)).
|
|
104
|
+
|
|
105
|
+
Adding a kind: [architecture/adding-a-kind.md](../architecture/adding-a-kind.md).
|
|
@@ -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)
|