@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,55 @@
|
|
|
1
|
+
// A flat polygon in 3D (fan-triangulated in its own plane): panels, decks, hatches, plates,
|
|
2
|
+
// fender tops, sign boards. One-sided; pass `doubleSided` for thin sheets seen from both sides.
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
cross,
|
|
6
|
+
dot,
|
|
7
|
+
type Mesh,
|
|
8
|
+
MeshBuilder,
|
|
9
|
+
normalize,
|
|
10
|
+
sub,
|
|
11
|
+
type Vec2,
|
|
12
|
+
type Vec3,
|
|
13
|
+
} from '../core/index.ts';
|
|
14
|
+
import { type StripId, stripRect } from '../material/index.ts';
|
|
15
|
+
import { triangulate } from './triangulate.ts';
|
|
16
|
+
|
|
17
|
+
export interface PlanarOptions {
|
|
18
|
+
/** Counter-clockwise as seen from the side the face points to. */
|
|
19
|
+
readonly points: readonly Vec3[];
|
|
20
|
+
readonly color: Vec3;
|
|
21
|
+
readonly strip?: StripId;
|
|
22
|
+
readonly doubleSided?: boolean;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function planar(o: PlanarOptions): Mesh {
|
|
26
|
+
const [a = [0, 0, 0], b = [1, 0, 0], c = [0, 1, 0]] = o.points;
|
|
27
|
+
const n = normalize(cross(sub(b, a), sub(c, a)));
|
|
28
|
+
const u = normalize(sub(b, a));
|
|
29
|
+
const v = cross(n, u);
|
|
30
|
+
const flat: Vec2[] = o.points.map((p) => [dot(sub(p, a), u), dot(sub(p, a), v)]);
|
|
31
|
+
const rect = stripRect(o.strip ?? 'plain');
|
|
32
|
+
const tris = triangulate(flat);
|
|
33
|
+
const mb = new MeshBuilder();
|
|
34
|
+
for (const side of o.doubleSided ? [1, -1] : [1]) {
|
|
35
|
+
const nn: Vec3 = [n[0] * side, n[1] * side, n[2] * side];
|
|
36
|
+
const ids = o.points.map((p, i) => {
|
|
37
|
+
const f = flat[i] ?? [0, 0];
|
|
38
|
+
return mb.vertex({
|
|
39
|
+
p,
|
|
40
|
+
n: nn,
|
|
41
|
+
uv: [
|
|
42
|
+
rect.u0 + (f[0] / rect.metres) * (rect.u1 - rect.u0),
|
|
43
|
+
rect.v0 + ((((f[1] / rect.metres) % 1) + 1) % 1) * (rect.v1 - rect.v0),
|
|
44
|
+
],
|
|
45
|
+
color: o.color,
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
for (const [x, y, z] of tris) {
|
|
49
|
+
const [i0 = 0, i1 = 0, i2 = 0] = [ids[x], ids[y], ids[z]];
|
|
50
|
+
if (side > 0) mb.tri(i0, i1, i2);
|
|
51
|
+
else mb.tri(i0, i2, i1);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return mb.build();
|
|
55
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
// Extrude a 2D profile into a solid: the workhorse for sloped armour, hull side profiles, gables,
|
|
2
|
+
// fins, wings in plan. The profile lives in a named plane and extrudes along the remaining axis.
|
|
3
|
+
// plane 'xz' (side profile, extrude along Y) · 'xy' (plan, extrude up Z) · 'yz' (front, along X)
|
|
4
|
+
|
|
5
|
+
import { type Mesh, MeshBuilder, normalize, type Vec2, type Vec3 } from '../core/index.ts';
|
|
6
|
+
import { type StripId, stripRect } from '../material/index.ts';
|
|
7
|
+
import { triangulate } from './triangulate.ts';
|
|
8
|
+
|
|
9
|
+
export interface PrismOptions {
|
|
10
|
+
readonly profile: readonly Vec2[];
|
|
11
|
+
readonly depth: number;
|
|
12
|
+
readonly plane?: 'xz' | 'xy' | 'yz';
|
|
13
|
+
/** Extrusion is centred on the plane unless `from` is given (0 = starts at the plane). */
|
|
14
|
+
readonly centred?: boolean;
|
|
15
|
+
readonly color: Vec3;
|
|
16
|
+
readonly strip?: StripId;
|
|
17
|
+
readonly caps?: boolean;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const lift = (plane: 'xz' | 'xy' | 'yz', p: Vec2, d: number): Vec3 =>
|
|
21
|
+
plane === 'xz' ? [p[0], d, p[1]] : plane === 'xy' ? [p[0], p[1], d] : [d, p[0], p[1]];
|
|
22
|
+
|
|
23
|
+
export function prism(o: PrismOptions): Mesh {
|
|
24
|
+
const plane = o.plane ?? 'xy';
|
|
25
|
+
const d0 = o.centred === false ? 0 : -o.depth / 2;
|
|
26
|
+
const d1 = d0 + o.depth;
|
|
27
|
+
const rect = stripRect(o.strip ?? 'plain');
|
|
28
|
+
const uv = (u: number, v: number): Vec2 => [
|
|
29
|
+
rect.u0 + (u / rect.metres) * (rect.u1 - rect.u0),
|
|
30
|
+
rect.v0 + (((v % 1) + 1) % 1) * (rect.v1 - rect.v0),
|
|
31
|
+
];
|
|
32
|
+
const b = new MeshBuilder();
|
|
33
|
+
const n = o.profile.length;
|
|
34
|
+
// Sides: one flat quad per edge, outward normal regardless of winding.
|
|
35
|
+
const sign =
|
|
36
|
+
o.profile.reduce((s, a, i) => {
|
|
37
|
+
const c = o.profile[(i + 1) % n] ?? a;
|
|
38
|
+
return s + a[0] * c[1] - c[0] * a[1];
|
|
39
|
+
}, 0) > 0
|
|
40
|
+
? 1
|
|
41
|
+
: -1;
|
|
42
|
+
// The extrusion axis flips handedness for 'xz' (Y = Z × X), so outward flips with it.
|
|
43
|
+
const hand = plane === 'xz' ? -1 : 1;
|
|
44
|
+
let run = 0;
|
|
45
|
+
for (let i = 0; i < n; i++) {
|
|
46
|
+
const a = o.profile[i] ?? [0, 0];
|
|
47
|
+
const c = o.profile[(i + 1) % n] ?? a;
|
|
48
|
+
const e: Vec2 = [c[0] - a[0], c[1] - a[1]];
|
|
49
|
+
const len = Math.hypot(e[0], e[1]);
|
|
50
|
+
if (len < 1e-9) continue;
|
|
51
|
+
const n2: Vec2 = [(e[1] / len) * sign, (-e[0] / len) * sign];
|
|
52
|
+
const nrm = normalize(lift(plane, n2, 0));
|
|
53
|
+
const p = [lift(plane, a, d0), lift(plane, c, d0), lift(plane, c, d1), lift(plane, a, d1)];
|
|
54
|
+
const ids = p.map((q, k) =>
|
|
55
|
+
b.vertex({
|
|
56
|
+
p: q,
|
|
57
|
+
n: nrm,
|
|
58
|
+
uv: uv(run + (k === 1 || k === 2 ? len : 0), k >= 2 ? o.depth : 0),
|
|
59
|
+
color: o.color,
|
|
60
|
+
}),
|
|
61
|
+
);
|
|
62
|
+
const [i0 = 0, i1 = 0, i2 = 0, i3 = 0] = ids;
|
|
63
|
+
if (sign * hand > 0) b.quad(i0, i1, i2, i3);
|
|
64
|
+
else b.quad(i0, i3, i2, i1);
|
|
65
|
+
run += len;
|
|
66
|
+
}
|
|
67
|
+
if (o.caps !== false) {
|
|
68
|
+
const tris = triangulate(o.profile);
|
|
69
|
+
for (const [d, s] of [
|
|
70
|
+
[d0, -1],
|
|
71
|
+
[d1, 1],
|
|
72
|
+
] as const) {
|
|
73
|
+
const nrm = normalize(lift(plane, [0, 0], s));
|
|
74
|
+
const ids = o.profile.map((q) =>
|
|
75
|
+
b.vertex({ p: lift(plane, q, d), n: nrm, uv: uv(q[0], q[1]), color: o.color }),
|
|
76
|
+
);
|
|
77
|
+
for (const [x, y, z] of tris) {
|
|
78
|
+
const [a = 0, c = 0, e = 0] = [ids[x], ids[y], ids[z]];
|
|
79
|
+
if (s * hand > 0) b.tri(a, c, e);
|
|
80
|
+
else b.tri(a, e, c);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return b.build();
|
|
85
|
+
}
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
// One sculpt edit → its distance field (rotated, optionally mirrored across Y) and its bounds.
|
|
2
|
+
// Rotation is precomputed into a 3×3 matrix; evaluation is scalar maths, no allocation.
|
|
3
|
+
|
|
4
|
+
import type { Vec3 } from '../core/index.ts';
|
|
5
|
+
import { invalid } from './check.ts';
|
|
6
|
+
import { type Field, shapes } from './sdf-ops.ts';
|
|
7
|
+
|
|
8
|
+
export type SculptShape = 'sphere' | 'ellipsoid' | 'capsule' | 'roundBox' | 'cone' | 'torus';
|
|
9
|
+
|
|
10
|
+
/** An edit: one shape, or a group of edits folded on their own first (local trims). */
|
|
11
|
+
export type SculptEdit = SculptShapeEdit | SculptGroup;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Edits folded on their own (from nothing), then applied to the sculpt as ONE edit with this
|
|
15
|
+
* op/blend/colour: subtract or intersect inside the group trims only the group's shapes (a
|
|
16
|
+
* half-sphere eyelid, a hair cap stopped at the hairline) and leaves earlier edits alone.
|
|
17
|
+
*/
|
|
18
|
+
export interface SculptGroup {
|
|
19
|
+
readonly group: readonly SculptEdit[];
|
|
20
|
+
readonly op?: 'add' | 'subtract' | 'intersect';
|
|
21
|
+
readonly blend?: number;
|
|
22
|
+
/** Default colour of the group's own edits (and its colour when it carves). */
|
|
23
|
+
readonly color?: Vec3;
|
|
24
|
+
/** Default mirror for the group's edits. */
|
|
25
|
+
readonly mirror?: boolean;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface SculptShapeEdit {
|
|
29
|
+
readonly shape: SculptShape;
|
|
30
|
+
/** Centre (sphere, ellipsoid, roundBox, torus), base centre (cone) or start (capsule). */
|
|
31
|
+
readonly at: Vec3;
|
|
32
|
+
/**
|
|
33
|
+
* sphere: radius · ellipsoid: [rx, ry, rz] · roundBox: full [x, y, z] · cone: [base radius,
|
|
34
|
+
* top radius, height] along local +Z · torus: [ring radius, tube radius] in local XY ·
|
|
35
|
+
* capsule: radius (segment `at` → `to`).
|
|
36
|
+
*/
|
|
37
|
+
readonly size: number | readonly [number, number] | Vec3;
|
|
38
|
+
/** Capsule end point. */
|
|
39
|
+
readonly to?: Vec3;
|
|
40
|
+
/** Degrees about X, then Y, then Z (world axes), around `at`. */
|
|
41
|
+
readonly rotate?: Vec3;
|
|
42
|
+
/** roundBox edge radius, metres. */
|
|
43
|
+
readonly round?: number;
|
|
44
|
+
readonly op?: 'add' | 'subtract' | 'intersect' | 'paint';
|
|
45
|
+
/** Smooth-blend radius in metres (0 = hard edge). For paint: the colour falloff width. */
|
|
46
|
+
readonly blend?: number;
|
|
47
|
+
readonly color?: Vec3;
|
|
48
|
+
/** Override the sculpt's `mirror` for this edit (e.g. one scar). */
|
|
49
|
+
readonly mirror?: boolean;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const dims = (e: SculptShapeEdit): Vec3 => {
|
|
53
|
+
if (typeof e.size === 'number') return [e.size, e.size, e.size];
|
|
54
|
+
const [a, b, c] = e.size;
|
|
55
|
+
return [a, b, c ?? b];
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export function sculptShape(e: SculptShapeEdit, i: number, mirrorAll: boolean): Field {
|
|
59
|
+
const [a, b, c] = dims(e);
|
|
60
|
+
const where = `sculpt edit ${i} (${e.shape})`;
|
|
61
|
+
if (![a, b, c].every((x) => Number.isFinite(x) && x > 0))
|
|
62
|
+
invalid(`${where}: size has a value ≤ 0`, 'give every size component in metres, > 0');
|
|
63
|
+
if ((e.blend ?? 0) < 0 || !Number.isFinite(e.blend ?? 0))
|
|
64
|
+
invalid(`${where}: blend is ${e.blend}`, 'use a blend radius ≥ 0 m (0.01 = 1 cm fillet)');
|
|
65
|
+
if (e.shape === 'capsule' && !e.to)
|
|
66
|
+
invalid(`${where}: capsule needs \`to\``, 'add to: [x, y, z], the far end');
|
|
67
|
+
const [rx, ry, rz] = (e.rotate ?? [0, 0, 0]).map((d) => (d * Math.PI) / 180);
|
|
68
|
+
const [cx, sx, cy, sy, cz, sz] = [
|
|
69
|
+
Math.cos(rx ?? 0),
|
|
70
|
+
Math.sin(rx ?? 0),
|
|
71
|
+
Math.cos(ry ?? 0),
|
|
72
|
+
Math.sin(ry ?? 0),
|
|
73
|
+
Math.cos(rz ?? 0),
|
|
74
|
+
Math.sin(rz ?? 0),
|
|
75
|
+
];
|
|
76
|
+
// R = Rz · Ry · Rx; local = Rᵀ (p − at).
|
|
77
|
+
const m = [
|
|
78
|
+
cz * cy,
|
|
79
|
+
cz * sy * sx - sz * cx,
|
|
80
|
+
cz * sy * cx + sz * sx,
|
|
81
|
+
sz * cy,
|
|
82
|
+
sz * sy * sx + cz * cx,
|
|
83
|
+
sz * sy * cx - cz * sx,
|
|
84
|
+
-sy,
|
|
85
|
+
cy * sx,
|
|
86
|
+
cy * cx,
|
|
87
|
+
];
|
|
88
|
+
const [m0 = 1, m1 = 0, m2 = 0, m3 = 0, m4 = 1, m5 = 0, m6 = 0, m7 = 0, m8 = 1] = m;
|
|
89
|
+
const [ox, oy, oz] = e.at;
|
|
90
|
+
const to = e.to ?? e.at;
|
|
91
|
+
const [dx, dy, dz] = [to[0] - ox, to[1] - oy, to[2] - oz];
|
|
92
|
+
const len = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
|
93
|
+
const round = Math.min(e.round ?? 0, a / 2, b / 2, c / 2);
|
|
94
|
+
// The shape kernel in local coordinates, chosen once (no per-call switch).
|
|
95
|
+
const kernel: (x: number, y: number, z: number) => number =
|
|
96
|
+
e.shape === 'sphere'
|
|
97
|
+
? (x, y, z) => shapes.sphere(x, y, z, a)
|
|
98
|
+
: e.shape === 'ellipsoid'
|
|
99
|
+
? (x, y, z) => shapes.ellipsoid(x, y, z, a, b, c)
|
|
100
|
+
: e.shape === 'roundBox'
|
|
101
|
+
? (x, y, z) => shapes.roundBox(x, y, z, a / 2, b / 2, c / 2, round)
|
|
102
|
+
: e.shape === 'cone'
|
|
103
|
+
? (x, y, z) => shapes.cone(x, y, z, a, b, c)
|
|
104
|
+
: (x, y, z) => shapes.torus(x, y, z, a, b);
|
|
105
|
+
const rotated = e.rotate?.some((d) => d !== 0) ?? false;
|
|
106
|
+
const one =
|
|
107
|
+
e.shape === 'capsule'
|
|
108
|
+
? (px: number, py: number, pz: number): number => {
|
|
109
|
+
// Capsule in world space along at → to.
|
|
110
|
+
const wx = px - ox;
|
|
111
|
+
const wy = py - oy;
|
|
112
|
+
const wz = pz - oz;
|
|
113
|
+
const t =
|
|
114
|
+
len > 0 ? Math.min(1, Math.max(0, (wx * dx + wy * dy + wz * dz) / (len * len))) : 0;
|
|
115
|
+
return Math.sqrt((wx - dx * t) ** 2 + (wy - dy * t) ** 2 + (wz - dz * t) ** 2) - a;
|
|
116
|
+
}
|
|
117
|
+
: rotated
|
|
118
|
+
? (px: number, py: number, pz: number): number => {
|
|
119
|
+
const wx = px - ox;
|
|
120
|
+
const wy = py - oy;
|
|
121
|
+
const wz = pz - oz;
|
|
122
|
+
return kernel(
|
|
123
|
+
m0 * wx + m3 * wy + m6 * wz,
|
|
124
|
+
m1 * wx + m4 * wy + m7 * wz,
|
|
125
|
+
m2 * wx + m5 * wy + m8 * wz,
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
: // Identity rotation: 1·x + 0·y + 0·z is x, so skipping the product changes no bits.
|
|
129
|
+
(px: number, py: number, pz: number): number => kernel(px - ox, py - oy, pz - oz);
|
|
130
|
+
if (!(e.mirror ?? mirrorAll)) return (p) => one(p[0], p[1], p[2]);
|
|
131
|
+
// Mirrored: min of the shape and its copy across y = 0. Evaluate the nearer copy first; the
|
|
132
|
+
// other only if its bound could undercut it (min is order-free, so this is exact).
|
|
133
|
+
const { c: sc, r: sr, scale, inside } = shapeSphere(e);
|
|
134
|
+
const bound = (dist: number): number => {
|
|
135
|
+
const out = dist - sr;
|
|
136
|
+
return out > 0 ? scale * out : inside ? out : Number.NEGATIVE_INFINITY;
|
|
137
|
+
};
|
|
138
|
+
return (p) => {
|
|
139
|
+
const dx0 = p[0] - sc[0];
|
|
140
|
+
const dz0 = p[2] - sc[2];
|
|
141
|
+
const near = Math.sqrt(dx0 * dx0 + (p[1] - sc[1]) ** 2 + dz0 * dz0);
|
|
142
|
+
const far = Math.sqrt(dx0 * dx0 + (p[1] + sc[1]) ** 2 + dz0 * dz0);
|
|
143
|
+
const [first, second, lb] =
|
|
144
|
+
near <= far ? [p[1], -p[1], bound(far)] : [-p[1], p[1], bound(near)];
|
|
145
|
+
const v = one(p[0], first, p[2]);
|
|
146
|
+
return lb >= v ? v : Math.min(v, one(p[0], second, p[2]));
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* A sphere the shape lies in, and how its field compares with the distance to it: every shape's
|
|
152
|
+
* value v satisfies v ≥ scale · (|p − centre| − radius) outside the sphere (scale 1 = exact SDFs;
|
|
153
|
+
* the ellipsoid approximation only guarantees (r_min / r_max)², and nothing inside its sphere).
|
|
154
|
+
*/
|
|
155
|
+
export function shapeSphere(e: SculptShapeEdit): {
|
|
156
|
+
c: Vec3;
|
|
157
|
+
r: number;
|
|
158
|
+
scale: number;
|
|
159
|
+
inside: boolean;
|
|
160
|
+
} {
|
|
161
|
+
const [a, b, c] = dims(e);
|
|
162
|
+
const to = e.to ?? e.at;
|
|
163
|
+
const centre: Vec3 =
|
|
164
|
+
e.shape === 'capsule'
|
|
165
|
+
? [(e.at[0] + to[0]) / 2, (e.at[1] + to[1]) / 2, (e.at[2] + to[2]) / 2]
|
|
166
|
+
: e.at;
|
|
167
|
+
const r =
|
|
168
|
+
e.shape === 'sphere'
|
|
169
|
+
? a
|
|
170
|
+
: e.shape === 'ellipsoid'
|
|
171
|
+
? Math.max(a, b, c)
|
|
172
|
+
: e.shape === 'roundBox'
|
|
173
|
+
? Math.hypot(a, b, c) / 2
|
|
174
|
+
: e.shape === 'cone'
|
|
175
|
+
? c + Math.max(a, b) // loose but unchanged: it also sets the grid bounds
|
|
176
|
+
: e.shape === 'torus'
|
|
177
|
+
? a + b
|
|
178
|
+
: a + Math.hypot(to[0] - e.at[0], to[1] - e.at[1], to[2] - e.at[2]) / 2;
|
|
179
|
+
const ell = e.shape === 'ellipsoid';
|
|
180
|
+
const lo = Math.min(a, b, c);
|
|
181
|
+
return { c: centre, r, scale: ell ? (lo / r) ** 2 : 1, inside: !ell };
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/** Conservative world bounds of an add edit (bounding sphere), grown by its blend radius. */
|
|
185
|
+
export function editBounds(e: SculptEdit, mirrorAll: boolean): [Vec3, Vec3] {
|
|
186
|
+
if ('group' in e) return groupBounds(e, mirrorAll);
|
|
187
|
+
const { c: centre, r } = shapeSphere(e);
|
|
188
|
+
const g = r + (e.blend ?? 0);
|
|
189
|
+
const lo: [number, number, number] = [centre[0] - g, centre[1] - g, centre[2] - g];
|
|
190
|
+
const hi: [number, number, number] = [centre[0] + g, centre[1] + g, centre[2] + g];
|
|
191
|
+
if (e.mirror ?? mirrorAll) {
|
|
192
|
+
const [l, h] = [lo[1], hi[1]];
|
|
193
|
+
lo[1] = Math.min(l, -h);
|
|
194
|
+
hi[1] = Math.max(h, -l);
|
|
195
|
+
}
|
|
196
|
+
return [lo, hi];
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function groupBounds(g: SculptGroup, mirrorAll: boolean): [Vec3, Vec3] {
|
|
200
|
+
const lo: [number, number, number] = [Infinity, Infinity, Infinity];
|
|
201
|
+
const hi: [number, number, number] = [-Infinity, -Infinity, -Infinity];
|
|
202
|
+
const k = g.blend ?? 0;
|
|
203
|
+
for (const e of g.group) {
|
|
204
|
+
if ((e.op ?? 'add') !== 'add') continue;
|
|
205
|
+
const [a, b] = editBounds(e, g.mirror ?? mirrorAll);
|
|
206
|
+
for (let i = 0; i < 3; i++) {
|
|
207
|
+
lo[i] = Math.min(lo[i] ?? 0, (a[i] ?? 0) - k);
|
|
208
|
+
hi[i] = Math.max(hi[i] ?? 0, (b[i] ?? 0) + k);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
return [lo, hi];
|
|
212
|
+
}
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
// Sculpt evaluation: the edit list compiled into a tree (groups fold their own edits first) and
|
|
2
|
+
// folded per query point. Sparse: every node carries a bounding sphere with a proven lower bound
|
|
3
|
+
// on its value, and an edit is skipped only when that bound shows its blend weight would be
|
|
4
|
+
// exactly 1 (nothing changes) — so skipping is bit-identical to evaluating, just faster. A weight
|
|
5
|
+
// of 1 passes the running value through untouched (no `v + (d − v)` rounding).
|
|
6
|
+
|
|
7
|
+
import type { Vec3 } from '../core/index.ts';
|
|
8
|
+
import { invalid } from './check.ts';
|
|
9
|
+
import { type SculptEdit, sculptShape, shapeSphere } from './sculpt-edit.ts';
|
|
10
|
+
import type { Field } from './sdf-ops.ts';
|
|
11
|
+
|
|
12
|
+
const ADD = 0;
|
|
13
|
+
const SUB = 1;
|
|
14
|
+
const INTER = 2;
|
|
15
|
+
const PAINT = 3;
|
|
16
|
+
const OPS = { add: ADD, subtract: SUB, intersect: INTER, paint: PAINT } as const;
|
|
17
|
+
|
|
18
|
+
export interface Node {
|
|
19
|
+
readonly op: number;
|
|
20
|
+
readonly k: number;
|
|
21
|
+
/** The edit's own colour (shapes), or null. */
|
|
22
|
+
readonly color: Vec3 | null;
|
|
23
|
+
readonly shape: Field | null;
|
|
24
|
+
readonly kids: readonly Node[] | null;
|
|
25
|
+
/** Default colour for this group's edits; its scratch colour buffer. */
|
|
26
|
+
readonly base: Vec3;
|
|
27
|
+
readonly rgb: Float64Array;
|
|
28
|
+
// Bound: value ≥ scale · (|p − c| − r) − under outside the sphere (and ≥ |p − c| − r − under
|
|
29
|
+
// inside it when `inside`); `mirror` also tests the sphere mirrored across y = 0.
|
|
30
|
+
readonly c: Vec3;
|
|
31
|
+
readonly r: number;
|
|
32
|
+
readonly scale: number;
|
|
33
|
+
readonly inside: boolean;
|
|
34
|
+
readonly mirror: boolean;
|
|
35
|
+
readonly under: number;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function compile(
|
|
39
|
+
edits: readonly SculptEdit[],
|
|
40
|
+
base: Vec3,
|
|
41
|
+
mirror: boolean,
|
|
42
|
+
path = 'sculpt',
|
|
43
|
+
): Node[] {
|
|
44
|
+
if (!edits.some((e) => (e.op ?? 'add') === 'add'))
|
|
45
|
+
invalid(`${path} has no add edits, so nothing to carve from`, "make the first edit op: 'add'");
|
|
46
|
+
return edits.map((e, i): Node => {
|
|
47
|
+
const op = OPS[e.op ?? 'add'];
|
|
48
|
+
const k = e.blend ?? 0;
|
|
49
|
+
const m = e.mirror ?? mirror;
|
|
50
|
+
if ('group' in e) {
|
|
51
|
+
const kids = compile(e.group, e.color ?? base, m, `${path} group ${i}`);
|
|
52
|
+
const b = enclose(kids);
|
|
53
|
+
return node(
|
|
54
|
+
op,
|
|
55
|
+
k,
|
|
56
|
+
e.color ?? null,
|
|
57
|
+
null,
|
|
58
|
+
kids,
|
|
59
|
+
e.color ?? base,
|
|
60
|
+
3,
|
|
61
|
+
b.c,
|
|
62
|
+
b.r,
|
|
63
|
+
b.scale,
|
|
64
|
+
b.inside,
|
|
65
|
+
false,
|
|
66
|
+
b.under,
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
const s = shapeSphere(e);
|
|
70
|
+
return node(
|
|
71
|
+
op,
|
|
72
|
+
k,
|
|
73
|
+
e.color ?? null,
|
|
74
|
+
sculptShape(e, i, m),
|
|
75
|
+
null,
|
|
76
|
+
base,
|
|
77
|
+
0,
|
|
78
|
+
s.c,
|
|
79
|
+
s.r,
|
|
80
|
+
s.scale,
|
|
81
|
+
s.inside,
|
|
82
|
+
m,
|
|
83
|
+
0,
|
|
84
|
+
);
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** Every node built in one property order: the fold loop stays monomorphic (one hidden class). */
|
|
89
|
+
function node(
|
|
90
|
+
op: number,
|
|
91
|
+
k: number,
|
|
92
|
+
color: Vec3 | null,
|
|
93
|
+
shape: Field | null,
|
|
94
|
+
kids: readonly Node[] | null,
|
|
95
|
+
base: Vec3,
|
|
96
|
+
rgb: number,
|
|
97
|
+
c: Vec3,
|
|
98
|
+
r: number,
|
|
99
|
+
scale: number,
|
|
100
|
+
inside: boolean,
|
|
101
|
+
mirror: boolean,
|
|
102
|
+
under: number,
|
|
103
|
+
): Node {
|
|
104
|
+
return {
|
|
105
|
+
op,
|
|
106
|
+
k,
|
|
107
|
+
color,
|
|
108
|
+
shape,
|
|
109
|
+
kids,
|
|
110
|
+
base,
|
|
111
|
+
rgb: new Float64Array(rgb),
|
|
112
|
+
c,
|
|
113
|
+
r,
|
|
114
|
+
scale,
|
|
115
|
+
inside,
|
|
116
|
+
mirror,
|
|
117
|
+
under,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** One sphere around all add children (and their mirror copies), scale = the weakest child's. */
|
|
122
|
+
function enclose(
|
|
123
|
+
kids: readonly Node[],
|
|
124
|
+
): Pick<Node, 'c' | 'r' | 'scale' | 'inside' | 'mirror' | 'under'> {
|
|
125
|
+
const adds = kids.filter((n) => n.op === ADD);
|
|
126
|
+
const spheres = adds.flatMap((n) =>
|
|
127
|
+
n.mirror ? [n, { ...n, c: [n.c[0], -n.c[1], n.c[2]] as Vec3 }] : [n],
|
|
128
|
+
);
|
|
129
|
+
const lo = [0, 1, 2].map((a) => Math.min(...spheres.map((n) => (n.c[a] ?? 0) - n.r)));
|
|
130
|
+
const hi = [0, 1, 2].map((a) => Math.max(...spheres.map((n) => (n.c[a] ?? 0) + n.r)));
|
|
131
|
+
const c: Vec3 = [
|
|
132
|
+
((lo[0] ?? 0) + (hi[0] ?? 0)) / 2,
|
|
133
|
+
((lo[1] ?? 0) + (hi[1] ?? 0)) / 2,
|
|
134
|
+
((lo[2] ?? 0) + (hi[2] ?? 0)) / 2,
|
|
135
|
+
];
|
|
136
|
+
const r = Math.max(
|
|
137
|
+
...spheres.map((n) => Math.hypot(n.c[0] - c[0], n.c[1] - c[1], n.c[2] - c[2]) + n.r),
|
|
138
|
+
);
|
|
139
|
+
return {
|
|
140
|
+
c,
|
|
141
|
+
r,
|
|
142
|
+
scale: Math.min(...adds.map((n) => n.scale)),
|
|
143
|
+
inside: adds.every((n) => n.inside),
|
|
144
|
+
mirror: false,
|
|
145
|
+
// Smooth union undershoots the plain min by at most k/4 per blend; children's own undershoot adds.
|
|
146
|
+
under: adds.reduce((u, n) => u + n.k / 4 + n.under, 0),
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function lowerBound(n: Node, p: Vec3): number {
|
|
151
|
+
const dx = p[0] - n.c[0];
|
|
152
|
+
const dz = p[2] - n.c[2];
|
|
153
|
+
let dy = p[1] - n.c[1];
|
|
154
|
+
let q = dx * dx + dy * dy + dz * dz;
|
|
155
|
+
if (n.mirror) {
|
|
156
|
+
dy = p[1] + n.c[1];
|
|
157
|
+
q = Math.min(q, dx * dx + dy * dy + dz * dz);
|
|
158
|
+
}
|
|
159
|
+
const out = Math.sqrt(q) - n.r;
|
|
160
|
+
if (out > 0) return n.scale * out - n.under;
|
|
161
|
+
return n.inside ? out - n.under : Number.NEGATIVE_INFINITY;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const clamp01 = (x: number): number => (x < 0 ? 0 : Math.min(x, 1));
|
|
165
|
+
/** Smooth-min weight of `a` against `b` (1 = all a). */
|
|
166
|
+
const weight = (a: number, b: number, k: number): number =>
|
|
167
|
+
k > 0 ? clamp01(0.5 + (0.5 * (b - a)) / k) : a < b ? 1 : 0;
|
|
168
|
+
/** Margin past which a bound proves the weight is exactly 1, rounding included. */
|
|
169
|
+
const slack = (d: number): number => 1e-9 * (1 + Math.abs(d));
|
|
170
|
+
|
|
171
|
+
/** Fold `nodes` at p; writes the blended colour into `rgb` (seeded with `base`) when given. */
|
|
172
|
+
export function fold(
|
|
173
|
+
nodes: readonly Node[],
|
|
174
|
+
p: Vec3,
|
|
175
|
+
rgb: Float64Array | null,
|
|
176
|
+
base: Vec3,
|
|
177
|
+
sparse = true,
|
|
178
|
+
): number {
|
|
179
|
+
let d = Number.POSITIVE_INFINITY;
|
|
180
|
+
if (rgb) rgb.set(base);
|
|
181
|
+
for (const n of nodes) {
|
|
182
|
+
const k = n.k;
|
|
183
|
+
if (n.op === PAINT) {
|
|
184
|
+
if (!rgb || !n.color || !n.shape) continue;
|
|
185
|
+
const kk = Math.max(k, 1e-9);
|
|
186
|
+
if (sparse && lowerBound(n, p) > kk / 2 + slack(kk)) continue;
|
|
187
|
+
const w = 1 - clamp01(0.5 - n.shape(p) / kk);
|
|
188
|
+
if (w < 1) mix(rgb, n.color, w);
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
const empty = d === Number.POSITIVE_INFINITY;
|
|
192
|
+
if (empty && n.op !== ADD) continue;
|
|
193
|
+
if (sparse && !empty) {
|
|
194
|
+
if (n.op === ADD && lowerBound(n, p) > d + k + slack(d)) continue;
|
|
195
|
+
if (n.op === SUB && lowerBound(n, p) > k - d + slack(d)) continue;
|
|
196
|
+
}
|
|
197
|
+
const v = n.kids
|
|
198
|
+
? fold(n.kids, p, rgb ? n.rgb : null, n.base, sparse)
|
|
199
|
+
: n.shape
|
|
200
|
+
? n.shape(p)
|
|
201
|
+
: 0;
|
|
202
|
+
let w = 0;
|
|
203
|
+
if (empty) d = v;
|
|
204
|
+
else if (n.op === ADD) {
|
|
205
|
+
w = weight(d, v, k);
|
|
206
|
+
if (w !== 1) d = v + (d - v) * w - k * w * (1 - w);
|
|
207
|
+
} else {
|
|
208
|
+
// subtract = smooth max(d, −v); intersect = smooth max(d, v).
|
|
209
|
+
const b = n.op === SUB ? -v : v;
|
|
210
|
+
w = weight(-d, -b, k);
|
|
211
|
+
if (w !== 1) d = -(-b + (-d + b) * w - k * w * (1 - w));
|
|
212
|
+
}
|
|
213
|
+
if (!rgb || w >= 1) continue;
|
|
214
|
+
if (n.kids) {
|
|
215
|
+
if (n.color || n.op === ADD) mixArr(rgb, n.rgb, w);
|
|
216
|
+
} else if (n.color || n.op === ADD) mix(rgb, n.color ?? base, w);
|
|
217
|
+
}
|
|
218
|
+
return d;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const mix = (rgb: Float64Array, c: Vec3, w: number): void => {
|
|
222
|
+
for (let j = 0; j < 3; j++) rgb[j] = (c[j] ?? 0) + ((rgb[j] ?? 0) - (c[j] ?? 0)) * w;
|
|
223
|
+
};
|
|
224
|
+
const mixArr = (rgb: Float64Array, c: Float64Array, w: number): void => {
|
|
225
|
+
for (let j = 0; j < 3; j++) rgb[j] = (c[j] ?? 0) + ((rgb[j] ?? 0) - (c[j] ?? 0)) * w;
|
|
226
|
+
};
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
// Sculpting as an ordered list of SDF edits (the Dreams model): each edit adds, subtracts,
|
|
2
|
+
// intersects or paints a simple shape, blended into everything before it by a smooth-min of
|
|
3
|
+
// radius `blend` metres. Colour rides the same blend weights, so skin, lips, eye whites and tusks
|
|
4
|
+
// come out as vertex colour on ONE mesh. `mirror: 'y'` makes every edit symmetric left/right
|
|
5
|
+
// (Y in our Z-up, +X-front frame); an edit can opt out. Meshed by `sdf` (smooth gradient normals),
|
|
6
|
+
// bounds found from the edits. `resolution` = cell edge in metres: ctx.pick([0.004, 0.012, 0.03]).
|
|
7
|
+
// `sculptField` exposes the field + colour so a recipe can displace it before meshing.
|
|
8
|
+
|
|
9
|
+
import type { Mesh, Vec3 } from '../core/index.ts';
|
|
10
|
+
import type { StripId } from '../material/index.ts';
|
|
11
|
+
import { invalid } from './check.ts';
|
|
12
|
+
import { editBounds, type SculptEdit } from './sculpt-edit.ts';
|
|
13
|
+
import { compile, fold } from './sculpt-tree.ts';
|
|
14
|
+
import { sdf } from './sdf.ts';
|
|
15
|
+
import type { Field } from './sdf-ops.ts';
|
|
16
|
+
|
|
17
|
+
export type { SculptEdit, SculptGroup, SculptShape, SculptShapeEdit } from './sculpt-edit.ts';
|
|
18
|
+
|
|
19
|
+
export interface SculptField {
|
|
20
|
+
readonly field: Field;
|
|
21
|
+
/** Blended edit colour at a point (call at surface points). */
|
|
22
|
+
readonly colorAt: (p: Vec3) => Vec3;
|
|
23
|
+
/** Edits' bounds grown by their blend radii. */
|
|
24
|
+
readonly bounds: readonly [Vec3, Vec3];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface SculptOptions {
|
|
28
|
+
readonly edits: readonly SculptEdit[];
|
|
29
|
+
/** Cell edge in metres (the LOD knob). */
|
|
30
|
+
readonly resolution: number;
|
|
31
|
+
/** Colour of edits that give none. */
|
|
32
|
+
readonly color: Vec3;
|
|
33
|
+
readonly mirror?: 'y';
|
|
34
|
+
readonly mesher?: 'nets' | 'cubes';
|
|
35
|
+
readonly strip?: StripId;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function sculptField(
|
|
39
|
+
edits: readonly SculptEdit[],
|
|
40
|
+
o: { readonly color: Vec3; readonly mirror?: 'y' },
|
|
41
|
+
): SculptField {
|
|
42
|
+
if (edits.length === 0)
|
|
43
|
+
invalid('sculpt has no edits', "start with an { shape: 'sphere', op: 'add', ... } edit");
|
|
44
|
+
const nodes = compile(edits, o.color, o.mirror === 'y');
|
|
45
|
+
const rgb = new Float64Array(3);
|
|
46
|
+
const adds = edits.filter((e) => (e.op ?? 'add') === 'add');
|
|
47
|
+
const lo = [Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY];
|
|
48
|
+
const hi = [Number.NEGATIVE_INFINITY, Number.NEGATIVE_INFINITY, Number.NEGATIVE_INFINITY];
|
|
49
|
+
for (const e of adds) {
|
|
50
|
+
const [a, b] = editBounds(e, o.mirror === 'y');
|
|
51
|
+
for (let k = 0; k < 3; k++) {
|
|
52
|
+
lo[k] = Math.min(lo[k] ?? 0, a[k] ?? 0);
|
|
53
|
+
hi[k] = Math.max(hi[k] ?? 0, b[k] ?? 0);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return {
|
|
57
|
+
field: (p) => fold(nodes, p, null, o.color),
|
|
58
|
+
colorAt: (p) => {
|
|
59
|
+
fold(nodes, p, rgb, o.color);
|
|
60
|
+
return [rgb[0] ?? 0, rgb[1] ?? 0, rgb[2] ?? 0];
|
|
61
|
+
},
|
|
62
|
+
bounds: [
|
|
63
|
+
[lo[0] ?? 0, lo[1] ?? 0, lo[2] ?? 0],
|
|
64
|
+
[hi[0] ?? 0, hi[1] ?? 0, hi[2] ?? 0],
|
|
65
|
+
],
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function sculpt(o: SculptOptions): Mesh {
|
|
70
|
+
const s = sculptField(o.edits, { color: o.color, ...(o.mirror ? { mirror: o.mirror } : {}) });
|
|
71
|
+
return sdf({
|
|
72
|
+
field: s.field,
|
|
73
|
+
bounds: s.bounds,
|
|
74
|
+
resolution: o.resolution,
|
|
75
|
+
color: (p) => s.colorAt(p),
|
|
76
|
+
...(o.mesher ? { mesher: o.mesher } : {}),
|
|
77
|
+
...(o.mirror === 'y' ? { symmetry: 'y' as const } : {}),
|
|
78
|
+
...(o.strip ? { strip: o.strip } : {}),
|
|
79
|
+
});
|
|
80
|
+
}
|