@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,184 @@
1
+ // Species presets: plain `TreeParams` objects — spread one and override what you need
2
+ // (`{ ...oak, leaves: { ...oak.leaves, color: hex('#7a8a2a') } }` is an autumn oak).
3
+ // `typical` sizes are mature open-grown trees; sources in each comment and in recipes/<name>/README.md.
4
+
5
+ import { hex } from '../material/index.ts';
6
+ import type { TreeParams } from './vegetation-params.ts';
7
+
8
+ /** Quercus robur (English oak): 20–25 m, crown about as wide; girth 4–12 m. Spreading, crooked limbs. */
9
+ export const oak: TreeParams = {
10
+ shape: 'hemispherical',
11
+ baseSize: 0.3,
12
+ ratioPower: 1.15,
13
+ taper: 0.3,
14
+ levels: [
15
+ { branches: 0, length: 0.55, downAngle: 0, curveVar: 5, segments: 8 },
16
+ {
17
+ branches: 8,
18
+ length: 0.95,
19
+ downAngle: 58,
20
+ downAngleV: -18,
21
+ curve: -8,
22
+ curveVar: 16,
23
+ segments: 7,
24
+ },
25
+ { branches: 6, length: 0.55, downAngle: 50, curve: 12, curveVar: 18, start: 0.2, segments: 4 },
26
+ { branches: 5, length: 0.5, downAngle: 45, curveVar: 14, start: 0.15, segments: 2 },
27
+ ],
28
+ bark: hex('#4a4036'),
29
+ barkTop: hex('#5a5045'),
30
+ leaves: {
31
+ shape: 'lobed',
32
+ size: 1.7,
33
+ width: 0.8,
34
+ perStem: 5,
35
+ color: hex('#3d6022'),
36
+ colorVar: 0.16,
37
+ },
38
+ roots: 5,
39
+ impostor: 'blob',
40
+ typical: { height: 22, spread: 22, dbh: 1.3 },
41
+ };
42
+
43
+ /** Pinus sylvestris (Scots pine): 25–35 m, DBH up to 1 m; long clean bole, orange upper bark,
44
+ * flat-topped crown in the top third, dark blue-green needle tufts on upturned tips. */
45
+ export const pine: TreeParams = {
46
+ shape: 'cylindrical',
47
+ baseSize: 0.6,
48
+ ratioPower: 1.2,
49
+ taper: 0.12,
50
+ levels: [
51
+ { branches: 0, length: 0.94, downAngle: 0, curveVar: 3, segments: 10 },
52
+ {
53
+ branches: 13,
54
+ length: 0.34,
55
+ downAngle: 94,
56
+ downAngleV: -14,
57
+ curve: 16,
58
+ curveVar: 14,
59
+ segments: 5,
60
+ },
61
+ { branches: 5, length: 0.42, downAngle: 60, curve: 18, curveVar: 14, start: 0.3, segments: 3 },
62
+ ],
63
+ bark: hex('#4a3a30'),
64
+ barkTop: hex('#c0652c'),
65
+ leaves: {
66
+ shape: 'tuft',
67
+ size: 2.1,
68
+ width: 0.95,
69
+ perStem: 5,
70
+ from: 0.5,
71
+ flat: 0.8,
72
+ color: hex('#24422e'),
73
+ colorVar: 0.1,
74
+ },
75
+ roots: 4,
76
+ impostor: 'blob',
77
+ typical: { height: 25, spread: 9, dbh: 0.6 },
78
+ };
79
+
80
+ /** Cocos nucifera (coconut palm): up to 30 m; single ringed stem 30–40 cm; 4–6 m pinnate fronds. */
81
+ export const palm: TreeParams = {
82
+ shape: 'cylindrical',
83
+ baseSize: 0.95,
84
+ ratioPower: 1.1,
85
+ taper: 0.7,
86
+ lean: 12,
87
+ levels: [
88
+ { branches: 0, length: 1, downAngle: 0, curve: 24, curveVar: 1.5, segments: 12 },
89
+ {
90
+ branches: 22,
91
+ length: 0.26,
92
+ downAngle: 115,
93
+ downAngleV: -85,
94
+ rotate: 137.5,
95
+ curve: -55,
96
+ curveVar: 4,
97
+ segments: 8,
98
+ },
99
+ ],
100
+ bark: hex('#6b5d4a'),
101
+ barkTop: hex('#8a7a62'),
102
+ leaves: {
103
+ shape: 'frond',
104
+ size: 0,
105
+ width: 0.2,
106
+ perStem: 26,
107
+ color: hex('#4f7a2a'),
108
+ colorVar: 0.12,
109
+ },
110
+ roots: 9,
111
+ fruit: { count: 7, radius: 0.13, color: hex('#5f6a22') },
112
+ impostor: 'cards',
113
+ typical: { height: 20, spread: 9, dbh: 0.35 },
114
+ };
115
+
116
+ /** Betula pendula (silver birch): 15–25 m, narrow crown, white bark with black marks, weeping twigs. */
117
+ export const birch: TreeParams = {
118
+ shape: 'flame',
119
+ baseSize: 0.3,
120
+ ratioPower: 1.4,
121
+ taper: 0.1,
122
+ levels: [
123
+ { branches: 0, length: 1, downAngle: 0, curveVar: 3, segments: 10 },
124
+ {
125
+ branches: 22,
126
+ length: 0.32,
127
+ downAngle: 45,
128
+ downAngleV: 10,
129
+ curve: -25,
130
+ curveVar: 8,
131
+ segments: 4,
132
+ },
133
+ { branches: 5, length: 0.55, downAngle: 30, curve: -45, curveVar: 10, start: 0.2, segments: 3 },
134
+ ],
135
+ bark: hex('#d9d6cc'),
136
+ barkMarks: { color: hex('#24221f'), amount: 0.12 },
137
+ leaves: {
138
+ shape: 'oval',
139
+ size: 0.8,
140
+ width: 0.7,
141
+ perStem: 4,
142
+ color: hex('#6f9a32'),
143
+ colorVar: 0.15,
144
+ },
145
+ impostor: 'blob',
146
+ typical: { height: 18, spread: 8, dbh: 0.4 },
147
+ };
148
+
149
+ /** Salix babylonica (weeping willow): 10–15 m, wider than tall; long hanging leafy strands. */
150
+ export const willow: TreeParams = {
151
+ shape: 'hemispherical',
152
+ baseSize: 0.25,
153
+ ratioPower: 1.7,
154
+ taper: 0.35,
155
+ levels: [
156
+ { branches: 0, length: 0.55, downAngle: 0, curveVar: 5, segments: 6 },
157
+ {
158
+ branches: 8,
159
+ length: 0.95,
160
+ downAngle: 42,
161
+ curve: 40,
162
+ curveBack: -70,
163
+ curveVar: 8,
164
+ segments: 6,
165
+ },
166
+ { branches: 7, length: 0.55, downAngle: 35, curve: -80, curveVar: 8, start: 0.3, segments: 4 },
167
+ ],
168
+ bark: hex('#4f463a'),
169
+ leaves: {
170
+ shape: 'strand',
171
+ size: 2.6,
172
+ width: 0.22,
173
+ perStem: 4,
174
+ color: hex('#8aa83e'),
175
+ colorVar: 0.12,
176
+ hang: 0.92,
177
+ },
178
+ roots: 6,
179
+ impostor: 'blob',
180
+ typical: { height: 12, spread: 14, dbh: 0.8 },
181
+ };
182
+
183
+ export const SPECIES = { oak, pine, palm, birch, willow } as const;
184
+ export type SpeciesName = keyof typeof SPECIES;
@@ -0,0 +1,168 @@
1
+ // The botanical skeleton: Weber–Penn recursive stems as plain polylines + radii. Always grown at
2
+ // full depth and LOD0 resolution from one seeded stream, so every LOD is the same tree; meshing
3
+ // decides what to emit. Children start inside their parent (centre offset r_parent − r_child along
4
+ // the attachment normal), so each one physically grows out of the bark.
5
+
6
+ import type { Rng, Vec3 } from '../core/index.ts';
7
+ import { arcLengths, transportFrames } from '../primitives/index.ts';
8
+ import { type BranchLevel, shapeRatio, type TreeParams } from './vegetation-params.ts';
9
+
10
+ export interface Stem {
11
+ readonly level: number;
12
+ readonly pts: readonly Vec3[];
13
+ readonly radii: readonly number[];
14
+ readonly length: number;
15
+ /** Index of the parent stem, −1 for the trunk. */
16
+ readonly parent: number;
17
+ /** Attachment point on the parent's bark (cluster cards root here at LOD1). */
18
+ readonly surface: Vec3;
19
+ /** Nominal length before shape/variation: drives the child count. */
20
+ readonly maxLength: number;
21
+ }
22
+
23
+ const deg = Math.PI / 180;
24
+ const unit = (a: Vec3): Vec3 => {
25
+ const l = Math.hypot(a[0], a[1], a[2]) || 1;
26
+ return [a[0] / l, a[1] / l, a[2] / l];
27
+ };
28
+
29
+ /** Rodrigues rotation of v about unit axis k by angle a (radians). */
30
+ export function rotate(v: Vec3, k: Vec3, a: number): Vec3 {
31
+ const c = Math.cos(a);
32
+ const s = Math.sin(a);
33
+ const d = (k[0] * v[0] + k[1] * v[1] + k[2] * v[2]) * (1 - c);
34
+ return [
35
+ v[0] * c + (k[1] * v[2] - k[2] * v[1]) * s + k[0] * d,
36
+ v[1] * c + (k[2] * v[0] - k[0] * v[2]) * s + k[1] * d,
37
+ v[2] * c + (k[0] * v[1] - k[1] * v[0]) * s + k[2] * d,
38
+ ];
39
+ }
40
+
41
+ /** Axis that turns `dir` towards +Z when rotated by a positive angle. */
42
+ function upAxis(dir: Vec3): Vec3 {
43
+ const k: Vec3 = [dir[1], -dir[0], 0]; // dir × Z
44
+ return Math.hypot(k[0], k[1]) < 1e-4 ? [0, 1, 0] : unit(k);
45
+ }
46
+
47
+ /** A stem's polyline: `res` segments, bending by curve/curveBack plus seeded wobble. */
48
+ function path(start: Vec3, dir0: Vec3, len: number, lv: BranchLevel, trunk: boolean, rng: Rng) {
49
+ const res = Math.max(1, lv.segments);
50
+ const step = len / res;
51
+ const pts: Vec3[] = [start];
52
+ let dir = dir0;
53
+ let p = start;
54
+ for (let i = 0; i < res; i++) {
55
+ const half = lv.curveBack !== undefined && i >= res / 2;
56
+ const bend = half
57
+ ? (lv.curveBack ?? 0) / Math.ceil(res / 2)
58
+ : (lv.curve ?? 0) / (lv.curveBack !== undefined ? Math.max(1, Math.floor(res / 2)) : res);
59
+ const wob = lv.curveVar ?? 0;
60
+ // The trunk stays in the XZ plane and bends about Y: + curve swings it back against the lean
61
+ // (−X), so a leaning palm bows and its crown comes back over its base.
62
+ if (trunk) dir = rotate(dir, [0, 1, 0], -(bend + rng.range(-wob, wob)) * deg);
63
+ else {
64
+ dir = rotate(dir, upAxis(dir), (bend + rng.range(-wob, wob)) * deg);
65
+ dir = rotate(dir, [0, 0, 1], rng.range(-wob, wob) * deg);
66
+ }
67
+ dir = unit(dir);
68
+ p = [p[0] + dir[0] * step, p[1] + dir[1] * step, p[2] + dir[2] * step];
69
+ pts.push(p);
70
+ }
71
+ return pts;
72
+ }
73
+
74
+ export function grow(p: TreeParams, size: { height: number; dbh: number }, rng: Rng): Stem[] {
75
+ const t0 = p.levels[0];
76
+ if (!t0) return [];
77
+ const L0 = size.height * t0.length;
78
+ const r0 = size.dbh / 2;
79
+ const lean = (p.lean ?? 0) * deg;
80
+ const trunkPts = path([0, 0, -0.05], [Math.sin(lean), 0, Math.cos(lean)], L0, t0, true, rng);
81
+ const arc0 = arcLengths(trunkPts);
82
+ const bh = 1.3; // breast height, metres (forestry DBH convention)
83
+ const trunkR = arc0.map((s) => {
84
+ const f = Math.max(0, (s - bh) / Math.max(0.1, L0 - bh));
85
+ return r0 * (1 - (1 - p.taper) * f);
86
+ });
87
+ const stems: Stem[] = [
88
+ {
89
+ level: 0,
90
+ pts: trunkPts,
91
+ radii: trunkR,
92
+ length: L0,
93
+ parent: -1,
94
+ surface: [0, 0, 0],
95
+ maxLength: L0,
96
+ },
97
+ ];
98
+ for (let si = 0; si < stems.length; si++) {
99
+ const par = stems[si];
100
+ const lv = par ? p.levels[par.level + 1] : undefined;
101
+ if (!par || !lv) continue;
102
+ const arc = arcLengths(par.pts);
103
+ const frames = transportFrames(par.pts);
104
+ const s0 = (par.level === 0 ? p.baseSize : (lv.start ?? 0.1)) * par.length;
105
+ const s1 = par.length * 0.97;
106
+ const fill = par.level === 0 ? 1 : Math.min(1, par.length / par.maxLength);
107
+ const n = par.level === 0 ? lv.branches : Math.round(lv.branches * (0.25 + 0.75 * fill));
108
+ let phi = rng.range(0, 360);
109
+ for (let i = 0; i < n; i++) {
110
+ const s = s0 + ((s1 - s0) * (i + rng.range(0.15, 0.85))) / Math.max(1, n);
111
+ let j = 0;
112
+ while (j < arc.length - 2 && (arc[j + 1] ?? 0) < s) j++;
113
+ const a = par.pts[j] ?? [0, 0, 0];
114
+ const b = par.pts[j + 1] ?? a;
115
+ const f = (s - (arc[j] ?? 0)) / ((arc[j + 1] ?? 1) - (arc[j] ?? 0) || 1);
116
+ const c: Vec3 = [
117
+ a[0] + (b[0] - a[0]) * f,
118
+ a[1] + (b[1] - a[1]) * f,
119
+ a[2] + (b[2] - a[2]) * f,
120
+ ];
121
+ const rp = (par.radii[j] ?? 0) * (1 - f) + (par.radii[j + 1] ?? 0) * f;
122
+ const T = unit([b[0] - a[0], b[1] - a[1], b[2] - a[2]]);
123
+ const fr = frames[j] ?? { n: [1, 0, 0], b: [0, 1, 0] };
124
+ phi += (lv.rotate ?? 137.5) + rng.range(-10, 10);
125
+ const cp = Math.cos(phi * deg);
126
+ const sp = Math.sin(phi * deg);
127
+ const raw: Vec3 = [
128
+ cp * fr.n[0] + sp * fr.b[0],
129
+ cp * fr.n[1] + sp * fr.b[1],
130
+ cp * fr.n[2] + sp * fr.b[2],
131
+ ];
132
+ const tdot = raw[0] * T[0] + raw[1] * T[1] + raw[2] * T[2];
133
+ const np = unit([raw[0] - tdot * T[0], raw[1] - tdot * T[1], raw[2] - tdot * T[2]]);
134
+ const zone = (s - s0) / Math.max(1e-6, s1 - s0);
135
+ const down = (lv.downAngle + (lv.downAngleV ?? 0) * zone + rng.range(-5, 5)) * deg;
136
+ const dir = unit([
137
+ T[0] * Math.cos(down) + np[0] * Math.sin(down),
138
+ T[1] * Math.cos(down) + np[1] * Math.sin(down),
139
+ T[2] * Math.cos(down) + np[2] * Math.sin(down),
140
+ ]);
141
+ const vary = 1 + rng.range(-(lv.lengthVar ?? 0.15), lv.lengthVar ?? 0.15);
142
+ const maxLength = lv.length * par.length;
143
+ const base =
144
+ par.level === 0
145
+ ? maxLength * shapeRatio(p.shape, (par.length - s) / Math.max(1e-6, par.length - s0))
146
+ : lv.length * (par.length - 0.6 * s);
147
+ const len = Math.max(0.15, base * vary);
148
+ const r0c = par.radii[0] ?? rp;
149
+ const rc = Math.max(0.006, Math.min(0.8 * rp, r0c * (len / par.length) ** p.ratioPower));
150
+ const start: Vec3 = [
151
+ c[0] + np[0] * (rp - rc),
152
+ c[1] + np[1] * (rp - rc),
153
+ c[2] + np[2] * (rp - rc),
154
+ ];
155
+ const pts = path(start, dir, len, lv, false, rng);
156
+ stems.push({
157
+ level: par.level + 1,
158
+ pts,
159
+ radii: pts.map((_, k) => rc * (1 - (0.75 * k) / Math.max(1, pts.length - 1))),
160
+ length: len,
161
+ parent: si,
162
+ surface: [c[0] + np[0] * rp, c[1] + np[1] * rp, c[2] + np[2] * rp],
163
+ maxLength,
164
+ });
165
+ }
166
+ }
167
+ return stems;
168
+ }
@@ -0,0 +1,199 @@
1
+ // Vegetation kind: `tree(ctx, …)` grows a Weber–Penn skeleton from species params and emits a
2
+ // game tree — `trunk`, `roots` (buttress flare), and a swaying group `crown` holding `branches`
3
+ // (merged) and `leaves` (merged cards) — then fits it onto its real height and crown spread.
4
+ // LOD policy: LOD0 every level and every leaf card; LOD1 one level fewer (trees with ≥ 3 levels),
5
+ // halved path resolution, twigs replaced by cluster cards; LOD2 an impostor under 300 triangles.
6
+
7
+ import {
8
+ BUDGETS,
9
+ type BuildContext,
10
+ defineFamily,
11
+ type Mesh,
12
+ merge,
13
+ type RealDimensions,
14
+ Ts23dError,
15
+ translate,
16
+ triangleCount,
17
+ type Vec3,
18
+ } from '../core/index.ts';
19
+ import { frondLeaflets, fruit } from './vegetation-fronds.ts';
20
+ import { blob, frondCards } from './vegetation-impostor.ts';
21
+ import { clusterCards, crownShade, leafCards } from './vegetation-leaves.ts';
22
+ import {
23
+ barkTint,
24
+ fitFactors,
25
+ radialSegments,
26
+ rootsMesh,
27
+ scaleMesh,
28
+ stemsMesh,
29
+ trunkMesh,
30
+ } from './vegetation-mesh.ts';
31
+ import type { TreeParams } from './vegetation-params.ts';
32
+ import { SPECIES } from './vegetation-presets.ts';
33
+ import { grow } from './vegetation-skeleton.ts';
34
+
35
+ export interface TreeOptions {
36
+ /** A preset name (`oak`, `pine`, `palm`, `birch`, `willow`) or a full parameter set. */
37
+ readonly species?: string | TreeParams;
38
+ /** Overrides on top of the species. */
39
+ readonly params?: Partial<TreeParams>;
40
+ /** Total height, metres (ground to crown top). */
41
+ readonly height: number;
42
+ /** Crown spread (X and Y), metres. Default: the species' typical ratio to height. */
43
+ readonly spread?: number;
44
+ /** Trunk diameter at breast height (1.3 m), metres. Default: the species' typical ratio. */
45
+ readonly dbh?: number;
46
+ readonly seed?: number;
47
+ /** Part-name prefix: `oak_3` → `oak_3_trunk`, `oak_3_crown`, … */
48
+ readonly prefix?: string;
49
+ }
50
+
51
+ export interface TreeInfo {
52
+ readonly parts: { trunk: string; crown: string; branches: string; leaves: string; roots: string };
53
+ readonly height: number;
54
+ readonly spread: number;
55
+ readonly dbh: number;
56
+ }
57
+
58
+ export function treeParams(o: Pick<TreeOptions, 'species' | 'params'>): TreeParams {
59
+ const s = o.species ?? 'oak';
60
+ const base = typeof s === 'string' ? SPECIES[s as keyof typeof SPECIES] : s;
61
+ if (!base) {
62
+ throw new Ts23dError('T3D_RECIPE_INVALID', {
63
+ cause: `unknown tree species '${String(s)}'`,
64
+ fix: `use one of ${Object.keys(SPECIES).join(', ')} or pass a TreeParams object (spread a preset and override)`,
65
+ });
66
+ }
67
+ return { ...base, ...o.params };
68
+ }
69
+
70
+ export function tree(ctx: BuildContext, o: TreeOptions): TreeInfo {
71
+ const p = treeParams(o);
72
+ if (!(o.height > 0) || p.levels.length < 2 || p.levels.length > 4) {
73
+ throw new Ts23dError('T3D_RECIPE_INVALID', {
74
+ cause: `tree height ${o.height} m with ${p.levels.length} branch levels`,
75
+ fix: 'height must be > 0 and levels must hold the trunk plus 1–3 branch levels',
76
+ });
77
+ }
78
+ const k = o.height / p.typical.height;
79
+ const spread = o.spread ?? p.typical.spread * k;
80
+ const dbh = o.dbh ?? p.typical.dbh * k;
81
+ const pre = o.prefix ? `${o.prefix}_` : '';
82
+ const parts = {
83
+ trunk: `${pre}trunk`,
84
+ crown: `${pre}crown`,
85
+ branches: `${pre}branches`,
86
+ leaves: `${pre}leaves`,
87
+ roots: `${pre}roots`,
88
+ };
89
+ const rng = ctx.rng.fork(`tree:${o.prefix ?? ''}:${o.seed ?? 0}`);
90
+ const stems = grow(p, { height: o.height, dbh }, rng.fork('skeleton'));
91
+ const trunkStem = stems[0];
92
+ if (!trunkStem) return { parts, height: o.height, spread, dbh };
93
+ const deepest = p.levels.length - 1;
94
+ const lod = ctx.lod;
95
+ const top = trunkStem.pts[trunkStem.pts.length - 1]?.[2] ?? o.height;
96
+ const shade = crownShade(stems, 1);
97
+ let trunk: (r: number) => Mesh;
98
+ let roots: ((r: number) => Mesh) | null = null;
99
+ let branches: Mesh | null = null;
100
+ let leaves: Mesh;
101
+ if (lod === 2) {
102
+ const imp =
103
+ p.impostor === 'cards' ? frondCards(stems, p, 1) : blob(stems, p, rng.fork('impostor'));
104
+ trunk = () => barkTint(imp.trunk, p, top, rng.fork('bark'));
105
+ leaves = imp.leaves;
106
+ } else {
107
+ const hi = lod === 0 || deepest < 2 ? deepest : deepest - 1;
108
+ trunk = (r) => trunkMesh(trunkStem, p, lod, r, rng.fork('bark'));
109
+ if (p.roots) roots = (r) => rootsMesh(trunkStem, p, lod, r, rng.fork('roots'));
110
+ const wood = [
111
+ barkTint(stemsMesh(stems, 1, hi, lod, p.bark), p, top, rng.fork('bark:branches')),
112
+ ];
113
+ if (p.fruit && lod === 0)
114
+ wood.push(fruit(trunkStem, p.baseSize - 0.01, p.fruit, rng.fork('fruit')));
115
+ branches = merge(wood);
116
+ const lr = rng.fork('leaves');
117
+ leaves =
118
+ p.leaves.shape === 'frond'
119
+ ? frondLeaflets(
120
+ stems,
121
+ deepest,
122
+ p.leaves,
123
+ lod === 0 ? p.leaves.perStem : Math.ceil(p.leaves.perStem / 2),
124
+ shade,
125
+ lr,
126
+ )
127
+ : hi === deepest
128
+ ? leafCards(stems, deepest, radialSegments(deepest, lod), p.leaves, shade, lr)
129
+ : clusterCards(stems, deepest, p.leaves, shade, lr);
130
+ }
131
+ // Fit onto the real envelope; the trunk is rebuilt thinner by 1/sy so DBH (across Y) is exact.
132
+ const s = fitFactors(
133
+ [trunk(1), branches, leaves, roots?.(1) ?? null].filter((m): m is Mesh => m !== null),
134
+ [spread, spread, o.height],
135
+ );
136
+ const fit = (m: Mesh): Mesh => scaleMesh(m, s);
137
+ const base = trunkStem.pts[Math.round(p.baseSize * (trunkStem.pts.length - 1))] ?? [0, 0, 0];
138
+ const pivot: Vec3 = [base[0] * s[0], base[1] * s[1], base[2] * s[2]];
139
+ const back: Vec3 = [-pivot[0], -pivot[1], -pivot[2]];
140
+ ctx.part(parts.trunk, fit(trunk(1 / s[1])));
141
+ if (roots) ctx.part(parts.roots, fit(roots(1 / s[1])));
142
+ ctx.group(parts.crown, {
143
+ at: pivot,
144
+ motion: { kind: 'sway', axis: [1, 0, 0], degrees: 2, hz: 0.4 },
145
+ // The crown grows out of the trunk: they meet by design, and the clash gate's conservative
146
+ // full-turn sway volume would otherwise flag the wood that holds it.
147
+ clearOf: roots ? [parts.trunk, parts.roots] : [parts.trunk],
148
+ });
149
+ if (branches && triangleCount(branches) > 0)
150
+ ctx.part(parts.branches, translate(fit(branches), back), { parent: parts.crown });
151
+ ctx.part(parts.leaves, translate(fit(leaves), back), { parent: parts.crown });
152
+ return { parts, height: o.height, spread, dbh };
153
+ }
154
+
155
+ /** `real` for a tree recipe: overall envelope plus DBH (trunk across Y) and total height. */
156
+ export function treeReal(o: {
157
+ height: number;
158
+ spread: number;
159
+ dbh: number;
160
+ prefix?: string;
161
+ source?: string;
162
+ }): RealDimensions {
163
+ const pre = o.prefix ? `${o.prefix}_` : '';
164
+ const src = o.source ? { source: o.source } : {};
165
+ return {
166
+ length: o.spread,
167
+ width: o.spread,
168
+ height: o.height,
169
+ measures: [
170
+ {
171
+ label: 'trunk diameter at breast height',
172
+ value: o.dbh,
173
+ axis: 'y',
174
+ parts: [`${pre}trunk`],
175
+ ...src,
176
+ },
177
+ {
178
+ label: 'total height',
179
+ value: o.height,
180
+ axis: 'z',
181
+ parts: [`${pre}trunk`, `${pre}crown`, `${pre}branches`, `${pre}leaves`, `${pre}roots`],
182
+ ...src,
183
+ },
184
+ ],
185
+ };
186
+ }
187
+
188
+ export type TreeFamilyParams = Omit<TreeOptions, 'prefix'>;
189
+
190
+ /** One family, many species: `trees.variant({ name: 'oak', params: { species: 'oak', height: 22 } })`. */
191
+ export const trees = defineFamily<TreeFamilyParams>({
192
+ kind: 'vegetation',
193
+ tags: ['vegetation', 'tree'],
194
+ budget: BUDGETS.tree,
195
+ defaults: { species: 'oak', height: 22 },
196
+ build: (ctx, p) => {
197
+ tree(ctx, p);
198
+ },
199
+ });
@@ -0,0 +1,144 @@
1
+ // Vehicle helpers: wheels that spin, tracks, propellers. Named parts follow the engine contract:
2
+ // `axle_*` and `wheel_*` spin about local Y, `prop_*`/`rotor_*` spin about their own axis.
3
+
4
+ import { type BuildContext, place, type Vec3 } from '../core/index.ts';
5
+ import { parseWhyte } from '../grammar/index.ts';
6
+ import { box, cylinder } from '../primitives/index.ts';
7
+
8
+ const segs = (ctx: BuildContext, hi: number): number => [hi, Math.max(8, hi / 2), 6][ctx.lod] ?? 6;
9
+
10
+ /** A pair of wheels on one spinning axle. */
11
+ export function axle(
12
+ ctx: BuildContext,
13
+ o: {
14
+ name: string;
15
+ at: Vec3;
16
+ radius: number;
17
+ gauge: number;
18
+ width: number;
19
+ color: Vec3;
20
+ rps: number;
21
+ },
22
+ ): void {
23
+ ctx.group(o.name, { at: o.at, motion: { kind: 'spin', axis: [0, 1, 0], rps: o.rps } });
24
+ for (const [s, y] of [
25
+ ['l', o.gauge / 2],
26
+ ['r', -o.gauge / 2],
27
+ ] as const) {
28
+ ctx.part(
29
+ `${o.name}_wheel_${s}`,
30
+ cylinder({
31
+ radius: o.radius,
32
+ length: o.width,
33
+ segments: segs(ctx, 24),
34
+ axis: 'y',
35
+ at: [0, y, 0],
36
+ color: o.color,
37
+ strip: 'iron',
38
+ }),
39
+ { parent: o.name },
40
+ );
41
+ }
42
+ }
43
+
44
+ /** Lays out wheel groups from a Whyte arrangement along X; returns axle x-positions per role. */
45
+ export function whyteAxles(
46
+ arrangement: string,
47
+ o: { leading: number; driving: number; trailing: number; spacing: number },
48
+ ): { role: string; x: number; radius: number }[] {
49
+ const groups = parseWhyte(arrangement);
50
+ const out: { role: string; x: number; radius: number }[] = [];
51
+ let x = 0;
52
+ for (const g of groups) {
53
+ const r = g.role === 'driving' ? o.driving : g.role === 'leading' ? o.leading : o.trailing;
54
+ for (let i = 0; i < g.count / 2; i++) {
55
+ out.push({ role: g.role, x: x + r, radius: r });
56
+ x += r * 2 + o.spacing;
57
+ }
58
+ }
59
+ const mid = x / 2;
60
+ // Front (+X) is the leading end.
61
+ return out.map((a) => ({ ...a, x: mid - a.x }));
62
+ }
63
+
64
+ /** One side of a tracked running gear: road wheels (spinning) + a flat track loop. */
65
+ export function trackRun(
66
+ ctx: BuildContext,
67
+ o: {
68
+ side: 'l' | 'r';
69
+ y: number;
70
+ length: number;
71
+ wheels: number;
72
+ radius: number;
73
+ width: number;
74
+ color: Vec3;
75
+ track: Vec3;
76
+ },
77
+ ): void {
78
+ const pitch = o.length / o.wheels;
79
+ for (let i = 0; i < o.wheels; i++) {
80
+ const x = -o.length / 2 + pitch * (i + 0.5);
81
+ ctx.part(
82
+ `road_wheel_${o.side}_${i}`,
83
+ cylinder({
84
+ radius: o.radius,
85
+ length: o.width * 0.8,
86
+ segments: segs(ctx, 20),
87
+ axis: 'y',
88
+ at: [0, 0, 0],
89
+ color: o.color,
90
+ strip: 'iron',
91
+ }),
92
+ { at: [x, o.y, o.radius + 0.06], motion: { kind: 'spin', axis: [0, 1, 0], rps: -0.6 } },
93
+ );
94
+ }
95
+ const t = 0.06;
96
+ ctx.part(
97
+ `track_${o.side}_bottom`,
98
+ box({
99
+ size: [o.length + o.radius, o.width, t],
100
+ at: [0, o.y, 0],
101
+ color: o.track,
102
+ strip: 'iron',
103
+ }),
104
+ );
105
+ ctx.part(
106
+ `track_${o.side}_top`,
107
+ box({
108
+ size: [o.length, o.width, t],
109
+ at: [0, o.y, o.radius * 2 + 0.06],
110
+ color: o.track,
111
+ strip: 'iron',
112
+ }),
113
+ );
114
+ }
115
+
116
+ /** A propeller hub + blades that spin about X (nose direction). */
117
+ export function propeller(
118
+ ctx: BuildContext,
119
+ o: { name: string; at: Vec3; radius: number; blades: number; color: Vec3; parent?: string },
120
+ ): void {
121
+ ctx.group(o.name, {
122
+ at: o.at,
123
+ ...(o.parent ? { parent: o.parent } : {}),
124
+ motion: { kind: 'spin', axis: [1, 0, 0], rps: 6 },
125
+ });
126
+ ctx.part(
127
+ `${o.name}_hub`,
128
+ cylinder({
129
+ radius: o.radius * 0.12,
130
+ length: o.radius * 0.3,
131
+ segments: segs(ctx, 12),
132
+ axis: 'x',
133
+ color: o.color,
134
+ }),
135
+ { parent: o.name },
136
+ );
137
+ // One blade built pointing up (+Z) from the hub, then rolled about the spin axis per blade.
138
+ const blade = box({ size: [0.03, o.radius * 0.12, o.radius], color: o.color });
139
+ for (let i = 0; i < o.blades; i++) {
140
+ ctx.part(`${o.name}_blade_${i}`, place(blade, { roll: (i / o.blades) * Math.PI * 2 }), {
141
+ parent: o.name,
142
+ });
143
+ }
144
+ }