@thunderclap-engine/engine 0.0.0-stage → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (566) hide show
  1. package/CHANGELOG.md +390 -0
  2. package/LICENSE +21 -0
  3. package/README.md +333 -2
  4. package/dist/analytics/analytics.d.ts +80 -0
  5. package/dist/analytics/analytics.js +129 -0
  6. package/dist/analytics/index.d.ts +1 -0
  7. package/dist/analytics/index.js +1 -0
  8. package/dist/anim/aim.d.ts +43 -0
  9. package/dist/anim/aim.js +157 -0
  10. package/dist/anim/clip.d.ts +98 -0
  11. package/dist/anim/clip.js +282 -0
  12. package/dist/anim/compress.d.ts +65 -0
  13. package/dist/anim/compress.js +339 -0
  14. package/dist/anim/foot-ik.d.ts +74 -0
  15. package/dist/anim/foot-ik.js +241 -0
  16. package/dist/anim/graph.d.ts +153 -0
  17. package/dist/anim/graph.js +532 -0
  18. package/dist/anim/ik.d.ts +105 -0
  19. package/dist/anim/ik.js +435 -0
  20. package/dist/anim/index.d.ts +36 -0
  21. package/dist/anim/index.js +21 -0
  22. package/dist/anim/match.d.ts +66 -0
  23. package/dist/anim/match.js +153 -0
  24. package/dist/anim/mixer.d.ts +113 -0
  25. package/dist/anim/mixer.js +314 -0
  26. package/dist/anim/morph.d.ts +41 -0
  27. package/dist/anim/morph.js +94 -0
  28. package/dist/anim/pose.d.ts +74 -0
  29. package/dist/anim/pose.js +254 -0
  30. package/dist/anim/quat.d.ts +44 -0
  31. package/dist/anim/quat.js +187 -0
  32. package/dist/anim/retarget.d.ts +97 -0
  33. package/dist/anim/retarget.js +366 -0
  34. package/dist/anim/root-motion.d.ts +44 -0
  35. package/dist/anim/root-motion.js +224 -0
  36. package/dist/anim/skin-pose.d.ts +39 -0
  37. package/dist/anim/skin-pose.js +53 -0
  38. package/dist/anim/socket.d.ts +47 -0
  39. package/dist/anim/socket.js +84 -0
  40. package/dist/anim/spring-chain.d.ts +73 -0
  41. package/dist/anim/spring-chain.js +294 -0
  42. package/dist/assets/atlas.d.ts +90 -0
  43. package/dist/assets/atlas.js +212 -0
  44. package/dist/assets/baked.d.ts +33 -0
  45. package/dist/assets/baked.js +38 -0
  46. package/dist/assets/bundle.d.ts +88 -0
  47. package/dist/assets/bundle.js +464 -0
  48. package/dist/assets/cache.d.ts +56 -0
  49. package/dist/assets/cache.js +123 -0
  50. package/dist/assets/gltf-load.d.ts +252 -0
  51. package/dist/assets/gltf-load.js +661 -0
  52. package/dist/assets/gltf-skin.d.ts +36 -0
  53. package/dist/assets/gltf-skin.js +109 -0
  54. package/dist/assets/gltf.d.ts +706 -0
  55. package/dist/assets/gltf.js +1376 -0
  56. package/dist/assets/index.d.ts +20 -0
  57. package/dist/assets/index.js +10 -0
  58. package/dist/assets/ktx2.d.ts +71 -0
  59. package/dist/assets/ktx2.js +213 -0
  60. package/dist/assets/manifest.d.ts +79 -0
  61. package/dist/assets/manifest.js +7 -0
  62. package/dist/assets/meshopt.d.ts +23 -0
  63. package/dist/assets/meshopt.js +403 -0
  64. package/dist/audio/audio.d.ts +733 -0
  65. package/dist/audio/audio.js +2013 -0
  66. package/dist/audio/dsp.d.ts +61 -0
  67. package/dist/audio/dsp.js +159 -0
  68. package/dist/audio/duck.d.ts +44 -0
  69. package/dist/audio/duck.js +101 -0
  70. package/dist/audio/envelope.d.ts +53 -0
  71. package/dist/audio/envelope.js +106 -0
  72. package/dist/audio/index.d.ts +13 -0
  73. package/dist/audio/index.js +14 -0
  74. package/dist/audio/music.d.ts +113 -0
  75. package/dist/audio/music.js +548 -0
  76. package/dist/audio/occlusion.d.ts +55 -0
  77. package/dist/audio/occlusion.js +61 -0
  78. package/dist/audio/persist.d.ts +23 -0
  79. package/dist/audio/persist.js +27 -0
  80. package/dist/audio/recipes.d.ts +55 -0
  81. package/dist/audio/recipes.js +227 -0
  82. package/dist/audio/send.d.ts +41 -0
  83. package/dist/audio/send.js +118 -0
  84. package/dist/audio/sprites.d.ts +59 -0
  85. package/dist/audio/sprites.js +43 -0
  86. package/dist/audio/synth.d.ts +103 -0
  87. package/dist/audio/synth.js +197 -0
  88. package/dist/audio/variations.d.ts +25 -0
  89. package/dist/audio/variations.js +55 -0
  90. package/dist/audio/zones.d.ts +100 -0
  91. package/dist/audio/zones.js +167 -0
  92. package/dist/core/detached-surface.d.ts +44 -0
  93. package/dist/core/detached-surface.js +104 -0
  94. package/dist/core/events.d.ts +24 -0
  95. package/dist/core/events.js +43 -0
  96. package/dist/core/game.d.ts +409 -0
  97. package/dist/core/game.js +530 -0
  98. package/dist/core/hash.d.ts +24 -0
  99. package/dist/core/hash.js +49 -0
  100. package/dist/core/index.d.ts +17 -0
  101. package/dist/core/index.js +9 -0
  102. package/dist/core/loop.d.ts +113 -0
  103. package/dist/core/loop.js +185 -0
  104. package/dist/core/math.d.ts +58 -0
  105. package/dist/core/math.js +210 -0
  106. package/dist/core/noise.d.ts +40 -0
  107. package/dist/core/noise.js +247 -0
  108. package/dist/core/pool.d.ts +42 -0
  109. package/dist/core/pool.js +85 -0
  110. package/dist/core/random.d.ts +61 -0
  111. package/dist/core/random.js +199 -0
  112. package/dist/core/scene.d.ts +79 -0
  113. package/dist/core/scene.js +195 -0
  114. package/dist/core/timer.d.ts +66 -0
  115. package/dist/core/timer.js +286 -0
  116. package/dist/fx/billboard-wgsl.d.ts +29 -0
  117. package/dist/fx/billboard-wgsl.js +158 -0
  118. package/dist/fx/billboard.d.ts +249 -0
  119. package/dist/fx/billboard.js +702 -0
  120. package/dist/fx/debug-draw.d.ts +127 -0
  121. package/dist/fx/debug-draw.js +523 -0
  122. package/dist/fx/debug-wgsl.d.ts +7 -0
  123. package/dist/fx/debug-wgsl.js +32 -0
  124. package/dist/fx/decals-wgsl.d.ts +15 -0
  125. package/dist/fx/decals-wgsl.js +173 -0
  126. package/dist/fx/decals.d.ts +216 -0
  127. package/dist/fx/decals.js +607 -0
  128. package/dist/fx/effect.d.ts +129 -0
  129. package/dist/fx/effect.js +349 -0
  130. package/dist/fx/effects/hit-spark.json +48 -0
  131. package/dist/fx/effects/index.d.ts +8 -0
  132. package/dist/fx/effects/index.js +9 -0
  133. package/dist/fx/effects/magic-aura.json +47 -0
  134. package/dist/fx/effects/smoke-puff.json +34 -0
  135. package/dist/fx/emit-surface.d.ts +40 -0
  136. package/dist/fx/emit-surface.js +124 -0
  137. package/dist/fx/flicker.d.ts +30 -0
  138. package/dist/fx/flicker.js +42 -0
  139. package/dist/fx/index.d.ts +31 -0
  140. package/dist/fx/index.js +20 -0
  141. package/dist/fx/particles-wgsl.d.ts +57 -0
  142. package/dist/fx/particles-wgsl.js +828 -0
  143. package/dist/fx/particles.d.ts +566 -0
  144. package/dist/fx/particles.js +1533 -0
  145. package/dist/fx/random.d.ts +15 -0
  146. package/dist/fx/random.js +20 -0
  147. package/dist/fx/sky-wgsl.d.ts +23 -0
  148. package/dist/fx/sky-wgsl.js +127 -0
  149. package/dist/fx/sky.d.ts +96 -0
  150. package/dist/fx/sky.js +175 -0
  151. package/dist/fx/text-atlas.d.ts +143 -0
  152. package/dist/fx/text-atlas.js +327 -0
  153. package/dist/fx/trail-wgsl.d.ts +11 -0
  154. package/dist/fx/trail-wgsl.js +42 -0
  155. package/dist/fx/trail.d.ts +194 -0
  156. package/dist/fx/trail.js +555 -0
  157. package/dist/fx/water-wgsl.d.ts +34 -0
  158. package/dist/fx/water-wgsl.js +197 -0
  159. package/dist/fx/water.d.ts +176 -0
  160. package/dist/fx/water.js +371 -0
  161. package/dist/geom/collide/bvh.d.ts +151 -0
  162. package/dist/geom/collide/bvh.js +493 -0
  163. package/dist/geom/collide/debug.d.ts +14 -0
  164. package/dist/geom/collide/debug.js +74 -0
  165. package/dist/geom/collide/grid.d.ts +26 -0
  166. package/dist/geom/collide/grid.js +91 -0
  167. package/dist/geom/collide/index.d.ts +8 -0
  168. package/dist/geom/collide/index.js +8 -0
  169. package/dist/geom/collide/queries.d.ts +70 -0
  170. package/dist/geom/collide/queries.js +583 -0
  171. package/dist/geom/collide/serialize.d.ts +14 -0
  172. package/dist/geom/collide/serialize.js +145 -0
  173. package/dist/geom/collide/shapes.d.ts +161 -0
  174. package/dist/geom/collide/shapes.js +686 -0
  175. package/dist/geom/collide/slide.d.ts +66 -0
  176. package/dist/geom/collide/slide.js +375 -0
  177. package/dist/geom/collide/tile-slide.d.ts +88 -0
  178. package/dist/geom/collide/tile-slide.js +255 -0
  179. package/dist/geom/geometry.d.ts +95 -0
  180. package/dist/geom/geometry.js +322 -0
  181. package/dist/geom/heightfield.d.ts +72 -0
  182. package/dist/geom/heightfield.js +295 -0
  183. package/dist/geom/hull.d.ts +27 -0
  184. package/dist/geom/hull.js +279 -0
  185. package/dist/geom/index.d.ts +35 -0
  186. package/dist/geom/index.js +23 -0
  187. package/dist/geom/iso.d.ts +74 -0
  188. package/dist/geom/iso.js +89 -0
  189. package/dist/geom/lod.d.ts +29 -0
  190. package/dist/geom/lod.js +27 -0
  191. package/dist/geom/ops.d.ts +47 -0
  192. package/dist/geom/ops.js +285 -0
  193. package/dist/geom/primitives.d.ts +97 -0
  194. package/dist/geom/primitives.js +359 -0
  195. package/dist/geom/shapes.d.ts +43 -0
  196. package/dist/geom/shapes.js +88 -0
  197. package/dist/geom/simplify.d.ts +38 -0
  198. package/dist/geom/simplify.js +519 -0
  199. package/dist/geom/split.d.ts +37 -0
  200. package/dist/geom/split.js +413 -0
  201. package/dist/geom/sweep.d.ts +83 -0
  202. package/dist/geom/sweep.js +534 -0
  203. package/dist/geom/tangents.d.ts +16 -0
  204. package/dist/geom/tangents.js +124 -0
  205. package/dist/geom/triangulate.d.ts +19 -0
  206. package/dist/geom/triangulate.js +147 -0
  207. package/dist/host/default.d.ts +10 -0
  208. package/dist/host/default.js +10 -0
  209. package/dist/host/index.d.ts +4 -0
  210. package/dist/host/index.js +3 -0
  211. package/dist/host/types.d.ts +144 -0
  212. package/dist/host/types.js +1 -0
  213. package/dist/host/web-links.d.ts +38 -0
  214. package/dist/host/web-links.js +110 -0
  215. package/dist/host/web.d.ts +30 -0
  216. package/dist/host/web.js +216 -0
  217. package/dist/host/xr.d.ts +24 -0
  218. package/dist/host/xr.js +59 -0
  219. package/dist/i18n/i18n.d.ts +59 -0
  220. package/dist/i18n/i18n.js +241 -0
  221. package/dist/i18n/index.d.ts +3 -0
  222. package/dist/i18n/index.js +3 -0
  223. package/dist/i18n/message.d.ts +72 -0
  224. package/dist/i18n/message.js +369 -0
  225. package/dist/i18n/negotiate.d.ts +17 -0
  226. package/dist/i18n/negotiate.js +58 -0
  227. package/dist/index.d.ts +7 -0
  228. package/dist/index.js +1 -0
  229. package/dist/input/bindings.d.ts +31 -0
  230. package/dist/input/bindings.js +141 -0
  231. package/dist/input/clock.d.ts +44 -0
  232. package/dist/input/clock.js +49 -0
  233. package/dist/input/follow.d.ts +133 -0
  234. package/dist/input/follow.js +218 -0
  235. package/dist/input/gestures.d.ts +281 -0
  236. package/dist/input/gestures.js +662 -0
  237. package/dist/input/glyphs.d.ts +35 -0
  238. package/dist/input/glyphs.js +216 -0
  239. package/dist/input/gyro.d.ts +85 -0
  240. package/dist/input/gyro.js +140 -0
  241. package/dist/input/index.d.ts +19 -0
  242. package/dist/input/index.js +16 -0
  243. package/dist/input/input.d.ts +543 -0
  244. package/dist/input/input.js +1492 -0
  245. package/dist/input/look.d.ts +327 -0
  246. package/dist/input/look.js +681 -0
  247. package/dist/input/magnify.d.ts +177 -0
  248. package/dist/input/magnify.js +131 -0
  249. package/dist/input/pointer-dispatch.d.ts +138 -0
  250. package/dist/input/pointer-dispatch.js +180 -0
  251. package/dist/input/pointer-router.d.ts +204 -0
  252. package/dist/input/pointer-router.js +533 -0
  253. package/dist/input/pointer-sources.d.ts +164 -0
  254. package/dist/input/pointer-sources.js +306 -0
  255. package/dist/input/rays.d.ts +138 -0
  256. package/dist/input/rays.js +615 -0
  257. package/dist/input/record.d.ts +209 -0
  258. package/dist/input/record.js +601 -0
  259. package/dist/input/touch-zones.d.ts +147 -0
  260. package/dist/input/touch-zones.js +300 -0
  261. package/dist/input/types.d.ts +158 -0
  262. package/dist/input/types.js +1 -0
  263. package/dist/input/xr-source.d.ts +53 -0
  264. package/dist/input/xr-source.js +164 -0
  265. package/dist/nav/astar.d.ts +45 -0
  266. package/dist/nav/astar.js +182 -0
  267. package/dist/nav/corridor.d.ts +40 -0
  268. package/dist/nav/corridor.js +95 -0
  269. package/dist/nav/crowd.d.ts +94 -0
  270. package/dist/nav/crowd.js +624 -0
  271. package/dist/nav/flow.d.ts +61 -0
  272. package/dist/nav/flow.js +193 -0
  273. package/dist/nav/grid.d.ts +108 -0
  274. package/dist/nav/grid.js +210 -0
  275. package/dist/nav/heap.d.ts +22 -0
  276. package/dist/nav/heap.js +92 -0
  277. package/dist/nav/index.d.ts +37 -0
  278. package/dist/nav/index.js +19 -0
  279. package/dist/nav/mesh/areas.d.ts +44 -0
  280. package/dist/nav/mesh/areas.js +98 -0
  281. package/dist/nav/mesh/bake-options.d.ts +33 -0
  282. package/dist/nav/mesh/bake-options.js +76 -0
  283. package/dist/nav/mesh/contour.d.ts +22 -0
  284. package/dist/nav/mesh/contour.js +323 -0
  285. package/dist/nav/mesh/flow3.d.ts +35 -0
  286. package/dist/nav/mesh/flow3.js +229 -0
  287. package/dist/nav/mesh/links.d.ts +38 -0
  288. package/dist/nav/mesh/links.js +72 -0
  289. package/dist/nav/mesh/mesh-path.d.ts +152 -0
  290. package/dist/nav/mesh/mesh-path.js +643 -0
  291. package/dist/nav/mesh/mesh-reach.d.ts +78 -0
  292. package/dist/nav/mesh/mesh-reach.js +450 -0
  293. package/dist/nav/mesh/navmesh.d.ts +117 -0
  294. package/dist/nav/mesh/navmesh.js +647 -0
  295. package/dist/nav/mesh/polymesh.d.ts +24 -0
  296. package/dist/nav/mesh/polymesh.js +384 -0
  297. package/dist/nav/mesh/regions.d.ts +12 -0
  298. package/dist/nav/mesh/regions.js +367 -0
  299. package/dist/nav/mesh/serialize.d.ts +13 -0
  300. package/dist/nav/mesh/serialize.js +197 -0
  301. package/dist/nav/mesh/voxelize.d.ts +111 -0
  302. package/dist/nav/mesh/voxelize.js +552 -0
  303. package/dist/nav/obstacles.d.ts +75 -0
  304. package/dist/nav/obstacles.js +294 -0
  305. package/dist/nav/path.d.ts +59 -0
  306. package/dist/nav/path.js +177 -0
  307. package/dist/nav/query-queue.d.ts +46 -0
  308. package/dist/nav/query-queue.js +308 -0
  309. package/dist/nav/reach.d.ts +53 -0
  310. package/dist/nav/reach.js +148 -0
  311. package/dist/nav/steer.d.ts +36 -0
  312. package/dist/nav/steer.js +78 -0
  313. package/dist/platform/boot.d.ts +28 -0
  314. package/dist/platform/boot.js +43 -0
  315. package/dist/platform/device-mode.d.ts +40 -0
  316. package/dist/platform/device-mode.js +104 -0
  317. package/dist/platform/device.d.ts +31 -0
  318. package/dist/platform/device.js +91 -0
  319. package/dist/platform/environment.d.ts +42 -0
  320. package/dist/platform/environment.js +82 -0
  321. package/dist/platform/gpu.d.ts +40 -0
  322. package/dist/platform/gpu.js +57 -0
  323. package/dist/platform/index.d.ts +18 -0
  324. package/dist/platform/index.js +10 -0
  325. package/dist/platform/letterbox.d.ts +43 -0
  326. package/dist/platform/letterbox.js +57 -0
  327. package/dist/platform/links.d.ts +20 -0
  328. package/dist/platform/links.js +43 -0
  329. package/dist/platform/screens.d.ts +36 -0
  330. package/dist/platform/screens.js +138 -0
  331. package/dist/platform/surface.d.ts +72 -0
  332. package/dist/platform/surface.js +157 -0
  333. package/dist/platform/xr-fake.d.ts +168 -0
  334. package/dist/platform/xr-fake.js +434 -0
  335. package/dist/platform/xr-surface.d.ts +120 -0
  336. package/dist/platform/xr-surface.js +214 -0
  337. package/dist/platform/xr-types.d.ts +262 -0
  338. package/dist/post/ao.d.ts +12 -0
  339. package/dist/post/ao.js +136 -0
  340. package/dist/post/blend.d.ts +27 -0
  341. package/dist/post/blend.js +129 -0
  342. package/dist/post/depth-pass.d.ts +43 -0
  343. package/dist/post/depth-pass.js +118 -0
  344. package/dist/post/dof.d.ts +1 -0
  345. package/dist/post/dof.js +66 -0
  346. package/dist/post/exposure.d.ts +80 -0
  347. package/dist/post/exposure.js +322 -0
  348. package/dist/post/index.d.ts +12 -0
  349. package/dist/post/index.js +11 -0
  350. package/dist/post/lens-dirt.d.ts +13 -0
  351. package/dist/post/lens-dirt.js +91 -0
  352. package/dist/post/lut.d.ts +19 -0
  353. package/dist/post/lut.js +50 -0
  354. package/dist/post/motion-blur.d.ts +9 -0
  355. package/dist/post/motion-blur.js +78 -0
  356. package/dist/post/post-stack.d.ts +302 -0
  357. package/dist/post/post-stack.js +1137 -0
  358. package/dist/post/settings.d.ts +485 -0
  359. package/dist/post/settings.js +416 -0
  360. package/dist/post/shaders.d.ts +25 -0
  361. package/dist/post/shaders.js +410 -0
  362. package/dist/post/ssr.d.ts +16 -0
  363. package/dist/post/ssr.js +155 -0
  364. package/dist/post/taa.d.ts +74 -0
  365. package/dist/post/taa.js +235 -0
  366. package/dist/post/white-balance.d.ts +25 -0
  367. package/dist/post/white-balance.js +98 -0
  368. package/dist/render/bindings.d.ts +98 -0
  369. package/dist/render/bindings.js +281 -0
  370. package/dist/render/blend.d.ts +13 -0
  371. package/dist/render/blend.js +26 -0
  372. package/dist/render/camera-2d.d.ts +72 -0
  373. package/dist/render/camera-2d.js +92 -0
  374. package/dist/render/camera.d.ts +302 -0
  375. package/dist/render/camera.js +671 -0
  376. package/dist/render/color.d.ts +16 -0
  377. package/dist/render/color.js +41 -0
  378. package/dist/render/compressed.d.ts +104 -0
  379. package/dist/render/compressed.js +248 -0
  380. package/dist/render/diagnostics.d.ts +15 -0
  381. package/dist/render/diagnostics.js +43 -0
  382. package/dist/render/dynamic-resolution.d.ts +70 -0
  383. package/dist/render/dynamic-resolution.js +126 -0
  384. package/dist/render/fake-gpu.d.ts +204 -0
  385. package/dist/render/fake-gpu.js +426 -0
  386. package/dist/render/flipbook.d.ts +56 -0
  387. package/dist/render/flipbook.js +87 -0
  388. package/dist/render/frame.d.ts +278 -0
  389. package/dist/render/frame.js +174 -0
  390. package/dist/render/fullscreen.d.ts +54 -0
  391. package/dist/render/fullscreen.js +91 -0
  392. package/dist/render/gpu-timer.d.ts +73 -0
  393. package/dist/render/gpu-timer.js +186 -0
  394. package/dist/render/index.d.ts +53 -0
  395. package/dist/render/index.js +29 -0
  396. package/dist/render/instance-list.d.ts +185 -0
  397. package/dist/render/instance-list.js +547 -0
  398. package/dist/render/lights.d.ts +181 -0
  399. package/dist/render/lights.js +233 -0
  400. package/dist/render/lod.d.ts +54 -0
  401. package/dist/render/lod.js +52 -0
  402. package/dist/render/material.d.ts +165 -0
  403. package/dist/render/material.js +292 -0
  404. package/dist/render/mesh.d.ts +284 -0
  405. package/dist/render/mesh.js +472 -0
  406. package/dist/render/mipmap.d.ts +16 -0
  407. package/dist/render/mipmap.js +121 -0
  408. package/dist/render/parallax.d.ts +50 -0
  409. package/dist/render/parallax.js +47 -0
  410. package/dist/render/pbr-wgsl.d.ts +31 -0
  411. package/dist/render/pbr-wgsl.js +119 -0
  412. package/dist/render/ping-pong.d.ts +42 -0
  413. package/dist/render/ping-pong.js +74 -0
  414. package/dist/render/renderer.d.ts +741 -0
  415. package/dist/render/renderer.js +2486 -0
  416. package/dist/render/scene-pipelines.d.ts +44 -0
  417. package/dist/render/scene-pipelines.js +92 -0
  418. package/dist/render/scene-target.d.ts +170 -0
  419. package/dist/render/scene-target.js +450 -0
  420. package/dist/render/shadow.d.ts +74 -0
  421. package/dist/render/shadow.js +208 -0
  422. package/dist/render/skin.d.ts +112 -0
  423. package/dist/render/skin.js +240 -0
  424. package/dist/render/snapshot.d.ts +36 -0
  425. package/dist/render/snapshot.js +34 -0
  426. package/dist/render/standard.d.ts +162 -0
  427. package/dist/render/standard.js +263 -0
  428. package/dist/render/texture.d.ts +142 -0
  429. package/dist/render/texture.js +228 -0
  430. package/dist/render/tile-layer.d.ts +110 -0
  431. package/dist/render/tile-layer.js +304 -0
  432. package/dist/render/transform.d.ts +115 -0
  433. package/dist/render/transform.js +318 -0
  434. package/dist/render/wgsl.d.ts +131 -0
  435. package/dist/render/wgsl.js +528 -0
  436. package/dist/save/codec.d.ts +49 -0
  437. package/dist/save/codec.js +169 -0
  438. package/dist/save/index.d.ts +8 -0
  439. package/dist/save/index.js +8 -0
  440. package/dist/save/journal.d.ts +24 -0
  441. package/dist/save/journal.js +102 -0
  442. package/dist/save/migrate.d.ts +15 -0
  443. package/dist/save/migrate.js +37 -0
  444. package/dist/save/schema.d.ts +72 -0
  445. package/dist/save/schema.js +154 -0
  446. package/dist/save/settings.d.ts +28 -0
  447. package/dist/save/settings.js +59 -0
  448. package/dist/save/slots.d.ts +56 -0
  449. package/dist/save/slots.js +200 -0
  450. package/dist/save/storage.d.ts +35 -0
  451. package/dist/save/storage.js +62 -0
  452. package/dist/save/store.d.ts +114 -0
  453. package/dist/save/store.js +269 -0
  454. package/dist/testing/fake-host.d.ts +103 -0
  455. package/dist/testing/fake-host.js +163 -0
  456. package/dist/testing/fixed-clock.d.ts +25 -0
  457. package/dist/testing/fixed-clock.js +30 -0
  458. package/dist/testing/index.d.ts +4 -0
  459. package/dist/testing/index.js +5 -0
  460. package/dist/testing/test-game.d.ts +76 -0
  461. package/dist/testing/test-game.js +92 -0
  462. package/dist/tween/ease.d.ts +77 -0
  463. package/dist/tween/ease.js +192 -0
  464. package/dist/tween/index.d.ts +8 -0
  465. package/dist/tween/index.js +7 -0
  466. package/dist/tween/timeline.d.ts +78 -0
  467. package/dist/tween/timeline.js +269 -0
  468. package/dist/tween/tween.d.ts +168 -0
  469. package/dist/tween/tween.js +320 -0
  470. package/dist/ui/anchor.d.ts +206 -0
  471. package/dist/ui/anchor.js +307 -0
  472. package/dist/ui/caption.d.ts +122 -0
  473. package/dist/ui/caption.js +286 -0
  474. package/dist/ui/cursor.d.ts +105 -0
  475. package/dist/ui/cursor.js +261 -0
  476. package/dist/ui/dom.d.ts +30 -0
  477. package/dist/ui/dom.js +92 -0
  478. package/dist/ui/floats.d.ts +86 -0
  479. package/dist/ui/floats.js +260 -0
  480. package/dist/ui/hud.d.ts +198 -0
  481. package/dist/ui/hud.js +431 -0
  482. package/dist/ui/index.d.ts +37 -0
  483. package/dist/ui/index.js +34 -0
  484. package/dist/ui/inventory-card.d.ts +52 -0
  485. package/dist/ui/inventory-card.js +173 -0
  486. package/dist/ui/inventory-context.d.ts +29 -0
  487. package/dist/ui/inventory-context.js +125 -0
  488. package/dist/ui/inventory-drag.d.ts +63 -0
  489. package/dist/ui/inventory-drag.js +394 -0
  490. package/dist/ui/inventory-grid.d.ts +66 -0
  491. package/dist/ui/inventory-grid.js +625 -0
  492. package/dist/ui/inventory-radial.d.ts +39 -0
  493. package/dist/ui/inventory-radial.js +168 -0
  494. package/dist/ui/inventory.d.ts +128 -0
  495. package/dist/ui/inventory.js +154 -0
  496. package/dist/ui/layout.d.ts +356 -0
  497. package/dist/ui/layout.js +856 -0
  498. package/dist/ui/link.d.ts +64 -0
  499. package/dist/ui/link.js +155 -0
  500. package/dist/ui/loading.d.ts +40 -0
  501. package/dist/ui/loading.js +157 -0
  502. package/dist/ui/loupe.d.ts +74 -0
  503. package/dist/ui/loupe.js +164 -0
  504. package/dist/ui/menu-nav.d.ts +33 -0
  505. package/dist/ui/menu-nav.js +28 -0
  506. package/dist/ui/minimap-canvas.d.ts +145 -0
  507. package/dist/ui/minimap-canvas.js +460 -0
  508. package/dist/ui/notify.d.ts +40 -0
  509. package/dist/ui/notify.js +141 -0
  510. package/dist/ui/orb.d.ts +53 -0
  511. package/dist/ui/orb.js +304 -0
  512. package/dist/ui/overlay.d.ts +213 -0
  513. package/dist/ui/overlay.js +394 -0
  514. package/dist/ui/padnav.d.ts +53 -0
  515. package/dist/ui/padnav.js +146 -0
  516. package/dist/ui/perf.d.ts +130 -0
  517. package/dist/ui/perf.js +329 -0
  518. package/dist/ui/rotate-card.d.ts +54 -0
  519. package/dist/ui/rotate-card.js +97 -0
  520. package/dist/ui/stack.d.ts +128 -0
  521. package/dist/ui/stack.js +312 -0
  522. package/dist/ui/styles.d.ts +13 -0
  523. package/dist/ui/styles.js +200 -0
  524. package/dist/ui/text-scale.d.ts +67 -0
  525. package/dist/ui/text-scale.js +102 -0
  526. package/dist/ui/theme.d.ts +70 -0
  527. package/dist/ui/theme.js +98 -0
  528. package/dist/ui/toast.d.ts +23 -0
  529. package/dist/ui/toast.js +46 -0
  530. package/dist/ui/tooltip.d.ts +89 -0
  531. package/dist/ui/tooltip.js +333 -0
  532. package/dist/ui/widgets-2.d.ts +134 -0
  533. package/dist/ui/widgets-2.js +399 -0
  534. package/dist/ui/widgets-3.d.ts +91 -0
  535. package/dist/ui/widgets-3.js +212 -0
  536. package/dist/ui/widgets.d.ts +34 -0
  537. package/dist/ui/widgets.js +50 -0
  538. package/dist/ui/world-panel/index.d.ts +5 -0
  539. package/dist/ui/world-panel/index.js +5 -0
  540. package/dist/ui/world-panel/layout.d.ts +38 -0
  541. package/dist/ui/world-panel/layout.js +68 -0
  542. package/dist/ui/world-panel/paint.d.ts +96 -0
  543. package/dist/ui/world-panel/paint.js +136 -0
  544. package/dist/ui/world-panel/panel.d.ts +235 -0
  545. package/dist/ui/world-panel/panel.js +762 -0
  546. package/dist/ui/world-panel/wgsl.d.ts +10 -0
  547. package/dist/ui/world-panel/wgsl.js +52 -0
  548. package/dist/ui/world-panel/widgets.d.ts +207 -0
  549. package/dist/ui/world-panel/widgets.js +569 -0
  550. package/dist/voxel/chunks.d.ts +77 -0
  551. package/dist/voxel/chunks.js +149 -0
  552. package/dist/voxel/grid.d.ts +84 -0
  553. package/dist/voxel/grid.js +223 -0
  554. package/dist/voxel/index.d.ts +22 -0
  555. package/dist/voxel/index.js +7 -0
  556. package/dist/voxel/mesher-worker.d.ts +14 -0
  557. package/dist/voxel/mesher-worker.js +47 -0
  558. package/dist/voxel/mesher.d.ts +73 -0
  559. package/dist/voxel/mesher.js +306 -0
  560. package/dist/voxel/ray.d.ts +32 -0
  561. package/dist/voxel/ray.js +73 -0
  562. package/dist/voxel/wgsl.d.ts +16 -0
  563. package/dist/voxel/wgsl.js +28 -0
  564. package/dist/voxel/worker-client.d.ts +79 -0
  565. package/dist/voxel/worker-client.js +122 -0
  566. package/package.json +177 -3
@@ -0,0 +1,828 @@
1
+ import { WGSL_MOTION } from '../render/scene-pipelines.js';
2
+ import { WGSL_GLOBALS, WGSL_LIGHTS } from '../render/wgsl.js';
3
+ const COMMON = /* wgsl */ `
4
+ struct Particle {
5
+ posVel: vec4f, // pos.xy, vel.xy (logical px, px/s)
6
+ c0: vec4f, // colour at birth (linear, premultiplied by nothing)
7
+ c1: vec4f, // colour at death
8
+ life: vec4f, // age, lifetime, size at birth, size at death
9
+ phys: vec4f, // gravity.xy, drag, emissive
10
+ };
11
+ struct Sim {
12
+ dt: f32,
13
+ floorY: f32,
14
+ bounce: f32,
15
+ time: f32,
16
+ capacity: f32,
17
+ stretch: f32,
18
+ spawnCount: f32,
19
+ _pad: f32,
20
+ };
21
+ `;
22
+ export const PARTICLE_COMPUTE_WGSL = /* wgsl */ `
23
+ ${COMMON}
24
+ struct Spawn {
25
+ a: vec4f, // x, y, shapeA, shapeB
26
+ b: vec4f, // angle, spread, speedMin, speedMax
27
+ c: vec4f, // lifeMin, lifeMax, sizeStart, sizeEnd
28
+ c0: vec4f,
29
+ c1: vec4f,
30
+ d: vec4f, // gravX, gravY, drag, shapeKind (0 point, 1 circle, 2 rect, 3 line)
31
+ e: vec4f, // start, count, seed, emissive
32
+ };
33
+ @group(0) @binding(0) var<storage, read_write> particles: array<Particle>;
34
+ @group(0) @binding(1) var<storage, read> spawns: array<Spawn>;
35
+ @group(0) @binding(2) var<uniform> sim: Sim;
36
+
37
+ fn pcg(v: u32) -> u32 {
38
+ let s = v * 747796405u + 2891336453u;
39
+ let w = ((s >> ((s >> 28u) + 4u)) ^ s) * 277803737u;
40
+ return (w >> 22u) ^ w;
41
+ }
42
+ fn rnd(state: ptr<function, u32>) -> f32 {
43
+ *state = pcg(*state);
44
+ return f32(*state) / 4294967295.0;
45
+ }
46
+
47
+ @compute @workgroup_size(64) fn spawn(@builtin(global_invocation_id) gid: vec3u) {
48
+ if (f32(gid.y) >= sim.spawnCount) { return; }
49
+ let cmd = spawns[gid.y];
50
+ let i = gid.x;
51
+ if (f32(i) >= cmd.e.y) { return; }
52
+ let cap = u32(sim.capacity);
53
+ let idx = (u32(cmd.e.x) + i) % cap;
54
+ var st = pcg(i ^ pcg(u32(cmd.e.z)));
55
+
56
+ var pos = cmd.a.xy;
57
+ let kind = cmd.d.w;
58
+ if (kind > 0.5 && kind < 1.5) {
59
+ let r = sqrt(rnd(&st)) * cmd.a.z;
60
+ let th = rnd(&st) * 6.2831853;
61
+ pos += vec2f(cos(th), sin(th)) * r;
62
+ } else if (kind > 1.5 && kind < 2.5) {
63
+ pos += (vec2f(rnd(&st), rnd(&st)) - 0.5) * cmd.a.zw;
64
+ } else if (kind > 2.5) {
65
+ pos += cmd.a.zw * rnd(&st);
66
+ }
67
+ let ang = cmd.b.x + (rnd(&st) - 0.5) * cmd.b.y;
68
+ let speed = mix(cmd.b.z, cmd.b.w, rnd(&st));
69
+ let life = mix(cmd.c.x, cmd.c.y, rnd(&st));
70
+ let sj = 0.7 + 0.6 * rnd(&st);
71
+
72
+ var p: Particle;
73
+ p.posVel = vec4f(pos, vec2f(cos(ang), sin(ang)) * speed);
74
+ p.c0 = cmd.c0;
75
+ p.c1 = cmd.c1;
76
+ p.life = vec4f(0.0, max(life, 1e-3), cmd.c.z * sj, cmd.c.w * sj);
77
+ p.phys = vec4f(cmd.d.xyz, cmd.e.w);
78
+ particles[idx] = p;
79
+ }
80
+
81
+ @compute @workgroup_size(64) fn update(@builtin(global_invocation_id) gid: vec3u) {
82
+ let i = gid.x;
83
+ if (f32(i) >= sim.capacity) { return; }
84
+ var p = particles[i];
85
+ if (p.life.x >= p.life.y) { return; }
86
+ let dt = sim.dt;
87
+ p.life.x += dt;
88
+ var v = p.posVel.zw + p.phys.xy * dt;
89
+ v *= exp(-p.phys.z * dt);
90
+ var pos = p.posVel.xy + v * dt;
91
+ if (pos.y > sim.floorY && v.y > 0.0) {
92
+ pos.y = sim.floorY;
93
+ v = vec2f(v.x * 0.75, -v.y * sim.bounce);
94
+ }
95
+ p.posVel = vec4f(pos, v);
96
+ particles[i] = p;
97
+ }
98
+ `;
99
+ export const PARTICLE_RENDER_WGSL = /* wgsl */ `
100
+ ${WGSL_GLOBALS}
101
+ ${COMMON}
102
+ @group(1) @binding(0) var<storage, read> particles: array<Particle>;
103
+ @group(1) @binding(1) var<uniform> sim: Sim;
104
+
105
+ struct PVOut {
106
+ @builtin(position) clip: vec4f,
107
+ @location(0) q: vec2f,
108
+ @location(1) color: vec4f,
109
+ @location(2) emissive: f32,
110
+ };
111
+ struct FragOut {
112
+ @location(0) color: vec4f,
113
+ @location(1) emissive: vec4f,
114
+ };
115
+
116
+ @vertex fn vs(@builtin(vertex_index) vi: u32, @builtin(instance_index) ii: u32) -> PVOut {
117
+ var o: PVOut;
118
+ let p = particles[ii];
119
+ if (p.life.x >= p.life.y) {
120
+ o.clip = vec4f(0.0, 0.0, 2.0, 1.0);
121
+ return o;
122
+ }
123
+ var corners = array<vec2f, 6>(
124
+ vec2f(-1.0, -1.0), vec2f(1.0, -1.0), vec2f(1.0, 1.0),
125
+ vec2f(-1.0, -1.0), vec2f(1.0, 1.0), vec2f(-1.0, 1.0)
126
+ );
127
+ let q = corners[vi];
128
+ let t = p.life.x / p.life.y;
129
+ let size = max(mix(p.life.z, p.life.w, t), 0.0);
130
+ let vel = p.posVel.zw;
131
+ let speed = length(vel);
132
+ var dir = vec2f(1.0, 0.0);
133
+ if (speed > 1e-3) { dir = vel / speed; }
134
+ let len = size + speed * sim.stretch;
135
+ let perp = vec2f(-dir.y, dir.x);
136
+ let world = p.posVel.xy + dir * (q.x * len * 0.5) + perp * (q.y * size * 0.5);
137
+ o.clip = camera.view_proj * vec4f(world, 0.0, 1.0);
138
+ o.q = q;
139
+ o.color = mix(p.c0, p.c1, t);
140
+ o.emissive = p.phys.w;
141
+ return o;
142
+ }
143
+
144
+ @fragment fn fs(in: PVOut) -> FragOut {
145
+ let d = length(in.q);
146
+ var a = clamp(1.0 - d, 0.0, 1.0);
147
+ a = a * a * in.color.a;
148
+ let c = in.color.rgb * a;
149
+ var o: FragOut;
150
+ o.color = vec4f(c, a);
151
+ o.emissive = vec4f(c * in.emissive, a);
152
+ return o;
153
+ }
154
+ `;
155
+ // ------------------------------------------------------------------ the extended kernel
156
+ // One kernel for world space and for the extended 2D system (ws/fx-3): 3D positions (z = 0 in
157
+ // 2D), spin, sprite-sheet tiles, multi-stop curves, forces, sub-emission and mesh emission. The
158
+ // plain 2D shaders above stay byte-for-byte what they were (E-008: 2D particles pixel-identical).
159
+ /** Particles per `Particle` struct below: 10 vec4 = 160 bytes. */
160
+ export const FX_PARTICLE_FLOATS = 40;
161
+ /** Floats per extended spawn record (`Spawn` below). */
162
+ export const FX_SPAWN_FLOATS = 52;
163
+ /** Forces the extended sim block carries. */
164
+ export const MAX_FORCES = 8;
165
+ /** Floats of the extended sim block: 20 scalars + 8 forces × 3 vec4. */
166
+ export const FX_SIM_FLOATS = 20 + MAX_FORCES * 12;
167
+ /** Floats per size-curve slot (count, 8 stops, 3 pad) and per colour-curve slot (count + pad, 8 RGBA). */
168
+ export const SIZE_CURVE_FLOATS = 12;
169
+ export const COLOR_CURVE_FLOATS = 36;
170
+ /** Floats per sub-emission request (`Req` below). */
171
+ export const FX_REQUEST_FLOATS = 8;
172
+ /** Floats per mesh-surface triangle (p0 + cdf, p1, p2, n0, n1, n2). */
173
+ export const SURFACE_TRI_FLOATS = 24;
174
+ function fxCommon(world) {
175
+ return /* wgsl */ `
176
+ const WORLD_SPACE: bool = ${world};
177
+ struct Particle {
178
+ pos: vec4f, // xyz, rotation (rad)
179
+ vel: vec4f, // xyz, spin (rad/s)
180
+ c0: vec4f,
181
+ c1: vec4f,
182
+ life: vec4f, // age, lifetime, size at birth (or the size-curve scale), size at death
183
+ phys: vec4f, // gravity xyz, drag
184
+ wind: vec4f, // wind xyz, emissive
185
+ tile: vec4f, // tile from, tile to, tile rate (fps, 0 = over life), flags (1 align to velocity)
186
+ curve: vec4f, // size slot, colour slot, death slot, life slot (-1 none)
187
+ sub: vec4f, // death count, life rate, life carry, unused
188
+ };
189
+ struct Force {
190
+ a: vec4f, // kind (0 attract, 1 vortex, 2 turbulence, 3 drag), x, y, z
191
+ b: vec4f, // strength, radius, frequency, seed
192
+ c: vec4f, // vortex axis xyz | attract falloff (0 constant, 1 linear, 2 inverse-square), dead zone; drag amount
193
+ };
194
+ struct Sim {
195
+ dt: f32,
196
+ floorY: f32,
197
+ bounce: f32,
198
+ time: f32,
199
+ capacity: f32,
200
+ stretch: f32,
201
+ spawnCount: f32,
202
+ spawnTotal: f32,
203
+ fadeNear: f32,
204
+ fadeFar: f32,
205
+ softness: f32,
206
+ softOn: f32,
207
+ lit: f32,
208
+ textured: f32,
209
+ columns: f32,
210
+ rows: f32,
211
+ forceCount: f32,
212
+ colorBase: f32,
213
+ maxRequests: f32,
214
+ _pad: f32,
215
+ forces: array<Force, ${MAX_FORCES}>,
216
+ };
217
+ `;
218
+ }
219
+ const FX_RANDOM = /* wgsl */ `
220
+ fn pcg(v: u32) -> u32 {
221
+ let s = v * 747796405u + 2891336453u;
222
+ let w = ((s >> ((s >> 28u) + 4u)) ^ s) * 277803737u;
223
+ return (w >> 22u) ^ w;
224
+ }
225
+ fn rnd(state: ptr<function, u32>) -> f32 {
226
+ *state = pcg(*state);
227
+ return f32(*state) / 4294967295.0;
228
+ }
229
+ `;
230
+ /**
231
+ * The extended compute kernel: \`spawn\` (CPU emits), \`advance\` (one thread: the ring head
232
+ * moves past this frame's emits and the sub-emission request count resets), \`update\`
233
+ * (integrate, forces, floor, spin, sub-emission requests) and \`subspawn\` (one thread per
234
+ * request: the children, one level deep).
235
+ */
236
+ function fxComputeWgsl(world) {
237
+ return /* wgsl */ `
238
+ ${fxCommon(world)}
239
+ struct Spawn {
240
+ a: vec4f, // x, y, z, shapeKind (0 point, 1 circle, 2 rect, 3 line, 4 box, 5 sphere, 6 mesh)
241
+ b: vec4f, // shapeA, shapeB, shapeC, spread
242
+ c: vec4f, // angle, vz, speedMin, speedMax
243
+ d: vec4f, // lifeMin, lifeMax, sizeStart, sizeEnd
244
+ c0: vec4f,
245
+ c1: vec4f,
246
+ e: vec4f, // gravity xyz, drag
247
+ f: vec4f, // wind xyz, emissive
248
+ g: vec4f, // start (relative to the frame's head), count, seed, inherit
249
+ h: vec4f, // tile from, tile to, tile rate, flags (1 align to velocity, 2 along normal)
250
+ i: vec4f, // rotation min, max, spin min, max
251
+ j: vec4f, // size slot, colour slot, death slot, life slot
252
+ k: vec4f, // death count, life rate, mesh offset (vec4s), mesh triangles
253
+ };
254
+ struct Req {
255
+ pos: vec4f, // xyz, slot
256
+ vel: vec4f, // xyz (already scaled by inherit), count
257
+ };
258
+ struct State {
259
+ head: atomic<u32>,
260
+ count: atomic<u32>,
261
+ _a: u32,
262
+ _b: u32,
263
+ reqs: array<Req>,
264
+ };
265
+ @group(0) @binding(0) var<storage, read_write> particles: array<Particle>;
266
+ @group(0) @binding(1) var<storage, read> spawns: array<Spawn>;
267
+ @group(0) @binding(2) var<uniform> sim: Sim;
268
+ @group(0) @binding(3) var<storage, read_write> state: State;
269
+ @group(0) @binding(4) var<storage, read> subs: array<Spawn>;
270
+ @group(0) @binding(5) var<storage, read> surface: array<vec4f>;
271
+ ${FX_RANDOM}
272
+ // A uniform direction in the cone of half-angle \`half\` around the unit vector \`axis\`.
273
+ fn cone(axis: vec3f, half: f32, st: ptr<function, u32>) -> vec3f {
274
+ let cz = mix(cos(half), 1.0, rnd(st));
275
+ let sz = sqrt(max(1.0 - cz * cz, 0.0));
276
+ let ph = rnd(st) * 6.2831853;
277
+ var t1 = cross(axis, vec3f(0.0, 0.0, 1.0));
278
+ if (dot(t1, t1) < 1e-6) { t1 = vec3f(1.0, 0.0, 0.0); }
279
+ t1 = normalize(t1);
280
+ let t2 = cross(axis, t1);
281
+ return axis * cz + (t1 * cos(ph) + t2 * sin(ph)) * sz;
282
+ }
283
+ // Launch direction: a 2D fan of \`spread\` around \`axis\` in the XY plane, or a 3D cone.
284
+ fn launch(axis: vec3f, spread: f32, st: ptr<function, u32>) -> vec3f {
285
+ if (WORLD_SPACE) {
286
+ return cone(axis, clamp(spread * 0.5, 0.0, 3.14159265), st);
287
+ }
288
+ let base = atan2(axis.y, axis.x);
289
+ let ang = base + (rnd(st) - 0.5) * spread;
290
+ return vec3f(cos(ang), sin(ang), 0.0);
291
+ }
292
+ // Value noise on the integer lattice, seeded; and the curl of three such fields.
293
+ fn lattice(p: vec3i, seed: u32) -> f32 {
294
+ let h = pcg((u32(p.x) * 73856093u) ^ (u32(p.y) * 19349663u) ^ (u32(p.z) * 83492791u) ^ seed);
295
+ return f32(h) / 4294967295.0;
296
+ }
297
+ fn vnoise(p: vec3f, seed: u32) -> f32 {
298
+ let i = vec3i(floor(p));
299
+ let f = fract(p);
300
+ let u = f * f * (3.0 - 2.0 * f);
301
+ let c000 = lattice(i, seed);
302
+ let c100 = lattice(i + vec3i(1, 0, 0), seed);
303
+ let c010 = lattice(i + vec3i(0, 1, 0), seed);
304
+ let c110 = lattice(i + vec3i(1, 1, 0), seed);
305
+ let c001 = lattice(i + vec3i(0, 0, 1), seed);
306
+ let c101 = lattice(i + vec3i(1, 0, 1), seed);
307
+ let c011 = lattice(i + vec3i(0, 1, 1), seed);
308
+ let c111 = lattice(i + vec3i(1, 1, 1), seed);
309
+ let x00 = mix(c000, c100, u.x);
310
+ let x10 = mix(c010, c110, u.x);
311
+ let x01 = mix(c001, c101, u.x);
312
+ let x11 = mix(c011, c111, u.x);
313
+ return mix(mix(x00, x10, u.y), mix(x01, x11, u.y), u.z);
314
+ }
315
+ fn curl(p: vec3f, seed: u32) -> vec3f {
316
+ let e = 0.05;
317
+ let dx = vec3f(e, 0.0, 0.0);
318
+ let dy = vec3f(0.0, e, 0.0);
319
+ let dz = vec3f(0.0, 0.0, e);
320
+ let sx = seed;
321
+ let sy = seed + 1u;
322
+ let sz = seed + 2u;
323
+ let dzdy = vnoise(p + dy, sz) - vnoise(p - dy, sz);
324
+ let dydz = vnoise(p + dz, sy) - vnoise(p - dz, sy);
325
+ let dxdz = vnoise(p + dz, sx) - vnoise(p - dz, sx);
326
+ let dzdx = vnoise(p + dx, sz) - vnoise(p - dx, sz);
327
+ let dydx = vnoise(p + dx, sy) - vnoise(p - dx, sy);
328
+ let dxdy = vnoise(p + dy, sx) - vnoise(p - dy, sx);
329
+ return vec3f(dzdy - dydz, dxdz - dzdx, dydx - dxdy) / (2.0 * e);
330
+ }
331
+ // Binary search of the surface's cumulative-area table: the triangle at \`u\`.
332
+ fn surface_tri(offset: u32, count: u32, u: f32) -> u32 {
333
+ var lo = 0u;
334
+ var hi = count - 1u;
335
+ for (var k = 0u; k < 32u; k++) {
336
+ if (lo >= hi) { break; }
337
+ let mid = (lo + hi) / 2u;
338
+ if (surface[offset + mid * 6u].w < u) { lo = mid + 1u; } else { hi = mid; }
339
+ }
340
+ return lo;
341
+ }
342
+
343
+ // A particle from a spawn record: at \`origin\` + the shape, with \`inherit\` added to its launch.
344
+ fn make(cmd: Spawn, origin: vec3f, inherit: vec3f, idx: u32, st: ptr<function, u32>, child: bool) {
345
+ var pos = origin + cmd.a.xyz;
346
+ let kind = cmd.a.w;
347
+ let s = cmd.b.xyz;
348
+ var axis = vec3f(cos(cmd.c.x), sin(cmd.c.x), 0.0);
349
+ if (kind > 0.5 && kind < 1.5) {
350
+ let r = sqrt(rnd(st)) * s.x;
351
+ let th = rnd(st) * 6.2831853;
352
+ pos += vec3f(cos(th), sin(th), 0.0) * r;
353
+ } else if (kind > 1.5 && kind < 2.5) {
354
+ pos += vec3f((vec2f(rnd(st), rnd(st)) - 0.5) * s.xy, 0.0);
355
+ } else if (kind > 2.5 && kind < 3.5) {
356
+ pos += s * rnd(st);
357
+ } else if (kind > 3.5 && kind < 4.5) {
358
+ pos += (vec3f(rnd(st), rnd(st), rnd(st)) - 0.5) * s;
359
+ } else if (kind > 4.5 && kind < 5.5) {
360
+ let r = pow(rnd(st), 1.0 / 3.0) * s.x;
361
+ pos += cone(vec3f(0.0, 1.0, 0.0), 3.14159265, st) * r;
362
+ } else if (kind > 5.5) {
363
+ let count = u32(cmd.k.w);
364
+ if (count > 0u) {
365
+ let base = u32(cmd.k.z);
366
+ let tri = base + surface_tri(base, count, rnd(st)) * 6u;
367
+ let r1 = sqrt(rnd(st));
368
+ let r2 = rnd(st);
369
+ let w = vec3f(1.0 - r1, r1 * (1.0 - r2), r1 * r2);
370
+ pos += surface[tri].xyz * w.x + surface[tri + 1u].xyz * w.y + surface[tri + 2u].xyz * w.z;
371
+ if ((u32(cmd.h.w) & 2u) != 0u) {
372
+ let n = surface[tri + 3u].xyz * w.x + surface[tri + 4u].xyz * w.y + surface[tri + 5u].xyz * w.z;
373
+ if (dot(n, n) > 1e-8) { axis = normalize(n); }
374
+ }
375
+ }
376
+ }
377
+ let dir = launch(axis, cmd.b.w, st);
378
+ let speed = mix(cmd.c.z, cmd.c.w, rnd(st));
379
+ let life = mix(cmd.d.x, cmd.d.y, rnd(st));
380
+ let sj = 0.7 + 0.6 * rnd(st);
381
+ let rot = mix(cmd.i.x, cmd.i.y, rnd(st));
382
+ let spin = mix(cmd.i.z, cmd.i.w, rnd(st));
383
+
384
+ var p: Particle;
385
+ p.pos = vec4f(pos, rot);
386
+ p.vel = vec4f(dir * speed + vec3f(0.0, 0.0, cmd.c.y) + inherit, spin);
387
+ p.c0 = cmd.c0;
388
+ p.c1 = cmd.c1;
389
+ if (cmd.j.x >= 0.0) {
390
+ p.life = vec4f(0.0, max(life, 1e-3), sj, sj);
391
+ } else {
392
+ p.life = vec4f(0.0, max(life, 1e-3), cmd.d.z * sj, cmd.d.w * sj);
393
+ }
394
+ p.phys = cmd.e;
395
+ p.wind = cmd.f;
396
+ p.tile = cmd.h;
397
+ if (child) {
398
+ p.curve = vec4f(cmd.j.xy, -1.0, -1.0);
399
+ p.sub = vec4f(0.0);
400
+ } else {
401
+ p.curve = cmd.j;
402
+ p.sub = vec4f(cmd.k.x, cmd.k.y, 0.0, 0.0);
403
+ }
404
+ particles[idx] = p;
405
+ }
406
+
407
+ @compute @workgroup_size(64) fn spawn(@builtin(global_invocation_id) gid: vec3u) {
408
+ if (f32(gid.y) >= sim.spawnCount) { return; }
409
+ let cmd = spawns[gid.y];
410
+ let i = gid.x;
411
+ if (f32(i) >= cmd.g.y) { return; }
412
+ let cap = u32(sim.capacity);
413
+ let idx = (atomicLoad(&state.head) + u32(cmd.g.x) + i) % cap;
414
+ var st = pcg(i ^ pcg(u32(cmd.g.z)));
415
+ make(cmd, vec3f(0.0), vec3f(0.0), idx, &st, false);
416
+ }
417
+
418
+ @compute @workgroup_size(1) fn advance() {
419
+ let cap = u32(sim.capacity);
420
+ let h = atomicLoad(&state.head);
421
+ atomicStore(&state.head, (h + u32(sim.spawnTotal)) % cap);
422
+ atomicStore(&state.count, 0u);
423
+ }
424
+
425
+ fn request(pos: vec3f, vel: vec3f, slot: f32, count: f32) {
426
+ let r = atomicAdd(&state.count, 1u);
427
+ if (f32(r) >= sim.maxRequests) { return; }
428
+ let cmd = subs[u32(slot)];
429
+ var q: Req;
430
+ q.pos = vec4f(pos, slot);
431
+ q.vel = vec4f(vel * cmd.g.w, count);
432
+ state.reqs[r] = q;
433
+ }
434
+
435
+ @compute @workgroup_size(64) fn update(@builtin(global_invocation_id) gid: vec3u) {
436
+ let i = gid.x;
437
+ if (f32(i) >= sim.capacity) { return; }
438
+ var p = particles[i];
439
+ if (p.life.x >= p.life.y) { return; }
440
+ let dt = sim.dt;
441
+ p.life.x += dt;
442
+ var pos = p.pos.xyz;
443
+ var v = p.vel.xyz;
444
+ var acc = p.phys.xyz;
445
+ var drag = p.phys.w;
446
+ for (var k = 0u; k < u32(sim.forceCount); k++) {
447
+ let f = sim.forces[k];
448
+ let kind = f.a.x;
449
+ if (kind < 0.5) {
450
+ let d = f.a.yzw - pos;
451
+ let r = length(d);
452
+ var w = select(1.0, max(1.0 - r / f.b.y, 0.0), f.b.y > 0.0);
453
+ if (f.c.x > 1.5) { w /= max(r * r, 1e-4); } else if (f.c.x > 0.5) { w *= r; }
454
+ if (r > 1e-4 && r >= f.c.y) { acc += d / r * f.b.x * w; }
455
+ } else if (kind < 1.5) {
456
+ let rel = pos - f.a.yzw;
457
+ let ax = normalize(f.c.xyz);
458
+ let radial = rel - ax * dot(rel, ax);
459
+ let r = length(radial);
460
+ let w = select(1.0, max(1.0 - r / f.b.y, 0.0), f.b.y > 0.0);
461
+ if (r > 1e-4) { acc += cross(ax, radial / r) * f.b.x * w; }
462
+ } else if (kind < 2.5) {
463
+ acc += curl(pos * f.b.z, u32(f.b.w)) * f.b.x;
464
+ } else {
465
+ drag += f.c.w;
466
+ }
467
+ }
468
+ let wind = p.wind.xyz;
469
+ v += acc * dt;
470
+ v = wind + (v - wind) * exp(-drag * dt);
471
+ pos += v * dt;
472
+ if (WORLD_SPACE) {
473
+ if (pos.y < sim.floorY && v.y < 0.0) {
474
+ pos.y = sim.floorY;
475
+ v = vec3f(v.x * 0.75, -v.y * sim.bounce, v.z * 0.75);
476
+ }
477
+ } else {
478
+ if (pos.y > sim.floorY && v.y > 0.0) {
479
+ pos.y = sim.floorY;
480
+ v = vec3f(v.x * 0.75, -v.y * sim.bounce, 0.0);
481
+ }
482
+ }
483
+ p.pos = vec4f(pos, p.pos.w + p.vel.w * dt);
484
+ p.vel = vec4f(v, p.vel.w);
485
+ // Sub-emission: a trail while alive, a burst on the frame the particle dies.
486
+ if (p.curve.w >= 0.0 && p.sub.y > 0.0) {
487
+ p.sub.z += p.sub.y * dt;
488
+ let n = floor(p.sub.z);
489
+ if (n >= 1.0) {
490
+ p.sub.z -= n;
491
+ request(pos, v, p.curve.w, n);
492
+ }
493
+ }
494
+ if (p.life.x >= p.life.y && p.curve.z >= 0.0 && p.sub.x >= 1.0) {
495
+ request(pos, v, p.curve.z, p.sub.x);
496
+ }
497
+ particles[i] = p;
498
+ }
499
+
500
+ @compute @workgroup_size(64) fn subspawn(@builtin(global_invocation_id) gid: vec3u) {
501
+ let r = gid.x;
502
+ let pending = min(atomicLoad(&state.count), u32(sim.maxRequests));
503
+ if (r >= pending) { return; }
504
+ let q = state.reqs[r];
505
+ let cmd = subs[u32(q.pos.w)];
506
+ let n = u32(q.vel.w);
507
+ let cap = u32(sim.capacity);
508
+ let base = atomicAdd(&state.head, n);
509
+ var st = pcg((r * 7919u) ^ pcg(u32(sim.time * 1000.0)) ^ pcg(base));
510
+ for (var i = 0u; i < n; i++) {
511
+ make(cmd, q.pos.xyz, q.vel.xyz, (base + i) % cap, &st, true);
512
+ }
513
+ }
514
+ `;
515
+ }
516
+ /**
517
+ * The extended render kernel: the quad is built from the view rows (world) or the XY axes (2D),
518
+ * aligned to the velocity (streaks) when the particle asks, rotated by its spin, sized and
519
+ * coloured by the curve table or the two-stop sugar, textured from the sprite sheet tile when
520
+ * the system has one (both blend modes; premultiplied on output), with the world-space fades
521
+ * and lighting of before.
522
+ */
523
+ /**
524
+ * The extended render shader; with `velocity` (the velocity attachment's fragment location, 2
525
+ * or 3; world space only) the build that writes its own motion (ws/render-11): each corner's
526
+ * previous position is `world - vel * sim.dt` (this frame's step back), projected through
527
+ * `camera.prev_view_proj`, and the fragment writes `vec4f(engine_motion(..), 0, alpha)`.
528
+ * `velocity` 0 is the exported strings exactly.
529
+ */
530
+ export function fxRenderWgsl(world, velocity = 0) {
531
+ const v = world && velocity > 0;
532
+ return /* wgsl */ `
533
+ ${WGSL_GLOBALS}${v ? WGSL_MOTION : ''}
534
+ ${world ? WGSL_LIGHTS : ''}
535
+ ${fxCommon(world)}
536
+ @group(1) @binding(0) var<storage, read> particles: array<Particle>;
537
+ @group(1) @binding(1) var<uniform> sim: Sim;
538
+ ${world ? '@group(1) @binding(2) var scene_depth: texture_depth_2d;' : ''}
539
+ @group(1) @binding(3) var<storage, read> curves: array<f32>;
540
+ @group(1) @binding(4) var sheet: texture_2d<f32>;
541
+ @group(1) @binding(5) var sheet_samp: sampler;
542
+
543
+ struct PVOut {
544
+ @builtin(position) clip: vec4f,
545
+ @location(0) q: vec2f,
546
+ @location(1) color: vec4f,
547
+ @location(2) emissive: f32,
548
+ @location(3) world: vec3f,
549
+ @location(4) view_z: f32,
550
+ @location(5) side: vec3f,
551
+ @location(6) uv: vec2f,${v ? '\n @location(7) prev_clip: vec4f,' : ''}
552
+ };
553
+ struct FragOut {
554
+ @location(0) color: vec4f,
555
+ @location(1) emissive: vec4f,${v ? `\n @location(${velocity}) velocity: vec4f,` : ''}
556
+ };
557
+
558
+ fn view_row(r: u32) -> vec3f {
559
+ return vec3f(camera.view[0][r], camera.view[1][r], camera.view[2][r]);
560
+ }
561
+
562
+ // Piecewise-linear over n stops spread evenly on 0..1.
563
+ fn size_at(slot: f32, t: f32, s0: f32, s1: f32) -> f32 {
564
+ if (slot < 0.0) { return mix(s0, s1, t); }
565
+ let base = u32(slot) * ${SIZE_CURVE_FLOATS}u;
566
+ let n = u32(curves[base]);
567
+ let x = clamp(t, 0.0, 1.0) * f32(n - 1u);
568
+ let i = min(u32(floor(x)), n - 2u);
569
+ return mix(curves[base + 1u + i], curves[base + 2u + i], x - f32(i)) * s0;
570
+ }
571
+ fn color_at(slot: f32, t: f32, c0: vec4f, c1: vec4f) -> vec4f {
572
+ if (slot < 0.0) { return mix(c0, c1, t); }
573
+ let base = u32(sim.colorBase) + u32(slot) * ${COLOR_CURVE_FLOATS}u;
574
+ let n = u32(curves[base]);
575
+ let x = clamp(t, 0.0, 1.0) * f32(n - 1u);
576
+ let i = min(u32(floor(x)), n - 2u);
577
+ let a = base + 4u + i * 4u;
578
+ let ca = vec4f(curves[a], curves[a + 1u], curves[a + 2u], curves[a + 3u]);
579
+ let cb = vec4f(curves[a + 4u], curves[a + 5u], curves[a + 6u], curves[a + 7u]);
580
+ return mix(ca, cb, x - f32(i));
581
+ }
582
+
583
+ @vertex fn vs(@builtin(vertex_index) vi: u32, @builtin(instance_index) ii: u32) -> PVOut {
584
+ var o: PVOut;
585
+ let p = particles[ii];
586
+ if (p.life.x >= p.life.y) {
587
+ o.clip = vec4f(0.0, 0.0, 2.0, 1.0);
588
+ return o;
589
+ }
590
+ var corners = array<vec2f, 6>(
591
+ vec2f(-1.0, -1.0), vec2f(1.0, -1.0), vec2f(1.0, 1.0),
592
+ vec2f(-1.0, -1.0), vec2f(1.0, 1.0), vec2f(-1.0, 1.0)
593
+ );
594
+ let q = corners[vi];
595
+ let t = p.life.x / p.life.y;
596
+ let size = max(size_at(p.curve.x, t, p.life.z, p.life.w), 0.0);
597
+ var right = vec3f(1.0, 0.0, 0.0);
598
+ var up = vec3f(0.0, 1.0, 0.0);
599
+ if (WORLD_SPACE) {
600
+ right = view_row(0u);
601
+ up = view_row(1u);
602
+ }
603
+ let vel = p.vel.xyz;
604
+ let v2 = vec2f(dot(vel, right), dot(vel, up));
605
+ let speed = length(v2);
606
+ var dir = vec2f(1.0, 0.0);
607
+ var len = size;
608
+ if ((u32(p.tile.w) & 1u) != 0u) {
609
+ if (speed > 1e-4) { dir = v2 / speed; }
610
+ len = size + speed * sim.stretch;
611
+ }
612
+ let cr = cos(p.pos.w);
613
+ let sr = sin(p.pos.w);
614
+ dir = vec2f(dir.x * cr - dir.y * sr, dir.x * sr + dir.y * cr);
615
+ let axis = right * dir.x + up * dir.y;
616
+ let perp = up * dir.x - right * dir.y;
617
+ let world = p.pos.xyz + axis * (q.x * len * 0.5) + perp * (q.y * size * 0.5);
618
+ o.clip = camera.view_proj * vec4f(world, 1.0);${v ? '\n o.prev_clip = camera.prev_view_proj * vec4f(world - vel * sim.dt, 1.0);' : ''}
619
+ o.q = q;
620
+ var color = color_at(p.curve.y, t, p.c0, p.c1);
621
+ if (WORLD_SPACE && sim.fadeFar > sim.fadeNear) {
622
+ let d = length(world - camera.camera_pos.xyz);
623
+ color.a *= 1.0 - smoothstep(sim.fadeNear, sim.fadeFar, d);
624
+ }
625
+ o.color = color;
626
+ o.emissive = p.wind.w;
627
+ o.world = world;
628
+ o.view_z = (camera.view * vec4f(world, 1.0)).z;
629
+ o.side = axis * q.x + perp * q.y;
630
+ // Sprite sheet tile: a fixed tile, a flipbook over life, or at a fixed rate looping.
631
+ let frames = max(p.tile.y - p.tile.x + 1.0, 1.0);
632
+ var tile = p.tile.x;
633
+ if (p.tile.z > 0.0) {
634
+ tile += floor(p.life.x * p.tile.z) % frames;
635
+ } else {
636
+ tile += min(floor(t * frames), frames - 1.0);
637
+ }
638
+ let col = tile % sim.columns;
639
+ let row = floor(tile / sim.columns);
640
+ let qv = select(0.5 - q.y * 0.5, 0.5 + q.y * 0.5, !WORLD_SPACE);
641
+ o.uv = (vec2f(col, row) + vec2f(q.x * 0.5 + 0.5, qv)) / vec2f(sim.columns, sim.rows);
642
+ return o;
643
+ }
644
+
645
+ // View-space z of a depth-buffer value under camera.proj (any perspective or ortho matrix).
646
+ fn view_z_of(d: f32) -> f32 {
647
+ let p = camera.proj;
648
+ let den = d * p[2][3] - p[2][2];
649
+ return (p[3][2] - d * p[3][3]) / select(den, 1e-6, abs(den) < 1e-6);
650
+ }
651
+
652
+ @fragment fn fs(in: PVOut) -> FragOut {
653
+ let tex = textureSample(sheet, sheet_samp, in.uv);
654
+ var a: f32;
655
+ var rgb: vec3f;
656
+ if (sim.textured > 0.5) {
657
+ a = in.color.a * tex.a;
658
+ rgb = in.color.rgb * tex.rgb;
659
+ } else {
660
+ let r = length(in.q);
661
+ a = clamp(1.0 - r, 0.0, 1.0);
662
+ a = a * a * in.color.a;
663
+ rgb = in.color.rgb;
664
+ }
665
+ ${world
666
+ ? /* wgsl */ `
667
+ if (sim.softOn > 0.5) {
668
+ let dims = vec2i(textureDimensions(scene_depth));
669
+ let px = clamp(vec2i(in.clip.xy), vec2i(0), dims - 1);
670
+ let scene_z = view_z_of(textureLoad(scene_depth, px, 0));
671
+ a *= saturate(abs(scene_z - in.view_z) / max(sim.softness, 1e-4));
672
+ }
673
+ if (sim.lit > 0.5) {
674
+ let back = view_row(2u);
675
+ let n = normalize(in.side + back * sqrt(max(1.0 - dot(in.side, in.side), 0.0)));
676
+ rgb *= engine_lambert(n, in.world);
677
+ }`
678
+ : ''}
679
+ let c = rgb * a;
680
+ var o: FragOut;
681
+ o.color = vec4f(c, a);
682
+ o.emissive = vec4f(c * in.emissive, a);${v ? '\n o.velocity = vec4f(engine_motion(in.clip, in.prev_clip), 0.0, a);' : ''}
683
+ return o;
684
+ }
685
+ `;
686
+ }
687
+ /** The extended 2D kernel (`extended: true`, or implied by `texture`, `forces`, `collide`). */
688
+ export const PARTICLE_EXT_COMPUTE_WGSL = fxComputeWgsl(false);
689
+ export const PARTICLE_EXT_RENDER_WGSL = fxRenderWgsl(false);
690
+ /** World space: the same extended kernel with a 3D cone launch, y-up floor and the view-row quad. */
691
+ export const PARTICLE_WORLD_COMPUTE_WGSL = fxComputeWgsl(true);
692
+ export const PARTICLE_WORLD_RENDER_WGSL = fxRenderWgsl(true);
693
+ /**
694
+ * Depth collision (world space, \`readsDepth\` + \`collide\`): runs in the depth hook over this
695
+ * frame's opaque depth. A live particle whose projected depth lies behind the scene by less
696
+ * than \`thickness\` (view-ray units) is pushed out along the surface normal (from the depth's
697
+ * neighbours) onto the surface, so a resting particle does not creep along the view ray, and its
698
+ * velocity is reflected about that normal, scaled by
699
+ * \`bounce\` normally and by \`1 - friction\` tangentially. A particle deeper than that is
700
+ * swept: if its position one step back (\`pos - vel * dt\`) was in front of the scene it
701
+ * crossed the surface this frame and is put back there, so a slow frame does not tunnel.
702
+ */
703
+ export const PARTICLE_COLLIDE_WGSL = /* wgsl */ `
704
+ ${fxCommon(true)}
705
+ struct Collide {
706
+ view_proj: mat4x4f,
707
+ inv_view_proj: mat4x4f,
708
+ viewport: vec4f, // x, y, width, height (target px)
709
+ params: vec4f, // bounce, friction, thickness, last step dt
710
+ };
711
+ @group(0) @binding(0) var<storage, read_write> particles: array<Particle>;
712
+ @group(0) @binding(1) var<uniform> col: Collide;
713
+ @group(0) @binding(2) var scene_depth: texture_depth_2d;
714
+
715
+ fn unproject(ndc: vec2f, d: f32) -> vec3f {
716
+ let w = col.inv_view_proj * vec4f(ndc, d, 1.0);
717
+ return w.xyz / w.w;
718
+ }
719
+ fn scene_at(px: vec2i, dims: vec2i) -> vec3f {
720
+ let c = clamp(px, vec2i(0), dims - 1);
721
+ let d = textureLoad(scene_depth, c, 0);
722
+ let ndc = (vec2f(c) + 0.5 - col.viewport.xy) / col.viewport.zw * vec2f(2.0, -2.0) + vec2f(-1.0, 1.0);
723
+ return unproject(ndc, d);
724
+ }
725
+
726
+ @compute @workgroup_size(64) fn collide(@builtin(global_invocation_id) gid: vec3u) {
727
+ let i = gid.x;
728
+ if (i >= arrayLength(&particles)) { return; }
729
+ var p = particles[i];
730
+ if (p.life.x >= p.life.y) { return; }
731
+ let clip = col.view_proj * vec4f(p.pos.xyz, 1.0);
732
+ if (clip.w <= 1e-5) { return; }
733
+ let ndc = clip.xyz / clip.w;
734
+ if (any(abs(ndc.xy) > vec2f(1.0)) || ndc.z < 0.0 || ndc.z > 1.0) { return; }
735
+ let dims = vec2i(textureDimensions(scene_depth));
736
+ let px = vec2i(col.viewport.xy + (ndc.xy * vec2f(0.5, -0.5) + 0.5) * col.viewport.zw);
737
+ let d = textureLoad(scene_depth, clamp(px, vec2i(0), dims - 1), 0);
738
+ if (d >= 1.0 || ndc.z <= d) { return; }
739
+ let near = unproject(ndc.xy, 0.0);
740
+ let hit = unproject(ndc.xy, d);
741
+ let ray = normalize(hit - near);
742
+ // The normal from pixel centres only: \`hit\` sits at the particle's sub-pixel position with the
743
+ // centre's depth, off the surface by that offset times the depth slope, so a difference taken
744
+ // from it tilts the normal by a fixed amount for a resting particle and the push slid it
745
+ // sideways every frame (0.13 units/s on the fx3 slab, read back by ws/fx-6). Per axis the
746
+ // shorter one-sided difference, so a silhouette neighbour does not bend it.
747
+ let c = scene_at(px, dims);
748
+ let rx = scene_at(px + vec2i(1, 0), dims) - c;
749
+ let lx = c - scene_at(px - vec2i(1, 0), dims);
750
+ let ry = scene_at(px + vec2i(0, 1), dims) - c;
751
+ let ly = c - scene_at(px - vec2i(0, 1), dims);
752
+ let sx = select(lx, rx, dot(rx, rx) < dot(lx, lx));
753
+ let sy = select(ly, ry, dot(ry, ry) < dot(ly, ly));
754
+ var n = cross(sx, sy);
755
+ if (dot(n, n) < 1e-12) { n = -ray; } else { n = normalize(n); }
756
+ if (dot(n, ray) > 0.0) { n = -n; }
757
+ // Out along the surface normal onto the plane through the hit, not back along the view ray:
758
+ // a ray push moves a resting particle toward the camera by its sink depth over tan(grazing
759
+ // angle) every frame, and it creeps off the front edge. dot(hit - pos, n) >= 0 and is at
760
+ // most the depth behind the surface, so a noisy normal cannot throw the particle far.
761
+ var put = p.pos.xyz + n * (max(dot(hit - p.pos.xyz, n), 0.0) + 1e-3);
762
+ if (length(p.pos.xyz - hit) > col.params.z) {
763
+ // Deeper than thickness: a hit only if one step back it was in front of the scene.
764
+ let prev = p.pos.xyz - p.vel.xyz * col.params.w;
765
+ let pc = col.view_proj * vec4f(prev, 1.0);
766
+ if (pc.w <= 1e-5) { return; }
767
+ let pn = pc.xyz / pc.w;
768
+ if (any(abs(pn.xy) > vec2f(1.0)) || pn.z < 0.0 || pn.z > 1.0) { return; }
769
+ let ppx = vec2i(col.viewport.xy + (pn.xy * vec2f(0.5, -0.5) + 0.5) * col.viewport.zw);
770
+ if (pn.z > textureLoad(scene_depth, clamp(ppx, vec2i(0), dims - 1), 0)) { return; }
771
+ put = prev;
772
+ }
773
+ let v = p.vel.xyz;
774
+ let vn = dot(v, n);
775
+ var nv = v;
776
+ if (vn < 0.0) {
777
+ nv = (v - n * vn) * (1.0 - col.params.y) - n * vn * col.params.x;
778
+ }
779
+ p.pos = vec4f(put, p.pos.w);
780
+ p.vel = vec4f(nv, p.vel.w);
781
+ particles[i] = p;
782
+ }
783
+ `;
784
+ /** Copies a depth texture into the particles' own depth32float copy (soft-particle source). */
785
+ export const PARTICLE_DEPTH_COPY_WGSL = /* wgsl */ `
786
+ @group(0) @binding(0) var src: texture_depth_2d;
787
+ @vertex fn vs(@builtin(vertex_index) i: u32) -> @builtin(position) vec4f {
788
+ let p = vec2f(f32((i << 1u) & 2u), f32(i & 2u));
789
+ return vec4f(p * 2.0 - 1.0, 0.0, 1.0);
790
+ }
791
+ @fragment fn fs(@builtin(position) pos: vec4f) -> @builtin(frag_depth) f32 {
792
+ return textureLoad(src, vec2i(pos.xy), 0);
793
+ }
794
+ `;
795
+ /**
796
+ * Counts live particles of either layout: \`stride\` vec4s per particle, \`life\` at vec4 index
797
+ * \`lifeAt\` (age in x, lifetime in y), and among them those whose y (vec4 \`posAt\`) is below
798
+ * \`belowY\`.
799
+ */
800
+ export const PARTICLE_COUNT_WGSL = /* wgsl */ `
801
+ struct CountParams {
802
+ capacity: u32,
803
+ stride: u32,
804
+ lifeAt: u32,
805
+ posAt: u32,
806
+ belowY: f32,
807
+ _a: f32,
808
+ _b: f32,
809
+ _c: f32,
810
+ };
811
+ struct Counts {
812
+ alive: atomic<u32>,
813
+ below: atomic<u32>,
814
+ };
815
+ @group(0) @binding(0) var<storage, read> data: array<vec4f>;
816
+ @group(0) @binding(1) var<storage, read_write> counts: Counts;
817
+ @group(0) @binding(2) var<uniform> params: CountParams;
818
+ @compute @workgroup_size(64) fn count(@builtin(global_invocation_id) gid: vec3u) {
819
+ if (gid.x >= params.capacity) { return; }
820
+ let life = data[gid.x * params.stride + params.lifeAt];
821
+ if (life.x < life.y) {
822
+ atomicAdd(&counts.alive, 1u);
823
+ if (data[gid.x * params.stride + params.posAt].y < params.belowY) {
824
+ atomicAdd(&counts.below, 1u);
825
+ }
826
+ }
827
+ }
828
+ `;