@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,230 @@
1
+ // Roof geometry as maths, no mesh: every pitched roof here is convex, so its surface is the LOWER
2
+ // ENVELOPE (min) of a few planes over a plan outline. Gable = 2 planes, hip/pyramid = 4, gambrel =
3
+ // 4, mansard = 8, shed = 1. Facets are the plan cells where one plane is lowest; ridges and hips
4
+ // fall out as the creases between them. Local frame: `a` along the ridge, `b` across it, z up from
5
+ // the wall plate; the roof UNDERSIDE passes through the top outer edge of every wall.
6
+
7
+ import { Ts23dError, type Vec2, type Vec3 } from '../core/index.ts';
8
+ import type { StripId } from '../material/index.ts';
9
+ import { area2, clipPoly } from './faces.ts';
10
+
11
+ export type RoofType = 'gable' | 'hip' | 'pyramid' | 'mansard' | 'gambrel' | 'shed';
12
+ export type RoofSide = 'front' | 'back' | 'left' | 'right';
13
+
14
+ export interface RoofOptions {
15
+ readonly type: RoofType;
16
+ /** Wall-line width (X) and depth (Y) of the mass the roof sits on, metres. */
17
+ readonly footprint: readonly [number, number];
18
+ /** Main slope, degrees (the lower, steep slope of a mansard or gambrel). */
19
+ readonly pitchDeg: number;
20
+ /** Centre of the footprint at wall-plate height (top of the walls). */
21
+ readonly at: Vec3;
22
+ readonly color: Vec3;
23
+ /** Roofing strip, laid in true-scale courses down the slope. Default 'slate'. */
24
+ readonly strip?: StripId;
25
+ /** Eave overhang past the walls, metres. Default 0.35. */
26
+ readonly overhang?: number;
27
+ /** Gable-end (rake) overhang; default = overhang. Hipped roofs ignore it. */
28
+ readonly rake?: number;
29
+ /** Slab thickness perpendicular to the slope (rafter + battens + slate). Default 0.18. */
30
+ readonly thickness?: number;
31
+ /** Ridge direction; default along the longer side. */
32
+ readonly ridge?: 'x' | 'y';
33
+ /** Shed only: the side the single slope falls toward. Default 'front' (+X). */
34
+ readonly low?: RoofSide;
35
+ /** Mansard / gambrel upper slope, degrees. Default 20 (mansard), 30 (gambrel). */
36
+ readonly upperPitchDeg?: number;
37
+ /** Mansard / gambrel: share of the half-span the lower slope covers in plan. Default 0.3 / 0.45. */
38
+ readonly knee?: number;
39
+ }
40
+
41
+ export interface Plane {
42
+ /** z = c + ga·a + gb·b (local, relative to the wall plate). */
43
+ readonly c: number;
44
+ readonly ga: number;
45
+ readonly gb: number;
46
+ }
47
+
48
+ export interface RoofShape {
49
+ readonly L: number;
50
+ readonly D: number;
51
+ /** Underside planes and the matching top planes (offset by the perpendicular thickness). */
52
+ readonly under: readonly Plane[];
53
+ readonly top: readonly Plane[];
54
+ /** Plan outline at the eaves (CCW, local a/b). */
55
+ readonly outline: readonly Vec2[];
56
+ /** Local unit axes in world XY: a → ex, b → ey (ex × ey = +Z). */
57
+ readonly ex: Vec2;
58
+ readonly ey: Vec2;
59
+ readonly thickness: number;
60
+ }
61
+
62
+ const RAD = Math.PI / 180;
63
+ const FRAMES: Record<RoofSide, [Vec2, Vec2]> = {
64
+ // Frame whose −b side faces the named world side.
65
+ front: [
66
+ [0, 1],
67
+ [-1, 0],
68
+ ],
69
+ back: [
70
+ [0, -1],
71
+ [1, 0],
72
+ ],
73
+ left: [
74
+ [-1, 0],
75
+ [0, -1],
76
+ ],
77
+ right: [
78
+ [1, 0],
79
+ [0, 1],
80
+ ],
81
+ };
82
+
83
+ const bad = (cause: string, fix: string): Ts23dError =>
84
+ new Ts23dError('T3D_RECIPE_INVALID', { cause, fix });
85
+
86
+ export function validateRoof(o: RoofOptions): void {
87
+ const [w, d] = o.footprint;
88
+ if (!(w > 0 && d > 0 && Number.isFinite(w) && Number.isFinite(d)))
89
+ throw bad(
90
+ `roof footprint is [${w}, ${d}]`,
91
+ 'pass the wall-line [width X, depth Y] in metres, both > 0',
92
+ );
93
+ const maxPitch = o.type === 'mansard' || o.type === 'gambrel' ? 85 : 75;
94
+ if (!(o.pitchDeg >= 3 && o.pitchDeg <= maxPitch))
95
+ throw bad(
96
+ `${o.type} roof pitchDeg is ${o.pitchDeg}`,
97
+ `use 3–${maxPitch}° (slate ≥ 20°, tile 30–45°, sheet metal ≥ 3°); for a flat roof use a box`,
98
+ );
99
+ const t = o.thickness ?? 0.18;
100
+ if (!(t > 0 && t < 1))
101
+ throw bad(`roof thickness is ${t}`, 'use 0.1–0.3 m (rafter + battens + slate)');
102
+ const ov = o.overhang ?? 0.35;
103
+ if (!(ov >= 0 && ov < 3) || !((o.rake ?? ov) >= 0))
104
+ throw bad(`roof overhang is ${ov}`, 'use 0–1.5 m past the wall line (0 for a party wall)');
105
+ if (o.type === 'mansard' || o.type === 'gambrel') {
106
+ const up = o.upperPitchDeg ?? (o.type === 'mansard' ? 20 : 30);
107
+ if (!(up > 0 && up < o.pitchDeg))
108
+ throw bad(
109
+ `${o.type} upperPitchDeg ${up} is not below pitchDeg ${o.pitchDeg}`,
110
+ 'the upper slope must be shallower than the lower one (mansard 70°/20°, gambrel 60°/30°)',
111
+ );
112
+ const k = o.knee ?? 0.3;
113
+ if (!(k > 0 && k < 1))
114
+ throw bad(`${o.type} knee is ${k}`, 'use a fraction 0.15–0.6 of the half-span');
115
+ }
116
+ }
117
+
118
+ /** Planes of the roof UNDERSIDE for a type, in the local frame. */
119
+ function underPlanes(o: RoofOptions, L: number, D: number): Plane[] {
120
+ const tp = Math.tan(o.pitchDeg * RAD);
121
+ const side = (s: number, c = 0, k = s): Plane[] => [
122
+ { c: c + D * k, ga: 0, gb: -s },
123
+ { c: c + D * k, ga: 0, gb: s },
124
+ ];
125
+ const ends = (s: number, c = 0, k = s): Plane[] => [
126
+ { c: c + L * k, ga: -s, gb: 0 },
127
+ { c: c + L * k, ga: s, gb: 0 },
128
+ ];
129
+ switch (o.type) {
130
+ case 'gable':
131
+ return side(tp);
132
+ case 'shed':
133
+ return [{ c: D * tp, ga: 0, gb: tp }];
134
+ case 'hip':
135
+ return [...side(tp), ...ends(tp)];
136
+ case 'pyramid': {
137
+ const h = Math.min(L, D) * tp;
138
+ return [...side(h / D), ...ends(h / L)];
139
+ }
140
+ case 'gambrel':
141
+ case 'mansard': {
142
+ const t2 = Math.tan((o.upperPitchDeg ?? (o.type === 'mansard' ? 20 : 30)) * RAD);
143
+ const r = (o.knee ?? (o.type === 'mansard' ? 0.3 : 0.45)) * Math.min(L, D);
144
+ // Upper plane: z = r·tp + (D − r − |b|)·t2 → c = r·tp + (D − r)·t2 with slope t2.
145
+ const upper = (half: number): number => r * tp + (half - r) * t2 - half * t2;
146
+ const lower = [...side(tp), ...(o.type === 'mansard' ? ends(tp) : [])];
147
+ const top = [
148
+ ...side(t2, upper(D), t2),
149
+ ...(o.type === 'mansard' ? ends(t2, upper(L), t2) : []),
150
+ ];
151
+ return [...lower, ...top];
152
+ }
153
+ }
154
+ }
155
+
156
+ export function roofShape(o: RoofOptions): RoofShape {
157
+ validateRoof(o);
158
+ const [w, d] = o.footprint;
159
+ const ridge = o.ridge ?? (w >= d ? 'x' : 'y');
160
+ const low: RoofSide = o.type === 'shed' ? (o.low ?? 'front') : ridge === 'x' ? 'right' : 'front';
161
+ const [ex, ey] = FRAMES[low];
162
+ const L = (Math.abs(ex[0]) > 0.5 ? w : d) / 2;
163
+ const D = (Math.abs(ex[0]) > 0.5 ? d : w) / 2;
164
+ const ov = o.overhang ?? 0.35;
165
+ const t = o.thickness ?? 0.18;
166
+ const under = underPlanes(o, L, D);
167
+ const top = under.map((p) => ({ ...p, c: p.c + t * Math.hypot(1, p.ga, p.gb) }));
168
+ const hipped = o.type === 'hip' || o.type === 'pyramid' || o.type === 'mansard';
169
+ const A = hipped ? 4 * (L + D + ov) : L + (o.rake ?? ov);
170
+ const B = D + ov;
171
+ // Eave height: the steepest-falling b-plane at the eave line; every plane must stay above it.
172
+ const g0 = under[0] ?? { c: 0, ga: 0, gb: 1 };
173
+ const zEave = -ov * Math.hypot(g0.ga, g0.gb);
174
+ let outline: Vec2[] = [
175
+ [-A, -B],
176
+ [A, -B],
177
+ [A, B],
178
+ [-A, B],
179
+ ];
180
+ for (const p of under) outline = clipPoly(outline, (q) => zEave - zAt(p, q));
181
+ return { L, D, under, top, outline, ex, ey, thickness: t };
182
+ }
183
+
184
+ export const zAt = (p: Plane, q: Vec2): number => p.c + p.ga * q[0] + p.gb * q[1];
185
+
186
+ /** Envelope height (min of planes) at a local plan point. */
187
+ export const envelope = (planes: readonly Plane[], q: Vec2): number =>
188
+ Math.min(...planes.map((p) => zAt(p, q)));
189
+
190
+ /** The plan cell of each plane on the envelope (empty cells dropped), with its plane. */
191
+ export function facets(planes: readonly Plane[], outline: readonly Vec2[]): [Plane, Vec2[]][] {
192
+ const out: [Plane, Vec2[]][] = [];
193
+ planes.forEach((p, i) => {
194
+ let cell: Vec2[] = [...outline];
195
+ planes.forEach((q, j) => {
196
+ if (j !== i && cell.length >= 3) cell = clipPoly(cell, (r) => zAt(p, r) - zAt(q, r));
197
+ });
198
+ if (cell.length >= 3 && Math.abs(area2(cell)) > 1e-6) out.push([p, cell]);
199
+ });
200
+ return out;
201
+ }
202
+
203
+ /** Local plan → world XY offset (add `at`). */
204
+ export const toWorld = (s: RoofShape, q: Vec2, z: number, at: Vec3): Vec3 => [
205
+ at[0] + s.ex[0] * q[0] + s.ey[0] * q[1],
206
+ at[1] + s.ex[1] * q[0] + s.ey[1] * q[1],
207
+ at[2] + z,
208
+ ];
209
+
210
+ /** World XY → local plan. */
211
+ export const toLocal = (s: RoofShape, x: number, y: number, at: Vec3): Vec2 => {
212
+ const dx = x - at[0];
213
+ const dy = y - at[1];
214
+ return [dx * s.ex[0] + dy * s.ex[1], dx * s.ey[0] + dy * s.ey[1]];
215
+ };
216
+
217
+ /** World z of the roof's top surface at world (x, y) — chimneys, dormers, aerials sit on this. */
218
+ export function roofHeightAt(o: RoofOptions, x: number, y: number): number {
219
+ const s = roofShape(o);
220
+ return o.at[2] + envelope(s.top, toLocal(s, x, y, o.at));
221
+ }
222
+
223
+ /** World z of the highest point of the roof surface (ridge or apex), slab included. */
224
+ export function ridgeHeight(o: RoofOptions): number {
225
+ const s = roofShape(o);
226
+ let z = -Infinity;
227
+ for (const [p, cell] of facets(s.top, s.outline))
228
+ for (const q of cell) z = Math.max(z, zAt(p, q));
229
+ return o.at[2] + z;
230
+ }
@@ -0,0 +1,250 @@
1
+ // Pitched roofs as one mesh: true-scale roofing courses down every slope, a slab with real
2
+ // thickness (soffit + fascia/bargeboards around the eaves), ridge and hip caps, gable infill walls
3
+ // where the roof leaves a wall open, chimneys that stand on the attic floor and clear the slope.
4
+ // LOD0/1 keep thickness, fascia and caps; LOD2 is the bare slopes + gables (a few dozen triangles).
5
+
6
+ import {
7
+ type BuildContext,
8
+ type Lod,
9
+ type Mesh,
10
+ MeshBuilder,
11
+ merge,
12
+ type Vec2,
13
+ type Vec3,
14
+ } from '../core/index.ts';
15
+ import type { StripId } from '../material/index.ts';
16
+ import { beam } from '../primitives/index.ts';
17
+ import { emitFace } from './faces.ts';
18
+ import { chimney, type RoofChimney } from './roof-chimney.ts';
19
+ import {
20
+ envelope,
21
+ facets,
22
+ type Plane,
23
+ type RoofOptions,
24
+ type RoofShape,
25
+ ridgeHeight,
26
+ roofHeightAt,
27
+ roofShape,
28
+ toWorld,
29
+ zAt,
30
+ } from './roof-shape.ts';
31
+
32
+ export interface RoofMeshOptions extends RoofOptions {
33
+ /** Fascia, bargeboards, soffit and ridge caps. Default: the roof colour, darkened. */
34
+ readonly trim?: Vec3;
35
+ /** Gable / shed end infill walls. Default pale render. */
36
+ readonly wall?: { readonly color: Vec3; readonly strip?: StripId };
37
+ /** Ridge and hip caps (LOD0/1). Default true. */
38
+ readonly caps?: boolean;
39
+ readonly chimneys?: readonly RoofChimney[];
40
+ }
41
+
42
+ export interface RoofInfo {
43
+ /** World z of the ridge / apex top. */
44
+ readonly ridge: number;
45
+ /** World z of the lowest eave edge (underside). */
46
+ readonly eave: number;
47
+ /** World z of the top surface at world (x, y). */
48
+ readonly heightAt: (x: number, y: number) => number;
49
+ }
50
+
51
+ const W = (s: RoofShape, q: Vec2, z: number, at: Vec3): Vec3 => toWorld(s, q, z, at);
52
+ const dirW = (s: RoofShape, a: number, b: number, z: number): Vec3 => [
53
+ s.ex[0] * a + s.ey[0] * b,
54
+ s.ex[1] * a + s.ey[1] * b,
55
+ z,
56
+ ];
57
+
58
+ const onOutline = (outline: readonly Vec2[], q: Vec2): boolean =>
59
+ outline.some((a, i) => {
60
+ const b = outline[(i + 1) % outline.length] ?? a;
61
+ const ex = b[0] - a[0];
62
+ const ey = b[1] - a[1];
63
+ const l2 = ex * ex + ey * ey || 1;
64
+ const t = Math.max(0, Math.min(1, ((q[0] - a[0]) * ex + (q[1] - a[1]) * ey) / l2));
65
+ return Math.hypot(a[0] + ex * t - q[0], a[1] + ey * t - q[1]) < 1e-6;
66
+ });
67
+
68
+ /** Slopes of one envelope: top (normal up) or underside (normal down). */
69
+ function slopes(
70
+ b: MeshBuilder,
71
+ s: RoofShape,
72
+ planes: readonly Plane[],
73
+ o: RoofMeshOptions,
74
+ face: { down: boolean; color: Vec3; strip: StripId; banded: boolean },
75
+ ): [Plane, Vec2[]][] {
76
+ const cells = facets(planes, s.outline);
77
+ for (const [p, cell] of cells) {
78
+ const pts = cell.map((q) => W(s, q, zAt(p, q), o.at));
79
+ const low = pts.reduce((m, q) => (q[2] < m[2] ? q : m), pts[0] ?? o.at);
80
+ const k = face.down ? -1 : 1;
81
+ emitFace(b, {
82
+ points: pts,
83
+ normal: dirW(s, -p.ga * k, -p.gb * k, k),
84
+ up: [0, 0, 1],
85
+ vOrigin: low,
86
+ color: face.color,
87
+ strip: face.strip,
88
+ banded: face.banded,
89
+ });
90
+ }
91
+ return cells;
92
+ }
93
+
94
+ /** Vertical fascia / bargeboard band round the outline, between the two envelopes. */
95
+ function fascia(b: MeshBuilder, s: RoofShape, o: RoofMeshOptions, trim: Vec3, verts: Vec2[]): void {
96
+ const n = s.outline.length;
97
+ for (let i = 0; i < n; i++) {
98
+ const a = s.outline[i] ?? [0, 0];
99
+ const c = s.outline[(i + 1) % n] ?? a;
100
+ const ex = c[0] - a[0];
101
+ const ey = c[1] - a[1];
102
+ const len2 = ex * ex + ey * ey;
103
+ if (len2 < 1e-12) continue;
104
+ const ts = [0, 1];
105
+ for (const q of verts) {
106
+ const t = ((q[0] - a[0]) * ex + (q[1] - a[1]) * ey) / len2;
107
+ const off = Math.abs((q[0] - a[0]) * ey - (q[1] - a[1]) * ex) / Math.sqrt(len2);
108
+ if (t > 1e-6 && t < 1 - 1e-6 && off < 1e-6) ts.push(t);
109
+ }
110
+ ts.sort((x, y) => x - y);
111
+ const out = dirW(s, ey, -ex, 0);
112
+ for (let k = 0; k + 1 < ts.length; k++) {
113
+ const q0: Vec2 = [a[0] + ex * (ts[k] ?? 0), a[1] + ey * (ts[k] ?? 0)];
114
+ const q1: Vec2 = [a[0] + ex * (ts[k + 1] ?? 1), a[1] + ey * (ts[k + 1] ?? 1)];
115
+ if (Math.hypot(q1[0] - q0[0], q1[1] - q0[1]) < 1e-6) continue;
116
+ emitFace(b, {
117
+ points: [
118
+ W(s, q0, envelope(s.under, q0), o.at),
119
+ W(s, q1, envelope(s.under, q1), o.at),
120
+ W(s, q1, envelope(s.top, q1), o.at),
121
+ W(s, q0, envelope(s.top, q0), o.at),
122
+ ],
123
+ normal: out,
124
+ up: [0, 0, 1],
125
+ vOrigin: o.at,
126
+ color: trim,
127
+ strip: 'plain',
128
+ });
129
+ }
130
+ }
131
+ }
132
+
133
+ /** Infill under the roof on each wall line where the underside rises above the plate. */
134
+ function gableWalls(b: MeshBuilder, s: RoofShape, o: RoofMeshOptions, banded: boolean): void {
135
+ const wall = o.wall ?? { color: [0.8, 0.76, 0.68] as Vec3 };
136
+ const lines: [Vec2, Vec2, Vec3][] = [
137
+ [[s.L, -s.D], [s.L, s.D], dirW(s, 1, 0, 0)],
138
+ [[-s.L, s.D], [-s.L, -s.D], dirW(s, -1, 0, 0)],
139
+ [[s.L, s.D], [-s.L, s.D], dirW(s, 0, 1, 0)],
140
+ [[-s.L, -s.D], [s.L, -s.D], dirW(s, 0, -1, 0)],
141
+ ];
142
+ for (const [q0, q1, out] of lines) {
143
+ const at = (t: number): Vec2 => [q0[0] + (q1[0] - q0[0]) * t, q0[1] + (q1[1] - q0[1]) * t];
144
+ const ts = new Set([0, 1]);
145
+ s.under.forEach((p, i) => {
146
+ s.under.forEach((q, j) => {
147
+ if (j <= i) return;
148
+ const d0 = zAt(p, q0) - zAt(q, q0);
149
+ const d1 = zAt(p, q1) - zAt(q, q1);
150
+ if (Math.abs(d0 - d1) < 1e-12) return;
151
+ const t = d0 / (d0 - d1);
152
+ if (t > 1e-6 && t < 1 - 1e-6 && Math.abs(envelope(s.under, at(t)) - zAt(p, at(t))) < 1e-7)
153
+ ts.add(t);
154
+ });
155
+ });
156
+ const prof = [...ts]
157
+ .sort((x, y) => x - y)
158
+ .map((t) => [at(t), envelope(s.under, at(t))] as const);
159
+ if (Math.max(...prof.map(([, z]) => z)) < 0.01) continue;
160
+ const pts: Vec3[] = [W(s, q0, 0, o.at)];
161
+ for (const [q, z] of prof) pts.push(W(s, q, Math.max(0, z), o.at));
162
+ pts.push(W(s, q1, 0, o.at));
163
+ emitFace(b, {
164
+ points: pts,
165
+ normal: out,
166
+ up: [0, 0, 1],
167
+ vOrigin: o.at,
168
+ color: wall.color,
169
+ strip: wall.strip ?? 'plain',
170
+ banded,
171
+ });
172
+ }
173
+ }
174
+
175
+ /** Ridge / hip / knee caps: a round roll on every crease between two top facets. */
176
+ function caps(s: RoofShape, o: RoofMeshOptions, cells: [Plane, Vec2[]][], trim: Vec3, lod: Lod) {
177
+ const seen = new Set<string>();
178
+ const out: Mesh[] = [];
179
+ const r = Math.max(0.06, s.thickness * 0.5);
180
+ for (const [p, cell] of cells) {
181
+ cell.forEach((q0, i) => {
182
+ const q1 = cell[(i + 1) % cell.length] ?? q0;
183
+ const mid: Vec2 = [(q0[0] + q1[0]) / 2, (q0[1] + q1[1]) / 2];
184
+ if (onOutline(s.outline, mid) || Math.hypot(q1[0] - q0[0], q1[1] - q0[1]) < 1e-3) return;
185
+ const key = [q0, q1]
186
+ .map((q) => `${q[0].toFixed(4)},${q[1].toFixed(4)}`)
187
+ .sort()
188
+ .join('|');
189
+ if (seen.has(key)) return;
190
+ seen.add(key);
191
+ const lift = r * 0.3;
192
+ out.push(
193
+ beam({
194
+ from: W(s, q0, zAt(p, q0) + lift, o.at),
195
+ to: W(s, q1, zAt(p, q1) + lift, o.at),
196
+ width: r * 2,
197
+ segments: lod === 0 ? 8 : 5,
198
+ color: trim,
199
+ strip: 'plain',
200
+ }),
201
+ );
202
+ });
203
+ }
204
+ return out;
205
+ }
206
+
207
+ /** The roof as one mesh at a detail level. Pure: compose with place/merge or pass to ctx.part. */
208
+ export function roofMesh(o: RoofMeshOptions, lod: Lod = 0): Mesh {
209
+ const s = roofShape(o);
210
+ const trim = o.trim ?? [o.color[0] * 0.55, o.color[1] * 0.55, o.color[2] * 0.55];
211
+ const strip = o.strip ?? 'slate';
212
+ const b = new MeshBuilder();
213
+ const extra: Mesh[] = [];
214
+ if (lod === 2) {
215
+ // Bare slopes on the underside envelope: it rests on the walls with no slab.
216
+ slopes(b, s, s.under, o, { down: false, color: o.color, strip, banded: false });
217
+ } else {
218
+ const top = slopes(b, s, s.top, o, { down: false, color: o.color, strip, banded: true });
219
+ const under = slopes(b, s, s.under, o, {
220
+ down: true,
221
+ color: trim,
222
+ strip: 'wood',
223
+ banded: lod === 0,
224
+ });
225
+ const verts = [...top, ...under].flatMap(([, cell]) => cell);
226
+ fascia(b, s, o, trim, verts);
227
+ if (o.caps !== false) extra.push(...caps(s, o, top, trim, lod));
228
+ }
229
+ gableWalls(b, s, o, lod < 2);
230
+ for (const c of o.chimneys ?? []) {
231
+ extra.push(chimney(c, (x, y) => roofHeightAt(o, x, y), o.at, lod));
232
+ }
233
+ return merge([b.build(), ...extra]);
234
+ }
235
+
236
+ /** Add the roof as part `name` (default 'roof') at the context's LOD; returns its heights. */
237
+ export function roof(ctx: BuildContext, o: RoofMeshOptions & { readonly name?: string }): RoofInfo {
238
+ ctx.part(o.name ?? 'roof', roofMesh(o, ctx.lod));
239
+ return roofInfo(o);
240
+ }
241
+
242
+ export function roofInfo(o: RoofOptions): RoofInfo {
243
+ const s = roofShape(o);
244
+ const eave = Math.min(...s.outline.map((q) => envelope(s.under, q)));
245
+ return {
246
+ ridge: ridgeHeight(o),
247
+ eave: o.at[2] + eave,
248
+ heightAt: (x, y) => roofHeightAt(o, x, y),
249
+ };
250
+ }
@@ -0,0 +1,126 @@
1
+ // Palm fronds: pinnate leaves as curved card strips — narrow double-sided leaflets either side of
2
+ // each rachis, folded into a V and drooping, longest mid-frond. Plus coconuts tucked under the
3
+ // crown. The rachis itself is a swept stem (in `branches`); leaflets root on its bark.
4
+
5
+ import { type Mesh, MeshBuilder, merge, type Rng, type Vec3 } from '../core/index.ts';
6
+ import { arcLengths, lathe, transportFrames } from '../primitives/index.ts';
7
+ import type { CrownShade } from './vegetation-leaves.ts';
8
+ import type { LeafParams } from './vegetation-params.ts';
9
+ import type { Stem } from './vegetation-skeleton.ts';
10
+
11
+ type V = [number, number, number];
12
+
13
+ function tri(mb: MeshBuilder, a: Vec3, b: Vec3, c: Vec3, col: Vec3, tipCol: Vec3): void {
14
+ const e1: V = [b[0] - a[0], b[1] - a[1], b[2] - a[2]];
15
+ const e2: V = [c[0] - a[0], c[1] - a[1], c[2] - a[2]];
16
+ const n: V = [
17
+ e1[1] * e2[2] - e1[2] * e2[1],
18
+ e1[2] * e2[0] - e1[0] * e2[2],
19
+ e1[0] * e2[1] - e1[1] * e2[0],
20
+ ];
21
+ const l = Math.hypot(n[0], n[1], n[2]) || 1;
22
+ for (const s of [1, -1]) {
23
+ const nn: Vec3 = [(n[0] / l) * s, (n[1] / l) * s, (n[2] / l) * s];
24
+ const i = mb.vertex({ p: a, n: nn, uv: [0, 0], color: col });
25
+ const j = mb.vertex({ p: b, n: nn, uv: [0, 0], color: col });
26
+ const k = mb.vertex({ p: c, n: nn, uv: [0, 0], color: tipCol });
27
+ if (s > 0) mb.tri(i, j, k);
28
+ else mb.tri(i, k, j);
29
+ }
30
+ }
31
+
32
+ /**
33
+ * Leaflets along every frond (stem at `level`). `perSide` leaflets each side; `from` overrides
34
+ * each frond's first rachis point (LOD2 roots them all on the trunk's top cap centre).
35
+ */
36
+ export function frondLeaflets(
37
+ stems: readonly Stem[],
38
+ level: number,
39
+ leaf: LeafParams,
40
+ perSide: number,
41
+ shade: CrownShade,
42
+ rng: Rng,
43
+ ): Mesh {
44
+ const mb = new MeshBuilder();
45
+ for (const st of stems) {
46
+ if (st.level !== level) continue;
47
+ const arc = arcLengths(st.pts);
48
+ const frames = transportFrames(st.pts);
49
+ const k = 1 + rng.range(-leaf.colorVar, leaf.colorVar);
50
+ const step = (st.length * 0.9) / perSide;
51
+ for (let i = 0; i < perSide; i++) {
52
+ const u = 0.1 + (0.9 * (i + 0.5)) / perSide;
53
+ const s = u * st.length;
54
+ let j = 0;
55
+ while (j < arc.length - 2 && (arc[j + 1] ?? 0) < s) j++;
56
+ const a = st.pts[j] ?? [0, 0, 0];
57
+ const b = st.pts[j + 1] ?? a;
58
+ const f = (s - (arc[j] ?? 0)) / ((arc[j + 1] ?? 1) - (arc[j] ?? 0) || 1);
59
+ const fr = frames[j] ?? { t: [1, 0, 0] as Vec3, n: [0, 0, 1] as Vec3, b: [0, 1, 0] as Vec3 };
60
+ const r = (st.radii[j] ?? 0) * (1 - f) + (st.radii[j + 1] ?? 0) * f;
61
+ const c: Vec3 = [
62
+ a[0] + (b[0] - a[0]) * f,
63
+ a[1] + (b[1] - a[1]) * f,
64
+ a[2] + (b[2] - a[2]) * f,
65
+ ];
66
+ const len = st.length * leaf.width * Math.sin(Math.PI * (0.12 + 0.88 * u)) ** 0.6;
67
+ const ao =
68
+ 0.6 +
69
+ 0.4 *
70
+ Math.min(1, Math.hypot(c[0] - shade.centre[0], c[1] - shade.centre[1]) / shade.radius);
71
+ const col: Vec3 = [
72
+ leaf.color[0] * k * ao * 0.75,
73
+ leaf.color[1] * k * ao * 0.75,
74
+ leaf.color[2] * k * ao * 0.75,
75
+ ];
76
+ const tipCol: Vec3 = [col[0] * 1.45, col[1] * 1.35, col[2] * 1.2];
77
+ for (const side of [1, -1]) {
78
+ const o: Vec3 = [fr.b[0] * side, fr.b[1] * side, fr.b[2] * side];
79
+ const root = (d: number): Vec3 => [
80
+ c[0] + o[0] * r + fr.t[0] * d,
81
+ c[1] + o[1] * r + fr.t[1] * d,
82
+ c[2] + o[2] * r + fr.t[2] * d,
83
+ ];
84
+ const tip: Vec3 = [
85
+ c[0] + (o[0] * 0.8 + fr.t[0] * 0.45 - fr.n[0] * 0.35) * len,
86
+ c[1] + (o[1] * 0.8 + fr.t[1] * 0.45 - fr.n[1] * 0.35) * len,
87
+ c[2] + (o[2] * 0.8 + fr.t[2] * 0.45 - fr.n[2] * 0.35) * len - 0.2 * len,
88
+ ];
89
+ tri(mb, root(-step * 0.35), root(step * 0.35), tip, col, tipCol);
90
+ }
91
+ }
92
+ }
93
+ return mb.build();
94
+ }
95
+
96
+ /** Coconuts: `count` spheres hugging the trunk just below the frond bases. */
97
+ export function fruit(
98
+ trunk: Stem,
99
+ at: number,
100
+ o: { count: number; radius: number; color: Vec3 },
101
+ rng: Rng,
102
+ ): Mesh {
103
+ const j = Math.max(0, Math.min(trunk.pts.length - 1, Math.round(at * (trunk.pts.length - 1))));
104
+ const c = trunk.pts[j] ?? [0, 0, 0];
105
+ const rt = trunk.radii[j] ?? 0.2;
106
+ const profile = [0, 1, 2, 3, 4].map((i): [number, number] => {
107
+ const a = -Math.PI / 2 + (i / 4) * Math.PI;
108
+ return [Math.max(0.0001, Math.cos(a)) * o.radius, Math.sin(a) * o.radius];
109
+ });
110
+ const out: Mesh[] = [];
111
+ for (let i = 0; i < o.count; i++) {
112
+ const a = (i / o.count) * Math.PI * 2 + rng.range(-0.3, 0.3);
113
+ const d = rt + o.radius * 0.6;
114
+ const dz = rng.range(-1, 0.5) * o.radius;
115
+ out.push(
116
+ lathe({
117
+ profile,
118
+ segments: 6,
119
+ caps: false,
120
+ at: [c[0] + Math.cos(a) * d, c[1] + Math.sin(a) * d, c[2] + dz],
121
+ color: o.color,
122
+ }),
123
+ );
124
+ }
125
+ return merge(out);
126
+ }