@developerz.ai/ts23d 0.0.0-stage → 0.1.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 +79 -0
- package/CLAUDE.md +85 -0
- package/LICENSE +21 -0
- package/README.md +380 -2
- package/dist/chunk-11v28977.js +1575 -0
- package/dist/chunk-11v28977.js.map +30 -0
- package/dist/chunk-1ypfs7ga.js +3571 -0
- package/dist/chunk-1ypfs7ga.js.map +42 -0
- package/dist/chunk-4ddzsqtc.js +464 -0
- package/dist/chunk-4ddzsqtc.js.map +19 -0
- package/dist/chunk-a8fxakcj.js +19 -0
- package/dist/chunk-a8fxakcj.js.map +11 -0
- package/dist/chunk-gcyp9b4d.js +755 -0
- package/dist/chunk-gcyp9b4d.js.map +22 -0
- package/dist/chunk-kgvbpqqw.js +44 -0
- package/dist/chunk-kgvbpqqw.js.map +10 -0
- package/dist/chunk-kvcep7ft.js +1919 -0
- package/dist/chunk-kvcep7ft.js.map +43 -0
- package/dist/chunk-ravbb06q.js +89 -0
- package/dist/chunk-ravbb06q.js.map +11 -0
- package/dist/chunk-w1zyhj40.js +1031 -0
- package/dist/chunk-w1zyhj40.js.map +21 -0
- package/dist/chunk-wtqhq529.js +2532 -0
- package/dist/chunk-wtqhq529.js.map +43 -0
- package/dist/chunk-xnt2sdhf.js +836 -0
- package/dist/chunk-xnt2sdhf.js.map +17 -0
- package/dist/cli/args.d.ts +24 -0
- package/dist/cli/bin.d.ts +2 -0
- package/dist/cli/bin.js +307 -0
- package/dist/cli/bin.js.map +14 -0
- package/dist/cli/load.d.ts +2 -0
- package/dist/cli/manifest.d.ts +49 -0
- package/dist/cli/render-cmds.d.ts +12 -0
- package/dist/cli/run.d.ts +1 -0
- package/dist/core/asset.d.ts +10 -0
- package/dist/core/definition.d.ts +148 -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/glb.d.ts +38 -0
- package/dist/export/index.d.ts +4 -0
- package/dist/export/index.js +26 -0
- package/dist/export/index.js.map +9 -0
- package/dist/export/lights.d.ts +8 -0
- package/dist/export/materials.d.ts +23 -0
- package/dist/export/merge-pivots.d.ts +6 -0
- package/dist/export/metadata.d.ts +19 -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 +411 -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 +9 -0
- package/dist/kinds/head-beard.d.ts +6 -0
- package/dist/kinds/head-bust.d.ts +4 -0
- package/dist/kinds/head-extras.d.ts +15 -0
- package/dist/kinds/head-face.d.ts +6 -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 +27 -0
- package/dist/kinds/head-params.d.ts +108 -0
- package/dist/kinds/head-presets.d.ts +19 -0
- package/dist/kinds/head-rim.d.ts +10 -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/generator.d.ts +4 -0
- package/dist/material/index.d.ts +4 -0
- package/dist/material/index.js +38 -0
- package/dist/material/index.js.map +9 -0
- package/dist/material/paint/bond.d.ts +3 -0
- package/dist/material/paint/canvas.d.ts +32 -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 +2 -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/masks.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 +35 -0
- package/dist/material/paint/validate.d.ts +3 -0
- package/dist/material/palette.d.ts +12 -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/trim-sheet.d.ts +22 -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 +20 -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 +52 -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 +80 -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/landscape.md +49 -0
- package/docs/plans/roadmap.md +127 -0
- package/examples/build-with-bun.ts +41 -0
- package/llms.txt +47 -0
- package/package.json +99 -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 +46 -0
- package/recipes/boxcar/boxcar.ts +916 -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 +62 -0
- package/recipes/goblin_head/goblin_head.ts +36 -0
- package/recipes/human_head/README.md +64 -0
- package/recipes/human_head/human_head.ts +37 -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 +183 -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 +66 -0
- package/recipes/orc_head/orc_head.ts +39 -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 +112 -0
- package/src/cli/bin.ts +7 -0
- package/src/cli/load.ts +17 -0
- package/src/cli/manifest.ts +89 -0
- package/src/cli/render-cmds.ts +66 -0
- package/src/cli/run.ts +168 -0
- package/src/core/asset.ts +128 -0
- package/src/core/definition.ts +149 -0
- package/src/core/errors.ts +45 -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/glb.ts +200 -0
- package/src/export/index.ts +18 -0
- package/src/export/lights.ts +49 -0
- package/src/export/materials.ts +126 -0
- package/src/export/merge-pivots.ts +146 -0
- package/src/export/metadata.ts +59 -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 +151 -0
- package/src/kinds/head-beard.ts +133 -0
- package/src/kinds/head-bust.ts +221 -0
- package/src/kinds/head-extras.ts +150 -0
- package/src/kinds/head-face.ts +222 -0
- package/src/kinds/head-geom.ts +31 -0
- package/src/kinds/head-hair.ts +152 -0
- package/src/kinds/head-layout.ts +63 -0
- package/src/kinds/head-mesh.ts +157 -0
- package/src/kinds/head-params.ts +153 -0
- package/src/kinds/head-presets.ts +152 -0
- package/src/kinds/head-rim.ts +128 -0
- package/src/kinds/head.ts +171 -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/generator.ts +56 -0
- package/src/material/index.ts +29 -0
- package/src/material/paint/bond.ts +60 -0
- package/src/material/paint/canvas.ts +91 -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 +100 -0
- package/src/material/paint/default-sheet.ts +61 -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/masks.ts +44 -0
- package/src/material/paint/noise.ts +52 -0
- package/src/material/paint/normal.ts +52 -0
- package/src/material/paint/orm.ts +95 -0
- package/src/material/paint/planks.ts +56 -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 +42 -0
- package/src/material/paint/surface.ts +60 -0
- package/src/material/paint/types.ts +51 -0
- package/src/material/paint/validate.ts +58 -0
- package/src/material/palette.ts +43 -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/trim-sheet.ts +100 -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 +95 -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 +199 -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,72 @@
|
|
|
1
|
+
// Riveted iron plates (kind 'plates'): lapped plates (each overlaps its left neighbour by a
|
|
2
|
+
// step), domed rivet rows along every seam, hammered surface, rust streaks running down from
|
|
3
|
+
// rivets, chipped paint showing bare metal on edges.
|
|
4
|
+
// params: width 1.0 · height 0.5 · lap 0.003 · rivet 0.014 (radius) · pitch 0.09 · inset 0.05
|
|
5
|
+
// rust 0.4 · chips 0.3
|
|
6
|
+
|
|
7
|
+
import { type Canvas, noiseAt, param, smooth } from './canvas.ts';
|
|
8
|
+
import { Courses } from './courses.ts';
|
|
9
|
+
import { rustStreaks } from './masks.ts';
|
|
10
|
+
import { hash2 } from './noise.ts';
|
|
11
|
+
import type { StripRecipe } from './types.ts';
|
|
12
|
+
|
|
13
|
+
/** Distance (m) from `q` to the nearest multiple of `pitch`. */
|
|
14
|
+
function along(q: number, pitch: number): number {
|
|
15
|
+
const t = q / pitch;
|
|
16
|
+
return Math.abs(t - Math.round(t)) * pitch;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function paintPlates(c: Canvas, r: StripRecipe, seed: number): void {
|
|
20
|
+
const p = r.params;
|
|
21
|
+
const lap = param(p, 'lap', 0.003);
|
|
22
|
+
const rivet = param(p, 'rivet', 0.014);
|
|
23
|
+
const pitch = param(p, 'pitch', 0.09);
|
|
24
|
+
const inset = param(p, 'inset', 0.05);
|
|
25
|
+
const chips = param(p, 'chips', 0.3);
|
|
26
|
+
const metal = r.metalness ?? 0;
|
|
27
|
+
const plates = new Courses(c, {
|
|
28
|
+
course: param(p, 'height', 0.5),
|
|
29
|
+
length: param(p, 'width', 1),
|
|
30
|
+
irregular: 0,
|
|
31
|
+
stagger: 0.5,
|
|
32
|
+
seed,
|
|
33
|
+
});
|
|
34
|
+
const pu = c.metresU / c.w;
|
|
35
|
+
const pv = c.metresV / c.h;
|
|
36
|
+
for (let y = 0; y < c.h; y++) {
|
|
37
|
+
const ym = (y + 0.5) * pv;
|
|
38
|
+
const k = plates.at(ym);
|
|
39
|
+
const dy = Math.min(ym - plates.y0, plates.y1 - ym);
|
|
40
|
+
for (let x = 0; x < c.w; x++) {
|
|
41
|
+
const i = y * c.w + x;
|
|
42
|
+
const xm = (x + 0.5) * pu;
|
|
43
|
+
const ux = plates.ux[x] ?? 0;
|
|
44
|
+
const dx = plates.dx[x] ?? 0;
|
|
45
|
+
const id = plates.id[x] ?? 0;
|
|
46
|
+
const hammer = noiseAt(c, xm, ym, 0.05, 0.05, seed + 3, 3);
|
|
47
|
+
const fine = noiseAt(c, xm, ym, 0.004, 0.004, seed + 9, 2);
|
|
48
|
+
// Lap: the plate sits `lap` proud of the one to its left; a tiny groove on every seam.
|
|
49
|
+
const seam = Math.min(dx, dy);
|
|
50
|
+
let h = lap * smooth(0, 0.004, ux) + 0.0004 * hammer - 0.001 * (1 - smooth(0, 0.0015, seam));
|
|
51
|
+
// Rivet rows: `inset` from each vertical seam and from each course edge, every `pitch`.
|
|
52
|
+
const ry = Math.abs(dy - inset);
|
|
53
|
+
const rx = Math.abs(dx - inset);
|
|
54
|
+
const d1 = Math.sqrt(rx * rx + along(ym - plates.y0, pitch) ** 2);
|
|
55
|
+
const d2 = Math.sqrt(ry * ry + along(xm, pitch) ** 2);
|
|
56
|
+
const d = Math.min(dx > inset * 2 ? 1 : d1, dy > inset * 2 ? 1 : d2);
|
|
57
|
+
const dome = d < rivet ? Math.sqrt(1 - (d / rivet) ** 2) : 0;
|
|
58
|
+
h += dome * rivet * 0.5;
|
|
59
|
+
const chip = smooth(
|
|
60
|
+
0.35,
|
|
61
|
+
0.45,
|
|
62
|
+
noiseAt(c, xm, ym, 0.03, 0.03, seed + 21, 4) - (seam * 6 + 0.2) + chips * 0.3,
|
|
63
|
+
);
|
|
64
|
+
c.height[i] = h - chip * 0.0003;
|
|
65
|
+
c.tone[i] =
|
|
66
|
+
(0.8 + 0.06 * hash2(k, id, seed)) * (1 + 0.06 * hammer + 0.03 * fine) * (1 + 0.15 * dome);
|
|
67
|
+
c.rough[i] = r.roughness + 0.08 * fine - 0.25 * chip;
|
|
68
|
+
c.metal[i] = metal + (1 - metal) * chip;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
rustStreaks(c, param(p, 'rust', 0.4), seed + 77);
|
|
72
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// Tiny SDF-style helpers for cell motifs: distances in metres, no allocation.
|
|
2
|
+
|
|
3
|
+
/** Distance from (px, py) to the segment (ax, ay)–(bx, by). */
|
|
4
|
+
export function segment(px: number, py: number, ax: number, ay: number, bx: number, by: number) {
|
|
5
|
+
const dx = bx - ax;
|
|
6
|
+
const dy = by - ay;
|
|
7
|
+
const t = Math.max(0, Math.min(1, ((px - ax) * dx + (py - ay) * dy) / (dx * dx + dy * dy)));
|
|
8
|
+
return Math.hypot(px - ax - t * dx, py - ay - t * dy);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** Signed distance to an axis-aligned rectangle (negative inside). */
|
|
12
|
+
export function rect(px: number, py: number, x0: number, y0: number, x1: number, y1: number) {
|
|
13
|
+
const qx = Math.max(x0 - px, px - x1);
|
|
14
|
+
const qy = Math.max(y0 - py, py - y1);
|
|
15
|
+
return qx > 0 || qy > 0 ? Math.hypot(Math.max(qx, 0), Math.max(qy, 0)) : Math.max(qx, qy);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Dome profile in [0, 1] for a round boss of radius `r` at distance `d`. */
|
|
19
|
+
export const dome = (d: number, r: number): number => (d < r ? Math.sqrt(1 - (d / r) ** 2) : 0);
|
|
20
|
+
|
|
21
|
+
/** Distance to the nearest of `n` points evenly spaced on a circle of radius `ring`. */
|
|
22
|
+
export function boltRing(px: number, py: number, ring: number, n: number, phase = 0): number {
|
|
23
|
+
const a = Math.atan2(py, px) - phase;
|
|
24
|
+
const step = (Math.PI * 2) / n;
|
|
25
|
+
const k = Math.round(a / step);
|
|
26
|
+
const ba = k * step + phase;
|
|
27
|
+
return Math.hypot(px - ring * Math.cos(ba), py - ring * Math.sin(ba));
|
|
28
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// Roof slate (kind 'slate'): overlapping courses — each visible slate rises from where it slides
|
|
2
|
+
// under the course above to a thick, chipped lower edge that casts the shadow line — staggered
|
|
3
|
+
// vertical gaps, riven (laminar) face, per-slate tone and tilt.
|
|
4
|
+
// params: course 0.11 (exposure) · length 0.25 · gap 0.003 · thick 0.007 · variation 0.3
|
|
5
|
+
|
|
6
|
+
import { type Canvas, noiseAt, param, smooth } from './canvas.ts';
|
|
7
|
+
import { Courses } from './courses.ts';
|
|
8
|
+
import { hash2 } from './noise.ts';
|
|
9
|
+
import type { StripRecipe } from './types.ts';
|
|
10
|
+
|
|
11
|
+
export function paintSlate(c: Canvas, r: StripRecipe, seed: number): void {
|
|
12
|
+
const p = r.params;
|
|
13
|
+
const course = param(p, 'course', 0.11);
|
|
14
|
+
const gap = param(p, 'gap', 0.003);
|
|
15
|
+
const thick = param(p, 'thick', 0.007);
|
|
16
|
+
const variation = param(p, 'variation', 0.3);
|
|
17
|
+
const rows = new Courses(c, {
|
|
18
|
+
course,
|
|
19
|
+
length: param(p, 'length', 0.25),
|
|
20
|
+
irregular: 0.15,
|
|
21
|
+
stagger: 0.5,
|
|
22
|
+
seed,
|
|
23
|
+
});
|
|
24
|
+
const pu = c.metresU / c.w;
|
|
25
|
+
const pv = c.metresV / c.h;
|
|
26
|
+
for (let y = 0; y < c.h; y++) {
|
|
27
|
+
const ym = (y + 0.5) * pv;
|
|
28
|
+
const k = rows.at(ym);
|
|
29
|
+
const span = rows.y1 - rows.y0;
|
|
30
|
+
for (let x = 0; x < c.w; x++) {
|
|
31
|
+
const i = y * c.w + x;
|
|
32
|
+
const xm = (x + 0.5) * pu;
|
|
33
|
+
const id = rows.id[x] ?? 0;
|
|
34
|
+
const h = hash2(k, id, seed);
|
|
35
|
+
const chip = (noiseAt(c, xm, ym, 0.015, 0.01, seed + 5, 3) + 0.3) * span * 0.08;
|
|
36
|
+
const uy = Math.max(0, ym - rows.y0 - Math.max(0, chip));
|
|
37
|
+
const t = uy / span;
|
|
38
|
+
const lam = noiseAt(c, xm, ym, 0.12, 0.004, seed + 9, 3);
|
|
39
|
+
const fine = noiseAt(c, xm, ym, 0.005, 0.005, seed + 13, 2);
|
|
40
|
+
const sideGap = smooth(gap * 0.5, gap * 1.2, rows.dx[x] ?? 0);
|
|
41
|
+
const bevel = smooth(0, 0.004, uy);
|
|
42
|
+
const tilt = (hash2(id, k, seed + 1) - 0.5) * 0.002 * ((rows.ux[x] ?? 0) / 0.25);
|
|
43
|
+
c.height[i] =
|
|
44
|
+
(thick * (1 - t) * bevel + 0.0005 * lam + tilt) * sideGap - thick * (1 - sideGap);
|
|
45
|
+
c.tone[i] =
|
|
46
|
+
(0.62 - variation * 0.5 + variation * h) *
|
|
47
|
+
(1 + 0.08 * lam + 0.04 * fine) *
|
|
48
|
+
(0.55 + 0.45 * sideGap);
|
|
49
|
+
c.rough[i] = r.roughness + 0.08 * lam + 0.05 * fine;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// Stamp a supplied PNG into a cell canvas: bilinear resample to the cell, flipped so the image's
|
|
2
|
+
// top row lands at world-up (high V, where box/planar put a face's top). Flat relief, the
|
|
3
|
+
// recipe-free roughness default; the image's own colour is kept (vertex colour still multiplies).
|
|
4
|
+
|
|
5
|
+
import type { Canvas } from './canvas.ts';
|
|
6
|
+
import { decodePng } from './decode-png.ts';
|
|
7
|
+
|
|
8
|
+
export const STAMP_ROUGHNESS = 0.6;
|
|
9
|
+
|
|
10
|
+
export function stampImage(c: Canvas, png: Uint8Array): void {
|
|
11
|
+
const img = decodePng(png);
|
|
12
|
+
const out = new Uint8Array(c.w * c.h * 4);
|
|
13
|
+
for (let y = 0; y < c.h; y++) {
|
|
14
|
+
const sy = Math.min(
|
|
15
|
+
img.height - 1,
|
|
16
|
+
Math.max(0, ((c.h - 1 - y + 0.5) / c.h) * img.height - 0.5),
|
|
17
|
+
);
|
|
18
|
+
const y0 = Math.floor(sy);
|
|
19
|
+
const y1 = Math.min(img.height - 1, y0 + 1);
|
|
20
|
+
const fy = sy - y0;
|
|
21
|
+
for (let x = 0; x < c.w; x++) {
|
|
22
|
+
const sx = Math.min(img.width - 1, Math.max(0, ((x + 0.5) / c.w) * img.width - 0.5));
|
|
23
|
+
const x0 = Math.floor(sx);
|
|
24
|
+
const x1 = Math.min(img.width - 1, x0 + 1);
|
|
25
|
+
const fx = sx - x0;
|
|
26
|
+
for (let k = 0; k < 4; k++) {
|
|
27
|
+
const a = img.data[(y0 * img.width + x0) * 4 + k] ?? 0;
|
|
28
|
+
const b = img.data[(y0 * img.width + x1) * 4 + k] ?? 0;
|
|
29
|
+
const d = img.data[(y1 * img.width + x0) * 4 + k] ?? 0;
|
|
30
|
+
const e = img.data[(y1 * img.width + x1) * 4 + k] ?? 0;
|
|
31
|
+
const top = a + (b - a) * fx;
|
|
32
|
+
out[(y * c.w + x) * 4 + k] = Math.round(top + (d + (e - d) * fx - top) * fy);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
c.rgba = out;
|
|
37
|
+
c.height.fill(0);
|
|
38
|
+
c.rough.fill(STAMP_ROUGHNESS);
|
|
39
|
+
c.metal.fill(0);
|
|
40
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// Dispatch a StripRecipe to its painter, then the shared grime pass (`params.grime`, 0..1).
|
|
2
|
+
|
|
3
|
+
import { paintBond } from './bond.ts';
|
|
4
|
+
import type { Canvas } from './canvas.ts';
|
|
5
|
+
import { param } from './canvas.ts';
|
|
6
|
+
import { paintConcrete } from './concrete.ts';
|
|
7
|
+
import { grime } from './masks.ts';
|
|
8
|
+
import { paintPlanks } from './planks.ts';
|
|
9
|
+
import { paintPlates } from './plates.ts';
|
|
10
|
+
import { paintSlate } from './slate.ts';
|
|
11
|
+
import { paintFlat, paintGlass, paintNoise } from './surface.ts';
|
|
12
|
+
import type { StripRecipe } from './types.ts';
|
|
13
|
+
|
|
14
|
+
export function paintStrip(c: Canvas, r: StripRecipe, seed: number): void {
|
|
15
|
+
switch (r.kind) {
|
|
16
|
+
case 'bond':
|
|
17
|
+
paintBond(c, r, seed);
|
|
18
|
+
break;
|
|
19
|
+
case 'planks':
|
|
20
|
+
paintPlanks(c, r, seed);
|
|
21
|
+
break;
|
|
22
|
+
case 'plates':
|
|
23
|
+
paintPlates(c, r, seed);
|
|
24
|
+
break;
|
|
25
|
+
case 'slate':
|
|
26
|
+
paintSlate(c, r, seed);
|
|
27
|
+
break;
|
|
28
|
+
case 'concrete':
|
|
29
|
+
paintConcrete(c, r, seed);
|
|
30
|
+
break;
|
|
31
|
+
case 'glass':
|
|
32
|
+
paintGlass(c, r, seed);
|
|
33
|
+
return;
|
|
34
|
+
case 'noise':
|
|
35
|
+
paintNoise(c, r, seed);
|
|
36
|
+
break;
|
|
37
|
+
case 'flat':
|
|
38
|
+
paintFlat(c, r);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
grime(c, param(r.params, 'grime', 0), seed + 101);
|
|
42
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// The simple kinds: 'noise' (a quiet painted/plain surface: soft tone and roughness mottling,
|
|
2
|
+
// faint relief), 'flat' (constant) and 'glass' (near-white, very smooth, faint ripples, smudges,
|
|
3
|
+
// grime toward the bottom edge).
|
|
4
|
+
// params (noise): tone 0.93 · mottle 0.03 · relief 0.0002 · size 0.3
|
|
5
|
+
// params (glass): ripple 0.0002 · smudge 0.08 · grime 0.4
|
|
6
|
+
|
|
7
|
+
import { type Canvas, noiseAt, param } from './canvas.ts';
|
|
8
|
+
import { grime } from './masks.ts';
|
|
9
|
+
import type { StripRecipe } from './types.ts';
|
|
10
|
+
|
|
11
|
+
export function paintFlat(c: Canvas, r: StripRecipe): void {
|
|
12
|
+
c.tone.fill(param(r.params, 'tone', 1));
|
|
13
|
+
c.rough.fill(r.roughness);
|
|
14
|
+
c.metal.fill(r.metalness ?? 0);
|
|
15
|
+
c.height.fill(0);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function paintNoise(c: Canvas, r: StripRecipe, seed: number): void {
|
|
19
|
+
const tone = param(r.params, 'tone', 0.93);
|
|
20
|
+
const mottle = param(r.params, 'mottle', 0.03);
|
|
21
|
+
const relief = param(r.params, 'relief', 0.0002);
|
|
22
|
+
const size = param(r.params, 'size', 0.3);
|
|
23
|
+
const pu = c.metresU / c.w;
|
|
24
|
+
const pv = c.metresV / c.h;
|
|
25
|
+
const metal = r.metalness ?? 0;
|
|
26
|
+
for (let y = 0; y < c.h; y++) {
|
|
27
|
+
const ym = (y + 0.5) * pv;
|
|
28
|
+
for (let x = 0; x < c.w; x++) {
|
|
29
|
+
const xm = (x + 0.5) * pu;
|
|
30
|
+
const big = noiseAt(c, xm, ym, size, size, seed, 4);
|
|
31
|
+
const fine = noiseAt(c, xm, ym, 0.01, 0.01, seed + 1, 2);
|
|
32
|
+
const i = y * c.w + x;
|
|
33
|
+
c.tone[i] = tone + mottle * big;
|
|
34
|
+
c.rough[i] = r.roughness + 0.06 * big + 0.03 * fine;
|
|
35
|
+
c.metal[i] = metal;
|
|
36
|
+
c.height[i] = relief * (big + 0.3 * fine);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function paintGlass(c: Canvas, r: StripRecipe, seed: number): void {
|
|
42
|
+
const ripple = param(r.params, 'ripple', 0.0002);
|
|
43
|
+
const smudge = param(r.params, 'smudge', 0.08);
|
|
44
|
+
const pu = c.metresU / c.w;
|
|
45
|
+
const pv = c.metresV / c.h;
|
|
46
|
+
for (let y = 0; y < c.h; y++) {
|
|
47
|
+
const ym = (y + 0.5) * pv;
|
|
48
|
+
for (let x = 0; x < c.w; x++) {
|
|
49
|
+
const xm = (x + 0.5) * pu;
|
|
50
|
+
const wave = noiseAt(c, xm, ym, 0.15, 0.4, seed, 3);
|
|
51
|
+
const smear = noiseAt(c, xm, ym, 0.08, 0.08, seed + 1, 4);
|
|
52
|
+
const i = y * c.w + x;
|
|
53
|
+
c.tone[i] = 0.92 + 0.02 * wave;
|
|
54
|
+
c.rough[i] = r.roughness + smudge * Math.max(0, smear);
|
|
55
|
+
c.metal[i] = 0;
|
|
56
|
+
c.height[i] = ripple * wave;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
grime(c, param(r.params, 'grime', 0.4), seed + 9);
|
|
60
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// The generator's public data contract (re-exported by generator.ts): what a sheet recipe says
|
|
2
|
+
// and what the painter returns.
|
|
3
|
+
|
|
4
|
+
import type { CellId, StripId } from '../trim-sheet.ts';
|
|
5
|
+
|
|
6
|
+
export interface PaintedSheet {
|
|
7
|
+
readonly size: number;
|
|
8
|
+
/** RGBA8, sRGB. Near-greyscale: hue comes from vertex colour multiplied over it. */
|
|
9
|
+
readonly albedo: Uint8Array;
|
|
10
|
+
/** RGBA8, tangent-space (glTF / OpenGL, +Y toward decreasing V). */
|
|
11
|
+
readonly normal: Uint8Array;
|
|
12
|
+
/** R = occlusion, G = roughness, B = metalness. */
|
|
13
|
+
readonly orm: Uint8Array;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export type StripKind =
|
|
17
|
+
| 'bond'
|
|
18
|
+
| 'planks'
|
|
19
|
+
| 'plates'
|
|
20
|
+
| 'noise'
|
|
21
|
+
| 'flat'
|
|
22
|
+
| 'slate'
|
|
23
|
+
| 'concrete'
|
|
24
|
+
| 'glass';
|
|
25
|
+
|
|
26
|
+
export interface StripRecipe {
|
|
27
|
+
readonly kind: StripKind;
|
|
28
|
+
/**
|
|
29
|
+
* World metres the strip's V span represents (its U span is always `stripRect().metres`).
|
|
30
|
+
* Pick the typical face height the strip is stretched over; texels are anisotropic if needed.
|
|
31
|
+
*/
|
|
32
|
+
readonly scaleMetres: number;
|
|
33
|
+
readonly roughness: number;
|
|
34
|
+
readonly metalness?: number;
|
|
35
|
+
/** Brick/plank courses, rivet pitch, etc. — read per kind (see each painter's header). */
|
|
36
|
+
readonly params?: Readonly<Record<string, number>>;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** A cell stamped from PNG bytes (8-bit grey/RGB/RGBA, non-interlaced), e.g. a kubeez.com decal. */
|
|
40
|
+
export interface CellImage {
|
|
41
|
+
readonly image: Uint8Array;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface SheetRecipe {
|
|
45
|
+
readonly seed: number;
|
|
46
|
+
readonly size?: 512 | 1024 | 2048;
|
|
47
|
+
/** Missing strips fall back to `DEFAULT_SHEET`'s recipe for that strip. */
|
|
48
|
+
readonly strips: Readonly<Partial<Record<StripId, StripRecipe>>>;
|
|
49
|
+
/** Cells painted from code, or stamped from a supplied image (e.g. a kubeez.com decal). */
|
|
50
|
+
readonly cells?: Readonly<Partial<Record<CellId, StripRecipe | CellImage>>>;
|
|
51
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// Recipe validation: every bad input throws T3D_RECIPE_INVALID with the fix, before any painting.
|
|
2
|
+
|
|
3
|
+
import { Ts23dError } from '../../core/index.ts';
|
|
4
|
+
import { CELLS, STRIPS } from '../trim-sheet.ts';
|
|
5
|
+
import type { CellImage, SheetRecipe, StripKind, StripRecipe } from './types.ts';
|
|
6
|
+
|
|
7
|
+
const KINDS: readonly StripKind[] = [
|
|
8
|
+
'bond',
|
|
9
|
+
'planks',
|
|
10
|
+
'plates',
|
|
11
|
+
'noise',
|
|
12
|
+
'flat',
|
|
13
|
+
'slate',
|
|
14
|
+
'concrete',
|
|
15
|
+
'glass',
|
|
16
|
+
];
|
|
17
|
+
const SIZES = [512, 1024, 2048];
|
|
18
|
+
|
|
19
|
+
function fail(cause: string, fix: string): never {
|
|
20
|
+
throw new Ts23dError('T3D_RECIPE_INVALID', { cause: `sheet recipe: ${cause}`, fix });
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const unit = (v: number): boolean => Number.isFinite(v) && v >= 0 && v <= 1;
|
|
24
|
+
|
|
25
|
+
export function validateStrip(where: string, r: StripRecipe): void {
|
|
26
|
+
if (!KINDS.includes(r.kind))
|
|
27
|
+
fail(`${where}.kind '${r.kind}' is unknown`, `use one of ${KINDS.join(' | ')}`);
|
|
28
|
+
if (!(Number.isFinite(r.scaleMetres) && r.scaleMetres > 0))
|
|
29
|
+
fail(
|
|
30
|
+
`${where}.scaleMetres is ${r.scaleMetres}`,
|
|
31
|
+
'set the metres the strip V span represents, > 0 (e.g. 0.6 for brick)',
|
|
32
|
+
);
|
|
33
|
+
if (!unit(r.roughness)) fail(`${where}.roughness is ${r.roughness}`, 'roughness is 0..1');
|
|
34
|
+
if (r.metalness !== undefined && !unit(r.metalness))
|
|
35
|
+
fail(`${where}.metalness is ${r.metalness}`, 'metalness is 0..1');
|
|
36
|
+
for (const [k, v] of Object.entries(r.params ?? {}))
|
|
37
|
+
if (!Number.isFinite(v)) fail(`${where}.params.${k} is ${v}`, 'params are finite numbers');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function validateSheet(recipe: SheetRecipe): void {
|
|
41
|
+
if (!Number.isInteger(recipe.seed)) fail(`seed ${recipe.seed}`, 'seed is an integer');
|
|
42
|
+
if (recipe.size !== undefined && !SIZES.includes(recipe.size))
|
|
43
|
+
fail(`size ${recipe.size}`, 'size is 512, 1024 or 2048');
|
|
44
|
+
for (const [id, r] of Object.entries(recipe.strips)) {
|
|
45
|
+
if (!(STRIPS as readonly string[]).includes(id))
|
|
46
|
+
fail(`strip '${id}' is not in the layout`, `strips are ${STRIPS.join(', ')}`);
|
|
47
|
+
if (r) validateStrip(`strips.${id}`, r);
|
|
48
|
+
}
|
|
49
|
+
for (const [id, e] of Object.entries(recipe.cells ?? {})) {
|
|
50
|
+
if (!(CELLS as readonly string[]).includes(id))
|
|
51
|
+
fail(`cell '${id}' is not in the layout`, `cells are ${CELLS.join(', ')}`);
|
|
52
|
+
if (!e) continue;
|
|
53
|
+
if ('image' in e) {
|
|
54
|
+
if (!((e as CellImage).image instanceof Uint8Array))
|
|
55
|
+
fail(`cells.${id}.image is not bytes`, 'pass PNG file bytes as a Uint8Array');
|
|
56
|
+
} else validateStrip(`cells.${id}`, e);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// Colour lives in vertex colour (linear RGB), multiplied over the greyscale trim sheet: a palette
|
|
2
|
+
// swap is a recipe edit, never a new texture.
|
|
3
|
+
|
|
4
|
+
import type { Vec3 } from '../core/index.ts';
|
|
5
|
+
|
|
6
|
+
export type Palette = Readonly<Record<string, Vec3>>;
|
|
7
|
+
|
|
8
|
+
/** sRGB hex → linear RGB, the space glTF vertex colours are in. */
|
|
9
|
+
export function hex(value: string): Vec3 {
|
|
10
|
+
const n = Number.parseInt(value.replace('#', ''), 16);
|
|
11
|
+
const lin = (c: number): number => {
|
|
12
|
+
const s = c / 255;
|
|
13
|
+
return s <= 0.04045 ? s / 12.92 : ((s + 0.055) / 1.055) ** 2.4;
|
|
14
|
+
};
|
|
15
|
+
return [lin((n >> 16) & 255), lin((n >> 8) & 255), lin(n & 255)];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function definePalette(entries: Readonly<Record<string, string>>): Palette {
|
|
19
|
+
return Object.fromEntries(Object.entries(entries).map(([k, v]) => [k, hex(v)]));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Era/region palettes ship as data; recipes extend them with object spread. */
|
|
23
|
+
export const palettes = {
|
|
24
|
+
victorianRail: definePalette({
|
|
25
|
+
paint: '#2f4a3a',
|
|
26
|
+
trim: '#b08d57',
|
|
27
|
+
iron: '#2b2b2e',
|
|
28
|
+
brass: '#c9a24a',
|
|
29
|
+
red: '#7a2a22',
|
|
30
|
+
}),
|
|
31
|
+
artDeco: definePalette({
|
|
32
|
+
limestone: '#d8cfbd',
|
|
33
|
+
steel: '#9aa3ad',
|
|
34
|
+
glass: '#4a6a80',
|
|
35
|
+
bronze: '#8a6a3c',
|
|
36
|
+
}),
|
|
37
|
+
modernGlass: definePalette({
|
|
38
|
+
glass: '#6f8fa8',
|
|
39
|
+
aluminium: '#c4c9cf',
|
|
40
|
+
concrete: '#a7a39b',
|
|
41
|
+
}),
|
|
42
|
+
harbour: definePalette({ hull: '#1f2f45', deck: '#8a6f4e', white: '#e8e6df', red: '#a1302a' }),
|
|
43
|
+
} as const;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
// The Godot 4 emitter behind `godotShaderSource`: prints a spatial .gdshader with the same uniforms
|
|
2
|
+
// as the three.js module. Values come pre-validated from shader.ts.
|
|
3
|
+
|
|
4
|
+
import type { ShaderParams } from './shader.ts';
|
|
5
|
+
import type { UvRect } from './trim-sheet.ts';
|
|
6
|
+
|
|
7
|
+
const f = (n: number): string => (Number.isInteger(n) ? `${n}.0` : `${+n.toFixed(6)}`);
|
|
8
|
+
const rect = (r: UvRect): string =>
|
|
9
|
+
`in_rect(UV, vec4(${f(r.u0)}, ${f(r.v0)}, ${f(r.u1)}, ${f(r.v1)}))`;
|
|
10
|
+
|
|
11
|
+
export function godotSource(p: ShaderParams): string {
|
|
12
|
+
const modes = ['blend_mix', 'depth_draw_opaque', p.cutout ? 'cull_disabled' : 'cull_back'];
|
|
13
|
+
const weight = p.windAttribute
|
|
14
|
+
? 'CUSTOM0.x /* import _WIND into CUSTOM0 (glTF import: custom attribute mapping) */'
|
|
15
|
+
: 'smoothstep(0.0, wind_height, VERTEX.z)';
|
|
16
|
+
return `// Generated by @developerz.ai/ts23d godotShaderSource(). A starting point your project owns:
|
|
17
|
+
// assign it to a ShaderMaterial and set albedo_tex / normal_tex / orm_tex from the GLB's
|
|
18
|
+
// ts23d_* material (or the shared trim_sheet_{albedo,normal,orm}.png). Godot 4.x.
|
|
19
|
+
shader_type spatial;
|
|
20
|
+
render_mode ${modes.join(', ')};
|
|
21
|
+
|
|
22
|
+
uniform sampler2D albedo_tex : source_color, filter_linear_mipmap, repeat_enable;
|
|
23
|
+
uniform sampler2D normal_tex : hint_normal, filter_linear_mipmap, repeat_enable;
|
|
24
|
+
uniform sampler2D orm_tex : hint_default_white, filter_linear_mipmap, repeat_enable;
|
|
25
|
+
|
|
26
|
+
uniform float night : hint_range(0.0, 1.0) = 0.0;
|
|
27
|
+
uniform float wind_strength = ${f(p.windStrength)};
|
|
28
|
+
uniform float wind_height = ${f(p.windHeight)};
|
|
29
|
+
uniform float wind_hz = ${f(p.windHz)};
|
|
30
|
+
uniform vec3 glow_color : source_color = vec3(${f(p.glow[0])}, ${f(p.glow[1])}, ${f(p.glow[2])});
|
|
31
|
+
uniform float glow_ratio : hint_range(0.0, 1.0) = ${f(p.glowRatio)};
|
|
32
|
+
uniform float glow_intensity = ${f(p.glowIntensity)};
|
|
33
|
+
uniform float wear : hint_range(0.0, 1.0) = ${f(p.wear)};
|
|
34
|
+
uniform float grime_height = ${f(p.grimeHeight)};
|
|
35
|
+
|
|
36
|
+
varying vec3 world_pos;
|
|
37
|
+
|
|
38
|
+
float in_rect(vec2 uv, vec4 r) {
|
|
39
|
+
float u = fract(uv.x);
|
|
40
|
+
return step(r.x, u) * step(u, r.z) * step(r.y, uv.y) * step(uv.y, r.w);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
float hash13(vec3 p) {
|
|
44
|
+
p = fract(p * 0.1031);
|
|
45
|
+
p += dot(p, p.zyx + 31.32);
|
|
46
|
+
return fract((p.x + p.y) * p.z);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
void vertex() {
|
|
50
|
+
// Wind sway: mesh-local Z is the authored up axis; sway in local XY.
|
|
51
|
+
float weight = ${weight};
|
|
52
|
+
float phase = VERTEX.x * 0.7 + VERTEX.y * 0.45;
|
|
53
|
+
float sway = sin(TIME * wind_hz * 6.2832 + phase) * wind_strength * weight * 0.1;
|
|
54
|
+
VERTEX.xy += vec2(sway, sway * 0.6);
|
|
55
|
+
world_pos = (MODEL_MATRIX * vec4(VERTEX, 1.0)).xyz;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
void fragment() {
|
|
59
|
+
vec4 base = texture(albedo_tex, UV);
|
|
60
|
+
vec3 orm = texture(orm_tex, UV).rgb;
|
|
61
|
+
float ground = 1.0 - smoothstep(0.0, grime_height, world_pos.y);
|
|
62
|
+
float grime = clamp(wear * max((1.0 - orm.r) * 1.5, ground), 0.0, 1.0);
|
|
63
|
+
ALBEDO = base.rgb * COLOR.rgb * (1.0 - grime * 0.45);
|
|
64
|
+
AO = orm.r;
|
|
65
|
+
ROUGHNESS = clamp(orm.g + grime * 0.2, 0.0, 1.0);
|
|
66
|
+
METALLIC = orm.b * (1.0 - grime);
|
|
67
|
+
NORMAL_MAP = texture(normal_tex, UV).rgb;
|
|
68
|
+
${p.cutout ? '\tALPHA = base.a;\n\tALPHA_SCISSOR_THRESHOLD = 0.5;\n' : p.blend ? '\tALPHA = base.a * 0.6;\n' : ''}
|
|
69
|
+
// Window glow: dark panes in window cells, or the glass strip; 1.5 m world slots, glow_ratio lit.
|
|
70
|
+
float luma = dot(base.rgb, vec3(0.2126, 0.7152, 0.0722));
|
|
71
|
+
float panes = (${p.glowRects.map(rect).join(' + ')}) * step(luma, 0.3);
|
|
72
|
+
float mask = max(panes, ${rect(p.glassRect)});
|
|
73
|
+
float lit = step(hash13(floor(world_pos / 1.5)), glow_ratio);
|
|
74
|
+
EMISSION = glow_color * mask * lit * night * glow_intensity;
|
|
75
|
+
}
|
|
76
|
+
`;
|
|
77
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
// The three.js TSL emitter behind `threeMaterialSource`: prints a self-contained TypeScript module
|
|
2
|
+
// for three r18x (`three/webgpu` + `three/tsl`). Values come pre-validated from shader.ts.
|
|
3
|
+
|
|
4
|
+
import type { ShaderParams } from './shader.ts';
|
|
5
|
+
import type { UvRect } from './trim-sheet.ts';
|
|
6
|
+
|
|
7
|
+
const f = (n: number): string => (Number.isInteger(n) ? `${n}.0` : `${+n.toFixed(6)}`);
|
|
8
|
+
|
|
9
|
+
const rectNode = (r: UvRect): string => `inRect(${f(r.u0)}, ${f(r.v0)}, ${f(r.u1)}, ${f(r.v1)})`;
|
|
10
|
+
|
|
11
|
+
export function threeSource(p: ShaderParams): string {
|
|
12
|
+
const weight = p.windAttribute
|
|
13
|
+
? "attribute('_wind', 'float')"
|
|
14
|
+
: 'smoothstep(float(0), uWindHeight, positionLocal.z)';
|
|
15
|
+
const glowCells = p.glowRects.map(rectNode).join('.add(') + ')'.repeat(p.glowRects.length - 1);
|
|
16
|
+
return `// Generated by @developerz.ai/ts23d threeMaterialSource(). A starting point your project owns:
|
|
17
|
+
// edit freely. Needs three r18x (WebGPURenderer + TSL). ts23d itself has no three dependency.
|
|
18
|
+
//
|
|
19
|
+
// Usage: const gltf = await loader.loadAsync('tank.glb'); upgradeTrimMaterials(gltf.scene);
|
|
20
|
+
// trim.uniforms.night.value = 1; // at dusk
|
|
21
|
+
|
|
22
|
+
import { DoubleSide, type Material, type Mesh, MeshStandardMaterial, MeshStandardNodeMaterial, type Object3D, type Texture } from 'three/webgpu';
|
|
23
|
+
import { attribute, color, float, floor, fract, hash, max, normalMap, positionLocal, positionWorld, sin, smoothstep, step, texture, time, uniform, uv, vec3, vertexColor } from 'three/tsl';
|
|
24
|
+
|
|
25
|
+
export interface TrimMaps {
|
|
26
|
+
readonly albedo: Texture;
|
|
27
|
+
readonly normal: Texture | null;
|
|
28
|
+
readonly orm: Texture | null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Shared uniforms: one set drives every trim material, so a scene-wide night switch is one write. */
|
|
32
|
+
export const uniforms = {
|
|
33
|
+
night: uniform(0),
|
|
34
|
+
windStrength: uniform(${f(p.windStrength)}),
|
|
35
|
+
windHeight: uniform(${f(p.windHeight)}),
|
|
36
|
+
windHz: uniform(${f(p.windHz)}),
|
|
37
|
+
glowColor: uniform(color(${f(p.glow[0])}, ${f(p.glow[1])}, ${f(p.glow[2])})),
|
|
38
|
+
glowRatio: uniform(${f(p.glowRatio)}),
|
|
39
|
+
glowIntensity: uniform(${f(p.glowIntensity)}),
|
|
40
|
+
wear: uniform(${f(p.wear)}),
|
|
41
|
+
grimeHeight: uniform(${f(p.grimeHeight)}),
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
/** 1 inside a trim-sheet UV rect (U wraps: strips tile along U). */
|
|
45
|
+
function inRect(u0: number, v0: number, u1: number, v1: number) {
|
|
46
|
+
const t = uv();
|
|
47
|
+
const u = fract(t.x);
|
|
48
|
+
return step(u0, u).mul(step(u, u1)).mul(step(v0, t.y)).mul(step(t.y, v1));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function createTrimMaterial(maps: TrimMaps, cutout = ${p.cutout}): MeshStandardNodeMaterial {
|
|
52
|
+
const { night, windStrength: uWindStrength, windHeight: uWindHeight, windHz, glowColor, glowRatio, glowIntensity, wear, grimeHeight } = uniforms;
|
|
53
|
+
const m = new MeshStandardNodeMaterial();
|
|
54
|
+
const base = texture(maps.albedo, uv());
|
|
55
|
+
const orm = maps.orm ? texture(maps.orm, uv()) : vec3(1, 0.8, 0);
|
|
56
|
+
const tint = vertexColor();
|
|
57
|
+
|
|
58
|
+
// Wear / grime: cavities (low AO) and the first metres above the ground get darker and rougher.
|
|
59
|
+
const ground = float(1).sub(smoothstep(float(0), grimeHeight, positionWorld.y));
|
|
60
|
+
const grime = wear.mul(max(float(1).sub(orm.x).mul(1.5), ground)).clamp(0, 1);
|
|
61
|
+
m.colorNode = base.rgb.mul(tint.rgb).mul(float(1).sub(grime.mul(0.45)));
|
|
62
|
+
m.roughnessNode = orm.y.add(grime.mul(0.2)).clamp(0, 1);
|
|
63
|
+
m.metalnessNode = orm.z.mul(float(1).sub(grime));
|
|
64
|
+
m.aoNode = orm.x;
|
|
65
|
+
if (maps.normal) m.normalNode = normalMap(texture(maps.normal, uv()));
|
|
66
|
+
if (cutout) {
|
|
67
|
+
m.opacityNode = base.a;
|
|
68
|
+
m.alphaTest = 0.5;
|
|
69
|
+
m.side = DoubleSide;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Wind sway (authored Z-up, so sway in local XY), phase-shifted across the crown.
|
|
73
|
+
const windWeight = ${weight};
|
|
74
|
+
const phase = positionLocal.x.mul(0.7).add(positionLocal.y.mul(0.45));
|
|
75
|
+
const sway = sin(time.mul(windHz).mul(6.2832).add(phase)).mul(uWindStrength).mul(windWeight).mul(0.1);
|
|
76
|
+
m.positionNode = positionLocal.add(vec3(sway, sway.mul(0.6), 0));
|
|
77
|
+
|
|
78
|
+
// Window glow: dark pane texels in window cells, or the glass strip; a world-space hash lights
|
|
79
|
+
// \`glowRatio\` of the 1.5 m window slots; scaled by \`night\`.
|
|
80
|
+
const luma = base.r.mul(0.2126).add(base.g.mul(0.7152)).add(base.b.mul(0.0722));
|
|
81
|
+
const panes = ${glowCells}.mul(step(luma, 0.3));
|
|
82
|
+
const mask = max(panes, ${rectNode(p.glassRect)});
|
|
83
|
+
const slot = floor(positionWorld.div(1.5));
|
|
84
|
+
const lit = step(hash(slot.x.mul(73).add(slot.y.mul(157)).add(slot.z.mul(311))), glowRatio);
|
|
85
|
+
m.emissiveNode = glowColor.mul(mask).mul(lit).mul(night).mul(glowIntensity);
|
|
86
|
+
return m;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Replace every ts23d_* material under \`root\` with the node material, reusing the GLB's maps. */
|
|
90
|
+
export function upgradeTrimMaterials(root: Object3D): void {
|
|
91
|
+
const cache = new Map<Material, MeshStandardNodeMaterial>();
|
|
92
|
+
root.traverse((o) => {
|
|
93
|
+
const mesh = o as Mesh;
|
|
94
|
+
const old = mesh.isMesh ? mesh.material : null;
|
|
95
|
+
if (!(old instanceof MeshStandardMaterial) || !old.name.startsWith('ts23d') || !old.map) return;
|
|
96
|
+
const next = cache.get(old) ?? createTrimMaterial({ albedo: old.map, normal: old.normalMap, orm: old.roughnessMap }, old.alphaTest > 0);
|
|
97
|
+
next.name = old.name;
|
|
98
|
+
cache.set(old, next);
|
|
99
|
+
mesh.material = next;
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
`;
|
|
103
|
+
}
|