@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,153 @@
1
+ // Building helpers: masses, then details. A facade is a split-grammar line per floor and side;
2
+ // `win`/`door`/`arch`/`shop` slots become openings, everything else stays wall. Plain mode: inset
3
+ // glass/door panels. With `kit`: real openings (reveal, sill, lintel, sash, panelled door).
4
+
5
+ import type { BuildContext, Vec3 } from '../core/index.ts';
6
+ import { split } from '../grammar/index.ts';
7
+ import type { StripId } from '../material/index.ts';
8
+ import { box } from '../primitives/index.ts';
9
+ import { opening } from './openings.ts';
10
+
11
+ export type Side = 'front' | 'back' | 'left' | 'right';
12
+
13
+ export interface FacadeOptions {
14
+ readonly prefix: string;
15
+ /** Centre of the mass's base. */
16
+ readonly at: Vec3;
17
+ /** Footprint width (X) and depth (Y) of the mass this facade wraps. */
18
+ readonly footprint: readonly [number, number];
19
+ readonly floors: number;
20
+ readonly floorHeight: number;
21
+ /** Grammar per floor; the same line for every floor unless `ground` overrides floor 0. */
22
+ readonly grammar: string;
23
+ readonly ground?: string;
24
+ readonly sides: readonly Side[];
25
+ readonly glass: Vec3;
26
+ readonly frame?: Vec3;
27
+ /** Window height as a fraction of the floor. */
28
+ readonly windowRatio?: number;
29
+ /** Detailed openings (see `opening`): reveal in the wall colour, stone sill/lintel, sash bars. */
30
+ readonly kit?: {
31
+ readonly wall: Vec3;
32
+ readonly trim?: Vec3;
33
+ readonly depth?: number;
34
+ readonly panes?: readonly [number, number];
35
+ /** The wall's strip, so reveals continue its courses. Default 'plain'. */
36
+ readonly strip?: StripId;
37
+ };
38
+ }
39
+
40
+ const OPENINGS = new Set(['win', 'door', 'arch', 'shop']);
41
+
42
+ export function facade(ctx: BuildContext, o: FacadeOptions): void {
43
+ const [w, d] = o.footprint;
44
+ const ratio = o.windowRatio ?? 0.6;
45
+ const depth = 0.08;
46
+ for (const side of o.sides) {
47
+ const along = side === 'front' || side === 'back' ? d : w;
48
+ for (let f = 0; f < o.floors; f++) {
49
+ const line = f === 0 && o.ground ? o.ground : o.grammar;
50
+ const z = o.at[2] + f * o.floorHeight;
51
+ split(line, along).forEach((slot, i) => {
52
+ if (!OPENINGS.has(slot.symbol) || slot.size < 0.2) return;
53
+ const isDoor = slot.symbol === 'door' || slot.symbol === 'shop';
54
+ const h = isDoor ? o.floorHeight * 0.85 : o.floorHeight * ratio;
55
+ const sill = isDoor ? 0 : (o.floorHeight - h) / 2;
56
+ const c = slot.start + slot.size / 2 - along / 2;
57
+ const inset = slot.size * 0.85;
58
+ const pos: Vec3 =
59
+ side === 'front'
60
+ ? [o.at[0] + w / 2, o.at[1] + c, z + sill]
61
+ : side === 'back'
62
+ ? [o.at[0] - w / 2, o.at[1] - c, z + sill]
63
+ : side === 'left'
64
+ ? [o.at[0] - c, o.at[1] + d / 2, z + sill]
65
+ : [o.at[0] + c, o.at[1] - d / 2, z + sill];
66
+ const name = `${o.prefix}_${side}_${f}_${i}`;
67
+ if (o.kit) {
68
+ const k = o.kit;
69
+ ctx.part(
70
+ name,
71
+ opening(
72
+ {
73
+ kind: isDoor ? 'door' : 'window',
74
+ width: Math.max(0.3, inset - 0.2),
75
+ height: h - (isDoor ? 0.25 : 0.2),
76
+ at: pos,
77
+ facing: side,
78
+ glass: o.glass,
79
+ frame: o.frame ?? [0.92, 0.9, 0.85],
80
+ wall: k.wall,
81
+ ...(k.trim ? { trim: k.trim } : {}),
82
+ ...(k.depth !== undefined ? { depth: k.depth } : {}),
83
+ ...(k.panes ? { panes: k.panes } : {}),
84
+ ...(k.strip ? { wallStrip: k.strip } : {}),
85
+ },
86
+ ctx.lod,
87
+ ),
88
+ );
89
+ return;
90
+ }
91
+ const size: Vec3 =
92
+ side === 'front' || side === 'back' ? [depth, inset, h] : [inset, depth, h];
93
+ ctx.part(
94
+ name,
95
+ box({
96
+ size,
97
+ at: pos,
98
+ color: isDoor ? (o.frame ?? o.glass) : o.glass,
99
+ strip: 'glass',
100
+ omit: ['bottom'],
101
+ }),
102
+ );
103
+ });
104
+ }
105
+ }
106
+ }
107
+
108
+ export interface SetbackTier {
109
+ readonly footprint: readonly [number, number];
110
+ readonly floors: number;
111
+ }
112
+
113
+ /** Stack of shrinking masses (Empire State, Chrysler-style towers). Returns the top z. */
114
+ export function setbacks(
115
+ ctx: BuildContext,
116
+ o: {
117
+ prefix: string;
118
+ tiers: readonly SetbackTier[];
119
+ floorHeight: number;
120
+ color: Vec3;
121
+ grammar: string;
122
+ glass: Vec3;
123
+ detail: boolean;
124
+ },
125
+ ): number {
126
+ let z = 0;
127
+ o.tiers.forEach((t, i) => {
128
+ const h = t.floors * o.floorHeight;
129
+ ctx.part(
130
+ `${o.prefix}_${i}`,
131
+ box({
132
+ size: [t.footprint[0], t.footprint[1], h],
133
+ at: [0, 0, z],
134
+ color: o.color,
135
+ strip: 'stone',
136
+ omit: i === 0 ? ['bottom'] : [],
137
+ }),
138
+ );
139
+ if (o.detail)
140
+ facade(ctx, {
141
+ prefix: `${o.prefix}_${i}_win`,
142
+ at: [0, 0, z],
143
+ footprint: t.footprint,
144
+ floors: t.floors,
145
+ floorHeight: o.floorHeight,
146
+ grammar: o.grammar,
147
+ sides: ['front', 'back', 'left', 'right'],
148
+ glass: o.glass,
149
+ });
150
+ z += h;
151
+ });
152
+ return z;
153
+ }
@@ -0,0 +1,138 @@
1
+ // Convex-polygon toolkit for the building kinds: half-plane clipping in plan, and emitting a flat
2
+ // convex 3D polygon onto a trim strip AT TRUE SCALE. A strip tiles along U only, so the polygon is
3
+ // cut into bands `scaleMetres` tall along its V direction (courses start on `vOrigin`); each band
4
+ // maps V 0→1 across the strip. That is how slates run down a slope and bricks up a gable.
5
+
6
+ import {
7
+ cross,
8
+ dot,
9
+ type MeshBuilder,
10
+ normalize,
11
+ sub,
12
+ type Vec2,
13
+ type Vec3,
14
+ } from '../core/index.ts';
15
+ import { DEFAULT_SHEET, type StripId, stripRect } from '../material/index.ts';
16
+
17
+ const EPS = 1e-9;
18
+
19
+ /** Keep the part of a convex polygon where the linear function `f` is ≤ 0. */
20
+ export function clipPoly<P extends Vec2 | Vec3>(poly: readonly P[], f: (p: P) => number): P[] {
21
+ const out: P[] = [];
22
+ const n = poly.length;
23
+ for (let i = 0; i < n; i++) {
24
+ const a = poly[i];
25
+ const b = poly[(i + 1) % n];
26
+ if (a === undefined || b === undefined) continue;
27
+ const fa = f(a);
28
+ const fb = f(b);
29
+ if (fa <= EPS) out.push(a);
30
+ if (fa <= EPS !== fb <= EPS) {
31
+ const t = fa / (fa - fb);
32
+ out.push(a.map((v, k) => v + ((b[k] ?? v) - v) * t) as unknown as P);
33
+ }
34
+ }
35
+ return dedupe(out);
36
+ }
37
+
38
+ /** Drop consecutive duplicate points (clipping through a vertex emits it twice). */
39
+ export function dedupe<P extends Vec2 | Vec3>(poly: readonly P[]): P[] {
40
+ const out: P[] = [];
41
+ for (const p of poly) {
42
+ const q = out.at(-1);
43
+ if (!q || p.some((v, k) => Math.abs(v - (q[k] ?? 0)) > 1e-7)) out.push(p);
44
+ }
45
+ const first = out[0];
46
+ const last = out.at(-1);
47
+ if (out.length > 1 && first && last && first.every((v, k) => Math.abs(v - (last[k] ?? 0)) < 1e-7))
48
+ out.pop();
49
+ return out;
50
+ }
51
+
52
+ /** Signed area of a 2D polygon (CCW positive). */
53
+ export const area2 = (poly: readonly Vec2[]): number =>
54
+ poly.reduce((s, a, i) => {
55
+ const b = poly[(i + 1) % poly.length] ?? a;
56
+ return s + a[0] * b[1] - b[0] * a[1];
57
+ }, 0) / 2;
58
+
59
+ /** Vertical extent of one band (one full V of the strip) in metres, from the house sheet. */
60
+ export const stripScale = (strip: StripId): number => DEFAULT_SHEET.strips[strip]?.scaleMetres ?? 1;
61
+
62
+ export interface FaceOptions {
63
+ /** Convex and planar; either winding (it is turned to face `normal`). */
64
+ readonly points: readonly Vec3[];
65
+ readonly normal: Vec3;
66
+ /** Direction V grows along (up a slope, up a wall); projected into the face plane. */
67
+ readonly up: Vec3;
68
+ /** A point on the line where courses start (eave, wall foot). */
69
+ readonly vOrigin: Vec3;
70
+ readonly color: Vec3 | ((p: Vec3) => Vec3);
71
+ readonly strip: StripId;
72
+ /** false: one polygon, V squeezed over the whole face (far LODs). Default true. */
73
+ readonly banded?: boolean;
74
+ }
75
+
76
+ /** Emit a convex face into `b`, banded at the strip's true scale. Returns triangles added. */
77
+ export function emitFace(b: MeshBuilder, o: FaceOptions): number {
78
+ if (o.points.length < 3) return 0;
79
+ const n = normalize(o.normal);
80
+ const points = dot(newell(o.points), n) < 0 ? [...o.points].reverse() : o.points;
81
+ const vDir = normalize(sub(o.up, scaleV(n, dot(o.up, n))));
82
+ const uDir = cross(vDir, n);
83
+ const rect = stripRect(o.strip);
84
+ const scale = stripScale(o.strip);
85
+ const vOf = (p: Vec3): number => dot(sub(p, o.vOrigin), vDir);
86
+ const vs = points.map(vOf);
87
+ const lo = Math.min(...vs);
88
+ const hi = Math.max(...vs);
89
+ if (hi - lo < 1e-6) return 0;
90
+ const colour = (p: Vec3): Vec3 => (typeof o.color === 'function' ? o.color(p) : o.color);
91
+ const bands: [number, number][] = [];
92
+ if (o.banded === false) bands.push([lo, hi]);
93
+ else {
94
+ for (let k = Math.floor(lo / scale + 1e-9); k * scale < hi - 1e-9; k++) {
95
+ bands.push([k * scale, (k + 1) * scale]);
96
+ }
97
+ }
98
+ let tris = 0;
99
+ for (const [v0, v1] of bands) {
100
+ const span = v1 - v0;
101
+ const poly = clipPoly(
102
+ clipPoly(points, (p) => v0 - vOf(p)),
103
+ (p) => vOf(p) - v1,
104
+ );
105
+ if (poly.length < 3) continue;
106
+ const ids = poly.map((p) => {
107
+ const f = Math.min(1, Math.max(0, (vOf(p) - v0) / span));
108
+ return b.vertex({
109
+ p,
110
+ n,
111
+ uv: [
112
+ rect.u0 + (dot(p, uDir) / rect.metres) * (rect.u1 - rect.u0),
113
+ rect.v0 + f * (rect.v1 - rect.v0),
114
+ ],
115
+ color: colour(p),
116
+ });
117
+ });
118
+ const first = ids[0] ?? 0;
119
+ for (let i = 1; i + 1 < ids.length; i++) b.tri(first, ids[i] ?? 0, ids[i + 1] ?? 0);
120
+ tris += ids.length - 2;
121
+ }
122
+ return tris;
123
+ }
124
+
125
+ /** Newell's polygon normal (unnormalised): robust for any planar polygon. */
126
+ export function newell(poly: readonly Vec3[]): Vec3 {
127
+ const n: [number, number, number] = [0, 0, 0];
128
+ for (let i = 0; i < poly.length; i++) {
129
+ const a = poly[i] ?? [0, 0, 0];
130
+ const b = poly[(i + 1) % poly.length] ?? a;
131
+ n[0] += (a[1] - b[1]) * (a[2] + b[2]);
132
+ n[1] += (a[2] - b[2]) * (a[0] + b[0]);
133
+ n[2] += (a[0] - b[0]) * (a[1] + b[1]);
134
+ }
135
+ return n;
136
+ }
137
+
138
+ const scaleV = (v: Vec3, s: number): Vec3 => [v[0] * s, v[1] * s, v[2] * s];
@@ -0,0 +1,151 @@
1
+ // The orc's armoured bust gear as sculpt groups: layered metal pauldron lames (domes trimmed by a
2
+ // tilted plane, each with a rolled rim), bone spikes, a leather baldric that follows the chest
3
+ // (a shell of the chest ∩ a diagonal slab) with an iron buckle, and a fur mantle of clumped,
4
+ // tapering strands. Head units, mirrored across Y unless an edit opts out.
5
+
6
+ import type { Vec3 } from '../core/index.ts';
7
+ import type { SculptEdit } from '../primitives/index.ts';
8
+ import { alongZ, unit } from './head-geom.ts';
9
+ import type { HeadParams } from './head-params.ts';
10
+
11
+ const RAD = Math.PI / 180;
12
+ const shade = (c: Vec3, k: number): Vec3 => [c[0] * k, c[1] * k, c[2] * k];
13
+
14
+ /** Half-space kept above the plane through `p` whose normal is +Z rolled `deg` about X. */
15
+ const above = (p: Vec3, deg: number, op: 'intersect' | 'subtract' = 'intersect'): SculptEdit => {
16
+ const n: Vec3 = [0, -Math.sin(deg * RAD), Math.cos(deg * RAD)];
17
+ return {
18
+ shape: 'roundBox',
19
+ at: [p[0] + n[0], p[1] + n[1], p[2] + n[2]],
20
+ size: [4, 4, 2],
21
+ rotate: [deg, 0, 0],
22
+ op,
23
+ };
24
+ };
25
+
26
+ /** Thin slab (thickness `t`) through `p`, normal +Z rolled `deg` about X. */
27
+ const slab = (p: Vec3, deg: number, t: number): SculptEdit => ({
28
+ shape: 'roundBox',
29
+ at: p,
30
+ size: [4, 4, t],
31
+ rotate: [deg, 0, 0],
32
+ op: 'intersect',
33
+ });
34
+
35
+ /** Chest ellipsoid of the bust (shared with head-bust.ts so the strap hugs it). */
36
+ export const chestOf = (cx: number, s: number, b: number): { at: Vec3; r: Vec3 } => ({
37
+ at: [cx - 0.04, 0, b + 0.2],
38
+ r: [0.36, s * 0.75, 0.42],
39
+ });
40
+
41
+ export function armour(p: HeadParams, cx: number, s: number, b: number, nz: number): SculptEdit[] {
42
+ const c = p.colors;
43
+ const metal = c.metal;
44
+ const out: SculptEdit[] = [];
45
+ // Pauldrons: three lames, top one biggest, stepping down and out; rims a darker iron.
46
+ for (let k = 0; k < 3; k++) {
47
+ const at: Vec3 = [cx - 0.03, s * (0.78 + k * 0.1), b + 0.53 - k * 0.12];
48
+ const r: Vec3 = [0.31 - k * 0.02, 0.27 - k * 0.03, 0.19 - k * 0.02];
49
+ const tilt = -(28 + k * 14);
50
+ const cut: Vec3 = [at[0], at[1], at[2] - r[2] * 0.35];
51
+ out.push(
52
+ {
53
+ group: [{ shape: 'ellipsoid', at, size: r }, above(cut, tilt)],
54
+ color: shade(metal, 1 - k * 0.12),
55
+ blend: 0.008,
56
+ },
57
+ {
58
+ group: [
59
+ { shape: 'ellipsoid', at, size: [r[0] + 0.018, r[1] + 0.018, r[2] + 0.018] },
60
+ slab(cut, tilt, 0.035),
61
+ ],
62
+ color: shade(metal, 0.55),
63
+ blend: 0.004,
64
+ },
65
+ );
66
+ // Rivets along the top of each lame's rim.
67
+ for (let j = 0; j < 4; j++) {
68
+ const t = (j - 1.5) * 0.11;
69
+ out.push({
70
+ shape: 'sphere',
71
+ at: [at[0] + t, at[1] + Math.sin(-tilt * RAD) * r[1] * 0.55, cut[2] + 0.03],
72
+ size: 0.016,
73
+ color: shade(metal, 1.4),
74
+ blend: 0.004,
75
+ });
76
+ }
77
+ }
78
+ // Bone spikes along the top lame's ridge.
79
+ for (let k = 0; k < 3; k++)
80
+ out.push({
81
+ shape: 'cone',
82
+ at: [cx - 0.17 + k * 0.14, s * 0.84, b + 0.68 - Math.abs(k - 1) * 0.03],
83
+ size: [0.065, 0.006, 0.26 - Math.abs(k - 1) * 0.08],
84
+ rotate: alongZ(unit([(k - 1) * 0.35, 0.45, 1])),
85
+ color: c.teeth,
86
+ blend: 0.01,
87
+ });
88
+ // Baldric: a shell of the chest cut to a diagonal band, over the left shoulder; iron buckle.
89
+ const chest = chestOf(cx, s, b);
90
+ const grow = (g: number): Vec3 => [chest.r[0] + g, chest.r[1] + g, chest.r[2] + g];
91
+ const band: Vec3 = [cx, 0, b + 0.28];
92
+ out.push(
93
+ {
94
+ group: [{ shape: 'ellipsoid', at: chest.at, size: grow(0.03) }, slab(band, 52, 0.17)],
95
+ color: c.leather,
96
+ blend: 0.006,
97
+ mirror: false,
98
+ },
99
+ {
100
+ group: [
101
+ { shape: 'ellipsoid', at: chest.at, size: grow(0.055) },
102
+ {
103
+ shape: 'roundBox',
104
+ at: [cx + 0.3, 0.02, b + 0.3],
105
+ size: [0.3, 0.15, 0.15],
106
+ rotate: [52, 0, 0],
107
+ op: 'intersect',
108
+ },
109
+ {
110
+ shape: 'ellipsoid',
111
+ at: [cx + 0.42, 0.02, b + 0.3],
112
+ size: [0.1, 0.045, 0.045],
113
+ op: 'subtract',
114
+ },
115
+ ],
116
+ color: shade(metal, 0.8),
117
+ blend: 0.004,
118
+ mirror: false,
119
+ },
120
+ );
121
+ // Fur mantle: a thick base roll, then clumped strands fanning out and down from it.
122
+ const fur = c.fur ?? c.leather;
123
+ const R = p.neck.radius + 0.1;
124
+ out.push({
125
+ shape: 'torus',
126
+ at: [cx - 0.08, 0, nz + 0.02],
127
+ size: [R, 0.07],
128
+ color: shade(fur, 0.75),
129
+ blend: 0.04,
130
+ });
131
+ for (let row = 0; row < 2; row++)
132
+ for (let i = 0; i < 10; i++) {
133
+ const a = (-85 + i * 20 + row * 10) * RAD;
134
+ const rr = R + row * 0.05;
135
+ const root: Vec3 = [
136
+ cx - 0.08 + Math.cos(a) * rr,
137
+ Math.abs(Math.sin(a)) * rr + 0.015,
138
+ nz + 0.07 - row * 0.06,
139
+ ];
140
+ const dir = unit([Math.cos(a), Math.abs(Math.sin(a)), -0.5 - ((i + row) % 2) * 0.35]);
141
+ out.push({
142
+ shape: 'cone',
143
+ at: root,
144
+ size: [0.07 - row * 0.01, 0.012, 0.13 + ((i + row) % 3) * 0.03],
145
+ rotate: alongZ(dir),
146
+ color: (i + row) % 2 ? fur : shade(fur, 0.7),
147
+ blend: 0.03,
148
+ });
149
+ }
150
+ return out;
151
+ }
@@ -0,0 +1,133 @@
1
+ // Facial hair as sculpt groups, part of the skull field (so a beard splits with the jaw):
2
+ // eyebrows riding just proud of the brow ridge (tapered, arched), and beards built as one clipped
3
+ // volume — a mass around jaw and chin, trimmed above a cheek line that rises to the ears, the lips
4
+ // carved clear — plus a two-segment moustache and optional braid. Head units, mirrored across Y.
5
+
6
+ import type { Vec3 } from '../core/index.ts';
7
+ import type { SculptEdit } from '../primitives/index.ts';
8
+ import { along, unit } from './head-geom.ts';
9
+ import type { HeadLayout } from './head-layout.ts';
10
+ import type { HeadParams } from './head-params.ts';
11
+
12
+ const RAD = Math.PI / 180;
13
+
14
+ /** Eyebrows: two capsule segments (inner thick → outer thin) offset off the brow ridge. */
15
+ export function browEdits(p: HeadParams, l: HeadLayout): SculptEdit[] {
16
+ const h = p.brow.hair ?? 0;
17
+ if (h <= 0) return [];
18
+ const [ex, ey] = l.eye;
19
+ const r = l.eyeR;
20
+ const lift: Vec3 = [p.brow.ridge * 0.55 + 0.012, 0, p.brow.ridge * 0.8 + 0.018];
21
+ const inner: Vec3 = [ex + r * 0.75 + lift[0], 0.035, l.browZ - p.brow.scowl + lift[2]];
22
+ const outer: Vec3 = [
23
+ ex + r * 0.1 + lift[0] * 0.6,
24
+ ey + r * 1.25,
25
+ l.browZ + p.brow.scowl * 0.5 + lift[2] * 0.7,
26
+ ];
27
+ const mid: Vec3 = [
28
+ (inner[0] + outer[0]) / 2,
29
+ inner[1] + (outer[1] - inner[1]) * 0.55,
30
+ (inner[2] + outer[2]) / 2 + 0.014,
31
+ ];
32
+ return [
33
+ {
34
+ group: [
35
+ { shape: 'capsule', at: inner, to: mid, size: h },
36
+ { shape: 'capsule', at: mid, to: outer, size: h * 0.65, blend: 0.006 },
37
+ ],
38
+ color: p.colors.hair,
39
+ blend: 0.006,
40
+ },
41
+ ];
42
+ }
43
+
44
+ /** Box whose top face is the plane through `p` tilted `deg` about Y (positive: higher at back). */
45
+ const below = (p: Vec3, deg: number): SculptEdit => {
46
+ const n: Vec3 = [-Math.sin(deg * RAD), 0, Math.cos(deg * RAD)];
47
+ return {
48
+ shape: 'roundBox',
49
+ at: [p[0] - n[0], p[1], p[2] - n[2]],
50
+ size: [4, 4, 2],
51
+ rotate: [0, -deg, 0],
52
+ op: 'intersect',
53
+ blend: 0.02,
54
+ };
55
+ };
56
+
57
+ export function beardEdits(p: HeadParams, l: HeadLayout): SculptEdit[] {
58
+ if (p.beard === 'none') return [];
59
+ const h = p.colors.hair;
60
+ const light: Vec3 = [h[0] * 1.35, h[1] * 1.35, h[2] * 1.35];
61
+ const [ax, ay, az] = l.angle;
62
+ const ch = l.chin;
63
+ const mw = p.mouth.width;
64
+ const lipT = p.mouth.lips;
65
+ const full = p.beard !== 'goatee';
66
+ const mass: SculptEdit = full
67
+ ? {
68
+ shape: 'ellipsoid',
69
+ at: [(ax + ch[0]) / 2 + 0.03, 0, (az + ch[2]) / 2 + 0.01],
70
+ size: [(ch[0] - ax) / 2 + 0.08, ay + 0.065, 0.17],
71
+ }
72
+ : { shape: 'ellipsoid', at: [ch[0] + 0.01, 0, ch[2] + 0.02], size: [0.09, mw * 0.9, 0.11] };
73
+ const out: SculptEdit[] = [
74
+ {
75
+ group: [
76
+ mass,
77
+ // Chin volume: the beard grows forward and down off the chin.
78
+ {
79
+ shape: 'ellipsoid',
80
+ at: [ch[0] + 0.025, 0, ch[2] - 0.03],
81
+ size: [0.08, full ? 0.13 : 0.08, 0.1],
82
+ blend: 0.04,
83
+ },
84
+ // Trimmed below a cheek line that rises towards the ears.
85
+ below([l.mouthX, 0, l.mouthZ + 0.03], full ? 28 : 0),
86
+ // Lips carved clear.
87
+ {
88
+ shape: 'ellipsoid',
89
+ at: [l.mouthX + 0.02, 0, l.mouthZ - lipT * 0.3],
90
+ size: [0.07, mw * 1.05, lipT * 2.3],
91
+ op: 'subtract',
92
+ blend: 0.012,
93
+ },
94
+ // Clump grooves down the chin.
95
+ ...[-0.05, 0.05].map(
96
+ (y): SculptEdit => ({
97
+ shape: 'capsule',
98
+ at: [ch[0] + 0.12, y, l.mouthZ - 0.06],
99
+ to: [ch[0] + 0.06, y * 1.3, ch[2] - 0.14],
100
+ size: 0.008,
101
+ op: 'subtract',
102
+ blend: 0.012,
103
+ mirror: false,
104
+ }),
105
+ ),
106
+ ],
107
+ color: h,
108
+ blend: 0.012,
109
+ },
110
+ ];
111
+ if (p.beard === 'braided')
112
+ out.push({
113
+ shape: 'capsule',
114
+ at: [ch[0] + 0.03, 0.035, ch[2] - 0.08],
115
+ to: [ch[0] + 0.01, 0.045, ch[2] - 0.34],
116
+ size: 0.032,
117
+ color: light,
118
+ blend: 0.02,
119
+ });
120
+ // Moustache: from under the nose, over the lip corners, drooping past them.
121
+ const root: Vec3 = [l.mouthX + 0.02, 0.012, l.mouthZ + lipT * 1.9];
122
+ const bend: Vec3 = [l.mouthX - 0.005, mw * 0.85, l.mouthZ + lipT * 0.6];
123
+ const tip = along(bend, unit([-0.4, 0.5, -1]), 0.05);
124
+ out.push({
125
+ group: [
126
+ { shape: 'capsule', at: root, to: bend, size: 0.024 },
127
+ { shape: 'capsule', at: bend, to: tip, size: 0.016, blend: 0.01 },
128
+ ],
129
+ color: light,
130
+ blend: 0.008,
131
+ });
132
+ return out;
133
+ }