@zephyr3d/scene 0.9.27 → 0.10.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 (502) hide show
  1. package/README.md +60 -27
  2. package/dist/animation/animation_bank.js +634 -0
  3. package/dist/animation/animation_bank.js.map +1 -0
  4. package/dist/animation/animation_bank_gpu.js +256 -0
  5. package/dist/animation/animation_bank_gpu.js.map +1 -0
  6. package/dist/animation/animationset.js +54 -0
  7. package/dist/animation/animationset.js.map +1 -1
  8. package/dist/animation/cloth/gpu_cloth_component.js +406 -0
  9. package/dist/animation/cloth/gpu_cloth_component.js.map +1 -0
  10. package/dist/animation/cloth/gpu_cloth_system.js +332 -202
  11. package/dist/animation/cloth/gpu_cloth_system.js.map +1 -1
  12. package/dist/animation/gpu_animation_driver.js +289 -0
  13. package/dist/animation/gpu_animation_driver.js.map +1 -0
  14. package/dist/animation/joint_dynamics/collision.js +112 -1
  15. package/dist/animation/joint_dynamics/collision.js.map +1 -1
  16. package/dist/animation/joint_dynamics/controller.js +6 -0
  17. package/dist/animation/joint_dynamics/controller.js.map +1 -1
  18. package/dist/animation/joint_dynamics/joint_dynamics_system.js +11 -0
  19. package/dist/animation/joint_dynamics/joint_dynamics_system.js.map +1 -1
  20. package/dist/animation/joint_dynamics/solver.js +50 -12
  21. package/dist/animation/joint_dynamics/solver.js.map +1 -1
  22. package/dist/animation/joint_dynamics/types.js.map +1 -1
  23. package/dist/animation/skeleton.js +279 -55
  24. package/dist/animation/skeleton.js.map +1 -1
  25. package/dist/animation/skin_palette_atlas.js +252 -0
  26. package/dist/animation/skin_palette_atlas.js.map +1 -0
  27. package/dist/animation/spring/multi_chain_spring_system.js +298 -38
  28. package/dist/animation/spring/multi_chain_spring_system.js.map +1 -1
  29. package/dist/animation/spring/spring_chain.js +2 -0
  30. package/dist/animation/spring/spring_chain.js.map +1 -1
  31. package/dist/animation/spring/spring_collider.js +159 -21
  32. package/dist/animation/spring/spring_collider.js.map +1 -1
  33. package/dist/animation/spring/spring_particle.js +1 -0
  34. package/dist/animation/spring/spring_particle.js.map +1 -1
  35. package/dist/animation/spring/spring_solver.js +173 -0
  36. package/dist/animation/spring/spring_solver.js.map +1 -0
  37. package/dist/animation/spring/spring_system.js +249 -24
  38. package/dist/animation/spring/spring_system.js.map +1 -1
  39. package/dist/animation/spring_modifier.js +32 -1
  40. package/dist/animation/spring_modifier.js.map +1 -1
  41. package/dist/app/app.js +26 -0
  42. package/dist/app/app.js.map +1 -1
  43. package/dist/app/engine.js +86 -15
  44. package/dist/app/engine.js.map +1 -1
  45. package/dist/app/gamepad.js +191 -0
  46. package/dist/app/gamepad.js.map +1 -0
  47. package/dist/app/inputactions.js +271 -0
  48. package/dist/app/inputactions.js.map +1 -0
  49. package/dist/app/inputmgr.js +331 -22
  50. package/dist/app/inputmgr.js.map +1 -1
  51. package/dist/app/loading.js +138 -0
  52. package/dist/app/loading.js.map +1 -0
  53. package/dist/app/pipelinecache.js +209 -0
  54. package/dist/app/pipelinecache.js.map +1 -0
  55. package/dist/app/runtimescript.js +25 -0
  56. package/dist/app/runtimescript.js.map +1 -1
  57. package/dist/app/scriptingsystem.js +54 -0
  58. package/dist/app/scriptingsystem.js.map +1 -1
  59. package/dist/app/scriptregistry.js +7 -2
  60. package/dist/app/scriptregistry.js.map +1 -1
  61. package/dist/asset/asset_manifest.js +59 -0
  62. package/dist/asset/asset_manifest.js.map +1 -0
  63. package/dist/asset/asset_preload.js +258 -0
  64. package/dist/asset/asset_preload.js.map +1 -0
  65. package/dist/asset/assetmanager.js +117 -65
  66. package/dist/asset/assetmanager.js.map +1 -1
  67. package/dist/asset/loaders/ktx2/basis/LICENSE +201 -0
  68. package/dist/asset/loaders/ktx2/basis/basis_transcoder.js +2 -0
  69. package/dist/asset/loaders/ktx2/basis/basis_transcoder.wasm +0 -0
  70. package/dist/asset/loaders/ktx2/basis_location.js +17 -0
  71. package/dist/asset/loaders/ktx2/basis_location.js.map +1 -0
  72. package/dist/asset/loaders/ktx2/ktx2_loader.js +88 -0
  73. package/dist/asset/loaders/ktx2/ktx2_loader.js.map +1 -0
  74. package/dist/asset/loaders/ktx2/ktx2_targets.js +118 -0
  75. package/dist/asset/loaders/ktx2/ktx2_targets.js.map +1 -0
  76. package/dist/asset/loaders/ktx2/transcoder_pool.js +259 -0
  77. package/dist/asset/loaders/ktx2/transcoder_pool.js.map +1 -0
  78. package/dist/asset/mesh_lod_builder.js +104 -0
  79. package/dist/asset/mesh_lod_builder.js.map +1 -0
  80. package/dist/asset/mesh_settings.js +111 -0
  81. package/dist/asset/mesh_settings.js.map +1 -0
  82. package/dist/asset/meshopt_decoder.js +18 -0
  83. package/dist/asset/meshopt_decoder.js.map +1 -0
  84. package/dist/asset/meshopt_simplifier.js +26 -0
  85. package/dist/asset/meshopt_simplifier.js.map +1 -0
  86. package/dist/asset/model.js +171 -1
  87. package/dist/asset/model.js.map +1 -1
  88. package/dist/asset/texture_settings.js +231 -0
  89. package/dist/asset/texture_settings.js.map +1 -0
  90. package/dist/asset/zmsh_binary.js +131 -0
  91. package/dist/asset/zmsh_binary.js.map +1 -0
  92. package/dist/asset/zsdf_binary.js +111 -0
  93. package/dist/asset/zsdf_binary.js.map +1 -0
  94. package/dist/blitter/bilateralblur.js +1 -2
  95. package/dist/blitter/bilateralblur.js.map +1 -1
  96. package/dist/blitter/gaussianblur.js +1 -6
  97. package/dist/blitter/gaussianblur.js.map +1 -1
  98. package/dist/camera/base.js +31 -10
  99. package/dist/camera/base.js.map +1 -1
  100. package/dist/camera/camera.js +104 -178
  101. package/dist/camera/camera.js.map +1 -1
  102. package/dist/camera/follow.js +342 -0
  103. package/dist/camera/follow.js.map +1 -0
  104. package/dist/camera/fps.js +9 -7
  105. package/dist/camera/fps.js.map +1 -1
  106. package/dist/camera/orbit.js +46 -19
  107. package/dist/camera/orbit.js.map +1 -1
  108. package/dist/index.d.ts +25436 -15990
  109. package/dist/index.js +65 -11
  110. package/dist/index.js.map +1 -1
  111. package/dist/material/blinn.js +3 -0
  112. package/dist/material/blinn.js.map +1 -1
  113. package/dist/material/eye.js +1 -1
  114. package/dist/material/eye.js.map +1 -1
  115. package/dist/material/hair.js +2 -3
  116. package/dist/material/hair.js.map +1 -1
  117. package/dist/material/lambert.js +4 -1
  118. package/dist/material/lambert.js.map +1 -1
  119. package/dist/material/material.js +66 -4
  120. package/dist/material/material.js.map +1 -1
  121. package/dist/material/meshmaterial.js +118 -45
  122. package/dist/material/meshmaterial.js.map +1 -1
  123. package/dist/material/mixins/lightmodel/blinnphong.js +1 -1
  124. package/dist/material/mixins/lightmodel/blinnphong.js.map +1 -1
  125. package/dist/material/mixins/lightmodel/lambert.js +1 -1
  126. package/dist/material/mixins/lightmodel/lambert.js.map +1 -1
  127. package/dist/material/mixins/lightmodel/pbrblueprintmixin.js +34 -61
  128. package/dist/material/mixins/lightmodel/pbrblueprintmixin.js.map +1 -1
  129. package/dist/material/mixins/lightmodel/pbrmetallicroughness.js +16 -2
  130. package/dist/material/mixins/lightmodel/pbrmetallicroughness.js.map +1 -1
  131. package/dist/material/mixins/lightmodel/pbrspecularglossness.js +2 -1
  132. package/dist/material/mixins/lightmodel/pbrspecularglossness.js.map +1 -1
  133. package/dist/material/mixins/lit.js +191 -83
  134. package/dist/material/mixins/lit.js.map +1 -1
  135. package/dist/material/mixins/pbr/common.js +31 -206
  136. package/dist/material/mixins/pbr/common.js.map +1 -1
  137. package/dist/material/mtoon.js +1 -1
  138. package/dist/material/mtoon.js.map +1 -1
  139. package/dist/material/pbrmr.js +3 -0
  140. package/dist/material/pbrmr.js.map +1 -1
  141. package/dist/material/pbrsg.js +3 -0
  142. package/dist/material/pbrsg.js.map +1 -1
  143. package/dist/material/sdf_debug.js +78 -0
  144. package/dist/material/sdf_debug.js.map +1 -0
  145. package/dist/material/shader/helper.js +333 -191
  146. package/dist/material/shader/helper.js.map +1 -1
  147. package/dist/material/shader/terrain_blend.js +204 -43
  148. package/dist/material/shader/terrain_blend.js.map +1 -1
  149. package/dist/material/skin.js +331 -297
  150. package/dist/material/skin.js.map +1 -1
  151. package/dist/material/skinprofile.js +1058 -0
  152. package/dist/material/skinprofile.js.map +1 -0
  153. package/dist/material/sss.js +353 -0
  154. package/dist/material/sss.js.map +1 -0
  155. package/dist/material/sss_kernel.js +265 -0
  156. package/dist/material/sss_kernel.js.map +1 -0
  157. package/dist/material/sssprofile.js +879 -0
  158. package/dist/material/sssprofile.js.map +1 -0
  159. package/dist/material/subsurfaceprofile.js +4 -8
  160. package/dist/material/subsurfaceprofile.js.map +1 -1
  161. package/dist/material/terrain-cm.js +464 -188
  162. package/dist/material/terrain-cm.js.map +1 -1
  163. package/dist/material/unlit.js +3 -0
  164. package/dist/material/unlit.js.map +1 -1
  165. package/dist/material/water.js +1261 -1438
  166. package/dist/material/water.js.map +1 -1
  167. package/dist/node_modules/meshoptimizer/LICENSE +21 -0
  168. package/dist/node_modules/meshoptimizer/meshopt_decoder.js +197 -0
  169. package/dist/node_modules/meshoptimizer/meshopt_decoder.js.map +1 -0
  170. package/dist/node_modules/meshoptimizer/meshopt_simplifier.js +677 -0
  171. package/dist/node_modules/meshoptimizer/meshopt_simplifier.js.map +1 -0
  172. package/dist/physics/character.js +226 -0
  173. package/dist/physics/character.js.map +1 -0
  174. package/dist/physics/collider.js +209 -0
  175. package/dist/physics/collider.js.map +1 -0
  176. package/dist/physics/events.js +79 -0
  177. package/dist/physics/events.js.map +1 -0
  178. package/dist/physics/joint.js +272 -0
  179. package/dist/physics/joint.js.map +1 -0
  180. package/dist/physics/node_physics.js +234 -0
  181. package/dist/physics/node_physics.js.map +1 -0
  182. package/dist/physics/part.js +91 -0
  183. package/dist/physics/part.js.map +1 -0
  184. package/dist/physics/rigid_body.js +379 -0
  185. package/dist/physics/rigid_body.js.map +1 -0
  186. package/dist/physics/vehicle.js +335 -0
  187. package/dist/physics/vehicle.js.map +1 -0
  188. package/dist/physics/world.js +25 -0
  189. package/dist/physics/world.js.map +1 -0
  190. package/dist/posteffect/bloom.js +49 -14
  191. package/dist/posteffect/bloom.js.map +1 -1
  192. package/dist/posteffect/motionblur.js +106 -106
  193. package/dist/posteffect/motionblur.js.map +1 -1
  194. package/dist/posteffect/postsss.js +572 -0
  195. package/dist/posteffect/postsss.js.map +1 -0
  196. package/dist/posteffect/sao.js +2 -6
  197. package/dist/posteffect/sao.js.map +1 -1
  198. package/dist/posteffect/skinsss.js +605 -355
  199. package/dist/posteffect/skinsss.js.map +1 -1
  200. package/dist/posteffect/ssgi.js +91 -162
  201. package/dist/posteffect/ssgi.js.map +1 -1
  202. package/dist/posteffect/ssr.js +3 -11
  203. package/dist/posteffect/ssr.js.map +1 -1
  204. package/dist/posteffect/sss.js +4 -13
  205. package/dist/posteffect/sss.js.map +1 -1
  206. package/dist/posteffect/taa.js +1 -0
  207. package/dist/posteffect/taa.js.map +1 -1
  208. package/dist/render/abuffer_oit.js +3 -2
  209. package/dist/render/abuffer_oit.js.map +1 -1
  210. package/dist/render/clipmap.js +179 -64
  211. package/dist/render/clipmap.js.map +1 -1
  212. package/dist/render/cluster_light.js +958 -224
  213. package/dist/render/cluster_light.js.map +1 -1
  214. package/dist/render/cull_visitor.js +25 -5
  215. package/dist/render/cull_visitor.js.map +1 -1
  216. package/dist/render/debug_views.js +37 -0
  217. package/dist/render/debug_views.js.map +1 -0
  218. package/dist/render/depthpass.js +5 -1
  219. package/dist/render/depthpass.js.map +1 -1
  220. package/dist/render/drawable_mixin.js +53 -6
  221. package/dist/render/drawable_mixin.js.map +1 -1
  222. package/dist/render/dualdepthpeeling_oit.js +5 -5
  223. package/dist/render/dualdepthpeeling_oit.js.map +1 -1
  224. package/dist/render/envlight.js +196 -113
  225. package/dist/render/envlight.js.map +1 -1
  226. package/dist/render/fbm_wavegenerator.js +5 -10
  227. package/dist/render/fbm_wavegenerator.js.map +1 -1
  228. package/dist/render/fft_wavegenerator.js +247 -157
  229. package/dist/render/fft_wavegenerator.js.map +1 -1
  230. package/dist/render/gerstner_wavegenerator.js +16 -38
  231. package/dist/render/gerstner_wavegenerator.js.map +1 -1
  232. package/dist/render/gi/probe_volume.js +1334 -0
  233. package/dist/render/gi/probe_volume.js.map +1 -0
  234. package/dist/render/gi/scene_gi.js +282 -0
  235. package/dist/render/gi/scene_gi.js.map +1 -0
  236. package/dist/render/gi/voxel_atlas.js +198 -0
  237. package/dist/render/gi/voxel_atlas.js.map +1 -0
  238. package/dist/render/gi/voxel_lighting.js +652 -0
  239. package/dist/render/gi/voxel_lighting.js.map +1 -0
  240. package/dist/render/gi/voxel_scene.js +642 -0
  241. package/dist/render/gi/voxel_scene.js.map +1 -0
  242. package/dist/render/gi/voxelize_pass.js +223 -0
  243. package/dist/render/gi/voxelize_pass.js.map +1 -0
  244. package/dist/render/globalbindgroup_allocator.js +3 -2
  245. package/dist/render/globalbindgroup_allocator.js.map +1 -1
  246. package/dist/render/gpu_picking.js +9 -36
  247. package/dist/render/gpu_picking.js.map +1 -1
  248. package/dist/render/instance_culling.js +499 -0
  249. package/dist/render/instance_culling.js.map +1 -0
  250. package/dist/render/lightpass.js +19 -8
  251. package/dist/render/lightpass.js.map +1 -1
  252. package/dist/render/lod.js +46 -0
  253. package/dist/render/lod.js.map +1 -0
  254. package/dist/render/primitive.js +301 -9
  255. package/dist/render/primitive.js.map +1 -1
  256. package/dist/render/primitive_readback.js +209 -0
  257. package/dist/render/primitive_readback.js.map +1 -0
  258. package/dist/render/render_queue.js +162 -11
  259. package/dist/render/render_queue.js.map +1 -1
  260. package/dist/render/renderer.js +10 -7
  261. package/dist/render/renderer.js.map +1 -1
  262. package/dist/render/rendergraph/blackboard.js +14 -1
  263. package/dist/render/rendergraph/blackboard.js.map +1 -1
  264. package/dist/render/rendergraph/executor.js +136 -0
  265. package/dist/render/rendergraph/executor.js.map +1 -1
  266. package/dist/render/rendergraph/forward_plus_builder.js +272 -55
  267. package/dist/render/rendergraph/forward_plus_builder.js.map +1 -1
  268. package/dist/render/rendergraph/frame_graph_context.js +41 -0
  269. package/dist/render/rendergraph/frame_graph_context.js.map +1 -0
  270. package/dist/render/rendergraph/scene_render_context.js +31 -2
  271. package/dist/render/rendergraph/scene_render_context.js.map +1 -1
  272. package/dist/render/rendergraph/types.js.map +1 -1
  273. package/dist/render/renderpass.js +28 -5
  274. package/dist/render/renderpass.js.map +1 -1
  275. package/dist/render/sdf/box_allocator.js +98 -0
  276. package/dist/render/sdf/box_allocator.js.map +1 -0
  277. package/dist/render/sdf/global_sdf.js +865 -0
  278. package/dist/render/sdf/global_sdf.js.map +1 -0
  279. package/dist/render/sdf/scene_sdf.js +562 -0
  280. package/dist/render/sdf/scene_sdf.js.map +1 -0
  281. package/dist/render/sdf/sdf_atlas.js +227 -0
  282. package/dist/render/sdf/sdf_atlas.js.map +1 -0
  283. package/dist/render/sdf/sdf_programs.js +42 -0
  284. package/dist/render/sdf/sdf_programs.js.map +1 -0
  285. package/dist/render/sdf/sdf_view.js +199 -0
  286. package/dist/render/sdf/sdf_view.js.map +1 -0
  287. package/dist/render/sdf/waterline.js +610 -0
  288. package/dist/render/sdf/waterline.js.map +1 -0
  289. package/dist/render/shadow_mask_pass.js +142 -25
  290. package/dist/render/shadow_mask_pass.js.map +1 -1
  291. package/dist/render/shore_wave_generator.js +1193 -0
  292. package/dist/render/shore_wave_generator.js.map +1 -0
  293. package/dist/render/sky.js +516 -86
  294. package/dist/render/sky.js.map +1 -1
  295. package/dist/render/transmission_thickness_pass.js +477 -0
  296. package/dist/render/transmission_thickness_pass.js.map +1 -0
  297. package/dist/render/underwater.js +58 -158
  298. package/dist/render/underwater.js.map +1 -1
  299. package/dist/render/virtualtexture/virtual_texture.js +1214 -0
  300. package/dist/render/virtualtexture/virtual_texture.js.map +1 -0
  301. package/dist/render/volumetric_cloud/cloud_common.js +472 -0
  302. package/dist/render/volumetric_cloud/cloud_common.js.map +1 -0
  303. package/dist/render/volumetric_cloud/cloud_noise.js +189 -0
  304. package/dist/render/volumetric_cloud/cloud_noise.js.map +1 -0
  305. package/dist/render/volumetric_cloud/debug_view.js +131 -0
  306. package/dist/render/volumetric_cloud/debug_view.js.map +1 -0
  307. package/dist/render/volumetric_cloud/resources.js +380 -0
  308. package/dist/render/volumetric_cloud/resources.js.map +1 -0
  309. package/dist/render/volumetric_cloud/sky_cache.js +380 -0
  310. package/dist/render/volumetric_cloud/sky_cache.js.map +1 -0
  311. package/dist/render/volumetric_cloud/volumetric_cloud.js +318 -0
  312. package/dist/render/volumetric_cloud/volumetric_cloud.js.map +1 -0
  313. package/dist/render/water_caustics.js +134 -319
  314. package/dist/render/water_caustics.js.map +1 -1
  315. package/dist/render/water_foam.js +627 -0
  316. package/dist/render/water_foam.js.map +1 -0
  317. package/dist/render/water_interaction.js +1795 -0
  318. package/dist/render/water_interaction.js.map +1 -0
  319. package/dist/render/weightedblended_oit.js +3 -3
  320. package/dist/render/weightedblended_oit.js.map +1 -1
  321. package/dist/scene/basesprite.js +9 -4
  322. package/dist/scene/basesprite.js.map +1 -1
  323. package/dist/scene/batchgroup.js +108 -12
  324. package/dist/scene/batchgroup.js.map +1 -1
  325. package/dist/scene/environment.js +28 -7
  326. package/dist/scene/environment.js.map +1 -1
  327. package/dist/scene/foliage.js +1020 -0
  328. package/dist/scene/foliage.js.map +1 -0
  329. package/dist/scene/foliage_buckets.js +147 -0
  330. package/dist/scene/foliage_buckets.js.map +1 -0
  331. package/dist/scene/foliage_mask.js +358 -0
  332. package/dist/scene/foliage_mask.js.map +1 -0
  333. package/dist/scene/hair.js +15 -4
  334. package/dist/scene/hair.js.map +1 -1
  335. package/dist/scene/light.js +179 -13
  336. package/dist/scene/light.js.map +1 -1
  337. package/dist/scene/mesh.js +327 -45
  338. package/dist/scene/mesh.js.map +1 -1
  339. package/dist/scene/meshdrawable.js +6 -0
  340. package/dist/scene/meshdrawable.js.map +1 -1
  341. package/dist/scene/msdftext.js +9 -3
  342. package/dist/scene/msdftext.js.map +1 -1
  343. package/dist/scene/msdftextsprite.js +9 -3
  344. package/dist/scene/msdftextsprite.js.map +1 -1
  345. package/dist/scene/particlesys.js +8 -2
  346. package/dist/scene/particlesys.js.map +1 -1
  347. package/dist/scene/physics_settings.js +157 -0
  348. package/dist/scene/physics_settings.js.map +1 -0
  349. package/dist/scene/scene.js +190 -44
  350. package/dist/scene/scene.js.map +1 -1
  351. package/dist/scene/scene_node.js +147 -6
  352. package/dist/scene/scene_node.js.map +1 -1
  353. package/dist/scene/terrain-cm/bladegrassmaterial.js +735 -0
  354. package/dist/scene/terrain-cm/bladegrassmaterial.js.map +1 -0
  355. package/dist/scene/terrain-cm/grass.js +881 -42
  356. package/dist/scene/terrain-cm/grass.js.map +1 -1
  357. package/dist/scene/terrain-cm/grass_gpu.js +929 -0
  358. package/dist/scene/terrain-cm/grass_gpu.js.map +1 -0
  359. package/dist/scene/terrain-cm/grassmaterial.js +180 -9
  360. package/dist/scene/terrain-cm/grassmaterial.js.map +1 -1
  361. package/dist/scene/terrain-cm/terrain-cm.js +465 -36
  362. package/dist/scene/terrain-cm/terrain-cm.js.map +1 -1
  363. package/dist/scene/terrain-cm/terrain_gi_albedo.js +257 -0
  364. package/dist/scene/terrain-cm/terrain_gi_albedo.js.map +1 -0
  365. package/dist/scene/terrain-cm/terrain_rvt.js +395 -0
  366. package/dist/scene/terrain-cm/terrain_rvt.js.map +1 -0
  367. package/dist/scene/update_queue.js +54 -0
  368. package/dist/scene/update_queue.js.map +1 -0
  369. package/dist/scene/water.js +633 -268
  370. package/dist/scene/water.js.map +1 -1
  371. package/dist/scene/water_surface_sampler.js +324 -0
  372. package/dist/scene/water_surface_sampler.js.map +1 -0
  373. package/dist/scene/wind.js +317 -0
  374. package/dist/scene/wind.js.map +1 -0
  375. package/dist/shaders/atmosphere.js +388 -166
  376. package/dist/shaders/atmosphere.js.map +1 -1
  377. package/dist/shaders/fog.js +100 -32
  378. package/dist/shaders/fog.js.map +1 -1
  379. package/dist/shaders/gi.js +365 -0
  380. package/dist/shaders/gi.js.map +1 -0
  381. package/dist/shaders/ltc_rect.js +202 -0
  382. package/dist/shaders/ltc_rect.js.map +1 -0
  383. package/dist/shaders/misc.js +1 -10
  384. package/dist/shaders/misc.js.map +1 -1
  385. package/dist/shaders/noise.js +5 -1
  386. package/dist/shaders/noise.js.map +1 -1
  387. package/dist/shaders/probes.js +309 -0
  388. package/dist/shaders/probes.js.map +1 -0
  389. package/dist/shaders/sdf.js +537 -0
  390. package/dist/shaders/sdf.js.map +1 -0
  391. package/dist/shaders/shadow.js +104 -70
  392. package/dist/shaders/shadow.js.map +1 -1
  393. package/dist/shaders/skin_brdf.js +243 -0
  394. package/dist/shaders/skin_brdf.js.map +1 -0
  395. package/dist/shaders/ssgi.js +63 -0
  396. package/dist/shaders/ssgi.js.map +1 -0
  397. package/dist/shaders/ssgi_reprojection.js +118 -0
  398. package/dist/shaders/ssgi_reprojection.js.map +1 -0
  399. package/dist/shaders/ssr.js +6 -159
  400. package/dist/shaders/ssr.js.map +1 -1
  401. package/dist/shaders/temporal.js +38 -27
  402. package/dist/shaders/temporal.js.map +1 -1
  403. package/dist/shaders/water.js +32 -82
  404. package/dist/shaders/water.js.map +1 -1
  405. package/dist/shaders/water_medium.js +7 -21
  406. package/dist/shaders/water_medium.js.map +1 -1
  407. package/dist/shadow/dom.js +110 -19
  408. package/dist/shadow/dom.js.map +1 -1
  409. package/dist/shadow/esm.js +3 -3
  410. package/dist/shadow/esm.js.map +1 -1
  411. package/dist/shadow/pcf_opt.js +15 -48
  412. package/dist/shadow/pcf_opt.js.map +1 -1
  413. package/dist/shadow/pcf_pd.js +15 -53
  414. package/dist/shadow/pcf_pd.js.map +1 -1
  415. package/dist/shadow/pcss.js +34 -9
  416. package/dist/shadow/pcss.js.map +1 -1
  417. package/dist/shadow/shadow_impl.js +3 -8
  418. package/dist/shadow/shadow_impl.js.map +1 -1
  419. package/dist/shadow/shadow_region.js +40 -2
  420. package/dist/shadow/shadow_region.js.map +1 -1
  421. package/dist/shadow/shadowmapper.js +93 -53
  422. package/dist/shadow/shadowmapper.js.map +1 -1
  423. package/dist/shadow/ssm.js +18 -72
  424. package/dist/shadow/ssm.js.map +1 -1
  425. package/dist/shadow/vsm.js +3 -3
  426. package/dist/shadow/vsm.js.map +1 -1
  427. package/dist/shapes/shape.js +44 -1
  428. package/dist/shapes/shape.js.map +1 -1
  429. package/dist/utility/buoyancy.js +287 -0
  430. package/dist/utility/buoyancy.js.map +1 -0
  431. package/dist/utility/physical.js +10 -1
  432. package/dist/utility/physical.js.map +1 -1
  433. package/dist/utility/pmrem.js +17 -43
  434. package/dist/utility/pmrem.js.map +1 -1
  435. package/dist/utility/sdf/bake.js +435 -0
  436. package/dist/utility/sdf/bake.js.map +1 -0
  437. package/dist/utility/sdf/bvh.js +576 -0
  438. package/dist/utility/sdf/bvh.js.map +1 -0
  439. package/dist/utility/serialization/manager.js +34 -6
  440. package/dist/utility/serialization/manager.js.map +1 -1
  441. package/dist/utility/serialization/scene/animation.js +46 -5
  442. package/dist/utility/serialization/scene/animation.js.map +1 -1
  443. package/dist/utility/serialization/scene/batch.js +11 -4
  444. package/dist/utility/serialization/scene/batch.js.map +1 -1
  445. package/dist/utility/serialization/scene/camera.js +87 -254
  446. package/dist/utility/serialization/scene/camera.js.map +1 -1
  447. package/dist/utility/serialization/scene/cloth.js +25 -0
  448. package/dist/utility/serialization/scene/cloth.js.map +1 -0
  449. package/dist/utility/serialization/scene/common.js +14 -6
  450. package/dist/utility/serialization/scene/common.js.map +1 -1
  451. package/dist/utility/serialization/scene/foliage.js +821 -0
  452. package/dist/utility/serialization/scene/foliage.js.map +1 -0
  453. package/dist/utility/serialization/scene/hair.js +11 -4
  454. package/dist/utility/serialization/scene/hair.js.map +1 -1
  455. package/dist/utility/serialization/scene/light.js +115 -7
  456. package/dist/utility/serialization/scene/light.js.map +1 -1
  457. package/dist/utility/serialization/scene/material.js +388 -220
  458. package/dist/utility/serialization/scene/material.js.map +1 -1
  459. package/dist/utility/serialization/scene/mesh.js +69 -4
  460. package/dist/utility/serialization/scene/mesh.js.map +1 -1
  461. package/dist/utility/serialization/scene/node.js +140 -5
  462. package/dist/utility/serialization/scene/node.js.map +1 -1
  463. package/dist/utility/serialization/scene/node_physics.js +1318 -0
  464. package/dist/utility/serialization/scene/node_physics.js.map +1 -0
  465. package/dist/utility/serialization/scene/particle.js +11 -4
  466. package/dist/utility/serialization/scene/particle.js.map +1 -1
  467. package/dist/utility/serialization/scene/physics.js +120 -0
  468. package/dist/utility/serialization/scene/physics.js.map +1 -0
  469. package/dist/utility/serialization/scene/primitive.js +124 -10
  470. package/dist/utility/serialization/scene/primitive.js.map +1 -1
  471. package/dist/utility/serialization/scene/scene.js +1687 -51
  472. package/dist/utility/serialization/scene/scene.js.map +1 -1
  473. package/dist/utility/serialization/scene/sprite.js +11 -4
  474. package/dist/utility/serialization/scene/sprite.js.map +1 -1
  475. package/dist/utility/serialization/scene/terrain.js +163 -33
  476. package/dist/utility/serialization/scene/terrain.js.map +1 -1
  477. package/dist/utility/serialization/scene/text.js +11 -4
  478. package/dist/utility/serialization/scene/text.js.map +1 -1
  479. package/dist/utility/serialization/scene/water.js +1543 -264
  480. package/dist/utility/serialization/scene/water.js.map +1 -1
  481. package/dist/utility/serialization/types.js.map +1 -1
  482. package/dist/utility/shprojector.js +10 -22
  483. package/dist/utility/shprojector.js.map +1 -1
  484. package/dist/utility/textures/foam.js +170 -0
  485. package/dist/utility/textures/foam.js.map +1 -0
  486. package/dist/utility/textures/ggxlut.js +17 -43
  487. package/dist/utility/textures/ggxlut.js.map +1 -1
  488. package/dist/utility/textures/ltcdata.js +4 -4
  489. package/dist/utility/textures/ltcdata.js.map +1 -1
  490. package/dist/utility/textures/ltclut.js +16 -6
  491. package/dist/utility/textures/ltclut.js.map +1 -1
  492. package/dist/utility/unsupported.js +24 -0
  493. package/dist/utility/unsupported.js.map +1 -0
  494. package/dist/values.js +13 -1
  495. package/dist/values.js.map +1 -1
  496. package/package.json +25 -12
  497. package/src/asset/loaders/ktx2/basis/LICENSE +201 -0
  498. package/src/asset/loaders/ktx2/basis/basis_transcoder.js +2 -0
  499. package/src/asset/loaders/ktx2/basis/basis_transcoder.wasm +0 -0
  500. package/src/asset/meshoptimizer/LICENSE +21 -0
  501. package/dist/animation/joint_dynamics/convex_collider.js +0 -320
  502. package/dist/animation/joint_dynamics/convex_collider.js.map +0 -1
@@ -0,0 +1,1795 @@
1
+ import { Vector2, Vector3, Disposable, Vector4, halfToFloat } from '@zephyr3d/base';
2
+ import { getDevice } from '../app/api.js';
3
+ import { fetchSampler } from '../utility/misc.js';
4
+ import { drawFullscreenQuad } from './fullscreenquad.js';
5
+ import { ShaderHelper } from '../material/shader/helper.js';
6
+ import { STANDARD_GRAVITY } from '../utility/buoyancy.js';
7
+
8
+ /** Most impulses one simulation step can take. @internal */ const MAX_IMPULSES = 16;
9
+ /** Simulation step, in seconds. @internal */ const FIXED_STEP = 1 / 60;
10
+ /** Most steps run in one frame; the rest of the backlog is dropped. @internal */ const MAX_SUBSTEPS = 4;
11
+ /** Largest frame delta accepted, so a stall does not turn into a burst of steps. @internal */ const MAX_FRAME_DELTA = 0.25;
12
+ /**
13
+ * Upper bound on `c * dt / dx` for the explicit scheme to stay stable in 2D
14
+ * (the CFL condition is `1 / sqrt(2)`; this leaves a margin). @internal
15
+ */ const MAX_COURANT = 0.6;
16
+ /** Window shift for steps after the first in a frame. @internal */ const ZERO_SHIFT = new Vector2(0, 0);
17
+ /** Texture formats tried in order for the field: height, previous height, foam. @internal */ const HEIGHT_FORMATS = [
18
+ 'rgba32f',
19
+ 'rgba16f'
20
+ ];
21
+ /** Most disturbers the field can carry at once. @internal */ const MAX_DISTURBERS = 16;
22
+ /** Floats per footprint: three vec4. @internal */ const FOOTPRINT_STRIDE = 12;
23
+ /** Floats per disturber in the uniform array: the current and the previous footprint. @internal */ const DISTURBER_STRIDE = FOOTPRINT_STRIDE * 2;
24
+ /** Vec4 per disturber in the uniform array. @internal */ const DISTURBER_VEC4S = DISTURBER_STRIDE / 4;
25
+ /** Footprint type codes. A blocking footprint adds {@link BLOCKING_FLAG}. @internal */ const SHAPE_SPHERE = 1;
26
+ const SHAPE_CAPSULE = 2;
27
+ const SHAPE_BOX = 3;
28
+ const BLOCKING_FLAG = 10;
29
+ /**
30
+ * Something that disturbs a water surface by moving through it.
31
+ *
32
+ * Follows a scene node and describes the volume the node occupies with one of
33
+ * three shapes. Each frame the field works out how much water column the shape
34
+ * displaces at every texel - its footprint on the surface, weighted by how deep
35
+ * it sits - and injects the change since the previous frame: something at rest
36
+ * leaves the water alone, something moving leaves a wake.
37
+ *
38
+ * A `blocking` disturber is also an obstacle: waves stop at its footprint and
39
+ * reflect off it, the way ripples do off a piling.
40
+ *
41
+ * @public
42
+ */ class WaterDisturber {
43
+ /** Node the shape follows, or null until {@link nodeId} has been resolved. */ node;
44
+ /**
45
+ * Persistent id of the node the shape follows, for a disturber loaded from a
46
+ * scene before the node it refers to exists. Resolved against the water's
47
+ * scene on the next update; {@link node} wins when both are set.
48
+ */ nodeId;
49
+ /** Footprint shape. */ shape;
50
+ /** Radius in metres, for `sphere` and `capsule`. Scaled by the node. */ radius;
51
+ /** Half the segment length in metres, for `capsule`, along the node's local Y axis. */ halfLength;
52
+ /** Extents in metres, for `box`, along the node's local axes. */ size;
53
+ /**
54
+ * Water column displaced by the fully submerged shape, in metres of surface
55
+ * height. Scales how hard the disturber pushes; not a physical volume.
56
+ */ strength;
57
+ /** Whether waves stop at and reflect off the footprint. */ blocking;
58
+ /**
59
+ * Whether the shape floats, as a boat hull does: it then displaces
60
+ * {@link strength} whatever its height against the surface, the way a
61
+ * floating body displaces its own weight however it bobs, and leaves a
62
+ * wake only by moving across the water. Otherwise how much it displaces
63
+ * follows how deep it sits, so a hull bobbing out of step with the waves
64
+ * pumps the field and its wake drowns in that.
65
+ */ floating;
66
+ /** Whether the disturber is taken into account. */ enabled;
67
+ /** @internal */ _hasPrev;
68
+ /** Slot the disturber took in the last step, whose surface height it reads back. @internal */ _slot;
69
+ /** @internal */ _now;
70
+ /** @internal */ _prev;
71
+ /**
72
+ * Creates a disturber.
73
+ * @param node - Node the shape follows.
74
+ * @param shape - Footprint shape. Defaults to `sphere`.
75
+ */ constructor(node = null, shape = 'sphere'){
76
+ this.node = node;
77
+ this.nodeId = '';
78
+ this.shape = shape;
79
+ this.radius = 0.5;
80
+ this.halfLength = 0.5;
81
+ this.size = new Vector3(1, 1, 1);
82
+ this.strength = 0.15;
83
+ this.blocking = false;
84
+ this.floating = false;
85
+ this.enabled = true;
86
+ this._hasPrev = false;
87
+ this._slot = -1;
88
+ this._now = new Float32Array(FOOTPRINT_STRIDE);
89
+ this._prev = new Float32Array(FOOTPRINT_STRIDE);
90
+ }
91
+ /**
92
+ * Evaluate the footprint into `_now`, its heights relative to the still
93
+ * water at `waterLevel`. How deep it sits is worked out on the GPU against
94
+ * the surface it floats on (see `wiFootprint`), which the ambient waves
95
+ * raise and lower under it.
96
+ *
97
+ * Layout: `(type, cx, cz, 1)`, `(p0, p1, p2, p3)` with the shape-specific
98
+ * parameters in world XZ metres (a sphere's full radius in p0),
99
+ * `(bottom, top, centre y, strength)`. The previous footprint's first `w`
100
+ * says whether the surface height read back for its slot is its own.
101
+ * @internal
102
+ */ _evaluate(waterLevel) {
103
+ const m = this.node.worldMatrix;
104
+ const out = this._now;
105
+ const flags = this.blocking ? BLOCKING_FLAG : 0;
106
+ const cx = m.m03;
107
+ const cy = m.m13 - waterLevel;
108
+ const cz = m.m23;
109
+ // Node scale, from the world matrix columns.
110
+ const sx = Math.hypot(m.m00, m.m10, m.m20);
111
+ const sy = Math.hypot(m.m01, m.m11, m.m21);
112
+ const sz = Math.hypot(m.m02, m.m12, m.m22);
113
+ out[3] = 1;
114
+ out[10] = cy;
115
+ out[11] = this.strength;
116
+ if (this.shape === 'sphere') {
117
+ const r = this.radius * Math.max(sx, sy, sz);
118
+ out[0] = SHAPE_SPHERE + flags;
119
+ out[1] = cx;
120
+ out[2] = cz;
121
+ out[4] = r;
122
+ out[5] = 0;
123
+ out[6] = 0;
124
+ out[7] = 0;
125
+ out[8] = cy - r;
126
+ out[9] = cy + r;
127
+ } else if (this.shape === 'capsule') {
128
+ const r = this.radius * Math.max(sx, sz);
129
+ // Endpoints of the segment along local Y; the column carries the scale.
130
+ const ax = cx - m.m01 * this.halfLength;
131
+ const ay = cy - m.m11 * this.halfLength;
132
+ const az = cz - m.m21 * this.halfLength;
133
+ const bx = cx + m.m01 * this.halfLength;
134
+ const by = cy + m.m11 * this.halfLength;
135
+ const bz = cz + m.m21 * this.halfLength;
136
+ out[0] = SHAPE_CAPSULE + flags;
137
+ out[1] = ax;
138
+ out[2] = az;
139
+ out[4] = bx;
140
+ out[5] = bz;
141
+ out[6] = r;
142
+ out[7] = 0;
143
+ out[8] = Math.min(ay, by) - r;
144
+ out[9] = Math.max(ay, by) + r;
145
+ } else {
146
+ // Half extents along the world-space columns.
147
+ const hx = this.size.x * 0.5;
148
+ const hy = this.size.y * 0.5;
149
+ const hz = this.size.z * 0.5;
150
+ const cxx = m.m00 * hx;
151
+ const cxy = m.m10 * hx;
152
+ const cxz = m.m20 * hx;
153
+ const cyx = m.m01 * hy;
154
+ const cyy = m.m11 * hy;
155
+ const cyz = m.m21 * hy;
156
+ const czx = m.m02 * hz;
157
+ const czy = m.m12 * hz;
158
+ const czz = m.m22 * hz;
159
+ const yExtent = Math.abs(cxy) + Math.abs(cyy) + Math.abs(czy);
160
+ // The footprint frame: the local X axis projected to the plane, or the
161
+ // local Z axis when X points straight up or down.
162
+ let ux = cxx;
163
+ let uz = cxz;
164
+ let len = Math.hypot(ux, uz);
165
+ if (len < 1e-6) {
166
+ ux = czx;
167
+ uz = czz;
168
+ len = Math.hypot(ux, uz);
169
+ }
170
+ if (len < 1e-6) {
171
+ ux = 1;
172
+ uz = 0;
173
+ len = 1;
174
+ }
175
+ ux /= len;
176
+ uz /= len;
177
+ const vx = -uz;
178
+ const vz = ux;
179
+ // Extent of the projected box along u and v: the sum of each column's
180
+ // projection, which is exact for a box.
181
+ const eu = Math.abs(cxx * ux + cxz * uz) + Math.abs(cyx * ux + cyz * uz) + Math.abs(czx * ux + czz * uz);
182
+ const ev = Math.abs(cxx * vx + cxz * vz) + Math.abs(cyx * vx + cyz * vz) + Math.abs(czx * vx + czz * vz);
183
+ out[0] = SHAPE_BOX + flags;
184
+ out[1] = cx;
185
+ out[2] = cz;
186
+ out[4] = ux;
187
+ out[5] = uz;
188
+ out[6] = Math.max(eu, 1e-3);
189
+ out[7] = Math.max(ev, 1e-3);
190
+ out[8] = cy - yExtent;
191
+ out[9] = cy + yExtent;
192
+ }
193
+ if (this.floating) {
194
+ // Bottom over top, which no shape has, marks a floating one
195
+ out[8] = 1;
196
+ out[9] = 0;
197
+ }
198
+ }
199
+ /** World XZ of the footprint's centre, where the surface under it is read. @internal */ _centre(fp, out, offset) {
200
+ if (fp[0] % BLOCKING_FLAG === SHAPE_CAPSULE) {
201
+ out[offset] = (fp[1] + fp[4]) * 0.5;
202
+ out[offset + 1] = (fp[2] + fp[5]) * 0.5;
203
+ } else {
204
+ out[offset] = fp[1];
205
+ out[offset + 1] = fp[2];
206
+ }
207
+ }
208
+ }
209
+ /**
210
+ * Declare the footprint functions the simulation shaders share.
211
+ *
212
+ * `wiFootprint(a, b, c, s, wp)` is the displaced column at world XZ `wp`, in
213
+ * metres of surface height, with the surface under the shape at `s` over the
214
+ * still water: the shape's amplitude times a dome over its footprint, so a
215
+ * moving shape's frame-to-frame difference is smooth. The amplitude is the
216
+ * strength times how much of the shape's height is under that surface, faded
217
+ * back out as the whole shape sinks further than its own height below it; a
218
+ * floating shape (bottom over top) displaces the full strength however it
219
+ * sits. A sphere's footprint is the circle the surface cuts from it. Measured against
220
+ * the surface the shape floats on rather than the still water, so a hull riding
221
+ * the swell does not pump rings into the field with every wave.
222
+ * `wiFootprintHard` is the footprint as a 0/1 mask, for the obstacle map. Both
223
+ * decode the packed layout {@link WaterDisturber._evaluate} writes.
224
+ * @internal
225
+ */ function declareFootprintFunctions(pb) {
226
+ pb.func('wiShapeMask', [
227
+ pb.vec4('a'),
228
+ pb.vec4('b'),
229
+ pb.vec4('c'),
230
+ pb.float('s'),
231
+ pb.vec2('wp'),
232
+ pb.bool('hard')
233
+ ], function() {
234
+ this.$l.t = pb.sub(this.a.x, pb.select(pb.float(0), pb.float(BLOCKING_FLAG), pb.greaterThanEqual(this.a.x, BLOCKING_FLAG)));
235
+ this.$l.m = pb.float(0);
236
+ this.$if(pb.lessThan(this.t, SHAPE_SPHERE + 0.5), function() {
237
+ this.$l.dv = pb.sub(this.wp, this.a.yz);
238
+ this.$l.d2 = pb.dot(this.dv, this.dv);
239
+ // Radius of the circle the surface cuts from the sphere
240
+ this.$l.dy = pb.sub(this.c.z, this.s);
241
+ this.$l.r = this.b.x;
242
+ this.$l.rEff = pb.select(pb.sqrt(pb.max(0, pb.sub(pb.mul(this.r, this.r), pb.mul(this.dy, this.dy)))), this.r, pb.lessThanEqual(this.dy, pb.neg(this.r)));
243
+ this.$l.r2 = pb.mul(this.rEff, this.rEff);
244
+ this.$if(pb.greaterThan(this.r2, 0), function() {
245
+ this.m = pb.select(pb.max(0, pb.sub(1, pb.div(this.d2, this.r2))), pb.select(pb.float(0), pb.float(1), pb.lessThan(this.d2, this.r2)), this.hard);
246
+ });
247
+ }).$elseif(pb.lessThan(this.t, SHAPE_CAPSULE + 0.5), function() {
248
+ this.$l.ab = pb.sub(this.b.xy, this.a.yz);
249
+ this.$l.ap = pb.sub(this.wp, this.a.yz);
250
+ this.$l.s = pb.clamp(pb.div(pb.dot(this.ap, this.ab), pb.max(pb.dot(this.ab, this.ab), 1e-6)), 0, 1);
251
+ this.$l.dv = pb.sub(this.ap, pb.mul(this.ab, this.s));
252
+ this.$l.d2 = pb.dot(this.dv, this.dv);
253
+ this.$l.r2 = pb.mul(this.b.z, this.b.z);
254
+ this.m = pb.select(pb.max(0, pb.sub(1, pb.div(this.d2, this.r2))), pb.select(pb.float(0), pb.float(1), pb.lessThan(this.d2, this.r2)), this.hard);
255
+ }).$else(function() {
256
+ this.$l.dv = pb.sub(this.wp, this.a.yz);
257
+ this.$l.u = this.b.xy;
258
+ this.$l.v = pb.vec2(pb.neg(this.u.y), this.u.x);
259
+ this.$l.du = pb.div(pb.abs(pb.dot(this.dv, this.u)), this.b.z);
260
+ this.$l.dw = pb.div(pb.abs(pb.dot(this.dv, this.v)), this.b.w);
261
+ this.$l.mx = pb.max(this.du, this.dw);
262
+ this.m = pb.select(pb.sub(1, pb.smoothStep(0.75, 1, this.mx)), pb.select(pb.float(0), pb.float(1), pb.lessThan(this.mx, 1)), this.hard);
263
+ });
264
+ this.$return(this.m);
265
+ });
266
+ pb.func('wiFootprint', [
267
+ pb.vec4('a'),
268
+ pb.vec4('b'),
269
+ pb.vec4('c'),
270
+ pb.float('s'),
271
+ pb.vec2('wp')
272
+ ], function() {
273
+ this.$l.height = pb.max(pb.sub(this.c.y, this.c.x), 1e-4);
274
+ this.$l.depth = pb.clamp(pb.div(pb.sub(this.s, this.c.x), this.height), 0, 1);
275
+ this.$l.deep = pb.clamp(pb.sub(1, pb.div(pb.sub(this.s, this.c.y), this.height)), 0, 1);
276
+ this.$l.immersion = pb.select(pb.mul(this.depth, this.deep), pb.float(1), pb.greaterThan(this.c.x, this.c.y));
277
+ this.$return(pb.mul(this.c.w, this.immersion, this.wiShapeMask(this.a, this.b, this.c, this.s, this.wp, false)));
278
+ });
279
+ pb.func('wiFootprintHard', [
280
+ pb.vec4('a'),
281
+ pb.vec4('b'),
282
+ pb.vec4('c'),
283
+ pb.float('s'),
284
+ pb.vec2('wp')
285
+ ], function() {
286
+ this.$return(this.wiShapeMask(this.a, this.b, this.c, this.s, this.wp, true));
287
+ });
288
+ }
289
+ /**
290
+ * Shader that advances the interaction field by one step: scrolls the window,
291
+ * integrates the wave equation with the sponge band and obstacles, and injects
292
+ * the queued impulses and the disturbers' displacement change.
293
+ * @internal
294
+ */ function createWaterInteractionStepShader() {
295
+ return {
296
+ vertex (pb) {
297
+ this.$inputs.pos = pb.vec2().attrib('position');
298
+ pb.main(function() {
299
+ this.$builtins.position = pb.vec4(this.$inputs.pos, 0, 1);
300
+ });
301
+ },
302
+ fragment (pb) {
303
+ this.$outputs.outColor = pb.vec4();
304
+ this.srcTex = pb.tex2D().uniform(0);
305
+ this.obstacleTex = pb.tex2D().uniform(0);
306
+ this.stepParams = pb.vec4().uniform(0);
307
+ // Foam: (rate * dt, per-step retention, kinetic energy per unit mass at
308
+ // the lower and upper speed), (breaking acceleration lower and upper
309
+ // bound, diffusion per step in texels squared, 1 / dt)
310
+ this.foamParams = pb.vec4().uniform(0);
311
+ this.foamParams2 = pb.vec4().uniform(0);
312
+ this.worldParams = pb.vec4().uniform(0);
313
+ this.resolution = pb.int().uniform(0);
314
+ this.shift = pb.vec2().uniform(0);
315
+ this.numImpulses = pb.int().uniform(0);
316
+ this.impulses = pb.vec4[MAX_IMPULSES]().uniform(0);
317
+ this.numDisturbers = pb.int().uniform(0);
318
+ this.disturbers = pb.vec4[MAX_DISTURBERS * DISTURBER_VEC4S]().uniform(0);
319
+ // Height of the ambient surface over the still water under each
320
+ // disturber, at this step and at the last
321
+ this.surfNow = pb.tex2D().uniform(0);
322
+ this.surfPrev = pb.tex2D().uniform(0);
323
+ // Water depth for the wave speed, see WaterInteraction.setBathymetry:
324
+ // (origin x, origin z, 1 / size x, 1 / size z),
325
+ // (source: 0 none / 1 distance field / 2 height map, water level, height scale, height offset),
326
+ // (shallow depth, absorb depth, 0, 0)
327
+ this.bathyTex = pb.tex2D().uniform(0);
328
+ this.bathyParams = pb.vec4().uniform(0);
329
+ this.bathyParams2 = pb.vec4().uniform(0);
330
+ this.shoreParams = pb.vec4().uniform(0);
331
+ declareFootprintFunctions(pb);
332
+ // Depth of water at a world XZ, a large value where nothing is known
333
+ pb.func('wiDepth', [
334
+ pb.vec2('xz')
335
+ ], function() {
336
+ this.$l.uv = pb.mul(pb.sub(this.xz, this.bathyParams.xy), this.bathyParams.zw);
337
+ this.$if(pb.or(pb.lessThan(this.bathyParams2.x, 0.5), pb.or(pb.any(pb.lessThan(this.uv, pb.vec2(0))), pb.any(pb.greaterThan(this.uv, pb.vec2(1))))), function() {
338
+ this.$return(pb.float(1e4));
339
+ });
340
+ this.$l.t = pb.textureSampleLevel(this.bathyTex, this.uv, 0);
341
+ // The distance field at the rest level is the distance to the bed below
342
+ // it: the depth on a gently shelving bed, a little under on a steep one
343
+ this.$if(pb.lessThan(this.bathyParams2.x, 1.5), function() {
344
+ this.$return(this.t.w);
345
+ });
346
+ this.$return(pb.sub(this.bathyParams2.y, pb.add(pb.mul(this.t.x, this.bathyParams2.z), this.bathyParams2.w)));
347
+ });
348
+ // Squared wave speed relative to the configured one: 1 in water deeper
349
+ // than the shallow depth, falling with the depth below it, as c = sqrt(g h)
350
+ pb.func('wiSpeed2', [
351
+ pb.float('depth')
352
+ ], function() {
353
+ this.$if(pb.lessThan(this.bathyParams2.x, 0.5), function() {
354
+ this.$return(pb.float(1));
355
+ });
356
+ this.$return(pb.clamp(pb.div(this.depth, pb.max(this.shoreParams.x, 1e-3)), 0, 1));
357
+ });
358
+ pb.func('wiInside', [
359
+ pb.ivec2('c')
360
+ ], function() {
361
+ this.$return(pb.and(pb.all(pb.greaterThanEqual(this.c, pb.ivec2(0))), pb.all(pb.lessThan(this.c, pb.ivec2(this.resolution)))));
362
+ });
363
+ pb.func('wiFetch', [
364
+ pb.ivec2('c'),
365
+ pb.float('fallback')
366
+ ], function() {
367
+ this.$if(this.wiInside(this.c), function() {
368
+ this.$return(pb.textureLoad(this.srcTex, this.c, 0).x);
369
+ }).$else(function() {
370
+ this.$return(this.fallback);
371
+ });
372
+ });
373
+ // An obstacle texel, in the window's new placement. Outside the window
374
+ // counts as one so that the edge and an obstacle get the same treatment.
375
+ pb.func('wiObstacle', [
376
+ pb.ivec2('c')
377
+ ], function() {
378
+ this.$if(this.wiInside(this.c), function() {
379
+ this.$return(pb.greaterThan(pb.textureLoad(this.obstacleTex, this.c, 0).x, 0.5));
380
+ }).$else(function() {
381
+ this.$return(true);
382
+ });
383
+ });
384
+ // A neighbour's height: `cur` when it is an obstacle (Neumann, so the
385
+ // wave reflects), else its own value from the field before the shift.
386
+ pb.func('wiNeighbour', [
387
+ pb.ivec2('pn'),
388
+ pb.ivec2('qn'),
389
+ pb.float('cur')
390
+ ], function() {
391
+ this.$if(this.wiObstacle(this.pn), function() {
392
+ this.$return(this.cur);
393
+ }).$else(function() {
394
+ this.$return(this.wiFetch(this.qn, this.cur));
395
+ });
396
+ });
397
+ // A neighbour's foam, the texel's own across an obstacle or the window
398
+ // edge so that none spreads into or out of them
399
+ pb.func('wiNeighbourFoam', [
400
+ pb.ivec2('pn'),
401
+ pb.ivec2('qn'),
402
+ pb.float('own')
403
+ ], function() {
404
+ this.$if(pb.or(this.wiObstacle(this.pn), pb.not(this.wiInside(this.qn))), function() {
405
+ this.$return(this.own);
406
+ });
407
+ this.$return(pb.textureLoad(this.srcTex, this.qn, 0).z);
408
+ });
409
+ // Ihmsen et al.'s potential, eq. (8): 0 below lo, 1 above hi, linear between
410
+ pb.func('wiPotential', [
411
+ pb.float('i'),
412
+ pb.float('lo'),
413
+ pb.float('hi')
414
+ ], function() {
415
+ this.$return(pb.div(pb.sub(pb.min(this.i, this.hi), pb.min(this.i, this.lo)), pb.max(pb.sub(this.hi, this.lo), 1e-6)));
416
+ });
417
+ pb.main(function() {
418
+ this.$l.p = pb.ivec2(this.$builtins.fragCoord.xy);
419
+ // Where this texel was in the field before the window moved. Texels
420
+ // that scrolled in from outside read as still water.
421
+ this.$l.q = pb.add(this.p, pb.ivec2(this.shift));
422
+ this.$l.cur = pb.float(0);
423
+ this.$l.prev = pb.float(0);
424
+ this.$l.foam = pb.float(0);
425
+ this.$if(this.wiInside(this.q), function() {
426
+ this.$l.s = pb.textureLoad(this.srcTex, this.q, 0).xyz;
427
+ this.cur = this.s.x;
428
+ this.prev = this.s.y;
429
+ this.foam = this.s.z;
430
+ });
431
+ this.$l.l = this.wiNeighbour(pb.add(this.p, pb.ivec2(-1, 0)), pb.add(this.q, pb.ivec2(-1, 0)), this.cur);
432
+ this.$l.r = this.wiNeighbour(pb.add(this.p, pb.ivec2(1, 0)), pb.add(this.q, pb.ivec2(1, 0)), this.cur);
433
+ this.$l.d = this.wiNeighbour(pb.add(this.p, pb.ivec2(0, -1)), pb.add(this.q, pb.ivec2(0, -1)), this.cur);
434
+ this.$l.u = this.wiNeighbour(pb.add(this.p, pb.ivec2(0, 1)), pb.add(this.q, pb.ivec2(0, 1)), this.cur);
435
+ // Flux form of div(c^2 grad h): each face carries the mean of the
436
+ // speeds either side, so a wave crossing into shallower water slows
437
+ // and steepens without gaining or losing energy at the change. With
438
+ // no depth every speed is 1 and this is the plain Laplacian.
439
+ this.$l.texelW = this.worldParams.z;
440
+ this.$l.wpc = pb.add(this.worldParams.xy, pb.mul(pb.add(pb.vec2(this.p), pb.vec2(0.5)), this.texelW));
441
+ this.$l.depthC = this.wiDepth(this.wpc);
442
+ this.$l.sC = this.wiSpeed2(this.depthC);
443
+ this.$l.sL = this.wiSpeed2(this.wiDepth(pb.sub(this.wpc, pb.vec2(this.texelW, 0))));
444
+ this.$l.sR = this.wiSpeed2(this.wiDepth(pb.add(this.wpc, pb.vec2(this.texelW, 0))));
445
+ this.$l.sD = this.wiSpeed2(this.wiDepth(pb.sub(this.wpc, pb.vec2(0, this.texelW))));
446
+ this.$l.sU = this.wiSpeed2(this.wiDepth(pb.add(this.wpc, pb.vec2(0, this.texelW))));
447
+ this.$l.lap = pb.mul(0.5, pb.add(pb.mul(pb.add(this.sC, this.sL), pb.sub(this.l, this.cur)), pb.mul(pb.add(this.sC, this.sR), pb.sub(this.r, this.cur)), pb.mul(pb.add(this.sC, this.sD), pb.sub(this.d, this.cur)), pb.mul(pb.add(this.sC, this.sU), pb.sub(this.u, this.cur))));
448
+ // Sponge: energy retention falls to zero over a band inside the edge,
449
+ // quadratically so the transition into it is soft.
450
+ this.$l.fres = pb.float(pb.sub(this.resolution, 1));
451
+ this.$l.fp = pb.vec2(this.p);
452
+ this.$l.distX = pb.min(this.fp.x, pb.sub(this.fres, this.fp.x));
453
+ this.$l.distY = pb.min(this.fp.y, pb.sub(this.fres, this.fp.y));
454
+ this.$l.ex = pb.sub(1, pb.smoothStep(0, this.stepParams.z, this.distX));
455
+ this.$l.ey = pb.sub(1, pb.smoothStep(0, this.stepParams.z, this.distY));
456
+ this.$l.e = pb.sub(1, pb.mul(pb.sub(1, this.ex), pb.sub(1, this.ey)));
457
+ this.$l.absorb = pb.clamp(pb.sub(1, pb.mul(this.e, this.e)), 0, 1);
458
+ // The beach is an absorber too: waves die in the last few centimetres
459
+ // instead of reflecting off the waterline as off a wall
460
+ this.$if(pb.greaterThan(this.bathyParams2.x, 0.5), function() {
461
+ this.absorb = pb.mul(this.absorb, pb.smoothStep(0, pb.max(this.shoreParams.y, 1e-3), this.depthC));
462
+ });
463
+ this.$l.retention = pb.mul(this.stepParams.y, this.absorb);
464
+ this.$l.next = pb.add(this.cur, pb.mul(pb.sub(this.cur, this.prev), this.retention), pb.mul(this.stepParams.x, this.lap));
465
+ // Downward acceleration of the surface under the waves alone, before
466
+ // anything is forced into it, for the breaking crests
467
+ this.$l.sink = pb.mul(pb.neg(this.stepParams.x), this.lap, this.foamParams2.w, this.foamParams2.w);
468
+ this.$l.centre = pb.add(this.fp, pb.vec2(0.5));
469
+ // Height forced into the surface this step by impulses and disturbers
470
+ this.$l.forced = pb.float(0);
471
+ this.$for(pb.int('i'), 0, MAX_IMPULSES, function() {
472
+ this.$if(pb.greaterThanEqual(this.i, this.numImpulses), function() {
473
+ this.$break();
474
+ });
475
+ this.$l.imp = this.impulses.at(this.i);
476
+ this.$l.dv = pb.sub(this.centre, this.imp.xy);
477
+ this.$l.r2 = pb.dot(this.dv, this.dv);
478
+ this.$l.rad = this.imp.z;
479
+ this.$if(pb.lessThan(this.r2, pb.mul(this.rad, this.rad)), function() {
480
+ this.$l.sigma = pb.mul(this.rad, 0.4);
481
+ this.$l.g = pb.exp(pb.neg(pb.div(this.r2, pb.mul(2, this.sigma, this.sigma))));
482
+ this.$l.push = pb.mul(this.g, this.imp.w, this.absorb);
483
+ this.next = pb.add(this.next, this.push);
484
+ this.forced = pb.add(this.forced, this.push);
485
+ });
486
+ });
487
+ this.next = pb.clamp(this.next, pb.neg(this.stepParams.w), this.stepParams.w);
488
+ // Disturbers: what each displaces now against what it displaced at
489
+ // the last step. More column pushes the surface down.
490
+ this.$l.wp = pb.add(this.worldParams.xy, pb.mul(this.centre, this.worldParams.z));
491
+ this.$for(pb.int('j'), 0, MAX_DISTURBERS, function() {
492
+ this.$if(pb.greaterThanEqual(this.j, this.numDisturbers), function() {
493
+ this.$break();
494
+ });
495
+ this.$l.k = pb.mul(this.j, DISTURBER_VEC4S);
496
+ this.$l.sNow = pb.textureLoad(this.surfNow, pb.ivec2(this.j, 0), 0).x;
497
+ this.$l.aBefore = this.disturbers.at(pb.add(this.k, 3));
498
+ this.$l.sBefore = this.$choice(pb.greaterThan(this.aBefore.w, 0.5), pb.textureLoad(this.surfPrev, pb.ivec2(this.j, 0), 0).x, this.sNow);
499
+ this.$l.now = this.wiFootprint(this.disturbers.at(this.k), this.disturbers.at(pb.add(this.k, 1)), this.disturbers.at(pb.add(this.k, 2)), this.sNow, this.wp);
500
+ this.$l.before = this.wiFootprint(this.aBefore, this.disturbers.at(pb.add(this.k, 4)), this.disturbers.at(pb.add(this.k, 5)), this.sBefore, this.wp);
501
+ this.$l.change = pb.mul(pb.sub(this.now, this.before), this.absorb);
502
+ this.next = pb.sub(this.next, this.change);
503
+ this.forced = pb.sub(this.forced, this.change);
504
+ });
505
+ // Foam, as air trapped in the water (Ihmsen et al.): made in
506
+ // proportion to the kinetic-energy potential of the surface's speed
507
+ // times what traps the air, the water forced aside by a disturber or an
508
+ // impulse, and crests breaking by Longuet-Higgins' limit, their
509
+ // downward acceleration past a share of gravity.
510
+ this.$l.speed = pb.mul(pb.abs(pb.sub(this.next, this.cur)), this.foamParams2.w);
511
+ this.$l.kinetic = this.wiPotential(pb.mul(this.speed, this.speed, 0.5), this.foamParams.z, this.foamParams.w);
512
+ this.$l.forcedSpeed = pb.mul(pb.abs(this.forced), this.foamParams2.w);
513
+ this.$l.trapped = this.wiPotential(pb.mul(this.forcedSpeed, this.forcedSpeed, 0.5), this.foamParams.z, this.foamParams.w);
514
+ this.$l.breaking = pb.mul(this.wiPotential(this.sink, this.foamParams2.x, this.foamParams2.y), pb.step(0, this.cur));
515
+ this.$l.foamIn = pb.mul(this.foamParams.x, this.kinetic, pb.min(1, pb.add(this.trapped, this.breaking)));
516
+ // Spread by turbulent diffusion, explicit, and faded as the bubbles rise
517
+ this.$l.fl = this.wiNeighbourFoam(pb.add(this.p, pb.ivec2(-1, 0)), pb.add(this.q, pb.ivec2(-1, 0)), this.foam);
518
+ this.$l.fr = this.wiNeighbourFoam(pb.add(this.p, pb.ivec2(1, 0)), pb.add(this.q, pb.ivec2(1, 0)), this.foam);
519
+ this.$l.fd = this.wiNeighbourFoam(pb.add(this.p, pb.ivec2(0, -1)), pb.add(this.q, pb.ivec2(0, -1)), this.foam);
520
+ this.$l.fu = this.wiNeighbourFoam(pb.add(this.p, pb.ivec2(0, 1)), pb.add(this.q, pb.ivec2(0, 1)), this.foam);
521
+ this.$l.spread = pb.add(this.foam, pb.mul(this.foamParams2.z, pb.sub(pb.add(this.fl, this.fr, this.fd, this.fu), pb.mul(this.foam, 4))));
522
+ this.$l.foamNext = pb.clamp(pb.add(pb.mul(this.spread, this.foamParams.y), pb.mul(this.foamIn, this.absorb)), 0, 1);
523
+ this.next = pb.clamp(this.next, pb.neg(this.stepParams.w), this.stepParams.w);
524
+ // Inside an obstacle, or on dry land, there is no water to move.
525
+ this.$if(pb.or(this.wiObstacle(this.p), pb.lessThanEqual(this.depthC, 0)), function() {
526
+ this.next = pb.float(0);
527
+ this.cur = pb.float(0);
528
+ this.foamNext = pb.float(0);
529
+ });
530
+ this.$outputs.outColor = pb.vec4(this.next, this.cur, this.foamNext, 1);
531
+ });
532
+ }
533
+ };
534
+ }
535
+ /**
536
+ * Shader that reads the ambient surface under each disturber: texel i of a
537
+ * row holds the height over the still water of the surface `generator` draws,
538
+ * at the centre of the disturber in slot i, displaced as the water's vertices
539
+ * are.
540
+ * @internal
541
+ */ function createWaterInteractionSurfaceShader(generator) {
542
+ return {
543
+ vertex (pb) {
544
+ this.$inputs.pos = pb.vec2().attrib('position');
545
+ pb.main(function() {
546
+ this.$builtins.position = pb.vec4(this.$inputs.pos, 0, 1);
547
+ });
548
+ },
549
+ fragment (pb) {
550
+ this.$outputs.outColor = pb.vec4();
551
+ generator.setupUniforms(this, 0);
552
+ // FBM and Gerstner animate from camera.elapsedTime, which a standalone
553
+ // program has to declare itself
554
+ ShaderHelper.declareStandaloneCameraTime(this, 0);
555
+ // (still water level, slots in use, 0, 0)
556
+ this.wiSurfParams = pb.vec4().uniform(0);
557
+ this.wiSurfCentres = pb.vec4[MAX_DISTURBERS]().uniform(0);
558
+ pb.main(function() {
559
+ this.$l.slot = pb.int(this.$builtins.fragCoord.x);
560
+ this.$l.height = pb.float(0);
561
+ this.$if(pb.lessThan(this.slot, pb.int(this.wiSurfParams.y)), function() {
562
+ this.$l.centre = this.wiSurfCentres.at(this.slot);
563
+ this.$l.inPos = pb.vec3(this.centre.x, this.wiSurfParams.x, this.centre.y);
564
+ this.$l.outPos = pb.vec3(0);
565
+ this.$l.outNormal = pb.vec3(0, 1, 0);
566
+ generator.calcVertexPositionAndNormal(this, this.inPos, this.outPos, this.outNormal);
567
+ this.height = pb.sub(this.outPos.y, this.wiSurfParams.x);
568
+ });
569
+ this.$outputs.outColor = pb.vec4(this.height, 0, 0, 1);
570
+ });
571
+ }
572
+ };
573
+ }
574
+ /**
575
+ * Shader that paints the blocking disturbers' footprints into the obstacle
576
+ * mask, in the window's current placement.
577
+ * @internal
578
+ */ function createWaterInteractionObstacleShader() {
579
+ return {
580
+ vertex (pb) {
581
+ this.$inputs.pos = pb.vec2().attrib('position');
582
+ pb.main(function() {
583
+ this.$builtins.position = pb.vec4(this.$inputs.pos, 0, 1);
584
+ });
585
+ },
586
+ fragment (pb) {
587
+ this.$outputs.outColor = pb.vec4();
588
+ this.worldParams = pb.vec4().uniform(0);
589
+ this.numDisturbers = pb.int().uniform(0);
590
+ this.disturbers = pb.vec4[MAX_DISTURBERS * DISTURBER_VEC4S]().uniform(0);
591
+ this.surfNow = pb.tex2D().uniform(0);
592
+ declareFootprintFunctions(pb);
593
+ pb.main(function() {
594
+ this.$l.centre = pb.add(pb.floor(this.$builtins.fragCoord.xy), pb.vec2(0.5));
595
+ this.$l.wp = pb.add(this.worldParams.xy, pb.mul(this.centre, this.worldParams.z));
596
+ this.$l.mask = pb.float(0);
597
+ this.$for(pb.int('j'), 0, MAX_DISTURBERS, function() {
598
+ this.$if(pb.greaterThanEqual(this.j, this.numDisturbers), function() {
599
+ this.$break();
600
+ });
601
+ this.$l.k = pb.mul(this.j, DISTURBER_VEC4S);
602
+ this.$l.a = this.disturbers.at(this.k);
603
+ this.$if(pb.greaterThanEqual(this.a.x, BLOCKING_FLAG), function() {
604
+ this.mask = pb.max(this.mask, this.wiFootprintHard(this.a, this.disturbers.at(pb.add(this.k, 1)), this.disturbers.at(pb.add(this.k, 2)), pb.textureLoad(this.surfNow, pb.ivec2(this.j, 0), 0).x, this.wp));
605
+ });
606
+ });
607
+ this.$outputs.outColor = pb.vec4(this.mask, 0, 0, 1);
608
+ });
609
+ }
610
+ };
611
+ }
612
+ /**
613
+ * A local, dynamic height field layered on top of a water surface, driven by
614
+ * things that touch it: a ring spreading from a dropped stone, the wake behind
615
+ * a hull. It is a 2D wave equation integrated on a square window of texels
616
+ * aligned to the world; the water material adds its height and slope to the
617
+ * wave generator's surface, so the ambient sea and the disturbances compose.
618
+ *
619
+ * The window follows a focus point - the camera by default - and covers
620
+ * {@link windowSize} metres at {@link resolution} texels. Its origin is snapped
621
+ * to the texel grid, so moving it is an integer shift of the field rather than
622
+ * a resample. What scrolls in is still water.
623
+ *
624
+ * The scheme is explicit and needs `waveSpeed * dt / texel` to stay under the
625
+ * CFL bound; {@link waveSpeed} is clamped to what the current window allows. A
626
+ * sponge band inside the window's edge absorbs outgoing waves so they do not
627
+ * reflect off the window boundary.
628
+ *
629
+ * Attach it with {@link Water.interaction}. Steps run in {@link Water.update}
630
+ * on the water's own clock, so pausing the water pauses this too.
631
+ *
632
+ * @public
633
+ */ class WaterInteraction extends Disposable {
634
+ _resolution;
635
+ _windowSize;
636
+ _waveSpeed;
637
+ _damping;
638
+ _maxAmplitude;
639
+ _foamAmount;
640
+ _foamDecay;
641
+ _foamMinSpeed;
642
+ _foamMaxSpeed;
643
+ _breakingAcceleration;
644
+ _foamDiffusion;
645
+ _foamParams;
646
+ _foamParams2;
647
+ _spongeWidth;
648
+ _followMode;
649
+ _followNode;
650
+ _followNodeId;
651
+ _center;
652
+ _focus;
653
+ _impulses;
654
+ _textures;
655
+ _framebuffers;
656
+ // Independent history: impulses only, so rendered wakes cannot feed buoyancy.
657
+ _externalTextures = null;
658
+ _externalFramebuffers = null;
659
+ _current;
660
+ _originX;
661
+ _originZ;
662
+ _hasOrigin;
663
+ _accumulator;
664
+ _lastTime;
665
+ _version;
666
+ _program;
667
+ _bindGroup;
668
+ _format;
669
+ _formatResolved;
670
+ _impulseData;
671
+ _disturbers;
672
+ _disturberData;
673
+ _waterLevel;
674
+ _obstacleTexture;
675
+ _obstacleFramebuffer;
676
+ _obstacleProgram;
677
+ _obstacleBindGroup;
678
+ _worldParams;
679
+ _stepParams;
680
+ _shift;
681
+ _sampleParams;
682
+ _sampleParams2;
683
+ _shallowDepth;
684
+ _shoreAbsorbDepth;
685
+ _bathymetry;
686
+ _bathyParams;
687
+ _bathyParams2;
688
+ _shoreParams;
689
+ _dummyBathyTexture;
690
+ /** The surface the field is layered over, which the disturbers float on; null for still water. */ _ambient;
691
+ /** Ambient surface height under each disturber slot, this step's and the last. */ _surfTextures;
692
+ _surfFramebuffers;
693
+ _surfCurrent;
694
+ _surfProgram;
695
+ _surfBindGroup;
696
+ _surfHash;
697
+ /** (centre x, centre z, 0, 0) per disturber slot */ _surfCentres;
698
+ /** (still water level, slots in use, 0, 0) */ _surfParams;
699
+ /**
700
+ * Creates a water interaction field.
701
+ */ constructor(){
702
+ super();
703
+ this._resolution = 512;
704
+ this._windowSize = 64;
705
+ this._waveSpeed = 1.5;
706
+ this._damping = 1;
707
+ this._maxAmplitude = 0.5;
708
+ this._foamAmount = 3;
709
+ this._foamDecay = 0.6;
710
+ this._foamMinSpeed = 0.5;
711
+ this._foamMaxSpeed = 2;
712
+ this._breakingAcceleration = 0.5;
713
+ this._foamDiffusion = 0.1;
714
+ this._foamParams = new Vector4();
715
+ this._foamParams2 = new Vector4();
716
+ this._spongeWidth = 0.1;
717
+ this._followMode = 'camera';
718
+ this._followNode = null;
719
+ this._followNodeId = '';
720
+ this._center = new Vector2(0, 0);
721
+ this._focus = new Vector2(0, 0);
722
+ this._impulses = [];
723
+ this._textures = null;
724
+ this._framebuffers = null;
725
+ this._current = 0;
726
+ this._originX = 0;
727
+ this._originZ = 0;
728
+ this._hasOrigin = false;
729
+ this._accumulator = 0;
730
+ this._lastTime = -1;
731
+ this._version = 0;
732
+ this._program = null;
733
+ this._bindGroup = null;
734
+ this._format = null;
735
+ this._formatResolved = false;
736
+ this._impulseData = new Float32Array(MAX_IMPULSES * 4);
737
+ this._disturbers = [];
738
+ this._disturberData = new Float32Array(MAX_DISTURBERS * DISTURBER_STRIDE);
739
+ this._waterLevel = 0;
740
+ this._obstacleTexture = null;
741
+ this._obstacleFramebuffer = null;
742
+ this._obstacleProgram = null;
743
+ this._obstacleBindGroup = null;
744
+ this._worldParams = new Vector4();
745
+ this._stepParams = new Vector4();
746
+ this._shift = new Vector2();
747
+ this._sampleParams = new Vector4();
748
+ this._sampleParams2 = new Vector4();
749
+ this._shallowDepth = 2;
750
+ this._shoreAbsorbDepth = 0.2;
751
+ this._bathymetry = null;
752
+ this._bathyParams = new Vector4();
753
+ this._bathyParams2 = new Vector4();
754
+ this._shoreParams = new Vector4();
755
+ this._dummyBathyTexture = null;
756
+ this._ambient = null;
757
+ this._surfTextures = null;
758
+ this._surfFramebuffers = null;
759
+ this._surfCurrent = 0;
760
+ this._surfProgram = null;
761
+ this._surfBindGroup = null;
762
+ this._surfHash = '';
763
+ this._surfCentres = new Float32Array(MAX_DISTURBERS * 4);
764
+ this._surfParams = new Vector4();
765
+ }
766
+ onDispose() {
767
+ super.onDispose();
768
+ this._disposeField();
769
+ this._bindGroup?.dispose();
770
+ this._bindGroup = null;
771
+ this._program?.dispose();
772
+ this._program = null;
773
+ this._obstacleBindGroup?.dispose();
774
+ this._obstacleBindGroup = null;
775
+ this._obstacleProgram?.dispose();
776
+ this._obstacleProgram = null;
777
+ this._surfBindGroup?.dispose();
778
+ this._surfBindGroup = null;
779
+ this._surfProgram?.dispose();
780
+ this._surfProgram = null;
781
+ this._ambient = null;
782
+ this._dummyBathyTexture?.dispose();
783
+ this._dummyBathyTexture = null;
784
+ this._bathymetry = null;
785
+ this._disturbers.length = 0;
786
+ this._followNode = null;
787
+ }
788
+ /**
789
+ * Bumped whenever the bound texture or uniforms change, so a material knows to
790
+ * rebind. Every simulation step swaps the ping-pong textures, so this moves
791
+ * every step.
792
+ * @internal
793
+ */ get version() {
794
+ return this._version;
795
+ }
796
+ /**
797
+ * Texels along each side of the window. The texel size in metres is
798
+ * {@link windowSize} over this.
799
+ *
800
+ * Changing it discards the field.
801
+ */ get resolution() {
802
+ return this._resolution;
803
+ }
804
+ set resolution(val) {
805
+ val = Math.max(16, Math.floor(val));
806
+ if (val !== this._resolution) {
807
+ this._resolution = val;
808
+ this._disposeField();
809
+ this._clampWaveSpeed();
810
+ }
811
+ }
812
+ /**
813
+ * Side of the window in metres.
814
+ *
815
+ * Changing it discards the field.
816
+ */ get windowSize() {
817
+ return this._windowSize;
818
+ }
819
+ set windowSize(val) {
820
+ val = Math.max(1, val);
821
+ if (val !== this._windowSize) {
822
+ this._windowSize = val;
823
+ this._disposeField();
824
+ this._clampWaveSpeed();
825
+ }
826
+ }
827
+ /** Size of one texel in metres. */ get texelSize() {
828
+ return this._windowSize / this._resolution;
829
+ }
830
+ /**
831
+ * Speed disturbances spread at, in metres per second.
832
+ *
833
+ * Capillary ripples run at well under a metre per second, a boat's wake at a
834
+ * few. Clamped to what the explicit scheme can integrate stably on the current
835
+ * texel size: raising {@link resolution} or shrinking {@link windowSize}
836
+ * lowers that ceiling.
837
+ */ get waveSpeed() {
838
+ return this._waveSpeed;
839
+ }
840
+ set waveSpeed(val) {
841
+ this._waveSpeed = Math.max(0, val);
842
+ this._clampWaveSpeed();
843
+ }
844
+ /** Highest wave speed the current window can integrate stably, in metres per second. */ get maxWaveSpeed() {
845
+ return MAX_COURANT * this.texelSize / FIXED_STEP;
846
+ }
847
+ /**
848
+ * Rate the field loses energy at, in 1/s. 0 keeps ripples going until they
849
+ * leave the window; a few damps them within a second or two.
850
+ */ get damping() {
851
+ return this._damping;
852
+ }
853
+ set damping(val) {
854
+ this._damping = Math.max(0, val);
855
+ }
856
+ /**
857
+ * Rate foam is made at where the water is churned to the full, in coverage
858
+ * per second; 0 disables the foam. Defaults to 3.
859
+ *
860
+ * Foam is air trapped in the water, as in Ihmsen et al., "Unified spray,
861
+ * foam and bubbles for particle-based fluids": it is made in proportion to
862
+ * the kinetic-energy potential of the water's speed (see
863
+ * {@link foamMinSpeed}) times what traps the air, which is the water forced
864
+ * aside by something moving through it or landing in it, and crests that
865
+ * break (see {@link breakingAcceleration}). Once made it spreads (see
866
+ * {@link foamDiffusion}) and fades at {@link foamDecay} as its bubbles rise.
867
+ */ get foamAmount() {
868
+ return this._foamAmount;
869
+ }
870
+ set foamAmount(val) {
871
+ this._foamAmount = Math.max(0, val);
872
+ }
873
+ /** Rate the foam fades at as its bubbles rise and burst, in 1/s. */ get foamDecay() {
874
+ return this._foamDecay;
875
+ }
876
+ set foamDecay(val) {
877
+ this._foamDecay = Math.max(0, val);
878
+ }
879
+ /**
880
+ * Speed of the water in m/s below which it traps no air: the lower bound of
881
+ * the kinetic-energy potential, between which and {@link foamMaxSpeed} the
882
+ * foam made grows with the water's kinetic energy. Defaults to 0.5.
883
+ */ get foamMinSpeed() {
884
+ return this._foamMinSpeed;
885
+ }
886
+ set foamMinSpeed(val) {
887
+ this._foamMinSpeed = Math.max(0, val);
888
+ }
889
+ /** Speed of the water in m/s from which it foams to the full. Defaults to 2. */ get foamMaxSpeed() {
890
+ return this._foamMaxSpeed;
891
+ }
892
+ set foamMaxSpeed(val) {
893
+ this._foamMaxSpeed = Math.max(0, val);
894
+ }
895
+ /**
896
+ * Downward acceleration of a crest, as a share of gravity, at which it
897
+ * breaks and foams: Longuet-Higgins' limit for a steady crest is half of
898
+ * gravity. Foam grows from there to twice that. Defaults to 0.5.
899
+ */ get breakingAcceleration() {
900
+ return this._breakingAcceleration;
901
+ }
902
+ set breakingAcceleration(val) {
903
+ this._breakingAcceleration = Math.max(0.01, val);
904
+ }
905
+ /**
906
+ * Turbulent diffusivity the foam spreads by, in m²/s: a wake's band of
907
+ * foam widens and thins behind the hull at this rate. 0 keeps foam where it
908
+ * was made. Defaults to 0.1.
909
+ */ get foamDiffusion() {
910
+ return this._foamDiffusion;
911
+ }
912
+ set foamDiffusion(val) {
913
+ this._foamDiffusion = Math.max(0, val);
914
+ }
915
+ /**
916
+ * Height in metres the field is clamped to, either side of the rest level.
917
+ *
918
+ * Also what the water's bounding volume is grown by, so keep it to what the
919
+ * disturbances actually reach.
920
+ */ get maxAmplitude() {
921
+ return this._maxAmplitude;
922
+ }
923
+ set maxAmplitude(val) {
924
+ this._maxAmplitude = Math.max(0.001, val);
925
+ }
926
+ /**
927
+ * Width of the absorbing band inside the window edge, as a fraction of the
928
+ * window. Waves reaching it die away instead of reflecting; the material also
929
+ * fades the field out over it so the window has no visible edge.
930
+ */ get spongeWidth() {
931
+ return this._spongeWidth;
932
+ }
933
+ set spongeWidth(val) {
934
+ this._spongeWidth = Math.min(0.45, Math.max(0.01, val));
935
+ }
936
+ /**
937
+ * Depth in metres below which waves slow down, as shallow water waves do:
938
+ * the speed is {@link waveSpeed} in deeper water and falls with the square
939
+ * root of the depth below this. A wave running onto a beach then slows,
940
+ * shortens, grows and turns to meet the shore. 0 ignores the depth. Needs a
941
+ * depth source, which the water sets each frame: the scene signed distance
942
+ * field where enabled, else the terrain.
943
+ */ get shallowDepth() {
944
+ return this._shallowDepth;
945
+ }
946
+ set shallowDepth(val) {
947
+ this._shallowDepth = Math.max(0, val);
948
+ }
949
+ /** Depth in metres over which the beach absorbs the waves reaching it instead of reflecting them. */ get shoreAbsorbDepth() {
950
+ return this._shoreAbsorbDepth;
951
+ }
952
+ set shoreAbsorbDepth(val) {
953
+ this._shoreAbsorbDepth = Math.max(0, val);
954
+ }
955
+ /**
956
+ * Sets where the depth of the water comes from, or null for none (constant
957
+ * speed). Set by the water each frame.
958
+ * @internal
959
+ */ setBathymetry(source) {
960
+ this._bathymetry = source;
961
+ }
962
+ /** How the window is positioned. See {@link WaterInteractionFollowMode}. */ get followMode() {
963
+ return this._followMode;
964
+ }
965
+ set followMode(val) {
966
+ this._followMode = val;
967
+ }
968
+ /** Node the window follows while {@link followMode} is `node`. */ get followNode() {
969
+ return this._followNode;
970
+ }
971
+ set followNode(val) {
972
+ this._followNode = val;
973
+ }
974
+ /**
975
+ * Persistent id of {@link followNode}, for a field loaded from a scene before
976
+ * the node exists. Resolved against the water's scene on the next update;
977
+ * {@link followNode} wins when both are set.
978
+ */ get followNodeId() {
979
+ return this._followNodeId;
980
+ }
981
+ set followNodeId(val) {
982
+ this._followNodeId = val ?? '';
983
+ }
984
+ /**
985
+ * Bind any node ids left by deserialization to the nodes of `scene`. Called
986
+ * by the water node before each update; cheap once everything is bound.
987
+ * @internal
988
+ */ resolveNodes(scene) {
989
+ if (!scene) {
990
+ return;
991
+ }
992
+ if (!this._followNode && this._followNodeId) {
993
+ this._followNode = scene.findNodeById(this._followNodeId) ?? null;
994
+ }
995
+ for (const d of this._disturbers){
996
+ if (!d.node && d.nodeId) {
997
+ d.node = scene.findNodeById(d.nodeId) ?? null;
998
+ }
999
+ }
1000
+ }
1001
+ /** World XZ the window is centred on while {@link followMode} is `fixed`. */ get center() {
1002
+ return this._center;
1003
+ }
1004
+ set center(val) {
1005
+ this._center.set(val);
1006
+ }
1007
+ /** World X of the window's low corner, as of the last step. */ get originX() {
1008
+ return this._originX;
1009
+ }
1010
+ /** World Z of the window's low corner, as of the last step. */ get originZ() {
1011
+ return this._originZ;
1012
+ }
1013
+ /**
1014
+ * Tell the field where the camera is. Called by the water node each time it
1015
+ * is updated for a camera; only used while {@link followMode} is `camera`.
1016
+ * @internal
1017
+ */ setFocus(x, z) {
1018
+ this._focus.setXY(x, z);
1019
+ }
1020
+ /**
1021
+ * Push the surface down (or up) at a point, once.
1022
+ *
1023
+ * The disturbance is a Gaussian bump added to the field on the next step,
1024
+ * so a stone hitting the water is one call, and something dragging through
1025
+ * it is a call per frame. Calls beyond what one step can take are carried
1026
+ * over to the following step.
1027
+ *
1028
+ * @param x - World X.
1029
+ * @param z - World Z.
1030
+ * @param radius - Radius of the bump in metres.
1031
+ * @param strength - Peak height of the bump in metres. Negative pushes the
1032
+ * surface down, which is what an object entering the water does.
1033
+ */ addImpulse(x, z, radius, strength) {
1034
+ this._impulses.push({
1035
+ x,
1036
+ z,
1037
+ radius: Math.max(radius, this.texelSize),
1038
+ strength
1039
+ });
1040
+ }
1041
+ /** The disturbers registered with this field. */ get disturbers() {
1042
+ return this._disturbers;
1043
+ }
1044
+ /**
1045
+ * Register a disturber. Beyond MAX_DISTURBERS enabled disturbers the
1046
+ * extra ones are ignored, earliest registered first.
1047
+ */ addDisturber(disturber) {
1048
+ if (this._disturbers.indexOf(disturber) < 0) {
1049
+ disturber._hasPrev = false;
1050
+ this._disturbers.push(disturber);
1051
+ }
1052
+ }
1053
+ /** Unregister a disturber. */ removeDisturber(disturber) {
1054
+ const i = this._disturbers.indexOf(disturber);
1055
+ if (i >= 0) {
1056
+ this._disturbers.splice(i, 1);
1057
+ }
1058
+ }
1059
+ /**
1060
+ * Tell the field where the still-water surface is. Called by the water node
1061
+ * before each update.
1062
+ * @internal
1063
+ */ setWaterLevel(y) {
1064
+ this._waterLevel = y;
1065
+ }
1066
+ /**
1067
+ * Tell the field what surface it is layered over: the ambient waves the
1068
+ * disturbers float on, whose height under each of them sets how deep it
1069
+ * sits. Called by the water material whenever it rebuilds its waves; null
1070
+ * for still water.
1071
+ * @internal
1072
+ */ setAmbientSurface(generator) {
1073
+ this._ambient = generator;
1074
+ }
1075
+ /**
1076
+ * Whether the device can host the field: it needs a filterable float or
1077
+ * half-float render target.
1078
+ */ isOk(device) {
1079
+ return !!this._resolveFormat(device ?? getDevice());
1080
+ }
1081
+ /**
1082
+ * Advance the field to `time`, the water's wave clock in seconds.
1083
+ *
1084
+ * Runs as many fixed steps as the elapsed time covers, up to a cap; the
1085
+ * window is moved and queued impulses are injected on the first step of a
1086
+ * frame. Nothing is done for a frame too short to reach a step.
1087
+ * @internal
1088
+ */ update(time) {
1089
+ const device = getDevice();
1090
+ if (!this._resolveFormat(device)) {
1091
+ return;
1092
+ }
1093
+ const dt = this._lastTime < 0 ? 0 : Math.min(MAX_FRAME_DELTA, Math.max(0, time - this._lastTime));
1094
+ this._lastTime = time;
1095
+ this._accumulator += dt;
1096
+ let steps = Math.floor(this._accumulator / FIXED_STEP);
1097
+ if (steps > MAX_SUBSTEPS) {
1098
+ steps = MAX_SUBSTEPS;
1099
+ this._accumulator = 0;
1100
+ } else {
1101
+ this._accumulator -= steps * FIXED_STEP;
1102
+ }
1103
+ // The textures have to exist before anything binds them this frame, step
1104
+ // or no step: creating them means a framebuffer clear, which is not
1105
+ // something to do from inside a draw.
1106
+ this._ensureField(device);
1107
+ if (steps === 0) {
1108
+ return;
1109
+ }
1110
+ this._moveWindow();
1111
+ const texel = this.texelSize;
1112
+ const k = this._waveSpeed * FIXED_STEP / texel;
1113
+ const retention = Math.max(0, 1 - this._damping * FIXED_STEP);
1114
+ this._stepParams.setXYZW(k * k, retention, this._spongeWidth * this._resolution, this._maxAmplitude);
1115
+ // Foam: (rate per step, per-step retention, kinetic energy per unit mass
1116
+ // at the lower and upper speed), (downward acceleration at which crests
1117
+ // start and finish breaking, diffusion per step in texels squared, held
1118
+ // under the explicit scheme's stability limit, 1 / dt)
1119
+ const minSpeed = Math.min(this._foamMinSpeed, this._foamMaxSpeed);
1120
+ this._foamParams.setXYZW(this._foamAmount * FIXED_STEP, Math.exp(-this._foamDecay * FIXED_STEP), 0.5 * minSpeed * minSpeed, 0.5 * this._foamMaxSpeed * this._foamMaxSpeed);
1121
+ this._foamParams2.setXYZW(this._breakingAcceleration * STANDARD_GRAVITY, 2 * this._breakingAcceleration * STANDARD_GRAVITY, Math.min(0.24, this._foamDiffusion * FIXED_STEP / (texel * texel)), 1 / FIXED_STEP);
1122
+ const numImpulses = Math.min(this._impulses.length, MAX_IMPULSES);
1123
+ for(let i = 0; i < numImpulses; i++){
1124
+ const imp = this._impulses[i];
1125
+ this._impulseData[i * 4 + 0] = (imp.x - this._originX) / texel;
1126
+ this._impulseData[i * 4 + 1] = (imp.z - this._originZ) / texel;
1127
+ this._impulseData[i * 4 + 2] = imp.radius / texel;
1128
+ this._impulseData[i * 4 + 3] = imp.strength;
1129
+ }
1130
+ this._impulses.splice(0, numImpulses);
1131
+ // Disturbers: the current footprint against the one from the last step.
1132
+ // A newly registered one starts with no previous footprint, so it does not
1133
+ // slap the water on its first frame.
1134
+ const numDisturbers = this._gatherDisturbers();
1135
+ this._worldParams.setXYZW(this._originX, this._originZ, texel, 0);
1136
+ device.pushDeviceStates();
1137
+ this._renderSurface(device, numDisturbers);
1138
+ this._renderObstacles(device, numDisturbers);
1139
+ const program = this._getProgram(device);
1140
+ const bindGroup = this._bindGroup;
1141
+ const nearest = fetchSampler('clamp_nearest_nomip');
1142
+ bindGroup.setValue('stepParams', this._stepParams);
1143
+ bindGroup.setValue('foamParams', this._foamParams);
1144
+ bindGroup.setValue('foamParams2', this._foamParams2);
1145
+ bindGroup.setValue('worldParams', this._worldParams);
1146
+ bindGroup.setValue('resolution', this._resolution);
1147
+ bindGroup.setTexture('obstacleTex', this._obstacleTexture, nearest);
1148
+ bindGroup.setTexture('surfNow', this._surfTextures[this._surfCurrent], nearest);
1149
+ bindGroup.setTexture('surfPrev', this._surfTextures[1 - this._surfCurrent], nearest);
1150
+ const bathy = this._shallowDepth > 0 ? this._bathymetry : null;
1151
+ if (bathy) {
1152
+ const [x, z, w, d] = bathy.region;
1153
+ this._bathyParams.setXYZW(x, z, 1 / w, 1 / d);
1154
+ this._bathyParams2.setXYZW(bathy.kind === 'field' ? 1 : 2, bathy.level, bathy.heightScale ?? 1, bathy.heightOffset ?? 0);
1155
+ } else {
1156
+ this._bathyParams.setXYZW(0, 0, 1, 1);
1157
+ this._bathyParams2.setXYZW(0, 0, 1, 0);
1158
+ }
1159
+ this._shoreParams.setXYZW(this._shallowDepth, this._shoreAbsorbDepth, 0, 0);
1160
+ bindGroup.setTexture('bathyTex', bathy?.texture ?? this._getDummyBathyTexture(device), fetchSampler('clamp_linear_nomip'));
1161
+ bindGroup.setValue('bathyParams', this._bathyParams);
1162
+ bindGroup.setValue('bathyParams2', this._bathyParams2);
1163
+ bindGroup.setValue('shoreParams', this._shoreParams);
1164
+ for(let i = 0; i < steps; i++){
1165
+ const src = this._textures[this._current];
1166
+ const dst = this._framebuffers[1 - this._current];
1167
+ bindGroup.setTexture('srcTex', src, nearest);
1168
+ if (i === 0) {
1169
+ bindGroup.setValue('shift', this._shift);
1170
+ bindGroup.setValue('numImpulses', numImpulses);
1171
+ bindGroup.setValue('impulses', this._impulseData);
1172
+ bindGroup.setValue('numDisturbers', numDisturbers);
1173
+ bindGroup.setValue('disturbers', this._disturberData);
1174
+ } else {
1175
+ bindGroup.setValue('shift', ZERO_SHIFT);
1176
+ bindGroup.setValue('numImpulses', 0);
1177
+ bindGroup.setValue('numDisturbers', 0);
1178
+ }
1179
+ device.setFramebuffer(dst);
1180
+ device.setProgram(program);
1181
+ device.setBindGroup(0, bindGroup);
1182
+ drawFullscreenQuad();
1183
+ // Same clock, window, impulses and obstacles, but no disturber sources.
1184
+ bindGroup.setTexture('srcTex', this._externalTextures[this._current], nearest);
1185
+ bindGroup.setValue('numDisturbers', 0);
1186
+ device.setFramebuffer(this._externalFramebuffers[1 - this._current]);
1187
+ device.setBindGroup(0, bindGroup);
1188
+ drawFullscreenQuad();
1189
+ this._current = 1 - this._current;
1190
+ }
1191
+ device.popDeviceStates();
1192
+ for (const d of this._disturbers){
1193
+ if (d.enabled) {
1194
+ d._prev.set(d._now);
1195
+ d._hasPrev = true;
1196
+ }
1197
+ }
1198
+ this._version++;
1199
+ }
1200
+ /**
1201
+ * Evaluate every enabled disturber and pack the current and previous
1202
+ * footprints for the shaders. Returns how many were packed.
1203
+ * @internal
1204
+ */ _gatherDisturbers() {
1205
+ let n = 0;
1206
+ for (const d of this._disturbers){
1207
+ if (!d.enabled || !d.node) {
1208
+ d._hasPrev = false;
1209
+ d._slot = -1;
1210
+ continue;
1211
+ }
1212
+ if (n >= MAX_DISTURBERS) {
1213
+ break;
1214
+ }
1215
+ d._evaluate(this._waterLevel);
1216
+ // The surface height read back for this slot at the last step is this
1217
+ // disturber's only if it held the same slot then
1218
+ const prevSurface = d._hasPrev && d._slot === n;
1219
+ d._slot = n;
1220
+ if (!d._hasPrev) {
1221
+ d._prev.set(d._now);
1222
+ }
1223
+ const o = n * DISTURBER_STRIDE;
1224
+ this._disturberData.set(d._now, o);
1225
+ this._disturberData.set(d._prev, o + FOOTPRINT_STRIDE);
1226
+ this._disturberData[o + FOOTPRINT_STRIDE + 3] = prevSurface ? 1 : 0;
1227
+ d._centre(d._now, this._surfCentres, n * 4);
1228
+ n++;
1229
+ }
1230
+ return n;
1231
+ }
1232
+ /**
1233
+ * Read the ambient surface under each disturber into a fresh slot texture,
1234
+ * keeping the last step's as the previous one: the height over the still
1235
+ * water at the footprint's centre, by the same generator the water draws.
1236
+ * Runs inside the caller's pushed device state.
1237
+ * @internal
1238
+ */ _renderSurface(device, numDisturbers) {
1239
+ this._surfCurrent = 1 - this._surfCurrent;
1240
+ device.setFramebuffer(this._surfFramebuffers[this._surfCurrent]);
1241
+ const ambient = this._ambient;
1242
+ if (!ambient || numDisturbers === 0 || !ambient.isOk(device)) {
1243
+ device.clearFrameBuffer(Vector4.zero(), null, null);
1244
+ return;
1245
+ }
1246
+ const hash = ambient.getHash();
1247
+ if (!this._surfProgram || this._surfHash !== hash) {
1248
+ this._surfBindGroup?.dispose();
1249
+ this._surfProgram?.dispose();
1250
+ this._surfProgram = device.buildRenderProgram(createWaterInteractionSurfaceShader(ambient));
1251
+ this._surfProgram.name = '@WaterInteraction_Surface';
1252
+ this._surfBindGroup = device.createBindGroup(this._surfProgram.bindGroupLayouts[0]);
1253
+ this._surfHash = hash;
1254
+ }
1255
+ const bindGroup = this._surfBindGroup;
1256
+ ambient.applyWaterBindGroup(bindGroup);
1257
+ ShaderHelper.setStandaloneCameraTime(bindGroup);
1258
+ this._surfParams.setXYZW(this._waterLevel, numDisturbers, 0, 0);
1259
+ bindGroup.setValue('wiSurfParams', this._surfParams);
1260
+ bindGroup.setValue('wiSurfCentres', this._surfCentres);
1261
+ device.setProgram(this._surfProgram);
1262
+ device.setBindGroup(0, bindGroup);
1263
+ drawFullscreenQuad();
1264
+ }
1265
+ /**
1266
+ * Paint the blocking footprints into the obstacle mask, in the window's
1267
+ * current placement. Runs inside the caller's pushed device state.
1268
+ * @internal
1269
+ */ _renderObstacles(device, numDisturbers) {
1270
+ const program = this._getObstacleProgram(device);
1271
+ const bindGroup = this._obstacleBindGroup;
1272
+ bindGroup.setValue('worldParams', this._worldParams);
1273
+ bindGroup.setValue('numDisturbers', numDisturbers);
1274
+ bindGroup.setValue('disturbers', this._disturberData);
1275
+ bindGroup.setTexture('surfNow', this._surfTextures[this._surfCurrent], fetchSampler('clamp_nearest_nomip'));
1276
+ device.setFramebuffer(this._obstacleFramebuffer);
1277
+ device.setProgram(program);
1278
+ device.setBindGroup(0, bindGroup);
1279
+ drawFullscreenQuad();
1280
+ }
1281
+ /**
1282
+ * Declare the uniforms the sampling functions read.
1283
+ *
1284
+ * Names are prefixed so they cannot collide with a wave generator's in the
1285
+ * same bind group.
1286
+ * @internal
1287
+ */ setupUniforms(scope, uniformGroup) {
1288
+ const pb = scope.$builder;
1289
+ scope.wiHeightTex = pb.tex2D().uniform(uniformGroup);
1290
+ scope.wiParams = pb.vec4().uniform(uniformGroup);
1291
+ scope.wiParams2 = pb.vec4().uniform(uniformGroup);
1292
+ }
1293
+ /**
1294
+ * Create the field's textures if they do not exist yet.
1295
+ *
1296
+ * Called when the field is attached and at the top of every update, so the
1297
+ * textures are in place before any pass binds them. Binding creates them as
1298
+ * a last resort, but that can land inside a render pass.
1299
+ * @internal
1300
+ */ ensureResources(device) {
1301
+ this._ensureField(device ?? getDevice());
1302
+ }
1303
+ /**
1304
+ * Bind the current field and its placement.
1305
+ * @internal
1306
+ */ applyBindGroup(bindGroup, includeDisturbers = true) {
1307
+ const device = getDevice();
1308
+ this._ensureField(device);
1309
+ const texel = this.texelSize;
1310
+ this._sampleParams.setXYZW(this._originX, this._originZ, 1 / this._windowSize, texel);
1311
+ this._sampleParams2.setXYZW(0.5 - this._spongeWidth, this._maxAmplitude, 0, 0);
1312
+ bindGroup.setTexture('wiHeightTex', (includeDisturbers ? this._textures : this._externalTextures)[this._current], fetchSampler('clamp_linear_nomip'));
1313
+ bindGroup.setValue('wiParams', this._sampleParams);
1314
+ bindGroup.setValue('wiParams2', this._sampleParams2);
1315
+ }
1316
+ /**
1317
+ * Field height at a world XZ, faded to zero across the sponge band.
1318
+ * @internal
1319
+ */ sampleHeight(scope, xz) {
1320
+ const pb = scope.$builder;
1321
+ pb.func('wiSampleHeight', [
1322
+ pb.vec2('xz')
1323
+ ], function() {
1324
+ this.$l.uv = pb.mul(pb.sub(this.xz, this.wiParams.xy), this.wiParams.z);
1325
+ this.$l.h = pb.textureSampleLevel(this.wiHeightTex, this.uv, 0).x;
1326
+ this.$l.d = pb.max(pb.abs(pb.sub(this.uv.x, 0.5)), pb.abs(pb.sub(this.uv.y, 0.5)));
1327
+ this.$l.w = pb.sub(1, pb.smoothStep(this.wiParams2.x, 0.5, this.d));
1328
+ this.$return(pb.mul(this.h, this.w));
1329
+ });
1330
+ return scope.wiSampleHeight(xz);
1331
+ }
1332
+ /**
1333
+ * Foam coverage of the field at a world XZ, 0 to 1, faded across the sponge band.
1334
+ * @internal
1335
+ */ sampleFoam(scope, xz) {
1336
+ const pb = scope.$builder;
1337
+ pb.func('wiSampleFoam', [
1338
+ pb.vec2('xz')
1339
+ ], function() {
1340
+ this.$l.uv = pb.mul(pb.sub(this.xz, this.wiParams.xy), this.wiParams.z);
1341
+ this.$l.f = pb.textureSampleLevel(this.wiHeightTex, this.uv, 0).z;
1342
+ this.$l.d = pb.max(pb.abs(pb.sub(this.uv.x, 0.5)), pb.abs(pb.sub(this.uv.y, 0.5)));
1343
+ this.$l.w = pb.sub(1, pb.smoothStep(this.wiParams2.x, 0.5, this.d));
1344
+ this.$return(pb.mul(this.f, this.w));
1345
+ });
1346
+ return scope.wiSampleFoam(xz);
1347
+ }
1348
+ /**
1349
+ * 1 inside the window, 0 outside, for the debug view to show where the field
1350
+ * reaches.
1351
+ * @internal
1352
+ */ windowMask(scope, xz) {
1353
+ const pb = scope.$builder;
1354
+ pb.func('wiWindowMask', [
1355
+ pb.vec2('xz')
1356
+ ], function() {
1357
+ this.$l.uv = pb.mul(pb.sub(this.xz, this.wiParams.xy), this.wiParams.z);
1358
+ this.$l.inside = pb.and(pb.all(pb.greaterThanEqual(this.uv, pb.vec2(0))), pb.all(pb.lessThanEqual(this.uv, pb.vec2(1))));
1359
+ this.$return(pb.select(pb.float(0), pb.float(1), this.inside));
1360
+ });
1361
+ return scope.wiWindowMask(xz);
1362
+ }
1363
+ /**
1364
+ * Slope of the field at a world XZ, as `(dh/dx, dh/dz)`, by central
1365
+ * differences one texel apart.
1366
+ * @internal
1367
+ */ sampleGradient(scope, xz) {
1368
+ const pb = scope.$builder;
1369
+ const that = this;
1370
+ pb.func('wiSampleGradient', [
1371
+ pb.vec2('xz')
1372
+ ], function() {
1373
+ this.$l.d = this.wiParams.w;
1374
+ this.$l.hr = that.sampleHeight(this, pb.add(this.xz, pb.vec2(this.d, 0)));
1375
+ this.$l.hl = that.sampleHeight(this, pb.sub(this.xz, pb.vec2(this.d, 0)));
1376
+ this.$l.hu = that.sampleHeight(this, pb.add(this.xz, pb.vec2(0, this.d)));
1377
+ this.$l.hd = that.sampleHeight(this, pb.sub(this.xz, pb.vec2(0, this.d)));
1378
+ this.$l.inv = pb.div(0.5, this.d);
1379
+ this.$return(pb.mul(pb.vec2(pb.sub(this.hr, this.hl), pb.sub(this.hu, this.hd)), this.inv));
1380
+ });
1381
+ return scope.wiSampleGradient(xz);
1382
+ }
1383
+ /**
1384
+ * Tilt a unit normal by the field's slope at a world XZ.
1385
+ *
1386
+ * Partial-derivative blending: the normal's own slope is `-n.xz / n.y`, the
1387
+ * field's is added to it and the result renormalised. Multiplying through by
1388
+ * `n.y` avoids the division, so a normal flattened to the horizon by a folded
1389
+ * wave crest contributes nothing rather than blowing up. Exact for a height
1390
+ * field, first-order for a surface with horizontal displacement, which is all
1391
+ * the ambient generators are to a ripple far smaller than their waves.
1392
+ * @internal
1393
+ */ blendNormal(scope, normal, xz) {
1394
+ const pb = scope.$builder;
1395
+ const that = this;
1396
+ pb.func('wiBlendNormal', [
1397
+ pb.vec3('n'),
1398
+ pb.vec2('xz')
1399
+ ], function() {
1400
+ this.$l.g = that.sampleGradient(this, this.xz);
1401
+ this.$return(pb.normalize(pb.vec3(pb.sub(this.n.x, pb.mul(this.g.x, this.n.y)), this.n.y, pb.sub(this.n.z, pb.mul(this.g.y, this.n.y)))));
1402
+ });
1403
+ return scope.wiBlendNormal(normal, xz);
1404
+ }
1405
+ /**
1406
+ * Grow a surface bounding box by what the field can add.
1407
+ * @internal
1408
+ */ expandAABB(outAABB) {
1409
+ outAABB.minPoint.y -= this._maxAmplitude;
1410
+ outAABB.maxPoint.y += this._maxAmplitude;
1411
+ }
1412
+ /**
1413
+ * Heights of the field along the row of texels nearest a world Z, read back
1414
+ * from the GPU: the world X of the first texel centre, the texel size, and
1415
+ * one height per texel. For tools and diagnosis; it costs a GPU readback.
1416
+ */ async readRow(z) {
1417
+ const texture = this._textures?.[this._current];
1418
+ if (!texture) {
1419
+ return null;
1420
+ }
1421
+ const n = this._resolution;
1422
+ const texel = this.texelSize;
1423
+ const row = Math.min(n - 1, Math.max(0, Math.floor((z - this._originZ) / texel)));
1424
+ const half = texture.format === 'rgba16f';
1425
+ const raw = half ? new Uint16Array(n * 4) : new Float32Array(n * 4);
1426
+ await texture.readPixels(0, row, n, 1, 0, 0, raw);
1427
+ const heights = new Float32Array(n);
1428
+ for(let i = 0; i < n; i++){
1429
+ heights[i] = half ? halfToFloat(raw[i * 4]) : raw[i * 4];
1430
+ }
1431
+ return {
1432
+ x0: this._originX + texel * 0.5,
1433
+ z: this._originZ + (row + 0.5) * texel,
1434
+ texel,
1435
+ heights
1436
+ };
1437
+ }
1438
+ /** @internal */ _getDummyBathyTexture(device) {
1439
+ if (!this._dummyBathyTexture) {
1440
+ this._dummyBathyTexture = device.createTexture2D('rgba16f', 1, 1, {
1441
+ mipmapping: false
1442
+ });
1443
+ }
1444
+ return this._dummyBathyTexture;
1445
+ }
1446
+ _clampWaveSpeed() {
1447
+ this._waveSpeed = Math.min(this._waveSpeed, this.maxWaveSpeed);
1448
+ }
1449
+ /** @internal */ _resolveFormat(device) {
1450
+ if (!this._formatResolved) {
1451
+ this._formatResolved = true;
1452
+ const caps = device.getDeviceCaps().textureCaps;
1453
+ for (const fmt of HEIGHT_FORMATS){
1454
+ const info = caps.getTextureFormatInfo(fmt);
1455
+ if (info && info.renderable && info.filterable) {
1456
+ this._format = fmt;
1457
+ break;
1458
+ }
1459
+ }
1460
+ }
1461
+ return this._format;
1462
+ }
1463
+ /** @internal */ _disposeField() {
1464
+ this._externalFramebuffers?.forEach((fb)=>fb.dispose());
1465
+ this._externalFramebuffers = null;
1466
+ this._externalTextures?.forEach((tex)=>tex.dispose());
1467
+ this._externalTextures = null;
1468
+ if (this._framebuffers) {
1469
+ this._framebuffers[0].dispose();
1470
+ this._framebuffers[1].dispose();
1471
+ this._framebuffers = null;
1472
+ }
1473
+ if (this._textures) {
1474
+ this._textures[0].dispose();
1475
+ this._textures[1].dispose();
1476
+ this._textures = null;
1477
+ }
1478
+ this._obstacleFramebuffer?.dispose();
1479
+ this._obstacleFramebuffer = null;
1480
+ this._obstacleTexture?.dispose();
1481
+ this._obstacleTexture = null;
1482
+ this._surfFramebuffers?.forEach((fb)=>fb.dispose());
1483
+ this._surfFramebuffers = null;
1484
+ this._surfTextures?.forEach((tex)=>tex.dispose());
1485
+ this._surfTextures = null;
1486
+ this._hasOrigin = false;
1487
+ this._current = 0;
1488
+ this._accumulator = 0;
1489
+ }
1490
+ /**
1491
+ * Create the ping-pong pair and the obstacle mask if they are missing,
1492
+ * cleared to still water and no obstacles.
1493
+ * @internal
1494
+ */ _ensureField(device) {
1495
+ if (this._textures) {
1496
+ return;
1497
+ }
1498
+ const format = this._resolveFormat(device);
1499
+ if (!format) {
1500
+ return;
1501
+ }
1502
+ const res = this._resolution;
1503
+ const texA = device.createTexture2D(format, res, res, {
1504
+ mipmapping: false
1505
+ });
1506
+ const texB = device.createTexture2D(format, res, res, {
1507
+ mipmapping: false
1508
+ });
1509
+ const texO = device.createTexture2D('r8unorm', res, res, {
1510
+ mipmapping: false
1511
+ });
1512
+ texA.name = 'WaterInteractionA';
1513
+ texB.name = 'WaterInteractionB';
1514
+ texO.name = 'WaterInteractionObstacle';
1515
+ const fbA = device.createFrameBuffer([
1516
+ texA
1517
+ ], null);
1518
+ const fbB = device.createFrameBuffer([
1519
+ texB
1520
+ ], null);
1521
+ const fbO = device.createFrameBuffer([
1522
+ texO
1523
+ ], null);
1524
+ const externalA = device.createTexture2D(format, res, res, {
1525
+ mipmapping: false
1526
+ });
1527
+ const externalB = device.createTexture2D(format, res, res, {
1528
+ mipmapping: false
1529
+ });
1530
+ externalA.name = 'WaterInteractionExternalA';
1531
+ externalB.name = 'WaterInteractionExternalB';
1532
+ this._externalTextures = [
1533
+ externalA,
1534
+ externalB
1535
+ ];
1536
+ this._externalFramebuffers = [
1537
+ device.createFrameBuffer([
1538
+ externalA
1539
+ ], null),
1540
+ device.createFrameBuffer([
1541
+ externalB
1542
+ ], null)
1543
+ ];
1544
+ device.pushDeviceStates();
1545
+ for (const fb of this._externalFramebuffers){
1546
+ device.setFramebuffer(fb);
1547
+ device.clearFrameBuffer(Vector4.zero(), null, null);
1548
+ }
1549
+ device.setFramebuffer(fbA);
1550
+ device.clearFrameBuffer(Vector4.zero(), null, null);
1551
+ device.setFramebuffer(fbB);
1552
+ device.clearFrameBuffer(Vector4.zero(), null, null);
1553
+ device.setFramebuffer(fbO);
1554
+ device.clearFrameBuffer(Vector4.zero(), null, null);
1555
+ device.popDeviceStates();
1556
+ this._textures = [
1557
+ texA,
1558
+ texB
1559
+ ];
1560
+ this._framebuffers = [
1561
+ fbA,
1562
+ fbB
1563
+ ];
1564
+ this._obstacleTexture = texO;
1565
+ this._obstacleFramebuffer = fbO;
1566
+ const surfA = device.createTexture2D(format, MAX_DISTURBERS, 1, {
1567
+ mipmapping: false
1568
+ });
1569
+ const surfB = device.createTexture2D(format, MAX_DISTURBERS, 1, {
1570
+ mipmapping: false
1571
+ });
1572
+ surfA.name = 'WaterInteractionSurfaceA';
1573
+ surfB.name = 'WaterInteractionSurfaceB';
1574
+ this._surfTextures = [
1575
+ surfA,
1576
+ surfB
1577
+ ];
1578
+ this._surfFramebuffers = [
1579
+ device.createFrameBuffer([
1580
+ surfA
1581
+ ], null),
1582
+ device.createFrameBuffer([
1583
+ surfB
1584
+ ], null)
1585
+ ];
1586
+ device.pushDeviceStates();
1587
+ for (const fb of this._surfFramebuffers){
1588
+ device.setFramebuffer(fb);
1589
+ device.clearFrameBuffer(Vector4.zero(), null, null);
1590
+ }
1591
+ device.popDeviceStates();
1592
+ this._surfCurrent = 0;
1593
+ this._current = 0;
1594
+ this._version++;
1595
+ }
1596
+ /**
1597
+ * Place the window on the focus, snapped to the texel grid, and record the
1598
+ * integer shift from where it was.
1599
+ * @internal
1600
+ */ _moveWindow() {
1601
+ let fx = this._focus.x;
1602
+ let fz = this._focus.y;
1603
+ if (this._followMode === 'fixed') {
1604
+ fx = this._center.x;
1605
+ fz = this._center.y;
1606
+ } else if (this._followMode === 'node' && this._followNode) {
1607
+ const m = this._followNode.worldMatrix;
1608
+ fx = m.m03;
1609
+ fz = m.m23;
1610
+ }
1611
+ const texel = this.texelSize;
1612
+ const half = this._windowSize * 0.5;
1613
+ const ox = Math.floor((fx - half) / texel) * texel;
1614
+ const oz = Math.floor((fz - half) / texel) * texel;
1615
+ if (!this._hasOrigin) {
1616
+ this._shift.setXY(0, 0);
1617
+ this._hasOrigin = true;
1618
+ } else {
1619
+ this._shift.setXY(Math.round((ox - this._originX) / texel), Math.round((oz - this._originZ) / texel));
1620
+ }
1621
+ this._originX = ox;
1622
+ this._originZ = oz;
1623
+ }
1624
+ /** @internal */ _getProgram(device) {
1625
+ if (!this._program) {
1626
+ this._program = device.buildRenderProgram(createWaterInteractionStepShader());
1627
+ this._program.name = '@WaterInteraction_Step';
1628
+ this._bindGroup = device.createBindGroup(this._program.bindGroupLayouts[0]);
1629
+ }
1630
+ return this._program;
1631
+ }
1632
+ /** @internal */ _getObstacleProgram(device) {
1633
+ if (!this._obstacleProgram) {
1634
+ this._obstacleProgram = device.buildRenderProgram(createWaterInteractionObstacleShader());
1635
+ this._obstacleProgram.name = '@WaterInteraction_Obstacle';
1636
+ this._obstacleBindGroup = device.createBindGroup(this._obstacleProgram.bindGroupLayouts[0]);
1637
+ }
1638
+ return this._obstacleProgram;
1639
+ }
1640
+ }
1641
+ /**
1642
+ * A wave generator that layers a {@link WaterInteraction} field on top of
1643
+ * another generator.
1644
+ *
1645
+ * Built by the water material when an interaction is attached, so that every
1646
+ * consumer of the surface - the material itself, the surface-point feedback
1647
+ * query, the caustics - sees one displaced surface without knowing about the
1648
+ * layering. The base generator is owned by the material and updated by it; this
1649
+ * object owns nothing.
1650
+ *
1651
+ * @internal
1652
+ */ class InteractiveWaveGenerator extends Disposable {
1653
+ _includeDisturbers;
1654
+ _base;
1655
+ _interaction;
1656
+ calcPrevVertexPosition;
1657
+ calcSurfaceCoverage;
1658
+ calcSheetThickness;
1659
+ calcUnresolvedSlopeVariance;
1660
+ constructor(base, interaction, _includeDisturbers = true){
1661
+ super(), this._includeDisturbers = _includeDisturbers;
1662
+ this._base = base;
1663
+ this._interaction = interaction;
1664
+ // The water's edge on the beach and its thickness there are the wrapped
1665
+ // generator's: the field is still water on dry land and dies out over the
1666
+ // last centimetres of depth, so it does not move that edge
1667
+ if (base.calcSurfaceCoverage) {
1668
+ this.calcSurfaceCoverage = base.calcSurfaceCoverage.bind(base);
1669
+ }
1670
+ if (base.calcSheetThickness) {
1671
+ this.calcSheetThickness = base.calcSheetThickness.bind(base);
1672
+ }
1673
+ if (base.calcUnresolvedSlopeVariance) {
1674
+ this.calcUnresolvedSlopeVariance = base.calcUnresolvedSlopeVariance.bind(base);
1675
+ }
1676
+ if (base.calcPrevVertexPosition) {
1677
+ this.calcPrevVertexPosition = this.prevVertexPosition;
1678
+ }
1679
+ }
1680
+ /**
1681
+ * The wrapped generator's previous position with the field's height of now
1682
+ * on it: the field keeps no height per frame, and its ripples are slow
1683
+ * against the temporal filter.
1684
+ */ prevVertexPosition(scope, inPos, outPos, vertexSpacing) {
1685
+ const pb = scope.$builder;
1686
+ const that = this;
1687
+ pb.func('wiCalcPrevPosition', [
1688
+ pb.vec3('inPos'),
1689
+ pb.vec3('outPos').out(),
1690
+ pb.float('cell')
1691
+ ], function() {
1692
+ that._base.calcPrevVertexPosition(this, this.inPos, this.outPos, this.cell);
1693
+ this.outPos = pb.add(this.outPos, pb.vec3(0, that._interaction.sampleHeight(this, this.inPos.xz), 0));
1694
+ });
1695
+ scope.wiCalcPrevPosition(inPos, outPos, vertexSpacing ?? pb.float(0));
1696
+ }
1697
+ /** The generator supplying the ambient surface. */ get base() {
1698
+ return this._base;
1699
+ }
1700
+ /** The field layered on top. */ get interaction() {
1701
+ return this._interaction;
1702
+ }
1703
+ clone() {
1704
+ return new InteractiveWaveGenerator(this._base.clone(), this._interaction, this._includeDisturbers);
1705
+ }
1706
+ get version() {
1707
+ return this._base.version + this._interaction.version;
1708
+ }
1709
+ setupUniforms(scope, uniformGroup) {
1710
+ this._base.setupUniforms(scope, uniformGroup);
1711
+ this._interaction.setupUniforms(scope, uniformGroup);
1712
+ }
1713
+ calcVertexPositionAndNormal(scope, inPos, outPos, outNormal, vertexSpacing) {
1714
+ const pb = scope.$builder;
1715
+ const that = this;
1716
+ pb.func('wiCalcPositionAndNormal', [
1717
+ pb.vec3('inPos'),
1718
+ pb.vec3('outPos').out(),
1719
+ pb.vec3('outNormal').out(),
1720
+ pb.float('cell')
1721
+ ], function() {
1722
+ that._base.calcVertexPositionAndNormal(this, this.inPos, this.outPos, this.outNormal, this.cell);
1723
+ // Sampled at the undisplaced position, which is also where the fragment
1724
+ // stage samples it, so the vertex height and the fragment slope agree.
1725
+ this.$l.h = that._interaction.sampleHeight(this, this.inPos.xz);
1726
+ this.outPos = pb.add(this.outPos, pb.vec3(0, this.h, 0));
1727
+ });
1728
+ scope.wiCalcPositionAndNormal(inPos, outPos, outNormal, vertexSpacing ?? pb.float(0));
1729
+ }
1730
+ calcFragmentNormal(scope, xz, vertexNormal) {
1731
+ const pb = scope.$builder;
1732
+ const that = this;
1733
+ pb.func('wiCalcFragmentNormal', [
1734
+ pb.vec2('xz'),
1735
+ pb.vec3('vn')
1736
+ ], function() {
1737
+ this.$l.n = pb.normalize(that._base.calcFragmentNormal(this, this.xz, this.vn));
1738
+ this.$return(that._interaction.blendNormal(this, this.n, this.xz));
1739
+ });
1740
+ return scope.wiCalcFragmentNormal(xz, vertexNormal);
1741
+ }
1742
+ calcFragmentNormalAndFoam(scope, xz, vertexNormal, foamSample, footprint) {
1743
+ const pb = scope.$builder;
1744
+ const that = this;
1745
+ // The foam sample and footprint, when there are, are passed on to the wrapped generator
1746
+ const params = [
1747
+ pb.vec2('xz'),
1748
+ pb.vec3('vn')
1749
+ ];
1750
+ const args = [
1751
+ xz,
1752
+ vertexNormal
1753
+ ];
1754
+ if (foamSample) {
1755
+ params.push(pb.vec3('fs').inout());
1756
+ args.push(foamSample);
1757
+ }
1758
+ if (footprint) {
1759
+ params.push(pb.vec4('fp'));
1760
+ args.push(footprint);
1761
+ }
1762
+ const funcName = `wiCalcNormalAndFoam${foamSample ? 'S' : ''}${footprint ? 'F' : ''}`;
1763
+ pb.func(funcName, params, function() {
1764
+ this.$l.nf = that._base.calcFragmentNormalAndFoam(this, this.xz, this.vn, foamSample ? this.fs : undefined, footprint ? this.fp : undefined);
1765
+ this.$l.n = pb.normalize(this.nf.xyz);
1766
+ // Independent densities compose as overlapping area, the same way the
1767
+ // material combines crest foam with shoreline foam.
1768
+ this.$l.f = that._interaction.sampleFoam(this, this.xz);
1769
+ this.$l.foam = pb.sub(1, pb.mul(pb.sub(1, pb.clamp(this.nf.w, 0, 1)), pb.sub(1, this.f)));
1770
+ this.$return(pb.vec4(that._interaction.blendNormal(this, this.n, this.xz), this.foam));
1771
+ });
1772
+ return scope[funcName](...args);
1773
+ }
1774
+ applyWaterBindGroup(bindGroup) {
1775
+ this._base.applyWaterBindGroup(bindGroup);
1776
+ this._interaction.applyBindGroup(bindGroup, this._includeDisturbers);
1777
+ }
1778
+ calcClipmapTileAABB(minX, maxX, minZ, maxZ, y, outAABB) {
1779
+ this._base.calcClipmapTileAABB(minX, maxX, minZ, maxZ, y, outAABB);
1780
+ this._interaction.expandAABB(outAABB);
1781
+ }
1782
+ /** The material steps the base and the field itself; nothing to do here. */ update() {}
1783
+ getHash() {
1784
+ return `${this._base.getHash()}:WI`;
1785
+ }
1786
+ needUpdate() {
1787
+ return true;
1788
+ }
1789
+ isOk(device) {
1790
+ return this._base.isOk(device) && this._interaction.isOk(device);
1791
+ }
1792
+ }
1793
+
1794
+ export { InteractiveWaveGenerator, WaterDisturber, WaterInteraction, createWaterInteractionObstacleShader, createWaterInteractionStepShader, createWaterInteractionSurfaceShader };
1795
+ //# sourceMappingURL=water_interaction.js.map