@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,781 @@
1
+ // Eiffel Tower (Paris, 1889; 330 m with the 2022 DAB antenna). Real: 124.9 × 124.9 × 330 m.
2
+ // Skyline from the operator's 29-panel table (Weidman & Pinelis 2004), arbalétrier layout and arch
3
+ // from Eiffel's 1887 plate PL. I. Every number and its URL: recipes/eiffel_tower/README.md.
4
+ //
5
+ // Structure: four legs of four box uprights (panels 1–11), eight uprights on the outer ring
6
+ // (12–17), four corner uprights (18–29). Every face panel is a St Andrew's cross plus a horizontal;
7
+ // at LOD0 each of those members is itself a lattice girder (two chords + zig-zag web).
8
+ //
9
+ // QA design: the four `leg_*` parts carry the whole lattice of their quadrant from the ground to
10
+ // the 3rd floor, so they are grounded directly; platforms hang off cheap parts (arches, belts, lift
11
+ // rails) through coplanar faces, so the contact gate never has to grid the 100k-triangle lattice.
12
+
13
+ import {
14
+ add,
15
+ applyAffine,
16
+ type BuildContext,
17
+ beam,
18
+ box,
19
+ cross,
20
+ defineAsset,
21
+ dot,
22
+ frame,
23
+ grime,
24
+ hex,
25
+ type Lod,
26
+ lathe,
27
+ type Mesh,
28
+ MeshBuilder,
29
+ merge,
30
+ normalize,
31
+ place,
32
+ scale,
33
+ sub,
34
+ type Vec2,
35
+ type Vec3,
36
+ } from '@developerz.ai/ts23d';
37
+
38
+ // ── Colour: three graduated shades of Eiffel Tower Brown, darkest at the bottom ──────────────────
39
+ const SHADES = [hex('#5e4834'), hex('#715a40'), hex('#87704f')] as const;
40
+ const IRON: Vec3 = [1, 1, 1]; // tint; `paint` multiplies by the shade at the vertex height
41
+ const IRON_DARK: Vec3 = [0.75, 0.75, 0.75];
42
+ const STONE = hex('#a3967e');
43
+ const GLASS = hex('#5d7380');
44
+ const GREY = hex('#9b9b97');
45
+ const WHITE = hex('#dcdcd6');
46
+ const RED = hex('#a8382c');
47
+
48
+ // ── Profile: (x down from the top of panel 29, half width w), SNTE via Weidman & Pinelis Table 1 ─
49
+ const PROFILE: readonly Vec2[] = [
50
+ [0, 5],
51
+ [11.28, 5],
52
+ [17.113, 5.284],
53
+ [23.278, 5.585],
54
+ [29.795, 5.903],
55
+ [36.683, 6.239],
56
+ [43.963, 6.594],
57
+ [51.658, 6.969],
58
+ [59.791, 7.365],
59
+ [68.387, 7.784],
60
+ [77.473, 8.227],
61
+ [87.076, 8.696],
62
+ [97.076, 9.245],
63
+ [107.076, 9.999],
64
+ [117.576, 10.79],
65
+ [128.076, 11.786],
66
+ [138.676, 12.88],
67
+ [149.376, 14.303],
68
+ [160.676, 15.806],
69
+ [165.576, 16.925],
70
+ [175.576, 19.21],
71
+ [185.776, 21.583],
72
+ [196.776, 24.365],
73
+ [207.776, 27.415],
74
+ [218.776, 30.919],
75
+ [225.776, 33.697],
76
+ [236.776, 38.637],
77
+ [247.776, 43.578],
78
+ [258.776, 48.519],
79
+ [269.776, 53.459],
80
+ ];
81
+ const TOP = 269.776;
82
+ /** The table's ground half width is 53.459 m; the official footprint is 124.90 m (dossier). */
83
+ const K = 62.45 / 53.459;
84
+ /** Z[p] = top of panel p (Z[0] = ground). */
85
+ const Z: readonly number[] = PROFILE.map(([x]) => TOP - x).reverse();
86
+ const lv = (p: number): number => Z[p] ?? 0;
87
+ const Z11 = lv(11);
88
+ const Z17 = lv(17);
89
+ const Z29 = lv(29);
90
+
91
+ const DECK1 = 57.63;
92
+ const DECK2 = 115.73;
93
+ const DECK3 = 276.13;
94
+ const SLAB = 0.6;
95
+ const SIDE1 = 70.69 / 2;
96
+ const SIDE2 = 40.96 / 2;
97
+ const SIDE3 = 18.65 / 2;
98
+ const TERRACE = 279.11;
99
+ const LEG0 = 25.335; // leg plan width at the ground, plate PL. I "25m339"
100
+ const ARCH = { r: 37.16, cz: 1.74, depth: 2.2, w: 0.6 }; // through the leg feet + keystone 38.90 m
101
+ const BELT1 = 35.0; // plane of the 1st-floor belt and the arches
102
+ const BELT2 = 17.0;
103
+ const OVER = 0.05; // members overlap their joints
104
+ const LANTERN_Z = 286.3;
105
+ const LANTERN_TOP = LANTERN_Z + 14.2; // upper terrace, plate PL. I: 334.015 − 33.50 = 300.5 m
106
+
107
+ function halfW(z: number): number {
108
+ const x = TOP - Math.min(Math.max(z, 0), TOP);
109
+ for (let i = 0; i + 1 < PROFILE.length; i++) {
110
+ const [x0, w0] = PROFILE[i] ?? [0, 0];
111
+ const [x1, w1] = PROFILE[i + 1] ?? [0, 0];
112
+ if (x <= x1) return K * (w0 + ((w1 - w0) * (x - x0)) / (x1 - x0 || 1));
113
+ }
114
+ return K * 53.459;
115
+ }
116
+ const legWidth = (z: number): number => LEG0 + ((0.6 * halfW(Z11) - LEG0) * z) / Z11;
117
+ const innerRaw = (z: number): number => (z <= Z11 ? halfW(z) - legWidth(z) : 0.4 * halfW(z));
118
+ const uw = (z: number): number => 1.4 - (0.7 * Math.min(z, TOP)) / TOP;
119
+ const outer = (z: number): number => halfW(z) - uw(z) / 2;
120
+ const inner = (z: number): number => innerRaw(z) + uw(z) / 2;
121
+
122
+ const QUAD: readonly Vec2[] = [
123
+ [1, 1],
124
+ [-1, 1],
125
+ [-1, -1],
126
+ [1, -1],
127
+ ];
128
+
129
+ // ── Members ─────────────────────────────────────────────────────────────────────────────────────
130
+ const dist = (a: Vec3, b: Vec3): number => Math.hypot(...sub(b, a));
131
+ const clamp = (v: number, lo: number, hi: number): number => Math.min(hi, Math.max(lo, v));
132
+
133
+ function rod(out: Mesh[], a: Vec3, b: Vec3, width: number, color: Vec3 = IRON): void {
134
+ const d = normalize(sub(b, a));
135
+ out.push(beam({ from: sub(a, scale(d, OVER)), to: add(b, scale(d, OVER)), width, color }));
136
+ }
137
+
138
+ /** A box upright whose faces stay parallel to the tower faces (beam would roll it 45°). */
139
+ function post(out: Mesh[], a: Vec3, b: Vec3, width: number): void {
140
+ const d = normalize(sub(b, a));
141
+ const x: Vec3 = [1, 0, 0];
142
+ const u = normalize(sub(x, scale(d, dot(x, d))));
143
+ const len = dist(a, b) + 2 * OVER;
144
+ const m = box({ size: [width, width, len], color: IRON });
145
+ out.push(applyAffine(m, frame(u, cross(d, u), d, sub(a, scale(d, OVER)))));
146
+ }
147
+
148
+ /** Lattice girder a→b: two chords `depth` apart in the plane ⟂ n, zig-zag web between. */
149
+ function girder(out: Mesh[], a: Vec3, b: Vec3, n: Vec3, depth: number): void {
150
+ const len = dist(a, b);
151
+ const dir = normalize(sub(b, a));
152
+ const d = normalize(cross(n, dir));
153
+ const at = (t: number, s: number): Vec3 =>
154
+ add(add(a, scale(dir, len * t)), scale(d, s * depth * 0.5));
155
+ const cw = Math.max(0.14, depth * 0.2);
156
+ rod(out, at(0, 1), at(1, 1), cw);
157
+ rod(out, at(0, -1), at(1, -1), cw);
158
+ const k = Math.max(2, Math.round(len / (depth * 1.6)));
159
+ for (let i = 0; i < k; i++) {
160
+ const s = i % 2 === 0 ? 1 : -1;
161
+ rod(out, at(i / k, s), at((i + 1) / k, -s), Math.max(0.07, depth * 0.09));
162
+ }
163
+ }
164
+
165
+ /** A member at this LOD: lattice girder at LOD0, a plain bar at LOD1. */
166
+ function member(out: Mesh[], a: Vec3, b: Vec3, n: Vec3, depth: number, lod: Lod): void {
167
+ // Below ~0.5 m deep a lattice is sub-pixel even up close: a plain bar reads the same.
168
+ if (lod === 0 && depth >= 0.5) girder(out, a, b, n, depth);
169
+ else rod(out, a, b, Math.max(0.3, depth * 0.5));
170
+ }
171
+
172
+ type Line = (z: number) => Vec3;
173
+ const line =
174
+ (sx: number, sy: number, fx: (z: number) => number, fy: (z: number) => number): Line =>
175
+ (z) => [sx * fx(z), sy * fy(z), z];
176
+
177
+ /** One face between two upright lines: a St Andrew's cross per panel + horizontals. */
178
+ function face(
179
+ out: Mesh[],
180
+ l1: Line,
181
+ l2: Line,
182
+ levels: readonly number[],
183
+ lod: Lod,
184
+ base: boolean,
185
+ ): void {
186
+ for (let k = 0; k + 1 < levels.length; k++) {
187
+ const [z0, z1] = [levels[k] ?? 0, levels[k + 1] ?? 0];
188
+ const [bl, br, tl, tr] = [l1(z0), l2(z0), l1(z1), l2(z1)];
189
+ const n = normalize(cross(sub(br, bl), sub(tl, bl)));
190
+ const dd = clamp(dist(bl, tr) * 0.04, 0.3, 1.1);
191
+ member(out, bl, tr, n, dd, lod);
192
+ member(out, br, tl, n, dd, lod);
193
+ const hd = clamp(dist(tl, tr) * 0.05, 0.3, 1.2);
194
+ if (lod === 0 || k % 2 === 1 || k === levels.length - 2) member(out, tl, tr, n, hd, lod);
195
+ if (k === 0 && base) member(out, bl, br, n, hd, lod);
196
+ }
197
+ }
198
+
199
+ function uprights(out: Mesh[], l: Line, levels: readonly number[]): void {
200
+ for (let k = 0; k + 1 < levels.length; k++) {
201
+ const [z0, z1] = [levels[k] ?? 0, levels[k + 1] ?? 0];
202
+ post(out, l(z0), l(z1), uw((z0 + z1) / 2));
203
+ }
204
+ }
205
+
206
+ // ── Recolour by height, then grime the feet ─────────────────────────────────────────────────────
207
+ const smooth = (t: number): number => {
208
+ const c = clamp(t, 0, 1);
209
+ return c * c * (3 - 2 * c);
210
+ };
211
+ function shade(z: number): Vec3 {
212
+ const [a, b, c] = SHADES;
213
+ const t1 = smooth((z - 50) / 14);
214
+ const t2 = smooth((z - 112) / 14);
215
+ return [0, 1, 2].map((k) => {
216
+ const lo = (a[k] ?? 0) + ((b[k] ?? 0) - (a[k] ?? 0)) * t1;
217
+ return lo + ((c[k] ?? 0) - lo) * t2;
218
+ }) as unknown as Vec3;
219
+ }
220
+ function iron(meshes: readonly Mesh[], dirt = false): Mesh {
221
+ const m = merge(meshes);
222
+ const colors = new Float32Array(m.colors);
223
+ for (let i = 0; i < m.positions.length / 3; i++) {
224
+ const s = shade(m.positions[i * 3 + 2] ?? 0);
225
+ for (let k = 0; k < 3; k++) colors[i * 3 + k] = (colors[i * 3 + k] ?? 1) * (s[k] ?? 0);
226
+ }
227
+ const painted = { ...m, colors };
228
+ return dirt ? grime(painted, { ground: 9, groundStrength: 0.35 }) : painted;
229
+ }
230
+ const ring4 = (m: Mesh): Mesh[] => [0, 1, 2, 3].map((q) => place(m, { yaw: (q * Math.PI) / 2 }));
231
+
232
+ // ── Legs: the whole lattice of one quadrant, ground → 3rd floor ─────────────────────────────────
233
+ const LEVELS_A = Z.slice(0, 12);
234
+ const LEVELS_B = Z.slice(11, 18);
235
+ const LEVELS_C = [...Z.slice(17, 30), DECK3 - SLAB];
236
+
237
+ function leg(q: number, lod: Lod): Mesh {
238
+ const [sx, sy] = QUAD[q] ?? [1, 1];
239
+ const out: Mesh[] = [];
240
+ const c = line(sx, sy, outer, outer);
241
+ const bx = line(sx, sy, outer, inner);
242
+ const by = line(sx, sy, inner, outer);
243
+ const a = line(sx, sy, inner, inner);
244
+ // Panels 1–11: four arbalétriers, four braced faces.
245
+ for (const l of [c, bx, by]) uprights(out, l, [...LEVELS_A, ...LEVELS_B.slice(1)]);
246
+ uprights(out, c, LEVELS_C);
247
+ uprights(out, a, LEVELS_A);
248
+ for (const [l1, l2] of [
249
+ [c, bx],
250
+ [c, by],
251
+ [by, a],
252
+ [bx, a],
253
+ ] as const)
254
+ face(out, l1, l2, LEVELS_A, lod, false);
255
+ if (lod === 0) {
256
+ // Plan diaphragms every other level inside the leg.
257
+ for (let p = 2; p <= 10; p += 2) {
258
+ const z = lv(p);
259
+ girder(out, c(z), a(z), [0, 0, 1], 0.8);
260
+ girder(out, bx(z), by(z), [0, 0, 1], 0.8);
261
+ }
262
+ }
263
+ // Panels 12–17: the outer faces of the L plus this leg's share of the middle bays.
264
+ face(out, c, bx, LEVELS_B, lod, false);
265
+ face(out, c, by, LEVELS_B, lod, false);
266
+ const even = q % 2 === 0;
267
+ const mid1 = even ? bx : by;
268
+ const mid2 = even ? line(sx, -sy, outer, inner) : line(-sx, sy, inner, outer);
269
+ face(out, mid1, mid2, LEVELS_B, lod, true);
270
+ // Panels 18–29 + the 3rd-floor stool: one face of the shaft per leg.
271
+ const c2 = even ? line(sx, -sy, outer, outer) : line(-sx, sy, outer, outer);
272
+ face(out, c, c2, LEVELS_C, lod, false);
273
+ // Intermediate belt (plancher intermédiaire, 195.4 m).
274
+ const zb = 194.2;
275
+ const nb = normalize(cross(sub(c2(zb), c(zb)), [0, 0, 1]));
276
+ member(out, c(zb), c2(zb), cross(nb, normalize(sub(c2(zb), c(zb)))), 3.4, lod);
277
+ // Consoles under the 3rd floor.
278
+ const zt = DECK3 - SLAB;
279
+ const edge = (t: number, z: number): Vec3 => {
280
+ const p = add(scale(c(z), 1 - t), scale(c2(z), t));
281
+ const r = SIDE3 - 0.25;
282
+ return even
283
+ ? [sx * r, p[1] * (r / Math.max(outer(z), 1e-3)), z]
284
+ : [p[0] * (r / Math.max(outer(z), 1e-3)), sy * r, z];
285
+ };
286
+ for (const t of lod === 0 ? [0, 0.25, 0.5, 0.75, 1] : [0, 0.5, 1]) {
287
+ const foot = add(scale(c(lv(28)), 1 - t), scale(c2(lv(28)), t));
288
+ rod(out, foot, edge(t, zt - 0.2), 0.35);
289
+ }
290
+ rod(out, edge(0, zt - 0.2), edge(1, zt - 0.2), 0.4);
291
+ return iron(out, true);
292
+ }
293
+
294
+ // ── Arches (Sauvestre) between the legs, in the belt plane ──────────────────────────────────────
295
+ const ARCH_SEGS: readonly number[] = [41, 25, 9];
296
+ function archTop(lod: Lod): number {
297
+ const n = ARCH_SEGS[lod] ?? 9;
298
+ const th0 = Math.asin(ARCH.cz / ARCH.r);
299
+ const step = (Math.PI + 2 * th0) / n;
300
+ const ro = ARCH.r + (lod === 2 ? 0 : ARCH.depth);
301
+ const w = lod === 2 ? 2.0 : ARCH.w;
302
+ return ARCH.cz + ro * Math.cos(step / 2) + w / 2;
303
+ }
304
+ function arch(lod: Lod): Mesh {
305
+ const out: Mesh[] = [];
306
+ const n = ARCH_SEGS[lod] ?? 9;
307
+ const th0 = Math.asin(ARCH.cz / ARCH.r);
308
+ const ang = (i: number): number => -th0 + (i * (Math.PI + 2 * th0)) / n;
309
+ const pt = (r: number, i: number): Vec3 => [
310
+ BELT1,
311
+ r * Math.cos(ang(i)),
312
+ ARCH.cz + r * Math.sin(ang(i)),
313
+ ];
314
+ const ri = ARCH.r;
315
+ const ro = ARCH.r + (lod === 2 ? 0 : ARCH.depth);
316
+ for (let i = 0; i < n; i++) {
317
+ rod(out, pt(ro, i), pt(ro, i + 1), lod === 2 ? 2.0 : ARCH.w);
318
+ if (lod === 2) continue;
319
+ rod(out, pt(ri, i), pt(ri, i + 1), ARCH.w);
320
+ rod(out, pt(ri, i), pt(ro, i + 1), 0.25);
321
+ if (lod === 0) rod(out, pt(ro, i), pt(ri, i + 1), 0.25);
322
+ }
323
+ if (lod === 0) {
324
+ // Spandrel fan: radial bars from the extrados up to the belt.
325
+ const top = archTop(0) + 0.35;
326
+ for (let i = 1; i < n; i++) {
327
+ const a = ang(i);
328
+ const r = (top - ARCH.cz) / Math.sin(a);
329
+ if (Math.abs(r * Math.cos(a)) > 33 || r - ro < 1) continue;
330
+ rod(out, pt(ro, i), [BELT1, r * Math.cos(a), top], 0.3);
331
+ }
332
+ }
333
+ return iron(out, true);
334
+ }
335
+
336
+ // ── 1st floor: belt girder, frieze, deck ring, railing, pavilions ───────────────────────────────
337
+ function belt1(lod: Lod): Mesh {
338
+ const out: Mesh[] = [];
339
+ const z0 = archTop(lod);
340
+ const zf = 53.4;
341
+ const zt = DECK1 - SLAB;
342
+ const s = SIDE1;
343
+ if (lod === 2) {
344
+ out.push(box({ size: [1.0, 2 * s, zt - z0], at: [BELT1, 0, z0], color: IRON }));
345
+ } else {
346
+ const cw = 0.7;
347
+ rod(out, [BELT1, -s, z0 + cw / 2], [BELT1, s, z0 + cw / 2], cw);
348
+ rod(out, [BELT1, -s, zf - cw / 2], [BELT1, s, zf - cw / 2], cw);
349
+ const bays = lod === 0 ? 16 : 8;
350
+ for (let i = 0; i <= bays; i++) {
351
+ const y = -s + (2 * s * i) / bays;
352
+ rod(out, [BELT1, y, z0 + cw], [BELT1, y, zf - cw], 0.45);
353
+ if (i === bays) continue;
354
+ const y2 = -s + (2 * s * (i + 1)) / bays;
355
+ rod(out, [BELT1, y, z0 + cw], [BELT1, y2, zf - cw], 0.35);
356
+ if (lod === 0) rod(out, [BELT1, y2, z0 + cw], [BELT1, y, zf - cw], 0.35);
357
+ }
358
+ // The frieze (names of 72 scientists) and its cornice.
359
+ out.push(box({ size: [0.5, 2 * s, zt - zf], at: [BELT1 + 0.1, 0, zf], color: IRON_DARK }));
360
+ if (lod === 0) {
361
+ out.push(box({ size: [0.8, 2 * s, 0.4], at: [BELT1 + 0.2, 0, zf + 0.2], color: IRON }));
362
+ for (let i = 0; i < 24; i++) {
363
+ const y = -s + 1.5 + ((2 * s - 3) * i) / 23;
364
+ out.push(box({ size: [0.2, 1.6, 2.2], at: [BELT1 + 0.4, y, zf + 0.9], color: IRON }));
365
+ }
366
+ }
367
+ }
368
+ return iron(ring4(merge(out)));
369
+ }
370
+
371
+ /** A square deck ring (outer half `so`, hole half `si`) with its top at `top`. */
372
+ function deckRing(so: number, si: number, top: number): Mesh {
373
+ const z = top - SLAB;
374
+ const parts = [
375
+ box({ size: [so - si, 2 * so, SLAB], at: [(so + si) / 2, 0, z], color: IRON_DARK }),
376
+ box({ size: [so - si, 2 * so, SLAB], at: [-(so + si) / 2, 0, z], color: IRON_DARK }),
377
+ box({ size: [2 * si, so - si, SLAB], at: [0, (so + si) / 2, z], color: IRON_DARK }),
378
+ box({ size: [2 * si, so - si, SLAB], at: [0, -(so + si) / 2, z], color: IRON_DARK }),
379
+ ];
380
+ return si > 0
381
+ ? merge(parts)
382
+ : box({ size: [2 * so, 2 * so, SLAB], at: [0, 0, z], color: IRON_DARK });
383
+ }
384
+
385
+ /** Railing round a square: posts every `pitch` m, top and mid rail. */
386
+ function railing(half: number, z: number, h: number, pitch: number, lod: Lod): Mesh {
387
+ const out: Mesh[] = [];
388
+ const s = half - 0.1;
389
+ const n = Math.max(2, Math.round((2 * s) / (lod === 0 ? pitch : pitch * 4)));
390
+ for (let i = 0; i < n; i++) {
391
+ const y = -s + (2 * s * i) / n;
392
+ out.push(beam({ from: [s, y, z], to: [s, y, z + h], width: 0.08, color: IRON }));
393
+ }
394
+ rod(out, [s, -s, z + h], [s, s, z + h], 0.08);
395
+ if (lod === 0) rod(out, [s, -s, z + h * 0.5], [s, s, z + h * 0.5], 0.05);
396
+ return iron(ring4(merge(out)));
397
+ }
398
+
399
+ function pavilions1(lod: Lod): Mesh {
400
+ const out: Mesh[] = [];
401
+ out.push(box({ size: [7, 28, 4.6], at: [30.8, 0, DECK1], color: GLASS }));
402
+ out.push(box({ size: [7.6, 29, 0.4], at: [30.8, 0, DECK1 + 4.6], color: SHADES[1] }));
403
+ if (lod === 0)
404
+ for (let i = 0; i <= 14; i++)
405
+ out.push(box({ size: [0.15, 0.15, 4.6], at: [34.35, -14 + i * 2, DECK1], color: SHADES[1] }));
406
+ return merge(ring4(merge(out)));
407
+ }
408
+
409
+ // ── 2nd floor ──────────────────────────────────────────────────────────────────────────────────
410
+ function belt2(lod: Lod): Mesh {
411
+ const out: Mesh[] = [];
412
+ const z0 = Z11 + 0.5;
413
+ const zt = DECK2 - SLAB;
414
+ const s = SIDE2;
415
+ const cw = 0.5;
416
+ rod(out, [BELT2, -s, z0], [BELT2, s, z0], cw);
417
+ rod(out, [BELT2, -s, zt - cw / 2], [BELT2, s, zt - cw / 2], cw);
418
+ const bays = lod === 0 ? 10 : 5;
419
+ for (let i = 0; i <= bays; i++) {
420
+ const y = -s + (2 * s * i) / bays;
421
+ rod(out, [BELT2, y, z0], [BELT2, y, zt - cw], 0.35);
422
+ rod(out, [BELT2, y, z0 + 0.5], [s - 0.2, y, zt - 0.2], 0.3); // console out to the deck edge
423
+ if (i < bays) rod(out, [BELT2, y, z0], [BELT2, -s + (2 * s * (i + 1)) / bays, zt - cw], 0.3);
424
+ }
425
+ return iron(ring4(merge(out)));
426
+ }
427
+
428
+ // ── Lifts: inclined rails up legs 0 and 2 to the 2nd floor; central rails to the top ───────────
429
+ function liftLeg(q: number, lod: Lod): Mesh {
430
+ const [sx, sy] = QUAD[q] ?? [1, 1];
431
+ const out: Mesh[] = [];
432
+ const zTop = DECK2 - SLAB - 1.4;
433
+ const centre = (z: number): number => (halfW(z) + innerRaw(z)) / 2;
434
+ const perp: Vec3 = [-sy / Math.SQRT2, sx / Math.SQRT2, 0];
435
+ const pts = Array.from({ length: 31 }, (_, i): Vec3 => {
436
+ const z = (zTop * i) / 30;
437
+ return [sx * centre(z), sy * centre(z), z];
438
+ });
439
+ for (const side of [-1.4, 1.4]) {
440
+ for (let i = 0; i + 1 < pts.length; i++) {
441
+ const [p0, p1] = [pts[i] ?? pts[0], pts[i + 1] ?? pts[0]];
442
+ if (!p0 || !p1) continue;
443
+ rod(out, add(p0, scale(perp, side)), add(p1, scale(perp, side)), 0.35, IRON_DARK);
444
+ if (lod === 0 && side > 0 && i % 2 === 0)
445
+ rod(out, add(p0, scale(perp, -1.4)), add(p0, scale(perp, 1.4)), 0.2, IRON_DARK);
446
+ }
447
+ }
448
+ const last = pts[pts.length - 1] ?? [0, 0, 0];
449
+ out.push(box({ size: [5, 5, DECK2 - SLAB - zTop], at: last, color: IRON_DARK }));
450
+ return iron(out);
451
+ }
452
+
453
+ function liftTop(lod: Lod): Mesh {
454
+ const out: Mesh[] = [];
455
+ const [z0, z1] = [DECK2, DECK3 - SLAB];
456
+ for (const y of [-1.3, 1.3])
457
+ out.push(beam({ from: [0, y, z0], to: [0, y, z1], width: 0.35, color: IRON_DARK }));
458
+ if (lod === 0)
459
+ for (let z = z0 + 6; z < z1; z += 8) rod(out, [0, -1.3, z], [0, 1.3, z], 0.15, IRON_DARK);
460
+ return iron(out);
461
+ }
462
+
463
+ // ── Summit: 3rd floor cabin, terrace, campanile, lantern, antenna ──────────────────────────────
464
+ function campanile(lod: Lod): Mesh {
465
+ const seg = [16, 10, 6][lod] ?? 6;
466
+ const out: Mesh[] = [];
467
+ const z0 = TERRACE;
468
+ out.push(
469
+ lathe({
470
+ profile: [
471
+ [6.4, 0],
472
+ [6.4, 0.5],
473
+ ],
474
+ segments: seg,
475
+ caps: true,
476
+ at: [0, 0, z0],
477
+ color: IRON,
478
+ }),
479
+ );
480
+ const zr = LANTERN_Z;
481
+ const rib = (r: (t: number) => number, h: (t: number) => number, yaw: number): void => {
482
+ const n = lod === 0 ? 10 : 5;
483
+ for (let i = 0; i < n; i++) {
484
+ const [t0, t1] = [i / n, (i + 1) / n];
485
+ const p = (t: number): Vec3 => {
486
+ const rr = r(t);
487
+ return [rr * Math.cos(yaw), rr * Math.sin(yaw), h(t)];
488
+ };
489
+ rod(out, p(t0), p(t1), 0.45);
490
+ }
491
+ };
492
+ for (let k = 0; k < 4; k++) {
493
+ const yaw = Math.PI / 4 + (k * Math.PI) / 2;
494
+ rib(
495
+ (t) => 2.4 + 6.4 * Math.cos((t * Math.PI) / 2),
496
+ (t) => z0 + 0.5 + (zr - z0 - 0.5) * Math.sin((t * Math.PI) / 2),
497
+ yaw,
498
+ );
499
+ }
500
+ if (lod === 0) {
501
+ // Side arches of the campanile.
502
+ const a = 4.4;
503
+ const arc: Mesh[] = [];
504
+ for (let i = 0; i < 12; i++) {
505
+ const p = (j: number): Vec3 => {
506
+ const t = (j / 12) * Math.PI;
507
+ return [a, a * Math.cos(t), z0 + 0.5 + 5.2 * Math.sin(t)];
508
+ };
509
+ rod(arc, p(i), p(i + 1), 0.35);
510
+ }
511
+ out.push(...ring4(merge(arc)));
512
+ }
513
+ const lantern: Vec2[] = [
514
+ [3.2, 0],
515
+ [3.2, 0.3],
516
+ [1.9, 0.3],
517
+ [1.9, 5.8],
518
+ [2.3, 5.9],
519
+ [2.3, 6.3],
520
+ [1.6, 7.6],
521
+ [0.9, 9.6],
522
+ [0.9, 13.6],
523
+ [2.4, 13.6],
524
+ [2.4, 14.2],
525
+ ];
526
+ out.push(lathe({ profile: lantern, segments: seg, caps: true, at: [0, 0, zr], color: IRON }));
527
+ return iron(out);
528
+ }
529
+
530
+ function antenna(lod: Lod, z0: number): Mesh {
531
+ const seg = [12, 8, 5][lod] ?? 5;
532
+ const stack: [number, number, Vec3][] = [
533
+ [1.1, 312, GREY],
534
+ [0.8, 320, WHITE],
535
+ [0.55, 323, RED],
536
+ [0.5, 326, WHITE],
537
+ ];
538
+ const out: Mesh[] = [];
539
+ let z = z0;
540
+ for (const [r, top, color] of stack) {
541
+ out.push(
542
+ lathe({
543
+ profile: [
544
+ [r, 0],
545
+ [r, top - z],
546
+ ],
547
+ segments: seg,
548
+ caps: true,
549
+ at: [0, 0, z],
550
+ color,
551
+ }),
552
+ );
553
+ z = top;
554
+ }
555
+ out.push(
556
+ lathe({
557
+ profile: [
558
+ [0.2, 0],
559
+ [0.08, 330 - z],
560
+ [0, 330 - z],
561
+ ],
562
+ segments: seg,
563
+ caps: true,
564
+ at: [0, 0, z],
565
+ color: GREY,
566
+ }),
567
+ );
568
+ return merge(out);
569
+ }
570
+
571
+ function cabin(lod: Lod): Mesh {
572
+ const out: Mesh[] = [
573
+ box({ size: [16.4, 16.4, TERRACE - 0.3 - DECK3], at: [0, 0, DECK3], color: GLASS }),
574
+ ];
575
+ if (lod === 0)
576
+ for (let i = 0; i <= 8; i++) {
577
+ const y = -8.2 + i * 2.05;
578
+ for (const m of ring4(
579
+ box({ size: [0.2, 0.2, TERRACE - 0.3 - DECK3], at: [8.2, y, DECK3], color: SHADES[2] }),
580
+ ))
581
+ out.push(m);
582
+ }
583
+ return merge(out);
584
+ }
585
+
586
+ // ── LOD2: silhouette shell ──────────────────────────────────────────────────────────────────────
587
+ function frustum(lo: readonly Vec3[], hi: readonly Vec3[]): Mesh {
588
+ const b = new MeshBuilder();
589
+ const all = [...lo, ...hi];
590
+ const c = scale(
591
+ all.reduce((s, p) => add(s, p), [0, 0, 0] as Vec3),
592
+ 1 / all.length,
593
+ );
594
+ const quad = (p: readonly Vec3[]): void => {
595
+ const [a, bb, cc, d] = [p[0] ?? c, p[1] ?? c, p[2] ?? c, p[3] ?? c];
596
+ let n = normalize(cross(sub(bb, a), sub(cc, a)));
597
+ const mid = scale(add(add(a, bb), add(cc, d)), 0.25);
598
+ const flip = dot(n, sub(mid, c)) < 0;
599
+ if (flip) n = scale(n, -1);
600
+ const ids = [a, bb, cc, d].map((q) => b.vertex({ p: q, n, uv: [0.5, 0.5], color: IRON }));
601
+ const [i0 = 0, i1 = 0, i2 = 0, i3 = 0] = ids;
602
+ if (flip) b.quad(i0, i3, i2, i1);
603
+ else b.quad(i0, i1, i2, i3);
604
+ };
605
+ for (let i = 0; i < 4; i++) {
606
+ const j = (i + 1) % 4;
607
+ quad([lo[i] ?? c, lo[j] ?? c, hi[j] ?? c, hi[i] ?? c]);
608
+ }
609
+ quad(hi);
610
+ if ((lo[0]?.[2] ?? 0) > 0.01) quad(lo);
611
+ return b.build();
612
+ }
613
+
614
+ const square = (x0: number, x1: number, y0: number, y1: number, z: number): Vec3[] => [
615
+ [x0, y0, z],
616
+ [x1, y0, z],
617
+ [x1, y1, z],
618
+ [x0, y1, z],
619
+ ];
620
+
621
+ function farLeg(q: number): Mesh {
622
+ const [sx, sy] = QUAD[q] ?? [1, 1];
623
+ const sq = (z: number): Vec3[] => {
624
+ const [o, i] = [halfW(z), innerRaw(z)];
625
+ return square(sx * i, sx * o, sy * i, sy * o, z);
626
+ };
627
+ const bands = [
628
+ [0, 22, 44, DECK1 - SLAB],
629
+ [DECK1, 84, Z11, DECK2 - SLAB],
630
+ ];
631
+ const out: Mesh[] = [];
632
+ for (const zs of bands)
633
+ for (let k = 0; k + 1 < zs.length; k++) out.push(frustum(sq(zs[k] ?? 0), sq(zs[k + 1] ?? 0)));
634
+ if (q === 0) {
635
+ const zs = [DECK2, Z17, 215, 245, Z29, DECK3 - SLAB];
636
+ const full = (z: number): Vec3[] => square(-halfW(z), halfW(z), -halfW(z), halfW(z), z);
637
+ for (let k = 0; k + 1 < zs.length; k++)
638
+ out.push(frustum(full(zs[k] ?? 0), full(zs[k + 1] ?? 0)));
639
+ }
640
+ return iron(out, true);
641
+ }
642
+
643
+ /**
644
+ * LOD2 is one silhouette shell. Every far part reaches the ground: `leg_0` also carries the shaft,
645
+ * the 2nd/3rd decks and the spire, `arch_0` the 1st-floor belt and deck. Tested against each
646
+ * other, the shell's 40 m faces would cost the contact gate ~15 s of triangle-grid cells.
647
+ */
648
+ function buildFar(ctx: BuildContext): void {
649
+ const spire = lathe({
650
+ profile: [
651
+ [2.4, 0],
652
+ [1.9, 8],
653
+ [0.9, 18],
654
+ [0.9, 21.7],
655
+ ],
656
+ segments: 6,
657
+ caps: true,
658
+ at: [0, 0, TERRACE - 0.3],
659
+ color: IRON,
660
+ });
661
+ const top = [deckRing(SIDE2, 0, DECK2), deckRing(SIDE3, 0, DECK3), spire];
662
+ for (let q = 0; q < 4; q++) {
663
+ const extra = q === 0 ? [iron(top), cabin(2), antenna(2, TERRACE + 21.4)] : [];
664
+ ctx.part(`leg_${q}`, merge([farLeg(q), ...extra]));
665
+ }
666
+ const a = arch(2);
667
+ const floor1 = [belt1(2), iron([deckRing(SIDE1, 15, DECK1)])];
668
+ for (let q = 0; q < 4; q++) {
669
+ const m = place(a, { yaw: (q * Math.PI) / 2 });
670
+ ctx.part(`arch_${q}`, q === 0 ? merge([m, ...floor1]) : m);
671
+ }
672
+ }
673
+
674
+ // ── The asset ───────────────────────────────────────────────────────────────────────────────────
675
+ export const eiffel_tower = defineAsset({
676
+ name: 'eiffel_tower',
677
+ kind: 'building',
678
+ description:
679
+ 'Eiffel Tower, Paris (1889; 330 m since the 2022 DAB antenna): four curved wrought-iron lattice legs on the SNTE 29-panel profile, the 1st-floor arches and belt, three platforms, campanile and antenna; three graded shades of Eiffel Tower Brown.',
680
+ tags: ['landmark', 'paris', 'lattice', 'wrought-iron', '1889'],
681
+ sources: [
682
+ 'https://comptes-rendus.academie-sciences.fr/mecanique/item/10.1016/j.crme.2004.02.021.pdf',
683
+ 'https://commons.wikimedia.org/wiki/File:Eiffel_Tower_plans_01.jpg',
684
+ 'https://fr.wikipedia.org/wiki/Tour_Eiffel',
685
+ 'https://en.wikipedia.org/wiki/Eiffel_Tower',
686
+ 'https://structurae.net/en/structures/20000021',
687
+ 'https://www.skyscrapercenter.com/building/wd/9410',
688
+ 'https://www.nationalgeographic.com/travel/article/behind-the-scenes-eiffel-tower-paris',
689
+ 'https://www.toureiffel.paris/en/the-monument/painting-eiffel-tower',
690
+ ],
691
+ real: {
692
+ length: 124.9,
693
+ width: 124.9,
694
+ height: 330,
695
+ measures: [
696
+ {
697
+ label: '1st floor deck height',
698
+ axis: 'z',
699
+ value: 57.63,
700
+ parts: ['pedestal_0', 'platform_1'],
701
+ source: 'structurae / plate PL. I',
702
+ },
703
+ {
704
+ label: '2nd floor deck height',
705
+ axis: 'z',
706
+ value: 115.73,
707
+ parts: ['pedestal_0', 'platform_2'],
708
+ source: 'structurae / plate PL. I',
709
+ },
710
+ {
711
+ label: '3rd floor deck height',
712
+ axis: 'z',
713
+ value: 276.13,
714
+ parts: ['pedestal_0', 'platform_3'],
715
+ source: 'structurae',
716
+ },
717
+ {
718
+ label: 'base square, outside of the legs',
719
+ axis: 'x',
720
+ value: 124.9,
721
+ parts: ['leg_0', 'leg_1'],
722
+ source: 'fr.wikipedia (124,90 m)',
723
+ },
724
+ {
725
+ label: 'leg centres at the ground',
726
+ axis: 'x',
727
+ value: 99.57,
728
+ between: ['pedestal_0', 'pedestal_1'],
729
+ source: 'plate PL. I: (124.90 + 74.24) / 2',
730
+ },
731
+ {
732
+ label: '1st floor outer side',
733
+ axis: 'x',
734
+ value: 70.69,
735
+ parts: ['platform_1'],
736
+ source: 'fr.wikipedia',
737
+ },
738
+ ],
739
+ },
740
+ budget: { triangles: [210000, 25000, 1250] }, // measured 187.9k / 21.9k / 1.06k
741
+ build(ctx) {
742
+ const lod = ctx.lod;
743
+ if (lod === 2) return buildFar(ctx);
744
+ // Legs first: the contact gate walks the cheap parts below before it ever reaches these.
745
+ for (let q = 0; q < 4; q++) ctx.part(`leg_${q}`, leg(q, lod));
746
+ for (let q = 0; q < 4; q++) {
747
+ const [sx, sy] = QUAD[q] ?? [1, 1];
748
+ const [o, i] = [halfW(0) - 2.5, halfW(0) - LEG0 + 2.5];
749
+ const blocks = [
750
+ [o, o],
751
+ [o, i],
752
+ [i, o],
753
+ [i, i],
754
+ ].map(([x = 0, y = 0]) =>
755
+ box({ size: [5, 5, 2], at: [sx * x, sy * y, 0], color: STONE, omit: ['bottom'] }),
756
+ );
757
+ ctx.part(`pedestal_${q}`, grime(merge(blocks), { ground: 2, groundStrength: 0.3 }));
758
+ }
759
+ const a = arch(lod);
760
+ for (let q = 0; q < 4; q++) ctx.part(`arch_${q}`, place(a, { yaw: (q * Math.PI) / 2 }));
761
+ ctx.part('lift_0', liftLeg(0, lod));
762
+ ctx.part('lift_2', liftLeg(2, lod));
763
+ ctx.part('belt_1', belt1(lod));
764
+ ctx.part('platform_1', iron([deckRing(SIDE1, 15, DECK1)]));
765
+ ctx.part('railing_1', railing(SIDE1, DECK1, 1.3, 2.5, lod));
766
+ ctx.part('pavilion_1', pavilions1(lod));
767
+ ctx.part('platform_2', iron([deckRing(SIDE2, 0, DECK2)]));
768
+ ctx.part('belt_2', belt2(lod));
769
+ ctx.part('railing_2', railing(SIDE2, DECK2, 1.3, 2.0, lod));
770
+ ctx.part('lift_top', liftTop(lod));
771
+ ctx.part('platform_3', iron([deckRing(SIDE3, 0, DECK3)]));
772
+ ctx.part('cabin_3', cabin(lod));
773
+ ctx.part(
774
+ 'terrace_3',
775
+ iron([box({ size: [18, 18, 0.3], at: [0, 0, TERRACE - 0.3], color: IRON_DARK })]),
776
+ );
777
+ ctx.part('railing_3', railing(9.0, TERRACE, 1.2, 1.5, lod));
778
+ ctx.part('campanile', campanile(lod));
779
+ ctx.part('antenna', antenna(lod, LANTERN_TOP));
780
+ },
781
+ });