@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,1919 @@
1
+ import {
2
+ Ts23dError2,
3
+ createRng2
4
+ } from "./chunk-4ddzsqtc.js";
5
+
6
+ // src/material/paint/noise.ts
7
+ function hash2(ix, iy, seed) {
8
+ let h = Math.imul(ix | 0, 668265261) ^ Math.imul(iy | 0, 374761393) ^ seed;
9
+ h = Math.imul(h ^ h >>> 15, 2246822507);
10
+ h = Math.imul(h ^ h >>> 13, 3266489909);
11
+ return ((h ^ h >>> 16) >>> 0) / 4294967296;
12
+ }
13
+ function valueNoise(x, y, period, seed) {
14
+ const xi = Math.floor(x);
15
+ const yi = Math.floor(y);
16
+ const fx = x - xi;
17
+ const fy = y - yi;
18
+ const sx = fx * fx * (3 - 2 * fx);
19
+ const sy = fy * fy * (3 - 2 * fy);
20
+ const x0 = (xi % period + period) % period;
21
+ const x1 = (x0 + 1) % period;
22
+ const a = hash2(x0, yi, seed);
23
+ const b = hash2(x1, yi, seed);
24
+ const c = hash2(x0, yi + 1, seed);
25
+ const d = hash2(x1, yi + 1, seed);
26
+ return a + (b - a) * sx + (c - a + (d - c - b + a) * sx) * sy;
27
+ }
28
+ function fbm(x, y, period, seed, octaves = 4, gain = 0.5) {
29
+ let sum = 0;
30
+ let amp = 1;
31
+ let norm = 0;
32
+ let f = 1;
33
+ for (let o = 0;o < octaves; o++) {
34
+ sum += amp * valueNoise(x * f, y * f, period * f, seed + o * 1013);
35
+ norm += amp;
36
+ amp *= gain;
37
+ f *= 2;
38
+ }
39
+ return sum / norm * 2 - 1;
40
+ }
41
+
42
+ // src/material/paint/canvas.ts
43
+ function createCanvas(w, h, metresU, metresV, wrapU) {
44
+ const n = w * h;
45
+ return {
46
+ w,
47
+ h,
48
+ metresU,
49
+ metresV,
50
+ wrapU,
51
+ height: new Float32Array(n),
52
+ tone: new Float32Array(n).fill(1),
53
+ rough: new Float32Array(n).fill(0.8),
54
+ metal: new Float32Array(n),
55
+ occl: new Float32Array(n).fill(1),
56
+ rgba: null
57
+ };
58
+ }
59
+ function periodFor(c, featureMetres) {
60
+ return c.wrapU ? Math.max(1, Math.round(c.metresU / featureMetres)) : 1 << 20;
61
+ }
62
+ function noiseAt(c, xm, ym, sizeU, sizeV, seed, octaves = 4) {
63
+ const finest = 1.5 * Math.max(c.metresU / c.w, c.metresV / c.h);
64
+ const k = Math.max(1, finest / Math.min(sizeU, sizeV));
65
+ const period = periodFor(c, sizeU * k);
66
+ const cell = c.wrapU ? c.metresU / period : sizeU * k;
67
+ return fbm(xm / cell, ym / (sizeV * k), period, seed, octaves);
68
+ }
69
+ var param = (params, key, fallback) => params?.[key] ?? fallback;
70
+ var clamp01 = (v) => Math.min(1, Math.max(0, v));
71
+ var smooth = (e0, e1, v) => {
72
+ const t = clamp01((v - e0) / (e1 - e0));
73
+ return t * t * (3 - 2 * t);
74
+ };
75
+
76
+ // src/material/trim-sheet.ts
77
+ var STRIP_H = 1 / 16;
78
+ var strips = [
79
+ "plain",
80
+ "brick",
81
+ "stone",
82
+ "slate",
83
+ "iron",
84
+ "wood",
85
+ "glass",
86
+ "concrete"
87
+ ];
88
+ var decals = [
89
+ "window_sash",
90
+ "door_panel",
91
+ "clock",
92
+ "smokebox",
93
+ "sign",
94
+ "porthole"
95
+ ];
96
+ var foliage = [
97
+ "foliage_broadleaf",
98
+ "foliage_maple",
99
+ "foliage_maple_autumn",
100
+ "foliage_birch",
101
+ "foliage_acacia",
102
+ "foliage_palm",
103
+ "foliage_spruce",
104
+ "foliage_spruce_snow",
105
+ "foliage_willow",
106
+ "foliage_reeds",
107
+ "foliage_needles"
108
+ ];
109
+ var cells = [...decals, ...foliage];
110
+ var FOLIAGE_SLOT = 8;
111
+ var slotOf = (id) => {
112
+ const d = decals.indexOf(id);
113
+ return d >= 0 ? d : FOLIAGE_SLOT + foliage.indexOf(id);
114
+ };
115
+ var TRIM_SHEET_SIZE2 = 1024;
116
+ function stripRect2(id) {
117
+ const i = strips.indexOf(id);
118
+ return { u0: 0, v0: i * STRIP_H, u1: 1, v1: (i + 1) * STRIP_H, metres: 2 };
119
+ }
120
+ function cellRect2(id) {
121
+ const i = slotOf(id);
122
+ const size = 1 / 8;
123
+ const u0 = i % 8 * size;
124
+ const v0 = 0.5 + Math.floor(i / 8) * size;
125
+ return { u0, v0, u1: u0 + size, v1: v0 + size, metres: 1 };
126
+ }
127
+ var STRIPS2 = strips;
128
+ var CELLS2 = cells;
129
+ var DECAL_CELLS2 = decals;
130
+ var FOLIAGE_CELLS2 = foliage;
131
+ var isFoliage2 = (id) => id.startsWith("foliage_");
132
+
133
+ // src/material/paint/shapes.ts
134
+ function segment(px, py, ax, ay, bx, by) {
135
+ const dx = bx - ax;
136
+ const dy = by - ay;
137
+ const t = Math.max(0, Math.min(1, ((px - ax) * dx + (py - ay) * dy) / (dx * dx + dy * dy)));
138
+ return Math.hypot(px - ax - t * dx, py - ay - t * dy);
139
+ }
140
+ function rect(px, py, x0, y0, x1, y1) {
141
+ const qx = Math.max(x0 - px, px - x1);
142
+ const qy = Math.max(y0 - py, py - y1);
143
+ return qx > 0 || qy > 0 ? Math.hypot(Math.max(qx, 0), Math.max(qy, 0)) : Math.max(qx, qy);
144
+ }
145
+ var dome = (d, r) => d < r ? Math.sqrt(1 - (d / r) ** 2) : 0;
146
+ function boltRing(px, py, ring, n, phase = 0) {
147
+ const a = Math.atan2(py, px) - phase;
148
+ const step = Math.PI * 2 / n;
149
+ const k = Math.round(a / step);
150
+ const ba = k * step + phase;
151
+ return Math.hypot(px - ring * Math.cos(ba), py - ring * Math.sin(ba));
152
+ }
153
+
154
+ // src/material/paint/cells.ts
155
+ var PANE_TONE = 0.12;
156
+ function paintWindowSash(c, r, seed) {
157
+ const cols = param(r.params, "cols", 2);
158
+ const rows = param(r.params, "rows", 2);
159
+ const frame = param(r.params, "frame", 0.07);
160
+ const bar = param(r.params, "bar", 0.022);
161
+ const meet = 0.5;
162
+ const pu = c.metresU / c.w;
163
+ const pv = c.metresV / c.h;
164
+ for (let y = 0;y < c.h; y++) {
165
+ const ym = (y + 0.5) * pv;
166
+ const lower = ym < meet;
167
+ const y0 = lower ? frame : meet + bar;
168
+ const y1 = lower ? meet - bar : 1 - frame;
169
+ for (let x = 0;x < c.w; x++) {
170
+ const xm = (x + 0.5) * pu;
171
+ const i = y * c.w + x;
172
+ const fx = (xm - frame) / (1 - 2 * frame) * cols;
173
+ const fy = (ym - y0) / (y1 - y0) * rows;
174
+ const inX = Math.min(fx - Math.floor(fx), Math.ceil(fx) - fx) * ((1 - 2 * frame) / cols);
175
+ const inY = Math.min(fy - Math.floor(fy), Math.ceil(fy) - fy) * ((y1 - y0) / rows);
176
+ const open = -rect(xm, ym, frame, y0, 1 - frame, y1);
177
+ const pane = Math.min(open, inX - bar / 2, inY - bar / 2);
178
+ const wood = noiseAt(c, xm, ym, 0.3, 0.02, seed, 3);
179
+ if (pane > 0) {
180
+ const sky = noiseAt(c, xm, ym, 0.6, 0.6, seed + 1, 2);
181
+ c.height[i] = -0.012;
182
+ c.tone[i] = PANE_TONE + 0.05 * (ym - y0) + 0.03 * sky;
183
+ c.rough[i] = 0.05;
184
+ c.metal[i] = 0;
185
+ } else {
186
+ const bevel = smooth(0, 0.01, -pane);
187
+ c.height[i] = -0.012 * (1 - bevel) + 0.0003 * wood;
188
+ c.tone[i] = 0.9 + 0.03 * wood;
189
+ c.rough[i] = r.roughness;
190
+ }
191
+ }
192
+ }
193
+ }
194
+ function paintDoorPanel(c, r, seed) {
195
+ const stile = param(r.params, "stile", 0.12);
196
+ const muntin = stile * 0.7;
197
+ const pu = c.metresU / c.w;
198
+ const pv = c.metresV / c.h;
199
+ const midX = 0.5;
200
+ const panels = [
201
+ [stile, 0.2, midX - muntin / 2, 0.42],
202
+ [midX + muntin / 2, 0.2, 1 - stile, 0.42],
203
+ [stile, 0.58, midX - muntin / 2, 1 - stile],
204
+ [midX + muntin / 2, 0.58, 1 - stile, 1 - stile]
205
+ ];
206
+ for (let y = 0;y < c.h; y++) {
207
+ const ym = (y + 0.5) * pv;
208
+ for (let x = 0;x < c.w; x++) {
209
+ const xm = (x + 0.5) * pu;
210
+ const i = y * c.w + x;
211
+ let d = 1;
212
+ for (const [x0, y0, x1, y1] of panels)
213
+ d = Math.min(d, rect(xm, ym, x0, y0, x1, y1));
214
+ const grain = noiseAt(c, xm, ym, 0.02, 0.4, seed, 3);
215
+ const recess = d < 0 ? 1 : 1 - smooth(0, 0.006, d);
216
+ const field = smooth(0.005, 0.04, -d);
217
+ c.height[i] = -0.012 * recess + 0.008 * field + 0.0003 * grain;
218
+ c.tone[i] = (0.8 + 0.08 * grain) * (d < 0 && d > -0.008 ? 0.65 : d < 0 ? 0.92 : 1);
219
+ c.rough[i] = r.roughness + 0.05 * grain;
220
+ }
221
+ }
222
+ }
223
+ function paintSign(c, r, _seed) {
224
+ const border = param(r.params, "border", 0.05);
225
+ const pu = c.metresU / c.w;
226
+ const pv = c.metresV / c.h;
227
+ const screws = [0.07, 0.93];
228
+ for (let y = 0;y < c.h; y++) {
229
+ const ym = (y + 0.5) * pv;
230
+ for (let x = 0;x < c.w; x++) {
231
+ const xm = (x + 0.5) * pu;
232
+ const i = y * c.w + x;
233
+ const inner = -rect(xm, ym, border, border, 1 - border, 1 - border);
234
+ const edge = Math.min(xm, ym, 1 - xm, 1 - ym);
235
+ const moulding = inner < 0 ? dome(Math.abs(-inner - border / 2), border / 2) : 0;
236
+ let screw = 0;
237
+ for (const sx of screws)
238
+ for (const sy of screws)
239
+ screw = Math.max(screw, dome(Math.hypot(xm - sx, ym - sy), 0.012));
240
+ c.height[i] = 0.006 * moulding + 0.002 * screw - 0.002 * (1 - smooth(0, 0.004, edge));
241
+ c.tone[i] = inner > 0 ? 0.95 : 0.85 + 0.1 * moulding;
242
+ if (screw > 0) {
243
+ c.tone[i] = 0.6;
244
+ c.metal[i] = 1;
245
+ c.rough[i] = 0.35;
246
+ }
247
+ }
248
+ }
249
+ }
250
+
251
+ // src/material/paint/cells-round.ts
252
+ var TAU = Math.PI * 2;
253
+ function hand(px, py, hours, len) {
254
+ const a = hours / 12 * TAU;
255
+ return segment(px, py, 0, 0, Math.sin(a) * len, Math.cos(a) * len);
256
+ }
257
+ function paintClock(c, r, _seed) {
258
+ const pu = c.metresU / c.w;
259
+ const pv = c.metresV / c.h;
260
+ for (let y = 0;y < c.h; y++) {
261
+ const py = (y + 0.5) * pv - 0.5;
262
+ for (let x = 0;x < c.w; x++) {
263
+ const px = (x + 0.5) * pu - 0.5;
264
+ const i = y * c.w + x;
265
+ const d = Math.hypot(px, py);
266
+ if (d > 0.48)
267
+ continue;
268
+ const i0 = i;
269
+ if (d > 0.42) {
270
+ c.height[i0] = 0.012 * dome(Math.abs(d - 0.45), 0.03);
271
+ c.tone[i0] = 0.55;
272
+ c.metal[i0] = 1;
273
+ c.rough[i0] = 0.35;
274
+ continue;
275
+ }
276
+ const a = Math.atan2(px, py);
277
+ const minute = Math.abs((a / TAU * 60 + 60.5) % 1 - 0.5) * (TAU / 60) * d;
278
+ const hour = Math.abs((a / TAU * 12 + 12.5) % 1 - 0.5) * (TAU / 12) * d;
279
+ const tickM = d > 0.38 && d < 0.41 && minute < 0.003 ? 1 : 0;
280
+ const tickH = d > 0.32 && d < 0.41 && hour < 0.008 ? 1 : 0;
281
+ const hands = Math.min(hand(px, py, 10 + 10 / 60, 0.22) - 0.012, hand(px, py, 2, 0.34) - 0.007);
282
+ const ink = Math.max(tickM, tickH, hands < 0 ? 1 : 0, d < 0.02 ? 1 : 0);
283
+ c.height[i0] = -0.004 + 0.002 * ink;
284
+ c.tone[i0] = ink ? 0.08 : 0.96;
285
+ c.rough[i0] = ink ? 0.5 : r.roughness;
286
+ c.metal[i0] = 0;
287
+ }
288
+ }
289
+ }
290
+ function paintSmokebox(c, r, seed) {
291
+ const pu = c.metresU / c.w;
292
+ const pv = c.metresV / c.h;
293
+ const metal = r.metalness ?? 0;
294
+ for (let y = 0;y < c.h; y++) {
295
+ const py = (y + 0.5) * pv - 0.5;
296
+ for (let x = 0;x < c.w; x++) {
297
+ const px = (x + 0.5) * pu - 0.5;
298
+ const i = y * c.w + x;
299
+ const d = Math.hypot(px, py);
300
+ if (d > 0.47)
301
+ continue;
302
+ const pit = noiseAt(c, px + 0.5, py + 0.5, 0.03, 0.03, seed, 3);
303
+ let h = 0.01 * (1 - (d / 0.47) ** 2) + 0.0004 * pit;
304
+ const flange = dome(Math.abs(d - 0.45), 0.02);
305
+ h += 0.006 * flange;
306
+ const bolt = dome(boltRing(px, py, 0.415, 28), 0.011);
307
+ h += 0.006 * bolt;
308
+ const strap = Math.min(segment(px, py, -0.47, 0.17, 0.12, 0.17), segment(px, py, -0.47, -0.17, 0.12, -0.17));
309
+ const strapH = smooth(0.024, 0.018, strap);
310
+ h = Math.max(h, 0.014 * strapH + h * (1 - strapH));
311
+ const boss = dome(d, 0.05);
312
+ const handle = smooth(0.012, 0.008, segment(px, py, -0.13, 0, 0.13, 0));
313
+ h += 0.02 * boss + 0.012 * handle;
314
+ c.height[i] = h;
315
+ c.tone[i] = (0.5 + 0.05 * pit) * (1 + 0.5 * Math.max(bolt, handle, strapH * 0.6, flange, boss));
316
+ c.rough[i] = r.roughness - 0.15 * Math.max(bolt, handle, boss);
317
+ c.metal[i] = metal + (1 - metal) * Math.max(handle, boss) * 0.8;
318
+ }
319
+ }
320
+ }
321
+ function paintPorthole(c, r, seed) {
322
+ const pu = c.metresU / c.w;
323
+ const pv = c.metresV / c.h;
324
+ for (let y = 0;y < c.h; y++) {
325
+ const py = (y + 0.5) * pv - 0.5;
326
+ for (let x = 0;x < c.w; x++) {
327
+ const px = (x + 0.5) * pu - 0.5;
328
+ const i = y * c.w + x;
329
+ const d = Math.hypot(px, py);
330
+ if (d > 0.44)
331
+ continue;
332
+ if (d < 0.29) {
333
+ const sky = noiseAt(c, px + 0.5, py + 0.5, 0.5, 0.5, seed, 2);
334
+ c.height[i] = -0.012;
335
+ c.tone[i] = PANE_TONE + 0.04 * (py + 0.3) + 0.02 * sky;
336
+ c.rough[i] = 0.05;
337
+ c.metal[i] = 0;
338
+ continue;
339
+ }
340
+ const ring = dome(Math.abs(d - 0.365), 0.075);
341
+ const bolt = dome(boltRing(px, py, 0.405, 10, Math.PI / 10), 0.018);
342
+ const lip = dome(Math.abs(d - 0.3), 0.012);
343
+ c.height[i] = 0.016 * ring + 0.008 * bolt + 0.004 * lip;
344
+ c.tone[i] = 0.75 + 0.15 * ring;
345
+ c.rough[i] = Math.max(0.2, r.roughness - 0.2 * ring);
346
+ c.metal[i] = 1;
347
+ }
348
+ }
349
+ }
350
+
351
+ // src/material/paint/foliage-common.ts
352
+ function curve(x0, y0, x1, y1, x2, y2, n = 16) {
353
+ const x = [];
354
+ const y = [];
355
+ const a = [];
356
+ for (let i = 0;i <= n; i++) {
357
+ const t = i / n;
358
+ const u = 1 - t;
359
+ x.push(u * u * x0 + 2 * u * t * x1 + t * t * x2);
360
+ y.push(u * u * y0 + 2 * u * t * y1 + t * t * y2);
361
+ const dx = 2 * u * (x1 - x0) + 2 * t * (x2 - x1);
362
+ const dy = 2 * u * (y1 - y0) + 2 * t * (y2 - y1);
363
+ a.push(Math.atan2(dx, dy));
364
+ }
365
+ return { x, y, a };
366
+ }
367
+ var at = (c, t) => Math.round(t * (c.x.length - 1));
368
+ function drawStem(f, c, w0, w1, col) {
369
+ const n = c.x.length - 1;
370
+ for (let i = 0;i < n; i++) {
371
+ const t0 = i / n;
372
+ const t1 = (i + 1) / n;
373
+ f.stem(c.x[i] ?? 0, c.y[i] ?? 0, c.x[i + 1] ?? 0, c.y[i + 1] ?? 0, w0 + (w1 - w0) * t0, w0 + (w1 - w0) * t1, col);
374
+ }
375
+ }
376
+ function vary(rng, col, amt) {
377
+ const k = 1 + amt * (rng.next() * 2 - 1);
378
+ const drift = 1 + amt * 0.6 * (rng.next() * 2 - 1);
379
+ return [col[0] * k * drift, col[1] * k, col[2] * k / drift];
380
+ }
381
+ var dim = (col, k) => [col[0] * k, col[1] * k, col[2] * k];
382
+ var LEAF = {
383
+ beech: [0.09, 0.2, 0.035],
384
+ maple: [0.1, 0.24, 0.04],
385
+ birch: [0.16, 0.3, 0.05],
386
+ acacia: [0.11, 0.2, 0.06],
387
+ palm: [0.12, 0.22, 0.05],
388
+ dry: [0.3, 0.2, 0.08],
389
+ spruce: [0.035, 0.09, 0.04],
390
+ pine: [0.06, 0.13, 0.04],
391
+ willow: [0.2, 0.3, 0.08],
392
+ reed: [0.2, 0.28, 0.08],
393
+ snow: [0.85, 0.88, 0.92],
394
+ bark: [0.08, 0.055, 0.035],
395
+ twig: [0.12, 0.09, 0.05]
396
+ };
397
+ var AUTUMN = [
398
+ [0.6, 0.08, 0.02],
399
+ [0.75, 0.25, 0.02],
400
+ [0.8, 0.45, 0.03],
401
+ [0.45, 0.04, 0.03],
402
+ [0.55, 0.35, 0.05]
403
+ ];
404
+
405
+ // src/material/paint/orm.ts
406
+ var AO_DEPTH = 0.006;
407
+ var AO_STRENGTH = 0.75;
408
+ var AO_RADIUS = 0.02;
409
+ function boxBlur(src, w, h, rx, ry, wrap) {
410
+ const tmp = new Float32Array(src.length);
411
+ const out = new Float32Array(src.length);
412
+ const ix = (i) => i < 0 ? wrap ? i % w + w : 0 : i >= w ? wrap ? i % w : w - 1 : i;
413
+ for (let y = 0;y < h; y++) {
414
+ const row = y * w;
415
+ let s = 0;
416
+ for (let k = -rx;k <= rx; k++)
417
+ s += src[row + ix(k)] ?? 0;
418
+ for (let x = 0;x < w; x++) {
419
+ tmp[row + x] = s / (2 * rx + 1);
420
+ s += (src[row + ix(x + rx + 1)] ?? 0) - (src[row + ix(x - rx)] ?? 0);
421
+ }
422
+ }
423
+ const iy = (j) => j < 0 ? 0 : j >= h ? h - 1 : j;
424
+ for (let x = 0;x < w; x++) {
425
+ let s = 0;
426
+ for (let k = -ry;k <= ry; k++)
427
+ s += tmp[iy(k) * w + x] ?? 0;
428
+ for (let y = 0;y < h; y++) {
429
+ out[y * w + x] = s / (2 * ry + 1);
430
+ s += (tmp[iy(y + ry + 1) * w + x] ?? 0) - (tmp[iy(y - ry) * w + x] ?? 0);
431
+ }
432
+ }
433
+ return out;
434
+ }
435
+ function cavityOcclusion(c) {
436
+ const rx = Math.max(1, Math.round(AO_RADIUS / (c.metresU / c.w)));
437
+ const ry = Math.max(1, Math.round(AO_RADIUS / (c.metresV / c.h)));
438
+ const blur = boxBlur(boxBlur(c.height, c.w, c.h, rx, ry, c.wrapU), c.w, c.h, rx, ry, c.wrapU);
439
+ const ao = new Float32Array(c.height.length);
440
+ for (let i = 0;i < ao.length; i++) {
441
+ const cavity = (blur[i] ?? 0) - (c.height[i] ?? 0);
442
+ const t = cavity <= 0 ? 0 : cavity >= AO_DEPTH ? 1 : cavity / AO_DEPTH;
443
+ ao[i] = (1 - AO_STRENGTH * t) * (c.occl[i] ?? 1);
444
+ }
445
+ return ao;
446
+ }
447
+ var SRGB = new Uint8Array(4096).map((_, i) => {
448
+ const l = i / 4095;
449
+ const s = l <= 0.0031308 ? l * 12.92 : 1.055 * l ** (1 / 2.4) - 0.055;
450
+ return Math.round(s * 255);
451
+ });
452
+ var byte = (v) => v <= 0 ? 0 : v >= 1 ? 255 : Math.round(v * 255);
453
+ function packOrm(c, ao) {
454
+ const out = new Uint8Array(c.w * c.h * 4);
455
+ for (let i = 0, o = 0;i < ao.length; i++, o += 4) {
456
+ out[o] = byte(ao[i] ?? 1);
457
+ out[o + 1] = byte(c.rough[i] ?? 0.8);
458
+ out[o + 2] = byte(c.metal[i] ?? 0);
459
+ out[o + 3] = 255;
460
+ }
461
+ return out;
462
+ }
463
+ function packAlbedo(c, ao) {
464
+ if (c.rgba)
465
+ return c.rgba;
466
+ const out = new Uint8Array(c.w * c.h * 4);
467
+ for (let i = 0, o = 0;i < ao.length; i++, o += 4) {
468
+ const t = (c.tone[i] ?? 1) * (0.8 + 0.2 * (ao[i] ?? 1));
469
+ const g = SRGB[t <= 0 ? 0 : t >= 1 ? 4095 : Math.round(t * 4095)] ?? 255;
470
+ out[o] = g;
471
+ out[o + 1] = g;
472
+ out[o + 2] = g;
473
+ out[o + 3] = 255;
474
+ }
475
+ return out;
476
+ }
477
+
478
+ // src/material/paint/foliage-raster.ts
479
+ var total = (a) => a.reduce((s, v) => s + v, 0);
480
+ var LOBE = Math.PI * 2 / 5;
481
+
482
+ class Foliage {
483
+ r;
484
+ g;
485
+ b;
486
+ a;
487
+ c;
488
+ px;
489
+ constructor(c) {
490
+ this.c = c;
491
+ const n = c.w * c.h;
492
+ this.r = new Float32Array(n);
493
+ this.g = new Float32Array(n);
494
+ this.b = new Float32Array(n);
495
+ this.a = new Float32Array(n);
496
+ this.px = c.metresU / c.w;
497
+ c.height.fill(0);
498
+ }
499
+ put(i, cov, col, shade, h, rough) {
500
+ if (cov <= 0)
501
+ return;
502
+ const k = 1 - cov;
503
+ const s = shade * cov;
504
+ this.r[i] = (this.r[i] ?? 0) * k + col[0] * s;
505
+ this.g[i] = (this.g[i] ?? 0) * k + col[1] * s;
506
+ this.b[i] = (this.b[i] ?? 0) * k + col[2] * s;
507
+ this.a[i] = (this.a[i] ?? 0) * k + cov;
508
+ this.c.height[i] = (this.c.height[i] ?? 0) * k + h * cov;
509
+ this.c.rough[i] = (this.c.rough[i] ?? 0.8) * k + rough * cov;
510
+ }
511
+ span(x0, y0, x1, y1) {
512
+ const { w, h } = this.c;
513
+ const p = this.px;
514
+ return [
515
+ Math.max(0, Math.floor(x0 / p) - 1),
516
+ Math.max(0, Math.floor(y0 / p) - 1),
517
+ Math.min(w - 1, Math.ceil(x1 / p) + 1),
518
+ Math.min(h - 1, Math.ceil(y1 / p) + 1)
519
+ ];
520
+ }
521
+ stem(ax, ay, bx, by, w0, w1, col) {
522
+ const m = Math.max(w0, w1);
523
+ const [sx0 = 0, sy0 = 0, sx1 = 0, sy1 = 0] = this.span(Math.min(ax, bx) - m, Math.min(ay, by) - m, Math.max(ax, bx) + m, Math.max(ay, by) + m);
524
+ const lx = bx - ax;
525
+ const ly = by - ay;
526
+ const l2 = lx * lx + ly * ly || 1;
527
+ for (let y = sy0;y <= sy1; y++) {
528
+ const ym = (y + 0.5) * this.px;
529
+ for (let x = sx0;x <= sx1; x++) {
530
+ const xm = (x + 0.5) * this.px;
531
+ const t = clamp01(((xm - ax) * lx + (ym - ay) * ly) / l2);
532
+ const half = (w0 + (w1 - w0) * t) / 2;
533
+ const d = segment(xm, ym, ax, ay, bx, by);
534
+ const cov = clamp01(0.5 - (d - half) / this.px);
535
+ const round = half > 0 ? Math.sqrt(Math.max(0, 1 - (d / half) ** 2)) : 0;
536
+ this.put(y * this.c.w + x, cov, col, 1, 0.0015 + half * 0.4 * round, 0.85);
537
+ }
538
+ }
539
+ }
540
+ leaf(shape, bx, by, angle, len, wid, col, serrate = 0, lift = 0) {
541
+ const ux = Math.sin(angle);
542
+ const uy = Math.cos(angle);
543
+ const tx = bx + ux * len;
544
+ const ty = by + uy * len;
545
+ const pad = shape === "palmate" ? len : wid;
546
+ const [sx0 = 0, sy0 = 0, sx1 = 0, sy1 = 0] = this.span(Math.min(bx, tx) - pad, Math.min(by, ty) - pad, Math.max(bx, tx) + pad, Math.max(by, ty) + pad);
547
+ for (let y = sy0;y <= sy1; y++) {
548
+ const ym = (y + 0.5) * this.px - by;
549
+ for (let x = sx0;x <= sx1; x++) {
550
+ const xm = (x + 0.5) * this.px - bx;
551
+ const along = xm * ux + ym * uy;
552
+ const across = xm * uy - ym * ux;
553
+ let sd;
554
+ let rib;
555
+ if (shape === "palmate") {
556
+ const rr = Math.hypot(along - len * 0.45, across);
557
+ const th = Math.atan2(across, along - len * 0.45);
558
+ const lobe = 1 - Math.abs(Math.sin(2.5 * th)) ** 0.6;
559
+ const edge = len * 0.5 * (0.35 + 0.65 * lobe) * (1 + serrate * Math.abs(Math.sin(18 * th)));
560
+ sd = rr - edge;
561
+ const axis = Math.round(th / LOBE) * LOBE;
562
+ rib = rr * Math.abs(Math.sin(th - axis));
563
+ } else {
564
+ const t = along / len;
565
+ if (t < 0 || t > 1) {
566
+ sd = Math.max(-along, along - len) + Math.abs(across) * 0.5;
567
+ } else {
568
+ const prof = shape === "ovate" ? Math.sin(Math.PI * t ** 0.8) ** 0.9 : shape === "lance" ? Math.sin(Math.PI * t ** 0.6) : shape === "blade" ? (1 - t) ** 0.4 : 1 - 0.6 * t;
569
+ const teeth = 1 - serrate * Math.abs(Math.sin(t * 40));
570
+ sd = Math.abs(across) - wid / 2 * prof * teeth;
571
+ }
572
+ rib = Math.abs(across);
573
+ }
574
+ const cov = clamp01(0.5 - sd / this.px);
575
+ if (cov <= 0)
576
+ continue;
577
+ const inside = clamp01(-sd / (wid * 0.5 + 0.000001));
578
+ const vein = 1 - smooth(0, this.px * 1.5, rib);
579
+ const shade = 0.85 + 0.15 * inside;
580
+ this.put(y * this.c.w + x, cov, col, shade, 0.002 + lift + 0.0015 * inside + 0.0006 * vein, 0.6);
581
+ }
582
+ }
583
+ }
584
+ finish(axisX = 0.5, reach = 0.35) {
585
+ const { w, h } = this.c;
586
+ const n = w * h;
587
+ const sa = total(this.a);
588
+ const mean = sa > 0 ? [total(this.r) / sa, total(this.g) / sa, total(this.b) / sa] : [0.1, 0.2, 0.05];
589
+ const out = new Uint8Array(n * 4);
590
+ const enc = (v) => SRGB[Math.round(clamp01(v) * 4095)] ?? 0;
591
+ for (let y = 0;y < h; y++) {
592
+ for (let x = 0;x < w; x++) {
593
+ const i = y * w + x;
594
+ const a = this.a[i] ?? 0;
595
+ const k = a > 0.02 ? 1 / a : 0;
596
+ const mix = clamp01(a * 4);
597
+ out[i * 4] = enc((this.r[i] ?? 0) * k * mix + mean[0] * (1 - mix));
598
+ out[i * 4 + 1] = enc((this.g[i] ?? 0) * k * mix + mean[1] * (1 - mix));
599
+ out[i * 4 + 2] = enc((this.b[i] ?? 0) * k * mix + mean[2] * (1 - mix));
600
+ out[i * 4 + 3] = Math.round(clamp01(a) * 255);
601
+ const d = Math.abs((x + 0.5) * this.px - axisX);
602
+ this.c.occl[i] = 0.55 + 0.45 * smooth(0, reach, d + (y + 0.5) * this.px * 0.4);
603
+ }
604
+ }
605
+ this.c.rgba = out;
606
+ this.c.metal.fill(0);
607
+ }
608
+ }
609
+
610
+ // src/material/paint/foliage-broad.ts
611
+ function cluster(c, seed, s) {
612
+ const rng = createRng2(seed);
613
+ const f = new Foliage(c);
614
+ const leafAt = (x, y, a, k, lift) => {
615
+ const len = s.len * rng.range(0.8, 1.15) * k;
616
+ f.leaf(s.shape, x, y, a, len, s.wid * (len / s.len), s.colour(rng), s.serrate, lift);
617
+ };
618
+ for (let i = 0;i < s.back; i++) {
619
+ const x = 0.5 + rng.range(-0.33, 0.33);
620
+ const y = rng.range(0.3, 0.85);
621
+ const col = dim(s.colour(rng), 0.5);
622
+ const len = s.len * rng.range(0.8, 1.1);
623
+ f.leaf(s.shape, x, y, rng.range(-1.4, 1.4), len, s.wid * (len / s.len), col, s.serrate);
624
+ }
625
+ const main = curve(0.5, 0.02, 0.5 + rng.range(-0.1, 0.1), 0.45, 0.5 + rng.range(-0.08, 0.08), 0.8);
626
+ drawStem(f, main, 0.016, 0.004, LEAF.twig);
627
+ for (let k = 0;k < s.twigs; k++) {
628
+ const t = 0.22 + k / Math.max(1, s.twigs - 1) * 0.6;
629
+ const side = k % 2 === 0 ? 1 : -1;
630
+ const i = at(main, t);
631
+ const bx = main.x[i] ?? 0.5;
632
+ const by = main.y[i] ?? 0.5;
633
+ const a = (main.a[i] ?? 0) + side * rng.range(0.7, 1.1);
634
+ const len = 0.3 * (1 - t * 0.45);
635
+ const ex = bx + Math.sin(a) * len;
636
+ const ey = by + Math.cos(a) * len - s.droop * len * 0.4;
637
+ const twig = curve(bx, by, (bx + ex) / 2, (by + ey) / 2 + len * 0.15, ex, ey, 8);
638
+ drawStem(f, twig, 0.007, 0.002, LEAF.twig);
639
+ for (let q = 1;q <= s.perTwig; q++) {
640
+ const j = at(twig, q / s.perTwig);
641
+ const alt = q % 2 === 0 ? 1 : -1;
642
+ const ta = (twig.a[j] ?? 0) + (q === s.perTwig ? 0 : alt * rng.range(0.5, 0.9));
643
+ leafAt(twig.x[j] ?? 0, twig.y[j] ?? 0, ta, 1 - 0.15 * (1 - q / s.perTwig), 0.002);
644
+ }
645
+ }
646
+ const tip = main.x.length - 1;
647
+ leafAt(main.x[tip] ?? 0.5, main.y[tip] ?? 0.8, main.a[tip] ?? 0, 1.1, 0.003);
648
+ f.finish(0.5, 0.35);
649
+ }
650
+ function paintBroadleaf(c, seed) {
651
+ cluster(c, seed, {
652
+ shape: "ovate",
653
+ len: 0.15,
654
+ wid: 0.085,
655
+ serrate: 0.03,
656
+ twigs: 6,
657
+ perTwig: 3,
658
+ back: 16,
659
+ droop: 0.1,
660
+ colour: (rng) => vary(rng, LEAF.beech, 0.18)
661
+ });
662
+ }
663
+ function paintMaple(c, seed) {
664
+ cluster(c, seed, {
665
+ shape: "palmate",
666
+ len: 0.24,
667
+ wid: 0.24,
668
+ serrate: 0.04,
669
+ twigs: 4,
670
+ perTwig: 2,
671
+ back: 7,
672
+ droop: 0,
673
+ colour: (rng) => vary(rng, LEAF.maple, 0.15)
674
+ });
675
+ }
676
+ function paintMapleAutumn(c, seed) {
677
+ cluster(c, seed, {
678
+ shape: "palmate",
679
+ len: 0.24,
680
+ wid: 0.24,
681
+ serrate: 0.04,
682
+ twigs: 4,
683
+ perTwig: 2,
684
+ back: 7,
685
+ droop: 0,
686
+ colour: (rng) => vary(rng, rng.pick(AUTUMN), 0.12)
687
+ });
688
+ }
689
+ function paintBirch(c, seed) {
690
+ cluster(c, seed, {
691
+ shape: "ovate",
692
+ len: 0.085,
693
+ wid: 0.06,
694
+ serrate: 0.12,
695
+ twigs: 8,
696
+ perTwig: 5,
697
+ back: 22,
698
+ droop: 0.6,
699
+ colour: (rng) => vary(rng, LEAF.birch, 0.2)
700
+ });
701
+ }
702
+
703
+ // src/material/paint/foliage-compound.ts
704
+ function paintAcacia(c, seed) {
705
+ const rng = createRng2(seed);
706
+ const f = new Foliage(c);
707
+ const rachis = curve(0.5, 0.02, 0.48 + rng.range(-0.05, 0.05), 0.5, 0.5, 0.95);
708
+ for (let layer = 0;layer < 2; layer++) {
709
+ const k = layer === 0 ? 0.5 : 1;
710
+ if (layer === 1)
711
+ drawStem(f, rachis, 0.01, 0.003, LEAF.twig);
712
+ const pairs = layer === 0 ? 6 : 8;
713
+ for (let p = 0;p < pairs; p++) {
714
+ const t = 0.15 + p / pairs * 0.8 + (layer === 0 ? 0.05 : 0);
715
+ const i = at(rachis, t);
716
+ for (const side of [-1, 1]) {
717
+ const a = (rachis.a[i] ?? 0) + side * rng.range(0.9, 1.2);
718
+ const len = 0.22 * (1 - t * 0.5);
719
+ const bx = rachis.x[i] ?? 0.5;
720
+ const by = rachis.y[i] ?? 0.5;
721
+ const pinna = curve(bx, by, bx + Math.sin(a) * len * 0.5, by + Math.cos(a) * len * 0.5 + 0.02, bx + Math.sin(a) * len, by + Math.cos(a) * len, 6);
722
+ drawStem(f, pinna, 0.003, 0.0015, dim(LEAF.twig, k));
723
+ for (let q = 1;q <= 12; q++) {
724
+ const j = at(pinna, q / 12.5);
725
+ const col = dim(vary(rng, LEAF.acacia, 0.15), k);
726
+ for (const s2 of [-1, 1])
727
+ f.leaf("ovate", pinna.x[j] ?? 0, pinna.y[j] ?? 0, (pinna.a[j] ?? 0) + s2 * 1.1, 0.03, 0.011, col, 0, layer * 0.002);
728
+ }
729
+ }
730
+ }
731
+ }
732
+ f.finish(0.5, 0.25);
733
+ }
734
+ function paintPalm(c, seed) {
735
+ const rng = createRng2(seed);
736
+ const f = new Foliage(c);
737
+ const rachis = curve(0.5, 0.02, 0.3, 0.7, 0.78, 0.96, 24);
738
+ for (let layer = 0;layer < 2; layer++) {
739
+ const k = layer === 0 ? 0.55 : 1;
740
+ const n = 26;
741
+ for (let q = 0;q < n; q++) {
742
+ const t = 0.1 + q / n * 0.9 + (layer === 0 ? 0.015 : 0);
743
+ const i = at(rachis, Math.min(1, t));
744
+ const bx = rachis.x[i] ?? 0.5;
745
+ const by = rachis.y[i] ?? 0.5;
746
+ for (const side of [-1, 1]) {
747
+ const a = (rachis.a[i] ?? 0) + side * rng.range(0.75, 1);
748
+ const len = 0.3 * Math.sin(Math.PI * (0.2 + 0.75 * t)) * rng.range(0.85, 1.05);
749
+ const wid = 0.03;
750
+ f.leaf("lance", bx, by, a, len, wid, dim(vary(rng, LEAF.palm, 0.15), k), 0, layer * 0.002);
751
+ if (rng.next() < 0.3) {
752
+ const tx = bx + Math.sin(a) * len * 0.72;
753
+ const ty = by + Math.cos(a) * len * 0.72;
754
+ f.leaf("blade", tx, ty, a, len * 0.28, wid * 0.55, dim(vary(rng, LEAF.dry, 0.2), k), 0, layer * 0.002 + 0.0002);
755
+ }
756
+ }
757
+ }
758
+ if (layer === 0)
759
+ drawStem(f, rachis, 0.018, 0.004, dim(LEAF.palm, 0.8));
760
+ }
761
+ f.finish(0.5, 0.2);
762
+ }
763
+ function paintWillow(c, seed) {
764
+ const rng = createRng2(seed);
765
+ const f = new Foliage(c);
766
+ const strands = 9;
767
+ for (let s = 0;s < strands; s++) {
768
+ const back = s < 3;
769
+ const k = back ? 0.55 : 1;
770
+ const ex = 0.5 + rng.range(-0.4, 0.4);
771
+ const strand = curve(0.5 + rng.range(-0.03, 0.03), 0.02, (0.5 + ex) / 2 + rng.range(-0.1, 0.1), 0.5, ex, rng.range(0.85, 0.97), 24);
772
+ drawStem(f, strand, 0.006, 0.0015, dim(LEAF.twig, k));
773
+ for (let q = 2;q < 30; q++) {
774
+ const j = at(strand, q / 30);
775
+ const side = q % 2 === 0 ? 1 : -1;
776
+ const a = (strand.a[j] ?? 0) + side * rng.range(0.2, 0.45);
777
+ const col = dim(vary(rng, LEAF.willow, 0.18), k);
778
+ f.leaf("lance", strand.x[j] ?? 0, strand.y[j] ?? 0, a, rng.range(0.07, 0.1), 0.014, col, 0, back ? 0 : 0.002);
779
+ }
780
+ }
781
+ f.finish(0.5, 0.2);
782
+ }
783
+
784
+ // src/material/paint/foliage-conifer.ts
785
+ function comb(f, rng, shoot, len, k, lift) {
786
+ const n = shoot.x.length - 1;
787
+ for (let i = 0;i <= n; i++) {
788
+ for (const side of [-1, 1]) {
789
+ const a = (shoot.a[i] ?? 0) + side * rng.range(0.7, 1);
790
+ const col = dim(vary(rng, LEAF.spruce, 0.2), k);
791
+ f.leaf("needle", shoot.x[i] ?? 0, shoot.y[i] ?? 0, a, len * rng.range(0.8, 1.1), 0.006, col, 0, lift);
792
+ }
793
+ }
794
+ }
795
+ function spruce(c, seed, snow) {
796
+ const rng = createRng2(seed);
797
+ const f = new Foliage(c);
798
+ const axis = curve(0.5, 0.02, 0.5 + rng.range(-0.04, 0.04), 0.5, 0.5, 0.95, 60);
799
+ const shoots = [];
800
+ for (let s = 0;s < 8; s++) {
801
+ const t = 0.15 + s / 8 * 0.75;
802
+ const i = at(axis, t);
803
+ const side = s % 2 === 0 ? 1 : -1;
804
+ const bx = axis.x[i] ?? 0.5;
805
+ const by = axis.y[i] ?? 0.5;
806
+ const len = 0.32 * (1 - t * 0.6);
807
+ const a = side * rng.range(0.9, 1.2);
808
+ shoots.push(curve(bx, by, bx + Math.sin(a) * len * 0.5, by + Math.cos(a) * len * 0.5, bx + Math.sin(a) * len, by + Math.cos(a) * len + 0.03, 24));
809
+ }
810
+ for (const sh of shoots)
811
+ comb(f, rng, sh, 0.045, 0.55, 0);
812
+ drawStem(f, axis, 0.012, 0.004, LEAF.bark);
813
+ for (const sh of shoots)
814
+ drawStem(f, sh, 0.005, 0.002, LEAF.bark);
815
+ comb(f, rng, axis, 0.05, 1, 0.002);
816
+ for (const sh of shoots)
817
+ comb(f, rng, sh, 0.04, 1, 0.002);
818
+ if (snow) {
819
+ for (const sh of [axis, ...shoots]) {
820
+ const n = sh.x.length - 1;
821
+ for (let i = 2;i < n; i += 3) {
822
+ const col = vary(rng, LEAF.snow, 0.05);
823
+ f.leaf("ovate", (sh.x[i] ?? 0) - 0.02, (sh.y[i] ?? 0) + 0.008, Math.PI / 2 + rng.range(-0.3, 0.3), rng.range(0.04, 0.07), rng.range(0.025, 0.04), col, 0, 0.006);
824
+ }
825
+ }
826
+ }
827
+ f.finish(0.5, 0.25);
828
+ }
829
+ var paintSpruce = (c, seed) => spruce(c, seed, false);
830
+ var paintSpruceSnow = (c, seed) => spruce(c, seed, true);
831
+ function paintNeedles(c, seed) {
832
+ const rng = createRng2(seed);
833
+ const f = new Foliage(c);
834
+ const twigs = [];
835
+ for (let s = 0;s < 5; s++) {
836
+ const ex = 0.5 + (s - 2) * 0.13 + rng.range(-0.03, 0.03);
837
+ twigs.push(curve(0.5, 0.02, 0.5 + (ex - 0.5) * 0.4, 0.3, ex, rng.range(0.55, 0.7), 12));
838
+ }
839
+ for (const [s, tw] of twigs.entries()) {
840
+ drawStem(f, tw, 0.012, 0.006, LEAF.bark);
841
+ const k = s % 2 === 0 ? 0.6 : 1;
842
+ for (let i = 6;i < tw.x.length; i++) {
843
+ const fan = i === tw.x.length - 1 ? 14 : 4;
844
+ for (let q = 0;q < fan; q++) {
845
+ const a = (tw.a[i] ?? 0) + rng.range(-1.1, 1.1) * (fan > 4 ? 0.8 : 1.2);
846
+ const col = dim(vary(rng, LEAF.pine, 0.2), k);
847
+ f.leaf("needle", tw.x[i] ?? 0, tw.y[i] ?? 0, a, rng.range(0.12, 0.2), 0.006, col, 0, k * 0.002);
848
+ }
849
+ }
850
+ }
851
+ f.finish(0.5, 0.3);
852
+ }
853
+ function paintReeds(c, seed) {
854
+ const rng = createRng2(seed);
855
+ const f = new Foliage(c);
856
+ for (let b = 0;b < 22; b++) {
857
+ const back = b < 8;
858
+ const x = 0.5 + rng.range(-0.2, 0.2);
859
+ const tilt = (x - 0.5) * 1.2 + rng.range(-0.25, 0.25);
860
+ const col = dim(vary(rng, rng.next() < 0.2 ? LEAF.dry : LEAF.reed, 0.2), back ? 0.55 : 1);
861
+ f.leaf("blade", x, 0, tilt, rng.range(0.55, 0.97), rng.range(0.018, 0.032), col, 0, back ? 0 : 0.002);
862
+ }
863
+ f.finish(0.5, 0.15);
864
+ }
865
+
866
+ // src/material/paint/foliage.ts
867
+ var FOLIAGE_PAINTERS = {
868
+ foliage_broadleaf: paintBroadleaf,
869
+ foliage_maple: paintMaple,
870
+ foliage_maple_autumn: paintMapleAutumn,
871
+ foliage_birch: paintBirch,
872
+ foliage_acacia: paintAcacia,
873
+ foliage_palm: paintPalm,
874
+ foliage_spruce: paintSpruce,
875
+ foliage_spruce_snow: paintSpruceSnow,
876
+ foliage_willow: paintWillow,
877
+ foliage_reeds: paintReeds,
878
+ foliage_needles: paintNeedles
879
+ };
880
+
881
+ // src/material/paint/decode-png.ts
882
+ import { inflateSync } from "node:zlib";
883
+ var SIG = [137, 80, 78, 71, 13, 10, 26, 10];
884
+ var CHANNELS = { 0: 1, 2: 3, 3: 1, 4: 2, 6: 4 };
885
+ function bad(cause) {
886
+ throw new Ts23dError2("T3D_RECIPE_INVALID", {
887
+ cause: `cell image: ${cause}`,
888
+ fix: "pass PNG bytes, 8 bits per channel, non-interlaced (e.g. re-save as RGBA8 PNG)"
889
+ });
890
+ }
891
+ function paeth(a, b, c) {
892
+ const p = a + b - c;
893
+ const pa = Math.abs(p - a);
894
+ const pb = Math.abs(p - b);
895
+ const pc = Math.abs(p - c);
896
+ return pa <= pb && pa <= pc ? a : pb <= pc ? b : c;
897
+ }
898
+ function decodePng(bytes) {
899
+ if (bytes.length < 33 || SIG.some((s, i) => bytes[i] !== s))
900
+ bad("not a PNG (bad signature)");
901
+ const dv = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
902
+ let width = 0;
903
+ let height = 0;
904
+ let type = -1;
905
+ let palette = new Uint8Array;
906
+ let trns = new Uint8Array;
907
+ const idat = [];
908
+ for (let o = 8;o + 8 <= bytes.length; ) {
909
+ const len = dv.getUint32(o);
910
+ const name = String.fromCharCode(...bytes.subarray(o + 4, o + 8));
911
+ const body = bytes.subarray(o + 8, o + 8 + len);
912
+ if (name === "IHDR") {
913
+ width = dv.getUint32(o + 8);
914
+ height = dv.getUint32(o + 12);
915
+ type = body[9] ?? -1;
916
+ if (body[8] !== 8)
917
+ bad(`bit depth ${body[8]} (only 8 is supported)`);
918
+ if (body[12] !== 0)
919
+ bad("interlaced");
920
+ } else if (name === "PLTE")
921
+ palette = body;
922
+ else if (name === "tRNS")
923
+ trns = body;
924
+ else if (name === "IDAT")
925
+ idat.push(body);
926
+ else if (name === "IEND")
927
+ break;
928
+ o += 12 + len;
929
+ }
930
+ const ch = CHANNELS[type];
931
+ if (!ch || width === 0 || height === 0)
932
+ bad(`colour type ${type} or empty image`);
933
+ const raw = new Uint8Array(inflateSync(Buffer.concat(idat)));
934
+ const stride = width * ch;
935
+ if (raw.length < height * (stride + 1))
936
+ bad("truncated image data");
937
+ const px = new Uint8Array(height * stride);
938
+ for (let y = 0;y < height; y++) {
939
+ const f = raw[y * (stride + 1)] ?? 0;
940
+ const src = y * (stride + 1) + 1;
941
+ const row = y * stride;
942
+ for (let x = 0;x < stride; x++) {
943
+ const a = x >= ch ? px[row + x - ch] ?? 0 : 0;
944
+ const b = y > 0 ? px[row - stride + x] ?? 0 : 0;
945
+ const c = x >= ch && y > 0 ? px[row - stride + x - ch] ?? 0 : 0;
946
+ const v = raw[src + x] ?? 0;
947
+ const pred = f === 1 ? a : f === 2 ? b : f === 3 ? a + b >> 1 : f === 4 ? paeth(a, b, c) : 0;
948
+ px[row + x] = v + pred & 255;
949
+ }
950
+ }
951
+ const data = new Uint8Array(width * height * 4);
952
+ for (let i = 0;i < width * height; i++) {
953
+ const s = i * ch;
954
+ const g = px[s] ?? 0;
955
+ const o = i * 4;
956
+ if (type === 3) {
957
+ data[o] = palette[g * 3] ?? 0;
958
+ data[o + 1] = palette[g * 3 + 1] ?? 0;
959
+ data[o + 2] = palette[g * 3 + 2] ?? 0;
960
+ data[o + 3] = trns[g] ?? 255;
961
+ } else if (ch >= 3) {
962
+ data[o] = g;
963
+ data[o + 1] = px[s + 1] ?? 0;
964
+ data[o + 2] = px[s + 2] ?? 0;
965
+ data[o + 3] = ch === 4 ? px[s + 3] ?? 255 : 255;
966
+ } else {
967
+ data[o] = g;
968
+ data[o + 1] = g;
969
+ data[o + 2] = g;
970
+ data[o + 3] = ch === 2 ? px[s + 1] ?? 255 : 255;
971
+ }
972
+ }
973
+ return { width, height, data };
974
+ }
975
+
976
+ // src/material/paint/stamp.ts
977
+ var STAMP_ROUGHNESS = 0.6;
978
+ function stampImage(c, png) {
979
+ const img = decodePng(png);
980
+ const out = new Uint8Array(c.w * c.h * 4);
981
+ for (let y = 0;y < c.h; y++) {
982
+ const sy = Math.min(img.height - 1, Math.max(0, (c.h - 1 - y + 0.5) / c.h * img.height - 0.5));
983
+ const y0 = Math.floor(sy);
984
+ const y1 = Math.min(img.height - 1, y0 + 1);
985
+ const fy = sy - y0;
986
+ for (let x = 0;x < c.w; x++) {
987
+ const sx = Math.min(img.width - 1, Math.max(0, (x + 0.5) / c.w * img.width - 0.5));
988
+ const x0 = Math.floor(sx);
989
+ const x1 = Math.min(img.width - 1, x0 + 1);
990
+ const fx = sx - x0;
991
+ for (let k = 0;k < 4; k++) {
992
+ const a = img.data[(y0 * img.width + x0) * 4 + k] ?? 0;
993
+ const b = img.data[(y0 * img.width + x1) * 4 + k] ?? 0;
994
+ const d = img.data[(y1 * img.width + x0) * 4 + k] ?? 0;
995
+ const e = img.data[(y1 * img.width + x1) * 4 + k] ?? 0;
996
+ const top = a + (b - a) * fx;
997
+ out[(y * c.w + x) * 4 + k] = Math.round(top + (d + (e - d) * fx - top) * fy);
998
+ }
999
+ }
1000
+ }
1001
+ c.rgba = out;
1002
+ c.height.fill(0);
1003
+ c.rough.fill(STAMP_ROUGHNESS);
1004
+ c.metal.fill(0);
1005
+ }
1006
+
1007
+ // src/material/paint/courses.ts
1008
+ class Courses {
1009
+ id;
1010
+ dx;
1011
+ ux;
1012
+ index = -1;
1013
+ y0 = 0;
1014
+ y1 = 0;
1015
+ bounds = [0];
1016
+ c;
1017
+ spec;
1018
+ constructor(c, spec) {
1019
+ this.c = c;
1020
+ this.spec = spec;
1021
+ this.id = new Int32Array(c.w);
1022
+ this.dx = new Float32Array(c.w);
1023
+ this.ux = new Float32Array(c.w);
1024
+ const rng = createRng2(spec.seed).fork("rows");
1025
+ let y = 0;
1026
+ while (y < c.metresV) {
1027
+ y += spec.course * (1 + spec.irregular * (rng.next() - 0.5));
1028
+ this.bounds.push(y);
1029
+ }
1030
+ }
1031
+ at(ym) {
1032
+ if (this.index >= 0 && ym >= this.y0 && ym < this.y1)
1033
+ return this.index;
1034
+ let k = 0;
1035
+ while (k < this.bounds.length - 2 && ym >= (this.bounds[k + 1] ?? 0))
1036
+ k++;
1037
+ this.index = k;
1038
+ this.y0 = this.bounds[k] ?? 0;
1039
+ this.y1 = this.bounds[k + 1] ?? this.c.metresV;
1040
+ this.fill(k);
1041
+ return k;
1042
+ }
1043
+ fill(k) {
1044
+ const { w, metresU } = this.c;
1045
+ const s = this.spec;
1046
+ const rng = createRng2(s.seed).fork(`course${k}`);
1047
+ const n = Math.max(1, Math.round(metresU / s.length));
1048
+ const lens = [];
1049
+ for (let i = 0;i < n; i++)
1050
+ lens.push(1 + s.irregular * (rng.next() - 0.5));
1051
+ const total = lens.reduce((a, b) => a + b, 0);
1052
+ const joints = [0];
1053
+ for (const l of lens)
1054
+ joints.push((joints[joints.length - 1] ?? 0) + l / total * metresU);
1055
+ const shift = s.irregular > 0 ? rng.next() * metresU : k % 2 * s.stagger * (metresU / n);
1056
+ const px = metresU / w;
1057
+ for (let x = 0;x < w; x++) {
1058
+ let u = ((x + 0.5) * px - shift) % metresU;
1059
+ if (u < 0)
1060
+ u += metresU;
1061
+ let t = 0;
1062
+ while (t < n - 1 && u >= (joints[t + 1] ?? metresU))
1063
+ t++;
1064
+ const a = joints[t] ?? 0;
1065
+ const b = joints[t + 1] ?? metresU;
1066
+ this.id[x] = t;
1067
+ this.ux[x] = u - a;
1068
+ this.dx[x] = Math.min(u - a, b - u);
1069
+ }
1070
+ }
1071
+ }
1072
+
1073
+ // src/material/paint/bond.ts
1074
+ function paintBond(c, r, seed) {
1075
+ const p = r.params;
1076
+ const mortar = param(p, "mortar", 0.01);
1077
+ const depth = param(p, "depth", 0.006);
1078
+ const bevel = param(p, "bevel", 0.004);
1079
+ const relief = param(p, "relief", 0.0008);
1080
+ const variation = param(p, "variation", 0.3);
1081
+ const course = param(p, "course", 0.075);
1082
+ const length = param(p, "length", 0.225);
1083
+ const courses = new Courses(c, {
1084
+ course,
1085
+ length,
1086
+ irregular: param(p, "irregular", 0),
1087
+ stagger: param(p, "stagger", 0.5),
1088
+ seed
1089
+ });
1090
+ const pu = c.metresU / c.w;
1091
+ const pv = c.metresV / c.h;
1092
+ const half = mortar / 2;
1093
+ for (let y = 0;y < c.h; y++) {
1094
+ const ym = (y + 0.5) * pv;
1095
+ const k = courses.at(ym);
1096
+ const dy = Math.min(ym - courses.y0, courses.y1 - ym);
1097
+ for (let x = 0;x < c.w; x++) {
1098
+ const i = y * c.w + x;
1099
+ const xm = (x + 0.5) * pu;
1100
+ const unit = courses.id[x] ?? 0;
1101
+ const chip = noiseAt(c, xm, ym, 0.02, 0.02, seed + 7, 3) * mortar * 0.6;
1102
+ const d = Math.min(courses.dx[x] ?? 0, dy) + chip;
1103
+ const fine = noiseAt(c, xm, ym, 0.006, 0.006, seed + 11, 2);
1104
+ if (d < half) {
1105
+ c.height[i] = -depth + fine * 0.0006;
1106
+ c.tone[i] = 0.95 + 0.04 * fine;
1107
+ c.rough[i] = Math.min(1, r.roughness + 0.08);
1108
+ continue;
1109
+ }
1110
+ const h = hash2(k, unit, seed);
1111
+ const h2 = hash2(unit, k, seed + 3);
1112
+ const big = noiseAt(c, xm, ym, length * 0.6, course * 0.8, seed + 5, 3);
1113
+ const edge = smooth(0, bevel, d - half);
1114
+ const tilt = ((h2 - 0.5) * (courses.ux[x] ?? 0) * 0.02 + (h - 0.5) * 0.001) * edge;
1115
+ const pit = fine > 0.55 ? (fine - 0.55) * 0.004 : 0;
1116
+ c.height[i] = -depth * (1 - edge) + relief * big + tilt - pit;
1117
+ c.tone[i] = (0.62 - variation * 0.5 + variation * h) * (1 + 0.15 * big + 0.05 * fine) - pit * 40;
1118
+ c.rough[i] = r.roughness + 0.06 * fine;
1119
+ }
1120
+ }
1121
+ }
1122
+
1123
+ // src/material/paint/concrete.ts
1124
+ var PORE_CELL = 0.008;
1125
+ function paintConcrete(c, r, seed) {
1126
+ const p = r.params;
1127
+ const pores = param(p, "pores", 0.35);
1128
+ const ties = param(p, "ties", 0.6);
1129
+ const lift = param(p, "lift", 0.6);
1130
+ const mottle = param(p, "mottle", 0.1);
1131
+ const pu = c.metresU / c.w;
1132
+ const pv = c.metresV / c.h;
1133
+ const cellM = Math.max(PORE_CELL, 2.5 * Math.max(pu, pv));
1134
+ const pPer = periodFor(c, cellM);
1135
+ const pCell = c.wrapU ? c.metresU / pPer : cellM;
1136
+ const tPer = ties > 0 ? periodFor(c, ties) : 1;
1137
+ const tCell = c.wrapU ? c.metresU / tPer : ties;
1138
+ for (let y = 0;y < c.h; y++) {
1139
+ const ym = (y + 0.5) * pv;
1140
+ const liftD = lift > 0 ? Math.abs(ym - Math.round(ym / lift) * lift) : 1;
1141
+ const liftGroove = 1 - smooth(0, 0.003, liftD);
1142
+ const liftBand = lift > 0 ? hash2(Math.floor(ym / lift), 0, seed + 4) - 0.5 : 0;
1143
+ for (let x = 0;x < c.w; x++) {
1144
+ const i = y * c.w + x;
1145
+ const xm = (x + 0.5) * pu;
1146
+ const big = noiseAt(c, xm, ym, 0.25, 0.25, seed, 4);
1147
+ const fine = noiseAt(c, xm, ym, 0.004, 0.004, seed + 2, 2);
1148
+ const ix = Math.floor(xm / pCell) % pPer;
1149
+ const iy = Math.floor(ym / pCell);
1150
+ let pit = 0;
1151
+ if (hash2(ix, iy, seed + 5) < pores) {
1152
+ const cx = (ix + 0.2 + 0.6 * hash2(ix, iy, seed + 6)) * pCell;
1153
+ const cy = (iy + 0.2 + 0.6 * hash2(ix, iy, seed + 7)) * pCell;
1154
+ const rad = pCell * (0.12 + 0.25 * hash2(ix, iy, seed + 8));
1155
+ const d = Math.hypot(xm - cx, ym - cy);
1156
+ pit = d < rad ? 1 - d / rad : 0;
1157
+ }
1158
+ let tie = 0;
1159
+ if (ties > 0) {
1160
+ const tx = xm - (Math.floor(xm / tCell) + 0.5) * tCell;
1161
+ const ty = ym - (Math.floor(ym / ties) + 0.5) * ties;
1162
+ const d = Math.hypot(tx, ty);
1163
+ tie = d < 0.012 ? 1 : 0;
1164
+ }
1165
+ c.height[i] = 0.0005 * big + 0.00015 * fine - 0.0015 * pit - 0.006 * tie - 0.0012 * liftGroove;
1166
+ c.tone[i] = (0.78 + mottle * big + 0.05 * liftBand) * (1 + 0.04 * fine) * (1 - 0.4 * pit) * (1 - 0.5 * tie);
1167
+ c.rough[i] = r.roughness + 0.05 * fine + 0.05 * big;
1168
+ }
1169
+ }
1170
+ }
1171
+
1172
+ // src/material/paint/masks.ts
1173
+ function rustStreaks(c, amount, seed) {
1174
+ if (amount <= 0)
1175
+ return;
1176
+ const pu = c.metresU / c.w;
1177
+ const pv = c.metresV / c.h;
1178
+ for (let y = 0;y < c.h; y++) {
1179
+ const ym = (y + 0.5) * pv;
1180
+ for (let x = 0;x < c.w; x++) {
1181
+ const xm = (x + 0.5) * pu;
1182
+ const run = noiseAt(c, xm, ym, 0.025, 0.6, seed, 3);
1183
+ const patch = noiseAt(c, xm, ym, 0.3, 0.3, seed + 1, 2);
1184
+ const m = amount * smooth(0.15, 0.6, run) * smooth(-0.1, 0.4, patch);
1185
+ if (m <= 0)
1186
+ continue;
1187
+ const i = y * c.w + x;
1188
+ c.tone[i] = (c.tone[i] ?? 1) * (1 - 0.4 * m);
1189
+ c.rough[i] = Math.min(1, (c.rough[i] ?? 0.8) + 0.2 * m);
1190
+ c.metal[i] = (c.metal[i] ?? 0) * (1 - m);
1191
+ }
1192
+ }
1193
+ }
1194
+ function grime(c, amount, seed) {
1195
+ if (amount <= 0)
1196
+ return;
1197
+ const pu = c.metresU / c.w;
1198
+ const pv = c.metresV / c.h;
1199
+ for (let y = 0;y < c.h; y++) {
1200
+ const ym = (y + 0.5) * pv;
1201
+ const low = (1 - ym / c.metresV) ** 2;
1202
+ for (let x = 0;x < c.w; x++) {
1203
+ const xm = (x + 0.5) * pu;
1204
+ const streak = smooth(0, 0.7, noiseAt(c, xm, ym, 0.05, 0.5, seed, 3));
1205
+ const blot = 0.6 + 0.4 * noiseAt(c, xm, ym, 0.2, 0.2, seed + 1, 3);
1206
+ const m = amount * Math.min(1, low * blot + 0.35 * streak);
1207
+ const i = y * c.w + x;
1208
+ c.tone[i] = (c.tone[i] ?? 1) * (1 - 0.3 * m);
1209
+ c.rough[i] = Math.min(1, (c.rough[i] ?? 0.8) + 0.1 * m);
1210
+ }
1211
+ }
1212
+ }
1213
+
1214
+ // src/material/paint/planks.ts
1215
+ function paintPlanks(c, r, seed) {
1216
+ const p = r.params;
1217
+ const width = param(p, "width", 0.15);
1218
+ const length = param(p, "length", 1);
1219
+ const gap = param(p, "gap", 0.004);
1220
+ const depth = param(p, "depth", 0.004);
1221
+ const knots = param(p, "knots", 1.5);
1222
+ const boards = new Courses(c, { course: width, length, irregular: 0.6, stagger: 0, seed });
1223
+ const pu = c.metresU / c.w;
1224
+ const pv = c.metresV / c.h;
1225
+ for (let y = 0;y < c.h; y++) {
1226
+ const ym = (y + 0.5) * pv;
1227
+ const k = boards.at(ym);
1228
+ const dy = Math.min(ym - boards.y0, boards.y1 - ym);
1229
+ const vy = (ym - boards.y0) / (boards.y1 - boards.y0);
1230
+ for (let x = 0;x < c.w; x++) {
1231
+ const i = y * c.w + x;
1232
+ const xm = (x + 0.5) * pu;
1233
+ const id = boards.id[x] ?? 0;
1234
+ const h = hash2(k, id, seed);
1235
+ const d = Math.min((boards.dx[x] ?? 0) * 1.5, dy);
1236
+ const edge = smooth(gap * 0.5, gap * 1.5, d);
1237
+ const warp = noiseAt(c, xm, ym, 0.4, 0.05, seed + id * 31 + k * 7, 3);
1238
+ const ring = Math.sin((vy * 9 + warp * 3 + h * 6) * Math.PI * 2);
1239
+ const fibre = noiseAt(c, xm, ym, 0.08, 0.002, seed + 17, 2);
1240
+ let knot = 0;
1241
+ const nk = Math.floor(knots * length * h * 2);
1242
+ for (let q = 0;q < nk; q++) {
1243
+ const kx = hash2(k * 13 + q, id, seed + 41) * c.metresU;
1244
+ const ky = boards.y0 + (0.25 + 0.5 * hash2(k, id * 5 + q, seed + 43)) * (boards.y1 - boards.y0);
1245
+ let ddx = Math.abs(xm - kx);
1246
+ if (c.wrapU)
1247
+ ddx = Math.min(ddx, c.metresU - ddx);
1248
+ const rr = Math.sqrt((ddx / 2.2) ** 2 + (ym - ky) ** 2) / (width * 0.12);
1249
+ if (rr < 1)
1250
+ knot = Math.max(knot, 1 - rr);
1251
+ }
1252
+ c.height[i] = -depth * (1 - edge) + 0.0004 * ring * edge + 0.0003 * fibre - 0.0008 * knot;
1253
+ c.tone[i] = (0.66 + 0.22 * h) * (1 + 0.08 * ring + 0.06 * fibre) * (1 - 0.5 * knot) * (0.5 + 0.5 * edge);
1254
+ c.rough[i] = r.roughness + 0.05 * fibre + 0.1 * (1 - edge);
1255
+ }
1256
+ }
1257
+ }
1258
+
1259
+ // src/material/paint/plates.ts
1260
+ function along(q, pitch) {
1261
+ const t = q / pitch;
1262
+ return Math.abs(t - Math.round(t)) * pitch;
1263
+ }
1264
+ function paintPlates(c, r, seed) {
1265
+ const p = r.params;
1266
+ const lap = param(p, "lap", 0.003);
1267
+ const rivet = param(p, "rivet", 0.014);
1268
+ const pitch = param(p, "pitch", 0.09);
1269
+ const inset = param(p, "inset", 0.05);
1270
+ const chips = param(p, "chips", 0.3);
1271
+ const metal = r.metalness ?? 0;
1272
+ const plates = new Courses(c, {
1273
+ course: param(p, "height", 0.5),
1274
+ length: param(p, "width", 1),
1275
+ irregular: 0,
1276
+ stagger: 0.5,
1277
+ seed
1278
+ });
1279
+ const pu = c.metresU / c.w;
1280
+ const pv = c.metresV / c.h;
1281
+ for (let y = 0;y < c.h; y++) {
1282
+ const ym = (y + 0.5) * pv;
1283
+ const k = plates.at(ym);
1284
+ const dy = Math.min(ym - plates.y0, plates.y1 - ym);
1285
+ for (let x = 0;x < c.w; x++) {
1286
+ const i = y * c.w + x;
1287
+ const xm = (x + 0.5) * pu;
1288
+ const ux = plates.ux[x] ?? 0;
1289
+ const dx = plates.dx[x] ?? 0;
1290
+ const id = plates.id[x] ?? 0;
1291
+ const hammer = noiseAt(c, xm, ym, 0.05, 0.05, seed + 3, 3);
1292
+ const fine = noiseAt(c, xm, ym, 0.004, 0.004, seed + 9, 2);
1293
+ const seam = Math.min(dx, dy);
1294
+ let h = lap * smooth(0, 0.004, ux) + 0.0004 * hammer - 0.001 * (1 - smooth(0, 0.0015, seam));
1295
+ const ry = Math.abs(dy - inset);
1296
+ const rx = Math.abs(dx - inset);
1297
+ const d1 = Math.sqrt(rx * rx + along(ym - plates.y0, pitch) ** 2);
1298
+ const d2 = Math.sqrt(ry * ry + along(xm, pitch) ** 2);
1299
+ const d = Math.min(dx > inset * 2 ? 1 : d1, dy > inset * 2 ? 1 : d2);
1300
+ const dome = d < rivet ? Math.sqrt(1 - (d / rivet) ** 2) : 0;
1301
+ h += dome * rivet * 0.5;
1302
+ const chip = smooth(0.35, 0.45, noiseAt(c, xm, ym, 0.03, 0.03, seed + 21, 4) - (seam * 6 + 0.2) + chips * 0.3);
1303
+ c.height[i] = h - chip * 0.0003;
1304
+ c.tone[i] = (0.8 + 0.06 * hash2(k, id, seed)) * (1 + 0.06 * hammer + 0.03 * fine) * (1 + 0.15 * dome);
1305
+ c.rough[i] = r.roughness + 0.08 * fine - 0.25 * chip;
1306
+ c.metal[i] = metal + (1 - metal) * chip;
1307
+ }
1308
+ }
1309
+ rustStreaks(c, param(p, "rust", 0.4), seed + 77);
1310
+ }
1311
+
1312
+ // src/material/paint/slate.ts
1313
+ function paintSlate(c, r, seed) {
1314
+ const p = r.params;
1315
+ const course = param(p, "course", 0.11);
1316
+ const gap = param(p, "gap", 0.003);
1317
+ const thick = param(p, "thick", 0.007);
1318
+ const variation = param(p, "variation", 0.3);
1319
+ const rows = new Courses(c, {
1320
+ course,
1321
+ length: param(p, "length", 0.25),
1322
+ irregular: 0.15,
1323
+ stagger: 0.5,
1324
+ seed
1325
+ });
1326
+ const pu = c.metresU / c.w;
1327
+ const pv = c.metresV / c.h;
1328
+ for (let y = 0;y < c.h; y++) {
1329
+ const ym = (y + 0.5) * pv;
1330
+ const k = rows.at(ym);
1331
+ const span = rows.y1 - rows.y0;
1332
+ for (let x = 0;x < c.w; x++) {
1333
+ const i = y * c.w + x;
1334
+ const xm = (x + 0.5) * pu;
1335
+ const id = rows.id[x] ?? 0;
1336
+ const h = hash2(k, id, seed);
1337
+ const chip = (noiseAt(c, xm, ym, 0.015, 0.01, seed + 5, 3) + 0.3) * span * 0.08;
1338
+ const uy = Math.max(0, ym - rows.y0 - Math.max(0, chip));
1339
+ const t = uy / span;
1340
+ const lam = noiseAt(c, xm, ym, 0.12, 0.004, seed + 9, 3);
1341
+ const fine = noiseAt(c, xm, ym, 0.005, 0.005, seed + 13, 2);
1342
+ const sideGap = smooth(gap * 0.5, gap * 1.2, rows.dx[x] ?? 0);
1343
+ const bevel = smooth(0, 0.004, uy);
1344
+ const tilt = (hash2(id, k, seed + 1) - 0.5) * 0.002 * ((rows.ux[x] ?? 0) / 0.25);
1345
+ c.height[i] = (thick * (1 - t) * bevel + 0.0005 * lam + tilt) * sideGap - thick * (1 - sideGap);
1346
+ c.tone[i] = (0.62 - variation * 0.5 + variation * h) * (1 + 0.08 * lam + 0.04 * fine) * (0.55 + 0.45 * sideGap);
1347
+ c.rough[i] = r.roughness + 0.08 * lam + 0.05 * fine;
1348
+ }
1349
+ }
1350
+ }
1351
+
1352
+ // src/material/paint/surface.ts
1353
+ function paintFlat(c, r) {
1354
+ c.tone.fill(param(r.params, "tone", 1));
1355
+ c.rough.fill(r.roughness);
1356
+ c.metal.fill(r.metalness ?? 0);
1357
+ c.height.fill(0);
1358
+ }
1359
+ function paintNoise(c, r, seed) {
1360
+ const tone = param(r.params, "tone", 0.93);
1361
+ const mottle = param(r.params, "mottle", 0.03);
1362
+ const relief = param(r.params, "relief", 0.0002);
1363
+ const size = param(r.params, "size", 0.3);
1364
+ const pu = c.metresU / c.w;
1365
+ const pv = c.metresV / c.h;
1366
+ const metal = r.metalness ?? 0;
1367
+ for (let y = 0;y < c.h; y++) {
1368
+ const ym = (y + 0.5) * pv;
1369
+ for (let x = 0;x < c.w; x++) {
1370
+ const xm = (x + 0.5) * pu;
1371
+ const big = noiseAt(c, xm, ym, size, size, seed, 4);
1372
+ const fine = noiseAt(c, xm, ym, 0.01, 0.01, seed + 1, 2);
1373
+ const i = y * c.w + x;
1374
+ c.tone[i] = tone + mottle * big;
1375
+ c.rough[i] = r.roughness + 0.06 * big + 0.03 * fine;
1376
+ c.metal[i] = metal;
1377
+ c.height[i] = relief * (big + 0.3 * fine);
1378
+ }
1379
+ }
1380
+ }
1381
+ function paintGlass(c, r, seed) {
1382
+ const ripple = param(r.params, "ripple", 0.0002);
1383
+ const smudge = param(r.params, "smudge", 0.08);
1384
+ const pu = c.metresU / c.w;
1385
+ const pv = c.metresV / c.h;
1386
+ for (let y = 0;y < c.h; y++) {
1387
+ const ym = (y + 0.5) * pv;
1388
+ for (let x = 0;x < c.w; x++) {
1389
+ const xm = (x + 0.5) * pu;
1390
+ const wave = noiseAt(c, xm, ym, 0.15, 0.4, seed, 3);
1391
+ const smear = noiseAt(c, xm, ym, 0.08, 0.08, seed + 1, 4);
1392
+ const i = y * c.w + x;
1393
+ c.tone[i] = 0.92 + 0.02 * wave;
1394
+ c.rough[i] = r.roughness + smudge * Math.max(0, smear);
1395
+ c.metal[i] = 0;
1396
+ c.height[i] = ripple * wave;
1397
+ }
1398
+ }
1399
+ grime(c, param(r.params, "grime", 0.4), seed + 9);
1400
+ }
1401
+
1402
+ // src/material/paint/strip.ts
1403
+ function paintStrip(c, r, seed) {
1404
+ switch (r.kind) {
1405
+ case "bond":
1406
+ paintBond(c, r, seed);
1407
+ break;
1408
+ case "planks":
1409
+ paintPlanks(c, r, seed);
1410
+ break;
1411
+ case "plates":
1412
+ paintPlates(c, r, seed);
1413
+ break;
1414
+ case "slate":
1415
+ paintSlate(c, r, seed);
1416
+ break;
1417
+ case "concrete":
1418
+ paintConcrete(c, r, seed);
1419
+ break;
1420
+ case "glass":
1421
+ paintGlass(c, r, seed);
1422
+ return;
1423
+ case "noise":
1424
+ paintNoise(c, r, seed);
1425
+ break;
1426
+ case "flat":
1427
+ paintFlat(c, r);
1428
+ return;
1429
+ }
1430
+ grime(c, param(r.params, "grime", 0), seed + 101);
1431
+ }
1432
+
1433
+ // src/material/paint/cell.ts
1434
+ var MOTIFS = {
1435
+ window_sash: paintWindowSash,
1436
+ door_panel: paintDoorPanel,
1437
+ clock: paintClock,
1438
+ smokebox: paintSmokebox,
1439
+ sign: paintSign,
1440
+ porthole: paintPorthole
1441
+ };
1442
+ var LEAF_ROUGHNESS = 0.6;
1443
+ function paintCell(c, id, entry, seed) {
1444
+ if ("image" in entry) {
1445
+ stampImage(c, entry.image);
1446
+ return;
1447
+ }
1448
+ if (isFoliage2(id)) {
1449
+ FOLIAGE_PAINTERS[id](c, seed);
1450
+ const k = entry.roughness / LEAF_ROUGHNESS;
1451
+ for (let i = 0;i < c.rough.length; i++)
1452
+ c.rough[i] = Math.min(1, (c.rough[i] ?? 0.6) * k);
1453
+ return;
1454
+ }
1455
+ paintStrip(c, entry, seed);
1456
+ MOTIFS[id]?.(c, entry, seed + 1);
1457
+ }
1458
+
1459
+ // src/material/paint/normal.ts
1460
+ function neighbours(n, wrap) {
1461
+ const lo = new Int32Array(n);
1462
+ const hi = new Int32Array(n);
1463
+ for (let i = 0;i < n; i++) {
1464
+ lo[i] = i > 0 ? i - 1 : wrap ? n - 1 : 0;
1465
+ hi[i] = i < n - 1 ? i + 1 : wrap ? 0 : n - 1;
1466
+ }
1467
+ return { lo, hi };
1468
+ }
1469
+ function sobelNormals(c, strength = 1) {
1470
+ const { w, h, height } = c;
1471
+ const out = new Uint8Array(w * h * 4);
1472
+ const cx = neighbours(w, c.wrapU);
1473
+ const cy = neighbours(h, false);
1474
+ const sx = strength / (8 * (c.metresU / w));
1475
+ const sy = strength / (8 * (c.metresV / h));
1476
+ for (let y = 0;y < h; y++) {
1477
+ const r0 = (cy.lo[y] ?? 0) * w;
1478
+ const r1 = y * w;
1479
+ const r2 = (cy.hi[y] ?? 0) * w;
1480
+ for (let x = 0;x < w; x++) {
1481
+ const xl = cx.lo[x] ?? 0;
1482
+ const xr = cx.hi[x] ?? 0;
1483
+ const tl = height[r0 + xl] ?? 0;
1484
+ const tc = height[r0 + x] ?? 0;
1485
+ const tr = height[r0 + xr] ?? 0;
1486
+ const ml = height[r1 + xl] ?? 0;
1487
+ const mr = height[r1 + xr] ?? 0;
1488
+ const bl = height[r2 + xl] ?? 0;
1489
+ const bc = height[r2 + x] ?? 0;
1490
+ const br = height[r2 + xr] ?? 0;
1491
+ const gx = (tr + 2 * mr + br - tl - 2 * ml - bl) * sx;
1492
+ const gy = (bl + 2 * bc + br - tl - 2 * tc - tr) * sy;
1493
+ const inv = 1 / Math.sqrt(gx * gx + gy * gy + 1);
1494
+ const o = (r1 + x) * 4;
1495
+ out[o] = Math.round((-gx * inv * 0.5 + 0.5) * 255);
1496
+ out[o + 1] = Math.round((gy * inv * 0.5 + 0.5) * 255);
1497
+ out[o + 2] = Math.round((inv * 0.5 + 0.5) * 255);
1498
+ out[o + 3] = 255;
1499
+ }
1500
+ }
1501
+ return out;
1502
+ }
1503
+
1504
+ // src/material/paint/compose.ts
1505
+ function blankSheet(size) {
1506
+ const n = size * size * 4;
1507
+ const albedo = new Uint8Array(n).fill(255);
1508
+ const normal = new Uint8Array(n);
1509
+ const orm = new Uint8Array(n);
1510
+ const flat = new Uint8Array([128, 128, 255, 255]);
1511
+ const base = new Uint8Array([255, 204, 0, 255]);
1512
+ for (let i = 0;i < n; i += 4) {
1513
+ normal.set(flat, i);
1514
+ orm.set(base, i);
1515
+ }
1516
+ return { size, albedo, normal, orm };
1517
+ }
1518
+ function composeRegion(sheet, c, x0, y0, pad) {
1519
+ const ao = cavityOcclusion(c);
1520
+ const maps = [
1521
+ [sheet.albedo, packAlbedo(c, ao)],
1522
+ [sheet.normal, sobelNormals(c)],
1523
+ [sheet.orm, packOrm(c, ao)]
1524
+ ];
1525
+ const padX = c.wrapU ? 0 : pad;
1526
+ for (let y = 0;y < c.h; y++) {
1527
+ const sy = Math.min(c.h - 1 - pad, Math.max(pad, y));
1528
+ const dst = ((y0 + y) * sheet.size + x0) * 4;
1529
+ const src = sy * c.w;
1530
+ const l = (src + padX) * 4;
1531
+ const r = (src + c.w - 1 - padX) * 4;
1532
+ for (const [to, from] of maps) {
1533
+ to.set(from.subarray(l, r + 4), dst + padX * 4);
1534
+ for (let x = 0;x < padX; x++) {
1535
+ to.set(from.subarray(l, l + 4), dst + x * 4);
1536
+ to.set(from.subarray(r, r + 4), dst + (c.w - 1 - x) * 4);
1537
+ }
1538
+ }
1539
+ }
1540
+ }
1541
+
1542
+ // src/material/paint/default-sheet.ts
1543
+ var leaf = { kind: "flat", scaleMetres: 1, roughness: 0.6 };
1544
+ var DEFAULT_SHEET2 = {
1545
+ seed: 1,
1546
+ size: 1024,
1547
+ strips: {
1548
+ plain: { kind: "noise", scaleMetres: 1, roughness: 0.6 },
1549
+ brick: { kind: "bond", scaleMetres: 0.45, roughness: 0.85, params: { grime: 0.2 } },
1550
+ stone: {
1551
+ kind: "bond",
1552
+ scaleMetres: 0.9,
1553
+ roughness: 0.9,
1554
+ params: {
1555
+ course: 0.22,
1556
+ length: 0.4,
1557
+ mortar: 0.016,
1558
+ depth: 0.012,
1559
+ bevel: 0.025,
1560
+ irregular: 0.5,
1561
+ relief: 0.004,
1562
+ variation: 0.3,
1563
+ grime: 0.2
1564
+ }
1565
+ },
1566
+ slate: { kind: "slate", scaleMetres: 0.66, roughness: 0.7 },
1567
+ iron: { kind: "plates", scaleMetres: 0.5, roughness: 0.55, metalness: 0 },
1568
+ wood: { kind: "planks", scaleMetres: 0.6, roughness: 0.75 },
1569
+ glass: { kind: "glass", scaleMetres: 1, roughness: 0.05 },
1570
+ concrete: { kind: "concrete", scaleMetres: 1, roughness: 0.9, params: { grime: 0.3 } }
1571
+ },
1572
+ cells: {
1573
+ window_sash: { kind: "noise", scaleMetres: 1, roughness: 0.5 },
1574
+ door_panel: { kind: "noise", scaleMetres: 1, roughness: 0.6 },
1575
+ clock: { kind: "noise", scaleMetres: 1, roughness: 0.3 },
1576
+ smokebox: {
1577
+ kind: "plates",
1578
+ scaleMetres: 1,
1579
+ roughness: 0.6,
1580
+ metalness: 0.2,
1581
+ params: { rust: 0.3 }
1582
+ },
1583
+ sign: { kind: "noise", scaleMetres: 1, roughness: 0.5 },
1584
+ porthole: { kind: "plates", scaleMetres: 1, roughness: 0.5 },
1585
+ foliage_broadleaf: leaf,
1586
+ foliage_maple: leaf,
1587
+ foliage_maple_autumn: leaf,
1588
+ foliage_birch: leaf,
1589
+ foliage_acacia: leaf,
1590
+ foliage_palm: leaf,
1591
+ foliage_spruce: leaf,
1592
+ foliage_spruce_snow: leaf,
1593
+ foliage_willow: leaf,
1594
+ foliage_reeds: leaf,
1595
+ foliage_needles: leaf
1596
+ }
1597
+ };
1598
+
1599
+ // src/material/paint/validate.ts
1600
+ var KINDS = [
1601
+ "bond",
1602
+ "planks",
1603
+ "plates",
1604
+ "noise",
1605
+ "flat",
1606
+ "slate",
1607
+ "concrete",
1608
+ "glass"
1609
+ ];
1610
+ var SIZES = [512, 1024, 2048];
1611
+ function fail(cause, fix) {
1612
+ throw new Ts23dError2("T3D_RECIPE_INVALID", { cause: `sheet recipe: ${cause}`, fix });
1613
+ }
1614
+ var unit = (v) => Number.isFinite(v) && v >= 0 && v <= 1;
1615
+ function validateStrip(where, r) {
1616
+ if (!KINDS.includes(r.kind))
1617
+ fail(`${where}.kind '${r.kind}' is unknown`, `use one of ${KINDS.join(" | ")}`);
1618
+ if (!(Number.isFinite(r.scaleMetres) && r.scaleMetres > 0))
1619
+ fail(`${where}.scaleMetres is ${r.scaleMetres}`, "set the metres the strip V span represents, > 0 (e.g. 0.6 for brick)");
1620
+ if (!unit(r.roughness))
1621
+ fail(`${where}.roughness is ${r.roughness}`, "roughness is 0..1");
1622
+ if (r.metalness !== undefined && !unit(r.metalness))
1623
+ fail(`${where}.metalness is ${r.metalness}`, "metalness is 0..1");
1624
+ for (const [k, v] of Object.entries(r.params ?? {}))
1625
+ if (!Number.isFinite(v))
1626
+ fail(`${where}.params.${k} is ${v}`, "params are finite numbers");
1627
+ }
1628
+ function validateSheet(recipe) {
1629
+ if (!Number.isInteger(recipe.seed))
1630
+ fail(`seed ${recipe.seed}`, "seed is an integer");
1631
+ if (recipe.size !== undefined && !SIZES.includes(recipe.size))
1632
+ fail(`size ${recipe.size}`, "size is 512, 1024 or 2048");
1633
+ for (const [id, r] of Object.entries(recipe.strips)) {
1634
+ if (!STRIPS2.includes(id))
1635
+ fail(`strip '${id}' is not in the layout`, `strips are ${STRIPS2.join(", ")}`);
1636
+ if (r)
1637
+ validateStrip(`strips.${id}`, r);
1638
+ }
1639
+ for (const [id, e] of Object.entries(recipe.cells ?? {})) {
1640
+ if (!CELLS2.includes(id))
1641
+ fail(`cell '${id}' is not in the layout`, `cells are ${CELLS2.join(", ")}`);
1642
+ if (!e)
1643
+ continue;
1644
+ if ("image" in e) {
1645
+ if (!(e.image instanceof Uint8Array))
1646
+ fail(`cells.${id}.image is not bytes`, "pass PNG file bytes as a Uint8Array");
1647
+ } else
1648
+ validateStrip(`cells.${id}`, e);
1649
+ }
1650
+ }
1651
+
1652
+ // src/material/generator.ts
1653
+ var FALLBACK = { kind: "noise", scaleMetres: 1, roughness: 0.6 };
1654
+ function paintTrimSheet2(recipe = DEFAULT_SHEET2) {
1655
+ validateSheet(recipe);
1656
+ const size = recipe.size ?? 1024;
1657
+ const sheet = blankSheet(size);
1658
+ const rng = createRng2(recipe.seed);
1659
+ const seedOf = (name) => rng.fork(name).int(0, 2147483647);
1660
+ const pad = Math.max(2, size / 256);
1661
+ for (const id of STRIPS2) {
1662
+ const r = recipe.strips[id] ?? DEFAULT_SHEET2.strips[id] ?? FALLBACK;
1663
+ validateStrip(`strips.${id}`, r);
1664
+ const rect = stripRect2(id);
1665
+ const h = Math.round((rect.v1 - rect.v0) * size);
1666
+ const c = createCanvas(size, h, rect.metres, r.scaleMetres, true);
1667
+ paintStrip(c, r, seedOf(id));
1668
+ composeRegion(sheet, c, 0, Math.round(rect.v0 * size), pad);
1669
+ }
1670
+ for (const id of CELLS2) {
1671
+ const e = recipe.cells?.[id] ?? DEFAULT_SHEET2.cells?.[id] ?? FALLBACK;
1672
+ const rect = cellRect2(id);
1673
+ const w = Math.round((rect.u1 - rect.u0) * size);
1674
+ const c = createCanvas(w, w, rect.metres, rect.metres, false);
1675
+ paintCell(c, id, e, seedOf(id));
1676
+ composeRegion(sheet, c, Math.round(rect.u0 * size), Math.round(rect.v0 * size), pad);
1677
+ }
1678
+ return sheet;
1679
+ }
1680
+ // src/material/palette.ts
1681
+ function hex2(value) {
1682
+ const n = Number.parseInt(value.replace("#", ""), 16);
1683
+ const lin = (c) => {
1684
+ const s = c / 255;
1685
+ return s <= 0.04045 ? s / 12.92 : ((s + 0.055) / 1.055) ** 2.4;
1686
+ };
1687
+ return [lin(n >> 16 & 255), lin(n >> 8 & 255), lin(n & 255)];
1688
+ }
1689
+ function definePalette2(entries) {
1690
+ return Object.fromEntries(Object.entries(entries).map(([k, v]) => [k, hex2(v)]));
1691
+ }
1692
+ var palettes2 = {
1693
+ victorianRail: definePalette2({
1694
+ paint: "#2f4a3a",
1695
+ trim: "#b08d57",
1696
+ iron: "#2b2b2e",
1697
+ brass: "#c9a24a",
1698
+ red: "#7a2a22"
1699
+ }),
1700
+ artDeco: definePalette2({
1701
+ limestone: "#d8cfbd",
1702
+ steel: "#9aa3ad",
1703
+ glass: "#4a6a80",
1704
+ bronze: "#8a6a3c"
1705
+ }),
1706
+ modernGlass: definePalette2({
1707
+ glass: "#6f8fa8",
1708
+ aluminium: "#c4c9cf",
1709
+ concrete: "#a7a39b"
1710
+ }),
1711
+ harbour: definePalette2({ hull: "#1f2f45", deck: "#8a6f4e", white: "#e8e6df", red: "#a1302a" })
1712
+ };
1713
+ // src/material/shader-godot.ts
1714
+ var f = (n) => Number.isInteger(n) ? `${n}.0` : `${+n.toFixed(6)}`;
1715
+ var rect2 = (r) => `in_rect(UV, vec4(${f(r.u0)}, ${f(r.v0)}, ${f(r.u1)}, ${f(r.v1)}))`;
1716
+ function godotSource(p) {
1717
+ const modes = ["blend_mix", "depth_draw_opaque", p.cutout ? "cull_disabled" : "cull_back"];
1718
+ const weight = p.windAttribute ? "CUSTOM0.x /* import _WIND into CUSTOM0 (glTF import: custom attribute mapping) */" : "smoothstep(0.0, wind_height, VERTEX.z)";
1719
+ return `// Generated by @developerz.ai/ts23d godotShaderSource(). A starting point your project owns:
1720
+ // assign it to a ShaderMaterial and set albedo_tex / normal_tex / orm_tex from the GLB's
1721
+ // ts23d_* material (or the shared trim_sheet_{albedo,normal,orm}.png). Godot 4.x.
1722
+ shader_type spatial;
1723
+ render_mode ${modes.join(", ")};
1724
+
1725
+ uniform sampler2D albedo_tex : source_color, filter_linear_mipmap, repeat_enable;
1726
+ uniform sampler2D normal_tex : hint_normal, filter_linear_mipmap, repeat_enable;
1727
+ uniform sampler2D orm_tex : hint_default_white, filter_linear_mipmap, repeat_enable;
1728
+
1729
+ uniform float night : hint_range(0.0, 1.0) = 0.0;
1730
+ uniform float wind_strength = ${f(p.windStrength)};
1731
+ uniform float wind_height = ${f(p.windHeight)};
1732
+ uniform float wind_hz = ${f(p.windHz)};
1733
+ uniform vec3 glow_color : source_color = vec3(${f(p.glow[0])}, ${f(p.glow[1])}, ${f(p.glow[2])});
1734
+ uniform float glow_ratio : hint_range(0.0, 1.0) = ${f(p.glowRatio)};
1735
+ uniform float glow_intensity = ${f(p.glowIntensity)};
1736
+ uniform float wear : hint_range(0.0, 1.0) = ${f(p.wear)};
1737
+ uniform float grime_height = ${f(p.grimeHeight)};
1738
+
1739
+ varying vec3 world_pos;
1740
+
1741
+ float in_rect(vec2 uv, vec4 r) {
1742
+ float u = fract(uv.x);
1743
+ return step(r.x, u) * step(u, r.z) * step(r.y, uv.y) * step(uv.y, r.w);
1744
+ }
1745
+
1746
+ float hash13(vec3 p) {
1747
+ p = fract(p * 0.1031);
1748
+ p += dot(p, p.zyx + 31.32);
1749
+ return fract((p.x + p.y) * p.z);
1750
+ }
1751
+
1752
+ void vertex() {
1753
+ // Wind sway: mesh-local Z is the authored up axis; sway in local XY.
1754
+ float weight = ${weight};
1755
+ float phase = VERTEX.x * 0.7 + VERTEX.y * 0.45;
1756
+ float sway = sin(TIME * wind_hz * 6.2832 + phase) * wind_strength * weight * 0.1;
1757
+ VERTEX.xy += vec2(sway, sway * 0.6);
1758
+ world_pos = (MODEL_MATRIX * vec4(VERTEX, 1.0)).xyz;
1759
+ }
1760
+
1761
+ void fragment() {
1762
+ vec4 base = texture(albedo_tex, UV);
1763
+ vec3 orm = texture(orm_tex, UV).rgb;
1764
+ float ground = 1.0 - smoothstep(0.0, grime_height, world_pos.y);
1765
+ float grime = clamp(wear * max((1.0 - orm.r) * 1.5, ground), 0.0, 1.0);
1766
+ ALBEDO = base.rgb * COLOR.rgb * (1.0 - grime * 0.45);
1767
+ AO = orm.r;
1768
+ ROUGHNESS = clamp(orm.g + grime * 0.2, 0.0, 1.0);
1769
+ METALLIC = orm.b * (1.0 - grime);
1770
+ NORMAL_MAP = texture(normal_tex, UV).rgb;
1771
+ ${p.cutout ? ` ALPHA = base.a;
1772
+ ALPHA_SCISSOR_THRESHOLD = 0.5;
1773
+ ` : p.blend ? ` ALPHA = base.a * 0.6;
1774
+ ` : ""}
1775
+ // Window glow: dark panes in window cells, or the glass strip; 1.5 m world slots, glow_ratio lit.
1776
+ float luma = dot(base.rgb, vec3(0.2126, 0.7152, 0.0722));
1777
+ float panes = (${p.glowRects.map(rect2).join(" + ")}) * step(luma, 0.3);
1778
+ float mask = max(panes, ${rect2(p.glassRect)});
1779
+ float lit = step(hash13(floor(world_pos / 1.5)), glow_ratio);
1780
+ EMISSION = glow_color * mask * lit * night * glow_intensity;
1781
+ }
1782
+ `;
1783
+ }
1784
+
1785
+ // src/material/shader-three.ts
1786
+ var f2 = (n) => Number.isInteger(n) ? `${n}.0` : `${+n.toFixed(6)}`;
1787
+ var rectNode = (r) => `inRect(${f2(r.u0)}, ${f2(r.v0)}, ${f2(r.u1)}, ${f2(r.v1)})`;
1788
+ function threeSource(p) {
1789
+ const weight = p.windAttribute ? "attribute('_wind', 'float')" : "smoothstep(float(0), uWindHeight, positionLocal.z)";
1790
+ const glowCells = p.glowRects.map(rectNode).join(".add(") + ")".repeat(p.glowRects.length - 1);
1791
+ return `// Generated by @developerz.ai/ts23d threeMaterialSource(). A starting point your project owns:
1792
+ // edit freely. Needs three r18x (WebGPURenderer + TSL). ts23d itself has no three dependency.
1793
+ //
1794
+ // Usage: const gltf = await loader.loadAsync('tank.glb'); upgradeTrimMaterials(gltf.scene);
1795
+ // trim.uniforms.night.value = 1; // at dusk
1796
+
1797
+ import { DoubleSide, type Material, type Mesh, MeshStandardMaterial, MeshStandardNodeMaterial, type Object3D, type Texture } from 'three/webgpu';
1798
+ import { attribute, color, float, floor, fract, hash, max, normalMap, positionLocal, positionWorld, sin, smoothstep, step, texture, time, uniform, uv, vec3, vertexColor } from 'three/tsl';
1799
+
1800
+ export interface TrimMaps {
1801
+ readonly albedo: Texture;
1802
+ readonly normal: Texture | null;
1803
+ readonly orm: Texture | null;
1804
+ }
1805
+
1806
+ /** Shared uniforms: one set drives every trim material, so a scene-wide night switch is one write. */
1807
+ export const uniforms = {
1808
+ night: uniform(0),
1809
+ windStrength: uniform(${f2(p.windStrength)}),
1810
+ windHeight: uniform(${f2(p.windHeight)}),
1811
+ windHz: uniform(${f2(p.windHz)}),
1812
+ glowColor: uniform(color(${f2(p.glow[0])}, ${f2(p.glow[1])}, ${f2(p.glow[2])})),
1813
+ glowRatio: uniform(${f2(p.glowRatio)}),
1814
+ glowIntensity: uniform(${f2(p.glowIntensity)}),
1815
+ wear: uniform(${f2(p.wear)}),
1816
+ grimeHeight: uniform(${f2(p.grimeHeight)}),
1817
+ };
1818
+
1819
+ /** 1 inside a trim-sheet UV rect (U wraps: strips tile along U). */
1820
+ function inRect(u0: number, v0: number, u1: number, v1: number) {
1821
+ const t = uv();
1822
+ const u = fract(t.x);
1823
+ return step(u0, u).mul(step(u, u1)).mul(step(v0, t.y)).mul(step(t.y, v1));
1824
+ }
1825
+
1826
+ export function createTrimMaterial(maps: TrimMaps, cutout = ${p.cutout}): MeshStandardNodeMaterial {
1827
+ const { night, windStrength: uWindStrength, windHeight: uWindHeight, windHz, glowColor, glowRatio, glowIntensity, wear, grimeHeight } = uniforms;
1828
+ const m = new MeshStandardNodeMaterial();
1829
+ const base = texture(maps.albedo, uv());
1830
+ const orm = maps.orm ? texture(maps.orm, uv()) : vec3(1, 0.8, 0);
1831
+ const tint = vertexColor();
1832
+
1833
+ // Wear / grime: cavities (low AO) and the first metres above the ground get darker and rougher.
1834
+ const ground = float(1).sub(smoothstep(float(0), grimeHeight, positionWorld.y));
1835
+ const grime = wear.mul(max(float(1).sub(orm.x).mul(1.5), ground)).clamp(0, 1);
1836
+ m.colorNode = base.rgb.mul(tint.rgb).mul(float(1).sub(grime.mul(0.45)));
1837
+ m.roughnessNode = orm.y.add(grime.mul(0.2)).clamp(0, 1);
1838
+ m.metalnessNode = orm.z.mul(float(1).sub(grime));
1839
+ m.aoNode = orm.x;
1840
+ if (maps.normal) m.normalNode = normalMap(texture(maps.normal, uv()));
1841
+ if (cutout) {
1842
+ m.opacityNode = base.a;
1843
+ m.alphaTest = 0.5;
1844
+ m.side = DoubleSide;
1845
+ }
1846
+
1847
+ // Wind sway (authored Z-up, so sway in local XY), phase-shifted across the crown.
1848
+ const windWeight = ${weight};
1849
+ const phase = positionLocal.x.mul(0.7).add(positionLocal.y.mul(0.45));
1850
+ const sway = sin(time.mul(windHz).mul(6.2832).add(phase)).mul(uWindStrength).mul(windWeight).mul(0.1);
1851
+ m.positionNode = positionLocal.add(vec3(sway, sway.mul(0.6), 0));
1852
+
1853
+ // Window glow: dark pane texels in window cells, or the glass strip; a world-space hash lights
1854
+ // \`glowRatio\` of the 1.5 m window slots; scaled by \`night\`.
1855
+ const luma = base.r.mul(0.2126).add(base.g.mul(0.7152)).add(base.b.mul(0.0722));
1856
+ const panes = ${glowCells}.mul(step(luma, 0.3));
1857
+ const mask = max(panes, ${rectNode(p.glassRect)});
1858
+ const slot = floor(positionWorld.div(1.5));
1859
+ const lit = step(hash(slot.x.mul(73).add(slot.y.mul(157)).add(slot.z.mul(311))), glowRatio);
1860
+ m.emissiveNode = glowColor.mul(mask).mul(lit).mul(night).mul(glowIntensity);
1861
+ return m;
1862
+ }
1863
+
1864
+ /** Replace every ts23d_* material under \`root\` with the node material, reusing the GLB's maps. */
1865
+ export function upgradeTrimMaterials(root: Object3D): void {
1866
+ const cache = new Map<Material, MeshStandardNodeMaterial>();
1867
+ root.traverse((o) => {
1868
+ const mesh = o as Mesh;
1869
+ const old = mesh.isMesh ? mesh.material : null;
1870
+ if (!(old instanceof MeshStandardMaterial) || !old.name.startsWith('ts23d') || !old.map) return;
1871
+ const next = cache.get(old) ?? createTrimMaterial({ albedo: old.map, normal: old.normalMap, orm: old.roughnessMap }, old.alphaTest > 0);
1872
+ next.name = old.name;
1873
+ cache.set(old, next);
1874
+ mesh.material = next;
1875
+ });
1876
+ }
1877
+ `;
1878
+ }
1879
+
1880
+ // src/material/shader.ts
1881
+ function fail2(cause, fix) {
1882
+ throw new Ts23dError2("T3D_RECIPE_INVALID", { cause: `shader hints: ${cause}`, fix });
1883
+ }
1884
+ function srgbHex(value) {
1885
+ if (!/^#[0-9a-fA-F]{6}$/.test(value))
1886
+ fail2(`colour '${value}'`, "use '#rrggbb' sRGB hex");
1887
+ const n = Number.parseInt(value.slice(1), 16);
1888
+ return [(n >> 16 & 255) / 255, (n >> 8 & 255) / 255, (n & 255) / 255];
1889
+ }
1890
+ function resolveHints(h) {
1891
+ const unit = (v, what) => Number.isFinite(v) && v >= 0 && v <= 1 ? v : fail2(`${what} is ${v}`, `${what} is 0..1`);
1892
+ const pos = (v, what) => Number.isFinite(v) && v > 0 ? v : fail2(`${what} is ${v}`, `${what} is a positive number`);
1893
+ const strength = h.wind ? pos(h.wind.strength, "wind.strength") : 0;
1894
+ return {
1895
+ windStrength: strength,
1896
+ windAttribute: h.wind?.source === "attribute",
1897
+ windHeight: pos(h.wind?.height ?? 4, "wind.height"),
1898
+ windHz: pos(h.wind?.hz ?? 0.4, "wind.hz"),
1899
+ glow: h.windowGlow ? srgbHex(h.windowGlow.color) : [1, 0.75, 0.4],
1900
+ glowRatio: h.windowGlow ? unit(h.windowGlow.ratio, "windowGlow.ratio") : 0,
1901
+ glowIntensity: pos(h.windowGlow?.intensity ?? 2, "windowGlow.intensity"),
1902
+ wear: h.wear ? unit(h.wear.amount, "wear.amount") : 0,
1903
+ grimeHeight: pos(h.wear?.grimeHeight ?? 0.6, "wear.grimeHeight"),
1904
+ cutout: h.surface === "cutout",
1905
+ blend: h.surface === "glass" || h.surface === "translucent",
1906
+ glowRects: [cellRect2("window_sash"), cellRect2("porthole")],
1907
+ glassRect: stripRect2("glass")
1908
+ };
1909
+ }
1910
+ function threeMaterialSource2(hints = {}) {
1911
+ return threeSource(resolveHints(hints));
1912
+ }
1913
+ function godotShaderSource2(hints = {}) {
1914
+ return godotSource(resolveHints(hints));
1915
+ }
1916
+ export { TRIM_SHEET_SIZE2, stripRect2, cellRect2, STRIPS2, CELLS2, DECAL_CELLS2, FOLIAGE_CELLS2, isFoliage2, DEFAULT_SHEET2, paintTrimSheet2, hex2, definePalette2, palettes2, threeMaterialSource2, godotShaderSource2 };
1917
+
1918
+ //# debugId=F437FE42A323CC4464756E2164756E21
1919
+ //# sourceMappingURL=chunk-kvcep7ft.js.map