@developerz.ai/ts23d 0.0.0-stage → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/agents/bug-hunter.md +21 -0
- package/.claude/agents/docs-author.md +13 -0
- package/.claude/agents/dx-auditor.md +14 -0
- package/.claude/agents/primitive-author.md +22 -0
- package/.claude/agents/recipe-author.md +36 -0
- package/.claude/agents/visual-reviewer.md +23 -0
- package/.claude/commands/feature.md +28 -0
- package/.claude/commands/new-recipe.md +19 -0
- package/.claude/commands/release.md +14 -0
- package/.claude/commands/verify.md +9 -0
- package/.claude/skills/author-recipe/SKILL.md +93 -0
- package/CHANGELOG.md +90 -0
- package/CLAUDE.md +85 -0
- package/LICENSE +21 -0
- package/README.md +385 -2
- package/dist/chunk-6b05mnmx.js +19 -0
- package/dist/chunk-6b05mnmx.js.map +11 -0
- package/dist/chunk-7rtfjh9f.js +45 -0
- package/dist/chunk-7rtfjh9f.js.map +10 -0
- package/dist/chunk-8j4c28jt.js +4085 -0
- package/dist/chunk-8j4c28jt.js.map +46 -0
- package/dist/chunk-d1fv6hrm.js +2532 -0
- package/dist/chunk-d1fv6hrm.js.map +43 -0
- package/dist/chunk-dbq4qf52.js +902 -0
- package/dist/chunk-dbq4qf52.js.map +25 -0
- package/dist/chunk-drna2rny.js +836 -0
- package/dist/chunk-drna2rny.js.map +17 -0
- package/dist/chunk-dxvypr7y.js +89 -0
- package/dist/chunk-dxvypr7y.js.map +11 -0
- package/dist/chunk-f6yme003.js +1589 -0
- package/dist/chunk-f6yme003.js.map +30 -0
- package/dist/chunk-hg7pf34j.js +465 -0
- package/dist/chunk-hg7pf34j.js.map +19 -0
- package/dist/chunk-jq4e17tj.js +1031 -0
- package/dist/chunk-jq4e17tj.js.map +21 -0
- package/dist/chunk-y63c6zcv.js +2668 -0
- package/dist/chunk-y63c6zcv.js.map +53 -0
- package/dist/cli/args.d.ts +30 -0
- package/dist/cli/bin.d.ts +2 -0
- package/dist/cli/bin.js +348 -0
- package/dist/cli/bin.js.map +15 -0
- package/dist/cli/load.d.ts +2 -0
- package/dist/cli/manifest.d.ts +53 -0
- package/dist/cli/render-cmds.d.ts +12 -0
- package/dist/cli/run.d.ts +1 -0
- package/dist/cli/texture-cache.d.ts +4 -0
- package/dist/core/asset.d.ts +10 -0
- package/dist/core/definition.d.ts +155 -0
- package/dist/core/errors.d.ts +17 -0
- package/dist/core/family.d.ts +23 -0
- package/dist/core/index.d.ts +9 -0
- package/dist/core/index.js +69 -0
- package/dist/core/index.js.map +9 -0
- package/dist/core/mesh.d.ts +35 -0
- package/dist/core/part.d.ts +114 -0
- package/dist/core/png.d.ts +1 -0
- package/dist/core/rng.d.ts +12 -0
- package/dist/core/transform.d.ts +20 -0
- package/dist/core/vec.d.ts +11 -0
- package/dist/core/world.d.ts +16 -0
- package/dist/export/bin-writer.d.ts +16 -0
- package/dist/export/container.d.ts +3 -0
- package/dist/export/credits.d.ts +4 -0
- package/dist/export/glb-options.d.ts +48 -0
- package/dist/export/glb.d.ts +8 -0
- package/dist/export/hierarchy.d.ts +23 -0
- package/dist/export/index.d.ts +5 -0
- package/dist/export/index.js +40 -0
- package/dist/export/index.js.map +9 -0
- package/dist/export/lights.d.ts +8 -0
- package/dist/export/lod-coverage.d.ts +12 -0
- package/dist/export/materials.d.ts +23 -0
- package/dist/export/merge-pivots.d.ts +6 -0
- package/dist/export/metadata.d.ts +21 -0
- package/dist/export/motion.d.ts +9 -0
- package/dist/export/optimize-parts.d.ts +26 -0
- package/dist/export/primitive.d.ts +3 -0
- package/dist/export/tangents.d.ts +2 -0
- package/dist/export/textures.d.ts +22 -0
- package/dist/export/version.d.ts +2 -0
- package/dist/grammar/index.d.ts +2 -0
- package/dist/grammar/index.js +16 -0
- package/dist/grammar/index.js.map +9 -0
- package/dist/grammar/split.d.ts +21 -0
- package/dist/grammar/whyte.d.ts +5 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +457 -0
- package/dist/index.js.map +9 -0
- package/dist/kinds/building.d.ts +44 -0
- package/dist/kinds/faces.d.ts +27 -0
- package/dist/kinds/head-armour.d.ts +5 -0
- package/dist/kinds/head-beard.d.ts +6 -0
- package/dist/kinds/head-bust.d.ts +5 -0
- package/dist/kinds/head-extras.d.ts +15 -0
- package/dist/kinds/head-face.d.ts +8 -0
- package/dist/kinds/head-garb.d.ts +4 -0
- package/dist/kinds/head-geom.d.ts +7 -0
- package/dist/kinds/head-hair.d.ts +4 -0
- package/dist/kinds/head-layout.d.ts +29 -0
- package/dist/kinds/head-mesh.d.ts +31 -0
- package/dist/kinds/head-mouth.d.ts +4 -0
- package/dist/kinds/head-params.d.ts +116 -0
- package/dist/kinds/head-presets.d.ts +19 -0
- package/dist/kinds/head-rim.d.ts +10 -0
- package/dist/kinds/head-tone.d.ts +10 -0
- package/dist/kinds/head-torso.d.ts +35 -0
- package/dist/kinds/head.d.ts +39 -0
- package/dist/kinds/hull-lines.d.ts +65 -0
- package/dist/kinds/hull-skin.d.ts +6 -0
- package/dist/kinds/hull.d.ts +14 -0
- package/dist/kinds/index.d.ts +16 -0
- package/dist/kinds/index.js +78 -0
- package/dist/kinds/index.js.map +9 -0
- package/dist/kinds/mass.d.ts +15 -0
- package/dist/kinds/openings.d.ts +30 -0
- package/dist/kinds/planned.d.ts +6 -0
- package/dist/kinds/roof-chimney.d.ts +16 -0
- package/dist/kinds/roof-shape.d.ts +64 -0
- package/dist/kinds/roof.d.ts +31 -0
- package/dist/kinds/vegetation-fronds.d.ts +15 -0
- package/dist/kinds/vegetation-impostor.d.ts +9 -0
- package/dist/kinds/vegetation-leaves.d.ts +15 -0
- package/dist/kinds/vegetation-mesh.d.ts +21 -0
- package/dist/kinds/vegetation-params.d.ts +90 -0
- package/dist/kinds/vegetation-presets.d.ts +20 -0
- package/dist/kinds/vegetation-skeleton.d.ts +20 -0
- package/dist/kinds/vegetation.d.ts +42 -0
- package/dist/kinds/vehicle.d.ts +42 -0
- package/dist/material/cc0.d.ts +4 -0
- package/dist/material/define.d.ts +34 -0
- package/dist/material/generator.d.ts +4 -0
- package/dist/material/index.d.ts +9 -0
- package/dist/material/index.js +70 -0
- package/dist/material/index.js.map +9 -0
- package/dist/material/library.d.ts +7 -0
- package/dist/material/material-def.d.ts +30 -0
- package/dist/material/paint/bond.d.ts +3 -0
- package/dist/material/paint/canvas.d.ts +39 -0
- package/dist/material/paint/cell.d.ts +4 -0
- package/dist/material/paint/cells-round.d.ts +5 -0
- package/dist/material/paint/cells.d.ts +7 -0
- package/dist/material/paint/compose.d.ts +5 -0
- package/dist/material/paint/concrete.d.ts +3 -0
- package/dist/material/paint/courses.d.ts +31 -0
- package/dist/material/paint/decode-png.d.ts +6 -0
- package/dist/material/paint/default-sheet.d.ts +4 -0
- package/dist/material/paint/fabric.d.ts +4 -0
- package/dist/material/paint/foliage-broad.d.ts +5 -0
- package/dist/material/paint/foliage-common.d.ts +35 -0
- package/dist/material/paint/foliage-compound.d.ts +4 -0
- package/dist/material/paint/foliage-conifer.d.ts +5 -0
- package/dist/material/paint/foliage-raster.d.ts +26 -0
- package/dist/material/paint/foliage.d.ts +3 -0
- package/dist/material/paint/image-strip.d.ts +4 -0
- package/dist/material/paint/kit.d.ts +5 -0
- package/dist/material/paint/masks.d.ts +3 -0
- package/dist/material/paint/metal.d.ts +3 -0
- package/dist/material/paint/noise.d.ts +9 -0
- package/dist/material/paint/normal.d.ts +8 -0
- package/dist/material/paint/orm.d.ts +8 -0
- package/dist/material/paint/planks.d.ts +3 -0
- package/dist/material/paint/plates.d.ts +3 -0
- package/dist/material/paint/shapes.d.ts +8 -0
- package/dist/material/paint/slate.d.ts +3 -0
- package/dist/material/paint/stamp.d.ts +3 -0
- package/dist/material/paint/strip.d.ts +3 -0
- package/dist/material/paint/surface.d.ts +5 -0
- package/dist/material/paint/types.d.ts +65 -0
- package/dist/material/paint/validate.d.ts +3 -0
- package/dist/material/palette.d.ts +14 -0
- package/dist/material/shader-godot.d.ts +2 -0
- package/dist/material/shader-three.d.ts +2 -0
- package/dist/material/shader.d.ts +46 -0
- package/dist/material/slots.d.ts +5 -0
- package/dist/material/texture-source.d.ts +34 -0
- package/dist/material/trim-sheet.d.ts +38 -0
- package/dist/neural/generator.d.ts +25 -0
- package/dist/neural/images.d.ts +17 -0
- package/dist/neural/index.d.ts +2 -0
- package/dist/neural/index.js +14 -0
- package/dist/neural/index.js.map +9 -0
- package/dist/passes/bake-ao.d.ts +17 -0
- package/dist/passes/grime.d.ts +15 -0
- package/dist/passes/index.d.ts +6 -0
- package/dist/passes/index.js +22 -0
- package/dist/passes/index.js.map +9 -0
- package/dist/passes/livery.d.ts +19 -0
- package/dist/passes/optimize-cache.d.ts +15 -0
- package/dist/passes/optimize-weld.d.ts +15 -0
- package/dist/passes/optimize.d.ts +28 -0
- package/dist/passes/ray-grid.d.ts +23 -0
- package/dist/passes/weighted-normals.d.ts +8 -0
- package/dist/primitives/beam.d.ts +17 -0
- package/dist/primitives/bevel-box.d.ts +13 -0
- package/dist/primitives/box.d.ts +16 -0
- package/dist/primitives/cell.d.ts +20 -0
- package/dist/primitives/check.d.ts +12 -0
- package/dist/primitives/closed.d.ts +11 -0
- package/dist/primitives/erosion.d.ts +22 -0
- package/dist/primitives/font-5x7.d.ts +3 -0
- package/dist/primitives/heightfield.d.ts +16 -0
- package/dist/primitives/index.d.ts +23 -0
- package/dist/primitives/index.js +81 -0
- package/dist/primitives/index.js.map +9 -0
- package/dist/primitives/lathe-facets.d.ts +8 -0
- package/dist/primitives/lathe.d.ts +26 -0
- package/dist/primitives/lattice.d.ts +30 -0
- package/dist/primitives/loft.d.ts +15 -0
- package/dist/primitives/mc-table.d.ts +2 -0
- package/dist/primitives/member.d.ts +19 -0
- package/dist/primitives/noise.d.ts +34 -0
- package/dist/primitives/planar.d.ts +10 -0
- package/dist/primitives/prism.d.ts +13 -0
- package/dist/primitives/sculpt-edit.d.ts +56 -0
- package/dist/primitives/sculpt-tree.d.ts +23 -0
- package/dist/primitives/sculpt.d.ts +27 -0
- package/dist/primitives/sdf-grid.d.ts +17 -0
- package/dist/primitives/sdf-ops.d.ts +37 -0
- package/dist/primitives/sdf.d.ts +21 -0
- package/dist/primitives/strip-uv.d.ts +11 -0
- package/dist/primitives/surface-nets.d.ts +7 -0
- package/dist/primitives/sweep.d.ts +19 -0
- package/dist/primitives/terrain-color.d.ts +30 -0
- package/dist/primitives/terrain-grid.d.ts +36 -0
- package/dist/primitives/text-layout.d.ts +22 -0
- package/dist/primitives/text.d.ts +27 -0
- package/dist/primitives/tiled.d.ts +22 -0
- package/dist/primitives/transport.d.ts +17 -0
- package/dist/primitives/triangulate.d.ts +2 -0
- package/dist/primitives/value-noise.d.ts +10 -0
- package/dist/qa/checks.d.ts +11 -0
- package/dist/qa/clash.d.ts +2 -0
- package/dist/qa/floating.d.ts +5 -0
- package/dist/qa/gates.d.ts +12 -0
- package/dist/qa/geometry.d.ts +21 -0
- package/dist/qa/index.d.ts +12 -0
- package/dist/qa/index.js +78 -0
- package/dist/qa/index.js.map +9 -0
- package/dist/qa/inside.d.ts +12 -0
- package/dist/qa/inspect.d.ts +18 -0
- package/dist/qa/lods.d.ts +12 -0
- package/dist/qa/measures.d.ts +4 -0
- package/dist/qa/occlusion.d.ts +3 -0
- package/dist/qa/sanity.d.ts +7 -0
- package/dist/qa/types.d.ts +12 -0
- package/dist/render/beauty.d.ts +40 -0
- package/dist/render/camera.d.ts +33 -0
- package/dist/render/depth-map.d.ts +38 -0
- package/dist/render/font.d.ts +1 -0
- package/dist/render/ground.d.ts +13 -0
- package/dist/render/index.d.ts +10 -0
- package/dist/render/index.js +47 -0
- package/dist/render/index.js.map +9 -0
- package/dist/render/lighting.d.ts +27 -0
- package/dist/render/lod-strip.d.ts +5 -0
- package/dist/render/metrics.d.ts +37 -0
- package/dist/render/options.d.ts +28 -0
- package/dist/render/photo-match.d.ts +19 -0
- package/dist/render/raster.d.ts +25 -0
- package/dist/render/resolve.d.ts +11 -0
- package/dist/render/review-prompt.d.ts +2 -0
- package/dist/render/review-warnings.d.ts +34 -0
- package/dist/render/review.d.ts +24 -0
- package/dist/render/scene.d.ts +18 -0
- package/dist/render/shade.d.ts +22 -0
- package/dist/render/sheet.d.ts +15 -0
- package/dist/render/texture.d.ts +15 -0
- package/dist/render/thumbnail.d.ts +8 -0
- package/dist/render/vis-buffer.d.ts +18 -0
- package/dist/testing/assert.d.ts +21 -0
- package/dist/testing/compiled.d.ts +15 -0
- package/dist/testing/hash.d.ts +1 -0
- package/dist/testing/index.d.ts +4 -0
- package/dist/testing/index.js +134 -0
- package/dist/testing/index.js.map +13 -0
- package/dist/testing/snapshot.d.ts +13 -0
- package/docs/README.md +56 -0
- package/docs/architecture/README.md +68 -0
- package/docs/architecture/adding-a-kind.md +44 -0
- package/docs/architecture/adding-a-primitive.md +49 -0
- package/docs/architecture/errors.md +47 -0
- package/docs/architecture/glb-production.md +214 -0
- package/docs/architecture/gltf-capabilities.md +117 -0
- package/docs/architecture/materials.md +151 -0
- package/docs/decisions/001-recipe-compiler-over-ai-meshes.md +27 -0
- package/docs/decisions/002-typescript-not-json.md +22 -0
- package/docs/decisions/003-shared-trim-sheet.md +21 -0
- package/docs/decisions/004-z-up-metres.md +21 -0
- package/docs/decisions/005-zero-runtime-deps.md +21 -0
- package/docs/decisions/006-single-package-subpath-exports.md +20 -0
- package/docs/decisions/007-cpu-contact-sheets.md +21 -0
- package/docs/decisions/008-surface-materials.md +21 -0
- package/docs/decisions/009-gltf-front-axis.md +21 -0
- package/docs/decisions/README.md +15 -0
- package/docs/idea/01-vision.md +117 -0
- package/docs/idea/02-dsl.md +343 -0
- package/docs/idea/03-ir.md +97 -0
- package/docs/idea/04-materials.md +114 -0
- package/docs/idea/05-agent-loop.md +141 -0
- package/docs/idea/06-qa.md +101 -0
- package/docs/idea/07-what-fits.md +45 -0
- package/docs/idea/08-neural-adapters.md +78 -0
- package/docs/idea/09-kinds.md +105 -0
- package/docs/idea/10-sculpting.md +166 -0
- package/docs/idea/11-materials-authoring.md +132 -0
- package/docs/idea/12-blender-bridge.md +136 -0
- package/docs/idea/13-asset-pipeline-research.md +89 -0
- package/docs/landscape.md +49 -0
- package/docs/plans/roadmap.md +139 -0
- package/examples/build-with-bun.ts +41 -0
- package/llms.txt +47 -0
- package/package.json +100 -4
- package/recipes/README.md +38 -0
- package/recipes/asteroid/README.md +46 -0
- package/recipes/asteroid/asteroid.ts +336 -0
- package/recipes/boulders/README.md +28 -0
- package/recipes/boulders/boulders.ts +117 -0
- package/recipes/boxcar/README.md +53 -0
- package/recipes/boxcar/boxcar.ts +1000 -0
- package/recipes/cargo_container_scifi/README.md +52 -0
- package/recipes/cargo_container_scifi/cargo_container_scifi.ts +461 -0
- package/recipes/dropship/README.md +84 -0
- package/recipes/dropship/dropship.ts +1144 -0
- package/recipes/eiffel_tower/README.md +110 -0
- package/recipes/eiffel_tower/eiffel_tower.ts +781 -0
- package/recipes/goblin_head/README.md +75 -0
- package/recipes/goblin_head/goblin_head.ts +38 -0
- package/recipes/human_head/README.md +68 -0
- package/recipes/human_head/human_head.ts +38 -0
- package/recipes/loco_440/README.md +32 -0
- package/recipes/loco_440/loco_440.ts +1404 -0
- package/recipes/material_swatches/README.md +21 -0
- package/recipes/material_swatches/material_swatches.ts +189 -0
- package/recipes/mountain_range/README.md +45 -0
- package/recipes/mountain_range/mountain_range.ts +128 -0
- package/recipes/oak/README.md +30 -0
- package/recipes/oak/oak.ts +19 -0
- package/recipes/orc_head/README.md +78 -0
- package/recipes/orc_head/orc_head.ts +41 -0
- package/recipes/palm/README.md +30 -0
- package/recipes/palm/palm.ts +19 -0
- package/recipes/pine/README.md +29 -0
- package/recipes/pine/pine.ts +19 -0
- package/recipes/signal_gantry/README.md +36 -0
- package/recipes/signal_gantry/signal_gantry.ts +522 -0
- package/recipes/station_halt/README.md +39 -0
- package/recipes/station_halt/station_halt.ts +722 -0
- package/recipes/t34/README.md +44 -0
- package/recipes/t34/t34.ts +1068 -0
- package/recipes/tiger_1/README.md +75 -0
- package/recipes/tiger_1/tiger_1.ts +1370 -0
- package/recipes/water_tower/README.md +40 -0
- package/recipes/water_tower/water_tower.ts +542 -0
- package/src/cli/args.ts +125 -0
- package/src/cli/bin.ts +7 -0
- package/src/cli/load.ts +17 -0
- package/src/cli/manifest.ts +95 -0
- package/src/cli/render-cmds.ts +66 -0
- package/src/cli/run.ts +192 -0
- package/src/cli/texture-cache.ts +24 -0
- package/src/core/asset.ts +129 -0
- package/src/core/definition.ts +156 -0
- package/src/core/errors.ts +50 -0
- package/src/core/family.ts +50 -0
- package/src/core/index.ts +66 -0
- package/src/core/mesh.ts +114 -0
- package/src/core/part.ts +130 -0
- package/src/core/png.ts +49 -0
- package/src/core/rng.ts +49 -0
- package/src/core/transform.ts +90 -0
- package/src/core/vec.ts +26 -0
- package/src/core/world.ts +80 -0
- package/src/export/bin-writer.ts +79 -0
- package/src/export/container.ts +35 -0
- package/src/export/credits.ts +25 -0
- package/src/export/glb-options.ts +53 -0
- package/src/export/glb.ts +187 -0
- package/src/export/hierarchy.ts +122 -0
- package/src/export/index.ts +34 -0
- package/src/export/lights.ts +49 -0
- package/src/export/lod-coverage.ts +71 -0
- package/src/export/materials.ts +126 -0
- package/src/export/merge-pivots.ts +146 -0
- package/src/export/metadata.ts +62 -0
- package/src/export/motion.ts +70 -0
- package/src/export/optimize-parts.ts +80 -0
- package/src/export/primitive.ts +35 -0
- package/src/export/tangents.ts +75 -0
- package/src/export/textures.ts +88 -0
- package/src/export/version.ts +6 -0
- package/src/grammar/index.ts +4 -0
- package/src/grammar/split.ts +87 -0
- package/src/grammar/whyte.ts +20 -0
- package/src/index.ts +14 -0
- package/src/kinds/building.ts +153 -0
- package/src/kinds/faces.ts +138 -0
- package/src/kinds/head-armour.ts +181 -0
- package/src/kinds/head-beard.ts +133 -0
- package/src/kinds/head-bust.ts +184 -0
- package/src/kinds/head-extras.ts +150 -0
- package/src/kinds/head-face.ts +242 -0
- package/src/kinds/head-garb.ts +181 -0
- package/src/kinds/head-geom.ts +31 -0
- package/src/kinds/head-hair.ts +193 -0
- package/src/kinds/head-layout.ts +63 -0
- package/src/kinds/head-mesh.ts +195 -0
- package/src/kinds/head-mouth.ts +111 -0
- package/src/kinds/head-params.ts +162 -0
- package/src/kinds/head-presets.ts +155 -0
- package/src/kinds/head-rim.ts +128 -0
- package/src/kinds/head-tone.ts +52 -0
- package/src/kinds/head-torso.ts +230 -0
- package/src/kinds/head.ts +176 -0
- package/src/kinds/hull-lines.ts +160 -0
- package/src/kinds/hull-skin.ts +83 -0
- package/src/kinds/hull.ts +209 -0
- package/src/kinds/index.ts +63 -0
- package/src/kinds/mass.ts +110 -0
- package/src/kinds/openings.ts +141 -0
- package/src/kinds/planned.ts +12 -0
- package/src/kinds/roof-chimney.ts +100 -0
- package/src/kinds/roof-shape.ts +230 -0
- package/src/kinds/roof.ts +250 -0
- package/src/kinds/vegetation-fronds.ts +126 -0
- package/src/kinds/vegetation-impostor.ts +118 -0
- package/src/kinds/vegetation-leaves.ts +168 -0
- package/src/kinds/vegetation-mesh.ts +174 -0
- package/src/kinds/vegetation-params.ts +147 -0
- package/src/kinds/vegetation-presets.ts +184 -0
- package/src/kinds/vegetation-skeleton.ts +168 -0
- package/src/kinds/vegetation.ts +199 -0
- package/src/kinds/vehicle.ts +144 -0
- package/src/material/cc0.ts +41 -0
- package/src/material/define.ts +130 -0
- package/src/material/generator.ts +63 -0
- package/src/material/index.ts +60 -0
- package/src/material/library.ts +196 -0
- package/src/material/material-def.ts +84 -0
- package/src/material/paint/bond.ts +60 -0
- package/src/material/paint/canvas.ts +101 -0
- package/src/material/paint/cell.ts +39 -0
- package/src/material/paint/cells-round.ts +119 -0
- package/src/material/paint/cells.ts +111 -0
- package/src/material/paint/compose.ts +53 -0
- package/src/material/paint/concrete.ts +62 -0
- package/src/material/paint/courses.ts +85 -0
- package/src/material/paint/decode-png.ts +106 -0
- package/src/material/paint/default-sheet.ts +73 -0
- package/src/material/paint/fabric.ts +63 -0
- package/src/material/paint/foliage-broad.ts +126 -0
- package/src/material/paint/foliage-common.ts +93 -0
- package/src/material/paint/foliage-compound.ts +135 -0
- package/src/material/paint/foliage-conifer.ts +143 -0
- package/src/material/paint/foliage-raster.ts +196 -0
- package/src/material/paint/foliage.ts +23 -0
- package/src/material/paint/image-strip.ts +88 -0
- package/src/material/paint/kit.ts +37 -0
- package/src/material/paint/masks.ts +46 -0
- package/src/material/paint/metal.ts +109 -0
- package/src/material/paint/noise.ts +52 -0
- package/src/material/paint/normal.ts +52 -0
- package/src/material/paint/orm.ts +108 -0
- package/src/material/paint/planks.ts +101 -0
- package/src/material/paint/plates.ts +72 -0
- package/src/material/paint/shapes.ts +28 -0
- package/src/material/paint/slate.ts +52 -0
- package/src/material/paint/stamp.ts +40 -0
- package/src/material/paint/strip.ts +48 -0
- package/src/material/paint/surface.ts +60 -0
- package/src/material/paint/types.ts +94 -0
- package/src/material/paint/validate.ts +74 -0
- package/src/material/palette.ts +46 -0
- package/src/material/shader-godot.ts +77 -0
- package/src/material/shader-three.ts +103 -0
- package/src/material/shader.ts +100 -0
- package/src/material/slots.ts +18 -0
- package/src/material/texture-source.ts +86 -0
- package/src/material/trim-sheet.ts +166 -0
- package/src/neural/generator.ts +37 -0
- package/src/neural/images.ts +27 -0
- package/src/neural/index.ts +10 -0
- package/src/passes/bake-ao.ts +183 -0
- package/src/passes/grime.ts +37 -0
- package/src/passes/index.ts +8 -0
- package/src/passes/livery.ts +66 -0
- package/src/passes/optimize-cache.ts +175 -0
- package/src/passes/optimize-weld.ts +147 -0
- package/src/passes/optimize.ts +85 -0
- package/src/passes/ray-grid.ts +237 -0
- package/src/passes/weighted-normals.ts +125 -0
- package/src/primitives/beam.ts +60 -0
- package/src/primitives/bevel-box.ts +140 -0
- package/src/primitives/box.ts +90 -0
- package/src/primitives/cell.ts +115 -0
- package/src/primitives/check.ts +49 -0
- package/src/primitives/closed.ts +74 -0
- package/src/primitives/erosion.ts +183 -0
- package/src/primitives/font-5x7.ts +117 -0
- package/src/primitives/heightfield.ts +101 -0
- package/src/primitives/index.ts +56 -0
- package/src/primitives/lathe-facets.ts +45 -0
- package/src/primitives/lathe.ts +174 -0
- package/src/primitives/lattice.ts +131 -0
- package/src/primitives/loft.ts +174 -0
- package/src/primitives/mc-table.ts +41 -0
- package/src/primitives/member.ts +130 -0
- package/src/primitives/noise.ts +157 -0
- package/src/primitives/planar.ts +55 -0
- package/src/primitives/prism.ts +85 -0
- package/src/primitives/sculpt-edit.ts +212 -0
- package/src/primitives/sculpt-tree.ts +226 -0
- package/src/primitives/sculpt.ts +80 -0
- package/src/primitives/sdf-grid.ts +131 -0
- package/src/primitives/sdf-ops.ts +145 -0
- package/src/primitives/sdf.ts +83 -0
- package/src/primitives/strip-uv.ts +26 -0
- package/src/primitives/surface-nets.ts +161 -0
- package/src/primitives/sweep.ts +162 -0
- package/src/primitives/terrain-color.ts +85 -0
- package/src/primitives/terrain-grid.ts +137 -0
- package/src/primitives/text-layout.ts +85 -0
- package/src/primitives/text.ts +197 -0
- package/src/primitives/tiled.ts +93 -0
- package/src/primitives/transport.ts +85 -0
- package/src/primitives/triangulate.ts +42 -0
- package/src/primitives/value-noise.ts +62 -0
- package/src/qa/checks.ts +110 -0
- package/src/qa/clash.ts +177 -0
- package/src/qa/floating.ts +63 -0
- package/src/qa/gates.ts +126 -0
- package/src/qa/geometry.ts +187 -0
- package/src/qa/index.ts +28 -0
- package/src/qa/inside.ts +114 -0
- package/src/qa/inspect.ts +107 -0
- package/src/qa/lods.ts +97 -0
- package/src/qa/measures.ts +67 -0
- package/src/qa/occlusion.ts +166 -0
- package/src/qa/sanity.ts +137 -0
- package/src/qa/types.ts +17 -0
- package/src/render/beauty.ts +115 -0
- package/src/render/camera.ts +142 -0
- package/src/render/depth-map.ts +191 -0
- package/src/render/font.ts +76 -0
- package/src/render/ground.ts +87 -0
- package/src/render/index.ts +34 -0
- package/src/render/lighting.ts +121 -0
- package/src/render/lod-strip.ts +34 -0
- package/src/render/metrics.ts +147 -0
- package/src/render/options.ts +81 -0
- package/src/render/photo-match.ts +28 -0
- package/src/render/raster.ts +143 -0
- package/src/render/resolve.ts +109 -0
- package/src/render/review-prompt.ts +59 -0
- package/src/render/review-warnings.ts +92 -0
- package/src/render/review.ts +103 -0
- package/src/render/scene.ts +119 -0
- package/src/render/shade.ts +204 -0
- package/src/render/sheet.ts +88 -0
- package/src/render/texture.ts +97 -0
- package/src/render/thumbnail.ts +75 -0
- package/src/render/vis-buffer.ts +119 -0
- package/src/testing/assert.ts +115 -0
- package/src/testing/compiled.ts +31 -0
- package/src/testing/hash.ts +14 -0
- package/src/testing/index.ts +14 -0
- package/src/testing/snapshot.ts +37 -0
- package/src/types/gltf-validator.d.ts +13 -0
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
// Sweep a closed 2D section along a 3D polyline: branches, trunks, roots, pipes, cables, handrails.
|
|
2
|
+
// SMOOTH normals (a swept tube reads as round; caps are flat). Parallel-transport frames, so no
|
|
3
|
+
// twist flip. Positions are absolute: the path is in the caller's space (no `at`). UVs: U runs
|
|
4
|
+
// along the path in true metres (the strip's tiling axis, like `planar`/`tiled`); V goes once
|
|
5
|
+
// around the section inside the strip's band. Triangles = 2·S·(P−1) + S per cap (S section
|
|
6
|
+
// points, P path points).
|
|
7
|
+
|
|
8
|
+
import { type Mesh, MeshBuilder, Ts23dError, type Vec2, type Vec3 } from '../core/index.ts';
|
|
9
|
+
import { type StripId, stripRect } from '../material/index.ts';
|
|
10
|
+
import { arcLengths, transportFrames } from './transport.ts';
|
|
11
|
+
|
|
12
|
+
export interface SweepOptions {
|
|
13
|
+
/** Closed section in the frame's (normal, binormal) plane, either winding; or a circle. */
|
|
14
|
+
readonly section: readonly Vec2[] | { readonly radius: number; readonly segments: number };
|
|
15
|
+
readonly path: readonly Vec3[];
|
|
16
|
+
/** Section scale per path point (taper). Default 1 everywhere. */
|
|
17
|
+
readonly scale?: readonly number[];
|
|
18
|
+
/** Total section rotation from first to last point, degrees, spread by arc length. */
|
|
19
|
+
readonly twist?: number;
|
|
20
|
+
readonly color: Vec3;
|
|
21
|
+
readonly strip?: StripId;
|
|
22
|
+
/** Close the ends with fans about the path point: both, or just one. */
|
|
23
|
+
readonly caps?: boolean | 'start' | 'end';
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
interface Section {
|
|
27
|
+
readonly pts: readonly Vec2[];
|
|
28
|
+
/** Outward 2D vertex normals. */
|
|
29
|
+
readonly nrm: readonly Vec2[];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function section(s: SweepOptions['section']): Section {
|
|
33
|
+
if (!Array.isArray(s)) {
|
|
34
|
+
const c = s as { radius: number; segments: number };
|
|
35
|
+
if (c.segments < 3 || !(c.radius > 0)) {
|
|
36
|
+
throw new Ts23dError('T3D_RECIPE_INVALID', {
|
|
37
|
+
cause: `sweep circle section has radius ${c.radius} and ${c.segments} segments`,
|
|
38
|
+
fix: 'use radius > 0 and segments >= 3 (pick segments from ctx.lod: e.g. 8 / 5 / 3)',
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
const nrm: Vec2[] = [];
|
|
42
|
+
for (let k = 0; k < c.segments; k++) {
|
|
43
|
+
const a = (k / c.segments) * Math.PI * 2;
|
|
44
|
+
nrm.push([Math.cos(a), Math.sin(a)]);
|
|
45
|
+
}
|
|
46
|
+
return { pts: nrm.map(([x, y]) => [x * c.radius, y * c.radius]), nrm };
|
|
47
|
+
}
|
|
48
|
+
const poly = s as readonly Vec2[];
|
|
49
|
+
if (poly.length < 3) {
|
|
50
|
+
throw new Ts23dError('T3D_RECIPE_INVALID', {
|
|
51
|
+
cause: `sweep section has ${poly.length} point(s)`,
|
|
52
|
+
fix: 'pass a closed polygon of at least 3 points, or { radius, segments }',
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
const area = poly.reduce((a, p, i) => {
|
|
56
|
+
const q = poly[(i + 1) % poly.length] ?? p;
|
|
57
|
+
return a + p[0] * q[1] - q[0] * p[1];
|
|
58
|
+
}, 0);
|
|
59
|
+
const pts = area < 0 ? [...poly].reverse() : poly;
|
|
60
|
+
const nrm = pts.map((p, i): Vec2 => {
|
|
61
|
+
const a = pts[(i + pts.length - 1) % pts.length] ?? p;
|
|
62
|
+
const b = pts[(i + 1) % pts.length] ?? p;
|
|
63
|
+
const e1 = Math.hypot(p[0] - a[0], p[1] - a[1]) || 1;
|
|
64
|
+
const e2 = Math.hypot(b[0] - p[0], b[1] - p[1]) || 1;
|
|
65
|
+
const x = (p[1] - a[1]) / e1 + (b[1] - p[1]) / e2;
|
|
66
|
+
const y = -(p[0] - a[0]) / e1 - (b[0] - p[0]) / e2;
|
|
67
|
+
const l = Math.hypot(x, y) || 1;
|
|
68
|
+
return [x / l, y / l];
|
|
69
|
+
});
|
|
70
|
+
return { pts, nrm };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function sweep(o: SweepOptions): Mesh {
|
|
74
|
+
const arc = arcLengths(o.path);
|
|
75
|
+
const frames = transportFrames(o.path);
|
|
76
|
+
const P = o.path.length;
|
|
77
|
+
if (o.scale && o.scale.length !== P) {
|
|
78
|
+
throw new Ts23dError('T3D_RECIPE_INVALID', {
|
|
79
|
+
cause: `sweep has ${P} path points but ${o.scale.length} scales`,
|
|
80
|
+
fix: 'pass exactly one scale per path point (or omit scale for a constant section)',
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
const sec = section(o.section);
|
|
84
|
+
const S = sec.pts.length;
|
|
85
|
+
const total = arc[P - 1] ?? 1;
|
|
86
|
+
const rect = stripRect(o.strip ?? 'plain');
|
|
87
|
+
const du = (rect.u1 - rect.u0) / rect.metres;
|
|
88
|
+
const twist = ((o.twist ?? 0) * Math.PI) / 180;
|
|
89
|
+
const b = new MeshBuilder();
|
|
90
|
+
/** Rim positions per ring (S each), kept for the caps. */
|
|
91
|
+
const rim = new Float64Array(P * S * 3);
|
|
92
|
+
for (let i = 0; i < P; i++) {
|
|
93
|
+
const c = o.path[i] ?? [0, 0, 0];
|
|
94
|
+
const { t, n, b: bn } = frames[i] ?? { t: [0, 0, 1], n: [1, 0, 0], b: [0, 1, 0] };
|
|
95
|
+
const k = o.scale?.[i] ?? 1;
|
|
96
|
+
// Taper slope dk/ds tilts the smooth normal back along the tangent (a cone, not a cylinder).
|
|
97
|
+
const i0 = Math.max(0, i - 1);
|
|
98
|
+
const i1 = Math.min(P - 1, i + 1);
|
|
99
|
+
const slope =
|
|
100
|
+
((o.scale?.[i1] ?? 1) - (o.scale?.[i0] ?? 1)) / ((arc[i1] ?? 0) - (arc[i0] ?? 0) || 1);
|
|
101
|
+
const rot = (twist * (arc[i] ?? 0)) / total;
|
|
102
|
+
const cr = Math.cos(rot);
|
|
103
|
+
const sr = Math.sin(rot);
|
|
104
|
+
const u = rect.u0 + (arc[i] ?? 0) * du;
|
|
105
|
+
for (let j = 0; j <= S; j++) {
|
|
106
|
+
const [px, py] = sec.pts[j % S] ?? [0, 0];
|
|
107
|
+
const [nx, ny] = sec.nrm[j % S] ?? [1, 0];
|
|
108
|
+
const x = (px * cr - py * sr) * k;
|
|
109
|
+
const y = (px * sr + py * cr) * k;
|
|
110
|
+
const mx = nx * cr - ny * sr;
|
|
111
|
+
const my = nx * sr + ny * cr;
|
|
112
|
+
const back = -slope * (px * nx + py * ny);
|
|
113
|
+
const qx = mx * n[0] + my * bn[0] + back * t[0];
|
|
114
|
+
const qy = mx * n[1] + my * bn[1] + back * t[1];
|
|
115
|
+
const qz = mx * n[2] + my * bn[2] + back * t[2];
|
|
116
|
+
const ql = Math.hypot(qx, qy, qz) || 1;
|
|
117
|
+
const p: Vec3 = [
|
|
118
|
+
c[0] + x * n[0] + y * bn[0],
|
|
119
|
+
c[1] + x * n[1] + y * bn[1],
|
|
120
|
+
c[2] + x * n[2] + y * bn[2],
|
|
121
|
+
];
|
|
122
|
+
if (j < S) rim.set(p, (i * S + j) * 3);
|
|
123
|
+
b.vertex({
|
|
124
|
+
p,
|
|
125
|
+
n: [qx / ql, qy / ql, qz / ql],
|
|
126
|
+
uv: [u, rect.v0 + (j / S) * (rect.v1 - rect.v0)],
|
|
127
|
+
color: o.color,
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
for (let i = 0; i + 1 < P; i++) {
|
|
132
|
+
const r0 = i * (S + 1);
|
|
133
|
+
const r1 = r0 + S + 1;
|
|
134
|
+
for (let j = 0; j < S; j++) b.quad(r0 + j, r0 + j + 1, r1 + j + 1, r1 + j);
|
|
135
|
+
}
|
|
136
|
+
const caps = o.caps === true ? ['start', 'end'] : o.caps ? [o.caps] : [];
|
|
137
|
+
for (const end of caps) {
|
|
138
|
+
const i = end === 'start' ? 0 : P - 1;
|
|
139
|
+
const f = frames[i]?.t ?? [0, 0, 1];
|
|
140
|
+
const sign = end === 'start' ? -1 : 1;
|
|
141
|
+
const nn: Vec3 = [f[0] * sign, f[1] * sign, f[2] * sign];
|
|
142
|
+
const centre = b.vertex({
|
|
143
|
+
p: o.path[i] ?? [0, 0, 0],
|
|
144
|
+
n: nn,
|
|
145
|
+
uv: [rect.u0, rect.v0],
|
|
146
|
+
color: o.color,
|
|
147
|
+
});
|
|
148
|
+
const first = centre + 1;
|
|
149
|
+
for (let j = 0; j < S; j++) {
|
|
150
|
+
const q = (i * S + j) * 3;
|
|
151
|
+
const p: Vec3 = [rim[q] ?? 0, rim[q + 1] ?? 0, rim[q + 2] ?? 0];
|
|
152
|
+
b.vertex({ p, n: nn, uv: [rect.u0, rect.v1], color: o.color });
|
|
153
|
+
}
|
|
154
|
+
for (let j = 0; j < S; j++) {
|
|
155
|
+
const a = first + j;
|
|
156
|
+
const c = first + ((j + 1) % S);
|
|
157
|
+
if (sign > 0) b.tri(centre, a, c);
|
|
158
|
+
else b.tri(centre, c, a);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return b.build();
|
|
162
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
// Vertex colour for terrain from what shapes real mountainsides: altitude bands (forest below the
|
|
2
|
+
// treeline, meadow above it, bare debris higher up), slope (rock where it is too steep to hold
|
|
3
|
+
// soil or snow), hydraulic deposit (scree fans and valley gravel) and flow (damp, dark gullies),
|
|
4
|
+
// and snow above the snowline on slopes gentle enough to hold it. Returns `(x, y, z) => colour`
|
|
5
|
+
// for `heightfield`'s `color`. Boundaries are jittered by an optional noise so bands never read as
|
|
6
|
+
// contour lines.
|
|
7
|
+
|
|
8
|
+
import type { Vec3 } from '../core/index.ts';
|
|
9
|
+
import type { Noise2 } from './noise.ts';
|
|
10
|
+
import { sampler, slopeMap, type TerrainGrid } from './terrain-grid.ts';
|
|
11
|
+
|
|
12
|
+
export interface TerrainPalette {
|
|
13
|
+
readonly forest: Vec3;
|
|
14
|
+
readonly grass: Vec3;
|
|
15
|
+
/** Alpine debris/tundra above the meadows and on deposits. */
|
|
16
|
+
readonly scree: Vec3;
|
|
17
|
+
readonly rock: Vec3;
|
|
18
|
+
readonly snow: Vec3;
|
|
19
|
+
/** Damp ground in gullies and river beds. */
|
|
20
|
+
readonly wet: Vec3;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface TerrainColorOptions {
|
|
24
|
+
readonly palette: TerrainPalette;
|
|
25
|
+
/** Altitude of z = 0 in metres above sea level (z is relative to it). Default 0. */
|
|
26
|
+
readonly datum?: number;
|
|
27
|
+
/** Altitude where forest gives way to meadow, m. */
|
|
28
|
+
readonly treeline: number;
|
|
29
|
+
/** Altitude above which snow lies on gentle slopes, m. */
|
|
30
|
+
readonly snowline: number;
|
|
31
|
+
/** Slope (rise/run) where bare rock takes over: default 0.8 (≈ 39°). */
|
|
32
|
+
readonly rockSlope?: number;
|
|
33
|
+
/** Steepest slope that holds snow: default 1.0 (45°). */
|
|
34
|
+
readonly snowSlope?: number;
|
|
35
|
+
/** Noise in [−1, 1] (metres in) jittering every boundary; `jitter` = its altitude reach, m. */
|
|
36
|
+
readonly noise?: Noise2;
|
|
37
|
+
readonly jitter?: number;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const ramp = (a: number, b: number, x: number): number => {
|
|
41
|
+
const t = Math.min(1, Math.max(0, (x - a) / (b - a)));
|
|
42
|
+
return t * t * (3 - 2 * t);
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export function terrainColor(
|
|
46
|
+
g: TerrainGrid,
|
|
47
|
+
o: TerrainColorOptions,
|
|
48
|
+
): (x: number, y: number, z: number) => Vec3 {
|
|
49
|
+
const slope = sampler(g, slopeMap(g));
|
|
50
|
+
const flow = g.flow ? sampler(g, g.flow) : () => 0;
|
|
51
|
+
const dep = g.deposit ? sampler(g, g.deposit) : () => 0;
|
|
52
|
+
const P = o.palette;
|
|
53
|
+
const datum = o.datum ?? 0;
|
|
54
|
+
const rockS = o.rockSlope ?? 0.8;
|
|
55
|
+
const snowS = o.snowSlope ?? 1;
|
|
56
|
+
const jit = o.jitter ?? 60;
|
|
57
|
+
const c = [0, 0, 0];
|
|
58
|
+
const mix = (to: Vec3, w: number): void => {
|
|
59
|
+
for (let k = 0; k < 3; k++) c[k] = (c[k] ?? 0) + ((to[k] ?? 0) - (c[k] ?? 0)) * w;
|
|
60
|
+
};
|
|
61
|
+
return (x, y, z) => {
|
|
62
|
+
const n = o.noise ? o.noise(x, y) : 0;
|
|
63
|
+
const alt = z + datum + n * jit;
|
|
64
|
+
const s = slope(x, y) * (1 + 0.15 * n);
|
|
65
|
+
c[0] = P.forest[0];
|
|
66
|
+
c[1] = P.forest[1];
|
|
67
|
+
c[2] = P.forest[2];
|
|
68
|
+
mix(P.grass, ramp(o.treeline - 80, o.treeline + 80, alt));
|
|
69
|
+
mix(P.scree, 0.85 * ramp(o.treeline + 350, o.treeline + 800, alt));
|
|
70
|
+
// Forest thins on steep ground well below the treeline too.
|
|
71
|
+
mix(P.grass, ramp(0.55, 0.8, s) * (1 - ramp(o.treeline - 80, o.treeline + 80, alt)) * 0.6);
|
|
72
|
+
// Scree: sediment dropped on sloping ground (fans, cones); flat floors stay vegetated.
|
|
73
|
+
mix(
|
|
74
|
+
P.scree,
|
|
75
|
+
ramp(0.4, 0.8, dep(x, y)) * ramp(0.2, 0.45, s) * (1 - ramp(rockS, rockS + 0.3, s)),
|
|
76
|
+
);
|
|
77
|
+
mix(P.wet, 0.7 * ramp(0.35, 0.8, flow(x, y)) * (1 - ramp(o.snowline - 100, o.snowline, alt)));
|
|
78
|
+
mix(P.rock, ramp(rockS - 0.15, rockS + 0.15, s));
|
|
79
|
+
mix(
|
|
80
|
+
P.snow,
|
|
81
|
+
ramp(o.snowline - 60, o.snowline + 120, alt) * (1 - ramp(snowS - 0.3, snowS + 0.2, s)),
|
|
82
|
+
);
|
|
83
|
+
return [c[0] ?? 0, c[1] ?? 0, c[2] ?? 0];
|
|
84
|
+
};
|
|
85
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
// A terrain as a regular grid of heights in metres, the working surface for erosion and colouring:
|
|
2
|
+
// sample it from any height function, read it back bilinearly at any (x, y) for `heightfield`
|
|
3
|
+
// (so every LOD meshes the SAME eroded terrain at its own density), and relax it with thermal
|
|
4
|
+
// erosion (material above the talus slope slides down). `flow`/`deposit` come from hydraulic
|
|
5
|
+
// erosion and drive terrain colouring.
|
|
6
|
+
|
|
7
|
+
import { count, invalid } from './check.ts';
|
|
8
|
+
|
|
9
|
+
export interface TerrainGrid {
|
|
10
|
+
/** Points along X and Y (cells + 1). Row-major: index = j · nx + i. */
|
|
11
|
+
readonly nx: number;
|
|
12
|
+
readonly ny: number;
|
|
13
|
+
/** [minX, minY, maxX, maxY] in metres. */
|
|
14
|
+
readonly rect: readonly [number, number, number, number];
|
|
15
|
+
readonly z: Float64Array;
|
|
16
|
+
/** Water passage per point, 0..1 (rivers, gullies). Set by hydraulicErosion. */
|
|
17
|
+
readonly flow?: Float64Array;
|
|
18
|
+
/** Sediment laid down per point, 0..1 (scree fans, valley fill). Set by hydraulicErosion. */
|
|
19
|
+
readonly deposit?: Float64Array;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface TerrainGridOptions {
|
|
23
|
+
readonly rect: readonly [number, number, number, number];
|
|
24
|
+
/** Cells per side (points = cells + 1); 256 ≈ 8 m on a 2 km tile. */
|
|
25
|
+
readonly cells: number;
|
|
26
|
+
readonly height: (x: number, y: number) => number;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function terrainGrid(o: TerrainGridOptions): TerrainGrid {
|
|
30
|
+
const [x0, y0, x1, y1] = o.rect;
|
|
31
|
+
if (!(x1 > x0 && y1 > y0))
|
|
32
|
+
invalid(
|
|
33
|
+
`terrain rect [${o.rect.join(', ')}] is empty`,
|
|
34
|
+
'pass [minX, minY, maxX, maxY] with max > min',
|
|
35
|
+
);
|
|
36
|
+
const n = count('terrain cells', o.cells, 2, 4096);
|
|
37
|
+
const z = new Float64Array((n + 1) * (n + 1));
|
|
38
|
+
for (let j = 0; j <= n; j++)
|
|
39
|
+
for (let i = 0; i <= n; i++) {
|
|
40
|
+
const h = o.height(x0 + ((x1 - x0) * i) / n, y0 + ((y1 - y0) * j) / n);
|
|
41
|
+
if (!Number.isFinite(h))
|
|
42
|
+
invalid(`terrain height is ${h} at point (${i}, ${j})`, 'return a finite height in metres');
|
|
43
|
+
z[j * (n + 1) + i] = h;
|
|
44
|
+
}
|
|
45
|
+
return { nx: n + 1, ny: n + 1, rect: o.rect, z };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Bilinear reader of any per-point map of the grid, clamped at the edges, in metres. */
|
|
49
|
+
export function sampler(g: TerrainGrid, map: Float64Array = g.z): (x: number, y: number) => number {
|
|
50
|
+
const [x0, y0, x1, y1] = g.rect;
|
|
51
|
+
const sx = (g.nx - 1) / (x1 - x0);
|
|
52
|
+
const sy = (g.ny - 1) / (y1 - y0);
|
|
53
|
+
return (x, y) => {
|
|
54
|
+
const fx = Math.min(g.nx - 1, Math.max(0, (x - x0) * sx));
|
|
55
|
+
const fy = Math.min(g.ny - 1, Math.max(0, (y - y0) * sy));
|
|
56
|
+
const i = Math.min(g.nx - 2, Math.floor(fx));
|
|
57
|
+
const j = Math.min(g.ny - 2, Math.floor(fy));
|
|
58
|
+
const u = fx - i;
|
|
59
|
+
const v = fy - j;
|
|
60
|
+
const k = j * g.nx + i;
|
|
61
|
+
const a = map[k] ?? 0;
|
|
62
|
+
const b = map[k + 1] ?? 0;
|
|
63
|
+
const c = map[k + g.nx] ?? 0;
|
|
64
|
+
const d = map[k + g.nx + 1] ?? 0;
|
|
65
|
+
return a + (b - a) * u + (c + (d - c) * u - (a + (b - a) * u)) * v;
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Slope (rise / run, tan of the angle) at every point, by central differences. */
|
|
70
|
+
export function slopeMap(g: TerrainGrid): Float64Array {
|
|
71
|
+
const [x0, y0, x1, y1] = g.rect;
|
|
72
|
+
const dx = (x1 - x0) / (g.nx - 1);
|
|
73
|
+
const dy = (y1 - y0) / (g.ny - 1);
|
|
74
|
+
const out = new Float64Array(g.z.length);
|
|
75
|
+
for (let j = 0; j < g.ny; j++)
|
|
76
|
+
for (let i = 0; i < g.nx; i++) {
|
|
77
|
+
const l = g.z[j * g.nx + Math.max(0, i - 1)] ?? 0;
|
|
78
|
+
const r = g.z[j * g.nx + Math.min(g.nx - 1, i + 1)] ?? 0;
|
|
79
|
+
const b = g.z[Math.max(0, j - 1) * g.nx + i] ?? 0;
|
|
80
|
+
const t = g.z[Math.min(g.ny - 1, j + 1) * g.nx + i] ?? 0;
|
|
81
|
+
const sx = (r - l) / (dx * (Math.min(g.nx - 1, i + 1) - Math.max(0, i - 1)));
|
|
82
|
+
const sy = (t - b) / (dy * (Math.min(g.ny - 1, j + 1) - Math.max(0, j - 1)));
|
|
83
|
+
out[j * g.nx + i] = Math.sqrt(sx * sx + sy * sy);
|
|
84
|
+
}
|
|
85
|
+
return out;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export interface ThermalOptions {
|
|
89
|
+
/** Stable slope as rise/run: 0.7 ≈ 35° (scree's angle of repose). Default 0.7. */
|
|
90
|
+
readonly talus?: number;
|
|
91
|
+
readonly iterations?: number;
|
|
92
|
+
/** Share of the excess moved per iteration, 0..0.5. Default 0.4. */
|
|
93
|
+
readonly rate?: number;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Thermal erosion: wherever a neighbour sits lower than the talus slope allows, part of the
|
|
98
|
+
* excess slides down to it. Every iteration reads the old heights and writes a new array, so the
|
|
99
|
+
* result is independent of visiting order.
|
|
100
|
+
*/
|
|
101
|
+
export function thermalErosion(g: TerrainGrid, o: ThermalOptions = {}): TerrainGrid {
|
|
102
|
+
const talus = o.talus ?? 0.7;
|
|
103
|
+
const rate = Math.min(0.5, Math.max(0, o.rate ?? 0.4));
|
|
104
|
+
const iterations = count('thermal iterations', o.iterations ?? 30, 0, 10000);
|
|
105
|
+
const [x0, , x1] = g.rect;
|
|
106
|
+
const cell = (x1 - x0) / (g.nx - 1);
|
|
107
|
+
const D = [-1, 0, 1, 0, 0, -1, 0, 1, -1, -1, 1, -1, -1, 1, 1, 1];
|
|
108
|
+
const limit = D.map((_, k) => (k < 8 ? talus * cell : talus * cell * Math.SQRT2));
|
|
109
|
+
let z = Float64Array.from(g.z);
|
|
110
|
+
for (let it = 0; it < iterations; it++) {
|
|
111
|
+
const next = Float64Array.from(z);
|
|
112
|
+
for (let j = 1; j < g.ny - 1; j++)
|
|
113
|
+
for (let i = 1; i < g.nx - 1; i++) {
|
|
114
|
+
const k = j * g.nx + i;
|
|
115
|
+
const h = z[k] ?? 0;
|
|
116
|
+
let total = 0;
|
|
117
|
+
let most = 0;
|
|
118
|
+
for (let d = 0; d < 16; d += 2) {
|
|
119
|
+
const ex = h - (z[k + (D[d] ?? 0) + (D[d + 1] ?? 0) * g.nx] ?? 0) - (limit[d] ?? 0);
|
|
120
|
+
if (ex > 0) {
|
|
121
|
+
total += ex;
|
|
122
|
+
if (ex > most) most = ex;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
if (total <= 0) continue;
|
|
126
|
+
const move = rate * most;
|
|
127
|
+
next[k] = (next[k] ?? 0) - move;
|
|
128
|
+
for (let d = 0; d < 16; d += 2) {
|
|
129
|
+
const n = k + (D[d] ?? 0) + (D[d + 1] ?? 0) * g.nx;
|
|
130
|
+
const ex = h - (z[n] ?? 0) - (limit[d] ?? 0);
|
|
131
|
+
if (ex > 0) next[n] = (next[n] ?? 0) + (move * ex) / total;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
z = next;
|
|
135
|
+
}
|
|
136
|
+
return { ...g, z };
|
|
137
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
// Lettering layout for `text`: glyph lookup, advances, measureText, and the merge of each glyph's
|
|
2
|
+
// pixels into as few rectangles as possible (runs along a row, then identical runs stacked down
|
|
3
|
+
// the rows), so a stem is one box, not seven. All in pixel units; one pixel = cap height / 7.
|
|
4
|
+
|
|
5
|
+
import { invalid } from './check.ts';
|
|
6
|
+
import { GLYPHS, SUPPORTED_CHARS } from './font-5x7.ts';
|
|
7
|
+
|
|
8
|
+
export type TextFont = '5x7' | 'block';
|
|
9
|
+
|
|
10
|
+
export interface MeasureOptions {
|
|
11
|
+
/** Cap height in metres (the 7 glyph rows). */
|
|
12
|
+
readonly height: number;
|
|
13
|
+
readonly font?: TextFont;
|
|
14
|
+
/** Gap between glyphs in pixels (1 px = height / 7). Default 1. */
|
|
15
|
+
readonly tracking?: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** A filled rectangle: columns [c0, c1) from the line start, rows [r0, r1) from the cap line. */
|
|
19
|
+
export interface PixelRect {
|
|
20
|
+
readonly c0: number;
|
|
21
|
+
readonly c1: number;
|
|
22
|
+
readonly r0: number;
|
|
23
|
+
readonly r1: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const SPACE = 3;
|
|
27
|
+
|
|
28
|
+
/** Rows of one glyph in `font`; block = emboldened (each pixel doubled rightward, 6 wide). */
|
|
29
|
+
function rows(ch: string, font: TextFont): { rows: readonly number[]; width: number } {
|
|
30
|
+
const g = GLYPHS[ch] ?? GLYPHS[ch.toUpperCase()];
|
|
31
|
+
if (!g)
|
|
32
|
+
invalid(
|
|
33
|
+
`text: no glyph for ${JSON.stringify(ch)} (U+${(ch.codePointAt(0) ?? 0).toString(16).toUpperCase().padStart(4, '0')})`,
|
|
34
|
+
`use only these characters (lower case is set as upper case): ${SUPPORTED_CHARS}`,
|
|
35
|
+
);
|
|
36
|
+
if (ch === ' ') return { rows: g, width: SPACE };
|
|
37
|
+
return font === 'block' ? { rows: g.map((r) => (r << 1) | r), width: 6 } : { rows: g, width: 5 };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Width in pixels of a line. */
|
|
41
|
+
function widthPx(str: string, font: TextFont, tracking: number): number {
|
|
42
|
+
const chars = [...str];
|
|
43
|
+
return (
|
|
44
|
+
chars.reduce((w, ch) => w + rows(ch, font).width, 0) + Math.max(0, chars.length - 1) * tracking
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Width of `str` in metres when set at cap `height`. Throws on characters without a glyph. */
|
|
49
|
+
export function measureText(str: string, o: MeasureOptions): number {
|
|
50
|
+
return (widthPx(str, o.font ?? '5x7', o.tracking ?? 1) * o.height) / 7;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** The line as merged pixel rectangles, plus its width in pixels. */
|
|
54
|
+
export function layout(
|
|
55
|
+
str: string,
|
|
56
|
+
font: TextFont,
|
|
57
|
+
tracking: number,
|
|
58
|
+
): { rects: PixelRect[]; width: number } {
|
|
59
|
+
const rects: PixelRect[] = [];
|
|
60
|
+
let pen = 0;
|
|
61
|
+
for (const ch of str) {
|
|
62
|
+
const g = rows(ch, font);
|
|
63
|
+
let open = new Map<string, { c0: number; c1: number; r0: number }>();
|
|
64
|
+
for (let r = 0; r <= 7; r++) {
|
|
65
|
+
const bits = g.rows[r] ?? 0;
|
|
66
|
+
const next = new Map<string, { c0: number; c1: number; r0: number }>();
|
|
67
|
+
let c = 0;
|
|
68
|
+
while (c < g.width) {
|
|
69
|
+
if (!((bits >> (g.width - 1 - c)) & 1)) {
|
|
70
|
+
c++;
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
const start = c;
|
|
74
|
+
while (c < g.width && (bits >> (g.width - 1 - c)) & 1) c++;
|
|
75
|
+
const key = `${start},${c}`;
|
|
76
|
+
next.set(key, open.get(key) ?? { c0: start, c1: c, r0: r });
|
|
77
|
+
}
|
|
78
|
+
for (const [key, run] of open)
|
|
79
|
+
if (!next.has(key)) rects.push({ c0: pen + run.c0, c1: pen + run.c1, r0: run.r0, r1: r });
|
|
80
|
+
open = next;
|
|
81
|
+
}
|
|
82
|
+
pen += g.width + tracking;
|
|
83
|
+
}
|
|
84
|
+
return { rects, width: Math.max(0, pen - tracking) };
|
|
85
|
+
}
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
// Lettering as geometry: name boards, wagon and locomotive lettering, hull markings, signs. Each
|
|
2
|
+
// glyph of the built-in 5 × 7 font (font-5x7.ts; Latin, digits, punctuation, Cyrillic) becomes a
|
|
3
|
+
// few merged rectangles: flush decals lifted 2 mm off the surface (depth 0, 2 triangles each) or
|
|
4
|
+
// raised letters (depth > 0: face + 4 sides, 10 each). Flat normals. `surface(u, v)` gives the
|
|
5
|
+
// face's offset under each letter piece, so lettering stays readable across stepped sheathing,
|
|
6
|
+
// posts and battens (pieces split where the offset changes). Reads along `along`, upright along
|
|
7
|
+
// `up`, facing along × up; `origin` is the baseline point at the `align` anchor.
|
|
8
|
+
|
|
9
|
+
import { type Mesh, MeshBuilder, normalize, type Vec3 } from '../core/index.ts';
|
|
10
|
+
import { type StripId, stripRect } from '../material/index.ts';
|
|
11
|
+
import { invalid } from './check.ts';
|
|
12
|
+
import { stripU, stripV } from './strip-uv.ts';
|
|
13
|
+
import { layout, measureText, type PixelRect, type TextFont } from './text-layout.ts';
|
|
14
|
+
|
|
15
|
+
export { type MeasureOptions, measureText, type TextFont } from './text-layout.ts';
|
|
16
|
+
|
|
17
|
+
export interface TextOptions {
|
|
18
|
+
readonly origin: Vec3;
|
|
19
|
+
/** Reading direction. Default +Y (a sign read from +X). */
|
|
20
|
+
readonly along?: Vec3;
|
|
21
|
+
/** Letter up. Default +Z. */
|
|
22
|
+
readonly up?: Vec3;
|
|
23
|
+
/** Cap height in metres. */
|
|
24
|
+
readonly height: number;
|
|
25
|
+
readonly color: Vec3;
|
|
26
|
+
readonly font?: TextFont;
|
|
27
|
+
/** Raised thickness in metres; 0 = flush decal lifted 2 mm. Default 0. */
|
|
28
|
+
readonly depth?: number;
|
|
29
|
+
/** Gap between glyphs in pixels (height / 7). Default 1. */
|
|
30
|
+
readonly tracking?: number;
|
|
31
|
+
readonly align?: 'left' | 'centre' | 'right';
|
|
32
|
+
/** Shrink the cap height so the line fits this width, metres. */
|
|
33
|
+
readonly maxWidth?: number;
|
|
34
|
+
/** Offset (m, along the facing normal) of the surface under a letter piece. `u` = metres along
|
|
35
|
+
* `along` from `origin` (negative left of a centre/right anchor), `v` = metres up from it. */
|
|
36
|
+
readonly surface?: (u: number, v: number) => number;
|
|
37
|
+
readonly strip?: StripId;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const LIFT = 0.002;
|
|
41
|
+
|
|
42
|
+
export function text(str: string, o: TextOptions): Mesh {
|
|
43
|
+
if (!(Number.isFinite(o.height) && o.height > 0 && o.height < 1000))
|
|
44
|
+
invalid(
|
|
45
|
+
`text height is ${o.height}`,
|
|
46
|
+
'give the cap height in metres, > 0 (e.g. 0.3 for wagon lettering)',
|
|
47
|
+
);
|
|
48
|
+
const depth = o.depth ?? 0;
|
|
49
|
+
if (!(Number.isFinite(depth) && depth >= 0))
|
|
50
|
+
invalid(`text depth is ${depth}`, 'use 0 (flush) or a raised thickness > 0 m');
|
|
51
|
+
const font = o.font ?? '5x7';
|
|
52
|
+
const tracking = o.tracking ?? 1;
|
|
53
|
+
let height = o.height;
|
|
54
|
+
if (o.maxWidth !== undefined) {
|
|
55
|
+
if (!(o.maxWidth > 0))
|
|
56
|
+
invalid(`text maxWidth is ${o.maxWidth}`, 'give the panel width in metres, > 0');
|
|
57
|
+
const w = measureText(str, { height, font, tracking });
|
|
58
|
+
if (w > o.maxWidth) height *= o.maxWidth / w;
|
|
59
|
+
}
|
|
60
|
+
const along = normalize(o.along ?? [0, 1, 0]);
|
|
61
|
+
const up0 = o.up ?? [0, 0, 1];
|
|
62
|
+
const k = up0[0] * along[0] + up0[1] * along[1] + up0[2] * along[2];
|
|
63
|
+
const upR: Vec3 = [up0[0] - along[0] * k, up0[1] - along[1] * k, up0[2] - along[2] * k];
|
|
64
|
+
if (Math.hypot(upR[0], upR[1], upR[2]) < 1e-9)
|
|
65
|
+
invalid('text: up is parallel to along', 'pass an `up` perpendicular to the reading direction');
|
|
66
|
+
const up = normalize(upR);
|
|
67
|
+
const n: Vec3 = [
|
|
68
|
+
along[1] * up[2] - along[2] * up[1],
|
|
69
|
+
along[2] * up[0] - along[0] * up[2],
|
|
70
|
+
along[0] * up[1] - along[1] * up[0],
|
|
71
|
+
];
|
|
72
|
+
const px = height / 7;
|
|
73
|
+
const { rects, width } = layout(str, font, tracking);
|
|
74
|
+
const start = o.align === 'centre' ? (-width * px) / 2 : o.align === 'right' ? -width * px : 0;
|
|
75
|
+
const rect = stripRect(o.strip ?? 'plain');
|
|
76
|
+
const b = new MeshBuilder();
|
|
77
|
+
const at = (u: number, v: number, w: number): Vec3 => [
|
|
78
|
+
o.origin[0] + along[0] * u + up[0] * v + n[0] * w,
|
|
79
|
+
o.origin[1] + along[1] * u + up[1] * v + n[1] * w,
|
|
80
|
+
o.origin[2] + along[2] * u + up[2] * v + n[2] * w,
|
|
81
|
+
];
|
|
82
|
+
/** Quad u0..u1 × v0..v1 at offsets w (per corner), facing `nrm`, CCW from that side. */
|
|
83
|
+
const quad = (
|
|
84
|
+
c: readonly Vec3[],
|
|
85
|
+
nrm: Vec3,
|
|
86
|
+
us: readonly number[],
|
|
87
|
+
vs: readonly number[],
|
|
88
|
+
): void => {
|
|
89
|
+
const ids = c.map((p, i) =>
|
|
90
|
+
b.vertex({
|
|
91
|
+
p,
|
|
92
|
+
n: nrm,
|
|
93
|
+
uv: [
|
|
94
|
+
stripU(rect, us[i] ?? 0),
|
|
95
|
+
stripV(rect, Math.min(1, Math.max(0, (vs[i] ?? 0) / height))),
|
|
96
|
+
],
|
|
97
|
+
color: o.color,
|
|
98
|
+
}),
|
|
99
|
+
);
|
|
100
|
+
const [a = n, e = n, f = n] = c;
|
|
101
|
+
const g = [0, 1, 2].map((j) => {
|
|
102
|
+
const [x, y] = [(j + 1) % 3, (j + 2) % 3];
|
|
103
|
+
return (
|
|
104
|
+
((e[x] ?? 0) - (a[x] ?? 0)) * ((f[y] ?? 0) - (a[y] ?? 0)) -
|
|
105
|
+
((e[y] ?? 0) - (a[y] ?? 0)) * ((f[x] ?? 0) - (a[x] ?? 0))
|
|
106
|
+
);
|
|
107
|
+
});
|
|
108
|
+
const [i0 = 0, i1 = 0, i2 = 0, i3 = 0] = ids;
|
|
109
|
+
if ((g[0] ?? 0) * nrm[0] + (g[1] ?? 0) * nrm[1] + (g[2] ?? 0) * nrm[2] >= 0)
|
|
110
|
+
b.quad(i0, i1, i2, i3);
|
|
111
|
+
else b.quad(i0, i3, i2, i1);
|
|
112
|
+
};
|
|
113
|
+
const neg = (v: Vec3): Vec3 => [-v[0], -v[1], -v[2]];
|
|
114
|
+
for (const piece of pieces(rects, px, start, o.surface)) {
|
|
115
|
+
const { u0, u1, v0, v1, s } = piece;
|
|
116
|
+
const lo = depth > 0 ? s : s + LIFT;
|
|
117
|
+
const hi = depth > 0 ? s + depth : lo;
|
|
118
|
+
quad(
|
|
119
|
+
[at(u0, v0, hi), at(u1, v0, hi), at(u1, v1, hi), at(u0, v1, hi)],
|
|
120
|
+
n,
|
|
121
|
+
[u0, u1, u1, u0],
|
|
122
|
+
[v0, v0, v1, v1],
|
|
123
|
+
);
|
|
124
|
+
if (depth <= 0) continue;
|
|
125
|
+
// Sides: bottom, right, top, left.
|
|
126
|
+
quad(
|
|
127
|
+
[at(u0, v0, lo), at(u1, v0, lo), at(u1, v0, hi), at(u0, v0, hi)],
|
|
128
|
+
neg(up),
|
|
129
|
+
[u0, u1, u1, u0],
|
|
130
|
+
[0, 0, depth, depth],
|
|
131
|
+
);
|
|
132
|
+
quad(
|
|
133
|
+
[at(u1, v0, lo), at(u1, v1, lo), at(u1, v1, hi), at(u1, v0, hi)],
|
|
134
|
+
along,
|
|
135
|
+
[v0, v1, v1, v0],
|
|
136
|
+
[0, 0, depth, depth],
|
|
137
|
+
);
|
|
138
|
+
quad(
|
|
139
|
+
[at(u1, v1, lo), at(u0, v1, lo), at(u0, v1, hi), at(u1, v1, hi)],
|
|
140
|
+
up,
|
|
141
|
+
[u1, u0, u0, u1],
|
|
142
|
+
[0, 0, depth, depth],
|
|
143
|
+
);
|
|
144
|
+
quad(
|
|
145
|
+
[at(u0, v1, lo), at(u0, v0, lo), at(u0, v0, hi), at(u0, v1, hi)],
|
|
146
|
+
neg(along),
|
|
147
|
+
[v1, v0, v0, v1],
|
|
148
|
+
[0, 0, depth, depth],
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
return b.build();
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
interface Piece {
|
|
155
|
+
readonly u0: number;
|
|
156
|
+
readonly u1: number;
|
|
157
|
+
readonly v0: number;
|
|
158
|
+
readonly v1: number;
|
|
159
|
+
/** Surface offset under this piece. */
|
|
160
|
+
readonly s: number;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/** Rects in metres, split into column groups wherever the surface offset changes. */
|
|
164
|
+
function pieces(
|
|
165
|
+
rects: readonly PixelRect[],
|
|
166
|
+
px: number,
|
|
167
|
+
start: number,
|
|
168
|
+
surface?: (u: number, v: number) => number,
|
|
169
|
+
): Piece[] {
|
|
170
|
+
const out: Piece[] = [];
|
|
171
|
+
for (const r of rects) {
|
|
172
|
+
const v0 = (7 - r.r1) * px;
|
|
173
|
+
const v1 = (7 - r.r0) * px;
|
|
174
|
+
if (!surface) {
|
|
175
|
+
out.push({ u0: start + r.c0 * px, u1: start + r.c1 * px, v0, v1, s: 0 });
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
// Each column sits on the highest surface anywhere under it (edges and centre, both ways),
|
|
179
|
+
// so a pixel half over a post is lifted onto the post, not buried in it.
|
|
180
|
+
const lift = (c: number): number => {
|
|
181
|
+
let top = Number.NEGATIVE_INFINITY;
|
|
182
|
+
for (const fu of [0.02, 0.5, 0.98])
|
|
183
|
+
for (const v of [v0 + 0.02 * px, (v0 + v1) / 2, v1 - 0.02 * px])
|
|
184
|
+
top = Math.max(top, surface(start + (c + fu) * px, v));
|
|
185
|
+
return top;
|
|
186
|
+
};
|
|
187
|
+
let c = r.c0;
|
|
188
|
+
while (c < r.c1) {
|
|
189
|
+
const s = lift(c);
|
|
190
|
+
let e = c + 1;
|
|
191
|
+
while (e < r.c1 && Math.abs(lift(e) - s) < 1e-6) e++;
|
|
192
|
+
out.push({ u0: start + c * px, u1: start + e * px, v0, v1, s });
|
|
193
|
+
c = e;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
return out;
|
|
197
|
+
}
|