@zephyr3d/scene 0.9.26 → 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 (506) 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 +146 -30
  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 +300 -56
  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 +177 -3
  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 +178 -200
  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 +30475 -19865
  109. package/dist/index.js +67 -12
  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/hairstrand.js +235 -235
  118. package/dist/material/hairstrand.js.map +1 -1
  119. package/dist/material/lambert.js +4 -1
  120. package/dist/material/lambert.js.map +1 -1
  121. package/dist/material/material.js +362 -300
  122. package/dist/material/material.js.map +1 -1
  123. package/dist/material/meshmaterial.js +118 -45
  124. package/dist/material/meshmaterial.js.map +1 -1
  125. package/dist/material/mixins/lightmodel/blinnphong.js +1 -1
  126. package/dist/material/mixins/lightmodel/blinnphong.js.map +1 -1
  127. package/dist/material/mixins/lightmodel/lambert.js +1 -1
  128. package/dist/material/mixins/lightmodel/lambert.js.map +1 -1
  129. package/dist/material/mixins/lightmodel/pbrblueprintmixin.js +34 -61
  130. package/dist/material/mixins/lightmodel/pbrblueprintmixin.js.map +1 -1
  131. package/dist/material/mixins/lightmodel/pbrmetallicroughness.js +16 -2
  132. package/dist/material/mixins/lightmodel/pbrmetallicroughness.js.map +1 -1
  133. package/dist/material/mixins/lightmodel/pbrspecularglossness.js +2 -1
  134. package/dist/material/mixins/lightmodel/pbrspecularglossness.js.map +1 -1
  135. package/dist/material/mixins/lit.js +223 -83
  136. package/dist/material/mixins/lit.js.map +1 -1
  137. package/dist/material/mixins/pbr/common.js +31 -206
  138. package/dist/material/mixins/pbr/common.js.map +1 -1
  139. package/dist/material/mtoon.js +1 -1
  140. package/dist/material/mtoon.js.map +1 -1
  141. package/dist/material/pbrmr.js +3 -0
  142. package/dist/material/pbrmr.js.map +1 -1
  143. package/dist/material/pbrsg.js +3 -0
  144. package/dist/material/pbrsg.js.map +1 -1
  145. package/dist/material/sdf_debug.js +78 -0
  146. package/dist/material/sdf_debug.js.map +1 -0
  147. package/dist/material/shader/helper.js +733 -190
  148. package/dist/material/shader/helper.js.map +1 -1
  149. package/dist/material/shader/terrain_blend.js +204 -43
  150. package/dist/material/shader/terrain_blend.js.map +1 -1
  151. package/dist/material/skin.js +331 -297
  152. package/dist/material/skin.js.map +1 -1
  153. package/dist/material/skinprofile.js +1058 -0
  154. package/dist/material/skinprofile.js.map +1 -0
  155. package/dist/material/sss.js +353 -0
  156. package/dist/material/sss.js.map +1 -0
  157. package/dist/material/sss_kernel.js +265 -0
  158. package/dist/material/sss_kernel.js.map +1 -0
  159. package/dist/material/sssprofile.js +879 -0
  160. package/dist/material/sssprofile.js.map +1 -0
  161. package/dist/material/subsurfaceprofile.js +4 -8
  162. package/dist/material/subsurfaceprofile.js.map +1 -1
  163. package/dist/material/terrain-cm.js +464 -188
  164. package/dist/material/terrain-cm.js.map +1 -1
  165. package/dist/material/unlit.js +3 -0
  166. package/dist/material/unlit.js.map +1 -1
  167. package/dist/material/water.js +2009 -201
  168. package/dist/material/water.js.map +1 -1
  169. package/dist/node_modules/meshoptimizer/LICENSE +21 -0
  170. package/dist/node_modules/meshoptimizer/meshopt_decoder.js +197 -0
  171. package/dist/node_modules/meshoptimizer/meshopt_decoder.js.map +1 -0
  172. package/dist/node_modules/meshoptimizer/meshopt_simplifier.js +677 -0
  173. package/dist/node_modules/meshoptimizer/meshopt_simplifier.js.map +1 -0
  174. package/dist/physics/character.js +226 -0
  175. package/dist/physics/character.js.map +1 -0
  176. package/dist/physics/collider.js +209 -0
  177. package/dist/physics/collider.js.map +1 -0
  178. package/dist/physics/events.js +79 -0
  179. package/dist/physics/events.js.map +1 -0
  180. package/dist/physics/joint.js +272 -0
  181. package/dist/physics/joint.js.map +1 -0
  182. package/dist/physics/node_physics.js +234 -0
  183. package/dist/physics/node_physics.js.map +1 -0
  184. package/dist/physics/part.js +91 -0
  185. package/dist/physics/part.js.map +1 -0
  186. package/dist/physics/rigid_body.js +379 -0
  187. package/dist/physics/rigid_body.js.map +1 -0
  188. package/dist/physics/vehicle.js +335 -0
  189. package/dist/physics/vehicle.js.map +1 -0
  190. package/dist/physics/world.js +25 -0
  191. package/dist/physics/world.js.map +1 -0
  192. package/dist/posteffect/bloom.js +49 -14
  193. package/dist/posteffect/bloom.js.map +1 -1
  194. package/dist/posteffect/motionblur.js +668 -63
  195. package/dist/posteffect/motionblur.js.map +1 -1
  196. package/dist/posteffect/posteffect.js +12 -1
  197. package/dist/posteffect/posteffect.js.map +1 -1
  198. package/dist/posteffect/postsss.js +572 -0
  199. package/dist/posteffect/postsss.js.map +1 -0
  200. package/dist/posteffect/sao.js +48 -50
  201. package/dist/posteffect/sao.js.map +1 -1
  202. package/dist/posteffect/skinsss.js +605 -355
  203. package/dist/posteffect/skinsss.js.map +1 -1
  204. package/dist/posteffect/ssgi.js +91 -162
  205. package/dist/posteffect/ssgi.js.map +1 -1
  206. package/dist/posteffect/ssr.js +3 -11
  207. package/dist/posteffect/ssr.js.map +1 -1
  208. package/dist/posteffect/sss.js +34 -15
  209. package/dist/posteffect/sss.js.map +1 -1
  210. package/dist/posteffect/taa.js +1 -0
  211. package/dist/posteffect/taa.js.map +1 -1
  212. package/dist/render/abuffer_oit.js +40 -39
  213. package/dist/render/abuffer_oit.js.map +1 -1
  214. package/dist/render/clipmap.js +328 -62
  215. package/dist/render/clipmap.js.map +1 -1
  216. package/dist/render/cluster_light.js +958 -224
  217. package/dist/render/cluster_light.js.map +1 -1
  218. package/dist/render/cull_visitor.js +26 -5
  219. package/dist/render/cull_visitor.js.map +1 -1
  220. package/dist/render/debug_views.js +37 -0
  221. package/dist/render/debug_views.js.map +1 -0
  222. package/dist/render/depthpass.js +5 -1
  223. package/dist/render/depthpass.js.map +1 -1
  224. package/dist/render/drawable_mixin.js +53 -6
  225. package/dist/render/drawable_mixin.js.map +1 -1
  226. package/dist/render/dualdepthpeeling_oit.js +53 -53
  227. package/dist/render/dualdepthpeeling_oit.js.map +1 -1
  228. package/dist/render/envlight.js +196 -113
  229. package/dist/render/envlight.js.map +1 -1
  230. package/dist/render/fbm_wavegenerator.js +8 -13
  231. package/dist/render/fbm_wavegenerator.js.map +1 -1
  232. package/dist/render/fft_wavegenerator.js +268 -136
  233. package/dist/render/fft_wavegenerator.js.map +1 -1
  234. package/dist/render/gerstner_wavegenerator.js +113 -28
  235. package/dist/render/gerstner_wavegenerator.js.map +1 -1
  236. package/dist/render/gi/probe_volume.js +1334 -0
  237. package/dist/render/gi/probe_volume.js.map +1 -0
  238. package/dist/render/gi/scene_gi.js +282 -0
  239. package/dist/render/gi/scene_gi.js.map +1 -0
  240. package/dist/render/gi/voxel_atlas.js +198 -0
  241. package/dist/render/gi/voxel_atlas.js.map +1 -0
  242. package/dist/render/gi/voxel_lighting.js +652 -0
  243. package/dist/render/gi/voxel_lighting.js.map +1 -0
  244. package/dist/render/gi/voxel_scene.js +642 -0
  245. package/dist/render/gi/voxel_scene.js.map +1 -0
  246. package/dist/render/gi/voxelize_pass.js +223 -0
  247. package/dist/render/gi/voxelize_pass.js.map +1 -0
  248. package/dist/render/globalbindgroup_allocator.js +3 -2
  249. package/dist/render/globalbindgroup_allocator.js.map +1 -1
  250. package/dist/render/gpu_picking.js +9 -36
  251. package/dist/render/gpu_picking.js.map +1 -1
  252. package/dist/render/hzb.js +80 -22
  253. package/dist/render/hzb.js.map +1 -1
  254. package/dist/render/instance_culling.js +499 -0
  255. package/dist/render/instance_culling.js.map +1 -0
  256. package/dist/render/lightpass.js +23 -9
  257. package/dist/render/lightpass.js.map +1 -1
  258. package/dist/render/lod.js +46 -0
  259. package/dist/render/lod.js.map +1 -0
  260. package/dist/render/oit.js +11 -11
  261. package/dist/render/oit.js.map +1 -1
  262. package/dist/render/primitive.js +301 -9
  263. package/dist/render/primitive.js.map +1 -1
  264. package/dist/render/primitive_readback.js +209 -0
  265. package/dist/render/primitive_readback.js.map +1 -0
  266. package/dist/render/render_queue.js +198 -11
  267. package/dist/render/render_queue.js.map +1 -1
  268. package/dist/render/renderer.js +14 -7
  269. package/dist/render/renderer.js.map +1 -1
  270. package/dist/render/rendergraph/blackboard.js +15 -1
  271. package/dist/render/rendergraph/blackboard.js.map +1 -1
  272. package/dist/render/rendergraph/executor.js +136 -0
  273. package/dist/render/rendergraph/executor.js.map +1 -1
  274. package/dist/render/rendergraph/forward_plus_builder.js +585 -59
  275. package/dist/render/rendergraph/forward_plus_builder.js.map +1 -1
  276. package/dist/render/rendergraph/frame_resource_requirements.js +1 -0
  277. package/dist/render/rendergraph/frame_resource_requirements.js.map +1 -1
  278. package/dist/render/rendergraph/history_resources.js +8 -1
  279. package/dist/render/rendergraph/history_resources.js.map +1 -1
  280. package/dist/render/rendergraph/scene_render_context.js +31 -2
  281. package/dist/render/rendergraph/scene_render_context.js.map +1 -1
  282. package/dist/render/rendergraph/types.js.map +1 -1
  283. package/dist/render/renderpass.js +28 -5
  284. package/dist/render/renderpass.js.map +1 -1
  285. package/dist/render/sdf/box_allocator.js +98 -0
  286. package/dist/render/sdf/box_allocator.js.map +1 -0
  287. package/dist/render/sdf/global_sdf.js +865 -0
  288. package/dist/render/sdf/global_sdf.js.map +1 -0
  289. package/dist/render/sdf/scene_sdf.js +562 -0
  290. package/dist/render/sdf/scene_sdf.js.map +1 -0
  291. package/dist/render/sdf/sdf_atlas.js +227 -0
  292. package/dist/render/sdf/sdf_atlas.js.map +1 -0
  293. package/dist/render/sdf/sdf_programs.js +42 -0
  294. package/dist/render/sdf/sdf_programs.js.map +1 -0
  295. package/dist/render/sdf/sdf_view.js +199 -0
  296. package/dist/render/sdf/sdf_view.js.map +1 -0
  297. package/dist/render/sdf/waterline.js +610 -0
  298. package/dist/render/sdf/waterline.js.map +1 -0
  299. package/dist/render/shadow_mask_pass.js +142 -25
  300. package/dist/render/shadow_mask_pass.js.map +1 -1
  301. package/dist/render/shore_wave_generator.js +1193 -0
  302. package/dist/render/shore_wave_generator.js.map +1 -0
  303. package/dist/render/sky.js +516 -86
  304. package/dist/render/sky.js.map +1 -1
  305. package/dist/render/transmission_thickness_pass.js +477 -0
  306. package/dist/render/transmission_thickness_pass.js.map +1 -0
  307. package/dist/render/underwater.js +524 -0
  308. package/dist/render/underwater.js.map +1 -0
  309. package/dist/render/virtualtexture/virtual_texture.js +1214 -0
  310. package/dist/render/virtualtexture/virtual_texture.js.map +1 -0
  311. package/dist/render/volumetric_cloud/cloud_common.js +472 -0
  312. package/dist/render/volumetric_cloud/cloud_common.js.map +1 -0
  313. package/dist/render/volumetric_cloud/cloud_noise.js +189 -0
  314. package/dist/render/volumetric_cloud/cloud_noise.js.map +1 -0
  315. package/dist/render/volumetric_cloud/debug_view.js +131 -0
  316. package/dist/render/volumetric_cloud/debug_view.js.map +1 -0
  317. package/dist/render/volumetric_cloud/resources.js +380 -0
  318. package/dist/render/volumetric_cloud/resources.js.map +1 -0
  319. package/dist/render/volumetric_cloud/sky_cache.js +380 -0
  320. package/dist/render/volumetric_cloud/sky_cache.js.map +1 -0
  321. package/dist/render/volumetric_cloud/volumetric_cloud.js +318 -0
  322. package/dist/render/volumetric_cloud/volumetric_cloud.js.map +1 -0
  323. package/dist/render/water_caustics.js +1195 -0
  324. package/dist/render/water_caustics.js.map +1 -0
  325. package/dist/render/water_foam.js +627 -0
  326. package/dist/render/water_foam.js.map +1 -0
  327. package/dist/render/water_interaction.js +1795 -0
  328. package/dist/render/water_interaction.js.map +1 -0
  329. package/dist/render/weightedblended_oit.js +36 -36
  330. package/dist/render/weightedblended_oit.js.map +1 -1
  331. package/dist/scene/basesprite.js +9 -4
  332. package/dist/scene/basesprite.js.map +1 -1
  333. package/dist/scene/batchgroup.js +108 -12
  334. package/dist/scene/batchgroup.js.map +1 -1
  335. package/dist/scene/environment.js +28 -7
  336. package/dist/scene/environment.js.map +1 -1
  337. package/dist/scene/foliage.js +1020 -0
  338. package/dist/scene/foliage.js.map +1 -0
  339. package/dist/scene/foliage_buckets.js +147 -0
  340. package/dist/scene/foliage_buckets.js.map +1 -0
  341. package/dist/scene/foliage_mask.js +358 -0
  342. package/dist/scene/foliage_mask.js.map +1 -0
  343. package/dist/scene/hair.js +15 -4
  344. package/dist/scene/hair.js.map +1 -1
  345. package/dist/scene/light.js +179 -13
  346. package/dist/scene/light.js.map +1 -1
  347. package/dist/scene/mesh.js +327 -45
  348. package/dist/scene/mesh.js.map +1 -1
  349. package/dist/scene/meshdrawable.js +6 -0
  350. package/dist/scene/meshdrawable.js.map +1 -1
  351. package/dist/scene/msdftext.js +9 -3
  352. package/dist/scene/msdftext.js.map +1 -1
  353. package/dist/scene/msdftextsprite.js +9 -3
  354. package/dist/scene/msdftextsprite.js.map +1 -1
  355. package/dist/scene/particlesys.js +8 -2
  356. package/dist/scene/particlesys.js.map +1 -1
  357. package/dist/scene/physics_settings.js +157 -0
  358. package/dist/scene/physics_settings.js.map +1 -0
  359. package/dist/scene/scene.js +190 -44
  360. package/dist/scene/scene.js.map +1 -1
  361. package/dist/scene/scene_node.js +147 -6
  362. package/dist/scene/scene_node.js.map +1 -1
  363. package/dist/scene/terrain-cm/bladegrassmaterial.js +735 -0
  364. package/dist/scene/terrain-cm/bladegrassmaterial.js.map +1 -0
  365. package/dist/scene/terrain-cm/grass.js +881 -42
  366. package/dist/scene/terrain-cm/grass.js.map +1 -1
  367. package/dist/scene/terrain-cm/grass_gpu.js +929 -0
  368. package/dist/scene/terrain-cm/grass_gpu.js.map +1 -0
  369. package/dist/scene/terrain-cm/grassmaterial.js +180 -9
  370. package/dist/scene/terrain-cm/grassmaterial.js.map +1 -1
  371. package/dist/scene/terrain-cm/terrain-cm.js +465 -36
  372. package/dist/scene/terrain-cm/terrain-cm.js.map +1 -1
  373. package/dist/scene/terrain-cm/terrain_gi_albedo.js +257 -0
  374. package/dist/scene/terrain-cm/terrain_gi_albedo.js.map +1 -0
  375. package/dist/scene/terrain-cm/terrain_rvt.js +395 -0
  376. package/dist/scene/terrain-cm/terrain_rvt.js.map +1 -0
  377. package/dist/scene/update_queue.js +54 -0
  378. package/dist/scene/update_queue.js.map +1 -0
  379. package/dist/scene/water.js +997 -96
  380. package/dist/scene/water.js.map +1 -1
  381. package/dist/scene/water_surface_sampler.js +324 -0
  382. package/dist/scene/water_surface_sampler.js.map +1 -0
  383. package/dist/scene/wind.js +317 -0
  384. package/dist/scene/wind.js.map +1 -0
  385. package/dist/shaders/atmosphere.js +388 -166
  386. package/dist/shaders/atmosphere.js.map +1 -1
  387. package/dist/shaders/fog.js +100 -32
  388. package/dist/shaders/fog.js.map +1 -1
  389. package/dist/shaders/gi.js +365 -0
  390. package/dist/shaders/gi.js.map +1 -0
  391. package/dist/shaders/ltc_rect.js +202 -0
  392. package/dist/shaders/ltc_rect.js.map +1 -0
  393. package/dist/shaders/misc.js +1 -10
  394. package/dist/shaders/misc.js.map +1 -1
  395. package/dist/shaders/noise.js +5 -1
  396. package/dist/shaders/noise.js.map +1 -1
  397. package/dist/shaders/probes.js +309 -0
  398. package/dist/shaders/probes.js.map +1 -0
  399. package/dist/shaders/sdf.js +537 -0
  400. package/dist/shaders/sdf.js.map +1 -0
  401. package/dist/shaders/shadow.js +104 -70
  402. package/dist/shaders/shadow.js.map +1 -1
  403. package/dist/shaders/skin_brdf.js +243 -0
  404. package/dist/shaders/skin_brdf.js.map +1 -0
  405. package/dist/shaders/ssr.js +6 -159
  406. package/dist/shaders/ssr.js.map +1 -1
  407. package/dist/shaders/temporal.js +38 -27
  408. package/dist/shaders/temporal.js.map +1 -1
  409. package/dist/shaders/water.js +40 -87
  410. package/dist/shaders/water.js.map +1 -1
  411. package/dist/shaders/water_medium.js +42 -0
  412. package/dist/shaders/water_medium.js.map +1 -0
  413. package/dist/shadow/dom.js +110 -19
  414. package/dist/shadow/dom.js.map +1 -1
  415. package/dist/shadow/esm.js +3 -3
  416. package/dist/shadow/esm.js.map +1 -1
  417. package/dist/shadow/pcf_opt.js +15 -48
  418. package/dist/shadow/pcf_opt.js.map +1 -1
  419. package/dist/shadow/pcf_pd.js +15 -53
  420. package/dist/shadow/pcf_pd.js.map +1 -1
  421. package/dist/shadow/pcss.js +34 -9
  422. package/dist/shadow/pcss.js.map +1 -1
  423. package/dist/shadow/shadow_impl.js +3 -8
  424. package/dist/shadow/shadow_impl.js.map +1 -1
  425. package/dist/shadow/shadow_region.js +87 -49
  426. package/dist/shadow/shadow_region.js.map +1 -1
  427. package/dist/shadow/shadowmapper.js +93 -53
  428. package/dist/shadow/shadowmapper.js.map +1 -1
  429. package/dist/shadow/ssm.js +18 -72
  430. package/dist/shadow/ssm.js.map +1 -1
  431. package/dist/shadow/vsm.js +3 -3
  432. package/dist/shadow/vsm.js.map +1 -1
  433. package/dist/shapes/shape.js +44 -1
  434. package/dist/shapes/shape.js.map +1 -1
  435. package/dist/utility/buoyancy.js +287 -0
  436. package/dist/utility/buoyancy.js.map +1 -0
  437. package/dist/utility/physical.js +10 -1
  438. package/dist/utility/physical.js.map +1 -1
  439. package/dist/utility/pmrem.js +17 -43
  440. package/dist/utility/pmrem.js.map +1 -1
  441. package/dist/utility/sdf/bake.js +435 -0
  442. package/dist/utility/sdf/bake.js.map +1 -0
  443. package/dist/utility/sdf/bvh.js +576 -0
  444. package/dist/utility/sdf/bvh.js.map +1 -0
  445. package/dist/utility/serialization/manager.js +36 -6
  446. package/dist/utility/serialization/manager.js.map +1 -1
  447. package/dist/utility/serialization/scene/animation.js +77 -7
  448. package/dist/utility/serialization/scene/animation.js.map +1 -1
  449. package/dist/utility/serialization/scene/batch.js +11 -4
  450. package/dist/utility/serialization/scene/batch.js.map +1 -1
  451. package/dist/utility/serialization/scene/camera.js +140 -279
  452. package/dist/utility/serialization/scene/camera.js.map +1 -1
  453. package/dist/utility/serialization/scene/cloth.js +25 -0
  454. package/dist/utility/serialization/scene/cloth.js.map +1 -0
  455. package/dist/utility/serialization/scene/common.js +14 -6
  456. package/dist/utility/serialization/scene/common.js.map +1 -1
  457. package/dist/utility/serialization/scene/foliage.js +821 -0
  458. package/dist/utility/serialization/scene/foliage.js.map +1 -0
  459. package/dist/utility/serialization/scene/hair.js +11 -4
  460. package/dist/utility/serialization/scene/hair.js.map +1 -1
  461. package/dist/utility/serialization/scene/light.js +115 -7
  462. package/dist/utility/serialization/scene/light.js.map +1 -1
  463. package/dist/utility/serialization/scene/material.js +388 -220
  464. package/dist/utility/serialization/scene/material.js.map +1 -1
  465. package/dist/utility/serialization/scene/mesh.js +69 -4
  466. package/dist/utility/serialization/scene/mesh.js.map +1 -1
  467. package/dist/utility/serialization/scene/node.js +140 -5
  468. package/dist/utility/serialization/scene/node.js.map +1 -1
  469. package/dist/utility/serialization/scene/node_physics.js +1318 -0
  470. package/dist/utility/serialization/scene/node_physics.js.map +1 -0
  471. package/dist/utility/serialization/scene/particle.js +11 -4
  472. package/dist/utility/serialization/scene/particle.js.map +1 -1
  473. package/dist/utility/serialization/scene/physics.js +120 -0
  474. package/dist/utility/serialization/scene/physics.js.map +1 -0
  475. package/dist/utility/serialization/scene/primitive.js +124 -10
  476. package/dist/utility/serialization/scene/primitive.js.map +1 -1
  477. package/dist/utility/serialization/scene/scene.js +1687 -51
  478. package/dist/utility/serialization/scene/scene.js.map +1 -1
  479. package/dist/utility/serialization/scene/sprite.js +11 -4
  480. package/dist/utility/serialization/scene/sprite.js.map +1 -1
  481. package/dist/utility/serialization/scene/terrain.js +163 -33
  482. package/dist/utility/serialization/scene/terrain.js.map +1 -1
  483. package/dist/utility/serialization/scene/text.js +11 -4
  484. package/dist/utility/serialization/scene/text.js.map +1 -1
  485. package/dist/utility/serialization/scene/water.js +2473 -114
  486. package/dist/utility/serialization/scene/water.js.map +1 -1
  487. package/dist/utility/serialization/types.js.map +1 -1
  488. package/dist/utility/shprojector.js +97 -109
  489. package/dist/utility/shprojector.js.map +1 -1
  490. package/dist/utility/textures/foam.js +170 -0
  491. package/dist/utility/textures/foam.js.map +1 -0
  492. package/dist/utility/textures/ggxlut.js +17 -43
  493. package/dist/utility/textures/ggxlut.js.map +1 -1
  494. package/dist/utility/textures/ltcdata.js +4 -4
  495. package/dist/utility/textures/ltcdata.js.map +1 -1
  496. package/dist/utility/textures/ltclut.js +16 -6
  497. package/dist/utility/textures/ltclut.js.map +1 -1
  498. package/dist/utility/unsupported.js +24 -0
  499. package/dist/utility/unsupported.js.map +1 -0
  500. package/dist/values.js +13 -1
  501. package/dist/values.js.map +1 -1
  502. package/package.json +25 -12
  503. package/src/asset/loaders/ktx2/basis/LICENSE +201 -0
  504. package/src/asset/loaders/ktx2/basis/basis_transcoder.js +2 -0
  505. package/src/asset/loaders/ktx2/basis/basis_transcoder.wasm +0 -0
  506. package/src/asset/meshoptimizer/LICENSE +21 -0
@@ -1,20 +1,21 @@
1
- import { applyMixins, DRef, Vector4, Vector3 } from '@zephyr3d/base';
1
+ import { Vector4, applyMixins, DRef, Vector3, Vector2 } from '@zephyr3d/base';
2
2
  import { GraphNode } from './graph_node.js';
3
3
  import { mixinDrawable } from '../render/drawable_mixin.js';
4
4
  import { QUEUE_OPAQUE } from '../values.js';
5
- import '../render/render_queue.js';
6
5
  import { getDevice } from '../app/api.js';
6
+ import '../render/render_queue.js';
7
7
  import { Primitive } from '../render/primitive.js';
8
8
  import '@zephyr3d/device';
9
- import '../material/shader/helper.js';
9
+ import { ShaderHelper } from '../material/shader/helper.js';
10
10
  import '../material/lambert.js';
11
11
  import '../material/blinn.js';
12
12
  import '../material/unlit.js';
13
+ import '../material/meshmaterial.js';
13
14
  import '../material/particle.js';
14
15
  import '../material/subsurfaceprofile.js';
15
- import '../material/skin.js';
16
+ import '../material/sssprofile.js';
17
+ import '../material/sss.js';
16
18
  import '../material/eye.js';
17
- import '../material/meshmaterial.js';
18
19
  import '../material/hair.js';
19
20
  import '../material/hairstrand.js';
20
21
  import '../material/grassmaterial.js';
@@ -31,13 +32,43 @@ import '../render/lightpass.js';
31
32
  import '../render/sky.js';
32
33
  import { Clipmap } from '../render/clipmap.js';
33
34
  import '../render/envlight.js';
35
+ import '../render/lod.js';
34
36
  import '../render/dualdepthpeeling_oit.js';
37
+ import { setWaterSubmergence } from '../render/underwater.js';
35
38
  import '../shaders/atmosphere.js';
36
39
  import { FBMWaveGenerator } from '../render/fbm_wavegenerator.js';
40
+ import { InteractiveWaveGenerator } from '../render/water_interaction.js';
41
+ import { RenderGraphExecutor } from '../render/rendergraph/executor.js';
37
42
  import '../render/rendergraph/scene_render_context.js';
43
+ import '../render/sdf/scene_sdf.js';
44
+ import '../render/sdf/global_sdf.js';
45
+ import { TerrainWaterlineSource, WaterlineField } from '../render/sdf/waterline.js';
46
+ import '../render/gi/voxelize_pass.js';
38
47
  import { WaterMaterial } from '../material/water.js';
39
48
  import { BoundingBox } from '../utility/bounding_volume.js';
40
49
 
50
+ /** Mean earth radius in meters, for the horizon distance. @internal */ const EARTH_RADIUS = 6371000;
51
+ /** Floor on the eye height feeding the horizon distance, in meters. @internal */ const MIN_HORIZON_EYE_HEIGHT = 0.1;
52
+ /**
53
+ * How far past the tessellated surface the skirt's outer edge is placed, as a
54
+ * multiple of the view distance. Its depth is pinned to the far plane.
55
+ * @internal
56
+ */ const SKIRT_DISTANCE_FACTOR = 10;
57
+ /**
58
+ * Region rectangle standing in for "no bounds", matching the WaterMaterial
59
+ * default. @internal
60
+ */ const WHOLE_DOMAIN = new Vector4(-99999, -99999, 99999, 99999);
61
+ /**
62
+ * The shore field's levels around the camera: cells along a side and the
63
+ * finest cell size in metres, each level twice the size of the one before,
64
+ * laid out as the foam field's are.
65
+ */ const SHORE_FIELD_LEVELS = 4;
66
+ const SHORE_FIELD_CELLS = 256;
67
+ const SHORE_FIELD_CELL_SIZE = 0.5;
68
+ /** Shore field levels rebuilt in a frame at most, so the camera crossing every level's cell at once is no spike. */ const SHORE_FIELD_REBUILDS = 2;
69
+ /** No waterline grid: 0 cells. */ const NO_REGIONS = [
70
+ new Vector4(0, 0, 1, 0)
71
+ ];
41
72
  /**
42
73
  * Water scene node
43
74
  * @public
@@ -46,15 +77,38 @@ import { BoundingBox } from '../utility/bounding_volume.js';
46
77
  _clipmap;
47
78
  _renderData;
48
79
  _gridScale;
80
+ _viewDistance;
49
81
  _animationSpeed;
50
82
  _timeStart;
83
+ /** Wave clock, in seconds. Advanced by `animationSpeed` per frame. */ _waveTime;
84
+ /** Elapsed time at the previous update, for the frame delta. */ _lastUpdateTime;
51
85
  _feedbackProgram;
86
+ /** Hash of the wave generator the feedback program was built for. */ _feedbackHash;
52
87
  _feedbackBindGroup;
53
88
  _feedbackPrimitive;
54
89
  _feedbackRenderTarget;
55
90
  _feedbackRenderStates;
91
+ /**
92
+ * Tail of the surface-query queue. The feedback pass reuses one vertex buffer
93
+ * and render target, so requests are serialized in issue order.
94
+ */ _feedbackQueue;
95
+ /**
96
+ * Frame numbers of the stages of the most recent surface-point query, for
97
+ * latency diagnostics: when it was requested, when its render was recorded,
98
+ * and when the readback resolved. All are `device.frameInfo.frameCounter`.
99
+ * @public
100
+ */ lastFeedbackTiming = {
101
+ issued: 0,
102
+ recorded: 0,
103
+ mapped: 0
104
+ };
56
105
  _material;
57
106
  /**
107
+ * Whether each camera was inside this body of water on its previous update;
108
+ * the submerged test has a dead band around the surface, so it needs the
109
+ * crossing direction.
110
+ */ _submerged;
111
+ /**
58
112
  * Creates an instance of Water node
59
113
  * @param scene - Scene object
60
114
  */ constructor(scene){
@@ -65,22 +119,32 @@ import { BoundingBox } from '../utility/bounding_volume.js';
65
119
  this._clipmap = new Clipmap(32, []);
66
120
  this._renderData = null;
67
121
  this._gridScale = 1;
122
+ this._viewDistance = 0;
68
123
  this._animationSpeed = 1;
69
124
  this._timeStart = 0;
125
+ this._waveTime = 0;
126
+ this._lastUpdateTime = 0;
70
127
  this._material = new DRef(new WaterMaterial());
71
128
  this._material.get().region = new Vector4(-1, -1, 1, 1);
72
- this._material.get().TAAStrength = 0.4;
129
+ this._material.get().TAAStrength = 0.2;
73
130
  this.waveGenerator = new FBMWaveGenerator();
74
131
  this._feedbackProgram = new DRef();
132
+ this._feedbackHash = '';
75
133
  this._feedbackBindGroup = new DRef();
76
134
  this._feedbackPrimitive = new DRef();
77
135
  this._feedbackRenderTarget = new DRef();
78
136
  this._feedbackRenderStates = null;
137
+ this._feedbackQueue = Promise.resolve();
138
+ this._submerged = new WeakMap();
79
139
  scene.queuePerCameraUpdateNode(this);
80
140
  }
81
141
  /** Disposes the water node */ onDispose() {
82
142
  super.onDispose();
83
143
  this._clipmap.dispose();
144
+ this._shoreField?.dispose();
145
+ this._shoreSource?.dispose();
146
+ this._shoreField = null;
147
+ this._shoreSource = null;
84
148
  this._renderData = null;
85
149
  this._feedbackBindGroup.dispose();
86
150
  this._feedbackPrimitive.dispose();
@@ -102,8 +166,14 @@ import { BoundingBox } from '../utility/bounding_volume.js';
102
166
  /** Material of the water */ get material() {
103
167
  return this._material.get();
104
168
  }
105
- /** Wave generator object of the water */ get waveGenerator() {
106
- return this.material.waveGenerator;
169
+ /**
170
+ * Wave generator object of the water: the source of the ambient surface.
171
+ *
172
+ * While an {@link interaction} is attached the surface actually drawn is
173
+ * this generator plus the interaction field; this still returns the
174
+ * generator on its own.
175
+ */ get waveGenerator() {
176
+ return this.material.baseWaveGenerator;
107
177
  }
108
178
  set waveGenerator(waveGenerator) {
109
179
  this.material.waveGenerator = waveGenerator;
@@ -111,52 +181,789 @@ import { BoundingBox } from '../utility/bounding_volume.js';
111
181
  this.scene?.queueUpdateNode(this);
112
182
  }
113
183
  }
114
- /** Animation speed of the water */ get animationSpeed() {
184
+ /**
185
+ * Dynamic height field layered over the surface, driven by things touching
186
+ * the water, or null for none. See {@link WaterInteraction}.
187
+ *
188
+ * The field is stepped on this water's own clock, so {@link animationSpeed}
189
+ * scales it too; it is added everywhere the surface is evaluated, so
190
+ * {@link getSurfacePoint} and the caustics see it as well.
191
+ */ get interaction() {
192
+ return this.material.interaction;
193
+ }
194
+ set interaction(val) {
195
+ this.material.interaction = val;
196
+ if (this.material.needUpdate()) {
197
+ this.scene?.queueUpdateNode(this);
198
+ }
199
+ }
200
+ /**
201
+ * Whether the surface reads as an unbounded ocean reaching the horizon,
202
+ * rather than a body of water ending at the node's extent.
203
+ *
204
+ * Off by default. The region test is skipped while this is on.
205
+ *
206
+ * The horizon is reached without touching the camera's far plane: the
207
+ * clipmap's outermost ring is drawn as a skirt whose outer vertices are
208
+ * pushed well past it with their depth pinned to the far value.
209
+ */ get infinite() {
210
+ return this.material.infinite;
211
+ }
212
+ set infinite(val) {
213
+ if (!!val !== this.material.infinite) {
214
+ this.material.infinite = !!val;
215
+ this.invalidateWorldBoundingVolume(false);
216
+ }
217
+ }
218
+ /**
219
+ * How far the surface is built out from the camera while {@link infinite} is
220
+ * on, in meters. 0 derives it from the true horizon distance for the camera's
221
+ * height above the water.
222
+ *
223
+ * Geometry cost grows only logarithmically. Keep it within the sky's
224
+ * {@link SkyRenderer.aerialPerspectiveDistance}, or the far water stops
225
+ * converging towards the sky colour and shows a hard band.
226
+ */ get viewDistance() {
227
+ return this._viewDistance;
228
+ }
229
+ set viewDistance(val) {
230
+ this._viewDistance = Math.max(0, val);
231
+ }
232
+ /**
233
+ * Animation speed of the water. Zero freezes the surface where it is rather
234
+ * than resetting it: the wave phase is accumulated, and this scales the
235
+ * increment.
236
+ */ get animationSpeed() {
115
237
  return this._animationSpeed;
116
238
  }
117
239
  set animationSpeed(val) {
118
240
  this._animationSpeed = val;
119
241
  }
242
+ /**
243
+ * The wave clock the surface is currently at, in seconds.
244
+ *
245
+ * The time the displaced surface was last evaluated with (the value
246
+ * {@link WaterMaterial.update} is handed), not the scene's elapsed time. CPU
247
+ * samplers of the surface should use this clock; it matches `elapsedInSeconds`
248
+ * from {@link SceneNode.update} only while {@link animationSpeed} is 1 and the
249
+ * water has not been paused.
250
+ */ get waveTime() {
251
+ return this._waveTime;
252
+ }
120
253
  /** TAA strength of the water */ get TAAStrength() {
121
254
  return this.material.TAAStrength;
122
255
  }
123
256
  set TAAStrength(val) {
124
257
  this.material.TAAStrength = val;
125
258
  }
259
+ /**
260
+ * Whether this water projects caustics onto the geometry below it.
261
+ *
262
+ * Needs a shadow-casting directional light and a WebGL2/WebGPU device; the
263
+ * caustics pass switches itself off when either is missing, and when the sun
264
+ * drops close to the horizon.
265
+ */ get causticsEnabled() {
266
+ return this.material.causticsEnabled;
267
+ }
268
+ set causticsEnabled(val) {
269
+ this.material.causticsEnabled = val;
270
+ }
271
+ /** Strength of the caustic contrast. 0 leaves the light unmodulated. */ get causticsIntensity() {
272
+ return this.material.causticsIntensity;
273
+ }
274
+ set causticsIntensity(val) {
275
+ this.material.causticsIntensity = val;
276
+ }
277
+ /** Depth in meters below the surface where the caustics are in focus. */ get causticsDepth() {
278
+ return this.material.causticsDepth;
279
+ }
280
+ set causticsDepth(val) {
281
+ this.material.causticsDepth = val;
282
+ }
283
+ /**
284
+ * Whether caustics land on the scene rather than on a plane at
285
+ * {@link causticsDepth}. WebGL2 keeps the plane regardless.
286
+ */ get causticsSceneDepth() {
287
+ return this.material.causticsSceneDepth;
288
+ }
289
+ set causticsSceneDepth(val) {
290
+ this.material.causticsSceneDepth = val;
291
+ }
292
+ /**
293
+ * Furthest distance in meters from the camera the caustic map reaches; the
294
+ * map is fitted to the part of the water within this distance.
295
+ */ get causticsRange() {
296
+ return this.material.causticsRange;
297
+ }
298
+ set causticsRange(val) {
299
+ this.material.causticsRange = val;
300
+ }
301
+ /**
302
+ * Width in meters of the band the caustics fade out over at the edge of the
303
+ * map, or 0 to derive it from {@link causticsRange}.
304
+ */ get causticsFadeDistance() {
305
+ return this.material.causticsFadeDistance;
306
+ }
307
+ set causticsFadeDistance(val) {
308
+ this.material.causticsFadeDistance = val;
309
+ }
310
+ /**
311
+ * How strongly caustic map texels are concentrated near the camera, 0 to
312
+ * spread them evenly. Ignored while the map already fits the water within
313
+ * {@link causticsRange}.
314
+ */ get causticsWarp() {
315
+ return this.material.causticsWarp;
316
+ }
317
+ set causticsWarp(val) {
318
+ this.material.causticsWarp = val;
319
+ }
320
+ /** Water depth in metres below which no caustics show. */ get causticsMinDepth() {
321
+ return this.material.causticsMinDepth;
322
+ }
323
+ set causticsMinDepth(val) {
324
+ this.material.causticsMinDepth = val;
325
+ }
326
+ /**
327
+ * Whether a camera inside this body of water sees the water from the inside:
328
+ * the medium applied to the whole scene, the sky replaced by water, and the
329
+ * surface read from below with its Snell window.
330
+ *
331
+ * Uses the same medium coefficients the surface does ({@link absorption},
332
+ * {@link scattering} and their scales). Atmospheric fog is suppressed while
333
+ * submerged. Only reaches opaque geometry, like the atmospheric fog it
334
+ * replaces.
335
+ */ get underwaterEnabled() {
336
+ return this.material.underwaterEnabled;
337
+ }
338
+ set underwaterEnabled(val) {
339
+ this.material.underwaterEnabled = val;
340
+ }
341
+ /**
342
+ * Scale on the downwelling sky light that fills the water column, 1 for the
343
+ * value the sky implies. This is what the water fades to in the distance, so
344
+ * it also sets how bright the underwater haze reads.
345
+ */ get underwaterAmbientIntensity() {
346
+ return this.material.underwaterAmbientIntensity;
347
+ }
348
+ set underwaterAmbientIntensity(val) {
349
+ this.material.underwaterAmbientIntensity = val;
350
+ }
351
+ /**
352
+ * Whether shafts of sunlight are marched through the water column.
353
+ *
354
+ * Reads the caustic map as the surface's transmittance. Needs
355
+ * {@link causticsEnabled}; switches itself off wherever the map is
356
+ * unavailable.
357
+ */ get underwaterGodRays() {
358
+ return this.material.underwaterGodRays;
359
+ }
360
+ set underwaterGodRays(val) {
361
+ this.material.underwaterGodRays = val;
362
+ }
363
+ /** Strength of the light shafts, 1 for the value the medium implies. */ get underwaterGodRayIntensity() {
364
+ return this.material.underwaterGodRayIntensity;
365
+ }
366
+ set underwaterGodRayIntensity(val) {
367
+ this.material.underwaterGodRayIntensity = val;
368
+ }
369
+ /**
370
+ * Samples taken along each view ray for the light shafts. Raise it when the
371
+ * shafts read as grain rather than as beams.
372
+ */ get underwaterGodRaySteps() {
373
+ return this.material.underwaterGodRaySteps;
374
+ }
375
+ set underwaterGodRaySteps(val) {
376
+ this.material.underwaterGodRaySteps = val;
377
+ }
378
+ /**
379
+ * Whether geometry standing in the water breaks the light shafts. Off by
380
+ * default.
381
+ *
382
+ * Without it a shaft runs straight through a piling or a hull. Costs a shadow
383
+ * map lookup per march step, roughly doubling the cost of the shafts.
384
+ */ get underwaterGodRayShadow() {
385
+ return this.material.underwaterGodRayShadow;
386
+ }
387
+ set underwaterGodRayShadow(val) {
388
+ this.material.underwaterGodRayShadow = val;
389
+ }
390
+ /**
391
+ * Half-width in meters of the dead band around the surface the submerged test
392
+ * uses, so a camera sitting at water level does not flip state every frame.
393
+ * The test is against the rest plane, not the displaced surface.
394
+ */ get underwaterHysteresis() {
395
+ return this.material.underwaterHysteresis;
396
+ }
397
+ set underwaterHysteresis(val) {
398
+ this.material.underwaterHysteresis = val;
399
+ }
400
+ /**
401
+ * Strength of the glow of a backlit wave crest, 0 to disable. Authored, not
402
+ * scaled by the light's intensity; {@link subsurfaceTint} supplies the hue.
403
+ */ get subsurfaceIntensity() {
404
+ return this.material.subsurfaceIntensity;
405
+ }
406
+ set subsurfaceIntensity(val) {
407
+ this.material.subsurfaceIntensity = val;
408
+ }
409
+ /**
410
+ * Height above the still-water level, in meters, over which the lit wall of
411
+ * a crest thins by a factor of `e`. Lower for a calm sea, higher so only the
412
+ * tallest crests glow.
413
+ */ get subsurfaceCrestHeight() {
414
+ return this.material.subsurfaceCrestHeight;
415
+ }
416
+ set subsurfaceCrestHeight(val) {
417
+ this.material.subsurfaceCrestHeight = val;
418
+ }
419
+ /**
420
+ * Colour of the crest glow, before the crest gate tints it with the medium's
421
+ * extinction.
422
+ */ get subsurfaceTint() {
423
+ return this.material.subsurfaceTint;
424
+ }
425
+ set subsurfaceTint(val) {
426
+ this.material.subsurfaceTint = val;
427
+ }
428
+ /**
429
+ * Strength of the sunlight scattered out of the water column towards the eye,
430
+ * 1 for the value the medium coefficients imply. Lets shadows darken the
431
+ * water body and a low sun tint it; 0 leaves it lit by the environment alone.
432
+ */ get sunScatteringIntensity() {
433
+ return this.material.sunScatteringIntensity;
434
+ }
435
+ set sunScatteringIntensity(val) {
436
+ this.material.sunScatteringIntensity = val;
437
+ }
438
+ /**
439
+ * Crest to trough height in metres of the waves rolling in to the shore,
440
+ * breaking and running up the beach, 0 for none. They come square on to the
441
+ * coast whatever its shape, and boats ride them. Needs the scene signed
442
+ * distance field (WebGPU, scene SDF enabled).
443
+ */ get shoreWaveHeight() {
444
+ return this.material.shoreWaveHeight;
445
+ }
446
+ set shoreWaveHeight(val) {
447
+ this.material.shoreWaveHeight = val;
448
+ }
449
+ /** Thickness in metres over which a thin sheet of water fades in from the ground under it; 0 disables it. */ get edgeFadeWidth() {
450
+ return this.material.edgeFadeWidth;
451
+ }
452
+ set edgeFadeWidth(val) {
453
+ this.material.edgeFadeWidth = val;
454
+ }
455
+ /** Distance in metres between shore wave crests at the outer edge of their zone. */ get shoreWaveLength() {
456
+ return this.material.shoreWaveLength;
457
+ }
458
+ set shoreWaveLength(val) {
459
+ this.material.shoreWaveLength = val;
460
+ }
461
+ /** Seconds between successive shore waves. */ get shoreWavePeriod() {
462
+ return this.material.shoreWavePeriod;
463
+ }
464
+ set shoreWavePeriod(val) {
465
+ this.material.shoreWavePeriod = val;
466
+ }
467
+ /** How far out from the waterline the shore waves rise, in metres. */ get shoreWaveZone() {
468
+ return this.material.shoreWaveZone;
469
+ }
470
+ set shoreWaveZone(val) {
471
+ this.material.shoreWaveZone = val;
472
+ }
473
+ /** How sharp the shore wave crests become at the shore, 0 for a sine and towards 1 for a cusp. */ get shoreWaveSharpness() {
474
+ return this.material.shoreWaveSharpness;
475
+ }
476
+ set shoreWaveSharpness(val) {
477
+ this.material.shoreWaveSharpness = val;
478
+ }
479
+ /** How far the shore wave crests lean towards the shore there, 0 for upright, 1 for a steep front face. */ get shoreWaveLean() {
480
+ return this.material.shoreWaveLean;
481
+ }
482
+ set shoreWaveLean(val) {
483
+ this.material.shoreWaveLean = val;
484
+ }
485
+ /** How irregular the shore waves are, 0..1: 0 for parallel crests a fixed wavelength apart, higher for each wave with a period and a bend of its own. */ get shoreWaveIrregularity() {
486
+ return this.material.shoreWaveIrregularity;
487
+ }
488
+ set shoreWaveIrregularity(val) {
489
+ this.material.shoreWaveIrregularity = val;
490
+ }
491
+ /** How fast the foam fades, per second (Crest's Foam Fade Rate): lower leaves longer trails behind breaking waves. */ get foamFadeRate() {
492
+ return this.material.foamFadeRate;
493
+ }
494
+ set foamFadeRate(val) {
495
+ this.material.foamFadeRate = val;
496
+ }
497
+ /** How much foam the open water's breaking crests leave behind to drift and fade; 0 keeps foam only while a crest breaks. */ get foamTrailStrength() {
498
+ return this.material.foamTrailStrength;
499
+ }
500
+ set foamTrailStrength(val) {
501
+ this.material.foamTrailStrength = val;
502
+ }
503
+ /** Share of the wind speed the foam drifts at, about 0.03 in nature. */ get foamDrift() {
504
+ return this.material.foamDrift;
505
+ }
506
+ set foamDrift(val) {
507
+ this.material.foamDrift = val;
508
+ }
509
+ /** Side in metres of the finest square of water around the view whose foam lingers, in the most detail; coarser squares reach eight times as far, beyond which foam shows only while crests break. */ get foamFieldSize() {
510
+ return this.material.foamFieldSize;
511
+ }
512
+ set foamFieldSize(val) {
513
+ this.material.foamFieldSize = val;
514
+ }
515
+ /** How much foam the breaking shore waves make (Crest's Wave Foam Strength). */ get shoreWaveFoamStrength() {
516
+ return this.material.shoreWaveFoamStrength;
517
+ }
518
+ set shoreWaveFoamStrength(val) {
519
+ this.material.shoreWaveFoamStrength = val;
520
+ }
521
+ /** How much of the shore waves makes foam, 0..1, higher for more (Crest's Wave Foam Coverage). */ get shoreWaveFoamCoverage() {
522
+ return this.material.shoreWaveFoamCoverage;
523
+ }
524
+ set shoreWaveFoamCoverage(val) {
525
+ this.material.shoreWaveFoamCoverage = val;
526
+ }
527
+ /** Water shallower than this many metres makes foam (Crest's Shoreline Foam Max Depth). */ get shoreWaveShallowFoamDepth() {
528
+ return this.material.shoreWaveShallowFoamDepth;
529
+ }
530
+ set shoreWaveShallowFoamDepth(val) {
531
+ this.material.shoreWaveShallowFoamDepth = val;
532
+ }
533
+ /** How much foam shallow water makes (Crest's Shoreline Foam Strength). */ get shoreWaveShallowFoamStrength() {
534
+ return this.material.shoreWaveShallowFoamStrength;
535
+ }
536
+ set shoreWaveShallowFoamStrength(val) {
537
+ this.material.shoreWaveShallowFoamStrength = val;
538
+ }
539
+ /** How opaque the foam is that the water makes against walls, rocks and piers standing in it, 0..1; it always rings them whole. */ get wallFoamStrength() {
540
+ return this.material.wallFoamStrength;
541
+ }
542
+ set wallFoamStrength(val) {
543
+ this.material.wallFoamStrength = val;
544
+ }
545
+ /** Speed in metres per second the water has to hit a wall at before it makes any foam there. */ get wallFoamMinSpeed() {
546
+ return this.material.wallFoamMinSpeed;
547
+ }
548
+ set wallFoamMinSpeed(val) {
549
+ this.material.wallFoamMinSpeed = val;
550
+ }
551
+ /** Speed in metres per second at which water hitting a wall makes the most foam. */ get wallFoamMaxSpeed() {
552
+ return this.material.wallFoamMaxSpeed;
553
+ }
554
+ set wallFoamMaxSpeed(val) {
555
+ this.material.wallFoamMaxSpeed = val;
556
+ }
557
+ /** Speed in metres per second the foam drifts out from walls, rocks and piers at: higher for a wider ring of foam around them. */ get wallFoamDrift() {
558
+ return this.material.wallFoamDrift;
559
+ }
560
+ set wallFoamDrift(val) {
561
+ this.material.wallFoamDrift = val;
562
+ }
563
+ /** Seconds the foam each breaking wave brings in lasts, behind the crest and up the beach. */ get surfFoamDuration() {
564
+ return this.material.surfFoamDuration;
565
+ }
566
+ set surfFoamDuration(val) {
567
+ this.material.surfFoamDuration = val;
568
+ }
569
+ /** Water depth in metres at and below which foam stops drifting and stays where it is. */ get foamSettleDepth() {
570
+ return this.material.foamSettleDepth;
571
+ }
572
+ set foamSettleDepth(val) {
573
+ this.material.foamSettleDepth = val;
574
+ }
575
+ /** Water depth in metres at and beyond which foam drifts freely. */ get foamDriftDepth() {
576
+ return this.material.foamDriftDepth;
577
+ }
578
+ set foamDriftDepth(val) {
579
+ this.material.foamDriftDepth = val;
580
+ }
581
+ /** How fast foam settled in shallow water and on the sand fades, as a share of the foam's fade. */ get settledFoamFade() {
582
+ return this.material.settledFoamFade;
583
+ }
584
+ set settledFoamFade(val) {
585
+ this.material.settledFoamFade = val;
586
+ }
587
+ /**
588
+ * How the refracted view sample is located. Defaults to `march`.
589
+ *
590
+ * `offset` skips the depth-buffer search and displaces the screen UV by the
591
+ * wave normal instead: cheaper, but submerged silhouettes smear. For hardware
592
+ * that cannot afford the search.
593
+ */ get refractionMode() {
594
+ return this.material.refractionMode;
595
+ }
596
+ set refractionMode(val) {
597
+ this.material.refractionMode = val;
598
+ }
599
+ /**
600
+ * Which intermediate shading term to display instead of the final colour.
601
+ * A debugging aid; leave it at `none` in production. See
602
+ * {@link WaterDebugOutput} for the available views.
603
+ */ get debugOutput() {
604
+ return this.material.debugOutput;
605
+ }
606
+ set debugOutput(val) {
607
+ this.material.debugOutput = val;
608
+ }
609
+ /**
610
+ * Depth in meters the cheap refraction mode assumes the water is, used only
611
+ * when {@link refractionMode} is `offset`.
612
+ *
613
+ * Sets how strong the cheap distortion looks. A constant, because an offset
614
+ * scaled by the real depth paints a second copy of anything breaking the surface.
615
+ */ get cheapRefractionDepth() {
616
+ return this.material.cheapRefractionDepth;
617
+ }
618
+ set cheapRefractionDepth(val) {
619
+ this.material.cheapRefractionDepth = val;
620
+ }
621
+ /**
622
+ * Scale on how much the medium blurs what is seen through it, 1 for the width
623
+ * the scattering coefficient and the path length imply. 0 keeps the background
624
+ * sharp at any depth.
625
+ */ get refractionBlur() {
626
+ return this.material.refractionBlur;
627
+ }
628
+ set refractionBlur(val) {
629
+ this.material.refractionBlur = val;
630
+ }
631
+ /** Absorption coefficient sigma_a in 1/m, per RGB channel. */ get absorption() {
632
+ return this.material.absorption;
633
+ }
634
+ set absorption(val) {
635
+ this.material.absorption = val;
636
+ }
637
+ /** Scale for the absorption coefficient. */ get absorptionScale() {
638
+ return this.material.absorptionScale;
639
+ }
640
+ set absorptionScale(val) {
641
+ this.material.absorptionScale = val;
642
+ }
643
+ /** Scattering coefficient sigma_s in 1/m, per RGB channel. */ get scattering() {
644
+ return this.material.scattering;
645
+ }
646
+ set scattering(val) {
647
+ this.material.scattering = val;
648
+ }
649
+ /** Scale for the scattering coefficient. */ get scatteringScale() {
650
+ return this.material.scatteringScale;
651
+ }
652
+ set scatteringScale(val) {
653
+ this.material.scatteringScale = val;
654
+ }
655
+ /**
656
+ * Mean cosine of a single scattering event in the water, in `[0, 0.95]`.
657
+ *
658
+ * 0 scatters equally in all directions; higher brightens the water when
659
+ * looking towards the sun through it.
660
+ */ get scatterAnisotropy() {
661
+ return this.material.scatterAnisotropy;
662
+ }
663
+ set scatterAnisotropy(val) {
664
+ this.material.scatterAnisotropy = val;
665
+ }
666
+ /**
667
+ * Artistic scale on the refracted view offset. 1 is physical, 0 disables it.
668
+ *
669
+ * The offset comes from refracting the view ray by Snell's law and projecting
670
+ * its hit point back to the screen; this only dials it for a stylised look.
671
+ * Values above 1 exaggerate, and the medium tint may stop matching what is
672
+ * visible through the water.
673
+ */ get refractionScale() {
674
+ return this.material.refractionScale;
675
+ }
676
+ set refractionScale(val) {
677
+ this.material.refractionScale = val;
678
+ }
679
+ /**
680
+ * Scale on the Fresnel reflectance, 1 for the physical value.
681
+ *
682
+ * Below 1 the surface reflects less and shows more of what is beneath it. The
683
+ * F0 floor is scaled with it, so 0 gives no specular response at all.
684
+ */ get reflectionStrength() {
685
+ return this.material.reflectionStrength;
686
+ }
687
+ set reflectionStrength(val) {
688
+ this.material.reflectionStrength = val;
689
+ }
690
+ /**
691
+ * How much of a folded texel reads as foam.
692
+ *
693
+ * Scales the wave generator's fold measure into a coverage fraction. 0
694
+ * disables foam.
695
+ */ get foamAmount() {
696
+ return this.material.foamAmount;
697
+ }
698
+ set foamAmount(val) {
699
+ this.material.foamAmount = val;
700
+ }
701
+ /**
702
+ * Falloff applied to foam coverage before it is scaled.
703
+ *
704
+ * Above 1 this pushes light folding towards no foam, so only broken crests
705
+ * show any.
706
+ */ get foamFalloff() {
707
+ return this.material.foamFalloff;
708
+ }
709
+ set foamFalloff(val) {
710
+ this.material.foamFalloff = val;
711
+ }
712
+ /** Diffuse albedo of the foam. */ get foamColor() {
713
+ return this.material.foamColor;
714
+ }
715
+ set foamColor(val) {
716
+ this.material.foamColor = val;
717
+ }
126
718
  /** {@inheritDoc SceneNode.update} */ update(frameId, elapsedInSeconds) {
127
719
  if (this.material.needUpdate()) {
128
720
  this.scene?.queueUpdateNode(this);
721
+ if (this.hidden) {
722
+ // Nothing shows the waves, the interaction or the foam: leave them be, and
723
+ // pick the phase up from here once the water shows again
724
+ this._lastUpdateTime = elapsedInSeconds;
725
+ return;
726
+ }
129
727
  if (this._timeStart === 0) {
130
728
  this._timeStart = elapsedInSeconds;
131
729
  }
132
- this.material.update(frameId, (elapsedInSeconds - this._timeStart) * this._animationSpeed);
730
+ // Accumulate the scaled delta so a speed of zero freezes the phase in place.
731
+ this._waveTime += Math.max(0, elapsedInSeconds - Math.max(this._timeStart, this._lastUpdateTime)) * this._animationSpeed;
732
+ this._lastUpdateTime = elapsedInSeconds;
733
+ const interaction = this.material.interaction;
734
+ if (interaction) {
735
+ interaction.resolveNodes(this.scene);
736
+ interaction.setWaterLevel(this.worldMatrix.m13);
737
+ }
738
+ this.material.update(frameId, this._waveTime);
133
739
  this.invalidateWorldBoundingVolume(false);
134
740
  }
135
741
  }
136
742
  /** {@inheritDoc SceneNode.updatePerCamera} */ updatePerCamera(camera, _elapsedInSeconds, _deltaInSeconds) {
137
743
  const mat = this._material.get();
744
+ if (mat && this.hidden) {
745
+ // Not drawn: no surface to gather, no shore or foam field to step
746
+ this.scene?.queuePerCameraUpdateNode(this);
747
+ return;
748
+ }
138
749
  if (mat) {
139
750
  const that = this;
751
+ const infinite = mat.infinite;
752
+ let viewDistance = 0;
753
+ if (infinite) {
754
+ viewDistance = this._viewDistance > 0 ? this._viewDistance : this.horizonDistance(camera);
755
+ // The skirt's outer edge sits beyond the tessellated surface to hide its last ring.
756
+ mat.setSkirtDistance(viewDistance * SKIRT_DISTANCE_FACTOR);
757
+ }
140
758
  this._renderData = this._clipmap.gather({
141
759
  camera,
142
- minMaxWorldPos: mat.region,
760
+ // An unbounded surface is not clipped to the node's region.
761
+ minMaxWorldPos: infinite ? WHOLE_DOMAIN : mat.region,
143
762
  gridScale: Math.max(0.01, this._gridScale),
144
763
  userData: this,
145
764
  frustumCulling: true,
765
+ viewDistance: infinite ? viewDistance : undefined,
766
+ skirt: infinite,
146
767
  calcAABB (userData, minX, maxX, minZ, maxZ, outAABB) {
147
768
  const p = that.worldMatrix.transformPointAffine(Vector3.zero());
148
- if (that.waveGenerator) {
149
- that.waveGenerator.calcClipmapTileAABB(minX, maxX, minZ, maxZ, p.y, outAABB);
769
+ if (mat.waveGenerator) {
770
+ mat.waveGenerator.calcClipmapTileAABB(minX, maxX, minZ, maxZ, p.y, outAABB);
150
771
  } else {
151
772
  outAABB.minPoint.setXYZ(minX, p.y, minZ);
152
773
  outAABB.maxPoint.setXYZ(maxX, p.y + 1, maxZ);
153
774
  }
154
775
  }
155
776
  });
777
+ this.updateSubmergence(camera, mat);
778
+ const interaction = mat.interaction;
779
+ const wantsDepth = !!interaction && interaction.shallowDepth > 0;
780
+ if ((mat.shoreWaveHeight > 0 || wantsDepth) && this.scene?.sdf.enabled) {
781
+ // The interaction field and the shore waves read the waterline at the rest level
782
+ this.scene.sdf.global?.waterline.request(this.worldMatrix.m13);
783
+ }
784
+ if (mat.shoreWaveHeight > 0) {
785
+ // The waves and the beach's foam follow the terrain alone, in levels
786
+ // out to the foam field's reach; the foam against walls, rocks and
787
+ // piers the scene field's single level. Without a terrain the scene
788
+ // field shapes the waves too.
789
+ const terrainField = this.updateShoreField(camera);
790
+ const waterline = this.scene?.sdf.enabled ? this.scene.sdf.global?.waterline : null;
791
+ const wall = waterline?.ready ? waterline : null;
792
+ const shore = terrainField?.ready ? terrainField : terrainField ? null : wall;
793
+ mat.setShoreWaveFrame(shore?.smoothTexture ?? null, shore?.regions ?? NO_REGIONS, wall?.smoothTexture ?? null, wall?.region ?? NO_REGIONS[0], this.worldMatrix.m13);
794
+ }
795
+ if (interaction) {
796
+ interaction.setBathymetry(wantsDepth ? this.findBathymetry() : null);
797
+ }
798
+ if (interaction && interaction.followMode === 'camera') {
799
+ const focus = this.viewFocus(camera, interaction.windowSize * 0.5);
800
+ interaction.setFocus(focus.x, focus.y);
801
+ }
802
+ // After the shore waves' frame, which the foam field's sources read
803
+ const foamFocus = this.viewFocus(camera, mat.foamFieldSize * 0.5);
804
+ RenderGraphExecutor.profileScope('WaterFoamField', ()=>mat.updateFoamField(foamFocus.x, foamFocus.y));
156
805
  this.scene?.queuePerCameraUpdateNode(this);
157
806
  }
158
807
  }
159
808
  /**
809
+ * Where the interaction field reads the depth of the water: the scene
810
+ * field's waterline grid when there is one, else the first terrain's height
811
+ * map, else nothing.
812
+ * @internal
813
+ */ findBathymetry() {
814
+ const level = this.worldMatrix.m13;
815
+ const waterline = this.scene?.sdf.enabled ? this.scene.sdf.global?.waterline : null;
816
+ if (waterline?.ready && waterline.texture) {
817
+ const r = waterline.region;
818
+ return {
819
+ kind: 'field',
820
+ texture: waterline.texture,
821
+ region: [
822
+ r.x,
823
+ r.y,
824
+ r.z * r.w,
825
+ r.z * r.w
826
+ ],
827
+ level
828
+ };
829
+ }
830
+ let terrain = this.findTerrain();
831
+ const heightMap = terrain?.heightMap;
832
+ if (!terrain || !heightMap) {
833
+ return null;
834
+ }
835
+ const region = terrain.worldRegion;
836
+ return {
837
+ kind: 'height',
838
+ texture: heightMap,
839
+ region: [
840
+ region.x,
841
+ region.y,
842
+ region.z - region.x,
843
+ region.w - region.y
844
+ ],
845
+ level,
846
+ heightScale: Math.abs(terrain.scale.y),
847
+ heightOffset: terrain.worldMatrix.m13
848
+ };
849
+ }
850
+ /** The first terrain in the scene, cached while it stays in it. @internal */ findTerrain() {
851
+ let terrain = this._bathyTerrain?.attached && this._bathyTerrain.scene === this.scene ? this._bathyTerrain : null;
852
+ if (!terrain && this.scene) {
853
+ this.scene.rootNode.iterate((node)=>{
854
+ if (node.isClipmapTerrain()) {
855
+ terrain = node;
856
+ return true;
857
+ }
858
+ return false;
859
+ });
860
+ this._bathyTerrain = terrain;
861
+ }
862
+ return terrain;
863
+ }
864
+ /**
865
+ * Rebuilds the shore field, the waterline of the terrain alone in levels
866
+ * around the camera, which the shore waves read, and returns it. Null
867
+ * without a terrain or off WebGPU.
868
+ * @internal
869
+ */ updateShoreField(camera) {
870
+ const terrain = this.findTerrain();
871
+ const device = getDevice();
872
+ if (!terrain?.heightMap || device.type !== 'webgpu') {
873
+ return null;
874
+ }
875
+ if (!this._shoreField) {
876
+ this._shoreSource = new TerrainWaterlineSource();
877
+ this._shoreField = new WaterlineField(this._shoreSource, {
878
+ levels: SHORE_FIELD_LEVELS,
879
+ resolution: SHORE_FIELD_CELLS,
880
+ cellSize: SHORE_FIELD_CELL_SIZE,
881
+ maxRebuilds: SHORE_FIELD_REBUILDS
882
+ });
883
+ }
884
+ this._shoreSource.setTerrain(terrain);
885
+ this._shoreField.request(this.worldMatrix.m13);
886
+ device.pushDeviceStates();
887
+ try {
888
+ RenderGraphExecutor.profileScope('WaterShoreField', ()=>this._shoreField.update(camera.getWorldPosition()));
889
+ } finally{
890
+ device.popDeviceStates();
891
+ }
892
+ return this._shoreField;
893
+ }
894
+ /** @internal */ _shoreSource = null;
895
+ /** @internal */ _shoreField = null;
896
+ /** @internal */ _bathyTerrain = null;
897
+ /**
898
+ * Decide whether `camera` is inside this body of water and publish the result.
899
+ *
900
+ * Run from `updatePerCamera`, which runs every frame regardless of culling, so
901
+ * the state survives the surface leaving the frustum.
902
+ *
903
+ * The test is against the rest plane rather than the displaced surface, which
904
+ * would need a GPU readback. A dead band of
905
+ * {@link WaterMaterial.underwaterHysteresis} around the plane keeps an eye at
906
+ * water level from flipping state every frame.
907
+ * @internal
908
+ */ updateSubmergence(camera, mat) {
909
+ const surfaceY = this.worldMatrix.transformPointAffine(Vector3.zero()).y;
910
+ let submerged = false;
911
+ if (!this.hidden && this.attached) {
912
+ const camPos = camera.getWorldPosition();
913
+ const region = mat.region;
914
+ // A bounded surface is only entered from within its own footprint.
915
+ const inside = mat.infinite || camPos.x >= region.x && camPos.x <= region.z && camPos.z >= region.y && camPos.z <= region.w;
916
+ if (inside) {
917
+ const band = mat.underwaterHysteresis;
918
+ submerged = this._submerged.get(camera) ? camPos.y < surfaceY + band : camPos.y < surfaceY - band;
919
+ }
920
+ }
921
+ this._submerged.set(camera, submerged);
922
+ setWaterSubmergence(camera, this, submerged, surfaceY);
923
+ }
924
+ /**
925
+ * The water the camera is looking at, on which a window that follows the
926
+ * view (the interaction field, the foam field) is centred.
927
+ *
928
+ * Where the view direction meets the rest plane, no further ahead than
929
+ * `reach` (half a window), so the window covers the visible water rather
930
+ * than putting it in the sponge band at its edge.
931
+ * @internal
932
+ */ viewFocus(camera, reach) {
933
+ const camPos = camera.getWorldPosition();
934
+ const m = camera.worldMatrix;
935
+ // The camera looks down its local -Z.
936
+ const fx = -m.m02;
937
+ const fy = -m.m12;
938
+ const fz = -m.m22;
939
+ const horizontal = Math.hypot(fx, fz);
940
+ let ahead = reach;
941
+ if (fy < -1e-4) {
942
+ const t = (this.worldMatrix.m13 - camPos.y) / fy;
943
+ if (t > 0) {
944
+ ahead = Math.min(reach, t * horizontal);
945
+ }
946
+ }
947
+ if (horizontal < 1e-6) {
948
+ return new Vector2(camPos.x, camPos.z);
949
+ }
950
+ return new Vector2(camPos.x + fx / horizontal * ahead, camPos.z + fz / horizontal * ahead);
951
+ }
952
+ /**
953
+ * Distance to the true horizon for the camera's height above this water, in
954
+ * meters.
955
+ *
956
+ * `sqrt(2 * R * h)` for earth's radius: about 5km from an eye 2m up, 25km
957
+ * from 50m.
958
+ * @internal
959
+ */ horizonDistance(camera) {
960
+ const camPos = camera.getWorldPosition();
961
+ const waterLevel = this.worldMatrix.transformPointAffine(Vector3.zero()).y;
962
+ // Floored for cameras at or below the surface.
963
+ const height = Math.max(camPos.y - waterLevel, MIN_HORIZON_EYE_HEIGHT);
964
+ return Math.sqrt(2 * EARTH_RADIUS * height);
965
+ }
966
+ /**
160
967
  * {@inheritDoc Drawable.getPickTarget }
161
968
  */ getPickTarget() {
162
969
  return this._pickTarget;
@@ -220,6 +1027,11 @@ import { BoundingBox } from '../utility/bounding_volume.js';
220
1027
  const p = this.worldMatrix.transformPointAffine(Vector3.zero());
221
1028
  const mat = this._material?.get();
222
1029
  if (mat) {
1030
+ // Null keeps an unbounded surface in the queue (ClipState.CLIPPED) and
1031
+ // leaves culling to the clipmap's per-tile test.
1032
+ if (mat.infinite) {
1033
+ return null;
1034
+ }
223
1035
  const boundingBox = new BoundingBox();
224
1036
  if (mat.waveGenerator) {
225
1037
  mat.waveGenerator.calcClipmapTileAABB(mat.region.x, mat.region.z, mat.region.y, mat.region.w, p.y, boundingBox);
@@ -267,7 +1079,7 @@ import { BoundingBox } from '../utility/bounding_volume.js';
267
1079
  const mat = this._material?.get();
268
1080
  if (mat) {
269
1081
  this.bind(ctx, renderQueue);
270
- mat.setClipmapGridInfo(this._gridScale, this.worldMatrix.m03, this.worldMatrix.m23);
1082
+ mat.setClipmapGridInfo(this._gridScale, this.worldMatrix.m03, this.worldMatrix.m23, this._clipmap.tileResolution);
271
1083
  mat.apply(ctx);
272
1084
  for (const info of this._renderData){
273
1085
  mat.draw(info.primitive, ctx, info.numInstances);
@@ -275,99 +1087,185 @@ import { BoundingBox } from '../utility/bounding_volume.js';
275
1087
  }
276
1088
  }
277
1089
  /**
278
- * Retreive the disturbed world position and normal at water surface
279
- */ async getSurfacePoint(points, outPos, outNorm) {
1090
+ * Retrieve the disturbed world position and normal at water surface.
1091
+ * Set includeDisturbers to false for buoyancy: retain ambient waves and
1092
+ * external impulses, but exclude waves generated by moving disturbers.
1093
+ */ async getSurfacePoint(points, outPos, outNorm, includeDisturbers = true) {
1094
+ if (!outPos && !outNorm) {
1095
+ return;
1096
+ }
1097
+ const hits = await this._evaluateSurfacePoints(points, !!outNorm, includeDisturbers);
1098
+ if (!hits) {
1099
+ return;
1100
+ }
1101
+ for(let i = 0; i < points.length; i++){
1102
+ outPos && outPos[i].setXYZ(hits[i].x, hits[i].y, hits[i].z);
1103
+ outNorm && outNorm[i].setXYZ(hits[i].nx, hits[i].ny, hits[i].nz);
1104
+ }
1105
+ }
1106
+ /**
1107
+ * Evaluate the disturbed surface over a set of world XZ positions.
1108
+ *
1109
+ * Evaluated by the same wave generator the material draws with, so it is
1110
+ * exact by construction. Costs one point-list draw plus one readback per call.
1111
+ * Requests are serialized because the vertex buffer and render target are
1112
+ * shared.
1113
+ *
1114
+ * @param points - World XZ positions to evaluate. Y is ignored.
1115
+ * @param withNormal - Whether to also read the surface normal back, which
1116
+ * costs a second readback of the same size.
1117
+ * @returns One hit per input point, or null if `points` was empty.
1118
+ * @internal
1119
+ */ async _evaluateSurfacePoints(points, withNormal, includeDisturbers = true) {
280
1120
  const device = getDevice();
281
1121
  if (!points || points.length === 0) {
282
- return;
1122
+ return null;
283
1123
  }
284
1124
  points = points.map((v)=>v.clone());
285
- await device.runNextFrameAsync(async ()=>{
286
- if (!this._feedbackProgram.get()) {
287
- this._feedbackProgram.set(this._createFeedbackProgram(device));
288
- this._feedbackBindGroup.set(device.createBindGroup(this._feedbackProgram.get().bindGroupLayouts[0]));
289
- }
290
- if (!this._feedbackPrimitive.get()) {
291
- this._feedbackPrimitive.set(new Primitive());
292
- this._feedbackPrimitive.get().primitiveType = 'point-list';
293
- }
294
- if (!this._feedbackRenderStates) {
295
- this._feedbackRenderStates = device.createRenderStateSet();
296
- this._feedbackRenderStates.useDepthState().enableTest(false).enableWrite(false);
297
- this._feedbackRenderStates.useRasterizerState().setCullMode('none');
298
- }
299
- const primitive = this._feedbackPrimitive.get();
300
- const vertices = new Float32Array(points.length * 4);
301
- for(let i = 0; i < points.length; i++){
302
- vertices[i * 4 + 0] = points[i].x;
303
- vertices[i * 4 + 1] = points[i].y;
304
- vertices[i * 4 + 2] = points[i].z;
305
- vertices[i * 4 + 3] = i;
306
- }
307
- let vb = primitive.getVertexBuffer('position');
308
- if (!vb || vb.byteLength !== vertices.byteLength) {
309
- vb = device.createVertexBuffer('position_f32x4', vertices, {
310
- dynamic: true
311
- });
312
- primitive.setVertexBuffer(vb);
313
- } else {
314
- vb.bufferSubData(0, vertices);
315
- }
316
- const fb = this._feedbackRenderTarget.get();
317
- if (!fb || fb.getColorAttachment(0).width < points.length) {
318
- const rt0 = device.createTexture2D('rgba32f', points.length, 1, {
319
- mipmapping: false
320
- });
321
- const rt1 = device.createTexture2D('rgba32f', points.length, 1, {
322
- mipmapping: false
323
- });
324
- if (fb) {
325
- fb.getColorAttachment(0).dispose();
326
- fb.getColorAttachment(1).dispose();
327
- this._feedbackRenderTarget.dispose();
1125
+ const timing = {
1126
+ issued: device.frameInfo.frameCounter,
1127
+ recorded: 0,
1128
+ mapped: 0
1129
+ };
1130
+ // Take and replace the queue's tail in one synchronous step before awaiting,
1131
+ // so two callers arriving together cannot both pass the same settled tail.
1132
+ const previous = this._feedbackQueue;
1133
+ let release = ()=>{};
1134
+ this._feedbackQueue = new Promise((resolve)=>release = resolve);
1135
+ await previous;
1136
+ const hits = points.map(()=>({
1137
+ x: 0,
1138
+ y: 0,
1139
+ z: 0,
1140
+ nx: 0,
1141
+ ny: 1,
1142
+ nz: 0
1143
+ }));
1144
+ try {
1145
+ await device.runNextFrameAsync(async ()=>{
1146
+ timing.recorded = device.frameInfo.frameCounter;
1147
+ const renderedGenerator = this.material.waveGenerator;
1148
+ const waveGenerator = !includeDisturbers && renderedGenerator instanceof InteractiveWaveGenerator ? new InteractiveWaveGenerator(renderedGenerator.base, renderedGenerator.interaction, false) : renderedGenerator;
1149
+ const feedbackHash = waveGenerator?.getHash() ?? '';
1150
+ if (this._feedbackProgram.get() && this._feedbackHash !== feedbackHash) {
1151
+ // The wave generator changed; rebuild the program for it.
1152
+ this._feedbackBindGroup.dispose();
1153
+ this._feedbackProgram.dispose();
328
1154
  }
329
- this._feedbackRenderTarget.set(device.createFrameBuffer([
330
- rt0,
331
- rt1
332
- ], null));
333
- }
334
- primitive.indexCount = points.length;
335
- const bindGroup = this._feedbackBindGroup.get();
336
- bindGroup.setValue('textureWidth', this._feedbackRenderTarget.get().getWidth());
337
- this.waveGenerator.applyWaterBindGroup(bindGroup);
338
- device.pushDeviceStates();
339
- device.setProgram(this._feedbackProgram.get());
340
- device.setBindGroup(0, this._feedbackBindGroup.get());
341
- device.setFramebuffer(this._feedbackRenderTarget.get());
342
- this._feedbackPrimitive.get().draw();
343
- device.popDeviceStates();
344
- const pos = new Float32Array(points.length * 4);
345
- const norm = new Float32Array(points.length * 4);
346
- await Promise.all([
347
- this._feedbackRenderTarget.get().getColorAttachment(0).readPixels(0, 0, points.length, 1, 0, 0, pos),
348
- this._feedbackRenderTarget.get().getColorAttachment(1).readPixels(0, 0, points.length, 1, 0, 0, norm)
349
- ]);
350
- for(let i = 0; i < points.length; i++){
351
- if (outPos) {
352
- outPos[i].setXYZ(pos[i * 4 + 0], pos[i * 4 + 1], pos[i * 4 + 2]);
1155
+ if (!this._feedbackProgram.get()) {
1156
+ this._feedbackHash = feedbackHash;
1157
+ this._feedbackProgram.set(this._createFeedbackProgram(device, waveGenerator));
1158
+ this._feedbackBindGroup.set(device.createBindGroup(this._feedbackProgram.get().bindGroupLayouts[0]));
353
1159
  }
354
- if (outNorm) {
355
- outNorm[i].setXYZ(norm[i * 4 + 0], norm[i * 4 + 1], norm[i * 4 + 2]);
1160
+ if (!this._feedbackPrimitive.get()) {
1161
+ this._feedbackPrimitive.set(new Primitive());
1162
+ this._feedbackPrimitive.get().primitiveType = 'point-list';
356
1163
  }
357
- }
358
- });
1164
+ if (!this._feedbackRenderStates) {
1165
+ this._feedbackRenderStates = device.createRenderStateSet();
1166
+ this._feedbackRenderStates.useDepthState().enableTest(false).enableWrite(false);
1167
+ this._feedbackRenderStates.useRasterizerState().setCullMode('none');
1168
+ }
1169
+ const primitive = this._feedbackPrimitive.get();
1170
+ const vertices = new Float32Array(points.length * 4);
1171
+ // The generators displace from the still-water level, so it replaces the caller's Y.
1172
+ const level = this.worldMatrix.m13;
1173
+ for(let i = 0; i < points.length; i++){
1174
+ vertices[i * 4 + 0] = points[i].x;
1175
+ vertices[i * 4 + 1] = level;
1176
+ vertices[i * 4 + 2] = points[i].z;
1177
+ vertices[i * 4 + 3] = i;
1178
+ }
1179
+ let vb = primitive.getVertexBuffer('position');
1180
+ if (!vb || vb.byteLength !== vertices.byteLength) {
1181
+ vb = device.createVertexBuffer('position_f32x4', vertices, {
1182
+ dynamic: true
1183
+ });
1184
+ primitive.setVertexBuffer(vb);
1185
+ } else {
1186
+ vb.bufferSubData(0, vertices);
1187
+ }
1188
+ const fb = this._feedbackRenderTarget.get();
1189
+ if (!fb || fb.getColorAttachment(0).width < points.length) {
1190
+ const rt0 = device.createTexture2D('rgba32f', points.length, 1, {
1191
+ mipmapping: false
1192
+ });
1193
+ const rt1 = device.createTexture2D('rgba32f', points.length, 1, {
1194
+ mipmapping: false
1195
+ });
1196
+ if (fb) {
1197
+ fb.getColorAttachment(0).dispose();
1198
+ fb.getColorAttachment(1).dispose();
1199
+ this._feedbackRenderTarget.dispose();
1200
+ }
1201
+ this._feedbackRenderTarget.set(device.createFrameBuffer([
1202
+ rt0,
1203
+ rt1
1204
+ ], null));
1205
+ }
1206
+ primitive.indexCount = points.length;
1207
+ const bindGroup = this._feedbackBindGroup.get();
1208
+ bindGroup.setValue('textureWidth', this._feedbackRenderTarget.get().getWidth());
1209
+ if (waveGenerator) {
1210
+ waveGenerator.applyWaterBindGroup(bindGroup);
1211
+ ShaderHelper.setStandaloneCameraTime(bindGroup);
1212
+ }
1213
+ device.pushDeviceStates();
1214
+ device.setProgram(this._feedbackProgram.get());
1215
+ device.setBindGroup(0, this._feedbackBindGroup.get());
1216
+ device.setFramebuffer(this._feedbackRenderTarget.get());
1217
+ this._feedbackPrimitive.get().draw();
1218
+ device.popDeviceStates();
1219
+ const pos = new Float32Array(points.length * 4);
1220
+ // The normal attachment is only read when wanted, halving the readback otherwise.
1221
+ const norm = withNormal ? new Float32Array(points.length * 4) : null;
1222
+ // readPixels records its copy synchronously; flushing right after lets the
1223
+ // readback start before this frame's main render is recorded. This runs from
1224
+ // beginFrame, so the submit carries only the feedback work.
1225
+ const readPos = this._feedbackRenderTarget.get().getColorAttachment(0).readPixels(0, 0, points.length, 1, 0, 0, pos);
1226
+ const readNorm = norm ? this._feedbackRenderTarget.get().getColorAttachment(1).readPixels(0, 0, points.length, 1, 0, 0, norm) : Promise.resolve();
1227
+ device.flush();
1228
+ await Promise.all([
1229
+ readPos,
1230
+ readNorm
1231
+ ]);
1232
+ timing.mapped = device.frameInfo.frameCounter;
1233
+ this.lastFeedbackTiming = timing;
1234
+ for(let i = 0; i < points.length; i++){
1235
+ hits[i].x = pos[i * 4 + 0];
1236
+ hits[i].y = pos[i * 4 + 1];
1237
+ hits[i].z = pos[i * 4 + 2];
1238
+ if (norm) {
1239
+ hits[i].nx = norm[i * 4 + 0];
1240
+ hits[i].ny = norm[i * 4 + 1];
1241
+ hits[i].nz = norm[i * 4 + 2];
1242
+ }
1243
+ }
1244
+ });
1245
+ } finally{
1246
+ release();
1247
+ }
1248
+ return hits;
359
1249
  }
360
- /** @internal */ _createFeedbackProgram(device) {
361
- const that = this;
1250
+ /** @internal */ _createFeedbackProgram(device, waveGenerator) {
362
1251
  const program = device.buildRenderProgram({
363
1252
  vertex (pb) {
364
1253
  this.$inputs.position = pb.vec4().attrib('position');
365
1254
  this.textureWidth = pb.float().uniform(0);
366
- that.waveGenerator.setupUniforms(this, 0);
1255
+ if (waveGenerator) {
1256
+ waveGenerator.setupUniforms(this, 0);
1257
+ // Wave generators animate from camera.elapsedTime, declared standalone here
1258
+ ShaderHelper.declareStandaloneCameraTime(this, 0);
1259
+ }
367
1260
  pb.main(function() {
368
- this.$l.worldPos = pb.vec3();
369
- this.$l.worldNorm = pb.vec3();
370
- that.waveGenerator.calcVertexPositionAndNormal(this, this.$inputs.position.xyz, this.worldPos, this.worldNorm);
1261
+ if (waveGenerator) {
1262
+ this.$l.worldPos = pb.vec3();
1263
+ this.$l.worldNorm = pb.vec3();
1264
+ waveGenerator.calcVertexPositionAndNormal(this, this.$inputs.position.xyz, this.worldPos, this.worldNorm);
1265
+ } else {
1266
+ this.$l.worldPos = this.$inputs.position.xyz;
1267
+ this.$l.worldNorm = pb.vec3(0, 1, 0);
1268
+ }
371
1269
  this.$outputs.worldPos = this.worldPos;
372
1270
  this.$outputs.worldNorm = this.worldNorm;
373
1271
  this.$outputs.xz = this.$inputs.position.xz;
@@ -381,10 +1279,13 @@ import { BoundingBox } from '../utility/bounding_volume.js';
381
1279
  fragment (pb) {
382
1280
  this.$outputs.worldPos = pb.vec4();
383
1281
  this.$outputs.worldNorm = pb.vec4();
384
- that.waveGenerator.setupUniforms(this, 0);
1282
+ if (waveGenerator) {
1283
+ waveGenerator.setupUniforms(this, 0);
1284
+ ShaderHelper.declareStandaloneCameraTime(this, 0);
1285
+ }
385
1286
  pb.main(function() {
386
1287
  this.$outputs.worldPos = pb.vec4(this.$inputs.worldPos, 1);
387
- this.$outputs.worldNorm = pb.vec4(that.waveGenerator.calcFragmentNormal(this, this.$inputs.xz, this.$inputs.worldNorm), 1);
1288
+ this.$outputs.worldNorm = waveGenerator ? pb.vec4(waveGenerator.calcFragmentNormal(this, this.$inputs.xz, this.$inputs.worldNorm), 1) : pb.vec4(0, 1, 0, 1);
388
1289
  });
389
1290
  }
390
1291
  });