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