@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.
Files changed (567) hide show
  1. package/.claude/agents/bug-hunter.md +21 -0
  2. package/.claude/agents/docs-author.md +13 -0
  3. package/.claude/agents/dx-auditor.md +14 -0
  4. package/.claude/agents/primitive-author.md +22 -0
  5. package/.claude/agents/recipe-author.md +36 -0
  6. package/.claude/agents/visual-reviewer.md +23 -0
  7. package/.claude/commands/feature.md +28 -0
  8. package/.claude/commands/new-recipe.md +19 -0
  9. package/.claude/commands/release.md +14 -0
  10. package/.claude/commands/verify.md +9 -0
  11. package/.claude/skills/author-recipe/SKILL.md +93 -0
  12. package/CHANGELOG.md +90 -0
  13. package/CLAUDE.md +85 -0
  14. package/LICENSE +21 -0
  15. package/README.md +385 -2
  16. package/dist/chunk-6b05mnmx.js +19 -0
  17. package/dist/chunk-6b05mnmx.js.map +11 -0
  18. package/dist/chunk-7rtfjh9f.js +45 -0
  19. package/dist/chunk-7rtfjh9f.js.map +10 -0
  20. package/dist/chunk-8j4c28jt.js +4085 -0
  21. package/dist/chunk-8j4c28jt.js.map +46 -0
  22. package/dist/chunk-d1fv6hrm.js +2532 -0
  23. package/dist/chunk-d1fv6hrm.js.map +43 -0
  24. package/dist/chunk-dbq4qf52.js +902 -0
  25. package/dist/chunk-dbq4qf52.js.map +25 -0
  26. package/dist/chunk-drna2rny.js +836 -0
  27. package/dist/chunk-drna2rny.js.map +17 -0
  28. package/dist/chunk-dxvypr7y.js +89 -0
  29. package/dist/chunk-dxvypr7y.js.map +11 -0
  30. package/dist/chunk-f6yme003.js +1589 -0
  31. package/dist/chunk-f6yme003.js.map +30 -0
  32. package/dist/chunk-hg7pf34j.js +465 -0
  33. package/dist/chunk-hg7pf34j.js.map +19 -0
  34. package/dist/chunk-jq4e17tj.js +1031 -0
  35. package/dist/chunk-jq4e17tj.js.map +21 -0
  36. package/dist/chunk-y63c6zcv.js +2668 -0
  37. package/dist/chunk-y63c6zcv.js.map +53 -0
  38. package/dist/cli/args.d.ts +30 -0
  39. package/dist/cli/bin.d.ts +2 -0
  40. package/dist/cli/bin.js +348 -0
  41. package/dist/cli/bin.js.map +15 -0
  42. package/dist/cli/load.d.ts +2 -0
  43. package/dist/cli/manifest.d.ts +53 -0
  44. package/dist/cli/render-cmds.d.ts +12 -0
  45. package/dist/cli/run.d.ts +1 -0
  46. package/dist/cli/texture-cache.d.ts +4 -0
  47. package/dist/core/asset.d.ts +10 -0
  48. package/dist/core/definition.d.ts +155 -0
  49. package/dist/core/errors.d.ts +17 -0
  50. package/dist/core/family.d.ts +23 -0
  51. package/dist/core/index.d.ts +9 -0
  52. package/dist/core/index.js +69 -0
  53. package/dist/core/index.js.map +9 -0
  54. package/dist/core/mesh.d.ts +35 -0
  55. package/dist/core/part.d.ts +114 -0
  56. package/dist/core/png.d.ts +1 -0
  57. package/dist/core/rng.d.ts +12 -0
  58. package/dist/core/transform.d.ts +20 -0
  59. package/dist/core/vec.d.ts +11 -0
  60. package/dist/core/world.d.ts +16 -0
  61. package/dist/export/bin-writer.d.ts +16 -0
  62. package/dist/export/container.d.ts +3 -0
  63. package/dist/export/credits.d.ts +4 -0
  64. package/dist/export/glb-options.d.ts +48 -0
  65. package/dist/export/glb.d.ts +8 -0
  66. package/dist/export/hierarchy.d.ts +23 -0
  67. package/dist/export/index.d.ts +5 -0
  68. package/dist/export/index.js +40 -0
  69. package/dist/export/index.js.map +9 -0
  70. package/dist/export/lights.d.ts +8 -0
  71. package/dist/export/lod-coverage.d.ts +12 -0
  72. package/dist/export/materials.d.ts +23 -0
  73. package/dist/export/merge-pivots.d.ts +6 -0
  74. package/dist/export/metadata.d.ts +21 -0
  75. package/dist/export/motion.d.ts +9 -0
  76. package/dist/export/optimize-parts.d.ts +26 -0
  77. package/dist/export/primitive.d.ts +3 -0
  78. package/dist/export/tangents.d.ts +2 -0
  79. package/dist/export/textures.d.ts +22 -0
  80. package/dist/export/version.d.ts +2 -0
  81. package/dist/grammar/index.d.ts +2 -0
  82. package/dist/grammar/index.js +16 -0
  83. package/dist/grammar/index.js.map +9 -0
  84. package/dist/grammar/split.d.ts +21 -0
  85. package/dist/grammar/whyte.d.ts +5 -0
  86. package/dist/index.d.ts +11 -0
  87. package/dist/index.js +457 -0
  88. package/dist/index.js.map +9 -0
  89. package/dist/kinds/building.d.ts +44 -0
  90. package/dist/kinds/faces.d.ts +27 -0
  91. package/dist/kinds/head-armour.d.ts +5 -0
  92. package/dist/kinds/head-beard.d.ts +6 -0
  93. package/dist/kinds/head-bust.d.ts +5 -0
  94. package/dist/kinds/head-extras.d.ts +15 -0
  95. package/dist/kinds/head-face.d.ts +8 -0
  96. package/dist/kinds/head-garb.d.ts +4 -0
  97. package/dist/kinds/head-geom.d.ts +7 -0
  98. package/dist/kinds/head-hair.d.ts +4 -0
  99. package/dist/kinds/head-layout.d.ts +29 -0
  100. package/dist/kinds/head-mesh.d.ts +31 -0
  101. package/dist/kinds/head-mouth.d.ts +4 -0
  102. package/dist/kinds/head-params.d.ts +116 -0
  103. package/dist/kinds/head-presets.d.ts +19 -0
  104. package/dist/kinds/head-rim.d.ts +10 -0
  105. package/dist/kinds/head-tone.d.ts +10 -0
  106. package/dist/kinds/head-torso.d.ts +35 -0
  107. package/dist/kinds/head.d.ts +39 -0
  108. package/dist/kinds/hull-lines.d.ts +65 -0
  109. package/dist/kinds/hull-skin.d.ts +6 -0
  110. package/dist/kinds/hull.d.ts +14 -0
  111. package/dist/kinds/index.d.ts +16 -0
  112. package/dist/kinds/index.js +78 -0
  113. package/dist/kinds/index.js.map +9 -0
  114. package/dist/kinds/mass.d.ts +15 -0
  115. package/dist/kinds/openings.d.ts +30 -0
  116. package/dist/kinds/planned.d.ts +6 -0
  117. package/dist/kinds/roof-chimney.d.ts +16 -0
  118. package/dist/kinds/roof-shape.d.ts +64 -0
  119. package/dist/kinds/roof.d.ts +31 -0
  120. package/dist/kinds/vegetation-fronds.d.ts +15 -0
  121. package/dist/kinds/vegetation-impostor.d.ts +9 -0
  122. package/dist/kinds/vegetation-leaves.d.ts +15 -0
  123. package/dist/kinds/vegetation-mesh.d.ts +21 -0
  124. package/dist/kinds/vegetation-params.d.ts +90 -0
  125. package/dist/kinds/vegetation-presets.d.ts +20 -0
  126. package/dist/kinds/vegetation-skeleton.d.ts +20 -0
  127. package/dist/kinds/vegetation.d.ts +42 -0
  128. package/dist/kinds/vehicle.d.ts +42 -0
  129. package/dist/material/cc0.d.ts +4 -0
  130. package/dist/material/define.d.ts +34 -0
  131. package/dist/material/generator.d.ts +4 -0
  132. package/dist/material/index.d.ts +9 -0
  133. package/dist/material/index.js +70 -0
  134. package/dist/material/index.js.map +9 -0
  135. package/dist/material/library.d.ts +7 -0
  136. package/dist/material/material-def.d.ts +30 -0
  137. package/dist/material/paint/bond.d.ts +3 -0
  138. package/dist/material/paint/canvas.d.ts +39 -0
  139. package/dist/material/paint/cell.d.ts +4 -0
  140. package/dist/material/paint/cells-round.d.ts +5 -0
  141. package/dist/material/paint/cells.d.ts +7 -0
  142. package/dist/material/paint/compose.d.ts +5 -0
  143. package/dist/material/paint/concrete.d.ts +3 -0
  144. package/dist/material/paint/courses.d.ts +31 -0
  145. package/dist/material/paint/decode-png.d.ts +6 -0
  146. package/dist/material/paint/default-sheet.d.ts +4 -0
  147. package/dist/material/paint/fabric.d.ts +4 -0
  148. package/dist/material/paint/foliage-broad.d.ts +5 -0
  149. package/dist/material/paint/foliage-common.d.ts +35 -0
  150. package/dist/material/paint/foliage-compound.d.ts +4 -0
  151. package/dist/material/paint/foliage-conifer.d.ts +5 -0
  152. package/dist/material/paint/foliage-raster.d.ts +26 -0
  153. package/dist/material/paint/foliage.d.ts +3 -0
  154. package/dist/material/paint/image-strip.d.ts +4 -0
  155. package/dist/material/paint/kit.d.ts +5 -0
  156. package/dist/material/paint/masks.d.ts +3 -0
  157. package/dist/material/paint/metal.d.ts +3 -0
  158. package/dist/material/paint/noise.d.ts +9 -0
  159. package/dist/material/paint/normal.d.ts +8 -0
  160. package/dist/material/paint/orm.d.ts +8 -0
  161. package/dist/material/paint/planks.d.ts +3 -0
  162. package/dist/material/paint/plates.d.ts +3 -0
  163. package/dist/material/paint/shapes.d.ts +8 -0
  164. package/dist/material/paint/slate.d.ts +3 -0
  165. package/dist/material/paint/stamp.d.ts +3 -0
  166. package/dist/material/paint/strip.d.ts +3 -0
  167. package/dist/material/paint/surface.d.ts +5 -0
  168. package/dist/material/paint/types.d.ts +65 -0
  169. package/dist/material/paint/validate.d.ts +3 -0
  170. package/dist/material/palette.d.ts +14 -0
  171. package/dist/material/shader-godot.d.ts +2 -0
  172. package/dist/material/shader-three.d.ts +2 -0
  173. package/dist/material/shader.d.ts +46 -0
  174. package/dist/material/slots.d.ts +5 -0
  175. package/dist/material/texture-source.d.ts +34 -0
  176. package/dist/material/trim-sheet.d.ts +38 -0
  177. package/dist/neural/generator.d.ts +25 -0
  178. package/dist/neural/images.d.ts +17 -0
  179. package/dist/neural/index.d.ts +2 -0
  180. package/dist/neural/index.js +14 -0
  181. package/dist/neural/index.js.map +9 -0
  182. package/dist/passes/bake-ao.d.ts +17 -0
  183. package/dist/passes/grime.d.ts +15 -0
  184. package/dist/passes/index.d.ts +6 -0
  185. package/dist/passes/index.js +22 -0
  186. package/dist/passes/index.js.map +9 -0
  187. package/dist/passes/livery.d.ts +19 -0
  188. package/dist/passes/optimize-cache.d.ts +15 -0
  189. package/dist/passes/optimize-weld.d.ts +15 -0
  190. package/dist/passes/optimize.d.ts +28 -0
  191. package/dist/passes/ray-grid.d.ts +23 -0
  192. package/dist/passes/weighted-normals.d.ts +8 -0
  193. package/dist/primitives/beam.d.ts +17 -0
  194. package/dist/primitives/bevel-box.d.ts +13 -0
  195. package/dist/primitives/box.d.ts +16 -0
  196. package/dist/primitives/cell.d.ts +20 -0
  197. package/dist/primitives/check.d.ts +12 -0
  198. package/dist/primitives/closed.d.ts +11 -0
  199. package/dist/primitives/erosion.d.ts +22 -0
  200. package/dist/primitives/font-5x7.d.ts +3 -0
  201. package/dist/primitives/heightfield.d.ts +16 -0
  202. package/dist/primitives/index.d.ts +23 -0
  203. package/dist/primitives/index.js +81 -0
  204. package/dist/primitives/index.js.map +9 -0
  205. package/dist/primitives/lathe-facets.d.ts +8 -0
  206. package/dist/primitives/lathe.d.ts +26 -0
  207. package/dist/primitives/lattice.d.ts +30 -0
  208. package/dist/primitives/loft.d.ts +15 -0
  209. package/dist/primitives/mc-table.d.ts +2 -0
  210. package/dist/primitives/member.d.ts +19 -0
  211. package/dist/primitives/noise.d.ts +34 -0
  212. package/dist/primitives/planar.d.ts +10 -0
  213. package/dist/primitives/prism.d.ts +13 -0
  214. package/dist/primitives/sculpt-edit.d.ts +56 -0
  215. package/dist/primitives/sculpt-tree.d.ts +23 -0
  216. package/dist/primitives/sculpt.d.ts +27 -0
  217. package/dist/primitives/sdf-grid.d.ts +17 -0
  218. package/dist/primitives/sdf-ops.d.ts +37 -0
  219. package/dist/primitives/sdf.d.ts +21 -0
  220. package/dist/primitives/strip-uv.d.ts +11 -0
  221. package/dist/primitives/surface-nets.d.ts +7 -0
  222. package/dist/primitives/sweep.d.ts +19 -0
  223. package/dist/primitives/terrain-color.d.ts +30 -0
  224. package/dist/primitives/terrain-grid.d.ts +36 -0
  225. package/dist/primitives/text-layout.d.ts +22 -0
  226. package/dist/primitives/text.d.ts +27 -0
  227. package/dist/primitives/tiled.d.ts +22 -0
  228. package/dist/primitives/transport.d.ts +17 -0
  229. package/dist/primitives/triangulate.d.ts +2 -0
  230. package/dist/primitives/value-noise.d.ts +10 -0
  231. package/dist/qa/checks.d.ts +11 -0
  232. package/dist/qa/clash.d.ts +2 -0
  233. package/dist/qa/floating.d.ts +5 -0
  234. package/dist/qa/gates.d.ts +12 -0
  235. package/dist/qa/geometry.d.ts +21 -0
  236. package/dist/qa/index.d.ts +12 -0
  237. package/dist/qa/index.js +78 -0
  238. package/dist/qa/index.js.map +9 -0
  239. package/dist/qa/inside.d.ts +12 -0
  240. package/dist/qa/inspect.d.ts +18 -0
  241. package/dist/qa/lods.d.ts +12 -0
  242. package/dist/qa/measures.d.ts +4 -0
  243. package/dist/qa/occlusion.d.ts +3 -0
  244. package/dist/qa/sanity.d.ts +7 -0
  245. package/dist/qa/types.d.ts +12 -0
  246. package/dist/render/beauty.d.ts +40 -0
  247. package/dist/render/camera.d.ts +33 -0
  248. package/dist/render/depth-map.d.ts +38 -0
  249. package/dist/render/font.d.ts +1 -0
  250. package/dist/render/ground.d.ts +13 -0
  251. package/dist/render/index.d.ts +10 -0
  252. package/dist/render/index.js +47 -0
  253. package/dist/render/index.js.map +9 -0
  254. package/dist/render/lighting.d.ts +27 -0
  255. package/dist/render/lod-strip.d.ts +5 -0
  256. package/dist/render/metrics.d.ts +37 -0
  257. package/dist/render/options.d.ts +28 -0
  258. package/dist/render/photo-match.d.ts +19 -0
  259. package/dist/render/raster.d.ts +25 -0
  260. package/dist/render/resolve.d.ts +11 -0
  261. package/dist/render/review-prompt.d.ts +2 -0
  262. package/dist/render/review-warnings.d.ts +34 -0
  263. package/dist/render/review.d.ts +24 -0
  264. package/dist/render/scene.d.ts +18 -0
  265. package/dist/render/shade.d.ts +22 -0
  266. package/dist/render/sheet.d.ts +15 -0
  267. package/dist/render/texture.d.ts +15 -0
  268. package/dist/render/thumbnail.d.ts +8 -0
  269. package/dist/render/vis-buffer.d.ts +18 -0
  270. package/dist/testing/assert.d.ts +21 -0
  271. package/dist/testing/compiled.d.ts +15 -0
  272. package/dist/testing/hash.d.ts +1 -0
  273. package/dist/testing/index.d.ts +4 -0
  274. package/dist/testing/index.js +134 -0
  275. package/dist/testing/index.js.map +13 -0
  276. package/dist/testing/snapshot.d.ts +13 -0
  277. package/docs/README.md +56 -0
  278. package/docs/architecture/README.md +68 -0
  279. package/docs/architecture/adding-a-kind.md +44 -0
  280. package/docs/architecture/adding-a-primitive.md +49 -0
  281. package/docs/architecture/errors.md +47 -0
  282. package/docs/architecture/glb-production.md +214 -0
  283. package/docs/architecture/gltf-capabilities.md +117 -0
  284. package/docs/architecture/materials.md +151 -0
  285. package/docs/decisions/001-recipe-compiler-over-ai-meshes.md +27 -0
  286. package/docs/decisions/002-typescript-not-json.md +22 -0
  287. package/docs/decisions/003-shared-trim-sheet.md +21 -0
  288. package/docs/decisions/004-z-up-metres.md +21 -0
  289. package/docs/decisions/005-zero-runtime-deps.md +21 -0
  290. package/docs/decisions/006-single-package-subpath-exports.md +20 -0
  291. package/docs/decisions/007-cpu-contact-sheets.md +21 -0
  292. package/docs/decisions/008-surface-materials.md +21 -0
  293. package/docs/decisions/009-gltf-front-axis.md +21 -0
  294. package/docs/decisions/README.md +15 -0
  295. package/docs/idea/01-vision.md +117 -0
  296. package/docs/idea/02-dsl.md +343 -0
  297. package/docs/idea/03-ir.md +97 -0
  298. package/docs/idea/04-materials.md +114 -0
  299. package/docs/idea/05-agent-loop.md +141 -0
  300. package/docs/idea/06-qa.md +101 -0
  301. package/docs/idea/07-what-fits.md +45 -0
  302. package/docs/idea/08-neural-adapters.md +78 -0
  303. package/docs/idea/09-kinds.md +105 -0
  304. package/docs/idea/10-sculpting.md +166 -0
  305. package/docs/idea/11-materials-authoring.md +132 -0
  306. package/docs/idea/12-blender-bridge.md +136 -0
  307. package/docs/idea/13-asset-pipeline-research.md +89 -0
  308. package/docs/landscape.md +49 -0
  309. package/docs/plans/roadmap.md +139 -0
  310. package/examples/build-with-bun.ts +41 -0
  311. package/llms.txt +47 -0
  312. package/package.json +100 -4
  313. package/recipes/README.md +38 -0
  314. package/recipes/asteroid/README.md +46 -0
  315. package/recipes/asteroid/asteroid.ts +336 -0
  316. package/recipes/boulders/README.md +28 -0
  317. package/recipes/boulders/boulders.ts +117 -0
  318. package/recipes/boxcar/README.md +53 -0
  319. package/recipes/boxcar/boxcar.ts +1000 -0
  320. package/recipes/cargo_container_scifi/README.md +52 -0
  321. package/recipes/cargo_container_scifi/cargo_container_scifi.ts +461 -0
  322. package/recipes/dropship/README.md +84 -0
  323. package/recipes/dropship/dropship.ts +1144 -0
  324. package/recipes/eiffel_tower/README.md +110 -0
  325. package/recipes/eiffel_tower/eiffel_tower.ts +781 -0
  326. package/recipes/goblin_head/README.md +75 -0
  327. package/recipes/goblin_head/goblin_head.ts +38 -0
  328. package/recipes/human_head/README.md +68 -0
  329. package/recipes/human_head/human_head.ts +38 -0
  330. package/recipes/loco_440/README.md +32 -0
  331. package/recipes/loco_440/loco_440.ts +1404 -0
  332. package/recipes/material_swatches/README.md +21 -0
  333. package/recipes/material_swatches/material_swatches.ts +189 -0
  334. package/recipes/mountain_range/README.md +45 -0
  335. package/recipes/mountain_range/mountain_range.ts +128 -0
  336. package/recipes/oak/README.md +30 -0
  337. package/recipes/oak/oak.ts +19 -0
  338. package/recipes/orc_head/README.md +78 -0
  339. package/recipes/orc_head/orc_head.ts +41 -0
  340. package/recipes/palm/README.md +30 -0
  341. package/recipes/palm/palm.ts +19 -0
  342. package/recipes/pine/README.md +29 -0
  343. package/recipes/pine/pine.ts +19 -0
  344. package/recipes/signal_gantry/README.md +36 -0
  345. package/recipes/signal_gantry/signal_gantry.ts +522 -0
  346. package/recipes/station_halt/README.md +39 -0
  347. package/recipes/station_halt/station_halt.ts +722 -0
  348. package/recipes/t34/README.md +44 -0
  349. package/recipes/t34/t34.ts +1068 -0
  350. package/recipes/tiger_1/README.md +75 -0
  351. package/recipes/tiger_1/tiger_1.ts +1370 -0
  352. package/recipes/water_tower/README.md +40 -0
  353. package/recipes/water_tower/water_tower.ts +542 -0
  354. package/src/cli/args.ts +125 -0
  355. package/src/cli/bin.ts +7 -0
  356. package/src/cli/load.ts +17 -0
  357. package/src/cli/manifest.ts +95 -0
  358. package/src/cli/render-cmds.ts +66 -0
  359. package/src/cli/run.ts +192 -0
  360. package/src/cli/texture-cache.ts +24 -0
  361. package/src/core/asset.ts +129 -0
  362. package/src/core/definition.ts +156 -0
  363. package/src/core/errors.ts +50 -0
  364. package/src/core/family.ts +50 -0
  365. package/src/core/index.ts +66 -0
  366. package/src/core/mesh.ts +114 -0
  367. package/src/core/part.ts +130 -0
  368. package/src/core/png.ts +49 -0
  369. package/src/core/rng.ts +49 -0
  370. package/src/core/transform.ts +90 -0
  371. package/src/core/vec.ts +26 -0
  372. package/src/core/world.ts +80 -0
  373. package/src/export/bin-writer.ts +79 -0
  374. package/src/export/container.ts +35 -0
  375. package/src/export/credits.ts +25 -0
  376. package/src/export/glb-options.ts +53 -0
  377. package/src/export/glb.ts +187 -0
  378. package/src/export/hierarchy.ts +122 -0
  379. package/src/export/index.ts +34 -0
  380. package/src/export/lights.ts +49 -0
  381. package/src/export/lod-coverage.ts +71 -0
  382. package/src/export/materials.ts +126 -0
  383. package/src/export/merge-pivots.ts +146 -0
  384. package/src/export/metadata.ts +62 -0
  385. package/src/export/motion.ts +70 -0
  386. package/src/export/optimize-parts.ts +80 -0
  387. package/src/export/primitive.ts +35 -0
  388. package/src/export/tangents.ts +75 -0
  389. package/src/export/textures.ts +88 -0
  390. package/src/export/version.ts +6 -0
  391. package/src/grammar/index.ts +4 -0
  392. package/src/grammar/split.ts +87 -0
  393. package/src/grammar/whyte.ts +20 -0
  394. package/src/index.ts +14 -0
  395. package/src/kinds/building.ts +153 -0
  396. package/src/kinds/faces.ts +138 -0
  397. package/src/kinds/head-armour.ts +181 -0
  398. package/src/kinds/head-beard.ts +133 -0
  399. package/src/kinds/head-bust.ts +184 -0
  400. package/src/kinds/head-extras.ts +150 -0
  401. package/src/kinds/head-face.ts +242 -0
  402. package/src/kinds/head-garb.ts +181 -0
  403. package/src/kinds/head-geom.ts +31 -0
  404. package/src/kinds/head-hair.ts +193 -0
  405. package/src/kinds/head-layout.ts +63 -0
  406. package/src/kinds/head-mesh.ts +195 -0
  407. package/src/kinds/head-mouth.ts +111 -0
  408. package/src/kinds/head-params.ts +162 -0
  409. package/src/kinds/head-presets.ts +155 -0
  410. package/src/kinds/head-rim.ts +128 -0
  411. package/src/kinds/head-tone.ts +52 -0
  412. package/src/kinds/head-torso.ts +230 -0
  413. package/src/kinds/head.ts +176 -0
  414. package/src/kinds/hull-lines.ts +160 -0
  415. package/src/kinds/hull-skin.ts +83 -0
  416. package/src/kinds/hull.ts +209 -0
  417. package/src/kinds/index.ts +63 -0
  418. package/src/kinds/mass.ts +110 -0
  419. package/src/kinds/openings.ts +141 -0
  420. package/src/kinds/planned.ts +12 -0
  421. package/src/kinds/roof-chimney.ts +100 -0
  422. package/src/kinds/roof-shape.ts +230 -0
  423. package/src/kinds/roof.ts +250 -0
  424. package/src/kinds/vegetation-fronds.ts +126 -0
  425. package/src/kinds/vegetation-impostor.ts +118 -0
  426. package/src/kinds/vegetation-leaves.ts +168 -0
  427. package/src/kinds/vegetation-mesh.ts +174 -0
  428. package/src/kinds/vegetation-params.ts +147 -0
  429. package/src/kinds/vegetation-presets.ts +184 -0
  430. package/src/kinds/vegetation-skeleton.ts +168 -0
  431. package/src/kinds/vegetation.ts +199 -0
  432. package/src/kinds/vehicle.ts +144 -0
  433. package/src/material/cc0.ts +41 -0
  434. package/src/material/define.ts +130 -0
  435. package/src/material/generator.ts +63 -0
  436. package/src/material/index.ts +60 -0
  437. package/src/material/library.ts +196 -0
  438. package/src/material/material-def.ts +84 -0
  439. package/src/material/paint/bond.ts +60 -0
  440. package/src/material/paint/canvas.ts +101 -0
  441. package/src/material/paint/cell.ts +39 -0
  442. package/src/material/paint/cells-round.ts +119 -0
  443. package/src/material/paint/cells.ts +111 -0
  444. package/src/material/paint/compose.ts +53 -0
  445. package/src/material/paint/concrete.ts +62 -0
  446. package/src/material/paint/courses.ts +85 -0
  447. package/src/material/paint/decode-png.ts +106 -0
  448. package/src/material/paint/default-sheet.ts +73 -0
  449. package/src/material/paint/fabric.ts +63 -0
  450. package/src/material/paint/foliage-broad.ts +126 -0
  451. package/src/material/paint/foliage-common.ts +93 -0
  452. package/src/material/paint/foliage-compound.ts +135 -0
  453. package/src/material/paint/foliage-conifer.ts +143 -0
  454. package/src/material/paint/foliage-raster.ts +196 -0
  455. package/src/material/paint/foliage.ts +23 -0
  456. package/src/material/paint/image-strip.ts +88 -0
  457. package/src/material/paint/kit.ts +37 -0
  458. package/src/material/paint/masks.ts +46 -0
  459. package/src/material/paint/metal.ts +109 -0
  460. package/src/material/paint/noise.ts +52 -0
  461. package/src/material/paint/normal.ts +52 -0
  462. package/src/material/paint/orm.ts +108 -0
  463. package/src/material/paint/planks.ts +101 -0
  464. package/src/material/paint/plates.ts +72 -0
  465. package/src/material/paint/shapes.ts +28 -0
  466. package/src/material/paint/slate.ts +52 -0
  467. package/src/material/paint/stamp.ts +40 -0
  468. package/src/material/paint/strip.ts +48 -0
  469. package/src/material/paint/surface.ts +60 -0
  470. package/src/material/paint/types.ts +94 -0
  471. package/src/material/paint/validate.ts +74 -0
  472. package/src/material/palette.ts +46 -0
  473. package/src/material/shader-godot.ts +77 -0
  474. package/src/material/shader-three.ts +103 -0
  475. package/src/material/shader.ts +100 -0
  476. package/src/material/slots.ts +18 -0
  477. package/src/material/texture-source.ts +86 -0
  478. package/src/material/trim-sheet.ts +166 -0
  479. package/src/neural/generator.ts +37 -0
  480. package/src/neural/images.ts +27 -0
  481. package/src/neural/index.ts +10 -0
  482. package/src/passes/bake-ao.ts +183 -0
  483. package/src/passes/grime.ts +37 -0
  484. package/src/passes/index.ts +8 -0
  485. package/src/passes/livery.ts +66 -0
  486. package/src/passes/optimize-cache.ts +175 -0
  487. package/src/passes/optimize-weld.ts +147 -0
  488. package/src/passes/optimize.ts +85 -0
  489. package/src/passes/ray-grid.ts +237 -0
  490. package/src/passes/weighted-normals.ts +125 -0
  491. package/src/primitives/beam.ts +60 -0
  492. package/src/primitives/bevel-box.ts +140 -0
  493. package/src/primitives/box.ts +90 -0
  494. package/src/primitives/cell.ts +115 -0
  495. package/src/primitives/check.ts +49 -0
  496. package/src/primitives/closed.ts +74 -0
  497. package/src/primitives/erosion.ts +183 -0
  498. package/src/primitives/font-5x7.ts +117 -0
  499. package/src/primitives/heightfield.ts +101 -0
  500. package/src/primitives/index.ts +56 -0
  501. package/src/primitives/lathe-facets.ts +45 -0
  502. package/src/primitives/lathe.ts +174 -0
  503. package/src/primitives/lattice.ts +131 -0
  504. package/src/primitives/loft.ts +174 -0
  505. package/src/primitives/mc-table.ts +41 -0
  506. package/src/primitives/member.ts +130 -0
  507. package/src/primitives/noise.ts +157 -0
  508. package/src/primitives/planar.ts +55 -0
  509. package/src/primitives/prism.ts +85 -0
  510. package/src/primitives/sculpt-edit.ts +212 -0
  511. package/src/primitives/sculpt-tree.ts +226 -0
  512. package/src/primitives/sculpt.ts +80 -0
  513. package/src/primitives/sdf-grid.ts +131 -0
  514. package/src/primitives/sdf-ops.ts +145 -0
  515. package/src/primitives/sdf.ts +83 -0
  516. package/src/primitives/strip-uv.ts +26 -0
  517. package/src/primitives/surface-nets.ts +161 -0
  518. package/src/primitives/sweep.ts +162 -0
  519. package/src/primitives/terrain-color.ts +85 -0
  520. package/src/primitives/terrain-grid.ts +137 -0
  521. package/src/primitives/text-layout.ts +85 -0
  522. package/src/primitives/text.ts +197 -0
  523. package/src/primitives/tiled.ts +93 -0
  524. package/src/primitives/transport.ts +85 -0
  525. package/src/primitives/triangulate.ts +42 -0
  526. package/src/primitives/value-noise.ts +62 -0
  527. package/src/qa/checks.ts +110 -0
  528. package/src/qa/clash.ts +177 -0
  529. package/src/qa/floating.ts +63 -0
  530. package/src/qa/gates.ts +126 -0
  531. package/src/qa/geometry.ts +187 -0
  532. package/src/qa/index.ts +28 -0
  533. package/src/qa/inside.ts +114 -0
  534. package/src/qa/inspect.ts +107 -0
  535. package/src/qa/lods.ts +97 -0
  536. package/src/qa/measures.ts +67 -0
  537. package/src/qa/occlusion.ts +166 -0
  538. package/src/qa/sanity.ts +137 -0
  539. package/src/qa/types.ts +17 -0
  540. package/src/render/beauty.ts +115 -0
  541. package/src/render/camera.ts +142 -0
  542. package/src/render/depth-map.ts +191 -0
  543. package/src/render/font.ts +76 -0
  544. package/src/render/ground.ts +87 -0
  545. package/src/render/index.ts +34 -0
  546. package/src/render/lighting.ts +121 -0
  547. package/src/render/lod-strip.ts +34 -0
  548. package/src/render/metrics.ts +147 -0
  549. package/src/render/options.ts +81 -0
  550. package/src/render/photo-match.ts +28 -0
  551. package/src/render/raster.ts +143 -0
  552. package/src/render/resolve.ts +109 -0
  553. package/src/render/review-prompt.ts +59 -0
  554. package/src/render/review-warnings.ts +92 -0
  555. package/src/render/review.ts +103 -0
  556. package/src/render/scene.ts +119 -0
  557. package/src/render/shade.ts +204 -0
  558. package/src/render/sheet.ts +88 -0
  559. package/src/render/texture.ts +97 -0
  560. package/src/render/thumbnail.ts +75 -0
  561. package/src/render/vis-buffer.ts +119 -0
  562. package/src/testing/assert.ts +115 -0
  563. package/src/testing/compiled.ts +31 -0
  564. package/src/testing/hash.ts +14 -0
  565. package/src/testing/index.ts +14 -0
  566. package/src/testing/snapshot.ts +37 -0
  567. package/src/types/gltf-validator.d.ts +13 -0
@@ -0,0 +1,191 @@
1
+ // Orthographic depth maps along a direction: the sun's shadow map, the sky-direction maps that
2
+ // give ambient occlusion (and the soft contact shadow on the ground), and the silhouette masks the
3
+ // review metrics compare. Stores, per texel, the highest dot(p, dir) — the surface nearest the light.
4
+
5
+ import type { Scene } from './scene.ts';
6
+
7
+ export interface DepthMap {
8
+ readonly size: number;
9
+ /** −Infinity where nothing was drawn. */
10
+ readonly data: Float32Array;
11
+ readonly dir: readonly [number, number, number];
12
+ readonly r: readonly [number, number, number];
13
+ readonly u: readonly [number, number, number];
14
+ /** Texel coordinate = (dot(p, r) − r0) · k, (u1 − dot(p, u)) · k. */
15
+ readonly r0: number;
16
+ readonly u1: number;
17
+ readonly k: number;
18
+ }
19
+
20
+ /** Map frame in the map's own (r, u) coordinates: centre and half-extent, metres. */
21
+ export interface MapFrame {
22
+ readonly cr: number;
23
+ readonly cu: number;
24
+ readonly half: number;
25
+ }
26
+
27
+ export type MapAlpha = (tri: number, b1: number, b2: number) => boolean;
28
+
29
+ export function mapAxes(dir: readonly [number, number, number]): {
30
+ r: [number, number, number];
31
+ u: [number, number, number];
32
+ } {
33
+ const up: [number, number, number] = Math.abs(dir[2]) > 0.99 ? [1, 0, 0] : [0, 0, 1];
34
+ let r: [number, number, number] = [
35
+ up[1] * dir[2] - up[2] * dir[1],
36
+ up[2] * dir[0] - up[0] * dir[2],
37
+ up[0] * dir[1] - up[1] * dir[0],
38
+ ];
39
+ const l = Math.hypot(r[0], r[1], r[2]) || 1;
40
+ r = [r[0] / l, r[1] / l, r[2] / l];
41
+ const u: [number, number, number] = [
42
+ dir[1] * r[2] - dir[2] * r[1],
43
+ dir[2] * r[0] - dir[0] * r[2],
44
+ dir[0] * r[1] - dir[1] * r[0],
45
+ ];
46
+ return { r, u };
47
+ }
48
+
49
+ /** The frame that just contains the scene seen along `dir`, with a 2% margin. */
50
+ export function fitFrame(s: Scene, dir: readonly [number, number, number]): MapFrame {
51
+ const { r, u } = mapAxes(dir);
52
+ let r0 = Infinity;
53
+ let r1 = -Infinity;
54
+ let u0 = Infinity;
55
+ let u1 = -Infinity;
56
+ const P = s.positions;
57
+ for (let i = 0; i < P.length; i += 3) {
58
+ const x = P[i] ?? 0;
59
+ const y = P[i + 1] ?? 0;
60
+ const z = P[i + 2] ?? 0;
61
+ const a = x * r[0] + y * r[1] + z * r[2];
62
+ const b = x * u[0] + y * u[1] + z * u[2];
63
+ if (a < r0) r0 = a;
64
+ if (a > r1) r1 = a;
65
+ if (b < u0) u0 = b;
66
+ if (b > u1) u1 = b;
67
+ }
68
+ if (!Number.isFinite(r0)) return { cr: 0, cu: 0, half: 1 };
69
+ return { cr: (r0 + r1) / 2, cu: (u0 + u1) / 2, half: Math.max(r1 - r0, u1 - u0, 1e-3) * 0.51 };
70
+ }
71
+
72
+ export function depthMap(
73
+ s: Scene,
74
+ dir: readonly [number, number, number],
75
+ size: number,
76
+ frame: MapFrame = fitFrame(s, dir),
77
+ alpha?: MapAlpha,
78
+ ): DepthMap {
79
+ const { r, u } = mapAxes(dir);
80
+ const k = size / (2 * frame.half);
81
+ const r0 = frame.cr - frame.half;
82
+ const u1 = frame.cu + frame.half;
83
+ const data = new Float32Array(size * size).fill(-Infinity);
84
+ const nv = s.positions.length / 3;
85
+ const X = new Float32Array(nv);
86
+ const Y = new Float32Array(nv);
87
+ const D = new Float32Array(nv);
88
+ const P = s.positions;
89
+ for (let v = 0; v < nv; v++) {
90
+ const x = P[v * 3] ?? 0;
91
+ const y = P[v * 3 + 1] ?? 0;
92
+ const z = P[v * 3 + 2] ?? 0;
93
+ X[v] = (x * r[0] + y * r[1] + z * r[2] - r0) * k;
94
+ Y[v] = (u1 - (x * u[0] + y * u[1] + z * u[2])) * k;
95
+ D[v] = x * dir[0] + y * dir[1] + z * dir[2];
96
+ }
97
+ const I = s.indices;
98
+ for (let t = 0; t < s.triangles; t++) {
99
+ const a = I[t * 3] ?? 0;
100
+ const b = I[t * 3 + 1] ?? 0;
101
+ const c = I[t * 3 + 2] ?? 0;
102
+ const ax = X[a] ?? 0;
103
+ const ay = Y[a] ?? 0;
104
+ const bx = X[b] ?? 0;
105
+ const by = Y[b] ?? 0;
106
+ const cx = X[c] ?? 0;
107
+ const cy = Y[c] ?? 0;
108
+ const area = (bx - ax) * (cy - ay) - (by - ay) * (cx - ax);
109
+ if (Math.abs(area) < 1e-12) continue;
110
+ const inv = 1 / area;
111
+ const da = D[a] ?? 0;
112
+ const db = D[b] ?? 0;
113
+ const dc = D[c] ?? 0;
114
+ const cut = alpha !== undefined && s.surface[t] === 1;
115
+ const lx = ax < bx ? Math.min(ax, cx) : Math.min(bx, cx);
116
+ const hx = ax > bx ? Math.max(ax, cx) : Math.max(bx, cx);
117
+ const ly = ay < by ? Math.min(ay, cy) : Math.min(by, cy);
118
+ const hy = ay > by ? Math.max(ay, cy) : Math.max(by, cy);
119
+ const minX = lx < 0 ? 0 : Math.floor(lx);
120
+ const maxX = hx > size - 1 ? size - 1 : Math.ceil(hx);
121
+ const minY = ly < 0 ? 0 : Math.floor(ly);
122
+ const maxY = hy > size - 1 ? size - 1 : Math.ceil(hy);
123
+ // Edge functions are affine in (px, py): step them instead of re-evaluating per texel.
124
+ const A0 = (by - cy) * inv;
125
+ const B0 = (cx - bx) * inv;
126
+ const A1 = (cy - ay) * inv;
127
+ const B1 = (ax - cx) * inv;
128
+ const C0 = (bx * cy - by * cx) * inv;
129
+ const C1 = (cx * ay - cy * ax) * inv;
130
+ for (let y = minY; y <= maxY; y++) {
131
+ const py = y + 0.5;
132
+ let w0 = C0 + A0 * (minX + 0.5) + B0 * py;
133
+ let w1 = C1 + A1 * (minX + 0.5) + B1 * py;
134
+ for (let x = minX; x <= maxX; x++, w0 += A0, w1 += A1) {
135
+ if (w0 < 0 || w1 < 0 || w0 + w1 > 1) continue;
136
+ const d = w0 * da + w1 * db + (1 - w0 - w1) * dc;
137
+ const i = y * size + x;
138
+ if (d <= (data[i] ?? 0)) continue;
139
+ if (cut && alpha && !alpha(t, w1, 1 - w0 - w1)) continue;
140
+ data[i] = d;
141
+ }
142
+ }
143
+ }
144
+ return { size, data, dir, r, u, r0, u1, k };
145
+ }
146
+
147
+ /**
148
+ * Fraction of `taps` (pairs of texel offsets) at which world point (x,y,z) sees past the map —
149
+ * 1 = fully lit/open, 0 = fully occluded. `bias` in metres along the map direction.
150
+ */
151
+ export function visibility(
152
+ m: DepthMap,
153
+ x: number,
154
+ y: number,
155
+ z: number,
156
+ bias: number,
157
+ taps: Float32Array,
158
+ ): number {
159
+ const tx = (x * m.r[0] + y * m.r[1] + z * m.r[2] - m.r0) * m.k;
160
+ const ty = (m.u1 - (x * m.u[0] + y * m.u[1] + z * m.u[2])) * m.k;
161
+ const d = x * m.dir[0] + y * m.dir[1] + z * m.dir[2] + bias;
162
+ const s = m.size;
163
+ let lit = 0;
164
+ for (let i = 0; i < taps.length; i += 2) {
165
+ const px = Math.floor(tx + (taps[i] ?? 0));
166
+ const py = Math.floor(ty + (taps[i + 1] ?? 0));
167
+ if (px < 0 || py < 0 || px >= s || py >= s || d >= (m.data[py * s + px] ?? 0)) lit++;
168
+ }
169
+ return (lit * 2) / taps.length;
170
+ }
171
+
172
+ /**
173
+ * Ambient openness at (x,y,z) along the map direction, 0…1: an occluder `reach` metres or more
174
+ * away counts for nothing, nearer ones linearly more — AO with a falloff, so a few sky directions
175
+ * give a soft contact shadow instead of long streaks.
176
+ */
177
+ export function openness(
178
+ m: DepthMap,
179
+ x: number,
180
+ y: number,
181
+ z: number,
182
+ bias: number,
183
+ reach: number,
184
+ ): number {
185
+ const px = Math.floor((x * m.r[0] + y * m.r[1] + z * m.r[2] - m.r0) * m.k);
186
+ const py = Math.floor((m.u1 - (x * m.u[0] + y * m.u[1] + z * m.u[2])) * m.k);
187
+ const s = m.size;
188
+ if (px < 0 || py < 0 || px >= s || py >= s) return 1;
189
+ const gap = (m.data[py * s + px] ?? 0) - (x * m.dir[0] + y * m.dir[1] + z * m.dir[2] + bias);
190
+ return gap <= 0 ? 1 : gap >= reach ? 1 : gap / reach;
191
+ }
@@ -0,0 +1,76 @@
1
+ // A 3×5 bitmap font for view labels on contact sheets, so an agent reading the PNG knows which
2
+ // view is which and what the grid step is. Uppercase, digits and a few marks only.
3
+
4
+ const GLYPHS: Readonly<Record<string, string>> = {
5
+ A: '010101111101101',
6
+ B: '110101110101110',
7
+ C: '011100100100011',
8
+ D: '110101101101110',
9
+ E: '111100110100111',
10
+ F: '111100110100100',
11
+ G: '011100101101011',
12
+ H: '101101111101101',
13
+ I: '111010010010111',
14
+ J: '001001001101010',
15
+ K: '101101110101101',
16
+ L: '100100100100111',
17
+ M: '101111111101101',
18
+ N: '110101101101101',
19
+ O: '010101101101010',
20
+ P: '110101110100100',
21
+ Q: '010101101110011',
22
+ R: '110101110101101',
23
+ S: '011100010001110',
24
+ T: '111010010010010',
25
+ U: '101101101101111',
26
+ V: '101101101101010',
27
+ W: '101101111111101',
28
+ X: '101101010101101',
29
+ Y: '101101010010010',
30
+ Z: '111001010100111',
31
+ '0': '111101101101111',
32
+ '1': '010110010010111',
33
+ '2': '110001010100111',
34
+ '3': '110001010001110',
35
+ '4': '101101111001001',
36
+ '5': '111100110001110',
37
+ '6': '011100111101111',
38
+ '7': '111001010010010',
39
+ '8': '111101111101111',
40
+ '9': '111101111001110',
41
+ '.': '000000000000010',
42
+ '/': '001001010100100',
43
+ '-': '000000111000000',
44
+ ':': '000010000010000',
45
+ ' ': '000000000000000',
46
+ '+': '000010111010000',
47
+ _: '000000000000111',
48
+ '=': '000111000111000',
49
+ };
50
+
51
+ export function drawText(
52
+ px: Uint8Array,
53
+ width: number,
54
+ x: number,
55
+ y: number,
56
+ text: string,
57
+ scale: number,
58
+ rgb: readonly [number, number, number],
59
+ ): void {
60
+ let cx = x;
61
+ for (const ch of text.toUpperCase()) {
62
+ const g = GLYPHS[ch] ?? GLYPHS[' '] ?? '';
63
+ for (let i = 0; i < 15; i++) {
64
+ if (g[i] !== '1') continue;
65
+ const gx = cx + (i % 3) * scale;
66
+ const gy = y + Math.floor(i / 3) * scale;
67
+ for (let dy = 0; dy < scale; dy++) {
68
+ for (let dx = 0; dx < scale; dx++) {
69
+ const o = ((gy + dy) * width + gx + dx) * 4;
70
+ if (o >= 0 && o + 3 < px.length) px.set([...rgb, 255], o);
71
+ }
72
+ }
73
+ }
74
+ cx += 4 * scale;
75
+ }
76
+ }
@@ -0,0 +1,87 @@
1
+ // The seamless studio floor as a shadow catcher: a world-space grid over the asset's footprint
2
+ // holding how much light reaches the floor (sun shadow × sky openness, 1 = untouched). The renderer
3
+ // multiplies the background by it, so the floor is invisible except for the shadows that ground
4
+ // the model — the clean product-shot look, with a soft contact shadow from the AO maps.
5
+
6
+ import type { PerspectiveCamera } from './camera.ts';
7
+ import { type ShadeContext, skyOpen, sunVisible } from './shade.ts';
8
+
9
+ export interface FloorGrid {
10
+ readonly z: number;
11
+ readonly x0: number;
12
+ readonly y0: number;
13
+ readonly cell: number;
14
+ readonly size: number;
15
+ readonly light: Float32Array;
16
+ }
17
+
18
+ const UP = [0, 0, 1];
19
+
20
+ export function floorGrid(c: ShadeContext, size: number): FloorGrid {
21
+ const s = c.scene;
22
+ const z = s.min[2];
23
+ const h = s.max[2] - s.min[2];
24
+ const half = Math.max(s.max[0] - s.min[0], s.max[1] - s.min[1]) / 2 + Math.max(h * 1.4, 0.2);
25
+ const cx = (s.min[0] + s.max[0]) / 2;
26
+ const cy = (s.min[1] + s.max[1]) / 2;
27
+ const cell = (2 * half) / size;
28
+ const light = new Float32Array(size * size);
29
+ const sunUp = Math.max(0, c.rig.sun[2]);
30
+ // Floor radiance split: sun share vs sky share of an open floor, so shadows read as soft
31
+ // (sky still lights the shadow) and the contact shadow comes from the AO term alone.
32
+ const sunW = sunUp * (c.rig.sunColor[1] ?? 1);
33
+ const skyW = c.rig.sky[1] ?? 1;
34
+ const p = [0, 0, z];
35
+ for (let j = 0; j < size; j++)
36
+ for (let i = 0; i < size; i++) {
37
+ p[0] = cx - half + (i + 0.5) * cell;
38
+ p[1] = cy - half + (j + 0.5) * cell;
39
+ const sun = sunVisible(c, p, UP, (i + j * 2) & 3);
40
+ const open = skyOpen(c, p, UP);
41
+ light[j * size + i] = (sunW * sun * (0.55 + 0.45 * open) + skyW * open) / (sunW + skyW);
42
+ }
43
+ return { z, x0: cx - half, y0: cy - half, cell, size, light: blur(blur(light, size), size) };
44
+ }
45
+
46
+ /** 3 × 3 box blur (edges clamp): hides the grid's cell steps under perspective magnification. */
47
+ function blur(src: Float32Array, n: number): Float32Array {
48
+ const out = new Float32Array(src.length);
49
+ for (let y = 0; y < n; y++)
50
+ for (let x = 0; x < n; x++) {
51
+ let s = 0;
52
+ for (let dy = -1; dy <= 1; dy++)
53
+ for (let dx = -1; dx <= 1; dx++) {
54
+ const yy = Math.min(n - 1, Math.max(0, y + dy));
55
+ const xx = Math.min(n - 1, Math.max(0, x + dx));
56
+ s += src[yy * n + xx] ?? 1;
57
+ }
58
+ out[y * n + x] = s / 9;
59
+ }
60
+ return out;
61
+ }
62
+
63
+ /** Floor light where the eye ray through pixel (px, py) meets the floor; 1 off the grid/above. */
64
+ export function floorLight(g: FloorGrid, cam: PerspectiveCamera, px: number, py: number): number {
65
+ const sx = (px - cam.cx) / cam.focal;
66
+ const sy = -(py - cam.cy) / cam.focal;
67
+ const dx = cam.f[0] + cam.r[0] * sx + cam.u[0] * sy;
68
+ const dy = cam.f[1] + cam.r[1] * sx + cam.u[1] * sy;
69
+ const dz = cam.f[2] + cam.r[2] * sx + cam.u[2] * sy;
70
+ if (dz >= -1e-6) return 1;
71
+ const t = (g.z - cam.eye[2]) / dz;
72
+ const gx = (cam.eye[0] + dx * t - g.x0) / g.cell - 0.5;
73
+ const gy = (cam.eye[1] + dy * t - g.y0) / g.cell - 0.5;
74
+ const ix = Math.floor(gx);
75
+ const iy = Math.floor(gy);
76
+ if (ix < 0 || iy < 0 || ix >= g.size - 1 || iy >= g.size - 1) return 1;
77
+ const fx = gx - ix;
78
+ const fy = gy - iy;
79
+ const L = g.light;
80
+ const o = iy * g.size + ix;
81
+ return (
82
+ (L[o] ?? 1) * (1 - fx) * (1 - fy) +
83
+ (L[o + 1] ?? 1) * fx * (1 - fy) +
84
+ (L[o + g.size] ?? 1) * (1 - fx) * fy +
85
+ (L[o + g.size + 1] ?? 1) * fx * fy
86
+ );
87
+ }
@@ -0,0 +1,34 @@
1
+ // Public API of /render: zero-GPU images an agent (and a README) can use — contact sheets,
2
+ // thumbnails, beauty product shots, LOD strips, and the AI review pack with image metrics.
3
+
4
+ export {
5
+ type BeautyOptions,
6
+ type BeautyRender,
7
+ beauty,
8
+ cameraFor,
9
+ renderBeauty,
10
+ } from './beauty.ts';
11
+ export type { PerspectiveCamera } from './camera.ts';
12
+ export { type LodStripOptions, lodStrip } from './lod-strip.ts';
13
+ export {
14
+ type ExposureStats,
15
+ exposure,
16
+ type MaskStats,
17
+ maskIoU,
18
+ maskStats,
19
+ poolMask,
20
+ type Silhouette,
21
+ silhouette,
22
+ viewFrame,
23
+ } from './metrics.ts';
24
+ export { checkBeautyOptions, parseSize } from './options.ts';
25
+ export { type PhotoCamera, type PhotoMatchOptions, photoMatch } from './photo-match.ts';
26
+ export { detailWindows, type ReviewOptions, type ReviewPack, reviewPack } from './review.ts';
27
+ export {
28
+ REVIEW_THRESHOLDS,
29
+ type ReviewMetrics,
30
+ type ReviewWarning,
31
+ type ViewMetrics,
32
+ } from './review-warnings.ts';
33
+ export { contactSheet, type SheetOptions, VIEWS } from './sheet.ts';
34
+ export { type ThumbnailOptions, thumbnail } from './thumbnail.ts';
@@ -0,0 +1,121 @@
1
+ // The studio rig every beauty render shares: a warm key sun placed relative to the camera, a cool
2
+ // sky hemisphere fill with a warm ground bounce, sky directions for ambient occlusion, PCF tap
3
+ // patterns, and the filmic tone curve. Constants live here so every shot is lit the same way.
4
+
5
+ export type V3 = [number, number, number];
6
+
7
+ export interface Rig {
8
+ /** Unit vector toward the sun. */
9
+ readonly sun: V3;
10
+ readonly sunColor: V3;
11
+ readonly sky: V3;
12
+ readonly bounce: V3;
13
+ /** Unit vectors toward the sky, upper hemisphere, for the AO maps. */
14
+ readonly skyDirs: readonly V3[];
15
+ readonly exposure: number;
16
+ }
17
+
18
+ const unit = (az: number, el: number): V3 => {
19
+ const a = (az * Math.PI) / 180;
20
+ const e = (el * Math.PI) / 180;
21
+ return [Math.cos(e) * Math.cos(a), Math.cos(e) * Math.sin(a), Math.sin(e)];
22
+ };
23
+
24
+ /** Rings of sky directions: elevations and how many azimuths each ring holds (12 total). */
25
+ const RINGS: readonly (readonly [number, number])[] = [
26
+ [14, 5],
27
+ [38, 4],
28
+ [64, 2],
29
+ [88, 1],
30
+ ];
31
+
32
+ export function studioRig(cameraAzDeg: number): Rig {
33
+ const skyDirs: V3[] = [];
34
+ RINGS.forEach(([el, n], ring) => {
35
+ for (let i = 0; i < n; i++) skyDirs.push(unit((360 * (i + ring * 0.37)) / n, el));
36
+ });
37
+ return {
38
+ // Key light from camera-left and above: the near flank is lit, the front falls to fill, and
39
+ // the shadow lands behind and to the right where it grounds the model without hiding it.
40
+ sun: unit(cameraAzDeg - 50, 55),
41
+ // Calibrated so an open, sun-facing surface receives ≈ 1.0 irradiance: the output pixel is
42
+ // the albedo itself (vertex colour × sheet), not a lifted version of it.
43
+ sunColor: [0.95, 0.91, 0.84],
44
+ sky: [0.3, 0.33, 0.4],
45
+ bounce: [0.16, 0.15, 0.13],
46
+ skyDirs,
47
+ exposure: 1,
48
+ };
49
+ }
50
+
51
+ const HORIZON: V3 = [0.82, 0.8, 0.76];
52
+ const ZENITH: V3 = [0.3, 0.34, 0.44];
53
+ const FLOOR: V3 = [0.3, 0.28, 0.26];
54
+ const BAND = 0.12;
55
+
56
+ /**
57
+ * The studio environment a reflection sees, channel `k`, along a reflection vector with up
58
+ * component `rz`: a bright cyclorama horizon fading to a cooler zenith, a dark band where the
59
+ * floor meets the wall, the lit floor below. Roughness pre-blurs it toward its mean, so polished
60
+ * metal shows the horizon line and rough metal a soft gradient — what makes metal read as metal.
61
+ */
62
+ export function envRadiance(rz: number, rough: number, k: number): number {
63
+ const h = HORIZON[k] ?? 0;
64
+ const sharp =
65
+ rz >= 0
66
+ ? h + ((ZENITH[k] ?? 0) - h) * Math.sqrt(rz)
67
+ : rz > -0.3
68
+ ? h * BAND + ((FLOOR[k] ?? 0) - h * BAND) * Math.min(1, -rz / 0.3) ** 0.5
69
+ : (FLOOR[k] ?? 0);
70
+ const blurred = 0.5 * h + 0.25 * (ZENITH[k] ?? 0) + 0.25 * (FLOOR[k] ?? 0);
71
+ // Above the horizon the blur keeps a little of the up/down split so rough metal still shades.
72
+ const tilt = 1 + 0.25 * Math.max(-1, Math.min(1, rz * 2));
73
+ const t = Math.min(1, rough * 1.6);
74
+ return sharp + (blurred * tilt - sharp) * t;
75
+ }
76
+
77
+ /** Poisson-ish disc of 12 taps (texel offsets), radius ~1.6, rotated four ways for dithering. */
78
+ const DISC: readonly (readonly [number, number])[] = [
79
+ [-0.94, -0.4],
80
+ [0.95, -0.77],
81
+ [-0.09, -0.93],
82
+ [0.34, 0.29],
83
+ [-0.92, 0.46],
84
+ [-0.38, -0.06],
85
+ [0.79, 0.19],
86
+ [-0.24, 0.78],
87
+ [0.44, -0.3],
88
+ [0.55, 0.85],
89
+ [-0.66, -0.87],
90
+ [0.06, 0.37],
91
+ ];
92
+
93
+ export function pcfTaps(radius: number): Float32Array[] {
94
+ return [0, 1, 2, 3].map((k) => {
95
+ const a = (k * Math.PI) / 8;
96
+ const c = Math.cos(a) * radius;
97
+ const s = Math.sin(a) * radius;
98
+ const out = new Float32Array(DISC.length * 2);
99
+ DISC.forEach(([x, y], i) => {
100
+ out[i * 2] = x * c - y * s;
101
+ out[i * 2 + 1] = x * s + y * c;
102
+ });
103
+ return out;
104
+ });
105
+ }
106
+
107
+ /**
108
+ * Tone curve, linear in → linear display out: identity up to 0.6 (mid-tones and the palette stay
109
+ * true), then a smooth exponential shoulder that rolls highlights into 1 without clipping.
110
+ */
111
+ export function tone(x: number): number {
112
+ if (x <= 0) return 0;
113
+ if (x <= 0.6) return x;
114
+ return 0.6 + 0.4 * (1 - Math.exp(-(x - 0.6) / 0.4));
115
+ }
116
+
117
+ export const toSrgb8 = (v: number): number =>
118
+ Math.round(255 * (v <= 0.0031308 ? v * 12.92 : 1.055 * Math.min(1, v) ** (1 / 2.4) - 0.055));
119
+
120
+ export const fromSrgb = (v: number): number =>
121
+ v <= 0.04045 ? v / 12.92 : ((v + 0.055) / 1.055) ** 2.4;
@@ -0,0 +1,34 @@
1
+ // LOD strip: LOD0 · LOD1 · LOD2 beauty renders side by side from ONE camera (framed on LOD0), so
2
+ // popping — a silhouette that jumps, a part that vanishes, a colour that shifts — reads at a glance.
3
+
4
+ import { type CompiledAsset, encodePng } from '../core/index.ts';
5
+ import { type BeautyOptions, renderBeauty } from './beauty.ts';
6
+ import { drawText } from './font.ts';
7
+
8
+ export interface LodStripOptions extends Omit<BeautyOptions, 'frameFrom' | 'window'> {}
9
+
10
+ const tris = (a: CompiledAsset): number =>
11
+ a.parts.reduce((s, p) => s + (p.mesh ? p.mesh.indices.length / 3 : 0), 0);
12
+
13
+ export function lodStrip(lods: readonly CompiledAsset[], o: LodStripOptions = {}): Uint8Array {
14
+ const w = o.width ?? 640;
15
+ const h = o.height ?? 480;
16
+ const first = lods[0];
17
+ const n = lods.length;
18
+ const out = new Uint8Array(w * n * h * 4);
19
+ lods.forEach((a, k) => {
20
+ const r = renderBeauty(a, {
21
+ ...o,
22
+ width: w,
23
+ height: h,
24
+ ...(first ? { frameFrom: first } : {}),
25
+ });
26
+ for (let y = 0; y < h; y++)
27
+ out.set(r.rgba.subarray(y * w * 4, (y + 1) * w * 4), (y * w * n + k * w) * 4);
28
+ if (k > 0)
29
+ for (let y = 0; y < h; y++)
30
+ out.fill(255, (y * w * n + k * w) * 4, (y * w * n + k * w + 2) * 4);
31
+ drawText(out, w * n, k * w + 12, 12, `LOD${a.lod} ${tris(a)} TRIS`, 3, [40, 44, 52]);
32
+ });
33
+ return encodePng(w * n, h, out);
34
+ }
@@ -0,0 +1,147 @@
1
+ // Image metrics an agent gets without any model: orthographic silhouette masks per contact-sheet
2
+ // view, coverage and bounding-box fill, silhouette IoU (LOD0 ↔ LOD2 = LOD popping; model ↔ photo
3
+ // mask = photo match), and the hero's exposure — mean, contrast, crushed blacks, blown whites.
4
+
5
+ import { type CompiledAsset, Ts23dError } from '../core/index.ts';
6
+ import { depthMap, fitFrame, type MapFrame } from './depth-map.ts';
7
+ import type { View } from './raster.ts';
8
+ import { sceneOf } from './scene.ts';
9
+
10
+ export interface Silhouette {
11
+ readonly size: number;
12
+ /** 1 = asset, 0 = empty. */
13
+ readonly mask: Uint8Array;
14
+ }
15
+
16
+ export interface MaskStats {
17
+ /** Fraction of the image the silhouette covers. */
18
+ readonly coverage: number;
19
+ /** Fraction of the silhouette's own bounding box it fills (low = spindly or scattered). */
20
+ readonly bboxFill: number;
21
+ }
22
+
23
+ const towardCamera = (v: View): [number, number, number] => {
24
+ const l = Math.hypot(v.dir[0], v.dir[1], v.dir[2]) || 1;
25
+ return [-v.dir[0] / l, -v.dir[1] / l, -v.dir[2] / l];
26
+ };
27
+
28
+ /** The frame that fits `asset` seen along `view` — pass it to silhouettes of other LODs. */
29
+ export function viewFrame(asset: CompiledAsset, view: View): MapFrame {
30
+ return fitFrame(sceneOf(asset), towardCamera(view));
31
+ }
32
+
33
+ export function silhouette(
34
+ asset: CompiledAsset,
35
+ view: View,
36
+ size = 256,
37
+ frame: MapFrame = viewFrame(asset, view),
38
+ ): Silhouette {
39
+ const m = depthMap(sceneOf(asset), towardCamera(view), size, frame);
40
+ const mask = new Uint8Array(size * size);
41
+ for (let i = 0; i < mask.length; i++) mask[i] = (m.data[i] ?? -Infinity) > -Infinity ? 1 : 0;
42
+ return { size, mask };
43
+ }
44
+
45
+ /** Intersection over union of two same-sized masks (nonzero = inside). Two empty masks → 1. */
46
+ export function maskIoU(a: Uint8Array, b: Uint8Array): number {
47
+ if (a.length !== b.length)
48
+ throw new Ts23dError('T3D_RENDER_OPTIONS', {
49
+ cause: `masks differ in size (${a.length} vs ${b.length} pixels)`,
50
+ fix: 'render both silhouettes at the same size and frame (pass the LOD0 frame to both)',
51
+ });
52
+ let inter = 0;
53
+ let union = 0;
54
+ for (let i = 0; i < a.length; i++) {
55
+ const x = (a[i] ?? 0) !== 0;
56
+ const y = (b[i] ?? 0) !== 0;
57
+ if (x && y) inter++;
58
+ if (x || y) union++;
59
+ }
60
+ return union === 0 ? 1 : inter / union;
61
+ }
62
+
63
+ /** Max-pool a mask by `f` (any covered texel covers the block): the silhouette as seen from afar,
64
+ * where lattice gaps and leaf holes close up — what LOD2 has to match. */
65
+ export function poolMask(s: Silhouette, f: number): Silhouette {
66
+ const size = Math.floor(s.size / f);
67
+ const mask = new Uint8Array(size * size);
68
+ for (let y = 0; y < s.size; y++)
69
+ for (let x = 0; x < s.size; x++)
70
+ if (s.mask[y * s.size + x]) {
71
+ const i = Math.floor(y / f) * size + Math.floor(x / f);
72
+ if (i < mask.length) mask[i] = 1;
73
+ }
74
+ return { size, mask };
75
+ }
76
+
77
+ export function maskStats(s: Silhouette): MaskStats {
78
+ let n = 0;
79
+ let x0 = s.size;
80
+ let y0 = s.size;
81
+ let x1 = -1;
82
+ let y1 = -1;
83
+ for (let y = 0; y < s.size; y++)
84
+ for (let x = 0; x < s.size; x++) {
85
+ if (!s.mask[y * s.size + x]) continue;
86
+ n++;
87
+ if (x < x0) x0 = x;
88
+ if (x > x1) x1 = x;
89
+ if (y < y0) y0 = y;
90
+ if (y > y1) y1 = y;
91
+ }
92
+ const box = x1 < 0 ? 0 : (x1 - x0 + 1) * (y1 - y0 + 1);
93
+ return { coverage: n / (s.size * s.size), bboxFill: box ? n / box : 0 };
94
+ }
95
+
96
+ export interface ExposureStats {
97
+ /** Fraction of the frame the asset covers. */
98
+ readonly coverage: number;
99
+ /** The asset's screen bounding box as a fraction of the frame, larger axis (≈ 0.8 when framed). */
100
+ readonly span: number;
101
+ /** Mean relative luminance (0–1, sRGB-encoded values) over asset pixels. */
102
+ readonly mean: number;
103
+ /** Standard deviation of that luminance — low = flat, unreadable shading. */
104
+ readonly contrast: number;
105
+ readonly nearBlack: number;
106
+ readonly nearWhite: number;
107
+ }
108
+
109
+ /** Exposure of a rendered RGBA image (`width` px wide) over the pixels where `mask` ≥ 128. */
110
+ export function exposure(rgba: Uint8Array, mask: Uint8Array, width: number): ExposureStats {
111
+ const height = Math.max(1, Math.floor(mask.length / Math.max(1, width)));
112
+ let x0 = width;
113
+ let x1 = -1;
114
+ let y0 = height;
115
+ let y1 = -1;
116
+ let n = 0;
117
+ let sum = 0;
118
+ let sq = 0;
119
+ let black = 0;
120
+ let white = 0;
121
+ for (let i = 0; i < mask.length; i++) {
122
+ if ((mask[i] ?? 0) < 128) continue;
123
+ const x = i % width;
124
+ const y = Math.floor(i / width);
125
+ if (x < x0) x0 = x;
126
+ if (x > x1) x1 = x;
127
+ if (y < y0) y0 = y;
128
+ if (y > y1) y1 = y;
129
+ const o = i * 4;
130
+ const l =
131
+ (0.2126 * (rgba[o] ?? 0) + 0.7152 * (rgba[o + 1] ?? 0) + 0.0722 * (rgba[o + 2] ?? 0)) / 255;
132
+ n++;
133
+ sum += l;
134
+ sq += l * l;
135
+ if (l < 0.06) black++;
136
+ if (l > 0.97) white++;
137
+ }
138
+ const mean = n ? sum / n : 0;
139
+ return {
140
+ coverage: n / Math.max(1, mask.length),
141
+ span: x1 < 0 ? 0 : Math.max((x1 - x0 + 1) / width, (y1 - y0 + 1) / height),
142
+ mean,
143
+ contrast: n ? Math.sqrt(Math.max(0, sq / n - mean * mean)) : 0,
144
+ nearBlack: n ? black / n : 0,
145
+ nearWhite: n ? white / n : 0,
146
+ };
147
+ }