@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,143 @@
1
+ // Needle and blade foliage cells, growing from the bottom-centre anchor toward +V: spruce comb
2
+ // (needle tufts off both sides of an axis and its side shoots), snow-loaded spruce (the same with
3
+ // snow lumps riding the upper side of each shoot), conifer needle spray (pine bundles fanning from
4
+ // twig tips) and reeds/grass (tapering blades, some dry).
5
+
6
+ import { createRng, type Rng } from '../../core/index.ts';
7
+ import type { Canvas } from './canvas.ts';
8
+ import { at, type Curve, curve, dim, drawStem, LEAF, vary } from './foliage-common.ts';
9
+ import { Foliage } from './foliage-raster.ts';
10
+
11
+ /** Needles off both sides of a shoot, angled forward, densest mid-shoot. */
12
+ function comb(f: Foliage, rng: Rng, shoot: Curve, len: number, k: number, lift: number): void {
13
+ const n = shoot.x.length - 1;
14
+ for (let i = 0; i <= n; i++) {
15
+ for (const side of [-1, 1]) {
16
+ const a = (shoot.a[i] ?? 0) + side * rng.range(0.7, 1.0);
17
+ const col = dim(vary(rng, LEAF.spruce, 0.2), k);
18
+ f.leaf(
19
+ 'needle',
20
+ shoot.x[i] ?? 0,
21
+ shoot.y[i] ?? 0,
22
+ a,
23
+ len * rng.range(0.8, 1.1),
24
+ 0.006,
25
+ col,
26
+ 0,
27
+ lift,
28
+ );
29
+ }
30
+ }
31
+ }
32
+
33
+ function spruce(c: Canvas, seed: number, snow: boolean): void {
34
+ const rng = createRng(seed);
35
+ const f = new Foliage(c);
36
+ const axis = curve(0.5, 0.02, 0.5 + rng.range(-0.04, 0.04), 0.5, 0.5, 0.95, 60);
37
+ const shoots: Curve[] = [];
38
+ for (let s = 0; s < 8; s++) {
39
+ const t = 0.15 + (s / 8) * 0.75;
40
+ const i = at(axis, t);
41
+ const side = s % 2 === 0 ? 1 : -1;
42
+ const bx = axis.x[i] ?? 0.5;
43
+ const by = axis.y[i] ?? 0.5;
44
+ const len = 0.32 * (1 - t * 0.6);
45
+ const a = side * rng.range(0.9, 1.2);
46
+ shoots.push(
47
+ curve(
48
+ bx,
49
+ by,
50
+ bx + Math.sin(a) * len * 0.5,
51
+ by + Math.cos(a) * len * 0.5,
52
+ bx + Math.sin(a) * len,
53
+ by + Math.cos(a) * len + 0.03,
54
+ 24,
55
+ ),
56
+ );
57
+ }
58
+ for (const sh of shoots) comb(f, rng, sh, 0.045, 0.55, 0);
59
+ drawStem(f, axis, 0.012, 0.004, LEAF.bark);
60
+ for (const sh of shoots) drawStem(f, sh, 0.005, 0.002, LEAF.bark);
61
+ comb(f, rng, axis, 0.05, 1, 0.002);
62
+ for (const sh of shoots) comb(f, rng, sh, 0.04, 1, 0.002);
63
+ if (snow) {
64
+ for (const sh of [axis, ...shoots]) {
65
+ const n = sh.x.length - 1;
66
+ for (let i = 2; i < n; i += 3) {
67
+ const col = vary(rng, LEAF.snow, 0.05);
68
+ f.leaf(
69
+ 'ovate',
70
+ (sh.x[i] ?? 0) - 0.02,
71
+ (sh.y[i] ?? 0) + 0.008,
72
+ Math.PI / 2 + rng.range(-0.3, 0.3),
73
+ rng.range(0.04, 0.07),
74
+ rng.range(0.025, 0.04),
75
+ col,
76
+ 0,
77
+ 0.006,
78
+ );
79
+ }
80
+ }
81
+ }
82
+ f.finish(0.5, 0.25);
83
+ }
84
+
85
+ export const paintSpruce = (c: Canvas, seed: number): void => spruce(c, seed, false);
86
+ export const paintSpruceSnow = (c: Canvas, seed: number): void => spruce(c, seed, true);
87
+
88
+ export function paintNeedles(c: Canvas, seed: number): void {
89
+ const rng = createRng(seed);
90
+ const f = new Foliage(c);
91
+ const twigs: Curve[] = [];
92
+ for (let s = 0; s < 5; s++) {
93
+ const ex = 0.5 + (s - 2) * 0.13 + rng.range(-0.03, 0.03);
94
+ twigs.push(curve(0.5, 0.02, 0.5 + (ex - 0.5) * 0.4, 0.3, ex, rng.range(0.55, 0.7), 12));
95
+ }
96
+ for (const [s, tw] of twigs.entries()) {
97
+ drawStem(f, tw, 0.012, 0.006, LEAF.bark);
98
+ const k = s % 2 === 0 ? 0.6 : 1;
99
+ // Bundles along the outer third and a full brush at the tip, fanning forward.
100
+ for (let i = 6; i < tw.x.length; i++) {
101
+ const fan = i === tw.x.length - 1 ? 14 : 4;
102
+ for (let q = 0; q < fan; q++) {
103
+ const a = (tw.a[i] ?? 0) + rng.range(-1.1, 1.1) * (fan > 4 ? 0.8 : 1.2);
104
+ const col = dim(vary(rng, LEAF.pine, 0.2), k);
105
+ f.leaf(
106
+ 'needle',
107
+ tw.x[i] ?? 0,
108
+ tw.y[i] ?? 0,
109
+ a,
110
+ rng.range(0.12, 0.2),
111
+ 0.006,
112
+ col,
113
+ 0,
114
+ k * 0.002,
115
+ );
116
+ }
117
+ }
118
+ }
119
+ f.finish(0.5, 0.3);
120
+ }
121
+
122
+ export function paintReeds(c: Canvas, seed: number): void {
123
+ const rng = createRng(seed);
124
+ const f = new Foliage(c);
125
+ for (let b = 0; b < 22; b++) {
126
+ const back = b < 8;
127
+ const x = 0.5 + rng.range(-0.2, 0.2);
128
+ const tilt = (x - 0.5) * 1.2 + rng.range(-0.25, 0.25);
129
+ const col = dim(vary(rng, rng.next() < 0.2 ? LEAF.dry : LEAF.reed, 0.2), back ? 0.55 : 1);
130
+ f.leaf(
131
+ 'blade',
132
+ x,
133
+ 0.0,
134
+ tilt,
135
+ rng.range(0.55, 0.97),
136
+ rng.range(0.018, 0.032),
137
+ col,
138
+ 0,
139
+ back ? 0 : 0.002,
140
+ );
141
+ }
142
+ f.finish(0.5, 0.15);
143
+ }
@@ -0,0 +1,196 @@
1
+ // Rasteriser for foliage cells: tapered stems and leaf shapes composited back-to-front into
2
+ // linear colour + coverage, with leaf-dome and midrib relief. `finish` writes sRGB RGBA (alpha =
3
+ // leaf mask, transparent texels bled with leaf colour so mips don't halo), height, roughness and
4
+ // an occlusion term that darkens toward the twig axis. Shapes iterate only their pixel bounds.
5
+
6
+ import { type Canvas, clamp01, smooth } from './canvas.ts';
7
+ import { SRGB } from './orm.ts';
8
+ import { segment } from './shapes.ts';
9
+
10
+ export type Rgb = readonly [number, number, number];
11
+ /** Width profile of a leaf along its length t ∈ [0, 1]; 1 = full half-width. */
12
+ export type LeafShape = 'ovate' | 'lance' | 'needle' | 'blade' | 'palmate';
13
+
14
+ const total = (a: Float32Array): number => a.reduce((s, v) => s + v, 0);
15
+
16
+ /** Angle between palmate lobe axes. */
17
+ const LOBE = (Math.PI * 2) / 5;
18
+
19
+ export class Foliage {
20
+ private readonly r: Float32Array;
21
+ private readonly g: Float32Array;
22
+ private readonly b: Float32Array;
23
+ private readonly a: Float32Array;
24
+ private readonly c: Canvas;
25
+ private readonly px: number;
26
+
27
+ constructor(c: Canvas) {
28
+ this.c = c;
29
+ const n = c.w * c.h;
30
+ this.r = new Float32Array(n);
31
+ this.g = new Float32Array(n);
32
+ this.b = new Float32Array(n);
33
+ this.a = new Float32Array(n);
34
+ this.px = c.metresU / c.w;
35
+ c.height.fill(0);
36
+ }
37
+
38
+ /** Composite `cov` of colour (cr, cg, cb) × shade over texel i. */
39
+ private put(i: number, cov: number, col: Rgb, shade: number, h: number, rough: number): void {
40
+ if (cov <= 0) return;
41
+ const k = 1 - cov;
42
+ const s = shade * cov;
43
+ this.r[i] = (this.r[i] ?? 0) * k + col[0] * s;
44
+ this.g[i] = (this.g[i] ?? 0) * k + col[1] * s;
45
+ this.b[i] = (this.b[i] ?? 0) * k + col[2] * s;
46
+ this.a[i] = (this.a[i] ?? 0) * k + cov;
47
+ this.c.height[i] = (this.c.height[i] ?? 0) * k + h * cov;
48
+ this.c.rough[i] = (this.c.rough[i] ?? 0.8) * k + rough * cov;
49
+ }
50
+
51
+ /** Pixel bounds of a metre-space box, clipped to the canvas. */
52
+ private span(x0: number, y0: number, x1: number, y1: number): readonly number[] {
53
+ const { w, h } = this.c;
54
+ const p = this.px;
55
+ return [
56
+ Math.max(0, Math.floor(x0 / p) - 1),
57
+ Math.max(0, Math.floor(y0 / p) - 1),
58
+ Math.min(w - 1, Math.ceil(x1 / p) + 1),
59
+ Math.min(h - 1, Math.ceil(y1 / p) + 1),
60
+ ];
61
+ }
62
+
63
+ /** A tapered stem / twig / rachis from (ax, ay) width w0 to (bx, by) width w1, metres. */
64
+ stem(ax: number, ay: number, bx: number, by: number, w0: number, w1: number, col: Rgb): void {
65
+ const m = Math.max(w0, w1);
66
+ const [sx0 = 0, sy0 = 0, sx1 = 0, sy1 = 0] = this.span(
67
+ Math.min(ax, bx) - m,
68
+ Math.min(ay, by) - m,
69
+ Math.max(ax, bx) + m,
70
+ Math.max(ay, by) + m,
71
+ );
72
+ const lx = bx - ax;
73
+ const ly = by - ay;
74
+ const l2 = lx * lx + ly * ly || 1;
75
+ for (let y = sy0; y <= sy1; y++) {
76
+ const ym = (y + 0.5) * this.px;
77
+ for (let x = sx0; x <= sx1; x++) {
78
+ const xm = (x + 0.5) * this.px;
79
+ const t = clamp01(((xm - ax) * lx + (ym - ay) * ly) / l2);
80
+ const half = (w0 + (w1 - w0) * t) / 2;
81
+ const d = segment(xm, ym, ax, ay, bx, by);
82
+ const cov = clamp01(0.5 - (d - half) / this.px);
83
+ const round = half > 0 ? Math.sqrt(Math.max(0, 1 - (d / half) ** 2)) : 0;
84
+ this.put(y * this.c.w + x, cov, col, 1, 0.0015 + half * 0.4 * round, 0.85);
85
+ }
86
+ }
87
+ }
88
+
89
+ /**
90
+ * A leaf from its base (bx, by) along `angle` (radians, 0 = +V, positive = toward +U), `len`
91
+ * long and `wid` wide at its widest. `serrate` adds teeth; `lift` raises it over earlier layers.
92
+ */
93
+ leaf(
94
+ shape: LeafShape,
95
+ bx: number,
96
+ by: number,
97
+ angle: number,
98
+ len: number,
99
+ wid: number,
100
+ col: Rgb,
101
+ serrate = 0,
102
+ lift = 0,
103
+ ): void {
104
+ const ux = Math.sin(angle);
105
+ const uy = Math.cos(angle);
106
+ // Bounds: the base-to-tip segment padded by the half-width (palmate: the whole blade disc).
107
+ const tx = bx + ux * len;
108
+ const ty = by + uy * len;
109
+ const pad = shape === 'palmate' ? len : wid;
110
+ const [sx0 = 0, sy0 = 0, sx1 = 0, sy1 = 0] = this.span(
111
+ Math.min(bx, tx) - pad,
112
+ Math.min(by, ty) - pad,
113
+ Math.max(bx, tx) + pad,
114
+ Math.max(by, ty) + pad,
115
+ );
116
+ for (let y = sy0; y <= sy1; y++) {
117
+ const ym = (y + 0.5) * this.px - by;
118
+ for (let x = sx0; x <= sx1; x++) {
119
+ const xm = (x + 0.5) * this.px - bx;
120
+ const along = xm * ux + ym * uy;
121
+ const across = xm * uy - ym * ux;
122
+ let sd: number;
123
+ let rib: number;
124
+ if (shape === 'palmate') {
125
+ // Five pointed lobes around the petiole tip; notch at the stem side.
126
+ const rr = Math.hypot(along - len * 0.45, across);
127
+ const th = Math.atan2(across, along - len * 0.45);
128
+ const lobe = 1 - Math.abs(Math.sin(2.5 * th)) ** 0.6;
129
+ const edge =
130
+ len * 0.5 * (0.35 + 0.65 * lobe) * (1 + serrate * Math.abs(Math.sin(18 * th)));
131
+ sd = rr - edge;
132
+ const axis = Math.round(th / LOBE) * LOBE;
133
+ rib = rr * Math.abs(Math.sin(th - axis));
134
+ } else {
135
+ const t = along / len;
136
+ if (t < 0 || t > 1) {
137
+ sd = Math.max(-along, along - len) + Math.abs(across) * 0.5;
138
+ } else {
139
+ const prof =
140
+ shape === 'ovate'
141
+ ? Math.sin(Math.PI * t ** 0.8) ** 0.9
142
+ : shape === 'lance'
143
+ ? Math.sin(Math.PI * t ** 0.6)
144
+ : shape === 'blade'
145
+ ? (1 - t) ** 0.4
146
+ : 1 - 0.6 * t;
147
+ const teeth = 1 - serrate * Math.abs(Math.sin(t * 40));
148
+ sd = Math.abs(across) - (wid / 2) * prof * teeth;
149
+ }
150
+ rib = Math.abs(across);
151
+ }
152
+ const cov = clamp01(0.5 - sd / this.px);
153
+ if (cov <= 0) continue;
154
+ const inside = clamp01(-sd / (wid * 0.5 + 1e-6));
155
+ const vein = 1 - smooth(0, this.px * 1.5, rib);
156
+ const shade = 0.85 + 0.15 * inside;
157
+ this.put(
158
+ y * this.c.w + x,
159
+ cov,
160
+ col,
161
+ shade,
162
+ 0.002 + lift + 0.0015 * inside + 0.0006 * vein,
163
+ 0.6,
164
+ );
165
+ }
166
+ }
167
+ }
168
+
169
+ /** Write the layers to the canvas: sRGB RGBA with bled colour, occlusion toward the twig. */
170
+ finish(axisX = 0.5, reach = 0.35): void {
171
+ const { w, h } = this.c;
172
+ const n = w * h;
173
+ const sa = total(this.a);
174
+ const mean: Rgb =
175
+ sa > 0 ? [total(this.r) / sa, total(this.g) / sa, total(this.b) / sa] : [0.1, 0.2, 0.05];
176
+ const out = new Uint8Array(n * 4);
177
+ const enc = (v: number): number => SRGB[Math.round(clamp01(v) * 4095)] ?? 0;
178
+ for (let y = 0; y < h; y++) {
179
+ for (let x = 0; x < w; x++) {
180
+ const i = y * w + x;
181
+ const a = this.a[i] ?? 0;
182
+ // Un-premultiply; transparent texels take the mean leaf colour (mip-safe bleed).
183
+ const k = a > 0.02 ? 1 / a : 0;
184
+ const mix = clamp01(a * 4);
185
+ out[i * 4] = enc((this.r[i] ?? 0) * k * mix + mean[0] * (1 - mix));
186
+ out[i * 4 + 1] = enc((this.g[i] ?? 0) * k * mix + mean[1] * (1 - mix));
187
+ out[i * 4 + 2] = enc((this.b[i] ?? 0) * k * mix + mean[2] * (1 - mix));
188
+ out[i * 4 + 3] = Math.round(clamp01(a) * 255);
189
+ const d = Math.abs((x + 0.5) * this.px - axisX);
190
+ this.c.occl[i] = 0.55 + 0.45 * smooth(0, reach, d + (y + 0.5) * this.px * 0.4);
191
+ }
192
+ }
193
+ this.c.rgba = out;
194
+ this.c.metal.fill(0);
195
+ }
196
+ }
@@ -0,0 +1,23 @@
1
+ // Foliage cell dispatch: FoliageCellId → painter. Each paints colour + alpha mask (cutout leaf
2
+ // cards), anchor at the cell's bottom-centre, growth toward +V.
3
+
4
+ import type { FoliageCellId } from '../trim-sheet.ts';
5
+ import type { Canvas } from './canvas.ts';
6
+ import { paintBirch, paintBroadleaf, paintMaple, paintMapleAutumn } from './foliage-broad.ts';
7
+ import { paintAcacia, paintPalm, paintWillow } from './foliage-compound.ts';
8
+ import { paintNeedles, paintReeds, paintSpruce, paintSpruceSnow } from './foliage-conifer.ts';
9
+
10
+ export const FOLIAGE_PAINTERS: Readonly<Record<FoliageCellId, (c: Canvas, seed: number) => void>> =
11
+ {
12
+ foliage_broadleaf: paintBroadleaf,
13
+ foliage_maple: paintMaple,
14
+ foliage_maple_autumn: paintMapleAutumn,
15
+ foliage_birch: paintBirch,
16
+ foliage_acacia: paintAcacia,
17
+ foliage_palm: paintPalm,
18
+ foliage_spruce: paintSpruce,
19
+ foliage_spruce_snow: paintSpruceSnow,
20
+ foliage_willow: paintWillow,
21
+ foliage_reeds: paintReeds,
22
+ foliage_needles: paintNeedles,
23
+ };
@@ -0,0 +1,44 @@
1
+ // Weathering masks applied over a painted canvas: rust streaks (vertical runs from a patchy
2
+ // source, darker and rougher, killing metalness) and grime (dirt pooling toward the bottom of the
3
+ // region — world-down is low V — plus soft vertical rain streaks).
4
+
5
+ import { type Canvas, noiseAt, smooth } from './canvas.ts';
6
+
7
+ export function rustStreaks(c: Canvas, amount: number, seed: number): void {
8
+ if (amount <= 0) return;
9
+ const pu = c.metresU / c.w;
10
+ const pv = c.metresV / c.h;
11
+ for (let y = 0; y < c.h; y++) {
12
+ const ym = (y + 0.5) * pv;
13
+ for (let x = 0; x < c.w; x++) {
14
+ const xm = (x + 0.5) * pu;
15
+ const run = noiseAt(c, xm, ym, 0.025, 0.6, seed, 3);
16
+ const patch = noiseAt(c, xm, ym, 0.3, 0.3, seed + 1, 2);
17
+ const m = amount * smooth(0.15, 0.6, run) * smooth(-0.1, 0.4, patch);
18
+ if (m <= 0) continue;
19
+ const i = y * c.w + x;
20
+ c.tone[i] = (c.tone[i] ?? 1) * (1 - 0.4 * m);
21
+ c.rough[i] = Math.min(1, (c.rough[i] ?? 0.8) + 0.2 * m);
22
+ c.metal[i] = (c.metal[i] ?? 0) * (1 - m);
23
+ }
24
+ }
25
+ }
26
+
27
+ export function grime(c: Canvas, amount: number, seed: number): void {
28
+ if (amount <= 0) return;
29
+ const pu = c.metresU / c.w;
30
+ const pv = c.metresV / c.h;
31
+ for (let y = 0; y < c.h; y++) {
32
+ const ym = (y + 0.5) * pv;
33
+ const low = (1 - ym / c.metresV) ** 2;
34
+ for (let x = 0; x < c.w; x++) {
35
+ const xm = (x + 0.5) * pu;
36
+ const streak = smooth(0, 0.7, noiseAt(c, xm, ym, 0.05, 0.5, seed, 3));
37
+ const blot = 0.6 + 0.4 * noiseAt(c, xm, ym, 0.2, 0.2, seed + 1, 3);
38
+ const m = amount * Math.min(1, low * blot + 0.35 * streak);
39
+ const i = y * c.w + x;
40
+ c.tone[i] = (c.tone[i] ?? 1) * (1 - 0.3 * m);
41
+ c.rough[i] = Math.min(1, (c.rough[i] ?? 0.8) + 0.1 * m);
42
+ }
43
+ }
44
+ }
@@ -0,0 +1,52 @@
1
+ // Seeded integer-hash value noise and fBm, periodic along X so strips tile seamlessly along U.
2
+ // Pure functions of (coords, seed): no state, no allocation, safe in per-pixel loops.
3
+
4
+ /** Integer hash of a lattice point to [0, 1). */
5
+ export function hash2(ix: number, iy: number, seed: number): number {
6
+ let h = Math.imul(ix | 0, 0x27d4eb2d) ^ Math.imul(iy | 0, 0x165667b1) ^ seed;
7
+ h = Math.imul(h ^ (h >>> 15), 0x85ebca6b);
8
+ h = Math.imul(h ^ (h >>> 13), 0xc2b2ae35);
9
+ return ((h ^ (h >>> 16)) >>> 0) / 4294967296;
10
+ }
11
+
12
+ /** Value noise in [0, 1] on a unit lattice, periodic in x with an integer `period` (cells). */
13
+ export function valueNoise(x: number, y: number, period: number, seed: number): number {
14
+ const xi = Math.floor(x);
15
+ const yi = Math.floor(y);
16
+ const fx = x - xi;
17
+ const fy = y - yi;
18
+ const sx = fx * fx * (3 - 2 * fx);
19
+ const sy = fy * fy * (3 - 2 * fy);
20
+ const x0 = ((xi % period) + period) % period;
21
+ const x1 = (x0 + 1) % period;
22
+ const a = hash2(x0, yi, seed);
23
+ const b = hash2(x1, yi, seed);
24
+ const c = hash2(x0, yi + 1, seed);
25
+ const d = hash2(x1, yi + 1, seed);
26
+ return a + (b - a) * sx + (c - a + (d - c - b + a) * sx) * sy;
27
+ }
28
+
29
+ /**
30
+ * Fractal Brownian motion in [-1, 1]: `octaves` layers of value noise, each at double frequency
31
+ * and `gain` amplitude. Doubling keeps the period an integer, so the sum stays tileable.
32
+ */
33
+ export function fbm(
34
+ x: number,
35
+ y: number,
36
+ period: number,
37
+ seed: number,
38
+ octaves = 4,
39
+ gain = 0.5,
40
+ ): number {
41
+ let sum = 0;
42
+ let amp = 1;
43
+ let norm = 0;
44
+ let f = 1;
45
+ for (let o = 0; o < octaves; o++) {
46
+ sum += amp * valueNoise(x * f, y * f, period * f, seed + o * 1013);
47
+ norm += amp;
48
+ amp *= gain;
49
+ f *= 2;
50
+ }
51
+ return (sum / norm) * 2 - 1;
52
+ }
@@ -0,0 +1,52 @@
1
+ // Normal from height: a Sobel gradient of the canvas height (metres) over its texel size (metres),
2
+ // so relief reads at true scale. glTF tangent space, +Y = toward decreasing V (OpenGL "green up").
3
+ // Wraps along U for strips, clamps for cells and along V.
4
+
5
+ import type { Canvas } from './canvas.ts';
6
+
7
+ /** Neighbour column indices, precomputed so the hot loop does no modulo. */
8
+ export function neighbours(n: number, wrap: boolean): { lo: Int32Array; hi: Int32Array } {
9
+ const lo = new Int32Array(n);
10
+ const hi = new Int32Array(n);
11
+ for (let i = 0; i < n; i++) {
12
+ lo[i] = i > 0 ? i - 1 : wrap ? n - 1 : 0;
13
+ hi[i] = i < n - 1 ? i + 1 : wrap ? 0 : n - 1;
14
+ }
15
+ return { lo, hi };
16
+ }
17
+
18
+ /** RGBA8 tangent-space normals for the canvas; `strength` scales the slope. */
19
+ export function sobelNormals(c: Canvas, strength = 1): Uint8Array {
20
+ const { w, h, height } = c;
21
+ const out = new Uint8Array(w * h * 4);
22
+ const cx = neighbours(w, c.wrapU);
23
+ const cy = neighbours(h, false);
24
+ const sx = strength / (8 * (c.metresU / w));
25
+ const sy = strength / (8 * (c.metresV / h));
26
+ for (let y = 0; y < h; y++) {
27
+ const r0 = (cy.lo[y] ?? 0) * w;
28
+ const r1 = y * w;
29
+ const r2 = (cy.hi[y] ?? 0) * w;
30
+ for (let x = 0; x < w; x++) {
31
+ const xl = cx.lo[x] ?? 0;
32
+ const xr = cx.hi[x] ?? 0;
33
+ const tl = height[r0 + xl] ?? 0;
34
+ const tc = height[r0 + x] ?? 0;
35
+ const tr = height[r0 + xr] ?? 0;
36
+ const ml = height[r1 + xl] ?? 0;
37
+ const mr = height[r1 + xr] ?? 0;
38
+ const bl = height[r2 + xl] ?? 0;
39
+ const bc = height[r2 + x] ?? 0;
40
+ const br = height[r2 + xr] ?? 0;
41
+ const gx = (tr + 2 * mr + br - tl - 2 * ml - bl) * sx;
42
+ const gy = (bl + 2 * bc + br - tl - 2 * tc - tr) * sy;
43
+ const inv = 1 / Math.sqrt(gx * gx + gy * gy + 1);
44
+ const o = (r1 + x) * 4;
45
+ out[o] = Math.round((-gx * inv * 0.5 + 0.5) * 255);
46
+ out[o + 1] = Math.round((gy * inv * 0.5 + 0.5) * 255);
47
+ out[o + 2] = Math.round((inv * 0.5 + 0.5) * 255);
48
+ out[o + 3] = 255;
49
+ }
50
+ }
51
+ return out;
52
+ }
@@ -0,0 +1,95 @@
1
+ // ORM + albedo packing. Occlusion comes from the height cavity (blurred height minus height: deep
2
+ // where a texel sits below its surroundings — mortar, seams, grain); roughness and metalness are
3
+ // the painted layers. Albedo is the linear tone, sRGB-encoded, with a mild cavity darkening baked
4
+ // in so engines that ignore the occlusion map still read the joints.
5
+
6
+ import type { Canvas } from './canvas.ts';
7
+
8
+ /** Depth (metres) below the local mean that counts as fully occluded. */
9
+ const AO_DEPTH = 0.006;
10
+ const AO_STRENGTH = 0.75;
11
+ /** Blur radius of the cavity estimate, metres. */
12
+ const AO_RADIUS = 0.02;
13
+
14
+ /** Separable sliding-window box blur: O(1) per texel. Wraps along x when `wrap`, else clamps. */
15
+ function boxBlur(
16
+ src: Float32Array,
17
+ w: number,
18
+ h: number,
19
+ rx: number,
20
+ ry: number,
21
+ wrap: boolean,
22
+ ): Float32Array {
23
+ const tmp = new Float32Array(src.length);
24
+ const out = new Float32Array(src.length);
25
+ const ix = (i: number): number =>
26
+ i < 0 ? (wrap ? (i % w) + w : 0) : i >= w ? (wrap ? i % w : w - 1) : i;
27
+ for (let y = 0; y < h; y++) {
28
+ const row = y * w;
29
+ let s = 0;
30
+ for (let k = -rx; k <= rx; k++) s += src[row + ix(k)] ?? 0;
31
+ for (let x = 0; x < w; x++) {
32
+ tmp[row + x] = s / (2 * rx + 1);
33
+ s += (src[row + ix(x + rx + 1)] ?? 0) - (src[row + ix(x - rx)] ?? 0);
34
+ }
35
+ }
36
+ const iy = (j: number): number => (j < 0 ? 0 : j >= h ? h - 1 : j);
37
+ for (let x = 0; x < w; x++) {
38
+ let s = 0;
39
+ for (let k = -ry; k <= ry; k++) s += tmp[iy(k) * w + x] ?? 0;
40
+ for (let y = 0; y < h; y++) {
41
+ out[y * w + x] = s / (2 * ry + 1);
42
+ s += (tmp[iy(y + ry + 1) * w + x] ?? 0) - (tmp[iy(y - ry) * w + x] ?? 0);
43
+ }
44
+ }
45
+ return out;
46
+ }
47
+
48
+ /** Per-texel ambient occlusion in [1 - AO_STRENGTH, 1]. */
49
+ export function cavityOcclusion(c: Canvas): Float32Array {
50
+ const rx = Math.max(1, Math.round(AO_RADIUS / (c.metresU / c.w)));
51
+ const ry = Math.max(1, Math.round(AO_RADIUS / (c.metresV / c.h)));
52
+ const blur = boxBlur(boxBlur(c.height, c.w, c.h, rx, ry, c.wrapU), c.w, c.h, rx, ry, c.wrapU);
53
+ const ao = new Float32Array(c.height.length);
54
+ for (let i = 0; i < ao.length; i++) {
55
+ const cavity = (blur[i] ?? 0) - (c.height[i] ?? 0);
56
+ const t = cavity <= 0 ? 0 : cavity >= AO_DEPTH ? 1 : cavity / AO_DEPTH;
57
+ ao[i] = (1 - AO_STRENGTH * t) * (c.occl[i] ?? 1);
58
+ }
59
+ return ao;
60
+ }
61
+
62
+ export const SRGB = new Uint8Array(4096).map((_, i) => {
63
+ const l = i / 4095;
64
+ const s = l <= 0.0031308 ? l * 12.92 : 1.055 * l ** (1 / 2.4) - 0.055;
65
+ return Math.round(s * 255);
66
+ });
67
+
68
+ const byte = (v: number): number => (v <= 0 ? 0 : v >= 1 ? 255 : Math.round(v * 255));
69
+
70
+ /** RGBA8 ORM: R = occlusion, G = roughness, B = metalness, A = 255. */
71
+ export function packOrm(c: Canvas, ao: Float32Array): Uint8Array {
72
+ const out = new Uint8Array(c.w * c.h * 4);
73
+ for (let i = 0, o = 0; i < ao.length; i++, o += 4) {
74
+ out[o] = byte(ao[i] ?? 1);
75
+ out[o + 1] = byte(c.rough[i] ?? 0.8);
76
+ out[o + 2] = byte(c.metal[i] ?? 0);
77
+ out[o + 3] = 255;
78
+ }
79
+ return out;
80
+ }
81
+
82
+ /** RGBA8 sRGB albedo: the stamped image if any, else grey tone × mild cavity. */
83
+ export function packAlbedo(c: Canvas, ao: Float32Array): Uint8Array {
84
+ if (c.rgba) return c.rgba;
85
+ const out = new Uint8Array(c.w * c.h * 4);
86
+ for (let i = 0, o = 0; i < ao.length; i++, o += 4) {
87
+ const t = (c.tone[i] ?? 1) * (0.8 + 0.2 * (ao[i] ?? 1));
88
+ const g = SRGB[t <= 0 ? 0 : t >= 1 ? 4095 : Math.round(t * 4095)] ?? 255;
89
+ out[o] = g;
90
+ out[o + 1] = g;
91
+ out[o + 2] = g;
92
+ out[o + 3] = 255;
93
+ }
94
+ return out;
95
+ }
@@ -0,0 +1,56 @@
1
+ // Timber boards (kind 'planks'): boards run along U, butt joints staggered at random, dark gaps,
2
+ // stretched grain with growth-ring streaks, occasional knots, per-board tone.
3
+ // params: width 0.15 · length 1.0 · gap 0.004 · depth 0.004 · knots 1.5 (per board-metre)
4
+
5
+ import { type Canvas, noiseAt, param, smooth } from './canvas.ts';
6
+ import { Courses } from './courses.ts';
7
+ import { hash2 } from './noise.ts';
8
+ import type { StripRecipe } from './types.ts';
9
+
10
+ export function paintPlanks(c: Canvas, r: StripRecipe, seed: number): void {
11
+ const p = r.params;
12
+ const width = param(p, 'width', 0.15);
13
+ const length = param(p, 'length', 1);
14
+ const gap = param(p, 'gap', 0.004);
15
+ const depth = param(p, 'depth', 0.004);
16
+ const knots = param(p, 'knots', 1.5);
17
+ const boards = new Courses(c, { course: width, length, irregular: 0.6, stagger: 0, seed });
18
+ const pu = c.metresU / c.w;
19
+ const pv = c.metresV / c.h;
20
+ for (let y = 0; y < c.h; y++) {
21
+ const ym = (y + 0.5) * pv;
22
+ const k = boards.at(ym);
23
+ const dy = Math.min(ym - boards.y0, boards.y1 - ym);
24
+ const vy = (ym - boards.y0) / (boards.y1 - boards.y0);
25
+ for (let x = 0; x < c.w; x++) {
26
+ const i = y * c.w + x;
27
+ const xm = (x + 0.5) * pu;
28
+ const id = boards.id[x] ?? 0;
29
+ const h = hash2(k, id, seed);
30
+ const d = Math.min((boards.dx[x] ?? 0) * 1.5, dy);
31
+ const edge = smooth(gap * 0.5, gap * 1.5, d);
32
+ // Grain: rings warped by a long, thin noise; the board's own seed shifts its figure.
33
+ const warp = noiseAt(c, xm, ym, 0.4, 0.05, seed + id * 31 + k * 7, 3);
34
+ const ring = Math.sin((vy * 9 + warp * 3 + h * 6) * Math.PI * 2);
35
+ const fibre = noiseAt(c, xm, ym, 0.08, 0.002, seed + 17, 2);
36
+ let knot = 0;
37
+ const nk = Math.floor(knots * length * h * 2);
38
+ for (let q = 0; q < nk; q++) {
39
+ const kx = hash2(k * 13 + q, id, seed + 41) * c.metresU;
40
+ const ky =
41
+ boards.y0 + (0.25 + 0.5 * hash2(k, id * 5 + q, seed + 43)) * (boards.y1 - boards.y0);
42
+ let ddx = Math.abs(xm - kx);
43
+ if (c.wrapU) ddx = Math.min(ddx, c.metresU - ddx);
44
+ const rr = Math.sqrt((ddx / 2.2) ** 2 + (ym - ky) ** 2) / (width * 0.12);
45
+ if (rr < 1) knot = Math.max(knot, 1 - rr);
46
+ }
47
+ c.height[i] = -depth * (1 - edge) + 0.0004 * ring * edge + 0.0003 * fibre - 0.0008 * knot;
48
+ c.tone[i] =
49
+ (0.66 + 0.22 * h) *
50
+ (1 + 0.08 * ring + 0.06 * fibre) *
51
+ (1 - 0.5 * knot) *
52
+ (0.5 + 0.5 * edge);
53
+ c.rough[i] = r.roughness + 0.05 * fibre + 0.1 * (1 - edge);
54
+ }
55
+ }
56
+ }