@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,118 @@
|
|
|
1
|
+
// LOD2 stand-ins, < 300 triangles. 'blob': a lathe of the real crown envelope (binned from the
|
|
2
|
+
// full skeleton, so it is THIS tree's silhouette) whose top apex is the trunk's tip — one shared
|
|
3
|
+
// point, so the crown provably touches the trunk. 'cards': a dozen coarse folded frond strips
|
|
4
|
+
// rooted on the trunk's top cap centre (palms). Smooth normals on the blob, flat on the cards.
|
|
5
|
+
|
|
6
|
+
import { type Mesh, MeshBuilder, type Rng, type Vec3 } from '../core/index.ts';
|
|
7
|
+
import { lathe, sweep, transportFrames } from '../primitives/index.ts';
|
|
8
|
+
import type { TreeParams } from './vegetation-params.ts';
|
|
9
|
+
import type { Stem } from './vegetation-skeleton.ts';
|
|
10
|
+
|
|
11
|
+
export interface Impostor {
|
|
12
|
+
readonly trunk: Mesh;
|
|
13
|
+
readonly leaves: Mesh;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Trunk at 4 path points; `apex` extends it straight up to a point (blob's top). */
|
|
17
|
+
function coarseTrunk(trunk: Stem, p: TreeParams, apex: number | null): Mesh {
|
|
18
|
+
const n = trunk.pts.length - 1;
|
|
19
|
+
const idx = [...new Set([0, Math.round(n / 3), Math.round((2 * n) / 3), n])];
|
|
20
|
+
const pts = idx.map((i): Vec3 => trunk.pts[i] ?? [0, 0, 0]);
|
|
21
|
+
const radii = idx.map((i) => trunk.radii[i] ?? 0.1);
|
|
22
|
+
const last = pts.length - 1;
|
|
23
|
+
const tip = pts[last] ?? [0, 0, 1];
|
|
24
|
+
if (apex !== null && apex > tip[2] + 1e-6) {
|
|
25
|
+
pts.push([tip[0], tip[1], apex]);
|
|
26
|
+
radii.push(0);
|
|
27
|
+
} else radii[last] = 0;
|
|
28
|
+
return sweep({
|
|
29
|
+
section: { radius: 1, segments: 5 },
|
|
30
|
+
path: pts,
|
|
31
|
+
scale: radii,
|
|
32
|
+
color: p.bark,
|
|
33
|
+
strip: 'wood',
|
|
34
|
+
caps: apex === null ? 'end' : false,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function blob(stems: readonly Stem[], p: TreeParams, rng: Rng): Impostor {
|
|
39
|
+
const trunk = stems[0];
|
|
40
|
+
if (!trunk) return { trunk: new MeshBuilder().build(), leaves: new MeshBuilder().build() };
|
|
41
|
+
const tip = trunk.pts[trunk.pts.length - 1] ?? [0, 0, 1];
|
|
42
|
+
const margin = p.leaves.size * 0.6;
|
|
43
|
+
let z0 = Infinity;
|
|
44
|
+
let z1 = tip[2];
|
|
45
|
+
for (const s of stems)
|
|
46
|
+
for (const q of s.level > 0 ? s.pts : []) {
|
|
47
|
+
z0 = Math.min(z0, q[2] - margin * (1 + (p.leaves.hang ?? 0) * 2));
|
|
48
|
+
z1 = Math.max(z1, q[2] + margin);
|
|
49
|
+
}
|
|
50
|
+
if (!Number.isFinite(z0)) z0 = tip[2] * 0.5;
|
|
51
|
+
const BINS = 6;
|
|
52
|
+
const r = new Array<number>(BINS).fill(0.2);
|
|
53
|
+
for (const s of stems)
|
|
54
|
+
for (const q of s.level > 0 ? s.pts : []) {
|
|
55
|
+
const b = Math.min(BINS - 1, Math.max(0, Math.floor(((q[2] - z0) / (z1 - z0)) * BINS)));
|
|
56
|
+
r[b] = Math.max(r[b] ?? 0, Math.hypot(q[0] - tip[0], q[1] - tip[1]) + margin);
|
|
57
|
+
}
|
|
58
|
+
const profile: [number, number][] = [[0, z0]];
|
|
59
|
+
for (let b = 0; b < BINS; b++) profile.push([r[b] ?? 0.2, z0 + ((b + 0.5) / BINS) * (z1 - z0)]);
|
|
60
|
+
profile.push([0, z1]);
|
|
61
|
+
const leaves = lathe({ profile, segments: 10, color: p.leaves.color, at: [tip[0], tip[1], 0] });
|
|
62
|
+
const [cr, cg, cb] = p.leaves.color;
|
|
63
|
+
for (let i = 0; i < leaves.colors.length / 3; i++) {
|
|
64
|
+
const t = ((leaves.positions[i * 3 + 2] ?? 0) - z0) / (z1 - z0);
|
|
65
|
+
const k = (0.55 + 0.5 * t) * (1 + rng.range(-0.08, 0.08));
|
|
66
|
+
leaves.colors.set([cr * k, cg * k, cb * k], i * 3);
|
|
67
|
+
}
|
|
68
|
+
return { trunk: coarseTrunk(trunk, p, z1), leaves };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function frondCards(stems: readonly Stem[], p: TreeParams, level: number): Impostor {
|
|
72
|
+
const trunk = stems[0];
|
|
73
|
+
const mb = new MeshBuilder();
|
|
74
|
+
if (!trunk) return { trunk: mb.build(), leaves: mb.build() };
|
|
75
|
+
const top = trunk.pts[trunk.pts.length - 1] ?? [0, 0, 1];
|
|
76
|
+
const fronds = stems.filter((s) => s.level === level);
|
|
77
|
+
const every = Math.max(1, Math.ceil(fronds.length / 12));
|
|
78
|
+
const [cr, cg, cb] = p.leaves.color;
|
|
79
|
+
for (const [fi, st] of fronds.entries()) {
|
|
80
|
+
if (fi % every !== 0) continue;
|
|
81
|
+
const n = st.pts.length - 1;
|
|
82
|
+
const pts = [top, st.pts[Math.round(n / 2)] ?? top, st.pts[n] ?? top];
|
|
83
|
+
const fr = transportFrames(pts);
|
|
84
|
+
const w = [0.25, 1, 0.35].map((k) => k * st.length * p.leaves.width);
|
|
85
|
+
for (const side of [1, -1]) {
|
|
86
|
+
const edge = pts.map((c, j): Vec3 => {
|
|
87
|
+
const f = fr[j];
|
|
88
|
+
const ww = (w[j] ?? 0) * side;
|
|
89
|
+
if (!f) return c;
|
|
90
|
+
return [
|
|
91
|
+
c[0] + (f.b[0] * 0.85 - f.n[0] * 0.45 * side) * ww,
|
|
92
|
+
c[1] + (f.b[1] * 0.85 - f.n[1] * 0.45 * side) * ww,
|
|
93
|
+
c[2] + (f.b[2] * 0.85 - f.n[2] * 0.45 * side) * ww,
|
|
94
|
+
];
|
|
95
|
+
});
|
|
96
|
+
for (let j = 0; j < 2; j++) {
|
|
97
|
+
const q = [pts[j], pts[j + 1], edge[j + 1], edge[j]] as Vec3[];
|
|
98
|
+
const [a, b, c] = [q[0] ?? top, q[1] ?? top, q[2] ?? top];
|
|
99
|
+
const e1 = [b[0] - a[0], b[1] - a[1], b[2] - a[2]];
|
|
100
|
+
const e2 = [c[0] - a[0], c[1] - a[1], c[2] - a[2]];
|
|
101
|
+
const nx = (e1[1] ?? 0) * (e2[2] ?? 0) - (e1[2] ?? 0) * (e2[1] ?? 0);
|
|
102
|
+
const ny = (e1[2] ?? 0) * (e2[0] ?? 0) - (e1[0] ?? 0) * (e2[2] ?? 0);
|
|
103
|
+
const nz = (e1[0] ?? 0) * (e2[1] ?? 0) - (e1[1] ?? 0) * (e2[0] ?? 0);
|
|
104
|
+
const l = Math.hypot(nx, ny, nz) || 1;
|
|
105
|
+
for (const s of [1, -1]) {
|
|
106
|
+
const nn: Vec3 = [(nx / l) * s, (ny / l) * s, (nz / l) * s];
|
|
107
|
+
const ids = q.map((v, k) => {
|
|
108
|
+
const sh = k < 2 ? 0.7 : 1;
|
|
109
|
+
return mb.vertex({ p: v, n: nn, uv: [0, 0], color: [cr * sh, cg * sh, cb * sh] });
|
|
110
|
+
});
|
|
111
|
+
const [i0 = 0, i1 = 0, i2 = 0, i3 = 0] = s > 0 ? ids : [...ids].reverse();
|
|
112
|
+
mb.quad(i0, i1, i2, i3);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return { trunk: coarseTrunk(trunk, p, null), leaves: mb.build() };
|
|
118
|
+
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
// Leaf cards: double-sided `planar` polygons, each standing for a cluster of leaves, with per-vertex
|
|
2
|
+
// colour (darker at the stem, lighter at the tip, darker deep inside the crown). LOD0 roots every
|
|
3
|
+
// card on an exact twig vertex (`transportFrames` = the sweep's own frames); LOD1 replaces each
|
|
4
|
+
// twig by one bigger cluster card rooted on its parent's bark.
|
|
5
|
+
|
|
6
|
+
import { type Mesh, merge, type Rng, type Vec3 } from '../core/index.ts';
|
|
7
|
+
import { planar, transportFrames } from '../primitives/index.ts';
|
|
8
|
+
import { type LeafParams, OUTLINES } from './vegetation-params.ts';
|
|
9
|
+
import type { Stem } from './vegetation-skeleton.ts';
|
|
10
|
+
|
|
11
|
+
const unit = (a: Vec3): Vec3 => {
|
|
12
|
+
const l = Math.hypot(a[0], a[1], a[2]) || 1;
|
|
13
|
+
return [a[0] / l, a[1] / l, a[2] / l];
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
/** Crown occlusion: cards deep inside / low in the crown are darker. */
|
|
17
|
+
export interface CrownShade {
|
|
18
|
+
readonly centre: Vec3;
|
|
19
|
+
readonly radius: number;
|
|
20
|
+
readonly zMin: number;
|
|
21
|
+
readonly zMax: number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function crownShade(stems: readonly Stem[], level: number): CrownShade {
|
|
25
|
+
let r = 0.1;
|
|
26
|
+
let z0 = Infinity;
|
|
27
|
+
let z1 = -Infinity;
|
|
28
|
+
const t = stems[0]?.pts;
|
|
29
|
+
const c = t?.[t.length - 1] ?? [0, 0, 0];
|
|
30
|
+
for (const s of stems) {
|
|
31
|
+
if (s.level < level) continue;
|
|
32
|
+
for (const p of s.pts) {
|
|
33
|
+
r = Math.max(r, Math.hypot(p[0] - c[0], p[1] - c[1]));
|
|
34
|
+
z0 = Math.min(z0, p[2]);
|
|
35
|
+
z1 = Math.max(z1, p[2]);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return { centre: c, radius: r, zMin: z0, zMax: Math.max(z1, z0 + 0.1) };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function card(
|
|
42
|
+
leaf: LeafParams,
|
|
43
|
+
base: Vec3,
|
|
44
|
+
dir: Vec3,
|
|
45
|
+
side: Vec3,
|
|
46
|
+
len: number,
|
|
47
|
+
shade: CrownShade,
|
|
48
|
+
rng: Rng,
|
|
49
|
+
): Mesh {
|
|
50
|
+
const outline = OUTLINES[leaf.shape === 'frond' ? 'oval' : leaf.shape];
|
|
51
|
+
const w = len * leaf.width;
|
|
52
|
+
const points = outline.map(
|
|
53
|
+
([u, v]): Vec3 => [
|
|
54
|
+
base[0] + dir[0] * u * len + side[0] * v * w,
|
|
55
|
+
base[1] + dir[1] * u * len + side[1] * v * w,
|
|
56
|
+
base[2] + dir[2] * u * len + side[2] * v * w,
|
|
57
|
+
],
|
|
58
|
+
);
|
|
59
|
+
const m = planar({ points, color: leaf.color, doubleSided: true });
|
|
60
|
+
const k = 1 + rng.range(-leaf.colorVar, leaf.colorVar);
|
|
61
|
+
const hue = rng.range(-leaf.colorVar, leaf.colorVar) * 0.6;
|
|
62
|
+
const radial = Math.hypot(base[0] - shade.centre[0], base[1] - shade.centre[1]) / shade.radius;
|
|
63
|
+
const up = (base[2] - shade.zMin) / (shade.zMax - shade.zMin);
|
|
64
|
+
const ao = 0.5 + 0.35 * Math.min(1, radial) ** 0.8 + 0.2 * Math.max(0, Math.min(1, up));
|
|
65
|
+
const [r, g, b] = leaf.color;
|
|
66
|
+
for (let i = 0; i < m.colors.length / 3; i++) {
|
|
67
|
+
const u = outline[i % outline.length]?.[0] ?? 0;
|
|
68
|
+
const s = ao * k * (0.7 + 0.4 * u);
|
|
69
|
+
m.colors[i * 3] = r * s * (1 + hue);
|
|
70
|
+
m.colors[i * 3 + 1] = g * s;
|
|
71
|
+
m.colors[i * 3 + 2] = b * s * (1 - hue);
|
|
72
|
+
}
|
|
73
|
+
return m;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Splay outward from the stem, lift a little, then droop towards the ground by `hang`. */
|
|
77
|
+
function aim(radial: Vec3, along: Vec3, leaf: LeafParams, rng: Rng): { dir: Vec3; side: Vec3 } {
|
|
78
|
+
const hang = leaf.hang ?? 0;
|
|
79
|
+
const flat = leaf.flat ?? 0;
|
|
80
|
+
const lift = rng.range(-0.3, 0.5);
|
|
81
|
+
const out = unit([
|
|
82
|
+
radial[0] * 0.9 + along[0] * 0.7,
|
|
83
|
+
radial[1] * 0.9 + along[1] * 0.7,
|
|
84
|
+
radial[2] * 0.9 + along[2] * 0.7 + lift,
|
|
85
|
+
]);
|
|
86
|
+
const d0 = unit([out[0] * (1 - hang), out[1] * (1 - hang), out[2] * (1 - hang) - hang]);
|
|
87
|
+
const dir = unit([d0[0], d0[1], d0[2] * (1 - flat)]);
|
|
88
|
+
const level = unit([-dir[1], dir[0], 0]);
|
|
89
|
+
const s1 = unit([
|
|
90
|
+
dir[1] * radial[2] - dir[2] * radial[1],
|
|
91
|
+
dir[2] * radial[0] - dir[0] * radial[2],
|
|
92
|
+
dir[0] * radial[1] - dir[1] * radial[0],
|
|
93
|
+
]);
|
|
94
|
+
const s0 = Math.hypot(...s1) < 0.5 ? level : s1;
|
|
95
|
+
// Flat cards (pine pads) lie level: side goes horizontal, roll fades out.
|
|
96
|
+
const side = unit([
|
|
97
|
+
s0[0] + (level[0] - s0[0]) * flat,
|
|
98
|
+
s0[1] + (level[1] - s0[1]) * flat,
|
|
99
|
+
s0[2] + (level[2] - s0[2]) * flat,
|
|
100
|
+
]);
|
|
101
|
+
const roll = rng.range(-0.7, 0.7) * (1 - flat);
|
|
102
|
+
const t: Vec3 = [
|
|
103
|
+
dir[1] * side[2] - dir[2] * side[1],
|
|
104
|
+
dir[2] * side[0] - dir[0] * side[2],
|
|
105
|
+
dir[0] * side[1] - dir[1] * side[0],
|
|
106
|
+
];
|
|
107
|
+
const c = Math.cos(roll);
|
|
108
|
+
const s = Math.sin(roll);
|
|
109
|
+
return { dir, side: [side[0] * c + t[0] * s, side[1] * c + t[1] * s, side[2] * c + t[2] * s] };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** LOD0: `perStem` cards along the outer 70% of every terminal stem, rooted on a ring vertex. */
|
|
113
|
+
export function leafCards(
|
|
114
|
+
stems: readonly Stem[],
|
|
115
|
+
level: number,
|
|
116
|
+
ringSegments: number,
|
|
117
|
+
leaf: LeafParams,
|
|
118
|
+
shade: CrownShade,
|
|
119
|
+
rng: Rng,
|
|
120
|
+
): Mesh {
|
|
121
|
+
const out: Mesh[] = [];
|
|
122
|
+
for (const st of stems) {
|
|
123
|
+
if (st.level !== level) continue;
|
|
124
|
+
const frames = transportFrames(st.pts);
|
|
125
|
+
const res = st.pts.length - 1;
|
|
126
|
+
for (let i = 0; i < leaf.perStem; i++) {
|
|
127
|
+
const from = leaf.from ?? 0.3;
|
|
128
|
+
const t = from + ((1 - from) * (i + 1)) / leaf.perStem;
|
|
129
|
+
const j = Math.max(1, Math.min(res, Math.round(t * res)));
|
|
130
|
+
const f = frames[j];
|
|
131
|
+
const c = st.pts[j];
|
|
132
|
+
if (!f || !c) continue;
|
|
133
|
+
const a = (rng.int(0, ringSegments - 1) / ringSegments) * Math.PI * 2;
|
|
134
|
+
const radial: Vec3 = [
|
|
135
|
+
Math.cos(a) * f.n[0] + Math.sin(a) * f.b[0],
|
|
136
|
+
Math.cos(a) * f.n[1] + Math.sin(a) * f.b[1],
|
|
137
|
+
Math.cos(a) * f.n[2] + Math.sin(a) * f.b[2],
|
|
138
|
+
];
|
|
139
|
+
const r = st.radii[j] ?? 0;
|
|
140
|
+
const base: Vec3 = [c[0] + radial[0] * r, c[1] + radial[1] * r, c[2] + radial[2] * r];
|
|
141
|
+
const { dir, side } = aim(radial, f.t, leaf, rng);
|
|
142
|
+
out.push(card(leaf, base, dir, side, leaf.size * rng.range(0.8, 1.2), shade, rng));
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return merge(out);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** LOD1: one cluster card per (unemitted) twig, rooted where the twig would leave its parent. */
|
|
149
|
+
export function clusterCards(
|
|
150
|
+
stems: readonly Stem[],
|
|
151
|
+
level: number,
|
|
152
|
+
leaf: LeafParams,
|
|
153
|
+
shade: CrownShade,
|
|
154
|
+
rng: Rng,
|
|
155
|
+
): Mesh {
|
|
156
|
+
const out: Mesh[] = [];
|
|
157
|
+
for (const st of stems) {
|
|
158
|
+
if (st.level !== level) continue;
|
|
159
|
+
const tip = st.pts[st.pts.length - 1] ?? st.surface;
|
|
160
|
+
const p0 = st.pts[0] ?? st.surface;
|
|
161
|
+
const along = unit([tip[0] - p0[0], tip[1] - p0[1], tip[2] - p0[2]]);
|
|
162
|
+
const radial = unit([st.surface[0] - p0[0], st.surface[1] - p0[1], st.surface[2] - p0[2]]);
|
|
163
|
+
const { dir, side } = aim(radial, along, leaf, rng);
|
|
164
|
+
const len = st.length * 1.1 + leaf.size;
|
|
165
|
+
out.push(card(leaf, st.surface, dir, side, len, shade, rng));
|
|
166
|
+
}
|
|
167
|
+
return merge(out);
|
|
168
|
+
}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
// Skeleton → wood meshes: stems as tapered sweeps (LOD sets radial and path resolution), the
|
|
2
|
+
// trunk with a bark gradient and marks, the buttressed root flare as a lobed `loft`. Plus the
|
|
3
|
+
// fit that scales the finished tree onto its real height and crown spread.
|
|
4
|
+
|
|
5
|
+
import { boundsOf, type Mesh, merge, type Rng, type Vec3 } from '../core/index.ts';
|
|
6
|
+
import { loft, ringXY, sweep } from '../primitives/index.ts';
|
|
7
|
+
import type { TreeParams } from './vegetation-params.ts';
|
|
8
|
+
import type { Stem } from './vegetation-skeleton.ts';
|
|
9
|
+
|
|
10
|
+
/** Radial segments per [level][lod]. The trunk's 8 keeps ring vertices on ±Y (exact DBH). */
|
|
11
|
+
const RADIAL = [
|
|
12
|
+
[8, 6, 5],
|
|
13
|
+
[6, 4, 4],
|
|
14
|
+
[4, 3, 3],
|
|
15
|
+
[3, 3, 3],
|
|
16
|
+
];
|
|
17
|
+
export const radialSegments = (level: number, lod: number): number =>
|
|
18
|
+
RADIAL[Math.min(3, level)]?.[lod] ?? 3;
|
|
19
|
+
|
|
20
|
+
/** Every `step`-th path point (always keeping both ends): LOD1 halves path resolution. */
|
|
21
|
+
export function coarsen(st: Stem, step: number): { pts: Vec3[]; radii: number[] } {
|
|
22
|
+
const pts: Vec3[] = [];
|
|
23
|
+
const radii: number[] = [];
|
|
24
|
+
const last = st.pts.length - 1;
|
|
25
|
+
for (let i = 0; i <= last; i += step) {
|
|
26
|
+
pts.push(st.pts[i] ?? [0, 0, 0]);
|
|
27
|
+
radii.push(st.radii[i] ?? 0);
|
|
28
|
+
}
|
|
29
|
+
if (last % step !== 0) {
|
|
30
|
+
pts.push(st.pts[last] ?? [0, 0, 0]);
|
|
31
|
+
radii.push(st.radii[last] ?? 0);
|
|
32
|
+
}
|
|
33
|
+
return { pts, radii };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** All stems with level in [lo, hi], merged. */
|
|
37
|
+
export function stemsMesh(
|
|
38
|
+
stems: readonly Stem[],
|
|
39
|
+
lo: number,
|
|
40
|
+
hi: number,
|
|
41
|
+
lod: number,
|
|
42
|
+
color: Vec3,
|
|
43
|
+
): Mesh {
|
|
44
|
+
const out: Mesh[] = [];
|
|
45
|
+
for (const st of stems) {
|
|
46
|
+
if (st.level < lo || st.level > hi) continue;
|
|
47
|
+
const { pts, radii } = coarsen(st, lod === 0 ? 1 : 2);
|
|
48
|
+
out.push(
|
|
49
|
+
sweep({
|
|
50
|
+
section: { radius: 1, segments: radialSegments(st.level, lod) },
|
|
51
|
+
path: pts,
|
|
52
|
+
scale: radii,
|
|
53
|
+
color,
|
|
54
|
+
strip: 'wood',
|
|
55
|
+
}),
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
return merge(out);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const mix = (a: Vec3, b: Vec3, t: number): [number, number, number] => [
|
|
62
|
+
a[0] + (b[0] - a[0]) * t,
|
|
63
|
+
a[1] + (b[1] - a[1]) * t,
|
|
64
|
+
a[2] + (b[2] - a[2]) * t,
|
|
65
|
+
];
|
|
66
|
+
|
|
67
|
+
/** Bark colour by height (bark → barkTop) with seeded marks; rewrites the mesh's colours. */
|
|
68
|
+
export function barkTint(m: Mesh, p: TreeParams, top: number, rng: Rng): Mesh {
|
|
69
|
+
const hi = p.barkTop ?? p.bark;
|
|
70
|
+
for (let i = 0; i < m.colors.length / 3; i++) {
|
|
71
|
+
const z = m.positions[i * 3 + 2] ?? 0;
|
|
72
|
+
const t = Math.min(1, Math.max(0, (z / Math.max(0.1, top) - 0.3) / 0.4));
|
|
73
|
+
let c = mix(p.bark, hi, t * t * (3 - 2 * t));
|
|
74
|
+
if (p.barkMarks && rng.next() < p.barkMarks.amount) c = mix(c, p.barkMarks.color, 0.85);
|
|
75
|
+
const k = 1 + rng.range(-0.06, 0.06);
|
|
76
|
+
m.colors.set([c[0] * k, c[1] * k, c[2] * k], i * 3);
|
|
77
|
+
}
|
|
78
|
+
return m;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** The trunk, radius scaled by `radiusScale` (the fit's 1/sy keeps DBH exact after scaling). */
|
|
82
|
+
export function trunkMesh(
|
|
83
|
+
trunk: Stem,
|
|
84
|
+
p: TreeParams,
|
|
85
|
+
lod: number,
|
|
86
|
+
radiusScale: number,
|
|
87
|
+
rng: Rng,
|
|
88
|
+
): Mesh {
|
|
89
|
+
const { pts, radii } = coarsen(trunk, lod === 0 ? 1 : 2);
|
|
90
|
+
const m = sweep({
|
|
91
|
+
section: { radius: 1, segments: radialSegments(0, lod) },
|
|
92
|
+
path: pts,
|
|
93
|
+
scale: radii.map((r) => r * radiusScale),
|
|
94
|
+
color: p.bark,
|
|
95
|
+
strip: 'wood',
|
|
96
|
+
caps: p.impostor === 'cards' ? 'end' : false,
|
|
97
|
+
});
|
|
98
|
+
return barkTint(m, p, pts[pts.length - 1]?.[2] ?? 1, rng);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** Trunk axis point at height z (linear between path points). */
|
|
102
|
+
function axisAt(trunk: Stem, z: number): Vec3 {
|
|
103
|
+
for (let i = 1; i < trunk.pts.length; i++) {
|
|
104
|
+
const a = trunk.pts[i - 1] ?? [0, 0, 0];
|
|
105
|
+
const b = trunk.pts[i] ?? a;
|
|
106
|
+
if (b[2] >= z || i === trunk.pts.length - 1) {
|
|
107
|
+
const f = Math.min(1, Math.max(0, (z - a[2]) / (b[2] - a[2] || 1)));
|
|
108
|
+
return [a[0] + (b[0] - a[0]) * f, a[1] + (b[1] - a[1]) * f, z];
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return [0, 0, z];
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Buttressed flare: `p.roots` lobes, wide at the ground, melting into the trunk by ~2·DBH. */
|
|
115
|
+
export function rootsMesh(
|
|
116
|
+
trunk: Stem,
|
|
117
|
+
p: TreeParams,
|
|
118
|
+
lod: number,
|
|
119
|
+
radiusScale: number,
|
|
120
|
+
rng: Rng,
|
|
121
|
+
): Mesh {
|
|
122
|
+
const r0 = (trunk.radii[0] ?? 0.2) * radiusScale;
|
|
123
|
+
const lobes = p.roots ?? 0;
|
|
124
|
+
const phase = rng.range(0, Math.PI * 2);
|
|
125
|
+
const levels = [
|
|
126
|
+
{ z: -0.1, k: 1.7, amp: 0.7 },
|
|
127
|
+
{ z: 0.5 * r0, k: 1.3, amp: 0.4 },
|
|
128
|
+
{ z: 1.3 * r0, k: 1.04, amp: 0.15 },
|
|
129
|
+
{ z: 2.4 * r0, k: 0.9, amp: 0 },
|
|
130
|
+
];
|
|
131
|
+
const segs = lod === 0 ? 16 : 10;
|
|
132
|
+
const rings = levels.map(({ z, k, amp }) => {
|
|
133
|
+
const c = axisAt(trunk, z);
|
|
134
|
+
const lobe = (a: number): number => 1 + amp * Math.max(0, Math.cos(lobes * (a - phase))) ** 2;
|
|
135
|
+
return ringXY(c, r0 * k, r0 * k, segs, lobe);
|
|
136
|
+
});
|
|
137
|
+
const m = loft({ rings, color: p.bark, strip: 'wood' });
|
|
138
|
+
return barkTint(m, p, trunk.pts[trunk.pts.length - 1]?.[2] ?? 1, rng);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** Per-axis factors that bring the union of `meshes` to `target` = [X, Y, Z] spans. */
|
|
142
|
+
export function fitFactors(meshes: readonly Mesh[], target: Vec3): Vec3 {
|
|
143
|
+
const lo = [Infinity, Infinity, Infinity];
|
|
144
|
+
const hi = [-Infinity, -Infinity, -Infinity];
|
|
145
|
+
for (const m of meshes) {
|
|
146
|
+
if (m.indices.length === 0) continue;
|
|
147
|
+
const b = boundsOf(m);
|
|
148
|
+
for (let k = 0; k < 3; k++) {
|
|
149
|
+
lo[k] = Math.min(lo[k] ?? 0, b.min[k] ?? 0);
|
|
150
|
+
hi[k] = Math.max(hi[k] ?? 0, b.max[k] ?? 0);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
const f = (k: 0 | 1 | 2): number => target[k] / Math.max(1e-6, (hi[k] ?? 1) - (lo[k] ?? 0));
|
|
154
|
+
return [f(0), f(1), f(2)];
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/** Non-uniform scale about the origin; normals by the inverse transpose, renormalised. */
|
|
158
|
+
export function scaleMesh(m: Mesh, s: Vec3): Mesh {
|
|
159
|
+
const positions = new Float32Array(m.positions.length);
|
|
160
|
+
const normals = new Float32Array(m.normals.length);
|
|
161
|
+
for (let i = 0; i < positions.length; i += 3) {
|
|
162
|
+
positions[i] = (m.positions[i] ?? 0) * s[0];
|
|
163
|
+
positions[i + 1] = (m.positions[i + 1] ?? 0) * s[1];
|
|
164
|
+
positions[i + 2] = (m.positions[i + 2] ?? 0) * s[2];
|
|
165
|
+
const nx = (m.normals[i] ?? 0) / s[0];
|
|
166
|
+
const ny = (m.normals[i + 1] ?? 0) / s[1];
|
|
167
|
+
const nz = (m.normals[i + 2] ?? 0) / s[2];
|
|
168
|
+
const l = Math.hypot(nx, ny, nz) || 1;
|
|
169
|
+
normals[i] = nx / l;
|
|
170
|
+
normals[i + 1] = ny / l;
|
|
171
|
+
normals[i + 2] = nz / l;
|
|
172
|
+
}
|
|
173
|
+
return { ...m, positions, normals };
|
|
174
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
// Tree vocabulary: Weber & Penn 1995 ("Creation and Rendering of Realistic Trees", SIGGRAPH)
|
|
2
|
+
// parameters, trimmed to what a game tree needs. Plain data, so presets are objects a recipe
|
|
3
|
+
// spreads and overrides.
|
|
4
|
+
|
|
5
|
+
import type { Vec2, Vec3 } from '../core/index.ts';
|
|
6
|
+
|
|
7
|
+
/** Crown envelope: how level-1 branch length varies up the trunk (Weber–Penn `Shape`). */
|
|
8
|
+
export type CrownShape =
|
|
9
|
+
| 'conical'
|
|
10
|
+
| 'spherical'
|
|
11
|
+
| 'hemispherical'
|
|
12
|
+
| 'cylindrical'
|
|
13
|
+
| 'tapered'
|
|
14
|
+
| 'flame';
|
|
15
|
+
|
|
16
|
+
/** One recursion level. Level 0 is the trunk; levels 1–3 are branches, sub-branches, twigs. */
|
|
17
|
+
export interface BranchLevel {
|
|
18
|
+
/** Children per parent (level 1: along the trunk). Ignored for level 0. */
|
|
19
|
+
readonly branches: number;
|
|
20
|
+
/** Level 0: fraction of tree height. Level ≥ 1: fraction of the parent's length. */
|
|
21
|
+
readonly length: number;
|
|
22
|
+
/** ± relative random length variation. */
|
|
23
|
+
readonly lengthVar?: number;
|
|
24
|
+
/** Degrees between child and parent axis at the attachment. */
|
|
25
|
+
readonly downAngle: number;
|
|
26
|
+
/** Added to downAngle from the first child (bottom) to the last (top) of the parent's zone. */
|
|
27
|
+
readonly downAngleV?: number;
|
|
28
|
+
/** Phyllotaxis step about the parent, degrees (golden angle 137.5° by default). */
|
|
29
|
+
readonly rotate?: number;
|
|
30
|
+
/** Total bend over the stem, degrees: + towards the sky, − towards the ground. Trunk: + bends
|
|
31
|
+
* back against `lean` (towards −X). */
|
|
32
|
+
readonly curve?: number;
|
|
33
|
+
/** If set, the second half bends by this instead (S-curves, weeping tips). */
|
|
34
|
+
readonly curveBack?: number;
|
|
35
|
+
/** Seeded wobble per path segment, ± degrees. */
|
|
36
|
+
readonly curveVar?: number;
|
|
37
|
+
/** Where children start along this stem's parent, as a fraction (level 1 uses `baseSize`). */
|
|
38
|
+
readonly start?: number;
|
|
39
|
+
/** Path segments at LOD0 (LOD1 halves them). */
|
|
40
|
+
readonly segments: number;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export type LeafShape = 'oval' | 'lobed' | 'tuft' | 'strand' | 'frond';
|
|
44
|
+
|
|
45
|
+
/** Card outlines: u along the card (0 = stem end), v across (±0.5). */
|
|
46
|
+
export const OUTLINES: Record<Exclude<LeafShape, 'frond'>, readonly Vec2[]> = {
|
|
47
|
+
oval: [
|
|
48
|
+
[0, 0],
|
|
49
|
+
[0.25, -0.4],
|
|
50
|
+
[0.65, -0.5],
|
|
51
|
+
[1, 0],
|
|
52
|
+
[0.65, 0.5],
|
|
53
|
+
[0.25, 0.4],
|
|
54
|
+
],
|
|
55
|
+
lobed: [
|
|
56
|
+
[0, 0],
|
|
57
|
+
[0.3, -0.45],
|
|
58
|
+
[0.5, -0.3],
|
|
59
|
+
[0.75, -0.5],
|
|
60
|
+
[1, 0],
|
|
61
|
+
[0.75, 0.5],
|
|
62
|
+
[0.5, 0.3],
|
|
63
|
+
[0.3, 0.45],
|
|
64
|
+
],
|
|
65
|
+
tuft: [
|
|
66
|
+
[0, 0],
|
|
67
|
+
[0.35, -0.5],
|
|
68
|
+
[0.45, -0.15],
|
|
69
|
+
[0.75, -0.4],
|
|
70
|
+
[1, 0],
|
|
71
|
+
[0.75, 0.4],
|
|
72
|
+
[0.45, 0.15],
|
|
73
|
+
[0.35, 0.5],
|
|
74
|
+
],
|
|
75
|
+
strand: [
|
|
76
|
+
[0, 0],
|
|
77
|
+
[0.1, -0.5],
|
|
78
|
+
[1, 0],
|
|
79
|
+
[0.1, 0.5],
|
|
80
|
+
],
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
export interface LeafParams {
|
|
84
|
+
readonly shape: LeafShape;
|
|
85
|
+
/** Card length at LOD0, metres. Cards stand for a cluster of leaves, not one leaf. */
|
|
86
|
+
readonly size: number;
|
|
87
|
+
/** Card width as a fraction of its length (fronds: leaflet length as a fraction of frond). */
|
|
88
|
+
readonly width: number;
|
|
89
|
+
/** Cards per terminal stem (fronds: leaflets per side). */
|
|
90
|
+
readonly perStem: number;
|
|
91
|
+
/** Where cards start along a terminal stem, 0–1 (default 0.3; tufts sit near the tip). */
|
|
92
|
+
readonly from?: number;
|
|
93
|
+
readonly color: Vec3;
|
|
94
|
+
/** ± relative colour variation per card. */
|
|
95
|
+
readonly colorVar: number;
|
|
96
|
+
/** 0–1: lay cards level, like the horizontal needle pads of a pine. */
|
|
97
|
+
readonly flat?: number;
|
|
98
|
+
/** 0 = cards splay outward, 1 = they hang straight down (willow strands). */
|
|
99
|
+
readonly hang?: number;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export interface TreeParams {
|
|
103
|
+
readonly shape: CrownShape;
|
|
104
|
+
/** Fraction of the trunk free of branches (clear bole). */
|
|
105
|
+
readonly baseSize: number;
|
|
106
|
+
/** [trunk, level 1, level 2?, level 3?]. */
|
|
107
|
+
readonly levels: readonly BranchLevel[];
|
|
108
|
+
/** Child radius = parent radius · (child length / parent length)^ratioPower. */
|
|
109
|
+
readonly ratioPower: number;
|
|
110
|
+
/** Trunk radius at the top as a fraction of its radius at breast height. */
|
|
111
|
+
readonly taper: number;
|
|
112
|
+
/** Trunk lean from vertical towards +X, degrees. The trunk stays in the XZ plane. */
|
|
113
|
+
readonly lean?: number;
|
|
114
|
+
readonly bark: Vec3;
|
|
115
|
+
/** Bark colour at the top of the trunk (Scots pine's orange upper bark); default `bark`. */
|
|
116
|
+
readonly barkTop?: Vec3;
|
|
117
|
+
/** Dark bark marks scattered per vertex (birch lenticels): colour + fraction of vertices. */
|
|
118
|
+
readonly barkMarks?: { readonly color: Vec3; readonly amount: number };
|
|
119
|
+
readonly leaves: LeafParams;
|
|
120
|
+
/** Buttress lobes in the root flare at the base (LOD0/1); 0 or absent = no `roots` part. */
|
|
121
|
+
readonly roots?: number;
|
|
122
|
+
/** Fruit hung under the crown (coconuts), merged into `branches`. LOD0 only. */
|
|
123
|
+
readonly fruit?: { readonly count: number; readonly radius: number; readonly color: Vec3 };
|
|
124
|
+
/** LOD2 stand-in: a lathe of the crown envelope, or a few coarse frond cards. */
|
|
125
|
+
readonly impostor: 'blob' | 'cards';
|
|
126
|
+
/** Typical mature size this preset was tuned on (used when the caller omits spread/dbh). */
|
|
127
|
+
readonly typical: { readonly height: number; readonly spread: number; readonly dbh: number };
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** Weber–Penn ShapeRatio; `r` is 1 at the crown base and 0 at the trunk tip. */
|
|
131
|
+
export function shapeRatio(shape: CrownShape, r: number): number {
|
|
132
|
+
const x = Math.min(1, Math.max(0, r));
|
|
133
|
+
switch (shape) {
|
|
134
|
+
case 'conical':
|
|
135
|
+
return 0.2 + 0.8 * x;
|
|
136
|
+
case 'spherical':
|
|
137
|
+
return 0.2 + 0.8 * Math.sin(Math.PI * x);
|
|
138
|
+
case 'hemispherical':
|
|
139
|
+
return 0.2 + 0.8 * Math.sin((Math.PI / 2) * x);
|
|
140
|
+
case 'cylindrical':
|
|
141
|
+
return 1;
|
|
142
|
+
case 'tapered':
|
|
143
|
+
return 0.5 + 0.5 * x;
|
|
144
|
+
case 'flame':
|
|
145
|
+
return x <= 0.7 ? 0.15 + (0.85 * x) / 0.7 : 0.15 + (0.85 * (1 - x)) / 0.3;
|
|
146
|
+
}
|
|
147
|
+
}
|