@downdraft/engine 0.0.0 → 0.1.1

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 (908) hide show
  1. package/README.md +33 -0
  2. package/app/src/index.ts +10 -0
  3. package/app/src/renderer/bootstrap.ts +271 -0
  4. package/app/src/renderer/downdraft-base.css +93 -0
  5. package/app/src/renderer/feature-log.ts +200 -0
  6. package/app/src/renderer/game-module.ts +923 -0
  7. package/app/src/renderer/hooks.ts +88 -0
  8. package/app/src/renderer/hot-reload.ts +178 -0
  9. package/app/src/renderer/import-cache.ts +88 -0
  10. package/app/src/renderer/index.ts +206 -0
  11. package/app/src/renderer/mcp-harness.ts +144 -0
  12. package/app/src/renderer/save-store-factory.ts +239 -0
  13. package/app/src/renderer/sim-bridge.ts +183 -0
  14. package/app/src/renderer/standard-automation-tools.ts +771 -0
  15. package/app/src/shared/feature-log.ts +71 -0
  16. package/app/src/shared/gpu-info.ts +82 -0
  17. package/app/src/shared/import-cache-store.ts +142 -0
  18. package/app/src/shared/index.ts +6 -0
  19. package/app/src/shared/mcp-discovery.ts +47 -0
  20. package/app/src/shared/mcp-proxy.ts +82 -0
  21. package/app/src/shared/types.ts +324 -0
  22. package/asset-bake/src/bake-audio.ts +119 -0
  23. package/asset-bake/src/bake-gltf.ts +172 -0
  24. package/asset-bake/src/bake.ts +78 -0
  25. package/asset-bake/src/cache.ts +188 -0
  26. package/asset-bake/src/config.ts +186 -0
  27. package/asset-bake/src/index.ts +36 -0
  28. package/core/src/animation/animation-event.ts +41 -0
  29. package/core/src/animation/bone-mask.ts +122 -0
  30. package/core/src/animation/clip.ts +252 -0
  31. package/core/src/animation/index.ts +22 -0
  32. package/core/src/animation/mixamo.ts +113 -0
  33. package/core/src/animation/morph-target.ts +69 -0
  34. package/core/src/animation/player.ts +697 -0
  35. package/core/src/animation/retarget.ts +158 -0
  36. package/core/src/animation/skeleton-animator.ts +1399 -0
  37. package/core/src/animation/skeleton.ts +214 -0
  38. package/core/src/animation/state-machine.ts +280 -0
  39. package/core/src/assets/blob-store.ts +66 -0
  40. package/core/src/assets/cache.ts +186 -0
  41. package/core/src/assets/cubemap-converter.ts +155 -0
  42. package/core/src/assets/environment-manager.ts +121 -0
  43. package/core/src/assets/import-cache.ts +65 -0
  44. package/core/src/assets/import-settings.ts +72 -0
  45. package/core/src/assets/importer.ts +108 -0
  46. package/core/src/assets/index.ts +74 -0
  47. package/core/src/assets/irradiance-generator.ts +198 -0
  48. package/core/src/assets/loader-audio.ts +86 -0
  49. package/core/src/assets/loader-cubemap.ts +211 -0
  50. package/core/src/assets/loader-dds.ts +236 -0
  51. package/core/src/assets/loader-hdr.ts +474 -0
  52. package/core/src/assets/loader-mesh.ts +143 -0
  53. package/core/src/assets/loader-shader.ts +73 -0
  54. package/core/src/assets/loader-texture.ts +349 -0
  55. package/core/src/assets/lod.ts +198 -0
  56. package/core/src/assets/manager.ts +480 -0
  57. package/core/src/assets/manifest.ts +81 -0
  58. package/core/src/assets/material-bridge.ts +186 -0
  59. package/core/src/assets/model-importer.ts +72 -0
  60. package/core/src/assets/model-normalizer.ts +248 -0
  61. package/core/src/assets/model-to-gpu.ts +70 -0
  62. package/core/src/assets/model-to-mesh.ts +237 -0
  63. package/core/src/assets/prefilter-generator.ts +250 -0
  64. package/core/src/audio/engine.ts +177 -0
  65. package/core/src/audio/interface.ts +130 -0
  66. package/core/src/audio/listener.ts +32 -0
  67. package/core/src/audio/mixer.ts +141 -0
  68. package/core/src/audio/registry.ts +52 -0
  69. package/core/src/audio/sab.ts +87 -0
  70. package/core/src/audio/source.ts +89 -0
  71. package/core/src/change-detection/atomics.ts +1 -0
  72. package/core/src/change-detection/tracker.ts +47 -0
  73. package/core/src/debug-draw/lines.ts +64 -0
  74. package/core/src/debug-draw/points.ts +17 -0
  75. package/core/src/debug-draw/queue.ts +124 -0
  76. package/core/src/debug-draw/text.ts +25 -0
  77. package/core/src/ecs/archetype.ts +323 -0
  78. package/core/src/ecs/component.ts +188 -0
  79. package/core/src/ecs/entity.ts +25 -0
  80. package/core/src/ecs/events.ts +93 -0
  81. package/core/src/ecs/hierarchy.ts +124 -0
  82. package/core/src/ecs/hmr-swap-registry.ts +22 -0
  83. package/core/src/ecs/index.ts +25 -0
  84. package/core/src/ecs/query.ts +141 -0
  85. package/core/src/ecs/resource.ts +8 -0
  86. package/core/src/ecs/schedule.ts +181 -0
  87. package/core/src/ecs/system-params.ts +99 -0
  88. package/core/src/ecs/system.ts +42 -0
  89. package/core/src/ecs/world.ts +348 -0
  90. package/core/src/index.ts +352 -0
  91. package/core/src/input/context.ts +73 -0
  92. package/core/src/input/dom-handler.ts +380 -0
  93. package/core/src/input/local-player-manager.ts +218 -0
  94. package/core/src/input/mapping.ts +40 -0
  95. package/core/src/input/multi-sab-bridge.ts +276 -0
  96. package/core/src/input/multi-state.ts +54 -0
  97. package/core/src/input/sab-bridge.ts +71 -0
  98. package/core/src/input/state.ts +132 -0
  99. package/core/src/input/ui-router.ts +87 -0
  100. package/core/src/library/host.ts +317 -0
  101. package/core/src/library/index.ts +16 -0
  102. package/core/src/library/library.ts +292 -0
  103. package/core/src/material/compiler.ts +80 -0
  104. package/core/src/material/graph-bridge.ts +172 -0
  105. package/core/src/material/index.ts +9 -0
  106. package/core/src/material/library.ts +442 -0
  107. package/core/src/material/material.ts +156 -0
  108. package/core/src/material/variants.ts +61 -0
  109. package/core/src/math/mat4.ts +202 -0
  110. package/core/src/math/perlin-noise-2d.ts +77 -0
  111. package/core/src/math/perlin-noise-3d.ts +110 -0
  112. package/core/src/math/quat.ts +33 -0
  113. package/core/src/math/rng.ts +29 -0
  114. package/core/src/math/trig.ts +47 -0
  115. package/core/src/math/value-noise.ts +67 -0
  116. package/core/src/mesh/builder.ts +177 -0
  117. package/core/src/mesh/csg.ts +312 -0
  118. package/core/src/mesh/greased-line.ts +145 -0
  119. package/core/src/mesh/mesh.ts +17 -0
  120. package/core/src/mesh/parametric.ts +501 -0
  121. package/core/src/mesh/skinning.ts +67 -0
  122. package/core/src/mesh/vertex-layout.ts +91 -0
  123. package/core/src/module/cross-thread.ts +163 -0
  124. package/core/src/module/diagnostics.ts +123 -0
  125. package/core/src/module/host.ts +451 -0
  126. package/core/src/module/index.ts +22 -0
  127. package/core/src/module/module.ts +109 -0
  128. package/core/src/module/registry.ts +94 -0
  129. package/core/src/module/renderer-module.ts +247 -0
  130. package/core/src/module/ts-loader.ts +26 -0
  131. package/core/src/particles/compute-pass.ts +378 -0
  132. package/core/src/particles/emitter.ts +156 -0
  133. package/core/src/particles/index.ts +11 -0
  134. package/core/src/particles/particle-data.ts +47 -0
  135. package/core/src/particles/render-pass.ts +365 -0
  136. package/core/src/particles/simulator.ts +264 -0
  137. package/core/src/particles/system.ts +193 -0
  138. package/core/src/physics/body.ts +59 -0
  139. package/core/src/physics/ccd-heuristic.ts +55 -0
  140. package/core/src/physics/character.ts +282 -0
  141. package/core/src/physics/collider.ts +55 -0
  142. package/core/src/physics/collision-system.ts +91 -0
  143. package/core/src/physics/events.ts +133 -0
  144. package/core/src/physics/index.ts +34 -0
  145. package/core/src/physics/interface.ts +473 -0
  146. package/core/src/physics/interpolation-buffer.ts +192 -0
  147. package/core/src/physics/lifecycle.ts +87 -0
  148. package/core/src/physics/load-shedder.ts +164 -0
  149. package/core/src/physics/physics-accumulator.ts +101 -0
  150. package/core/src/physics/physics-system.ts +240 -0
  151. package/core/src/physics/ragdoll-presets.ts +272 -0
  152. package/core/src/physics/ragdoll-system.ts +220 -0
  153. package/core/src/physics/ragdoll.ts +182 -0
  154. package/core/src/physics/raycast.ts +143 -0
  155. package/core/src/physics/realm-manager.ts +499 -0
  156. package/core/src/physics/realm-worker-pool.ts +191 -0
  157. package/core/src/physics/realm-worker.ts +102 -0
  158. package/core/src/physics/realm.ts +189 -0
  159. package/core/src/physics/registry.ts +52 -0
  160. package/core/src/physics/safety.ts +247 -0
  161. package/core/src/physics/snapshot-manager.ts +141 -0
  162. package/core/src/physics/worker-protocol.ts +30 -0
  163. package/core/src/platform/bun-preload.ts +169 -0
  164. package/core/src/platform/css-stub.ts +4 -0
  165. package/core/src/platform/glob-polyfill.ts +124 -0
  166. package/core/src/platform/hidpi.ts +57 -0
  167. package/core/src/platform/render-surface.ts +77 -0
  168. package/core/src/platform/rpc.ts +98 -0
  169. package/core/src/platform/runtime.ts +132 -0
  170. package/core/src/plugin/context.ts +364 -0
  171. package/core/src/plugin/diagnostics.ts +61 -0
  172. package/core/src/plugin/discovery.ts +186 -0
  173. package/core/src/plugin/extension-loaders.ts +172 -0
  174. package/core/src/plugin/host.ts +659 -0
  175. package/core/src/plugin/index.ts +106 -0
  176. package/core/src/plugin/loader-asset.ts +124 -0
  177. package/core/src/plugin/loader-quickjs.ts +84 -0
  178. package/core/src/plugin/loader-wasm.ts +411 -0
  179. package/core/src/plugin/loader-worker.ts +261 -0
  180. package/core/src/plugin/manifest.ts +653 -0
  181. package/core/src/plugin/material-registry.ts +65 -0
  182. package/core/src/plugin/mcp-tools.ts +165 -0
  183. package/core/src/plugin/permissions.ts +175 -0
  184. package/core/src/plugin/quickjs-bridge.ts +294 -0
  185. package/core/src/plugin/registry.ts +107 -0
  186. package/core/src/plugin/sandbox-shim.ts +86 -0
  187. package/core/src/plugin/sandbox-worker.ts +215 -0
  188. package/core/src/plugin/wasm-abi.ts +152 -0
  189. package/core/src/plugin/wasm-worker.ts +229 -0
  190. package/core/src/plugin/workshop.ts +169 -0
  191. package/core/src/profiling/event-loop.ts +180 -0
  192. package/core/src/profiling/index.ts +69 -0
  193. package/core/src/profiling/iops/idb-patch.ts +242 -0
  194. package/core/src/profiling/iops/opfs-patch.ts +276 -0
  195. package/core/src/profiling/profiling-sab.ts +839 -0
  196. package/core/src/profiling/task-latency.ts +134 -0
  197. package/core/src/profiling/thread-metrics.ts +126 -0
  198. package/core/src/profiling/trace-event-writer.ts +205 -0
  199. package/core/src/profiling/warnings.ts +423 -0
  200. package/core/src/profiling/worker-prelude.ts +376 -0
  201. package/core/src/render/bind-group.ts +38 -0
  202. package/core/src/render/bindless/bindless-struct.ts +42 -0
  203. package/core/src/render/bindless/bindless.wgsl.ts +167 -0
  204. package/core/src/render/bindless/frame-bindings.ts +176 -0
  205. package/core/src/render/bindless/index.ts +10 -0
  206. package/core/src/render/bindless/material-manager.ts +257 -0
  207. package/core/src/render/bindless/texture-registry.ts +609 -0
  208. package/core/src/render/buffer.ts +82 -0
  209. package/core/src/render/camera-2d.ts +217 -0
  210. package/core/src/render/camera-controller.ts +440 -0
  211. package/core/src/render/camera.ts +409 -0
  212. package/core/src/render/canvas-resize-watcher.ts +46 -0
  213. package/core/src/render/compute-kernel.ts +172 -0
  214. package/core/src/render/constants.ts +2 -0
  215. package/core/src/render/device.ts +111 -0
  216. package/core/src/render/frame-graph.ts +768 -0
  217. package/core/src/render/frustum.ts +185 -0
  218. package/core/src/render/fullscreen-vs.ts +25 -0
  219. package/core/src/render/g-buffer.ts +186 -0
  220. package/core/src/render/game-renderer.ts +1452 -0
  221. package/core/src/render/gpu-driven/cull-pass.ts +394 -0
  222. package/core/src/render/gpu-driven/hzb.ts +215 -0
  223. package/core/src/render/gpu-driven/index.ts +7 -0
  224. package/core/src/render/gpu-driven/indirect-draw-pass.ts +324 -0
  225. package/core/src/render/gpu-driven/instance-buffer.ts +178 -0
  226. package/core/src/render/gpu-driven/mesh-table.ts +175 -0
  227. package/core/src/render/gpu-driven/passes/hzb-build-pass.ts +47 -0
  228. package/core/src/render/gpu-utils.ts +96 -0
  229. package/core/src/render/hdr.ts +88 -0
  230. package/core/src/render/hot-reload.ts +295 -0
  231. package/core/src/render/ibl-bind-group.ts +266 -0
  232. package/core/src/render/ibl.ts +180 -0
  233. package/core/src/render/index.ts +163 -0
  234. package/core/src/render/input-manager.ts +259 -0
  235. package/core/src/render/lighting/area-light.ts +196 -0
  236. package/core/src/render/lighting/cluster-grid.ts +288 -0
  237. package/core/src/render/lighting/cluster-types.ts +193 -0
  238. package/core/src/render/lighting/ies-parser.ts +162 -0
  239. package/core/src/render/lighting.ts +454 -0
  240. package/core/src/render/material-types/depth.wgsl +44 -0
  241. package/core/src/render/material-types/line.wgsl +50 -0
  242. package/core/src/render/material-types/matcap.wgsl +51 -0
  243. package/core/src/render/material-types/normal.wgsl +41 -0
  244. package/core/src/render/material-types/physical.wgsl +152 -0
  245. package/core/src/render/material-types/sprite.wgsl +46 -0
  246. package/core/src/render/material-types/sss.wgsl +79 -0
  247. package/core/src/render/material-types/toon.wgsl +79 -0
  248. package/core/src/render/passes/README.md +121 -0
  249. package/core/src/render/passes/atmosphere.ts +152 -0
  250. package/core/src/render/passes/cluster-lighting-pass.ts +476 -0
  251. package/core/src/render/passes/csm.ts +408 -0
  252. package/core/src/render/passes/cubemap-capture.ts +114 -0
  253. package/core/src/render/passes/debug-viz.ts +278 -0
  254. package/core/src/render/passes/debug.ts +538 -0
  255. package/core/src/render/passes/decal-mesh.ts +111 -0
  256. package/core/src/render/passes/decal-pass.ts +377 -0
  257. package/core/src/render/passes/deferred-lighting.ts +434 -0
  258. package/core/src/render/passes/depth-prepass.ts +163 -0
  259. package/core/src/render/passes/dome-360.ts +83 -0
  260. package/core/src/render/passes/fluid-render.ts +137 -0
  261. package/core/src/render/passes/gi-types.ts +118 -0
  262. package/core/src/render/passes/graph-compute.ts +300 -0
  263. package/core/src/render/passes/greased-line-pass.ts +232 -0
  264. package/core/src/render/passes/morph-target.ts +40 -0
  265. package/core/src/render/passes/opaque.ts +1028 -0
  266. package/core/src/render/passes/point-light-shadow.ts +266 -0
  267. package/core/src/render/passes/procedural-texture.ts +185 -0
  268. package/core/src/render/passes/reflection-probe.ts +140 -0
  269. package/core/src/render/passes/rsm-pass.ts +248 -0
  270. package/core/src/render/passes/sdf-text.ts +155 -0
  271. package/core/src/render/passes/shadow-map.ts +163 -0
  272. package/core/src/render/passes/shadow.ts +220 -0
  273. package/core/src/render/passes/skinning-vs.ts +121 -0
  274. package/core/src/render/passes/skinning.ts +326 -0
  275. package/core/src/render/passes/sky-dome.ts +404 -0
  276. package/core/src/render/passes/skybox.ts +162 -0
  277. package/core/src/render/passes/spot-light-shadow.ts +273 -0
  278. package/core/src/render/passes/terrain.ts +326 -0
  279. package/core/src/render/passes/transparent.ts +353 -0
  280. package/core/src/render/passes/underwater-fog.ts +185 -0
  281. package/core/src/render/passes/video-texture.ts +79 -0
  282. package/core/src/render/passes/volumetric-pass.ts +312 -0
  283. package/core/src/render/passes/volumetric-types.ts +171 -0
  284. package/core/src/render/passes/water.ts +753 -0
  285. package/core/src/render/pbr.ts +296 -0
  286. package/core/src/render/pipeline.ts +52 -0
  287. package/core/src/render/render-pass.ts +30 -0
  288. package/core/src/render/renderer-input-bus.ts +220 -0
  289. package/core/src/render/renderer-module-host.ts +623 -0
  290. package/core/src/render/resource-tracker.ts +67 -0
  291. package/core/src/render/sab-canvas-overlay.ts +122 -0
  292. package/core/src/render/shader-validator.ts +200 -0
  293. package/core/src/render/shaders/debug/debug-line.wgsl +27 -0
  294. package/core/src/render/shaders/debug/debug-point.wgsl +30 -0
  295. package/core/src/render/shaders/debug/debug-text.wgsl +42 -0
  296. package/core/src/render/shaders/debug-viz/aabb.wgsl +20 -0
  297. package/core/src/render/shaders/debug-viz/depth.wgsl +29 -0
  298. package/core/src/render/shaders/debug-viz/lod.wgsl +38 -0
  299. package/core/src/render/shaders/debug-viz/normals.wgsl +30 -0
  300. package/core/src/render/shaders/debug-viz/overdraw.wgsl +21 -0
  301. package/core/src/render/shaders/debug-viz/tangents.wgsl +29 -0
  302. package/core/src/render/shaders/debug-viz/wireframe.wgsl +33 -0
  303. package/core/src/render/shaders/shadow-chunks.ts +135 -0
  304. package/core/src/render/skybox.ts +146 -0
  305. package/core/src/render/splitscreen.ts +85 -0
  306. package/core/src/render/surface.ts +78 -0
  307. package/core/src/render/tonemap.ts +180 -0
  308. package/core/src/render/tracked-render-pass.ts +158 -0
  309. package/core/src/render/viewport.ts +109 -0
  310. package/core/src/render/wgsl-hmr.ts +70 -0
  311. package/core/src/sab/core-input-channel.ts +23 -0
  312. package/core/src/sab/define.ts +239 -0
  313. package/core/src/sab/errors.ts +76 -0
  314. package/core/src/sab/game-input.ts +343 -0
  315. package/core/src/sab/gamepad-devices.ts +259 -0
  316. package/core/src/sab/grid-buffer.ts +117 -0
  317. package/core/src/sab/input.ts +55 -0
  318. package/core/src/sab/layout.ts +219 -0
  319. package/core/src/sab/multi-input-channel.ts +50 -0
  320. package/core/src/sab/record-buffer.ts +155 -0
  321. package/core/src/sab/sab-polyfill.ts +56 -0
  322. package/core/src/sab/sim-channel.ts +453 -0
  323. package/core/src/sab/slot-buffer.ts +190 -0
  324. package/core/src/sab/types.ts +158 -0
  325. package/core/src/safety/bounds.ts +76 -0
  326. package/core/src/safety/json.ts +80 -0
  327. package/core/src/safety/path.ts +167 -0
  328. package/core/src/save/binary-format.ts +117 -0
  329. package/core/src/save/game-save-system.ts +161 -0
  330. package/core/src/save/grid-save-system.ts +158 -0
  331. package/core/src/save/migrate.ts +44 -0
  332. package/core/src/save/migration-registry.ts +94 -0
  333. package/core/src/save/persist-types.ts +208 -0
  334. package/core/src/save/schema.ts +46 -0
  335. package/core/src/save/serializer.ts +180 -0
  336. package/core/src/scene/builder.ts +65 -0
  337. package/core/src/scene/camera.ts +142 -0
  338. package/core/src/scene/checkpoint.ts +109 -0
  339. package/core/src/scene/game-loop.ts +198 -0
  340. package/core/src/scene/index.ts +24 -0
  341. package/core/src/scene/layer.ts +6 -0
  342. package/core/src/scene/prefab.ts +145 -0
  343. package/core/src/scene/scene-manager.ts +157 -0
  344. package/core/src/scene/scene.ts +192 -0
  345. package/core/src/scene/sky.ts +256 -0
  346. package/core/src/scene/spatial-grid.ts +176 -0
  347. package/core/src/scene/streaming.ts +304 -0
  348. package/core/src/scene/vegetation.ts +163 -0
  349. package/core/src/scene/world.ts +89 -0
  350. package/core/src/scripting/binding.ts +129 -0
  351. package/core/src/scripting/hot-reload.ts +144 -0
  352. package/core/src/scripting/script.ts +99 -0
  353. package/core/src/sim/hot-reload-pipeline.ts +49 -0
  354. package/core/src/sim/sim-state-helper.ts +47 -0
  355. package/core/src/sim/transient-state-registry.ts +77 -0
  356. package/core/src/sim/types.ts +113 -0
  357. package/core/src/sim/worker-loop.ts +222 -0
  358. package/core/src/stores/game-store.ts +158 -0
  359. package/core/src/stores/hot-reload-store.ts +44 -0
  360. package/core/src/telemetry/collector.ts +410 -0
  361. package/core/src/telemetry/gc-controller.ts +453 -0
  362. package/core/src/telemetry/gc-profiler.ts +126 -0
  363. package/core/src/telemetry/gc-tracker.ts +69 -0
  364. package/core/src/telemetry/gpu-profiler.ts +703 -0
  365. package/core/src/telemetry/gpu-resource-tracker.ts +285 -0
  366. package/core/src/telemetry/gpu-timer-pool.ts +234 -0
  367. package/core/src/telemetry/gpu-timer.ts +123 -0
  368. package/core/src/telemetry/gpu-timestamp-gate.ts +21 -0
  369. package/core/src/telemetry/reporter.ts +62 -0
  370. package/core/src/types/engine-types.ts +129 -0
  371. package/core/src/ui-font-scale.ts +112 -0
  372. package/core/src/util/broad-phase-grid.ts +82 -0
  373. package/core/src/util/feature-log.ts +220 -0
  374. package/core/src/util/index.ts +17 -0
  375. package/core/src/util/logger.ts +604 -0
  376. package/core/src/webgpu-destroy-augmentation.d.ts +25 -0
  377. package/core/src/worker/base-worker-host.ts +183 -0
  378. package/core/src/worker/buffer-sync.ts +550 -0
  379. package/core/src/worker/crash-recovery.ts +201 -0
  380. package/core/src/worker/entity-sim-worker-host.ts +190 -0
  381. package/core/src/worker/instrumented-worker-host.ts +171 -0
  382. package/core/src/worker/rpc.ts +253 -0
  383. package/core/src/worker/sim-worker-base.ts +913 -0
  384. package/core/src/worker/sim-worker-host.ts +358 -0
  385. package/core/src/worker/task-pool.ts +256 -0
  386. package/core/src/worker/task-worker.ts +83 -0
  387. package/libraries/asset-browser/src/index.ts +11 -0
  388. package/libraries/asset-browser/src/software-thumbnails.ts +949 -0
  389. package/libraries/audio-kira/README.md +17 -0
  390. package/libraries/audio-kira/native/Cargo.toml +14 -0
  391. package/libraries/audio-kira/native/src/engine.rs +27 -0
  392. package/libraries/audio-kira/native/src/lib.rs +185 -0
  393. package/libraries/audio-kira/native/src/mixer.rs +11 -0
  394. package/libraries/audio-kira/native/src/source.rs +80 -0
  395. package/libraries/audio-kira/src/backend.ts +469 -0
  396. package/libraries/audio-kira/src/index.ts +8 -0
  397. package/libraries/audio-kira/src/library.ts +82 -0
  398. package/libraries/blitz-ui/native/Cargo.toml +33 -0
  399. package/libraries/blitz-ui/native/assets/DejaVuSans.woff2 +0 -0
  400. package/libraries/blitz-ui/native/src/bridge.rs +100 -0
  401. package/libraries/blitz-ui/native/src/fonts.rs +20 -0
  402. package/libraries/blitz-ui/native/src/input.rs +80 -0
  403. package/libraries/blitz-ui/native/src/lib.rs +96 -0
  404. package/libraries/blitz-ui/native/src/shell.rs +301 -0
  405. package/libraries/blitz-ui/native-osr/Cargo.toml +24 -0
  406. package/libraries/blitz-ui/native-osr/src/events.rs +237 -0
  407. package/libraries/blitz-ui/native-osr/src/lib.rs +846 -0
  408. package/libraries/blitz-ui/native-osr/src/mutate.rs +385 -0
  409. package/libraries/blitz-ui/native-osr/src/resources.rs +45 -0
  410. package/libraries/blitz-ui/native-osr-spike.ts +70 -0
  411. package/libraries/blitz-ui/src/index.ts +15 -0
  412. package/libraries/blitz-ui/src/native-module.ts +221 -0
  413. package/libraries/blitz-ui/src/native-osr-ffi.ts +423 -0
  414. package/libraries/blitz-ui/src/router.ts +101 -0
  415. package/libraries/blitz-ui/src/shaders/ui-blit.wgsl.ts +35 -0
  416. package/libraries/blitz-ui/src/ui-blit-pass.ts +116 -0
  417. package/libraries/character/README.md +25 -0
  418. package/libraries/character/src/character-animator.ts +374 -0
  419. package/libraries/character/src/index.ts +24 -0
  420. package/libraries/character/src/loader.ts +375 -0
  421. package/libraries/character/src/locomotion.ts +124 -0
  422. package/libraries/character/src/preview.ts +287 -0
  423. package/libraries/devtools/README.md +51 -0
  424. package/libraries/devtools/native/Cargo.toml +12 -0
  425. package/libraries/devtools/native/src/lib.rs +1646 -0
  426. package/libraries/devtools/native/src/panels/console.rs +338 -0
  427. package/libraries/devtools/native/src/panels/dom_tree.rs +47 -0
  428. package/libraries/devtools/native/src/panels/generic.rs +317 -0
  429. package/libraries/devtools/native/src/panels/gpu.rs +107 -0
  430. package/libraries/devtools/native/src/panels/input.rs +88 -0
  431. package/libraries/devtools/native/src/panels/mod.rs +154 -0
  432. package/libraries/devtools/native/src/panels/perf_metrics.rs +91 -0
  433. package/libraries/devtools/native/src/panels/perf_recorder.rs +158 -0
  434. package/libraries/devtools/native/src/panels/scene.rs +72 -0
  435. package/libraries/devtools/native/src/state.rs +394 -0
  436. package/libraries/devtools/src/cdp-bridge.ts +349 -0
  437. package/libraries/devtools/src/collectors.ts +346 -0
  438. package/libraries/devtools/src/egui-ffi.ts +804 -0
  439. package/libraries/devtools/src/egui-renderer.ts +407 -0
  440. package/libraries/devtools/src/host.ts +363 -0
  441. package/libraries/devtools/src/index.ts +40 -0
  442. package/libraries/devtools/src/mirror.ts +794 -0
  443. package/libraries/devtools/src/native-providers.ts +744 -0
  444. package/libraries/devtools/src/verify.ts +167 -0
  445. package/libraries/devtools/src/web/browser.ts +102 -0
  446. package/libraries/devtools/src/web/host.ts +212 -0
  447. package/libraries/devtools/src/web/mirror.ts +430 -0
  448. package/libraries/devtools/src/web/server.ts +198 -0
  449. package/libraries/entities/README.md +17 -0
  450. package/libraries/entities/src/index.ts +4 -0
  451. package/libraries/entities/src/library.ts +64 -0
  452. package/libraries/entities/src/model-renderer.ts +1345 -0
  453. package/libraries/entities/src/shaders/model.wgsl +375 -0
  454. package/libraries/gamepad/src/feed-input.ts +83 -0
  455. package/libraries/gamepad/src/filters.ts +49 -0
  456. package/libraries/gamepad/src/glyphs.ts +93 -0
  457. package/libraries/gamepad/src/hub.ts +206 -0
  458. package/libraries/gamepad/src/index.ts +22 -0
  459. package/libraries/gamepad/src/library.ts +68 -0
  460. package/libraries/gamepad/src/source-sab.ts +70 -0
  461. package/libraries/gamepad/src/sysfs-enrich.ts +138 -0
  462. package/libraries/gaussian-splats/README.md +25 -0
  463. package/libraries/gaussian-splats/src/gpu-sort.ts +595 -0
  464. package/libraries/gaussian-splats/src/index.ts +31 -0
  465. package/libraries/gaussian-splats/src/library.ts +60 -0
  466. package/libraries/gaussian-splats/src/parser.ts +382 -0
  467. package/libraries/gaussian-splats/src/renderer.ts +427 -0
  468. package/libraries/gaussian-splats/src/sh-eval.ts +211 -0
  469. package/libraries/gaussian-splats/src/sorter.ts +110 -0
  470. package/libraries/gaussian-splats/src/tile-raster.ts +473 -0
  471. package/libraries/gpu-kernels/README.md +154 -0
  472. package/libraries/gpu-kernels/src/index.ts +11 -0
  473. package/libraries/gpu-kernels/src/kernel.ts +696 -0
  474. package/libraries/gpu-kernels/src/transpile.ts +958 -0
  475. package/libraries/html-ui-kit/src/behaviors.ts +250 -0
  476. package/libraries/html-ui-kit/src/components.ts +309 -0
  477. package/libraries/html-ui-kit/src/index.ts +30 -0
  478. package/libraries/html-ui-kit/src/nav/nav-controller.ts +238 -0
  479. package/libraries/html-ui-kit/src/nav/nav-input.ts +145 -0
  480. package/libraries/html-ui-kit/src/nav/osk.ts +153 -0
  481. package/libraries/html-ui-kit/src/nav/router.ts +167 -0
  482. package/libraries/html-ui-kit/src/nav/vcursor.ts +203 -0
  483. package/libraries/html-ui-kit/src/ten-foot.ts +182 -0
  484. package/libraries/html-ui-kit/src/theme.ts +238 -0
  485. package/libraries/lighting/README.md +20 -0
  486. package/libraries/lighting/src/index.ts +5 -0
  487. package/libraries/lighting/src/library.ts +66 -0
  488. package/libraries/lighting/src/light-system.ts +452 -0
  489. package/libraries/lighting/src/lighting-system.ts +68 -0
  490. package/libraries/lighting/src/shaders/light-debug.wgsl +30 -0
  491. package/libraries/marching-cubes/README.md +25 -0
  492. package/libraries/marching-cubes/src/chunked-field.ts +177 -0
  493. package/libraries/marching-cubes/src/deformation.ts +63 -0
  494. package/libraries/marching-cubes/src/generator.ts +407 -0
  495. package/libraries/marching-cubes/src/index.ts +32 -0
  496. package/libraries/marching-cubes/src/library.ts +80 -0
  497. package/libraries/marching-cubes/src/lod.ts +122 -0
  498. package/libraries/marching-cubes/src/marching-tetrahedra.ts +252 -0
  499. package/libraries/marching-cubes/src/sab.ts +45 -0
  500. package/libraries/marching-cubes/src/streaming-config.ts +48 -0
  501. package/libraries/marching-cubes/src/streaming-manager.ts +686 -0
  502. package/libraries/marching-cubes/src/tables.ts +323 -0
  503. package/libraries/marching-cubes/src/types.ts +18 -0
  504. package/libraries/models/README.md +25 -0
  505. package/libraries/models/src/bake-node-transforms.ts +171 -0
  506. package/libraries/models/src/codecs/basisu-codec.ts +180 -0
  507. package/libraries/models/src/codecs/draco-codec.ts +372 -0
  508. package/libraries/models/src/codecs/draco3dgltf.d.ts +40 -0
  509. package/libraries/models/src/codecs/index.ts +22 -0
  510. package/libraries/models/src/codecs/meshopt-codec.ts +91 -0
  511. package/libraries/models/src/codecs/registry.ts +221 -0
  512. package/libraries/models/src/dae.ts +753 -0
  513. package/libraries/models/src/default-part-selection.ts +56 -0
  514. package/libraries/models/src/dxf.ts +200 -0
  515. package/libraries/models/src/fbx/index.ts +457 -0
  516. package/libraries/models/src/fbx/interpreter/animation.ts +431 -0
  517. package/libraries/models/src/fbx/interpreter/blend-shapes.ts +323 -0
  518. package/libraries/models/src/fbx/interpreter/connections.ts +166 -0
  519. package/libraries/models/src/fbx/interpreter/diagnostics.ts +65 -0
  520. package/libraries/models/src/fbx/interpreter/geometry.ts +535 -0
  521. package/libraries/models/src/fbx/interpreter/global-settings.ts +92 -0
  522. package/libraries/models/src/fbx/interpreter/materials.ts +265 -0
  523. package/libraries/models/src/fbx/interpreter/nodes.ts +197 -0
  524. package/libraries/models/src/fbx/interpreter/skeleton.ts +476 -0
  525. package/libraries/models/src/fbx/parsers/binary-parser.ts +244 -0
  526. package/libraries/models/src/fbx/types.ts +119 -0
  527. package/libraries/models/src/gltf-extensions.ts +196 -0
  528. package/libraries/models/src/gltf.ts +935 -0
  529. package/libraries/models/src/index.ts +87 -0
  530. package/libraries/models/src/library.ts +75 -0
  531. package/libraries/models/src/loader.ts +265 -0
  532. package/libraries/models/src/material-adapter.ts +108 -0
  533. package/libraries/models/src/mini-xml.ts +174 -0
  534. package/libraries/models/src/normalize.ts +216 -0
  535. package/libraries/models/src/obj.ts +264 -0
  536. package/libraries/models/src/off.ts +95 -0
  537. package/libraries/models/src/ply.ts +348 -0
  538. package/libraries/models/src/pose-sampler.ts +219 -0
  539. package/libraries/models/src/sidecar/blender-extras.ts +37 -0
  540. package/libraries/models/src/sidecar/ddmeta.ts +114 -0
  541. package/libraries/models/src/sidecar/godot-import.ts +86 -0
  542. package/libraries/models/src/sidecar/resolver.ts +145 -0
  543. package/libraries/models/src/sidecar/types.ts +11 -0
  544. package/libraries/models/src/sidecar/unity-meta.ts +61 -0
  545. package/libraries/models/src/skeleton-synthesis.ts +132 -0
  546. package/libraries/models/src/stl.ts +149 -0
  547. package/libraries/models/src/threeds.ts +363 -0
  548. package/libraries/models/src/threemf.ts +201 -0
  549. package/libraries/models/src/types.ts +188 -0
  550. package/libraries/models/src/vtk.ts +218 -0
  551. package/libraries/models/test-fixtures/fbx/LICENSE-notice.txt +24 -0
  552. package/libraries/models/test-fixtures/fbx/README.md +46 -0
  553. package/libraries/models/test-fixtures/fbx/m01_cube_phong.fbx +0 -0
  554. package/libraries/models/test-fixtures/fbx/m02_geo_ngons.fbx +0 -0
  555. package/libraries/models/test-fixtures/fbx/m03_normals.fbx +0 -0
  556. package/libraries/models/test-fixtures/fbx/m04_material_properties.fbx +0 -0
  557. package/libraries/models/test-fixtures/fbx/m05_external_diffuse.png +0 -0
  558. package/libraries/models/test-fixtures/fbx/m05_textures.fbx +0 -0
  559. package/libraries/models/test-fixtures/fbx/m06_uv_transform.fbx +0 -0
  560. package/libraries/models/test-fixtures/fbx/m07_multimaterial.fbx +0 -0
  561. package/libraries/models/test-fixtures/fbx/m08_transforms.fbx +0 -0
  562. package/libraries/models/test-fixtures/fbx/m09_skinning.fbx +0 -0
  563. package/libraries/models/test-fixtures/fbx/m10_morph.fbx +0 -0
  564. package/libraries/models/test-fixtures/fbx/m11_node_anim.fbx +0 -0
  565. package/libraries/models/test-fixtures/fbx/m12_skeletal_anim.fbx +0 -0
  566. package/libraries/models/test-fixtures/fbx/m13_morph_anim.fbx +0 -0
  567. package/libraries/models/test-fixtures/fbx/m14_multiclip.fbx +0 -0
  568. package/libraries/models/test-fixtures/fbx/m15_camera_lights.fbx +0 -0
  569. package/libraries/models/test-fixtures/fbx/m16_axis_yup.fbx +0 -0
  570. package/libraries/models/test-fixtures/fbx/m16_axis_zup.fbx +0 -0
  571. package/libraries/models/test-fixtures/fbx/m16_units_254.fbx +0 -0
  572. package/libraries/navmesh/README.md +23 -0
  573. package/libraries/navmesh/src/crowd-system.ts +240 -0
  574. package/libraries/navmesh/src/debug-viz.ts +74 -0
  575. package/libraries/navmesh/src/index.ts +15 -0
  576. package/libraries/navmesh/src/library.ts +64 -0
  577. package/libraries/navmesh/src/navmesh-generator.ts +407 -0
  578. package/libraries/navmesh/src/navmesh.ts +159 -0
  579. package/libraries/navmesh/src/pathfinder.ts +252 -0
  580. package/libraries/navmesh/src/types.ts +63 -0
  581. package/libraries/networking/README.md +25 -0
  582. package/libraries/networking/src/authority.ts +121 -0
  583. package/libraries/networking/src/connection.ts +139 -0
  584. package/libraries/networking/src/discovery.ts +373 -0
  585. package/libraries/networking/src/enhanced-replication.ts +355 -0
  586. package/libraries/networking/src/epic-adapter.ts +129 -0
  587. package/libraries/networking/src/index.ts +45 -0
  588. package/libraries/networking/src/library.ts +69 -0
  589. package/libraries/networking/src/lobby.ts +108 -0
  590. package/libraries/networking/src/platform-adapter.ts +163 -0
  591. package/libraries/networking/src/rcs-adapter.ts +127 -0
  592. package/libraries/networking/src/remote-input.ts +188 -0
  593. package/libraries/networking/src/replication.ts +327 -0
  594. package/libraries/networking/src/rpc.ts +188 -0
  595. package/libraries/networking/src/session.ts +173 -0
  596. package/libraries/networking/src/transport.ts +201 -0
  597. package/libraries/networking/src/webrtc.ts +323 -0
  598. package/libraries/pathfinding-2d/README.md +15 -0
  599. package/libraries/pathfinding-2d/src/astar-grid.ts +312 -0
  600. package/libraries/pathfinding-2d/src/index.ts +11 -0
  601. package/libraries/persistence/README.md +25 -0
  602. package/libraries/persistence/src/autosave-manager.ts +112 -0
  603. package/libraries/persistence/src/binary-record-store.ts +244 -0
  604. package/libraries/persistence/src/browser.ts +51 -0
  605. package/libraries/persistence/src/file-save-store.ts +553 -0
  606. package/libraries/persistence/src/hash-utils.ts +59 -0
  607. package/libraries/persistence/src/index.ts +57 -0
  608. package/libraries/persistence/src/library.ts +74 -0
  609. package/libraries/persistence/src/mock-opfs.ts +141 -0
  610. package/libraries/persistence/src/node-fs-directory-handle.ts +161 -0
  611. package/libraries/persistence/src/opfs-save-store.ts +745 -0
  612. package/libraries/persistence/src/save-worker-proxy.ts +127 -0
  613. package/libraries/persistence/src/save-worker.ts +99 -0
  614. package/libraries/persistence/src/xxh3-ts.d.ts +10 -0
  615. package/libraries/physics-native/README.md +21 -0
  616. package/libraries/physics-native/native/Cargo.toml +11 -0
  617. package/libraries/physics-native/native/src/body.rs +403 -0
  618. package/libraries/physics-native/native/src/character.rs +226 -0
  619. package/libraries/physics-native/native/src/collider.rs +299 -0
  620. package/libraries/physics-native/native/src/joint.rs +81 -0
  621. package/libraries/physics-native/native/src/lib.rs +830 -0
  622. package/libraries/physics-native/native/src/query.rs +261 -0
  623. package/libraries/physics-native/native/src/realm.rs +33 -0
  624. package/libraries/physics-native/native/src/step.rs +54 -0
  625. package/libraries/physics-native/native/src/world.rs +148 -0
  626. package/libraries/physics-native/src/backend.ts +1046 -0
  627. package/libraries/physics-native/src/broadphase.ts +101 -0
  628. package/libraries/physics-native/src/ffi-spike-worker.ts +71 -0
  629. package/libraries/physics-native/src/index.ts +11 -0
  630. package/libraries/physics-native/src/library.ts +79 -0
  631. package/libraries/physics-native/src/narrowphase-shapes.ts +420 -0
  632. package/libraries/physics-native/src/narrowphase.ts +123 -0
  633. package/libraries/physics-native/src/solver.ts +141 -0
  634. package/libraries/physics-native/src/types.ts +76 -0
  635. package/libraries/physics-rapier/README.md +21 -0
  636. package/libraries/physics-rapier/bench/ffi-bench.ts +79 -0
  637. package/libraries/physics-rapier/bench/wasm-baseline.ts +90 -0
  638. package/libraries/physics-rapier/src/api.ts +452 -0
  639. package/libraries/physics-rapier/src/auto-backend.ts +59 -0
  640. package/libraries/physics-rapier/src/backend.ts +1207 -0
  641. package/libraries/physics-rapier/src/bulk-ops.ts +68 -0
  642. package/libraries/physics-rapier/src/ffi-backend.ts +21 -0
  643. package/libraries/physics-rapier/src/ffi-lib.ts +597 -0
  644. package/libraries/physics-rapier/src/index.ts +12 -0
  645. package/libraries/physics-rapier/src/library.ts +107 -0
  646. package/libraries/physics-rapier/src/rapier-backend.ts +1052 -0
  647. package/libraries/physics-rapier/src/realm-worker-script.ts +237 -0
  648. package/libraries/postfx/README.md +17 -0
  649. package/libraries/postfx/src/index.ts +19 -0
  650. package/libraries/postfx/src/library.ts +337 -0
  651. package/libraries/postfx/src/post-process-stack.ts +1752 -0
  652. package/libraries/postfx/src/shaders/post-process/afterimage.wgsl +14 -0
  653. package/libraries/postfx/src/shaders/post-process/ascii.wgsl +24 -0
  654. package/libraries/postfx/src/shaders/post-process/blit.wgsl +12 -0
  655. package/libraries/postfx/src/shaders/post-process/bloom-blur.wgsl +21 -0
  656. package/libraries/postfx/src/shaders/post-process/bloom-bright.wgsl +15 -0
  657. package/libraries/postfx/src/shaders/post-process/bloom-composite.wgsl +14 -0
  658. package/libraries/postfx/src/shaders/post-process/bloom-downsample.wgsl +60 -0
  659. package/libraries/postfx/src/shaders/post-process/bloom-soft.wgsl +46 -0
  660. package/libraries/postfx/src/shaders/post-process/bloom-upsample.wgsl +51 -0
  661. package/libraries/postfx/src/shaders/post-process/channel-mixer.wgsl +36 -0
  662. package/libraries/postfx/src/shaders/post-process/chromatic-aberration.wgsl +37 -0
  663. package/libraries/postfx/src/shaders/post-process/dithering.wgsl +64 -0
  664. package/libraries/postfx/src/shaders/post-process/dof.wgsl +131 -0
  665. package/libraries/postfx/src/shaders/post-process/edges.wgsl +45 -0
  666. package/libraries/postfx/src/shaders/post-process/fullscreen-vs.wgsl +15 -0
  667. package/libraries/postfx/src/shaders/post-process/fxaa.wgsl +102 -0
  668. package/libraries/postfx/src/shaders/post-process/gaussian-blur.wgsl +37 -0
  669. package/libraries/postfx/src/shaders/post-process/glow-blur.wgsl +33 -0
  670. package/libraries/postfx/src/shaders/post-process/glow-composite.wgsl +25 -0
  671. package/libraries/postfx/src/shaders/post-process/grain.wgsl +37 -0
  672. package/libraries/postfx/src/shaders/post-process/halftone.wgsl +80 -0
  673. package/libraries/postfx/src/shaders/post-process/highlight-blur.wgsl +33 -0
  674. package/libraries/postfx/src/shaders/post-process/highlight-composite.wgsl +32 -0
  675. package/libraries/postfx/src/shaders/post-process/lens-distortion.wgsl +43 -0
  676. package/libraries/postfx/src/shaders/post-process/lens-flare.wgsl +68 -0
  677. package/libraries/postfx/src/shaders/post-process/lut.wgsl +30 -0
  678. package/libraries/postfx/src/shaders/post-process/motion-blur.wgsl +55 -0
  679. package/libraries/postfx/src/shaders/post-process/occluder-chunk.wgsl +28 -0
  680. package/libraries/postfx/src/shaders/post-process/outline.wgsl +51 -0
  681. package/libraries/postfx/src/shaders/post-process/pixelation.wgsl +47 -0
  682. package/libraries/postfx/src/shaders/post-process/sharpen.wgsl +32 -0
  683. package/libraries/postfx/src/shaders/post-process/sobel.wgsl +26 -0
  684. package/libraries/postfx/src/shaders/post-process/split-tone.wgsl +36 -0
  685. package/libraries/postfx/src/shaders/post-process/ssao-blur.wgsl +38 -0
  686. package/libraries/postfx/src/shaders/post-process/ssao-composite.wgsl +25 -0
  687. package/libraries/postfx/src/shaders/post-process/ssao.wgsl +147 -0
  688. package/libraries/postfx/src/shaders/post-process/ssr.wgsl +155 -0
  689. package/libraries/postfx/src/shaders/post-process/taa.wgsl +98 -0
  690. package/libraries/postfx/src/shaders/post-process/tonemap.wgsl +40 -0
  691. package/libraries/postfx/src/shaders/post-process/watercolor.wgsl +82 -0
  692. package/libraries/postfx/src/shaders/post-process/white-balance.wgsl +41 -0
  693. package/libraries/recast/README.md +21 -0
  694. package/libraries/recast/src/backend.ts +150 -0
  695. package/libraries/recast/src/crowd-system.ts +352 -0
  696. package/libraries/recast/src/index.ts +12 -0
  697. package/libraries/recast/src/library.ts +91 -0
  698. package/libraries/recast/src/types.ts +86 -0
  699. package/libraries/sand/README.md +25 -0
  700. package/libraries/sand/src/cell.ts +43 -0
  701. package/libraries/sand/src/fields.ts +29 -0
  702. package/libraries/sand/src/fluid-grid.ts +261 -0
  703. package/libraries/sand/src/grid-dims.ts +74 -0
  704. package/libraries/sand/src/grid-sim-buffer.ts +271 -0
  705. package/libraries/sand/src/index.ts +81 -0
  706. package/libraries/sand/src/library.ts +92 -0
  707. package/libraries/sand/src/materials.ts +492 -0
  708. package/libraries/sand/src/palette.ts +93 -0
  709. package/libraries/sand/src/particles.ts +185 -0
  710. package/libraries/sand/src/render/sand-grid-pass.ts +430 -0
  711. package/libraries/sand/src/rng.ts +51 -0
  712. package/libraries/sand/src/rules/builtin-rules.ts +132 -0
  713. package/libraries/sand/src/rules/rule-compiler.ts +162 -0
  714. package/libraries/sand/src/rules/rule-engine.ts +231 -0
  715. package/libraries/sand/src/rules/rule-types.ts +87 -0
  716. package/libraries/sand/src/sand-step-pool.ts +484 -0
  717. package/libraries/sand/src/sand-step-worker.ts +97 -0
  718. package/libraries/sand/src/sand-world.ts +3227 -0
  719. package/libraries/stickman/README.md +25 -0
  720. package/libraries/stickman/src/index.ts +25 -0
  721. package/libraries/stickman/src/library.ts +61 -0
  722. package/libraries/stickman/src/proportions.ts +45 -0
  723. package/libraries/stickman/src/shader.ts +117 -0
  724. package/libraries/stickman/src/skeleton.ts +197 -0
  725. package/libraries/stickman/src/stickman-pass.ts +255 -0
  726. package/libraries/stickman/src/thick-line.ts +123 -0
  727. package/libraries/surface-nets/README.md +25 -0
  728. package/libraries/surface-nets/src/chunked-field.ts +14 -0
  729. package/libraries/surface-nets/src/deformation.ts +45 -0
  730. package/libraries/surface-nets/src/index.ts +36 -0
  731. package/libraries/surface-nets/src/library.ts +59 -0
  732. package/libraries/surface-nets/src/lod.ts +7 -0
  733. package/libraries/surface-nets/src/sab.ts +4 -0
  734. package/libraries/surface-nets/src/streaming-config.ts +5 -0
  735. package/libraries/surface-nets/src/streaming-manager.ts +15 -0
  736. package/libraries/surface-nets/src/surface-nets.ts +334 -0
  737. package/libraries/surface-nets/src/types.ts +23 -0
  738. package/libraries/water/README.md +25 -0
  739. package/libraries/water/src/buoyancy.ts +103 -0
  740. package/libraries/water/src/index.ts +43 -0
  741. package/libraries/water/src/library.ts +85 -0
  742. package/libraries/water/src/shore-damping.ts +93 -0
  743. package/libraries/water/src/water-buffer.ts +89 -0
  744. package/libraries/water/src/water-chunks.ts +13 -0
  745. package/libraries/water/src/water-physics.ts +376 -0
  746. package/libraries/water/src/water-renderer.ts +809 -0
  747. package/libraries/water/src/water-sab.ts +161 -0
  748. package/libraries/water/src/wave-sources.ts +84 -0
  749. package/libraries/weather/README.md +20 -0
  750. package/libraries/weather/src/index.ts +9 -0
  751. package/libraries/weather/src/library.ts +72 -0
  752. package/libraries/weather/src/types.ts +64 -0
  753. package/libraries/weather/src/weather-blend.ts +112 -0
  754. package/libraries/weather/src/weather-system.ts +271 -0
  755. package/libraries/weatherfx/README.md +24 -0
  756. package/libraries/weatherfx/src/cloud-provider.ts +31 -0
  757. package/libraries/weatherfx/src/cloud-system.ts +372 -0
  758. package/libraries/weatherfx/src/index.ts +9 -0
  759. package/libraries/weatherfx/src/library.ts +58 -0
  760. package/libraries/weatherfx/src/particle-system.ts +434 -0
  761. package/libraries/weatherfx/src/shaders/cloud.wgsl +111 -0
  762. package/libraries/weatherfx/src/shaders/particle-compute.wgsl +187 -0
  763. package/libraries/weatherfx/src/shaders/particle-render.wgsl +95 -0
  764. package/lint-plugin.mjs +41 -0
  765. package/mcp/src/client.ts +703 -0
  766. package/mcp/src/engine-context.ts +186 -0
  767. package/mcp/src/http-transport.ts +471 -0
  768. package/mcp/src/index.ts +13 -0
  769. package/mcp/src/prompts/add-entity.ts +42 -0
  770. package/mcp/src/prompts/create-scene.ts +44 -0
  771. package/mcp/src/prompts/debug-frame.ts +39 -0
  772. package/mcp/src/prompts/index.ts +12 -0
  773. package/mcp/src/resources/asset-list.ts +32 -0
  774. package/mcp/src/resources/checkpoint-list.ts +33 -0
  775. package/mcp/src/resources/entity-state.ts +55 -0
  776. package/mcp/src/resources/gpu-info.ts +103 -0
  777. package/mcp/src/resources/index.ts +57 -0
  778. package/mcp/src/resources/performance.ts +37 -0
  779. package/mcp/src/resources/scene-tree.ts +43 -0
  780. package/mcp/src/server.ts +375 -0
  781. package/mcp/src/tools/animation.ts +118 -0
  782. package/mcp/src/tools/asset.ts +161 -0
  783. package/mcp/src/tools/audio.ts +96 -0
  784. package/mcp/src/tools/build.ts +143 -0
  785. package/mcp/src/tools/camera.ts +167 -0
  786. package/mcp/src/tools/checkpoint.ts +100 -0
  787. package/mcp/src/tools/component.ts +199 -0
  788. package/mcp/src/tools/debug.ts +300 -0
  789. package/mcp/src/tools/entity.ts +361 -0
  790. package/mcp/src/tools/inspect.ts +210 -0
  791. package/mcp/src/tools/lighting.ts +263 -0
  792. package/mcp/src/tools/material.ts +243 -0
  793. package/mcp/src/tools/mesh.ts +239 -0
  794. package/mcp/src/tools/physics.ts +266 -0
  795. package/mcp/src/tools/scene.ts +153 -0
  796. package/mcp/src/tools/script.ts +122 -0
  797. package/mcp/src/types.ts +92 -0
  798. package/mcp/src/undo-redo.ts +77 -0
  799. package/modules/camera-controls/README.md +20 -0
  800. package/modules/camera-controls/src/index.ts +161 -0
  801. package/modules/controller-ui/src/index.ts +164 -0
  802. package/modules/devtools/README.md +25 -0
  803. package/modules/devtools/src/api.ts +461 -0
  804. package/modules/devtools/src/asset-utils.ts +105 -0
  805. package/modules/devtools/src/bind-debug-store.ts +48 -0
  806. package/modules/devtools/src/data-bridge.ts +286 -0
  807. package/modules/devtools/src/debug-overlay.ts +238 -0
  808. package/modules/devtools/src/debug-raycast.ts +328 -0
  809. package/modules/devtools/src/debug-store.ts +92 -0
  810. package/modules/devtools/src/debug-view-descriptors.ts +59 -0
  811. package/modules/devtools/src/doctor-panel.ts +182 -0
  812. package/modules/devtools/src/gizmo-module.ts +136 -0
  813. package/modules/devtools/src/graph-model.ts +509 -0
  814. package/modules/devtools/src/grid-renderer.ts +157 -0
  815. package/modules/devtools/src/height-ruler-renderer.ts +216 -0
  816. package/modules/devtools/src/index.ts +81 -0
  817. package/modules/devtools/src/init.ts +261 -0
  818. package/modules/devtools/src/label-overlay.ts +154 -0
  819. package/modules/devtools/src/material-stats-panel.ts +188 -0
  820. package/modules/devtools/src/profiling-bridge.ts +377 -0
  821. package/modules/devtools/src/profiling-hooks.ts +98 -0
  822. package/modules/devtools/src/renderer-adapter.ts +76 -0
  823. package/modules/devtools/src/scene-inspector.ts +637 -0
  824. package/modules/devtools/src/scene-store.ts +320 -0
  825. package/modules/devtools/src/scene-sync.ts +116 -0
  826. package/modules/devtools/src/shaders/transform-gizmo.wgsl +38 -0
  827. package/modules/devtools/src/sim-stats-panel.ts +272 -0
  828. package/modules/devtools/src/sim-stats-provider.ts +105 -0
  829. package/modules/devtools/src/skeleton-renderer.ts +351 -0
  830. package/modules/devtools/src/transform-gizmo.ts +928 -0
  831. package/modules/devtools/src/types.ts +290 -0
  832. package/modules/devtools/src/worker-expose.ts +67 -0
  833. package/modules/devtools/src/worker-sync.ts +137 -0
  834. package/modules/editor/src/commands/component.ts +156 -0
  835. package/modules/editor/src/commands/entity.ts +287 -0
  836. package/modules/editor/src/commands/hierarchy.ts +83 -0
  837. package/modules/editor/src/commands/index.ts +61 -0
  838. package/modules/editor/src/commands/prefab.ts +163 -0
  839. package/modules/editor/src/commands/registry.ts +331 -0
  840. package/modules/editor/src/commands/scene-io.ts +211 -0
  841. package/modules/editor/src/commands/selection.ts +73 -0
  842. package/modules/editor/src/commands/transform.ts +91 -0
  843. package/modules/editor/src/ddscene.ts +267 -0
  844. package/modules/editor/src/document.ts +123 -0
  845. package/modules/editor/src/editor-context.ts +172 -0
  846. package/modules/editor/src/index.ts +41 -0
  847. package/modules/editor/src/mcp.ts +103 -0
  848. package/modules/editor/src/module.ts +148 -0
  849. package/modules/editor/src/selection.ts +70 -0
  850. package/modules/editor/src/shell.ts +456 -0
  851. package/modules/editor/src/viewport.ts +199 -0
  852. package/modules/html-ui/src/composite.ts +135 -0
  853. package/modules/html-ui/src/create-html-ui.ts +188 -0
  854. package/modules/html-ui/src/doc-backend.ts +343 -0
  855. package/modules/html-ui/src/host.ts +646 -0
  856. package/modules/html-ui/src/index.ts +13 -0
  857. package/modules/html-ui/src/jsx-dev-runtime.ts +12 -0
  858. package/modules/html-ui/src/jsx-runtime.ts +126 -0
  859. package/modules/html-ui/src/protocol.ts +88 -0
  860. package/modules/html-ui/src/ui-worker.ts +16 -0
  861. package/modules/mcp/README.md +18 -0
  862. package/modules/mcp/src/index.ts +27 -0
  863. package/modules/movement-2d/README.md +19 -0
  864. package/modules/movement-2d/src/grid-character.ts +455 -0
  865. package/modules/movement-2d/src/index.ts +45 -0
  866. package/modules/movement-3d/README.md +21 -0
  867. package/modules/movement-3d/src/character-motor.ts +230 -0
  868. package/modules/movement-3d/src/index.ts +46 -0
  869. package/modules/native-osr/src/index.ts +22 -0
  870. package/modules/native-osr/src/input-router.ts +401 -0
  871. package/modules/native-osr/src/native-osr-manager.ts +247 -0
  872. package/modules/native-osr/src/native-texture-source.ts +86 -0
  873. package/modules/native-osr/src/types.ts +112 -0
  874. package/modules/native-osr/src/world-space-ui-pass.ts +351 -0
  875. package/modules/sailing/README.md +18 -0
  876. package/modules/sailing/src/index.ts +51 -0
  877. package/modules/terrain/README.md +18 -0
  878. package/modules/terrain/src/index.ts +78 -0
  879. package/modules/vitals/README.md +17 -0
  880. package/modules/vitals/src/index.ts +43 -0
  881. package/modules/vitals/src/vitals.ts +265 -0
  882. package/modules/xr/README.md +25 -0
  883. package/modules/xr/src/camera-rig.ts +146 -0
  884. package/modules/xr/src/debug.ts +59 -0
  885. package/modules/xr/src/frame-loop.ts +197 -0
  886. package/modules/xr/src/index.ts +16 -0
  887. package/modules/xr/src/input.ts +208 -0
  888. package/modules/xr/src/layer.ts +140 -0
  889. package/modules/xr/src/session.ts +156 -0
  890. package/modules/xr/src/sim-module.ts +23 -0
  891. package/modules/xr/src/types.ts +32 -0
  892. package/modules/xr/src/webxr.d.ts +182 -0
  893. package/modules/xr/src/xr-module.ts +179 -0
  894. package/package.json +232 -3
  895. package/shader-graph/src/chunks.ts +349 -0
  896. package/shader-graph/src/compiler.ts +732 -0
  897. package/shader-graph/src/compute-compiler.ts +453 -0
  898. package/shader-graph/src/compute-graph.ts +112 -0
  899. package/shader-graph/src/compute-profiles.ts +54 -0
  900. package/shader-graph/src/graph.ts +52 -0
  901. package/shader-graph/src/index.ts +53 -0
  902. package/shader-graph/src/profiles.ts +407 -0
  903. package/shader-graph/src/validator.ts +48 -0
  904. package/shader-graph/src/wgsl-binding-validator.ts +143 -0
  905. package/shader-graph/src/wgsl-struct-validator.ts +245 -0
  906. package/shader-graph/src/wgsl-struct.ts +397 -0
  907. package/test/src/index.ts +7 -0
  908. package/test/src/vision-test.ts +369 -0
@@ -0,0 +1,949 @@
1
+ // ============================================================================
2
+ // SoftwareThumbnailRenderer — native counterpart to ThumbnailRenderer.
3
+ //
4
+ // The browser path rasterizes turntable thumbnails with a WebGL2
5
+ // OffscreenCanvas in a worker. Native has no WebGL2, so this
6
+ // renderer rasterizes the same models on the CPU: positions + flat normals
7
+ // through a lookAt+perspective transform into a z-buffered Uint8Array, then
8
+ // putImageData into an OffscreenCanvas whose 2D context is the NativeCanvas2D
9
+ // shim. The scene composites it identically: ctx.drawImage(canvasElement).
10
+ //
11
+ // Shading mirrors THUMB_VS/THUMB_FS: two-sided lambert fill + subtle rim.
12
+ //
13
+ // `loadBytes` (optional) replaces fetch() for URI → bytes resolution, e.g.
14
+ // reading absolute filesystem paths through a game's `downdraft` bridge on
15
+ // native.
16
+ // ============================================================================
17
+
18
+ import { declaredCompanionUri, defaultCharacterMeshIndices, loadModel, PoseSampler, resolveImportSettings, type ModelData } from "@downdraft/engine/libraries/models";
19
+ import { createLogger } from "@downdraft/engine/util/logger";
20
+
21
+ const log = createLogger("info");
22
+
23
+ export interface SoftwareThumbnailOptions {
24
+ /** Resolve a modelUri to bytes. Default: fetch(uri).arrayBuffer(). */
25
+ loadBytes?: (uri: string) => Promise<ArrayBuffer>;
26
+ /** Resolve a material texture URI to bytes (e.g. sibling-file lookup
27
+ * relative to the model's directory). Omit to skip external textures. */
28
+ resolveTexture?: (modelUri: string, uri: string) => Promise<ArrayBuffer | null>;
29
+ }
30
+
31
+ interface SoftTex {
32
+ w: number;
33
+ h: number;
34
+ data: Uint8Array | Uint8ClampedArray; // RGBA
35
+ }
36
+
37
+ interface SoftMat {
38
+ baseColor: [number, number, number];
39
+ emissive: [number, number, number];
40
+ tex: SoftTex | null;
41
+ /** Decoding in flight — render untextured until it lands. */
42
+ texPending: boolean;
43
+ texOffset: [number, number];
44
+ texScale: [number, number];
45
+ }
46
+
47
+ interface SoftAnim {
48
+ /** Skeleton + clip sampling (null for static models). */
49
+ sampler: PoseSampler;
50
+ /** First playable clip index (as accepted by sampler.sample), or -1. */
51
+ clipIndex: number;
52
+ /** Duration of the previewed clip in seconds (0 = no playable clip). */
53
+ duration: number;
54
+ /** Per-frame skinned/scratch position buffer (xyz per vertex) — grown
55
+ * lazily on first use so skinned models without playable clips pay
56
+ * nothing. */
57
+ posedPos: Float32Array;
58
+ /** Per-frame normal buffer (xyz per vertex). */
59
+ posedNrm: Float32Array;
60
+ /** Skeleton-only models: fixed index topology for the bone octahedra. */
61
+ skelIndices: Uint32Array | null;
62
+ /** Skeleton-only models: bone world positions from the last pose (xyz/bone). */
63
+ bonePos: Float32Array | null;
64
+ }
65
+
66
+ interface SoftModel {
67
+ positions: Float32Array; // xyz per vertex (bind pose)
68
+ indices: Uint16Array | Uint32Array;
69
+ normals: Float32Array; // smooth vertex normals, xyz per vertex
70
+ uvs: Float32Array | null; // uv per vertex
71
+ vcols: Float32Array | null; // rgb per vertex
72
+ triMat: Uint16Array | null; // material index per triangle (null → mats[0])
73
+ mats: SoftMat[];
74
+ joints: Uint32Array | null; // 4 bone indices per vertex (skinned meshes)
75
+ weights: Float32Array | null; // 4 bone weights per vertex (all-zero = rigid)
76
+ /** True for animation-only files (no meshes) — bones are the content. */
77
+ skeletonOnly: boolean;
78
+ anim: SoftAnim | null;
79
+ /** Resolves when all async material texture decodes have settled — awaited
80
+ * by loadModelThumb so worker-rendered frames never bake untextured. */
81
+ texReady: Promise<void>;
82
+ center: [number, number, number];
83
+ radius: number;
84
+ }
85
+
86
+ function computeFlatNormals(positions: Float32Array, indices: Uint16Array | Uint32Array, out?: Float32Array): Float32Array {
87
+ const normals = out ?? new Float32Array(positions.length);
88
+ normals.fill(0);
89
+ for (let i = 0; i < indices.length; i += 3) {
90
+ const a = indices[i] * 3, b = indices[i + 1] * 3, c = indices[i + 2] * 3;
91
+ const ux = positions[b] - positions[a];
92
+ const uy = positions[b + 1] - positions[a + 1];
93
+ const uz = positions[b + 2] - positions[a + 2];
94
+ const vx = positions[c] - positions[a];
95
+ const vy = positions[c + 1] - positions[a + 1];
96
+ const vz = positions[c + 2] - positions[a + 2];
97
+ let nx = uy * vz - uz * vy;
98
+ let ny = uz * vx - ux * vz;
99
+ let nz = ux * vy - uy * vx;
100
+ const nl = Math.hypot(nx, ny, nz) || 1;
101
+ nx /= nl; ny /= nl; nz /= nl;
102
+ [a, b, c].forEach((j) => {
103
+ normals[j] += nx; normals[j + 1] += ny; normals[j + 2] += nz;
104
+ });
105
+ }
106
+ for (let i = 0; i < normals.length; i += 3) {
107
+ const nl = Math.hypot(normals[i], normals[i + 1], normals[i + 2]) || 1;
108
+ normals[i] /= nl; normals[i + 1] /= nl; normals[i + 2] /= nl;
109
+ }
110
+ return normals;
111
+ }
112
+
113
+ export class SoftwareThumbnailRenderer {
114
+ private canvas: OffscreenCanvas;
115
+ private ctx: OffscreenCanvasRenderingContext2D;
116
+ private size: number;
117
+ private pixels: Uint8ClampedArray;
118
+ private depth: Float32Array;
119
+ private loadBytes: (uri: string) => Promise<ArrayBuffer>;
120
+ private resolveTexture: ((modelUri: string, uri: string) => Promise<ArrayBuffer | null>) | null;
121
+
122
+ /** LRU cache: contentId → SoftModel. */
123
+ private cache = new Map<string, SoftModel>();
124
+ private loading = new Set<string>();
125
+ private maxCache = 512;
126
+
127
+ // Scratch transform buffers (reused per renderThumb call).
128
+ private sx: Float32Array;
129
+ private sy: Float32Array;
130
+ private sz: Float32Array;
131
+ private snx: Float32Array;
132
+ private sny: Float32Array;
133
+ private snz: Float32Array;
134
+
135
+ constructor(size = 192, opts?: SoftwareThumbnailOptions) {
136
+ this.size = size;
137
+ this.canvas = new OffscreenCanvas(size, size);
138
+ this.ctx = this.canvas.getContext("2d")!;
139
+ this.pixels = new Uint8ClampedArray(size * size * 4);
140
+ this.depth = new Float32Array(size * size);
141
+ this.loadBytes = opts?.loadBytes ?? (async (uri) => {
142
+ const resp = await fetch(uri);
143
+ if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
144
+ return resp.arrayBuffer();
145
+ });
146
+ this.resolveTexture = opts?.resolveTexture ?? null;
147
+ const INIT_VERTS = 1 << 16;
148
+ this.sx = new Float32Array(INIT_VERTS);
149
+ this.sy = new Float32Array(INIT_VERTS);
150
+ this.sz = new Float32Array(INIT_VERTS);
151
+ this.snx = new Float32Array(INIT_VERTS);
152
+ this.sny = new Float32Array(INIT_VERTS);
153
+ this.snz = new Float32Array(INIT_VERTS);
154
+ }
155
+
156
+ /** Grow the transform scratch buffers to fit `vcount` vertices. Called once
157
+ * per renderThumb — arrays double in place so a single oversized model
158
+ * doesn't wedge every subsequent card into the cube fallback. */
159
+ private ensureScratch(vcount: number): void {
160
+ if (vcount <= this.sx.length) return;
161
+ let cap = this.sx.length;
162
+ while (cap < vcount) cap <<= 1;
163
+ this.sx = new Float32Array(cap);
164
+ this.sy = new Float32Array(cap);
165
+ this.sz = new Float32Array(cap);
166
+ this.snx = new Float32Array(cap);
167
+ this.sny = new Float32Array(cap);
168
+ this.snz = new Float32Array(cap);
169
+ }
170
+
171
+ get canvasElement(): OffscreenCanvas { return this.canvas; }
172
+
173
+ has(contentId: string): boolean {
174
+ return this.cache.has(contentId) || this.loading.has(contentId);
175
+ }
176
+
177
+ async loadModelThumb(modelUri: string, contentId: string): Promise<void> {
178
+ if (this.cache.has(contentId) || this.loading.has(contentId)) return;
179
+ this.loading.add(contentId);
180
+ try {
181
+ const buffer = await this.loadBytes(modelUri);
182
+ const filename = modelUri.split("/").pop() ?? "model.glb";
183
+ // Companion files — .mtl for OBJ (carries material colors + texture
184
+ // URIs) and .bin for text .gltf. Resolved through the same sibling
185
+ // mechanism as textures so filesystem-backed libraries work natively.
186
+ const ext = filename.split(".").pop()?.toLowerCase();
187
+ let mtl: ArrayBuffer | null = null, bin: ArrayBuffer | null = null;
188
+ if (this.resolveTexture) {
189
+ const stem = filename.replace(/\.[^.]+$/, "");
190
+ const declared = declaredCompanionUri(buffer, ext ?? "");
191
+ if (ext === "obj") {
192
+ mtl = (declared ? await this.resolveTexture(modelUri, decodeURIComponent(declared)) : null)
193
+ ?? await this.resolveTexture(modelUri, `${stem}.mtl`);
194
+ } else if (ext === "gltf") {
195
+ bin = (declared ? await this.resolveTexture(modelUri, decodeURIComponent(declared)) : null)
196
+ ?? await this.resolveTexture(modelUri, `${stem}.bin`);
197
+ }
198
+ }
199
+ const model = await loadModel(buffer, filename, mtl, bin, {
200
+ // Apply the same sidecar import settings (.ddmeta.json/.meta/.import)
201
+ // the game's loader uses — thumbnails should match what Load shows.
202
+ // The resolver's relative URIs route through the sibling-resolution
203
+ // callback, wrapped in a minimal Response shim.
204
+ sidecarResolver: this.resolveTexture
205
+ ? (_name, md) => resolveImportSettings({
206
+ modelPath: filename,
207
+ modelData: md,
208
+ fetchFn: async (uri) => {
209
+ const bytes = await this.resolveTexture!(modelUri, decodeURIComponent(uri));
210
+ if (!bytes) {
211
+ return { ok: false, status: 404, text: async () => "", arrayBuffer: async () => new ArrayBuffer(0) } as Response;
212
+ }
213
+ return {
214
+ ok: true, status: 200,
215
+ text: async () => new TextDecoder().decode(bytes),
216
+ arrayBuffer: async () => bytes,
217
+ } as Response;
218
+ },
219
+ })
220
+ : undefined,
221
+ }) as ModelData;
222
+ this.uploadModel(contentId, model, modelUri);
223
+ // Texture decodes run async — wait for them so the first rendered frame
224
+ // already has its materials (the worker snapshots PNGs immediately).
225
+ await this.cache.get(contentId)?.texReady;
226
+ } catch (err) {
227
+ log.warn("SoftwareThumbnailRenderer", `Failed to load ${contentId} (${modelUri}): ${err}`);
228
+ this.loadBuiltinCube(contentId);
229
+ } finally {
230
+ this.loading.delete(contentId);
231
+ }
232
+ }
233
+
234
+ loadBuiltinCube(contentId: string): void {
235
+ if (this.cache.has(contentId)) return;
236
+ const positions = new Float32Array([
237
+ -0.5, -0.5, -0.5, 0.5, -0.5, -0.5, 0.5, 0.5, -0.5, -0.5, 0.5, -0.5,
238
+ -0.5, -0.5, 0.5, 0.5, -0.5, 0.5, 0.5, 0.5, 0.5, -0.5, 0.5, 0.5,
239
+ ]);
240
+ const indices = new Uint16Array([
241
+ 0, 1, 2, 0, 2, 3, 4, 6, 5, 4, 7, 6,
242
+ 0, 3, 7, 0, 7, 4, 1, 5, 6, 1, 6, 2,
243
+ 3, 2, 6, 3, 6, 7, 0, 4, 5, 0, 5, 1,
244
+ ]);
245
+ this.uploadGeometry(contentId, positions, indices, { center: [0, 0, 0], radius: Math.sqrt(3) / 2 });
246
+ }
247
+
248
+ loadBuiltinSphere(contentId: string): void {
249
+ if (this.cache.has(contentId)) return;
250
+ const lat = 12, lon = 16;
251
+ const positions: number[] = [];
252
+ const indices: number[] = [];
253
+ for (let i = 0; i <= lat; i++) {
254
+ const theta = (i / lat) * Math.PI;
255
+ const st = Math.sin(theta), ct = Math.cos(theta);
256
+ for (let j = 0; j <= lon; j++) {
257
+ const phi = (j / lon) * 2 * Math.PI;
258
+ positions.push(st * Math.cos(phi) * 0.5, ct * 0.5, st * Math.sin(phi) * 0.5);
259
+ }
260
+ }
261
+ for (let i = 0; i < lat; i++) {
262
+ for (let j = 0; j < lon; j++) {
263
+ const a = i * (lon + 1) + j, b = a + lon + 1;
264
+ indices.push(a, b, a + 1, b, b + 1, a + 1);
265
+ }
266
+ }
267
+ this.uploadGeometry(contentId, new Float32Array(positions), new Uint16Array(indices), { center: [0, 0, 0], radius: 0.5 });
268
+ }
269
+
270
+ /**
271
+ * Upload an already-parsed model — used when the game's own loader applies
272
+ * accessory/variant filtering that raw loadModel() skips (unfiltered
273
+ * player FBXs draw every variant superimposed).
274
+ */
275
+ uploadModelData(contentId: string, model: ModelData): void {
276
+ this.uploadModel(contentId, model);
277
+ }
278
+
279
+ private uploadModel(contentId: string, model: ModelData, modelUri?: string): void {
280
+ // Rigged character packs ship every outfit/body variant as its own mesh
281
+ // node (ash_torso, ash_torso.001…); drawing all of them superimposes
282
+ // hundreds of overlapping parts. Keep the first variant per name-prefix
283
+ // group — the same default outfit the model viewer loads with.
284
+ const sel = defaultCharacterMeshIndices(model);
285
+ const meshes = sel ? model.meshes.filter((_, i) => sel.has(i)) : model.meshes;
286
+ if (!meshes || meshes.length === 0) {
287
+ // Mesh-less files aren't necessarily empty — animation-only exports
288
+ // (e.g. Mixamo `Character@Action.fbx`) carry a synthesized skeleton the
289
+ // loader builds from the node tree. Show the bones, not a cube.
290
+ if (model.skin && model.skin.bones.length > 0) {
291
+ this.uploadSkeletonModel(contentId, model);
292
+ if (!this.cache.get(contentId)?.anim) {
293
+ // No usable skeleton after all — fall through to the cube.
294
+ this.loadBuiltinCube(contentId);
295
+ }
296
+ return;
297
+ }
298
+ this.loadBuiltinCube(contentId);
299
+ return;
300
+ }
301
+ let totalVerts = 0, totalIdx = 0;
302
+ meshes.forEach((m) => { totalVerts += m.vertexCount; totalIdx += m.indexCount; });
303
+ const positions = new Float32Array(totalVerts * 3);
304
+ const is32 = totalVerts > 65535;
305
+ const indices = is32 ? new Uint32Array(totalIdx) : new Uint16Array(totalIdx);
306
+ const multiMat = (model.materials?.length ?? 0) > 1
307
+ || meshes.some((m) => (m.materialIndex ?? 0) > 0);
308
+ const triMat = multiMat ? new Uint16Array(totalIdx / 3) : null;
309
+ const anyUvs = meshes.some((m) => !!m.uvs);
310
+ const anyCols = meshes.some((m) => !!m.colors);
311
+ const anySkin = meshes.some((m) => !!m.joints && !!m.weights);
312
+ const uvs = anyUvs ? new Float32Array(totalVerts * 2) : null;
313
+ const vcols = anyCols ? new Float32Array(totalVerts * 3).fill(1) : null;
314
+ const joints = anySkin ? new Uint32Array(totalVerts * 4) : null;
315
+ const weights = anySkin ? new Float32Array(totalVerts * 4) : null;
316
+ let vOff = 0, iOff = 0, vBase = 0;
317
+ meshes.forEach((m) => {
318
+ const stride = m.vertices.length / m.vertexCount;
319
+ for (let v = 0; v < m.vertexCount; v++) {
320
+ positions[vOff + v * 3] = m.vertices[v * stride];
321
+ positions[vOff + v * 3 + 1] = m.vertices[v * stride + 1];
322
+ positions[vOff + v * 3 + 2] = m.vertices[v * stride + 2];
323
+ }
324
+ if (uvs) {
325
+ if (m.uvs) uvs.set(m.uvs.subarray(0, m.vertexCount * 2), vOff / 3 * 2);
326
+ // missing uvs leave (0,0) — samples the texture's corner; acceptable
327
+ }
328
+ if (vcols && m.colors) {
329
+ const cstride = m.colors.length / m.vertexCount;
330
+ for (let v = 0; v < m.vertexCount; v++) {
331
+ vcols[vOff + v * 3] = m.colors[v * cstride];
332
+ vcols[vOff + v * 3 + 1] = m.colors[v * cstride + 1];
333
+ vcols[vOff + v * 3 + 2] = m.colors[v * cstride + 2];
334
+ }
335
+ }
336
+ if (joints && weights) {
337
+ // Bone indices are into the shared skeleton — no vBase rebase.
338
+ // Rigid (unskinned) vertices keep all-zero weights → bind pose.
339
+ if (m.joints && m.weights) {
340
+ for (let v = 0; v < m.vertexCount; v++) {
341
+ const j4 = v * 4, o4 = vOff / 3 * 4 + j4;
342
+ joints[o4] = m.joints[j4];
343
+ joints[o4 + 1] = m.joints[j4 + 1];
344
+ joints[o4 + 2] = m.joints[j4 + 2];
345
+ joints[o4 + 3] = m.joints[j4 + 3];
346
+ weights[o4] = m.weights[j4];
347
+ weights[o4 + 1] = m.weights[j4 + 1];
348
+ weights[o4 + 2] = m.weights[j4 + 2];
349
+ weights[o4 + 3] = m.weights[j4 + 3];
350
+ }
351
+ }
352
+ }
353
+ vOff += m.vertexCount * 3;
354
+ const src = m.indices;
355
+ const mi = m.materialIndex ?? 0;
356
+ for (let i = 0; i < m.indexCount; i++) (indices as any)[iOff + i] = src[i] + vBase;
357
+ if (triMat) triMat.fill(mi, iOff / 3, (iOff + m.indexCount) / 3);
358
+ iOff += m.indexCount;
359
+ vBase += m.vertexCount;
360
+ });
361
+ let center: [number, number, number] = [0, 0, 0];
362
+ let radius = 0.5;
363
+ // model.bounds covers every variant — when a part selection trimmed the
364
+ // mesh list, recompute framing from the merged vertices instead.
365
+ if (model.bounds && !sel) {
366
+ const b = model.bounds;
367
+ center = [(b.min[0] + b.max[0]) / 2, (b.min[1] + b.max[1]) / 2, (b.min[2] + b.max[2]) / 2];
368
+ radius = Math.max(
369
+ Math.hypot(b.max[0] - center[0], b.max[1] - center[1], b.max[2] - center[2]),
370
+ Math.hypot(b.min[0] - center[0], b.min[1] - center[1], b.min[2] - center[2]),
371
+ );
372
+ } else {
373
+ let min = [Infinity, Infinity, Infinity], max = [-Infinity, -Infinity, -Infinity];
374
+ for (let i = 0; i < positions.length; i += 3) {
375
+ for (let k = 0; k < 3; k++) {
376
+ if (positions[i + k] < min[k]) min[k] = positions[i + k];
377
+ if (positions[i + k] > max[k]) max[k] = positions[i + k];
378
+ }
379
+ }
380
+ center = [(min[0] + max[0]) / 2, (min[1] + max[1]) / 2, (min[2] + max[2]) / 2];
381
+ radius = Math.max(
382
+ Math.hypot(max[0] - center[0], max[1] - center[1], max[2] - center[2]),
383
+ Math.hypot(min[0] - center[0], min[1] - center[1], min[2] - center[2]),
384
+ );
385
+ }
386
+ if (radius <= 0) radius = 0.5;
387
+
388
+ // Materials: per-mesh baseColor/emissive + async texture decode.
389
+ const decodes: Promise<void>[] = [];
390
+ const mats: SoftMat[] = (model.materials ?? []).map((mat) => {
391
+ const sm: SoftMat = {
392
+ baseColor: [mat.baseColor[0], mat.baseColor[1], mat.baseColor[2]],
393
+ emissive: mat.emissiveColor ? [...mat.emissiveColor] : [0, 0, 0],
394
+ tex: null,
395
+ texPending: !!(mat.textureData || mat.textureUri),
396
+ texOffset: mat.textureTransform?.offset ?? [0, 0],
397
+ texScale: mat.textureTransform?.scale ?? [1, 1],
398
+ };
399
+ if (sm.texPending) {
400
+ decodes.push(this.decodeMaterialTexture(mat.textureData, mat.textureUri, modelUri)
401
+ .then((tex) => { sm.tex = tex; sm.texPending = false; })
402
+ .catch(() => { sm.texPending = false; }));
403
+ }
404
+ return sm;
405
+ });
406
+ if (mats.length === 0) {
407
+ mats.push({
408
+ baseColor: [0.78, 0.82, 0.88], emissive: [0, 0, 0],
409
+ tex: null, texPending: false, texOffset: [0, 0], texScale: [1, 1],
410
+ });
411
+ }
412
+
413
+ const normals = computeFlatNormals(positions, indices);
414
+ if (this.cache.size >= this.maxCache) {
415
+ const oldest = this.cache.keys().next().value;
416
+ if (oldest) this.cache.delete(oldest);
417
+ }
418
+ this.cache.set(contentId, {
419
+ positions, indices, normals, uvs, vcols, triMat, mats,
420
+ joints, weights, skeletonOnly: false,
421
+ anim: this.buildAnim(contentId, model),
422
+ texReady: Promise.all(decodes).then(() => {}),
423
+ center, radius,
424
+ });
425
+ }
426
+
427
+ /**
428
+ * Animation-only model (no meshes): synthesize a bone-octahedron thumbnail
429
+ * source. Bounds come from the skeleton's bind world positions; per-frame
430
+ * geometry is generated by poseSkeleton() during renderThumb.
431
+ */
432
+ private uploadSkeletonModel(contentId: string, model: ModelData): void {
433
+ const anim = this.buildAnim(contentId, model);
434
+ if (!anim) return; // caller falls back to the cube
435
+ let center: [number, number, number] = [0, 0, 0];
436
+ let radius = 0.5;
437
+ {
438
+ // Bind-pose bone world positions define the framing.
439
+ anim.sampler.sample(null);
440
+ const T = anim.sampler.normalizationMatrix;
441
+ const world = anim.sampler.getBoneWorldMatrices();
442
+ let min = [Infinity, Infinity, Infinity], max = [-Infinity, -Infinity, -Infinity];
443
+ for (let i = 0; i < world.length; i++) {
444
+ const w = world[i];
445
+ let px = w[12], py = w[13], pz = w[14];
446
+ if (T) {
447
+ const nx = T[0] * px + T[4] * py + T[8] * pz + T[12];
448
+ const ny = T[1] * px + T[5] * py + T[9] * pz + T[13];
449
+ const nz = T[2] * px + T[6] * py + T[10] * pz + T[14];
450
+ px = nx; py = ny; pz = nz;
451
+ }
452
+ for (let k = 0; k < 3; k++) {
453
+ const v = k === 0 ? px : k === 1 ? py : pz;
454
+ if (v < min[k]) min[k] = v;
455
+ if (v > max[k]) max[k] = v;
456
+ }
457
+ }
458
+ if (min[0] !== Infinity) {
459
+ center = [(min[0] + max[0]) / 2, (min[1] + max[1]) / 2, (min[2] + max[2]) / 2];
460
+ radius = Math.max(
461
+ Math.hypot(max[0] - center[0], max[1] - center[1], max[2] - center[2]),
462
+ Math.hypot(min[0] - center[0], min[1] - center[1], min[2] - center[2]),
463
+ 0.05,
464
+ );
465
+ }
466
+ }
467
+ if (this.cache.size >= this.maxCache) {
468
+ const oldest = this.cache.keys().next().value;
469
+ if (oldest) this.cache.delete(oldest);
470
+ }
471
+ this.cache.set(contentId, {
472
+ positions: new Float32Array(0), indices: new Uint16Array(0),
473
+ normals: new Float32Array(0),
474
+ uvs: null, vcols: null, triMat: null,
475
+ mats: [{ baseColor: [0.52, 0.8, 0.97], emissive: [0, 0, 0], tex: null, texPending: false, texOffset: [0, 0], texScale: [1, 1] }],
476
+ joints: null, weights: null,
477
+ skeletonOnly: true, anim, texReady: Promise.resolve(), center, radius,
478
+ });
479
+ }
480
+
481
+ /**
482
+ * Build the animation state for a model — a PoseSampler over its skin.
483
+ * Returns null when the model has no usable skeleton.
484
+ */
485
+ private buildAnim(contentId: string, model: ModelData): SoftAnim | null {
486
+ const skin = model.skin;
487
+ if (!skin || skin.bones.length === 0) return null;
488
+ try {
489
+ const sampler = new PoseSampler(skin, model.animations ?? []);
490
+ const clipIndex = sampler.firstClipIndex();
491
+ const duration = clipIndex >= 0 ? sampler.clips[clipIndex]!.duration : 0;
492
+ return {
493
+ sampler, clipIndex, duration,
494
+ // Grown lazily at first posed render.
495
+ posedPos: new Float32Array(0),
496
+ posedNrm: new Float32Array(0),
497
+ skelIndices: null, bonePos: null,
498
+ };
499
+ } catch (err) {
500
+ log.warn("SoftwareThumbnailRenderer", `Pose sampler failed for ${contentId}: ${err}`);
501
+ return null;
502
+ }
503
+ }
504
+
505
+ /** Duration of the model's preview animation in seconds (0 = static).
506
+ * Reports 0 when the clip can't affect the rendered geometry — mesh
507
+ * models without skinning data render identically at every time. */
508
+ thumbDuration(contentId: string): number {
509
+ const m = this.cache.get(contentId);
510
+ if (!m?.anim) return 0;
511
+ if (!m.skeletonOnly && (!m.joints || !m.weights)) return 0;
512
+ return m.anim.duration;
513
+ }
514
+
515
+ /**
516
+ * CPU linear-blend skinning: posed = Σ wᵢ · (skinMatrix[jᵢ] · bind).
517
+ * Vertices with all-zero weights (rigid meshes sharing a skinned model)
518
+ * keep their bind position. Writes anim.posedPos + anim.posedNrm.
519
+ */
520
+ private skinVertices(m: SoftModel): void {
521
+ const anim = m.anim!;
522
+ const mats = anim.sampler.skinMatrices; // already normalization-conjugated
523
+ const pos = m.positions;
524
+ if (anim.posedPos.length < pos.length) {
525
+ anim.posedPos = new Float32Array(pos.length);
526
+ anim.posedNrm = new Float32Array(pos.length);
527
+ }
528
+ const out = anim.posedPos;
529
+ const joints = m.joints!;
530
+ const weights = m.weights!;
531
+ const vcount = pos.length / 3;
532
+ for (let v = 0; v < vcount; v++) {
533
+ const i3 = v * 3, i4 = v * 4;
534
+ const x = pos[i3], y = pos[i3 + 1], z = pos[i3 + 2];
535
+ let ax = 0, ay = 0, az = 0, sum = 0;
536
+ for (let k = 0; k < 4; k++) {
537
+ const wk = weights[i4 + k];
538
+ if (wk <= 0) continue;
539
+ const M = joints[i4 + k] * 16;
540
+ ax += wk * (mats[M] * x + mats[M + 4] * y + mats[M + 8] * z + mats[M + 12]);
541
+ ay += wk * (mats[M + 1] * x + mats[M + 5] * y + mats[M + 9] * z + mats[M + 13]);
542
+ az += wk * (mats[M + 2] * x + mats[M + 6] * y + mats[M + 10] * z + mats[M + 14]);
543
+ sum += wk;
544
+ }
545
+ if (sum > 1e-6) {
546
+ const s = 1 / sum;
547
+ out[i3] = ax * s; out[i3 + 1] = ay * s; out[i3 + 2] = az * s;
548
+ } else {
549
+ out[i3] = x; out[i3 + 1] = y; out[i3 + 2] = z;
550
+ }
551
+ }
552
+ computeFlatNormals(out, m.indices, anim.posedNrm);
553
+ }
554
+
555
+ /**
556
+ * Generate bone-octahedron geometry for a skeleton-only model at the pose
557
+ * currently sampled in m.anim.sampler. Bone world positions (source space)
558
+ * are mapped through the normalization matrix into vertex space. Each bone
559
+ * is a tapered 8-triangle octahedron from its parent's joint to its own;
560
+ * roots and zero-length segments get a small joint marker instead.
561
+ */
562
+ private poseSkeleton(m: SoftModel): { pos: Float32Array; nrm: Float32Array; idx: Uint32Array } {
563
+ const anim = m.anim!;
564
+ const sampler = anim.sampler;
565
+ const bones = sampler.skeleton.data.bones;
566
+ const world = sampler.getBoneWorldMatrices();
567
+ const T = sampler.normalizationMatrix;
568
+ const n = bones.length;
569
+
570
+ if (!anim.bonePos || anim.bonePos.length < n * 3) anim.bonePos = new Float32Array(n * 3);
571
+ const bp = anim.bonePos;
572
+ for (let i = 0; i < n; i++) {
573
+ const w = world[i];
574
+ let x = w[12], y = w[13], z = w[14];
575
+ if (T) {
576
+ const nx = T[0] * x + T[4] * y + T[8] * z + T[12];
577
+ const ny = T[1] * x + T[5] * y + T[9] * z + T[13];
578
+ const nz = T[2] * x + T[6] * y + T[10] * z + T[14];
579
+ x = nx; y = ny; z = nz;
580
+ }
581
+ bp[i * 3] = x; bp[i * 3 + 1] = y; bp[i * 3 + 2] = z;
582
+ }
583
+
584
+ // Topology is constant per skeleton — 6 verts / 8 tris per bone.
585
+ if (!anim.skelIndices) {
586
+ const idx = new Uint32Array(n * 24);
587
+ for (let b = 0; b < n; b++) {
588
+ const vb = b * 6, ib = b * 24;
589
+ for (let k = 0; k < 4; k++) {
590
+ idx[ib + k * 3] = vb;
591
+ idx[ib + k * 3 + 1] = vb + 1 + k;
592
+ idx[ib + k * 3 + 2] = vb + 1 + ((k + 1) % 4);
593
+ idx[ib + 12 + k * 3] = vb + 5;
594
+ idx[ib + 12 + k * 3 + 1] = vb + 1 + ((k + 1) % 4);
595
+ idx[ib + 12 + k * 3 + 2] = vb + 1 + k;
596
+ }
597
+ }
598
+ anim.skelIndices = idx;
599
+ }
600
+
601
+ if (anim.posedPos.length < n * 18) {
602
+ anim.posedPos = new Float32Array(n * 18);
603
+ anim.posedNrm = new Float32Array(n * 18);
604
+ }
605
+ const posed = anim.posedPos;
606
+ const dotR = Math.max(0.02 * m.radius, 1e-4);
607
+ for (let b = 0; b < n; b++) {
608
+ const vb = b * 18;
609
+ const hx = bp[b * 3], hy = bp[b * 3 + 1], hz = bp[b * 3 + 2];
610
+ const p = bones[b].parentIndex;
611
+ let ux = 0, uy = 1, uz = 0, len = 0, tx = hx, ty = hy, tz = hz;
612
+ if (p >= 0) {
613
+ tx = bp[p * 3]; ty = bp[p * 3 + 1]; tz = bp[p * 3 + 2];
614
+ ux = hx - tx; uy = hy - ty; uz = hz - tz;
615
+ len = Math.hypot(ux, uy, uz);
616
+ }
617
+ if (p < 0 || len < m.radius * 0.005) {
618
+ // Root or degenerate segment → small octahedron joint marker.
619
+ const r = dotR;
620
+ posed[vb] = hx; posed[vb + 1] = hy - r; posed[vb + 2] = hz;
621
+ posed[vb + 3] = hx + r; posed[vb + 4] = hy; posed[vb + 5] = hz;
622
+ posed[vb + 6] = hx; posed[vb + 7] = hy; posed[vb + 8] = hz + r;
623
+ posed[vb + 9] = hx - r; posed[vb + 10] = hy; posed[vb + 11] = hz;
624
+ posed[vb + 12] = hx; posed[vb + 13] = hy; posed[vb + 14] = hz - r;
625
+ posed[vb + 15] = hx; posed[vb + 16] = hy + r; posed[vb + 17] = hz;
626
+ continue;
627
+ }
628
+ ux /= len; uy /= len; uz /= len;
629
+ // Orthonormal frame around the bone axis: v1 = normalize(u × a) where
630
+ // a = +Y unless the bone runs near-vertical (then a = +X).
631
+ let v1x: number, v1y: number, v1z: number;
632
+ if (Math.abs(uy) < 0.9) { v1x = -uz; v1y = 0; v1z = ux; }
633
+ else { v1x = 0; v1y = uz; v1z = -uy; }
634
+ const v1l = Math.hypot(v1x, v1y, v1z) || 1;
635
+ v1x /= v1l; v1y /= v1l; v1z /= v1l;
636
+ const v2x = uy * v1z - uz * v1y;
637
+ const v2y = uz * v1x - ux * v1z;
638
+ const v2z = ux * v1y - uy * v1x;
639
+ const r = len * 0.09;
640
+ const cx = tx + ux * len * 0.18, cy = ty + uy * len * 0.18, cz = tz + uz * len * 0.18;
641
+ // tail, ring ×4, head
642
+ posed[vb] = tx; posed[vb + 1] = ty; posed[vb + 2] = tz;
643
+ posed[vb + 3] = cx + v1x * r; posed[vb + 4] = cy + v1y * r; posed[vb + 5] = cz + v1z * r;
644
+ posed[vb + 6] = cx + v2x * r; posed[vb + 7] = cy + v2y * r; posed[vb + 8] = cz + v2z * r;
645
+ posed[vb + 9] = cx - v1x * r; posed[vb + 10] = cy - v1y * r; posed[vb + 11] = cz - v1z * r;
646
+ posed[vb + 12] = cx - v2x * r; posed[vb + 13] = cy - v2y * r; posed[vb + 14] = cz - v2z * r;
647
+ posed[vb + 15] = hx; posed[vb + 16] = hy; posed[vb + 17] = hz;
648
+ }
649
+ computeFlatNormals(posed, anim.skelIndices, anim.posedNrm);
650
+ return { pos: posed, nrm: anim.posedNrm, idx: anim.skelIndices };
651
+ }
652
+
653
+ /** Decode embedded bytes or a resolved external URI into an RGBA tex map.
654
+ * Downscales to <=256px — thumbnails sample at 96px, no need for more. */
655
+ private async decodeMaterialTexture(
656
+ textureData: ArrayBuffer | null | undefined,
657
+ textureUri: string | undefined,
658
+ modelUri: string | undefined,
659
+ ): Promise<SoftTex | null> {
660
+ let bytes: ArrayBuffer | null = textureData ?? null;
661
+ if (!bytes && textureUri && !textureUri.startsWith("data:") && modelUri && this.resolveTexture) {
662
+ bytes = await this.resolveTexture(modelUri, decodeURIComponent(textureUri));
663
+ // Basename fallback — FBX URIs frequently carry stale absolute paths.
664
+ if (!bytes) {
665
+ const base = decodeURIComponent(textureUri).split("/").pop() ?? "";
666
+ if (base) bytes = await this.resolveTexture(modelUri, base);
667
+ }
668
+ }
669
+ if (!bytes || bytes.byteLength <= 4) return null;
670
+ try {
671
+ const bmp = await createImageBitmap(bytes as any);
672
+ const anyBmp = bmp as any;
673
+ const MAX_TEX = 256;
674
+ const scale = Math.min(1, MAX_TEX / Math.max(bmp.width, bmp.height));
675
+ const w = Math.max(1, Math.round(bmp.width * scale));
676
+ const h = Math.max(1, Math.round(bmp.height * scale));
677
+ // Native fast path — raw pixels, no canvas readback.
678
+ if (scale >= 1 && typeof anyBmp.getPixelData === "function") {
679
+ return { w, h, data: anyBmp.getPixelData() };
680
+ }
681
+ const c = new OffscreenCanvas(w, h);
682
+ const cx = c.getContext("2d")!;
683
+ cx.drawImage(bmp as any, 0, 0, w, h);
684
+ const id = cx.getImageData(0, 0, w, h);
685
+ try { (bmp as any).close?.(); } catch {}
686
+ return { w, h, data: id.data };
687
+ } catch {
688
+ return null;
689
+ }
690
+ }
691
+
692
+ private uploadGeometry(
693
+ contentId: string,
694
+ positions: Float32Array,
695
+ indices: Uint16Array | Uint32Array,
696
+ bounds: { center: [number, number, number]; radius: number },
697
+ baseColor: [number, number, number] = [0.78, 0.82, 0.88],
698
+ ): void {
699
+ const normals = computeFlatNormals(positions, indices);
700
+ if (this.cache.size >= this.maxCache) {
701
+ const oldest = this.cache.keys().next().value;
702
+ if (oldest) this.cache.delete(oldest);
703
+ }
704
+ this.cache.set(contentId, {
705
+ positions, indices, normals,
706
+ uvs: null, vcols: null, triMat: null,
707
+ mats: [{
708
+ baseColor, emissive: [0, 0, 0], tex: null, texPending: false,
709
+ texOffset: [0, 0], texScale: [1, 1],
710
+ }],
711
+ joints: null, weights: null, skeletonOnly: false, anim: null,
712
+ texReady: Promise.resolve(),
713
+ center: bounds.center, radius: bounds.radius,
714
+ });
715
+ }
716
+
717
+ /**
718
+ * Rasterize one turntable frame for contentId into the canvas.
719
+ * `time` (seconds) samples the model's first embedded clip — skinned meshes
720
+ * are CPU-skinned per frame and skeleton-only models (animation-only files)
721
+ * draw their bones. Omit it for a static bind-pose frame.
722
+ * Returns false when the model isn't loaded yet.
723
+ */
724
+ renderThumb(contentId: string, angle: number, time?: number): boolean {
725
+ const m = this.cache.get(contentId);
726
+ if (!m) return false;
727
+ const size = this.size;
728
+ const px = this.pixels;
729
+ const depth = this.depth;
730
+
731
+ // Pick the geometry to draw: bind-pose mesh, CPU-skinned mesh at `time`,
732
+ // or generated bone octahedra for skeleton-only models.
733
+ let positions = m.positions;
734
+ let normals = m.normals;
735
+ let indices: Uint16Array | Uint32Array = m.indices;
736
+ let uvs = m.uvs, vcols = m.vcols, triMat = m.triMat;
737
+ if (m.anim) {
738
+ if (m.skeletonOnly) {
739
+ // Bones are the content — pose even for a static frame.
740
+ m.anim.sampler.sample(m.anim.clipIndex >= 0 ? m.anim.clipIndex : null, time ?? 0);
741
+ const g = this.poseSkeleton(m);
742
+ positions = g.pos; normals = g.nrm; indices = g.idx;
743
+ uvs = null; vcols = null; triMat = null;
744
+ } else if (time !== undefined && m.joints && m.weights && m.anim.clipIndex >= 0) {
745
+ m.anim.sampler.sample(m.anim.clipIndex, time);
746
+ this.skinVertices(m);
747
+ positions = m.anim.posedPos; normals = m.anim.posedNrm;
748
+ }
749
+ }
750
+
751
+ // Clear to the same bg the WebGL version uses: rgba(0.16, 0.17, 0.22, 1).
752
+ for (let i = 0; i < px.length; i += 4) {
753
+ px[i] = 41; px[i + 1] = 43; px[i + 2] = 56; px[i + 3] = 255;
754
+ }
755
+ depth.fill(Infinity);
756
+
757
+ // Camera: fixed position looking at the model center (matches renderThumb).
758
+ const radius = Math.max(m.radius, 0.1);
759
+ const dist = radius * 3.0;
760
+ const ex = m.center[0];
761
+ const ey = m.center[1] + m.radius * 0.4;
762
+ const ez = m.center[2] + dist;
763
+ const near = dist * 0.01;
764
+ const far = dist * 10;
765
+
766
+ // View basis (lookAt: eye→center, up +Y)
767
+ let zx = ex - m.center[0], zy = ey - m.center[1], zz = ez - m.center[2];
768
+ let zl = Math.hypot(zx, zy, zz) || 1; zx /= zl; zy /= zl; zz /= zl;
769
+ // x = normalize(up × z) = normalize(zz, 0, -zx); y = z × x
770
+ let xx = zz, xy = 0, xz = -zx;
771
+ const xl = Math.hypot(xx, xy, xz) || 1; xx /= xl; xy /= xl; xz /= xl;
772
+ const yx = zy * xz - zz * xy, yy = zz * xx - zx * xz, yz = zx * xy - zy * xx;
773
+
774
+ const f = 1 / Math.tan((45 * Math.PI / 180) / 2);
775
+ const nf = 1 / (near - far);
776
+ const rotC = Math.cos(angle), rotS = Math.sin(angle);
777
+
778
+ const vcount = positions.length / 3;
779
+ this.ensureScratch(vcount);
780
+ const sx = this.sx, sy = this.sy, sz = this.sz;
781
+ const snx = this.snx, sny = this.sny, snz = this.snz;
782
+ const cx = m.center[0], cy = m.center[1], cz = m.center[2];
783
+
784
+ for (let v = 0; v < vcount; v++) {
785
+ const i3 = v * 3;
786
+ // model = rotY(angle): rotate around the model's bounding-sphere center
787
+ // so off-center models still rotate in place.
788
+ const lx = positions[i3] - cx, ly = positions[i3 + 1] - cy, lz = positions[i3 + 2] - cz;
789
+ const mx = rotC * lx - rotS * lz + cx;
790
+ const my = ly + cy;
791
+ const mz = rotS * lx + rotC * lz + cz;
792
+ // view = lookAt(eye, center): v' = (x·(p-e), y·(p-e), z·(p-e))
793
+ const dx = mx - ex, dy = my - ey, dz = mz - ez;
794
+ const vx = xx * dx + xy * dy + xz * dz;
795
+ const vy = yx * dx + yy * dy + yz * dz;
796
+ const vz = zx * dx + zy * dy + zz * dz; // negative in front of camera
797
+ // perspective projection → NDC → screen
798
+ const w = -vz;
799
+ if (w > 0.0001) {
800
+ const ndcX = (f * vx) / w; // aspect = 1
801
+ const ndcY = (f * vy) / w;
802
+ const ndcZ = ((far + near) * nf * vz + 2 * far * near * nf) / w;
803
+ sx[v] = (ndcX * 0.5 + 0.5) * size;
804
+ sy[v] = (0.5 - ndcY * 0.5) * size; // NDC y-up → screen y-down
805
+ sz[v] = ndcZ;
806
+ } else {
807
+ sx[v] = NaN; // clipped — any tri touching this vertex is skipped
808
+ }
809
+ // rotate the normal by the model rotation (view-space lighting isn't
810
+ // needed — the shader lights in model space relative to camera fwd).
811
+ const nx = normals[i3], ny = normals[i3 + 1], nz = normals[i3 + 2];
812
+ snx[v] = rotC * nx - rotS * nz;
813
+ sny[v] = ny;
814
+ snz[v] = rotS * nx + rotC * nz;
815
+ }
816
+
817
+ // Lighting: hemisphere ambient (brighter from above) + key + weak fill.
818
+ // Per-pixel interpolated normals → smooth shading instead of flat facets.
819
+ const kl = Math.hypot(0.55, 0.75, 0.65);
820
+ const Kx = 0.55 / kl, Ky = 0.75 / kl, Kz = 0.65 / kl;
821
+ const fl2 = Math.hypot(-0.5, 0.1, 0.55);
822
+ const Fx = -0.5 / fl2, Fy = 0.1 / fl2, Fz = 0.55 / fl2;
823
+ const mats = m.mats;
824
+
825
+ for (let t = 0; t < indices.length; t += 3) {
826
+ const a = indices[t], b = indices[t + 1], c = indices[t + 2];
827
+ const ax = sx[a], ay = sy[a], az = sz[a];
828
+ const bx = sx[b], by = sy[b], bz = sz[b];
829
+ const cx2 = sx[c], cy2 = sy[c], cz2 = sz[c];
830
+ if (Number.isNaN(ax) || Number.isNaN(bx) || Number.isNaN(cx2)) continue;
831
+ // Depth clip: WebGL clips at ndc z in [-1,1]; approximate by dropping
832
+ // tris entirely behind the near plane.
833
+ if (az < -1 && bz < -1 && cz2 < -1) continue;
834
+ if (az > 1 && bz > 1 && cz2 > 1) continue;
835
+
836
+ // Two-sided: flip shading normal by screen-space winding (same signal
837
+ // the GLSL uses via gl_FrontFacing). Screen y is flipped vs NDC, so a
838
+ // front-facing (CCW in NDC) tri has negative signed area here — keep
839
+ // its authored normal; only back faces get flipped toward the camera.
840
+ const winding = (bx - ax) * (cy2 - ay) - (cx2 - ax) * (by - ay);
841
+ const nSign = winding < 0 ? 1 : -1;
842
+
843
+ const mat = mats[triMat ? triMat[t / 3] : 0] ?? mats[0];
844
+ const br = mat.baseColor[0], bg = mat.baseColor[1], bb = mat.baseColor[2];
845
+ const er = mat.emissive[0], eg = mat.emissive[1], eb = mat.emissive[2];
846
+ const tex = mat.tex;
847
+ const to0 = mat.texOffset[0], to1 = mat.texOffset[1];
848
+ const ts0 = mat.texScale[0], ts1 = mat.texScale[1];
849
+ const useTex = !!(tex && uvs);
850
+
851
+ // Per-vertex shading inputs.
852
+ const nax = snx[a] * nSign, nay = sny[a] * nSign, naz = snz[a] * nSign;
853
+ const nbx = snx[b] * nSign, nby = sny[b] * nSign, nbz = snz[b] * nSign;
854
+ const ncx = snx[c] * nSign, ncy = sny[c] * nSign, ncz = snz[c] * nSign;
855
+ const ua = uvs ? uvs[a * 2] * ts0 + to0 : 0, va = uvs ? uvs[a * 2 + 1] * ts1 + to1 : 0;
856
+ const ub = uvs ? uvs[b * 2] * ts0 + to0 : 0, vb = uvs ? uvs[b * 2 + 1] * ts1 + to1 : 0;
857
+ const uc = uvs ? uvs[c * 2] * ts0 + to0 : 0, vc = uvs ? uvs[c * 2 + 1] * ts1 + to1 : 0;
858
+ const cra = vcols ? vcols[a * 3] : 1, cga = vcols ? vcols[a * 3 + 1] : 1, cba = vcols ? vcols[a * 3 + 2] : 1;
859
+ const crb = vcols ? vcols[b * 3] : 1, cgb = vcols ? vcols[b * 3 + 1] : 1, cbb = vcols ? vcols[b * 3 + 2] : 1;
860
+ const crc = vcols ? vcols[c * 3] : 1, cgc = vcols ? vcols[c * 3 + 1] : 1, cbc = vcols ? vcols[c * 3 + 2] : 1;
861
+ const tw = tex?.w ?? 0, th = tex?.h ?? 0, td = tex?.data;
862
+
863
+ // Rasterize: bounding box + edge functions, z-interpolated.
864
+ const minX = Math.max(0, Math.floor(Math.min(ax, bx, cx2)));
865
+ const maxX = Math.min(size - 1, Math.ceil(Math.max(ax, bx, cx2)));
866
+ const minY = Math.max(0, Math.floor(Math.min(ay, by, cy2)));
867
+ const maxY = Math.min(size - 1, Math.ceil(Math.max(ay, by, cy2)));
868
+ const area = (bx - ax) * (cy2 - ay) - (cx2 - ax) * (by - ay);
869
+ if (area === 0) continue;
870
+ const invArea = 1 / area;
871
+ for (let y = minY; y <= maxY; y++) {
872
+ const py = y + 0.5;
873
+ for (let x = minX; x <= maxX; x++) {
874
+ const pxx = x + 0.5;
875
+ const w0 = ((bx - pxx) * (cy2 - py) - (cx2 - pxx) * (by - py)) * invArea;
876
+ const w1 = ((cx2 - pxx) * (ay - py) - (ax - pxx) * (cy2 - py)) * invArea;
877
+ const w2 = 1 - w0 - w1;
878
+ if (w0 < 0 || w1 < 0 || w2 < 0) continue;
879
+ const z = w0 * az + w1 * bz + w2 * cz2;
880
+ const di = y * size + x;
881
+ if (z >= depth[di]) continue;
882
+ depth[di] = z;
883
+
884
+ // Interpolated normal → per-pixel lighting.
885
+ let nx = w0 * nax + w1 * nbx + w2 * ncx;
886
+ let ny = w0 * nay + w1 * nby + w2 * ncy;
887
+ let nz = w0 * naz + w1 * nbz + w2 * ncz;
888
+ const nl = Math.hypot(nx, ny, nz) || 1;
889
+ nx /= nl; ny /= nl; nz /= nl;
890
+ const key = nx * Kx + ny * Ky + nz * Kz;
891
+ const fill = nx * Fx + ny * Fy + nz * Fz;
892
+ const hemi = ny * 0.5 + 0.5;
893
+ const light = 0.30 + 0.30 * hemi + (key > 0 ? key * 0.62 : 0) + (fill > 0 ? fill * 0.16 : 0);
894
+
895
+ // Albedo: material baseColor × vertex color × texture.
896
+ let ar = br, ag = bg, ab = bb;
897
+ if (vcols) {
898
+ ar *= w0 * cra + w1 * crb + w2 * crc;
899
+ ag *= w0 * cga + w1 * cgb + w2 * cgc;
900
+ ab *= w0 * cba + w1 * cbb + w2 * cbc;
901
+ }
902
+ if (useTex && td) {
903
+ let u = w0 * ua + w1 * ub + w2 * uc;
904
+ let v = w0 * va + w1 * vb + w2 * vc;
905
+ u -= Math.floor(u); v -= Math.floor(v);
906
+ // Bilinear sample.
907
+ const fu = u * tw - 0.5, fv = v * th - 0.5;
908
+ let x0 = Math.floor(fu), y0 = Math.floor(fv);
909
+ const fx = fu - x0, fy = fv - y0;
910
+ x0 = ((x0 % tw) + tw) % tw; y0 = ((y0 % th) + th) % th;
911
+ const x1 = (x0 + 1) % tw, y1 = (y0 + 1) % th;
912
+ const p00 = (y0 * tw + x0) * 4, p10 = (y0 * tw + x1) * 4;
913
+ const p01 = (y1 * tw + x0) * 4, p11 = (y1 * tw + x1) * 4;
914
+ const wA = (1 - fx) * (1 - fy), wB = fx * (1 - fy);
915
+ const wC = (1 - fx) * fy, wD = fx * fy;
916
+ const inv255 = 1 / 255;
917
+ ar *= (td[p00] * wA + td[p10] * wB + td[p01] * wC + td[p11] * wD) * inv255;
918
+ ag *= (td[p00 + 1] * wA + td[p10 + 1] * wB + td[p01 + 1] * wC + td[p11 + 1] * wD) * inv255;
919
+ ab *= (td[p00 + 2] * wA + td[p10 + 2] * wB + td[p01 + 2] * wC + td[p11 + 2] * wD) * inv255;
920
+ }
921
+
922
+ const ndz = nz > 0 ? nz : 0;
923
+ const rimT = 1 - ndz;
924
+ const rim = rimT * rimT * 0.10;
925
+ const pi = di * 4;
926
+ px[pi] = Math.min(255, (ar * light + er + rim) * 255) | 0;
927
+ px[pi + 1] = Math.min(255, (ag * light + eg + rim) * 255) | 0;
928
+ px[pi + 2] = Math.min(255, (ab * light + eb + rim) * 255) | 0;
929
+ px[pi + 3] = 255;
930
+ }
931
+ }
932
+ }
933
+
934
+ this.ctx.putImageData({ data: this.pixels, width: this.size, height: this.size } as ImageData, 0, 0);
935
+ return true;
936
+ }
937
+
938
+ renderPlaceholder(): void {
939
+ for (let i = 0; i < this.pixels.length; i += 4) {
940
+ this.pixels[i] = 41; this.pixels[i + 1] = 43; this.pixels[i + 2] = 56; this.pixels[i + 3] = 255;
941
+ }
942
+ this.ctx.putImageData({ data: this.pixels, width: this.size, height: this.size } as ImageData, 0, 0);
943
+ }
944
+
945
+ dispose(): void {
946
+ this.cache.clear();
947
+ this.loading.clear();
948
+ }
949
+ }