@developerz.ai/ts23d 0.0.0-stage → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (567) hide show
  1. package/.claude/agents/bug-hunter.md +21 -0
  2. package/.claude/agents/docs-author.md +13 -0
  3. package/.claude/agents/dx-auditor.md +14 -0
  4. package/.claude/agents/primitive-author.md +22 -0
  5. package/.claude/agents/recipe-author.md +36 -0
  6. package/.claude/agents/visual-reviewer.md +23 -0
  7. package/.claude/commands/feature.md +28 -0
  8. package/.claude/commands/new-recipe.md +19 -0
  9. package/.claude/commands/release.md +14 -0
  10. package/.claude/commands/verify.md +9 -0
  11. package/.claude/skills/author-recipe/SKILL.md +93 -0
  12. package/CHANGELOG.md +90 -0
  13. package/CLAUDE.md +85 -0
  14. package/LICENSE +21 -0
  15. package/README.md +385 -2
  16. package/dist/chunk-6b05mnmx.js +19 -0
  17. package/dist/chunk-6b05mnmx.js.map +11 -0
  18. package/dist/chunk-7rtfjh9f.js +45 -0
  19. package/dist/chunk-7rtfjh9f.js.map +10 -0
  20. package/dist/chunk-8j4c28jt.js +4085 -0
  21. package/dist/chunk-8j4c28jt.js.map +46 -0
  22. package/dist/chunk-d1fv6hrm.js +2532 -0
  23. package/dist/chunk-d1fv6hrm.js.map +43 -0
  24. package/dist/chunk-dbq4qf52.js +902 -0
  25. package/dist/chunk-dbq4qf52.js.map +25 -0
  26. package/dist/chunk-drna2rny.js +836 -0
  27. package/dist/chunk-drna2rny.js.map +17 -0
  28. package/dist/chunk-dxvypr7y.js +89 -0
  29. package/dist/chunk-dxvypr7y.js.map +11 -0
  30. package/dist/chunk-f6yme003.js +1589 -0
  31. package/dist/chunk-f6yme003.js.map +30 -0
  32. package/dist/chunk-hg7pf34j.js +465 -0
  33. package/dist/chunk-hg7pf34j.js.map +19 -0
  34. package/dist/chunk-jq4e17tj.js +1031 -0
  35. package/dist/chunk-jq4e17tj.js.map +21 -0
  36. package/dist/chunk-y63c6zcv.js +2668 -0
  37. package/dist/chunk-y63c6zcv.js.map +53 -0
  38. package/dist/cli/args.d.ts +30 -0
  39. package/dist/cli/bin.d.ts +2 -0
  40. package/dist/cli/bin.js +348 -0
  41. package/dist/cli/bin.js.map +15 -0
  42. package/dist/cli/load.d.ts +2 -0
  43. package/dist/cli/manifest.d.ts +53 -0
  44. package/dist/cli/render-cmds.d.ts +12 -0
  45. package/dist/cli/run.d.ts +1 -0
  46. package/dist/cli/texture-cache.d.ts +4 -0
  47. package/dist/core/asset.d.ts +10 -0
  48. package/dist/core/definition.d.ts +155 -0
  49. package/dist/core/errors.d.ts +17 -0
  50. package/dist/core/family.d.ts +23 -0
  51. package/dist/core/index.d.ts +9 -0
  52. package/dist/core/index.js +69 -0
  53. package/dist/core/index.js.map +9 -0
  54. package/dist/core/mesh.d.ts +35 -0
  55. package/dist/core/part.d.ts +114 -0
  56. package/dist/core/png.d.ts +1 -0
  57. package/dist/core/rng.d.ts +12 -0
  58. package/dist/core/transform.d.ts +20 -0
  59. package/dist/core/vec.d.ts +11 -0
  60. package/dist/core/world.d.ts +16 -0
  61. package/dist/export/bin-writer.d.ts +16 -0
  62. package/dist/export/container.d.ts +3 -0
  63. package/dist/export/credits.d.ts +4 -0
  64. package/dist/export/glb-options.d.ts +48 -0
  65. package/dist/export/glb.d.ts +8 -0
  66. package/dist/export/hierarchy.d.ts +23 -0
  67. package/dist/export/index.d.ts +5 -0
  68. package/dist/export/index.js +40 -0
  69. package/dist/export/index.js.map +9 -0
  70. package/dist/export/lights.d.ts +8 -0
  71. package/dist/export/lod-coverage.d.ts +12 -0
  72. package/dist/export/materials.d.ts +23 -0
  73. package/dist/export/merge-pivots.d.ts +6 -0
  74. package/dist/export/metadata.d.ts +21 -0
  75. package/dist/export/motion.d.ts +9 -0
  76. package/dist/export/optimize-parts.d.ts +26 -0
  77. package/dist/export/primitive.d.ts +3 -0
  78. package/dist/export/tangents.d.ts +2 -0
  79. package/dist/export/textures.d.ts +22 -0
  80. package/dist/export/version.d.ts +2 -0
  81. package/dist/grammar/index.d.ts +2 -0
  82. package/dist/grammar/index.js +16 -0
  83. package/dist/grammar/index.js.map +9 -0
  84. package/dist/grammar/split.d.ts +21 -0
  85. package/dist/grammar/whyte.d.ts +5 -0
  86. package/dist/index.d.ts +11 -0
  87. package/dist/index.js +457 -0
  88. package/dist/index.js.map +9 -0
  89. package/dist/kinds/building.d.ts +44 -0
  90. package/dist/kinds/faces.d.ts +27 -0
  91. package/dist/kinds/head-armour.d.ts +5 -0
  92. package/dist/kinds/head-beard.d.ts +6 -0
  93. package/dist/kinds/head-bust.d.ts +5 -0
  94. package/dist/kinds/head-extras.d.ts +15 -0
  95. package/dist/kinds/head-face.d.ts +8 -0
  96. package/dist/kinds/head-garb.d.ts +4 -0
  97. package/dist/kinds/head-geom.d.ts +7 -0
  98. package/dist/kinds/head-hair.d.ts +4 -0
  99. package/dist/kinds/head-layout.d.ts +29 -0
  100. package/dist/kinds/head-mesh.d.ts +31 -0
  101. package/dist/kinds/head-mouth.d.ts +4 -0
  102. package/dist/kinds/head-params.d.ts +116 -0
  103. package/dist/kinds/head-presets.d.ts +19 -0
  104. package/dist/kinds/head-rim.d.ts +10 -0
  105. package/dist/kinds/head-tone.d.ts +10 -0
  106. package/dist/kinds/head-torso.d.ts +35 -0
  107. package/dist/kinds/head.d.ts +39 -0
  108. package/dist/kinds/hull-lines.d.ts +65 -0
  109. package/dist/kinds/hull-skin.d.ts +6 -0
  110. package/dist/kinds/hull.d.ts +14 -0
  111. package/dist/kinds/index.d.ts +16 -0
  112. package/dist/kinds/index.js +78 -0
  113. package/dist/kinds/index.js.map +9 -0
  114. package/dist/kinds/mass.d.ts +15 -0
  115. package/dist/kinds/openings.d.ts +30 -0
  116. package/dist/kinds/planned.d.ts +6 -0
  117. package/dist/kinds/roof-chimney.d.ts +16 -0
  118. package/dist/kinds/roof-shape.d.ts +64 -0
  119. package/dist/kinds/roof.d.ts +31 -0
  120. package/dist/kinds/vegetation-fronds.d.ts +15 -0
  121. package/dist/kinds/vegetation-impostor.d.ts +9 -0
  122. package/dist/kinds/vegetation-leaves.d.ts +15 -0
  123. package/dist/kinds/vegetation-mesh.d.ts +21 -0
  124. package/dist/kinds/vegetation-params.d.ts +90 -0
  125. package/dist/kinds/vegetation-presets.d.ts +20 -0
  126. package/dist/kinds/vegetation-skeleton.d.ts +20 -0
  127. package/dist/kinds/vegetation.d.ts +42 -0
  128. package/dist/kinds/vehicle.d.ts +42 -0
  129. package/dist/material/cc0.d.ts +4 -0
  130. package/dist/material/define.d.ts +34 -0
  131. package/dist/material/generator.d.ts +4 -0
  132. package/dist/material/index.d.ts +9 -0
  133. package/dist/material/index.js +70 -0
  134. package/dist/material/index.js.map +9 -0
  135. package/dist/material/library.d.ts +7 -0
  136. package/dist/material/material-def.d.ts +30 -0
  137. package/dist/material/paint/bond.d.ts +3 -0
  138. package/dist/material/paint/canvas.d.ts +39 -0
  139. package/dist/material/paint/cell.d.ts +4 -0
  140. package/dist/material/paint/cells-round.d.ts +5 -0
  141. package/dist/material/paint/cells.d.ts +7 -0
  142. package/dist/material/paint/compose.d.ts +5 -0
  143. package/dist/material/paint/concrete.d.ts +3 -0
  144. package/dist/material/paint/courses.d.ts +31 -0
  145. package/dist/material/paint/decode-png.d.ts +6 -0
  146. package/dist/material/paint/default-sheet.d.ts +4 -0
  147. package/dist/material/paint/fabric.d.ts +4 -0
  148. package/dist/material/paint/foliage-broad.d.ts +5 -0
  149. package/dist/material/paint/foliage-common.d.ts +35 -0
  150. package/dist/material/paint/foliage-compound.d.ts +4 -0
  151. package/dist/material/paint/foliage-conifer.d.ts +5 -0
  152. package/dist/material/paint/foliage-raster.d.ts +26 -0
  153. package/dist/material/paint/foliage.d.ts +3 -0
  154. package/dist/material/paint/image-strip.d.ts +4 -0
  155. package/dist/material/paint/kit.d.ts +5 -0
  156. package/dist/material/paint/masks.d.ts +3 -0
  157. package/dist/material/paint/metal.d.ts +3 -0
  158. package/dist/material/paint/noise.d.ts +9 -0
  159. package/dist/material/paint/normal.d.ts +8 -0
  160. package/dist/material/paint/orm.d.ts +8 -0
  161. package/dist/material/paint/planks.d.ts +3 -0
  162. package/dist/material/paint/plates.d.ts +3 -0
  163. package/dist/material/paint/shapes.d.ts +8 -0
  164. package/dist/material/paint/slate.d.ts +3 -0
  165. package/dist/material/paint/stamp.d.ts +3 -0
  166. package/dist/material/paint/strip.d.ts +3 -0
  167. package/dist/material/paint/surface.d.ts +5 -0
  168. package/dist/material/paint/types.d.ts +65 -0
  169. package/dist/material/paint/validate.d.ts +3 -0
  170. package/dist/material/palette.d.ts +14 -0
  171. package/dist/material/shader-godot.d.ts +2 -0
  172. package/dist/material/shader-three.d.ts +2 -0
  173. package/dist/material/shader.d.ts +46 -0
  174. package/dist/material/slots.d.ts +5 -0
  175. package/dist/material/texture-source.d.ts +34 -0
  176. package/dist/material/trim-sheet.d.ts +38 -0
  177. package/dist/neural/generator.d.ts +25 -0
  178. package/dist/neural/images.d.ts +17 -0
  179. package/dist/neural/index.d.ts +2 -0
  180. package/dist/neural/index.js +14 -0
  181. package/dist/neural/index.js.map +9 -0
  182. package/dist/passes/bake-ao.d.ts +17 -0
  183. package/dist/passes/grime.d.ts +15 -0
  184. package/dist/passes/index.d.ts +6 -0
  185. package/dist/passes/index.js +22 -0
  186. package/dist/passes/index.js.map +9 -0
  187. package/dist/passes/livery.d.ts +19 -0
  188. package/dist/passes/optimize-cache.d.ts +15 -0
  189. package/dist/passes/optimize-weld.d.ts +15 -0
  190. package/dist/passes/optimize.d.ts +28 -0
  191. package/dist/passes/ray-grid.d.ts +23 -0
  192. package/dist/passes/weighted-normals.d.ts +8 -0
  193. package/dist/primitives/beam.d.ts +17 -0
  194. package/dist/primitives/bevel-box.d.ts +13 -0
  195. package/dist/primitives/box.d.ts +16 -0
  196. package/dist/primitives/cell.d.ts +20 -0
  197. package/dist/primitives/check.d.ts +12 -0
  198. package/dist/primitives/closed.d.ts +11 -0
  199. package/dist/primitives/erosion.d.ts +22 -0
  200. package/dist/primitives/font-5x7.d.ts +3 -0
  201. package/dist/primitives/heightfield.d.ts +16 -0
  202. package/dist/primitives/index.d.ts +23 -0
  203. package/dist/primitives/index.js +81 -0
  204. package/dist/primitives/index.js.map +9 -0
  205. package/dist/primitives/lathe-facets.d.ts +8 -0
  206. package/dist/primitives/lathe.d.ts +26 -0
  207. package/dist/primitives/lattice.d.ts +30 -0
  208. package/dist/primitives/loft.d.ts +15 -0
  209. package/dist/primitives/mc-table.d.ts +2 -0
  210. package/dist/primitives/member.d.ts +19 -0
  211. package/dist/primitives/noise.d.ts +34 -0
  212. package/dist/primitives/planar.d.ts +10 -0
  213. package/dist/primitives/prism.d.ts +13 -0
  214. package/dist/primitives/sculpt-edit.d.ts +56 -0
  215. package/dist/primitives/sculpt-tree.d.ts +23 -0
  216. package/dist/primitives/sculpt.d.ts +27 -0
  217. package/dist/primitives/sdf-grid.d.ts +17 -0
  218. package/dist/primitives/sdf-ops.d.ts +37 -0
  219. package/dist/primitives/sdf.d.ts +21 -0
  220. package/dist/primitives/strip-uv.d.ts +11 -0
  221. package/dist/primitives/surface-nets.d.ts +7 -0
  222. package/dist/primitives/sweep.d.ts +19 -0
  223. package/dist/primitives/terrain-color.d.ts +30 -0
  224. package/dist/primitives/terrain-grid.d.ts +36 -0
  225. package/dist/primitives/text-layout.d.ts +22 -0
  226. package/dist/primitives/text.d.ts +27 -0
  227. package/dist/primitives/tiled.d.ts +22 -0
  228. package/dist/primitives/transport.d.ts +17 -0
  229. package/dist/primitives/triangulate.d.ts +2 -0
  230. package/dist/primitives/value-noise.d.ts +10 -0
  231. package/dist/qa/checks.d.ts +11 -0
  232. package/dist/qa/clash.d.ts +2 -0
  233. package/dist/qa/floating.d.ts +5 -0
  234. package/dist/qa/gates.d.ts +12 -0
  235. package/dist/qa/geometry.d.ts +21 -0
  236. package/dist/qa/index.d.ts +12 -0
  237. package/dist/qa/index.js +78 -0
  238. package/dist/qa/index.js.map +9 -0
  239. package/dist/qa/inside.d.ts +12 -0
  240. package/dist/qa/inspect.d.ts +18 -0
  241. package/dist/qa/lods.d.ts +12 -0
  242. package/dist/qa/measures.d.ts +4 -0
  243. package/dist/qa/occlusion.d.ts +3 -0
  244. package/dist/qa/sanity.d.ts +7 -0
  245. package/dist/qa/types.d.ts +12 -0
  246. package/dist/render/beauty.d.ts +40 -0
  247. package/dist/render/camera.d.ts +33 -0
  248. package/dist/render/depth-map.d.ts +38 -0
  249. package/dist/render/font.d.ts +1 -0
  250. package/dist/render/ground.d.ts +13 -0
  251. package/dist/render/index.d.ts +10 -0
  252. package/dist/render/index.js +47 -0
  253. package/dist/render/index.js.map +9 -0
  254. package/dist/render/lighting.d.ts +27 -0
  255. package/dist/render/lod-strip.d.ts +5 -0
  256. package/dist/render/metrics.d.ts +37 -0
  257. package/dist/render/options.d.ts +28 -0
  258. package/dist/render/photo-match.d.ts +19 -0
  259. package/dist/render/raster.d.ts +25 -0
  260. package/dist/render/resolve.d.ts +11 -0
  261. package/dist/render/review-prompt.d.ts +2 -0
  262. package/dist/render/review-warnings.d.ts +34 -0
  263. package/dist/render/review.d.ts +24 -0
  264. package/dist/render/scene.d.ts +18 -0
  265. package/dist/render/shade.d.ts +22 -0
  266. package/dist/render/sheet.d.ts +15 -0
  267. package/dist/render/texture.d.ts +15 -0
  268. package/dist/render/thumbnail.d.ts +8 -0
  269. package/dist/render/vis-buffer.d.ts +18 -0
  270. package/dist/testing/assert.d.ts +21 -0
  271. package/dist/testing/compiled.d.ts +15 -0
  272. package/dist/testing/hash.d.ts +1 -0
  273. package/dist/testing/index.d.ts +4 -0
  274. package/dist/testing/index.js +134 -0
  275. package/dist/testing/index.js.map +13 -0
  276. package/dist/testing/snapshot.d.ts +13 -0
  277. package/docs/README.md +56 -0
  278. package/docs/architecture/README.md +68 -0
  279. package/docs/architecture/adding-a-kind.md +44 -0
  280. package/docs/architecture/adding-a-primitive.md +49 -0
  281. package/docs/architecture/errors.md +47 -0
  282. package/docs/architecture/glb-production.md +214 -0
  283. package/docs/architecture/gltf-capabilities.md +117 -0
  284. package/docs/architecture/materials.md +151 -0
  285. package/docs/decisions/001-recipe-compiler-over-ai-meshes.md +27 -0
  286. package/docs/decisions/002-typescript-not-json.md +22 -0
  287. package/docs/decisions/003-shared-trim-sheet.md +21 -0
  288. package/docs/decisions/004-z-up-metres.md +21 -0
  289. package/docs/decisions/005-zero-runtime-deps.md +21 -0
  290. package/docs/decisions/006-single-package-subpath-exports.md +20 -0
  291. package/docs/decisions/007-cpu-contact-sheets.md +21 -0
  292. package/docs/decisions/008-surface-materials.md +21 -0
  293. package/docs/decisions/009-gltf-front-axis.md +21 -0
  294. package/docs/decisions/README.md +15 -0
  295. package/docs/idea/01-vision.md +117 -0
  296. package/docs/idea/02-dsl.md +343 -0
  297. package/docs/idea/03-ir.md +97 -0
  298. package/docs/idea/04-materials.md +114 -0
  299. package/docs/idea/05-agent-loop.md +141 -0
  300. package/docs/idea/06-qa.md +101 -0
  301. package/docs/idea/07-what-fits.md +45 -0
  302. package/docs/idea/08-neural-adapters.md +78 -0
  303. package/docs/idea/09-kinds.md +105 -0
  304. package/docs/idea/10-sculpting.md +166 -0
  305. package/docs/idea/11-materials-authoring.md +132 -0
  306. package/docs/idea/12-blender-bridge.md +136 -0
  307. package/docs/idea/13-asset-pipeline-research.md +89 -0
  308. package/docs/landscape.md +49 -0
  309. package/docs/plans/roadmap.md +139 -0
  310. package/examples/build-with-bun.ts +41 -0
  311. package/llms.txt +47 -0
  312. package/package.json +100 -4
  313. package/recipes/README.md +38 -0
  314. package/recipes/asteroid/README.md +46 -0
  315. package/recipes/asteroid/asteroid.ts +336 -0
  316. package/recipes/boulders/README.md +28 -0
  317. package/recipes/boulders/boulders.ts +117 -0
  318. package/recipes/boxcar/README.md +53 -0
  319. package/recipes/boxcar/boxcar.ts +1000 -0
  320. package/recipes/cargo_container_scifi/README.md +52 -0
  321. package/recipes/cargo_container_scifi/cargo_container_scifi.ts +461 -0
  322. package/recipes/dropship/README.md +84 -0
  323. package/recipes/dropship/dropship.ts +1144 -0
  324. package/recipes/eiffel_tower/README.md +110 -0
  325. package/recipes/eiffel_tower/eiffel_tower.ts +781 -0
  326. package/recipes/goblin_head/README.md +75 -0
  327. package/recipes/goblin_head/goblin_head.ts +38 -0
  328. package/recipes/human_head/README.md +68 -0
  329. package/recipes/human_head/human_head.ts +38 -0
  330. package/recipes/loco_440/README.md +32 -0
  331. package/recipes/loco_440/loco_440.ts +1404 -0
  332. package/recipes/material_swatches/README.md +21 -0
  333. package/recipes/material_swatches/material_swatches.ts +189 -0
  334. package/recipes/mountain_range/README.md +45 -0
  335. package/recipes/mountain_range/mountain_range.ts +128 -0
  336. package/recipes/oak/README.md +30 -0
  337. package/recipes/oak/oak.ts +19 -0
  338. package/recipes/orc_head/README.md +78 -0
  339. package/recipes/orc_head/orc_head.ts +41 -0
  340. package/recipes/palm/README.md +30 -0
  341. package/recipes/palm/palm.ts +19 -0
  342. package/recipes/pine/README.md +29 -0
  343. package/recipes/pine/pine.ts +19 -0
  344. package/recipes/signal_gantry/README.md +36 -0
  345. package/recipes/signal_gantry/signal_gantry.ts +522 -0
  346. package/recipes/station_halt/README.md +39 -0
  347. package/recipes/station_halt/station_halt.ts +722 -0
  348. package/recipes/t34/README.md +44 -0
  349. package/recipes/t34/t34.ts +1068 -0
  350. package/recipes/tiger_1/README.md +75 -0
  351. package/recipes/tiger_1/tiger_1.ts +1370 -0
  352. package/recipes/water_tower/README.md +40 -0
  353. package/recipes/water_tower/water_tower.ts +542 -0
  354. package/src/cli/args.ts +125 -0
  355. package/src/cli/bin.ts +7 -0
  356. package/src/cli/load.ts +17 -0
  357. package/src/cli/manifest.ts +95 -0
  358. package/src/cli/render-cmds.ts +66 -0
  359. package/src/cli/run.ts +192 -0
  360. package/src/cli/texture-cache.ts +24 -0
  361. package/src/core/asset.ts +129 -0
  362. package/src/core/definition.ts +156 -0
  363. package/src/core/errors.ts +50 -0
  364. package/src/core/family.ts +50 -0
  365. package/src/core/index.ts +66 -0
  366. package/src/core/mesh.ts +114 -0
  367. package/src/core/part.ts +130 -0
  368. package/src/core/png.ts +49 -0
  369. package/src/core/rng.ts +49 -0
  370. package/src/core/transform.ts +90 -0
  371. package/src/core/vec.ts +26 -0
  372. package/src/core/world.ts +80 -0
  373. package/src/export/bin-writer.ts +79 -0
  374. package/src/export/container.ts +35 -0
  375. package/src/export/credits.ts +25 -0
  376. package/src/export/glb-options.ts +53 -0
  377. package/src/export/glb.ts +187 -0
  378. package/src/export/hierarchy.ts +122 -0
  379. package/src/export/index.ts +34 -0
  380. package/src/export/lights.ts +49 -0
  381. package/src/export/lod-coverage.ts +71 -0
  382. package/src/export/materials.ts +126 -0
  383. package/src/export/merge-pivots.ts +146 -0
  384. package/src/export/metadata.ts +62 -0
  385. package/src/export/motion.ts +70 -0
  386. package/src/export/optimize-parts.ts +80 -0
  387. package/src/export/primitive.ts +35 -0
  388. package/src/export/tangents.ts +75 -0
  389. package/src/export/textures.ts +88 -0
  390. package/src/export/version.ts +6 -0
  391. package/src/grammar/index.ts +4 -0
  392. package/src/grammar/split.ts +87 -0
  393. package/src/grammar/whyte.ts +20 -0
  394. package/src/index.ts +14 -0
  395. package/src/kinds/building.ts +153 -0
  396. package/src/kinds/faces.ts +138 -0
  397. package/src/kinds/head-armour.ts +181 -0
  398. package/src/kinds/head-beard.ts +133 -0
  399. package/src/kinds/head-bust.ts +184 -0
  400. package/src/kinds/head-extras.ts +150 -0
  401. package/src/kinds/head-face.ts +242 -0
  402. package/src/kinds/head-garb.ts +181 -0
  403. package/src/kinds/head-geom.ts +31 -0
  404. package/src/kinds/head-hair.ts +193 -0
  405. package/src/kinds/head-layout.ts +63 -0
  406. package/src/kinds/head-mesh.ts +195 -0
  407. package/src/kinds/head-mouth.ts +111 -0
  408. package/src/kinds/head-params.ts +162 -0
  409. package/src/kinds/head-presets.ts +155 -0
  410. package/src/kinds/head-rim.ts +128 -0
  411. package/src/kinds/head-tone.ts +52 -0
  412. package/src/kinds/head-torso.ts +230 -0
  413. package/src/kinds/head.ts +176 -0
  414. package/src/kinds/hull-lines.ts +160 -0
  415. package/src/kinds/hull-skin.ts +83 -0
  416. package/src/kinds/hull.ts +209 -0
  417. package/src/kinds/index.ts +63 -0
  418. package/src/kinds/mass.ts +110 -0
  419. package/src/kinds/openings.ts +141 -0
  420. package/src/kinds/planned.ts +12 -0
  421. package/src/kinds/roof-chimney.ts +100 -0
  422. package/src/kinds/roof-shape.ts +230 -0
  423. package/src/kinds/roof.ts +250 -0
  424. package/src/kinds/vegetation-fronds.ts +126 -0
  425. package/src/kinds/vegetation-impostor.ts +118 -0
  426. package/src/kinds/vegetation-leaves.ts +168 -0
  427. package/src/kinds/vegetation-mesh.ts +174 -0
  428. package/src/kinds/vegetation-params.ts +147 -0
  429. package/src/kinds/vegetation-presets.ts +184 -0
  430. package/src/kinds/vegetation-skeleton.ts +168 -0
  431. package/src/kinds/vegetation.ts +199 -0
  432. package/src/kinds/vehicle.ts +144 -0
  433. package/src/material/cc0.ts +41 -0
  434. package/src/material/define.ts +130 -0
  435. package/src/material/generator.ts +63 -0
  436. package/src/material/index.ts +60 -0
  437. package/src/material/library.ts +196 -0
  438. package/src/material/material-def.ts +84 -0
  439. package/src/material/paint/bond.ts +60 -0
  440. package/src/material/paint/canvas.ts +101 -0
  441. package/src/material/paint/cell.ts +39 -0
  442. package/src/material/paint/cells-round.ts +119 -0
  443. package/src/material/paint/cells.ts +111 -0
  444. package/src/material/paint/compose.ts +53 -0
  445. package/src/material/paint/concrete.ts +62 -0
  446. package/src/material/paint/courses.ts +85 -0
  447. package/src/material/paint/decode-png.ts +106 -0
  448. package/src/material/paint/default-sheet.ts +73 -0
  449. package/src/material/paint/fabric.ts +63 -0
  450. package/src/material/paint/foliage-broad.ts +126 -0
  451. package/src/material/paint/foliage-common.ts +93 -0
  452. package/src/material/paint/foliage-compound.ts +135 -0
  453. package/src/material/paint/foliage-conifer.ts +143 -0
  454. package/src/material/paint/foliage-raster.ts +196 -0
  455. package/src/material/paint/foliage.ts +23 -0
  456. package/src/material/paint/image-strip.ts +88 -0
  457. package/src/material/paint/kit.ts +37 -0
  458. package/src/material/paint/masks.ts +46 -0
  459. package/src/material/paint/metal.ts +109 -0
  460. package/src/material/paint/noise.ts +52 -0
  461. package/src/material/paint/normal.ts +52 -0
  462. package/src/material/paint/orm.ts +108 -0
  463. package/src/material/paint/planks.ts +101 -0
  464. package/src/material/paint/plates.ts +72 -0
  465. package/src/material/paint/shapes.ts +28 -0
  466. package/src/material/paint/slate.ts +52 -0
  467. package/src/material/paint/stamp.ts +40 -0
  468. package/src/material/paint/strip.ts +48 -0
  469. package/src/material/paint/surface.ts +60 -0
  470. package/src/material/paint/types.ts +94 -0
  471. package/src/material/paint/validate.ts +74 -0
  472. package/src/material/palette.ts +46 -0
  473. package/src/material/shader-godot.ts +77 -0
  474. package/src/material/shader-three.ts +103 -0
  475. package/src/material/shader.ts +100 -0
  476. package/src/material/slots.ts +18 -0
  477. package/src/material/texture-source.ts +86 -0
  478. package/src/material/trim-sheet.ts +166 -0
  479. package/src/neural/generator.ts +37 -0
  480. package/src/neural/images.ts +27 -0
  481. package/src/neural/index.ts +10 -0
  482. package/src/passes/bake-ao.ts +183 -0
  483. package/src/passes/grime.ts +37 -0
  484. package/src/passes/index.ts +8 -0
  485. package/src/passes/livery.ts +66 -0
  486. package/src/passes/optimize-cache.ts +175 -0
  487. package/src/passes/optimize-weld.ts +147 -0
  488. package/src/passes/optimize.ts +85 -0
  489. package/src/passes/ray-grid.ts +237 -0
  490. package/src/passes/weighted-normals.ts +125 -0
  491. package/src/primitives/beam.ts +60 -0
  492. package/src/primitives/bevel-box.ts +140 -0
  493. package/src/primitives/box.ts +90 -0
  494. package/src/primitives/cell.ts +115 -0
  495. package/src/primitives/check.ts +49 -0
  496. package/src/primitives/closed.ts +74 -0
  497. package/src/primitives/erosion.ts +183 -0
  498. package/src/primitives/font-5x7.ts +117 -0
  499. package/src/primitives/heightfield.ts +101 -0
  500. package/src/primitives/index.ts +56 -0
  501. package/src/primitives/lathe-facets.ts +45 -0
  502. package/src/primitives/lathe.ts +174 -0
  503. package/src/primitives/lattice.ts +131 -0
  504. package/src/primitives/loft.ts +174 -0
  505. package/src/primitives/mc-table.ts +41 -0
  506. package/src/primitives/member.ts +130 -0
  507. package/src/primitives/noise.ts +157 -0
  508. package/src/primitives/planar.ts +55 -0
  509. package/src/primitives/prism.ts +85 -0
  510. package/src/primitives/sculpt-edit.ts +212 -0
  511. package/src/primitives/sculpt-tree.ts +226 -0
  512. package/src/primitives/sculpt.ts +80 -0
  513. package/src/primitives/sdf-grid.ts +131 -0
  514. package/src/primitives/sdf-ops.ts +145 -0
  515. package/src/primitives/sdf.ts +83 -0
  516. package/src/primitives/strip-uv.ts +26 -0
  517. package/src/primitives/surface-nets.ts +161 -0
  518. package/src/primitives/sweep.ts +162 -0
  519. package/src/primitives/terrain-color.ts +85 -0
  520. package/src/primitives/terrain-grid.ts +137 -0
  521. package/src/primitives/text-layout.ts +85 -0
  522. package/src/primitives/text.ts +197 -0
  523. package/src/primitives/tiled.ts +93 -0
  524. package/src/primitives/transport.ts +85 -0
  525. package/src/primitives/triangulate.ts +42 -0
  526. package/src/primitives/value-noise.ts +62 -0
  527. package/src/qa/checks.ts +110 -0
  528. package/src/qa/clash.ts +177 -0
  529. package/src/qa/floating.ts +63 -0
  530. package/src/qa/gates.ts +126 -0
  531. package/src/qa/geometry.ts +187 -0
  532. package/src/qa/index.ts +28 -0
  533. package/src/qa/inside.ts +114 -0
  534. package/src/qa/inspect.ts +107 -0
  535. package/src/qa/lods.ts +97 -0
  536. package/src/qa/measures.ts +67 -0
  537. package/src/qa/occlusion.ts +166 -0
  538. package/src/qa/sanity.ts +137 -0
  539. package/src/qa/types.ts +17 -0
  540. package/src/render/beauty.ts +115 -0
  541. package/src/render/camera.ts +142 -0
  542. package/src/render/depth-map.ts +191 -0
  543. package/src/render/font.ts +76 -0
  544. package/src/render/ground.ts +87 -0
  545. package/src/render/index.ts +34 -0
  546. package/src/render/lighting.ts +121 -0
  547. package/src/render/lod-strip.ts +34 -0
  548. package/src/render/metrics.ts +147 -0
  549. package/src/render/options.ts +81 -0
  550. package/src/render/photo-match.ts +28 -0
  551. package/src/render/raster.ts +143 -0
  552. package/src/render/resolve.ts +109 -0
  553. package/src/render/review-prompt.ts +59 -0
  554. package/src/render/review-warnings.ts +92 -0
  555. package/src/render/review.ts +103 -0
  556. package/src/render/scene.ts +119 -0
  557. package/src/render/shade.ts +204 -0
  558. package/src/render/sheet.ts +88 -0
  559. package/src/render/texture.ts +97 -0
  560. package/src/render/thumbnail.ts +75 -0
  561. package/src/render/vis-buffer.ts +119 -0
  562. package/src/testing/assert.ts +115 -0
  563. package/src/testing/compiled.ts +31 -0
  564. package/src/testing/hash.ts +14 -0
  565. package/src/testing/index.ts +14 -0
  566. package/src/testing/snapshot.ts +37 -0
  567. package/src/types/gltf-validator.d.ts +13 -0
@@ -0,0 +1,141 @@
1
+ // Windows and doors as one mesh each: a reveal (surround in the wall's colour, so the glass reads
2
+ // set back by `depth`), stone sill and lintel, sash frame with glazing bars, or a panelled door
3
+ // leaf. The wall mass behind stays whole (no boolean cut): half the triangles, no T-junctions.
4
+ // Authored with the face at x = 0 looking +X, then turned to the requested side.
5
+ // LOD0: everything · LOD1: no glazing bars / door panels · LOD2: glass or leaf only.
6
+
7
+ import { type Lod, type Mesh, merge, place, Ts23dError, type Vec3 } from '../core/index.ts';
8
+ import type { StripId } from '../material/index.ts';
9
+ import { box } from '../primitives/index.ts';
10
+ import { mass } from './mass.ts';
11
+
12
+ export type OpeningSide = 'front' | 'back' | 'left' | 'right';
13
+
14
+ export interface OpeningOptions {
15
+ readonly kind: 'window' | 'door';
16
+ /** Clear opening, metres (UK sash ≈ 0.9 × 1.6; door ≈ 0.9 × 2.1). */
17
+ readonly width: number;
18
+ readonly height: number;
19
+ /** Bottom centre of the opening ON the wall face. */
20
+ readonly at: Vec3;
21
+ /** Which way the wall faces (front = +X, left = +Y). */
22
+ readonly facing: OpeningSide;
23
+ readonly glass: Vec3;
24
+ /** Sash frame / door leaf colour. */
25
+ readonly frame: Vec3;
26
+ /** Reveal (surround) colour: the wall's, so the opening reads recessed. */
27
+ readonly wall: Vec3;
28
+ readonly wallStrip?: StripId;
29
+ /** Sill + lintel colour (dressed stone). */
30
+ readonly trim?: Vec3;
31
+ /** Reveal depth, metres. Default 0.12 (half a brick). */
32
+ readonly depth?: number;
33
+ /** Reveal band width either side. Default 0.1. */
34
+ readonly surround?: number;
35
+ /** Glazing panes [across, up]: 2-over-2 sash = [2, 2]. Default window [2, 2]. */
36
+ readonly panes?: readonly [number, number];
37
+ readonly sill?: boolean;
38
+ readonly lintel?: boolean;
39
+ }
40
+
41
+ const YAW: Record<OpeningSide, number> = {
42
+ front: 0,
43
+ left: Math.PI / 2,
44
+ back: Math.PI,
45
+ right: -Math.PI / 2,
46
+ };
47
+
48
+ /** Base-centred box flush on the wall face (x = 0), back face omitted (it is against the wall). */
49
+ const onWall = (size: Vec3, y: number, z: number, color: Vec3, strip?: StripId): Mesh =>
50
+ box({ size, at: [size[0] / 2, y, z], color, omit: ['back'], ...(strip ? { strip } : {}) });
51
+
52
+ export function opening(o: OpeningOptions, lod: Lod = 0): Mesh {
53
+ const { width: w, height: h } = o;
54
+ if (!(w > 0.2 && h > 0.2 && Number.isFinite(w) && Number.isFinite(h)))
55
+ throw new Ts23dError('T3D_RECIPE_INVALID', {
56
+ cause: `${o.kind} opening is ${w} × ${h} m`,
57
+ fix: 'give a clear width and height > 0.2 m (sash ≈ 0.9 × 1.6, door ≈ 0.9 × 2.1)',
58
+ });
59
+ const r = o.depth ?? 0.12;
60
+ const s = o.surround ?? 0.1;
61
+ const trim = o.trim ?? [0.78, 0.75, 0.68];
62
+ const door = o.kind === 'door';
63
+ const parts: Mesh[] = [];
64
+ // The infill: glass flush on the wall, or the door leaf a little proud of it.
65
+ parts.push(
66
+ door
67
+ ? onWall([0.05, w, h], 0, 0, o.frame, 'wood')
68
+ : onWall([0.02, w, h], 0, 0, o.glass, 'glass'),
69
+ );
70
+ if (lod < 2) {
71
+ const lintel = o.lintel ?? true;
72
+ // Reveal masonry in true-scale courses aligned with the wall's (courses start at world z 0).
73
+ const reveal = (size: Vec3, y: number, z: number): Mesh =>
74
+ mass(
75
+ {
76
+ size,
77
+ at: [size[0] / 2, y, z],
78
+ color: o.wall,
79
+ strip: o.wallStrip ?? 'plain',
80
+ courseOrigin: -o.at[2],
81
+ omit: ['back'],
82
+ },
83
+ lod,
84
+ );
85
+ for (const side of [-1, 1]) parts.push(reveal([r, s, h], side * (w / 2 + s / 2), 0));
86
+ if (lintel) parts.push(onWall([r + 0.03, w + 2 * s + 0.2, 0.22], 0, h, trim, 'stone'));
87
+ else parts.push(reveal([r, w + 2 * s, s], 0, h));
88
+ if (!door && (o.sill ?? true))
89
+ parts.push(onWall([r + 0.07, w + 2 * s + 0.12, 0.07], 0, -0.07, trim, 'stone'));
90
+ if (!door) parts.push(...sash(o, w, h, lod));
91
+ else if (lod === 0) parts.push(...panels(o, w, h));
92
+ }
93
+ return place(merge(parts), { yaw: YAW[o.facing], at: o.at });
94
+ }
95
+
96
+ /** Sash frame round the glass; glazing bars at LOD0. */
97
+ function sash(o: OpeningOptions, w: number, h: number, lod: Lod): Mesh[] {
98
+ const f = 0.06;
99
+ const d = 0.05;
100
+ const out = [
101
+ onWall([d, f, h], -(w / 2 - f / 2), 0, o.frame),
102
+ onWall([d, f, h], w / 2 - f / 2, 0, o.frame),
103
+ onWall([d, w - 2 * f, f], 0, 0, o.frame),
104
+ onWall([d, w - 2 * f, f], 0, h - f, o.frame),
105
+ ];
106
+ if (lod > 0) return out;
107
+ const [across, up] = o.panes ?? [2, 2];
108
+ const bar = 0.03;
109
+ for (let i = 1; i < across; i++)
110
+ out.push(
111
+ onWall([d * 0.8, bar, h - 2 * f], -w / 2 + f + ((w - 2 * f) * i) / across, f, o.frame),
112
+ );
113
+ for (let j = 1; j < up; j++)
114
+ out.push(onWall([d * 0.8, w - 2 * f, bar], 0, f + ((h - 2 * f) * j) / up - bar / 2, o.frame));
115
+ return out;
116
+ }
117
+
118
+ /** Four raised fielded panels on a door leaf (LOD0). */
119
+ function panels(o: OpeningOptions, w: number, h: number): Mesh[] {
120
+ const out: Mesh[] = [];
121
+ const pw = (w - 0.36) / 2;
122
+ const tone: Vec3 = [o.frame[0] * 0.85, o.frame[1] * 0.85, o.frame[2] * 0.85];
123
+ for (const y of [-1, 1])
124
+ for (const [z0, ph] of [
125
+ [0.15, h * 0.38],
126
+ [0.15 + h * 0.38 + 0.15, h - 0.45 - h * 0.38],
127
+ ] as const)
128
+ out.push(
129
+ box({
130
+ size: [0.02, pw, ph],
131
+ at: [0.06, y * (pw / 2 + 0.06), z0],
132
+ color: tone,
133
+ omit: ['back'],
134
+ }),
135
+ );
136
+ // Knob on the lock side.
137
+ out.push(
138
+ box({ size: [0.06, 0.06, 0.06], at: [0.08, w / 2 - 0.12, h * 0.48], color: [0.7, 0.6, 0.3] }),
139
+ );
140
+ return out;
141
+ }
@@ -0,0 +1,12 @@
1
+ // Kinds with a fixed entry point and no body yet. Each is a library of builders recipes call;
2
+ // the roadmap slice that builds it is named in the error.
3
+
4
+ import { type BuildContext, notImplemented } from '../core/index.ts';
5
+
6
+ /** Stylised creature from a skeleton + SDF flesh (Spore-like). Realistic ones go via /neural. M4. */
7
+ export function creature(
8
+ _ctx: BuildContext,
9
+ _o: { skeleton: 'biped' | 'quadruped' | 'bird'; height: number },
10
+ ): void {
11
+ notImplemented('creature (SDF-skinned skeleton)', 'docs/idea/07-what-fits.md');
12
+ }
@@ -0,0 +1,100 @@
1
+ // A chimney stack for `roofMesh`: brick shaft from the wall plate (hidden in the attic, so it
2
+ // always meets the roof) to a set height above where it leaves the slope, with an oversailing
3
+ // cap course (LOD0/1) and clay pots (LOD0). Building-regs rule of thumb: ≥ 0.9 m above the
4
+ // highest point where it passes through the roof.
5
+
6
+ import { type Lod, type Mesh, MeshBuilder, merge, type Vec3 } from '../core/index.ts';
7
+ import type { StripId } from '../material/index.ts';
8
+ import { box, lathe } from '../primitives/index.ts';
9
+ import { emitFace } from './faces.ts';
10
+
11
+ export interface RoofChimney {
12
+ /** Centre offset from the roof's `at`, world X/Y metres. */
13
+ readonly at: readonly [number, number];
14
+ /** Stack width (X) and depth (Y). Default [0.9, 0.6]. */
15
+ readonly size?: readonly [number, number];
16
+ /** Height above the highest roof point it touches. Default 0.9. */
17
+ readonly height?: number;
18
+ readonly color: Vec3;
19
+ readonly strip?: StripId;
20
+ /** Clay pots on top (LOD0). Default: one per 0.4 m of width. */
21
+ readonly pots?: number;
22
+ readonly potColor?: Vec3;
23
+ }
24
+
25
+ export function chimney(
26
+ c: RoofChimney,
27
+ heightAt: (x: number, y: number) => number,
28
+ at: Vec3,
29
+ lod: Lod,
30
+ ): Mesh {
31
+ const [w, d] = c.size ?? [0.9, 0.6];
32
+ const cx = at[0] + c.at[0];
33
+ const cy = at[1] + c.at[1];
34
+ const corners: [number, number][] = [
35
+ [cx - w / 2, cy - d / 2],
36
+ [cx + w / 2, cy - d / 2],
37
+ [cx + w / 2, cy + d / 2],
38
+ [cx - w / 2, cy + d / 2],
39
+ ];
40
+ const exit = Math.max(...corners.map(([x, y]) => heightAt(x, y)));
41
+ const top = exit + (c.height ?? 0.9);
42
+ const z0 = at[2];
43
+ const strip = c.strip ?? 'brick';
44
+ const b = new MeshBuilder();
45
+ corners.forEach(([x0, y0], i) => {
46
+ const [x1, y1] = corners[(i + 1) % 4] ?? [x0, y0];
47
+ emitFace(b, {
48
+ points: [
49
+ [x0, y0, z0],
50
+ [x1, y1, z0],
51
+ [x1, y1, top],
52
+ [x0, y0, top],
53
+ ],
54
+ normal: [y1 - y0, -(x1 - x0), 0],
55
+ up: [0, 0, 1],
56
+ vOrigin: [0, 0, z0],
57
+ color: c.color,
58
+ strip,
59
+ banded: lod < 2,
60
+ });
61
+ });
62
+ emitFace(b, {
63
+ points: corners.map(([x, y]) => [x, y, top] as Vec3),
64
+ normal: [0, 0, 1],
65
+ up: [0, 1, 0],
66
+ vOrigin: [cx, cy, top],
67
+ color: c.color,
68
+ strip: 'plain',
69
+ banded: false,
70
+ });
71
+ const parts = [b.build()];
72
+ if (lod < 2) {
73
+ const k = 0.06;
74
+ const cap: Vec3 = [c.color[0] * 0.85, c.color[1] * 0.85, c.color[2] * 0.85];
75
+ parts.push(box({ size: [w + 2 * k, d + 2 * k, 0.1], at: [cx, cy, top - 0.22], color: cap }));
76
+ parts.push(box({ size: [w + k, d + k, 0.07], at: [cx, cy, top - 0.07], color: cap }));
77
+ }
78
+ if (lod === 0) {
79
+ const n = c.pots ?? Math.max(1, Math.round(w / 0.4));
80
+ const pot = c.potColor ?? [0.62, 0.32, 0.2];
81
+ for (let i = 0; i < n; i++) {
82
+ const x = cx - w / 2 + (w * (i + 0.5)) / n;
83
+ parts.push(
84
+ lathe({
85
+ profile: [
86
+ [0.13, 0],
87
+ [0.1, 0.36],
88
+ [0.125, 0.4],
89
+ [0.125, 0.46],
90
+ ],
91
+ segments: 8,
92
+ caps: true,
93
+ at: [x, cy, top - 0.02],
94
+ color: pot,
95
+ }),
96
+ );
97
+ }
98
+ }
99
+ return merge(parts);
100
+ }
@@ -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
+ }