@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,128 @@
1
+ // Seamless split: a mesher rounds the edge where the jaw cut meets the skin by about one cell, so
2
+ // the head and jaw would show a groove along the cut. This pass snaps every vertex near that rim
3
+ // back onto the exact surfaces — skin vertices onto skull = 0, cut-face vertices onto cut = 0, both
4
+ // clamped to their side — and gives skin vertices the skull's own normal, so the two parts meet on
5
+ // one crease line with matching shading.
6
+
7
+ import type { Mesh, Vec3 } from '../core/index.ts';
8
+
9
+ /** A mutable point/vector (core's Vec3 is readonly). */
10
+ type P3 = [number, number, number];
11
+
12
+ import type { Field } from '../primitives/index.ts';
13
+
14
+ function grad(f: Field, p: Vec3, e: number, out: P3): number {
15
+ const q: P3 = [p[0], p[1], p[2]];
16
+ const at = (dx: number, dy: number, dz: number): number => {
17
+ q[0] = p[0] + dx;
18
+ q[1] = p[1] + dy;
19
+ q[2] = p[2] + dz;
20
+ return f(q);
21
+ };
22
+ out[0] = (at(e, 0, 0) - at(-e, 0, 0)) / (2 * e);
23
+ out[1] = (at(0, e, 0) - at(0, -e, 0)) / (2 * e);
24
+ out[2] = (at(0, 0, e) - at(0, 0, -e)) / (2 * e);
25
+ return f(p);
26
+ }
27
+
28
+ /** Newton step of `p` towards f = 0 (only when f > 0 if `onlyOutside`). */
29
+ function project(f: Field, p: P3, e: number, onlyOutside: boolean): void {
30
+ const g: P3 = [0, 0, 0];
31
+ const v = grad(f, p, e, g);
32
+ if (onlyOutside && v <= 0) return;
33
+ const gg = g[0] * g[0] + g[1] * g[1] + g[2] * g[2];
34
+ if (gg < 1e-12) return;
35
+ for (let k = 0; k < 3; k++) p[k] = (p[k] ?? 0) - (v * (g[k] ?? 0)) / gg;
36
+ }
37
+
38
+ /** Part ≈ max(skull, side); `side` is the cut field signed so the part is where side < 0;
39
+ * `part` is the part's full field (with ears/tusks), so their vertices are left alone. */
40
+ export function sharpenRim(m: Mesh, skull: Field, side: Field, part: Field, res: number): Mesh {
41
+ const positions = new Float32Array(m.positions);
42
+ const normals = new Float32Array(m.normals);
43
+ const e = res * 0.25;
44
+ const gs: P3 = [0, 0, 0];
45
+ const gc: P3 = [0, 0, 0];
46
+ for (let i = 0; i < positions.length; i += 3) {
47
+ const p: P3 = [positions[i] ?? 0, positions[i + 1] ?? 0, positions[i + 2] ?? 0];
48
+ // The cut is cheap and analytic; the skull is the costly sculpt — test the cut first.
49
+ if (Math.abs(side(p)) > res * 2) continue;
50
+ const c = grad(side, p, e, gc);
51
+ const s = grad(skull, p, e, gs);
52
+ if (Math.abs(s) > res * 2) continue;
53
+ // Skip where an extra (ear, tusk) owns the surface.
54
+ if (part(p) < Math.max(s, c) - res * 0.5) continue;
55
+ const n: Vec3 = [normals[i] ?? 0, normals[i + 1] ?? 0, normals[i + 2] ?? 0];
56
+ const ls = Math.hypot(...gs) || 1;
57
+ const lc = Math.hypot(...gc) || 1;
58
+ const onSkin =
59
+ (n[0] * gs[0] + n[1] * gs[1] + n[2] * gs[2]) / ls >=
60
+ (n[0] * gc[0] + n[1] * gc[1] + n[2] * gc[2]) / lc;
61
+ for (let k = 0; k < 4; k++) {
62
+ if (onSkin) {
63
+ project(skull, p, e, false);
64
+ project(side, p, e, true);
65
+ } else {
66
+ project(side, p, e, false);
67
+ project(skull, p, e, true);
68
+ }
69
+ }
70
+ // Within ~half a cell of the other surface: snap onto the crease line itself, so no bevel
71
+ // strip (seen from outside as a dark groove) is left between skin and cut face.
72
+ const other = onSkin ? side(p) : skull(p);
73
+ if (other > -res * 0.6)
74
+ for (let k = 0; k < 6; k++) {
75
+ project(skull, p, e, false);
76
+ project(side, p, e, false);
77
+ }
78
+ positions[i] = p[0];
79
+ positions[i + 1] = p[1];
80
+ positions[i + 2] = p[2];
81
+ if (onSkin) {
82
+ grad(skull, p, e, gs);
83
+ const l = Math.hypot(...gs) || 1;
84
+ normals[i] = gs[0] / l;
85
+ normals[i + 1] = gs[1] / l;
86
+ normals[i + 2] = gs[2] / l;
87
+ }
88
+ }
89
+ return { ...m, positions, normals };
90
+ }
91
+
92
+ /**
93
+ * Where a field gradient smears across a hard crease (the jaw cut at coarse cells), a vertex
94
+ * normal can oppose its triangles; those vertices take their area-weighted face normal instead.
95
+ */
96
+ export function creaseNormals(m: Mesh, minDot = 0.2): Mesh {
97
+ const P = m.positions;
98
+ const acc = new Float64Array(P.length);
99
+ for (let t = 0; t < m.indices.length; t += 3) {
100
+ const a = (m.indices[t] ?? 0) * 3;
101
+ const b = (m.indices[t + 1] ?? 0) * 3;
102
+ const c = (m.indices[t + 2] ?? 0) * 3;
103
+ const ux = (P[b] ?? 0) - (P[a] ?? 0);
104
+ const uy = (P[b + 1] ?? 0) - (P[a + 1] ?? 0);
105
+ const uz = (P[b + 2] ?? 0) - (P[a + 2] ?? 0);
106
+ const vx = (P[c] ?? 0) - (P[a] ?? 0);
107
+ const vy = (P[c + 1] ?? 0) - (P[a + 1] ?? 0);
108
+ const vz = (P[c + 2] ?? 0) - (P[a + 2] ?? 0);
109
+ const n = [uy * vz - uz * vy, uz * vx - ux * vz, ux * vy - uy * vx];
110
+ for (const v of [a, b, c])
111
+ for (let k = 0; k < 3; k++) acc[v + k] = (acc[v + k] ?? 0) + (n[k] ?? 0);
112
+ }
113
+ const normals = new Float32Array(m.normals);
114
+ for (let i = 0; i < normals.length; i += 3) {
115
+ const l = Math.hypot(acc[i] ?? 0, acc[i + 1] ?? 0, acc[i + 2] ?? 0);
116
+ if (l < 1e-20) continue;
117
+ const f = [(acc[i] ?? 0) / l, (acc[i + 1] ?? 0) / l, (acc[i + 2] ?? 0) / l];
118
+ const d =
119
+ (normals[i] ?? 0) * (f[0] ?? 0) +
120
+ (normals[i + 1] ?? 0) * (f[1] ?? 0) +
121
+ (normals[i + 2] ?? 0) * (f[2] ?? 0);
122
+ if (d >= minDot) continue;
123
+ normals[i] = f[0] ?? 0;
124
+ normals[i + 1] = f[1] ?? 0;
125
+ normals[i + 2] = f[2] ?? 0;
126
+ }
127
+ return { ...m, normals };
128
+ }
@@ -0,0 +1,52 @@
1
+ // Hand-painted tone for sculpted head/bust vertex colour: the painter's three passes done from the
2
+ // distance field itself — cavity (crevices, folds, sockets go darker and a touch more saturated),
3
+ // a warm key from above (top planes lifted and warmed), and a cool bounce underneath. Pure field
4
+ // sampling at the surface point, so it is deterministic and costs ~9 field evaluations per vertex.
5
+
6
+ import type { Vec3 } from '../core/index.ts';
7
+ import type { Field } from '../primitives/index.ts';
8
+
9
+ export interface ToneOptions {
10
+ /** Probe distance for the cavity test, head units (≈ the size of a fold). */
11
+ readonly reach: number;
12
+ /** 0 = flat colour, 1 = full painted tone. */
13
+ readonly strength?: number;
14
+ }
15
+
16
+ /** Wraps `colorAt` with cavity darkening and a warm-top / cool-under gradient. */
17
+ export function toned(field: Field, colorAt: (q: Vec3) => Vec3, o: ToneOptions): (q: Vec3) => Vec3 {
18
+ const k = o.strength ?? 1;
19
+ const d = o.reach;
20
+ const e = d * 0.25;
21
+ return (q) => {
22
+ const c = colorAt(q);
23
+ const [x, y, z] = q;
24
+ const gx = field([x + e, y, z]) - field([x - e, y, z]);
25
+ const gy = field([x, y + e, z]) - field([x, y - e, z]);
26
+ const gz = field([x, y, z + e]) - field([x, y, z - e]);
27
+ const gl = Math.hypot(gx, gy, gz) || 1;
28
+ const n: Vec3 = [gx / gl, gy / gl, gz / gl];
29
+ // Cavity: walk out along the normal; a crevice brings other surface closer than the walk.
30
+ let occ = 0;
31
+ for (let i = 1; i <= 3; i++) {
32
+ const t = (d * i) / 3;
33
+ occ += Math.max(0, t - field([x + n[0] * t, y + n[1] * t, z + n[2] * t])) / t / i;
34
+ }
35
+ const cav = Math.min(1, occ * 2.5) * k;
36
+ const up = n[2] * k;
37
+ const lift = up > 0 ? 1 + up * 0.22 : 1 + up * 0.3;
38
+ const warm: Vec3 =
39
+ up > 0 ? [1 + up * 0.12, 1 + up * 0.05, 1 - up * 0.15] : [1 + up * 0.1, 1, 1 - up * 0.12];
40
+ const dark = 1 - cav * 0.55;
41
+ const lum = (c[0] + c[1] + c[2]) / 3;
42
+ // Saturate into the crevice: push each channel away from the grey by the cavity.
43
+ const sat = 1 + cav * 0.35;
44
+ return [
45
+ clamp((lum + (c[0] - lum) * sat) * dark * lift * warm[0]),
46
+ clamp((lum + (c[1] - lum) * sat) * dark * lift * warm[1]),
47
+ clamp((lum + (c[2] - lum) * sat) * dark * lift * warm[2]),
48
+ ];
49
+ };
50
+ }
51
+
52
+ const clamp = (v: number): number => Math.min(Math.max(v, 0), 1);
@@ -0,0 +1,230 @@
1
+ // The bust's anatomy, cut and plinth: ribcage, pectorals, deltoids, the trapezius sloping from
2
+ // behind the ear to the shoulder (it is what joins a head to a body), clavicles and the sternal
3
+ // notch — all scaled by `bust.mass` (0 bony … 1 brute) — trimmed by the classic collectible-bust
4
+ // cut (a U under the chest rising to the arms) and set on a turned socle + disc plinth.
5
+ // Head units, mirrored across Y; `torsoForms(f, g)` grows every form by g for clothing shells.
6
+
7
+ import type { Vec3 } from '../core/index.ts';
8
+ import { type Field, type SculptEdit, sd } from '../primitives/index.ts';
9
+ import { alongX } from './head-geom.ts';
10
+ import type { HeadLayout } from './head-layout.ts';
11
+ import type { HeadParams } from './head-params.ts';
12
+
13
+ /** Where the bust sits relative to the head: centre x, shoulder half-span, base, neck root… */
14
+ export interface BustFrame {
15
+ readonly cx: number;
16
+ readonly s: number;
17
+ /** Reference z the torso is laid out from (the floor lifted by the build's hunch). */
18
+ readonly b: number;
19
+ /** The asset's floor: bottom of the plinth. */
20
+ readonly floor: number;
21
+ /** Neck root (top of the sternum), z. */
22
+ readonly nz: number;
23
+ readonly m: number;
24
+ /** Neck radius. */
25
+ readonly n: number;
26
+ /** Where the trapezius meets the neck, z (high behind the ear on a brute). */
27
+ readonly trapZ: number;
28
+ /** Top of the plinth = bottom of the chest cut, z. */
29
+ readonly cutZ: number;
30
+ }
31
+
32
+ const sub = (a: Vec3, b: Vec3): Vec3 => [a[0] - b[0], a[1] - b[1], a[2] - b[2]];
33
+ const mid = (a: Vec3, b: Vec3): Vec3 => [(a[0] + b[0]) / 2, (a[1] + b[1]) / 2, (a[2] + b[2]) / 2];
34
+ const dist = (a: Vec3, b: Vec3): number => Math.hypot(...sub(a, b));
35
+
36
+ /** Plinth height, head units. */
37
+ const PLINTH = 0.17;
38
+
39
+ export function frameOf(p: HeadParams, l: HeadLayout): BustFrame {
40
+ const m = p.bust.mass ?? 0.5;
41
+ // A brute's shoulders ride up round its ears (the hunch): lift the torso, lengthen the socle.
42
+ const b = l.base + 0.06 + 0.1 * m;
43
+ const nz = b + 0.6;
44
+ return {
45
+ cx: l.cranium.at[0] + p.jaw.muzzle * 0.3,
46
+ s: p.bust.shoulders,
47
+ b,
48
+ floor: l.base,
49
+ nz,
50
+ m,
51
+ n: p.neck.radius,
52
+ trapZ: nz + 0.12 + (l.hinge[1] - 0.2 - nz - 0.12) * m,
53
+ cutZ: l.base + PLINTH + 0.06 + 0.1 * m,
54
+ };
55
+ }
56
+
57
+ /** Anatomy volumes (plain adds), every one grown by `g`. */
58
+ export function torsoForms(f: BustFrame, g = 0): SculptEdit[] {
59
+ const { cx, s, b, m, n } = f;
60
+ const grow = (r: Vec3): Vec3 => [r[0] + g, r[1] + g, r[2] + g];
61
+ const top: Vec3 = [cx - 0.12, n * 0.5, f.trapZ];
62
+ const acromion: Vec3 = [cx - 0.05, s * 0.78, b + 0.62 + 0.05 * m];
63
+ return [
64
+ // Ribcage: the barrel everything hangs on.
65
+ {
66
+ shape: 'ellipsoid',
67
+ at: [cx - 0.05, 0, b + 0.36],
68
+ size: grow([0.25 + 0.07 * m, s * 0.66, 0.38]),
69
+ },
70
+ // Pectorals: two slabs angled up to the shoulder; their lower edge is the chest's shadow line.
71
+ {
72
+ shape: 'ellipsoid',
73
+ at: [cx + 0.1 + 0.05 * m, s * 0.27, b + 0.46 + 0.02 * m],
74
+ size: grow([0.09 + 0.06 * m, s * 0.27, 0.11 + 0.03 * m]),
75
+ rotate: [-12, 0, 16],
76
+ blend: 0.05,
77
+ },
78
+ // Deltoids: a cap over each shoulder joint.
79
+ {
80
+ shape: 'ellipsoid',
81
+ at: [cx - 0.03, s * 0.8, b + 0.5 + 0.03 * m],
82
+ size: grow([0.13 + 0.06 * m, 0.11 + 0.05 * m, 0.16 + 0.05 * m]),
83
+ rotate: [-18, 0, 0],
84
+ blend: 0.08,
85
+ },
86
+ // Trapezius: a broad flat sheet from the neck behind the ear down to the acromion — the slope
87
+ // of a brute (local X along the slope, Y front-back, Z its thickness).
88
+ {
89
+ shape: 'ellipsoid',
90
+ at: mid(top, acromion),
91
+ size: [dist(top, acromion) / 2 + 0.08 + g, 0.11 + 0.12 * m + g, 0.05 + 0.1 * m + g],
92
+ rotate: alongX(sub(acromion, top)),
93
+ blend: 0.08 + 0.08 * m,
94
+ },
95
+ // Upper arm: a stump the bust cut ends, filling the shoulder under any pauldron.
96
+ {
97
+ shape: 'capsule',
98
+ at: [cx - 0.04, s * 0.8, b + 0.55],
99
+ to: [cx - 0.02, s * 0.9, b + 0.05],
100
+ size: 0.12 + 0.05 * m + g,
101
+ blend: 0.06,
102
+ },
103
+ // Clavicles: a bony ridge from the sternal notch out to the shoulder (clothes drape over it).
104
+ {
105
+ shape: 'capsule',
106
+ at: [cx + 0.2 + 0.04 * m, 0.045, f.nz - 0.03],
107
+ to: [cx + 0.0, s * 0.68, b + 0.6 + 0.04 * m],
108
+ size: 0.02 + 0.006 * (1 - m) + g,
109
+ blend: 0.035,
110
+ },
111
+ // Latissimus: widens the back under the armpit (a V-taper on a brute).
112
+ {
113
+ shape: 'ellipsoid',
114
+ at: [cx - 0.1, s * 0.55, b + 0.3],
115
+ size: grow([0.17 + 0.05 * m, 0.12 + 0.1 * m, 0.2]),
116
+ blend: 0.08,
117
+ },
118
+ // Upper back: the hump of trapezius and rhomboids behind the neck.
119
+ {
120
+ shape: 'ellipsoid',
121
+ at: [cx - 0.18, 0, f.nz + 0.04 + 0.08 * m],
122
+ size: grow([0.12 + 0.06 * m, s * 0.32, 0.16 + 0.12 * m]),
123
+ blend: 0.1,
124
+ },
125
+ ];
126
+ }
127
+
128
+ /** Signed distance to the bust-cut region (negative inside): ≈ 0 on the cut faces. */
129
+ export function cutField(f: BustFrame): Field {
130
+ const rz = 0.64;
131
+ const ry = f.s * 1.08;
132
+ const cz = f.cutZ + rz;
133
+ const e = sd.ellipsoid([f.cx, 0, cz], [1.6, ry, rz]);
134
+ const box = sd.box([f.cx, 0, cz + 1], [3.2, ry * 2, 2]);
135
+ return (q) => Math.min(e(q), box(q));
136
+ }
137
+
138
+ /** Keeps a group to the bust silhouette: the U-cut under the chest, rising to cut the arms. */
139
+ export function bustCut(f: BustFrame, g = 0): SculptEdit {
140
+ const rz = 0.64;
141
+ const ry = f.s * 1.08 + g;
142
+ // (cutField mirrors these numbers.)
143
+ const cz = f.cutZ + rz - g;
144
+ return {
145
+ group: [
146
+ { shape: 'ellipsoid', at: [f.cx, 0, cz], size: [1.6, ry, rz] },
147
+ { shape: 'roundBox', at: [f.cx, 0, cz + 1], size: [3.2, ry * 2, 2] },
148
+ ],
149
+ op: 'intersect',
150
+ blend: 0.012,
151
+ };
152
+ }
153
+
154
+ /** The bare body: anatomy in `color`, sternal notch and groove, then the bust cut. */
155
+ export function bodyEdits(p: HeadParams, f: BustFrame, color: Vec3): SculptEdit[] {
156
+ const { cx, b, m, nz } = f;
157
+ const shade = p.colors.shade;
158
+ return [
159
+ {
160
+ group: [
161
+ ...torsoForms(f),
162
+ {
163
+ shape: 'sphere',
164
+ at: [cx + 0.24 + 0.04 * m, 0, nz - 0.04],
165
+ size: 0.035,
166
+ op: 'subtract',
167
+ blend: 0.04,
168
+ color: shade,
169
+ },
170
+ {
171
+ shape: 'capsule',
172
+ at: [cx + 0.26 + 0.06 * m, 0, b + 0.5],
173
+ to: [cx + 0.26 + 0.05 * m, 0, b + 0.22],
174
+ size: 0.01 + 0.006 * m,
175
+ op: 'subtract',
176
+ blend: 0.04,
177
+ color: shade,
178
+ },
179
+ bustCut(f),
180
+ ],
181
+ color,
182
+ },
183
+ ];
184
+ }
185
+
186
+ /** Solid cylinder about Z from z0 to z1 (a capsule cut by a slab). */
187
+ const cylinder = (at: Vec3, r: number, z0: number, z1: number): SculptEdit => ({
188
+ group: [
189
+ { shape: 'capsule', at: [at[0], at[1], z0 - r], to: [at[0], at[1], z1 + r], size: r },
190
+ {
191
+ shape: 'roundBox',
192
+ at: [at[0], at[1], (z0 + z1) / 2],
193
+ size: [4, 4, z1 - z0],
194
+ op: 'intersect',
195
+ },
196
+ ],
197
+ });
198
+
199
+ /** A turned plinth: wide disc with a bead moulding, then a narrower socle up to the chest cut.
200
+ * Both start below the floor, so the bust's flat base cut alone makes the underside. */
201
+ /** `minT`: floor for the disc's thickness (≈ a cell), so far LODs keep the bust on the ground. */
202
+ export function plinthEdits(p: HeadParams, f: BustFrame, minT = 0): SculptEdit[] {
203
+ const c = p.colors.plinth ?? ([0.16, 0.15, 0.15] as Vec3);
204
+ const light: Vec3 = [c[0] * 1.35, c[1] * 1.35, c[2] * 1.35];
205
+ const x = f.cx - 0.04;
206
+ const R = 0.3 + 0.08 * f.s;
207
+ // Coarse cells would lose a thin disc: one solid drum from the floor to the chest cut instead.
208
+ if (minT > 0.07)
209
+ return [
210
+ { ...cylinder([x, 0, 0], R * 1.05, f.floor - 0.1, f.cutZ + 0.02), color: c, mirror: false },
211
+ ];
212
+ const disc = f.floor + 0.07;
213
+ return [
214
+ { ...cylinder([x, 0, 0], R, f.floor - 0.1, disc), color: c, mirror: false },
215
+ {
216
+ shape: 'torus',
217
+ at: [x, 0, disc],
218
+ size: [R - 0.03, 0.025],
219
+ color: light,
220
+ blend: 0.01,
221
+ mirror: false,
222
+ },
223
+ {
224
+ ...cylinder([x, 0, 0], R * 0.62, disc, f.cutZ + 0.02),
225
+ color: c,
226
+ blend: 0.02,
227
+ mirror: false,
228
+ },
229
+ ];
230
+ }
@@ -0,0 +1,176 @@
1
+ // Head kind: `head(ctx, …)` sculpts a stylised fantasy head + bust from a race preset (orc, goblin,
2
+ // human, dwarf, elf) or a full `HeadParams`, and emits the engine contract: `bust` (on z = 0),
3
+ // `head`, `jaw` (pivot at the hinge, opens about Y), `eye_l` / `eye_r` (pivots at the eyeball
4
+ // centres, gaze +X so an engine can aim them). Head units are scaled by `height` metres.
5
+ // LOD policy: sculpt cell 2 % / 4.5 % / 14 % of the head height (HEAD_RES); LOD2 drops warts, wiry
6
+ // tufts and the AO bake and floors ear/tusk thickness at about a cell so they survive. Vertex AO is
7
+ // baked across all parts at LOD0/1 (creases, under the jaw, eye sockets).
8
+
9
+ import {
10
+ type BuildContext,
11
+ type Mesh,
12
+ merge,
13
+ place,
14
+ type Rng,
15
+ Ts23dError,
16
+ type Vec3,
17
+ } from '../core/index.ts';
18
+ import { bakeAo } from '../passes/index.ts';
19
+ import { sculptField } from '../primitives/index.ts';
20
+ import { beardEdits, browEdits } from './head-beard.ts';
21
+ import { bustEdits } from './head-bust.ts';
22
+ import { earEdits, paintEdits, tuskEdits, wartEdits } from './head-extras.ts';
23
+ import { primaryEdits, skullEdits } from './head-face.ts';
24
+ import { hairEdits } from './head-hair.ts';
25
+ import { type HeadLayout, headLayout } from './head-layout.ts';
26
+ import { bustMesh, eyeMesh, type HeadEditSet, headAndJaw } from './head-mesh.ts';
27
+ import { checkHead, type HeadParams } from './head-params.ts';
28
+ import { HEADS, type HeadPreset } from './head-presets.ts';
29
+ import { cutField, frameOf } from './head-torso.ts';
30
+
31
+ /** Overrides: top-level keys replace, nested objects merge one level (`{ jaw: { underbite: 0.1 } }`). */
32
+ export type HeadOverrides = { readonly [K in keyof HeadParams]?: Partial<HeadParams[K]> };
33
+
34
+ export interface HeadOptions {
35
+ readonly race?: HeadPreset | HeadParams;
36
+ readonly params?: HeadOverrides;
37
+ /** Part-name prefix: `orc` → `orc_head`, `orc_jaw`, `orc_eye_l` … Default none. */
38
+ readonly prefix?: string;
39
+ /** Where the bust base sits, metres. */
40
+ readonly at?: Vec3;
41
+ /** Baked jaw motion (degrees either side of rest, 0 = static pivot). Default 0. */
42
+ readonly jawSway?: number;
43
+ }
44
+
45
+ export interface HeadInfo {
46
+ readonly parts: { bust: string; head: string; jaw: string; eyeL: string; eyeR: string };
47
+ /** Overall height of bust + head, metres. */
48
+ readonly height: number;
49
+ readonly params: HeadParams;
50
+ }
51
+
52
+ export function headParams(o: Pick<HeadOptions, 'race' | 'params'> = {}): HeadParams {
53
+ const r = o.race ?? 'human';
54
+ const base = typeof r === 'string' ? HEADS[r] : r;
55
+ if (!base)
56
+ throw new Ts23dError('T3D_RECIPE_INVALID', {
57
+ cause: `unknown head race '${String(r)}'`,
58
+ fix: `use one of ${Object.keys(HEADS).join(', ')} or pass a HeadParams object`,
59
+ });
60
+ const out: Record<string, unknown> = { ...base };
61
+ for (const [k, v] of Object.entries(o.params ?? {})) {
62
+ const was = out[k];
63
+ out[k] =
64
+ v && typeof v === 'object' && !Array.isArray(v) && was && typeof was === 'object'
65
+ ? { ...was, ...v }
66
+ : v;
67
+ }
68
+ return checkHead(out as unknown as HeadParams);
69
+ }
70
+
71
+ /** Moves the eyes forward/back so they sit in the face surface: probe the brain case + face
72
+ * plate along −X at the eye, then sink the eyeball by `brow.overhang`. */
73
+ function seatEyes(p: HeadParams, l: HeadLayout): HeadLayout {
74
+ const f = sculptField(primaryEdits(l), { color: p.colors.skin }).field;
75
+ const [, ey, ez] = l.eye;
76
+ let x = l.faceX + 0.4;
77
+ for (let i = 0; i < 400 && f([x, ey, ez]) > 0; i++) x -= 0.002;
78
+ const r = l.eyeR;
79
+ return { ...l, eye: [x - r * 0.7 - p.brow.overhang * 0.3, ey, ez] };
80
+ }
81
+
82
+ /** The bust's cell as a multiple of the head's (the bust carries less detail). */
83
+ const BUST_CELL = 1.6;
84
+
85
+ /** Sculpt cell edge per LOD, in head units (× height = metres). */
86
+ export const HEAD_RES = [0.02, 0.045, 0.14] as const;
87
+
88
+ /** Every edit list of a head, in head units (pure: same params + rng state → same edits). */
89
+ export function headEdits(
90
+ p: HeadParams,
91
+ rng: Rng,
92
+ lod: 0 | 1 | 2 = 0,
93
+ ): HeadEditSet & { layout: HeadLayout } {
94
+ const l = seatEyes(p, headLayout(p));
95
+ const res = HEAD_RES[lod];
96
+ const warts = lod < 2 ? wartEdits(p, l, rng) : [];
97
+ const hair = lod < 2 || p.hair !== 'tufts' ? hairEdits(p, l) : [];
98
+ return {
99
+ layout: l,
100
+ skull: [
101
+ ...skullEdits(p, l, res),
102
+ ...hair,
103
+ ...browEdits(p, l),
104
+ ...beardEdits(p, l),
105
+ ...warts,
106
+ ...paintEdits(p, l),
107
+ ],
108
+ ears: earEdits(p, l, res * 0.7),
109
+ tusks: tuskEdits(p, l, res * 0.45),
110
+ bust: bustEdits(p, l, res * BUST_CELL),
111
+ bustCut: cutField(frameOf(p, l)),
112
+ };
113
+ }
114
+
115
+ export function head(ctx: BuildContext, o: HeadOptions = {}): HeadInfo {
116
+ const p = headParams(o);
117
+ const set = headEdits(p, ctx.rng.fork('head'), ctx.lod);
118
+ const l = set.layout;
119
+ const H = p.height;
120
+ const res = HEAD_RES[ctx.lod];
121
+ const { head: hm, jaw: jm } = headAndJaw(p, l, set, res);
122
+ const bm = bustMesh(p, set, res * BUST_CELL);
123
+ const eye = eyeMesh(p, l.eyeR, l.eyeR / ctx.pick([7, 3.5, 2.2]));
124
+ const [ox, oy, oz] = o.at ?? [0, 0, 0];
125
+ const toWorld = (m: Mesh, at: Vec3 = [0, 0, 0]): Mesh =>
126
+ place(m, { scale: H, at: [ox + at[0] * H, oy + at[1] * H, oz + (at[2] - l.base) * H] });
127
+ // Coarse LODs: the coarse socket would swallow the eyeball, so it sits a little proud.
128
+ const ex = l.eye[0] + (ctx.lod > 0 ? res * 0.4 : 0);
129
+ const [, ey, ez] = l.eye;
130
+ const world = {
131
+ bust: toWorld(bm),
132
+ head: toWorld(hm),
133
+ jaw: toWorld(jm),
134
+ eye_l: toWorld(eye, [ex, ey, ez]),
135
+ eye_r: toWorld(eye, [ex, -ey, ez]),
136
+ };
137
+ // AO occluders leave out the other half of the jaw split: its cut face lies flush against ours
138
+ // and would draw a dark line along the seam.
139
+ const occ = (skip: Mesh): Mesh => merge(Object.values(world).filter((m) => m !== skip));
140
+ const occHead = occ(world.jaw);
141
+ const occJaw = occ(world.head);
142
+ const ao = (m: Mesh, by: Mesh = occHead): Mesh =>
143
+ ctx.lod < 2
144
+ ? bakeAo(m, { samples: ctx.pick([16, 10, 8]), radius: (0.12 * H) / 0.23, strength: 0.55 }, by)
145
+ : m;
146
+ const pre = o.prefix ? `${o.prefix}_` : '';
147
+ const pivot = (x: number, y: number, z: number): Vec3 => [
148
+ ox + x * H,
149
+ oy + y * H,
150
+ oz + (z - l.base) * H,
151
+ ];
152
+ const local = (m: Mesh, at: Vec3, by?: Mesh): Mesh =>
153
+ place(ao(m, by), { at: [-at[0], -at[1], -at[2]] });
154
+ const hinge = pivot(l.hinge[0], 0, l.hinge[1]);
155
+ const eyeL = pivot(ex, ey, ez);
156
+ const eyeR = pivot(ex, -ey, ez);
157
+ const names = {
158
+ bust: `${pre}bust`,
159
+ head: `${pre}head`,
160
+ jaw: `${pre}jaw`,
161
+ eyeL: `${pre}eye_l`,
162
+ eyeR: `${pre}eye_r`,
163
+ };
164
+ ctx.part(names.bust, ao(world.bust));
165
+ ctx.part(names.head, ao(world.head));
166
+ const sway = o.jawSway ?? 0;
167
+ ctx.part(names.jaw, local(world.jaw, hinge, occJaw), {
168
+ at: hinge,
169
+ ...(sway > 0
170
+ ? { motion: { kind: 'sway', axis: [0, 1, 0], degrees: sway, hz: 0.5 }, clearOf: [names.head] }
171
+ : {}),
172
+ });
173
+ ctx.part(names.eyeL, local(world.eye_l, eyeL), { at: eyeL });
174
+ ctx.part(names.eyeR, local(world.eye_r, eyeR), { at: eyeR });
175
+ return { parts: names, height: (l.crown - l.base) * H, params: p };
176
+ }