@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,59 @@
|
|
|
1
|
+
// Provenance inside the file: `asset.generator` with the version, `asset.copyright`, and an XMP
|
|
2
|
+
// packet (KHR_xmp_json_ld, Ratified) with title, description, creators, rights, licence and the
|
|
3
|
+
// recipe's sources — what Sketchfab, Blender and asset stores read. No dates: bytes stay
|
|
4
|
+
// deterministic.
|
|
5
|
+
|
|
6
|
+
import type { Json } from './bin-writer.ts';
|
|
7
|
+
import { GENERATOR } from './version.ts';
|
|
8
|
+
|
|
9
|
+
export interface GlbMeta {
|
|
10
|
+
/** Default: the asset name. */
|
|
11
|
+
readonly title?: string;
|
|
12
|
+
readonly description?: string;
|
|
13
|
+
readonly creators?: readonly string[];
|
|
14
|
+
/** Goes to `asset.copyright` and `dc:rights`, e.g. "© 2026 Jane Doe". */
|
|
15
|
+
readonly copyright?: string;
|
|
16
|
+
/** SPDX id or licence URL → `xmpRights:UsageTerms`, e.g. "CC-BY-4.0". */
|
|
17
|
+
readonly license?: string;
|
|
18
|
+
/** Where the numbers came from (the recipe's `sources`) → `dc:relation`. */
|
|
19
|
+
readonly sources?: readonly string[];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const XMP = 'KHR_xmp_json_ld';
|
|
23
|
+
const alt = (value: string): Json => ({
|
|
24
|
+
'@type': 'rdf:Alt',
|
|
25
|
+
'rdf:_1': { '@language': 'x-default', '@value': value },
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
/** `asset` block, plus root `extensions` and the extension name when an XMP packet is written. */
|
|
29
|
+
export function metadata(
|
|
30
|
+
name: string,
|
|
31
|
+
meta: GlbMeta | undefined,
|
|
32
|
+
): { asset: Json; extensions?: Json; used: string[] } {
|
|
33
|
+
const asset: Json = {
|
|
34
|
+
version: '2.0',
|
|
35
|
+
generator: GENERATOR,
|
|
36
|
+
...(meta?.copyright ? { copyright: meta.copyright } : {}),
|
|
37
|
+
};
|
|
38
|
+
if (!meta) return { asset, used: [] };
|
|
39
|
+
const packet: Json = {
|
|
40
|
+
'@context': {
|
|
41
|
+
dc: 'http://purl.org/dc/elements/1.1/',
|
|
42
|
+
rdf: 'http://www.w3.org/1999/02/22-rdf-syntax-ns#',
|
|
43
|
+
xmp: 'http://ns.adobe.com/xap/1.0/',
|
|
44
|
+
xmpRights: 'http://ns.adobe.com/xap/1.0/rights/',
|
|
45
|
+
},
|
|
46
|
+
'dc:title': alt(meta.title ?? name),
|
|
47
|
+
...(meta.description ? { 'dc:description': alt(meta.description) } : {}),
|
|
48
|
+
...(meta.creators?.length ? { 'dc:creator': { '@list': [...meta.creators] } } : {}),
|
|
49
|
+
...(meta.copyright ? { 'dc:rights': alt(meta.copyright) } : {}),
|
|
50
|
+
...(meta.license ? { 'xmpRights:UsageTerms': alt(meta.license) } : {}),
|
|
51
|
+
...(meta.sources?.length ? { 'dc:relation': { '@set': [...meta.sources] } } : {}),
|
|
52
|
+
'xmp:CreatorTool': GENERATOR,
|
|
53
|
+
};
|
|
54
|
+
return {
|
|
55
|
+
asset: { ...asset, extensions: { [XMP]: { packet: 0 } } },
|
|
56
|
+
extensions: { [XMP]: { packets: [packet] } },
|
|
57
|
+
used: [XMP],
|
|
58
|
+
};
|
|
59
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// Bakes a part's Motion into one glTF sampler + channel of the looping `run` clip.
|
|
2
|
+
// Seamless by construction: every motion is rounded to a WHOLE number of cycles over the clip,
|
|
3
|
+
// so the last key equals the first (a 0.8 rps wheel over 4 s = 3 turns, not 3.2 — no jump at
|
|
4
|
+
// the loop point). Spins get ≥ 12 keys per turn so LINEAR quaternion interpolation never takes
|
|
5
|
+
// the short way round. Time arrays are shared between channels of equal key count.
|
|
6
|
+
|
|
7
|
+
import type { Motion } from '../core/index.ts';
|
|
8
|
+
import { quatFromAxisAngle } from '../core/index.ts';
|
|
9
|
+
import type { BinWriter, Json } from './bin-writer.ts';
|
|
10
|
+
|
|
11
|
+
export const LOOP_SECONDS = 4;
|
|
12
|
+
|
|
13
|
+
/** Whole cycles of `rate` (Hz or rev/s) over the clip, at least one. */
|
|
14
|
+
export const cyclesOf = (rate: number): number =>
|
|
15
|
+
Math.max(1, Math.round(Math.abs(rate) * LOOP_SECONDS)) * Math.sign(rate || 1);
|
|
16
|
+
|
|
17
|
+
const timeAccessors = new WeakMap<BinWriter, Map<number, number>>();
|
|
18
|
+
function times(bin: BinWriter, keys: number): { input: number; t: Float32Array } {
|
|
19
|
+
const t = new Float32Array(keys + 1).map((_, i) => (i / keys) * LOOP_SECONDS);
|
|
20
|
+
const byBin = timeAccessors.get(bin) ?? new Map<number, number>();
|
|
21
|
+
timeAccessors.set(bin, byBin);
|
|
22
|
+
let input = byBin.get(keys);
|
|
23
|
+
if (input === undefined) {
|
|
24
|
+
input = bin.accessor(t, 'SCALAR', undefined, true);
|
|
25
|
+
byBin.set(keys, input);
|
|
26
|
+
}
|
|
27
|
+
return { input, t };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function motionChannel(
|
|
31
|
+
bin: BinWriter,
|
|
32
|
+
motion: Motion,
|
|
33
|
+
node: number,
|
|
34
|
+
rest: readonly number[],
|
|
35
|
+
): { sampler: Json; channel: Json } {
|
|
36
|
+
const rate = motion.kind === 'spin' ? motion.rps : motion.hz;
|
|
37
|
+
const cycles = cyclesOf(rate);
|
|
38
|
+
const keys = Math.max(24, Math.abs(cycles) * (motion.kind === 'spin' ? 12 : 16));
|
|
39
|
+
const { input, t } = times(bin, keys);
|
|
40
|
+
const phase = (time: number): number => (time / LOOP_SECONDS) * cycles * Math.PI * 2;
|
|
41
|
+
// Two-sided: −1..1 around rest. One-way: 0..1, starting and ending at rest (seamless loop).
|
|
42
|
+
const wave = (time: number): number =>
|
|
43
|
+
motion.kind !== 'spin' && motion.oneWay
|
|
44
|
+
? (1 - Math.cos(phase(time))) / 2
|
|
45
|
+
: Math.sin(phase(time));
|
|
46
|
+
if (motion.kind === 'slide') {
|
|
47
|
+
const values = new Float32Array((keys + 1) * 3);
|
|
48
|
+
t.forEach((time, i) => {
|
|
49
|
+
const s = wave(time);
|
|
50
|
+
values.set(
|
|
51
|
+
[0, 1, 2].map((k) => (rest[k] ?? 0) + (motion.offset[k] ?? 0) * s),
|
|
52
|
+
i * 3,
|
|
53
|
+
);
|
|
54
|
+
});
|
|
55
|
+
return {
|
|
56
|
+
sampler: { input, output: bin.accessor(values, 'VEC3'), interpolation: 'LINEAR' },
|
|
57
|
+
channel: { target: { node, path: 'translation' } },
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
const values = new Float32Array((keys + 1) * 4);
|
|
61
|
+
const peak = motion.kind === 'sway' ? (motion.degrees * Math.PI) / 180 : 0;
|
|
62
|
+
t.forEach((time, i) => {
|
|
63
|
+
const angle = motion.kind === 'spin' ? phase(time) : peak * wave(time);
|
|
64
|
+
values.set(quatFromAxisAngle(motion.axis, angle), i * 4);
|
|
65
|
+
});
|
|
66
|
+
return {
|
|
67
|
+
sampler: { input, output: bin.accessor(values, 'VEC4'), interpolation: 'LINEAR' },
|
|
68
|
+
channel: { target: { node, path: 'rotation' } },
|
|
69
|
+
};
|
|
70
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
// Runs the mesh optimiser (passes/optimize: weld → vertex cache → vertex fetch) over every part
|
|
2
|
+
// headed for the GLB, and sums what it did into one report the CLI and manifest can print.
|
|
3
|
+
|
|
4
|
+
import type { CompiledAsset } from '../core/index.ts';
|
|
5
|
+
import { type OptimizeOptions, type OptimizeReport, optimize } from '../passes/index.ts';
|
|
6
|
+
|
|
7
|
+
export interface PartOptimizeReport extends OptimizeReport {
|
|
8
|
+
readonly part: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** Totals over all parts; ACMR/ATVR are triangle-weighted means. */
|
|
12
|
+
export interface ExportReport {
|
|
13
|
+
readonly optimized: boolean;
|
|
14
|
+
readonly verticesBefore: number;
|
|
15
|
+
readonly verticesAfter: number;
|
|
16
|
+
readonly trianglesBefore: number;
|
|
17
|
+
readonly trianglesAfter: number;
|
|
18
|
+
readonly degenerate: number;
|
|
19
|
+
readonly duplicate: number;
|
|
20
|
+
readonly acmrBefore: number;
|
|
21
|
+
readonly acmr: number;
|
|
22
|
+
readonly atvrBefore: number;
|
|
23
|
+
readonly atvr: number;
|
|
24
|
+
readonly parts: readonly PartOptimizeReport[];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function summarize(parts: readonly PartOptimizeReport[], optimized: boolean): ExportReport {
|
|
28
|
+
const sum = (f: (r: PartOptimizeReport) => number): number => parts.reduce((s, r) => s + f(r), 0);
|
|
29
|
+
const tb = sum((r) => r.trianglesBefore);
|
|
30
|
+
const ta = sum((r) => r.trianglesAfter);
|
|
31
|
+
const mean = (f: (r: PartOptimizeReport) => number, by: number, w: 'b' | 'a'): number =>
|
|
32
|
+
by ? sum((r) => f(r) * (w === 'b' ? r.trianglesBefore : r.trianglesAfter)) / by : 0;
|
|
33
|
+
return {
|
|
34
|
+
optimized,
|
|
35
|
+
verticesBefore: sum((r) => r.verticesBefore),
|
|
36
|
+
verticesAfter: sum((r) => r.verticesAfter),
|
|
37
|
+
trianglesBefore: tb,
|
|
38
|
+
trianglesAfter: ta,
|
|
39
|
+
degenerate: sum((r) => r.degenerate),
|
|
40
|
+
duplicate: sum((r) => r.duplicate),
|
|
41
|
+
acmrBefore: mean((r) => r.acmrBefore, tb, 'b'),
|
|
42
|
+
acmr: mean((r) => r.acmr, ta, 'a'),
|
|
43
|
+
atvrBefore: mean((r) => r.atvrBefore, tb, 'b'),
|
|
44
|
+
atvr: mean((r) => r.atvr, ta, 'a'),
|
|
45
|
+
parts,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Optimise every part mesh. `false` passes meshes through and reports them as-is. */
|
|
50
|
+
export function optimizeParts(
|
|
51
|
+
asset: CompiledAsset,
|
|
52
|
+
o: OptimizeOptions | false,
|
|
53
|
+
): { asset: CompiledAsset; report: ExportReport } {
|
|
54
|
+
const reports: PartOptimizeReport[] = [];
|
|
55
|
+
const parts = asset.parts.map((p) => {
|
|
56
|
+
if (!p.mesh || p.mesh.indices.length === 0) return p;
|
|
57
|
+
if (o === false) {
|
|
58
|
+
const v = p.mesh.positions.length / 3;
|
|
59
|
+
const t = p.mesh.indices.length / 3;
|
|
60
|
+
reports.push({
|
|
61
|
+
part: p.name,
|
|
62
|
+
verticesBefore: v,
|
|
63
|
+
verticesAfter: v,
|
|
64
|
+
trianglesBefore: t,
|
|
65
|
+
trianglesAfter: t,
|
|
66
|
+
degenerate: 0,
|
|
67
|
+
duplicate: 0,
|
|
68
|
+
acmrBefore: 0,
|
|
69
|
+
acmr: 0,
|
|
70
|
+
atvrBefore: 0,
|
|
71
|
+
atvr: 0,
|
|
72
|
+
});
|
|
73
|
+
return p;
|
|
74
|
+
}
|
|
75
|
+
const { mesh, report } = optimize(p.mesh, o);
|
|
76
|
+
reports.push({ part: p.name, ...report });
|
|
77
|
+
return { ...p, mesh };
|
|
78
|
+
});
|
|
79
|
+
return { asset: { ...asset, parts }, report: summarize(reports, o !== false) };
|
|
80
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// One mesh → one glTF primitive: POSITION (with bounds), NORMAL, TEXCOORD_0, COLOR_0 as unorm16
|
|
2
|
+
// RGBA, TANGENT only when a normal map is bound, and the smallest index type that fits.
|
|
3
|
+
|
|
4
|
+
import type { Mesh } from '../core/index.ts';
|
|
5
|
+
import { ARRAY_BUFFER, type BinWriter, ELEMENT_ARRAY_BUFFER, type Json } from './bin-writer.ts';
|
|
6
|
+
import { tangentsOf } from './tangents.ts';
|
|
7
|
+
|
|
8
|
+
export function writePrimitive(bin: BinWriter, m: Mesh, material: number, tangents: boolean): Json {
|
|
9
|
+
// COLOR_0 as normalised unsigned short RGBA: 8 bytes/vertex instead of 16, no banding in
|
|
10
|
+
// darks (8-bit would). VEC4 because vertex attributes must be 4-byte aligned.
|
|
11
|
+
const colors = new Uint16Array((m.colors.length / 3) * 4);
|
|
12
|
+
const q = (v: number): number => Math.round(Math.min(1, Math.max(0, v)) * 65535);
|
|
13
|
+
for (let i = 0; i < m.colors.length / 3; i++)
|
|
14
|
+
colors.set(
|
|
15
|
+
[q(m.colors[i * 3] ?? 1), q(m.colors[i * 3 + 1] ?? 1), q(m.colors[i * 3 + 2] ?? 1), 65535],
|
|
16
|
+
i * 4,
|
|
17
|
+
);
|
|
18
|
+
return {
|
|
19
|
+
attributes: {
|
|
20
|
+
POSITION: bin.accessor(m.positions, 'VEC3', ARRAY_BUFFER, true),
|
|
21
|
+
NORMAL: bin.accessor(m.normals, 'VEC3', ARRAY_BUFFER),
|
|
22
|
+
TEXCOORD_0: bin.accessor(m.uvs, 'VEC2', ARRAY_BUFFER),
|
|
23
|
+
COLOR_0: bin.accessor(colors, 'VEC4', ARRAY_BUFFER, false, true),
|
|
24
|
+
// A bound normal map needs a tangent space (validator S10 / G15).
|
|
25
|
+
...(tangents ? { TANGENT: bin.accessor(tangentsOf(m), 'VEC4', ARRAY_BUFFER) } : {}),
|
|
26
|
+
},
|
|
27
|
+
// 16-bit indices whenever the primitive allows: half the bytes, every engine reads them.
|
|
28
|
+
indices: bin.accessor(
|
|
29
|
+
m.positions.length / 3 <= 65535 ? Uint16Array.from(m.indices) : m.indices,
|
|
30
|
+
'SCALAR',
|
|
31
|
+
ELEMENT_ARRAY_BUFFER,
|
|
32
|
+
),
|
|
33
|
+
material,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
// Per-vertex tangents (glTF TANGENT, VEC4) for normal-mapped meshes: Lengyel's UV-derivative
|
|
2
|
+
// method, Gram-Schmidt against the vertex normal, w = handedness. The bitangent cross(N, T) · w
|
|
3
|
+
// points toward DECREASING glTF v (the "green up" convention the painter writes). Identical to
|
|
4
|
+
// MikkTSpace on flat-shaded quads; close, not bit-equal, on smooth curved surfaces.
|
|
5
|
+
|
|
6
|
+
import type { Mesh } from '../core/index.ts';
|
|
7
|
+
|
|
8
|
+
export function tangentsOf(m: Mesh): Float32Array {
|
|
9
|
+
const n = m.positions.length / 3;
|
|
10
|
+
const tan = new Float32Array(n * 3);
|
|
11
|
+
const bit = new Float32Array(n * 3);
|
|
12
|
+
const P = m.positions;
|
|
13
|
+
const U = m.uvs;
|
|
14
|
+
for (let t = 0; t < m.indices.length; t += 3) {
|
|
15
|
+
const a = m.indices[t] ?? 0;
|
|
16
|
+
const b = m.indices[t + 1] ?? 0;
|
|
17
|
+
const c = m.indices[t + 2] ?? 0;
|
|
18
|
+
const e1x = (P[b * 3] ?? 0) - (P[a * 3] ?? 0);
|
|
19
|
+
const e1y = (P[b * 3 + 1] ?? 0) - (P[a * 3 + 1] ?? 0);
|
|
20
|
+
const e1z = (P[b * 3 + 2] ?? 0) - (P[a * 3 + 2] ?? 0);
|
|
21
|
+
const e2x = (P[c * 3] ?? 0) - (P[a * 3] ?? 0);
|
|
22
|
+
const e2y = (P[c * 3 + 1] ?? 0) - (P[a * 3 + 1] ?? 0);
|
|
23
|
+
const e2z = (P[c * 3 + 2] ?? 0) - (P[a * 3 + 2] ?? 0);
|
|
24
|
+
const du1 = (U[b * 2] ?? 0) - (U[a * 2] ?? 0);
|
|
25
|
+
const dv1 = -((U[b * 2 + 1] ?? 0) - (U[a * 2 + 1] ?? 0));
|
|
26
|
+
const du2 = (U[c * 2] ?? 0) - (U[a * 2] ?? 0);
|
|
27
|
+
const dv2 = -((U[c * 2 + 1] ?? 0) - (U[a * 2 + 1] ?? 0));
|
|
28
|
+
const det = du1 * dv2 - du2 * dv1;
|
|
29
|
+
if (Math.abs(det) < 1e-12) continue;
|
|
30
|
+
const r = 1 / det;
|
|
31
|
+
const tx = (e1x * dv2 - e2x * dv1) * r;
|
|
32
|
+
const ty = (e1y * dv2 - e2y * dv1) * r;
|
|
33
|
+
const tz = (e1z * dv2 - e2z * dv1) * r;
|
|
34
|
+
const bx = (e2x * du1 - e1x * du2) * r;
|
|
35
|
+
const by = (e2y * du1 - e1y * du2) * r;
|
|
36
|
+
const bz = (e2z * du1 - e1z * du2) * r;
|
|
37
|
+
for (const v of [a, b, c]) {
|
|
38
|
+
tan[v * 3] = (tan[v * 3] ?? 0) + tx;
|
|
39
|
+
tan[v * 3 + 1] = (tan[v * 3 + 1] ?? 0) + ty;
|
|
40
|
+
tan[v * 3 + 2] = (tan[v * 3 + 2] ?? 0) + tz;
|
|
41
|
+
bit[v * 3] = (bit[v * 3] ?? 0) + bx;
|
|
42
|
+
bit[v * 3 + 1] = (bit[v * 3 + 1] ?? 0) + by;
|
|
43
|
+
bit[v * 3 + 2] = (bit[v * 3 + 2] ?? 0) + bz;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
const out = new Float32Array(n * 4);
|
|
47
|
+
for (let v = 0; v < n; v++) {
|
|
48
|
+
const nx = m.normals[v * 3] ?? 0;
|
|
49
|
+
const ny = m.normals[v * 3 + 1] ?? 0;
|
|
50
|
+
const nz = m.normals[v * 3 + 2] ?? 1;
|
|
51
|
+
let tx = tan[v * 3] ?? 0;
|
|
52
|
+
let ty = tan[v * 3 + 1] ?? 0;
|
|
53
|
+
let tz = tan[v * 3 + 2] ?? 0;
|
|
54
|
+
const d = nx * tx + ny * ty + nz * tz;
|
|
55
|
+
tx -= nx * d;
|
|
56
|
+
ty -= ny * d;
|
|
57
|
+
tz -= nz * d;
|
|
58
|
+
let len = Math.hypot(tx, ty, tz);
|
|
59
|
+
if (len < 1e-8) {
|
|
60
|
+
// No usable UV gradient: any unit vector perpendicular to the normal.
|
|
61
|
+
[tx, ty, tz] = Math.abs(nx) < 0.9 ? [0, -nz, ny] : [nz, 0, -nx];
|
|
62
|
+
len = Math.hypot(tx, ty, tz) || 1;
|
|
63
|
+
}
|
|
64
|
+
tx /= len;
|
|
65
|
+
ty /= len;
|
|
66
|
+
tz /= len;
|
|
67
|
+
// Handedness: does cross(N, T) agree with the accumulated bitangent?
|
|
68
|
+
const cx = ny * tz - nz * ty;
|
|
69
|
+
const cy = nz * tx - nx * tz;
|
|
70
|
+
const cz = nx * ty - ny * tx;
|
|
71
|
+
const w = cx * (bit[v * 3] ?? 0) + cy * (bit[v * 3 + 1] ?? 0) + cz * (bit[v * 3 + 2] ?? 0);
|
|
72
|
+
out.set([tx, ty, tz, w < 0 ? -1 : 1], v * 4);
|
|
73
|
+
}
|
|
74
|
+
return out;
|
|
75
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
// The trim sheet in glTF: three images (albedo sRGB, normal + ORM linear), three textures and one
|
|
2
|
+
// sampler (REPEAT along U so strips tile, CLAMP along V so the atlas never wraps rows, trilinear
|
|
3
|
+
// mips). Embedded: PNGs ride in the BIN chunk after the geometry (self-contained single files).
|
|
4
|
+
// External: images reference `<prefix>_{albedo,normal,orm}.png` by URI, so fifty assets share one
|
|
5
|
+
// texture set on disk and in GPU memory — the point of the trim sheet. Write the PNGs once with
|
|
6
|
+
// `sheetPngs`. Texture indices are fixed: 0 albedo, 1 normal, 2 ORM (materials.ts binds them).
|
|
7
|
+
|
|
8
|
+
import { encodePng } from '../core/index.ts';
|
|
9
|
+
import type { PaintedSheet } from '../material/index.ts';
|
|
10
|
+
import type { Json } from './bin-writer.ts';
|
|
11
|
+
|
|
12
|
+
export type SheetMap = 'albedo' | 'normal' | 'orm';
|
|
13
|
+
export const SHEET_MAPS: readonly SheetMap[] = ['albedo', 'normal', 'orm'];
|
|
14
|
+
|
|
15
|
+
/** Reference a shared sheet by URI instead of embedding it. */
|
|
16
|
+
export interface ExternalSheet {
|
|
17
|
+
readonly external: true;
|
|
18
|
+
/** URI prefix relative to the .glb, e.g. `../textures/trim_sheet`. Default `trim_sheet`. */
|
|
19
|
+
readonly prefix?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export type SheetBinding = PaintedSheet | ExternalSheet;
|
|
23
|
+
|
|
24
|
+
export const sheetFileName = (map: SheetMap, prefix = 'trim_sheet'): string =>
|
|
25
|
+
`${prefix}_${map}.png`;
|
|
26
|
+
|
|
27
|
+
const LINEAR = 9729;
|
|
28
|
+
const LINEAR_MIPMAP_LINEAR = 9987;
|
|
29
|
+
const REPEAT = 10497;
|
|
30
|
+
const CLAMP_TO_EDGE = 33071;
|
|
31
|
+
|
|
32
|
+
const pngCache = new WeakMap<PaintedSheet, Readonly<Record<SheetMap, Uint8Array>>>();
|
|
33
|
+
|
|
34
|
+
/** PNG bytes of each map, encoded once per sheet (every LOD's GLB reuses them). */
|
|
35
|
+
export function sheetPngs(sheet: PaintedSheet): Readonly<Record<SheetMap, Uint8Array>> {
|
|
36
|
+
const hit = pngCache.get(sheet);
|
|
37
|
+
if (hit) return hit;
|
|
38
|
+
const pngs = {
|
|
39
|
+
albedo: encodePng(sheet.size, sheet.size, sheet.albedo),
|
|
40
|
+
normal: encodePng(sheet.size, sheet.size, sheet.normal),
|
|
41
|
+
orm: encodePng(sheet.size, sheet.size, sheet.orm),
|
|
42
|
+
};
|
|
43
|
+
pngCache.set(sheet, pngs);
|
|
44
|
+
return pngs;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const isExternal = (s: SheetBinding): s is ExternalSheet => 'external' in s;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* glTF `images` / `textures` / `samplers` for the sheet. Embedded image bytes are returned as
|
|
51
|
+
* `tail`, to append to the BIN chunk at `offset` (4-aligned); their bufferViews go into `views`.
|
|
52
|
+
*/
|
|
53
|
+
export function sheetGltf(
|
|
54
|
+
sheet: SheetBinding,
|
|
55
|
+
views: Json[],
|
|
56
|
+
offset: number,
|
|
57
|
+
): { json: Json; tail: Uint8Array } {
|
|
58
|
+
const textures = SHEET_MAPS.map((_, i) => ({ sampler: 0, source: i }));
|
|
59
|
+
const samplers = [
|
|
60
|
+
{ magFilter: LINEAR, minFilter: LINEAR_MIPMAP_LINEAR, wrapS: REPEAT, wrapT: CLAMP_TO_EDGE },
|
|
61
|
+
];
|
|
62
|
+
if (isExternal(sheet)) {
|
|
63
|
+
const images = SHEET_MAPS.map((m) => ({
|
|
64
|
+
name: `trim_sheet_${m}`,
|
|
65
|
+
uri: sheetFileName(m, sheet.prefix),
|
|
66
|
+
}));
|
|
67
|
+
return { json: { images, textures, samplers }, tail: new Uint8Array() };
|
|
68
|
+
}
|
|
69
|
+
const pngs = sheetPngs(sheet);
|
|
70
|
+
const parts: Uint8Array[] = [];
|
|
71
|
+
const images: Json[] = [];
|
|
72
|
+
let at = offset;
|
|
73
|
+
for (const m of SHEET_MAPS) {
|
|
74
|
+
const png = pngs[m];
|
|
75
|
+
views.push({ buffer: 0, byteOffset: at, byteLength: png.byteLength });
|
|
76
|
+
images.push({ name: `trim_sheet_${m}`, bufferView: views.length - 1, mimeType: 'image/png' });
|
|
77
|
+
const pad = (4 - (png.byteLength % 4)) % 4;
|
|
78
|
+
parts.push(png, new Uint8Array(pad));
|
|
79
|
+
at += png.byteLength + pad;
|
|
80
|
+
}
|
|
81
|
+
const tail = new Uint8Array(at - offset);
|
|
82
|
+
let o = 0;
|
|
83
|
+
for (const p of parts) {
|
|
84
|
+
tail.set(p, o);
|
|
85
|
+
o += p.byteLength;
|
|
86
|
+
}
|
|
87
|
+
return { json: { images, textures, samplers }, tail };
|
|
88
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// The package version written into every GLB (`asset.generator`, XMP CreatorTool). Kept as a
|
|
2
|
+
// constant because the library may not import package.json (rootDir is src); version.test.ts
|
|
3
|
+
// fails the gate when the two disagree, and `bun run release` stamps both.
|
|
4
|
+
|
|
5
|
+
export const VERSION = '0.1.0';
|
|
6
|
+
export const GENERATOR = `@developerz.ai/ts23d ${VERSION}`;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
// Split grammar (after CGA shape, Müller et al. 2006): one line lays out a facade strip.
|
|
2
|
+
// "pier:0.9 | {arch:2.5}* | door:3 | pier:~"
|
|
3
|
+
// x:n fixed width · {a:n | b:m}* repeat to fill · x:~ stretch (shares leftover equally)
|
|
4
|
+
|
|
5
|
+
import { Ts23dError } from '../core/index.ts';
|
|
6
|
+
|
|
7
|
+
export type SplitTerm =
|
|
8
|
+
| { readonly kind: 'fixed'; readonly symbol: string; readonly size: number }
|
|
9
|
+
| { readonly kind: 'stretch'; readonly symbol: string; readonly weight: number }
|
|
10
|
+
| { readonly kind: 'repeat'; readonly body: readonly SplitTerm[] };
|
|
11
|
+
|
|
12
|
+
export interface Slot {
|
|
13
|
+
readonly symbol: string;
|
|
14
|
+
readonly start: number;
|
|
15
|
+
readonly size: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const syntax = (src: string, why: string): Ts23dError =>
|
|
19
|
+
new Ts23dError('T3D_GRAMMAR_SYNTAX', {
|
|
20
|
+
cause: `cannot parse split "${src}": ${why}`,
|
|
21
|
+
fix: 'use `sym:<metres>`, `sym:~`, `sym:~<weight>` or `{a:1 | b:2}*`, separated by `|`',
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
function parseTerm(raw: string, src: string): SplitTerm {
|
|
25
|
+
const t = raw.trim();
|
|
26
|
+
const m = /^([a-z_][a-z0-9_]*):(~(\d*\.?\d+)?|\d*\.?\d+)$/.exec(t);
|
|
27
|
+
if (!m) throw syntax(src, `bad term '${t}'`);
|
|
28
|
+
const [, symbol = '', size = ''] = m;
|
|
29
|
+
if (size.startsWith('~')) return { kind: 'stretch', symbol, weight: m[3] ? Number(m[3]) : 1 };
|
|
30
|
+
return { kind: 'fixed', symbol, size: Number(size) };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function parseSplit(src: string): SplitTerm[] {
|
|
34
|
+
const out: SplitTerm[] = [];
|
|
35
|
+
const re = /\{([^}]*)\}\*|([^|{}]+)/g;
|
|
36
|
+
for (const m of src.matchAll(re)) {
|
|
37
|
+
if (m[1] !== undefined) {
|
|
38
|
+
const body = m[1].split('|').map((s) => parseTerm(s, src));
|
|
39
|
+
if (body.some((b) => b.kind !== 'fixed'))
|
|
40
|
+
throw syntax(src, 'a repeat body takes fixed sizes');
|
|
41
|
+
out.push({ kind: 'repeat', body });
|
|
42
|
+
} else if (m[2] !== undefined && m[2].trim() !== '') {
|
|
43
|
+
out.push(parseTerm(m[2], src));
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
if (out.length === 0) throw syntax(src, 'empty');
|
|
47
|
+
if (out.filter((t) => t.kind === 'repeat').length > 1) throw syntax(src, 'at most one repeat');
|
|
48
|
+
return out;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const sizeOf = (terms: readonly SplitTerm[]): number =>
|
|
52
|
+
terms.reduce((s, t) => s + (t.kind === 'fixed' ? t.size : 0), 0);
|
|
53
|
+
|
|
54
|
+
/** Lay a parsed split across `length` metres. Repeats take whole copies; stretches share the rest. */
|
|
55
|
+
export function layoutSplit(terms: readonly SplitTerm[], length: number): Slot[] {
|
|
56
|
+
const fixed = sizeOf(terms);
|
|
57
|
+
const repeat = terms.find((t) => t.kind === 'repeat');
|
|
58
|
+
const unit = repeat?.kind === 'repeat' ? sizeOf(repeat.body) : 0;
|
|
59
|
+
if (fixed > length + 1e-9) {
|
|
60
|
+
throw new Ts23dError('T3D_GRAMMAR_OVERFLOW', {
|
|
61
|
+
cause: `fixed terms need ${fixed} m but the strip is ${length} m`,
|
|
62
|
+
fix: 'shrink a fixed term, or turn one into a stretch (`sym:~`)',
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
const copies = unit > 0 ? Math.floor((length - fixed) / unit) : 0;
|
|
66
|
+
const weights = terms.reduce((s, t) => s + (t.kind === 'stretch' ? t.weight : 0), 0);
|
|
67
|
+
const leftover = length - fixed - copies * unit;
|
|
68
|
+
// No stretch term: the repeat absorbs the leftover by widening each copy evenly.
|
|
69
|
+
const grow = weights === 0 && copies > 0 ? (copies * unit + leftover) / (copies * unit) : 1;
|
|
70
|
+
const slots: Slot[] = [];
|
|
71
|
+
let x = 0;
|
|
72
|
+
const push = (symbol: string, size: number): void => {
|
|
73
|
+
slots.push({ symbol, start: x, size });
|
|
74
|
+
x += size;
|
|
75
|
+
};
|
|
76
|
+
for (const t of terms) {
|
|
77
|
+
if (t.kind === 'fixed') push(t.symbol, t.size);
|
|
78
|
+
else if (t.kind === 'stretch')
|
|
79
|
+
push(t.symbol, weights > 0 ? (leftover * t.weight) / weights : 0);
|
|
80
|
+
else
|
|
81
|
+
for (let i = 0; i < copies; i++)
|
|
82
|
+
for (const b of t.body) if (b.kind === 'fixed') push(b.symbol, b.size * grow);
|
|
83
|
+
}
|
|
84
|
+
return slots;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export const split = (src: string, length: number): Slot[] => layoutSplit(parseSplit(src), length);
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// Whyte notation ("4-4-0", "2-6-2") → wheel groups. Domain-native params: the loco kind reads it.
|
|
2
|
+
|
|
3
|
+
import { Ts23dError } from '../core/index.ts';
|
|
4
|
+
|
|
5
|
+
export interface WheelGroup {
|
|
6
|
+
readonly role: 'leading' | 'driving' | 'trailing';
|
|
7
|
+
readonly count: number;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function parseWhyte(arrangement: string): WheelGroup[] {
|
|
11
|
+
const parts = arrangement.split('-').map(Number);
|
|
12
|
+
if (parts.length !== 3 || parts.some((n) => !Number.isInteger(n) || n < 0 || n % 2 !== 0)) {
|
|
13
|
+
throw new Ts23dError('T3D_GRAMMAR_SYNTAX', {
|
|
14
|
+
cause: `'${arrangement}' is not a rigid-frame Whyte arrangement`,
|
|
15
|
+
fix: "use three even numbers: leading-driving-trailing, e.g. '4-4-0' or '2-8-2'",
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
const roles = ['leading', 'driving', 'trailing'] as const;
|
|
19
|
+
return roles.map((role, i) => ({ role, count: parts[i] ?? 0 }));
|
|
20
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// @developerz.ai/ts23d — the whole DSL from one import. Subpaths (`/core`, `/grammar`, …) exist for
|
|
2
|
+
// tree-shaking and for reading one layer at a time.
|
|
3
|
+
|
|
4
|
+
export { type AssetManifest, manifestOf } from './cli/manifest.ts';
|
|
5
|
+
export * from './core/index.ts';
|
|
6
|
+
export * from './export/index.ts';
|
|
7
|
+
export * from './grammar/index.ts';
|
|
8
|
+
export * from './kinds/index.ts';
|
|
9
|
+
export * from './material/index.ts';
|
|
10
|
+
export * from './neural/index.ts';
|
|
11
|
+
export * from './passes/index.ts';
|
|
12
|
+
export * from './primitives/index.ts';
|
|
13
|
+
export * from './qa/index.ts';
|
|
14
|
+
export * from './render/index.ts';
|