@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,80 @@
1
+ // Part-space → world-space. Parts chain translation + yaw through their parents; QA, the
2
+ // rasteriser and any back end that flattens a scene read world positions from here.
3
+
4
+ import type { Part } from './asset.ts';
5
+ import type { Mesh } from './mesh.ts';
6
+ import type { Vec3 } from './vec.ts';
7
+
8
+ export interface WorldTransform {
9
+ readonly origin: Vec3;
10
+ readonly yaw: number;
11
+ }
12
+
13
+ const rotZ = (p: Vec3, yaw: number): Vec3 => {
14
+ const c = Math.cos(yaw);
15
+ const s = Math.sin(yaw);
16
+ return [p[0] * c - p[1] * s, p[0] * s + p[1] * c, p[2]];
17
+ };
18
+
19
+ export function worldTransforms(parts: readonly Part[]): Map<string, WorldTransform> {
20
+ const out = new Map<string, WorldTransform>();
21
+ for (const p of parts) {
22
+ const parent = (p.parent ? out.get(p.parent) : undefined) ?? {
23
+ origin: [0, 0, 0] as Vec3,
24
+ yaw: 0,
25
+ };
26
+ const at = rotZ(p.at, parent.yaw);
27
+ out.set(p.name, {
28
+ origin: [parent.origin[0] + at[0], parent.origin[1] + at[1], parent.origin[2] + at[2]],
29
+ yaw: parent.yaw + p.yaw,
30
+ });
31
+ }
32
+ return out;
33
+ }
34
+
35
+ export function toWorld(t: WorldTransform, p: Vec3): Vec3 {
36
+ const r = rotZ(p, t.yaw);
37
+ return [r[0] + t.origin[0], r[1] + t.origin[1], r[2] + t.origin[2]];
38
+ }
39
+
40
+ export const rotateZ = rotZ;
41
+
42
+ /** A part's mesh baked into world space (positions and normals), ready for QA or rasterising. */
43
+ export function worldMesh(mesh: Mesh, t: WorldTransform): Mesh {
44
+ const positions = new Float32Array(mesh.positions.length);
45
+ const normals = new Float32Array(mesh.normals.length);
46
+ for (let i = 0; i < positions.length; i += 3) {
47
+ positions.set(
48
+ toWorld(t, [mesh.positions[i] ?? 0, mesh.positions[i + 1] ?? 0, mesh.positions[i + 2] ?? 0]),
49
+ i,
50
+ );
51
+ normals.set(
52
+ rotZ([mesh.normals[i] ?? 0, mesh.normals[i + 1] ?? 0, mesh.normals[i + 2] ?? 0], t.yaw),
53
+ i,
54
+ );
55
+ }
56
+ return { ...mesh, positions, normals };
57
+ }
58
+
59
+ // QA gates, checks, renderers and measures all ask for the same world meshes: compute once per
60
+ // parts array (a CompiledAsset's parts are immutable).
61
+ const worldCache = new WeakMap<readonly Part[], Map<string, Mesh>>();
62
+
63
+ /** Every part with geometry, in world space, keyed by part name. Cached per parts array. */
64
+ export function worldMeshes(parts: readonly Part[]): Map<string, Mesh> {
65
+ const hit = worldCache.get(parts);
66
+ if (hit) return hit;
67
+ const out = computeWorldMeshes(parts);
68
+ worldCache.set(parts, out);
69
+ return out;
70
+ }
71
+
72
+ function computeWorldMeshes(parts: readonly Part[]): Map<string, Mesh> {
73
+ const xf = worldTransforms(parts);
74
+ const out = new Map<string, Mesh>();
75
+ for (const p of parts) {
76
+ const t = xf.get(p.name);
77
+ if (p.mesh && t && p.mesh.indices.length > 0) out.set(p.name, worldMesh(p.mesh, t));
78
+ }
79
+ return out;
80
+ }
@@ -0,0 +1,79 @@
1
+ // Packs typed arrays into one 4-byte-aligned glTF buffer and records bufferViews + accessors.
2
+
3
+ export const ARRAY_BUFFER = 34962;
4
+ export const ELEMENT_ARRAY_BUFFER = 34963;
5
+ const FLOAT = 5126;
6
+ const UINT32 = 5125;
7
+ const UINT16 = 5123;
8
+
9
+ export interface Json {
10
+ [key: string]: unknown;
11
+ }
12
+
13
+ export class BinWriter {
14
+ private readonly chunks: Uint8Array[] = [];
15
+ length = 0;
16
+ readonly views: Json[] = [];
17
+ readonly accessors: Json[] = [];
18
+
19
+ private push(bytes: Uint8Array, target?: number): number {
20
+ const pad = (4 - (this.length % 4)) % 4;
21
+ if (pad) {
22
+ this.chunks.push(new Uint8Array(pad));
23
+ this.length += pad;
24
+ }
25
+ this.views.push({
26
+ buffer: 0,
27
+ byteOffset: this.length,
28
+ byteLength: bytes.byteLength,
29
+ ...(target ? { target } : {}),
30
+ });
31
+ this.chunks.push(bytes);
32
+ this.length += bytes.byteLength;
33
+ return this.views.length - 1;
34
+ }
35
+
36
+ accessor(
37
+ data: Float32Array | Uint32Array | Uint16Array,
38
+ type: 'SCALAR' | 'VEC2' | 'VEC3' | 'VEC4',
39
+ target?: number,
40
+ bounds = false,
41
+ /** Integer data read as 0..1 (vertex colour as normalised unsigned short). */
42
+ normalized = false,
43
+ ): number {
44
+ const view = this.push(new Uint8Array(data.buffer, data.byteOffset, data.byteLength), target);
45
+ const width = { SCALAR: 1, VEC2: 2, VEC3: 3, VEC4: 4 }[type];
46
+ const acc: Json = {
47
+ bufferView: view,
48
+ componentType:
49
+ data instanceof Uint32Array ? UINT32 : data instanceof Uint16Array ? UINT16 : FLOAT,
50
+ count: data.length / width,
51
+ type,
52
+ ...(normalized ? { normalized: true } : {}),
53
+ };
54
+ if (bounds) {
55
+ const min = Array<number>(width).fill(Infinity);
56
+ const max = Array<number>(width).fill(-Infinity);
57
+ for (let i = 0; i < data.length; i++) {
58
+ const k = i % width;
59
+ const v = data[i] ?? 0;
60
+ if (v < (min[k] ?? 0)) min[k] = v;
61
+ if (v > (max[k] ?? 0)) max[k] = v;
62
+ }
63
+ acc['min'] = min;
64
+ acc['max'] = max;
65
+ }
66
+ this.accessors.push(acc);
67
+ return this.accessors.length - 1;
68
+ }
69
+
70
+ bytes(): Uint8Array {
71
+ const out = new Uint8Array(Math.ceil(this.length / 4) * 4);
72
+ let o = 0;
73
+ for (const c of this.chunks) {
74
+ out.set(c, o);
75
+ o += c.byteLength;
76
+ }
77
+ return out;
78
+ }
79
+ }
@@ -0,0 +1,35 @@
1
+ // The GLB container: 12-byte header, JSON chunk (space-padded), BIN chunk (zero-padded), every
2
+ // length a multiple of 4 [glTF 2.0 §glb-file-format-specification].
3
+
4
+ import type { Json } from './bin-writer.ts';
5
+
6
+ export function packGlb(gltf: Json, body: Uint8Array): Uint8Array {
7
+ let json = new TextEncoder().encode(JSON.stringify(gltf));
8
+ const jsonPad = (4 - (json.byteLength % 4)) % 4;
9
+ if (jsonPad) {
10
+ const padded = new Uint8Array(json.byteLength + jsonPad).fill(0x20);
11
+ padded.set(json);
12
+ json = padded;
13
+ }
14
+ const total = 12 + 8 + json.byteLength + 8 + body.byteLength;
15
+ const out = new Uint8Array(total);
16
+ const dv = new DataView(out.buffer);
17
+ dv.setUint32(0, 0x46546c67, true);
18
+ dv.setUint32(4, 2, true);
19
+ dv.setUint32(8, total, true);
20
+ dv.setUint32(12, json.byteLength, true);
21
+ dv.setUint32(16, 0x4e4f534a, true);
22
+ out.set(json, 20);
23
+ const binAt = 20 + json.byteLength;
24
+ dv.setUint32(binAt, body.byteLength, true);
25
+ dv.setUint32(binAt + 4, 0x004e4942, true);
26
+ out.set(body, binAt + 8);
27
+ return out;
28
+ }
29
+
30
+ export function concat(a: Uint8Array, b: Uint8Array): Uint8Array {
31
+ const out = new Uint8Array(a.byteLength + b.byteLength);
32
+ out.set(a);
33
+ out.set(b, a.byteLength);
34
+ return out;
35
+ }
@@ -0,0 +1,25 @@
1
+ // Texture credits into the GLB: when an asset samples an image strip (a CC0 texture set registered
2
+ // with defineMaterial), its credit line joins the XMP packet's `dc:contributor`. Assets that use
3
+ // only procedural strips get no change, so their bytes stay identical.
4
+
5
+ import type { CompiledAsset } from '../core/index.ts';
6
+ import { stripCredits } from '../material/index.ts';
7
+ import type { GlbMeta } from './metadata.ts';
8
+
9
+ export function creditsOf(asset: CompiledAsset): string[] {
10
+ const out = new Set<string>();
11
+ for (const p of asset.parts) {
12
+ const uv = p.mesh?.uvs;
13
+ if (!uv) continue;
14
+ const v = new Float32Array(uv.length >> 1);
15
+ for (let i = 0; i < v.length; i++) v[i] = uv[i * 2 + 1] ?? 0;
16
+ for (const c of stripCredits(v)) out.add(c);
17
+ }
18
+ return [...out].sort();
19
+ }
20
+
21
+ export function withCredits(asset: CompiledAsset, meta: GlbMeta | undefined): GlbMeta | undefined {
22
+ const credits = creditsOf(asset);
23
+ if (!credits.length) return meta;
24
+ return { ...meta, credits: [...(meta?.credits ?? []), ...credits] };
25
+ }
@@ -0,0 +1,53 @@
1
+ // Options and results of the GLB writer (glb.ts): what goes into the file and what comes back.
2
+
3
+ import type { OptimizeOptions } from '../passes/index.ts';
4
+ import type { LodCoverage } from './lod-coverage.ts';
5
+ import type { GlowRule } from './materials.ts';
6
+ import type { GlbMeta } from './metadata.ts';
7
+ import type { ExportReport } from './optimize-parts.ts';
8
+ import type { SheetBinding } from './textures.ts';
9
+
10
+ export interface GlbOptions {
11
+ /** Keep every part as its own node (debugging, per-part picking). Default merges to pivots. */
12
+ readonly keepParts?: boolean;
13
+ /**
14
+ * Bake each part's `motion` into a looping `run` clip. Off by default: glTF has no autoplay
15
+ * flag and most viewers play the first clip, while engines usually drive the named pivots
16
+ * from game code. Turn it on for previews, cutscenes and ambient props.
17
+ */
18
+ readonly animations?: boolean;
19
+ /**
20
+ * Bind the painted trim sheet to every material: a `PaintedSheet` embeds the three PNGs (one
21
+ * self-contained file); `{ external: true, prefix? }` references shared `trim_sheet_*.png` by
22
+ * URI. Omitted: no textures, byte-identical to the untextured output.
23
+ */
24
+ readonly sheet?: SheetBinding;
25
+ /**
26
+ * Weld + vertex-cache + vertex-fetch optimisation per primitive (passes/optimize). On by
27
+ * default; `false` writes the meshes exactly as compiled (debugging the IR).
28
+ */
29
+ readonly optimize?: boolean | OptimizeOptions;
30
+ /** Parts that emit light (lamp lenses, lit windows): one emissive material per rule. */
31
+ readonly glow?: readonly GlowRule[];
32
+ /** Provenance: asset.copyright + an XMP packet (title, creators, licence, sources). */
33
+ readonly meta?: GlbMeta;
34
+ /**
35
+ * Single-file LODs only: [LOD0→LOD1, LOD1→LOD2, cull] screen-height fractions written as
36
+ * `MSFT_screencoverage`. Default: the asset's `lodCoverage`, else derived (lod-coverage.ts).
37
+ */
38
+ readonly lodCoverage?: LodCoverage;
39
+ }
40
+
41
+ /** The GLB bytes plus what the optimiser did (vertex counts, ACMR before/after, per part). */
42
+ export interface GlbResult {
43
+ readonly glb: Uint8Array;
44
+ readonly report: ExportReport;
45
+ }
46
+
47
+ /** One GLB holding several LODs, plus one optimiser report per LOD (same order as the input). */
48
+ export interface LodGlbResult {
49
+ readonly glb: Uint8Array;
50
+ readonly reports: readonly ExportReport[];
51
+ /** The `MSFT_screencoverage` written (empty for a single LOD). */
52
+ readonly coverage: readonly number[];
53
+ }
@@ -0,0 +1,187 @@
1
+ // glTF 2.0 binary writer. One material (the trim sheet) for the whole asset, one node per part
2
+ // with its pivot as translation, and each part's motion baked as a looping animation clip.
3
+ // Extras a stock viewer understands: light sockets → KHR_lights_punctual, glowing parts →
4
+ // emissive (+ KHR_materials_emissive_strength), provenance → asset.copyright + KHR_xmp_json_ld.
5
+ // `exportLodGlb` packs several LODs into ONE file (MSFT_lod alternates sharing materials,
6
+ // textures and motion samplers). Zero dependencies and byte-deterministic.
7
+
8
+ import type { CompiledAsset, Part } from '../core/index.ts';
9
+ import { Ts23dError } from '../core/index.ts';
10
+ import { BinWriter, type Json } from './bin-writer.ts';
11
+ import { concat, packGlb } from './container.ts';
12
+ import { withCredits } from './credits.ts';
13
+ import type { GlbOptions, GlbResult, LodGlbResult } from './glb-options.ts';
14
+ import { newDoc, writeHierarchy } from './hierarchy.ts';
15
+ import { LIGHTS } from './lights.ts';
16
+ import {
17
+ checkCoverage,
18
+ coverageFor,
19
+ defaultCoverage,
20
+ MSFT_LOD,
21
+ SCREEN_COVERAGE,
22
+ } from './lod-coverage.ts';
23
+ import { EMISSIVE_STRENGTH, type GlowRule, glowMaterial, materialsFor } from './materials.ts';
24
+ import { globRe, glowLayer, mergeToPivots } from './merge-pivots.ts';
25
+ import { metadata } from './metadata.ts';
26
+ import { optimizeParts, summarize } from './optimize-parts.ts';
27
+ import { sheetGltf } from './textures.ts';
28
+
29
+ // Viewers auto-play the FIRST clip and glTF has no "paused" flag, so the first clip is `rest`: two
30
+ // keys holding the root's rest rotation. The file opens still; pick `run` to play it.
31
+ // Authoring frame: Z up, +X front. glTF: Y up, +Z front, +X = the asset's left. This root
32
+ // rotation maps +X→+Z, +Z→+Y, +Y→+X (verified numerically), so assets face forward in engines.
33
+ const Z_UP_TO_Y_UP = [-0.5, -0.5, -0.5, 0.5];
34
+ function restClip(bin: BinWriter, roots: readonly number[]): Json {
35
+ const input = bin.accessor(new Float32Array([0, 1]), 'SCALAR', undefined, true);
36
+ const output = bin.accessor(new Float32Array([...Z_UP_TO_Y_UP, ...Z_UP_TO_Y_UP]), 'VEC4');
37
+ return {
38
+ name: 'rest',
39
+ samplers: [{ input, output, interpolation: 'STEP' }],
40
+ channels: roots.map((node) => ({ sampler: 0, target: { node, path: 'rotation' } })),
41
+ };
42
+ }
43
+
44
+ export type { GlbOptions, GlbResult, LodGlbResult } from './glb-options.ts';
45
+
46
+ export function toGlb(input: CompiledAsset, o: GlbOptions = {}): Uint8Array {
47
+ return exportGlb(input, o).glb;
48
+ }
49
+
50
+ export function exportGlb(input: CompiledAsset, o: GlbOptions = {}): GlbResult {
51
+ const { glb, reports } = exportLodGlb([input], o);
52
+ return { glb, report: reports[0] ?? summarize([], false) };
53
+ }
54
+
55
+ /** All LODs in one GLB: LOD0 in the scene, the rest as MSFT_lod alternates outside it. */
56
+ export function toLodGlb(lods: readonly CompiledAsset[], o: GlbOptions = {}): Uint8Array {
57
+ return exportLodGlb(lods, o).glb;
58
+ }
59
+
60
+ function checkGlow(rules: readonly GlowRule[]): void {
61
+ for (const r of rules)
62
+ if ((r.strength ?? 4) < 0 || (r.color ?? [0, 0, 0]).some((c) => c < 0 || c > 1))
63
+ throw new Ts23dError('T3D_RECIPE_INVALID', {
64
+ cause: `glow rule for ${JSON.stringify(r.parts)} has a colour outside 0..1 or a negative strength`,
65
+ fix: 'colour is linear 0..1 (hex() converts sRGB); brightness goes in `strength`',
66
+ });
67
+ }
68
+
69
+ function checkLods(lods: readonly CompiledAsset[]): void {
70
+ const names = new Set(lods.map((l) => l.name));
71
+ const levels = lods.map((l) => l.lod);
72
+ if (
73
+ lods.length === 0 ||
74
+ names.size !== 1 ||
75
+ levels.some((l, i) => i > 0 && l <= (levels[i - 1] ?? 0))
76
+ )
77
+ throw new Ts23dError('T3D_RECIPE_INVALID', {
78
+ cause: `exportLodGlb got LODs [${levels.join(', ')}] of [${[...names].join(', ')}]`,
79
+ fix: 'pass compile(asset, { lod }) results of ONE asset, highest detail first: LOD0, LOD1, LOD2',
80
+ });
81
+ }
82
+
83
+ /** One LOD prepared for writing: merged + optimised parts and its glow-layer lookup. */
84
+ function prepare(input: CompiledAsset, o: GlbOptions, glow: readonly GlowRule[]) {
85
+ const opt = o.optimize === false ? false : o.optimize === true ? {} : (o.optimize ?? {});
86
+ const merged = o.keepParts
87
+ ? input
88
+ : mergeToPivots(
89
+ input,
90
+ glow.map((g) => g.parts),
91
+ );
92
+ const { asset, report } = optimizeParts(merged, opt);
93
+ // optimizeParts copies parts: carry the glow tag across by position.
94
+ const glowRes = glow.map((g) => g.parts.map(globRe));
95
+ const glowOf = (p: Part, i: number): number =>
96
+ glowLayer.get(merged.parts[i] ?? p) ??
97
+ (o.keepParts ? glowRes.findIndex((rs) => rs.some((r) => r.test(p.name))) : -1);
98
+ return { asset, report, glowOf };
99
+ }
100
+
101
+ export function exportLodGlb(lods: readonly CompiledAsset[], o: GlbOptions = {}): LodGlbResult {
102
+ checkLods(lods);
103
+ const top = lods[0] as CompiledAsset;
104
+ const glow = o.glow ?? top.glow ?? [];
105
+ checkGlow(glow);
106
+ const preps = lods.map((l) => prepare(l, o, glow));
107
+ const withSheet = o.sheet !== undefined;
108
+ const mats = materialsFor(
109
+ new Set(preps.flatMap((p) => p.asset.parts.flatMap((q) => (q.surface ? [q.surface] : [])))),
110
+ withSheet,
111
+ );
112
+ const glowAt = mats.materials.length;
113
+ const materials = [...mats.materials, ...glow.map((g, i) => glowMaterial(g, i, withSheet))];
114
+ const doc = newDoc(new BinWriter());
115
+ const roots = preps.map(({ asset, glowOf }, k) =>
116
+ writeHierarchy(doc, asset, k === 0 ? asset.name : `${asset.name}_LOD${asset.lod}`, {
117
+ withSheet,
118
+ animations: o.animations === true,
119
+ materialOf: (part, i) => {
120
+ const g = glowOf(part, i);
121
+ return g >= 0 ? glowAt + g : (mats.index.get(part.surface ?? 'opaque') ?? 0);
122
+ },
123
+ }),
124
+ );
125
+ for (const r of roots) {
126
+ const node = doc.nodes[r];
127
+ if (node) node['rotation'] = Z_UP_TO_Y_UP;
128
+ }
129
+ const coverage: number[] = [];
130
+ const rootNode = doc.nodes[0];
131
+ if (roots.length > 1 && rootNode) {
132
+ const c = o.lodCoverage ?? top.lodCoverage ?? defaultCoverage(lods);
133
+ checkCoverage(c, top.name);
134
+ coverage.push(
135
+ ...coverageFor(
136
+ lods.map((l) => l.lod),
137
+ c,
138
+ ),
139
+ );
140
+ // Alternates stay OUT of the scene's root list: a viewer without MSFT_lod shows LOD0 only.
141
+ rootNode['extensions'] = { [MSFT_LOD]: { ids: roots.slice(1) } };
142
+ rootNode['extras'] = { [SCREEN_COVERAGE]: coverage };
143
+ }
144
+
145
+ // Every accessor — the rest clip's included — must be written BEFORE the buffer is sealed.
146
+ const { bin, lights, samplers, channels } = doc;
147
+ const animations = channels.length
148
+ ? [restClip(bin, roots), { name: 'run', samplers, channels }]
149
+ : [];
150
+ const geometry = bin.bytes();
151
+ const tex = o.sheet ? sheetGltf(o.sheet, bin.views, geometry.byteLength) : null;
152
+ const body = tex?.tail.byteLength ? concat(geometry, tex.tail) : geometry;
153
+ const meta = metadata(top.name, withCredits(top, o.meta ?? top.meta));
154
+ const used = [
155
+ ...mats.extensionsUsed,
156
+ ...(glow.some((g) => (g.strength ?? 4) !== 1) ? [EMISSIVE_STRENGTH] : []),
157
+ ...(lights.length ? [LIGHTS] : []),
158
+ ...(coverage.length ? [MSFT_LOD] : []),
159
+ ...meta.used,
160
+ ].sort();
161
+ const extensions: Json = {
162
+ ...(lights.length ? { [LIGHTS]: { lights } } : {}),
163
+ ...(meta.extensions ?? {}),
164
+ };
165
+ const gltf: Json = {
166
+ asset: meta.asset,
167
+ extras: {
168
+ kind: top.kind,
169
+ lod: coverage.length ? lods.map((l) => l.lod) : top.lod,
170
+ units: 'metres',
171
+ up: 'Z (rotated to glTF Y-up at the root)',
172
+ },
173
+ ...(used.length ? { extensionsUsed: used } : {}),
174
+ ...(Object.keys(extensions).length ? { extensions } : {}),
175
+ scene: 0,
176
+ scenes: [{ name: top.name, nodes: [0] }],
177
+ nodes: doc.nodes,
178
+ meshes: doc.meshes,
179
+ materials,
180
+ ...(tex ? tex.json : {}),
181
+ buffers: [{ byteLength: body.byteLength }],
182
+ bufferViews: bin.views,
183
+ accessors: bin.accessors,
184
+ ...(animations.length ? { animations } : {}),
185
+ };
186
+ return { glb: packGlb(gltf, body), reports: preps.map((p) => p.report), coverage };
187
+ }
@@ -0,0 +1,122 @@
1
+ // Writes one LOD's node tree into a shared glTF document: a root node, one node per (merged) part
2
+ // with its pivot as translation, light nodes under light sockets, meshes, and the part motions as
3
+ // `run` channels. Called once per LOD; the bin, materials, lights and motion samplers are shared,
4
+ // so a single-file GLB stores each identical motion and light only once.
5
+
6
+ import type { CompiledAsset, Part } from '../core/index.ts';
7
+ import { quatFromAxisAngle } from '../core/index.ts';
8
+ import type { BinWriter, Json } from './bin-writer.ts';
9
+ import { LIGHTS, lightOf } from './lights.ts';
10
+ import { motionChannel } from './motion.ts';
11
+ import { writePrimitive } from './primitive.ts';
12
+
13
+ /** The growing glTF arrays every LOD appends to. */
14
+ export interface GltfDoc {
15
+ readonly bin: BinWriter;
16
+ readonly nodes: Json[];
17
+ readonly meshes: Json[];
18
+ readonly lights: Json[];
19
+ readonly samplers: Json[];
20
+ readonly channels: Json[];
21
+ /** motion + rest pose → sampler index: the same wheel spin at LOD0 and LOD1 is one sampler. */
22
+ readonly motionSamplers: Map<string, number>;
23
+ readonly lightIndex: Map<string, number>;
24
+ }
25
+
26
+ export const newDoc = (bin: BinWriter): GltfDoc => ({
27
+ bin,
28
+ nodes: [],
29
+ meshes: [],
30
+ lights: [],
31
+ samplers: [],
32
+ channels: [],
33
+ motionSamplers: new Map(),
34
+ lightIndex: new Map(),
35
+ });
36
+
37
+ export interface HierarchyOptions {
38
+ /** Material index for a part (trim sheet by surface, or its glow layer). */
39
+ readonly materialOf: (part: Part, i: number) => number;
40
+ readonly withSheet: boolean;
41
+ readonly animations: boolean;
42
+ }
43
+
44
+ function attach(doc: GltfDoc, parentId: number, id: number): void {
45
+ const parent = doc.nodes[parentId];
46
+ if (!parent) return;
47
+ parent['children'] = [...((parent['children'] as number[] | undefined) ?? []), id];
48
+ }
49
+
50
+ function lightIndexOf(doc: GltfDoc, light: Json): number {
51
+ const key = JSON.stringify(light);
52
+ const known = doc.lightIndex.get(key);
53
+ if (known !== undefined) return known;
54
+ doc.lights.push(light);
55
+ doc.lightIndex.set(key, doc.lights.length - 1);
56
+ return doc.lights.length - 1;
57
+ }
58
+
59
+ function addMotion(doc: GltfDoc, part: Part, id: number): void {
60
+ if (!part.motion) return;
61
+ const key = JSON.stringify([part.motion, part.at]);
62
+ let sampler = doc.motionSamplers.get(key);
63
+ let channel: Json;
64
+ if (sampler === undefined) {
65
+ const written = motionChannel(doc.bin, part.motion, id, part.at);
66
+ doc.samplers.push(written.sampler);
67
+ sampler = doc.samplers.length - 1;
68
+ doc.motionSamplers.set(key, sampler);
69
+ channel = written.channel;
70
+ } else {
71
+ channel = {
72
+ target: { node: id, path: part.motion.kind === 'slide' ? 'translation' : 'rotation' },
73
+ };
74
+ }
75
+ doc.channels.push({ ...channel, sampler });
76
+ }
77
+
78
+ /** Appends `asset`'s tree under a new root named `rootName`; returns the root node index. */
79
+ export function writeHierarchy(
80
+ doc: GltfDoc,
81
+ asset: CompiledAsset,
82
+ rootName: string,
83
+ o: HierarchyOptions,
84
+ ): number {
85
+ doc.nodes.push({ name: rootName, children: [] as number[] });
86
+ const root = doc.nodes.length - 1;
87
+ const index = new Map<string, number>();
88
+ asset.parts.forEach((part: Part, i) => {
89
+ const node: Json = { name: part.name, translation: [...part.at] };
90
+ if (part.yaw) node['rotation'] = [...quatFromAxisAngle([0, 0, 1], part.yaw)];
91
+ if (part.emitter || part.socket) {
92
+ node['extras'] = {
93
+ ts23d: {
94
+ ...(part.emitter ? { emitter: part.emitter } : {}),
95
+ ...(part.socket ? { socket: part.socket } : {}),
96
+ },
97
+ };
98
+ }
99
+ if (part.mesh && part.mesh.indices.length > 0) {
100
+ doc.meshes.push({
101
+ name: part.name,
102
+ primitives: [writePrimitive(doc.bin, part.mesh, o.materialOf(part, i), o.withSheet)],
103
+ });
104
+ node['mesh'] = doc.meshes.length - 1;
105
+ }
106
+ doc.nodes.push(node);
107
+ const id = doc.nodes.length - 1;
108
+ index.set(part.name, id);
109
+ attach(doc, part.parent === null ? root : (index.get(part.parent) ?? root), id);
110
+ const lit = lightOf(part);
111
+ if (lit) {
112
+ doc.nodes.push({
113
+ name: `${part.name}_light`,
114
+ rotation: [...lit.rotation],
115
+ extensions: { [LIGHTS]: { light: lightIndexOf(doc, lit.light) } },
116
+ });
117
+ attach(doc, id, doc.nodes.length - 1);
118
+ }
119
+ if (o.animations) addMotion(doc, part, id);
120
+ });
121
+ return root;
122
+ }
@@ -0,0 +1,34 @@
1
+ // Public API of /export: back ends that turn a CompiledAsset into a file.
2
+
3
+ export {
4
+ exportGlb,
5
+ exportLodGlb,
6
+ type GlbOptions,
7
+ type GlbResult,
8
+ type LodGlbResult,
9
+ toGlb,
10
+ toLodGlb,
11
+ } from './glb.ts';
12
+ export {
13
+ checkCoverage,
14
+ coverageFor,
15
+ defaultCoverage,
16
+ type LodCoverage,
17
+ MSFT_LOD,
18
+ SCREEN_COVERAGE,
19
+ } from './lod-coverage.ts';
20
+ export { mergeToPivots } from './merge-pivots.ts';
21
+ export {
22
+ type ExportReport,
23
+ optimizeParts,
24
+ type PartOptimizeReport,
25
+ summarize,
26
+ } from './optimize-parts.ts';
27
+ export {
28
+ type ExternalSheet,
29
+ SHEET_MAPS,
30
+ type SheetBinding,
31
+ type SheetMap,
32
+ sheetFileName,
33
+ sheetPngs,
34
+ } from './textures.ts';
@@ -0,0 +1,49 @@
1
+ // KHR_lights_punctual from light sockets: `ctx.socket('headlamp_l', { kind: 'light', dir,
2
+ // data: { light: 'spot', color: '#ffe8c0', intensity: 200, range: 30, cone: 25 } })` becomes a
3
+ // real light every engine and viewer can switch on. The socket node stays as authored; the light
4
+ // rides a child node `<socket>_light` rotated so its −Z (the glTF light direction) is `dir`.
5
+
6
+ import type { Part, Quat, Vec3 } from '../core/index.ts';
7
+ import { hex } from '../material/index.ts';
8
+ import type { Json } from './bin-writer.ts';
9
+
10
+ export const LIGHTS = 'KHR_lights_punctual';
11
+
12
+ /** Shortest rotation taking unit `a` onto unit `b`. */
13
+ function between(a: Vec3, b: Vec3): Quat {
14
+ const d = a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
15
+ if (d < -0.999999) return [1, 0, 0, 0]; // opposite: half turn about X (⊥ to −Z)
16
+ const c: Vec3 = [a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0]];
17
+ const w = 1 + d;
18
+ const n = Math.hypot(c[0], c[1], c[2], w);
19
+ return [c[0] / n, c[1] / n, c[2] / n, w / n];
20
+ }
21
+
22
+ const num = (v: unknown, fallback: number): number =>
23
+ typeof v === 'number' && Number.isFinite(v) ? v : fallback;
24
+
25
+ /** The glTF light for a light socket, or null for any other part. */
26
+ export function lightOf(part: Part): { light: Json; rotation: Quat } | null {
27
+ const s = part.socket;
28
+ if (s?.kind !== 'light') return null;
29
+ const d = s.data ?? {};
30
+ const type = d['light'] === 'point' || d['light'] === 'directional' ? d['light'] : 'spot';
31
+ const color = typeof d['color'] === 'string' ? hex(d['color']) : [1, 1, 1];
32
+ const dir = s.dir ?? [1, 0, 0];
33
+ const len = Math.hypot(...dir) || 1;
34
+ const outer = (Math.min(89, num(d['cone'], 30)) * Math.PI) / 180;
35
+ const inner = Math.min(outer * 0.999, (num(d['inner'], 0) * Math.PI) / 180);
36
+ const range = num(d['range'], 0);
37
+ return {
38
+ light: {
39
+ name: part.name,
40
+ type,
41
+ color,
42
+ // Spot/point: candela; directional: lux. Defaults suit a vehicle lamp / a sun.
43
+ intensity: num(d['intensity'], type === 'directional' ? 3 : type === 'point' ? 20 : 100),
44
+ ...(range > 0 && type !== 'directional' ? { range } : {}),
45
+ ...(type === 'spot' ? { spot: { innerConeAngle: inner, outerConeAngle: outer } } : {}),
46
+ },
47
+ rotation: between([0, 0, -1], [dir[0] / len, dir[1] / len, dir[2] / len]),
48
+ };
49
+ }