@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,152 @@
|
|
|
1
|
+
// Scalp hair as edit lists, part of the skull field: a top-knot, mohawk ridge, chunky swept locks
|
|
2
|
+
// over a cap clipped at the hairline, wiry tufts, braids. Beards and eyebrows: head-beard.ts.
|
|
3
|
+
// Head units, mirrored across Y unless an edit opts out.
|
|
4
|
+
|
|
5
|
+
import type { Vec3 } from '../core/index.ts';
|
|
6
|
+
import type { SculptEdit } from '../primitives/index.ts';
|
|
7
|
+
import { alongZ } from './head-geom.ts';
|
|
8
|
+
import type { HeadLayout } from './head-layout.ts';
|
|
9
|
+
import type { HeadParams } from './head-params.ts';
|
|
10
|
+
|
|
11
|
+
const RAD = Math.PI / 180;
|
|
12
|
+
|
|
13
|
+
export function hairEdits(p: HeadParams, l: HeadLayout): SculptEdit[] {
|
|
14
|
+
const h = p.colors.hair;
|
|
15
|
+
const [cx, , cz] = l.cranium.at;
|
|
16
|
+
const [rx, ry, rz] = l.cranium.r;
|
|
17
|
+
const top = l.crown;
|
|
18
|
+
switch (p.hair) {
|
|
19
|
+
case 'topknot':
|
|
20
|
+
return [
|
|
21
|
+
{ shape: 'ellipsoid', at: [cx - 0.12, 0, top - 0.02], size: [0.11, 0.08, 0.07], color: h },
|
|
22
|
+
{
|
|
23
|
+
shape: 'sphere',
|
|
24
|
+
at: [cx - 0.16, 0, top + 0.07],
|
|
25
|
+
size: 0.075,
|
|
26
|
+
color: h,
|
|
27
|
+
blend: 0.03,
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
shape: 'capsule',
|
|
31
|
+
at: [cx - 0.2, 0, top + 0.08],
|
|
32
|
+
to: [cx - 0.42, 0, top - 0.25],
|
|
33
|
+
size: 0.04,
|
|
34
|
+
color: h,
|
|
35
|
+
blend: 0.04,
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
shape: 'torus',
|
|
39
|
+
at: [cx - 0.17, 0, top + 0.02],
|
|
40
|
+
size: [0.06, 0.018],
|
|
41
|
+
color: p.colors.metal,
|
|
42
|
+
},
|
|
43
|
+
];
|
|
44
|
+
case 'mohawk':
|
|
45
|
+
return Array.from({ length: 6 }, (_, i): SculptEdit => {
|
|
46
|
+
const a = (-20 + i * 26) * RAD;
|
|
47
|
+
return {
|
|
48
|
+
shape: 'ellipsoid',
|
|
49
|
+
at: [cx + Math.sin(a) * rx * 0.95, 0, cz + Math.cos(a) * rz * 0.97],
|
|
50
|
+
size: [0.06, 0.035, 0.09],
|
|
51
|
+
rotate: [0, (a * 180) / Math.PI - 15, 0],
|
|
52
|
+
color: h,
|
|
53
|
+
blend: 0.03,
|
|
54
|
+
};
|
|
55
|
+
});
|
|
56
|
+
case 'swept': {
|
|
57
|
+
// Cap clipped to a rounded hairline (face and ears carved out), then big tapered locks
|
|
58
|
+
// sweeping back from the hairline — thick roots proud, tips sunk — in two shades.
|
|
59
|
+
const C: Vec3 = [cx - 0.04, 0, cz + 0.04];
|
|
60
|
+
const R: Vec3 = [rx + 0.035, ry + 0.04, rz + 0.02];
|
|
61
|
+
const on = (az: number, el: number, lift = 0): Vec3 => [
|
|
62
|
+
C[0] + (R[0] + lift) * Math.cos(el * RAD) * Math.cos(az * RAD),
|
|
63
|
+
C[1] + (R[1] + lift) * Math.cos(el * RAD) * Math.sin(az * RAD),
|
|
64
|
+
C[2] + (R[2] + lift) * Math.sin(el * RAD),
|
|
65
|
+
];
|
|
66
|
+
const light: Vec3 = [h[0] * 1.45, h[1] * 1.45, h[2] * 1.45];
|
|
67
|
+
// A lock hugs the cap: two tapering cones root → mid → tip, mid on the surface.
|
|
68
|
+
const cone = (a: Vec3, b: Vec3, r0: number, r1: number): SculptEdit => ({
|
|
69
|
+
shape: 'cone',
|
|
70
|
+
at: a,
|
|
71
|
+
size: [r0, r1, Math.hypot(b[0] - a[0], b[1] - a[1], b[2] - a[2])],
|
|
72
|
+
rotate: alongZ([b[0] - a[0], b[1] - a[1], b[2] - a[2]]),
|
|
73
|
+
});
|
|
74
|
+
const lock = (az: number, el: number, len: number, r: number, i: number): SculptEdit => {
|
|
75
|
+
const a = on(az, el, -0.005);
|
|
76
|
+
const m = on(az + len * 0.3, el - len * 0.45, -0.012);
|
|
77
|
+
const b = on(az + len * 0.6, el - len, -0.045);
|
|
78
|
+
return {
|
|
79
|
+
group: [cone(a, m, r, r * 0.75), cone(m, b, r * 0.75, r * 0.15)],
|
|
80
|
+
color: i % 2 ? h : light,
|
|
81
|
+
blend: 0.035,
|
|
82
|
+
...(az === 0 ? { mirror: false } : {}),
|
|
83
|
+
};
|
|
84
|
+
};
|
|
85
|
+
return [
|
|
86
|
+
{
|
|
87
|
+
group: [
|
|
88
|
+
{ shape: 'ellipsoid', at: C, size: R },
|
|
89
|
+
{
|
|
90
|
+
shape: 'ellipsoid',
|
|
91
|
+
at: [l.faceX + 0.12, 0, l.eye[2] - 0.08],
|
|
92
|
+
size: [0.34, ry * 0.92, 0.36],
|
|
93
|
+
op: 'subtract',
|
|
94
|
+
blend: 0.03,
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
shape: 'ellipsoid',
|
|
98
|
+
at: [cx + 0.02, ry, l.eye[2] - 0.08],
|
|
99
|
+
size: [0.13, 0.25, 0.16],
|
|
100
|
+
op: 'subtract',
|
|
101
|
+
blend: 0.03,
|
|
102
|
+
},
|
|
103
|
+
],
|
|
104
|
+
color: h,
|
|
105
|
+
blend: 0.012,
|
|
106
|
+
},
|
|
107
|
+
lock(0, 44, 50, 0.07, 1),
|
|
108
|
+
lock(22, 46, 50, 0.07, 0),
|
|
109
|
+
lock(45, 40, 45, 0.068, 1),
|
|
110
|
+
lock(70, 34, 40, 0.06, 0),
|
|
111
|
+
lock(95, 30, 35, 0.055, 1),
|
|
112
|
+
lock(120, 30, 30, 0.05, 0),
|
|
113
|
+
];
|
|
114
|
+
}
|
|
115
|
+
case 'tufts':
|
|
116
|
+
return Array.from({ length: 5 }, (_, i): SculptEdit => {
|
|
117
|
+
const a = (i / 4 - 0.5) * 1.6;
|
|
118
|
+
const base: Vec3 = [cx - 0.1 + Math.cos(a) * 0.05, Math.sin(a) * 0.12, top - 0.04];
|
|
119
|
+
return {
|
|
120
|
+
shape: 'cone',
|
|
121
|
+
at: base,
|
|
122
|
+
size: [0.025, 0.004, 0.12 + (i % 2) * 0.05],
|
|
123
|
+
rotate: alongZ([-0.6, Math.sin(a) * 0.8, 1]),
|
|
124
|
+
color: h,
|
|
125
|
+
blend: 0.01,
|
|
126
|
+
mirror: false,
|
|
127
|
+
};
|
|
128
|
+
});
|
|
129
|
+
case 'braids':
|
|
130
|
+
return [
|
|
131
|
+
{ ...capOf(l, h) },
|
|
132
|
+
{
|
|
133
|
+
shape: 'capsule',
|
|
134
|
+
at: [cx - 0.2, ry * 0.7, cz - 0.1],
|
|
135
|
+
to: [cx - 0.1, ry * 0.8, cz - 0.6],
|
|
136
|
+
size: 0.035,
|
|
137
|
+
color: h,
|
|
138
|
+
blend: 0.02,
|
|
139
|
+
},
|
|
140
|
+
];
|
|
141
|
+
default:
|
|
142
|
+
return [];
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const capOf = (l: HeadLayout, h: Vec3): SculptEdit => ({
|
|
147
|
+
shape: 'ellipsoid',
|
|
148
|
+
at: [l.cranium.at[0] - 0.07, 0, l.cranium.at[2] + 0.09],
|
|
149
|
+
size: [l.cranium.r[0] + 0.02, l.cranium.r[1] + 0.025, l.cranium.r[2]],
|
|
150
|
+
color: h,
|
|
151
|
+
blend: 0.02,
|
|
152
|
+
});
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// Head landmarks from params: where the cranium, eyes, brow, nose base, mouth, chin, jaw hinge and
|
|
2
|
+
// jaw angle sit, in head units (x front, y left, z up; z 0 = chin of an undropped jaw, x 0 ≈ ear
|
|
3
|
+
// canal). Every edit list reads this one table, so moving the eye line moves lids, brow and nose.
|
|
4
|
+
|
|
5
|
+
import type { Vec3 } from '../core/index.ts';
|
|
6
|
+
import type { HeadParams } from './head-params.ts';
|
|
7
|
+
|
|
8
|
+
export interface HeadLayout {
|
|
9
|
+
/** Cranium ellipsoid centre and radii. */
|
|
10
|
+
readonly cranium: { readonly at: Vec3; readonly r: Vec3 };
|
|
11
|
+
/** Forehead front at the eye line. */
|
|
12
|
+
readonly faceX: number;
|
|
13
|
+
/** Left eye centre (mirror for the right) and eyeball radius. */
|
|
14
|
+
readonly eye: Vec3;
|
|
15
|
+
readonly eyeR: number;
|
|
16
|
+
readonly browZ: number;
|
|
17
|
+
readonly noseZ: number;
|
|
18
|
+
readonly mouthZ: number;
|
|
19
|
+
/** Front of the lips. */
|
|
20
|
+
readonly mouthX: number;
|
|
21
|
+
readonly chin: Vec3;
|
|
22
|
+
/** Jaw hinge (x, z) — the jaw part's pivot. */
|
|
23
|
+
readonly hinge: readonly [number, number];
|
|
24
|
+
/** Left jaw angle (gonion). */
|
|
25
|
+
readonly angle: Vec3;
|
|
26
|
+
/** Top of the head, units. */
|
|
27
|
+
readonly crown: number;
|
|
28
|
+
/** Bottom of the bust (the asset's z = 0), units. */
|
|
29
|
+
readonly base: number;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function headLayout(p: HeadParams): HeadLayout {
|
|
33
|
+
const L = p.eyes.line;
|
|
34
|
+
const crH = p.cranium.height;
|
|
35
|
+
const rx = p.cranium.depth / 2;
|
|
36
|
+
const ry = p.cranium.width / 2;
|
|
37
|
+
const rz = crH * 0.8;
|
|
38
|
+
const cx = -0.02 - p.jaw.muzzle * 0.4;
|
|
39
|
+
const cz = L + crH * 0.2;
|
|
40
|
+
const faceX = cx + rx * 0.97;
|
|
41
|
+
const jut = p.jaw.muzzle;
|
|
42
|
+
const eyeR = p.eyes.radius;
|
|
43
|
+
const eye: Vec3 = [faceX - eyeR - p.brow.overhang, p.eyes.spacing, L];
|
|
44
|
+
const noseZ = L - p.nose.length;
|
|
45
|
+
const mouthZ = noseZ - 0.1 - p.jaw.drop * 0.3;
|
|
46
|
+
const chinZ = -p.jaw.drop;
|
|
47
|
+
const mouthX = faceX - 0.01 + jut + p.jaw.underbite * 0.3;
|
|
48
|
+
return {
|
|
49
|
+
cranium: { at: [cx, 0, cz], r: [rx, ry, rz] },
|
|
50
|
+
faceX,
|
|
51
|
+
eye,
|
|
52
|
+
eyeR,
|
|
53
|
+
browZ: L + eyeR * 0.9 + p.brow.ridge * 0.6,
|
|
54
|
+
noseZ,
|
|
55
|
+
mouthZ,
|
|
56
|
+
mouthX,
|
|
57
|
+
chin: [faceX - 0.07 + jut + p.jaw.underbite, 0, chinZ + 0.07 * p.jaw.chin],
|
|
58
|
+
hinge: [cx + 0.04, L - 0.09],
|
|
59
|
+
angle: [cx - 0.02, p.jaw.width, 0.15 - p.jaw.drop * 0.6],
|
|
60
|
+
crown: cz + rz,
|
|
61
|
+
base: -0.85 - p.jaw.drop * 0.5,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
// Head edit lists → meshes. One skull field is split by the jaw cutter into `head` (skull − cut,
|
|
2
|
+
// smooth-unioned with the ears) and `jaw` (skull ∩ cut, with the tusks), so the two parts share
|
|
3
|
+
// the exact same outer surface; their cut faces are painted mouth-dark for when the jaw opens.
|
|
4
|
+
// Eyeballs are their own spheres: sclera, iris, pupil by angle from the +X gaze.
|
|
5
|
+
|
|
6
|
+
import type { Mesh, Vec3 } from '../core/index.ts';
|
|
7
|
+
import { type Field, type SculptEdit, sculptField, sd, sdf } from '../primitives/index.ts';
|
|
8
|
+
import type { HeadLayout } from './head-layout.ts';
|
|
9
|
+
import type { HeadParams } from './head-params.ts';
|
|
10
|
+
import { creaseNormals, sharpenRim } from './head-rim.ts';
|
|
11
|
+
|
|
12
|
+
/** The jaw region (head units): an ellipsoid around chin and lower lip, cut flat at the mouth
|
|
13
|
+
* line. Its skin seam runs along the lips, down the marionette lines and under the chin — creases
|
|
14
|
+
* a face already has — instead of across the cheek. */
|
|
15
|
+
export function jawRegion(
|
|
16
|
+
p: HeadParams,
|
|
17
|
+
l: HeadLayout,
|
|
18
|
+
): { field: Field; centre: Vec3; radii: Vec3 } {
|
|
19
|
+
const chin = l.chin;
|
|
20
|
+
const reach = Math.max(p.mouth.width * 1.25, (p.tusks?.spread ?? 0) + p.mouth.width * 0.5);
|
|
21
|
+
const centre: Vec3 = [l.mouthX - 0.15, 0, l.mouthZ];
|
|
22
|
+
const radii: Vec3 = [Math.max(0.27, l.mouthX - chin[0] + 0.2), reach, l.mouthZ - chin[2] + 0.1];
|
|
23
|
+
const e = sd.ellipsoid(centre, radii);
|
|
24
|
+
return { field: (q) => Math.max(q[2] - l.mouthZ, e(q)), centre, radii };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const unionBounds = (a: readonly [Vec3, Vec3], b?: readonly [Vec3, Vec3]): [Vec3, Vec3] =>
|
|
28
|
+
b
|
|
29
|
+
? [
|
|
30
|
+
[Math.min(a[0][0], b[0][0]), Math.min(a[0][1], b[0][1]), Math.min(a[0][2], b[0][2])],
|
|
31
|
+
[Math.max(a[1][0], b[1][0]), Math.max(a[1][1], b[1][1]), Math.max(a[1][2], b[1][2])],
|
|
32
|
+
]
|
|
33
|
+
: [a[0], a[1]];
|
|
34
|
+
|
|
35
|
+
/** The jaw region as a sculpt group (ellipsoid ∩ below the mouth line), grown by `grow`. */
|
|
36
|
+
function regionGroup(l: HeadLayout, r: { centre: Vec3; radii: Vec3 }, grow: number): SculptEdit {
|
|
37
|
+
const cx = r.centre[0];
|
|
38
|
+
const [rx, ry, rz] = r.radii;
|
|
39
|
+
return {
|
|
40
|
+
group: [
|
|
41
|
+
{ shape: 'ellipsoid', at: r.centre, size: [rx + grow, ry + grow, rz + grow] },
|
|
42
|
+
{
|
|
43
|
+
shape: 'roundBox',
|
|
44
|
+
at: [cx, 0, l.mouthZ + grow - 1],
|
|
45
|
+
size: [4, 4, 2],
|
|
46
|
+
op: 'intersect',
|
|
47
|
+
},
|
|
48
|
+
],
|
|
49
|
+
mirror: false,
|
|
50
|
+
blend: 0,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface HeadEditSet {
|
|
55
|
+
readonly skull: readonly SculptEdit[];
|
|
56
|
+
readonly ears: readonly SculptEdit[];
|
|
57
|
+
readonly tusks: readonly SculptEdit[];
|
|
58
|
+
readonly bust: readonly SculptEdit[];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** `head` and `jaw` meshes in head units at cell size `res` (units). Each is ONE sculpt (skull +
|
|
62
|
+
* ears − region, skull ∩ region + tusks), so the sculpt's range skipping covers every edit. */
|
|
63
|
+
export function headAndJaw(
|
|
64
|
+
p: HeadParams,
|
|
65
|
+
l: HeadLayout,
|
|
66
|
+
set: HeadEditSet,
|
|
67
|
+
res: number,
|
|
68
|
+
): { head: Mesh; jaw: Mesh } {
|
|
69
|
+
const opts = { color: p.colors.skin, mirror: 'y' as const };
|
|
70
|
+
const region = jawRegion(p, l);
|
|
71
|
+
const cut = region.field;
|
|
72
|
+
// The jaw reaches a little past the cut and sits a hair proud of the skin, so it laps over the
|
|
73
|
+
// head like a lip: no gap opens along the seam where meshing leaves the two rims a cell apart.
|
|
74
|
+
const lap = res * 0.75;
|
|
75
|
+
const proud = res * 0.15;
|
|
76
|
+
const ears: SculptEdit[] = set.ears.length > 0 ? [{ group: set.ears, blend: 0.02 }] : [];
|
|
77
|
+
const tusks: SculptEdit[] = set.tusks.length > 0 ? [{ group: set.tusks, blend: 0.01 }] : [];
|
|
78
|
+
const h = sculptField(
|
|
79
|
+
[...set.skull, ...ears, { ...regionGroup(l, region, 0), op: 'subtract' }],
|
|
80
|
+
opts,
|
|
81
|
+
);
|
|
82
|
+
const jRaw = sculptField(
|
|
83
|
+
[...set.skull, { ...regionGroup(l, region, lap), op: 'intersect' }, ...tusks],
|
|
84
|
+
opts,
|
|
85
|
+
);
|
|
86
|
+
const jField: Field = (q) => jRaw.field(q) - proud;
|
|
87
|
+
const mouth = p.colors.mouth;
|
|
88
|
+
// Only the skin-only skull is needed near the cut (paint + rim snapping): evaluated lazily.
|
|
89
|
+
const skull = sculptField(set.skull, opts).field;
|
|
90
|
+
const deep = -Math.max(res * 4, 0.03);
|
|
91
|
+
const paint =
|
|
92
|
+
(colorAt: (q: Vec3) => Vec3) =>
|
|
93
|
+
(q: Vec3): Vec3 =>
|
|
94
|
+
Math.abs(cut(q)) < res * 1.5 && skull(q) < deep ? mouth : colorAt(q);
|
|
95
|
+
const [ec, er] = [region.centre, region.radii];
|
|
96
|
+
const jb: [Vec3, Vec3] = [
|
|
97
|
+
[ec[0] - er[0] - lap, -er[1] - lap, ec[2] - er[2] - lap],
|
|
98
|
+
[ec[0] + er[0] + lap, er[1] + lap, ec[2] + lap + 0.01],
|
|
99
|
+
];
|
|
100
|
+
const tb = set.tusks.length > 0 ? sculptField(set.tusks, opts).bounds : undefined;
|
|
101
|
+
const head = sdf({
|
|
102
|
+
field: h.field,
|
|
103
|
+
bounds: h.bounds,
|
|
104
|
+
resolution: res,
|
|
105
|
+
color: paint(h.colorAt),
|
|
106
|
+
symmetry: 'y',
|
|
107
|
+
});
|
|
108
|
+
const jaw = sdf({
|
|
109
|
+
field: jField,
|
|
110
|
+
bounds: unionBounds(jb, tb),
|
|
111
|
+
resolution: res,
|
|
112
|
+
color: paint(jRaw.colorAt),
|
|
113
|
+
symmetry: 'y',
|
|
114
|
+
});
|
|
115
|
+
// Coarse cells: snapping folds triangles over, and nobody sees a seam at that distance.
|
|
116
|
+
if (res > 0.03) return { head: creaseNormals(head), jaw: creaseNormals(jaw) };
|
|
117
|
+
const jawSkull: Field = (q) => skull(q) - proud;
|
|
118
|
+
return {
|
|
119
|
+
// The head's rim lies under the jaw's lap, so only the jaw rim (the visible one) is snapped.
|
|
120
|
+
head: creaseNormals(head),
|
|
121
|
+
jaw: creaseNormals(sharpenRim(jaw, jawSkull, (q) => cut(q) - lap, jField, res)),
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export function bustMesh(p: HeadParams, set: HeadEditSet, res: number): Mesh {
|
|
126
|
+
const s = sculptField(set.bust, { color: p.colors.skin, mirror: 'y' });
|
|
127
|
+
return sdf({
|
|
128
|
+
field: s.field,
|
|
129
|
+
bounds: s.bounds,
|
|
130
|
+
resolution: res,
|
|
131
|
+
color: (q) => s.colorAt(q),
|
|
132
|
+
symmetry: 'y',
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/** Eyeball of radius `r` centred on the origin, gazing +X. */
|
|
137
|
+
export function eyeMesh(p: HeadParams, r: number, res: number): Mesh {
|
|
138
|
+
const c = p.colors;
|
|
139
|
+
const iris = Math.cos(0.62);
|
|
140
|
+
const limbus = Math.cos(0.7);
|
|
141
|
+
const pupil = Math.cos(0.3);
|
|
142
|
+
return sdf({
|
|
143
|
+
field: (q) => Math.hypot(q[0], q[1], q[2]) - r,
|
|
144
|
+
bounds: [
|
|
145
|
+
[-r * 1.3, -r * 1.3, -r * 1.3],
|
|
146
|
+
[r * 1.3, r * 1.3, r * 1.3],
|
|
147
|
+
],
|
|
148
|
+
resolution: res,
|
|
149
|
+
color: (q) => {
|
|
150
|
+
const cos = q[0] / (Math.hypot(q[0], q[1], q[2]) || 1);
|
|
151
|
+
if (cos > pupil) return c.pupil;
|
|
152
|
+
if (cos > iris) return c.iris;
|
|
153
|
+
if (cos > limbus) return [c.iris[0] * 0.45, c.iris[1] * 0.45, c.iris[2] * 0.45];
|
|
154
|
+
return c.sclera;
|
|
155
|
+
},
|
|
156
|
+
});
|
|
157
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
// Head kind parameters: the typed knobs of a stylised head, in HEAD UNITS (1 = chin to crown of a
|
|
2
|
+
// human-proportioned skull; the builder scales by `height` metres at the end), plus the guard that
|
|
3
|
+
// turns an unusable set into a T3D_RECIPE_INVALID with the fix.
|
|
4
|
+
|
|
5
|
+
import { Ts23dError, type Vec3 } from '../core/index.ts';
|
|
6
|
+
|
|
7
|
+
export type EarShape = 'round' | 'pointed';
|
|
8
|
+
export type HairStyle = 'none' | 'topknot' | 'mohawk' | 'swept' | 'tufts' | 'braids';
|
|
9
|
+
export type BeardStyle = 'none' | 'short' | 'braided' | 'goatee';
|
|
10
|
+
export type BustStyle = 'pauldrons' | 'hood' | 'tunic';
|
|
11
|
+
|
|
12
|
+
export interface HeadColors {
|
|
13
|
+
readonly skin: Vec3;
|
|
14
|
+
/** Darker skin: lips edge, nostrils, inner ear, wrinkles. */
|
|
15
|
+
readonly shade: Vec3;
|
|
16
|
+
readonly lips: Vec3;
|
|
17
|
+
readonly sclera: Vec3;
|
|
18
|
+
readonly iris: Vec3;
|
|
19
|
+
readonly pupil: Vec3;
|
|
20
|
+
readonly teeth: Vec3;
|
|
21
|
+
readonly mouth: Vec3;
|
|
22
|
+
readonly hair: Vec3;
|
|
23
|
+
readonly cloth: Vec3;
|
|
24
|
+
readonly leather: Vec3;
|
|
25
|
+
readonly metal: Vec3;
|
|
26
|
+
/** Cloak / mantle over the shoulders; default `leather`. */
|
|
27
|
+
readonly cape?: Vec3;
|
|
28
|
+
/** Fur trim on the bust (mantle, hood lining); default `leather`. */
|
|
29
|
+
readonly fur?: Vec3;
|
|
30
|
+
/** War paint / tattoo stripes (orc), or none. */
|
|
31
|
+
readonly paint?: Vec3;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface HeadParams {
|
|
35
|
+
/** Chin to crown, metres (adult human ≈ 0.23). */
|
|
36
|
+
readonly height: number;
|
|
37
|
+
/** Full width / depth of the brain case; `height` = crown above the eye line. */
|
|
38
|
+
readonly cranium: { readonly width: number; readonly depth: number; readonly height: number };
|
|
39
|
+
/** Ridge radius, how far it juts past the eyes, and the scowl (inner ends lowered). */
|
|
40
|
+
/** Ridge radius, overhang past the eyes, scowl (inner ends lowered), eyebrow hair radius
|
|
41
|
+
* (omit or 0 = no eyebrows). */
|
|
42
|
+
readonly brow: {
|
|
43
|
+
readonly ridge: number;
|
|
44
|
+
readonly overhang: number;
|
|
45
|
+
readonly scowl: number;
|
|
46
|
+
readonly hair?: number;
|
|
47
|
+
};
|
|
48
|
+
readonly cheeks: { readonly size: number; readonly width: number };
|
|
49
|
+
/** Half-width at the jaw angle, chin size, lower-jaw jut past the upper lip, chin drop,
|
|
50
|
+
* muzzle (how far mouth and jaw push forward of the brow: prognathism). */
|
|
51
|
+
readonly jaw: {
|
|
52
|
+
readonly muzzle: number;
|
|
53
|
+
readonly width: number;
|
|
54
|
+
readonly chin: number;
|
|
55
|
+
readonly underbite: number;
|
|
56
|
+
readonly drop: number;
|
|
57
|
+
};
|
|
58
|
+
/** Eye line → nose base, nostril half-width, tip radius, hook (droop), bridge projection. */
|
|
59
|
+
readonly nose: {
|
|
60
|
+
readonly length: number;
|
|
61
|
+
readonly width: number;
|
|
62
|
+
readonly tip: number;
|
|
63
|
+
readonly hook: number;
|
|
64
|
+
readonly bridge: number;
|
|
65
|
+
};
|
|
66
|
+
/** Length (root → tip), width, sweep back (deg), tilt up (deg). */
|
|
67
|
+
readonly ears: {
|
|
68
|
+
readonly shape: EarShape;
|
|
69
|
+
readonly length: number;
|
|
70
|
+
readonly width: number;
|
|
71
|
+
readonly sweep: number;
|
|
72
|
+
readonly lift: number;
|
|
73
|
+
};
|
|
74
|
+
/** Eyeball radius, centre-to-midline, eye line (fraction of height), lid angles (deg). */
|
|
75
|
+
readonly eyes: {
|
|
76
|
+
readonly radius: number;
|
|
77
|
+
readonly spacing: number;
|
|
78
|
+
readonly line: number;
|
|
79
|
+
readonly lid: number;
|
|
80
|
+
readonly lowerLid: number;
|
|
81
|
+
};
|
|
82
|
+
/** Half-width, lip thickness, grin (0 = closed line, 1 = wide toothy smile). */
|
|
83
|
+
readonly mouth: { readonly width: number; readonly lips: number; readonly grin: number };
|
|
84
|
+
readonly tusks: {
|
|
85
|
+
readonly length: number;
|
|
86
|
+
readonly radius: number;
|
|
87
|
+
readonly spread: number;
|
|
88
|
+
readonly flare: number;
|
|
89
|
+
} | null;
|
|
90
|
+
readonly neck: { readonly radius: number };
|
|
91
|
+
readonly bust: { readonly shoulders: number; readonly style: BustStyle };
|
|
92
|
+
readonly hair: HairStyle;
|
|
93
|
+
readonly beard: BeardStyle;
|
|
94
|
+
/** Wart / bump count scattered on the skin (seeded). */
|
|
95
|
+
readonly warts: number;
|
|
96
|
+
readonly colors: HeadColors;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const bad = (cause: string, fix: string): never => {
|
|
100
|
+
throw new Ts23dError('T3D_RECIPE_INVALID', { cause, fix });
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
/** Every number in a params object, with its path, for the finite/positive sweep. */
|
|
104
|
+
function numbers(o: unknown, path: string, out: Array<[string, number]>): void {
|
|
105
|
+
if (typeof o === 'number') out.push([path, o]);
|
|
106
|
+
else if (o && typeof o === 'object' && !Array.isArray(o))
|
|
107
|
+
for (const [k, v] of Object.entries(o)) numbers(v, path ? `${path}.${k}` : k, out);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const SIGNED = new Set([
|
|
111
|
+
'brow.scowl',
|
|
112
|
+
'jaw.underbite',
|
|
113
|
+
'nose.hook',
|
|
114
|
+
'ears.lift',
|
|
115
|
+
'ears.sweep',
|
|
116
|
+
'nose.bridge',
|
|
117
|
+
]);
|
|
118
|
+
const ZERO_OK = new Set([
|
|
119
|
+
'mouth.grin',
|
|
120
|
+
'warts',
|
|
121
|
+
'jaw.drop',
|
|
122
|
+
'jaw.muzzle',
|
|
123
|
+
'brow.overhang',
|
|
124
|
+
'brow.hair',
|
|
125
|
+
]);
|
|
126
|
+
|
|
127
|
+
/** Throws T3D_RECIPE_INVALID (with a fix) when `p` cannot make a head. */
|
|
128
|
+
export function checkHead(p: HeadParams): HeadParams {
|
|
129
|
+
if (!(p.height >= 0.05 && p.height <= 3))
|
|
130
|
+
bad(`head height ${p.height} m`, 'give height in metres, chin to crown: human 0.23, orc ≈ 0.3');
|
|
131
|
+
const flat: Array<[string, number]> = [];
|
|
132
|
+
numbers({ ...p, colors: undefined, height: undefined }, '', flat);
|
|
133
|
+
for (const [k, v] of flat) {
|
|
134
|
+
if (!Number.isFinite(v)) bad(`head param ${k} is ${v}`, 'use finite numbers (head units)');
|
|
135
|
+
if (SIGNED.has(k)) continue;
|
|
136
|
+
if (v < 0 || (v === 0 && !ZERO_OK.has(k)))
|
|
137
|
+
bad(`head param ${k} is ${v}`, `make ${k} > 0 (head units: 1 = chin to crown)`);
|
|
138
|
+
}
|
|
139
|
+
const e = p.eyes;
|
|
140
|
+
if (e.line < 0.3 || e.line > 0.7)
|
|
141
|
+
bad(`eyes.line ${e.line}`, 'keep the eye line between 0.3 and 0.7 of the head height');
|
|
142
|
+
if (e.spacing < e.radius * 1.1)
|
|
143
|
+
bad(
|
|
144
|
+
`eyes.spacing ${e.spacing} < eyes.radius ${e.radius} × 1.1: the eyeballs overlap`,
|
|
145
|
+
'raise eyes.spacing (centre to midline) or shrink eyes.radius',
|
|
146
|
+
);
|
|
147
|
+
if (e.lid <= e.lowerLid * -1 || e.lid > 80)
|
|
148
|
+
bad(`eye lids ${e.lid}°/${e.lowerLid}° leave no opening`, 'use lid in (−lowerLid, 80] degrees');
|
|
149
|
+
if (p.mouth.grin > 1) bad(`mouth.grin ${p.mouth.grin}`, 'grin runs 0 (closed) … 1 (wide)');
|
|
150
|
+
if (p.tusks && p.tusks.length > 0.6)
|
|
151
|
+
bad(`tusks.length ${p.tusks.length}`, 'tusks longer than 0.6 head units read as horns');
|
|
152
|
+
return p;
|
|
153
|
+
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
// Race presets for the head kind: plain `HeadParams` data in head units (1 = chin to crown) —
|
|
2
|
+
// spread one and override (`{ ...orc, tusks: null }` is a tuskless orc). Original stylised designs
|
|
3
|
+
// in the chunky hand-painted fantasy idiom, not copies of any game's characters. Proportions and
|
|
4
|
+
// palettes are documented per recipe in recipes/<race>_head/README.md.
|
|
5
|
+
|
|
6
|
+
import { hex } from '../material/index.ts';
|
|
7
|
+
import type { HeadParams } from './head-params.ts';
|
|
8
|
+
|
|
9
|
+
/** Classical proportions, slightly heroic: eye line at half height, thirds brow/nose/chin. */
|
|
10
|
+
export const human: HeadParams = {
|
|
11
|
+
height: 0.23,
|
|
12
|
+
cranium: { width: 0.66, depth: 0.86, height: 0.5 },
|
|
13
|
+
brow: { ridge: 0.035, overhang: 0.03, scowl: 0.012, hair: 0.018 },
|
|
14
|
+
cheeks: { size: 1, width: 1 },
|
|
15
|
+
jaw: { muzzle: 0, width: 0.22, chin: 1.05, underbite: 0, drop: 0 },
|
|
16
|
+
nose: { length: 0.18, width: 0.06, tip: 0.036, hook: 0, bridge: 0 },
|
|
17
|
+
ears: { shape: 'round', length: 0.27, width: 0.15, sweep: 12, lift: 0 },
|
|
18
|
+
eyes: { radius: 0.052, spacing: 0.135, line: 0.5, lid: 20, lowerLid: 30 },
|
|
19
|
+
mouth: { width: 0.1, lips: 0.018, grin: 0 },
|
|
20
|
+
tusks: null,
|
|
21
|
+
neck: { radius: 0.27 },
|
|
22
|
+
bust: { shoulders: 0.95, style: 'tunic' },
|
|
23
|
+
hair: 'swept',
|
|
24
|
+
beard: 'short',
|
|
25
|
+
warts: 0,
|
|
26
|
+
colors: {
|
|
27
|
+
skin: hex('#d9a07a'),
|
|
28
|
+
shade: hex('#a8644a'),
|
|
29
|
+
lips: hex('#b86b5c'),
|
|
30
|
+
sclera: hex('#ece6dc'),
|
|
31
|
+
iris: hex('#3d6e9e'),
|
|
32
|
+
pupil: hex('#141414'),
|
|
33
|
+
teeth: hex('#ece2c8'),
|
|
34
|
+
mouth: hex('#4a1a18'),
|
|
35
|
+
hair: hex('#5a3a22'),
|
|
36
|
+
cloth: hex('#2e4f8a'),
|
|
37
|
+
cape: hex('#7a2420'),
|
|
38
|
+
leather: hex('#6b4428'),
|
|
39
|
+
metal: hex('#c9a24a'),
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/** Massive underbitten jaw, tusks, heavy brow over small deep eyes, flat broad nose, thick neck. */
|
|
44
|
+
export const orc: HeadParams = {
|
|
45
|
+
height: 0.3,
|
|
46
|
+
cranium: { width: 0.7, depth: 0.8, height: 0.34 },
|
|
47
|
+
brow: { ridge: 0.065, overhang: 0.075, scowl: 0.035 },
|
|
48
|
+
cheeks: { size: 1.35, width: 1.15 },
|
|
49
|
+
jaw: { muzzle: 0.1, width: 0.37, chin: 1.6, underbite: 0.07, drop: 0.1 },
|
|
50
|
+
nose: { length: 0.15, width: 0.09, tip: 0.04, hook: -0.01, bridge: -0.045 },
|
|
51
|
+
ears: { shape: 'pointed', length: 0.3, width: 0.17, sweep: 25, lift: 8 },
|
|
52
|
+
eyes: { radius: 0.05, spacing: 0.155, line: 0.56, lid: 16, lowerLid: 26 },
|
|
53
|
+
mouth: { width: 0.17, lips: 0.03, grin: 0 },
|
|
54
|
+
tusks: { length: 0.27, radius: 0.05, spread: 0.14, flare: 0.35 },
|
|
55
|
+
neck: { radius: 0.32 },
|
|
56
|
+
bust: { shoulders: 1.05, style: 'pauldrons' },
|
|
57
|
+
hair: 'topknot',
|
|
58
|
+
beard: 'none',
|
|
59
|
+
warts: 6,
|
|
60
|
+
colors: {
|
|
61
|
+
skin: hex('#5f8b3a'),
|
|
62
|
+
shade: hex('#3c5c24'),
|
|
63
|
+
lips: hex('#4f6f30'),
|
|
64
|
+
sclera: hex('#8a5a1a'),
|
|
65
|
+
iris: hex('#ffb030'),
|
|
66
|
+
pupil: hex('#140a06'),
|
|
67
|
+
teeth: hex('#e2d6b0'),
|
|
68
|
+
mouth: hex('#2a0e0c'),
|
|
69
|
+
hair: hex('#1c1714'),
|
|
70
|
+
cloth: hex('#6b2a20'),
|
|
71
|
+
leather: hex('#5a3a22'),
|
|
72
|
+
metal: hex('#6e6a62'),
|
|
73
|
+
fur: hex('#5e4a34'),
|
|
74
|
+
paint: hex('#8a1c12'),
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
/** Small and sly: huge leaf ears, long hooked nose, wide toothy grin, big bright eyes. */
|
|
79
|
+
export const goblin: HeadParams = {
|
|
80
|
+
height: 0.19,
|
|
81
|
+
cranium: { width: 0.66, depth: 0.84, height: 0.5 },
|
|
82
|
+
brow: { ridge: 0.03, overhang: 0.015, scowl: 0.025 },
|
|
83
|
+
cheeks: { size: 1.15, width: 1.1 },
|
|
84
|
+
jaw: { muzzle: 0.06, width: 0.2, chin: 0.85, underbite: 0, drop: 0 },
|
|
85
|
+
nose: { length: 0.2, width: 0.07, tip: 0.05, hook: 0.07, bridge: 0.12 },
|
|
86
|
+
ears: { shape: 'pointed', length: 0.62, width: 0.3, sweep: 22, lift: 18 },
|
|
87
|
+
eyes: { radius: 0.072, spacing: 0.15, line: 0.5, lid: 16, lowerLid: 26 },
|
|
88
|
+
mouth: { width: 0.2, lips: 0.016, grin: 0.75 },
|
|
89
|
+
tusks: null,
|
|
90
|
+
neck: { radius: 0.2 },
|
|
91
|
+
bust: { shoulders: 0.75, style: 'hood' },
|
|
92
|
+
hair: 'tufts',
|
|
93
|
+
beard: 'none',
|
|
94
|
+
warts: 4,
|
|
95
|
+
colors: {
|
|
96
|
+
skin: hex('#9cb84a'),
|
|
97
|
+
shade: hex('#6a8a2c'),
|
|
98
|
+
lips: hex('#7a8e36'),
|
|
99
|
+
sclera: hex('#f0e6a0'),
|
|
100
|
+
iris: hex('#e0a020'),
|
|
101
|
+
pupil: hex('#120c04'),
|
|
102
|
+
teeth: hex('#e8dcae'),
|
|
103
|
+
mouth: hex('#3a1010'),
|
|
104
|
+
hair: hex('#3a3024'),
|
|
105
|
+
cloth: hex('#6a4a7a'),
|
|
106
|
+
leather: hex('#5a3a22'),
|
|
107
|
+
metal: hex('#a88a4a'),
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
/** Broad and bearded: wide skull, big bulbous nose, braided beard, deep-set eyes. */
|
|
112
|
+
export const dwarf: HeadParams = {
|
|
113
|
+
...human,
|
|
114
|
+
height: 0.24,
|
|
115
|
+
cranium: { width: 0.74, depth: 0.86, height: 0.48 },
|
|
116
|
+
brow: { ridge: 0.05, overhang: 0.05, scowl: 0.02, hair: 0.024 },
|
|
117
|
+
cheeks: { size: 1.25, width: 1.1 },
|
|
118
|
+
jaw: { muzzle: 0.02, width: 0.27, chin: 1.2, underbite: 0, drop: 0.02 },
|
|
119
|
+
nose: { length: 0.2, width: 0.08, tip: 0.06, hook: 0.02, bridge: 0.02 },
|
|
120
|
+
eyes: { radius: 0.045, spacing: 0.14, line: 0.52, lid: 10, lowerLid: 24 },
|
|
121
|
+
neck: { radius: 0.34 },
|
|
122
|
+
bust: { shoulders: 1.1, style: 'tunic' },
|
|
123
|
+
hair: 'braids',
|
|
124
|
+
beard: 'braided',
|
|
125
|
+
colors: { ...human.colors, skin: hex('#d49470'), hair: hex('#8a3f1c'), cloth: hex('#4a5a2a') },
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
/** Slender and ageless: long swept-back ears, high cheekbones, fine nose, almond eyes. */
|
|
129
|
+
export const elf: HeadParams = {
|
|
130
|
+
...human,
|
|
131
|
+
height: 0.235,
|
|
132
|
+
cranium: { width: 0.62, depth: 0.86, height: 0.52 },
|
|
133
|
+
brow: { ridge: 0.025, overhang: 0.02, scowl: -0.01, hair: 0.012 },
|
|
134
|
+
cheeks: { size: 1.1, width: 1.05 },
|
|
135
|
+
jaw: { muzzle: 0, width: 0.19, chin: 0.85, underbite: 0, drop: 0 },
|
|
136
|
+
nose: { length: 0.18, width: 0.045, tip: 0.028, hook: 0, bridge: 0.01 },
|
|
137
|
+
ears: { shape: 'pointed', length: 0.42, width: 0.16, sweep: 40, lift: 24 },
|
|
138
|
+
eyes: { radius: 0.05, spacing: 0.13, line: 0.5, lid: 14, lowerLid: 22 },
|
|
139
|
+
bust: { shoulders: 0.85, style: 'tunic' },
|
|
140
|
+
hair: 'swept',
|
|
141
|
+
beard: 'none',
|
|
142
|
+
colors: {
|
|
143
|
+
...human.colors,
|
|
144
|
+
skin: hex('#e8c0a0'),
|
|
145
|
+
hair: hex('#e0d090'),
|
|
146
|
+
iris: hex('#3a8a5a'),
|
|
147
|
+
cloth: hex('#2a6a4a'),
|
|
148
|
+
},
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
export const HEADS = { human, orc, goblin, dwarf, elf } as const;
|
|
152
|
+
export type HeadPreset = keyof typeof HEADS;
|