@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,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,171 @@
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
+
30
+ /** Overrides: top-level keys replace, nested objects merge one level (`{ jaw: { underbite: 0.1 } }`). */
31
+ export type HeadOverrides = { readonly [K in keyof HeadParams]?: Partial<HeadParams[K]> };
32
+
33
+ export interface HeadOptions {
34
+ readonly race?: HeadPreset | HeadParams;
35
+ readonly params?: HeadOverrides;
36
+ /** Part-name prefix: `orc` → `orc_head`, `orc_jaw`, `orc_eye_l` … Default none. */
37
+ readonly prefix?: string;
38
+ /** Where the bust base sits, metres. */
39
+ readonly at?: Vec3;
40
+ /** Baked jaw motion (degrees either side of rest, 0 = static pivot). Default 0. */
41
+ readonly jawSway?: number;
42
+ }
43
+
44
+ export interface HeadInfo {
45
+ readonly parts: { bust: string; head: string; jaw: string; eyeL: string; eyeR: string };
46
+ /** Overall height of bust + head, metres. */
47
+ readonly height: number;
48
+ readonly params: HeadParams;
49
+ }
50
+
51
+ export function headParams(o: Pick<HeadOptions, 'race' | 'params'> = {}): HeadParams {
52
+ const r = o.race ?? 'human';
53
+ const base = typeof r === 'string' ? HEADS[r] : r;
54
+ if (!base)
55
+ throw new Ts23dError('T3D_RECIPE_INVALID', {
56
+ cause: `unknown head race '${String(r)}'`,
57
+ fix: `use one of ${Object.keys(HEADS).join(', ')} or pass a HeadParams object`,
58
+ });
59
+ const out: Record<string, unknown> = { ...base };
60
+ for (const [k, v] of Object.entries(o.params ?? {})) {
61
+ const was = out[k];
62
+ out[k] =
63
+ v && typeof v === 'object' && !Array.isArray(v) && was && typeof was === 'object'
64
+ ? { ...was, ...v }
65
+ : v;
66
+ }
67
+ return checkHead(out as unknown as HeadParams);
68
+ }
69
+
70
+ /** Moves the eyes forward/back so they sit in the face surface: probe the brain case + face
71
+ * plate along −X at the eye, then sink the eyeball by `brow.overhang`. */
72
+ function seatEyes(p: HeadParams, l: HeadLayout): HeadLayout {
73
+ const f = sculptField(primaryEdits(l), { color: p.colors.skin }).field;
74
+ const [, ey, ez] = l.eye;
75
+ let x = l.faceX + 0.4;
76
+ for (let i = 0; i < 400 && f([x, ey, ez]) > 0; i++) x -= 0.002;
77
+ const r = l.eyeR;
78
+ return { ...l, eye: [x - r * 0.7 - p.brow.overhang * 0.3, ey, ez] };
79
+ }
80
+
81
+ /** Sculpt cell edge per LOD, in head units (× height = metres). */
82
+ export const HEAD_RES = [0.02, 0.045, 0.14] as const;
83
+
84
+ /** Every edit list of a head, in head units (pure: same params + rng state → same edits). */
85
+ export function headEdits(
86
+ p: HeadParams,
87
+ rng: Rng,
88
+ lod: 0 | 1 | 2 = 0,
89
+ ): HeadEditSet & { layout: HeadLayout } {
90
+ const l = seatEyes(p, headLayout(p));
91
+ const res = HEAD_RES[lod];
92
+ const warts = lod < 2 ? wartEdits(p, l, rng) : [];
93
+ const hair = lod < 2 || p.hair !== 'tufts' ? hairEdits(p, l) : [];
94
+ return {
95
+ layout: l,
96
+ skull: [
97
+ ...skullEdits(p, l),
98
+ ...hair,
99
+ ...browEdits(p, l),
100
+ ...beardEdits(p, l),
101
+ ...warts,
102
+ ...paintEdits(p, l),
103
+ ],
104
+ ears: earEdits(p, l, res * 0.7),
105
+ tusks: tuskEdits(p, l, res * 0.45),
106
+ bust: bustEdits(p, l),
107
+ };
108
+ }
109
+
110
+ export function head(ctx: BuildContext, o: HeadOptions = {}): HeadInfo {
111
+ const p = headParams(o);
112
+ const set = headEdits(p, ctx.rng.fork('head'), ctx.lod);
113
+ const l = set.layout;
114
+ const H = p.height;
115
+ const res = HEAD_RES[ctx.lod];
116
+ const { head: hm, jaw: jm } = headAndJaw(p, l, set, res);
117
+ const bm = bustMesh(p, set, res * 1.6);
118
+ const eye = eyeMesh(p, l.eyeR, l.eyeR / ctx.pick([7, 3.5, 2.2]));
119
+ const [ox, oy, oz] = o.at ?? [0, 0, 0];
120
+ const toWorld = (m: Mesh, at: Vec3 = [0, 0, 0]): Mesh =>
121
+ place(m, { scale: H, at: [ox + at[0] * H, oy + at[1] * H, oz + (at[2] - l.base) * H] });
122
+ // Coarse LODs: the coarse socket would swallow the eyeball, so it sits a little proud.
123
+ const ex = l.eye[0] + (ctx.lod > 0 ? res * 0.4 : 0);
124
+ const [, ey, ez] = l.eye;
125
+ const world = {
126
+ bust: toWorld(bm),
127
+ head: toWorld(hm),
128
+ jaw: toWorld(jm),
129
+ eye_l: toWorld(eye, [ex, ey, ez]),
130
+ eye_r: toWorld(eye, [ex, -ey, ez]),
131
+ };
132
+ // AO occluders leave out the other half of the jaw split: its cut face lies flush against ours
133
+ // and would draw a dark line along the seam.
134
+ const occ = (skip: Mesh): Mesh => merge(Object.values(world).filter((m) => m !== skip));
135
+ const occHead = occ(world.jaw);
136
+ const occJaw = occ(world.head);
137
+ const ao = (m: Mesh, by: Mesh = occHead): Mesh =>
138
+ ctx.lod < 2
139
+ ? bakeAo(m, { samples: ctx.pick([16, 10, 8]), radius: (0.12 * H) / 0.23, strength: 0.55 }, by)
140
+ : m;
141
+ const pre = o.prefix ? `${o.prefix}_` : '';
142
+ const pivot = (x: number, y: number, z: number): Vec3 => [
143
+ ox + x * H,
144
+ oy + y * H,
145
+ oz + (z - l.base) * H,
146
+ ];
147
+ const local = (m: Mesh, at: Vec3, by?: Mesh): Mesh =>
148
+ place(ao(m, by), { at: [-at[0], -at[1], -at[2]] });
149
+ const hinge = pivot(l.hinge[0], 0, l.hinge[1]);
150
+ const eyeL = pivot(ex, ey, ez);
151
+ const eyeR = pivot(ex, -ey, ez);
152
+ const names = {
153
+ bust: `${pre}bust`,
154
+ head: `${pre}head`,
155
+ jaw: `${pre}jaw`,
156
+ eyeL: `${pre}eye_l`,
157
+ eyeR: `${pre}eye_r`,
158
+ };
159
+ ctx.part(names.bust, ao(world.bust));
160
+ ctx.part(names.head, ao(world.head));
161
+ const sway = o.jawSway ?? 0;
162
+ ctx.part(names.jaw, local(world.jaw, hinge, occJaw), {
163
+ at: hinge,
164
+ ...(sway > 0
165
+ ? { motion: { kind: 'sway', axis: [0, 1, 0], degrees: sway, hz: 0.5 }, clearOf: [names.head] }
166
+ : {}),
167
+ });
168
+ ctx.part(names.eyeL, local(world.eye_l, eyeL), { at: eyeL });
169
+ ctx.part(names.eyeR, local(world.eye_r, eyeR), { at: eyeR });
170
+ return { parts: names, height: (l.crown - l.base) * H, params: p };
171
+ }
@@ -0,0 +1,160 @@
1
+ // A hull's lines plan as functions, no mesh: deck-edge plan (half-breadth), sheer (deck-edge
2
+ // height), keel/stem/counter profile and the section shape at any station. Waterline at z = 0,
3
+ // bow toward +X, x = 0 amidships. Sections are superellipse quarters (n = 2 round bilge, 4 hard
4
+ // bilge), going V toward the ends; the stem is where the keel line rises to meet the sheer.
5
+
6
+ import { Ts23dError, type Vec3 } from '../core/index.ts';
7
+
8
+ export interface HullColors {
9
+ /** Above the boot-top. */
10
+ readonly topsides: Vec3;
11
+ /** Below the waterline (antifouling). */
12
+ readonly bottom: Vec3;
13
+ /** The boot-top band at the waterline. Default near-black. */
14
+ readonly boot?: Vec3;
15
+ readonly deck: Vec3;
16
+ /** Inside of the bulwarks and the cap rail. Default = topsides. */
17
+ readonly bulwark?: Vec3;
18
+ }
19
+
20
+ export interface HullOptions {
21
+ /** Length overall at the deck, metres. */
22
+ readonly length: number;
23
+ /** Maximum beam at the deck edge. */
24
+ readonly beam: number;
25
+ /** Keel below the waterline. */
26
+ readonly draft: number;
27
+ /** Deck edge above the waterline amidships (lowest point of the sheer). */
28
+ readonly freeboard: number;
29
+ /** Sheer rise of the deck edge at [bow, stern] above amidships. Default [0.035, 0.012]·L. */
30
+ readonly sheer?: readonly [number, number];
31
+ /** 'round' (cruiser / tug) or 'transom' (flat stern). Default 'round'. */
32
+ readonly stern?: 'round' | 'transom';
33
+ /** Transom half-width as a share of the half-beam. Default 0.75. */
34
+ readonly transomWidth?: number;
35
+ /** Bilge: section superellipse exponent, 1.5 (soft V) … 5 (barge). Default 3. */
36
+ readonly fullness?: number;
37
+ /** Plan entrance exponent at the bow: 1.4 sharp … 3 bluff. Default 2. */
38
+ readonly entrance?: number;
39
+ /** Deck-edge breadth over waterline breadth − 1. Default 0.06. */
40
+ readonly flare?: number;
41
+ /** Bottom deadrise, degrees. Default 5. */
42
+ readonly deadriseDeg?: number;
43
+ /** Bulwark height above the deck, metres; 0 = none. Default 0. */
44
+ readonly bulwark?: number;
45
+ /** Deck camber at the centreline, metres. Default beam / 50. */
46
+ readonly camber?: number;
47
+ /** Height of the boot-top band above the waterline. Default 0.3. */
48
+ readonly bootTop?: number;
49
+ readonly colors: HullColors;
50
+ /** A rubbing strake / fender along each side just below the sheer (LOD0/1). */
51
+ readonly fender?: { readonly color: Vec3; readonly radius?: number };
52
+ /** Waterline amidships. Default [0, 0, 0]. */
53
+ readonly at?: Vec3;
54
+ }
55
+
56
+ export interface HullLines {
57
+ readonly L: number;
58
+ /** Deck-edge half-breadth at x (outer hull). */
59
+ readonly halfBreadth: (x: number) => number;
60
+ /** Deck-edge height at x (sheer line), relative to the waterline. */
61
+ readonly deck: (x: number) => number;
62
+ /** Keel / stem / counter height at x. */
63
+ readonly keel: (x: number) => number;
64
+ /** Section point at x for θ ∈ [0, π/2] (0 = keel, π/2 = deck edge), starboard (y ≥ 0). */
65
+ readonly section: (x: number, theta: number) => [number, number];
66
+ }
67
+
68
+ const bad = (cause: string, fix: string): Ts23dError =>
69
+ new Ts23dError('T3D_RECIPE_INVALID', { cause, fix });
70
+
71
+ export function validateHull(o: HullOptions): void {
72
+ const dims = [o.length, o.beam, o.draft, o.freeboard];
73
+ if (!dims.every((v) => v > 0 && Number.isFinite(v)))
74
+ throw bad(
75
+ `hull length/beam/draft/freeboard = ${dims.join(' / ')}`,
76
+ 'give all four in metres, > 0 (a harbour tug: 30 / 9 / 4 / 1.2)',
77
+ );
78
+ if (o.beam > o.length * 0.8)
79
+ throw bad(
80
+ `hull beam ${o.beam} is over 0.8 × length ${o.length}`,
81
+ 'check the numbers: length/beam is 2.5–4 for tugs, 6–9 for ships',
82
+ );
83
+ const n = o.fullness ?? 3;
84
+ if (!(n >= 1.2 && n <= 8)) throw bad(`hull fullness is ${n}`, 'use 1.5 (soft V) … 5 (barge)');
85
+ const tw = o.transomWidth ?? 0.75;
86
+ if (!(tw > 0 && tw <= 1))
87
+ throw bad(`hull transomWidth is ${tw}`, 'use a share 0.3–1 of the half-beam');
88
+ }
89
+
90
+ const smooth = (t: number): number => (t <= 0 ? 0 : t >= 1 ? 1 : t * t * (3 - 2 * t));
91
+
92
+ export function hullLines(o: HullOptions): HullLines {
93
+ validateHull(o);
94
+ const L = o.length;
95
+ const half = L / 2;
96
+ const [sb, ss] = o.sheer ?? [0.035 * L, 0.012 * L];
97
+ const round = (o.stern ?? 'round') === 'round';
98
+ const tw = o.transomWidth ?? 0.75;
99
+ const pe = o.entrance ?? 2;
100
+ const n = o.fullness ?? 3;
101
+ const flare = o.flare ?? 0.06;
102
+ const dead = Math.tan(((o.deadriseDeg ?? 5) * Math.PI) / 180);
103
+ // Parallel middle body and where the keel starts to rise, as shares of L.
104
+ const xf = 0.1 * L;
105
+ const xa = -0.15 * L;
106
+ const kf = 0.22 * L;
107
+ const ka = -0.25 * L;
108
+ const deck = (x: number): number => {
109
+ const u = x / half;
110
+ return o.freeboard + (u > 0 ? sb : ss) * u * u;
111
+ };
112
+ const plan = (x: number): number => {
113
+ if (x > xf) return 1 - ((x - xf) / (half - xf)) ** pe;
114
+ if (x < xa) {
115
+ const v = Math.min(1, (xa - x) / (xa + half));
116
+ return round ? Math.sqrt(Math.max(0, 1 - v * v)) : 1 - (1 - tw) * v * v;
117
+ }
118
+ return 1;
119
+ };
120
+ const halfBreadth = (x: number): number => (o.beam / 2) * Math.max(0, plan(x));
121
+ const counter = round ? 0.35 * o.freeboard : 0.15 * o.freeboard;
122
+ const keel = (x: number): number => {
123
+ if (x > kf) {
124
+ const u = Math.min(1, (x - kf) / (half - kf));
125
+ return -o.draft + (deck(half) + o.draft) * u ** 2;
126
+ }
127
+ if (x < ka) {
128
+ const v = Math.min(1, (ka - x) / (ka + half));
129
+ return -o.draft + (counter + o.draft) * v ** 1.6;
130
+ }
131
+ return -o.draft;
132
+ };
133
+ const section = (x: number, theta: number): [number, number] => {
134
+ const f = plan(x);
135
+ // Fine ends go V-sectioned; the middle body keeps the full bilge.
136
+ const ns = 1.2 + (n - 1.2) * smooth(f / 0.8);
137
+ const s = Math.sin(theta) ** (2 / ns);
138
+ const c = Math.cos(theta) ** (2 / ns);
139
+ const zk = keel(x);
140
+ const zd = deck(x);
141
+ const zf = 1 - c;
142
+ const y = halfBreadth(x) * (1 - flare + flare * zf * zf) * s;
143
+ return [y, zk + (zd - zk) * zf + dead * y * c];
144
+ };
145
+ return { L, halfBreadth, deck, keel, section };
146
+ }
147
+
148
+ /** θ where the section crosses height z (bisection; the section rises monotonically). */
149
+ export function thetaAt(lines: HullLines, x: number, z: number): number {
150
+ let lo = 0;
151
+ let hi = Math.PI / 2;
152
+ if (lines.section(x, lo)[1] >= z) return lo;
153
+ if (lines.section(x, hi)[1] <= z) return hi;
154
+ for (let i = 0; i < 40; i++) {
155
+ const mid = (lo + hi) / 2;
156
+ if (lines.section(x, mid)[1] < z) lo = mid;
157
+ else hi = mid;
158
+ }
159
+ return (lo + hi) / 2;
160
+ }
@@ -0,0 +1,83 @@
1
+ // Skinning for hull-like surfaces: a station × column grid of points becomes quads with smooth
2
+ // normals (central differences; collapsed rows such as a stem head borrow their neighbour's), and
3
+ // any column range can be emitted as its own colour band, mirrored to port for a symmetric hull.
4
+
5
+ import type { MeshBuilder, Vec3 } from '../core/index.ts';
6
+ import { stripRect } from '../material/index.ts';
7
+
8
+ export type Grid = Vec3[][];
9
+ const sub3 = (a: Vec3, b: Vec3): Vec3 => [a[0] - b[0], a[1] - b[1], a[2] - b[2]];
10
+ const cross3 = (a: Vec3, b: Vec3): Vec3 => [
11
+ a[1] * b[2] - a[2] * b[1],
12
+ a[2] * b[0] - a[0] * b[2],
13
+ a[0] * b[1] - a[1] * b[0],
14
+ ];
15
+
16
+ /** Smooth normals over a station × column grid (outward = tj × ti for the starboard side). */
17
+ export function normalsOf(g: Grid): Grid {
18
+ const R = g.length;
19
+ const out: Grid = g.map((row, i) =>
20
+ row.map((p, j) => {
21
+ const r = (k: number): Vec3[] => g[Math.max(0, Math.min(R - 1, k))] ?? row;
22
+ const ti = sub3(r(i + 1)[j] ?? p, r(i - 1)[j] ?? p);
23
+ const tj = sub3(row[Math.min(row.length - 1, j + 1)] ?? p, row[Math.max(0, j - 1)] ?? p);
24
+ const n = cross3(tj, ti);
25
+ const l = Math.hypot(...n);
26
+ return l < 1e-9 ? ([0, 0, 0] as Vec3) : ([n[0] / l, n[1] / l, n[2] / l] as Vec3);
27
+ }),
28
+ );
29
+ // Collapsed rows (stem head, round stern post) borrow the nearest row's normals.
30
+ for (let i = 0; i < R; i++) {
31
+ const row = out[i] ?? [];
32
+ row.forEach((n, j) => {
33
+ if (n[0] || n[1] || n[2]) return;
34
+ for (let d = 1; d < R; d++) {
35
+ const m = out[i - d]?.[j] ?? out[i + d]?.[j];
36
+ if (m && (m[0] || m[1] || m[2])) {
37
+ row[j] = m;
38
+ return;
39
+ }
40
+ }
41
+ });
42
+ }
43
+ return out;
44
+ }
45
+
46
+ /** Emit columns [j0, j1] of a starboard grid and its port mirror. */
47
+ export function emitBand(
48
+ b: MeshBuilder,
49
+ g: Grid,
50
+ nrm: Grid,
51
+ j0: number,
52
+ j1: number,
53
+ color: Vec3,
54
+ mirror: boolean,
55
+ ): void {
56
+ const rect = stripRect('plain');
57
+ for (const side of mirror ? [1, -1] : [1]) {
58
+ const ids = g.map((row, i) =>
59
+ row.slice(j0, j1 + 1).map((p, k) => {
60
+ const n = nrm[i]?.[j0 + k] ?? [0, 0, 1];
61
+ return b.vertex({
62
+ p: [p[0], p[1] * side, p[2]],
63
+ n: [n[0], n[1] * side, n[2]],
64
+ uv: [
65
+ rect.u0 + p[0] / rect.metres,
66
+ rect.v0 + (k / Math.max(1, j1 - j0)) * (rect.v1 - rect.v0),
67
+ ],
68
+ color,
69
+ });
70
+ }),
71
+ );
72
+ for (let i = 0; i + 1 < ids.length; i++) {
73
+ for (let k = 0; k < j1 - j0; k++) {
74
+ const a = ids[i]?.[k] ?? 0;
75
+ const c = ids[i]?.[k + 1] ?? 0;
76
+ const e = ids[i + 1]?.[k + 1] ?? 0;
77
+ const d = ids[i + 1]?.[k] ?? 0;
78
+ if (side > 0) b.quad(a, c, e, d);
79
+ else b.quad(a, d, e, c);
80
+ }
81
+ }
82
+ }
83
+ }