@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,40 @@
1
+ // Stamp a supplied PNG into a cell canvas: bilinear resample to the cell, flipped so the image's
2
+ // top row lands at world-up (high V, where box/planar put a face's top). Flat relief, the
3
+ // recipe-free roughness default; the image's own colour is kept (vertex colour still multiplies).
4
+
5
+ import type { Canvas } from './canvas.ts';
6
+ import { decodePng } from './decode-png.ts';
7
+
8
+ export const STAMP_ROUGHNESS = 0.6;
9
+
10
+ export function stampImage(c: Canvas, png: Uint8Array): void {
11
+ const img = decodePng(png);
12
+ const out = new Uint8Array(c.w * c.h * 4);
13
+ for (let y = 0; y < c.h; y++) {
14
+ const sy = Math.min(
15
+ img.height - 1,
16
+ Math.max(0, ((c.h - 1 - y + 0.5) / c.h) * img.height - 0.5),
17
+ );
18
+ const y0 = Math.floor(sy);
19
+ const y1 = Math.min(img.height - 1, y0 + 1);
20
+ const fy = sy - y0;
21
+ for (let x = 0; x < c.w; x++) {
22
+ const sx = Math.min(img.width - 1, Math.max(0, ((x + 0.5) / c.w) * img.width - 0.5));
23
+ const x0 = Math.floor(sx);
24
+ const x1 = Math.min(img.width - 1, x0 + 1);
25
+ const fx = sx - x0;
26
+ for (let k = 0; k < 4; k++) {
27
+ const a = img.data[(y0 * img.width + x0) * 4 + k] ?? 0;
28
+ const b = img.data[(y0 * img.width + x1) * 4 + k] ?? 0;
29
+ const d = img.data[(y1 * img.width + x0) * 4 + k] ?? 0;
30
+ const e = img.data[(y1 * img.width + x1) * 4 + k] ?? 0;
31
+ const top = a + (b - a) * fx;
32
+ out[(y * c.w + x) * 4 + k] = Math.round(top + (d + (e - d) * fx - top) * fy);
33
+ }
34
+ }
35
+ }
36
+ c.rgba = out;
37
+ c.height.fill(0);
38
+ c.rough.fill(STAMP_ROUGHNESS);
39
+ c.metal.fill(0);
40
+ }
@@ -0,0 +1,48 @@
1
+ // Dispatch a StripRecipe to its painter, then the shared grime pass (`params.grime`, 0..1), then
2
+ // the recipe's own `paint` layer (project materials), checked for finite output. A `texture`
3
+ // recipe paints from its cached CC0 image set instead of a kind (no grime: the photo has its own).
4
+
5
+ import { paintBond } from './bond.ts';
6
+ import type { Canvas } from './canvas.ts';
7
+ import { param } from './canvas.ts';
8
+ import { paintConcrete } from './concrete.ts';
9
+ import { paintLeather, paintWeave } from './fabric.ts';
10
+ import { paintImageStrip } from './image-strip.ts';
11
+ import { checkFinite, paintKit } from './kit.ts';
12
+ import { grime } from './masks.ts';
13
+ import { paintMetal } from './metal.ts';
14
+ import { paintPlanks } from './planks.ts';
15
+ import { paintPlates } from './plates.ts';
16
+ import { paintSlate } from './slate.ts';
17
+ import { paintFlat, paintGlass, paintNoise } from './surface.ts';
18
+ import type { StripKind, StripRecipe } from './types.ts';
19
+
20
+ type Painter = (c: Canvas, r: StripRecipe, seed: number) => void;
21
+
22
+ const PAINTERS: Readonly<Record<StripKind, Painter>> = {
23
+ bond: paintBond,
24
+ planks: paintPlanks,
25
+ plates: paintPlates,
26
+ slate: paintSlate,
27
+ concrete: paintConcrete,
28
+ metal: paintMetal,
29
+ weave: paintWeave,
30
+ leather: paintLeather,
31
+ noise: paintNoise,
32
+ glass: paintGlass,
33
+ flat: (c, r) => paintFlat(c, r),
34
+ };
35
+
36
+ export function paintStrip(c: Canvas, r: StripRecipe, seed: number, where = 'strip'): void {
37
+ if (r.texture) paintImageStrip(c, r, r.texture, where);
38
+ else {
39
+ if (r.albedo || r.albedo2)
40
+ c.colours = [r.albedo ?? [1, 1, 1], r.albedo2 ?? r.albedo ?? [1, 1, 1]];
41
+ PAINTERS[r.kind](c, r, seed);
42
+ if (r.kind !== 'glass' && r.kind !== 'flat') grime(c, param(r.params, 'grime', 0), seed + 101);
43
+ }
44
+ if (r.paint) {
45
+ r.paint(c, paintKit(c, seed + 211));
46
+ checkFinite(c, where);
47
+ }
48
+ }
@@ -0,0 +1,60 @@
1
+ // The simple kinds: 'noise' (a quiet painted/plain surface: soft tone and roughness mottling,
2
+ // faint relief), 'flat' (constant) and 'glass' (near-white, very smooth, faint ripples, smudges,
3
+ // grime toward the bottom edge).
4
+ // params (noise): tone 0.93 · mottle 0.03 · relief 0.0002 · size 0.3
5
+ // params (glass): ripple 0.0002 · smudge 0.08 · grime 0.4
6
+
7
+ import { type Canvas, noiseAt, param } from './canvas.ts';
8
+ import { grime } from './masks.ts';
9
+ import type { StripRecipe } from './types.ts';
10
+
11
+ export function paintFlat(c: Canvas, r: StripRecipe): void {
12
+ c.tone.fill(param(r.params, 'tone', 1));
13
+ c.rough.fill(r.roughness);
14
+ c.metal.fill(r.metalness ?? 0);
15
+ c.height.fill(0);
16
+ }
17
+
18
+ export function paintNoise(c: Canvas, r: StripRecipe, seed: number): void {
19
+ const tone = param(r.params, 'tone', 0.93);
20
+ const mottle = param(r.params, 'mottle', 0.03);
21
+ const relief = param(r.params, 'relief', 0.0002);
22
+ const size = param(r.params, 'size', 0.3);
23
+ const pu = c.metresU / c.w;
24
+ const pv = c.metresV / c.h;
25
+ const metal = r.metalness ?? 0;
26
+ for (let y = 0; y < c.h; y++) {
27
+ const ym = (y + 0.5) * pv;
28
+ for (let x = 0; x < c.w; x++) {
29
+ const xm = (x + 0.5) * pu;
30
+ const big = noiseAt(c, xm, ym, size, size, seed, 4);
31
+ const fine = noiseAt(c, xm, ym, 0.01, 0.01, seed + 1, 2);
32
+ const i = y * c.w + x;
33
+ c.tone[i] = tone + mottle * big;
34
+ c.rough[i] = r.roughness + 0.06 * big + 0.03 * fine;
35
+ c.metal[i] = metal;
36
+ c.height[i] = relief * (big + 0.3 * fine);
37
+ }
38
+ }
39
+ }
40
+
41
+ export function paintGlass(c: Canvas, r: StripRecipe, seed: number): void {
42
+ const ripple = param(r.params, 'ripple', 0.0002);
43
+ const smudge = param(r.params, 'smudge', 0.08);
44
+ const pu = c.metresU / c.w;
45
+ const pv = c.metresV / c.h;
46
+ for (let y = 0; y < c.h; y++) {
47
+ const ym = (y + 0.5) * pv;
48
+ for (let x = 0; x < c.w; x++) {
49
+ const xm = (x + 0.5) * pu;
50
+ const wave = noiseAt(c, xm, ym, 0.15, 0.4, seed, 3);
51
+ const smear = noiseAt(c, xm, ym, 0.08, 0.08, seed + 1, 4);
52
+ const i = y * c.w + x;
53
+ c.tone[i] = 0.92 + 0.02 * wave;
54
+ c.rough[i] = r.roughness + smudge * Math.max(0, smear);
55
+ c.metal[i] = 0;
56
+ c.height[i] = ripple * wave;
57
+ }
58
+ }
59
+ grime(c, param(r.params, 'grime', 0.4), seed + 9);
60
+ }
@@ -0,0 +1,94 @@
1
+ // The generator's public data contract (re-exported by generator.ts): what a sheet recipe says
2
+ // and what the painter returns.
3
+
4
+ import type { Vec3 } from '../../core/index.ts';
5
+ import type { TextureSource } from '../texture-source.ts';
6
+ import type { CellId, StripId } from '../trim-sheet.ts';
7
+ import type { Canvas } from './canvas.ts';
8
+
9
+ export interface PaintedSheet {
10
+ readonly size: number;
11
+ /** RGBA8, sRGB. Classic strips are greyscale (vertex colour carries hue); materials carry colour. */
12
+ readonly albedo: Uint8Array;
13
+ /** RGBA8, tangent-space (glTF / OpenGL, +Y toward decreasing V). */
14
+ readonly normal: Uint8Array;
15
+ /** R = occlusion, G = roughness, B = metalness. */
16
+ readonly orm: Uint8Array;
17
+ }
18
+
19
+ export type StripKind =
20
+ | 'bond'
21
+ | 'planks'
22
+ | 'plates'
23
+ | 'noise'
24
+ | 'flat'
25
+ | 'slate'
26
+ | 'concrete'
27
+ | 'glass'
28
+ | 'metal'
29
+ | 'weave'
30
+ | 'leather';
31
+
32
+ /**
33
+ * The surface a painter writes, one float per texel per layer, in real metres. Row y grows with
34
+ * world up (+V); strips wrap along x. Layers: `height` (metres, + is out), `tone` (linear
35
+ * brightness multiplier), `rough` and `metal` (0–1), `mix` (0 = albedo, 1 = albedo2), `occl`.
36
+ */
37
+ export type MaterialSurface = Canvas;
38
+
39
+ /** Helpers handed to a custom painter: seeded, tileable noise and smoothstep, all in metres. */
40
+ export interface PaintKit {
41
+ readonly seed: number;
42
+ /** Tileable fBm in [−1, 1] with feature size `sizeU` × `sizeV` metres; `salt` varies it. */
43
+ noise(
44
+ xm: number,
45
+ ym: number,
46
+ sizeU: number,
47
+ sizeV: number,
48
+ salt?: number,
49
+ octaves?: number,
50
+ ): number;
51
+ /** Integer hash in [0, 1). */
52
+ hash(a: number, b: number, salt?: number): number;
53
+ smooth(e0: number, e1: number, v: number): number;
54
+ /** Visit every texel: index into the layers and its centre in metres. */
55
+ each(fn: (i: number, xm: number, ym: number) => void): void;
56
+ }
57
+
58
+ /** A project painter: runs after the base `kind`, layering its own pattern over it. */
59
+ export type MaterialPainter = (s: MaterialSurface, kit: PaintKit) => void;
60
+
61
+ export interface StripRecipe {
62
+ readonly kind: StripKind;
63
+ /**
64
+ * World metres the strip's V span represents (its U span is always `stripRect().metres`).
65
+ * Pick the typical face height the strip is stretched over; texels are anisotropic if needed.
66
+ */
67
+ readonly scaleMetres: number;
68
+ readonly roughness: number;
69
+ readonly metalness?: number;
70
+ /** Brick/plank courses, rivet pitch, etc. — read per kind (see each painter's header). */
71
+ readonly params?: Readonly<Record<string, number>>;
72
+ /** Intrinsic linear albedo; omitted = white (greyscale strip, vertex colour carries hue). */
73
+ readonly albedo?: Vec3;
74
+ /** Second linear albedo the painter blends to through `mix` (latewood, rust, bare wood…). */
75
+ readonly albedo2?: Vec3;
76
+ /** Extra layer painted after `kind` (see `defineMaterial`). */
77
+ readonly paint?: MaterialPainter;
78
+ /** Paint from a cached CC0 texture set instead of `kind` (see texture-source.ts). */
79
+ readonly texture?: TextureSource;
80
+ }
81
+
82
+ /** A cell stamped from PNG bytes (8-bit grey/RGB/RGBA, non-interlaced), e.g. a kubeez.com decal. */
83
+ export interface CellImage {
84
+ readonly image: Uint8Array;
85
+ }
86
+
87
+ export interface SheetRecipe {
88
+ readonly seed: number;
89
+ readonly size?: 512 | 1024 | 2048;
90
+ /** Missing strips fall back to `DEFAULT_SHEET`'s recipe for that strip. */
91
+ readonly strips: Readonly<Partial<Record<StripId, StripRecipe>>>;
92
+ /** Cells painted from code, or stamped from a supplied image (e.g. a kubeez.com decal). */
93
+ readonly cells?: Readonly<Partial<Record<CellId, StripRecipe | CellImage>>>;
94
+ }
@@ -0,0 +1,74 @@
1
+ // Recipe validation: every bad input throws T3D_RECIPE_INVALID with the fix, before any painting.
2
+
3
+ import { Ts23dError } from '../../core/index.ts';
4
+ import { CELLS, STRIPS, stripRow } from '../trim-sheet.ts';
5
+ import type { CellImage, SheetRecipe, StripKind, StripRecipe } from './types.ts';
6
+
7
+ const KINDS: readonly StripKind[] = [
8
+ 'bond',
9
+ 'planks',
10
+ 'plates',
11
+ 'noise',
12
+ 'flat',
13
+ 'slate',
14
+ 'concrete',
15
+ 'glass',
16
+ 'metal',
17
+ 'weave',
18
+ 'leather',
19
+ ];
20
+ const SIZES = [512, 1024, 2048];
21
+
22
+ function fail(cause: string, fix: string): never {
23
+ throw new Ts23dError('T3D_RECIPE_INVALID', { cause: `sheet recipe: ${cause}`, fix });
24
+ }
25
+
26
+ const unit = (v: number): boolean => Number.isFinite(v) && v >= 0 && v <= 1;
27
+
28
+ export function validateStrip(where: string, r: StripRecipe): void {
29
+ if (!KINDS.includes(r.kind))
30
+ fail(`${where}.kind '${r.kind}' is unknown`, `use one of ${KINDS.join(' | ')}`);
31
+ if (!(Number.isFinite(r.scaleMetres) && r.scaleMetres > 0))
32
+ fail(
33
+ `${where}.scaleMetres is ${r.scaleMetres}`,
34
+ 'set the metres the strip V span represents, > 0 (e.g. 0.6 for brick)',
35
+ );
36
+ if (!unit(r.roughness)) fail(`${where}.roughness is ${r.roughness}`, 'roughness is 0..1');
37
+ if (r.metalness !== undefined && !unit(r.metalness))
38
+ fail(`${where}.metalness is ${r.metalness}`, 'metalness is 0..1');
39
+ for (const key of ['albedo', 'albedo2'] as const) {
40
+ const a = r[key];
41
+ if (a !== undefined && !(a.length === 3 && a.every(unit)))
42
+ fail(
43
+ `${where}.${key} is [${a}]`,
44
+ `${key} is linear RGB, three numbers 0..1 (use hex('#rrggbb'))`,
45
+ );
46
+ }
47
+ if (r.paint !== undefined && typeof r.paint !== 'function')
48
+ fail(`${where}.paint is not a function`, 'paint is (surface, kit) => void');
49
+ for (const [k, v] of Object.entries(r.params ?? {}))
50
+ if (!Number.isFinite(v)) fail(`${where}.params.${k} is ${v}`, 'params are finite numbers');
51
+ }
52
+
53
+ export function validateSheet(recipe: SheetRecipe): void {
54
+ if (!Number.isInteger(recipe.seed)) fail(`seed ${recipe.seed}`, 'seed is an integer');
55
+ if (recipe.size !== undefined && !SIZES.includes(recipe.size))
56
+ fail(`size ${recipe.size}`, 'size is 512, 1024 or 2048');
57
+ for (const [id, r] of Object.entries(recipe.strips)) {
58
+ if (stripRow(id) < 0)
59
+ fail(
60
+ `strip '${id}' is not in the layout`,
61
+ `strips are ${STRIPS.join(', ')} or a defineMaterial() id`,
62
+ );
63
+ if (r) validateStrip(`strips.${id}`, r);
64
+ }
65
+ for (const [id, e] of Object.entries(recipe.cells ?? {})) {
66
+ if (!(CELLS as readonly string[]).includes(id))
67
+ fail(`cell '${id}' is not in the layout`, `cells are ${CELLS.join(', ')}`);
68
+ if (!e) continue;
69
+ if ('image' in e) {
70
+ if (!((e as CellImage).image instanceof Uint8Array))
71
+ fail(`cells.${id}.image is not bytes`, 'pass PNG file bytes as a Uint8Array');
72
+ } else validateStrip(`cells.${id}`, e);
73
+ }
74
+ }
@@ -0,0 +1,46 @@
1
+ // Colour lives in vertex colour (linear RGB), multiplied over the greyscale trim sheet: a palette
2
+ // swap is a recipe edit, never a new texture.
3
+
4
+ import type { Vec3 } from '../core/index.ts';
5
+
6
+ export type Palette = Readonly<Record<string, Vec3>>;
7
+
8
+ /** sRGB hex → linear RGB, the space glTF vertex colours are in. */
9
+ export function hex(value: string): Vec3 {
10
+ const n = Number.parseInt(value.replace('#', ''), 16);
11
+ const lin = (c: number): number => {
12
+ const s = c / 255;
13
+ return s <= 0.04045 ? s / 12.92 : ((s + 0.055) / 1.055) ** 2.4;
14
+ };
15
+ return [lin((n >> 16) & 255), lin((n >> 8) & 255), lin(n & 255)];
16
+ }
17
+
18
+ /** Vertex colour for material strips that carry their own colour (oak, brass, rubber…). */
19
+ export const NATURAL: Vec3 = [1, 1, 1];
20
+
21
+ export function definePalette(entries: Readonly<Record<string, string>>): Palette {
22
+ return Object.fromEntries(Object.entries(entries).map(([k, v]) => [k, hex(v)]));
23
+ }
24
+
25
+ /** Era/region palettes ship as data; recipes extend them with object spread. */
26
+ export const palettes = {
27
+ victorianRail: definePalette({
28
+ paint: '#2f4a3a',
29
+ trim: '#b08d57',
30
+ iron: '#2b2b2e',
31
+ brass: '#c9a24a',
32
+ red: '#7a2a22',
33
+ }),
34
+ artDeco: definePalette({
35
+ limestone: '#d8cfbd',
36
+ steel: '#9aa3ad',
37
+ glass: '#4a6a80',
38
+ bronze: '#8a6a3c',
39
+ }),
40
+ modernGlass: definePalette({
41
+ glass: '#6f8fa8',
42
+ aluminium: '#c4c9cf',
43
+ concrete: '#a7a39b',
44
+ }),
45
+ harbour: definePalette({ hull: '#1f2f45', deck: '#8a6f4e', white: '#e8e6df', red: '#a1302a' }),
46
+ } as const;
@@ -0,0 +1,77 @@
1
+ // The Godot 4 emitter behind `godotShaderSource`: prints a spatial .gdshader with the same uniforms
2
+ // as the three.js module. Values come pre-validated from shader.ts.
3
+
4
+ import type { ShaderParams } from './shader.ts';
5
+ import type { UvRect } from './trim-sheet.ts';
6
+
7
+ const f = (n: number): string => (Number.isInteger(n) ? `${n}.0` : `${+n.toFixed(6)}`);
8
+ const rect = (r: UvRect): string =>
9
+ `in_rect(UV, vec4(${f(r.u0)}, ${f(r.v0)}, ${f(r.u1)}, ${f(r.v1)}))`;
10
+
11
+ export function godotSource(p: ShaderParams): string {
12
+ const modes = ['blend_mix', 'depth_draw_opaque', p.cutout ? 'cull_disabled' : 'cull_back'];
13
+ const weight = p.windAttribute
14
+ ? 'CUSTOM0.x /* import _WIND into CUSTOM0 (glTF import: custom attribute mapping) */'
15
+ : 'smoothstep(0.0, wind_height, VERTEX.z)';
16
+ return `// Generated by @developerz.ai/ts23d godotShaderSource(). A starting point your project owns:
17
+ // assign it to a ShaderMaterial and set albedo_tex / normal_tex / orm_tex from the GLB's
18
+ // ts23d_* material (or the shared trim_sheet_{albedo,normal,orm}.png). Godot 4.x.
19
+ shader_type spatial;
20
+ render_mode ${modes.join(', ')};
21
+
22
+ uniform sampler2D albedo_tex : source_color, filter_linear_mipmap, repeat_enable;
23
+ uniform sampler2D normal_tex : hint_normal, filter_linear_mipmap, repeat_enable;
24
+ uniform sampler2D orm_tex : hint_default_white, filter_linear_mipmap, repeat_enable;
25
+
26
+ uniform float night : hint_range(0.0, 1.0) = 0.0;
27
+ uniform float wind_strength = ${f(p.windStrength)};
28
+ uniform float wind_height = ${f(p.windHeight)};
29
+ uniform float wind_hz = ${f(p.windHz)};
30
+ uniform vec3 glow_color : source_color = vec3(${f(p.glow[0])}, ${f(p.glow[1])}, ${f(p.glow[2])});
31
+ uniform float glow_ratio : hint_range(0.0, 1.0) = ${f(p.glowRatio)};
32
+ uniform float glow_intensity = ${f(p.glowIntensity)};
33
+ uniform float wear : hint_range(0.0, 1.0) = ${f(p.wear)};
34
+ uniform float grime_height = ${f(p.grimeHeight)};
35
+
36
+ varying vec3 world_pos;
37
+
38
+ float in_rect(vec2 uv, vec4 r) {
39
+ float u = fract(uv.x);
40
+ return step(r.x, u) * step(u, r.z) * step(r.y, uv.y) * step(uv.y, r.w);
41
+ }
42
+
43
+ float hash13(vec3 p) {
44
+ p = fract(p * 0.1031);
45
+ p += dot(p, p.zyx + 31.32);
46
+ return fract((p.x + p.y) * p.z);
47
+ }
48
+
49
+ void vertex() {
50
+ // Wind sway: mesh-local Z is the authored up axis; sway in local XY.
51
+ float weight = ${weight};
52
+ float phase = VERTEX.x * 0.7 + VERTEX.y * 0.45;
53
+ float sway = sin(TIME * wind_hz * 6.2832 + phase) * wind_strength * weight * 0.1;
54
+ VERTEX.xy += vec2(sway, sway * 0.6);
55
+ world_pos = (MODEL_MATRIX * vec4(VERTEX, 1.0)).xyz;
56
+ }
57
+
58
+ void fragment() {
59
+ vec4 base = texture(albedo_tex, UV);
60
+ vec3 orm = texture(orm_tex, UV).rgb;
61
+ float ground = 1.0 - smoothstep(0.0, grime_height, world_pos.y);
62
+ float grime = clamp(wear * max((1.0 - orm.r) * 1.5, ground), 0.0, 1.0);
63
+ ALBEDO = base.rgb * COLOR.rgb * (1.0 - grime * 0.45);
64
+ AO = orm.r;
65
+ ROUGHNESS = clamp(orm.g + grime * 0.2, 0.0, 1.0);
66
+ METALLIC = orm.b * (1.0 - grime);
67
+ NORMAL_MAP = texture(normal_tex, UV).rgb;
68
+ ${p.cutout ? '\tALPHA = base.a;\n\tALPHA_SCISSOR_THRESHOLD = 0.5;\n' : p.blend ? '\tALPHA = base.a * 0.6;\n' : ''}
69
+ // Window glow: dark panes in window cells, or the glass strip; 1.5 m world slots, glow_ratio lit.
70
+ float luma = dot(base.rgb, vec3(0.2126, 0.7152, 0.0722));
71
+ float panes = (${p.glowRects.map(rect).join(' + ')}) * step(luma, 0.3);
72
+ float mask = max(panes, ${rect(p.glassRect)});
73
+ float lit = step(hash13(floor(world_pos / 1.5)), glow_ratio);
74
+ EMISSION = glow_color * mask * lit * night * glow_intensity;
75
+ }
76
+ `;
77
+ }
@@ -0,0 +1,103 @@
1
+ // The three.js TSL emitter behind `threeMaterialSource`: prints a self-contained TypeScript module
2
+ // for three r18x (`three/webgpu` + `three/tsl`). Values come pre-validated from shader.ts.
3
+
4
+ import type { ShaderParams } from './shader.ts';
5
+ import type { UvRect } from './trim-sheet.ts';
6
+
7
+ const f = (n: number): string => (Number.isInteger(n) ? `${n}.0` : `${+n.toFixed(6)}`);
8
+
9
+ const rectNode = (r: UvRect): string => `inRect(${f(r.u0)}, ${f(r.v0)}, ${f(r.u1)}, ${f(r.v1)})`;
10
+
11
+ export function threeSource(p: ShaderParams): string {
12
+ const weight = p.windAttribute
13
+ ? "attribute('_wind', 'float')"
14
+ : 'smoothstep(float(0), uWindHeight, positionLocal.z)';
15
+ const glowCells = p.glowRects.map(rectNode).join('.add(') + ')'.repeat(p.glowRects.length - 1);
16
+ return `// Generated by @developerz.ai/ts23d threeMaterialSource(). A starting point your project owns:
17
+ // edit freely. Needs three r18x (WebGPURenderer + TSL). ts23d itself has no three dependency.
18
+ //
19
+ // Usage: const gltf = await loader.loadAsync('tank.glb'); upgradeTrimMaterials(gltf.scene);
20
+ // trim.uniforms.night.value = 1; // at dusk
21
+
22
+ import { DoubleSide, type Material, type Mesh, MeshStandardMaterial, MeshStandardNodeMaterial, type Object3D, type Texture } from 'three/webgpu';
23
+ import { attribute, color, float, floor, fract, hash, max, normalMap, positionLocal, positionWorld, sin, smoothstep, step, texture, time, uniform, uv, vec3, vertexColor } from 'three/tsl';
24
+
25
+ export interface TrimMaps {
26
+ readonly albedo: Texture;
27
+ readonly normal: Texture | null;
28
+ readonly orm: Texture | null;
29
+ }
30
+
31
+ /** Shared uniforms: one set drives every trim material, so a scene-wide night switch is one write. */
32
+ export const uniforms = {
33
+ night: uniform(0),
34
+ windStrength: uniform(${f(p.windStrength)}),
35
+ windHeight: uniform(${f(p.windHeight)}),
36
+ windHz: uniform(${f(p.windHz)}),
37
+ glowColor: uniform(color(${f(p.glow[0])}, ${f(p.glow[1])}, ${f(p.glow[2])})),
38
+ glowRatio: uniform(${f(p.glowRatio)}),
39
+ glowIntensity: uniform(${f(p.glowIntensity)}),
40
+ wear: uniform(${f(p.wear)}),
41
+ grimeHeight: uniform(${f(p.grimeHeight)}),
42
+ };
43
+
44
+ /** 1 inside a trim-sheet UV rect (U wraps: strips tile along U). */
45
+ function inRect(u0: number, v0: number, u1: number, v1: number) {
46
+ const t = uv();
47
+ const u = fract(t.x);
48
+ return step(u0, u).mul(step(u, u1)).mul(step(v0, t.y)).mul(step(t.y, v1));
49
+ }
50
+
51
+ export function createTrimMaterial(maps: TrimMaps, cutout = ${p.cutout}): MeshStandardNodeMaterial {
52
+ const { night, windStrength: uWindStrength, windHeight: uWindHeight, windHz, glowColor, glowRatio, glowIntensity, wear, grimeHeight } = uniforms;
53
+ const m = new MeshStandardNodeMaterial();
54
+ const base = texture(maps.albedo, uv());
55
+ const orm = maps.orm ? texture(maps.orm, uv()) : vec3(1, 0.8, 0);
56
+ const tint = vertexColor();
57
+
58
+ // Wear / grime: cavities (low AO) and the first metres above the ground get darker and rougher.
59
+ const ground = float(1).sub(smoothstep(float(0), grimeHeight, positionWorld.y));
60
+ const grime = wear.mul(max(float(1).sub(orm.x).mul(1.5), ground)).clamp(0, 1);
61
+ m.colorNode = base.rgb.mul(tint.rgb).mul(float(1).sub(grime.mul(0.45)));
62
+ m.roughnessNode = orm.y.add(grime.mul(0.2)).clamp(0, 1);
63
+ m.metalnessNode = orm.z.mul(float(1).sub(grime));
64
+ m.aoNode = orm.x;
65
+ if (maps.normal) m.normalNode = normalMap(texture(maps.normal, uv()));
66
+ if (cutout) {
67
+ m.opacityNode = base.a;
68
+ m.alphaTest = 0.5;
69
+ m.side = DoubleSide;
70
+ }
71
+
72
+ // Wind sway (authored Z-up, so sway in local XY), phase-shifted across the crown.
73
+ const windWeight = ${weight};
74
+ const phase = positionLocal.x.mul(0.7).add(positionLocal.y.mul(0.45));
75
+ const sway = sin(time.mul(windHz).mul(6.2832).add(phase)).mul(uWindStrength).mul(windWeight).mul(0.1);
76
+ m.positionNode = positionLocal.add(vec3(sway, sway.mul(0.6), 0));
77
+
78
+ // Window glow: dark pane texels in window cells, or the glass strip; a world-space hash lights
79
+ // \`glowRatio\` of the 1.5 m window slots; scaled by \`night\`.
80
+ const luma = base.r.mul(0.2126).add(base.g.mul(0.7152)).add(base.b.mul(0.0722));
81
+ const panes = ${glowCells}.mul(step(luma, 0.3));
82
+ const mask = max(panes, ${rectNode(p.glassRect)});
83
+ const slot = floor(positionWorld.div(1.5));
84
+ const lit = step(hash(slot.x.mul(73).add(slot.y.mul(157)).add(slot.z.mul(311))), glowRatio);
85
+ m.emissiveNode = glowColor.mul(mask).mul(lit).mul(night).mul(glowIntensity);
86
+ return m;
87
+ }
88
+
89
+ /** Replace every ts23d_* material under \`root\` with the node material, reusing the GLB's maps. */
90
+ export function upgradeTrimMaterials(root: Object3D): void {
91
+ const cache = new Map<Material, MeshStandardNodeMaterial>();
92
+ root.traverse((o) => {
93
+ const mesh = o as Mesh;
94
+ const old = mesh.isMesh ? mesh.material : null;
95
+ if (!(old instanceof MeshStandardMaterial) || !old.name.startsWith('ts23d') || !old.map) return;
96
+ const next = cache.get(old) ?? createTrimMaterial({ albedo: old.map, normal: old.normalMap, orm: old.roughnessMap }, old.alphaTest > 0);
97
+ next.name = old.name;
98
+ cache.set(old, next);
99
+ mesh.material = next;
100
+ });
101
+ }
102
+ `;
103
+ }
@@ -0,0 +1,100 @@
1
+ // Engine shader sources for the trim-sheet material: per-asset effects that stay on the one shared
2
+ // material — vertex-colour tint, wind sway, night window glow, wear/grime. Both functions return
3
+ // plain source strings (a three.js TSL module, a Godot 4 .gdshader). They are STARTING POINTS an
4
+ // engine project loads and owns, not runtime dependencies: ts23d never imports three or Godot.
5
+ //
6
+ // Shared conventions both sources implement:
7
+ // tint albedo.rgb × COLOR_0.rgb (linear), alpha from albedo.a (cutout leaf masks)
8
+ // ORM R occlusion · G roughness · B metalness (one texture, as the GLB binds it)
9
+ // wind weight from the `_WIND` vertex attribute (three: `_wind`) or, by default, the
10
+ // mesh-local height (authored Z-up), smoothstep(0, wind.height, z); sway in local XY
11
+ // glow emissive on window panes: window_sash/porthole texels darker than luma 0.3, and the
12
+ // whole glass strip; a world-space 1.5 m hash lights `ratio` of windows; × `night`
13
+ // wear darkens + roughens cavities (1 − AO) and the first `grimeHeight` metres of world height
14
+
15
+ import type { Surface } from '../core/index.ts';
16
+ import { Ts23dError } from '../core/index.ts';
17
+ import { godotSource } from './shader-godot.ts';
18
+ import { threeSource } from './shader-three.ts';
19
+ import { cellRect, stripRect, type UvRect } from './trim-sheet.ts';
20
+
21
+ export interface ShaderHints {
22
+ /** Vertex sway for vegetation. Weight from the `_WIND` attribute or mesh-local height. */
23
+ readonly wind?: {
24
+ readonly strength: number;
25
+ readonly source?: 'attribute' | 'height';
26
+ /** Metres of height at which sway reaches full weight (height source). Default 4. */
27
+ readonly height?: number;
28
+ readonly hz?: number;
29
+ };
30
+ /** Emissive mask on window cells at night: `ratio` of windows lit. */
31
+ readonly windowGlow?: {
32
+ readonly color: string;
33
+ readonly ratio: number;
34
+ readonly intensity?: number;
35
+ };
36
+ /** Wear/grime mask strength 0..1 and how high ground grime climbs (metres). */
37
+ readonly wear?: { readonly amount: number; readonly grimeHeight?: number };
38
+ /** The surface the material is for: 'cutout' adds alpha test + double-sided. */
39
+ readonly surface?: Surface;
40
+ }
41
+
42
+ /** Resolved, validated numbers both emitters print. */
43
+ export interface ShaderParams {
44
+ readonly windStrength: number;
45
+ readonly windAttribute: boolean;
46
+ readonly windHeight: number;
47
+ readonly windHz: number;
48
+ readonly glow: readonly [number, number, number];
49
+ readonly glowRatio: number;
50
+ readonly glowIntensity: number;
51
+ readonly wear: number;
52
+ readonly grimeHeight: number;
53
+ readonly cutout: boolean;
54
+ readonly blend: boolean;
55
+ readonly glowRects: readonly UvRect[];
56
+ readonly glassRect: UvRect;
57
+ }
58
+
59
+ function fail(cause: string, fix: string): never {
60
+ throw new Ts23dError('T3D_RECIPE_INVALID', { cause: `shader hints: ${cause}`, fix });
61
+ }
62
+
63
+ function srgbHex(value: string): [number, number, number] {
64
+ if (!/^#[0-9a-fA-F]{6}$/.test(value)) fail(`colour '${value}'`, "use '#rrggbb' sRGB hex");
65
+ const n = Number.parseInt(value.slice(1), 16);
66
+ return [((n >> 16) & 255) / 255, ((n >> 8) & 255) / 255, (n & 255) / 255];
67
+ }
68
+
69
+ export function resolveHints(h: ShaderHints): ShaderParams {
70
+ const unit = (v: number, what: string): number =>
71
+ Number.isFinite(v) && v >= 0 && v <= 1 ? v : fail(`${what} is ${v}`, `${what} is 0..1`);
72
+ const pos = (v: number, what: string): number =>
73
+ Number.isFinite(v) && v > 0 ? v : fail(`${what} is ${v}`, `${what} is a positive number`);
74
+ const strength = h.wind ? pos(h.wind.strength, 'wind.strength') : 0;
75
+ return {
76
+ windStrength: strength,
77
+ windAttribute: h.wind?.source === 'attribute',
78
+ windHeight: pos(h.wind?.height ?? 4, 'wind.height'),
79
+ windHz: pos(h.wind?.hz ?? 0.4, 'wind.hz'),
80
+ glow: h.windowGlow ? srgbHex(h.windowGlow.color) : [1, 0.75, 0.4],
81
+ glowRatio: h.windowGlow ? unit(h.windowGlow.ratio, 'windowGlow.ratio') : 0,
82
+ glowIntensity: pos(h.windowGlow?.intensity ?? 2, 'windowGlow.intensity'),
83
+ wear: h.wear ? unit(h.wear.amount, 'wear.amount') : 0,
84
+ grimeHeight: pos(h.wear?.grimeHeight ?? 0.6, 'wear.grimeHeight'),
85
+ cutout: h.surface === 'cutout',
86
+ blend: h.surface === 'glass' || h.surface === 'translucent',
87
+ glowRects: [cellRect('window_sash'), cellRect('porthole')],
88
+ glassRect: stripRect('glass'),
89
+ };
90
+ }
91
+
92
+ /** A three.js (r18x, WebGPU/TSL) TypeScript module: `createTrimMaterial(maps)` + `upgradeTrimMaterials(root)`. */
93
+ export function threeMaterialSource(hints: ShaderHints = {}): string {
94
+ return threeSource(resolveHints(hints));
95
+ }
96
+
97
+ /** A Godot 4 spatial shader (.gdshader) with the same features and uniform names. */
98
+ export function godotShaderSource(hints: ShaderHints = {}): string {
99
+ return godotSource(resolveHints(hints));
100
+ }
@@ -0,0 +1,18 @@
1
+ // The custom-strip slot table: ids registered by `defineMaterial`, in registration order. A leaf
2
+ // module (no imports) so the layout (trim-sheet.ts) and the registry (registry.ts) can both read it.
3
+
4
+ const custom: string[] = [];
5
+
6
+ /** Slot index among the custom strips, or −1. */
7
+ export const customIndex = (id: string): number => custom.indexOf(id);
8
+
9
+ export const customIds = (): readonly string[] => custom;
10
+
11
+ export function pushCustom(id: string): number {
12
+ custom.push(id);
13
+ return custom.length - 1;
14
+ }
15
+
16
+ export function clearCustom(): void {
17
+ custom.length = 0;
18
+ }