@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,1919 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Ts23dError2,
|
|
3
|
+
createRng2
|
|
4
|
+
} from "./chunk-4ddzsqtc.js";
|
|
5
|
+
|
|
6
|
+
// src/material/paint/noise.ts
|
|
7
|
+
function hash2(ix, iy, seed) {
|
|
8
|
+
let h = Math.imul(ix | 0, 668265261) ^ Math.imul(iy | 0, 374761393) ^ seed;
|
|
9
|
+
h = Math.imul(h ^ h >>> 15, 2246822507);
|
|
10
|
+
h = Math.imul(h ^ h >>> 13, 3266489909);
|
|
11
|
+
return ((h ^ h >>> 16) >>> 0) / 4294967296;
|
|
12
|
+
}
|
|
13
|
+
function valueNoise(x, y, period, seed) {
|
|
14
|
+
const xi = Math.floor(x);
|
|
15
|
+
const yi = Math.floor(y);
|
|
16
|
+
const fx = x - xi;
|
|
17
|
+
const fy = y - yi;
|
|
18
|
+
const sx = fx * fx * (3 - 2 * fx);
|
|
19
|
+
const sy = fy * fy * (3 - 2 * fy);
|
|
20
|
+
const x0 = (xi % period + period) % period;
|
|
21
|
+
const x1 = (x0 + 1) % period;
|
|
22
|
+
const a = hash2(x0, yi, seed);
|
|
23
|
+
const b = hash2(x1, yi, seed);
|
|
24
|
+
const c = hash2(x0, yi + 1, seed);
|
|
25
|
+
const d = hash2(x1, yi + 1, seed);
|
|
26
|
+
return a + (b - a) * sx + (c - a + (d - c - b + a) * sx) * sy;
|
|
27
|
+
}
|
|
28
|
+
function fbm(x, y, period, seed, octaves = 4, gain = 0.5) {
|
|
29
|
+
let sum = 0;
|
|
30
|
+
let amp = 1;
|
|
31
|
+
let norm = 0;
|
|
32
|
+
let f = 1;
|
|
33
|
+
for (let o = 0;o < octaves; o++) {
|
|
34
|
+
sum += amp * valueNoise(x * f, y * f, period * f, seed + o * 1013);
|
|
35
|
+
norm += amp;
|
|
36
|
+
amp *= gain;
|
|
37
|
+
f *= 2;
|
|
38
|
+
}
|
|
39
|
+
return sum / norm * 2 - 1;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// src/material/paint/canvas.ts
|
|
43
|
+
function createCanvas(w, h, metresU, metresV, wrapU) {
|
|
44
|
+
const n = w * h;
|
|
45
|
+
return {
|
|
46
|
+
w,
|
|
47
|
+
h,
|
|
48
|
+
metresU,
|
|
49
|
+
metresV,
|
|
50
|
+
wrapU,
|
|
51
|
+
height: new Float32Array(n),
|
|
52
|
+
tone: new Float32Array(n).fill(1),
|
|
53
|
+
rough: new Float32Array(n).fill(0.8),
|
|
54
|
+
metal: new Float32Array(n),
|
|
55
|
+
occl: new Float32Array(n).fill(1),
|
|
56
|
+
rgba: null
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
function periodFor(c, featureMetres) {
|
|
60
|
+
return c.wrapU ? Math.max(1, Math.round(c.metresU / featureMetres)) : 1 << 20;
|
|
61
|
+
}
|
|
62
|
+
function noiseAt(c, xm, ym, sizeU, sizeV, seed, octaves = 4) {
|
|
63
|
+
const finest = 1.5 * Math.max(c.metresU / c.w, c.metresV / c.h);
|
|
64
|
+
const k = Math.max(1, finest / Math.min(sizeU, sizeV));
|
|
65
|
+
const period = periodFor(c, sizeU * k);
|
|
66
|
+
const cell = c.wrapU ? c.metresU / period : sizeU * k;
|
|
67
|
+
return fbm(xm / cell, ym / (sizeV * k), period, seed, octaves);
|
|
68
|
+
}
|
|
69
|
+
var param = (params, key, fallback) => params?.[key] ?? fallback;
|
|
70
|
+
var clamp01 = (v) => Math.min(1, Math.max(0, v));
|
|
71
|
+
var smooth = (e0, e1, v) => {
|
|
72
|
+
const t = clamp01((v - e0) / (e1 - e0));
|
|
73
|
+
return t * t * (3 - 2 * t);
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
// src/material/trim-sheet.ts
|
|
77
|
+
var STRIP_H = 1 / 16;
|
|
78
|
+
var strips = [
|
|
79
|
+
"plain",
|
|
80
|
+
"brick",
|
|
81
|
+
"stone",
|
|
82
|
+
"slate",
|
|
83
|
+
"iron",
|
|
84
|
+
"wood",
|
|
85
|
+
"glass",
|
|
86
|
+
"concrete"
|
|
87
|
+
];
|
|
88
|
+
var decals = [
|
|
89
|
+
"window_sash",
|
|
90
|
+
"door_panel",
|
|
91
|
+
"clock",
|
|
92
|
+
"smokebox",
|
|
93
|
+
"sign",
|
|
94
|
+
"porthole"
|
|
95
|
+
];
|
|
96
|
+
var foliage = [
|
|
97
|
+
"foliage_broadleaf",
|
|
98
|
+
"foliage_maple",
|
|
99
|
+
"foliage_maple_autumn",
|
|
100
|
+
"foliage_birch",
|
|
101
|
+
"foliage_acacia",
|
|
102
|
+
"foliage_palm",
|
|
103
|
+
"foliage_spruce",
|
|
104
|
+
"foliage_spruce_snow",
|
|
105
|
+
"foliage_willow",
|
|
106
|
+
"foliage_reeds",
|
|
107
|
+
"foliage_needles"
|
|
108
|
+
];
|
|
109
|
+
var cells = [...decals, ...foliage];
|
|
110
|
+
var FOLIAGE_SLOT = 8;
|
|
111
|
+
var slotOf = (id) => {
|
|
112
|
+
const d = decals.indexOf(id);
|
|
113
|
+
return d >= 0 ? d : FOLIAGE_SLOT + foliage.indexOf(id);
|
|
114
|
+
};
|
|
115
|
+
var TRIM_SHEET_SIZE2 = 1024;
|
|
116
|
+
function stripRect2(id) {
|
|
117
|
+
const i = strips.indexOf(id);
|
|
118
|
+
return { u0: 0, v0: i * STRIP_H, u1: 1, v1: (i + 1) * STRIP_H, metres: 2 };
|
|
119
|
+
}
|
|
120
|
+
function cellRect2(id) {
|
|
121
|
+
const i = slotOf(id);
|
|
122
|
+
const size = 1 / 8;
|
|
123
|
+
const u0 = i % 8 * size;
|
|
124
|
+
const v0 = 0.5 + Math.floor(i / 8) * size;
|
|
125
|
+
return { u0, v0, u1: u0 + size, v1: v0 + size, metres: 1 };
|
|
126
|
+
}
|
|
127
|
+
var STRIPS2 = strips;
|
|
128
|
+
var CELLS2 = cells;
|
|
129
|
+
var DECAL_CELLS2 = decals;
|
|
130
|
+
var FOLIAGE_CELLS2 = foliage;
|
|
131
|
+
var isFoliage2 = (id) => id.startsWith("foliage_");
|
|
132
|
+
|
|
133
|
+
// src/material/paint/shapes.ts
|
|
134
|
+
function segment(px, py, ax, ay, bx, by) {
|
|
135
|
+
const dx = bx - ax;
|
|
136
|
+
const dy = by - ay;
|
|
137
|
+
const t = Math.max(0, Math.min(1, ((px - ax) * dx + (py - ay) * dy) / (dx * dx + dy * dy)));
|
|
138
|
+
return Math.hypot(px - ax - t * dx, py - ay - t * dy);
|
|
139
|
+
}
|
|
140
|
+
function rect(px, py, x0, y0, x1, y1) {
|
|
141
|
+
const qx = Math.max(x0 - px, px - x1);
|
|
142
|
+
const qy = Math.max(y0 - py, py - y1);
|
|
143
|
+
return qx > 0 || qy > 0 ? Math.hypot(Math.max(qx, 0), Math.max(qy, 0)) : Math.max(qx, qy);
|
|
144
|
+
}
|
|
145
|
+
var dome = (d, r) => d < r ? Math.sqrt(1 - (d / r) ** 2) : 0;
|
|
146
|
+
function boltRing(px, py, ring, n, phase = 0) {
|
|
147
|
+
const a = Math.atan2(py, px) - phase;
|
|
148
|
+
const step = Math.PI * 2 / n;
|
|
149
|
+
const k = Math.round(a / step);
|
|
150
|
+
const ba = k * step + phase;
|
|
151
|
+
return Math.hypot(px - ring * Math.cos(ba), py - ring * Math.sin(ba));
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// src/material/paint/cells.ts
|
|
155
|
+
var PANE_TONE = 0.12;
|
|
156
|
+
function paintWindowSash(c, r, seed) {
|
|
157
|
+
const cols = param(r.params, "cols", 2);
|
|
158
|
+
const rows = param(r.params, "rows", 2);
|
|
159
|
+
const frame = param(r.params, "frame", 0.07);
|
|
160
|
+
const bar = param(r.params, "bar", 0.022);
|
|
161
|
+
const meet = 0.5;
|
|
162
|
+
const pu = c.metresU / c.w;
|
|
163
|
+
const pv = c.metresV / c.h;
|
|
164
|
+
for (let y = 0;y < c.h; y++) {
|
|
165
|
+
const ym = (y + 0.5) * pv;
|
|
166
|
+
const lower = ym < meet;
|
|
167
|
+
const y0 = lower ? frame : meet + bar;
|
|
168
|
+
const y1 = lower ? meet - bar : 1 - frame;
|
|
169
|
+
for (let x = 0;x < c.w; x++) {
|
|
170
|
+
const xm = (x + 0.5) * pu;
|
|
171
|
+
const i = y * c.w + x;
|
|
172
|
+
const fx = (xm - frame) / (1 - 2 * frame) * cols;
|
|
173
|
+
const fy = (ym - y0) / (y1 - y0) * rows;
|
|
174
|
+
const inX = Math.min(fx - Math.floor(fx), Math.ceil(fx) - fx) * ((1 - 2 * frame) / cols);
|
|
175
|
+
const inY = Math.min(fy - Math.floor(fy), Math.ceil(fy) - fy) * ((y1 - y0) / rows);
|
|
176
|
+
const open = -rect(xm, ym, frame, y0, 1 - frame, y1);
|
|
177
|
+
const pane = Math.min(open, inX - bar / 2, inY - bar / 2);
|
|
178
|
+
const wood = noiseAt(c, xm, ym, 0.3, 0.02, seed, 3);
|
|
179
|
+
if (pane > 0) {
|
|
180
|
+
const sky = noiseAt(c, xm, ym, 0.6, 0.6, seed + 1, 2);
|
|
181
|
+
c.height[i] = -0.012;
|
|
182
|
+
c.tone[i] = PANE_TONE + 0.05 * (ym - y0) + 0.03 * sky;
|
|
183
|
+
c.rough[i] = 0.05;
|
|
184
|
+
c.metal[i] = 0;
|
|
185
|
+
} else {
|
|
186
|
+
const bevel = smooth(0, 0.01, -pane);
|
|
187
|
+
c.height[i] = -0.012 * (1 - bevel) + 0.0003 * wood;
|
|
188
|
+
c.tone[i] = 0.9 + 0.03 * wood;
|
|
189
|
+
c.rough[i] = r.roughness;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
function paintDoorPanel(c, r, seed) {
|
|
195
|
+
const stile = param(r.params, "stile", 0.12);
|
|
196
|
+
const muntin = stile * 0.7;
|
|
197
|
+
const pu = c.metresU / c.w;
|
|
198
|
+
const pv = c.metresV / c.h;
|
|
199
|
+
const midX = 0.5;
|
|
200
|
+
const panels = [
|
|
201
|
+
[stile, 0.2, midX - muntin / 2, 0.42],
|
|
202
|
+
[midX + muntin / 2, 0.2, 1 - stile, 0.42],
|
|
203
|
+
[stile, 0.58, midX - muntin / 2, 1 - stile],
|
|
204
|
+
[midX + muntin / 2, 0.58, 1 - stile, 1 - stile]
|
|
205
|
+
];
|
|
206
|
+
for (let y = 0;y < c.h; y++) {
|
|
207
|
+
const ym = (y + 0.5) * pv;
|
|
208
|
+
for (let x = 0;x < c.w; x++) {
|
|
209
|
+
const xm = (x + 0.5) * pu;
|
|
210
|
+
const i = y * c.w + x;
|
|
211
|
+
let d = 1;
|
|
212
|
+
for (const [x0, y0, x1, y1] of panels)
|
|
213
|
+
d = Math.min(d, rect(xm, ym, x0, y0, x1, y1));
|
|
214
|
+
const grain = noiseAt(c, xm, ym, 0.02, 0.4, seed, 3);
|
|
215
|
+
const recess = d < 0 ? 1 : 1 - smooth(0, 0.006, d);
|
|
216
|
+
const field = smooth(0.005, 0.04, -d);
|
|
217
|
+
c.height[i] = -0.012 * recess + 0.008 * field + 0.0003 * grain;
|
|
218
|
+
c.tone[i] = (0.8 + 0.08 * grain) * (d < 0 && d > -0.008 ? 0.65 : d < 0 ? 0.92 : 1);
|
|
219
|
+
c.rough[i] = r.roughness + 0.05 * grain;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
function paintSign(c, r, _seed) {
|
|
224
|
+
const border = param(r.params, "border", 0.05);
|
|
225
|
+
const pu = c.metresU / c.w;
|
|
226
|
+
const pv = c.metresV / c.h;
|
|
227
|
+
const screws = [0.07, 0.93];
|
|
228
|
+
for (let y = 0;y < c.h; y++) {
|
|
229
|
+
const ym = (y + 0.5) * pv;
|
|
230
|
+
for (let x = 0;x < c.w; x++) {
|
|
231
|
+
const xm = (x + 0.5) * pu;
|
|
232
|
+
const i = y * c.w + x;
|
|
233
|
+
const inner = -rect(xm, ym, border, border, 1 - border, 1 - border);
|
|
234
|
+
const edge = Math.min(xm, ym, 1 - xm, 1 - ym);
|
|
235
|
+
const moulding = inner < 0 ? dome(Math.abs(-inner - border / 2), border / 2) : 0;
|
|
236
|
+
let screw = 0;
|
|
237
|
+
for (const sx of screws)
|
|
238
|
+
for (const sy of screws)
|
|
239
|
+
screw = Math.max(screw, dome(Math.hypot(xm - sx, ym - sy), 0.012));
|
|
240
|
+
c.height[i] = 0.006 * moulding + 0.002 * screw - 0.002 * (1 - smooth(0, 0.004, edge));
|
|
241
|
+
c.tone[i] = inner > 0 ? 0.95 : 0.85 + 0.1 * moulding;
|
|
242
|
+
if (screw > 0) {
|
|
243
|
+
c.tone[i] = 0.6;
|
|
244
|
+
c.metal[i] = 1;
|
|
245
|
+
c.rough[i] = 0.35;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// src/material/paint/cells-round.ts
|
|
252
|
+
var TAU = Math.PI * 2;
|
|
253
|
+
function hand(px, py, hours, len) {
|
|
254
|
+
const a = hours / 12 * TAU;
|
|
255
|
+
return segment(px, py, 0, 0, Math.sin(a) * len, Math.cos(a) * len);
|
|
256
|
+
}
|
|
257
|
+
function paintClock(c, r, _seed) {
|
|
258
|
+
const pu = c.metresU / c.w;
|
|
259
|
+
const pv = c.metresV / c.h;
|
|
260
|
+
for (let y = 0;y < c.h; y++) {
|
|
261
|
+
const py = (y + 0.5) * pv - 0.5;
|
|
262
|
+
for (let x = 0;x < c.w; x++) {
|
|
263
|
+
const px = (x + 0.5) * pu - 0.5;
|
|
264
|
+
const i = y * c.w + x;
|
|
265
|
+
const d = Math.hypot(px, py);
|
|
266
|
+
if (d > 0.48)
|
|
267
|
+
continue;
|
|
268
|
+
const i0 = i;
|
|
269
|
+
if (d > 0.42) {
|
|
270
|
+
c.height[i0] = 0.012 * dome(Math.abs(d - 0.45), 0.03);
|
|
271
|
+
c.tone[i0] = 0.55;
|
|
272
|
+
c.metal[i0] = 1;
|
|
273
|
+
c.rough[i0] = 0.35;
|
|
274
|
+
continue;
|
|
275
|
+
}
|
|
276
|
+
const a = Math.atan2(px, py);
|
|
277
|
+
const minute = Math.abs((a / TAU * 60 + 60.5) % 1 - 0.5) * (TAU / 60) * d;
|
|
278
|
+
const hour = Math.abs((a / TAU * 12 + 12.5) % 1 - 0.5) * (TAU / 12) * d;
|
|
279
|
+
const tickM = d > 0.38 && d < 0.41 && minute < 0.003 ? 1 : 0;
|
|
280
|
+
const tickH = d > 0.32 && d < 0.41 && hour < 0.008 ? 1 : 0;
|
|
281
|
+
const hands = Math.min(hand(px, py, 10 + 10 / 60, 0.22) - 0.012, hand(px, py, 2, 0.34) - 0.007);
|
|
282
|
+
const ink = Math.max(tickM, tickH, hands < 0 ? 1 : 0, d < 0.02 ? 1 : 0);
|
|
283
|
+
c.height[i0] = -0.004 + 0.002 * ink;
|
|
284
|
+
c.tone[i0] = ink ? 0.08 : 0.96;
|
|
285
|
+
c.rough[i0] = ink ? 0.5 : r.roughness;
|
|
286
|
+
c.metal[i0] = 0;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
function paintSmokebox(c, r, seed) {
|
|
291
|
+
const pu = c.metresU / c.w;
|
|
292
|
+
const pv = c.metresV / c.h;
|
|
293
|
+
const metal = r.metalness ?? 0;
|
|
294
|
+
for (let y = 0;y < c.h; y++) {
|
|
295
|
+
const py = (y + 0.5) * pv - 0.5;
|
|
296
|
+
for (let x = 0;x < c.w; x++) {
|
|
297
|
+
const px = (x + 0.5) * pu - 0.5;
|
|
298
|
+
const i = y * c.w + x;
|
|
299
|
+
const d = Math.hypot(px, py);
|
|
300
|
+
if (d > 0.47)
|
|
301
|
+
continue;
|
|
302
|
+
const pit = noiseAt(c, px + 0.5, py + 0.5, 0.03, 0.03, seed, 3);
|
|
303
|
+
let h = 0.01 * (1 - (d / 0.47) ** 2) + 0.0004 * pit;
|
|
304
|
+
const flange = dome(Math.abs(d - 0.45), 0.02);
|
|
305
|
+
h += 0.006 * flange;
|
|
306
|
+
const bolt = dome(boltRing(px, py, 0.415, 28), 0.011);
|
|
307
|
+
h += 0.006 * bolt;
|
|
308
|
+
const strap = Math.min(segment(px, py, -0.47, 0.17, 0.12, 0.17), segment(px, py, -0.47, -0.17, 0.12, -0.17));
|
|
309
|
+
const strapH = smooth(0.024, 0.018, strap);
|
|
310
|
+
h = Math.max(h, 0.014 * strapH + h * (1 - strapH));
|
|
311
|
+
const boss = dome(d, 0.05);
|
|
312
|
+
const handle = smooth(0.012, 0.008, segment(px, py, -0.13, 0, 0.13, 0));
|
|
313
|
+
h += 0.02 * boss + 0.012 * handle;
|
|
314
|
+
c.height[i] = h;
|
|
315
|
+
c.tone[i] = (0.5 + 0.05 * pit) * (1 + 0.5 * Math.max(bolt, handle, strapH * 0.6, flange, boss));
|
|
316
|
+
c.rough[i] = r.roughness - 0.15 * Math.max(bolt, handle, boss);
|
|
317
|
+
c.metal[i] = metal + (1 - metal) * Math.max(handle, boss) * 0.8;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
function paintPorthole(c, r, seed) {
|
|
322
|
+
const pu = c.metresU / c.w;
|
|
323
|
+
const pv = c.metresV / c.h;
|
|
324
|
+
for (let y = 0;y < c.h; y++) {
|
|
325
|
+
const py = (y + 0.5) * pv - 0.5;
|
|
326
|
+
for (let x = 0;x < c.w; x++) {
|
|
327
|
+
const px = (x + 0.5) * pu - 0.5;
|
|
328
|
+
const i = y * c.w + x;
|
|
329
|
+
const d = Math.hypot(px, py);
|
|
330
|
+
if (d > 0.44)
|
|
331
|
+
continue;
|
|
332
|
+
if (d < 0.29) {
|
|
333
|
+
const sky = noiseAt(c, px + 0.5, py + 0.5, 0.5, 0.5, seed, 2);
|
|
334
|
+
c.height[i] = -0.012;
|
|
335
|
+
c.tone[i] = PANE_TONE + 0.04 * (py + 0.3) + 0.02 * sky;
|
|
336
|
+
c.rough[i] = 0.05;
|
|
337
|
+
c.metal[i] = 0;
|
|
338
|
+
continue;
|
|
339
|
+
}
|
|
340
|
+
const ring = dome(Math.abs(d - 0.365), 0.075);
|
|
341
|
+
const bolt = dome(boltRing(px, py, 0.405, 10, Math.PI / 10), 0.018);
|
|
342
|
+
const lip = dome(Math.abs(d - 0.3), 0.012);
|
|
343
|
+
c.height[i] = 0.016 * ring + 0.008 * bolt + 0.004 * lip;
|
|
344
|
+
c.tone[i] = 0.75 + 0.15 * ring;
|
|
345
|
+
c.rough[i] = Math.max(0.2, r.roughness - 0.2 * ring);
|
|
346
|
+
c.metal[i] = 1;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// src/material/paint/foliage-common.ts
|
|
352
|
+
function curve(x0, y0, x1, y1, x2, y2, n = 16) {
|
|
353
|
+
const x = [];
|
|
354
|
+
const y = [];
|
|
355
|
+
const a = [];
|
|
356
|
+
for (let i = 0;i <= n; i++) {
|
|
357
|
+
const t = i / n;
|
|
358
|
+
const u = 1 - t;
|
|
359
|
+
x.push(u * u * x0 + 2 * u * t * x1 + t * t * x2);
|
|
360
|
+
y.push(u * u * y0 + 2 * u * t * y1 + t * t * y2);
|
|
361
|
+
const dx = 2 * u * (x1 - x0) + 2 * t * (x2 - x1);
|
|
362
|
+
const dy = 2 * u * (y1 - y0) + 2 * t * (y2 - y1);
|
|
363
|
+
a.push(Math.atan2(dx, dy));
|
|
364
|
+
}
|
|
365
|
+
return { x, y, a };
|
|
366
|
+
}
|
|
367
|
+
var at = (c, t) => Math.round(t * (c.x.length - 1));
|
|
368
|
+
function drawStem(f, c, w0, w1, col) {
|
|
369
|
+
const n = c.x.length - 1;
|
|
370
|
+
for (let i = 0;i < n; i++) {
|
|
371
|
+
const t0 = i / n;
|
|
372
|
+
const t1 = (i + 1) / n;
|
|
373
|
+
f.stem(c.x[i] ?? 0, c.y[i] ?? 0, c.x[i + 1] ?? 0, c.y[i + 1] ?? 0, w0 + (w1 - w0) * t0, w0 + (w1 - w0) * t1, col);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
function vary(rng, col, amt) {
|
|
377
|
+
const k = 1 + amt * (rng.next() * 2 - 1);
|
|
378
|
+
const drift = 1 + amt * 0.6 * (rng.next() * 2 - 1);
|
|
379
|
+
return [col[0] * k * drift, col[1] * k, col[2] * k / drift];
|
|
380
|
+
}
|
|
381
|
+
var dim = (col, k) => [col[0] * k, col[1] * k, col[2] * k];
|
|
382
|
+
var LEAF = {
|
|
383
|
+
beech: [0.09, 0.2, 0.035],
|
|
384
|
+
maple: [0.1, 0.24, 0.04],
|
|
385
|
+
birch: [0.16, 0.3, 0.05],
|
|
386
|
+
acacia: [0.11, 0.2, 0.06],
|
|
387
|
+
palm: [0.12, 0.22, 0.05],
|
|
388
|
+
dry: [0.3, 0.2, 0.08],
|
|
389
|
+
spruce: [0.035, 0.09, 0.04],
|
|
390
|
+
pine: [0.06, 0.13, 0.04],
|
|
391
|
+
willow: [0.2, 0.3, 0.08],
|
|
392
|
+
reed: [0.2, 0.28, 0.08],
|
|
393
|
+
snow: [0.85, 0.88, 0.92],
|
|
394
|
+
bark: [0.08, 0.055, 0.035],
|
|
395
|
+
twig: [0.12, 0.09, 0.05]
|
|
396
|
+
};
|
|
397
|
+
var AUTUMN = [
|
|
398
|
+
[0.6, 0.08, 0.02],
|
|
399
|
+
[0.75, 0.25, 0.02],
|
|
400
|
+
[0.8, 0.45, 0.03],
|
|
401
|
+
[0.45, 0.04, 0.03],
|
|
402
|
+
[0.55, 0.35, 0.05]
|
|
403
|
+
];
|
|
404
|
+
|
|
405
|
+
// src/material/paint/orm.ts
|
|
406
|
+
var AO_DEPTH = 0.006;
|
|
407
|
+
var AO_STRENGTH = 0.75;
|
|
408
|
+
var AO_RADIUS = 0.02;
|
|
409
|
+
function boxBlur(src, w, h, rx, ry, wrap) {
|
|
410
|
+
const tmp = new Float32Array(src.length);
|
|
411
|
+
const out = new Float32Array(src.length);
|
|
412
|
+
const ix = (i) => i < 0 ? wrap ? i % w + w : 0 : i >= w ? wrap ? i % w : w - 1 : i;
|
|
413
|
+
for (let y = 0;y < h; y++) {
|
|
414
|
+
const row = y * w;
|
|
415
|
+
let s = 0;
|
|
416
|
+
for (let k = -rx;k <= rx; k++)
|
|
417
|
+
s += src[row + ix(k)] ?? 0;
|
|
418
|
+
for (let x = 0;x < w; x++) {
|
|
419
|
+
tmp[row + x] = s / (2 * rx + 1);
|
|
420
|
+
s += (src[row + ix(x + rx + 1)] ?? 0) - (src[row + ix(x - rx)] ?? 0);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
const iy = (j) => j < 0 ? 0 : j >= h ? h - 1 : j;
|
|
424
|
+
for (let x = 0;x < w; x++) {
|
|
425
|
+
let s = 0;
|
|
426
|
+
for (let k = -ry;k <= ry; k++)
|
|
427
|
+
s += tmp[iy(k) * w + x] ?? 0;
|
|
428
|
+
for (let y = 0;y < h; y++) {
|
|
429
|
+
out[y * w + x] = s / (2 * ry + 1);
|
|
430
|
+
s += (tmp[iy(y + ry + 1) * w + x] ?? 0) - (tmp[iy(y - ry) * w + x] ?? 0);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
return out;
|
|
434
|
+
}
|
|
435
|
+
function cavityOcclusion(c) {
|
|
436
|
+
const rx = Math.max(1, Math.round(AO_RADIUS / (c.metresU / c.w)));
|
|
437
|
+
const ry = Math.max(1, Math.round(AO_RADIUS / (c.metresV / c.h)));
|
|
438
|
+
const blur = boxBlur(boxBlur(c.height, c.w, c.h, rx, ry, c.wrapU), c.w, c.h, rx, ry, c.wrapU);
|
|
439
|
+
const ao = new Float32Array(c.height.length);
|
|
440
|
+
for (let i = 0;i < ao.length; i++) {
|
|
441
|
+
const cavity = (blur[i] ?? 0) - (c.height[i] ?? 0);
|
|
442
|
+
const t = cavity <= 0 ? 0 : cavity >= AO_DEPTH ? 1 : cavity / AO_DEPTH;
|
|
443
|
+
ao[i] = (1 - AO_STRENGTH * t) * (c.occl[i] ?? 1);
|
|
444
|
+
}
|
|
445
|
+
return ao;
|
|
446
|
+
}
|
|
447
|
+
var SRGB = new Uint8Array(4096).map((_, i) => {
|
|
448
|
+
const l = i / 4095;
|
|
449
|
+
const s = l <= 0.0031308 ? l * 12.92 : 1.055 * l ** (1 / 2.4) - 0.055;
|
|
450
|
+
return Math.round(s * 255);
|
|
451
|
+
});
|
|
452
|
+
var byte = (v) => v <= 0 ? 0 : v >= 1 ? 255 : Math.round(v * 255);
|
|
453
|
+
function packOrm(c, ao) {
|
|
454
|
+
const out = new Uint8Array(c.w * c.h * 4);
|
|
455
|
+
for (let i = 0, o = 0;i < ao.length; i++, o += 4) {
|
|
456
|
+
out[o] = byte(ao[i] ?? 1);
|
|
457
|
+
out[o + 1] = byte(c.rough[i] ?? 0.8);
|
|
458
|
+
out[o + 2] = byte(c.metal[i] ?? 0);
|
|
459
|
+
out[o + 3] = 255;
|
|
460
|
+
}
|
|
461
|
+
return out;
|
|
462
|
+
}
|
|
463
|
+
function packAlbedo(c, ao) {
|
|
464
|
+
if (c.rgba)
|
|
465
|
+
return c.rgba;
|
|
466
|
+
const out = new Uint8Array(c.w * c.h * 4);
|
|
467
|
+
for (let i = 0, o = 0;i < ao.length; i++, o += 4) {
|
|
468
|
+
const t = (c.tone[i] ?? 1) * (0.8 + 0.2 * (ao[i] ?? 1));
|
|
469
|
+
const g = SRGB[t <= 0 ? 0 : t >= 1 ? 4095 : Math.round(t * 4095)] ?? 255;
|
|
470
|
+
out[o] = g;
|
|
471
|
+
out[o + 1] = g;
|
|
472
|
+
out[o + 2] = g;
|
|
473
|
+
out[o + 3] = 255;
|
|
474
|
+
}
|
|
475
|
+
return out;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// src/material/paint/foliage-raster.ts
|
|
479
|
+
var total = (a) => a.reduce((s, v) => s + v, 0);
|
|
480
|
+
var LOBE = Math.PI * 2 / 5;
|
|
481
|
+
|
|
482
|
+
class Foliage {
|
|
483
|
+
r;
|
|
484
|
+
g;
|
|
485
|
+
b;
|
|
486
|
+
a;
|
|
487
|
+
c;
|
|
488
|
+
px;
|
|
489
|
+
constructor(c) {
|
|
490
|
+
this.c = c;
|
|
491
|
+
const n = c.w * c.h;
|
|
492
|
+
this.r = new Float32Array(n);
|
|
493
|
+
this.g = new Float32Array(n);
|
|
494
|
+
this.b = new Float32Array(n);
|
|
495
|
+
this.a = new Float32Array(n);
|
|
496
|
+
this.px = c.metresU / c.w;
|
|
497
|
+
c.height.fill(0);
|
|
498
|
+
}
|
|
499
|
+
put(i, cov, col, shade, h, rough) {
|
|
500
|
+
if (cov <= 0)
|
|
501
|
+
return;
|
|
502
|
+
const k = 1 - cov;
|
|
503
|
+
const s = shade * cov;
|
|
504
|
+
this.r[i] = (this.r[i] ?? 0) * k + col[0] * s;
|
|
505
|
+
this.g[i] = (this.g[i] ?? 0) * k + col[1] * s;
|
|
506
|
+
this.b[i] = (this.b[i] ?? 0) * k + col[2] * s;
|
|
507
|
+
this.a[i] = (this.a[i] ?? 0) * k + cov;
|
|
508
|
+
this.c.height[i] = (this.c.height[i] ?? 0) * k + h * cov;
|
|
509
|
+
this.c.rough[i] = (this.c.rough[i] ?? 0.8) * k + rough * cov;
|
|
510
|
+
}
|
|
511
|
+
span(x0, y0, x1, y1) {
|
|
512
|
+
const { w, h } = this.c;
|
|
513
|
+
const p = this.px;
|
|
514
|
+
return [
|
|
515
|
+
Math.max(0, Math.floor(x0 / p) - 1),
|
|
516
|
+
Math.max(0, Math.floor(y0 / p) - 1),
|
|
517
|
+
Math.min(w - 1, Math.ceil(x1 / p) + 1),
|
|
518
|
+
Math.min(h - 1, Math.ceil(y1 / p) + 1)
|
|
519
|
+
];
|
|
520
|
+
}
|
|
521
|
+
stem(ax, ay, bx, by, w0, w1, col) {
|
|
522
|
+
const m = Math.max(w0, w1);
|
|
523
|
+
const [sx0 = 0, sy0 = 0, sx1 = 0, sy1 = 0] = this.span(Math.min(ax, bx) - m, Math.min(ay, by) - m, Math.max(ax, bx) + m, Math.max(ay, by) + m);
|
|
524
|
+
const lx = bx - ax;
|
|
525
|
+
const ly = by - ay;
|
|
526
|
+
const l2 = lx * lx + ly * ly || 1;
|
|
527
|
+
for (let y = sy0;y <= sy1; y++) {
|
|
528
|
+
const ym = (y + 0.5) * this.px;
|
|
529
|
+
for (let x = sx0;x <= sx1; x++) {
|
|
530
|
+
const xm = (x + 0.5) * this.px;
|
|
531
|
+
const t = clamp01(((xm - ax) * lx + (ym - ay) * ly) / l2);
|
|
532
|
+
const half = (w0 + (w1 - w0) * t) / 2;
|
|
533
|
+
const d = segment(xm, ym, ax, ay, bx, by);
|
|
534
|
+
const cov = clamp01(0.5 - (d - half) / this.px);
|
|
535
|
+
const round = half > 0 ? Math.sqrt(Math.max(0, 1 - (d / half) ** 2)) : 0;
|
|
536
|
+
this.put(y * this.c.w + x, cov, col, 1, 0.0015 + half * 0.4 * round, 0.85);
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
leaf(shape, bx, by, angle, len, wid, col, serrate = 0, lift = 0) {
|
|
541
|
+
const ux = Math.sin(angle);
|
|
542
|
+
const uy = Math.cos(angle);
|
|
543
|
+
const tx = bx + ux * len;
|
|
544
|
+
const ty = by + uy * len;
|
|
545
|
+
const pad = shape === "palmate" ? len : wid;
|
|
546
|
+
const [sx0 = 0, sy0 = 0, sx1 = 0, sy1 = 0] = this.span(Math.min(bx, tx) - pad, Math.min(by, ty) - pad, Math.max(bx, tx) + pad, Math.max(by, ty) + pad);
|
|
547
|
+
for (let y = sy0;y <= sy1; y++) {
|
|
548
|
+
const ym = (y + 0.5) * this.px - by;
|
|
549
|
+
for (let x = sx0;x <= sx1; x++) {
|
|
550
|
+
const xm = (x + 0.5) * this.px - bx;
|
|
551
|
+
const along = xm * ux + ym * uy;
|
|
552
|
+
const across = xm * uy - ym * ux;
|
|
553
|
+
let sd;
|
|
554
|
+
let rib;
|
|
555
|
+
if (shape === "palmate") {
|
|
556
|
+
const rr = Math.hypot(along - len * 0.45, across);
|
|
557
|
+
const th = Math.atan2(across, along - len * 0.45);
|
|
558
|
+
const lobe = 1 - Math.abs(Math.sin(2.5 * th)) ** 0.6;
|
|
559
|
+
const edge = len * 0.5 * (0.35 + 0.65 * lobe) * (1 + serrate * Math.abs(Math.sin(18 * th)));
|
|
560
|
+
sd = rr - edge;
|
|
561
|
+
const axis = Math.round(th / LOBE) * LOBE;
|
|
562
|
+
rib = rr * Math.abs(Math.sin(th - axis));
|
|
563
|
+
} else {
|
|
564
|
+
const t = along / len;
|
|
565
|
+
if (t < 0 || t > 1) {
|
|
566
|
+
sd = Math.max(-along, along - len) + Math.abs(across) * 0.5;
|
|
567
|
+
} else {
|
|
568
|
+
const prof = shape === "ovate" ? Math.sin(Math.PI * t ** 0.8) ** 0.9 : shape === "lance" ? Math.sin(Math.PI * t ** 0.6) : shape === "blade" ? (1 - t) ** 0.4 : 1 - 0.6 * t;
|
|
569
|
+
const teeth = 1 - serrate * Math.abs(Math.sin(t * 40));
|
|
570
|
+
sd = Math.abs(across) - wid / 2 * prof * teeth;
|
|
571
|
+
}
|
|
572
|
+
rib = Math.abs(across);
|
|
573
|
+
}
|
|
574
|
+
const cov = clamp01(0.5 - sd / this.px);
|
|
575
|
+
if (cov <= 0)
|
|
576
|
+
continue;
|
|
577
|
+
const inside = clamp01(-sd / (wid * 0.5 + 0.000001));
|
|
578
|
+
const vein = 1 - smooth(0, this.px * 1.5, rib);
|
|
579
|
+
const shade = 0.85 + 0.15 * inside;
|
|
580
|
+
this.put(y * this.c.w + x, cov, col, shade, 0.002 + lift + 0.0015 * inside + 0.0006 * vein, 0.6);
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
finish(axisX = 0.5, reach = 0.35) {
|
|
585
|
+
const { w, h } = this.c;
|
|
586
|
+
const n = w * h;
|
|
587
|
+
const sa = total(this.a);
|
|
588
|
+
const mean = sa > 0 ? [total(this.r) / sa, total(this.g) / sa, total(this.b) / sa] : [0.1, 0.2, 0.05];
|
|
589
|
+
const out = new Uint8Array(n * 4);
|
|
590
|
+
const enc = (v) => SRGB[Math.round(clamp01(v) * 4095)] ?? 0;
|
|
591
|
+
for (let y = 0;y < h; y++) {
|
|
592
|
+
for (let x = 0;x < w; x++) {
|
|
593
|
+
const i = y * w + x;
|
|
594
|
+
const a = this.a[i] ?? 0;
|
|
595
|
+
const k = a > 0.02 ? 1 / a : 0;
|
|
596
|
+
const mix = clamp01(a * 4);
|
|
597
|
+
out[i * 4] = enc((this.r[i] ?? 0) * k * mix + mean[0] * (1 - mix));
|
|
598
|
+
out[i * 4 + 1] = enc((this.g[i] ?? 0) * k * mix + mean[1] * (1 - mix));
|
|
599
|
+
out[i * 4 + 2] = enc((this.b[i] ?? 0) * k * mix + mean[2] * (1 - mix));
|
|
600
|
+
out[i * 4 + 3] = Math.round(clamp01(a) * 255);
|
|
601
|
+
const d = Math.abs((x + 0.5) * this.px - axisX);
|
|
602
|
+
this.c.occl[i] = 0.55 + 0.45 * smooth(0, reach, d + (y + 0.5) * this.px * 0.4);
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
this.c.rgba = out;
|
|
606
|
+
this.c.metal.fill(0);
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
// src/material/paint/foliage-broad.ts
|
|
611
|
+
function cluster(c, seed, s) {
|
|
612
|
+
const rng = createRng2(seed);
|
|
613
|
+
const f = new Foliage(c);
|
|
614
|
+
const leafAt = (x, y, a, k, lift) => {
|
|
615
|
+
const len = s.len * rng.range(0.8, 1.15) * k;
|
|
616
|
+
f.leaf(s.shape, x, y, a, len, s.wid * (len / s.len), s.colour(rng), s.serrate, lift);
|
|
617
|
+
};
|
|
618
|
+
for (let i = 0;i < s.back; i++) {
|
|
619
|
+
const x = 0.5 + rng.range(-0.33, 0.33);
|
|
620
|
+
const y = rng.range(0.3, 0.85);
|
|
621
|
+
const col = dim(s.colour(rng), 0.5);
|
|
622
|
+
const len = s.len * rng.range(0.8, 1.1);
|
|
623
|
+
f.leaf(s.shape, x, y, rng.range(-1.4, 1.4), len, s.wid * (len / s.len), col, s.serrate);
|
|
624
|
+
}
|
|
625
|
+
const main = curve(0.5, 0.02, 0.5 + rng.range(-0.1, 0.1), 0.45, 0.5 + rng.range(-0.08, 0.08), 0.8);
|
|
626
|
+
drawStem(f, main, 0.016, 0.004, LEAF.twig);
|
|
627
|
+
for (let k = 0;k < s.twigs; k++) {
|
|
628
|
+
const t = 0.22 + k / Math.max(1, s.twigs - 1) * 0.6;
|
|
629
|
+
const side = k % 2 === 0 ? 1 : -1;
|
|
630
|
+
const i = at(main, t);
|
|
631
|
+
const bx = main.x[i] ?? 0.5;
|
|
632
|
+
const by = main.y[i] ?? 0.5;
|
|
633
|
+
const a = (main.a[i] ?? 0) + side * rng.range(0.7, 1.1);
|
|
634
|
+
const len = 0.3 * (1 - t * 0.45);
|
|
635
|
+
const ex = bx + Math.sin(a) * len;
|
|
636
|
+
const ey = by + Math.cos(a) * len - s.droop * len * 0.4;
|
|
637
|
+
const twig = curve(bx, by, (bx + ex) / 2, (by + ey) / 2 + len * 0.15, ex, ey, 8);
|
|
638
|
+
drawStem(f, twig, 0.007, 0.002, LEAF.twig);
|
|
639
|
+
for (let q = 1;q <= s.perTwig; q++) {
|
|
640
|
+
const j = at(twig, q / s.perTwig);
|
|
641
|
+
const alt = q % 2 === 0 ? 1 : -1;
|
|
642
|
+
const ta = (twig.a[j] ?? 0) + (q === s.perTwig ? 0 : alt * rng.range(0.5, 0.9));
|
|
643
|
+
leafAt(twig.x[j] ?? 0, twig.y[j] ?? 0, ta, 1 - 0.15 * (1 - q / s.perTwig), 0.002);
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
const tip = main.x.length - 1;
|
|
647
|
+
leafAt(main.x[tip] ?? 0.5, main.y[tip] ?? 0.8, main.a[tip] ?? 0, 1.1, 0.003);
|
|
648
|
+
f.finish(0.5, 0.35);
|
|
649
|
+
}
|
|
650
|
+
function paintBroadleaf(c, seed) {
|
|
651
|
+
cluster(c, seed, {
|
|
652
|
+
shape: "ovate",
|
|
653
|
+
len: 0.15,
|
|
654
|
+
wid: 0.085,
|
|
655
|
+
serrate: 0.03,
|
|
656
|
+
twigs: 6,
|
|
657
|
+
perTwig: 3,
|
|
658
|
+
back: 16,
|
|
659
|
+
droop: 0.1,
|
|
660
|
+
colour: (rng) => vary(rng, LEAF.beech, 0.18)
|
|
661
|
+
});
|
|
662
|
+
}
|
|
663
|
+
function paintMaple(c, seed) {
|
|
664
|
+
cluster(c, seed, {
|
|
665
|
+
shape: "palmate",
|
|
666
|
+
len: 0.24,
|
|
667
|
+
wid: 0.24,
|
|
668
|
+
serrate: 0.04,
|
|
669
|
+
twigs: 4,
|
|
670
|
+
perTwig: 2,
|
|
671
|
+
back: 7,
|
|
672
|
+
droop: 0,
|
|
673
|
+
colour: (rng) => vary(rng, LEAF.maple, 0.15)
|
|
674
|
+
});
|
|
675
|
+
}
|
|
676
|
+
function paintMapleAutumn(c, seed) {
|
|
677
|
+
cluster(c, seed, {
|
|
678
|
+
shape: "palmate",
|
|
679
|
+
len: 0.24,
|
|
680
|
+
wid: 0.24,
|
|
681
|
+
serrate: 0.04,
|
|
682
|
+
twigs: 4,
|
|
683
|
+
perTwig: 2,
|
|
684
|
+
back: 7,
|
|
685
|
+
droop: 0,
|
|
686
|
+
colour: (rng) => vary(rng, rng.pick(AUTUMN), 0.12)
|
|
687
|
+
});
|
|
688
|
+
}
|
|
689
|
+
function paintBirch(c, seed) {
|
|
690
|
+
cluster(c, seed, {
|
|
691
|
+
shape: "ovate",
|
|
692
|
+
len: 0.085,
|
|
693
|
+
wid: 0.06,
|
|
694
|
+
serrate: 0.12,
|
|
695
|
+
twigs: 8,
|
|
696
|
+
perTwig: 5,
|
|
697
|
+
back: 22,
|
|
698
|
+
droop: 0.6,
|
|
699
|
+
colour: (rng) => vary(rng, LEAF.birch, 0.2)
|
|
700
|
+
});
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
// src/material/paint/foliage-compound.ts
|
|
704
|
+
function paintAcacia(c, seed) {
|
|
705
|
+
const rng = createRng2(seed);
|
|
706
|
+
const f = new Foliage(c);
|
|
707
|
+
const rachis = curve(0.5, 0.02, 0.48 + rng.range(-0.05, 0.05), 0.5, 0.5, 0.95);
|
|
708
|
+
for (let layer = 0;layer < 2; layer++) {
|
|
709
|
+
const k = layer === 0 ? 0.5 : 1;
|
|
710
|
+
if (layer === 1)
|
|
711
|
+
drawStem(f, rachis, 0.01, 0.003, LEAF.twig);
|
|
712
|
+
const pairs = layer === 0 ? 6 : 8;
|
|
713
|
+
for (let p = 0;p < pairs; p++) {
|
|
714
|
+
const t = 0.15 + p / pairs * 0.8 + (layer === 0 ? 0.05 : 0);
|
|
715
|
+
const i = at(rachis, t);
|
|
716
|
+
for (const side of [-1, 1]) {
|
|
717
|
+
const a = (rachis.a[i] ?? 0) + side * rng.range(0.9, 1.2);
|
|
718
|
+
const len = 0.22 * (1 - t * 0.5);
|
|
719
|
+
const bx = rachis.x[i] ?? 0.5;
|
|
720
|
+
const by = rachis.y[i] ?? 0.5;
|
|
721
|
+
const pinna = curve(bx, by, bx + Math.sin(a) * len * 0.5, by + Math.cos(a) * len * 0.5 + 0.02, bx + Math.sin(a) * len, by + Math.cos(a) * len, 6);
|
|
722
|
+
drawStem(f, pinna, 0.003, 0.0015, dim(LEAF.twig, k));
|
|
723
|
+
for (let q = 1;q <= 12; q++) {
|
|
724
|
+
const j = at(pinna, q / 12.5);
|
|
725
|
+
const col = dim(vary(rng, LEAF.acacia, 0.15), k);
|
|
726
|
+
for (const s2 of [-1, 1])
|
|
727
|
+
f.leaf("ovate", pinna.x[j] ?? 0, pinna.y[j] ?? 0, (pinna.a[j] ?? 0) + s2 * 1.1, 0.03, 0.011, col, 0, layer * 0.002);
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
f.finish(0.5, 0.25);
|
|
733
|
+
}
|
|
734
|
+
function paintPalm(c, seed) {
|
|
735
|
+
const rng = createRng2(seed);
|
|
736
|
+
const f = new Foliage(c);
|
|
737
|
+
const rachis = curve(0.5, 0.02, 0.3, 0.7, 0.78, 0.96, 24);
|
|
738
|
+
for (let layer = 0;layer < 2; layer++) {
|
|
739
|
+
const k = layer === 0 ? 0.55 : 1;
|
|
740
|
+
const n = 26;
|
|
741
|
+
for (let q = 0;q < n; q++) {
|
|
742
|
+
const t = 0.1 + q / n * 0.9 + (layer === 0 ? 0.015 : 0);
|
|
743
|
+
const i = at(rachis, Math.min(1, t));
|
|
744
|
+
const bx = rachis.x[i] ?? 0.5;
|
|
745
|
+
const by = rachis.y[i] ?? 0.5;
|
|
746
|
+
for (const side of [-1, 1]) {
|
|
747
|
+
const a = (rachis.a[i] ?? 0) + side * rng.range(0.75, 1);
|
|
748
|
+
const len = 0.3 * Math.sin(Math.PI * (0.2 + 0.75 * t)) * rng.range(0.85, 1.05);
|
|
749
|
+
const wid = 0.03;
|
|
750
|
+
f.leaf("lance", bx, by, a, len, wid, dim(vary(rng, LEAF.palm, 0.15), k), 0, layer * 0.002);
|
|
751
|
+
if (rng.next() < 0.3) {
|
|
752
|
+
const tx = bx + Math.sin(a) * len * 0.72;
|
|
753
|
+
const ty = by + Math.cos(a) * len * 0.72;
|
|
754
|
+
f.leaf("blade", tx, ty, a, len * 0.28, wid * 0.55, dim(vary(rng, LEAF.dry, 0.2), k), 0, layer * 0.002 + 0.0002);
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
if (layer === 0)
|
|
759
|
+
drawStem(f, rachis, 0.018, 0.004, dim(LEAF.palm, 0.8));
|
|
760
|
+
}
|
|
761
|
+
f.finish(0.5, 0.2);
|
|
762
|
+
}
|
|
763
|
+
function paintWillow(c, seed) {
|
|
764
|
+
const rng = createRng2(seed);
|
|
765
|
+
const f = new Foliage(c);
|
|
766
|
+
const strands = 9;
|
|
767
|
+
for (let s = 0;s < strands; s++) {
|
|
768
|
+
const back = s < 3;
|
|
769
|
+
const k = back ? 0.55 : 1;
|
|
770
|
+
const ex = 0.5 + rng.range(-0.4, 0.4);
|
|
771
|
+
const strand = curve(0.5 + rng.range(-0.03, 0.03), 0.02, (0.5 + ex) / 2 + rng.range(-0.1, 0.1), 0.5, ex, rng.range(0.85, 0.97), 24);
|
|
772
|
+
drawStem(f, strand, 0.006, 0.0015, dim(LEAF.twig, k));
|
|
773
|
+
for (let q = 2;q < 30; q++) {
|
|
774
|
+
const j = at(strand, q / 30);
|
|
775
|
+
const side = q % 2 === 0 ? 1 : -1;
|
|
776
|
+
const a = (strand.a[j] ?? 0) + side * rng.range(0.2, 0.45);
|
|
777
|
+
const col = dim(vary(rng, LEAF.willow, 0.18), k);
|
|
778
|
+
f.leaf("lance", strand.x[j] ?? 0, strand.y[j] ?? 0, a, rng.range(0.07, 0.1), 0.014, col, 0, back ? 0 : 0.002);
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
f.finish(0.5, 0.2);
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
// src/material/paint/foliage-conifer.ts
|
|
785
|
+
function comb(f, rng, shoot, len, k, lift) {
|
|
786
|
+
const n = shoot.x.length - 1;
|
|
787
|
+
for (let i = 0;i <= n; i++) {
|
|
788
|
+
for (const side of [-1, 1]) {
|
|
789
|
+
const a = (shoot.a[i] ?? 0) + side * rng.range(0.7, 1);
|
|
790
|
+
const col = dim(vary(rng, LEAF.spruce, 0.2), k);
|
|
791
|
+
f.leaf("needle", shoot.x[i] ?? 0, shoot.y[i] ?? 0, a, len * rng.range(0.8, 1.1), 0.006, col, 0, lift);
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
function spruce(c, seed, snow) {
|
|
796
|
+
const rng = createRng2(seed);
|
|
797
|
+
const f = new Foliage(c);
|
|
798
|
+
const axis = curve(0.5, 0.02, 0.5 + rng.range(-0.04, 0.04), 0.5, 0.5, 0.95, 60);
|
|
799
|
+
const shoots = [];
|
|
800
|
+
for (let s = 0;s < 8; s++) {
|
|
801
|
+
const t = 0.15 + s / 8 * 0.75;
|
|
802
|
+
const i = at(axis, t);
|
|
803
|
+
const side = s % 2 === 0 ? 1 : -1;
|
|
804
|
+
const bx = axis.x[i] ?? 0.5;
|
|
805
|
+
const by = axis.y[i] ?? 0.5;
|
|
806
|
+
const len = 0.32 * (1 - t * 0.6);
|
|
807
|
+
const a = side * rng.range(0.9, 1.2);
|
|
808
|
+
shoots.push(curve(bx, by, bx + Math.sin(a) * len * 0.5, by + Math.cos(a) * len * 0.5, bx + Math.sin(a) * len, by + Math.cos(a) * len + 0.03, 24));
|
|
809
|
+
}
|
|
810
|
+
for (const sh of shoots)
|
|
811
|
+
comb(f, rng, sh, 0.045, 0.55, 0);
|
|
812
|
+
drawStem(f, axis, 0.012, 0.004, LEAF.bark);
|
|
813
|
+
for (const sh of shoots)
|
|
814
|
+
drawStem(f, sh, 0.005, 0.002, LEAF.bark);
|
|
815
|
+
comb(f, rng, axis, 0.05, 1, 0.002);
|
|
816
|
+
for (const sh of shoots)
|
|
817
|
+
comb(f, rng, sh, 0.04, 1, 0.002);
|
|
818
|
+
if (snow) {
|
|
819
|
+
for (const sh of [axis, ...shoots]) {
|
|
820
|
+
const n = sh.x.length - 1;
|
|
821
|
+
for (let i = 2;i < n; i += 3) {
|
|
822
|
+
const col = vary(rng, LEAF.snow, 0.05);
|
|
823
|
+
f.leaf("ovate", (sh.x[i] ?? 0) - 0.02, (sh.y[i] ?? 0) + 0.008, Math.PI / 2 + rng.range(-0.3, 0.3), rng.range(0.04, 0.07), rng.range(0.025, 0.04), col, 0, 0.006);
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
f.finish(0.5, 0.25);
|
|
828
|
+
}
|
|
829
|
+
var paintSpruce = (c, seed) => spruce(c, seed, false);
|
|
830
|
+
var paintSpruceSnow = (c, seed) => spruce(c, seed, true);
|
|
831
|
+
function paintNeedles(c, seed) {
|
|
832
|
+
const rng = createRng2(seed);
|
|
833
|
+
const f = new Foliage(c);
|
|
834
|
+
const twigs = [];
|
|
835
|
+
for (let s = 0;s < 5; s++) {
|
|
836
|
+
const ex = 0.5 + (s - 2) * 0.13 + rng.range(-0.03, 0.03);
|
|
837
|
+
twigs.push(curve(0.5, 0.02, 0.5 + (ex - 0.5) * 0.4, 0.3, ex, rng.range(0.55, 0.7), 12));
|
|
838
|
+
}
|
|
839
|
+
for (const [s, tw] of twigs.entries()) {
|
|
840
|
+
drawStem(f, tw, 0.012, 0.006, LEAF.bark);
|
|
841
|
+
const k = s % 2 === 0 ? 0.6 : 1;
|
|
842
|
+
for (let i = 6;i < tw.x.length; i++) {
|
|
843
|
+
const fan = i === tw.x.length - 1 ? 14 : 4;
|
|
844
|
+
for (let q = 0;q < fan; q++) {
|
|
845
|
+
const a = (tw.a[i] ?? 0) + rng.range(-1.1, 1.1) * (fan > 4 ? 0.8 : 1.2);
|
|
846
|
+
const col = dim(vary(rng, LEAF.pine, 0.2), k);
|
|
847
|
+
f.leaf("needle", tw.x[i] ?? 0, tw.y[i] ?? 0, a, rng.range(0.12, 0.2), 0.006, col, 0, k * 0.002);
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
f.finish(0.5, 0.3);
|
|
852
|
+
}
|
|
853
|
+
function paintReeds(c, seed) {
|
|
854
|
+
const rng = createRng2(seed);
|
|
855
|
+
const f = new Foliage(c);
|
|
856
|
+
for (let b = 0;b < 22; b++) {
|
|
857
|
+
const back = b < 8;
|
|
858
|
+
const x = 0.5 + rng.range(-0.2, 0.2);
|
|
859
|
+
const tilt = (x - 0.5) * 1.2 + rng.range(-0.25, 0.25);
|
|
860
|
+
const col = dim(vary(rng, rng.next() < 0.2 ? LEAF.dry : LEAF.reed, 0.2), back ? 0.55 : 1);
|
|
861
|
+
f.leaf("blade", x, 0, tilt, rng.range(0.55, 0.97), rng.range(0.018, 0.032), col, 0, back ? 0 : 0.002);
|
|
862
|
+
}
|
|
863
|
+
f.finish(0.5, 0.15);
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
// src/material/paint/foliage.ts
|
|
867
|
+
var FOLIAGE_PAINTERS = {
|
|
868
|
+
foliage_broadleaf: paintBroadleaf,
|
|
869
|
+
foliage_maple: paintMaple,
|
|
870
|
+
foliage_maple_autumn: paintMapleAutumn,
|
|
871
|
+
foliage_birch: paintBirch,
|
|
872
|
+
foliage_acacia: paintAcacia,
|
|
873
|
+
foliage_palm: paintPalm,
|
|
874
|
+
foliage_spruce: paintSpruce,
|
|
875
|
+
foliage_spruce_snow: paintSpruceSnow,
|
|
876
|
+
foliage_willow: paintWillow,
|
|
877
|
+
foliage_reeds: paintReeds,
|
|
878
|
+
foliage_needles: paintNeedles
|
|
879
|
+
};
|
|
880
|
+
|
|
881
|
+
// src/material/paint/decode-png.ts
|
|
882
|
+
import { inflateSync } from "node:zlib";
|
|
883
|
+
var SIG = [137, 80, 78, 71, 13, 10, 26, 10];
|
|
884
|
+
var CHANNELS = { 0: 1, 2: 3, 3: 1, 4: 2, 6: 4 };
|
|
885
|
+
function bad(cause) {
|
|
886
|
+
throw new Ts23dError2("T3D_RECIPE_INVALID", {
|
|
887
|
+
cause: `cell image: ${cause}`,
|
|
888
|
+
fix: "pass PNG bytes, 8 bits per channel, non-interlaced (e.g. re-save as RGBA8 PNG)"
|
|
889
|
+
});
|
|
890
|
+
}
|
|
891
|
+
function paeth(a, b, c) {
|
|
892
|
+
const p = a + b - c;
|
|
893
|
+
const pa = Math.abs(p - a);
|
|
894
|
+
const pb = Math.abs(p - b);
|
|
895
|
+
const pc = Math.abs(p - c);
|
|
896
|
+
return pa <= pb && pa <= pc ? a : pb <= pc ? b : c;
|
|
897
|
+
}
|
|
898
|
+
function decodePng(bytes) {
|
|
899
|
+
if (bytes.length < 33 || SIG.some((s, i) => bytes[i] !== s))
|
|
900
|
+
bad("not a PNG (bad signature)");
|
|
901
|
+
const dv = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
902
|
+
let width = 0;
|
|
903
|
+
let height = 0;
|
|
904
|
+
let type = -1;
|
|
905
|
+
let palette = new Uint8Array;
|
|
906
|
+
let trns = new Uint8Array;
|
|
907
|
+
const idat = [];
|
|
908
|
+
for (let o = 8;o + 8 <= bytes.length; ) {
|
|
909
|
+
const len = dv.getUint32(o);
|
|
910
|
+
const name = String.fromCharCode(...bytes.subarray(o + 4, o + 8));
|
|
911
|
+
const body = bytes.subarray(o + 8, o + 8 + len);
|
|
912
|
+
if (name === "IHDR") {
|
|
913
|
+
width = dv.getUint32(o + 8);
|
|
914
|
+
height = dv.getUint32(o + 12);
|
|
915
|
+
type = body[9] ?? -1;
|
|
916
|
+
if (body[8] !== 8)
|
|
917
|
+
bad(`bit depth ${body[8]} (only 8 is supported)`);
|
|
918
|
+
if (body[12] !== 0)
|
|
919
|
+
bad("interlaced");
|
|
920
|
+
} else if (name === "PLTE")
|
|
921
|
+
palette = body;
|
|
922
|
+
else if (name === "tRNS")
|
|
923
|
+
trns = body;
|
|
924
|
+
else if (name === "IDAT")
|
|
925
|
+
idat.push(body);
|
|
926
|
+
else if (name === "IEND")
|
|
927
|
+
break;
|
|
928
|
+
o += 12 + len;
|
|
929
|
+
}
|
|
930
|
+
const ch = CHANNELS[type];
|
|
931
|
+
if (!ch || width === 0 || height === 0)
|
|
932
|
+
bad(`colour type ${type} or empty image`);
|
|
933
|
+
const raw = new Uint8Array(inflateSync(Buffer.concat(idat)));
|
|
934
|
+
const stride = width * ch;
|
|
935
|
+
if (raw.length < height * (stride + 1))
|
|
936
|
+
bad("truncated image data");
|
|
937
|
+
const px = new Uint8Array(height * stride);
|
|
938
|
+
for (let y = 0;y < height; y++) {
|
|
939
|
+
const f = raw[y * (stride + 1)] ?? 0;
|
|
940
|
+
const src = y * (stride + 1) + 1;
|
|
941
|
+
const row = y * stride;
|
|
942
|
+
for (let x = 0;x < stride; x++) {
|
|
943
|
+
const a = x >= ch ? px[row + x - ch] ?? 0 : 0;
|
|
944
|
+
const b = y > 0 ? px[row - stride + x] ?? 0 : 0;
|
|
945
|
+
const c = x >= ch && y > 0 ? px[row - stride + x - ch] ?? 0 : 0;
|
|
946
|
+
const v = raw[src + x] ?? 0;
|
|
947
|
+
const pred = f === 1 ? a : f === 2 ? b : f === 3 ? a + b >> 1 : f === 4 ? paeth(a, b, c) : 0;
|
|
948
|
+
px[row + x] = v + pred & 255;
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
const data = new Uint8Array(width * height * 4);
|
|
952
|
+
for (let i = 0;i < width * height; i++) {
|
|
953
|
+
const s = i * ch;
|
|
954
|
+
const g = px[s] ?? 0;
|
|
955
|
+
const o = i * 4;
|
|
956
|
+
if (type === 3) {
|
|
957
|
+
data[o] = palette[g * 3] ?? 0;
|
|
958
|
+
data[o + 1] = palette[g * 3 + 1] ?? 0;
|
|
959
|
+
data[o + 2] = palette[g * 3 + 2] ?? 0;
|
|
960
|
+
data[o + 3] = trns[g] ?? 255;
|
|
961
|
+
} else if (ch >= 3) {
|
|
962
|
+
data[o] = g;
|
|
963
|
+
data[o + 1] = px[s + 1] ?? 0;
|
|
964
|
+
data[o + 2] = px[s + 2] ?? 0;
|
|
965
|
+
data[o + 3] = ch === 4 ? px[s + 3] ?? 255 : 255;
|
|
966
|
+
} else {
|
|
967
|
+
data[o] = g;
|
|
968
|
+
data[o + 1] = g;
|
|
969
|
+
data[o + 2] = g;
|
|
970
|
+
data[o + 3] = ch === 2 ? px[s + 1] ?? 255 : 255;
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
return { width, height, data };
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
// src/material/paint/stamp.ts
|
|
977
|
+
var STAMP_ROUGHNESS = 0.6;
|
|
978
|
+
function stampImage(c, png) {
|
|
979
|
+
const img = decodePng(png);
|
|
980
|
+
const out = new Uint8Array(c.w * c.h * 4);
|
|
981
|
+
for (let y = 0;y < c.h; y++) {
|
|
982
|
+
const sy = Math.min(img.height - 1, Math.max(0, (c.h - 1 - y + 0.5) / c.h * img.height - 0.5));
|
|
983
|
+
const y0 = Math.floor(sy);
|
|
984
|
+
const y1 = Math.min(img.height - 1, y0 + 1);
|
|
985
|
+
const fy = sy - y0;
|
|
986
|
+
for (let x = 0;x < c.w; x++) {
|
|
987
|
+
const sx = Math.min(img.width - 1, Math.max(0, (x + 0.5) / c.w * img.width - 0.5));
|
|
988
|
+
const x0 = Math.floor(sx);
|
|
989
|
+
const x1 = Math.min(img.width - 1, x0 + 1);
|
|
990
|
+
const fx = sx - x0;
|
|
991
|
+
for (let k = 0;k < 4; k++) {
|
|
992
|
+
const a = img.data[(y0 * img.width + x0) * 4 + k] ?? 0;
|
|
993
|
+
const b = img.data[(y0 * img.width + x1) * 4 + k] ?? 0;
|
|
994
|
+
const d = img.data[(y1 * img.width + x0) * 4 + k] ?? 0;
|
|
995
|
+
const e = img.data[(y1 * img.width + x1) * 4 + k] ?? 0;
|
|
996
|
+
const top = a + (b - a) * fx;
|
|
997
|
+
out[(y * c.w + x) * 4 + k] = Math.round(top + (d + (e - d) * fx - top) * fy);
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
c.rgba = out;
|
|
1002
|
+
c.height.fill(0);
|
|
1003
|
+
c.rough.fill(STAMP_ROUGHNESS);
|
|
1004
|
+
c.metal.fill(0);
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
// src/material/paint/courses.ts
|
|
1008
|
+
class Courses {
|
|
1009
|
+
id;
|
|
1010
|
+
dx;
|
|
1011
|
+
ux;
|
|
1012
|
+
index = -1;
|
|
1013
|
+
y0 = 0;
|
|
1014
|
+
y1 = 0;
|
|
1015
|
+
bounds = [0];
|
|
1016
|
+
c;
|
|
1017
|
+
spec;
|
|
1018
|
+
constructor(c, spec) {
|
|
1019
|
+
this.c = c;
|
|
1020
|
+
this.spec = spec;
|
|
1021
|
+
this.id = new Int32Array(c.w);
|
|
1022
|
+
this.dx = new Float32Array(c.w);
|
|
1023
|
+
this.ux = new Float32Array(c.w);
|
|
1024
|
+
const rng = createRng2(spec.seed).fork("rows");
|
|
1025
|
+
let y = 0;
|
|
1026
|
+
while (y < c.metresV) {
|
|
1027
|
+
y += spec.course * (1 + spec.irregular * (rng.next() - 0.5));
|
|
1028
|
+
this.bounds.push(y);
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
at(ym) {
|
|
1032
|
+
if (this.index >= 0 && ym >= this.y0 && ym < this.y1)
|
|
1033
|
+
return this.index;
|
|
1034
|
+
let k = 0;
|
|
1035
|
+
while (k < this.bounds.length - 2 && ym >= (this.bounds[k + 1] ?? 0))
|
|
1036
|
+
k++;
|
|
1037
|
+
this.index = k;
|
|
1038
|
+
this.y0 = this.bounds[k] ?? 0;
|
|
1039
|
+
this.y1 = this.bounds[k + 1] ?? this.c.metresV;
|
|
1040
|
+
this.fill(k);
|
|
1041
|
+
return k;
|
|
1042
|
+
}
|
|
1043
|
+
fill(k) {
|
|
1044
|
+
const { w, metresU } = this.c;
|
|
1045
|
+
const s = this.spec;
|
|
1046
|
+
const rng = createRng2(s.seed).fork(`course${k}`);
|
|
1047
|
+
const n = Math.max(1, Math.round(metresU / s.length));
|
|
1048
|
+
const lens = [];
|
|
1049
|
+
for (let i = 0;i < n; i++)
|
|
1050
|
+
lens.push(1 + s.irregular * (rng.next() - 0.5));
|
|
1051
|
+
const total = lens.reduce((a, b) => a + b, 0);
|
|
1052
|
+
const joints = [0];
|
|
1053
|
+
for (const l of lens)
|
|
1054
|
+
joints.push((joints[joints.length - 1] ?? 0) + l / total * metresU);
|
|
1055
|
+
const shift = s.irregular > 0 ? rng.next() * metresU : k % 2 * s.stagger * (metresU / n);
|
|
1056
|
+
const px = metresU / w;
|
|
1057
|
+
for (let x = 0;x < w; x++) {
|
|
1058
|
+
let u = ((x + 0.5) * px - shift) % metresU;
|
|
1059
|
+
if (u < 0)
|
|
1060
|
+
u += metresU;
|
|
1061
|
+
let t = 0;
|
|
1062
|
+
while (t < n - 1 && u >= (joints[t + 1] ?? metresU))
|
|
1063
|
+
t++;
|
|
1064
|
+
const a = joints[t] ?? 0;
|
|
1065
|
+
const b = joints[t + 1] ?? metresU;
|
|
1066
|
+
this.id[x] = t;
|
|
1067
|
+
this.ux[x] = u - a;
|
|
1068
|
+
this.dx[x] = Math.min(u - a, b - u);
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
// src/material/paint/bond.ts
|
|
1074
|
+
function paintBond(c, r, seed) {
|
|
1075
|
+
const p = r.params;
|
|
1076
|
+
const mortar = param(p, "mortar", 0.01);
|
|
1077
|
+
const depth = param(p, "depth", 0.006);
|
|
1078
|
+
const bevel = param(p, "bevel", 0.004);
|
|
1079
|
+
const relief = param(p, "relief", 0.0008);
|
|
1080
|
+
const variation = param(p, "variation", 0.3);
|
|
1081
|
+
const course = param(p, "course", 0.075);
|
|
1082
|
+
const length = param(p, "length", 0.225);
|
|
1083
|
+
const courses = new Courses(c, {
|
|
1084
|
+
course,
|
|
1085
|
+
length,
|
|
1086
|
+
irregular: param(p, "irregular", 0),
|
|
1087
|
+
stagger: param(p, "stagger", 0.5),
|
|
1088
|
+
seed
|
|
1089
|
+
});
|
|
1090
|
+
const pu = c.metresU / c.w;
|
|
1091
|
+
const pv = c.metresV / c.h;
|
|
1092
|
+
const half = mortar / 2;
|
|
1093
|
+
for (let y = 0;y < c.h; y++) {
|
|
1094
|
+
const ym = (y + 0.5) * pv;
|
|
1095
|
+
const k = courses.at(ym);
|
|
1096
|
+
const dy = Math.min(ym - courses.y0, courses.y1 - ym);
|
|
1097
|
+
for (let x = 0;x < c.w; x++) {
|
|
1098
|
+
const i = y * c.w + x;
|
|
1099
|
+
const xm = (x + 0.5) * pu;
|
|
1100
|
+
const unit = courses.id[x] ?? 0;
|
|
1101
|
+
const chip = noiseAt(c, xm, ym, 0.02, 0.02, seed + 7, 3) * mortar * 0.6;
|
|
1102
|
+
const d = Math.min(courses.dx[x] ?? 0, dy) + chip;
|
|
1103
|
+
const fine = noiseAt(c, xm, ym, 0.006, 0.006, seed + 11, 2);
|
|
1104
|
+
if (d < half) {
|
|
1105
|
+
c.height[i] = -depth + fine * 0.0006;
|
|
1106
|
+
c.tone[i] = 0.95 + 0.04 * fine;
|
|
1107
|
+
c.rough[i] = Math.min(1, r.roughness + 0.08);
|
|
1108
|
+
continue;
|
|
1109
|
+
}
|
|
1110
|
+
const h = hash2(k, unit, seed);
|
|
1111
|
+
const h2 = hash2(unit, k, seed + 3);
|
|
1112
|
+
const big = noiseAt(c, xm, ym, length * 0.6, course * 0.8, seed + 5, 3);
|
|
1113
|
+
const edge = smooth(0, bevel, d - half);
|
|
1114
|
+
const tilt = ((h2 - 0.5) * (courses.ux[x] ?? 0) * 0.02 + (h - 0.5) * 0.001) * edge;
|
|
1115
|
+
const pit = fine > 0.55 ? (fine - 0.55) * 0.004 : 0;
|
|
1116
|
+
c.height[i] = -depth * (1 - edge) + relief * big + tilt - pit;
|
|
1117
|
+
c.tone[i] = (0.62 - variation * 0.5 + variation * h) * (1 + 0.15 * big + 0.05 * fine) - pit * 40;
|
|
1118
|
+
c.rough[i] = r.roughness + 0.06 * fine;
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
// src/material/paint/concrete.ts
|
|
1124
|
+
var PORE_CELL = 0.008;
|
|
1125
|
+
function paintConcrete(c, r, seed) {
|
|
1126
|
+
const p = r.params;
|
|
1127
|
+
const pores = param(p, "pores", 0.35);
|
|
1128
|
+
const ties = param(p, "ties", 0.6);
|
|
1129
|
+
const lift = param(p, "lift", 0.6);
|
|
1130
|
+
const mottle = param(p, "mottle", 0.1);
|
|
1131
|
+
const pu = c.metresU / c.w;
|
|
1132
|
+
const pv = c.metresV / c.h;
|
|
1133
|
+
const cellM = Math.max(PORE_CELL, 2.5 * Math.max(pu, pv));
|
|
1134
|
+
const pPer = periodFor(c, cellM);
|
|
1135
|
+
const pCell = c.wrapU ? c.metresU / pPer : cellM;
|
|
1136
|
+
const tPer = ties > 0 ? periodFor(c, ties) : 1;
|
|
1137
|
+
const tCell = c.wrapU ? c.metresU / tPer : ties;
|
|
1138
|
+
for (let y = 0;y < c.h; y++) {
|
|
1139
|
+
const ym = (y + 0.5) * pv;
|
|
1140
|
+
const liftD = lift > 0 ? Math.abs(ym - Math.round(ym / lift) * lift) : 1;
|
|
1141
|
+
const liftGroove = 1 - smooth(0, 0.003, liftD);
|
|
1142
|
+
const liftBand = lift > 0 ? hash2(Math.floor(ym / lift), 0, seed + 4) - 0.5 : 0;
|
|
1143
|
+
for (let x = 0;x < c.w; x++) {
|
|
1144
|
+
const i = y * c.w + x;
|
|
1145
|
+
const xm = (x + 0.5) * pu;
|
|
1146
|
+
const big = noiseAt(c, xm, ym, 0.25, 0.25, seed, 4);
|
|
1147
|
+
const fine = noiseAt(c, xm, ym, 0.004, 0.004, seed + 2, 2);
|
|
1148
|
+
const ix = Math.floor(xm / pCell) % pPer;
|
|
1149
|
+
const iy = Math.floor(ym / pCell);
|
|
1150
|
+
let pit = 0;
|
|
1151
|
+
if (hash2(ix, iy, seed + 5) < pores) {
|
|
1152
|
+
const cx = (ix + 0.2 + 0.6 * hash2(ix, iy, seed + 6)) * pCell;
|
|
1153
|
+
const cy = (iy + 0.2 + 0.6 * hash2(ix, iy, seed + 7)) * pCell;
|
|
1154
|
+
const rad = pCell * (0.12 + 0.25 * hash2(ix, iy, seed + 8));
|
|
1155
|
+
const d = Math.hypot(xm - cx, ym - cy);
|
|
1156
|
+
pit = d < rad ? 1 - d / rad : 0;
|
|
1157
|
+
}
|
|
1158
|
+
let tie = 0;
|
|
1159
|
+
if (ties > 0) {
|
|
1160
|
+
const tx = xm - (Math.floor(xm / tCell) + 0.5) * tCell;
|
|
1161
|
+
const ty = ym - (Math.floor(ym / ties) + 0.5) * ties;
|
|
1162
|
+
const d = Math.hypot(tx, ty);
|
|
1163
|
+
tie = d < 0.012 ? 1 : 0;
|
|
1164
|
+
}
|
|
1165
|
+
c.height[i] = 0.0005 * big + 0.00015 * fine - 0.0015 * pit - 0.006 * tie - 0.0012 * liftGroove;
|
|
1166
|
+
c.tone[i] = (0.78 + mottle * big + 0.05 * liftBand) * (1 + 0.04 * fine) * (1 - 0.4 * pit) * (1 - 0.5 * tie);
|
|
1167
|
+
c.rough[i] = r.roughness + 0.05 * fine + 0.05 * big;
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
// src/material/paint/masks.ts
|
|
1173
|
+
function rustStreaks(c, amount, seed) {
|
|
1174
|
+
if (amount <= 0)
|
|
1175
|
+
return;
|
|
1176
|
+
const pu = c.metresU / c.w;
|
|
1177
|
+
const pv = c.metresV / c.h;
|
|
1178
|
+
for (let y = 0;y < c.h; y++) {
|
|
1179
|
+
const ym = (y + 0.5) * pv;
|
|
1180
|
+
for (let x = 0;x < c.w; x++) {
|
|
1181
|
+
const xm = (x + 0.5) * pu;
|
|
1182
|
+
const run = noiseAt(c, xm, ym, 0.025, 0.6, seed, 3);
|
|
1183
|
+
const patch = noiseAt(c, xm, ym, 0.3, 0.3, seed + 1, 2);
|
|
1184
|
+
const m = amount * smooth(0.15, 0.6, run) * smooth(-0.1, 0.4, patch);
|
|
1185
|
+
if (m <= 0)
|
|
1186
|
+
continue;
|
|
1187
|
+
const i = y * c.w + x;
|
|
1188
|
+
c.tone[i] = (c.tone[i] ?? 1) * (1 - 0.4 * m);
|
|
1189
|
+
c.rough[i] = Math.min(1, (c.rough[i] ?? 0.8) + 0.2 * m);
|
|
1190
|
+
c.metal[i] = (c.metal[i] ?? 0) * (1 - m);
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
}
|
|
1194
|
+
function grime(c, amount, seed) {
|
|
1195
|
+
if (amount <= 0)
|
|
1196
|
+
return;
|
|
1197
|
+
const pu = c.metresU / c.w;
|
|
1198
|
+
const pv = c.metresV / c.h;
|
|
1199
|
+
for (let y = 0;y < c.h; y++) {
|
|
1200
|
+
const ym = (y + 0.5) * pv;
|
|
1201
|
+
const low = (1 - ym / c.metresV) ** 2;
|
|
1202
|
+
for (let x = 0;x < c.w; x++) {
|
|
1203
|
+
const xm = (x + 0.5) * pu;
|
|
1204
|
+
const streak = smooth(0, 0.7, noiseAt(c, xm, ym, 0.05, 0.5, seed, 3));
|
|
1205
|
+
const blot = 0.6 + 0.4 * noiseAt(c, xm, ym, 0.2, 0.2, seed + 1, 3);
|
|
1206
|
+
const m = amount * Math.min(1, low * blot + 0.35 * streak);
|
|
1207
|
+
const i = y * c.w + x;
|
|
1208
|
+
c.tone[i] = (c.tone[i] ?? 1) * (1 - 0.3 * m);
|
|
1209
|
+
c.rough[i] = Math.min(1, (c.rough[i] ?? 0.8) + 0.1 * m);
|
|
1210
|
+
}
|
|
1211
|
+
}
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
// src/material/paint/planks.ts
|
|
1215
|
+
function paintPlanks(c, r, seed) {
|
|
1216
|
+
const p = r.params;
|
|
1217
|
+
const width = param(p, "width", 0.15);
|
|
1218
|
+
const length = param(p, "length", 1);
|
|
1219
|
+
const gap = param(p, "gap", 0.004);
|
|
1220
|
+
const depth = param(p, "depth", 0.004);
|
|
1221
|
+
const knots = param(p, "knots", 1.5);
|
|
1222
|
+
const boards = new Courses(c, { course: width, length, irregular: 0.6, stagger: 0, seed });
|
|
1223
|
+
const pu = c.metresU / c.w;
|
|
1224
|
+
const pv = c.metresV / c.h;
|
|
1225
|
+
for (let y = 0;y < c.h; y++) {
|
|
1226
|
+
const ym = (y + 0.5) * pv;
|
|
1227
|
+
const k = boards.at(ym);
|
|
1228
|
+
const dy = Math.min(ym - boards.y0, boards.y1 - ym);
|
|
1229
|
+
const vy = (ym - boards.y0) / (boards.y1 - boards.y0);
|
|
1230
|
+
for (let x = 0;x < c.w; x++) {
|
|
1231
|
+
const i = y * c.w + x;
|
|
1232
|
+
const xm = (x + 0.5) * pu;
|
|
1233
|
+
const id = boards.id[x] ?? 0;
|
|
1234
|
+
const h = hash2(k, id, seed);
|
|
1235
|
+
const d = Math.min((boards.dx[x] ?? 0) * 1.5, dy);
|
|
1236
|
+
const edge = smooth(gap * 0.5, gap * 1.5, d);
|
|
1237
|
+
const warp = noiseAt(c, xm, ym, 0.4, 0.05, seed + id * 31 + k * 7, 3);
|
|
1238
|
+
const ring = Math.sin((vy * 9 + warp * 3 + h * 6) * Math.PI * 2);
|
|
1239
|
+
const fibre = noiseAt(c, xm, ym, 0.08, 0.002, seed + 17, 2);
|
|
1240
|
+
let knot = 0;
|
|
1241
|
+
const nk = Math.floor(knots * length * h * 2);
|
|
1242
|
+
for (let q = 0;q < nk; q++) {
|
|
1243
|
+
const kx = hash2(k * 13 + q, id, seed + 41) * c.metresU;
|
|
1244
|
+
const ky = boards.y0 + (0.25 + 0.5 * hash2(k, id * 5 + q, seed + 43)) * (boards.y1 - boards.y0);
|
|
1245
|
+
let ddx = Math.abs(xm - kx);
|
|
1246
|
+
if (c.wrapU)
|
|
1247
|
+
ddx = Math.min(ddx, c.metresU - ddx);
|
|
1248
|
+
const rr = Math.sqrt((ddx / 2.2) ** 2 + (ym - ky) ** 2) / (width * 0.12);
|
|
1249
|
+
if (rr < 1)
|
|
1250
|
+
knot = Math.max(knot, 1 - rr);
|
|
1251
|
+
}
|
|
1252
|
+
c.height[i] = -depth * (1 - edge) + 0.0004 * ring * edge + 0.0003 * fibre - 0.0008 * knot;
|
|
1253
|
+
c.tone[i] = (0.66 + 0.22 * h) * (1 + 0.08 * ring + 0.06 * fibre) * (1 - 0.5 * knot) * (0.5 + 0.5 * edge);
|
|
1254
|
+
c.rough[i] = r.roughness + 0.05 * fibre + 0.1 * (1 - edge);
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1259
|
+
// src/material/paint/plates.ts
|
|
1260
|
+
function along(q, pitch) {
|
|
1261
|
+
const t = q / pitch;
|
|
1262
|
+
return Math.abs(t - Math.round(t)) * pitch;
|
|
1263
|
+
}
|
|
1264
|
+
function paintPlates(c, r, seed) {
|
|
1265
|
+
const p = r.params;
|
|
1266
|
+
const lap = param(p, "lap", 0.003);
|
|
1267
|
+
const rivet = param(p, "rivet", 0.014);
|
|
1268
|
+
const pitch = param(p, "pitch", 0.09);
|
|
1269
|
+
const inset = param(p, "inset", 0.05);
|
|
1270
|
+
const chips = param(p, "chips", 0.3);
|
|
1271
|
+
const metal = r.metalness ?? 0;
|
|
1272
|
+
const plates = new Courses(c, {
|
|
1273
|
+
course: param(p, "height", 0.5),
|
|
1274
|
+
length: param(p, "width", 1),
|
|
1275
|
+
irregular: 0,
|
|
1276
|
+
stagger: 0.5,
|
|
1277
|
+
seed
|
|
1278
|
+
});
|
|
1279
|
+
const pu = c.metresU / c.w;
|
|
1280
|
+
const pv = c.metresV / c.h;
|
|
1281
|
+
for (let y = 0;y < c.h; y++) {
|
|
1282
|
+
const ym = (y + 0.5) * pv;
|
|
1283
|
+
const k = plates.at(ym);
|
|
1284
|
+
const dy = Math.min(ym - plates.y0, plates.y1 - ym);
|
|
1285
|
+
for (let x = 0;x < c.w; x++) {
|
|
1286
|
+
const i = y * c.w + x;
|
|
1287
|
+
const xm = (x + 0.5) * pu;
|
|
1288
|
+
const ux = plates.ux[x] ?? 0;
|
|
1289
|
+
const dx = plates.dx[x] ?? 0;
|
|
1290
|
+
const id = plates.id[x] ?? 0;
|
|
1291
|
+
const hammer = noiseAt(c, xm, ym, 0.05, 0.05, seed + 3, 3);
|
|
1292
|
+
const fine = noiseAt(c, xm, ym, 0.004, 0.004, seed + 9, 2);
|
|
1293
|
+
const seam = Math.min(dx, dy);
|
|
1294
|
+
let h = lap * smooth(0, 0.004, ux) + 0.0004 * hammer - 0.001 * (1 - smooth(0, 0.0015, seam));
|
|
1295
|
+
const ry = Math.abs(dy - inset);
|
|
1296
|
+
const rx = Math.abs(dx - inset);
|
|
1297
|
+
const d1 = Math.sqrt(rx * rx + along(ym - plates.y0, pitch) ** 2);
|
|
1298
|
+
const d2 = Math.sqrt(ry * ry + along(xm, pitch) ** 2);
|
|
1299
|
+
const d = Math.min(dx > inset * 2 ? 1 : d1, dy > inset * 2 ? 1 : d2);
|
|
1300
|
+
const dome = d < rivet ? Math.sqrt(1 - (d / rivet) ** 2) : 0;
|
|
1301
|
+
h += dome * rivet * 0.5;
|
|
1302
|
+
const chip = smooth(0.35, 0.45, noiseAt(c, xm, ym, 0.03, 0.03, seed + 21, 4) - (seam * 6 + 0.2) + chips * 0.3);
|
|
1303
|
+
c.height[i] = h - chip * 0.0003;
|
|
1304
|
+
c.tone[i] = (0.8 + 0.06 * hash2(k, id, seed)) * (1 + 0.06 * hammer + 0.03 * fine) * (1 + 0.15 * dome);
|
|
1305
|
+
c.rough[i] = r.roughness + 0.08 * fine - 0.25 * chip;
|
|
1306
|
+
c.metal[i] = metal + (1 - metal) * chip;
|
|
1307
|
+
}
|
|
1308
|
+
}
|
|
1309
|
+
rustStreaks(c, param(p, "rust", 0.4), seed + 77);
|
|
1310
|
+
}
|
|
1311
|
+
|
|
1312
|
+
// src/material/paint/slate.ts
|
|
1313
|
+
function paintSlate(c, r, seed) {
|
|
1314
|
+
const p = r.params;
|
|
1315
|
+
const course = param(p, "course", 0.11);
|
|
1316
|
+
const gap = param(p, "gap", 0.003);
|
|
1317
|
+
const thick = param(p, "thick", 0.007);
|
|
1318
|
+
const variation = param(p, "variation", 0.3);
|
|
1319
|
+
const rows = new Courses(c, {
|
|
1320
|
+
course,
|
|
1321
|
+
length: param(p, "length", 0.25),
|
|
1322
|
+
irregular: 0.15,
|
|
1323
|
+
stagger: 0.5,
|
|
1324
|
+
seed
|
|
1325
|
+
});
|
|
1326
|
+
const pu = c.metresU / c.w;
|
|
1327
|
+
const pv = c.metresV / c.h;
|
|
1328
|
+
for (let y = 0;y < c.h; y++) {
|
|
1329
|
+
const ym = (y + 0.5) * pv;
|
|
1330
|
+
const k = rows.at(ym);
|
|
1331
|
+
const span = rows.y1 - rows.y0;
|
|
1332
|
+
for (let x = 0;x < c.w; x++) {
|
|
1333
|
+
const i = y * c.w + x;
|
|
1334
|
+
const xm = (x + 0.5) * pu;
|
|
1335
|
+
const id = rows.id[x] ?? 0;
|
|
1336
|
+
const h = hash2(k, id, seed);
|
|
1337
|
+
const chip = (noiseAt(c, xm, ym, 0.015, 0.01, seed + 5, 3) + 0.3) * span * 0.08;
|
|
1338
|
+
const uy = Math.max(0, ym - rows.y0 - Math.max(0, chip));
|
|
1339
|
+
const t = uy / span;
|
|
1340
|
+
const lam = noiseAt(c, xm, ym, 0.12, 0.004, seed + 9, 3);
|
|
1341
|
+
const fine = noiseAt(c, xm, ym, 0.005, 0.005, seed + 13, 2);
|
|
1342
|
+
const sideGap = smooth(gap * 0.5, gap * 1.2, rows.dx[x] ?? 0);
|
|
1343
|
+
const bevel = smooth(0, 0.004, uy);
|
|
1344
|
+
const tilt = (hash2(id, k, seed + 1) - 0.5) * 0.002 * ((rows.ux[x] ?? 0) / 0.25);
|
|
1345
|
+
c.height[i] = (thick * (1 - t) * bevel + 0.0005 * lam + tilt) * sideGap - thick * (1 - sideGap);
|
|
1346
|
+
c.tone[i] = (0.62 - variation * 0.5 + variation * h) * (1 + 0.08 * lam + 0.04 * fine) * (0.55 + 0.45 * sideGap);
|
|
1347
|
+
c.rough[i] = r.roughness + 0.08 * lam + 0.05 * fine;
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1352
|
+
// src/material/paint/surface.ts
|
|
1353
|
+
function paintFlat(c, r) {
|
|
1354
|
+
c.tone.fill(param(r.params, "tone", 1));
|
|
1355
|
+
c.rough.fill(r.roughness);
|
|
1356
|
+
c.metal.fill(r.metalness ?? 0);
|
|
1357
|
+
c.height.fill(0);
|
|
1358
|
+
}
|
|
1359
|
+
function paintNoise(c, r, seed) {
|
|
1360
|
+
const tone = param(r.params, "tone", 0.93);
|
|
1361
|
+
const mottle = param(r.params, "mottle", 0.03);
|
|
1362
|
+
const relief = param(r.params, "relief", 0.0002);
|
|
1363
|
+
const size = param(r.params, "size", 0.3);
|
|
1364
|
+
const pu = c.metresU / c.w;
|
|
1365
|
+
const pv = c.metresV / c.h;
|
|
1366
|
+
const metal = r.metalness ?? 0;
|
|
1367
|
+
for (let y = 0;y < c.h; y++) {
|
|
1368
|
+
const ym = (y + 0.5) * pv;
|
|
1369
|
+
for (let x = 0;x < c.w; x++) {
|
|
1370
|
+
const xm = (x + 0.5) * pu;
|
|
1371
|
+
const big = noiseAt(c, xm, ym, size, size, seed, 4);
|
|
1372
|
+
const fine = noiseAt(c, xm, ym, 0.01, 0.01, seed + 1, 2);
|
|
1373
|
+
const i = y * c.w + x;
|
|
1374
|
+
c.tone[i] = tone + mottle * big;
|
|
1375
|
+
c.rough[i] = r.roughness + 0.06 * big + 0.03 * fine;
|
|
1376
|
+
c.metal[i] = metal;
|
|
1377
|
+
c.height[i] = relief * (big + 0.3 * fine);
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1380
|
+
}
|
|
1381
|
+
function paintGlass(c, r, seed) {
|
|
1382
|
+
const ripple = param(r.params, "ripple", 0.0002);
|
|
1383
|
+
const smudge = param(r.params, "smudge", 0.08);
|
|
1384
|
+
const pu = c.metresU / c.w;
|
|
1385
|
+
const pv = c.metresV / c.h;
|
|
1386
|
+
for (let y = 0;y < c.h; y++) {
|
|
1387
|
+
const ym = (y + 0.5) * pv;
|
|
1388
|
+
for (let x = 0;x < c.w; x++) {
|
|
1389
|
+
const xm = (x + 0.5) * pu;
|
|
1390
|
+
const wave = noiseAt(c, xm, ym, 0.15, 0.4, seed, 3);
|
|
1391
|
+
const smear = noiseAt(c, xm, ym, 0.08, 0.08, seed + 1, 4);
|
|
1392
|
+
const i = y * c.w + x;
|
|
1393
|
+
c.tone[i] = 0.92 + 0.02 * wave;
|
|
1394
|
+
c.rough[i] = r.roughness + smudge * Math.max(0, smear);
|
|
1395
|
+
c.metal[i] = 0;
|
|
1396
|
+
c.height[i] = ripple * wave;
|
|
1397
|
+
}
|
|
1398
|
+
}
|
|
1399
|
+
grime(c, param(r.params, "grime", 0.4), seed + 9);
|
|
1400
|
+
}
|
|
1401
|
+
|
|
1402
|
+
// src/material/paint/strip.ts
|
|
1403
|
+
function paintStrip(c, r, seed) {
|
|
1404
|
+
switch (r.kind) {
|
|
1405
|
+
case "bond":
|
|
1406
|
+
paintBond(c, r, seed);
|
|
1407
|
+
break;
|
|
1408
|
+
case "planks":
|
|
1409
|
+
paintPlanks(c, r, seed);
|
|
1410
|
+
break;
|
|
1411
|
+
case "plates":
|
|
1412
|
+
paintPlates(c, r, seed);
|
|
1413
|
+
break;
|
|
1414
|
+
case "slate":
|
|
1415
|
+
paintSlate(c, r, seed);
|
|
1416
|
+
break;
|
|
1417
|
+
case "concrete":
|
|
1418
|
+
paintConcrete(c, r, seed);
|
|
1419
|
+
break;
|
|
1420
|
+
case "glass":
|
|
1421
|
+
paintGlass(c, r, seed);
|
|
1422
|
+
return;
|
|
1423
|
+
case "noise":
|
|
1424
|
+
paintNoise(c, r, seed);
|
|
1425
|
+
break;
|
|
1426
|
+
case "flat":
|
|
1427
|
+
paintFlat(c, r);
|
|
1428
|
+
return;
|
|
1429
|
+
}
|
|
1430
|
+
grime(c, param(r.params, "grime", 0), seed + 101);
|
|
1431
|
+
}
|
|
1432
|
+
|
|
1433
|
+
// src/material/paint/cell.ts
|
|
1434
|
+
var MOTIFS = {
|
|
1435
|
+
window_sash: paintWindowSash,
|
|
1436
|
+
door_panel: paintDoorPanel,
|
|
1437
|
+
clock: paintClock,
|
|
1438
|
+
smokebox: paintSmokebox,
|
|
1439
|
+
sign: paintSign,
|
|
1440
|
+
porthole: paintPorthole
|
|
1441
|
+
};
|
|
1442
|
+
var LEAF_ROUGHNESS = 0.6;
|
|
1443
|
+
function paintCell(c, id, entry, seed) {
|
|
1444
|
+
if ("image" in entry) {
|
|
1445
|
+
stampImage(c, entry.image);
|
|
1446
|
+
return;
|
|
1447
|
+
}
|
|
1448
|
+
if (isFoliage2(id)) {
|
|
1449
|
+
FOLIAGE_PAINTERS[id](c, seed);
|
|
1450
|
+
const k = entry.roughness / LEAF_ROUGHNESS;
|
|
1451
|
+
for (let i = 0;i < c.rough.length; i++)
|
|
1452
|
+
c.rough[i] = Math.min(1, (c.rough[i] ?? 0.6) * k);
|
|
1453
|
+
return;
|
|
1454
|
+
}
|
|
1455
|
+
paintStrip(c, entry, seed);
|
|
1456
|
+
MOTIFS[id]?.(c, entry, seed + 1);
|
|
1457
|
+
}
|
|
1458
|
+
|
|
1459
|
+
// src/material/paint/normal.ts
|
|
1460
|
+
function neighbours(n, wrap) {
|
|
1461
|
+
const lo = new Int32Array(n);
|
|
1462
|
+
const hi = new Int32Array(n);
|
|
1463
|
+
for (let i = 0;i < n; i++) {
|
|
1464
|
+
lo[i] = i > 0 ? i - 1 : wrap ? n - 1 : 0;
|
|
1465
|
+
hi[i] = i < n - 1 ? i + 1 : wrap ? 0 : n - 1;
|
|
1466
|
+
}
|
|
1467
|
+
return { lo, hi };
|
|
1468
|
+
}
|
|
1469
|
+
function sobelNormals(c, strength = 1) {
|
|
1470
|
+
const { w, h, height } = c;
|
|
1471
|
+
const out = new Uint8Array(w * h * 4);
|
|
1472
|
+
const cx = neighbours(w, c.wrapU);
|
|
1473
|
+
const cy = neighbours(h, false);
|
|
1474
|
+
const sx = strength / (8 * (c.metresU / w));
|
|
1475
|
+
const sy = strength / (8 * (c.metresV / h));
|
|
1476
|
+
for (let y = 0;y < h; y++) {
|
|
1477
|
+
const r0 = (cy.lo[y] ?? 0) * w;
|
|
1478
|
+
const r1 = y * w;
|
|
1479
|
+
const r2 = (cy.hi[y] ?? 0) * w;
|
|
1480
|
+
for (let x = 0;x < w; x++) {
|
|
1481
|
+
const xl = cx.lo[x] ?? 0;
|
|
1482
|
+
const xr = cx.hi[x] ?? 0;
|
|
1483
|
+
const tl = height[r0 + xl] ?? 0;
|
|
1484
|
+
const tc = height[r0 + x] ?? 0;
|
|
1485
|
+
const tr = height[r0 + xr] ?? 0;
|
|
1486
|
+
const ml = height[r1 + xl] ?? 0;
|
|
1487
|
+
const mr = height[r1 + xr] ?? 0;
|
|
1488
|
+
const bl = height[r2 + xl] ?? 0;
|
|
1489
|
+
const bc = height[r2 + x] ?? 0;
|
|
1490
|
+
const br = height[r2 + xr] ?? 0;
|
|
1491
|
+
const gx = (tr + 2 * mr + br - tl - 2 * ml - bl) * sx;
|
|
1492
|
+
const gy = (bl + 2 * bc + br - tl - 2 * tc - tr) * sy;
|
|
1493
|
+
const inv = 1 / Math.sqrt(gx * gx + gy * gy + 1);
|
|
1494
|
+
const o = (r1 + x) * 4;
|
|
1495
|
+
out[o] = Math.round((-gx * inv * 0.5 + 0.5) * 255);
|
|
1496
|
+
out[o + 1] = Math.round((gy * inv * 0.5 + 0.5) * 255);
|
|
1497
|
+
out[o + 2] = Math.round((inv * 0.5 + 0.5) * 255);
|
|
1498
|
+
out[o + 3] = 255;
|
|
1499
|
+
}
|
|
1500
|
+
}
|
|
1501
|
+
return out;
|
|
1502
|
+
}
|
|
1503
|
+
|
|
1504
|
+
// src/material/paint/compose.ts
|
|
1505
|
+
function blankSheet(size) {
|
|
1506
|
+
const n = size * size * 4;
|
|
1507
|
+
const albedo = new Uint8Array(n).fill(255);
|
|
1508
|
+
const normal = new Uint8Array(n);
|
|
1509
|
+
const orm = new Uint8Array(n);
|
|
1510
|
+
const flat = new Uint8Array([128, 128, 255, 255]);
|
|
1511
|
+
const base = new Uint8Array([255, 204, 0, 255]);
|
|
1512
|
+
for (let i = 0;i < n; i += 4) {
|
|
1513
|
+
normal.set(flat, i);
|
|
1514
|
+
orm.set(base, i);
|
|
1515
|
+
}
|
|
1516
|
+
return { size, albedo, normal, orm };
|
|
1517
|
+
}
|
|
1518
|
+
function composeRegion(sheet, c, x0, y0, pad) {
|
|
1519
|
+
const ao = cavityOcclusion(c);
|
|
1520
|
+
const maps = [
|
|
1521
|
+
[sheet.albedo, packAlbedo(c, ao)],
|
|
1522
|
+
[sheet.normal, sobelNormals(c)],
|
|
1523
|
+
[sheet.orm, packOrm(c, ao)]
|
|
1524
|
+
];
|
|
1525
|
+
const padX = c.wrapU ? 0 : pad;
|
|
1526
|
+
for (let y = 0;y < c.h; y++) {
|
|
1527
|
+
const sy = Math.min(c.h - 1 - pad, Math.max(pad, y));
|
|
1528
|
+
const dst = ((y0 + y) * sheet.size + x0) * 4;
|
|
1529
|
+
const src = sy * c.w;
|
|
1530
|
+
const l = (src + padX) * 4;
|
|
1531
|
+
const r = (src + c.w - 1 - padX) * 4;
|
|
1532
|
+
for (const [to, from] of maps) {
|
|
1533
|
+
to.set(from.subarray(l, r + 4), dst + padX * 4);
|
|
1534
|
+
for (let x = 0;x < padX; x++) {
|
|
1535
|
+
to.set(from.subarray(l, l + 4), dst + x * 4);
|
|
1536
|
+
to.set(from.subarray(r, r + 4), dst + (c.w - 1 - x) * 4);
|
|
1537
|
+
}
|
|
1538
|
+
}
|
|
1539
|
+
}
|
|
1540
|
+
}
|
|
1541
|
+
|
|
1542
|
+
// src/material/paint/default-sheet.ts
|
|
1543
|
+
var leaf = { kind: "flat", scaleMetres: 1, roughness: 0.6 };
|
|
1544
|
+
var DEFAULT_SHEET2 = {
|
|
1545
|
+
seed: 1,
|
|
1546
|
+
size: 1024,
|
|
1547
|
+
strips: {
|
|
1548
|
+
plain: { kind: "noise", scaleMetres: 1, roughness: 0.6 },
|
|
1549
|
+
brick: { kind: "bond", scaleMetres: 0.45, roughness: 0.85, params: { grime: 0.2 } },
|
|
1550
|
+
stone: {
|
|
1551
|
+
kind: "bond",
|
|
1552
|
+
scaleMetres: 0.9,
|
|
1553
|
+
roughness: 0.9,
|
|
1554
|
+
params: {
|
|
1555
|
+
course: 0.22,
|
|
1556
|
+
length: 0.4,
|
|
1557
|
+
mortar: 0.016,
|
|
1558
|
+
depth: 0.012,
|
|
1559
|
+
bevel: 0.025,
|
|
1560
|
+
irregular: 0.5,
|
|
1561
|
+
relief: 0.004,
|
|
1562
|
+
variation: 0.3,
|
|
1563
|
+
grime: 0.2
|
|
1564
|
+
}
|
|
1565
|
+
},
|
|
1566
|
+
slate: { kind: "slate", scaleMetres: 0.66, roughness: 0.7 },
|
|
1567
|
+
iron: { kind: "plates", scaleMetres: 0.5, roughness: 0.55, metalness: 0 },
|
|
1568
|
+
wood: { kind: "planks", scaleMetres: 0.6, roughness: 0.75 },
|
|
1569
|
+
glass: { kind: "glass", scaleMetres: 1, roughness: 0.05 },
|
|
1570
|
+
concrete: { kind: "concrete", scaleMetres: 1, roughness: 0.9, params: { grime: 0.3 } }
|
|
1571
|
+
},
|
|
1572
|
+
cells: {
|
|
1573
|
+
window_sash: { kind: "noise", scaleMetres: 1, roughness: 0.5 },
|
|
1574
|
+
door_panel: { kind: "noise", scaleMetres: 1, roughness: 0.6 },
|
|
1575
|
+
clock: { kind: "noise", scaleMetres: 1, roughness: 0.3 },
|
|
1576
|
+
smokebox: {
|
|
1577
|
+
kind: "plates",
|
|
1578
|
+
scaleMetres: 1,
|
|
1579
|
+
roughness: 0.6,
|
|
1580
|
+
metalness: 0.2,
|
|
1581
|
+
params: { rust: 0.3 }
|
|
1582
|
+
},
|
|
1583
|
+
sign: { kind: "noise", scaleMetres: 1, roughness: 0.5 },
|
|
1584
|
+
porthole: { kind: "plates", scaleMetres: 1, roughness: 0.5 },
|
|
1585
|
+
foliage_broadleaf: leaf,
|
|
1586
|
+
foliage_maple: leaf,
|
|
1587
|
+
foliage_maple_autumn: leaf,
|
|
1588
|
+
foliage_birch: leaf,
|
|
1589
|
+
foliage_acacia: leaf,
|
|
1590
|
+
foliage_palm: leaf,
|
|
1591
|
+
foliage_spruce: leaf,
|
|
1592
|
+
foliage_spruce_snow: leaf,
|
|
1593
|
+
foliage_willow: leaf,
|
|
1594
|
+
foliage_reeds: leaf,
|
|
1595
|
+
foliage_needles: leaf
|
|
1596
|
+
}
|
|
1597
|
+
};
|
|
1598
|
+
|
|
1599
|
+
// src/material/paint/validate.ts
|
|
1600
|
+
var KINDS = [
|
|
1601
|
+
"bond",
|
|
1602
|
+
"planks",
|
|
1603
|
+
"plates",
|
|
1604
|
+
"noise",
|
|
1605
|
+
"flat",
|
|
1606
|
+
"slate",
|
|
1607
|
+
"concrete",
|
|
1608
|
+
"glass"
|
|
1609
|
+
];
|
|
1610
|
+
var SIZES = [512, 1024, 2048];
|
|
1611
|
+
function fail(cause, fix) {
|
|
1612
|
+
throw new Ts23dError2("T3D_RECIPE_INVALID", { cause: `sheet recipe: ${cause}`, fix });
|
|
1613
|
+
}
|
|
1614
|
+
var unit = (v) => Number.isFinite(v) && v >= 0 && v <= 1;
|
|
1615
|
+
function validateStrip(where, r) {
|
|
1616
|
+
if (!KINDS.includes(r.kind))
|
|
1617
|
+
fail(`${where}.kind '${r.kind}' is unknown`, `use one of ${KINDS.join(" | ")}`);
|
|
1618
|
+
if (!(Number.isFinite(r.scaleMetres) && r.scaleMetres > 0))
|
|
1619
|
+
fail(`${where}.scaleMetres is ${r.scaleMetres}`, "set the metres the strip V span represents, > 0 (e.g. 0.6 for brick)");
|
|
1620
|
+
if (!unit(r.roughness))
|
|
1621
|
+
fail(`${where}.roughness is ${r.roughness}`, "roughness is 0..1");
|
|
1622
|
+
if (r.metalness !== undefined && !unit(r.metalness))
|
|
1623
|
+
fail(`${where}.metalness is ${r.metalness}`, "metalness is 0..1");
|
|
1624
|
+
for (const [k, v] of Object.entries(r.params ?? {}))
|
|
1625
|
+
if (!Number.isFinite(v))
|
|
1626
|
+
fail(`${where}.params.${k} is ${v}`, "params are finite numbers");
|
|
1627
|
+
}
|
|
1628
|
+
function validateSheet(recipe) {
|
|
1629
|
+
if (!Number.isInteger(recipe.seed))
|
|
1630
|
+
fail(`seed ${recipe.seed}`, "seed is an integer");
|
|
1631
|
+
if (recipe.size !== undefined && !SIZES.includes(recipe.size))
|
|
1632
|
+
fail(`size ${recipe.size}`, "size is 512, 1024 or 2048");
|
|
1633
|
+
for (const [id, r] of Object.entries(recipe.strips)) {
|
|
1634
|
+
if (!STRIPS2.includes(id))
|
|
1635
|
+
fail(`strip '${id}' is not in the layout`, `strips are ${STRIPS2.join(", ")}`);
|
|
1636
|
+
if (r)
|
|
1637
|
+
validateStrip(`strips.${id}`, r);
|
|
1638
|
+
}
|
|
1639
|
+
for (const [id, e] of Object.entries(recipe.cells ?? {})) {
|
|
1640
|
+
if (!CELLS2.includes(id))
|
|
1641
|
+
fail(`cell '${id}' is not in the layout`, `cells are ${CELLS2.join(", ")}`);
|
|
1642
|
+
if (!e)
|
|
1643
|
+
continue;
|
|
1644
|
+
if ("image" in e) {
|
|
1645
|
+
if (!(e.image instanceof Uint8Array))
|
|
1646
|
+
fail(`cells.${id}.image is not bytes`, "pass PNG file bytes as a Uint8Array");
|
|
1647
|
+
} else
|
|
1648
|
+
validateStrip(`cells.${id}`, e);
|
|
1649
|
+
}
|
|
1650
|
+
}
|
|
1651
|
+
|
|
1652
|
+
// src/material/generator.ts
|
|
1653
|
+
var FALLBACK = { kind: "noise", scaleMetres: 1, roughness: 0.6 };
|
|
1654
|
+
function paintTrimSheet2(recipe = DEFAULT_SHEET2) {
|
|
1655
|
+
validateSheet(recipe);
|
|
1656
|
+
const size = recipe.size ?? 1024;
|
|
1657
|
+
const sheet = blankSheet(size);
|
|
1658
|
+
const rng = createRng2(recipe.seed);
|
|
1659
|
+
const seedOf = (name) => rng.fork(name).int(0, 2147483647);
|
|
1660
|
+
const pad = Math.max(2, size / 256);
|
|
1661
|
+
for (const id of STRIPS2) {
|
|
1662
|
+
const r = recipe.strips[id] ?? DEFAULT_SHEET2.strips[id] ?? FALLBACK;
|
|
1663
|
+
validateStrip(`strips.${id}`, r);
|
|
1664
|
+
const rect = stripRect2(id);
|
|
1665
|
+
const h = Math.round((rect.v1 - rect.v0) * size);
|
|
1666
|
+
const c = createCanvas(size, h, rect.metres, r.scaleMetres, true);
|
|
1667
|
+
paintStrip(c, r, seedOf(id));
|
|
1668
|
+
composeRegion(sheet, c, 0, Math.round(rect.v0 * size), pad);
|
|
1669
|
+
}
|
|
1670
|
+
for (const id of CELLS2) {
|
|
1671
|
+
const e = recipe.cells?.[id] ?? DEFAULT_SHEET2.cells?.[id] ?? FALLBACK;
|
|
1672
|
+
const rect = cellRect2(id);
|
|
1673
|
+
const w = Math.round((rect.u1 - rect.u0) * size);
|
|
1674
|
+
const c = createCanvas(w, w, rect.metres, rect.metres, false);
|
|
1675
|
+
paintCell(c, id, e, seedOf(id));
|
|
1676
|
+
composeRegion(sheet, c, Math.round(rect.u0 * size), Math.round(rect.v0 * size), pad);
|
|
1677
|
+
}
|
|
1678
|
+
return sheet;
|
|
1679
|
+
}
|
|
1680
|
+
// src/material/palette.ts
|
|
1681
|
+
function hex2(value) {
|
|
1682
|
+
const n = Number.parseInt(value.replace("#", ""), 16);
|
|
1683
|
+
const lin = (c) => {
|
|
1684
|
+
const s = c / 255;
|
|
1685
|
+
return s <= 0.04045 ? s / 12.92 : ((s + 0.055) / 1.055) ** 2.4;
|
|
1686
|
+
};
|
|
1687
|
+
return [lin(n >> 16 & 255), lin(n >> 8 & 255), lin(n & 255)];
|
|
1688
|
+
}
|
|
1689
|
+
function definePalette2(entries) {
|
|
1690
|
+
return Object.fromEntries(Object.entries(entries).map(([k, v]) => [k, hex2(v)]));
|
|
1691
|
+
}
|
|
1692
|
+
var palettes2 = {
|
|
1693
|
+
victorianRail: definePalette2({
|
|
1694
|
+
paint: "#2f4a3a",
|
|
1695
|
+
trim: "#b08d57",
|
|
1696
|
+
iron: "#2b2b2e",
|
|
1697
|
+
brass: "#c9a24a",
|
|
1698
|
+
red: "#7a2a22"
|
|
1699
|
+
}),
|
|
1700
|
+
artDeco: definePalette2({
|
|
1701
|
+
limestone: "#d8cfbd",
|
|
1702
|
+
steel: "#9aa3ad",
|
|
1703
|
+
glass: "#4a6a80",
|
|
1704
|
+
bronze: "#8a6a3c"
|
|
1705
|
+
}),
|
|
1706
|
+
modernGlass: definePalette2({
|
|
1707
|
+
glass: "#6f8fa8",
|
|
1708
|
+
aluminium: "#c4c9cf",
|
|
1709
|
+
concrete: "#a7a39b"
|
|
1710
|
+
}),
|
|
1711
|
+
harbour: definePalette2({ hull: "#1f2f45", deck: "#8a6f4e", white: "#e8e6df", red: "#a1302a" })
|
|
1712
|
+
};
|
|
1713
|
+
// src/material/shader-godot.ts
|
|
1714
|
+
var f = (n) => Number.isInteger(n) ? `${n}.0` : `${+n.toFixed(6)}`;
|
|
1715
|
+
var rect2 = (r) => `in_rect(UV, vec4(${f(r.u0)}, ${f(r.v0)}, ${f(r.u1)}, ${f(r.v1)}))`;
|
|
1716
|
+
function godotSource(p) {
|
|
1717
|
+
const modes = ["blend_mix", "depth_draw_opaque", p.cutout ? "cull_disabled" : "cull_back"];
|
|
1718
|
+
const weight = p.windAttribute ? "CUSTOM0.x /* import _WIND into CUSTOM0 (glTF import: custom attribute mapping) */" : "smoothstep(0.0, wind_height, VERTEX.z)";
|
|
1719
|
+
return `// Generated by @developerz.ai/ts23d godotShaderSource(). A starting point your project owns:
|
|
1720
|
+
// assign it to a ShaderMaterial and set albedo_tex / normal_tex / orm_tex from the GLB's
|
|
1721
|
+
// ts23d_* material (or the shared trim_sheet_{albedo,normal,orm}.png). Godot 4.x.
|
|
1722
|
+
shader_type spatial;
|
|
1723
|
+
render_mode ${modes.join(", ")};
|
|
1724
|
+
|
|
1725
|
+
uniform sampler2D albedo_tex : source_color, filter_linear_mipmap, repeat_enable;
|
|
1726
|
+
uniform sampler2D normal_tex : hint_normal, filter_linear_mipmap, repeat_enable;
|
|
1727
|
+
uniform sampler2D orm_tex : hint_default_white, filter_linear_mipmap, repeat_enable;
|
|
1728
|
+
|
|
1729
|
+
uniform float night : hint_range(0.0, 1.0) = 0.0;
|
|
1730
|
+
uniform float wind_strength = ${f(p.windStrength)};
|
|
1731
|
+
uniform float wind_height = ${f(p.windHeight)};
|
|
1732
|
+
uniform float wind_hz = ${f(p.windHz)};
|
|
1733
|
+
uniform vec3 glow_color : source_color = vec3(${f(p.glow[0])}, ${f(p.glow[1])}, ${f(p.glow[2])});
|
|
1734
|
+
uniform float glow_ratio : hint_range(0.0, 1.0) = ${f(p.glowRatio)};
|
|
1735
|
+
uniform float glow_intensity = ${f(p.glowIntensity)};
|
|
1736
|
+
uniform float wear : hint_range(0.0, 1.0) = ${f(p.wear)};
|
|
1737
|
+
uniform float grime_height = ${f(p.grimeHeight)};
|
|
1738
|
+
|
|
1739
|
+
varying vec3 world_pos;
|
|
1740
|
+
|
|
1741
|
+
float in_rect(vec2 uv, vec4 r) {
|
|
1742
|
+
float u = fract(uv.x);
|
|
1743
|
+
return step(r.x, u) * step(u, r.z) * step(r.y, uv.y) * step(uv.y, r.w);
|
|
1744
|
+
}
|
|
1745
|
+
|
|
1746
|
+
float hash13(vec3 p) {
|
|
1747
|
+
p = fract(p * 0.1031);
|
|
1748
|
+
p += dot(p, p.zyx + 31.32);
|
|
1749
|
+
return fract((p.x + p.y) * p.z);
|
|
1750
|
+
}
|
|
1751
|
+
|
|
1752
|
+
void vertex() {
|
|
1753
|
+
// Wind sway: mesh-local Z is the authored up axis; sway in local XY.
|
|
1754
|
+
float weight = ${weight};
|
|
1755
|
+
float phase = VERTEX.x * 0.7 + VERTEX.y * 0.45;
|
|
1756
|
+
float sway = sin(TIME * wind_hz * 6.2832 + phase) * wind_strength * weight * 0.1;
|
|
1757
|
+
VERTEX.xy += vec2(sway, sway * 0.6);
|
|
1758
|
+
world_pos = (MODEL_MATRIX * vec4(VERTEX, 1.0)).xyz;
|
|
1759
|
+
}
|
|
1760
|
+
|
|
1761
|
+
void fragment() {
|
|
1762
|
+
vec4 base = texture(albedo_tex, UV);
|
|
1763
|
+
vec3 orm = texture(orm_tex, UV).rgb;
|
|
1764
|
+
float ground = 1.0 - smoothstep(0.0, grime_height, world_pos.y);
|
|
1765
|
+
float grime = clamp(wear * max((1.0 - orm.r) * 1.5, ground), 0.0, 1.0);
|
|
1766
|
+
ALBEDO = base.rgb * COLOR.rgb * (1.0 - grime * 0.45);
|
|
1767
|
+
AO = orm.r;
|
|
1768
|
+
ROUGHNESS = clamp(orm.g + grime * 0.2, 0.0, 1.0);
|
|
1769
|
+
METALLIC = orm.b * (1.0 - grime);
|
|
1770
|
+
NORMAL_MAP = texture(normal_tex, UV).rgb;
|
|
1771
|
+
${p.cutout ? ` ALPHA = base.a;
|
|
1772
|
+
ALPHA_SCISSOR_THRESHOLD = 0.5;
|
|
1773
|
+
` : p.blend ? ` ALPHA = base.a * 0.6;
|
|
1774
|
+
` : ""}
|
|
1775
|
+
// Window glow: dark panes in window cells, or the glass strip; 1.5 m world slots, glow_ratio lit.
|
|
1776
|
+
float luma = dot(base.rgb, vec3(0.2126, 0.7152, 0.0722));
|
|
1777
|
+
float panes = (${p.glowRects.map(rect2).join(" + ")}) * step(luma, 0.3);
|
|
1778
|
+
float mask = max(panes, ${rect2(p.glassRect)});
|
|
1779
|
+
float lit = step(hash13(floor(world_pos / 1.5)), glow_ratio);
|
|
1780
|
+
EMISSION = glow_color * mask * lit * night * glow_intensity;
|
|
1781
|
+
}
|
|
1782
|
+
`;
|
|
1783
|
+
}
|
|
1784
|
+
|
|
1785
|
+
// src/material/shader-three.ts
|
|
1786
|
+
var f2 = (n) => Number.isInteger(n) ? `${n}.0` : `${+n.toFixed(6)}`;
|
|
1787
|
+
var rectNode = (r) => `inRect(${f2(r.u0)}, ${f2(r.v0)}, ${f2(r.u1)}, ${f2(r.v1)})`;
|
|
1788
|
+
function threeSource(p) {
|
|
1789
|
+
const weight = p.windAttribute ? "attribute('_wind', 'float')" : "smoothstep(float(0), uWindHeight, positionLocal.z)";
|
|
1790
|
+
const glowCells = p.glowRects.map(rectNode).join(".add(") + ")".repeat(p.glowRects.length - 1);
|
|
1791
|
+
return `// Generated by @developerz.ai/ts23d threeMaterialSource(). A starting point your project owns:
|
|
1792
|
+
// edit freely. Needs three r18x (WebGPURenderer + TSL). ts23d itself has no three dependency.
|
|
1793
|
+
//
|
|
1794
|
+
// Usage: const gltf = await loader.loadAsync('tank.glb'); upgradeTrimMaterials(gltf.scene);
|
|
1795
|
+
// trim.uniforms.night.value = 1; // at dusk
|
|
1796
|
+
|
|
1797
|
+
import { DoubleSide, type Material, type Mesh, MeshStandardMaterial, MeshStandardNodeMaterial, type Object3D, type Texture } from 'three/webgpu';
|
|
1798
|
+
import { attribute, color, float, floor, fract, hash, max, normalMap, positionLocal, positionWorld, sin, smoothstep, step, texture, time, uniform, uv, vec3, vertexColor } from 'three/tsl';
|
|
1799
|
+
|
|
1800
|
+
export interface TrimMaps {
|
|
1801
|
+
readonly albedo: Texture;
|
|
1802
|
+
readonly normal: Texture | null;
|
|
1803
|
+
readonly orm: Texture | null;
|
|
1804
|
+
}
|
|
1805
|
+
|
|
1806
|
+
/** Shared uniforms: one set drives every trim material, so a scene-wide night switch is one write. */
|
|
1807
|
+
export const uniforms = {
|
|
1808
|
+
night: uniform(0),
|
|
1809
|
+
windStrength: uniform(${f2(p.windStrength)}),
|
|
1810
|
+
windHeight: uniform(${f2(p.windHeight)}),
|
|
1811
|
+
windHz: uniform(${f2(p.windHz)}),
|
|
1812
|
+
glowColor: uniform(color(${f2(p.glow[0])}, ${f2(p.glow[1])}, ${f2(p.glow[2])})),
|
|
1813
|
+
glowRatio: uniform(${f2(p.glowRatio)}),
|
|
1814
|
+
glowIntensity: uniform(${f2(p.glowIntensity)}),
|
|
1815
|
+
wear: uniform(${f2(p.wear)}),
|
|
1816
|
+
grimeHeight: uniform(${f2(p.grimeHeight)}),
|
|
1817
|
+
};
|
|
1818
|
+
|
|
1819
|
+
/** 1 inside a trim-sheet UV rect (U wraps: strips tile along U). */
|
|
1820
|
+
function inRect(u0: number, v0: number, u1: number, v1: number) {
|
|
1821
|
+
const t = uv();
|
|
1822
|
+
const u = fract(t.x);
|
|
1823
|
+
return step(u0, u).mul(step(u, u1)).mul(step(v0, t.y)).mul(step(t.y, v1));
|
|
1824
|
+
}
|
|
1825
|
+
|
|
1826
|
+
export function createTrimMaterial(maps: TrimMaps, cutout = ${p.cutout}): MeshStandardNodeMaterial {
|
|
1827
|
+
const { night, windStrength: uWindStrength, windHeight: uWindHeight, windHz, glowColor, glowRatio, glowIntensity, wear, grimeHeight } = uniforms;
|
|
1828
|
+
const m = new MeshStandardNodeMaterial();
|
|
1829
|
+
const base = texture(maps.albedo, uv());
|
|
1830
|
+
const orm = maps.orm ? texture(maps.orm, uv()) : vec3(1, 0.8, 0);
|
|
1831
|
+
const tint = vertexColor();
|
|
1832
|
+
|
|
1833
|
+
// Wear / grime: cavities (low AO) and the first metres above the ground get darker and rougher.
|
|
1834
|
+
const ground = float(1).sub(smoothstep(float(0), grimeHeight, positionWorld.y));
|
|
1835
|
+
const grime = wear.mul(max(float(1).sub(orm.x).mul(1.5), ground)).clamp(0, 1);
|
|
1836
|
+
m.colorNode = base.rgb.mul(tint.rgb).mul(float(1).sub(grime.mul(0.45)));
|
|
1837
|
+
m.roughnessNode = orm.y.add(grime.mul(0.2)).clamp(0, 1);
|
|
1838
|
+
m.metalnessNode = orm.z.mul(float(1).sub(grime));
|
|
1839
|
+
m.aoNode = orm.x;
|
|
1840
|
+
if (maps.normal) m.normalNode = normalMap(texture(maps.normal, uv()));
|
|
1841
|
+
if (cutout) {
|
|
1842
|
+
m.opacityNode = base.a;
|
|
1843
|
+
m.alphaTest = 0.5;
|
|
1844
|
+
m.side = DoubleSide;
|
|
1845
|
+
}
|
|
1846
|
+
|
|
1847
|
+
// Wind sway (authored Z-up, so sway in local XY), phase-shifted across the crown.
|
|
1848
|
+
const windWeight = ${weight};
|
|
1849
|
+
const phase = positionLocal.x.mul(0.7).add(positionLocal.y.mul(0.45));
|
|
1850
|
+
const sway = sin(time.mul(windHz).mul(6.2832).add(phase)).mul(uWindStrength).mul(windWeight).mul(0.1);
|
|
1851
|
+
m.positionNode = positionLocal.add(vec3(sway, sway.mul(0.6), 0));
|
|
1852
|
+
|
|
1853
|
+
// Window glow: dark pane texels in window cells, or the glass strip; a world-space hash lights
|
|
1854
|
+
// \`glowRatio\` of the 1.5 m window slots; scaled by \`night\`.
|
|
1855
|
+
const luma = base.r.mul(0.2126).add(base.g.mul(0.7152)).add(base.b.mul(0.0722));
|
|
1856
|
+
const panes = ${glowCells}.mul(step(luma, 0.3));
|
|
1857
|
+
const mask = max(panes, ${rectNode(p.glassRect)});
|
|
1858
|
+
const slot = floor(positionWorld.div(1.5));
|
|
1859
|
+
const lit = step(hash(slot.x.mul(73).add(slot.y.mul(157)).add(slot.z.mul(311))), glowRatio);
|
|
1860
|
+
m.emissiveNode = glowColor.mul(mask).mul(lit).mul(night).mul(glowIntensity);
|
|
1861
|
+
return m;
|
|
1862
|
+
}
|
|
1863
|
+
|
|
1864
|
+
/** Replace every ts23d_* material under \`root\` with the node material, reusing the GLB's maps. */
|
|
1865
|
+
export function upgradeTrimMaterials(root: Object3D): void {
|
|
1866
|
+
const cache = new Map<Material, MeshStandardNodeMaterial>();
|
|
1867
|
+
root.traverse((o) => {
|
|
1868
|
+
const mesh = o as Mesh;
|
|
1869
|
+
const old = mesh.isMesh ? mesh.material : null;
|
|
1870
|
+
if (!(old instanceof MeshStandardMaterial) || !old.name.startsWith('ts23d') || !old.map) return;
|
|
1871
|
+
const next = cache.get(old) ?? createTrimMaterial({ albedo: old.map, normal: old.normalMap, orm: old.roughnessMap }, old.alphaTest > 0);
|
|
1872
|
+
next.name = old.name;
|
|
1873
|
+
cache.set(old, next);
|
|
1874
|
+
mesh.material = next;
|
|
1875
|
+
});
|
|
1876
|
+
}
|
|
1877
|
+
`;
|
|
1878
|
+
}
|
|
1879
|
+
|
|
1880
|
+
// src/material/shader.ts
|
|
1881
|
+
function fail2(cause, fix) {
|
|
1882
|
+
throw new Ts23dError2("T3D_RECIPE_INVALID", { cause: `shader hints: ${cause}`, fix });
|
|
1883
|
+
}
|
|
1884
|
+
function srgbHex(value) {
|
|
1885
|
+
if (!/^#[0-9a-fA-F]{6}$/.test(value))
|
|
1886
|
+
fail2(`colour '${value}'`, "use '#rrggbb' sRGB hex");
|
|
1887
|
+
const n = Number.parseInt(value.slice(1), 16);
|
|
1888
|
+
return [(n >> 16 & 255) / 255, (n >> 8 & 255) / 255, (n & 255) / 255];
|
|
1889
|
+
}
|
|
1890
|
+
function resolveHints(h) {
|
|
1891
|
+
const unit = (v, what) => Number.isFinite(v) && v >= 0 && v <= 1 ? v : fail2(`${what} is ${v}`, `${what} is 0..1`);
|
|
1892
|
+
const pos = (v, what) => Number.isFinite(v) && v > 0 ? v : fail2(`${what} is ${v}`, `${what} is a positive number`);
|
|
1893
|
+
const strength = h.wind ? pos(h.wind.strength, "wind.strength") : 0;
|
|
1894
|
+
return {
|
|
1895
|
+
windStrength: strength,
|
|
1896
|
+
windAttribute: h.wind?.source === "attribute",
|
|
1897
|
+
windHeight: pos(h.wind?.height ?? 4, "wind.height"),
|
|
1898
|
+
windHz: pos(h.wind?.hz ?? 0.4, "wind.hz"),
|
|
1899
|
+
glow: h.windowGlow ? srgbHex(h.windowGlow.color) : [1, 0.75, 0.4],
|
|
1900
|
+
glowRatio: h.windowGlow ? unit(h.windowGlow.ratio, "windowGlow.ratio") : 0,
|
|
1901
|
+
glowIntensity: pos(h.windowGlow?.intensity ?? 2, "windowGlow.intensity"),
|
|
1902
|
+
wear: h.wear ? unit(h.wear.amount, "wear.amount") : 0,
|
|
1903
|
+
grimeHeight: pos(h.wear?.grimeHeight ?? 0.6, "wear.grimeHeight"),
|
|
1904
|
+
cutout: h.surface === "cutout",
|
|
1905
|
+
blend: h.surface === "glass" || h.surface === "translucent",
|
|
1906
|
+
glowRects: [cellRect2("window_sash"), cellRect2("porthole")],
|
|
1907
|
+
glassRect: stripRect2("glass")
|
|
1908
|
+
};
|
|
1909
|
+
}
|
|
1910
|
+
function threeMaterialSource2(hints = {}) {
|
|
1911
|
+
return threeSource(resolveHints(hints));
|
|
1912
|
+
}
|
|
1913
|
+
function godotShaderSource2(hints = {}) {
|
|
1914
|
+
return godotSource(resolveHints(hints));
|
|
1915
|
+
}
|
|
1916
|
+
export { TRIM_SHEET_SIZE2, stripRect2, cellRect2, STRIPS2, CELLS2, DECAL_CELLS2, FOLIAGE_CELLS2, isFoliage2, DEFAULT_SHEET2, paintTrimSheet2, hex2, definePalette2, palettes2, threeMaterialSource2, godotShaderSource2 };
|
|
1917
|
+
|
|
1918
|
+
//# debugId=F437FE42A323CC4464756E2164756E21
|
|
1919
|
+
//# sourceMappingURL=chunk-kvcep7ft.js.map
|