@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,196 @@
|
|
|
1
|
+
// The house material library: sixteen physically distinct strips with measured or sourced PBR
|
|
2
|
+
// numbers (docs/architecture/materials.md has the table and the reasoning). Metals use the
|
|
3
|
+
// physicallybased.info reflectance (sRGB here), dielectrics stay inside the 30–240 sRGB albedo
|
|
4
|
+
// range, roughness follows engine/asset references. Colour lives in the texture, so tint these
|
|
5
|
+
// with a near-white vertex colour (`NATURAL`) — except where noted (paint films take the hue).
|
|
6
|
+
|
|
7
|
+
import type { MaterialDef } from './material-def.ts';
|
|
8
|
+
import type { MaterialStripId } from './trim-sheet.ts';
|
|
9
|
+
|
|
10
|
+
const PB = 'https://physicallybased.info';
|
|
11
|
+
const ROBLOX = 'https://create.roblox.com/docs/art/modeling/material-reference';
|
|
12
|
+
const PBR_GUIDE = 'https://www.adobe.com/learn/substance-3d-designer/web/the-pbr-guide-part-2';
|
|
13
|
+
|
|
14
|
+
type Library = Readonly<Record<MaterialStripId, MaterialDef & { readonly id: MaterialStripId }>>;
|
|
15
|
+
|
|
16
|
+
export const MATERIAL_LIBRARY: Library = {
|
|
17
|
+
oak: {
|
|
18
|
+
id: 'oak',
|
|
19
|
+
kind: 'planks',
|
|
20
|
+
scaleMetres: 0.6,
|
|
21
|
+
roughness: 0.68,
|
|
22
|
+
albedo: '#a27b52',
|
|
23
|
+
albedo2: '#6a482b',
|
|
24
|
+
params: { width: 0.14, rings: 0.55, rays: 0.6, knots: 0.6 },
|
|
25
|
+
description: 'Oak boards: warm brown, dark latewood rings, pale ray flecks.',
|
|
26
|
+
sources: [PBR_GUIDE, ROBLOX],
|
|
27
|
+
},
|
|
28
|
+
pine: {
|
|
29
|
+
id: 'pine',
|
|
30
|
+
kind: 'planks',
|
|
31
|
+
scaleMetres: 0.6,
|
|
32
|
+
roughness: 0.72,
|
|
33
|
+
albedo: '#d2b183',
|
|
34
|
+
albedo2: '#9b6a3c',
|
|
35
|
+
params: { width: 0.16, rings: 0.75, knots: 2.5 },
|
|
36
|
+
description: 'Pine boards: pale yellow, strong orange latewood, frequent knots.',
|
|
37
|
+
sources: [PBR_GUIDE, ROBLOX],
|
|
38
|
+
},
|
|
39
|
+
wood_weathered: {
|
|
40
|
+
id: 'wood_weathered',
|
|
41
|
+
kind: 'planks',
|
|
42
|
+
scaleMetres: 0.6,
|
|
43
|
+
roughness: 0.86,
|
|
44
|
+
albedo: '#9b968c',
|
|
45
|
+
albedo2: '#4f463c',
|
|
46
|
+
params: { width: 0.2, rings: 0.35, checks: 1, knots: 1.5, grime: 0.15 },
|
|
47
|
+
description: 'Barn-grey weathered boards: silvered, eroded grain, open checks.',
|
|
48
|
+
sources: [ROBLOX],
|
|
49
|
+
},
|
|
50
|
+
wood_painted: {
|
|
51
|
+
id: 'wood_painted',
|
|
52
|
+
kind: 'planks',
|
|
53
|
+
scaleMetres: 0.6,
|
|
54
|
+
roughness: 0.84,
|
|
55
|
+
albedo: '#ededed',
|
|
56
|
+
albedo2: '#a08c74',
|
|
57
|
+
params: { width: 0.14, paint: 1, paintRough: 0.62, chips: 0.38, knots: 0.8, grime: 0.3 },
|
|
58
|
+
description: 'Painted boards (vertex colour = paint) worn through to grey wood at the edges.',
|
|
59
|
+
sources: [ROBLOX],
|
|
60
|
+
},
|
|
61
|
+
iron_rust: {
|
|
62
|
+
id: 'iron_rust',
|
|
63
|
+
kind: 'metal',
|
|
64
|
+
scaleMetres: 0.5,
|
|
65
|
+
roughness: 0.55,
|
|
66
|
+
metalness: 0.9,
|
|
67
|
+
albedo: '#8e8a85',
|
|
68
|
+
albedo2: '#764323',
|
|
69
|
+
params: { oxide: 0.62, cast: 0.25, streaks: 0.5 },
|
|
70
|
+
description: 'Weathered wrought/rolled iron, rust blooming in patches and runs.',
|
|
71
|
+
sources: [PB, ROBLOX],
|
|
72
|
+
},
|
|
73
|
+
iron_cast: {
|
|
74
|
+
id: 'iron_cast',
|
|
75
|
+
kind: 'metal',
|
|
76
|
+
scaleMetres: 0.5,
|
|
77
|
+
roughness: 0.68,
|
|
78
|
+
metalness: 0.75,
|
|
79
|
+
albedo: '#6c6966',
|
|
80
|
+
albedo2: '#55372a',
|
|
81
|
+
params: { cast: 1, oxide: 0.35 },
|
|
82
|
+
description: 'Sand-cast iron (wheels, frames): dark pitted skin, dull oxide.',
|
|
83
|
+
sources: [PB],
|
|
84
|
+
},
|
|
85
|
+
steel_painted: {
|
|
86
|
+
id: 'steel_painted',
|
|
87
|
+
kind: 'metal',
|
|
88
|
+
scaleMetres: 0.5,
|
|
89
|
+
roughness: 0.4,
|
|
90
|
+
metalness: 1,
|
|
91
|
+
albedo: '#eeeeee',
|
|
92
|
+
albedo2: '#5a3b2a',
|
|
93
|
+
params: { paint: 1, paintRough: 0.42, chips: 0.2, streaks: 0.2, grime: 0.2 },
|
|
94
|
+
description: 'Painted steel (vertex colour = paint): chips show rusty steel beneath.',
|
|
95
|
+
sources: [ROBLOX],
|
|
96
|
+
},
|
|
97
|
+
steel_galvanised: {
|
|
98
|
+
id: 'steel_galvanised',
|
|
99
|
+
kind: 'metal',
|
|
100
|
+
scaleMetres: 0.5,
|
|
101
|
+
roughness: 0.42,
|
|
102
|
+
metalness: 1,
|
|
103
|
+
albedo: '#d5dadc',
|
|
104
|
+
albedo2: '#9a9c98',
|
|
105
|
+
params: { spangle: 1, oxide: 0.12 },
|
|
106
|
+
description: 'Hot-dip galvanised steel: zinc spangle, white-rust bloom.',
|
|
107
|
+
sources: [PB],
|
|
108
|
+
},
|
|
109
|
+
brass: {
|
|
110
|
+
id: 'brass',
|
|
111
|
+
kind: 'metal',
|
|
112
|
+
scaleMetres: 0.5,
|
|
113
|
+
roughness: 0.28,
|
|
114
|
+
metalness: 1,
|
|
115
|
+
albedo: '#f5e5ae',
|
|
116
|
+
albedo2: '#6b5a3a',
|
|
117
|
+
params: { brush: 0.3, oxide: 0.1 },
|
|
118
|
+
description: 'Brass fittings: warm gold reflectance, dark tarnish in patches.',
|
|
119
|
+
sources: [PB, ROBLOX],
|
|
120
|
+
},
|
|
121
|
+
copper: {
|
|
122
|
+
id: 'copper',
|
|
123
|
+
kind: 'metal',
|
|
124
|
+
scaleMetres: 0.5,
|
|
125
|
+
roughness: 0.32,
|
|
126
|
+
metalness: 1,
|
|
127
|
+
albedo: '#f2b89c',
|
|
128
|
+
albedo2: '#6fa58f',
|
|
129
|
+
params: { oxide: 0.15 },
|
|
130
|
+
description: 'Copper: salmon reflectance with verdigris patches.',
|
|
131
|
+
sources: [PB],
|
|
132
|
+
},
|
|
133
|
+
aluminium: {
|
|
134
|
+
id: 'aluminium',
|
|
135
|
+
kind: 'metal',
|
|
136
|
+
scaleMetres: 0.5,
|
|
137
|
+
roughness: 0.34,
|
|
138
|
+
metalness: 1,
|
|
139
|
+
albedo: '#e6e8e9',
|
|
140
|
+
params: { brush: 1 },
|
|
141
|
+
description: 'Brushed aluminium: bright, cool, streaked along U.',
|
|
142
|
+
sources: [PB],
|
|
143
|
+
},
|
|
144
|
+
chrome: {
|
|
145
|
+
id: 'chrome',
|
|
146
|
+
kind: 'metal',
|
|
147
|
+
scaleMetres: 0.5,
|
|
148
|
+
roughness: 0.07,
|
|
149
|
+
metalness: 1,
|
|
150
|
+
albedo: '#d3d9dc',
|
|
151
|
+
description: 'Chrome plate: near-mirror.',
|
|
152
|
+
sources: [PB],
|
|
153
|
+
},
|
|
154
|
+
rubber: {
|
|
155
|
+
id: 'rubber',
|
|
156
|
+
kind: 'noise',
|
|
157
|
+
scaleMetres: 0.5,
|
|
158
|
+
roughness: 0.78,
|
|
159
|
+
albedo: '#2c2c2c',
|
|
160
|
+
params: { tone: 0.95, mottle: 0.06, relief: 0.0002, size: 0.05, grime: 0.2 },
|
|
161
|
+
description: 'Vulcanised rubber (tyres, hoses, seals).',
|
|
162
|
+
sources: [PB, ROBLOX],
|
|
163
|
+
},
|
|
164
|
+
canvas: {
|
|
165
|
+
id: 'canvas',
|
|
166
|
+
kind: 'weave',
|
|
167
|
+
scaleMetres: 1,
|
|
168
|
+
roughness: 0.88,
|
|
169
|
+
albedo: '#cbbea2',
|
|
170
|
+
albedo2: '#8a7c62',
|
|
171
|
+
params: { pitch: 0.004, folds: 0.6, stains: 0.35 },
|
|
172
|
+
description: 'Cotton duck canvas: natural, weave relief, folds and water stains.',
|
|
173
|
+
sources: [ROBLOX],
|
|
174
|
+
},
|
|
175
|
+
leather: {
|
|
176
|
+
id: 'leather',
|
|
177
|
+
kind: 'leather',
|
|
178
|
+
scaleMetres: 0.5,
|
|
179
|
+
roughness: 0.58,
|
|
180
|
+
albedo: '#7c5438',
|
|
181
|
+
albedo2: '#3e281a',
|
|
182
|
+
params: { creases: 0.5, wear: 0.4 },
|
|
183
|
+
description: 'Worn brown leather: pebble grain, dark creases, burnished wear.',
|
|
184
|
+
sources: [ROBLOX],
|
|
185
|
+
},
|
|
186
|
+
plastic: {
|
|
187
|
+
id: 'plastic',
|
|
188
|
+
kind: 'noise',
|
|
189
|
+
scaleMetres: 0.5,
|
|
190
|
+
roughness: 0.35,
|
|
191
|
+
albedo: '#ececec',
|
|
192
|
+
params: { tone: 0.97, mottle: 0.01, relief: 0.00004, size: 0.2 },
|
|
193
|
+
description: 'Moulded plastic (vertex colour = the plastic colour): satin, even.',
|
|
194
|
+
sources: [ROBLOX],
|
|
195
|
+
},
|
|
196
|
+
};
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
// The material definition contract: what a project (or the house library) says about a strip —
|
|
2
|
+
// PBR numbers, intrinsic colour, a base painter kind and an optional painter layer — and its
|
|
3
|
+
// translation to the sheet's StripRecipe. Validation throws T3D_MATERIAL_INVALID with the fix.
|
|
4
|
+
|
|
5
|
+
import { Ts23dError, type Vec3 } from '../core/index.ts';
|
|
6
|
+
import type { MaterialPainter, StripKind, StripRecipe } from './paint/types.ts';
|
|
7
|
+
import { hex } from './palette.ts';
|
|
8
|
+
import { type TextureSource, validateTexture } from './texture-source.ts';
|
|
9
|
+
|
|
10
|
+
export interface MaterialDef {
|
|
11
|
+
/** snake_case, unique; becomes the strip id recipes pass as `strip`. */
|
|
12
|
+
readonly id: string;
|
|
13
|
+
/** Base painter the strip starts from. Default 'noise' (a quiet surface). */
|
|
14
|
+
readonly kind?: StripKind;
|
|
15
|
+
/** World metres the strip's V span represents (typical face height). Default 1. */
|
|
16
|
+
readonly scaleMetres?: number;
|
|
17
|
+
/** Perceptual roughness 0..1 (glTF): 0 mirror, 1 chalk. */
|
|
18
|
+
readonly roughness: number;
|
|
19
|
+
/** 0 dielectric, 1 metal. Default 0. Avoid in-between values except for dirt/oxide blends. */
|
|
20
|
+
readonly metalness?: number;
|
|
21
|
+
/** Intrinsic base colour: sRGB '#rrggbb' or linear RGB. Default white (vertex colour = hue). */
|
|
22
|
+
readonly albedo?: string | Vec3;
|
|
23
|
+
/** Second colour the painter blends to (latewood, rust, verdigris, bare wood under paint). */
|
|
24
|
+
readonly albedo2?: string | Vec3;
|
|
25
|
+
/** Painter knobs for `kind` (see each painter's header in src/material/paint/). */
|
|
26
|
+
readonly params?: Readonly<Record<string, number>>;
|
|
27
|
+
/** Your own layer, painted after `kind`: write height/tone/rough/metal/mix per texel. */
|
|
28
|
+
readonly paint?: MaterialPainter;
|
|
29
|
+
/** Paint from a CC0 texture set (opt-in, needs `bun run textures`) instead of `kind`. */
|
|
30
|
+
readonly texture?: TextureSource;
|
|
31
|
+
readonly description?: string;
|
|
32
|
+
/** Where the numbers come from (URLs). */
|
|
33
|
+
readonly sources?: readonly string[];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const ID = /^[a-z][a-z0-9_]{1,31}$/;
|
|
37
|
+
const unit = (v: number): boolean => Number.isFinite(v) && v >= 0 && v <= 1;
|
|
38
|
+
|
|
39
|
+
function fail(id: string, cause: string, fix: string): never {
|
|
40
|
+
throw new Ts23dError('T3D_MATERIAL_INVALID', { cause: `material '${id}': ${cause}`, fix });
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function colour(id: string, key: string, v: string | Vec3 | undefined): Vec3 | undefined {
|
|
44
|
+
if (v === undefined) return undefined;
|
|
45
|
+
if (typeof v === 'string') {
|
|
46
|
+
if (!/^#?[0-9a-fA-F]{6}$/.test(v))
|
|
47
|
+
fail(id, `${key} '${v}' is not a hex colour`, "use '#rrggbb'");
|
|
48
|
+
return hex(v);
|
|
49
|
+
}
|
|
50
|
+
if (!(v.length === 3 && v.every(unit)))
|
|
51
|
+
fail(id, `${key} [${v}] is not linear RGB 0..1`, "use '#rrggbb' or three numbers in 0..1");
|
|
52
|
+
return v;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Validate a definition and turn it into the strip recipe the sheet painter runs. */
|
|
56
|
+
export function toStripRecipe(d: MaterialDef): StripRecipe {
|
|
57
|
+
const id = String(d.id);
|
|
58
|
+
if (!ID.test(id))
|
|
59
|
+
fail(id, 'id is not snake_case (2–32 chars, starts with a letter)', "e.g. 'teak_deck'");
|
|
60
|
+
if (!unit(d.roughness)) fail(id, `roughness ${d.roughness}`, 'roughness is 0..1');
|
|
61
|
+
if (d.metalness !== undefined && !unit(d.metalness))
|
|
62
|
+
fail(id, `metalness ${d.metalness}`, 'metalness is 0..1 (0 dielectric, 1 metal)');
|
|
63
|
+
const scale = d.scaleMetres ?? 1;
|
|
64
|
+
if (!(Number.isFinite(scale) && scale > 0))
|
|
65
|
+
fail(id, `scaleMetres ${scale}`, 'scaleMetres is the face height in metres, > 0');
|
|
66
|
+
if (d.paint !== undefined && typeof d.paint !== 'function')
|
|
67
|
+
fail(id, 'paint is not a function', 'paint is (surface, kit) => void');
|
|
68
|
+
for (const [k, v] of Object.entries(d.params ?? {}))
|
|
69
|
+
if (!Number.isFinite(v)) fail(id, `params.${k} is ${v}`, 'params are finite numbers');
|
|
70
|
+
if (d.texture) validateTexture(id, d.texture);
|
|
71
|
+
const albedo = colour(id, 'albedo', d.albedo);
|
|
72
|
+
const albedo2 = colour(id, 'albedo2', d.albedo2);
|
|
73
|
+
return {
|
|
74
|
+
kind: d.kind ?? 'noise',
|
|
75
|
+
scaleMetres: scale,
|
|
76
|
+
roughness: d.roughness,
|
|
77
|
+
...(d.metalness !== undefined ? { metalness: d.metalness } : {}),
|
|
78
|
+
...(d.params ? { params: d.params } : {}),
|
|
79
|
+
...(albedo ? { albedo } : {}),
|
|
80
|
+
...(albedo2 ? { albedo2 } : {}),
|
|
81
|
+
...(d.paint ? { paint: d.paint } : {}),
|
|
82
|
+
...(d.texture ? { texture: d.texture } : {}),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// Brick and stone bond (kind 'bond'): staggered courses, recessed mortar with chipped edges,
|
|
2
|
+
// per-unit tone and tilt, face pitting. Stone is the same bond with `irregular` > 0 and bigger,
|
|
3
|
+
// rougher units.
|
|
4
|
+
// params: course 0.075 · length 0.225 · mortar 0.01 · depth 0.006 · bevel 0.004 · irregular 0
|
|
5
|
+
// stagger 0.5 · relief 0.0008 (face roughness amplitude, m) · variation 0.3 (tone spread)
|
|
6
|
+
|
|
7
|
+
import { type Canvas, noiseAt, param, smooth } from './canvas.ts';
|
|
8
|
+
import { Courses } from './courses.ts';
|
|
9
|
+
import { hash2 } from './noise.ts';
|
|
10
|
+
import type { StripRecipe } from './types.ts';
|
|
11
|
+
|
|
12
|
+
export function paintBond(c: Canvas, r: StripRecipe, seed: number): void {
|
|
13
|
+
const p = r.params;
|
|
14
|
+
const mortar = param(p, 'mortar', 0.01);
|
|
15
|
+
const depth = param(p, 'depth', 0.006);
|
|
16
|
+
const bevel = param(p, 'bevel', 0.004);
|
|
17
|
+
const relief = param(p, 'relief', 0.0008);
|
|
18
|
+
const variation = param(p, 'variation', 0.3);
|
|
19
|
+
const course = param(p, 'course', 0.075);
|
|
20
|
+
const length = param(p, 'length', 0.225);
|
|
21
|
+
const courses = new Courses(c, {
|
|
22
|
+
course,
|
|
23
|
+
length,
|
|
24
|
+
irregular: param(p, 'irregular', 0),
|
|
25
|
+
stagger: param(p, 'stagger', 0.5),
|
|
26
|
+
seed,
|
|
27
|
+
});
|
|
28
|
+
const pu = c.metresU / c.w;
|
|
29
|
+
const pv = c.metresV / c.h;
|
|
30
|
+
const half = mortar / 2;
|
|
31
|
+
for (let y = 0; y < c.h; y++) {
|
|
32
|
+
const ym = (y + 0.5) * pv;
|
|
33
|
+
const k = courses.at(ym);
|
|
34
|
+
const dy = Math.min(ym - courses.y0, courses.y1 - ym);
|
|
35
|
+
for (let x = 0; x < c.w; x++) {
|
|
36
|
+
const i = y * c.w + x;
|
|
37
|
+
const xm = (x + 0.5) * pu;
|
|
38
|
+
const unit = courses.id[x] ?? 0;
|
|
39
|
+
const chip = noiseAt(c, xm, ym, 0.02, 0.02, seed + 7, 3) * mortar * 0.6;
|
|
40
|
+
const d = Math.min(courses.dx[x] ?? 0, dy) + chip;
|
|
41
|
+
const fine = noiseAt(c, xm, ym, 0.006, 0.006, seed + 11, 2);
|
|
42
|
+
if (d < half) {
|
|
43
|
+
c.height[i] = -depth + fine * 0.0006;
|
|
44
|
+
c.tone[i] = 0.95 + 0.04 * fine;
|
|
45
|
+
c.rough[i] = Math.min(1, r.roughness + 0.08);
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
const h = hash2(k, unit, seed);
|
|
49
|
+
const h2 = hash2(unit, k, seed + 3);
|
|
50
|
+
const big = noiseAt(c, xm, ym, length * 0.6, course * 0.8, seed + 5, 3);
|
|
51
|
+
const edge = smooth(0, bevel, d - half);
|
|
52
|
+
const tilt = ((h2 - 0.5) * (courses.ux[x] ?? 0) * 0.02 + (h - 0.5) * 0.001) * edge;
|
|
53
|
+
const pit = fine > 0.55 ? (fine - 0.55) * 0.004 : 0;
|
|
54
|
+
c.height[i] = -depth * (1 - edge) + relief * big + tilt - pit;
|
|
55
|
+
c.tone[i] =
|
|
56
|
+
(0.62 - variation * 0.5 + variation * h) * (1 + 0.15 * big + 0.05 * fine) - pit * 40;
|
|
57
|
+
c.rough[i] = r.roughness + 0.06 * fine;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
// The painting surface for one sheet region (a strip or a cell): float layers in real metres.
|
|
2
|
+
// Painters write height (metres of relief, + is out of the surface), linear grey tone,
|
|
3
|
+
// roughness, metalness and a two-colour `mix`; `compose` turns them into albedo + normal + ORM.
|
|
4
|
+
|
|
5
|
+
import type { Vec3 } from '../../core/index.ts';
|
|
6
|
+
import { fbm } from './noise.ts';
|
|
7
|
+
|
|
8
|
+
export interface Canvas {
|
|
9
|
+
readonly w: number;
|
|
10
|
+
readonly h: number;
|
|
11
|
+
/** World metres the canvas spans along U (x) and V (y). Row y grows with world up (+V). */
|
|
12
|
+
readonly metresU: number;
|
|
13
|
+
readonly metresV: number;
|
|
14
|
+
/** Strips wrap along U (tile seamlessly); cells clamp. */
|
|
15
|
+
readonly wrapU: boolean;
|
|
16
|
+
readonly height: Float32Array;
|
|
17
|
+
readonly tone: Float32Array;
|
|
18
|
+
readonly rough: Float32Array;
|
|
19
|
+
readonly metal: Float32Array;
|
|
20
|
+
/** Extra occlusion multiplied into the cavity AO (foliage darkens toward the twig). */
|
|
21
|
+
readonly occl: Float32Array;
|
|
22
|
+
/** 0 = `colours[0]`, 1 = `colours[1]` (latewood, rust, bare wood under chipped paint…). */
|
|
23
|
+
readonly mix: Float32Array;
|
|
24
|
+
/** Linear albedo pair for material strips; null = greyscale (vertex colour carries hue). */
|
|
25
|
+
colours: readonly [Vec3, Vec3] | null;
|
|
26
|
+
/** sRGB RGBA override (stamped images, coloured foliage); null when painted greyscale. */
|
|
27
|
+
rgba: Uint8Array | null;
|
|
28
|
+
/** Tangent-space normal override (image strips); null = derived from `height`. */
|
|
29
|
+
normalRgba: Uint8Array | null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function createCanvas(
|
|
33
|
+
w: number,
|
|
34
|
+
h: number,
|
|
35
|
+
metresU: number,
|
|
36
|
+
metresV: number,
|
|
37
|
+
wrapU: boolean,
|
|
38
|
+
): Canvas {
|
|
39
|
+
const n = w * h;
|
|
40
|
+
return {
|
|
41
|
+
w,
|
|
42
|
+
h,
|
|
43
|
+
metresU,
|
|
44
|
+
metresV,
|
|
45
|
+
wrapU,
|
|
46
|
+
height: new Float32Array(n),
|
|
47
|
+
tone: new Float32Array(n).fill(1),
|
|
48
|
+
rough: new Float32Array(n).fill(0.8),
|
|
49
|
+
metal: new Float32Array(n),
|
|
50
|
+
occl: new Float32Array(n).fill(1),
|
|
51
|
+
mix: new Float32Array(n),
|
|
52
|
+
colours: null,
|
|
53
|
+
rgba: null,
|
|
54
|
+
normalRgba: null,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Metres per pixel along x and y. */
|
|
59
|
+
export const pxU = (c: Canvas): number => c.metresU / c.w;
|
|
60
|
+
export const pxV = (c: Canvas): number => c.metresV / c.h;
|
|
61
|
+
|
|
62
|
+
/** Noise period (lattice cells across the canvas width) for a feature size in metres. */
|
|
63
|
+
export function periodFor(c: Canvas, featureMetres: number): number {
|
|
64
|
+
return c.wrapU ? Math.max(1, Math.round(c.metresU / featureMetres)) : 1 << 20;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* fBm sampled at metres (xm, ym) with feature size `sizeU` along U and `sizeV` along V,
|
|
69
|
+
* snapped so it tiles along U. Returns [-1, 1].
|
|
70
|
+
*/
|
|
71
|
+
export function noiseAt(
|
|
72
|
+
c: Canvas,
|
|
73
|
+
xm: number,
|
|
74
|
+
ym: number,
|
|
75
|
+
sizeU: number,
|
|
76
|
+
sizeV: number,
|
|
77
|
+
seed: number,
|
|
78
|
+
octaves = 4,
|
|
79
|
+
): number {
|
|
80
|
+
// Strips are coarse along V: never ask for a feature finer than ~1.5 texels on either axis,
|
|
81
|
+
// or it aliases into streaks. Scale both axes together so the feature keeps its aspect.
|
|
82
|
+
const finest = 1.5 * Math.max(c.metresU / c.w, c.metresV / c.h);
|
|
83
|
+
const k = Math.max(1, finest / Math.min(sizeU, sizeV));
|
|
84
|
+
const period = periodFor(c, sizeU * k);
|
|
85
|
+
const cell = c.wrapU ? c.metresU / period : sizeU * k;
|
|
86
|
+
return fbm(xm / cell, ym / (sizeV * k), period, seed, octaves);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Read a parameter with a default, rejecting nothing (validation happens in the generator). */
|
|
90
|
+
export const param = (
|
|
91
|
+
params: Readonly<Record<string, number>> | undefined,
|
|
92
|
+
key: string,
|
|
93
|
+
fallback: number,
|
|
94
|
+
): number => params?.[key] ?? fallback;
|
|
95
|
+
|
|
96
|
+
export const clamp01 = (v: number): number => Math.min(1, Math.max(0, v));
|
|
97
|
+
|
|
98
|
+
export const smooth = (e0: number, e1: number, v: number): number => {
|
|
99
|
+
const t = clamp01((v - e0) / (e1 - e0));
|
|
100
|
+
return t * t * (3 - 2 * t);
|
|
101
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// Paint one cell: a stamped image, a foliage card (its own painter; the recipe's roughness scales
|
|
2
|
+
// the leaves), or a decal motif over a backdrop of the recipe's kind.
|
|
3
|
+
|
|
4
|
+
import { type CellId, isFoliage } from '../trim-sheet.ts';
|
|
5
|
+
import type { Canvas } from './canvas.ts';
|
|
6
|
+
import { paintDoorPanel, paintSign, paintWindowSash } from './cells.ts';
|
|
7
|
+
import { paintClock, paintPorthole, paintSmokebox } from './cells-round.ts';
|
|
8
|
+
import { FOLIAGE_PAINTERS } from './foliage.ts';
|
|
9
|
+
import { stampImage } from './stamp.ts';
|
|
10
|
+
import { paintStrip } from './strip.ts';
|
|
11
|
+
import type { CellImage, StripRecipe } from './types.ts';
|
|
12
|
+
|
|
13
|
+
type Motif = (c: Canvas, r: StripRecipe, seed: number) => void;
|
|
14
|
+
const MOTIFS: Readonly<Record<string, Motif>> = {
|
|
15
|
+
window_sash: paintWindowSash,
|
|
16
|
+
door_panel: paintDoorPanel,
|
|
17
|
+
clock: paintClock,
|
|
18
|
+
smokebox: paintSmokebox,
|
|
19
|
+
sign: paintSign,
|
|
20
|
+
porthole: paintPorthole,
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/** Roughness the foliage painter assumes; a recipe's own roughness rescales it. */
|
|
24
|
+
const LEAF_ROUGHNESS = 0.6;
|
|
25
|
+
|
|
26
|
+
export function paintCell(c: Canvas, id: CellId, entry: StripRecipe | CellImage, seed: number) {
|
|
27
|
+
if ('image' in entry) {
|
|
28
|
+
stampImage(c, entry.image);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
if (isFoliage(id)) {
|
|
32
|
+
FOLIAGE_PAINTERS[id](c, seed);
|
|
33
|
+
const k = entry.roughness / LEAF_ROUGHNESS;
|
|
34
|
+
for (let i = 0; i < c.rough.length; i++) c.rough[i] = Math.min(1, (c.rough[i] ?? 0.6) * k);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
paintStrip(c, entry, seed);
|
|
38
|
+
MOTIFS[id]?.(c, entry, seed + 1);
|
|
39
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
// Round decal cells (1 m each), centred, over the cell recipe's backdrop: station clock (bezel,
|
|
2
|
+
// minute and hour ticks, hands at 10:10), locomotive smokebox door (flanged, bolted rim, hinge
|
|
3
|
+
// straps, centre dart with handle), porthole (bolted brass ring, dark glass). Row y grows with
|
|
4
|
+
// world up, so 12 o'clock is +y.
|
|
5
|
+
|
|
6
|
+
import { type Canvas, noiseAt, smooth } from './canvas.ts';
|
|
7
|
+
import { PANE_TONE } from './cells.ts';
|
|
8
|
+
import { boltRing, dome, segment } from './shapes.ts';
|
|
9
|
+
import type { StripRecipe } from './types.ts';
|
|
10
|
+
|
|
11
|
+
const TAU = Math.PI * 2;
|
|
12
|
+
|
|
13
|
+
/** Hand from the centre at clock position `hours` (0..12), length `len`. */
|
|
14
|
+
function hand(px: number, py: number, hours: number, len: number): number {
|
|
15
|
+
const a = (hours / 12) * TAU;
|
|
16
|
+
return segment(px, py, 0, 0, Math.sin(a) * len, Math.cos(a) * len);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function paintClock(c: Canvas, r: StripRecipe, _seed: number): void {
|
|
20
|
+
const pu = c.metresU / c.w;
|
|
21
|
+
const pv = c.metresV / c.h;
|
|
22
|
+
for (let y = 0; y < c.h; y++) {
|
|
23
|
+
const py = (y + 0.5) * pv - 0.5;
|
|
24
|
+
for (let x = 0; x < c.w; x++) {
|
|
25
|
+
const px = (x + 0.5) * pu - 0.5;
|
|
26
|
+
const i = y * c.w + x;
|
|
27
|
+
const d = Math.hypot(px, py);
|
|
28
|
+
if (d > 0.48) continue;
|
|
29
|
+
const i0 = i;
|
|
30
|
+
if (d > 0.42) {
|
|
31
|
+
c.height[i0] = 0.012 * dome(Math.abs(d - 0.45), 0.03);
|
|
32
|
+
c.tone[i0] = 0.55;
|
|
33
|
+
c.metal[i0] = 1;
|
|
34
|
+
c.rough[i0] = 0.35;
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
const a = Math.atan2(px, py);
|
|
38
|
+
const minute = Math.abs((((a / TAU) * 60 + 60.5) % 1) - 0.5) * (TAU / 60) * d;
|
|
39
|
+
const hour = Math.abs((((a / TAU) * 12 + 12.5) % 1) - 0.5) * (TAU / 12) * d;
|
|
40
|
+
const tickM = d > 0.38 && d < 0.41 && minute < 0.003 ? 1 : 0;
|
|
41
|
+
const tickH = d > 0.32 && d < 0.41 && hour < 0.008 ? 1 : 0;
|
|
42
|
+
const hands = Math.min(
|
|
43
|
+
hand(px, py, 10 + 10 / 60, 0.22) - 0.012,
|
|
44
|
+
hand(px, py, 2, 0.34) - 0.007,
|
|
45
|
+
);
|
|
46
|
+
const ink = Math.max(tickM, tickH, hands < 0 ? 1 : 0, d < 0.02 ? 1 : 0);
|
|
47
|
+
c.height[i0] = -0.004 + 0.002 * ink;
|
|
48
|
+
c.tone[i0] = ink ? 0.08 : 0.96;
|
|
49
|
+
c.rough[i0] = ink ? 0.5 : r.roughness;
|
|
50
|
+
c.metal[i0] = 0;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function paintSmokebox(c: Canvas, r: StripRecipe, seed: number): void {
|
|
56
|
+
const pu = c.metresU / c.w;
|
|
57
|
+
const pv = c.metresV / c.h;
|
|
58
|
+
const metal = r.metalness ?? 0;
|
|
59
|
+
for (let y = 0; y < c.h; y++) {
|
|
60
|
+
const py = (y + 0.5) * pv - 0.5;
|
|
61
|
+
for (let x = 0; x < c.w; x++) {
|
|
62
|
+
const px = (x + 0.5) * pu - 0.5;
|
|
63
|
+
const i = y * c.w + x;
|
|
64
|
+
const d = Math.hypot(px, py);
|
|
65
|
+
if (d > 0.47) continue;
|
|
66
|
+
const pit = noiseAt(c, px + 0.5, py + 0.5, 0.03, 0.03, seed, 3);
|
|
67
|
+
// Slightly dished door, a raised flange, a bolt ring just inside it.
|
|
68
|
+
let h = 0.01 * (1 - (d / 0.47) ** 2) + 0.0004 * pit;
|
|
69
|
+
const flange = dome(Math.abs(d - 0.45), 0.02);
|
|
70
|
+
h += 0.006 * flange;
|
|
71
|
+
const bolt = dome(boltRing(px, py, 0.415, 28), 0.011);
|
|
72
|
+
h += 0.006 * bolt;
|
|
73
|
+
// Two hinge straps from the left rim to the dart's line, ending in a rounded tip.
|
|
74
|
+
const strap = Math.min(
|
|
75
|
+
segment(px, py, -0.47, 0.17, 0.12, 0.17),
|
|
76
|
+
segment(px, py, -0.47, -0.17, 0.12, -0.17),
|
|
77
|
+
);
|
|
78
|
+
const strapH = smooth(0.024, 0.018, strap);
|
|
79
|
+
h = Math.max(h, 0.014 * strapH + h * (1 - strapH));
|
|
80
|
+
const boss = dome(d, 0.05);
|
|
81
|
+
const handle = smooth(0.012, 0.008, segment(px, py, -0.13, 0, 0.13, 0));
|
|
82
|
+
h += 0.02 * boss + 0.012 * handle;
|
|
83
|
+
c.height[i] = h;
|
|
84
|
+
c.tone[i] =
|
|
85
|
+
(0.5 + 0.05 * pit) * (1 + 0.5 * Math.max(bolt, handle, strapH * 0.6, flange, boss));
|
|
86
|
+
c.rough[i] = r.roughness - 0.15 * Math.max(bolt, handle, boss);
|
|
87
|
+
c.metal[i] = metal + (1 - metal) * Math.max(handle, boss) * 0.8;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function paintPorthole(c: Canvas, r: StripRecipe, seed: number): void {
|
|
93
|
+
const pu = c.metresU / c.w;
|
|
94
|
+
const pv = c.metresV / c.h;
|
|
95
|
+
for (let y = 0; y < c.h; y++) {
|
|
96
|
+
const py = (y + 0.5) * pv - 0.5;
|
|
97
|
+
for (let x = 0; x < c.w; x++) {
|
|
98
|
+
const px = (x + 0.5) * pu - 0.5;
|
|
99
|
+
const i = y * c.w + x;
|
|
100
|
+
const d = Math.hypot(px, py);
|
|
101
|
+
if (d > 0.44) continue;
|
|
102
|
+
if (d < 0.29) {
|
|
103
|
+
const sky = noiseAt(c, px + 0.5, py + 0.5, 0.5, 0.5, seed, 2);
|
|
104
|
+
c.height[i] = -0.012;
|
|
105
|
+
c.tone[i] = PANE_TONE + 0.04 * (py + 0.3) + 0.02 * sky;
|
|
106
|
+
c.rough[i] = 0.05;
|
|
107
|
+
c.metal[i] = 0;
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
const ring = dome(Math.abs(d - 0.365), 0.075);
|
|
111
|
+
const bolt = dome(boltRing(px, py, 0.405, 10, Math.PI / 10), 0.018);
|
|
112
|
+
const lip = dome(Math.abs(d - 0.3), 0.012);
|
|
113
|
+
c.height[i] = 0.016 * ring + 0.008 * bolt + 0.004 * lip;
|
|
114
|
+
c.tone[i] = 0.75 + 0.15 * ring;
|
|
115
|
+
c.rough[i] = Math.max(0.2, r.roughness - 0.2 * ring);
|
|
116
|
+
c.metal[i] = 1;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|