@developerz.ai/ts23d 0.0.0-stage → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/agents/bug-hunter.md +21 -0
- package/.claude/agents/docs-author.md +13 -0
- package/.claude/agents/dx-auditor.md +14 -0
- package/.claude/agents/primitive-author.md +22 -0
- package/.claude/agents/recipe-author.md +36 -0
- package/.claude/agents/visual-reviewer.md +23 -0
- package/.claude/commands/feature.md +28 -0
- package/.claude/commands/new-recipe.md +19 -0
- package/.claude/commands/release.md +14 -0
- package/.claude/commands/verify.md +9 -0
- package/.claude/skills/author-recipe/SKILL.md +93 -0
- package/CHANGELOG.md +90 -0
- package/CLAUDE.md +85 -0
- package/LICENSE +21 -0
- package/README.md +385 -2
- package/dist/chunk-6b05mnmx.js +19 -0
- package/dist/chunk-6b05mnmx.js.map +11 -0
- package/dist/chunk-7rtfjh9f.js +45 -0
- package/dist/chunk-7rtfjh9f.js.map +10 -0
- package/dist/chunk-8j4c28jt.js +4085 -0
- package/dist/chunk-8j4c28jt.js.map +46 -0
- package/dist/chunk-d1fv6hrm.js +2532 -0
- package/dist/chunk-d1fv6hrm.js.map +43 -0
- package/dist/chunk-dbq4qf52.js +902 -0
- package/dist/chunk-dbq4qf52.js.map +25 -0
- package/dist/chunk-drna2rny.js +836 -0
- package/dist/chunk-drna2rny.js.map +17 -0
- package/dist/chunk-dxvypr7y.js +89 -0
- package/dist/chunk-dxvypr7y.js.map +11 -0
- package/dist/chunk-f6yme003.js +1589 -0
- package/dist/chunk-f6yme003.js.map +30 -0
- package/dist/chunk-hg7pf34j.js +465 -0
- package/dist/chunk-hg7pf34j.js.map +19 -0
- package/dist/chunk-jq4e17tj.js +1031 -0
- package/dist/chunk-jq4e17tj.js.map +21 -0
- package/dist/chunk-y63c6zcv.js +2668 -0
- package/dist/chunk-y63c6zcv.js.map +53 -0
- package/dist/cli/args.d.ts +30 -0
- package/dist/cli/bin.d.ts +2 -0
- package/dist/cli/bin.js +348 -0
- package/dist/cli/bin.js.map +15 -0
- package/dist/cli/load.d.ts +2 -0
- package/dist/cli/manifest.d.ts +53 -0
- package/dist/cli/render-cmds.d.ts +12 -0
- package/dist/cli/run.d.ts +1 -0
- package/dist/cli/texture-cache.d.ts +4 -0
- package/dist/core/asset.d.ts +10 -0
- package/dist/core/definition.d.ts +155 -0
- package/dist/core/errors.d.ts +17 -0
- package/dist/core/family.d.ts +23 -0
- package/dist/core/index.d.ts +9 -0
- package/dist/core/index.js +69 -0
- package/dist/core/index.js.map +9 -0
- package/dist/core/mesh.d.ts +35 -0
- package/dist/core/part.d.ts +114 -0
- package/dist/core/png.d.ts +1 -0
- package/dist/core/rng.d.ts +12 -0
- package/dist/core/transform.d.ts +20 -0
- package/dist/core/vec.d.ts +11 -0
- package/dist/core/world.d.ts +16 -0
- package/dist/export/bin-writer.d.ts +16 -0
- package/dist/export/container.d.ts +3 -0
- package/dist/export/credits.d.ts +4 -0
- package/dist/export/glb-options.d.ts +48 -0
- package/dist/export/glb.d.ts +8 -0
- package/dist/export/hierarchy.d.ts +23 -0
- package/dist/export/index.d.ts +5 -0
- package/dist/export/index.js +40 -0
- package/dist/export/index.js.map +9 -0
- package/dist/export/lights.d.ts +8 -0
- package/dist/export/lod-coverage.d.ts +12 -0
- package/dist/export/materials.d.ts +23 -0
- package/dist/export/merge-pivots.d.ts +6 -0
- package/dist/export/metadata.d.ts +21 -0
- package/dist/export/motion.d.ts +9 -0
- package/dist/export/optimize-parts.d.ts +26 -0
- package/dist/export/primitive.d.ts +3 -0
- package/dist/export/tangents.d.ts +2 -0
- package/dist/export/textures.d.ts +22 -0
- package/dist/export/version.d.ts +2 -0
- package/dist/grammar/index.d.ts +2 -0
- package/dist/grammar/index.js +16 -0
- package/dist/grammar/index.js.map +9 -0
- package/dist/grammar/split.d.ts +21 -0
- package/dist/grammar/whyte.d.ts +5 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +457 -0
- package/dist/index.js.map +9 -0
- package/dist/kinds/building.d.ts +44 -0
- package/dist/kinds/faces.d.ts +27 -0
- package/dist/kinds/head-armour.d.ts +5 -0
- package/dist/kinds/head-beard.d.ts +6 -0
- package/dist/kinds/head-bust.d.ts +5 -0
- package/dist/kinds/head-extras.d.ts +15 -0
- package/dist/kinds/head-face.d.ts +8 -0
- package/dist/kinds/head-garb.d.ts +4 -0
- package/dist/kinds/head-geom.d.ts +7 -0
- package/dist/kinds/head-hair.d.ts +4 -0
- package/dist/kinds/head-layout.d.ts +29 -0
- package/dist/kinds/head-mesh.d.ts +31 -0
- package/dist/kinds/head-mouth.d.ts +4 -0
- package/dist/kinds/head-params.d.ts +116 -0
- package/dist/kinds/head-presets.d.ts +19 -0
- package/dist/kinds/head-rim.d.ts +10 -0
- package/dist/kinds/head-tone.d.ts +10 -0
- package/dist/kinds/head-torso.d.ts +35 -0
- package/dist/kinds/head.d.ts +39 -0
- package/dist/kinds/hull-lines.d.ts +65 -0
- package/dist/kinds/hull-skin.d.ts +6 -0
- package/dist/kinds/hull.d.ts +14 -0
- package/dist/kinds/index.d.ts +16 -0
- package/dist/kinds/index.js +78 -0
- package/dist/kinds/index.js.map +9 -0
- package/dist/kinds/mass.d.ts +15 -0
- package/dist/kinds/openings.d.ts +30 -0
- package/dist/kinds/planned.d.ts +6 -0
- package/dist/kinds/roof-chimney.d.ts +16 -0
- package/dist/kinds/roof-shape.d.ts +64 -0
- package/dist/kinds/roof.d.ts +31 -0
- package/dist/kinds/vegetation-fronds.d.ts +15 -0
- package/dist/kinds/vegetation-impostor.d.ts +9 -0
- package/dist/kinds/vegetation-leaves.d.ts +15 -0
- package/dist/kinds/vegetation-mesh.d.ts +21 -0
- package/dist/kinds/vegetation-params.d.ts +90 -0
- package/dist/kinds/vegetation-presets.d.ts +20 -0
- package/dist/kinds/vegetation-skeleton.d.ts +20 -0
- package/dist/kinds/vegetation.d.ts +42 -0
- package/dist/kinds/vehicle.d.ts +42 -0
- package/dist/material/cc0.d.ts +4 -0
- package/dist/material/define.d.ts +34 -0
- package/dist/material/generator.d.ts +4 -0
- package/dist/material/index.d.ts +9 -0
- package/dist/material/index.js +70 -0
- package/dist/material/index.js.map +9 -0
- package/dist/material/library.d.ts +7 -0
- package/dist/material/material-def.d.ts +30 -0
- package/dist/material/paint/bond.d.ts +3 -0
- package/dist/material/paint/canvas.d.ts +39 -0
- package/dist/material/paint/cell.d.ts +4 -0
- package/dist/material/paint/cells-round.d.ts +5 -0
- package/dist/material/paint/cells.d.ts +7 -0
- package/dist/material/paint/compose.d.ts +5 -0
- package/dist/material/paint/concrete.d.ts +3 -0
- package/dist/material/paint/courses.d.ts +31 -0
- package/dist/material/paint/decode-png.d.ts +6 -0
- package/dist/material/paint/default-sheet.d.ts +4 -0
- package/dist/material/paint/fabric.d.ts +4 -0
- package/dist/material/paint/foliage-broad.d.ts +5 -0
- package/dist/material/paint/foliage-common.d.ts +35 -0
- package/dist/material/paint/foliage-compound.d.ts +4 -0
- package/dist/material/paint/foliage-conifer.d.ts +5 -0
- package/dist/material/paint/foliage-raster.d.ts +26 -0
- package/dist/material/paint/foliage.d.ts +3 -0
- package/dist/material/paint/image-strip.d.ts +4 -0
- package/dist/material/paint/kit.d.ts +5 -0
- package/dist/material/paint/masks.d.ts +3 -0
- package/dist/material/paint/metal.d.ts +3 -0
- package/dist/material/paint/noise.d.ts +9 -0
- package/dist/material/paint/normal.d.ts +8 -0
- package/dist/material/paint/orm.d.ts +8 -0
- package/dist/material/paint/planks.d.ts +3 -0
- package/dist/material/paint/plates.d.ts +3 -0
- package/dist/material/paint/shapes.d.ts +8 -0
- package/dist/material/paint/slate.d.ts +3 -0
- package/dist/material/paint/stamp.d.ts +3 -0
- package/dist/material/paint/strip.d.ts +3 -0
- package/dist/material/paint/surface.d.ts +5 -0
- package/dist/material/paint/types.d.ts +65 -0
- package/dist/material/paint/validate.d.ts +3 -0
- package/dist/material/palette.d.ts +14 -0
- package/dist/material/shader-godot.d.ts +2 -0
- package/dist/material/shader-three.d.ts +2 -0
- package/dist/material/shader.d.ts +46 -0
- package/dist/material/slots.d.ts +5 -0
- package/dist/material/texture-source.d.ts +34 -0
- package/dist/material/trim-sheet.d.ts +38 -0
- package/dist/neural/generator.d.ts +25 -0
- package/dist/neural/images.d.ts +17 -0
- package/dist/neural/index.d.ts +2 -0
- package/dist/neural/index.js +14 -0
- package/dist/neural/index.js.map +9 -0
- package/dist/passes/bake-ao.d.ts +17 -0
- package/dist/passes/grime.d.ts +15 -0
- package/dist/passes/index.d.ts +6 -0
- package/dist/passes/index.js +22 -0
- package/dist/passes/index.js.map +9 -0
- package/dist/passes/livery.d.ts +19 -0
- package/dist/passes/optimize-cache.d.ts +15 -0
- package/dist/passes/optimize-weld.d.ts +15 -0
- package/dist/passes/optimize.d.ts +28 -0
- package/dist/passes/ray-grid.d.ts +23 -0
- package/dist/passes/weighted-normals.d.ts +8 -0
- package/dist/primitives/beam.d.ts +17 -0
- package/dist/primitives/bevel-box.d.ts +13 -0
- package/dist/primitives/box.d.ts +16 -0
- package/dist/primitives/cell.d.ts +20 -0
- package/dist/primitives/check.d.ts +12 -0
- package/dist/primitives/closed.d.ts +11 -0
- package/dist/primitives/erosion.d.ts +22 -0
- package/dist/primitives/font-5x7.d.ts +3 -0
- package/dist/primitives/heightfield.d.ts +16 -0
- package/dist/primitives/index.d.ts +23 -0
- package/dist/primitives/index.js +81 -0
- package/dist/primitives/index.js.map +9 -0
- package/dist/primitives/lathe-facets.d.ts +8 -0
- package/dist/primitives/lathe.d.ts +26 -0
- package/dist/primitives/lattice.d.ts +30 -0
- package/dist/primitives/loft.d.ts +15 -0
- package/dist/primitives/mc-table.d.ts +2 -0
- package/dist/primitives/member.d.ts +19 -0
- package/dist/primitives/noise.d.ts +34 -0
- package/dist/primitives/planar.d.ts +10 -0
- package/dist/primitives/prism.d.ts +13 -0
- package/dist/primitives/sculpt-edit.d.ts +56 -0
- package/dist/primitives/sculpt-tree.d.ts +23 -0
- package/dist/primitives/sculpt.d.ts +27 -0
- package/dist/primitives/sdf-grid.d.ts +17 -0
- package/dist/primitives/sdf-ops.d.ts +37 -0
- package/dist/primitives/sdf.d.ts +21 -0
- package/dist/primitives/strip-uv.d.ts +11 -0
- package/dist/primitives/surface-nets.d.ts +7 -0
- package/dist/primitives/sweep.d.ts +19 -0
- package/dist/primitives/terrain-color.d.ts +30 -0
- package/dist/primitives/terrain-grid.d.ts +36 -0
- package/dist/primitives/text-layout.d.ts +22 -0
- package/dist/primitives/text.d.ts +27 -0
- package/dist/primitives/tiled.d.ts +22 -0
- package/dist/primitives/transport.d.ts +17 -0
- package/dist/primitives/triangulate.d.ts +2 -0
- package/dist/primitives/value-noise.d.ts +10 -0
- package/dist/qa/checks.d.ts +11 -0
- package/dist/qa/clash.d.ts +2 -0
- package/dist/qa/floating.d.ts +5 -0
- package/dist/qa/gates.d.ts +12 -0
- package/dist/qa/geometry.d.ts +21 -0
- package/dist/qa/index.d.ts +12 -0
- package/dist/qa/index.js +78 -0
- package/dist/qa/index.js.map +9 -0
- package/dist/qa/inside.d.ts +12 -0
- package/dist/qa/inspect.d.ts +18 -0
- package/dist/qa/lods.d.ts +12 -0
- package/dist/qa/measures.d.ts +4 -0
- package/dist/qa/occlusion.d.ts +3 -0
- package/dist/qa/sanity.d.ts +7 -0
- package/dist/qa/types.d.ts +12 -0
- package/dist/render/beauty.d.ts +40 -0
- package/dist/render/camera.d.ts +33 -0
- package/dist/render/depth-map.d.ts +38 -0
- package/dist/render/font.d.ts +1 -0
- package/dist/render/ground.d.ts +13 -0
- package/dist/render/index.d.ts +10 -0
- package/dist/render/index.js +47 -0
- package/dist/render/index.js.map +9 -0
- package/dist/render/lighting.d.ts +27 -0
- package/dist/render/lod-strip.d.ts +5 -0
- package/dist/render/metrics.d.ts +37 -0
- package/dist/render/options.d.ts +28 -0
- package/dist/render/photo-match.d.ts +19 -0
- package/dist/render/raster.d.ts +25 -0
- package/dist/render/resolve.d.ts +11 -0
- package/dist/render/review-prompt.d.ts +2 -0
- package/dist/render/review-warnings.d.ts +34 -0
- package/dist/render/review.d.ts +24 -0
- package/dist/render/scene.d.ts +18 -0
- package/dist/render/shade.d.ts +22 -0
- package/dist/render/sheet.d.ts +15 -0
- package/dist/render/texture.d.ts +15 -0
- package/dist/render/thumbnail.d.ts +8 -0
- package/dist/render/vis-buffer.d.ts +18 -0
- package/dist/testing/assert.d.ts +21 -0
- package/dist/testing/compiled.d.ts +15 -0
- package/dist/testing/hash.d.ts +1 -0
- package/dist/testing/index.d.ts +4 -0
- package/dist/testing/index.js +134 -0
- package/dist/testing/index.js.map +13 -0
- package/dist/testing/snapshot.d.ts +13 -0
- package/docs/README.md +56 -0
- package/docs/architecture/README.md +68 -0
- package/docs/architecture/adding-a-kind.md +44 -0
- package/docs/architecture/adding-a-primitive.md +49 -0
- package/docs/architecture/errors.md +47 -0
- package/docs/architecture/glb-production.md +214 -0
- package/docs/architecture/gltf-capabilities.md +117 -0
- package/docs/architecture/materials.md +151 -0
- package/docs/decisions/001-recipe-compiler-over-ai-meshes.md +27 -0
- package/docs/decisions/002-typescript-not-json.md +22 -0
- package/docs/decisions/003-shared-trim-sheet.md +21 -0
- package/docs/decisions/004-z-up-metres.md +21 -0
- package/docs/decisions/005-zero-runtime-deps.md +21 -0
- package/docs/decisions/006-single-package-subpath-exports.md +20 -0
- package/docs/decisions/007-cpu-contact-sheets.md +21 -0
- package/docs/decisions/008-surface-materials.md +21 -0
- package/docs/decisions/009-gltf-front-axis.md +21 -0
- package/docs/decisions/README.md +15 -0
- package/docs/idea/01-vision.md +117 -0
- package/docs/idea/02-dsl.md +343 -0
- package/docs/idea/03-ir.md +97 -0
- package/docs/idea/04-materials.md +114 -0
- package/docs/idea/05-agent-loop.md +141 -0
- package/docs/idea/06-qa.md +101 -0
- package/docs/idea/07-what-fits.md +45 -0
- package/docs/idea/08-neural-adapters.md +78 -0
- package/docs/idea/09-kinds.md +105 -0
- package/docs/idea/10-sculpting.md +166 -0
- package/docs/idea/11-materials-authoring.md +132 -0
- package/docs/idea/12-blender-bridge.md +136 -0
- package/docs/idea/13-asset-pipeline-research.md +89 -0
- package/docs/landscape.md +49 -0
- package/docs/plans/roadmap.md +139 -0
- package/examples/build-with-bun.ts +41 -0
- package/llms.txt +47 -0
- package/package.json +100 -4
- package/recipes/README.md +38 -0
- package/recipes/asteroid/README.md +46 -0
- package/recipes/asteroid/asteroid.ts +336 -0
- package/recipes/boulders/README.md +28 -0
- package/recipes/boulders/boulders.ts +117 -0
- package/recipes/boxcar/README.md +53 -0
- package/recipes/boxcar/boxcar.ts +1000 -0
- package/recipes/cargo_container_scifi/README.md +52 -0
- package/recipes/cargo_container_scifi/cargo_container_scifi.ts +461 -0
- package/recipes/dropship/README.md +84 -0
- package/recipes/dropship/dropship.ts +1144 -0
- package/recipes/eiffel_tower/README.md +110 -0
- package/recipes/eiffel_tower/eiffel_tower.ts +781 -0
- package/recipes/goblin_head/README.md +75 -0
- package/recipes/goblin_head/goblin_head.ts +38 -0
- package/recipes/human_head/README.md +68 -0
- package/recipes/human_head/human_head.ts +38 -0
- package/recipes/loco_440/README.md +32 -0
- package/recipes/loco_440/loco_440.ts +1404 -0
- package/recipes/material_swatches/README.md +21 -0
- package/recipes/material_swatches/material_swatches.ts +189 -0
- package/recipes/mountain_range/README.md +45 -0
- package/recipes/mountain_range/mountain_range.ts +128 -0
- package/recipes/oak/README.md +30 -0
- package/recipes/oak/oak.ts +19 -0
- package/recipes/orc_head/README.md +78 -0
- package/recipes/orc_head/orc_head.ts +41 -0
- package/recipes/palm/README.md +30 -0
- package/recipes/palm/palm.ts +19 -0
- package/recipes/pine/README.md +29 -0
- package/recipes/pine/pine.ts +19 -0
- package/recipes/signal_gantry/README.md +36 -0
- package/recipes/signal_gantry/signal_gantry.ts +522 -0
- package/recipes/station_halt/README.md +39 -0
- package/recipes/station_halt/station_halt.ts +722 -0
- package/recipes/t34/README.md +44 -0
- package/recipes/t34/t34.ts +1068 -0
- package/recipes/tiger_1/README.md +75 -0
- package/recipes/tiger_1/tiger_1.ts +1370 -0
- package/recipes/water_tower/README.md +40 -0
- package/recipes/water_tower/water_tower.ts +542 -0
- package/src/cli/args.ts +125 -0
- package/src/cli/bin.ts +7 -0
- package/src/cli/load.ts +17 -0
- package/src/cli/manifest.ts +95 -0
- package/src/cli/render-cmds.ts +66 -0
- package/src/cli/run.ts +192 -0
- package/src/cli/texture-cache.ts +24 -0
- package/src/core/asset.ts +129 -0
- package/src/core/definition.ts +156 -0
- package/src/core/errors.ts +50 -0
- package/src/core/family.ts +50 -0
- package/src/core/index.ts +66 -0
- package/src/core/mesh.ts +114 -0
- package/src/core/part.ts +130 -0
- package/src/core/png.ts +49 -0
- package/src/core/rng.ts +49 -0
- package/src/core/transform.ts +90 -0
- package/src/core/vec.ts +26 -0
- package/src/core/world.ts +80 -0
- package/src/export/bin-writer.ts +79 -0
- package/src/export/container.ts +35 -0
- package/src/export/credits.ts +25 -0
- package/src/export/glb-options.ts +53 -0
- package/src/export/glb.ts +187 -0
- package/src/export/hierarchy.ts +122 -0
- package/src/export/index.ts +34 -0
- package/src/export/lights.ts +49 -0
- package/src/export/lod-coverage.ts +71 -0
- package/src/export/materials.ts +126 -0
- package/src/export/merge-pivots.ts +146 -0
- package/src/export/metadata.ts +62 -0
- package/src/export/motion.ts +70 -0
- package/src/export/optimize-parts.ts +80 -0
- package/src/export/primitive.ts +35 -0
- package/src/export/tangents.ts +75 -0
- package/src/export/textures.ts +88 -0
- package/src/export/version.ts +6 -0
- package/src/grammar/index.ts +4 -0
- package/src/grammar/split.ts +87 -0
- package/src/grammar/whyte.ts +20 -0
- package/src/index.ts +14 -0
- package/src/kinds/building.ts +153 -0
- package/src/kinds/faces.ts +138 -0
- package/src/kinds/head-armour.ts +181 -0
- package/src/kinds/head-beard.ts +133 -0
- package/src/kinds/head-bust.ts +184 -0
- package/src/kinds/head-extras.ts +150 -0
- package/src/kinds/head-face.ts +242 -0
- package/src/kinds/head-garb.ts +181 -0
- package/src/kinds/head-geom.ts +31 -0
- package/src/kinds/head-hair.ts +193 -0
- package/src/kinds/head-layout.ts +63 -0
- package/src/kinds/head-mesh.ts +195 -0
- package/src/kinds/head-mouth.ts +111 -0
- package/src/kinds/head-params.ts +162 -0
- package/src/kinds/head-presets.ts +155 -0
- package/src/kinds/head-rim.ts +128 -0
- package/src/kinds/head-tone.ts +52 -0
- package/src/kinds/head-torso.ts +230 -0
- package/src/kinds/head.ts +176 -0
- package/src/kinds/hull-lines.ts +160 -0
- package/src/kinds/hull-skin.ts +83 -0
- package/src/kinds/hull.ts +209 -0
- package/src/kinds/index.ts +63 -0
- package/src/kinds/mass.ts +110 -0
- package/src/kinds/openings.ts +141 -0
- package/src/kinds/planned.ts +12 -0
- package/src/kinds/roof-chimney.ts +100 -0
- package/src/kinds/roof-shape.ts +230 -0
- package/src/kinds/roof.ts +250 -0
- package/src/kinds/vegetation-fronds.ts +126 -0
- package/src/kinds/vegetation-impostor.ts +118 -0
- package/src/kinds/vegetation-leaves.ts +168 -0
- package/src/kinds/vegetation-mesh.ts +174 -0
- package/src/kinds/vegetation-params.ts +147 -0
- package/src/kinds/vegetation-presets.ts +184 -0
- package/src/kinds/vegetation-skeleton.ts +168 -0
- package/src/kinds/vegetation.ts +199 -0
- package/src/kinds/vehicle.ts +144 -0
- package/src/material/cc0.ts +41 -0
- package/src/material/define.ts +130 -0
- package/src/material/generator.ts +63 -0
- package/src/material/index.ts +60 -0
- package/src/material/library.ts +196 -0
- package/src/material/material-def.ts +84 -0
- package/src/material/paint/bond.ts +60 -0
- package/src/material/paint/canvas.ts +101 -0
- package/src/material/paint/cell.ts +39 -0
- package/src/material/paint/cells-round.ts +119 -0
- package/src/material/paint/cells.ts +111 -0
- package/src/material/paint/compose.ts +53 -0
- package/src/material/paint/concrete.ts +62 -0
- package/src/material/paint/courses.ts +85 -0
- package/src/material/paint/decode-png.ts +106 -0
- package/src/material/paint/default-sheet.ts +73 -0
- package/src/material/paint/fabric.ts +63 -0
- package/src/material/paint/foliage-broad.ts +126 -0
- package/src/material/paint/foliage-common.ts +93 -0
- package/src/material/paint/foliage-compound.ts +135 -0
- package/src/material/paint/foliage-conifer.ts +143 -0
- package/src/material/paint/foliage-raster.ts +196 -0
- package/src/material/paint/foliage.ts +23 -0
- package/src/material/paint/image-strip.ts +88 -0
- package/src/material/paint/kit.ts +37 -0
- package/src/material/paint/masks.ts +46 -0
- package/src/material/paint/metal.ts +109 -0
- package/src/material/paint/noise.ts +52 -0
- package/src/material/paint/normal.ts +52 -0
- package/src/material/paint/orm.ts +108 -0
- package/src/material/paint/planks.ts +101 -0
- package/src/material/paint/plates.ts +72 -0
- package/src/material/paint/shapes.ts +28 -0
- package/src/material/paint/slate.ts +52 -0
- package/src/material/paint/stamp.ts +40 -0
- package/src/material/paint/strip.ts +48 -0
- package/src/material/paint/surface.ts +60 -0
- package/src/material/paint/types.ts +94 -0
- package/src/material/paint/validate.ts +74 -0
- package/src/material/palette.ts +46 -0
- package/src/material/shader-godot.ts +77 -0
- package/src/material/shader-three.ts +103 -0
- package/src/material/shader.ts +100 -0
- package/src/material/slots.ts +18 -0
- package/src/material/texture-source.ts +86 -0
- package/src/material/trim-sheet.ts +166 -0
- package/src/neural/generator.ts +37 -0
- package/src/neural/images.ts +27 -0
- package/src/neural/index.ts +10 -0
- package/src/passes/bake-ao.ts +183 -0
- package/src/passes/grime.ts +37 -0
- package/src/passes/index.ts +8 -0
- package/src/passes/livery.ts +66 -0
- package/src/passes/optimize-cache.ts +175 -0
- package/src/passes/optimize-weld.ts +147 -0
- package/src/passes/optimize.ts +85 -0
- package/src/passes/ray-grid.ts +237 -0
- package/src/passes/weighted-normals.ts +125 -0
- package/src/primitives/beam.ts +60 -0
- package/src/primitives/bevel-box.ts +140 -0
- package/src/primitives/box.ts +90 -0
- package/src/primitives/cell.ts +115 -0
- package/src/primitives/check.ts +49 -0
- package/src/primitives/closed.ts +74 -0
- package/src/primitives/erosion.ts +183 -0
- package/src/primitives/font-5x7.ts +117 -0
- package/src/primitives/heightfield.ts +101 -0
- package/src/primitives/index.ts +56 -0
- package/src/primitives/lathe-facets.ts +45 -0
- package/src/primitives/lathe.ts +174 -0
- package/src/primitives/lattice.ts +131 -0
- package/src/primitives/loft.ts +174 -0
- package/src/primitives/mc-table.ts +41 -0
- package/src/primitives/member.ts +130 -0
- package/src/primitives/noise.ts +157 -0
- package/src/primitives/planar.ts +55 -0
- package/src/primitives/prism.ts +85 -0
- package/src/primitives/sculpt-edit.ts +212 -0
- package/src/primitives/sculpt-tree.ts +226 -0
- package/src/primitives/sculpt.ts +80 -0
- package/src/primitives/sdf-grid.ts +131 -0
- package/src/primitives/sdf-ops.ts +145 -0
- package/src/primitives/sdf.ts +83 -0
- package/src/primitives/strip-uv.ts +26 -0
- package/src/primitives/surface-nets.ts +161 -0
- package/src/primitives/sweep.ts +162 -0
- package/src/primitives/terrain-color.ts +85 -0
- package/src/primitives/terrain-grid.ts +137 -0
- package/src/primitives/text-layout.ts +85 -0
- package/src/primitives/text.ts +197 -0
- package/src/primitives/tiled.ts +93 -0
- package/src/primitives/transport.ts +85 -0
- package/src/primitives/triangulate.ts +42 -0
- package/src/primitives/value-noise.ts +62 -0
- package/src/qa/checks.ts +110 -0
- package/src/qa/clash.ts +177 -0
- package/src/qa/floating.ts +63 -0
- package/src/qa/gates.ts +126 -0
- package/src/qa/geometry.ts +187 -0
- package/src/qa/index.ts +28 -0
- package/src/qa/inside.ts +114 -0
- package/src/qa/inspect.ts +107 -0
- package/src/qa/lods.ts +97 -0
- package/src/qa/measures.ts +67 -0
- package/src/qa/occlusion.ts +166 -0
- package/src/qa/sanity.ts +137 -0
- package/src/qa/types.ts +17 -0
- package/src/render/beauty.ts +115 -0
- package/src/render/camera.ts +142 -0
- package/src/render/depth-map.ts +191 -0
- package/src/render/font.ts +76 -0
- package/src/render/ground.ts +87 -0
- package/src/render/index.ts +34 -0
- package/src/render/lighting.ts +121 -0
- package/src/render/lod-strip.ts +34 -0
- package/src/render/metrics.ts +147 -0
- package/src/render/options.ts +81 -0
- package/src/render/photo-match.ts +28 -0
- package/src/render/raster.ts +143 -0
- package/src/render/resolve.ts +109 -0
- package/src/render/review-prompt.ts +59 -0
- package/src/render/review-warnings.ts +92 -0
- package/src/render/review.ts +103 -0
- package/src/render/scene.ts +119 -0
- package/src/render/shade.ts +204 -0
- package/src/render/sheet.ts +88 -0
- package/src/render/texture.ts +97 -0
- package/src/render/thumbnail.ts +75 -0
- package/src/render/vis-buffer.ts +119 -0
- package/src/testing/assert.ts +115 -0
- package/src/testing/compiled.ts +31 -0
- package/src/testing/hash.ts +14 -0
- package/src/testing/index.ts +14 -0
- package/src/testing/snapshot.ts +37 -0
- package/src/types/gltf-validator.d.ts +13 -0
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
// Face-weighted vertex normals with a hard-edge split: the mid-poly hard-surface look. Each corner
|
|
2
|
+
// averages the face normals around its position, weighted by face area × corner angle, but only
|
|
3
|
+
// over faces within `hardAngle` of its own face. Big flat faces dominate, so a bevelled box keeps
|
|
4
|
+
// dead-flat sides while its bevels bend the light into soft highlights — no normal map needed.
|
|
5
|
+
|
|
6
|
+
import type { Mesh, Vec3 } from '../core/index.ts';
|
|
7
|
+
|
|
8
|
+
export interface WeightedNormalsOptions {
|
|
9
|
+
/** Faces meeting at more than this many degrees keep a hard edge. Default 40. */
|
|
10
|
+
readonly hardAngle?: number;
|
|
11
|
+
/** Positions closer than this (metres) count as one point. Default 1e-5. */
|
|
12
|
+
readonly epsilon?: number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface Corner {
|
|
16
|
+
readonly n: Vec3;
|
|
17
|
+
readonly w: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function faceData(m: Mesh, t: number): { n: Vec3; area: number; angles: Vec3 } {
|
|
21
|
+
const p = (k: number): Vec3 => {
|
|
22
|
+
const i = (m.indices[t * 3 + k] ?? 0) * 3;
|
|
23
|
+
return [m.positions[i] ?? 0, m.positions[i + 1] ?? 0, m.positions[i + 2] ?? 0];
|
|
24
|
+
};
|
|
25
|
+
const v = [p(0), p(1), p(2)] as const;
|
|
26
|
+
const e = (a: Vec3, b: Vec3): Vec3 => [b[0] - a[0], b[1] - a[1], b[2] - a[2]];
|
|
27
|
+
const u = e(v[0], v[1]);
|
|
28
|
+
const w = e(v[0], v[2]);
|
|
29
|
+
const g: Vec3 = [u[1] * w[2] - u[2] * w[1], u[2] * w[0] - u[0] * w[2], u[0] * w[1] - u[1] * w[0]];
|
|
30
|
+
const len = Math.hypot(...g);
|
|
31
|
+
const angle = (k: number): number => {
|
|
32
|
+
const a = e(v[k] ?? v[0], v[(k + 1) % 3] ?? v[0]);
|
|
33
|
+
const b = e(v[k] ?? v[0], v[(k + 2) % 3] ?? v[0]);
|
|
34
|
+
const d = Math.hypot(...a) * Math.hypot(...b);
|
|
35
|
+
return d
|
|
36
|
+
? Math.acos(Math.max(-1, Math.min(1, (a[0] * b[0] + a[1] * b[1] + a[2] * b[2]) / d)))
|
|
37
|
+
: 0;
|
|
38
|
+
};
|
|
39
|
+
return {
|
|
40
|
+
n: len ? [g[0] / len, g[1] / len, g[2] / len] : [0, 0, 0],
|
|
41
|
+
area: len / 2,
|
|
42
|
+
angles: [angle(0), angle(1), angle(2)],
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function weightedNormals(m: Mesh, o: WeightedNormalsOptions = {}): Mesh {
|
|
47
|
+
const cosHard = Math.cos(((o.hardAngle ?? 40) * Math.PI) / 180);
|
|
48
|
+
const eps = o.epsilon ?? 1e-5;
|
|
49
|
+
const tris = m.indices.length / 3;
|
|
50
|
+
const corners: Corner[] = [];
|
|
51
|
+
for (let t = 0; t < tris; t++) {
|
|
52
|
+
const f = faceData(m, t);
|
|
53
|
+
for (let k = 0; k < 3; k++) corners.push({ n: f.n, w: f.area * (f.angles[k] ?? 0) });
|
|
54
|
+
}
|
|
55
|
+
// Group corners by quantised position (insertion order — deterministic).
|
|
56
|
+
const groups = new Map<string, number[]>();
|
|
57
|
+
const keyOf = (i: number): string =>
|
|
58
|
+
[0, 1, 2].map((k) => Math.round((m.positions[i * 3 + k] ?? 0) / eps)).join(',');
|
|
59
|
+
for (let c = 0; c < corners.length; c++) {
|
|
60
|
+
const key = keyOf(m.indices[c] ?? 0);
|
|
61
|
+
const g = groups.get(key);
|
|
62
|
+
if (g) g.push(c);
|
|
63
|
+
else groups.set(key, [c]);
|
|
64
|
+
}
|
|
65
|
+
const normal = new Float32Array(corners.length * 3);
|
|
66
|
+
for (const g of groups.values())
|
|
67
|
+
for (const c of g) {
|
|
68
|
+
const me = corners[c];
|
|
69
|
+
if (!me) continue;
|
|
70
|
+
let x = 0;
|
|
71
|
+
let y = 0;
|
|
72
|
+
let z = 0;
|
|
73
|
+
for (const d of g) {
|
|
74
|
+
const o2 = corners[d];
|
|
75
|
+
if (!o2 || me.n[0] * o2.n[0] + me.n[1] * o2.n[1] + me.n[2] * o2.n[2] < cosHard) continue;
|
|
76
|
+
x += o2.n[0] * o2.w;
|
|
77
|
+
y += o2.n[1] * o2.w;
|
|
78
|
+
z += o2.n[2] * o2.w;
|
|
79
|
+
}
|
|
80
|
+
const len = Math.hypot(x, y, z);
|
|
81
|
+
// A degenerate face keeps its original normal.
|
|
82
|
+
const src = (m.indices[c] ?? 0) * 3;
|
|
83
|
+
normal.set(
|
|
84
|
+
len > 1e-12
|
|
85
|
+
? [x / len, y / len, z / len]
|
|
86
|
+
: [m.normals[src] ?? 0, m.normals[src + 1] ?? 0, m.normals[src + 2] ?? 0],
|
|
87
|
+
c * 3,
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
return splitByNormal(m, normal);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** One output vertex per (input vertex, distinct new normal); first appearance order. */
|
|
94
|
+
function splitByNormal(m: Mesh, cornerNormals: Float32Array): Mesh {
|
|
95
|
+
const seen = new Map<string, number>();
|
|
96
|
+
const p: number[] = [];
|
|
97
|
+
const n: number[] = [];
|
|
98
|
+
const uv: number[] = [];
|
|
99
|
+
const col: number[] = [];
|
|
100
|
+
const idx = new Uint32Array(m.indices.length);
|
|
101
|
+
for (let c = 0; c < m.indices.length; c++) {
|
|
102
|
+
const v = m.indices[c] ?? 0;
|
|
103
|
+
const nx = cornerNormals[c * 3] ?? 0;
|
|
104
|
+
const ny = cornerNormals[c * 3 + 1] ?? 0;
|
|
105
|
+
const nz = cornerNormals[c * 3 + 2] ?? 0;
|
|
106
|
+
const key = `${v}:${Math.round(nx * 1e4)},${Math.round(ny * 1e4)},${Math.round(nz * 1e4)}`;
|
|
107
|
+
let out = seen.get(key);
|
|
108
|
+
if (out === undefined) {
|
|
109
|
+
out = p.length / 3;
|
|
110
|
+
seen.set(key, out);
|
|
111
|
+
p.push(m.positions[v * 3] ?? 0, m.positions[v * 3 + 1] ?? 0, m.positions[v * 3 + 2] ?? 0);
|
|
112
|
+
n.push(nx, ny, nz);
|
|
113
|
+
uv.push(m.uvs[v * 2] ?? 0, m.uvs[v * 2 + 1] ?? 0);
|
|
114
|
+
col.push(m.colors[v * 3] ?? 0, m.colors[v * 3 + 1] ?? 0, m.colors[v * 3 + 2] ?? 0);
|
|
115
|
+
}
|
|
116
|
+
idx[c] = out;
|
|
117
|
+
}
|
|
118
|
+
return {
|
|
119
|
+
positions: new Float32Array(p),
|
|
120
|
+
normals: new Float32Array(n),
|
|
121
|
+
uvs: new Float32Array(uv),
|
|
122
|
+
colors: new Float32Array(col),
|
|
123
|
+
indices: idx,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// A member between two points: girders, rods, rails, handrails, masts, tow cables, antennae.
|
|
2
|
+
// Square section by default; `segments` ≥ 3 makes it round (pipes, barrels, rods).
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
applyAffine,
|
|
6
|
+
cross,
|
|
7
|
+
frame,
|
|
8
|
+
length,
|
|
9
|
+
type Mesh,
|
|
10
|
+
normalize,
|
|
11
|
+
sub,
|
|
12
|
+
type Vec3,
|
|
13
|
+
} from '../core/index.ts';
|
|
14
|
+
import type { StripId } from '../material/index.ts';
|
|
15
|
+
import { box } from './box.ts';
|
|
16
|
+
import { lathe } from './lathe.ts';
|
|
17
|
+
|
|
18
|
+
export interface BeamOptions {
|
|
19
|
+
readonly from: Vec3;
|
|
20
|
+
readonly to: Vec3;
|
|
21
|
+
/** Side of a square section, or diameter of a round one. */
|
|
22
|
+
readonly width: number;
|
|
23
|
+
readonly height?: number;
|
|
24
|
+
readonly segments?: number;
|
|
25
|
+
readonly color: Vec3;
|
|
26
|
+
readonly strip?: StripId;
|
|
27
|
+
/** Which way the section's local Y faces (roll). Default: world Z, or X for vertical beams. */
|
|
28
|
+
readonly up?: Vec3;
|
|
29
|
+
/** End caps; turn off for lattice members whose ends are buried in joints (−⅓ triangles). */
|
|
30
|
+
readonly caps?: boolean;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function beam(o: BeamOptions): Mesh {
|
|
34
|
+
const dir = sub(o.to, o.from);
|
|
35
|
+
const len = length(dir);
|
|
36
|
+
const w = normalize(dir);
|
|
37
|
+
const ref: Vec3 = o.up ?? (Math.abs(w[2]) > 0.95 ? [1, 0, 0] : [0, 0, 1]);
|
|
38
|
+
const u = normalize(cross(ref, w));
|
|
39
|
+
const v = cross(w, u);
|
|
40
|
+
// Local Z runs from→to, so the section is built standing on the origin.
|
|
41
|
+
const local =
|
|
42
|
+
o.segments && o.segments >= 3
|
|
43
|
+
? lathe({
|
|
44
|
+
profile: [
|
|
45
|
+
[o.width / 2, 0],
|
|
46
|
+
[o.width / 2, len],
|
|
47
|
+
],
|
|
48
|
+
segments: o.segments,
|
|
49
|
+
color: o.color,
|
|
50
|
+
caps: o.caps ?? true,
|
|
51
|
+
...(o.strip ? { strip: o.strip } : {}),
|
|
52
|
+
})
|
|
53
|
+
: box({
|
|
54
|
+
size: [o.width, o.height ?? o.width, len],
|
|
55
|
+
...(o.caps === false ? { omit: ['top', 'bottom'] as const } : {}),
|
|
56
|
+
color: o.color,
|
|
57
|
+
...(o.strip ? { strip: o.strip } : {}),
|
|
58
|
+
});
|
|
59
|
+
return applyAffine(local, frame(u, v, w, o.from));
|
|
60
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
// A box with every edge chamfered once (`box({ bevel })`): the hard-surface "mid-poly" look. Six
|
|
2
|
+
// faces shrink by `bevel`, 12 chamfer quads take the 45° normal and 8 corner triangles the
|
|
3
|
+
// 3-axis diagonal, all flat per face (own vertices), so a face-weighted-normal pass turns the
|
|
4
|
+
// chamfers into rounded highlights without a bake. UVs: every vertex keeps its own face's mapping
|
|
5
|
+
// (the box's), so the thin chamfer reuses the nearest face's strip texels.
|
|
6
|
+
// triangles = 44 − 2 per omitted face
|
|
7
|
+
|
|
8
|
+
import { type Mesh, MeshBuilder, normalize, type Vec3 } from '../core/index.ts';
|
|
9
|
+
import { type StripId, stripRect } from '../material/index.ts';
|
|
10
|
+
|
|
11
|
+
type Sign = 1 | -1;
|
|
12
|
+
type FaceName = 'top' | 'bottom' | 'front' | 'back' | 'left' | 'right';
|
|
13
|
+
|
|
14
|
+
/** Face axes as in box.ts: index = axis * 2 + (sign > 0 ? 0 : 1). */
|
|
15
|
+
const FACES: readonly { name: FaceName; u: Vec3; v: Vec3 }[] = [
|
|
16
|
+
{ name: 'front', u: [0, 1, 0], v: [0, 0, 1] },
|
|
17
|
+
{ name: 'back', u: [0, -1, 0], v: [0, 0, 1] },
|
|
18
|
+
{ name: 'left', u: [-1, 0, 0], v: [0, 0, 1] },
|
|
19
|
+
{ name: 'right', u: [1, 0, 0], v: [0, 0, 1] },
|
|
20
|
+
{ name: 'top', u: [1, 0, 0], v: [0, 1, 0] },
|
|
21
|
+
{ name: 'bottom', u: [1, 0, 0], v: [0, -1, 0] },
|
|
22
|
+
];
|
|
23
|
+
const SIGNS: readonly Sign[] = [-1, 1];
|
|
24
|
+
|
|
25
|
+
export interface BevelBoxInput {
|
|
26
|
+
readonly half: Vec3;
|
|
27
|
+
readonly centre: Vec3;
|
|
28
|
+
readonly bevel: number;
|
|
29
|
+
readonly color: Vec3;
|
|
30
|
+
readonly strip: StripId;
|
|
31
|
+
readonly omit: readonly FaceName[];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function bevelBox(o: BevelBoxInput): Mesh {
|
|
35
|
+
const { half, centre, bevel: bv } = o;
|
|
36
|
+
const rect = stripRect(o.strip);
|
|
37
|
+
const b = new MeshBuilder();
|
|
38
|
+
const dot3 = (a: Vec3, c: Vec3): number => a[0] * c[0] + a[1] * c[1] + a[2] * c[2];
|
|
39
|
+
const ext = (a: Vec3): number =>
|
|
40
|
+
Math.abs(a[0]) * half[0] + Math.abs(a[1]) * half[1] + Math.abs(a[2]) * half[2];
|
|
41
|
+
/** Corner s on the face of axis k: inset by the bevel on the two other axes. */
|
|
42
|
+
const point = (s: readonly Sign[], k: number): Vec3 => [
|
|
43
|
+
centre[0] + (s[0] ?? 1) * (half[0] - (k === 0 ? 0 : bv)),
|
|
44
|
+
centre[1] + (s[1] ?? 1) * (half[1] - (k === 1 ? 0 : bv)),
|
|
45
|
+
centre[2] + (s[2] ?? 1) * (half[2] - (k === 2 ? 0 : bv)),
|
|
46
|
+
];
|
|
47
|
+
const put = (s: readonly Sign[], k: number, n: Vec3): number => {
|
|
48
|
+
const f = FACES[k * 2 + ((s[k] ?? 1) > 0 ? 0 : 1)] ?? { u: n, v: n };
|
|
49
|
+
const p = point(s, k);
|
|
50
|
+
const rel: Vec3 = [p[0] - centre[0], p[1] - centre[1], p[2] - centre[2]];
|
|
51
|
+
const eu = ext(f.u);
|
|
52
|
+
const ev = ext(f.v);
|
|
53
|
+
return b.vertex({
|
|
54
|
+
p,
|
|
55
|
+
n,
|
|
56
|
+
uv: [
|
|
57
|
+
rect.u0 + ((dot3(rel, f.u) + eu) / rect.metres) * (rect.u1 - rect.u0),
|
|
58
|
+
rect.v0 + ((dot3(rel, f.v) + ev) / (2 * ev)) * (rect.v1 - rect.v0),
|
|
59
|
+
],
|
|
60
|
+
color: o.color,
|
|
61
|
+
});
|
|
62
|
+
};
|
|
63
|
+
/** Emit a polygon, flipping it if its winding disagrees with the outward normal. */
|
|
64
|
+
const poly = (corners: readonly (readonly [readonly Sign[], number])[], n: Vec3): void => {
|
|
65
|
+
const pts = corners.map(([s, k]) => point(s, k));
|
|
66
|
+
const [a = n, c = n, d = n] = pts;
|
|
67
|
+
const e1: Vec3 = [c[0] - a[0], c[1] - a[1], c[2] - a[2]];
|
|
68
|
+
const e2: Vec3 = [d[0] - a[0], d[1] - a[1], d[2] - a[2]];
|
|
69
|
+
const g: Vec3 = [
|
|
70
|
+
e1[1] * e2[2] - e1[2] * e2[1],
|
|
71
|
+
e1[2] * e2[0] - e1[0] * e2[2],
|
|
72
|
+
e1[0] * e2[1] - e1[1] * e2[0],
|
|
73
|
+
];
|
|
74
|
+
const ids = corners.map(([s, k]) => put(s, k, n));
|
|
75
|
+
if (dot3(g, n) < 0) ids.reverse();
|
|
76
|
+
const [i0 = 0, i1 = 0, i2 = 0, i3] = ids;
|
|
77
|
+
if (i3 === undefined) b.tri(i0, i1, i2);
|
|
78
|
+
else b.quad(i0, i1, i2, i3);
|
|
79
|
+
};
|
|
80
|
+
const unit = (k: number, s: number): Vec3 => [k === 0 ? s : 0, k === 1 ? s : 0, k === 2 ? s : 0];
|
|
81
|
+
const corner = (k: number, sk: Sign, l: number, sl: Sign, m: number, sm: Sign): Sign[] => {
|
|
82
|
+
const s: Sign[] = [1, 1, 1];
|
|
83
|
+
s[k] = sk;
|
|
84
|
+
s[l] = sl;
|
|
85
|
+
s[m] = sm;
|
|
86
|
+
return s;
|
|
87
|
+
};
|
|
88
|
+
// Faces (axis k, sign sk): four corners in a ring over the other two axes.
|
|
89
|
+
for (let k = 0; k < 3; k++) {
|
|
90
|
+
const [l, m] = [(k + 1) % 3, (k + 2) % 3];
|
|
91
|
+
for (const sk of SIGNS) {
|
|
92
|
+
if (o.omit.includes((FACES[k * 2 + (sk > 0 ? 0 : 1)] ?? { name: 'top' }).name)) continue;
|
|
93
|
+
const ring = (
|
|
94
|
+
[
|
|
95
|
+
[-1, -1],
|
|
96
|
+
[1, -1],
|
|
97
|
+
[1, 1],
|
|
98
|
+
[-1, 1],
|
|
99
|
+
] as const
|
|
100
|
+
).map(([a, c]) => [corner(k, sk, l, a, m, c), k] as const);
|
|
101
|
+
poly(ring, unit(k, sk));
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
// Chamfers along axis m between the faces of axes k and l.
|
|
105
|
+
for (let m = 0; m < 3; m++) {
|
|
106
|
+
const [k, l] = [(m + 1) % 3, (m + 2) % 3];
|
|
107
|
+
for (const sk of SIGNS)
|
|
108
|
+
for (const sl of SIGNS) {
|
|
109
|
+
const lo = corner(k, sk, l, sl, m, -1);
|
|
110
|
+
const hi = corner(k, sk, l, sl, m, 1);
|
|
111
|
+
const n = normalize(
|
|
112
|
+
[0, 1, 2].map((j) => (j === k ? sk : j === l ? sl : 0)) as unknown as Vec3,
|
|
113
|
+
);
|
|
114
|
+
poly(
|
|
115
|
+
[
|
|
116
|
+
[lo, k],
|
|
117
|
+
[hi, k],
|
|
118
|
+
[hi, l],
|
|
119
|
+
[lo, l],
|
|
120
|
+
],
|
|
121
|
+
n,
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
// Corners: one triangle across the three face corners.
|
|
126
|
+
for (const sx of SIGNS)
|
|
127
|
+
for (const sy of SIGNS)
|
|
128
|
+
for (const sz of SIGNS) {
|
|
129
|
+
const s: Sign[] = [sx, sy, sz];
|
|
130
|
+
poly(
|
|
131
|
+
[
|
|
132
|
+
[s, 0],
|
|
133
|
+
[s, 1],
|
|
134
|
+
[s, 2],
|
|
135
|
+
],
|
|
136
|
+
normalize([sx, sy, sz]),
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
return b.build();
|
|
140
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// Axis-aligned box, base at z=0 (things stand on the ground). UVs tile a trim strip by metres.
|
|
2
|
+
// Flat normals. `bevel` > 0 chamfers every edge once (bevel-box.ts); 0 keeps these exact bytes.
|
|
3
|
+
|
|
4
|
+
import { type Mesh, MeshBuilder, type Vec3 } from '../core/index.ts';
|
|
5
|
+
import { type StripId, stripRect } from '../material/index.ts';
|
|
6
|
+
import { bevelBox } from './bevel-box.ts';
|
|
7
|
+
import { invalid } from './check.ts';
|
|
8
|
+
|
|
9
|
+
export interface BoxOptions {
|
|
10
|
+
/** Width (X), depth (Y), height (Z) in metres. */
|
|
11
|
+
readonly size: Vec3;
|
|
12
|
+
readonly color: Vec3;
|
|
13
|
+
readonly strip?: StripId;
|
|
14
|
+
/** Centre of the base face. */
|
|
15
|
+
readonly at?: Vec3;
|
|
16
|
+
/** Skip faces nobody sees (e.g. the bottom on the ground). */
|
|
17
|
+
readonly omit?: readonly ('top' | 'bottom' | 'front' | 'back' | 'left' | 'right')[];
|
|
18
|
+
/** Chamfer width in metres on every edge (12 + 8 extra faces, 44 triangles). LOD it:
|
|
19
|
+
* `bevel: ctx.pick([0.02, 0.01, 0])`. Must stay under half the smallest size. Default 0. */
|
|
20
|
+
readonly bevel?: number;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
type Face = { name: NonNullable<BoxOptions['omit']>[number]; n: Vec3; u: Vec3; v: Vec3 };
|
|
24
|
+
const FACES: readonly Face[] = [
|
|
25
|
+
{ name: 'front', n: [1, 0, 0], u: [0, 1, 0], v: [0, 0, 1] },
|
|
26
|
+
{ name: 'back', n: [-1, 0, 0], u: [0, -1, 0], v: [0, 0, 1] },
|
|
27
|
+
{ name: 'right', n: [0, -1, 0], u: [1, 0, 0], v: [0, 0, 1] },
|
|
28
|
+
{ name: 'left', n: [0, 1, 0], u: [-1, 0, 0], v: [0, 0, 1] },
|
|
29
|
+
{ name: 'top', n: [0, 0, 1], u: [1, 0, 0], v: [0, 1, 0] },
|
|
30
|
+
{ name: 'bottom', n: [0, 0, -1], u: [1, 0, 0], v: [0, -1, 0] },
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
export function box(o: BoxOptions): Mesh {
|
|
34
|
+
const [w, d, h] = o.size;
|
|
35
|
+
const [cx, cy, cz] = o.at ?? [0, 0, 0];
|
|
36
|
+
const half: Vec3 = [w / 2, d / 2, h / 2];
|
|
37
|
+
const centre: Vec3 = [cx, cy, cz + h / 2];
|
|
38
|
+
const bevel = o.bevel ?? 0;
|
|
39
|
+
if (bevel !== 0) {
|
|
40
|
+
const limit = Math.min(w, d, h) / 2;
|
|
41
|
+
if (!(bevel > 0 && bevel < limit))
|
|
42
|
+
invalid(
|
|
43
|
+
`box bevel ${bevel} m does not fit a ${w} × ${d} × ${h} m box`,
|
|
44
|
+
`use 0 < bevel < ${limit} m (half the smallest size), e.g. ${Math.min(0.02, limit / 4)}`,
|
|
45
|
+
);
|
|
46
|
+
return bevelBox({
|
|
47
|
+
half,
|
|
48
|
+
centre,
|
|
49
|
+
bevel,
|
|
50
|
+
color: o.color,
|
|
51
|
+
strip: o.strip ?? 'plain',
|
|
52
|
+
omit: o.omit ?? [],
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
const rect = stripRect(o.strip ?? 'plain');
|
|
56
|
+
const b = new MeshBuilder();
|
|
57
|
+
for (const f of FACES) {
|
|
58
|
+
if (o.omit?.includes(f.name)) continue;
|
|
59
|
+
const extent = (axis: Vec3): number =>
|
|
60
|
+
Math.abs(axis[0]) * half[0] + Math.abs(axis[1]) * half[1] + Math.abs(axis[2]) * half[2];
|
|
61
|
+
const eu = extent(f.u);
|
|
62
|
+
const ev = extent(f.v);
|
|
63
|
+
const en = extent(f.n);
|
|
64
|
+
const ids = [
|
|
65
|
+
[-1, -1],
|
|
66
|
+
[1, -1],
|
|
67
|
+
[1, 1],
|
|
68
|
+
[-1, 1],
|
|
69
|
+
].map(([su = 0, sv = 0]) => {
|
|
70
|
+
const p: Vec3 = [0, 1, 2].map(
|
|
71
|
+
(k) =>
|
|
72
|
+
(centre[k] ?? 0) + (f.n[k] ?? 0) * en + (f.u[k] ?? 0) * eu * su + (f.v[k] ?? 0) * ev * sv,
|
|
73
|
+
) as unknown as Vec3;
|
|
74
|
+
const uMetres = ((su + 1) / 2) * eu * 2;
|
|
75
|
+
const vFrac = (sv + 1) / 2;
|
|
76
|
+
return b.vertex({
|
|
77
|
+
p,
|
|
78
|
+
n: f.n,
|
|
79
|
+
uv: [
|
|
80
|
+
rect.u0 + (uMetres / rect.metres) * (rect.u1 - rect.u0),
|
|
81
|
+
rect.v0 + vFrac * (rect.v1 - rect.v0),
|
|
82
|
+
],
|
|
83
|
+
color: o.color,
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
const [a = 0, c = 0, e = 0, g = 0] = ids;
|
|
87
|
+
b.quad(a, c, e, g);
|
|
88
|
+
}
|
|
89
|
+
return b.build();
|
|
90
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
// A quad mapped to ONE fixed trim-sheet cell: windows, doors, clocks, signs, leaf cards. Flat
|
|
2
|
+
// normal u × v; V grows along `v` (keep `v` world-up so the decal stands upright).
|
|
3
|
+
// `inset` > 0 recesses the panel by that depth behind the face plane and closes the gap with four
|
|
4
|
+
// reveal faces (jambs, head, sill) mapped at true scale onto `reveal`: real depth that catches
|
|
5
|
+
// light and shadow, the cheapest trick that makes a facade read as built. `inset` < 0 raises it
|
|
6
|
+
// proud of the plane instead (plaques, sign boards), sides facing out.
|
|
7
|
+
// The surrounding wall must leave the opening free (build it from tiled pieces around it).
|
|
8
|
+
// triangles = (doubleSided ? 4 : 2) + (inset ≠ 0 ? 8 : 0)
|
|
9
|
+
|
|
10
|
+
import { type Mesh, MeshBuilder, normalize, type Vec3 } from '../core/index.ts';
|
|
11
|
+
import { type CellId, cellRect, type StripId, stripRect } from '../material/index.ts';
|
|
12
|
+
import { invalid, spanOf } from './check.ts';
|
|
13
|
+
import { HALF_TEXEL, stripU, stripV, stripVMetres } from './strip-uv.ts';
|
|
14
|
+
|
|
15
|
+
export interface CellOptions {
|
|
16
|
+
/** Bottom-left corner as seen from the front. */
|
|
17
|
+
readonly origin: Vec3;
|
|
18
|
+
/** Width edge (cell U), metres. */
|
|
19
|
+
readonly u: Vec3;
|
|
20
|
+
/** Height edge (cell V, up), metres. */
|
|
21
|
+
readonly v: Vec3;
|
|
22
|
+
readonly color: Vec3;
|
|
23
|
+
readonly cell: CellId;
|
|
24
|
+
/** Recess depth in metres (> 0 behind the plane, < 0 proud of it). Default 0. */
|
|
25
|
+
readonly inset?: number;
|
|
26
|
+
/** Strip the reveal faces tile at true scale. Default 'plain'. */
|
|
27
|
+
readonly reveal?: StripId;
|
|
28
|
+
readonly revealColor?: Vec3;
|
|
29
|
+
/** Both sides (leaf cards, hanging signs). Not with an inset. */
|
|
30
|
+
readonly doubleSided?: boolean;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const add3 = (a: Vec3, b: Vec3, s: number): Vec3 => [
|
|
34
|
+
a[0] + b[0] * s,
|
|
35
|
+
a[1] + b[1] * s,
|
|
36
|
+
a[2] + b[2] * s,
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
export function cell(o: CellOptions): Mesh {
|
|
40
|
+
const { lu, lv, n } = spanOf('cell', o.origin, o.u, o.v);
|
|
41
|
+
const d = o.inset ?? 0;
|
|
42
|
+
if (!Number.isFinite(d))
|
|
43
|
+
invalid(`cell inset is ${d}`, 'use a finite depth in metres (0.1 = a 10 cm recess)');
|
|
44
|
+
if (d !== 0 && o.doubleSided)
|
|
45
|
+
invalid(
|
|
46
|
+
'cell: inset with doubleSided',
|
|
47
|
+
'drop doubleSided for a recessed/raised panel, or set inset: 0',
|
|
48
|
+
);
|
|
49
|
+
const r = cellRect(o.cell);
|
|
50
|
+
const e = HALF_TEXEL;
|
|
51
|
+
const rim: Vec3[] = [
|
|
52
|
+
o.origin,
|
|
53
|
+
add3(o.origin, o.u, 1),
|
|
54
|
+
add3(add3(o.origin, o.u, 1), o.v, 1),
|
|
55
|
+
add3(o.origin, o.v, 1),
|
|
56
|
+
];
|
|
57
|
+
const face = rim.map((p) => add3(p, n, -d));
|
|
58
|
+
const uvs: [number, number][] = [
|
|
59
|
+
[r.u0 + e, r.v0 + e],
|
|
60
|
+
[r.u1 - e, r.v0 + e],
|
|
61
|
+
[r.u1 - e, r.v1 - e],
|
|
62
|
+
[r.u0 + e, r.v1 - e],
|
|
63
|
+
];
|
|
64
|
+
const b = new MeshBuilder();
|
|
65
|
+
for (const side of o.doubleSided ? [1, -1] : [1]) {
|
|
66
|
+
const nn: Vec3 = [n[0] * side, n[1] * side, n[2] * side];
|
|
67
|
+
const [i0 = 0, i1 = 0, i2 = 0, i3 = 0] = face.map((p, k) =>
|
|
68
|
+
b.vertex({ p, n: nn, uv: uvs[k] ?? [0, 0], color: o.color }),
|
|
69
|
+
);
|
|
70
|
+
if (side > 0) b.quad(i0, i1, i2, i3);
|
|
71
|
+
else b.quad(i0, i3, i2, i1);
|
|
72
|
+
}
|
|
73
|
+
if (d !== 0) reveals(b, o, rim, face, n, d, [lu, lv, lu, lv]);
|
|
74
|
+
return b.build();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Four side quads rim[i] → rim[i+1] → face[i+1] → face[i]; that order is CCW for either sign. */
|
|
78
|
+
function reveals(
|
|
79
|
+
b: MeshBuilder,
|
|
80
|
+
o: CellOptions,
|
|
81
|
+
rim: readonly Vec3[],
|
|
82
|
+
face: readonly Vec3[],
|
|
83
|
+
n: Vec3,
|
|
84
|
+
d: number,
|
|
85
|
+
lens: readonly number[],
|
|
86
|
+
): void {
|
|
87
|
+
const strip = o.reveal ?? 'plain';
|
|
88
|
+
const rect = stripRect(strip);
|
|
89
|
+
const fv = Math.min(1, Math.abs(d) / stripVMetres(strip));
|
|
90
|
+
const color = o.revealColor ?? o.color;
|
|
91
|
+
for (let i = 0; i < 4; i++) {
|
|
92
|
+
const a = rim[i] ?? n;
|
|
93
|
+
const c = rim[(i + 1) % 4] ?? n;
|
|
94
|
+
const ed: Vec3 = [c[0] - a[0], c[1] - a[1], c[2] - a[2]];
|
|
95
|
+
// n × edge points into the opening; a recess faces inward, a plaque's sides outward.
|
|
96
|
+
const s = Math.sign(d);
|
|
97
|
+
const nn = normalize([
|
|
98
|
+
(n[1] * ed[2] - n[2] * ed[1]) * s,
|
|
99
|
+
(n[2] * ed[0] - n[0] * ed[2]) * s,
|
|
100
|
+
(n[0] * ed[1] - n[1] * ed[0]) * s,
|
|
101
|
+
]);
|
|
102
|
+
const len = lens[i] ?? 0;
|
|
103
|
+
const q = [a, c, face[(i + 1) % 4] ?? c, face[i] ?? a];
|
|
104
|
+
const uv: [number, number][] = [
|
|
105
|
+
[stripU(rect, 0), stripV(rect, 0)],
|
|
106
|
+
[stripU(rect, len), stripV(rect, 0)],
|
|
107
|
+
[stripU(rect, len), stripV(rect, fv)],
|
|
108
|
+
[stripU(rect, 0), stripV(rect, fv)],
|
|
109
|
+
];
|
|
110
|
+
const [j0 = 0, j1 = 0, j2 = 0, j3 = 0] = q.map((p, k) =>
|
|
111
|
+
b.vertex({ p, n: nn, uv: uv[k] ?? [0, 0], color }),
|
|
112
|
+
);
|
|
113
|
+
b.quad(j0, j1, j2, j3);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// Input guards shared by primitives: every bad option throws T3D_RECIPE_INVALID with the fix, so
|
|
2
|
+
// an agent reads what to change instead of debugging NaN geometry downstream.
|
|
3
|
+
|
|
4
|
+
import { cross, length, Ts23dError, type Vec3 } from '../core/index.ts';
|
|
5
|
+
|
|
6
|
+
export function invalid(cause: string, fix: string): never {
|
|
7
|
+
throw new Ts23dError('T3D_RECIPE_INVALID', { cause, fix });
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const finite = (v: Vec3): boolean => v.every(Number.isFinite);
|
|
11
|
+
|
|
12
|
+
/** Two edge vectors spanning a real parallelogram; returns |u|, |v| and the unit normal u×v. */
|
|
13
|
+
export function spanOf(
|
|
14
|
+
what: string,
|
|
15
|
+
origin: Vec3,
|
|
16
|
+
u: Vec3,
|
|
17
|
+
v: Vec3,
|
|
18
|
+
): { lu: number; lv: number; n: Vec3 } {
|
|
19
|
+
if (!finite(origin) || !finite(u) || !finite(v))
|
|
20
|
+
invalid(`${what}: origin/u/v has a NaN or Infinity`, 'pass finite metre vectors');
|
|
21
|
+
const lu = length(u);
|
|
22
|
+
const lv = length(v);
|
|
23
|
+
if (lu < 1e-9 || lv < 1e-9)
|
|
24
|
+
invalid(
|
|
25
|
+
`${what}: edge u (${lu} m) or v (${lv} m) has zero length`,
|
|
26
|
+
'give u and v the face edges in metres, both non-zero (e.g. u: [4, 0, 0], v: [0, 0, 3])',
|
|
27
|
+
);
|
|
28
|
+
const c = cross(u, v);
|
|
29
|
+
const lc = length(c);
|
|
30
|
+
if (lc < 1e-6 * lu * lv)
|
|
31
|
+
invalid(
|
|
32
|
+
`${what}: u and v are parallel, so the face has no area`,
|
|
33
|
+
'make v perpendicular to u (u along the face, v up it)',
|
|
34
|
+
);
|
|
35
|
+
return { lu, lv, n: [c[0] / lc, c[1] / lc, c[2] / lc] };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** An integer count in [min, max]. */
|
|
39
|
+
export function count(what: string, n: number, min: number, max: number): number {
|
|
40
|
+
if (!Number.isInteger(n) || n < min || n > max)
|
|
41
|
+
invalid(`${what} is ${n}`, `use a whole number from ${min} to ${max}`);
|
|
42
|
+
return n;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** A finite number > 0. */
|
|
46
|
+
export function positive(what: string, n: number, example: string): number {
|
|
47
|
+
if (!Number.isFinite(n) || n <= 0) invalid(`${what} is ${n}`, `use a length > 0 m (${example})`);
|
|
48
|
+
return n;
|
|
49
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// Closed-surface checks for primitives that claim watertight output (bevelled box, sdf). Weld by
|
|
2
|
+
// position (flat faces own their vertices); a surface is closed and consistently wound when every
|
|
3
|
+
// edge is walked as often a→b as b→a. Non-manifold edges (surface nets on sub-cell features) are
|
|
4
|
+
// counted separately. Internal; tests use them.
|
|
5
|
+
|
|
6
|
+
import type { Mesh } from '../core/index.ts';
|
|
7
|
+
|
|
8
|
+
/** Undirected edges walked a different number of times a→b than b→a (0 = closed, oriented). */
|
|
9
|
+
export function openEdges(m: Mesh): number {
|
|
10
|
+
const key = (i: number): string =>
|
|
11
|
+
`${Math.round((m.positions[i * 3] ?? 0) * 1e4)},${Math.round((m.positions[i * 3 + 1] ?? 0) * 1e4)},${Math.round((m.positions[i * 3 + 2] ?? 0) * 1e4)}`;
|
|
12
|
+
const edges = new Map<string, number>();
|
|
13
|
+
for (let t = 0; t < m.indices.length; t += 3) {
|
|
14
|
+
for (let j = 0; j < 3; j++) {
|
|
15
|
+
const a = key(m.indices[t + j] ?? 0);
|
|
16
|
+
const b = key(m.indices[t + ((j + 1) % 3)] ?? 0);
|
|
17
|
+
if (a !== b) edges.set(`${a}>${b}`, (edges.get(`${a}>${b}`) ?? 0) + 1);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
let open = 0;
|
|
21
|
+
for (const [e, n] of edges) {
|
|
22
|
+
const [a, b] = e.split('>');
|
|
23
|
+
if (edges.get(`${b}>${a}`) !== n) open++;
|
|
24
|
+
}
|
|
25
|
+
return open;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Directed edges used more than once: 0 = 2-manifold (given openEdges = 0). */
|
|
29
|
+
export function nonManifoldEdges(m: Mesh): number {
|
|
30
|
+
const key = (i: number): string =>
|
|
31
|
+
`${Math.round((m.positions[i * 3] ?? 0) * 1e4)},${Math.round((m.positions[i * 3 + 1] ?? 0) * 1e4)},${Math.round((m.positions[i * 3 + 2] ?? 0) * 1e4)}`;
|
|
32
|
+
const seen = new Map<string, number>();
|
|
33
|
+
for (let t = 0; t < m.indices.length; t += 3)
|
|
34
|
+
for (let j = 0; j < 3; j++) {
|
|
35
|
+
const e = `${key(m.indices[t + j] ?? 0)}>${key(m.indices[t + ((j + 1) % 3)] ?? 0)}`;
|
|
36
|
+
seen.set(e, (seen.get(e) ?? 0) + 1);
|
|
37
|
+
}
|
|
38
|
+
let bad = 0;
|
|
39
|
+
for (const n of seen.values()) if (n > 1) bad++;
|
|
40
|
+
return bad;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Triangles whose winding (b − a) × (c − a) clearly disagrees with their mean vertex normal
|
|
45
|
+
* (cosine < −0.1; inside-out scores ≈ −1). Slivers whose face normal lies in the surface
|
|
46
|
+
* (cosine ≈ 0, e.g. surface nets at sub-cell features) are orientation-ambiguous, not counted.
|
|
47
|
+
*/
|
|
48
|
+
export function windingFlips(m: Mesh): number {
|
|
49
|
+
let bad = 0;
|
|
50
|
+
const P = m.positions;
|
|
51
|
+
const N = m.normals;
|
|
52
|
+
for (let t = 0; t < m.indices.length; t += 3) {
|
|
53
|
+
const a = (m.indices[t] ?? 0) * 3;
|
|
54
|
+
const b = (m.indices[t + 1] ?? 0) * 3;
|
|
55
|
+
const c = (m.indices[t + 2] ?? 0) * 3;
|
|
56
|
+
const e = [0, 1, 2].map((k) => (P[b + k] ?? 0) - (P[a + k] ?? 0));
|
|
57
|
+
const f = [0, 1, 2].map((k) => (P[c + k] ?? 0) - (P[a + k] ?? 0));
|
|
58
|
+
const g = [
|
|
59
|
+
(e[1] ?? 0) * (f[2] ?? 0) - (e[2] ?? 0) * (f[1] ?? 0),
|
|
60
|
+
(e[2] ?? 0) * (f[0] ?? 0) - (e[0] ?? 0) * (f[2] ?? 0),
|
|
61
|
+
(e[0] ?? 0) * (f[1] ?? 0) - (e[1] ?? 0) * (f[0] ?? 0),
|
|
62
|
+
];
|
|
63
|
+
let s = 0;
|
|
64
|
+
let nn = 0;
|
|
65
|
+
for (let k = 0; k < 3; k++) {
|
|
66
|
+
const m = (N[a + k] ?? 0) + (N[b + k] ?? 0) + (N[c + k] ?? 0);
|
|
67
|
+
s += (g[k] ?? 0) * m;
|
|
68
|
+
nn += m * m;
|
|
69
|
+
}
|
|
70
|
+
const gl = Math.hypot(g[0] ?? 0, g[1] ?? 0, g[2] ?? 0);
|
|
71
|
+
if (gl > 0 && nn > 0 && s / (gl * Math.sqrt(nn)) < -0.1) bad++;
|
|
72
|
+
}
|
|
73
|
+
return bad;
|
|
74
|
+
}
|