@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,1575 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Ts23dError2,
|
|
3
|
+
notImplemented2,
|
|
4
|
+
compile2,
|
|
5
|
+
encodePng2,
|
|
6
|
+
sub2,
|
|
7
|
+
dot2,
|
|
8
|
+
cross2,
|
|
9
|
+
normalize2,
|
|
10
|
+
worldTransforms2,
|
|
11
|
+
toWorld2,
|
|
12
|
+
rotateZ2,
|
|
13
|
+
worldMeshes2
|
|
14
|
+
} from "./chunk-4ddzsqtc.js";
|
|
15
|
+
import {
|
|
16
|
+
DEFAULT_SHEET2,
|
|
17
|
+
paintTrimSheet2
|
|
18
|
+
} from "./chunk-kvcep7ft.js";
|
|
19
|
+
|
|
20
|
+
// src/render/camera.ts
|
|
21
|
+
function basis(azDeg, elDeg) {
|
|
22
|
+
const az = azDeg * Math.PI / 180;
|
|
23
|
+
const el = elDeg * Math.PI / 180;
|
|
24
|
+
const dir = [Math.cos(el) * Math.cos(az), Math.cos(el) * Math.sin(az), Math.sin(el)];
|
|
25
|
+
const f = [-dir[0], -dir[1], -dir[2]];
|
|
26
|
+
const rl = Math.hypot(f[1], f[0]) || 1;
|
|
27
|
+
const r = [f[1] / rl, -f[0] / rl, 0];
|
|
28
|
+
const u = [r[1] * f[2] - r[2] * f[1], r[2] * f[0] - r[0] * f[2], r[0] * f[1] - r[1] * f[0]];
|
|
29
|
+
return { dir, r, u, f };
|
|
30
|
+
}
|
|
31
|
+
function project(c, x, y, z) {
|
|
32
|
+
const dx = x - c.eye[0];
|
|
33
|
+
const dy = y - c.eye[1];
|
|
34
|
+
const dz = z - c.eye[2];
|
|
35
|
+
const w = dx * c.f[0] + dy * c.f[1] + dz * c.f[2];
|
|
36
|
+
const sx = dx * c.r[0] + dy * c.r[1] + dz * c.r[2];
|
|
37
|
+
const sy = dx * c.u[0] + dy * c.u[1] + dz * c.u[2];
|
|
38
|
+
return [c.cx + sx / w * c.focal, c.cy - sy / w * c.focal, w];
|
|
39
|
+
}
|
|
40
|
+
function frameOrbit(points, o, width, height) {
|
|
41
|
+
const { dir, r, u, f } = basis(o.azDeg, o.elDeg);
|
|
42
|
+
const stride = Math.max(1, Math.floor(points.length / 3 / 20000)) * 3;
|
|
43
|
+
let lo = [Infinity, Infinity, Infinity];
|
|
44
|
+
let hi = [-Infinity, -Infinity, -Infinity];
|
|
45
|
+
for (let i = 0;i < points.length; i += 3)
|
|
46
|
+
for (let k = 0;k < 3; k++) {
|
|
47
|
+
const v = points[i + k] ?? 0;
|
|
48
|
+
lo[k] = Math.min(lo[k] ?? 0, v);
|
|
49
|
+
hi[k] = Math.max(hi[k] ?? 0, v);
|
|
50
|
+
}
|
|
51
|
+
if (points.length === 0) {
|
|
52
|
+
lo = [-0.5, -0.5, -0.5];
|
|
53
|
+
hi = [0.5, 0.5, 0.5];
|
|
54
|
+
}
|
|
55
|
+
const target = [(lo[0] + hi[0]) / 2, (lo[1] + hi[1]) / 2, (lo[2] + hi[2]) / 2];
|
|
56
|
+
const radius = Math.max(Math.hypot(hi[0] - lo[0], hi[1] - lo[1], hi[2] - lo[2]) / 2, 0.05);
|
|
57
|
+
const tanY = Math.tan(o.fovDeg * Math.PI / 360);
|
|
58
|
+
const focal = height / 2 / tanY;
|
|
59
|
+
let dist = radius / Math.sin(Math.atan(tanY)) + radius;
|
|
60
|
+
const at = (d) => ({
|
|
61
|
+
eye: [target[0] + dir[0] * d, target[1] + dir[1] * d, target[2] + dir[2] * d],
|
|
62
|
+
r,
|
|
63
|
+
u,
|
|
64
|
+
f,
|
|
65
|
+
focal,
|
|
66
|
+
cx: width / 2,
|
|
67
|
+
cy: height / 2,
|
|
68
|
+
width,
|
|
69
|
+
height
|
|
70
|
+
});
|
|
71
|
+
for (let iter = 0;iter < 6; iter++) {
|
|
72
|
+
const cam = at(dist);
|
|
73
|
+
let x0 = Infinity;
|
|
74
|
+
let x1 = -Infinity;
|
|
75
|
+
let y0 = Infinity;
|
|
76
|
+
let y1 = -Infinity;
|
|
77
|
+
for (let i = 0;i < points.length; i += stride) {
|
|
78
|
+
const p = project(cam, points[i] ?? 0, points[i + 1] ?? 0, points[i + 2] ?? 0);
|
|
79
|
+
x0 = Math.min(x0, p[0]);
|
|
80
|
+
x1 = Math.max(x1, p[0]);
|
|
81
|
+
y0 = Math.min(y0, p[1]);
|
|
82
|
+
y1 = Math.max(y1, p[1]);
|
|
83
|
+
}
|
|
84
|
+
if (!Number.isFinite(x0))
|
|
85
|
+
break;
|
|
86
|
+
const k = dist / focal;
|
|
87
|
+
const ox = ((x0 + x1) / 2 - width / 2) * k;
|
|
88
|
+
const oy = -((y0 + y1) / 2 - height / 2) * k;
|
|
89
|
+
for (let a = 0;a < 3; a++)
|
|
90
|
+
target[a] = (target[a] ?? 0) + (r[a] ?? 0) * ox + (u[a] ?? 0) * oy;
|
|
91
|
+
const need = Math.max((x1 - x0) / (width * o.fill), (y1 - y0) / (height * o.fill));
|
|
92
|
+
dist = Math.max(dist * need ** 0.85, radius * 1.05);
|
|
93
|
+
}
|
|
94
|
+
return at(dist);
|
|
95
|
+
}
|
|
96
|
+
function zoomWindow(c, nx, ny, k) {
|
|
97
|
+
return {
|
|
98
|
+
...c,
|
|
99
|
+
focal: c.focal * k,
|
|
100
|
+
cx: c.width / 2 - (nx * c.width - c.cx) * k,
|
|
101
|
+
cy: c.height / 2 - (ny * c.height - c.cy) * k
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
function scaled(c, s) {
|
|
105
|
+
return {
|
|
106
|
+
...c,
|
|
107
|
+
focal: c.focal * s,
|
|
108
|
+
cx: c.cx * s,
|
|
109
|
+
cy: c.cy * s,
|
|
110
|
+
width: c.width * s,
|
|
111
|
+
height: c.height * s
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// src/render/depth-map.ts
|
|
116
|
+
function mapAxes(dir) {
|
|
117
|
+
const up = Math.abs(dir[2]) > 0.99 ? [1, 0, 0] : [0, 0, 1];
|
|
118
|
+
let r = [
|
|
119
|
+
up[1] * dir[2] - up[2] * dir[1],
|
|
120
|
+
up[2] * dir[0] - up[0] * dir[2],
|
|
121
|
+
up[0] * dir[1] - up[1] * dir[0]
|
|
122
|
+
];
|
|
123
|
+
const l = Math.hypot(r[0], r[1], r[2]) || 1;
|
|
124
|
+
r = [r[0] / l, r[1] / l, r[2] / l];
|
|
125
|
+
const u = [
|
|
126
|
+
dir[1] * r[2] - dir[2] * r[1],
|
|
127
|
+
dir[2] * r[0] - dir[0] * r[2],
|
|
128
|
+
dir[0] * r[1] - dir[1] * r[0]
|
|
129
|
+
];
|
|
130
|
+
return { r, u };
|
|
131
|
+
}
|
|
132
|
+
function fitFrame(s, dir) {
|
|
133
|
+
const { r, u } = mapAxes(dir);
|
|
134
|
+
let r0 = Infinity;
|
|
135
|
+
let r1 = -Infinity;
|
|
136
|
+
let u0 = Infinity;
|
|
137
|
+
let u1 = -Infinity;
|
|
138
|
+
const P = s.positions;
|
|
139
|
+
for (let i = 0;i < P.length; i += 3) {
|
|
140
|
+
const x = P[i] ?? 0;
|
|
141
|
+
const y = P[i + 1] ?? 0;
|
|
142
|
+
const z = P[i + 2] ?? 0;
|
|
143
|
+
const a = x * r[0] + y * r[1] + z * r[2];
|
|
144
|
+
const b = x * u[0] + y * u[1] + z * u[2];
|
|
145
|
+
if (a < r0)
|
|
146
|
+
r0 = a;
|
|
147
|
+
if (a > r1)
|
|
148
|
+
r1 = a;
|
|
149
|
+
if (b < u0)
|
|
150
|
+
u0 = b;
|
|
151
|
+
if (b > u1)
|
|
152
|
+
u1 = b;
|
|
153
|
+
}
|
|
154
|
+
if (!Number.isFinite(r0))
|
|
155
|
+
return { cr: 0, cu: 0, half: 1 };
|
|
156
|
+
return { cr: (r0 + r1) / 2, cu: (u0 + u1) / 2, half: Math.max(r1 - r0, u1 - u0, 0.001) * 0.51 };
|
|
157
|
+
}
|
|
158
|
+
function depthMap(s, dir, size, frame = fitFrame(s, dir), alpha) {
|
|
159
|
+
const { r, u } = mapAxes(dir);
|
|
160
|
+
const k = size / (2 * frame.half);
|
|
161
|
+
const r0 = frame.cr - frame.half;
|
|
162
|
+
const u1 = frame.cu + frame.half;
|
|
163
|
+
const data = new Float32Array(size * size).fill(-Infinity);
|
|
164
|
+
const nv = s.positions.length / 3;
|
|
165
|
+
const X = new Float32Array(nv);
|
|
166
|
+
const Y = new Float32Array(nv);
|
|
167
|
+
const D = new Float32Array(nv);
|
|
168
|
+
const P = s.positions;
|
|
169
|
+
for (let v = 0;v < nv; v++) {
|
|
170
|
+
const x = P[v * 3] ?? 0;
|
|
171
|
+
const y = P[v * 3 + 1] ?? 0;
|
|
172
|
+
const z = P[v * 3 + 2] ?? 0;
|
|
173
|
+
X[v] = (x * r[0] + y * r[1] + z * r[2] - r0) * k;
|
|
174
|
+
Y[v] = (u1 - (x * u[0] + y * u[1] + z * u[2])) * k;
|
|
175
|
+
D[v] = x * dir[0] + y * dir[1] + z * dir[2];
|
|
176
|
+
}
|
|
177
|
+
const I = s.indices;
|
|
178
|
+
for (let t = 0;t < s.triangles; t++) {
|
|
179
|
+
const a = I[t * 3] ?? 0;
|
|
180
|
+
const b = I[t * 3 + 1] ?? 0;
|
|
181
|
+
const c = I[t * 3 + 2] ?? 0;
|
|
182
|
+
const ax = X[a] ?? 0;
|
|
183
|
+
const ay = Y[a] ?? 0;
|
|
184
|
+
const bx = X[b] ?? 0;
|
|
185
|
+
const by = Y[b] ?? 0;
|
|
186
|
+
const cx = X[c] ?? 0;
|
|
187
|
+
const cy = Y[c] ?? 0;
|
|
188
|
+
const area = (bx - ax) * (cy - ay) - (by - ay) * (cx - ax);
|
|
189
|
+
if (Math.abs(area) < 0.000000000001)
|
|
190
|
+
continue;
|
|
191
|
+
const inv = 1 / area;
|
|
192
|
+
const da = D[a] ?? 0;
|
|
193
|
+
const db = D[b] ?? 0;
|
|
194
|
+
const dc = D[c] ?? 0;
|
|
195
|
+
const cut = alpha !== undefined && s.surface[t] === 1;
|
|
196
|
+
const lx = ax < bx ? Math.min(ax, cx) : Math.min(bx, cx);
|
|
197
|
+
const hx = ax > bx ? Math.max(ax, cx) : Math.max(bx, cx);
|
|
198
|
+
const ly = ay < by ? Math.min(ay, cy) : Math.min(by, cy);
|
|
199
|
+
const hy = ay > by ? Math.max(ay, cy) : Math.max(by, cy);
|
|
200
|
+
const minX = lx < 0 ? 0 : Math.floor(lx);
|
|
201
|
+
const maxX = hx > size - 1 ? size - 1 : Math.ceil(hx);
|
|
202
|
+
const minY = ly < 0 ? 0 : Math.floor(ly);
|
|
203
|
+
const maxY = hy > size - 1 ? size - 1 : Math.ceil(hy);
|
|
204
|
+
const A0 = (by - cy) * inv;
|
|
205
|
+
const B0 = (cx - bx) * inv;
|
|
206
|
+
const A1 = (cy - ay) * inv;
|
|
207
|
+
const B1 = (ax - cx) * inv;
|
|
208
|
+
const C0 = (bx * cy - by * cx) * inv;
|
|
209
|
+
const C1 = (cx * ay - cy * ax) * inv;
|
|
210
|
+
for (let y = minY;y <= maxY; y++) {
|
|
211
|
+
const py = y + 0.5;
|
|
212
|
+
let w0 = C0 + A0 * (minX + 0.5) + B0 * py;
|
|
213
|
+
let w1 = C1 + A1 * (minX + 0.5) + B1 * py;
|
|
214
|
+
for (let x = minX;x <= maxX; x++, w0 += A0, w1 += A1) {
|
|
215
|
+
if (w0 < 0 || w1 < 0 || w0 + w1 > 1)
|
|
216
|
+
continue;
|
|
217
|
+
const d = w0 * da + w1 * db + (1 - w0 - w1) * dc;
|
|
218
|
+
const i = y * size + x;
|
|
219
|
+
if (d <= (data[i] ?? 0))
|
|
220
|
+
continue;
|
|
221
|
+
if (cut && alpha && !alpha(t, w1, 1 - w0 - w1))
|
|
222
|
+
continue;
|
|
223
|
+
data[i] = d;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
return { size, data, dir, r, u, r0, u1, k };
|
|
228
|
+
}
|
|
229
|
+
function visibility(m, x, y, z, bias, taps) {
|
|
230
|
+
const tx = (x * m.r[0] + y * m.r[1] + z * m.r[2] - m.r0) * m.k;
|
|
231
|
+
const ty = (m.u1 - (x * m.u[0] + y * m.u[1] + z * m.u[2])) * m.k;
|
|
232
|
+
const d = x * m.dir[0] + y * m.dir[1] + z * m.dir[2] + bias;
|
|
233
|
+
const s = m.size;
|
|
234
|
+
let lit = 0;
|
|
235
|
+
for (let i = 0;i < taps.length; i += 2) {
|
|
236
|
+
const px = Math.floor(tx + (taps[i] ?? 0));
|
|
237
|
+
const py = Math.floor(ty + (taps[i + 1] ?? 0));
|
|
238
|
+
if (px < 0 || py < 0 || px >= s || py >= s || d >= (m.data[py * s + px] ?? 0))
|
|
239
|
+
lit++;
|
|
240
|
+
}
|
|
241
|
+
return lit * 2 / taps.length;
|
|
242
|
+
}
|
|
243
|
+
function openness(m, x, y, z, bias, reach) {
|
|
244
|
+
const px = Math.floor((x * m.r[0] + y * m.r[1] + z * m.r[2] - m.r0) * m.k);
|
|
245
|
+
const py = Math.floor((m.u1 - (x * m.u[0] + y * m.u[1] + z * m.u[2])) * m.k);
|
|
246
|
+
const s = m.size;
|
|
247
|
+
if (px < 0 || py < 0 || px >= s || py >= s)
|
|
248
|
+
return 1;
|
|
249
|
+
const gap = (m.data[py * s + px] ?? 0) - (x * m.dir[0] + y * m.dir[1] + z * m.dir[2] + bias);
|
|
250
|
+
return gap <= 0 ? 1 : gap >= reach ? 1 : gap / reach;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// src/render/texture.ts
|
|
254
|
+
var SRGB = new Float32Array(256).map((_, i) => {
|
|
255
|
+
const v = i / 255;
|
|
256
|
+
return v <= 0.04045 ? v / 12.92 : ((v + 0.055) / 1.055) ** 2.4;
|
|
257
|
+
});
|
|
258
|
+
function chain(src, size, decode) {
|
|
259
|
+
const top = new Float32Array(size * size * 4);
|
|
260
|
+
for (let i = 0;i < top.length; i++)
|
|
261
|
+
top[i] = decode(i & 3, src[i] ?? 0);
|
|
262
|
+
const out = [{ size, data: top }];
|
|
263
|
+
let cur = out[0];
|
|
264
|
+
while (cur && cur.size > 4) {
|
|
265
|
+
const s = cur.size / 2;
|
|
266
|
+
const d = new Float32Array(s * s * 4);
|
|
267
|
+
const p = cur.data;
|
|
268
|
+
const w = cur.size;
|
|
269
|
+
for (let y = 0;y < s; y++)
|
|
270
|
+
for (let x = 0;x < s; x++)
|
|
271
|
+
for (let c = 0;c < 4; c++) {
|
|
272
|
+
const a = (2 * y * w + 2 * x << 2) + c;
|
|
273
|
+
const b = a + w * 4;
|
|
274
|
+
d[(y * s + x << 2) + c] = ((p[a] ?? 0) + (p[a + 4] ?? 0) + (p[b] ?? 0) + (p[b + 4] ?? 0)) * 0.25;
|
|
275
|
+
}
|
|
276
|
+
cur = { size: s, data: d };
|
|
277
|
+
out.push(cur);
|
|
278
|
+
}
|
|
279
|
+
return out;
|
|
280
|
+
}
|
|
281
|
+
var cache = new WeakMap;
|
|
282
|
+
function sheetTextures(sheet) {
|
|
283
|
+
const hit = cache.get(sheet);
|
|
284
|
+
if (hit)
|
|
285
|
+
return hit;
|
|
286
|
+
const t = {
|
|
287
|
+
size: sheet.size,
|
|
288
|
+
albedo: chain(sheet.albedo, sheet.size, (c, v) => c === 3 ? v / 255 : SRGB[v] ?? 0),
|
|
289
|
+
normal: chain(sheet.normal, sheet.size, (c, v) => c === 3 ? 1 : v / 127.5 - 1),
|
|
290
|
+
orm: chain(sheet.orm, sheet.size, (_c, v) => v / 255)
|
|
291
|
+
};
|
|
292
|
+
cache.set(sheet, t);
|
|
293
|
+
return t;
|
|
294
|
+
}
|
|
295
|
+
function sample(mips, level, u, v, out) {
|
|
296
|
+
const m = mips[Math.min(mips.length - 1, Math.max(0, level))] ?? mips[0];
|
|
297
|
+
if (!m)
|
|
298
|
+
return;
|
|
299
|
+
const s = m.size;
|
|
300
|
+
const x = u * s - 0.5;
|
|
301
|
+
const y = Math.min(s - 1, Math.max(0, v * s - 0.5));
|
|
302
|
+
const xf = Math.floor(x);
|
|
303
|
+
const yf = Math.floor(y);
|
|
304
|
+
const fx = x - xf;
|
|
305
|
+
const fy = y - yf;
|
|
306
|
+
const x0 = (xf % s + s) % s;
|
|
307
|
+
const x1 = (x0 + 1) % s;
|
|
308
|
+
const y0 = yf;
|
|
309
|
+
const y1 = Math.min(s - 1, yf + 1);
|
|
310
|
+
const d = m.data;
|
|
311
|
+
const a = y0 * s + x0 << 2;
|
|
312
|
+
const b = y0 * s + x1 << 2;
|
|
313
|
+
const c = y1 * s + x0 << 2;
|
|
314
|
+
const e = y1 * s + x1 << 2;
|
|
315
|
+
const w0 = (1 - fx) * (1 - fy);
|
|
316
|
+
const w1 = fx * (1 - fy);
|
|
317
|
+
const w2 = (1 - fx) * fy;
|
|
318
|
+
const w3 = fx * fy;
|
|
319
|
+
for (let k = 0;k < 4; k++)
|
|
320
|
+
out[k] = (d[a + k] ?? 0) * w0 + (d[b + k] ?? 0) * w1 + (d[c + k] ?? 0) * w2 + (d[e + k] ?? 0) * w3;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// src/render/shade.ts
|
|
324
|
+
var ONE_TAP = new Float32Array([0, 0]);
|
|
325
|
+
var hyp = (x, y, z) => Math.sqrt(x * x + y * y + z * z);
|
|
326
|
+
var texel = new Float32Array(4);
|
|
327
|
+
function skyOpen(c, p, n) {
|
|
328
|
+
let open = 0;
|
|
329
|
+
let total = 0;
|
|
330
|
+
const dirs = c.rig.skyDirs;
|
|
331
|
+
for (let k = 0;k < dirs.length; k++) {
|
|
332
|
+
const d = dirs[k];
|
|
333
|
+
const m = c.aoMaps[k];
|
|
334
|
+
if (!d || !m)
|
|
335
|
+
continue;
|
|
336
|
+
const w = 0.5 + 0.5 * ((n[0] ?? 0) * d[0] + (n[1] ?? 0) * d[1] + (n[2] ?? 0) * d[2]);
|
|
337
|
+
if (w <= 0)
|
|
338
|
+
continue;
|
|
339
|
+
const off = 1.5 / m.k;
|
|
340
|
+
const v = openness(m, (p[0] ?? 0) + (n[0] ?? 0) * off, (p[1] ?? 0) + (n[1] ?? 0) * off, (p[2] ?? 0) + (n[2] ?? 0) * off, 2 / m.k, c.reach);
|
|
341
|
+
open += w * v;
|
|
342
|
+
total += w;
|
|
343
|
+
}
|
|
344
|
+
return total > 0 ? open / total : 1;
|
|
345
|
+
}
|
|
346
|
+
function sunVisible(c, p, n, set) {
|
|
347
|
+
const m = c.sunMap;
|
|
348
|
+
const off = 1.2 / m.k;
|
|
349
|
+
return visibility(m, (p[0] ?? 0) + (n[0] ?? 0) * off, (p[1] ?? 0) + (n[1] ?? 0) * off, (p[2] ?? 0) + (n[2] ?? 0) * off, 1.5 / m.k, c.pcf[set & 3] ?? ONE_TAP);
|
|
350
|
+
}
|
|
351
|
+
var pos = [0, 0, 0];
|
|
352
|
+
var nrm = [0, 0, 0];
|
|
353
|
+
var geo = [0, 0, 0];
|
|
354
|
+
function shadeSample(c, t, b1, b2, mip, set, out) {
|
|
355
|
+
const s = c.scene;
|
|
356
|
+
const ia = (s.indices[t * 3] ?? 0) * 3;
|
|
357
|
+
const ib = (s.indices[t * 3 + 1] ?? 0) * 3;
|
|
358
|
+
const ic = (s.indices[t * 3 + 2] ?? 0) * 3;
|
|
359
|
+
const b0 = 1 - b1 - b2;
|
|
360
|
+
const P = s.positions;
|
|
361
|
+
const N = s.normals;
|
|
362
|
+
const C = s.colors;
|
|
363
|
+
for (let k = 0;k < 3; k++) {
|
|
364
|
+
pos[k] = (P[ia + k] ?? 0) * b0 + (P[ib + k] ?? 0) * b1 + (P[ic + k] ?? 0) * b2;
|
|
365
|
+
nrm[k] = (N[ia + k] ?? 0) * b0 + (N[ib + k] ?? 0) * b1 + (N[ic + k] ?? 0) * b2;
|
|
366
|
+
}
|
|
367
|
+
const ua = ia / 3 * 2;
|
|
368
|
+
const ub = ib / 3 * 2;
|
|
369
|
+
const uc = ic / 3 * 2;
|
|
370
|
+
const U = s.uvs;
|
|
371
|
+
const u = (U[ua] ?? 0) * b0 + (U[ub] ?? 0) * b1 + (U[uc] ?? 0) * b2;
|
|
372
|
+
const v = (U[ua + 1] ?? 0) * b0 + (U[ub + 1] ?? 0) * b1 + (U[uc + 1] ?? 0) * b2;
|
|
373
|
+
const e = c.cam.eye;
|
|
374
|
+
let vx = e[0] - (pos[0] ?? 0);
|
|
375
|
+
let vy = e[1] - (pos[1] ?? 0);
|
|
376
|
+
let vz = e[2] - (pos[2] ?? 0);
|
|
377
|
+
const vl = hyp(vx, vy, vz) || 1;
|
|
378
|
+
vx /= vl;
|
|
379
|
+
vy /= vl;
|
|
380
|
+
vz /= vl;
|
|
381
|
+
let nl = hyp(nrm[0] ?? 0, nrm[1] ?? 0, nrm[2] ?? 1) || 1;
|
|
382
|
+
if ((nrm[0] ?? 0) * vx + (nrm[1] ?? 0) * vy + (nrm[2] ?? 0) * vz < 0)
|
|
383
|
+
nl = -nl;
|
|
384
|
+
for (let k = 0;k < 3; k++) {
|
|
385
|
+
nrm[k] = (nrm[k] ?? 0) / nl;
|
|
386
|
+
geo[k] = nrm[k] ?? 0;
|
|
387
|
+
}
|
|
388
|
+
const T = s.tangents;
|
|
389
|
+
const to = t * 6;
|
|
390
|
+
const tx = T[to] ?? 0;
|
|
391
|
+
const ty = T[to + 1] ?? 0;
|
|
392
|
+
const tz = T[to + 2] ?? 0;
|
|
393
|
+
if (tx !== 0 || ty !== 0 || tz !== 0) {
|
|
394
|
+
sample(c.tex.normal, mip, u, v, texel);
|
|
395
|
+
const mx = texel[0] ?? 0;
|
|
396
|
+
const my = texel[1] ?? 0;
|
|
397
|
+
const mz = texel[2] ?? 1;
|
|
398
|
+
const n0 = nrm[0] ?? 0;
|
|
399
|
+
const n1 = nrm[1] ?? 0;
|
|
400
|
+
const n2 = nrm[2] ?? 0;
|
|
401
|
+
const bx = T[to + 3] ?? 0;
|
|
402
|
+
const by = T[to + 4] ?? 0;
|
|
403
|
+
const bz = T[to + 5] ?? 0;
|
|
404
|
+
const px = tx * mx + bx * my + n0 * mz;
|
|
405
|
+
const py = ty * mx + by * my + n1 * mz;
|
|
406
|
+
const pz = tz * mx + bz * my + n2 * mz;
|
|
407
|
+
const pl = hyp(px, py, pz) || 1;
|
|
408
|
+
nrm[0] = px / pl;
|
|
409
|
+
nrm[1] = py / pl;
|
|
410
|
+
nrm[2] = pz / pl;
|
|
411
|
+
}
|
|
412
|
+
sample(c.tex.albedo, mip, u, v, texel);
|
|
413
|
+
const surf = s.surface[t] ?? 0;
|
|
414
|
+
const glass = surf === 2;
|
|
415
|
+
let ar = (texel[0] ?? 1) * ((C[ia] ?? 1) * b0 + (C[ib] ?? 1) * b1 + (C[ic] ?? 1) * b2);
|
|
416
|
+
let ag = (texel[1] ?? 1) * ((C[ia + 1] ?? 1) * b0 + (C[ib + 1] ?? 1) * b1 + (C[ic + 1] ?? 1) * b2);
|
|
417
|
+
let ab = (texel[2] ?? 1) * ((C[ia + 2] ?? 1) * b0 + (C[ib + 2] ?? 1) * b1 + (C[ic + 2] ?? 1) * b2);
|
|
418
|
+
sample(c.tex.orm, mip, u, v, texel);
|
|
419
|
+
const occ = texel[0] ?? 1;
|
|
420
|
+
const rough = glass ? 0.06 : Math.max(0.08, texel[1] ?? 0.8);
|
|
421
|
+
const metal = glass ? 0 : texel[2] ?? 0;
|
|
422
|
+
if (glass) {
|
|
423
|
+
ar *= 0.12;
|
|
424
|
+
ag *= 0.14;
|
|
425
|
+
ab *= 0.16;
|
|
426
|
+
}
|
|
427
|
+
const r = c.rig;
|
|
428
|
+
const n0 = nrm[0] ?? 0;
|
|
429
|
+
const n1 = nrm[1] ?? 0;
|
|
430
|
+
const n2 = nrm[2] ?? 1;
|
|
431
|
+
const ndl = n0 * r.sun[0] + n1 * r.sun[1] + n2 * r.sun[2];
|
|
432
|
+
const gdl = (geo[0] ?? 0) * r.sun[0] + (geo[1] ?? 0) * r.sun[1] + (geo[2] ?? 0) * r.sun[2];
|
|
433
|
+
const lit = ndl > 0 && gdl > -0.05 ? sunVisible(c, pos, geo, set) * ndl : 0;
|
|
434
|
+
const open = skyOpen(c, pos, geo);
|
|
435
|
+
const hemi = 0.5 + 0.5 * n2;
|
|
436
|
+
const amb = open * (0.35 + 0.65 * occ);
|
|
437
|
+
const dk = 1 - metal;
|
|
438
|
+
let hx = r.sun[0] + vx;
|
|
439
|
+
let hy = r.sun[1] + vy;
|
|
440
|
+
let hz = r.sun[2] + vz;
|
|
441
|
+
const hl = hyp(hx, hy, hz) || 1;
|
|
442
|
+
hx /= hl;
|
|
443
|
+
hy /= hl;
|
|
444
|
+
hz /= hl;
|
|
445
|
+
const ndh = Math.max(0, n0 * hx + n1 * hy + n2 * hz);
|
|
446
|
+
const vdh = Math.max(0, vx * hx + vy * hy + vz * hz);
|
|
447
|
+
const ndv = Math.max(0, n0 * vx + n1 * vy + n2 * vz);
|
|
448
|
+
const shin = Math.min(2048, 2 / (rough * rough * rough * rough) - 2);
|
|
449
|
+
const lobe = (shin + 8) / 25.13 * ndh ** shin;
|
|
450
|
+
const fr = (1 - vdh) ** 5;
|
|
451
|
+
const fe = (1 - ndv) ** 5 * (1 - rough) * (1 - rough);
|
|
452
|
+
const ch = [ar, ag, ab];
|
|
453
|
+
for (let k = 0;k < 3; k++) {
|
|
454
|
+
const al = ch[k] ?? 0;
|
|
455
|
+
const f0 = 0.04 * dk + al * metal;
|
|
456
|
+
const sky = (r.bounce[k] ?? 0) * (1 - hemi) + (r.sky[k] ?? 0) * hemi;
|
|
457
|
+
const diffuse = al * dk * ((r.sunColor[k] ?? 0) * lit + sky * amb);
|
|
458
|
+
const spec = (f0 + (1 - f0) * fr) * lobe * lit * (r.sunColor[k] ?? 0) * 0.8;
|
|
459
|
+
const env = (f0 + (1 - f0) * fe) * (r.sky[k] ?? 0) * open * (glass ? 2.5 : 0.3 + 0.7 * (1 - rough));
|
|
460
|
+
out[k] = (diffuse + spec + env) * r.exposure;
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// src/render/ground.ts
|
|
465
|
+
var UP = [0, 0, 1];
|
|
466
|
+
function floorGrid(c, size) {
|
|
467
|
+
const s = c.scene;
|
|
468
|
+
const z = s.min[2];
|
|
469
|
+
const h = s.max[2] - s.min[2];
|
|
470
|
+
const half = Math.max(s.max[0] - s.min[0], s.max[1] - s.min[1]) / 2 + Math.max(h * 1.4, 0.2);
|
|
471
|
+
const cx = (s.min[0] + s.max[0]) / 2;
|
|
472
|
+
const cy = (s.min[1] + s.max[1]) / 2;
|
|
473
|
+
const cell = 2 * half / size;
|
|
474
|
+
const light = new Float32Array(size * size);
|
|
475
|
+
const sunUp = Math.max(0, c.rig.sun[2]);
|
|
476
|
+
const sunW = sunUp * (c.rig.sunColor[1] ?? 1);
|
|
477
|
+
const skyW = c.rig.sky[1] ?? 1;
|
|
478
|
+
const p = [0, 0, z];
|
|
479
|
+
for (let j = 0;j < size; j++)
|
|
480
|
+
for (let i = 0;i < size; i++) {
|
|
481
|
+
p[0] = cx - half + (i + 0.5) * cell;
|
|
482
|
+
p[1] = cy - half + (j + 0.5) * cell;
|
|
483
|
+
const sun = sunVisible(c, p, UP, i + j * 2 & 3);
|
|
484
|
+
const open = skyOpen(c, p, UP);
|
|
485
|
+
light[j * size + i] = (sunW * sun * (0.55 + 0.45 * open) + skyW * open) / (sunW + skyW);
|
|
486
|
+
}
|
|
487
|
+
return { z, x0: cx - half, y0: cy - half, cell, size, light: blur(blur(light, size), size) };
|
|
488
|
+
}
|
|
489
|
+
function blur(src, n) {
|
|
490
|
+
const out = new Float32Array(src.length);
|
|
491
|
+
for (let y = 0;y < n; y++)
|
|
492
|
+
for (let x = 0;x < n; x++) {
|
|
493
|
+
let s = 0;
|
|
494
|
+
for (let dy = -1;dy <= 1; dy++)
|
|
495
|
+
for (let dx = -1;dx <= 1; dx++) {
|
|
496
|
+
const yy = Math.min(n - 1, Math.max(0, y + dy));
|
|
497
|
+
const xx = Math.min(n - 1, Math.max(0, x + dx));
|
|
498
|
+
s += src[yy * n + xx] ?? 1;
|
|
499
|
+
}
|
|
500
|
+
out[y * n + x] = s / 9;
|
|
501
|
+
}
|
|
502
|
+
return out;
|
|
503
|
+
}
|
|
504
|
+
function floorLight(g, cam, px, py) {
|
|
505
|
+
const sx = (px - cam.cx) / cam.focal;
|
|
506
|
+
const sy = -(py - cam.cy) / cam.focal;
|
|
507
|
+
const dx = cam.f[0] + cam.r[0] * sx + cam.u[0] * sy;
|
|
508
|
+
const dy = cam.f[1] + cam.r[1] * sx + cam.u[1] * sy;
|
|
509
|
+
const dz = cam.f[2] + cam.r[2] * sx + cam.u[2] * sy;
|
|
510
|
+
if (dz >= -0.000001)
|
|
511
|
+
return 1;
|
|
512
|
+
const t = (g.z - cam.eye[2]) / dz;
|
|
513
|
+
const gx = (cam.eye[0] + dx * t - g.x0) / g.cell - 0.5;
|
|
514
|
+
const gy = (cam.eye[1] + dy * t - g.y0) / g.cell - 0.5;
|
|
515
|
+
const ix = Math.floor(gx);
|
|
516
|
+
const iy = Math.floor(gy);
|
|
517
|
+
if (ix < 0 || iy < 0 || ix >= g.size - 1 || iy >= g.size - 1)
|
|
518
|
+
return 1;
|
|
519
|
+
const fx = gx - ix;
|
|
520
|
+
const fy = gy - iy;
|
|
521
|
+
const L = g.light;
|
|
522
|
+
const o = iy * g.size + ix;
|
|
523
|
+
return (L[o] ?? 1) * (1 - fx) * (1 - fy) + (L[o + 1] ?? 1) * fx * (1 - fy) + (L[o + g.size] ?? 1) * (1 - fx) * fy + (L[o + g.size + 1] ?? 1) * fx * fy;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
// src/render/lighting.ts
|
|
527
|
+
var unit = (az, el) => {
|
|
528
|
+
const a = az * Math.PI / 180;
|
|
529
|
+
const e = el * Math.PI / 180;
|
|
530
|
+
return [Math.cos(e) * Math.cos(a), Math.cos(e) * Math.sin(a), Math.sin(e)];
|
|
531
|
+
};
|
|
532
|
+
var RINGS = [
|
|
533
|
+
[14, 5],
|
|
534
|
+
[38, 4],
|
|
535
|
+
[64, 2],
|
|
536
|
+
[88, 1]
|
|
537
|
+
];
|
|
538
|
+
function studioRig(cameraAzDeg) {
|
|
539
|
+
const skyDirs = [];
|
|
540
|
+
RINGS.forEach(([el, n], ring) => {
|
|
541
|
+
for (let i = 0;i < n; i++)
|
|
542
|
+
skyDirs.push(unit(360 * (i + ring * 0.37) / n, el));
|
|
543
|
+
});
|
|
544
|
+
return {
|
|
545
|
+
sun: unit(cameraAzDeg - 50, 55),
|
|
546
|
+
sunColor: [0.95, 0.91, 0.84],
|
|
547
|
+
sky: [0.3, 0.33, 0.4],
|
|
548
|
+
bounce: [0.16, 0.15, 0.13],
|
|
549
|
+
skyDirs,
|
|
550
|
+
exposure: 1
|
|
551
|
+
};
|
|
552
|
+
}
|
|
553
|
+
var DISC = [
|
|
554
|
+
[-0.94, -0.4],
|
|
555
|
+
[0.95, -0.77],
|
|
556
|
+
[-0.09, -0.93],
|
|
557
|
+
[0.34, 0.29],
|
|
558
|
+
[-0.92, 0.46],
|
|
559
|
+
[-0.38, -0.06],
|
|
560
|
+
[0.79, 0.19],
|
|
561
|
+
[-0.24, 0.78],
|
|
562
|
+
[0.44, -0.3],
|
|
563
|
+
[0.55, 0.85],
|
|
564
|
+
[-0.66, -0.87],
|
|
565
|
+
[0.06, 0.37]
|
|
566
|
+
];
|
|
567
|
+
function pcfTaps(radius) {
|
|
568
|
+
return [0, 1, 2, 3].map((k) => {
|
|
569
|
+
const a = k * Math.PI / 8;
|
|
570
|
+
const c = Math.cos(a) * radius;
|
|
571
|
+
const s = Math.sin(a) * radius;
|
|
572
|
+
const out = new Float32Array(DISC.length * 2);
|
|
573
|
+
DISC.forEach(([x, y], i) => {
|
|
574
|
+
out[i * 2] = x * c - y * s;
|
|
575
|
+
out[i * 2 + 1] = x * s + y * c;
|
|
576
|
+
});
|
|
577
|
+
return out;
|
|
578
|
+
});
|
|
579
|
+
}
|
|
580
|
+
function tone(x) {
|
|
581
|
+
if (x <= 0)
|
|
582
|
+
return 0;
|
|
583
|
+
if (x <= 0.6)
|
|
584
|
+
return x;
|
|
585
|
+
return 0.6 + 0.4 * (1 - Math.exp(-(x - 0.6) / 0.4));
|
|
586
|
+
}
|
|
587
|
+
var toSrgb8 = (v) => Math.round(255 * (v <= 0.0031308 ? v * 12.92 : 1.055 * Math.min(1, v) ** (1 / 2.4) - 0.055));
|
|
588
|
+
var fromSrgb = (v) => v <= 0.04045 ? v / 12.92 : ((v + 0.055) / 1.055) ** 2.4;
|
|
589
|
+
|
|
590
|
+
// src/render/options.ts
|
|
591
|
+
function need(ok, cause, fix) {
|
|
592
|
+
if (!ok)
|
|
593
|
+
throw new Ts23dError2("T3D_RENDER_OPTIONS", { cause, fix });
|
|
594
|
+
}
|
|
595
|
+
function checkBeautyOptions2(o) {
|
|
596
|
+
const v = {
|
|
597
|
+
width: o.width ?? 1600,
|
|
598
|
+
height: o.height ?? 900,
|
|
599
|
+
azDeg: o.azDeg ?? -35,
|
|
600
|
+
elDeg: o.elDeg ?? 18,
|
|
601
|
+
fovDeg: o.fovDeg ?? 30,
|
|
602
|
+
supersample: o.supersample ?? 2
|
|
603
|
+
};
|
|
604
|
+
for (const k of ["width", "height"])
|
|
605
|
+
need(Number.isInteger(v[k]) && v[k] >= 16 && v[k] <= 8192, `${k} ${v[k]} is not an integer in 16…8192 pixels`, "pass a size like 1600x900 (--size WxH), each side 16–8192");
|
|
606
|
+
need(Number.isFinite(v.azDeg), `azimuth ${v.azDeg} is not a number`, "pass degrees, e.g. --az -35");
|
|
607
|
+
need(Number.isFinite(v.elDeg) && v.elDeg >= -10 && v.elDeg <= 89, `elevation ${v.elDeg} is outside −10…89°`, "pass degrees above the horizon, e.g. --el 18 (89 = straight down)");
|
|
608
|
+
need(Number.isFinite(v.fovDeg) && v.fovDeg >= 5 && v.fovDeg <= 120, `field of view ${v.fovDeg} is outside 5…120°`, "pass a vertical FOV in degrees, e.g. --fov 30");
|
|
609
|
+
need(Number.isInteger(v.supersample) && v.supersample >= 1 && v.supersample <= 4, `supersample ${v.supersample} is not 1, 2, 3 or 4`, "use supersample: 2 (4 samples per pixel)");
|
|
610
|
+
if (o.window)
|
|
611
|
+
need(Number.isFinite(o.window.zoom) && o.window.zoom >= 1 && o.window.zoom <= 64, `window zoom ${o.window.zoom} is outside 1…64`, "zoom into the shot with window: { x, y, zoom } and 1 ≤ zoom ≤ 64");
|
|
612
|
+
return v;
|
|
613
|
+
}
|
|
614
|
+
function parseSize2(text) {
|
|
615
|
+
const m = /^(\d+)[x×](\d+)$/i.exec(text.trim());
|
|
616
|
+
need(m !== null, `size "${text}" is not WIDTHxHEIGHT`, "pass --size 1600x900");
|
|
617
|
+
const width = Number(m?.[1]);
|
|
618
|
+
const height = Number(m?.[2]);
|
|
619
|
+
checkBeautyOptions2({ width, height });
|
|
620
|
+
return { width, height };
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
// src/render/resolve.ts
|
|
624
|
+
function backdrop(nx, ny, out) {
|
|
625
|
+
const dx = (nx - 0.5) * 1.1;
|
|
626
|
+
const dy = ny - 0.62;
|
|
627
|
+
const fall = Math.min(1, dx * dx + dy * dy * 1.6);
|
|
628
|
+
out[0] = fromSrgb(0.93 - 0.17 * fall);
|
|
629
|
+
out[1] = fromSrgb(0.935 - 0.15 * fall);
|
|
630
|
+
out[2] = fromSrgb(0.94 - 0.12 * fall);
|
|
631
|
+
}
|
|
632
|
+
function resolve(ctx, vis, floor, width, height, ss) {
|
|
633
|
+
const rgba = new Uint8Array(width * height * 4);
|
|
634
|
+
const mask = new Uint8Array(width * height);
|
|
635
|
+
const counted = new Int32Array(ctx.scene.triangles).fill(-1);
|
|
636
|
+
const detail = new Uint32Array(16 * 9);
|
|
637
|
+
const lin = new Float32Array(3);
|
|
638
|
+
const bg = new Float32Array(3);
|
|
639
|
+
const n = ss * ss;
|
|
640
|
+
const ids = new Int32Array(n);
|
|
641
|
+
const cnt = new Int32Array(n);
|
|
642
|
+
const sb1 = new Float64Array(n);
|
|
643
|
+
const sb2 = new Float64Array(n);
|
|
644
|
+
const W = vis.width;
|
|
645
|
+
const inv = 1 / n;
|
|
646
|
+
for (let y = 0;y < height; y++) {
|
|
647
|
+
for (let x = 0;x < width; x++) {
|
|
648
|
+
backdrop(x / width, y / height, bg);
|
|
649
|
+
let r = 0;
|
|
650
|
+
let g = 0;
|
|
651
|
+
let b = 0;
|
|
652
|
+
let groups = 0;
|
|
653
|
+
let hits = 0;
|
|
654
|
+
for (let sy = 0;sy < ss; sy++)
|
|
655
|
+
for (let sx = 0;sx < ss; sx++) {
|
|
656
|
+
const X = x * ss + sx;
|
|
657
|
+
const Y = y * ss + sy;
|
|
658
|
+
const i = Y * W + X;
|
|
659
|
+
const t = vis.tri[i] ?? -1;
|
|
660
|
+
if (t < 0) {
|
|
661
|
+
const l = floorLight(floor, ctx.cam, X + 0.5, Y + 0.5);
|
|
662
|
+
r += (bg[0] ?? 0) * l;
|
|
663
|
+
g += (bg[1] ?? 0) * l;
|
|
664
|
+
b += (bg[2] ?? 0) * l;
|
|
665
|
+
continue;
|
|
666
|
+
}
|
|
667
|
+
hits++;
|
|
668
|
+
let k = 0;
|
|
669
|
+
while (k < groups && ids[k] !== t)
|
|
670
|
+
k++;
|
|
671
|
+
if (k === groups) {
|
|
672
|
+
ids[k] = t;
|
|
673
|
+
cnt[k] = 0;
|
|
674
|
+
sb1[k] = 0;
|
|
675
|
+
sb2[k] = 0;
|
|
676
|
+
groups++;
|
|
677
|
+
}
|
|
678
|
+
cnt[k] = (cnt[k] ?? 0) + 1;
|
|
679
|
+
sb1[k] = (sb1[k] ?? 0) + (vis.b1[i] ?? 0);
|
|
680
|
+
sb2[k] = (sb2[k] ?? 0) + (vis.b2[i] ?? 0);
|
|
681
|
+
}
|
|
682
|
+
const cell = Math.floor(y * 9 / height) * 16 + Math.floor(x * 16 / width);
|
|
683
|
+
for (let k = 0;k < groups; k++) {
|
|
684
|
+
const t = ids[k] ?? 0;
|
|
685
|
+
const c = cnt[k] ?? 1;
|
|
686
|
+
if (counted[t] !== cell) {
|
|
687
|
+
counted[t] = cell;
|
|
688
|
+
detail[cell] = (detail[cell] ?? 0) + 1;
|
|
689
|
+
}
|
|
690
|
+
shadeSample(ctx, t, (sb1[k] ?? 0) / c, (sb2[k] ?? 0) / c, vis.mip[t] ?? 0, x + y * 2, lin);
|
|
691
|
+
r += tone(lin[0] ?? 0) * c;
|
|
692
|
+
g += tone(lin[1] ?? 0) * c;
|
|
693
|
+
b += tone(lin[2] ?? 0) * c;
|
|
694
|
+
}
|
|
695
|
+
const o = (y * width + x) * 4;
|
|
696
|
+
rgba[o] = toSrgb8(r * inv);
|
|
697
|
+
rgba[o + 1] = toSrgb8(g * inv);
|
|
698
|
+
rgba[o + 2] = toSrgb8(b * inv);
|
|
699
|
+
rgba[o + 3] = 255;
|
|
700
|
+
mask[y * width + x] = Math.round(hits * inv * 255);
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
return { width, height, rgba, mask, detail };
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
// src/render/scene.ts
|
|
707
|
+
var SURFACE_CODE = {
|
|
708
|
+
opaque: 0,
|
|
709
|
+
cutout: 1,
|
|
710
|
+
glass: 2,
|
|
711
|
+
translucent: 3
|
|
712
|
+
};
|
|
713
|
+
function sceneOf(asset) {
|
|
714
|
+
const meshes = worldMeshes2(asset.parts);
|
|
715
|
+
const surfOf = new Map(asset.parts.map((p) => [p.name, SURFACE_CODE[p.surface ?? "opaque"]]));
|
|
716
|
+
let nv = 0;
|
|
717
|
+
let nt = 0;
|
|
718
|
+
for (const m of meshes.values()) {
|
|
719
|
+
nv += m.positions.length / 3;
|
|
720
|
+
nt += m.indices.length / 3;
|
|
721
|
+
}
|
|
722
|
+
const positions = new Float32Array(nv * 3);
|
|
723
|
+
const normals = new Float32Array(nv * 3);
|
|
724
|
+
const uvs = new Float32Array(nv * 2);
|
|
725
|
+
const colors = new Float32Array(nv * 3);
|
|
726
|
+
const indices = new Uint32Array(nt * 3);
|
|
727
|
+
const surface = new Uint8Array(nt);
|
|
728
|
+
let vo = 0;
|
|
729
|
+
let to = 0;
|
|
730
|
+
for (const [name, m] of meshes) {
|
|
731
|
+
positions.set(m.positions, vo * 3);
|
|
732
|
+
normals.set(m.normals, vo * 3);
|
|
733
|
+
uvs.set(m.uvs, vo * 2);
|
|
734
|
+
colors.set(m.colors, vo * 3);
|
|
735
|
+
for (let i = 0;i < m.indices.length; i++)
|
|
736
|
+
indices[to * 3 + i] = (m.indices[i] ?? 0) + vo;
|
|
737
|
+
surface.fill(surfOf.get(name) ?? 0, to, to + m.indices.length / 3);
|
|
738
|
+
vo += m.positions.length / 3;
|
|
739
|
+
to += m.indices.length / 3;
|
|
740
|
+
}
|
|
741
|
+
const min = [Infinity, Infinity, Infinity];
|
|
742
|
+
const max = [-Infinity, -Infinity, -Infinity];
|
|
743
|
+
for (let i = 0;i < positions.length; i++) {
|
|
744
|
+
const v = positions[i] ?? 0;
|
|
745
|
+
const k = i % 3;
|
|
746
|
+
if (v < (min[k] ?? 0))
|
|
747
|
+
min[k] = v;
|
|
748
|
+
if (v > (max[k] ?? 0))
|
|
749
|
+
max[k] = v;
|
|
750
|
+
}
|
|
751
|
+
if (nv === 0) {
|
|
752
|
+
min.fill(-0.5);
|
|
753
|
+
max.fill(0.5);
|
|
754
|
+
}
|
|
755
|
+
return {
|
|
756
|
+
positions,
|
|
757
|
+
normals,
|
|
758
|
+
uvs,
|
|
759
|
+
colors,
|
|
760
|
+
indices,
|
|
761
|
+
surface,
|
|
762
|
+
tangents: tangentsOf(positions, uvs, indices),
|
|
763
|
+
min,
|
|
764
|
+
max,
|
|
765
|
+
triangles: nt
|
|
766
|
+
};
|
|
767
|
+
}
|
|
768
|
+
function tangentsOf(p, uv, idx) {
|
|
769
|
+
const out = new Float32Array(idx.length * 2);
|
|
770
|
+
for (let t = 0;t < idx.length; t += 3) {
|
|
771
|
+
const a = idx[t] ?? 0;
|
|
772
|
+
const b = idx[t + 1] ?? 0;
|
|
773
|
+
const c = idx[t + 2] ?? 0;
|
|
774
|
+
const e1x = (p[b * 3] ?? 0) - (p[a * 3] ?? 0);
|
|
775
|
+
const e1y = (p[b * 3 + 1] ?? 0) - (p[a * 3 + 1] ?? 0);
|
|
776
|
+
const e1z = (p[b * 3 + 2] ?? 0) - (p[a * 3 + 2] ?? 0);
|
|
777
|
+
const e2x = (p[c * 3] ?? 0) - (p[a * 3] ?? 0);
|
|
778
|
+
const e2y = (p[c * 3 + 1] ?? 0) - (p[a * 3 + 1] ?? 0);
|
|
779
|
+
const e2z = (p[c * 3 + 2] ?? 0) - (p[a * 3 + 2] ?? 0);
|
|
780
|
+
const du1 = (uv[b * 2] ?? 0) - (uv[a * 2] ?? 0);
|
|
781
|
+
const dv1 = (uv[b * 2 + 1] ?? 0) - (uv[a * 2 + 1] ?? 0);
|
|
782
|
+
const du2 = (uv[c * 2] ?? 0) - (uv[a * 2] ?? 0);
|
|
783
|
+
const dv2 = (uv[c * 2 + 1] ?? 0) - (uv[a * 2 + 1] ?? 0);
|
|
784
|
+
const det = du1 * dv2 - du2 * dv1;
|
|
785
|
+
if (Math.abs(det) < 0.000000000001)
|
|
786
|
+
continue;
|
|
787
|
+
const tx = (e1x * dv2 - e2x * dv1) / det;
|
|
788
|
+
const ty = (e1y * dv2 - e2y * dv1) / det;
|
|
789
|
+
const tz = (e1z * dv2 - e2z * dv1) / det;
|
|
790
|
+
const bx = (e2x * du1 - e1x * du2) / det;
|
|
791
|
+
const by = (e2y * du1 - e1y * du2) / det;
|
|
792
|
+
const bz = (e2z * du1 - e1z * du2) / det;
|
|
793
|
+
const lt = Math.hypot(tx, ty, tz) || 1;
|
|
794
|
+
const lb = -(Math.hypot(bx, by, bz) || 1);
|
|
795
|
+
const o = t * 2;
|
|
796
|
+
out[o] = tx / lt;
|
|
797
|
+
out[o + 1] = ty / lt;
|
|
798
|
+
out[o + 2] = tz / lt;
|
|
799
|
+
out[o + 3] = bx / lb;
|
|
800
|
+
out[o + 4] = by / lb;
|
|
801
|
+
out[o + 5] = bz / lb;
|
|
802
|
+
}
|
|
803
|
+
return out;
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
// src/render/vis-buffer.ts
|
|
807
|
+
function rasteriseVis(s, cam, texSize, alpha) {
|
|
808
|
+
const W = cam.width;
|
|
809
|
+
const H = cam.height;
|
|
810
|
+
const n = W * H;
|
|
811
|
+
const tri = new Int32Array(n).fill(-1);
|
|
812
|
+
const b1 = new Float32Array(n);
|
|
813
|
+
const b2 = new Float32Array(n);
|
|
814
|
+
const invw = new Float32Array(n);
|
|
815
|
+
const nv = s.positions.length / 3;
|
|
816
|
+
const sx = new Float32Array(nv);
|
|
817
|
+
const sy = new Float32Array(nv);
|
|
818
|
+
const iw = new Float32Array(nv);
|
|
819
|
+
const P = s.positions;
|
|
820
|
+
const [ex, ey, ez] = cam.eye;
|
|
821
|
+
for (let v = 0;v < nv; v++) {
|
|
822
|
+
const dx = (P[v * 3] ?? 0) - ex;
|
|
823
|
+
const dy = (P[v * 3 + 1] ?? 0) - ey;
|
|
824
|
+
const dz = (P[v * 3 + 2] ?? 0) - ez;
|
|
825
|
+
const w = dx * cam.f[0] + dy * cam.f[1] + dz * cam.f[2];
|
|
826
|
+
const q = w > 0.0001 ? 1 / w : -1;
|
|
827
|
+
iw[v] = q;
|
|
828
|
+
sx[v] = cam.cx + (dx * cam.r[0] + dy * cam.r[1] + dz * cam.r[2]) * q * cam.focal;
|
|
829
|
+
sy[v] = cam.cy - (dx * cam.u[0] + dy * cam.u[1] + dz * cam.u[2]) * q * cam.focal;
|
|
830
|
+
}
|
|
831
|
+
const mip = new Uint8Array(s.triangles);
|
|
832
|
+
const I = s.indices;
|
|
833
|
+
const UV = s.uvs;
|
|
834
|
+
for (let t = 0;t < s.triangles; t++) {
|
|
835
|
+
const a = I[t * 3] ?? 0;
|
|
836
|
+
const b = I[t * 3 + 1] ?? 0;
|
|
837
|
+
const c = I[t * 3 + 2] ?? 0;
|
|
838
|
+
const qa = iw[a] ?? -1;
|
|
839
|
+
const qb = iw[b] ?? -1;
|
|
840
|
+
const qc = iw[c] ?? -1;
|
|
841
|
+
if (qa < 0 || qb < 0 || qc < 0)
|
|
842
|
+
continue;
|
|
843
|
+
const ax = sx[a] ?? 0;
|
|
844
|
+
const ay = sy[a] ?? 0;
|
|
845
|
+
const bx = sx[b] ?? 0;
|
|
846
|
+
const by = sy[b] ?? 0;
|
|
847
|
+
const cx = sx[c] ?? 0;
|
|
848
|
+
const cy = sy[c] ?? 0;
|
|
849
|
+
const area = (bx - ax) * (cy - ay) - (by - ay) * (cx - ax);
|
|
850
|
+
if (Math.abs(area) < 0.000000000001)
|
|
851
|
+
continue;
|
|
852
|
+
const uvArea = Math.abs(((UV[b * 2] ?? 0) - (UV[a * 2] ?? 0)) * ((UV[c * 2 + 1] ?? 0) - (UV[a * 2 + 1] ?? 0)) - ((UV[b * 2 + 1] ?? 0) - (UV[a * 2 + 1] ?? 0)) * ((UV[c * 2] ?? 0) - (UV[a * 2] ?? 0)));
|
|
853
|
+
const lod = 0.5 * Math.log2(uvArea * texSize * texSize / Math.abs(area) + 0.000000000001);
|
|
854
|
+
const m = Math.max(0, Math.min(10, Math.floor(lod + 0.25)));
|
|
855
|
+
mip[t] = m;
|
|
856
|
+
const lx = ax < bx ? Math.min(ax, cx) : Math.min(bx, cx);
|
|
857
|
+
const hx = ax > bx ? Math.max(ax, cx) : Math.max(bx, cx);
|
|
858
|
+
const ly = ay < by ? Math.min(ay, cy) : Math.min(by, cy);
|
|
859
|
+
const hy = ay > by ? Math.max(ay, cy) : Math.max(by, cy);
|
|
860
|
+
const minX = lx < 0 ? 0 : Math.floor(lx);
|
|
861
|
+
const maxX = hx > W - 1 ? W - 1 : Math.ceil(hx);
|
|
862
|
+
const minY = ly < 0 ? 0 : Math.floor(ly);
|
|
863
|
+
const maxY = hy > H - 1 ? H - 1 : Math.ceil(hy);
|
|
864
|
+
if (minX > maxX || minY > maxY)
|
|
865
|
+
continue;
|
|
866
|
+
const inv = 1 / area;
|
|
867
|
+
const cut = alpha !== undefined && s.surface[t] === 1;
|
|
868
|
+
const A0 = (by - cy) * inv;
|
|
869
|
+
const B0 = (cx - bx) * inv;
|
|
870
|
+
const A1 = (cy - ay) * inv;
|
|
871
|
+
const B1 = (ax - cx) * inv;
|
|
872
|
+
const C0 = (bx * cy - by * cx) * inv;
|
|
873
|
+
const C1 = (cx * ay - cy * ax) * inv;
|
|
874
|
+
for (let y = minY;y <= maxY; y++) {
|
|
875
|
+
const py = y + 0.5;
|
|
876
|
+
let w0 = C0 + A0 * (minX + 0.5) + B0 * py;
|
|
877
|
+
let w1 = C1 + A1 * (minX + 0.5) + B1 * py;
|
|
878
|
+
for (let x = minX;x <= maxX; x++, w0 += A0, w1 += A1) {
|
|
879
|
+
const w2 = 1 - w0 - w1;
|
|
880
|
+
if (w0 < 0 || w1 < 0 || w2 < 0)
|
|
881
|
+
continue;
|
|
882
|
+
const z = w0 * qa + w1 * qb + w2 * qc;
|
|
883
|
+
const i = y * W + x;
|
|
884
|
+
if (z <= (invw[i] ?? 0))
|
|
885
|
+
continue;
|
|
886
|
+
const pb = w1 * qb / z;
|
|
887
|
+
const pc = w2 * qc / z;
|
|
888
|
+
if (cut && alpha && !alpha(t, pb, pc, m))
|
|
889
|
+
continue;
|
|
890
|
+
invw[i] = z;
|
|
891
|
+
tri[i] = t;
|
|
892
|
+
b1[i] = pb;
|
|
893
|
+
b2[i] = pc;
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
return { width: W, height: H, tri, b1, b2, invw, mip };
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
// src/render/beauty.ts
|
|
901
|
+
var defaultSheet;
|
|
902
|
+
var sheetOf = (o) => {
|
|
903
|
+
if (o.sheet)
|
|
904
|
+
return o.sheet;
|
|
905
|
+
defaultSheet ??= paintTrimSheet2({ ...DEFAULT_SHEET2, size: 512 });
|
|
906
|
+
return defaultSheet;
|
|
907
|
+
};
|
|
908
|
+
function cameraFor2(asset, o = {}) {
|
|
909
|
+
const v = checkBeautyOptions2(o);
|
|
910
|
+
const cam = frameOrbit(sceneOf(o.frameFrom ?? asset).positions, { azDeg: v.azDeg, elDeg: v.elDeg, fovDeg: v.fovDeg, fill: 0.8 }, v.width, v.height);
|
|
911
|
+
return o.window ? zoomWindow(cam, o.window.x, o.window.y, o.window.zoom) : cam;
|
|
912
|
+
}
|
|
913
|
+
function renderBeauty2(asset, o = {}) {
|
|
914
|
+
const v = checkBeautyOptions2(o);
|
|
915
|
+
const ss = v.supersample;
|
|
916
|
+
const scene = sceneOf(asset);
|
|
917
|
+
const tex = sheetTextures(sheetOf(o));
|
|
918
|
+
const rig = studioRig(v.azDeg);
|
|
919
|
+
const texel = new Float32Array(4);
|
|
920
|
+
const alpha = (t, b1, b2, mip = 2) => {
|
|
921
|
+
const I = scene.indices;
|
|
922
|
+
const U = scene.uvs;
|
|
923
|
+
const b0 = 1 - b1 - b2;
|
|
924
|
+
const a = (I[t * 3] ?? 0) * 2;
|
|
925
|
+
const b = (I[t * 3 + 1] ?? 0) * 2;
|
|
926
|
+
const c = (I[t * 3 + 2] ?? 0) * 2;
|
|
927
|
+
const u = (U[a] ?? 0) * b0 + (U[b] ?? 0) * b1 + (U[c] ?? 0) * b2;
|
|
928
|
+
const w = (U[a + 1] ?? 0) * b0 + (U[b + 1] ?? 0) * b1 + (U[c + 1] ?? 0) * b2;
|
|
929
|
+
sample(tex.albedo, mip, u, w, texel);
|
|
930
|
+
return (texel[3] ?? 1) >= 0.5;
|
|
931
|
+
};
|
|
932
|
+
const cam = scaled(cameraFor2(asset, o), ss);
|
|
933
|
+
const vis = rasteriseVis(scene, cam, tex.size, alpha);
|
|
934
|
+
const extent = Math.max(...scene.max.map((m, k) => m - (scene.min[k] ?? 0)));
|
|
935
|
+
const px = Math.max(v.width, v.height) * ss;
|
|
936
|
+
const res = (lo, hi, f) => Math.max(lo, Math.min(hi, Math.round(px * f)));
|
|
937
|
+
const sunMap = depthMap(scene, rig.sun, res(256, 2048, 1), undefined, alpha);
|
|
938
|
+
const ctx = {
|
|
939
|
+
scene,
|
|
940
|
+
cam,
|
|
941
|
+
tex,
|
|
942
|
+
rig,
|
|
943
|
+
sunMap,
|
|
944
|
+
aoMaps: rig.skyDirs.map((d) => depthMap(scene, d, res(96, 384, 0.25), undefined, alpha)),
|
|
945
|
+
pcf: pcfTaps(Math.max(1.2, 0.004 * extent * sunMap.k)),
|
|
946
|
+
reach: 0.12 * extent
|
|
947
|
+
};
|
|
948
|
+
const floor = floorGrid(ctx, res(96, 512, 0.25));
|
|
949
|
+
return resolve(ctx, vis, floor, v.width, v.height, ss);
|
|
950
|
+
}
|
|
951
|
+
function beauty2(asset, o = {}) {
|
|
952
|
+
const r = renderBeauty2(asset, o);
|
|
953
|
+
return encodePng2(r.width, r.height, r.rgba);
|
|
954
|
+
}
|
|
955
|
+
// src/render/font.ts
|
|
956
|
+
var GLYPHS = {
|
|
957
|
+
A: "010101111101101",
|
|
958
|
+
B: "110101110101110",
|
|
959
|
+
C: "011100100100011",
|
|
960
|
+
D: "110101101101110",
|
|
961
|
+
E: "111100110100111",
|
|
962
|
+
F: "111100110100100",
|
|
963
|
+
G: "011100101101011",
|
|
964
|
+
H: "101101111101101",
|
|
965
|
+
I: "111010010010111",
|
|
966
|
+
J: "001001001101010",
|
|
967
|
+
K: "101101110101101",
|
|
968
|
+
L: "100100100100111",
|
|
969
|
+
M: "101111111101101",
|
|
970
|
+
N: "110101101101101",
|
|
971
|
+
O: "010101101101010",
|
|
972
|
+
P: "110101110100100",
|
|
973
|
+
Q: "010101101110011",
|
|
974
|
+
R: "110101110101101",
|
|
975
|
+
S: "011100010001110",
|
|
976
|
+
T: "111010010010010",
|
|
977
|
+
U: "101101101101111",
|
|
978
|
+
V: "101101101101010",
|
|
979
|
+
W: "101101111111101",
|
|
980
|
+
X: "101101010101101",
|
|
981
|
+
Y: "101101010010010",
|
|
982
|
+
Z: "111001010100111",
|
|
983
|
+
"0": "111101101101111",
|
|
984
|
+
"1": "010110010010111",
|
|
985
|
+
"2": "110001010100111",
|
|
986
|
+
"3": "110001010001110",
|
|
987
|
+
"4": "101101111001001",
|
|
988
|
+
"5": "111100110001110",
|
|
989
|
+
"6": "011100111101111",
|
|
990
|
+
"7": "111001010010010",
|
|
991
|
+
"8": "111101111101111",
|
|
992
|
+
"9": "111101111001110",
|
|
993
|
+
".": "000000000000010",
|
|
994
|
+
"/": "001001010100100",
|
|
995
|
+
"-": "000000111000000",
|
|
996
|
+
":": "000010000010000",
|
|
997
|
+
" ": "000000000000000",
|
|
998
|
+
"+": "000010111010000",
|
|
999
|
+
_: "000000000000111",
|
|
1000
|
+
"=": "000111000111000"
|
|
1001
|
+
};
|
|
1002
|
+
function drawText(px, width, x, y, text, scale, rgb) {
|
|
1003
|
+
let cx = x;
|
|
1004
|
+
for (const ch of text.toUpperCase()) {
|
|
1005
|
+
const g = GLYPHS[ch] ?? GLYPHS[" "] ?? "";
|
|
1006
|
+
for (let i = 0;i < 15; i++) {
|
|
1007
|
+
if (g[i] !== "1")
|
|
1008
|
+
continue;
|
|
1009
|
+
const gx = cx + i % 3 * scale;
|
|
1010
|
+
const gy = y + Math.floor(i / 3) * scale;
|
|
1011
|
+
for (let dy = 0;dy < scale; dy++) {
|
|
1012
|
+
for (let dx = 0;dx < scale; dx++) {
|
|
1013
|
+
const o = ((gy + dy) * width + gx + dx) * 4;
|
|
1014
|
+
if (o >= 0 && o + 3 < px.length)
|
|
1015
|
+
px.set([...rgb, 255], o);
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
cx += 4 * scale;
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
// src/render/lod-strip.ts
|
|
1024
|
+
var tris = (a) => a.parts.reduce((s, p) => s + (p.mesh ? p.mesh.indices.length / 3 : 0), 0);
|
|
1025
|
+
function lodStrip2(lods, o = {}) {
|
|
1026
|
+
const w = o.width ?? 640;
|
|
1027
|
+
const h = o.height ?? 480;
|
|
1028
|
+
const first = lods[0];
|
|
1029
|
+
const n = lods.length;
|
|
1030
|
+
const out = new Uint8Array(w * n * h * 4);
|
|
1031
|
+
lods.forEach((a, k) => {
|
|
1032
|
+
const r = renderBeauty2(a, {
|
|
1033
|
+
...o,
|
|
1034
|
+
width: w,
|
|
1035
|
+
height: h,
|
|
1036
|
+
...first ? { frameFrom: first } : {}
|
|
1037
|
+
});
|
|
1038
|
+
for (let y = 0;y < h; y++)
|
|
1039
|
+
out.set(r.rgba.subarray(y * w * 4, (y + 1) * w * 4), (y * w * n + k * w) * 4);
|
|
1040
|
+
if (k > 0)
|
|
1041
|
+
for (let y = 0;y < h; y++)
|
|
1042
|
+
out.fill(255, (y * w * n + k * w) * 4, (y * w * n + k * w + 2) * 4);
|
|
1043
|
+
drawText(out, w * n, k * w + 12, 12, `LOD${a.lod} ${tris(a)} TRIS`, 3, [40, 44, 52]);
|
|
1044
|
+
});
|
|
1045
|
+
return encodePng2(w * n, h, out);
|
|
1046
|
+
}
|
|
1047
|
+
// src/render/metrics.ts
|
|
1048
|
+
var towardCamera = (v) => {
|
|
1049
|
+
const l = Math.hypot(v.dir[0], v.dir[1], v.dir[2]) || 1;
|
|
1050
|
+
return [-v.dir[0] / l, -v.dir[1] / l, -v.dir[2] / l];
|
|
1051
|
+
};
|
|
1052
|
+
function viewFrame2(asset, view) {
|
|
1053
|
+
return fitFrame(sceneOf(asset), towardCamera(view));
|
|
1054
|
+
}
|
|
1055
|
+
function silhouette2(asset, view, size = 256, frame = viewFrame2(asset, view)) {
|
|
1056
|
+
const m = depthMap(sceneOf(asset), towardCamera(view), size, frame);
|
|
1057
|
+
const mask = new Uint8Array(size * size);
|
|
1058
|
+
for (let i = 0;i < mask.length; i++)
|
|
1059
|
+
mask[i] = (m.data[i] ?? -Infinity) > -Infinity ? 1 : 0;
|
|
1060
|
+
return { size, mask };
|
|
1061
|
+
}
|
|
1062
|
+
function maskIoU2(a, b) {
|
|
1063
|
+
if (a.length !== b.length)
|
|
1064
|
+
throw new Ts23dError2("T3D_RENDER_OPTIONS", {
|
|
1065
|
+
cause: `masks differ in size (${a.length} vs ${b.length} pixels)`,
|
|
1066
|
+
fix: "render both silhouettes at the same size and frame (pass the LOD0 frame to both)"
|
|
1067
|
+
});
|
|
1068
|
+
let inter = 0;
|
|
1069
|
+
let union = 0;
|
|
1070
|
+
for (let i = 0;i < a.length; i++) {
|
|
1071
|
+
const x = (a[i] ?? 0) !== 0;
|
|
1072
|
+
const y = (b[i] ?? 0) !== 0;
|
|
1073
|
+
if (x && y)
|
|
1074
|
+
inter++;
|
|
1075
|
+
if (x || y)
|
|
1076
|
+
union++;
|
|
1077
|
+
}
|
|
1078
|
+
return union === 0 ? 1 : inter / union;
|
|
1079
|
+
}
|
|
1080
|
+
function poolMask2(s, f) {
|
|
1081
|
+
const size = Math.floor(s.size / f);
|
|
1082
|
+
const mask = new Uint8Array(size * size);
|
|
1083
|
+
for (let y = 0;y < s.size; y++)
|
|
1084
|
+
for (let x = 0;x < s.size; x++)
|
|
1085
|
+
if (s.mask[y * s.size + x]) {
|
|
1086
|
+
const i = Math.floor(y / f) * size + Math.floor(x / f);
|
|
1087
|
+
if (i < mask.length)
|
|
1088
|
+
mask[i] = 1;
|
|
1089
|
+
}
|
|
1090
|
+
return { size, mask };
|
|
1091
|
+
}
|
|
1092
|
+
function maskStats2(s) {
|
|
1093
|
+
let n = 0;
|
|
1094
|
+
let x0 = s.size;
|
|
1095
|
+
let y0 = s.size;
|
|
1096
|
+
let x1 = -1;
|
|
1097
|
+
let y1 = -1;
|
|
1098
|
+
for (let y = 0;y < s.size; y++)
|
|
1099
|
+
for (let x = 0;x < s.size; x++) {
|
|
1100
|
+
if (!s.mask[y * s.size + x])
|
|
1101
|
+
continue;
|
|
1102
|
+
n++;
|
|
1103
|
+
if (x < x0)
|
|
1104
|
+
x0 = x;
|
|
1105
|
+
if (x > x1)
|
|
1106
|
+
x1 = x;
|
|
1107
|
+
if (y < y0)
|
|
1108
|
+
y0 = y;
|
|
1109
|
+
if (y > y1)
|
|
1110
|
+
y1 = y;
|
|
1111
|
+
}
|
|
1112
|
+
const box = x1 < 0 ? 0 : (x1 - x0 + 1) * (y1 - y0 + 1);
|
|
1113
|
+
return { coverage: n / (s.size * s.size), bboxFill: box ? n / box : 0 };
|
|
1114
|
+
}
|
|
1115
|
+
function exposure2(rgba, mask, width) {
|
|
1116
|
+
const height = Math.max(1, Math.floor(mask.length / Math.max(1, width)));
|
|
1117
|
+
let x0 = width;
|
|
1118
|
+
let x1 = -1;
|
|
1119
|
+
let y0 = height;
|
|
1120
|
+
let y1 = -1;
|
|
1121
|
+
let n = 0;
|
|
1122
|
+
let sum = 0;
|
|
1123
|
+
let sq = 0;
|
|
1124
|
+
let black = 0;
|
|
1125
|
+
let white = 0;
|
|
1126
|
+
for (let i = 0;i < mask.length; i++) {
|
|
1127
|
+
if ((mask[i] ?? 0) < 128)
|
|
1128
|
+
continue;
|
|
1129
|
+
const x = i % width;
|
|
1130
|
+
const y = Math.floor(i / width);
|
|
1131
|
+
if (x < x0)
|
|
1132
|
+
x0 = x;
|
|
1133
|
+
if (x > x1)
|
|
1134
|
+
x1 = x;
|
|
1135
|
+
if (y < y0)
|
|
1136
|
+
y0 = y;
|
|
1137
|
+
if (y > y1)
|
|
1138
|
+
y1 = y;
|
|
1139
|
+
const o = i * 4;
|
|
1140
|
+
const l = (0.2126 * (rgba[o] ?? 0) + 0.7152 * (rgba[o + 1] ?? 0) + 0.0722 * (rgba[o + 2] ?? 0)) / 255;
|
|
1141
|
+
n++;
|
|
1142
|
+
sum += l;
|
|
1143
|
+
sq += l * l;
|
|
1144
|
+
if (l < 0.06)
|
|
1145
|
+
black++;
|
|
1146
|
+
if (l > 0.97)
|
|
1147
|
+
white++;
|
|
1148
|
+
}
|
|
1149
|
+
const mean = n ? sum / n : 0;
|
|
1150
|
+
return {
|
|
1151
|
+
coverage: n / Math.max(1, mask.length),
|
|
1152
|
+
span: x1 < 0 ? 0 : Math.max((x1 - x0 + 1) / width, (y1 - y0 + 1) / height),
|
|
1153
|
+
mean,
|
|
1154
|
+
contrast: n ? Math.sqrt(Math.max(0, sq / n - mean * mean)) : 0,
|
|
1155
|
+
nearBlack: n ? black / n : 0,
|
|
1156
|
+
nearWhite: n ? white / n : 0
|
|
1157
|
+
};
|
|
1158
|
+
}
|
|
1159
|
+
// src/render/photo-match.ts
|
|
1160
|
+
function photoMatch2(_asset, _o) {
|
|
1161
|
+
return notImplemented2("photoMatch (photo · model · overlay sheet + silhouette IoU)");
|
|
1162
|
+
}
|
|
1163
|
+
// src/render/review-prompt.ts
|
|
1164
|
+
var CHECKLIST = [
|
|
1165
|
+
"**Floating bits** — every bar, bracket, lamp, rod, handle touches what holds it. Look at the close-ups and the TOP view.",
|
|
1166
|
+
"**Clipping** — wheels through frames or fenders, rods through guides, brackets through roofs, branches through trunks.",
|
|
1167
|
+
"**Proportions vs reality** — compare overall length/width/height ratios and signature features with the reference dossier and its photos.",
|
|
1168
|
+
"**Open stacks and throats** — chimneys, funnels, exhausts, muzzles and pipes are OPEN (rim + dark inner throat), never capped.",
|
|
1169
|
+
"**Hidden detail** — detail buried inside another mass (a clock behind a roof, a hatch under a turret) is wasted triangles.",
|
|
1170
|
+
"**Silhouette at LOD2** — in lods.png, LOD2 keeps the outline and the big colour blocks; nothing pops, vanishes or shifts colour.",
|
|
1171
|
+
"**Texture stretching** — trim-sheet strips run along faces at a sensible scale: no smeared bricks, giant planks or streaked rivets.",
|
|
1172
|
+
"**Colour / value contrast** — materials separate (paint vs rubber vs metal vs glass); not one flat tone; no pure black or white.",
|
|
1173
|
+
"**Lettering and decals** — numbers and signs are legible in the close-ups and read the right way round.",
|
|
1174
|
+
"**Organic forms** — foliage, snow and terrain read as volume from the 3/4 hero, not as blotches, slabs or cards edge-on.",
|
|
1175
|
+
"**Grounding** — the asset stands on the floor (contact shadow under every wheel, foot or base), not sunk or hovering."
|
|
1176
|
+
];
|
|
1177
|
+
function reviewPrompt(m, files, refs) {
|
|
1178
|
+
const pct = (v) => `${(v * 100).toFixed(1)}%`;
|
|
1179
|
+
const views = m.views.map((v) => `| ${v.view} | ${pct(v.lod0.coverage)} | ${pct(v.lod0.bboxFill)} | ${v.lodIoU.toFixed(3)} | ${v.lodIoUExact.toFixed(3)} |`).join(`
|
|
1180
|
+
`);
|
|
1181
|
+
const warnings = m.warnings.length ? m.warnings.map((w) => `- **${w.code}**: ${w.message}
|
|
1182
|
+
fix: ${w.fix}`).join(`
|
|
1183
|
+
`) : "- none — the metrics are proxies, so still run the checklist.";
|
|
1184
|
+
return `# Visual review: ${m.asset}
|
|
1185
|
+
|
|
1186
|
+
You are reviewing a game asset generated from code. READ every image in this folder before
|
|
1187
|
+
answering: ${files.filter((f) => f.endsWith(".png")).join(", ")}.
|
|
1188
|
+
${refs ? `Reference dossier (specs, measures, photo links): \`${refs}\` — read it first.
|
|
1189
|
+
` : `No reference dossier was found (expected recipes/<name>/README.md): judge against well-known photos of the real thing.
|
|
1190
|
+
`}
|
|
1191
|
+
- \`hero.png\` — 3/4 product shot, LOD0. · \`sheet.png\` — six orthographic views on a metre grid.
|
|
1192
|
+
- \`detail_*.png\` — 3× close-ups where the triangles are. · \`lods.png\` — LOD0 · LOD1 · LOD2, same camera.
|
|
1193
|
+
|
|
1194
|
+
Triangles per LOD: ${m.triangles.join(" / ")}.
|
|
1195
|
+
|
|
1196
|
+
## Checklist — answer each with PASS or FAIL + where + the fix
|
|
1197
|
+
${CHECKLIST.map((c, i) => `${i + 1}. ${c}`).join(`
|
|
1198
|
+
`)}
|
|
1199
|
+
|
|
1200
|
+
## Automatic metrics (review.json)
|
|
1201
|
+
Hero: coverage ${pct(m.hero.coverage)}, span ${pct(m.hero.span)}, mean luminance ${m.hero.mean.toFixed(3)}, contrast ${m.hero.contrast.toFixed(3)}, near-black ${pct(m.hero.nearBlack)}, near-white ${pct(m.hero.nearWhite)}.
|
|
1202
|
+
|
|
1203
|
+
| view | LOD0 coverage | bbox fill | LOD0↔LOD2 IoU (far) | IoU (exact) |
|
|
1204
|
+
|---|---|---|---|---|
|
|
1205
|
+
${views}
|
|
1206
|
+
|
|
1207
|
+
## Warnings
|
|
1208
|
+
${warnings}
|
|
1209
|
+
|
|
1210
|
+
## Output
|
|
1211
|
+
A list of findings, worst first: \`[FAIL] <check> — <where, which view/image> — <fix in the recipe>\`,
|
|
1212
|
+
then one line: \`VERDICT: SHIP\` or \`VERDICT: ITERATE\`.
|
|
1213
|
+
`;
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
// src/render/review-warnings.ts
|
|
1217
|
+
var REVIEW_THRESHOLDS2 = {
|
|
1218
|
+
lodIoU: 0.8,
|
|
1219
|
+
heroMeanMin: 0.18,
|
|
1220
|
+
heroMeanMax: 0.85,
|
|
1221
|
+
heroContrastMin: 0.06,
|
|
1222
|
+
nearBlackMax: 0.2,
|
|
1223
|
+
nearWhiteMax: 0.1,
|
|
1224
|
+
heroSpanMin: 0.5
|
|
1225
|
+
};
|
|
1226
|
+
var f = (v) => v.toFixed(3);
|
|
1227
|
+
function warningsOf(m) {
|
|
1228
|
+
const t = REVIEW_THRESHOLDS2;
|
|
1229
|
+
const out = [];
|
|
1230
|
+
for (const v of m.views)
|
|
1231
|
+
if (v.lodIoU < t.lodIoU)
|
|
1232
|
+
out.push({
|
|
1233
|
+
code: "lod-popping",
|
|
1234
|
+
message: `${v.view}: LOD0↔LOD2 silhouette IoU ${f(v.lodIoU)} < ${t.lodIoU}`,
|
|
1235
|
+
fix: "keep the LOD2 massing on the LOD0 silhouette — same outer envelope, fewer cuts (see lods.png)"
|
|
1236
|
+
});
|
|
1237
|
+
const h = m.hero;
|
|
1238
|
+
if (h.span < t.heroSpanMin)
|
|
1239
|
+
out.push({
|
|
1240
|
+
code: "tiny-in-frame",
|
|
1241
|
+
message: `asset spans ${f(h.span)} of the hero frame (< ${t.heroSpanMin})`,
|
|
1242
|
+
fix: "a stray or far-off part is stretching the bounds — find it in sheet.png and attach or remove it"
|
|
1243
|
+
});
|
|
1244
|
+
if (h.mean < t.heroMeanMin)
|
|
1245
|
+
out.push({
|
|
1246
|
+
code: "too-dark",
|
|
1247
|
+
message: `hero mean luminance ${f(h.mean)} < ${t.heroMeanMin}`,
|
|
1248
|
+
fix: "raise vertex colours (dark paint ≈ 0.05–0.1 linear, never 0) or check for inward-facing normals"
|
|
1249
|
+
});
|
|
1250
|
+
if (h.mean > t.heroMeanMax)
|
|
1251
|
+
out.push({
|
|
1252
|
+
code: "washed-out",
|
|
1253
|
+
message: `hero mean luminance ${f(h.mean)} > ${t.heroMeanMax}`,
|
|
1254
|
+
fix: "lower vertex colours: real paint and stone rarely exceed ~0.7 linear albedo"
|
|
1255
|
+
});
|
|
1256
|
+
if (h.contrast < t.heroContrastMin)
|
|
1257
|
+
out.push({
|
|
1258
|
+
code: "low-contrast",
|
|
1259
|
+
message: `hero luminance contrast ${f(h.contrast)} < ${t.heroContrastMin}`,
|
|
1260
|
+
fix: "add value contrast between materials (trim, rubber, metal, glass) and grime in crevices"
|
|
1261
|
+
});
|
|
1262
|
+
if (h.nearBlack > t.nearBlackMax)
|
|
1263
|
+
out.push({
|
|
1264
|
+
code: "crushed-blacks",
|
|
1265
|
+
message: `${f(h.nearBlack)} of asset pixels are near black (> ${t.nearBlackMax})`,
|
|
1266
|
+
fix: "lift near-black colours to ≥ 0.03 linear and check normals point outward"
|
|
1267
|
+
});
|
|
1268
|
+
if (h.nearWhite > t.nearWhiteMax)
|
|
1269
|
+
out.push({
|
|
1270
|
+
code: "blown-whites",
|
|
1271
|
+
message: `${f(h.nearWhite)} of asset pixels are near white (> ${t.nearWhiteMax})`,
|
|
1272
|
+
fix: "bring white paint down to ~0.8 linear so texture and shading stay readable"
|
|
1273
|
+
});
|
|
1274
|
+
return out;
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
// src/render/raster.ts
|
|
1278
|
+
function orthoCamera(view, centre, extent, size) {
|
|
1279
|
+
const f = normalize2(view.dir);
|
|
1280
|
+
const r = normalize2(cross2(f, view.up));
|
|
1281
|
+
const u = cross2(r, f);
|
|
1282
|
+
const k = size / (extent * 1.15);
|
|
1283
|
+
return {
|
|
1284
|
+
project: (p) => {
|
|
1285
|
+
const d = sub2(p, centre);
|
|
1286
|
+
return [size / 2 + dot2(d, r) * k, size / 2 - dot2(d, u) * k, dot2(d, f)];
|
|
1287
|
+
}
|
|
1288
|
+
};
|
|
1289
|
+
}
|
|
1290
|
+
var LIGHT = normalize2([0.4, -0.6, 0.8]);
|
|
1291
|
+
function worldParts(asset) {
|
|
1292
|
+
const xf = worldTransforms2(asset.parts);
|
|
1293
|
+
const out = [];
|
|
1294
|
+
for (const p of asset.parts) {
|
|
1295
|
+
const t = xf.get(p.name);
|
|
1296
|
+
if (!p.mesh || !t)
|
|
1297
|
+
continue;
|
|
1298
|
+
const positions = new Float32Array(p.mesh.positions.length);
|
|
1299
|
+
const normals = new Float32Array(p.mesh.normals.length);
|
|
1300
|
+
for (let i = 0;i < positions.length; i += 3) {
|
|
1301
|
+
positions.set(toWorld2(t, [
|
|
1302
|
+
p.mesh.positions[i] ?? 0,
|
|
1303
|
+
p.mesh.positions[i + 1] ?? 0,
|
|
1304
|
+
p.mesh.positions[i + 2] ?? 0
|
|
1305
|
+
]), i);
|
|
1306
|
+
normals.set(rotateZ2([p.mesh.normals[i] ?? 0, p.mesh.normals[i + 1] ?? 0, p.mesh.normals[i + 2] ?? 0], t.yaw), i);
|
|
1307
|
+
}
|
|
1308
|
+
out.push({ positions, normals, colors: p.mesh.colors, indices: p.mesh.indices });
|
|
1309
|
+
}
|
|
1310
|
+
return out;
|
|
1311
|
+
}
|
|
1312
|
+
function tri(t, a, b, c, rgb) {
|
|
1313
|
+
const minX = Math.max(0, Math.floor(Math.min(a[0], b[0], c[0])));
|
|
1314
|
+
const maxX = Math.min(t.width - 1, Math.ceil(Math.max(a[0], b[0], c[0])));
|
|
1315
|
+
const minY = Math.max(0, Math.floor(Math.min(a[1], b[1], c[1])));
|
|
1316
|
+
const maxY = Math.min(t.height - 1, Math.ceil(Math.max(a[1], b[1], c[1])));
|
|
1317
|
+
const area = (b[0] - a[0]) * (c[1] - a[1]) - (b[1] - a[1]) * (c[0] - a[0]);
|
|
1318
|
+
if (Math.abs(area) < 0.000000001)
|
|
1319
|
+
return;
|
|
1320
|
+
for (let y = minY;y <= maxY; y++) {
|
|
1321
|
+
for (let x = minX;x <= maxX; x++) {
|
|
1322
|
+
const px = x + 0.5;
|
|
1323
|
+
const py = y + 0.5;
|
|
1324
|
+
const w0 = ((b[0] - px) * (c[1] - py) - (b[1] - py) * (c[0] - px)) / area;
|
|
1325
|
+
const w1 = ((c[0] - px) * (a[1] - py) - (c[1] - py) * (a[0] - px)) / area;
|
|
1326
|
+
const w2 = 1 - w0 - w1;
|
|
1327
|
+
if (w0 < 0 || w1 < 0 || w2 < 0)
|
|
1328
|
+
continue;
|
|
1329
|
+
const z = w0 * a[2] + w1 * b[2] + w2 * c[2];
|
|
1330
|
+
const i = y * t.width + x;
|
|
1331
|
+
if (z >= (t.depth[i] ?? Infinity))
|
|
1332
|
+
continue;
|
|
1333
|
+
t.depth[i] = z;
|
|
1334
|
+
t.px.set([rgb[0], rgb[1], rgb[2], 255], i * 4);
|
|
1335
|
+
}
|
|
1336
|
+
}
|
|
1337
|
+
}
|
|
1338
|
+
var toSrgb = (v) => Math.round(255 * Math.min(1, v <= 0.0031308 ? v * 12.92 : 1.055 * v ** (1 / 2.4) - 0.055));
|
|
1339
|
+
function drawAsset(t, cam, asset) {
|
|
1340
|
+
for (const m of worldParts(asset)) {
|
|
1341
|
+
for (let i = 0;i < m.indices.length; i += 3) {
|
|
1342
|
+
const ids = [m.indices[i] ?? 0, m.indices[i + 1] ?? 0, m.indices[i + 2] ?? 0];
|
|
1343
|
+
const pts = ids.map((v) => cam.project([
|
|
1344
|
+
m.positions[v * 3] ?? 0,
|
|
1345
|
+
m.positions[v * 3 + 1] ?? 0,
|
|
1346
|
+
m.positions[v * 3 + 2] ?? 0
|
|
1347
|
+
]));
|
|
1348
|
+
const v0 = ids[0] ?? 0;
|
|
1349
|
+
const n = [
|
|
1350
|
+
m.normals[v0 * 3] ?? 0,
|
|
1351
|
+
m.normals[v0 * 3 + 1] ?? 0,
|
|
1352
|
+
m.normals[v0 * 3 + 2] ?? 1
|
|
1353
|
+
];
|
|
1354
|
+
const shade = 0.35 + 0.65 * Math.max(0, dot2(n, LIGHT));
|
|
1355
|
+
const rgb = [
|
|
1356
|
+
toSrgb((m.colors[v0 * 3] ?? 1) * shade),
|
|
1357
|
+
toSrgb((m.colors[v0 * 3 + 1] ?? 1) * shade),
|
|
1358
|
+
toSrgb((m.colors[v0 * 3 + 2] ?? 1) * shade)
|
|
1359
|
+
];
|
|
1360
|
+
const [a, b, c] = pts;
|
|
1361
|
+
if (a && b && c)
|
|
1362
|
+
tri(t, a, b, c, rgb);
|
|
1363
|
+
}
|
|
1364
|
+
}
|
|
1365
|
+
}
|
|
1366
|
+
function line(t, a, b, rgb) {
|
|
1367
|
+
const steps = Math.ceil(Math.max(Math.abs(b[0] - a[0]), Math.abs(b[1] - a[1]), 1));
|
|
1368
|
+
for (let s = 0;s <= steps; s++) {
|
|
1369
|
+
const k = s / steps;
|
|
1370
|
+
const x = Math.round(a[0] + (b[0] - a[0]) * k);
|
|
1371
|
+
const y = Math.round(a[1] + (b[1] - a[1]) * k);
|
|
1372
|
+
if (x < 0 || y < 0 || x >= t.width || y >= t.height)
|
|
1373
|
+
continue;
|
|
1374
|
+
const z = a[2] + (b[2] - a[2]) * k;
|
|
1375
|
+
const i = y * t.width + x;
|
|
1376
|
+
if (z > (t.depth[i] ?? Infinity) + 0.001)
|
|
1377
|
+
continue;
|
|
1378
|
+
t.px.set([rgb[0], rgb[1], rgb[2], 255], i * 4);
|
|
1379
|
+
}
|
|
1380
|
+
}
|
|
1381
|
+
|
|
1382
|
+
// src/render/sheet.ts
|
|
1383
|
+
var VIEWS2 = [
|
|
1384
|
+
{ label: "3/4 FRONT", dir: [-1, 0.8, -0.6], up: [0, 0, 1] },
|
|
1385
|
+
{ label: "FRONT +X", dir: [-1, 0, 0], up: [0, 0, 1] },
|
|
1386
|
+
{ label: "SIDE -Y", dir: [0, 1, 0], up: [0, 0, 1] },
|
|
1387
|
+
{ label: "TOP", dir: [0, 0, -1], up: [1, 0, 0] },
|
|
1388
|
+
{ label: "3/4 REAR", dir: [1, -0.8, -0.6], up: [0, 0, 1] },
|
|
1389
|
+
{ label: "FAR LOD2", dir: [-1, 0.8, -0.6], up: [0, 0, 1] }
|
|
1390
|
+
];
|
|
1391
|
+
function bounds(asset) {
|
|
1392
|
+
const min = [Infinity, Infinity, Infinity];
|
|
1393
|
+
const max = [-Infinity, -Infinity, -Infinity];
|
|
1394
|
+
for (const m of worldParts(asset)) {
|
|
1395
|
+
for (let i = 0;i < m.positions.length; i += 3) {
|
|
1396
|
+
for (let k = 0;k < 3; k++) {
|
|
1397
|
+
const v = m.positions[i + k] ?? 0;
|
|
1398
|
+
min[k] = Math.min(min[k] ?? 0, v);
|
|
1399
|
+
max[k] = Math.max(max[k] ?? 0, v);
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
1402
|
+
}
|
|
1403
|
+
const lo = [min[0] ?? 0, min[1] ?? 0, min[2] ?? 0];
|
|
1404
|
+
const hi = [max[0] ?? 0, max[1] ?? 0, max[2] ?? 0];
|
|
1405
|
+
const extent = Math.max(hi[0] - lo[0], hi[1] - lo[1], hi[2] - lo[2], 0.1);
|
|
1406
|
+
return {
|
|
1407
|
+
centre: [(lo[0] + hi[0]) / 2, (lo[1] + hi[1]) / 2, (lo[2] + hi[2]) / 2],
|
|
1408
|
+
extent,
|
|
1409
|
+
min: lo,
|
|
1410
|
+
max: hi
|
|
1411
|
+
};
|
|
1412
|
+
}
|
|
1413
|
+
var gridStep = (extent) => [0.1, 0.5, 1, 5, 10, 50, 100].find((s) => extent / s <= 24) ?? 100;
|
|
1414
|
+
function contactSheet2(asset, o = {}) {
|
|
1415
|
+
const cell = o.cell ?? 400;
|
|
1416
|
+
const width = cell * 3;
|
|
1417
|
+
const height = cell * 2 + 40;
|
|
1418
|
+
const px = new Uint8Array(width * height * 4).fill(255);
|
|
1419
|
+
const b = bounds(asset);
|
|
1420
|
+
const step = gridStep(b.extent);
|
|
1421
|
+
VIEWS2.forEach((view, i) => {
|
|
1422
|
+
const t = {
|
|
1423
|
+
px: new Uint8Array(cell * cell * 4),
|
|
1424
|
+
depth: new Float32Array(cell * cell).fill(Infinity),
|
|
1425
|
+
width: cell,
|
|
1426
|
+
height: cell
|
|
1427
|
+
};
|
|
1428
|
+
for (let k = 0;k < cell * cell; k++)
|
|
1429
|
+
t.px.set([238, 240, 243, 255], k * 4);
|
|
1430
|
+
const cam = orthoCamera(view, b.centre, b.extent, cell);
|
|
1431
|
+
drawAsset(t, cam, i === 5 && o.far ? o.far : asset);
|
|
1432
|
+
const g0 = Math.floor(Math.min(b.min[0], b.min[1]) / step - 1) * step;
|
|
1433
|
+
const g1 = Math.ceil(Math.max(b.max[0], b.max[1]) / step + 1) * step;
|
|
1434
|
+
for (let g = g0;g <= g1 + 0.000000001; g += step) {
|
|
1435
|
+
line(t, cam.project([g, g0, 0]), cam.project([g, g1, 0]), [190, 196, 204]);
|
|
1436
|
+
line(t, cam.project([g0, g, 0]), cam.project([g1, g, 0]), [190, 196, 204]);
|
|
1437
|
+
}
|
|
1438
|
+
const ox = i % 3 * cell;
|
|
1439
|
+
const oy = 40 + Math.floor(i / 3) * cell;
|
|
1440
|
+
for (let y = 0;y < cell; y++)
|
|
1441
|
+
px.set(t.px.subarray(y * cell * 4, (y + 1) * cell * 4), ((oy + y) * width + ox) * 4);
|
|
1442
|
+
drawText(px, width, ox + 8, oy + 8, view.label, 3, [40, 44, 52]);
|
|
1443
|
+
});
|
|
1444
|
+
const dims = `${asset.name} ${(b.max[0] - b.min[0]).toFixed(1)}X${(b.max[1] - b.min[1]).toFixed(1)}X${(b.max[2] - b.min[2]).toFixed(1)}M GRID ${step}M LOD${asset.lod}`;
|
|
1445
|
+
drawText(px, width, 10, 10, dims, 4, [20, 22, 26]);
|
|
1446
|
+
return encodePng2(width, height, px);
|
|
1447
|
+
}
|
|
1448
|
+
|
|
1449
|
+
// src/render/review.ts
|
|
1450
|
+
var tris2 = (a) => a.parts.reduce((s, p) => s + (p.mesh ? p.mesh.indices.length / 3 : 0), 0);
|
|
1451
|
+
function detailWindows2(detail) {
|
|
1452
|
+
const order = [...detail.keys()].sort((a, b) => (detail[b] ?? 0) - (detail[a] ?? 0) || a - b);
|
|
1453
|
+
const picked = [];
|
|
1454
|
+
for (const c of order) {
|
|
1455
|
+
if ((detail[c] ?? 0) === 0 || picked.length === 2)
|
|
1456
|
+
break;
|
|
1457
|
+
if (picked.every((p) => Math.hypot(p % 16 - c % 16, Math.floor(p / 16) - Math.floor(c / 16)) >= 4))
|
|
1458
|
+
picked.push(c);
|
|
1459
|
+
}
|
|
1460
|
+
const clamp = (v) => Math.min(5 / 6, Math.max(1 / 6, v));
|
|
1461
|
+
return picked.map((c) => ({
|
|
1462
|
+
x: clamp((c % 16 + 0.5) / 16),
|
|
1463
|
+
y: clamp((Math.floor(c / 16) + 0.5) / 9)
|
|
1464
|
+
}));
|
|
1465
|
+
}
|
|
1466
|
+
function reviewPack2(asset, o = {}) {
|
|
1467
|
+
const lods = [0, 1, 2].map((lod) => compile2(asset, { lod }));
|
|
1468
|
+
const lod0 = lods[0] ?? compile2(asset);
|
|
1469
|
+
const lod2 = lods[2] ?? lod0;
|
|
1470
|
+
const base = {
|
|
1471
|
+
...o.sheet ? { sheet: o.sheet } : {},
|
|
1472
|
+
...o.width ? { width: o.width } : {},
|
|
1473
|
+
...o.height ? { height: o.height } : {},
|
|
1474
|
+
...o.azDeg !== undefined ? { azDeg: o.azDeg } : {},
|
|
1475
|
+
...o.elDeg !== undefined ? { elDeg: o.elDeg } : {},
|
|
1476
|
+
...o.fovDeg !== undefined ? { fovDeg: o.fovDeg } : {}
|
|
1477
|
+
};
|
|
1478
|
+
const files = new Map;
|
|
1479
|
+
const hero = renderBeauty2(lod0, base);
|
|
1480
|
+
files.set("hero.png", encodePng2(hero.width, hero.height, hero.rgba));
|
|
1481
|
+
files.set("sheet.png", contactSheet2(lod0, { far: lod2 }));
|
|
1482
|
+
detailWindows2(hero.detail).forEach((w, i) => {
|
|
1483
|
+
const r = renderBeauty2(lod0, { ...base, window: { ...w, zoom: 3 } });
|
|
1484
|
+
files.set(`detail_${i + 1}.png`, encodePng2(r.width, r.height, r.rgba));
|
|
1485
|
+
});
|
|
1486
|
+
const k = Math.min(1, (o.width ?? 1600) / 640);
|
|
1487
|
+
const panel = {
|
|
1488
|
+
width: Math.max(16, Math.round(640 * k)),
|
|
1489
|
+
height: Math.max(16, Math.round(480 * k))
|
|
1490
|
+
};
|
|
1491
|
+
files.set("lods.png", lodStrip2(lods, { ...base, ...panel }));
|
|
1492
|
+
const views = VIEWS2.slice(0, 5).map((view) => {
|
|
1493
|
+
const frame = viewFrame2(lod0, view);
|
|
1494
|
+
const a = silhouette2(lod0, view, 256, frame);
|
|
1495
|
+
const b = silhouette2(lod2, view, 256, frame);
|
|
1496
|
+
return {
|
|
1497
|
+
view: view.label,
|
|
1498
|
+
lod0: maskStats2(a),
|
|
1499
|
+
lod2: maskStats2(b),
|
|
1500
|
+
lodIoU: maskIoU2(poolMask2(a, 4).mask, poolMask2(b, 4).mask),
|
|
1501
|
+
lodIoUExact: maskIoU2(a.mask, b.mask)
|
|
1502
|
+
};
|
|
1503
|
+
});
|
|
1504
|
+
const partial = {
|
|
1505
|
+
asset: asset.name,
|
|
1506
|
+
triangles: lods.map(tris2),
|
|
1507
|
+
hero: exposure2(hero.rgba, hero.mask, hero.width),
|
|
1508
|
+
views
|
|
1509
|
+
};
|
|
1510
|
+
const metrics = { ...partial, warnings: warningsOf(partial) };
|
|
1511
|
+
files.set("review.json", `${JSON.stringify(metrics, null, 2)}
|
|
1512
|
+
`);
|
|
1513
|
+
files.set("review.md", reviewPrompt(metrics, [...files.keys()], o.refs));
|
|
1514
|
+
return { files, metrics };
|
|
1515
|
+
}
|
|
1516
|
+
// src/render/thumbnail.ts
|
|
1517
|
+
var HERO = { label: "HERO", dir: [-1, 0.75, -0.45], up: [0, 0, 1] };
|
|
1518
|
+
function thumbnail2(asset, o = {}) {
|
|
1519
|
+
const size = o.size ?? 512;
|
|
1520
|
+
const big = size * 3;
|
|
1521
|
+
const t = {
|
|
1522
|
+
px: new Uint8Array(big * big * 4),
|
|
1523
|
+
depth: new Float32Array(big * big).fill(Infinity),
|
|
1524
|
+
width: big,
|
|
1525
|
+
height: big
|
|
1526
|
+
};
|
|
1527
|
+
const b = bounds(asset);
|
|
1528
|
+
drawAsset(t, orthoCamera(o.view ?? HERO, b.centre, b.extent * 0.92, big), asset);
|
|
1529
|
+
let x0 = big;
|
|
1530
|
+
let y0 = big;
|
|
1531
|
+
let x1 = -1;
|
|
1532
|
+
let y1 = -1;
|
|
1533
|
+
for (let y = 0;y < big; y++)
|
|
1534
|
+
for (let x = 0;x < big; x++)
|
|
1535
|
+
if ((t.px[(y * big + x) * 4 + 3] ?? 0) > 0) {
|
|
1536
|
+
x0 = Math.min(x0, x);
|
|
1537
|
+
y0 = Math.min(y0, y);
|
|
1538
|
+
x1 = Math.max(x1, x);
|
|
1539
|
+
y1 = Math.max(y1, y);
|
|
1540
|
+
}
|
|
1541
|
+
const side = x1 < 0 ? big : Math.max(x1 - x0, y1 - y0) * 1.12 + 1;
|
|
1542
|
+
const ox = x1 < 0 ? 0 : (x0 + x1) / 2 - side / 2;
|
|
1543
|
+
const oy = x1 < 0 ? 0 : (y0 + y1) / 2 - side / 2;
|
|
1544
|
+
const step = side / size;
|
|
1545
|
+
const out = new Uint8Array(size * size * 4);
|
|
1546
|
+
const taps = [0.25, 0.75];
|
|
1547
|
+
for (let y = 0;y < size; y++) {
|
|
1548
|
+
for (let x = 0;x < size; x++) {
|
|
1549
|
+
let r = 0;
|
|
1550
|
+
let g = 0;
|
|
1551
|
+
let bl = 0;
|
|
1552
|
+
let a = 0;
|
|
1553
|
+
for (const fy of taps)
|
|
1554
|
+
for (const fx of taps) {
|
|
1555
|
+
const sx = Math.floor(ox + (x + fx) * step);
|
|
1556
|
+
const sy = Math.floor(oy + (y + fy) * step);
|
|
1557
|
+
if (sx < 0 || sy < 0 || sx >= big || sy >= big)
|
|
1558
|
+
continue;
|
|
1559
|
+
const i = (sy * big + sx) * 4;
|
|
1560
|
+
const w = t.px[i + 3] ?? 0;
|
|
1561
|
+
r += (t.px[i] ?? 0) * w;
|
|
1562
|
+
g += (t.px[i + 1] ?? 0) * w;
|
|
1563
|
+
bl += (t.px[i + 2] ?? 0) * w;
|
|
1564
|
+
a += w;
|
|
1565
|
+
}
|
|
1566
|
+
if (a > 0)
|
|
1567
|
+
out.set([Math.round(r / a), Math.round(g / a), Math.round(bl / a), Math.round(a / 4)], (y * size + x) * 4);
|
|
1568
|
+
}
|
|
1569
|
+
}
|
|
1570
|
+
return encodePng2(size, size, out);
|
|
1571
|
+
}
|
|
1572
|
+
export { checkBeautyOptions2, parseSize2, cameraFor2, renderBeauty2, beauty2, lodStrip2, viewFrame2, silhouette2, maskIoU2, poolMask2, maskStats2, exposure2, photoMatch2, REVIEW_THRESHOLDS2, VIEWS2, contactSheet2, detailWindows2, reviewPack2, thumbnail2 };
|
|
1573
|
+
|
|
1574
|
+
//# debugId=42A245B665257F8A64756E2164756E21
|
|
1575
|
+
//# sourceMappingURL=chunk-11v28977.js.map
|