@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
@@ -1,20 +1,21 @@
1
- import { Vector4, applyMixins, DRef, 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,11 +32,18 @@ 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';
35
37
  import { setWaterSubmergence } from '../render/underwater.js';
36
38
  import '../shaders/atmosphere.js';
37
39
  import { FBMWaveGenerator } from '../render/fbm_wavegenerator.js';
40
+ import { InteractiveWaveGenerator } from '../render/water_interaction.js';
41
+ import { RenderGraphExecutor } from '../render/rendergraph/executor.js';
38
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';
39
47
  import { WaterMaterial } from '../material/water.js';
40
48
  import { BoundingBox } from '../utility/bounding_volume.js';
41
49
 
@@ -43,17 +51,24 @@ import { BoundingBox } from '../utility/bounding_volume.js';
43
51
  /** Floor on the eye height feeding the horizon distance, in meters. @internal */ const MIN_HORIZON_EYE_HEIGHT = 0.1;
44
52
  /**
45
53
  * How far past the tessellated surface the skirt's outer edge is placed, as a
46
- * multiple of the view distance.
47
- *
48
- * Only has to be far enough that the ring reads as a horizon line rather than a
49
- * visible band; it is depth-pinned to the far plane regardless of how far out
50
- * it actually lands.
54
+ * multiple of the view distance. Its depth is pinned to the far plane.
51
55
  * @internal
52
56
  */ const SKIRT_DISTANCE_FACTOR = 10;
53
57
  /**
54
58
  * Region rectangle standing in for "no bounds", matching the WaterMaterial
55
59
  * default. @internal
56
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
+ ];
57
72
  /**
58
73
  * Water scene node
59
74
  * @public
@@ -68,17 +83,30 @@ import { BoundingBox } from '../utility/bounding_volume.js';
68
83
  /** Wave clock, in seconds. Advanced by `animationSpeed` per frame. */ _waveTime;
69
84
  /** Elapsed time at the previous update, for the frame delta. */ _lastUpdateTime;
70
85
  _feedbackProgram;
86
+ /** Hash of the wave generator the feedback program was built for. */ _feedbackHash;
71
87
  _feedbackBindGroup;
72
88
  _feedbackPrimitive;
73
89
  _feedbackRenderTarget;
74
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
+ };
75
105
  _material;
76
106
  /**
77
- * Whether each camera was inside this body of water on its previous update.
78
- *
79
- * The submerged test has a dead band around the surface, so it needs to know
80
- * which way it is crossing; per camera, because two views of one ocean can be
81
- * on opposite sides of it.
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.
82
110
  */ _submerged;
83
111
  /**
84
112
  * Creates an instance of Water node
@@ -98,19 +126,25 @@ import { BoundingBox } from '../utility/bounding_volume.js';
98
126
  this._lastUpdateTime = 0;
99
127
  this._material = new DRef(new WaterMaterial());
100
128
  this._material.get().region = new Vector4(-1, -1, 1, 1);
101
- this._material.get().TAAStrength = 0.4;
129
+ this._material.get().TAAStrength = 0.2;
102
130
  this.waveGenerator = new FBMWaveGenerator();
103
131
  this._feedbackProgram = new DRef();
132
+ this._feedbackHash = '';
104
133
  this._feedbackBindGroup = new DRef();
105
134
  this._feedbackPrimitive = new DRef();
106
135
  this._feedbackRenderTarget = new DRef();
107
136
  this._feedbackRenderStates = null;
137
+ this._feedbackQueue = Promise.resolve();
108
138
  this._submerged = new WeakMap();
109
139
  scene.queuePerCameraUpdateNode(this);
110
140
  }
111
141
  /** Disposes the water node */ onDispose() {
112
142
  super.onDispose();
113
143
  this._clipmap.dispose();
144
+ this._shoreField?.dispose();
145
+ this._shoreSource?.dispose();
146
+ this._shoreField = null;
147
+ this._shoreSource = null;
114
148
  this._renderData = null;
115
149
  this._feedbackBindGroup.dispose();
116
150
  this._feedbackPrimitive.dispose();
@@ -132,8 +166,14 @@ import { BoundingBox } from '../utility/bounding_volume.js';
132
166
  /** Material of the water */ get material() {
133
167
  return this._material.get();
134
168
  }
135
- /** Wave generator object of the water */ get waveGenerator() {
136
- 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;
137
177
  }
138
178
  set waveGenerator(waveGenerator) {
139
179
  this.material.waveGenerator = waveGenerator;
@@ -142,16 +182,30 @@ import { BoundingBox } from '../utility/bounding_volume.js';
142
182
  }
143
183
  }
144
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
+ /**
145
201
  * Whether the surface reads as an unbounded ocean reaching the horizon,
146
202
  * rather than a body of water ending at the node's extent.
147
203
  *
148
- * Off by default. A pond, a lake or a pool has an edge, and the region test
149
- * that draws it is skipped entirely while this is on.
204
+ * Off by default. The region test is skipped while this is on.
150
205
  *
151
206
  * The horizon is reached without touching the camera's far plane: the
152
207
  * clipmap's outermost ring is drawn as a skirt whose outer vertices are
153
- * pushed well past it with their depth pinned to the far value. A far plane
154
- * sized for the near scene therefore keeps its depth precision.
208
+ * pushed well past it with their depth pinned to the far value.
155
209
  */ get infinite() {
156
210
  return this.material.infinite;
157
211
  }
@@ -166,12 +220,9 @@ import { BoundingBox } from '../utility/bounding_volume.js';
166
220
  * on, in meters. 0 derives it from the true horizon distance for the camera's
167
221
  * height above the water.
168
222
  *
169
- * Raising it costs clipmap levels, which are logarithmic in the distance, so
170
- * the geometry cost of a much larger value is small. What it really trades is
171
- * against the sky's {@link SkyRenderer.aerialPerspectiveDistance}: build the
172
- * water out further than the aerial perspective LUT reaches and the far water
173
- * stops converging towards the sky colour, which puts back the hard band this
174
- * setting exists to remove.
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.
175
226
  */ get viewDistance() {
176
227
  return this._viewDistance;
177
228
  }
@@ -188,6 +239,17 @@ import { BoundingBox } from '../utility/bounding_volume.js';
188
239
  set animationSpeed(val) {
189
240
  this._animationSpeed = val;
190
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
+ }
191
253
  /** TAA strength of the water */ get TAAStrength() {
192
254
  return this.material.TAAStrength;
193
255
  }
@@ -228,10 +290,8 @@ import { BoundingBox } from '../utility/bounding_volume.js';
228
290
  this.material.causticsSceneDepth = val;
229
291
  }
230
292
  /**
231
- * Furthest distance in meters from the camera the caustic map reaches.
232
- *
233
- * A cap rather than a fixed extent: the map is fitted to the part of the water
234
- * within this distance.
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.
235
295
  */ get causticsRange() {
236
296
  return this.material.causticsRange;
237
297
  }
@@ -257,18 +317,21 @@ import { BoundingBox } from '../utility/bounding_volume.js';
257
317
  set causticsWarp(val) {
258
318
  this.material.causticsWarp = val;
259
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
+ }
260
326
  /**
261
327
  * Whether a camera inside this body of water sees the water from the inside:
262
328
  * the medium applied to the whole scene, the sky replaced by water, and the
263
329
  * surface read from below with its Snell window.
264
330
  *
265
331
  * Uses the same medium coefficients the surface does ({@link absorption},
266
- * {@link scattering} and their scales), so the volume and the surface never
267
- * disagree. Atmospheric fog is suppressed while submerged - the medium the eye
268
- * is looking through is water, not air.
269
- *
270
- * Only reaches opaque geometry, like the atmospheric fog it replaces;
271
- * transparent surfaces are not tinted by the column in front of them.
332
+ * {@link scattering} and their scales). Atmospheric fog is suppressed while
333
+ * submerged. Only reaches opaque geometry, like the atmospheric fog it
334
+ * replaces.
272
335
  */ get underwaterEnabled() {
273
336
  return this.material.underwaterEnabled;
274
337
  }
@@ -288,9 +351,8 @@ import { BoundingBox } from '../utility/bounding_volume.js';
288
351
  /**
289
352
  * Whether shafts of sunlight are marched through the water column.
290
353
  *
291
- * Reads the caustic map as the surface's transmittance, so the shafts carry
292
- * the same pattern that lands on the sea bed. Needs {@link causticsEnabled}
293
- * and everything it needs; switches itself off wherever the map is
354
+ * Reads the caustic map as the surface's transmittance. Needs
355
+ * {@link causticsEnabled}; switches itself off wherever the map is
294
356
  * unavailable.
295
357
  */ get underwaterGodRays() {
296
358
  return this.material.underwaterGodRays;
@@ -306,8 +368,7 @@ import { BoundingBox } from '../utility/bounding_volume.js';
306
368
  }
307
369
  /**
308
370
  * Samples taken along each view ray for the light shafts. Raise it when the
309
- * shafts read as grain rather than as beams, which is what too few samples of
310
- * a sharp caustic pattern look like.
371
+ * shafts read as grain rather than as beams.
311
372
  */ get underwaterGodRaySteps() {
312
373
  return this.material.underwaterGodRaySteps;
313
374
  }
@@ -318,11 +379,8 @@ import { BoundingBox } from '../utility/bounding_volume.js';
318
379
  * Whether geometry standing in the water breaks the light shafts. Off by
319
380
  * default.
320
381
  *
321
- * The shafts come from the caustic map, which describes what the *surface* did
322
- * to the sunlight and knows nothing about what is under it, so without this a
323
- * shaft runs straight through a piling or a hull. Costs a shadow map lookup
324
- * per march step, roughly doubling what the shafts cost. The sea bed's own
325
- * shadows are unaffected either way.
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.
326
384
  */ get underwaterGodRayShadow() {
327
385
  return this.material.underwaterGodRayShadow;
328
386
  }
@@ -340,11 +398,37 @@ import { BoundingBox } from '../utility/bounding_volume.js';
340
398
  this.material.underwaterHysteresis = val;
341
399
  }
342
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
+ /**
343
429
  * Strength of the sunlight scattered out of the water column towards the eye,
344
- * 1 for the value the medium coefficients imply.
345
- *
346
- * This is what makes a shadow falling on the water darken the water itself,
347
- * and a low sun tint it. 0 leaves the body lit by the environment alone.
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.
348
432
  */ get sunScatteringIntensity() {
349
433
  return this.material.sunScatteringIntensity;
350
434
  }
@@ -352,91 +436,160 @@ import { BoundingBox } from '../utility/bounding_volume.js';
352
436
  this.material.sunScatteringIntensity = val;
353
437
  }
354
438
  /**
355
- * Coverage of the foam that collects where the water meets a surface, 0 to
356
- * disable. Off by default.
357
- *
358
- * Independent of the crest foam the wave generator produces: this one is keyed
359
- * on how far the surface is from whatever is behind it, measured to that
360
- * surface's own plane - the depth of water over a bed, the horizontal distance
361
- * to a piling or a hull - so one band covers both the shoreline and the contact
362
- * line around anything standing in the water.
363
- * {@link shoreFoamWashAmount} is what makes it run up and drain back rather
364
- * than sit there as a rim.
365
- *
366
- * Estimated from the depth buffer, so it only knows about what the camera can
367
- * see: water in front of something standing above the surface gets no foam
368
- * rather than a guess. See {@link WaterMaterial.shoreFoamAmount}.
369
- */ get shoreFoamAmount() {
370
- return this.material.shoreFoamAmount;
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;
371
445
  }
372
- set shoreFoamAmount(val) {
373
- this.material.shoreFoamAmount = val;
446
+ set shoreWaveHeight(val) {
447
+ this.material.shoreWaveHeight = val;
374
448
  }
375
- /**
376
- * How far, in meters, the foam band reaches from the surface behind it. A depth
377
- * of water over a bed, a horizontal distance against anything vertical. How
378
- * wide that is on screen depends on the geometry: a thin line on a steep
379
- * drop-off, a broad stretch on a flat shelf.
380
- */ get shoreFoamDepth() {
381
- return this.material.shoreFoamDepth;
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;
382
451
  }
383
- set shoreFoamDepth(val) {
384
- this.material.shoreFoamDepth = val;
452
+ set edgeFadeWidth(val) {
453
+ this.material.edgeFadeWidth = val;
385
454
  }
386
- /**
387
- * Falloff across the band. Above 1 pushes the coverage towards the near end and
388
- * keeps the outer edge thin and broken.
389
- */ get shoreFoamFalloff() {
390
- return this.material.shoreFoamFalloff;
455
+ /** Distance in metres between shore wave crests at the outer edge of their zone. */ get shoreWaveLength() {
456
+ return this.material.shoreWaveLength;
391
457
  }
392
- set shoreFoamFalloff(val) {
393
- this.material.shoreFoamFalloff = val;
458
+ set shoreWaveLength(val) {
459
+ this.material.shoreWaveLength = val;
394
460
  }
395
- /**
396
- * Size of the clumps the band's edge breaks into, as cycles across the band.
397
- *
398
- * Relative to {@link shoreFoamDepth} rather than in cycles per metre, so one
399
- * setting reads the same on a shoreline metres across and on a collar a
400
- * handspan wide around a piling.
401
- */ get shoreFoamScale() {
402
- return this.material.shoreFoamScale;
461
+ /** Seconds between successive shore waves. */ get shoreWavePeriod() {
462
+ return this.material.shoreWavePeriod;
403
463
  }
404
- set shoreFoamScale(val) {
405
- this.material.shoreFoamScale = val;
464
+ set shoreWavePeriod(val) {
465
+ this.material.shoreWavePeriod = val;
406
466
  }
407
- /**
408
- * How far the band's edge runs back and forth, as a fraction of
409
- * {@link shoreFoamDepth}. 0 leaves a static band.
410
- */ get shoreFoamWashAmount() {
411
- return this.material.shoreFoamWashAmount;
467
+ /** How far out from the waterline the shore waves rise, in metres. */ get shoreWaveZone() {
468
+ return this.material.shoreWaveZone;
412
469
  }
413
- set shoreFoamWashAmount(val) {
414
- this.material.shoreFoamWashAmount = val;
470
+ set shoreWaveZone(val) {
471
+ this.material.shoreWaveZone = val;
415
472
  }
416
- /** Run-up cycles per second. Swell rather than wind waves, so well under 1. */ get shoreFoamWashSpeed() {
417
- return this.material.shoreFoamWashSpeed;
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;
418
475
  }
419
- set shoreFoamWashSpeed(val) {
420
- this.material.shoreFoamWashSpeed = val;
476
+ set shoreWaveSharpness(val) {
477
+ this.material.shoreWaveSharpness = val;
421
478
  }
422
- /**
423
- * Spatial frequency of the run-up phase, in cycles per meter. At 0 the whole
424
- * waterline advances in lockstep, which reads as the water level rising and
425
- * falling; a cycle every few tens of meters breaks it into sections.
426
- */ get shoreFoamWashScale() {
427
- return this.material.shoreFoamWashScale;
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;
428
532
  }
429
- set shoreFoamWashScale(val) {
430
- this.material.shoreFoamWashScale = val;
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;
431
586
  }
432
587
  /**
433
588
  * How the refracted view sample is located. Defaults to `march`.
434
589
  *
435
- * `offset` drops the depth-buffer search - a fixed number of texture fetches
436
- * per water pixel - and displaces the screen UV by the wave normal instead.
437
- * The water still refracts, but to the wrong point: a submerged silhouette
438
- * smears rather than holding still. Set it on hardware that cannot afford the
439
- * search.
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.
440
593
  */ get refractionMode() {
441
594
  return this.material.refractionMode;
442
595
  }
@@ -457,9 +610,8 @@ import { BoundingBox } from '../utility/bounding_volume.js';
457
610
  * Depth in meters the cheap refraction mode assumes the water is, used only
458
611
  * when {@link refractionMode} is `offset`.
459
612
  *
460
- * Sets how strong the cheap distortion looks. A constant rather than the real
461
- * distance to the bottom, because an offset scaled by that distance paints a
462
- * second copy of anything breaking the surface.
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.
463
615
  */ get cheapRefractionDepth() {
464
616
  return this.material.cheapRefractionDepth;
465
617
  }
@@ -514,15 +666,10 @@ import { BoundingBox } from '../utility/bounding_volume.js';
514
666
  /**
515
667
  * Artistic scale on the refracted view offset. 1 is physical, 0 disables it.
516
668
  *
517
- * The offset itself is derived: the view ray is refracted at the surface by
518
- * Snell's law, walked to whatever is behind the water, and the hit point is
519
- * projected back to the screen. That already accounts for the incidence angle,
520
- * the depth of the receiver and the perspective foreshortening, so this exists
521
- * only to dial the result back for a stylised look - not to make it correct.
522
- *
523
- * Values above 1 exaggerate; the surface stays continuous, but the sample can
524
- * wander far enough from the true hit point that the medium tint stops
525
- * matching what is visible through it.
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.
526
673
  */ get refractionScale() {
527
674
  return this.material.refractionScale;
528
675
  }
@@ -532,11 +679,8 @@ import { BoundingBox } from '../utility/bounding_volume.js';
532
679
  /**
533
680
  * Scale on the Fresnel reflectance, 1 for the physical value.
534
681
  *
535
- * Below 1 the surface reflects less than it should and shows more of what is
536
- * beneath it. Water reflects almost everything at a grazing angle, which is
537
- * physically right but can bury a sea bed the shot is about; this is the knob
538
- * that trades that reflection away. The F0 floor is scaled with it, so 0 gives
539
- * a surface with no specular response at all.
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.
540
684
  */ get reflectionStrength() {
541
685
  return this.material.reflectionStrength;
542
686
  }
@@ -546,9 +690,8 @@ import { BoundingBox } from '../utility/bounding_volume.js';
546
690
  /**
547
691
  * How much of a folded texel reads as foam.
548
692
  *
549
- * The wave generator reports where the surface has folded over on itself,
550
- * which is a measure of the fold rather than of area; this scales it into a
551
- * coverage fraction. 0 disables foam.
693
+ * Scales the wave generator's fold measure into a coverage fraction. 0
694
+ * disables foam.
552
695
  */ get foamAmount() {
553
696
  return this.material.foamAmount;
554
697
  }
@@ -558,9 +701,8 @@ import { BoundingBox } from '../utility/bounding_volume.js';
558
701
  /**
559
702
  * Falloff applied to foam coverage before it is scaled.
560
703
  *
561
- * Above 1 this pushes light folding towards no foam at all, so only a crest
562
- * that has genuinely broken shows any - which is what keeps a windy sea from
563
- * turning uniformly white.
704
+ * Above 1 this pushes light folding towards no foam, so only broken crests
705
+ * show any.
564
706
  */ get foamFalloff() {
565
707
  return this.material.foamFalloff;
566
708
  }
@@ -576,38 +718,46 @@ import { BoundingBox } from '../utility/bounding_volume.js';
576
718
  /** {@inheritDoc SceneNode.update} */ update(frameId, elapsedInSeconds) {
577
719
  if (this.material.needUpdate()) {
578
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
+ }
579
727
  if (this._timeStart === 0) {
580
728
  this._timeStart = elapsedInSeconds;
581
729
  }
582
- // A running clock rather than `elapsed * speed`: scaling the absolute
583
- // elapsed time by the speed makes a speed of zero freeze the surface at
584
- // the origin of the timeline instead of where it is, so pausing snapped
585
- // the waves back to their first frame. Accumulating the delta keeps the
586
- // phase where it was, and a speed of zero simply stops advancing it.
730
+ // Accumulate the scaled delta so a speed of zero freezes the phase in place.
587
731
  this._waveTime += Math.max(0, elapsedInSeconds - Math.max(this._timeStart, this._lastUpdateTime)) * this._animationSpeed;
588
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
+ }
589
738
  this.material.update(frameId, this._waveTime);
590
739
  this.invalidateWorldBoundingVolume(false);
591
740
  }
592
741
  }
593
742
  /** {@inheritDoc SceneNode.updatePerCamera} */ updatePerCamera(camera, _elapsedInSeconds, _deltaInSeconds) {
594
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
+ }
595
749
  if (mat) {
596
750
  const that = this;
597
751
  const infinite = mat.infinite;
598
752
  let viewDistance = 0;
599
753
  if (infinite) {
600
754
  viewDistance = this._viewDistance > 0 ? this._viewDistance : this.horizonDistance(camera);
601
- // The skirt's outer edge sits beyond where the surface is tessellated,
602
- // so the band between them is what hides the last ring of geometry.
755
+ // The skirt's outer edge sits beyond the tessellated surface to hide its last ring.
603
756
  mat.setSkirtDistance(viewDistance * SKIRT_DISTANCE_FACTOR);
604
757
  }
605
758
  this._renderData = this._clipmap.gather({
606
759
  camera,
607
- // An unbounded surface is not clipped to the node's extent, and the
608
- // region rectangle is what the clipmap would otherwise reject tiles
609
- // against. The material's own default is already this whole-domain
610
- // value; passing it keeps the two agreeing.
760
+ // An unbounded surface is not clipped to the node's region.
611
761
  minMaxWorldPos: infinite ? WHOLE_DOMAIN : mat.region,
612
762
  gridScale: Math.max(0.01, this._gridScale),
613
763
  userData: this,
@@ -616,8 +766,8 @@ import { BoundingBox } from '../utility/bounding_volume.js';
616
766
  skirt: infinite,
617
767
  calcAABB (userData, minX, maxX, minZ, maxZ, outAABB) {
618
768
  const p = that.worldMatrix.transformPointAffine(Vector3.zero());
619
- if (that.waveGenerator) {
620
- 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);
621
771
  } else {
622
772
  outAABB.minPoint.setXYZ(minX, p.y, minZ);
623
773
  outAABB.maxPoint.setXYZ(maxX, p.y + 1, maxZ);
@@ -625,25 +775,135 @@ import { BoundingBox } from '../utility/bounding_volume.js';
625
775
  }
626
776
  });
627
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));
628
805
  this.scene?.queuePerCameraUpdateNode(this);
629
806
  }
630
807
  }
631
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
+ /**
632
898
  * Decide whether `camera` is inside this body of water and publish the result.
633
899
  *
634
- * Run from `updatePerCamera`, which the node re-queues every frame regardless
635
- * of culling - the state has to survive the surface leaving the frustum, which
636
- * is exactly what a camera on the sea bed looking down does to it.
637
- *
638
- * The test is against the rest plane rather than the displaced surface. Reading
639
- * the real wave height means a GPU readback ({@link Water.getSurfacePoint}) and
640
- * the frame or two of latency that comes with it; against that, the plane is
641
- * wrong only while the eye is within a wave height of the surface, which is
642
- * also the one case this feature deliberately does not try to render.
900
+ * Run from `updatePerCamera`, which runs every frame regardless of culling, so
901
+ * the state survives the surface leaving the frustum.
643
902
  *
644
- * A dead band of {@link WaterMaterial.underwaterHysteresis} around the plane
645
- * keeps an eye parked at water level from flipping state every frame: crossing
646
- * in has to reach below the band, crossing out has to reach above it.
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.
647
907
  * @internal
648
908
  */ updateSubmergence(camera, mat) {
649
909
  const surfaceY = this.worldMatrix.transformPointAffine(Vector3.zero()).y;
@@ -651,9 +911,7 @@ import { BoundingBox } from '../utility/bounding_volume.js';
651
911
  if (!this.hidden && this.attached) {
652
912
  const camPos = camera.getWorldPosition();
653
913
  const region = mat.region;
654
- // An unbounded surface has no edge to fall outside of; a bounded one is
655
- // only entered from within its own footprint, so a camera beside a pool at
656
- // the height of its water is not in it.
914
+ // A bounded surface is only entered from within its own footprint.
657
915
  const inside = mat.infinite || camPos.x >= region.x && camPos.x <= region.z && camPos.z >= region.y && camPos.z <= region.w;
658
916
  if (inside) {
659
917
  const band = mat.underwaterHysteresis;
@@ -664,19 +922,44 @@ import { BoundingBox } from '../utility/bounding_volume.js';
664
922
  setWaterSubmergence(camera, this, submerged, surfaceY);
665
923
  }
666
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
+ /**
667
953
  * Distance to the true horizon for the camera's height above this water, in
668
954
  * meters.
669
955
  *
670
956
  * `sqrt(2 * R * h)` for earth's radius: about 5km from an eye 2m up, 25km
671
- * from 50m. Using the real figure rather than a constant is what makes the
672
- * horizon move correctly as the camera climbs, which is most of what sells
673
- * the scale of an ocean.
957
+ * from 50m.
674
958
  * @internal
675
959
  */ horizonDistance(camera) {
676
960
  const camPos = camera.getWorldPosition();
677
961
  const waterLevel = this.worldMatrix.transformPointAffine(Vector3.zero()).y;
678
- // A camera below the surface still needs a surface to look at; the floor
679
- // also keeps the sqrt away from zero when the eye is exactly at water level.
962
+ // Floored for cameras at or below the surface.
680
963
  const height = Math.max(camPos.y - waterLevel, MIN_HORIZON_EYE_HEIGHT);
681
964
  return Math.sqrt(2 * EARTH_RADIUS * height);
682
965
  }
@@ -719,11 +1002,6 @@ import { BoundingBox } from '../utility/bounding_volume.js';
719
1002
  return this._material.get()?.needSceneDepth() ?? false;
720
1003
  }
721
1004
  /**
722
- * {@inheritDoc Drawable.needHiZNearest}
723
- */ needHiZNearest() {
724
- return this._material.get()?.needHiZNearest() ?? false;
725
- }
726
- /**
727
1005
  * {@inheritDoc Drawable.getMaterial}
728
1006
  */ getMaterial() {
729
1007
  return this._material.get();
@@ -749,10 +1027,8 @@ import { BoundingBox } from '../utility/bounding_volume.js';
749
1027
  const p = this.worldMatrix.transformPointAffine(Vector3.zero());
750
1028
  const mat = this._material?.get();
751
1029
  if (mat) {
752
- // An unbounded surface has no box to test. Null reaches the cull visitor
753
- // as ClipState.CLIPPED, which keeps the node in the queue and defers the
754
- // decision to the clipmap's per-tile culling - the only place that can
755
- // decide it, since the surface is built around wherever the camera is.
1030
+ // Null keeps an unbounded surface in the queue (ClipState.CLIPPED) and
1031
+ // leaves culling to the clipmap's per-tile test.
756
1032
  if (mat.infinite) {
757
1033
  return null;
758
1034
  }
@@ -803,7 +1079,7 @@ import { BoundingBox } from '../utility/bounding_volume.js';
803
1079
  const mat = this._material?.get();
804
1080
  if (mat) {
805
1081
  this.bind(ctx, renderQueue);
806
- mat.setClipmapGridInfo(this._gridScale, this.worldMatrix.m03, this.worldMatrix.m23);
1082
+ mat.setClipmapGridInfo(this._gridScale, this.worldMatrix.m03, this.worldMatrix.m23, this._clipmap.tileResolution);
807
1083
  mat.apply(ctx);
808
1084
  for (const info of this._renderData){
809
1085
  mat.draw(info.primitive, ctx, info.numInstances);
@@ -811,99 +1087,185 @@ import { BoundingBox } from '../utility/bounding_volume.js';
811
1087
  }
812
1088
  }
813
1089
  /**
814
- * Retreive the disturbed world position and normal at water surface
815
- */ 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) {
816
1120
  const device = getDevice();
817
1121
  if (!points || points.length === 0) {
818
- return;
1122
+ return null;
819
1123
  }
820
1124
  points = points.map((v)=>v.clone());
821
- await device.runNextFrameAsync(async ()=>{
822
- if (!this._feedbackProgram.get()) {
823
- this._feedbackProgram.set(this._createFeedbackProgram(device));
824
- this._feedbackBindGroup.set(device.createBindGroup(this._feedbackProgram.get().bindGroupLayouts[0]));
825
- }
826
- if (!this._feedbackPrimitive.get()) {
827
- this._feedbackPrimitive.set(new Primitive());
828
- this._feedbackPrimitive.get().primitiveType = 'point-list';
829
- }
830
- if (!this._feedbackRenderStates) {
831
- this._feedbackRenderStates = device.createRenderStateSet();
832
- this._feedbackRenderStates.useDepthState().enableTest(false).enableWrite(false);
833
- this._feedbackRenderStates.useRasterizerState().setCullMode('none');
834
- }
835
- const primitive = this._feedbackPrimitive.get();
836
- const vertices = new Float32Array(points.length * 4);
837
- for(let i = 0; i < points.length; i++){
838
- vertices[i * 4 + 0] = points[i].x;
839
- vertices[i * 4 + 1] = points[i].y;
840
- vertices[i * 4 + 2] = points[i].z;
841
- vertices[i * 4 + 3] = i;
842
- }
843
- let vb = primitive.getVertexBuffer('position');
844
- if (!vb || vb.byteLength !== vertices.byteLength) {
845
- vb = device.createVertexBuffer('position_f32x4', vertices, {
846
- dynamic: true
847
- });
848
- primitive.setVertexBuffer(vb);
849
- } else {
850
- vb.bufferSubData(0, vertices);
851
- }
852
- const fb = this._feedbackRenderTarget.get();
853
- if (!fb || fb.getColorAttachment(0).width < points.length) {
854
- const rt0 = device.createTexture2D('rgba32f', points.length, 1, {
855
- mipmapping: false
856
- });
857
- const rt1 = device.createTexture2D('rgba32f', points.length, 1, {
858
- mipmapping: false
859
- });
860
- if (fb) {
861
- fb.getColorAttachment(0).dispose();
862
- fb.getColorAttachment(1).dispose();
863
- 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();
864
1154
  }
865
- this._feedbackRenderTarget.set(device.createFrameBuffer([
866
- rt0,
867
- rt1
868
- ], null));
869
- }
870
- primitive.indexCount = points.length;
871
- const bindGroup = this._feedbackBindGroup.get();
872
- bindGroup.setValue('textureWidth', this._feedbackRenderTarget.get().getWidth());
873
- this.waveGenerator.applyWaterBindGroup(bindGroup);
874
- device.pushDeviceStates();
875
- device.setProgram(this._feedbackProgram.get());
876
- device.setBindGroup(0, this._feedbackBindGroup.get());
877
- device.setFramebuffer(this._feedbackRenderTarget.get());
878
- this._feedbackPrimitive.get().draw();
879
- device.popDeviceStates();
880
- const pos = new Float32Array(points.length * 4);
881
- const norm = new Float32Array(points.length * 4);
882
- await Promise.all([
883
- this._feedbackRenderTarget.get().getColorAttachment(0).readPixels(0, 0, points.length, 1, 0, 0, pos),
884
- this._feedbackRenderTarget.get().getColorAttachment(1).readPixels(0, 0, points.length, 1, 0, 0, norm)
885
- ]);
886
- for(let i = 0; i < points.length; i++){
887
- if (outPos) {
888
- 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]));
889
1159
  }
890
- if (outNorm) {
891
- 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';
892
1163
  }
893
- }
894
- });
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;
895
1249
  }
896
- /** @internal */ _createFeedbackProgram(device) {
897
- const that = this;
1250
+ /** @internal */ _createFeedbackProgram(device, waveGenerator) {
898
1251
  const program = device.buildRenderProgram({
899
1252
  vertex (pb) {
900
1253
  this.$inputs.position = pb.vec4().attrib('position');
901
1254
  this.textureWidth = pb.float().uniform(0);
902
- 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
+ }
903
1260
  pb.main(function() {
904
- this.$l.worldPos = pb.vec3();
905
- this.$l.worldNorm = pb.vec3();
906
- 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
+ }
907
1269
  this.$outputs.worldPos = this.worldPos;
908
1270
  this.$outputs.worldNorm = this.worldNorm;
909
1271
  this.$outputs.xz = this.$inputs.position.xz;
@@ -917,10 +1279,13 @@ import { BoundingBox } from '../utility/bounding_volume.js';
917
1279
  fragment (pb) {
918
1280
  this.$outputs.worldPos = pb.vec4();
919
1281
  this.$outputs.worldNorm = pb.vec4();
920
- that.waveGenerator.setupUniforms(this, 0);
1282
+ if (waveGenerator) {
1283
+ waveGenerator.setupUniforms(this, 0);
1284
+ ShaderHelper.declareStandaloneCameraTime(this, 0);
1285
+ }
921
1286
  pb.main(function() {
922
1287
  this.$outputs.worldPos = pb.vec4(this.$inputs.worldPos, 1);
923
- 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);
924
1289
  });
925
1290
  }
926
1291
  });