@developerz.ai/ts23d 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (524) 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 +79 -0
  13. package/CLAUDE.md +85 -0
  14. package/LICENSE +21 -0
  15. package/README.md +380 -2
  16. package/dist/chunk-11v28977.js +1575 -0
  17. package/dist/chunk-11v28977.js.map +30 -0
  18. package/dist/chunk-1ypfs7ga.js +3571 -0
  19. package/dist/chunk-1ypfs7ga.js.map +42 -0
  20. package/dist/chunk-4ddzsqtc.js +464 -0
  21. package/dist/chunk-4ddzsqtc.js.map +19 -0
  22. package/dist/chunk-a8fxakcj.js +19 -0
  23. package/dist/chunk-a8fxakcj.js.map +11 -0
  24. package/dist/chunk-gcyp9b4d.js +755 -0
  25. package/dist/chunk-gcyp9b4d.js.map +22 -0
  26. package/dist/chunk-kgvbpqqw.js +44 -0
  27. package/dist/chunk-kgvbpqqw.js.map +10 -0
  28. package/dist/chunk-kvcep7ft.js +1919 -0
  29. package/dist/chunk-kvcep7ft.js.map +43 -0
  30. package/dist/chunk-ravbb06q.js +89 -0
  31. package/dist/chunk-ravbb06q.js.map +11 -0
  32. package/dist/chunk-w1zyhj40.js +1031 -0
  33. package/dist/chunk-w1zyhj40.js.map +21 -0
  34. package/dist/chunk-wtqhq529.js +2532 -0
  35. package/dist/chunk-wtqhq529.js.map +43 -0
  36. package/dist/chunk-xnt2sdhf.js +836 -0
  37. package/dist/chunk-xnt2sdhf.js.map +17 -0
  38. package/dist/cli/args.d.ts +24 -0
  39. package/dist/cli/bin.d.ts +2 -0
  40. package/dist/cli/bin.js +307 -0
  41. package/dist/cli/bin.js.map +14 -0
  42. package/dist/cli/load.d.ts +2 -0
  43. package/dist/cli/manifest.d.ts +49 -0
  44. package/dist/cli/render-cmds.d.ts +12 -0
  45. package/dist/cli/run.d.ts +1 -0
  46. package/dist/core/asset.d.ts +10 -0
  47. package/dist/core/definition.d.ts +148 -0
  48. package/dist/core/errors.d.ts +17 -0
  49. package/dist/core/family.d.ts +23 -0
  50. package/dist/core/index.d.ts +9 -0
  51. package/dist/core/index.js +69 -0
  52. package/dist/core/index.js.map +9 -0
  53. package/dist/core/mesh.d.ts +35 -0
  54. package/dist/core/part.d.ts +114 -0
  55. package/dist/core/png.d.ts +1 -0
  56. package/dist/core/rng.d.ts +12 -0
  57. package/dist/core/transform.d.ts +20 -0
  58. package/dist/core/vec.d.ts +11 -0
  59. package/dist/core/world.d.ts +16 -0
  60. package/dist/export/bin-writer.d.ts +16 -0
  61. package/dist/export/container.d.ts +3 -0
  62. package/dist/export/glb.d.ts +38 -0
  63. package/dist/export/index.d.ts +4 -0
  64. package/dist/export/index.js +26 -0
  65. package/dist/export/index.js.map +9 -0
  66. package/dist/export/lights.d.ts +8 -0
  67. package/dist/export/materials.d.ts +23 -0
  68. package/dist/export/merge-pivots.d.ts +6 -0
  69. package/dist/export/metadata.d.ts +19 -0
  70. package/dist/export/motion.d.ts +9 -0
  71. package/dist/export/optimize-parts.d.ts +26 -0
  72. package/dist/export/primitive.d.ts +3 -0
  73. package/dist/export/tangents.d.ts +2 -0
  74. package/dist/export/textures.d.ts +22 -0
  75. package/dist/export/version.d.ts +2 -0
  76. package/dist/grammar/index.d.ts +2 -0
  77. package/dist/grammar/index.js +16 -0
  78. package/dist/grammar/index.js.map +9 -0
  79. package/dist/grammar/split.d.ts +21 -0
  80. package/dist/grammar/whyte.d.ts +5 -0
  81. package/dist/index.d.ts +11 -0
  82. package/dist/index.js +411 -0
  83. package/dist/index.js.map +9 -0
  84. package/dist/kinds/building.d.ts +44 -0
  85. package/dist/kinds/faces.d.ts +27 -0
  86. package/dist/kinds/head-armour.d.ts +9 -0
  87. package/dist/kinds/head-beard.d.ts +6 -0
  88. package/dist/kinds/head-bust.d.ts +4 -0
  89. package/dist/kinds/head-extras.d.ts +15 -0
  90. package/dist/kinds/head-face.d.ts +6 -0
  91. package/dist/kinds/head-geom.d.ts +7 -0
  92. package/dist/kinds/head-hair.d.ts +4 -0
  93. package/dist/kinds/head-layout.d.ts +29 -0
  94. package/dist/kinds/head-mesh.d.ts +27 -0
  95. package/dist/kinds/head-params.d.ts +108 -0
  96. package/dist/kinds/head-presets.d.ts +19 -0
  97. package/dist/kinds/head-rim.d.ts +10 -0
  98. package/dist/kinds/head.d.ts +39 -0
  99. package/dist/kinds/hull-lines.d.ts +65 -0
  100. package/dist/kinds/hull-skin.d.ts +6 -0
  101. package/dist/kinds/hull.d.ts +14 -0
  102. package/dist/kinds/index.d.ts +16 -0
  103. package/dist/kinds/index.js +78 -0
  104. package/dist/kinds/index.js.map +9 -0
  105. package/dist/kinds/mass.d.ts +15 -0
  106. package/dist/kinds/openings.d.ts +30 -0
  107. package/dist/kinds/planned.d.ts +6 -0
  108. package/dist/kinds/roof-chimney.d.ts +16 -0
  109. package/dist/kinds/roof-shape.d.ts +64 -0
  110. package/dist/kinds/roof.d.ts +31 -0
  111. package/dist/kinds/vegetation-fronds.d.ts +15 -0
  112. package/dist/kinds/vegetation-impostor.d.ts +9 -0
  113. package/dist/kinds/vegetation-leaves.d.ts +15 -0
  114. package/dist/kinds/vegetation-mesh.d.ts +21 -0
  115. package/dist/kinds/vegetation-params.d.ts +90 -0
  116. package/dist/kinds/vegetation-presets.d.ts +20 -0
  117. package/dist/kinds/vegetation-skeleton.d.ts +20 -0
  118. package/dist/kinds/vegetation.d.ts +42 -0
  119. package/dist/kinds/vehicle.d.ts +42 -0
  120. package/dist/material/generator.d.ts +4 -0
  121. package/dist/material/index.d.ts +4 -0
  122. package/dist/material/index.js +38 -0
  123. package/dist/material/index.js.map +9 -0
  124. package/dist/material/paint/bond.d.ts +3 -0
  125. package/dist/material/paint/canvas.d.ts +32 -0
  126. package/dist/material/paint/cell.d.ts +4 -0
  127. package/dist/material/paint/cells-round.d.ts +5 -0
  128. package/dist/material/paint/cells.d.ts +7 -0
  129. package/dist/material/paint/compose.d.ts +5 -0
  130. package/dist/material/paint/concrete.d.ts +3 -0
  131. package/dist/material/paint/courses.d.ts +31 -0
  132. package/dist/material/paint/decode-png.d.ts +6 -0
  133. package/dist/material/paint/default-sheet.d.ts +2 -0
  134. package/dist/material/paint/foliage-broad.d.ts +5 -0
  135. package/dist/material/paint/foliage-common.d.ts +35 -0
  136. package/dist/material/paint/foliage-compound.d.ts +4 -0
  137. package/dist/material/paint/foliage-conifer.d.ts +5 -0
  138. package/dist/material/paint/foliage-raster.d.ts +26 -0
  139. package/dist/material/paint/foliage.d.ts +3 -0
  140. package/dist/material/paint/masks.d.ts +3 -0
  141. package/dist/material/paint/noise.d.ts +9 -0
  142. package/dist/material/paint/normal.d.ts +8 -0
  143. package/dist/material/paint/orm.d.ts +8 -0
  144. package/dist/material/paint/planks.d.ts +3 -0
  145. package/dist/material/paint/plates.d.ts +3 -0
  146. package/dist/material/paint/shapes.d.ts +8 -0
  147. package/dist/material/paint/slate.d.ts +3 -0
  148. package/dist/material/paint/stamp.d.ts +3 -0
  149. package/dist/material/paint/strip.d.ts +3 -0
  150. package/dist/material/paint/surface.d.ts +5 -0
  151. package/dist/material/paint/types.d.ts +35 -0
  152. package/dist/material/paint/validate.d.ts +3 -0
  153. package/dist/material/palette.d.ts +12 -0
  154. package/dist/material/shader-godot.d.ts +2 -0
  155. package/dist/material/shader-three.d.ts +2 -0
  156. package/dist/material/shader.d.ts +46 -0
  157. package/dist/material/trim-sheet.d.ts +22 -0
  158. package/dist/neural/generator.d.ts +25 -0
  159. package/dist/neural/images.d.ts +17 -0
  160. package/dist/neural/index.d.ts +2 -0
  161. package/dist/neural/index.js +14 -0
  162. package/dist/neural/index.js.map +9 -0
  163. package/dist/passes/bake-ao.d.ts +17 -0
  164. package/dist/passes/grime.d.ts +15 -0
  165. package/dist/passes/index.d.ts +6 -0
  166. package/dist/passes/index.js +22 -0
  167. package/dist/passes/index.js.map +9 -0
  168. package/dist/passes/livery.d.ts +19 -0
  169. package/dist/passes/optimize-cache.d.ts +15 -0
  170. package/dist/passes/optimize-weld.d.ts +15 -0
  171. package/dist/passes/optimize.d.ts +28 -0
  172. package/dist/passes/ray-grid.d.ts +23 -0
  173. package/dist/passes/weighted-normals.d.ts +8 -0
  174. package/dist/primitives/beam.d.ts +17 -0
  175. package/dist/primitives/bevel-box.d.ts +13 -0
  176. package/dist/primitives/box.d.ts +16 -0
  177. package/dist/primitives/cell.d.ts +20 -0
  178. package/dist/primitives/check.d.ts +12 -0
  179. package/dist/primitives/closed.d.ts +11 -0
  180. package/dist/primitives/erosion.d.ts +22 -0
  181. package/dist/primitives/font-5x7.d.ts +3 -0
  182. package/dist/primitives/heightfield.d.ts +16 -0
  183. package/dist/primitives/index.d.ts +23 -0
  184. package/dist/primitives/index.js +81 -0
  185. package/dist/primitives/index.js.map +9 -0
  186. package/dist/primitives/lathe-facets.d.ts +8 -0
  187. package/dist/primitives/lathe.d.ts +26 -0
  188. package/dist/primitives/lattice.d.ts +30 -0
  189. package/dist/primitives/loft.d.ts +15 -0
  190. package/dist/primitives/mc-table.d.ts +2 -0
  191. package/dist/primitives/member.d.ts +19 -0
  192. package/dist/primitives/noise.d.ts +34 -0
  193. package/dist/primitives/planar.d.ts +10 -0
  194. package/dist/primitives/prism.d.ts +13 -0
  195. package/dist/primitives/sculpt-edit.d.ts +56 -0
  196. package/dist/primitives/sculpt-tree.d.ts +23 -0
  197. package/dist/primitives/sculpt.d.ts +27 -0
  198. package/dist/primitives/sdf-grid.d.ts +17 -0
  199. package/dist/primitives/sdf-ops.d.ts +37 -0
  200. package/dist/primitives/sdf.d.ts +21 -0
  201. package/dist/primitives/strip-uv.d.ts +11 -0
  202. package/dist/primitives/surface-nets.d.ts +7 -0
  203. package/dist/primitives/sweep.d.ts +19 -0
  204. package/dist/primitives/terrain-color.d.ts +30 -0
  205. package/dist/primitives/terrain-grid.d.ts +36 -0
  206. package/dist/primitives/text-layout.d.ts +22 -0
  207. package/dist/primitives/text.d.ts +27 -0
  208. package/dist/primitives/tiled.d.ts +22 -0
  209. package/dist/primitives/transport.d.ts +17 -0
  210. package/dist/primitives/triangulate.d.ts +2 -0
  211. package/dist/primitives/value-noise.d.ts +10 -0
  212. package/dist/qa/checks.d.ts +11 -0
  213. package/dist/qa/clash.d.ts +2 -0
  214. package/dist/qa/floating.d.ts +5 -0
  215. package/dist/qa/gates.d.ts +12 -0
  216. package/dist/qa/geometry.d.ts +21 -0
  217. package/dist/qa/index.d.ts +12 -0
  218. package/dist/qa/index.js +78 -0
  219. package/dist/qa/index.js.map +9 -0
  220. package/dist/qa/inside.d.ts +12 -0
  221. package/dist/qa/inspect.d.ts +18 -0
  222. package/dist/qa/lods.d.ts +12 -0
  223. package/dist/qa/measures.d.ts +4 -0
  224. package/dist/qa/occlusion.d.ts +3 -0
  225. package/dist/qa/sanity.d.ts +7 -0
  226. package/dist/qa/types.d.ts +12 -0
  227. package/dist/render/beauty.d.ts +40 -0
  228. package/dist/render/camera.d.ts +33 -0
  229. package/dist/render/depth-map.d.ts +38 -0
  230. package/dist/render/font.d.ts +1 -0
  231. package/dist/render/ground.d.ts +13 -0
  232. package/dist/render/index.d.ts +10 -0
  233. package/dist/render/index.js +47 -0
  234. package/dist/render/index.js.map +9 -0
  235. package/dist/render/lighting.d.ts +20 -0
  236. package/dist/render/lod-strip.d.ts +5 -0
  237. package/dist/render/metrics.d.ts +37 -0
  238. package/dist/render/options.d.ts +28 -0
  239. package/dist/render/photo-match.d.ts +19 -0
  240. package/dist/render/raster.d.ts +25 -0
  241. package/dist/render/resolve.d.ts +11 -0
  242. package/dist/render/review-prompt.d.ts +2 -0
  243. package/dist/render/review-warnings.d.ts +34 -0
  244. package/dist/render/review.d.ts +24 -0
  245. package/dist/render/scene.d.ts +18 -0
  246. package/dist/render/shade.d.ts +22 -0
  247. package/dist/render/sheet.d.ts +15 -0
  248. package/dist/render/texture.d.ts +15 -0
  249. package/dist/render/thumbnail.d.ts +8 -0
  250. package/dist/render/vis-buffer.d.ts +18 -0
  251. package/dist/testing/assert.d.ts +21 -0
  252. package/dist/testing/compiled.d.ts +15 -0
  253. package/dist/testing/hash.d.ts +1 -0
  254. package/dist/testing/index.d.ts +4 -0
  255. package/dist/testing/index.js +134 -0
  256. package/dist/testing/index.js.map +13 -0
  257. package/dist/testing/snapshot.d.ts +13 -0
  258. package/docs/README.md +52 -0
  259. package/docs/architecture/README.md +68 -0
  260. package/docs/architecture/adding-a-kind.md +44 -0
  261. package/docs/architecture/adding-a-primitive.md +49 -0
  262. package/docs/architecture/errors.md +47 -0
  263. package/docs/architecture/glb-production.md +214 -0
  264. package/docs/architecture/gltf-capabilities.md +80 -0
  265. package/docs/decisions/001-recipe-compiler-over-ai-meshes.md +27 -0
  266. package/docs/decisions/002-typescript-not-json.md +22 -0
  267. package/docs/decisions/003-shared-trim-sheet.md +21 -0
  268. package/docs/decisions/004-z-up-metres.md +21 -0
  269. package/docs/decisions/005-zero-runtime-deps.md +21 -0
  270. package/docs/decisions/006-single-package-subpath-exports.md +20 -0
  271. package/docs/decisions/007-cpu-contact-sheets.md +21 -0
  272. package/docs/decisions/008-surface-materials.md +21 -0
  273. package/docs/decisions/009-gltf-front-axis.md +21 -0
  274. package/docs/decisions/README.md +15 -0
  275. package/docs/idea/01-vision.md +117 -0
  276. package/docs/idea/02-dsl.md +343 -0
  277. package/docs/idea/03-ir.md +97 -0
  278. package/docs/idea/04-materials.md +114 -0
  279. package/docs/idea/05-agent-loop.md +141 -0
  280. package/docs/idea/06-qa.md +101 -0
  281. package/docs/idea/07-what-fits.md +45 -0
  282. package/docs/idea/08-neural-adapters.md +78 -0
  283. package/docs/idea/09-kinds.md +105 -0
  284. package/docs/landscape.md +49 -0
  285. package/docs/plans/roadmap.md +127 -0
  286. package/examples/build-with-bun.ts +41 -0
  287. package/llms.txt +47 -0
  288. package/package.json +99 -4
  289. package/recipes/README.md +38 -0
  290. package/recipes/asteroid/README.md +46 -0
  291. package/recipes/asteroid/asteroid.ts +336 -0
  292. package/recipes/boulders/README.md +28 -0
  293. package/recipes/boulders/boulders.ts +117 -0
  294. package/recipes/boxcar/README.md +46 -0
  295. package/recipes/boxcar/boxcar.ts +916 -0
  296. package/recipes/cargo_container_scifi/README.md +52 -0
  297. package/recipes/cargo_container_scifi/cargo_container_scifi.ts +461 -0
  298. package/recipes/dropship/README.md +84 -0
  299. package/recipes/dropship/dropship.ts +1144 -0
  300. package/recipes/eiffel_tower/README.md +110 -0
  301. package/recipes/eiffel_tower/eiffel_tower.ts +781 -0
  302. package/recipes/goblin_head/README.md +62 -0
  303. package/recipes/goblin_head/goblin_head.ts +36 -0
  304. package/recipes/human_head/README.md +64 -0
  305. package/recipes/human_head/human_head.ts +37 -0
  306. package/recipes/loco_440/README.md +32 -0
  307. package/recipes/loco_440/loco_440.ts +1404 -0
  308. package/recipes/material_swatches/README.md +21 -0
  309. package/recipes/material_swatches/material_swatches.ts +183 -0
  310. package/recipes/mountain_range/README.md +45 -0
  311. package/recipes/mountain_range/mountain_range.ts +128 -0
  312. package/recipes/oak/README.md +30 -0
  313. package/recipes/oak/oak.ts +19 -0
  314. package/recipes/orc_head/README.md +66 -0
  315. package/recipes/orc_head/orc_head.ts +39 -0
  316. package/recipes/palm/README.md +30 -0
  317. package/recipes/palm/palm.ts +19 -0
  318. package/recipes/pine/README.md +29 -0
  319. package/recipes/pine/pine.ts +19 -0
  320. package/recipes/signal_gantry/README.md +36 -0
  321. package/recipes/signal_gantry/signal_gantry.ts +522 -0
  322. package/recipes/station_halt/README.md +39 -0
  323. package/recipes/station_halt/station_halt.ts +722 -0
  324. package/recipes/t34/README.md +44 -0
  325. package/recipes/t34/t34.ts +1068 -0
  326. package/recipes/tiger_1/README.md +75 -0
  327. package/recipes/tiger_1/tiger_1.ts +1370 -0
  328. package/recipes/water_tower/README.md +40 -0
  329. package/recipes/water_tower/water_tower.ts +542 -0
  330. package/src/cli/args.ts +112 -0
  331. package/src/cli/bin.ts +7 -0
  332. package/src/cli/load.ts +17 -0
  333. package/src/cli/manifest.ts +89 -0
  334. package/src/cli/render-cmds.ts +66 -0
  335. package/src/cli/run.ts +168 -0
  336. package/src/core/asset.ts +128 -0
  337. package/src/core/definition.ts +149 -0
  338. package/src/core/errors.ts +45 -0
  339. package/src/core/family.ts +50 -0
  340. package/src/core/index.ts +66 -0
  341. package/src/core/mesh.ts +114 -0
  342. package/src/core/part.ts +130 -0
  343. package/src/core/png.ts +49 -0
  344. package/src/core/rng.ts +49 -0
  345. package/src/core/transform.ts +90 -0
  346. package/src/core/vec.ts +26 -0
  347. package/src/core/world.ts +80 -0
  348. package/src/export/bin-writer.ts +79 -0
  349. package/src/export/container.ts +35 -0
  350. package/src/export/glb.ts +200 -0
  351. package/src/export/index.ts +18 -0
  352. package/src/export/lights.ts +49 -0
  353. package/src/export/materials.ts +126 -0
  354. package/src/export/merge-pivots.ts +146 -0
  355. package/src/export/metadata.ts +59 -0
  356. package/src/export/motion.ts +70 -0
  357. package/src/export/optimize-parts.ts +80 -0
  358. package/src/export/primitive.ts +35 -0
  359. package/src/export/tangents.ts +75 -0
  360. package/src/export/textures.ts +88 -0
  361. package/src/export/version.ts +6 -0
  362. package/src/grammar/index.ts +4 -0
  363. package/src/grammar/split.ts +87 -0
  364. package/src/grammar/whyte.ts +20 -0
  365. package/src/index.ts +14 -0
  366. package/src/kinds/building.ts +153 -0
  367. package/src/kinds/faces.ts +138 -0
  368. package/src/kinds/head-armour.ts +151 -0
  369. package/src/kinds/head-beard.ts +133 -0
  370. package/src/kinds/head-bust.ts +221 -0
  371. package/src/kinds/head-extras.ts +150 -0
  372. package/src/kinds/head-face.ts +222 -0
  373. package/src/kinds/head-geom.ts +31 -0
  374. package/src/kinds/head-hair.ts +152 -0
  375. package/src/kinds/head-layout.ts +63 -0
  376. package/src/kinds/head-mesh.ts +157 -0
  377. package/src/kinds/head-params.ts +153 -0
  378. package/src/kinds/head-presets.ts +152 -0
  379. package/src/kinds/head-rim.ts +128 -0
  380. package/src/kinds/head.ts +171 -0
  381. package/src/kinds/hull-lines.ts +160 -0
  382. package/src/kinds/hull-skin.ts +83 -0
  383. package/src/kinds/hull.ts +209 -0
  384. package/src/kinds/index.ts +63 -0
  385. package/src/kinds/mass.ts +110 -0
  386. package/src/kinds/openings.ts +141 -0
  387. package/src/kinds/planned.ts +12 -0
  388. package/src/kinds/roof-chimney.ts +100 -0
  389. package/src/kinds/roof-shape.ts +230 -0
  390. package/src/kinds/roof.ts +250 -0
  391. package/src/kinds/vegetation-fronds.ts +126 -0
  392. package/src/kinds/vegetation-impostor.ts +118 -0
  393. package/src/kinds/vegetation-leaves.ts +168 -0
  394. package/src/kinds/vegetation-mesh.ts +174 -0
  395. package/src/kinds/vegetation-params.ts +147 -0
  396. package/src/kinds/vegetation-presets.ts +184 -0
  397. package/src/kinds/vegetation-skeleton.ts +168 -0
  398. package/src/kinds/vegetation.ts +199 -0
  399. package/src/kinds/vehicle.ts +144 -0
  400. package/src/material/generator.ts +56 -0
  401. package/src/material/index.ts +29 -0
  402. package/src/material/paint/bond.ts +60 -0
  403. package/src/material/paint/canvas.ts +91 -0
  404. package/src/material/paint/cell.ts +39 -0
  405. package/src/material/paint/cells-round.ts +119 -0
  406. package/src/material/paint/cells.ts +111 -0
  407. package/src/material/paint/compose.ts +53 -0
  408. package/src/material/paint/concrete.ts +62 -0
  409. package/src/material/paint/courses.ts +85 -0
  410. package/src/material/paint/decode-png.ts +100 -0
  411. package/src/material/paint/default-sheet.ts +61 -0
  412. package/src/material/paint/foliage-broad.ts +126 -0
  413. package/src/material/paint/foliage-common.ts +93 -0
  414. package/src/material/paint/foliage-compound.ts +135 -0
  415. package/src/material/paint/foliage-conifer.ts +143 -0
  416. package/src/material/paint/foliage-raster.ts +196 -0
  417. package/src/material/paint/foliage.ts +23 -0
  418. package/src/material/paint/masks.ts +44 -0
  419. package/src/material/paint/noise.ts +52 -0
  420. package/src/material/paint/normal.ts +52 -0
  421. package/src/material/paint/orm.ts +95 -0
  422. package/src/material/paint/planks.ts +56 -0
  423. package/src/material/paint/plates.ts +72 -0
  424. package/src/material/paint/shapes.ts +28 -0
  425. package/src/material/paint/slate.ts +52 -0
  426. package/src/material/paint/stamp.ts +40 -0
  427. package/src/material/paint/strip.ts +42 -0
  428. package/src/material/paint/surface.ts +60 -0
  429. package/src/material/paint/types.ts +51 -0
  430. package/src/material/paint/validate.ts +58 -0
  431. package/src/material/palette.ts +43 -0
  432. package/src/material/shader-godot.ts +77 -0
  433. package/src/material/shader-three.ts +103 -0
  434. package/src/material/shader.ts +100 -0
  435. package/src/material/trim-sheet.ts +100 -0
  436. package/src/neural/generator.ts +37 -0
  437. package/src/neural/images.ts +27 -0
  438. package/src/neural/index.ts +10 -0
  439. package/src/passes/bake-ao.ts +183 -0
  440. package/src/passes/grime.ts +37 -0
  441. package/src/passes/index.ts +8 -0
  442. package/src/passes/livery.ts +66 -0
  443. package/src/passes/optimize-cache.ts +175 -0
  444. package/src/passes/optimize-weld.ts +147 -0
  445. package/src/passes/optimize.ts +85 -0
  446. package/src/passes/ray-grid.ts +237 -0
  447. package/src/passes/weighted-normals.ts +125 -0
  448. package/src/primitives/beam.ts +60 -0
  449. package/src/primitives/bevel-box.ts +140 -0
  450. package/src/primitives/box.ts +90 -0
  451. package/src/primitives/cell.ts +115 -0
  452. package/src/primitives/check.ts +49 -0
  453. package/src/primitives/closed.ts +74 -0
  454. package/src/primitives/erosion.ts +183 -0
  455. package/src/primitives/font-5x7.ts +117 -0
  456. package/src/primitives/heightfield.ts +101 -0
  457. package/src/primitives/index.ts +56 -0
  458. package/src/primitives/lathe-facets.ts +45 -0
  459. package/src/primitives/lathe.ts +174 -0
  460. package/src/primitives/lattice.ts +131 -0
  461. package/src/primitives/loft.ts +174 -0
  462. package/src/primitives/mc-table.ts +41 -0
  463. package/src/primitives/member.ts +130 -0
  464. package/src/primitives/noise.ts +157 -0
  465. package/src/primitives/planar.ts +55 -0
  466. package/src/primitives/prism.ts +85 -0
  467. package/src/primitives/sculpt-edit.ts +212 -0
  468. package/src/primitives/sculpt-tree.ts +226 -0
  469. package/src/primitives/sculpt.ts +80 -0
  470. package/src/primitives/sdf-grid.ts +131 -0
  471. package/src/primitives/sdf-ops.ts +145 -0
  472. package/src/primitives/sdf.ts +83 -0
  473. package/src/primitives/strip-uv.ts +26 -0
  474. package/src/primitives/surface-nets.ts +161 -0
  475. package/src/primitives/sweep.ts +162 -0
  476. package/src/primitives/terrain-color.ts +85 -0
  477. package/src/primitives/terrain-grid.ts +137 -0
  478. package/src/primitives/text-layout.ts +85 -0
  479. package/src/primitives/text.ts +197 -0
  480. package/src/primitives/tiled.ts +93 -0
  481. package/src/primitives/transport.ts +85 -0
  482. package/src/primitives/triangulate.ts +42 -0
  483. package/src/primitives/value-noise.ts +62 -0
  484. package/src/qa/checks.ts +110 -0
  485. package/src/qa/clash.ts +177 -0
  486. package/src/qa/floating.ts +63 -0
  487. package/src/qa/gates.ts +126 -0
  488. package/src/qa/geometry.ts +187 -0
  489. package/src/qa/index.ts +28 -0
  490. package/src/qa/inside.ts +114 -0
  491. package/src/qa/inspect.ts +107 -0
  492. package/src/qa/lods.ts +97 -0
  493. package/src/qa/measures.ts +67 -0
  494. package/src/qa/occlusion.ts +166 -0
  495. package/src/qa/sanity.ts +137 -0
  496. package/src/qa/types.ts +17 -0
  497. package/src/render/beauty.ts +115 -0
  498. package/src/render/camera.ts +142 -0
  499. package/src/render/depth-map.ts +191 -0
  500. package/src/render/font.ts +76 -0
  501. package/src/render/ground.ts +87 -0
  502. package/src/render/index.ts +34 -0
  503. package/src/render/lighting.ts +95 -0
  504. package/src/render/lod-strip.ts +34 -0
  505. package/src/render/metrics.ts +147 -0
  506. package/src/render/options.ts +81 -0
  507. package/src/render/photo-match.ts +28 -0
  508. package/src/render/raster.ts +143 -0
  509. package/src/render/resolve.ts +109 -0
  510. package/src/render/review-prompt.ts +59 -0
  511. package/src/render/review-warnings.ts +92 -0
  512. package/src/render/review.ts +103 -0
  513. package/src/render/scene.ts +119 -0
  514. package/src/render/shade.ts +199 -0
  515. package/src/render/sheet.ts +88 -0
  516. package/src/render/texture.ts +97 -0
  517. package/src/render/thumbnail.ts +75 -0
  518. package/src/render/vis-buffer.ts +119 -0
  519. package/src/testing/assert.ts +115 -0
  520. package/src/testing/compiled.ts +31 -0
  521. package/src/testing/hash.ts +14 -0
  522. package/src/testing/index.ts +14 -0
  523. package/src/testing/snapshot.ts +37 -0
  524. package/src/types/gltf-validator.d.ts +13 -0
@@ -0,0 +1,100 @@
1
+ // Engine shader sources for the trim-sheet material: per-asset effects that stay on the one shared
2
+ // material — vertex-colour tint, wind sway, night window glow, wear/grime. Both functions return
3
+ // plain source strings (a three.js TSL module, a Godot 4 .gdshader). They are STARTING POINTS an
4
+ // engine project loads and owns, not runtime dependencies: ts23d never imports three or Godot.
5
+ //
6
+ // Shared conventions both sources implement:
7
+ // tint albedo.rgb × COLOR_0.rgb (linear), alpha from albedo.a (cutout leaf masks)
8
+ // ORM R occlusion · G roughness · B metalness (one texture, as the GLB binds it)
9
+ // wind weight from the `_WIND` vertex attribute (three: `_wind`) or, by default, the
10
+ // mesh-local height (authored Z-up), smoothstep(0, wind.height, z); sway in local XY
11
+ // glow emissive on window panes: window_sash/porthole texels darker than luma 0.3, and the
12
+ // whole glass strip; a world-space 1.5 m hash lights `ratio` of windows; × `night`
13
+ // wear darkens + roughens cavities (1 − AO) and the first `grimeHeight` metres of world height
14
+
15
+ import type { Surface } from '../core/index.ts';
16
+ import { Ts23dError } from '../core/index.ts';
17
+ import { godotSource } from './shader-godot.ts';
18
+ import { threeSource } from './shader-three.ts';
19
+ import { cellRect, stripRect, type UvRect } from './trim-sheet.ts';
20
+
21
+ export interface ShaderHints {
22
+ /** Vertex sway for vegetation. Weight from the `_WIND` attribute or mesh-local height. */
23
+ readonly wind?: {
24
+ readonly strength: number;
25
+ readonly source?: 'attribute' | 'height';
26
+ /** Metres of height at which sway reaches full weight (height source). Default 4. */
27
+ readonly height?: number;
28
+ readonly hz?: number;
29
+ };
30
+ /** Emissive mask on window cells at night: `ratio` of windows lit. */
31
+ readonly windowGlow?: {
32
+ readonly color: string;
33
+ readonly ratio: number;
34
+ readonly intensity?: number;
35
+ };
36
+ /** Wear/grime mask strength 0..1 and how high ground grime climbs (metres). */
37
+ readonly wear?: { readonly amount: number; readonly grimeHeight?: number };
38
+ /** The surface the material is for: 'cutout' adds alpha test + double-sided. */
39
+ readonly surface?: Surface;
40
+ }
41
+
42
+ /** Resolved, validated numbers both emitters print. */
43
+ export interface ShaderParams {
44
+ readonly windStrength: number;
45
+ readonly windAttribute: boolean;
46
+ readonly windHeight: number;
47
+ readonly windHz: number;
48
+ readonly glow: readonly [number, number, number];
49
+ readonly glowRatio: number;
50
+ readonly glowIntensity: number;
51
+ readonly wear: number;
52
+ readonly grimeHeight: number;
53
+ readonly cutout: boolean;
54
+ readonly blend: boolean;
55
+ readonly glowRects: readonly UvRect[];
56
+ readonly glassRect: UvRect;
57
+ }
58
+
59
+ function fail(cause: string, fix: string): never {
60
+ throw new Ts23dError('T3D_RECIPE_INVALID', { cause: `shader hints: ${cause}`, fix });
61
+ }
62
+
63
+ function srgbHex(value: string): [number, number, number] {
64
+ if (!/^#[0-9a-fA-F]{6}$/.test(value)) fail(`colour '${value}'`, "use '#rrggbb' sRGB hex");
65
+ const n = Number.parseInt(value.slice(1), 16);
66
+ return [((n >> 16) & 255) / 255, ((n >> 8) & 255) / 255, (n & 255) / 255];
67
+ }
68
+
69
+ export function resolveHints(h: ShaderHints): ShaderParams {
70
+ const unit = (v: number, what: string): number =>
71
+ Number.isFinite(v) && v >= 0 && v <= 1 ? v : fail(`${what} is ${v}`, `${what} is 0..1`);
72
+ const pos = (v: number, what: string): number =>
73
+ Number.isFinite(v) && v > 0 ? v : fail(`${what} is ${v}`, `${what} is a positive number`);
74
+ const strength = h.wind ? pos(h.wind.strength, 'wind.strength') : 0;
75
+ return {
76
+ windStrength: strength,
77
+ windAttribute: h.wind?.source === 'attribute',
78
+ windHeight: pos(h.wind?.height ?? 4, 'wind.height'),
79
+ windHz: pos(h.wind?.hz ?? 0.4, 'wind.hz'),
80
+ glow: h.windowGlow ? srgbHex(h.windowGlow.color) : [1, 0.75, 0.4],
81
+ glowRatio: h.windowGlow ? unit(h.windowGlow.ratio, 'windowGlow.ratio') : 0,
82
+ glowIntensity: pos(h.windowGlow?.intensity ?? 2, 'windowGlow.intensity'),
83
+ wear: h.wear ? unit(h.wear.amount, 'wear.amount') : 0,
84
+ grimeHeight: pos(h.wear?.grimeHeight ?? 0.6, 'wear.grimeHeight'),
85
+ cutout: h.surface === 'cutout',
86
+ blend: h.surface === 'glass' || h.surface === 'translucent',
87
+ glowRects: [cellRect('window_sash'), cellRect('porthole')],
88
+ glassRect: stripRect('glass'),
89
+ };
90
+ }
91
+
92
+ /** A three.js (r18x, WebGPU/TSL) TypeScript module: `createTrimMaterial(maps)` + `upgradeTrimMaterials(root)`. */
93
+ export function threeMaterialSource(hints: ShaderHints = {}): string {
94
+ return threeSource(resolveHints(hints));
95
+ }
96
+
97
+ /** A Godot 4 spatial shader (.gdshader) with the same features and uniform names. */
98
+ export function godotShaderSource(hints: ShaderHints = {}): string {
99
+ return godotSource(resolveHints(hints));
100
+ }
@@ -0,0 +1,100 @@
1
+ // The shared trim sheet: ONE 1024² atlas (albedo + normal + ORM) every asset maps into, so every
2
+ // asset is one material and one draw call. Strips tile along U; cells are fixed decals.
3
+ // Layout is data here; `paintTrimSheet` (generator.ts) paints it. V grows with world up on
4
+ // box/planar faces. Foliage cells (cutout leaf cards, alpha = leaf mask) sit in their own rows.
5
+
6
+ export type StripId =
7
+ | 'plain'
8
+ | 'brick'
9
+ | 'stone'
10
+ | 'slate'
11
+ | 'iron'
12
+ | 'wood'
13
+ | 'glass'
14
+ | 'concrete';
15
+ export type DecalCellId = 'window_sash' | 'door_panel' | 'clock' | 'smokebox' | 'sign' | 'porthole';
16
+ /** Cutout leaf cards: the anchor (twig base) is the cell's bottom-centre, growth toward +V. */
17
+ export type FoliageCellId =
18
+ | 'foliage_broadleaf'
19
+ | 'foliage_maple'
20
+ | 'foliage_maple_autumn'
21
+ | 'foliage_birch'
22
+ | 'foliage_acacia'
23
+ | 'foliage_palm'
24
+ | 'foliage_spruce'
25
+ | 'foliage_spruce_snow'
26
+ | 'foliage_willow'
27
+ | 'foliage_reeds'
28
+ | 'foliage_needles';
29
+ export type CellId = DecalCellId | FoliageCellId;
30
+
31
+ export interface UvRect {
32
+ readonly u0: number;
33
+ readonly v0: number;
34
+ readonly u1: number;
35
+ readonly v1: number;
36
+ /** Metres of world surface one full rect covers, so texel density is equal across assets. */
37
+ readonly metres: number;
38
+ }
39
+
40
+ const STRIP_H = 1 / 16;
41
+ const strips: readonly StripId[] = [
42
+ 'plain',
43
+ 'brick',
44
+ 'stone',
45
+ 'slate',
46
+ 'iron',
47
+ 'wood',
48
+ 'glass',
49
+ 'concrete',
50
+ ];
51
+ const decals: readonly DecalCellId[] = [
52
+ 'window_sash',
53
+ 'door_panel',
54
+ 'clock',
55
+ 'smokebox',
56
+ 'sign',
57
+ 'porthole',
58
+ ];
59
+ const foliage: readonly FoliageCellId[] = [
60
+ 'foliage_broadleaf',
61
+ 'foliage_maple',
62
+ 'foliage_maple_autumn',
63
+ 'foliage_birch',
64
+ 'foliage_acacia',
65
+ 'foliage_palm',
66
+ 'foliage_spruce',
67
+ 'foliage_spruce_snow',
68
+ 'foliage_willow',
69
+ 'foliage_reeds',
70
+ 'foliage_needles',
71
+ ];
72
+ const cells: readonly CellId[] = [...decals, ...foliage];
73
+ /** Decals fill row 0 of the cell region (V 0.5); foliage starts on row 1 (V 0.625). */
74
+ const FOLIAGE_SLOT = 8;
75
+ const slotOf = (id: CellId): number => {
76
+ const d = decals.indexOf(id as DecalCellId);
77
+ return d >= 0 ? d : FOLIAGE_SLOT + foliage.indexOf(id as FoliageCellId);
78
+ };
79
+
80
+ export const TRIM_SHEET_SIZE = 1024;
81
+
82
+ export function stripRect(id: StripId): UvRect {
83
+ const i = strips.indexOf(id);
84
+ return { u0: 0, v0: i * STRIP_H, u1: 1, v1: (i + 1) * STRIP_H, metres: 2 };
85
+ }
86
+
87
+ /** Cells sit in the lower half (V ≥ 0.5), 8 per row: decals on row 0, foliage from row 1. */
88
+ export function cellRect(id: CellId): UvRect {
89
+ const i = slotOf(id);
90
+ const size = 1 / 8;
91
+ const u0 = (i % 8) * size;
92
+ const v0 = 0.5 + Math.floor(i / 8) * size;
93
+ return { u0, v0, u1: u0 + size, v1: v0 + size, metres: 1 };
94
+ }
95
+
96
+ export const STRIPS = strips;
97
+ export const CELLS = cells;
98
+ export const DECAL_CELLS = decals;
99
+ export const FOLIAGE_CELLS = foliage;
100
+ export const isFoliage = (id: CellId): id is FoliageCellId => id.startsWith('foliage_');
@@ -0,0 +1,37 @@
1
+ // Optional seam for organic shapes the recipe approach handles poorly: people, animals, sculpted
2
+ // rocks. Runs an open-weight image/text-to-3D model on the USER's GPU (TRELLIS, Hunyuan3D, SF3D…)
3
+ // behind a local HTTP endpoint, then wraps the result as an ordinary part. Never a core dependency.
4
+ //
5
+ // Determinism: a generated mesh is cached by (model, prompt/image hash, seed) under
6
+ // .ts23d/neural/ and committed or published like any asset, so CI rebuilds without a GPU.
7
+
8
+ import { type Mesh, notImplemented, type Vec3 } from '../core/index.ts';
9
+
10
+ export interface GenerateRequest {
11
+ readonly prompt?: string;
12
+ /** PNG/JPEG bytes of a reference or concept image. */
13
+ readonly image?: Uint8Array;
14
+ readonly seed: number;
15
+ /** Fit the result into this box (metres), Z up, base on the ground. */
16
+ readonly fit: Vec3;
17
+ readonly targetTriangles: number;
18
+ }
19
+
20
+ export interface MeshGenerator {
21
+ /** e.g. 'trellis-2', 'hunyuan3d-2.1', 'sf3d'. Part of the cache key. */
22
+ readonly model: string;
23
+ generate(request: GenerateRequest): Promise<Mesh>;
24
+ }
25
+
26
+ /** Talks to a local server (e.g. a ComfyUI / gradio wrapper) at `endpoint`. Planned: M5. */
27
+ export function localGenerator(_o: { model: string; endpoint: string }): MeshGenerator {
28
+ return notImplemented(
29
+ 'localGenerator (local GPU model adapter)',
30
+ 'docs/idea/08-neural-adapters.md',
31
+ );
32
+ }
33
+
34
+ /** Import an existing GLB (a CC0 base mesh, a sculpt, a generator's output) as a part. Planned: M5. */
35
+ export function importGlb(_bytes: Uint8Array, _o: { fit: Vec3; recolor?: Vec3 }): Mesh {
36
+ return notImplemented('importGlb', 'docs/idea/08-neural-adapters.md');
37
+ }
@@ -0,0 +1,27 @@
1
+ // Image sources for the material generator and the agent loop: reference/concept images, decals,
2
+ // texture source photos. kubeez.com is the recommended hosted source; any function returning PNG
3
+ // bytes fits. Results are cached by (source, prompt, seed) like generated meshes. Planned: M5.
4
+
5
+ import { notImplemented } from '../core/index.ts';
6
+
7
+ export interface ImageRequest {
8
+ readonly prompt: string;
9
+ readonly seed: number;
10
+ readonly width: number;
11
+ readonly height: number;
12
+ /** e.g. 'decal', 'tileable', 'reference', 'concept'. */
13
+ readonly purpose: 'decal' | 'tileable' | 'reference' | 'concept';
14
+ }
15
+
16
+ export interface ImageSource {
17
+ readonly name: string;
18
+ generate(request: ImageRequest): Promise<Uint8Array>;
19
+ }
20
+
21
+ /** kubeez.com image generation. Reads the key from the caller, never from ambient env. */
22
+ export function kubeezImages(_o: { apiKey: string; model?: string }): ImageSource {
23
+ return notImplemented(
24
+ 'kubeezImages (kubeez.com image source)',
25
+ 'docs/idea/08-neural-adapters.md',
26
+ );
27
+ }
@@ -0,0 +1,10 @@
1
+ // Public API of /neural: optional AI adapters — local-GPU mesh generators for organic shapes,
2
+ // mesh import, and image sources (kubeez.com) for references and materials. Never required.
3
+
4
+ export {
5
+ type GenerateRequest,
6
+ importGlb,
7
+ localGenerator,
8
+ type MeshGenerator,
9
+ } from './generator.ts';
10
+ export { type ImageRequest, type ImageSource, kubeezImages } from './images.ts';
@@ -0,0 +1,183 @@
1
+ // Ambient occlusion baked into vertex colour: the cheapest big look upgrade for a vertex-coloured
2
+ // asset — creases, wheel wells and the gap under a turret darken, as a lit render would. Each
3
+ // vertex fires a fixed cosine-weighted hemisphere of rays (Fibonacci spiral, no randomness) at the
4
+ // asset's own triangles (and optionally the ground plane); the hit fraction darkens its colour.
5
+
6
+ import { type CompiledAsset, type Mesh, merge, type Vec3, worldMeshes } from '../core/index.ts';
7
+ import { RayGrid } from './ray-grid.ts';
8
+
9
+ export interface AoOptions {
10
+ /** Rays per vertex. Default 24. */
11
+ readonly samples?: number;
12
+ /** Occluders farther than this (metres) don't count. Default 1. */
13
+ readonly radius?: number;
14
+ /** 0 = no change, 1 = fully occluded vertices go black. Default 0.6. */
15
+ readonly strength?: number;
16
+ /** Treat z = 0 as a ground plane (contact shadow under the asset). Default true. */
17
+ readonly ground?: boolean;
18
+ }
19
+
20
+ const GOLDEN = Math.PI * (3 - Math.sqrt(5));
21
+
22
+ /** Fixed cosine-weighted directions around +Z. */
23
+ function hemisphere(n: number): Vec3[] {
24
+ return Array.from({ length: n }, (_, i) => {
25
+ const r = Math.sqrt((i + 0.5) / n);
26
+ const phi = i * GOLDEN;
27
+ return [r * Math.cos(phi), r * Math.sin(phi), Math.sqrt(Math.max(0, 1 - r * r))] as Vec3;
28
+ });
29
+ }
30
+
31
+ /** Orthonormal frame with `n` as its Z (Frisvad / Duff et al. branchless basis). */
32
+ function frame(n: Vec3): [Vec3, Vec3] {
33
+ const s = n[2] >= 0 ? 1 : -1;
34
+ const a = -1 / (s + n[2]);
35
+ const b = n[0] * n[1] * a;
36
+ return [
37
+ [1 + s * n[0] * n[0] * a, s * b, -s * n[0]],
38
+ [b, s + n[1] * n[1] * a, -n[1]],
39
+ ];
40
+ }
41
+
42
+ /**
43
+ * Vertices sharing a position (0.1 mm) and normal (0.001) share one result: welded seams and
44
+ * duplicated soup vertices cost one hemisphere, not many. Open addressing on the six integers.
45
+ */
46
+ class Memo {
47
+ private readonly keys: Float64Array;
48
+ private readonly vals: Float64Array;
49
+ private readonly used: Uint8Array;
50
+ private readonly mask: number;
51
+
52
+ constructor(n: number) {
53
+ let cap = 16;
54
+ while (cap < n * 2) cap *= 2;
55
+ this.mask = cap - 1;
56
+ this.keys = new Float64Array(cap * 6);
57
+ this.vals = new Float64Array(cap);
58
+ this.used = new Uint8Array(cap);
59
+ }
60
+
61
+ /** Slot for the key; `found` tells whether it already holds a value. */
62
+ slot(k: Float64Array): { at: number; found: boolean } {
63
+ let h = 2166136261;
64
+ for (let i = 0; i < 6; i++) h = Math.imul(h ^ ((k[i] ?? 0) | 0), 16777619);
65
+ let at = (h >>> 0) & this.mask;
66
+ for (;;) {
67
+ if (!this.used[at]) return { at, found: false };
68
+ let same = true;
69
+ for (let i = 0; i < 6 && same; i++) same = this.keys[at * 6 + i] === k[i];
70
+ if (same) return { at, found: true };
71
+ at = (at + 1) & this.mask;
72
+ }
73
+ }
74
+
75
+ get(at: number): number {
76
+ return this.vals[at] ?? 0;
77
+ }
78
+
79
+ set(at: number, k: Float64Array, v: number): void {
80
+ this.used[at] = 1;
81
+ this.keys.set(k, at * 6);
82
+ this.vals[at] = v;
83
+ }
84
+ }
85
+
86
+ /** Per-vertex visibility (1 = open sky, 0 = buried) of `mesh` against `grid`. */
87
+ function visibility(mesh: Mesh, grid: RayGrid, o: AoOptions): Float32Array {
88
+ const hemi = hemisphere(o.samples ?? 24);
89
+ const D = Float64Array.from(hemi.flat());
90
+ const count = hemi.length;
91
+ const radius = o.radius ?? 1;
92
+ const ground = o.ground ?? true;
93
+ const P = mesh.positions;
94
+ const N = mesh.normals;
95
+ const out = new Float32Array(P.length / 3);
96
+ const memo = new Memo(out.length);
97
+ const key = new Float64Array(6);
98
+ // Lift off the surface so a ray never hits the face it starts on.
99
+ const bias = 2e-3;
100
+ for (let v = 0; v < out.length; v++) {
101
+ const px = P[v * 3] ?? 0;
102
+ const py = P[v * 3 + 1] ?? 0;
103
+ const pz = P[v * 3 + 2] ?? 0;
104
+ const rx = N[v * 3] ?? 0;
105
+ const ry = N[v * 3 + 1] ?? 0;
106
+ const rz = N[v * 3 + 2] ?? 1;
107
+ const len = Math.hypot(rx, ry, rz) || 1;
108
+ const n: Vec3 = [rx / len, ry / len, rz / len];
109
+ key[0] = Math.round(px * 1e4);
110
+ key[1] = Math.round(py * 1e4);
111
+ key[2] = Math.round(pz * 1e4);
112
+ key[3] = Math.round(n[0] * 1e3);
113
+ key[4] = Math.round(n[1] * 1e3);
114
+ key[5] = Math.round(n[2] * 1e3);
115
+ const s = memo.slot(key);
116
+ if (s.found) {
117
+ out[v] = memo.get(s.at);
118
+ continue;
119
+ }
120
+ const [t, b] = frame(n);
121
+ const ox = px + n[0] * bias;
122
+ const oy = py + n[1] * bias;
123
+ const oz = pz + n[2] * bias;
124
+ let blocked = 0;
125
+ for (let i = 0; i < count; i++) {
126
+ const d0 = D[i * 3] ?? 0;
127
+ const d1 = D[i * 3 + 1] ?? 0;
128
+ const d2 = D[i * 3 + 2] ?? 0;
129
+ const wx = t[0] * d0 + b[0] * d1 + n[0] * d2;
130
+ const wy = t[1] * d0 + b[1] * d1 + n[1] * d2;
131
+ const wz = t[2] * d0 + b[2] * d1 + n[2] * d2;
132
+ if (ground && wz < -1e-6 && oz >= 0 && -oz / wz <= radius) blocked++;
133
+ else if (grid.hit(ox, oy, oz, wx, wy, wz, radius)) blocked++;
134
+ }
135
+ const vis = 1 - blocked / count;
136
+ memo.set(s.at, key, vis);
137
+ out[v] = vis;
138
+ }
139
+ return out;
140
+ }
141
+
142
+ const darken = (mesh: Mesh, vis: Float32Array, strength: number): Mesh => {
143
+ const colors = new Float32Array(mesh.colors);
144
+ for (let v = 0; v < vis.length; v++) {
145
+ const f = 1 - strength * (1 - (vis[v] ?? 1));
146
+ for (let k = 0; k < 3; k++) colors[v * 3 + k] = (colors[v * 3 + k] ?? 0) * f;
147
+ }
148
+ return { ...mesh, colors };
149
+ };
150
+
151
+ /** Bake AO of one mesh against itself (or `occluder`, in the same space). */
152
+ // Callers bake several parts against one occluder (a head's skin, jaw and eyes against the
153
+ // whole head): index each occluder once. Meshes are immutable, so the grid stays valid.
154
+ const grids = new WeakMap<Mesh, RayGrid>();
155
+ const gridOf = (m: Mesh): RayGrid => {
156
+ const hit = grids.get(m);
157
+ if (hit) return hit;
158
+ const g = new RayGrid(m);
159
+ grids.set(m, g);
160
+ return g;
161
+ };
162
+
163
+ export function bakeAo(mesh: Mesh, o: AoOptions = {}, occluder: Mesh = mesh): Mesh {
164
+ return darken(mesh, visibility(mesh, gridOf(occluder), o), o.strength ?? 0.6);
165
+ }
166
+
167
+ /**
168
+ * Bake AO across a whole compiled asset: every part is occluded by every other (a wheel darkens
169
+ * the hull behind it). Colours only change, so parts keep their local meshes and pivots.
170
+ */
171
+ export function bakeAoAsset(asset: CompiledAsset, o: AoOptions = {}): CompiledAsset {
172
+ const world = worldMeshes(asset.parts);
173
+ if (world.size === 0) return asset;
174
+ const grid = new RayGrid(merge([...world.values()]));
175
+ const strength = o.strength ?? 0.6;
176
+ return {
177
+ ...asset,
178
+ parts: asset.parts.map((p) => {
179
+ const w = world.get(p.name);
180
+ return p.mesh && w ? { ...p, mesh: darken(p.mesh, visibility(w, grid, o), strength) } : p;
181
+ }),
182
+ };
183
+ }
@@ -0,0 +1,37 @@
1
+ // Grime: darken vertex colour near the ground and in named zones (soot, mud, dust). A pass over a
2
+ // finished mesh, so recipes stay about shape; wear is applied once, the same way everywhere.
3
+
4
+ import type { Mesh, Vec3 } from '../core/index.ts';
5
+
6
+ export interface GrimeZone {
7
+ readonly at: Vec3;
8
+ readonly radius: number;
9
+ /** 0..1 darkening at the centre. */
10
+ readonly strength: number;
11
+ }
12
+
13
+ export interface GrimeOptions {
14
+ /** Height (m) below which mud/dust builds up toward the ground. */
15
+ readonly ground?: number;
16
+ /** Darkening at z = 0. */
17
+ readonly groundStrength?: number;
18
+ readonly zones?: readonly GrimeZone[];
19
+ }
20
+
21
+ export function grime(mesh: Mesh, o: GrimeOptions): Mesh {
22
+ const colors = new Float32Array(mesh.colors);
23
+ const g = o.ground ?? 0;
24
+ const gs = o.groundStrength ?? 0.3;
25
+ for (let i = 0; i < mesh.positions.length / 3; i++) {
26
+ const x = mesh.positions[i * 3] ?? 0;
27
+ const y = mesh.positions[i * 3 + 1] ?? 0;
28
+ const z = mesh.positions[i * 3 + 2] ?? 0;
29
+ let f = g > 0 && z < g ? 1 - gs * (1 - Math.max(0, z) / g) : 1;
30
+ for (const zone of o.zones ?? []) {
31
+ const d = Math.hypot(x - zone.at[0], y - zone.at[1], z - zone.at[2]) / zone.radius;
32
+ if (d < 1) f *= 1 - zone.strength * (1 - d);
33
+ }
34
+ for (let k = 0; k < 3; k++) colors[i * 3 + k] = (colors[i * 3 + k] ?? 1) * f;
35
+ }
36
+ return { ...mesh, colors };
37
+ }
@@ -0,0 +1,8 @@
1
+ // Public API of /passes: whole-mesh post-processing applied after geometry — wear, optimisation.
2
+
3
+ export { type AoOptions, bakeAo, bakeAoAsset } from './bake-ao.ts';
4
+ export { type GrimeOptions, type GrimeZone, grime } from './grime.ts';
5
+ export { type Livery, type LiveryRule, livery } from './livery.ts';
6
+ export { type OptimizeOptions, type OptimizeReport, optimize } from './optimize.ts';
7
+ export { RayGrid } from './ray-grid.ts';
8
+ export { type WeightedNormalsOptions, weightedNormals } from './weighted-normals.ts';
@@ -0,0 +1,66 @@
1
+ // Livery: paint a finished mesh by rules instead of by hand. Base colour, then ordered rules —
2
+ // the last matching rule wins — keyed on height bands, an along-length range, a side-elevation
3
+ // polygon (stripes, wings, numbers' backgrounds), and surface orientation (roof, front, back,
4
+ // sides, underside). One call turns a grey loco into "Daylight", a tank into its camo scheme.
5
+
6
+ import type { Mesh, Vec2, Vec3 } from '../core/index.ts';
7
+
8
+ export interface LiveryRule {
9
+ readonly color: Vec3;
10
+ /** Height band [z0, z1] (metres). */
11
+ readonly z?: readonly [number, number];
12
+ /** Along-length band [x0, x1] (metres, +X forward). */
13
+ readonly x?: readonly [number, number];
14
+ /** Polygon in the side elevation (x, z): the region a stripe or panel covers. */
15
+ readonly poly?: readonly Vec2[];
16
+ /** Only faces whose normal points this way (n·axis > 0.5; roof: n.z > 0.55). */
17
+ readonly facing?: 'roof' | 'under' | 'front' | 'back' | 'side';
18
+ /** Multiply into the existing colour instead of replacing it (weathering, shading). */
19
+ readonly multiply?: boolean;
20
+ }
21
+
22
+ export interface Livery {
23
+ readonly base?: Vec3;
24
+ readonly rules: readonly LiveryRule[];
25
+ }
26
+
27
+ function inPoly(poly: readonly Vec2[], x: number, z: number): boolean {
28
+ let inside = false;
29
+ for (let i = 0, j = poly.length - 1; i < poly.length; j = i++) {
30
+ const [xi, zi] = poly[i] ?? [0, 0];
31
+ const [xj, zj] = poly[j] ?? [0, 0];
32
+ if (zi > z !== zj > z && x < ((xj - xi) * (z - zi)) / (zj - zi) + xi) inside = !inside;
33
+ }
34
+ return inside;
35
+ }
36
+
37
+ function faces(f: NonNullable<LiveryRule['facing']>, n: Vec3): boolean {
38
+ if (f === 'roof') return n[2] > 0.55;
39
+ if (f === 'under') return n[2] < -0.55;
40
+ if (f === 'front') return n[0] > 0.5;
41
+ if (f === 'back') return n[0] < -0.5;
42
+ return Math.abs(n[1]) > 0.5;
43
+ }
44
+
45
+ export function livery(mesh: Mesh, l: Livery): Mesh {
46
+ const colors = new Float32Array(mesh.colors);
47
+ for (let i = 0; i < mesh.positions.length / 3; i++) {
48
+ const x = mesh.positions[i * 3] ?? 0;
49
+ const z = mesh.positions[i * 3 + 2] ?? 0;
50
+ const n: Vec3 = [
51
+ mesh.normals[i * 3] ?? 0,
52
+ mesh.normals[i * 3 + 1] ?? 0,
53
+ mesh.normals[i * 3 + 2] ?? 0,
54
+ ];
55
+ let c: Vec3 = l.base ?? [colors[i * 3] ?? 1, colors[i * 3 + 1] ?? 1, colors[i * 3 + 2] ?? 1];
56
+ for (const r of l.rules) {
57
+ if (r.z && (z < r.z[0] || z > r.z[1])) continue;
58
+ if (r.x && (x < r.x[0] || x > r.x[1])) continue;
59
+ if (r.poly && !inPoly(r.poly, x, z)) continue;
60
+ if (r.facing && !faces(r.facing, n)) continue;
61
+ c = r.multiply ? [c[0] * r.color[0], c[1] * r.color[1], c[2] * r.color[2]] : r.color;
62
+ }
63
+ colors.set(c, i * 3);
64
+ }
65
+ return { ...mesh, colors };
66
+ }