@zephyr3d/scene 0.9.27 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (502) hide show
  1. package/README.md +60 -27
  2. package/dist/animation/animation_bank.js +634 -0
  3. package/dist/animation/animation_bank.js.map +1 -0
  4. package/dist/animation/animation_bank_gpu.js +256 -0
  5. package/dist/animation/animation_bank_gpu.js.map +1 -0
  6. package/dist/animation/animationset.js +54 -0
  7. package/dist/animation/animationset.js.map +1 -1
  8. package/dist/animation/cloth/gpu_cloth_component.js +406 -0
  9. package/dist/animation/cloth/gpu_cloth_component.js.map +1 -0
  10. package/dist/animation/cloth/gpu_cloth_system.js +332 -202
  11. package/dist/animation/cloth/gpu_cloth_system.js.map +1 -1
  12. package/dist/animation/gpu_animation_driver.js +289 -0
  13. package/dist/animation/gpu_animation_driver.js.map +1 -0
  14. package/dist/animation/joint_dynamics/collision.js +112 -1
  15. package/dist/animation/joint_dynamics/collision.js.map +1 -1
  16. package/dist/animation/joint_dynamics/controller.js +6 -0
  17. package/dist/animation/joint_dynamics/controller.js.map +1 -1
  18. package/dist/animation/joint_dynamics/joint_dynamics_system.js +11 -0
  19. package/dist/animation/joint_dynamics/joint_dynamics_system.js.map +1 -1
  20. package/dist/animation/joint_dynamics/solver.js +50 -12
  21. package/dist/animation/joint_dynamics/solver.js.map +1 -1
  22. package/dist/animation/joint_dynamics/types.js.map +1 -1
  23. package/dist/animation/skeleton.js +279 -55
  24. package/dist/animation/skeleton.js.map +1 -1
  25. package/dist/animation/skin_palette_atlas.js +252 -0
  26. package/dist/animation/skin_palette_atlas.js.map +1 -0
  27. package/dist/animation/spring/multi_chain_spring_system.js +298 -38
  28. package/dist/animation/spring/multi_chain_spring_system.js.map +1 -1
  29. package/dist/animation/spring/spring_chain.js +2 -0
  30. package/dist/animation/spring/spring_chain.js.map +1 -1
  31. package/dist/animation/spring/spring_collider.js +159 -21
  32. package/dist/animation/spring/spring_collider.js.map +1 -1
  33. package/dist/animation/spring/spring_particle.js +1 -0
  34. package/dist/animation/spring/spring_particle.js.map +1 -1
  35. package/dist/animation/spring/spring_solver.js +173 -0
  36. package/dist/animation/spring/spring_solver.js.map +1 -0
  37. package/dist/animation/spring/spring_system.js +249 -24
  38. package/dist/animation/spring/spring_system.js.map +1 -1
  39. package/dist/animation/spring_modifier.js +32 -1
  40. package/dist/animation/spring_modifier.js.map +1 -1
  41. package/dist/app/app.js +26 -0
  42. package/dist/app/app.js.map +1 -1
  43. package/dist/app/engine.js +86 -15
  44. package/dist/app/engine.js.map +1 -1
  45. package/dist/app/gamepad.js +191 -0
  46. package/dist/app/gamepad.js.map +1 -0
  47. package/dist/app/inputactions.js +271 -0
  48. package/dist/app/inputactions.js.map +1 -0
  49. package/dist/app/inputmgr.js +331 -22
  50. package/dist/app/inputmgr.js.map +1 -1
  51. package/dist/app/loading.js +138 -0
  52. package/dist/app/loading.js.map +1 -0
  53. package/dist/app/pipelinecache.js +209 -0
  54. package/dist/app/pipelinecache.js.map +1 -0
  55. package/dist/app/runtimescript.js +25 -0
  56. package/dist/app/runtimescript.js.map +1 -1
  57. package/dist/app/scriptingsystem.js +54 -0
  58. package/dist/app/scriptingsystem.js.map +1 -1
  59. package/dist/app/scriptregistry.js +7 -2
  60. package/dist/app/scriptregistry.js.map +1 -1
  61. package/dist/asset/asset_manifest.js +59 -0
  62. package/dist/asset/asset_manifest.js.map +1 -0
  63. package/dist/asset/asset_preload.js +258 -0
  64. package/dist/asset/asset_preload.js.map +1 -0
  65. package/dist/asset/assetmanager.js +117 -65
  66. package/dist/asset/assetmanager.js.map +1 -1
  67. package/dist/asset/loaders/ktx2/basis/LICENSE +201 -0
  68. package/dist/asset/loaders/ktx2/basis/basis_transcoder.js +2 -0
  69. package/dist/asset/loaders/ktx2/basis/basis_transcoder.wasm +0 -0
  70. package/dist/asset/loaders/ktx2/basis_location.js +17 -0
  71. package/dist/asset/loaders/ktx2/basis_location.js.map +1 -0
  72. package/dist/asset/loaders/ktx2/ktx2_loader.js +88 -0
  73. package/dist/asset/loaders/ktx2/ktx2_loader.js.map +1 -0
  74. package/dist/asset/loaders/ktx2/ktx2_targets.js +118 -0
  75. package/dist/asset/loaders/ktx2/ktx2_targets.js.map +1 -0
  76. package/dist/asset/loaders/ktx2/transcoder_pool.js +259 -0
  77. package/dist/asset/loaders/ktx2/transcoder_pool.js.map +1 -0
  78. package/dist/asset/mesh_lod_builder.js +104 -0
  79. package/dist/asset/mesh_lod_builder.js.map +1 -0
  80. package/dist/asset/mesh_settings.js +111 -0
  81. package/dist/asset/mesh_settings.js.map +1 -0
  82. package/dist/asset/meshopt_decoder.js +18 -0
  83. package/dist/asset/meshopt_decoder.js.map +1 -0
  84. package/dist/asset/meshopt_simplifier.js +26 -0
  85. package/dist/asset/meshopt_simplifier.js.map +1 -0
  86. package/dist/asset/model.js +171 -1
  87. package/dist/asset/model.js.map +1 -1
  88. package/dist/asset/texture_settings.js +231 -0
  89. package/dist/asset/texture_settings.js.map +1 -0
  90. package/dist/asset/zmsh_binary.js +131 -0
  91. package/dist/asset/zmsh_binary.js.map +1 -0
  92. package/dist/asset/zsdf_binary.js +111 -0
  93. package/dist/asset/zsdf_binary.js.map +1 -0
  94. package/dist/blitter/bilateralblur.js +1 -2
  95. package/dist/blitter/bilateralblur.js.map +1 -1
  96. package/dist/blitter/gaussianblur.js +1 -6
  97. package/dist/blitter/gaussianblur.js.map +1 -1
  98. package/dist/camera/base.js +31 -10
  99. package/dist/camera/base.js.map +1 -1
  100. package/dist/camera/camera.js +104 -178
  101. package/dist/camera/camera.js.map +1 -1
  102. package/dist/camera/follow.js +342 -0
  103. package/dist/camera/follow.js.map +1 -0
  104. package/dist/camera/fps.js +9 -7
  105. package/dist/camera/fps.js.map +1 -1
  106. package/dist/camera/orbit.js +46 -19
  107. package/dist/camera/orbit.js.map +1 -1
  108. package/dist/index.d.ts +25436 -15990
  109. package/dist/index.js +65 -11
  110. package/dist/index.js.map +1 -1
  111. package/dist/material/blinn.js +3 -0
  112. package/dist/material/blinn.js.map +1 -1
  113. package/dist/material/eye.js +1 -1
  114. package/dist/material/eye.js.map +1 -1
  115. package/dist/material/hair.js +2 -3
  116. package/dist/material/hair.js.map +1 -1
  117. package/dist/material/lambert.js +4 -1
  118. package/dist/material/lambert.js.map +1 -1
  119. package/dist/material/material.js +66 -4
  120. package/dist/material/material.js.map +1 -1
  121. package/dist/material/meshmaterial.js +118 -45
  122. package/dist/material/meshmaterial.js.map +1 -1
  123. package/dist/material/mixins/lightmodel/blinnphong.js +1 -1
  124. package/dist/material/mixins/lightmodel/blinnphong.js.map +1 -1
  125. package/dist/material/mixins/lightmodel/lambert.js +1 -1
  126. package/dist/material/mixins/lightmodel/lambert.js.map +1 -1
  127. package/dist/material/mixins/lightmodel/pbrblueprintmixin.js +34 -61
  128. package/dist/material/mixins/lightmodel/pbrblueprintmixin.js.map +1 -1
  129. package/dist/material/mixins/lightmodel/pbrmetallicroughness.js +16 -2
  130. package/dist/material/mixins/lightmodel/pbrmetallicroughness.js.map +1 -1
  131. package/dist/material/mixins/lightmodel/pbrspecularglossness.js +2 -1
  132. package/dist/material/mixins/lightmodel/pbrspecularglossness.js.map +1 -1
  133. package/dist/material/mixins/lit.js +191 -83
  134. package/dist/material/mixins/lit.js.map +1 -1
  135. package/dist/material/mixins/pbr/common.js +31 -206
  136. package/dist/material/mixins/pbr/common.js.map +1 -1
  137. package/dist/material/mtoon.js +1 -1
  138. package/dist/material/mtoon.js.map +1 -1
  139. package/dist/material/pbrmr.js +3 -0
  140. package/dist/material/pbrmr.js.map +1 -1
  141. package/dist/material/pbrsg.js +3 -0
  142. package/dist/material/pbrsg.js.map +1 -1
  143. package/dist/material/sdf_debug.js +78 -0
  144. package/dist/material/sdf_debug.js.map +1 -0
  145. package/dist/material/shader/helper.js +333 -191
  146. package/dist/material/shader/helper.js.map +1 -1
  147. package/dist/material/shader/terrain_blend.js +204 -43
  148. package/dist/material/shader/terrain_blend.js.map +1 -1
  149. package/dist/material/skin.js +331 -297
  150. package/dist/material/skin.js.map +1 -1
  151. package/dist/material/skinprofile.js +1058 -0
  152. package/dist/material/skinprofile.js.map +1 -0
  153. package/dist/material/sss.js +353 -0
  154. package/dist/material/sss.js.map +1 -0
  155. package/dist/material/sss_kernel.js +265 -0
  156. package/dist/material/sss_kernel.js.map +1 -0
  157. package/dist/material/sssprofile.js +879 -0
  158. package/dist/material/sssprofile.js.map +1 -0
  159. package/dist/material/subsurfaceprofile.js +4 -8
  160. package/dist/material/subsurfaceprofile.js.map +1 -1
  161. package/dist/material/terrain-cm.js +464 -188
  162. package/dist/material/terrain-cm.js.map +1 -1
  163. package/dist/material/unlit.js +3 -0
  164. package/dist/material/unlit.js.map +1 -1
  165. package/dist/material/water.js +1261 -1438
  166. package/dist/material/water.js.map +1 -1
  167. package/dist/node_modules/meshoptimizer/LICENSE +21 -0
  168. package/dist/node_modules/meshoptimizer/meshopt_decoder.js +197 -0
  169. package/dist/node_modules/meshoptimizer/meshopt_decoder.js.map +1 -0
  170. package/dist/node_modules/meshoptimizer/meshopt_simplifier.js +677 -0
  171. package/dist/node_modules/meshoptimizer/meshopt_simplifier.js.map +1 -0
  172. package/dist/physics/character.js +226 -0
  173. package/dist/physics/character.js.map +1 -0
  174. package/dist/physics/collider.js +209 -0
  175. package/dist/physics/collider.js.map +1 -0
  176. package/dist/physics/events.js +79 -0
  177. package/dist/physics/events.js.map +1 -0
  178. package/dist/physics/joint.js +272 -0
  179. package/dist/physics/joint.js.map +1 -0
  180. package/dist/physics/node_physics.js +234 -0
  181. package/dist/physics/node_physics.js.map +1 -0
  182. package/dist/physics/part.js +91 -0
  183. package/dist/physics/part.js.map +1 -0
  184. package/dist/physics/rigid_body.js +379 -0
  185. package/dist/physics/rigid_body.js.map +1 -0
  186. package/dist/physics/vehicle.js +335 -0
  187. package/dist/physics/vehicle.js.map +1 -0
  188. package/dist/physics/world.js +25 -0
  189. package/dist/physics/world.js.map +1 -0
  190. package/dist/posteffect/bloom.js +49 -14
  191. package/dist/posteffect/bloom.js.map +1 -1
  192. package/dist/posteffect/motionblur.js +106 -106
  193. package/dist/posteffect/motionblur.js.map +1 -1
  194. package/dist/posteffect/postsss.js +572 -0
  195. package/dist/posteffect/postsss.js.map +1 -0
  196. package/dist/posteffect/sao.js +2 -6
  197. package/dist/posteffect/sao.js.map +1 -1
  198. package/dist/posteffect/skinsss.js +605 -355
  199. package/dist/posteffect/skinsss.js.map +1 -1
  200. package/dist/posteffect/ssgi.js +91 -162
  201. package/dist/posteffect/ssgi.js.map +1 -1
  202. package/dist/posteffect/ssr.js +3 -11
  203. package/dist/posteffect/ssr.js.map +1 -1
  204. package/dist/posteffect/sss.js +4 -13
  205. package/dist/posteffect/sss.js.map +1 -1
  206. package/dist/posteffect/taa.js +1 -0
  207. package/dist/posteffect/taa.js.map +1 -1
  208. package/dist/render/abuffer_oit.js +3 -2
  209. package/dist/render/abuffer_oit.js.map +1 -1
  210. package/dist/render/clipmap.js +179 -64
  211. package/dist/render/clipmap.js.map +1 -1
  212. package/dist/render/cluster_light.js +958 -224
  213. package/dist/render/cluster_light.js.map +1 -1
  214. package/dist/render/cull_visitor.js +25 -5
  215. package/dist/render/cull_visitor.js.map +1 -1
  216. package/dist/render/debug_views.js +37 -0
  217. package/dist/render/debug_views.js.map +1 -0
  218. package/dist/render/depthpass.js +5 -1
  219. package/dist/render/depthpass.js.map +1 -1
  220. package/dist/render/drawable_mixin.js +53 -6
  221. package/dist/render/drawable_mixin.js.map +1 -1
  222. package/dist/render/dualdepthpeeling_oit.js +5 -5
  223. package/dist/render/dualdepthpeeling_oit.js.map +1 -1
  224. package/dist/render/envlight.js +196 -113
  225. package/dist/render/envlight.js.map +1 -1
  226. package/dist/render/fbm_wavegenerator.js +5 -10
  227. package/dist/render/fbm_wavegenerator.js.map +1 -1
  228. package/dist/render/fft_wavegenerator.js +247 -157
  229. package/dist/render/fft_wavegenerator.js.map +1 -1
  230. package/dist/render/gerstner_wavegenerator.js +16 -38
  231. package/dist/render/gerstner_wavegenerator.js.map +1 -1
  232. package/dist/render/gi/probe_volume.js +1334 -0
  233. package/dist/render/gi/probe_volume.js.map +1 -0
  234. package/dist/render/gi/scene_gi.js +282 -0
  235. package/dist/render/gi/scene_gi.js.map +1 -0
  236. package/dist/render/gi/voxel_atlas.js +198 -0
  237. package/dist/render/gi/voxel_atlas.js.map +1 -0
  238. package/dist/render/gi/voxel_lighting.js +652 -0
  239. package/dist/render/gi/voxel_lighting.js.map +1 -0
  240. package/dist/render/gi/voxel_scene.js +642 -0
  241. package/dist/render/gi/voxel_scene.js.map +1 -0
  242. package/dist/render/gi/voxelize_pass.js +223 -0
  243. package/dist/render/gi/voxelize_pass.js.map +1 -0
  244. package/dist/render/globalbindgroup_allocator.js +3 -2
  245. package/dist/render/globalbindgroup_allocator.js.map +1 -1
  246. package/dist/render/gpu_picking.js +9 -36
  247. package/dist/render/gpu_picking.js.map +1 -1
  248. package/dist/render/instance_culling.js +499 -0
  249. package/dist/render/instance_culling.js.map +1 -0
  250. package/dist/render/lightpass.js +19 -8
  251. package/dist/render/lightpass.js.map +1 -1
  252. package/dist/render/lod.js +46 -0
  253. package/dist/render/lod.js.map +1 -0
  254. package/dist/render/primitive.js +301 -9
  255. package/dist/render/primitive.js.map +1 -1
  256. package/dist/render/primitive_readback.js +209 -0
  257. package/dist/render/primitive_readback.js.map +1 -0
  258. package/dist/render/render_queue.js +162 -11
  259. package/dist/render/render_queue.js.map +1 -1
  260. package/dist/render/renderer.js +10 -7
  261. package/dist/render/renderer.js.map +1 -1
  262. package/dist/render/rendergraph/blackboard.js +14 -1
  263. package/dist/render/rendergraph/blackboard.js.map +1 -1
  264. package/dist/render/rendergraph/executor.js +136 -0
  265. package/dist/render/rendergraph/executor.js.map +1 -1
  266. package/dist/render/rendergraph/forward_plus_builder.js +272 -55
  267. package/dist/render/rendergraph/forward_plus_builder.js.map +1 -1
  268. package/dist/render/rendergraph/frame_graph_context.js +41 -0
  269. package/dist/render/rendergraph/frame_graph_context.js.map +1 -0
  270. package/dist/render/rendergraph/scene_render_context.js +31 -2
  271. package/dist/render/rendergraph/scene_render_context.js.map +1 -1
  272. package/dist/render/rendergraph/types.js.map +1 -1
  273. package/dist/render/renderpass.js +28 -5
  274. package/dist/render/renderpass.js.map +1 -1
  275. package/dist/render/sdf/box_allocator.js +98 -0
  276. package/dist/render/sdf/box_allocator.js.map +1 -0
  277. package/dist/render/sdf/global_sdf.js +865 -0
  278. package/dist/render/sdf/global_sdf.js.map +1 -0
  279. package/dist/render/sdf/scene_sdf.js +562 -0
  280. package/dist/render/sdf/scene_sdf.js.map +1 -0
  281. package/dist/render/sdf/sdf_atlas.js +227 -0
  282. package/dist/render/sdf/sdf_atlas.js.map +1 -0
  283. package/dist/render/sdf/sdf_programs.js +42 -0
  284. package/dist/render/sdf/sdf_programs.js.map +1 -0
  285. package/dist/render/sdf/sdf_view.js +199 -0
  286. package/dist/render/sdf/sdf_view.js.map +1 -0
  287. package/dist/render/sdf/waterline.js +610 -0
  288. package/dist/render/sdf/waterline.js.map +1 -0
  289. package/dist/render/shadow_mask_pass.js +142 -25
  290. package/dist/render/shadow_mask_pass.js.map +1 -1
  291. package/dist/render/shore_wave_generator.js +1193 -0
  292. package/dist/render/shore_wave_generator.js.map +1 -0
  293. package/dist/render/sky.js +516 -86
  294. package/dist/render/sky.js.map +1 -1
  295. package/dist/render/transmission_thickness_pass.js +477 -0
  296. package/dist/render/transmission_thickness_pass.js.map +1 -0
  297. package/dist/render/underwater.js +58 -158
  298. package/dist/render/underwater.js.map +1 -1
  299. package/dist/render/virtualtexture/virtual_texture.js +1214 -0
  300. package/dist/render/virtualtexture/virtual_texture.js.map +1 -0
  301. package/dist/render/volumetric_cloud/cloud_common.js +472 -0
  302. package/dist/render/volumetric_cloud/cloud_common.js.map +1 -0
  303. package/dist/render/volumetric_cloud/cloud_noise.js +189 -0
  304. package/dist/render/volumetric_cloud/cloud_noise.js.map +1 -0
  305. package/dist/render/volumetric_cloud/debug_view.js +131 -0
  306. package/dist/render/volumetric_cloud/debug_view.js.map +1 -0
  307. package/dist/render/volumetric_cloud/resources.js +380 -0
  308. package/dist/render/volumetric_cloud/resources.js.map +1 -0
  309. package/dist/render/volumetric_cloud/sky_cache.js +380 -0
  310. package/dist/render/volumetric_cloud/sky_cache.js.map +1 -0
  311. package/dist/render/volumetric_cloud/volumetric_cloud.js +318 -0
  312. package/dist/render/volumetric_cloud/volumetric_cloud.js.map +1 -0
  313. package/dist/render/water_caustics.js +134 -319
  314. package/dist/render/water_caustics.js.map +1 -1
  315. package/dist/render/water_foam.js +627 -0
  316. package/dist/render/water_foam.js.map +1 -0
  317. package/dist/render/water_interaction.js +1795 -0
  318. package/dist/render/water_interaction.js.map +1 -0
  319. package/dist/render/weightedblended_oit.js +3 -3
  320. package/dist/render/weightedblended_oit.js.map +1 -1
  321. package/dist/scene/basesprite.js +9 -4
  322. package/dist/scene/basesprite.js.map +1 -1
  323. package/dist/scene/batchgroup.js +108 -12
  324. package/dist/scene/batchgroup.js.map +1 -1
  325. package/dist/scene/environment.js +28 -7
  326. package/dist/scene/environment.js.map +1 -1
  327. package/dist/scene/foliage.js +1020 -0
  328. package/dist/scene/foliage.js.map +1 -0
  329. package/dist/scene/foliage_buckets.js +147 -0
  330. package/dist/scene/foliage_buckets.js.map +1 -0
  331. package/dist/scene/foliage_mask.js +358 -0
  332. package/dist/scene/foliage_mask.js.map +1 -0
  333. package/dist/scene/hair.js +15 -4
  334. package/dist/scene/hair.js.map +1 -1
  335. package/dist/scene/light.js +179 -13
  336. package/dist/scene/light.js.map +1 -1
  337. package/dist/scene/mesh.js +327 -45
  338. package/dist/scene/mesh.js.map +1 -1
  339. package/dist/scene/meshdrawable.js +6 -0
  340. package/dist/scene/meshdrawable.js.map +1 -1
  341. package/dist/scene/msdftext.js +9 -3
  342. package/dist/scene/msdftext.js.map +1 -1
  343. package/dist/scene/msdftextsprite.js +9 -3
  344. package/dist/scene/msdftextsprite.js.map +1 -1
  345. package/dist/scene/particlesys.js +8 -2
  346. package/dist/scene/particlesys.js.map +1 -1
  347. package/dist/scene/physics_settings.js +157 -0
  348. package/dist/scene/physics_settings.js.map +1 -0
  349. package/dist/scene/scene.js +190 -44
  350. package/dist/scene/scene.js.map +1 -1
  351. package/dist/scene/scene_node.js +147 -6
  352. package/dist/scene/scene_node.js.map +1 -1
  353. package/dist/scene/terrain-cm/bladegrassmaterial.js +735 -0
  354. package/dist/scene/terrain-cm/bladegrassmaterial.js.map +1 -0
  355. package/dist/scene/terrain-cm/grass.js +881 -42
  356. package/dist/scene/terrain-cm/grass.js.map +1 -1
  357. package/dist/scene/terrain-cm/grass_gpu.js +929 -0
  358. package/dist/scene/terrain-cm/grass_gpu.js.map +1 -0
  359. package/dist/scene/terrain-cm/grassmaterial.js +180 -9
  360. package/dist/scene/terrain-cm/grassmaterial.js.map +1 -1
  361. package/dist/scene/terrain-cm/terrain-cm.js +465 -36
  362. package/dist/scene/terrain-cm/terrain-cm.js.map +1 -1
  363. package/dist/scene/terrain-cm/terrain_gi_albedo.js +257 -0
  364. package/dist/scene/terrain-cm/terrain_gi_albedo.js.map +1 -0
  365. package/dist/scene/terrain-cm/terrain_rvt.js +395 -0
  366. package/dist/scene/terrain-cm/terrain_rvt.js.map +1 -0
  367. package/dist/scene/update_queue.js +54 -0
  368. package/dist/scene/update_queue.js.map +1 -0
  369. package/dist/scene/water.js +633 -268
  370. package/dist/scene/water.js.map +1 -1
  371. package/dist/scene/water_surface_sampler.js +324 -0
  372. package/dist/scene/water_surface_sampler.js.map +1 -0
  373. package/dist/scene/wind.js +317 -0
  374. package/dist/scene/wind.js.map +1 -0
  375. package/dist/shaders/atmosphere.js +388 -166
  376. package/dist/shaders/atmosphere.js.map +1 -1
  377. package/dist/shaders/fog.js +100 -32
  378. package/dist/shaders/fog.js.map +1 -1
  379. package/dist/shaders/gi.js +365 -0
  380. package/dist/shaders/gi.js.map +1 -0
  381. package/dist/shaders/ltc_rect.js +202 -0
  382. package/dist/shaders/ltc_rect.js.map +1 -0
  383. package/dist/shaders/misc.js +1 -10
  384. package/dist/shaders/misc.js.map +1 -1
  385. package/dist/shaders/noise.js +5 -1
  386. package/dist/shaders/noise.js.map +1 -1
  387. package/dist/shaders/probes.js +309 -0
  388. package/dist/shaders/probes.js.map +1 -0
  389. package/dist/shaders/sdf.js +537 -0
  390. package/dist/shaders/sdf.js.map +1 -0
  391. package/dist/shaders/shadow.js +104 -70
  392. package/dist/shaders/shadow.js.map +1 -1
  393. package/dist/shaders/skin_brdf.js +243 -0
  394. package/dist/shaders/skin_brdf.js.map +1 -0
  395. package/dist/shaders/ssgi.js +63 -0
  396. package/dist/shaders/ssgi.js.map +1 -0
  397. package/dist/shaders/ssgi_reprojection.js +118 -0
  398. package/dist/shaders/ssgi_reprojection.js.map +1 -0
  399. package/dist/shaders/ssr.js +6 -159
  400. package/dist/shaders/ssr.js.map +1 -1
  401. package/dist/shaders/temporal.js +38 -27
  402. package/dist/shaders/temporal.js.map +1 -1
  403. package/dist/shaders/water.js +32 -82
  404. package/dist/shaders/water.js.map +1 -1
  405. package/dist/shaders/water_medium.js +7 -21
  406. package/dist/shaders/water_medium.js.map +1 -1
  407. package/dist/shadow/dom.js +110 -19
  408. package/dist/shadow/dom.js.map +1 -1
  409. package/dist/shadow/esm.js +3 -3
  410. package/dist/shadow/esm.js.map +1 -1
  411. package/dist/shadow/pcf_opt.js +15 -48
  412. package/dist/shadow/pcf_opt.js.map +1 -1
  413. package/dist/shadow/pcf_pd.js +15 -53
  414. package/dist/shadow/pcf_pd.js.map +1 -1
  415. package/dist/shadow/pcss.js +34 -9
  416. package/dist/shadow/pcss.js.map +1 -1
  417. package/dist/shadow/shadow_impl.js +3 -8
  418. package/dist/shadow/shadow_impl.js.map +1 -1
  419. package/dist/shadow/shadow_region.js +40 -2
  420. package/dist/shadow/shadow_region.js.map +1 -1
  421. package/dist/shadow/shadowmapper.js +93 -53
  422. package/dist/shadow/shadowmapper.js.map +1 -1
  423. package/dist/shadow/ssm.js +18 -72
  424. package/dist/shadow/ssm.js.map +1 -1
  425. package/dist/shadow/vsm.js +3 -3
  426. package/dist/shadow/vsm.js.map +1 -1
  427. package/dist/shapes/shape.js +44 -1
  428. package/dist/shapes/shape.js.map +1 -1
  429. package/dist/utility/buoyancy.js +287 -0
  430. package/dist/utility/buoyancy.js.map +1 -0
  431. package/dist/utility/physical.js +10 -1
  432. package/dist/utility/physical.js.map +1 -1
  433. package/dist/utility/pmrem.js +17 -43
  434. package/dist/utility/pmrem.js.map +1 -1
  435. package/dist/utility/sdf/bake.js +435 -0
  436. package/dist/utility/sdf/bake.js.map +1 -0
  437. package/dist/utility/sdf/bvh.js +576 -0
  438. package/dist/utility/sdf/bvh.js.map +1 -0
  439. package/dist/utility/serialization/manager.js +34 -6
  440. package/dist/utility/serialization/manager.js.map +1 -1
  441. package/dist/utility/serialization/scene/animation.js +46 -5
  442. package/dist/utility/serialization/scene/animation.js.map +1 -1
  443. package/dist/utility/serialization/scene/batch.js +11 -4
  444. package/dist/utility/serialization/scene/batch.js.map +1 -1
  445. package/dist/utility/serialization/scene/camera.js +87 -254
  446. package/dist/utility/serialization/scene/camera.js.map +1 -1
  447. package/dist/utility/serialization/scene/cloth.js +25 -0
  448. package/dist/utility/serialization/scene/cloth.js.map +1 -0
  449. package/dist/utility/serialization/scene/common.js +14 -6
  450. package/dist/utility/serialization/scene/common.js.map +1 -1
  451. package/dist/utility/serialization/scene/foliage.js +821 -0
  452. package/dist/utility/serialization/scene/foliage.js.map +1 -0
  453. package/dist/utility/serialization/scene/hair.js +11 -4
  454. package/dist/utility/serialization/scene/hair.js.map +1 -1
  455. package/dist/utility/serialization/scene/light.js +115 -7
  456. package/dist/utility/serialization/scene/light.js.map +1 -1
  457. package/dist/utility/serialization/scene/material.js +388 -220
  458. package/dist/utility/serialization/scene/material.js.map +1 -1
  459. package/dist/utility/serialization/scene/mesh.js +69 -4
  460. package/dist/utility/serialization/scene/mesh.js.map +1 -1
  461. package/dist/utility/serialization/scene/node.js +140 -5
  462. package/dist/utility/serialization/scene/node.js.map +1 -1
  463. package/dist/utility/serialization/scene/node_physics.js +1318 -0
  464. package/dist/utility/serialization/scene/node_physics.js.map +1 -0
  465. package/dist/utility/serialization/scene/particle.js +11 -4
  466. package/dist/utility/serialization/scene/particle.js.map +1 -1
  467. package/dist/utility/serialization/scene/physics.js +120 -0
  468. package/dist/utility/serialization/scene/physics.js.map +1 -0
  469. package/dist/utility/serialization/scene/primitive.js +124 -10
  470. package/dist/utility/serialization/scene/primitive.js.map +1 -1
  471. package/dist/utility/serialization/scene/scene.js +1687 -51
  472. package/dist/utility/serialization/scene/scene.js.map +1 -1
  473. package/dist/utility/serialization/scene/sprite.js +11 -4
  474. package/dist/utility/serialization/scene/sprite.js.map +1 -1
  475. package/dist/utility/serialization/scene/terrain.js +163 -33
  476. package/dist/utility/serialization/scene/terrain.js.map +1 -1
  477. package/dist/utility/serialization/scene/text.js +11 -4
  478. package/dist/utility/serialization/scene/text.js.map +1 -1
  479. package/dist/utility/serialization/scene/water.js +1543 -264
  480. package/dist/utility/serialization/scene/water.js.map +1 -1
  481. package/dist/utility/serialization/types.js.map +1 -1
  482. package/dist/utility/shprojector.js +10 -22
  483. package/dist/utility/shprojector.js.map +1 -1
  484. package/dist/utility/textures/foam.js +170 -0
  485. package/dist/utility/textures/foam.js.map +1 -0
  486. package/dist/utility/textures/ggxlut.js +17 -43
  487. package/dist/utility/textures/ggxlut.js.map +1 -1
  488. package/dist/utility/textures/ltcdata.js +4 -4
  489. package/dist/utility/textures/ltcdata.js.map +1 -1
  490. package/dist/utility/textures/ltclut.js +16 -6
  491. package/dist/utility/textures/ltclut.js.map +1 -1
  492. package/dist/utility/unsupported.js +24 -0
  493. package/dist/utility/unsupported.js.map +1 -0
  494. package/dist/values.js +13 -1
  495. package/dist/values.js.map +1 -1
  496. package/package.json +25 -12
  497. package/src/asset/loaders/ktx2/basis/LICENSE +201 -0
  498. package/src/asset/loaders/ktx2/basis/basis_transcoder.js +2 -0
  499. package/src/asset/loaders/ktx2/basis/basis_transcoder.wasm +0 -0
  500. package/src/asset/meshoptimizer/LICENSE +21 -0
  501. package/dist/animation/joint_dynamics/convex_collider.js +0 -320
  502. package/dist/animation/joint_dynamics/convex_collider.js.map +0 -1
@@ -2,25 +2,21 @@ import { Vector4, Vector3, Matrix4x4, REVERSE_Z } from '@zephyr3d/base';
2
2
  import { drawFullscreenQuad } from './fullscreenquad.js';
3
3
  import { fetchSampler } from '../utility/misc.js';
4
4
  import { MAX_CAUSTIC_WATERS, ShaderHelper, CAUSTIC_HEIGHT_BIAS } from '../material/shader/helper.js';
5
+ import { warnUnsupported } from '../utility/unsupported.js';
5
6
 
6
7
  /** Index of refraction of water relative to air. */ const WATER_IOR = 1.333;
7
8
  /** Air-to-water eta used when refracting the sun ray at the surface. */ const WATER_ETA = 1 / WATER_IOR;
8
9
  /**
9
- * Minimum |sin(sun elevation)| for caustics to be generated. The photon sweep
10
- * degenerates below this, and real caustics vanish there too, so the pass
11
- * simply switches off.
10
+ * Minimum |sin(sun elevation)| for caustics to be generated; below it the
11
+ * photon sweep degenerates and the pass switches off.
12
12
  */ const MIN_SUN_ELEVATION = 0.15;
13
13
  /**
14
14
  * Fixed-point steps taken to land a photon on the scene rather than on the
15
- * focal plane. Two suffice for a receiver flat over the distance the guess
16
- * moves; the third covers a steep slope. Beyond that the sequence has either
17
- * converged or is oscillating between two surfaces.
15
+ * focal plane. Two suffice for a flat receiver; the third covers a steep slope.
18
16
  */ const SCENE_DEPTH_ITERATIONS = 3;
19
17
  /**
20
- * Photons per covered map texel the automatic grid size solves for.
21
- *
22
- * Measured against a converged (16x denser) map: four lands within about 4% of
23
- * it, for a quarter of the photons full convergence would need.
18
+ * Photons per covered map texel the automatic grid size solves for. Four lands
19
+ * within about 4% of a converged (16x denser) map.
24
20
  */ const PHOTONS_PER_TEXEL = 4;
25
21
  /** Automatic edge fade width, as a fraction of the map half-extent. */ const AUTO_FADE_FRACTION = 0.2;
26
22
  /** Floor in meters under the automatic fade width. */ const AUTO_FADE_MIN_DISTANCE = 3;
@@ -33,36 +29,25 @@ const MAX_FADE_FRACTION = 0.9;
33
29
  */ const MAX_SLICE_ASPECT = 2;
34
30
  /**
35
31
  * Steps per octave the fitted half-extents are quantised to. Four costs at most
36
- * 19% of linear resolution against a perfect fit, and keeps a level change small
37
- * enough to pass as a slight change in sharpness rather than a pop.
32
+ * 19% of linear resolution and keeps a level change from reading as a pop.
38
33
  */ const SLICE_QUANTISE_STEPS = 4;
39
34
  /** Smallest half-extent in meters a fitted slice may shrink to. */ const MIN_SLICE_EXTENT = 0.5;
40
35
  /**
41
- * Divisions of the half-extent the slice centre snaps to under a warp.
42
- *
43
- * A whole-texel shift is exact only on a linear map; under a warp it is a
44
- * different number of texels at each position, so the map has to be resampled
45
- * whenever the centre moves. Snapping coarsely holds the centre still for many
46
- * frames and pays for one resample, which the resolve's neighbourhood clamp
47
- * absorbs.
36
+ * Divisions of the half-extent the slice centre snaps to under a warp. A warped
37
+ * map must be resampled whenever the centre moves, so it snaps coarsely.
48
38
  */ const WARPED_CENTER_SNAP_DIVISIONS = 8;
49
39
  /**
50
40
  * How far past the map border the photon grid is laid out, as a fraction of the
51
41
  * half-extent, on the sides where the range - not the water - is what bounds it.
52
42
  *
53
- * Horizontal displacement carries a photon across the border in either
54
- * direction, so a grid cut exactly at the border loses the photons that would
55
- * have arrived from outside while still losing the ones that leave; the border
56
- * ring then holds a density no interior texel does. Launching from a margin
57
- * outside feeds the border from both sides.
58
- *
59
- * Only the launch area grows - `_gridFraction` scales the per-photon weight and
60
- * the solved grid size by it - so density per texel is unchanged and the cost is
61
- * photon count. At the default warp this margin buys about 11% more photons.
43
+ * Horizontal displacement carries photons across the border both ways, so
44
+ * launching from a margin outside keeps the border density consistent.
45
+ * `_gridFraction` accounts for the larger launch area, so density per texel is
46
+ * unchanged.
62
47
  */ const GRID_BORDER_MARGIN = 0.15;
63
48
  /**
64
49
  * Concentrates a map coordinate towards the centre. CPU twin of
65
- * `ShaderHelper.warpCausticNDC`; see it for the reasoning.
50
+ * `ShaderHelper.warpCausticNDC`.
66
51
  *
67
52
  * @internal
68
53
  */ function warpCausticNDC(ndc, strength) {
@@ -83,21 +68,17 @@ const MAX_FADE_FRACTION = 0.9;
83
68
  return range / Math.pow(2, steps / SLICE_QUANTISE_STEPS);
84
69
  }
85
70
  /**
86
- * Vertices per side of the grid the height map rasterises the surface with.
87
- *
88
- * Laid out in warped map space over the body's footprint, so each cell covers a
89
- * few map texels at most and the linear height across a cell is well inside what
90
- * a wave of any visible wavelength needs.
71
+ * Vertices per side of the grid the height map rasterises the surface with, laid
72
+ * out in warped map space over the body's footprint.
91
73
  */ const HEIGHT_GRID_SIZE = 256;
92
74
  /**
93
75
  * Shader for the photon splat pass.
94
76
  *
95
- * Everything happens in the vertex stage: one vertex is one photon, and the
96
- * position it emits is where that photon lands. The fragment stage only deposits
77
+ * One vertex is one photon, emitted where it lands; the fragment stage deposits
97
78
  * the weight, which additive blending accumulates.
98
79
  *
99
- * Returned as a descriptor rather than a built program so it can be compiled
100
- * against a bare {@link ProgramBuilder} in tests, without a device.
80
+ * Returned as a descriptor so it can be compiled against a bare
81
+ * {@link ProgramBuilder} in tests, without a device.
101
82
  *
102
83
  * @param waveGenerator - Supplies the surface displacement and normal.
103
84
  * @internal
@@ -113,50 +94,37 @@ const MAX_FADE_FRACTION = 0.9;
113
94
  scope.causticSplatParams = pb.vec4().uniform(0);
114
95
  /** (grid size, 1 / grid size, 0, 0) */ scope.causticPhotonParams = pb.vec4().uniform(0);
115
96
  if (sceneDepth) {
116
- // The sun's own shadow cascade, reused as a light-space depth map of the
117
- // scene: already orthographic, along the light, covering what the light
118
- // reaches, and costing no extra geometry pass.
97
+ // The sun's shadow cascade, reused as an orthographic light-space depth map.
119
98
  scope.causticSceneMatrix = pb.mat4().uniform(0);
120
99
  scope.causticSceneMatrixInv = pb.mat4().uniform(0);
121
100
  /** (cascade layer, 0, 0, 0) */ scope.causticSceneParams = pb.vec4().uniform(0);
122
- // Sampled for its stored depth, not compared against a reference, so this
123
- // takes an ordinary sampler rather than the comparison one the lighting
124
- // pass binds to the same texture.
101
+ // Read for its stored depth, not compared.
125
102
  scope.causticSceneDepth = pb.tex2DArrayShadow().uniform(0);
126
103
  }
127
- // FBM and Gerstner animate from camera.elapsedTime, which this pass has to
128
- // supply itself - it is not one of the engine's camera passes.
104
+ // Wave generators animate from camera.elapsedTime, which this standalone
105
+ // pass supplies itself.
129
106
  ShaderHelper.declareStandaloneCameraTime(scope, 0);
130
107
  waveGenerator.setupUniforms(scope, 0);
131
108
  };
132
109
  return {
133
110
  vertex (pb) {
134
- // Photon ordinal, one vertex per photon. The grid coordinate is derived
135
- // from it here rather than stored, so the buffer outlives any one grid
136
- // size - see _updatePhotonLayout.
111
+ // Photon ordinal; the grid coordinate is derived from it, so the buffer
112
+ // serves any grid size.
137
113
  this.$inputs.photonIndex = pb.float().attrib('position');
138
114
  this.$outputs.photonWeight = pb.float();
139
115
  setupUniforms(this);
140
116
  pb.main(function() {
141
- // Per-axis: the slice is fitted to the water, so its two half-extents
142
- // are independent and the map is only square in texels.
117
+ // Per-axis: the slice's two half-extents are independent.
143
118
  this.$l.invRadius = pb.vec2(this.causticFrameX.w, this.causticFrameY.w);
144
119
  this.$l.radius = pb.div(pb.vec2(1), this.invRadius);
145
120
  this.$l.focalDepth = this.causticSplatParams.y;
146
121
  this.$l.eta = this.causticSplatParams.z;
147
122
  this.$l.L = this.causticLightDir.xyz;
148
123
  this.$l.waterLevel = this.causticCenter.w;
149
- // Grid position on the orthographic slice through the map centre.
150
- //
151
- // The grid spans only the part of the slice the water can actually cast
152
- // through, not the whole map: spread over the whole map, a pool covering
153
- // a tenth of it would leave most of the lit area with no photon at all,
154
- // and a caustic value of zero puts the sun out.
155
- //
156
- // Laid out in warped map space and unwarped to reach the plane, so the
157
- // grid is uniform in texels rather than in meters. That is what keeps
158
- // photon (i,j) on texel (i,j) for calm water under any warp strength,
159
- // and with it the map's normalisation to 1.0.
124
+ // Grid position on the orthographic slice through the map centre. The
125
+ // grid spans only the part of the slice the water casts through, and is
126
+ // uniform in warped map space so calm water keeps photon (i,j) on texel
127
+ // (i,j) and the map normalised to 1.0.
160
128
  //
161
129
  // Ordinal to grid coordinate: row is the quotient, column the remainder.
162
130
  this.$l.gridSize = this.causticPhotonParams.x;
@@ -171,17 +139,11 @@ const MAX_FADE_FRACTION = 0.9;
171
139
  this.$l.sweep = pb.div(pb.sub(this.waterLevel, this.planePos.y), this.L.y);
172
140
  this.$l.surfaceXZ = pb.add(this.planePos, pb.mul(this.L, this.sweep));
173
141
  this.$l.outside = pb.or(pb.any(pb.lessThan(this.surfaceXZ.xz, this.causticRegion.xy)), pb.any(pb.greaterThan(this.surfaceXZ.xz, this.causticRegion.zw)));
174
- // Displaced surface point and the detail normal that bends the ray. Both
175
- // wave generators sample with an explicit LOD, which is what makes this
176
- // legal in a vertex shader.
142
+ // Displaced surface point and the detail normal that bends the ray. The
143
+ // wave generators sample with an explicit LOD, legal in a vertex shader.
177
144
  //
178
- // The photon is launched from the rest point and lands wherever the
179
- // surface carried it, so the refraction is right. What it is not is
180
- // uniform in world xz - the horizontal displacement bunches the launch
181
- // points by the surface Jacobian, over-weighting compressed crests by
182
- // 1/J. Inverting the displacement per photon would fix that, but the
183
- // inversion only converges where the surface does not fold, and a choppy
184
- // FFT folds exactly at the crests it would matter for.
145
+ // Launching from the rest point over-weights compressed crests by 1/J;
146
+ // inverting the displacement does not converge where the surface folds.
185
147
  this.$l.surfacePos = pb.vec3();
186
148
  this.$l.coarseNormal = pb.vec3();
187
149
  waveGenerator.calcVertexPositionAndNormal(this, this.surfaceXZ, this.surfacePos, this.coarseNormal);
@@ -192,54 +154,35 @@ const MAX_FADE_FRACTION = 0.9;
192
154
  this.$l.refracted = pb.refract(this.L, this.normal, this.eta);
193
155
  this.$l.refractedFlat = pb.refract(this.L, pb.vec3(0, 1, 0), this.eta);
194
156
  this.$l.dir = pb.normalize(pb.add(pb.sub(this.refracted, this.refractedFlat), this.L));
195
- // Intersect the horizontal focal plane below the surface. The ray always
196
- // travels downwards here, but clamp anyway so a grazing ray cannot
197
- // produce an enormous or negative step.
157
+ // Intersect the horizontal focal plane below the surface; clamped so a
158
+ // grazing ray cannot produce an enormous or negative step.
198
159
  this.$l.invDirY = pb.min(this.dir.y, -1e-4);
199
160
  this.$l.targetY = pb.sub(this.waterLevel, this.focalDepth);
200
161
  this.$l.hitT = pb.div(pb.sub(this.targetY, this.surfacePos.y), this.invDirY);
201
162
  this.$l.hitPos = pb.add(this.surfacePos, pb.mul(this.dir, pb.max(this.hitT, 0)));
202
163
  if (sceneDepth) {
203
- // Land the photon on the scene rather than on a plane through it.
204
- //
205
- // Fixed-point iteration, not a ray march: read the height of the scene
206
- // under the current guess, re-intersect the ray with a horizontal
207
- // plane at that height, repeat. Each step is one texture fetch and the
208
- // sequence converges in two or three for any receiver that is smooth
209
- // over the distance the guess moves. A march would need an order of
210
- // magnitude more fetches, which the photon count cannot afford - at
211
- // the default density a full-coverage 1024 map launches four million.
212
- //
213
- // Where the assumption fails - a silhouette, an overhang - the
214
- // iteration lands on one of the two surfaces rather than diverging,
215
- // because every step is still a valid intersection of the ray with
216
- // some horizontal plane.
164
+ // Land the photon on the scene by fixed-point iteration: read the scene
165
+ // height under the current guess, re-intersect the ray with a
166
+ // horizontal plane at that height, repeat. One fetch per step; at a
167
+ // silhouette it lands on one of the two surfaces rather than diverging.
217
168
  this.$l.probe = this.hitPos;
218
169
  this.$l.layer = pb.int(this.causticSceneParams.x);
219
170
  for(let i = 0; i < SCENE_DEPTH_ITERATIONS; i++){
220
171
  this.$l[`clip${i}`] = pb.mul(this.causticSceneMatrix, pb.vec4(this.probe, 1));
221
172
  this.$l[`ndc${i}`] = pb.div(this[`clip${i}`].xyz, this[`clip${i}`].w);
222
173
  this.$l[`uv${i}`] = pb.add(pb.mul(this[`ndc${i}`].xy, 0.5), pb.vec2(0.5));
223
- // Outside the cascade there is nothing to intersect against; keep
224
- // whatever the previous step produced, which for the first step is
225
- // the focal plane hit the flat path would have used.
174
+ // Outside the cascade, keep the previous step's result.
226
175
  this.$l[`inside${i}`] = pb.and(pb.all(pb.greaterThanEqual(this[`uv${i}`], pb.vec2(0))), pb.all(pb.lessThanEqual(this[`uv${i}`], pb.vec2(1))));
227
- // texelFetch rather than a filtered sample: the value is a depth,
228
- // and averaging two depths across a silhouette names a surface that
229
- // is not there. WebGPU only - GLSL ES 3.0 forbids texelFetch on a
230
- // shadow-sampler texture, which is what gates this whole path.
176
+ // texelFetch, since filtered depths across a silhouette are wrong.
177
+ // WebGPU only - GLSL ES 3.0 forbids texelFetch on a shadow-sampler
178
+ // texture, which gates this whole path.
231
179
  this.$l[`depth${i}`] = pb.textureArrayLoad(this.causticSceneDepth, pb.ivec2(pb.mul(this[`uv${i}`], this.causticSceneParams.y)), this.layer, pb.int(0));
232
- // Back to a world position. The cascade is orthographic, so the
233
- // stored depth and the sample's uv are enough to place the point.
180
+ // Back to a world position through the orthographic cascade.
234
181
  this.$l[`sceneNDC${i}`] = pb.vec3(this[`ndc${i}`].xy, REVERSE_Z ? this[`depth${i}`] : pb.sub(pb.mul(this[`depth${i}`], 2), 1));
235
182
  this.$l[`sceneClip${i}`] = pb.mul(this.causticSceneMatrixInv, pb.vec4(this[`sceneNDC${i}`], 1));
236
183
  this.$l[`scenePos${i}`] = pb.div(this[`sceneClip${i}`].xyz, this[`sceneClip${i}`].w);
237
- // Re-intersect at the height just read. Only accept it while it is
238
- // below the *displaced* surface at this photon's entry: a scene
239
- // point above that is not something the photon passed through. The
240
- // rest level is the wrong bar - it would drop every photon landing
241
- // on a receiver that pokes above the rest plane under a passing
242
- // crest, cutting the caustics off along a flat line.
184
+ // Re-intersect at the height just read, accepted only below the
185
+ // *displaced* surface at this photon's entry (not the rest level).
243
186
  this.$l[`t${i}`] = pb.div(pb.sub(this[`scenePos${i}`].y, this.surfacePos.y), this.invDirY);
244
187
  this.$l[`next${i}`] = pb.add(this.surfacePos, pb.mul(this.dir, pb.max(this[`t${i}`], 0)));
245
188
  // mix rather than select: the builder has no vec3 select.
@@ -254,19 +197,15 @@ const MAX_FADE_FRACTION = 0.9;
254
197
  // Into warped space, where the map's texels live.
255
198
  this.$l.hitWarped = ShaderHelper.warpCausticNDC(this, this.hitNDC, this.causticSplatParams.x);
256
199
  this.$outputs.photonWeight = this.causticSplatParams.w;
257
- // No per-backend y flip. A fragment written at clip y lands on the same
258
- // texture row on both backends, so the map the receiver samples with
259
- // `ndc * 0.5 + 0.5` is the one this writes at clip `ndc`.
200
+ // No per-backend y flip here; the blur and resolve quads account for it.
260
201
  this.$builtins.position = pb.vec4(this.hitWarped, 0, 1);
261
202
  if (pb.getDevice().type !== 'webgpu') {
262
- // GLSL leaves gl_PointSize undefined unless it is written, and an
263
- // undefined size rasterises nothing at all. WebGPU points are always
264
- // one pixel.
203
+ // GLSL rasterises nothing unless gl_PointSize is written; WebGPU points
204
+ // are always one pixel.
265
205
  this.$builtins.pointSize = 1;
266
206
  }
267
207
  this.$if(this.outside, function() {
268
- // Park the photon outside the clip volume so it is discarded. Keep
269
- // w at 1: a zero w would divide to NaN instead of clipping.
208
+ // Park the photon outside the clip volume; w stays 1 to avoid NaN.
270
209
  this.$builtins.position = pb.vec4(-2, -2, 0, 1);
271
210
  });
272
211
  });
@@ -282,30 +221,17 @@ const MAX_FADE_FRACTION = 0.9;
282
221
  /**
283
222
  * Shader for the displaced surface height map.
284
223
  *
285
- * Rasterises the water surface itself into the light-space slice, one body per
286
- * draw. A grid is laid out over the body's footprint in warped map space; each
287
- * vertex sweeps its grid point along the light onto the rest plane, lets the
288
- * wave generator carry it to where that bit of surface is, and projects the
289
- * displaced point back into the map as its clip position. The fragment writes
290
- * its height above the rest plane, offset by `CAUSTIC_HEIGHT_BIAS`, into this
291
- * body's channel - selected with a colour write mask, so where the surface
292
- * overlaps itself in the light's view the last triangle wins rather than the two
293
- * heights adding. The target is cleared first, and a texel the surface never
294
- * reaches stays at zero, which the receiver reads as "no water on this ray".
224
+ * Rasterises the water surface into the light-space slice, one body per draw. A
225
+ * grid over the body's footprint in warped map space is swept along the light
226
+ * onto the rest plane, displaced by the wave generator, and projected back into
227
+ * the map. The fragment writes its height above the rest plane, offset by
228
+ * `CAUSTIC_HEIGHT_BIAS`, into this body's channel via a colour write mask (last
229
+ * triangle wins). A texel the surface never reaches stays zero, which the
230
+ * receiver reads as "no water on this ray".
295
231
  *
296
- * Rasterising is what makes the horizontal displacement a non-issue: sampling
297
- * the generator at the map position would return the surface that *started*
298
- * there, and inverting that displacement only converges where the surface does
299
- * not fold. It also makes the footprint edge right - a receiver whose sun ray
300
- * crosses the rest plane outside the footprint finds an empty texel and stays
301
- * dry, where a flat stored height would put it under water that does not exist.
302
- *
303
- * The receiver looks this up with the very uv it uses for the caustic pattern:
304
- * its fragment and the ray's crossing of the rest plane lie on one light ray,
305
- * and the map's projection is invariant along the light.
306
- *
307
- * Everything runs in the vertex stage; the generators sample with an explicit
308
- * LOD, which is what makes that legal.
232
+ * Rasterising handles horizontal displacement without inverting it, and keeps
233
+ * the footprint edge exact. The receiver looks this up with the same uv as the
234
+ * caustic pattern, since the projection is invariant along the light.
309
235
  *
310
236
  * @param waveGenerator - Supplies the surface displacement of this body.
311
237
  * @internal
@@ -330,10 +256,8 @@ const MAX_FADE_FRACTION = 0.9;
330
256
  this.$l.radius = pb.div(pb.vec2(1), this.invRadius);
331
257
  this.$l.L = this.causticLightDir.xyz;
332
258
  this.$l.waterLevel = this.causticCenter.w;
333
- // Ordinal to grid coordinate, (gridSize + 1) vertices per side. Half a
334
- // vertex up before the floor: the reciprocal is inexact, and an ordinal
335
- // that is an exact multiple of the row length must not round down into
336
- // the previous row.
259
+ // Ordinal to grid coordinate, (gridSize + 1) vertices per side. The +0.5
260
+ // keeps the inexact reciprocal from rounding into the previous row.
337
261
  this.$l.row = pb.floor(pb.mul(pb.add(this.$inputs.vertexIndex, 0.5), this.causticHeightParams.z));
338
262
  this.$l.col = pb.sub(this.$inputs.vertexIndex, pb.mul(this.row, pb.add(this.causticHeightParams.y, 1)));
339
263
  this.$l.gridUV = pb.mul(pb.vec2(this.col, this.row), this.causticHeightParams.w);
@@ -344,33 +268,27 @@ const MAX_FADE_FRACTION = 0.9;
344
268
  // Along the light onto the rest plane: the rest point of this vertex.
345
269
  this.$l.sweep = pb.div(pb.sub(this.waterLevel, this.planePos.y), this.L.y);
346
270
  this.$l.surfaceXZ = pb.add(this.planePos, pb.mul(this.L, this.sweep));
347
- // Outside the footprint there is no surface. Resolved per vertex and
348
- // interpolated, which fades the edge over one grid cell.
271
+ // Footprint test, interpolated so the edge fades over one grid cell.
349
272
  this.$outputs.inside = pb.float(pb.and(pb.all(pb.greaterThanEqual(this.surfaceXZ.xz, this.causticRegion.xy)), pb.all(pb.lessThanEqual(this.surfaceXZ.xz, this.causticRegion.zw))));
350
273
  // Where the surface carried this rest point, and how high it is there.
351
274
  this.$l.surfacePos = pb.vec3();
352
275
  this.$l.surfaceNormal = pb.vec3();
353
276
  waveGenerator.calcVertexPositionAndNormal(this, this.surfaceXZ, this.surfacePos, this.surfaceNormal);
354
277
  this.$outputs.height = pb.sub(this.surfacePos.y, this.waterLevel);
355
- // The displaced point back into the map, exactly as the splat projects a
356
- // photon hit: two dot products, then the warp.
278
+ // The displaced point back into the map, as the splat projects a hit.
357
279
  this.$l.rel = pb.sub(this.surfacePos, this.causticCenter.xyz);
358
280
  this.$l.hitNDC = pb.mul(pb.vec2(pb.dot(this.rel, this.causticFrameX.xyz), pb.dot(this.rel, this.causticFrameY.xyz)), this.invRadius);
359
281
  this.$l.hitWarped = ShaderHelper.warpCausticNDC(this, this.hitNDC, this.causticHeightParams.x);
360
- // The receiver reads the map at `v = 0.5 + 0.5 * ndc.y`. On WebGL a
361
- // fragment at clip y lands on that row; on WebGPU clip +1 is the first
362
- // row, so the surface is drawn upside down to land where the receiver
363
- // looks. The splat needs no such flip because its map reaches the
364
- // receiver through the blur and resolve quads, each of which flips it
365
- // once more on WebGPU; this map goes straight.
282
+ // The receiver reads the map at `v = 0.5 + 0.5 * ndc.y`. On WebGPU clip
283
+ // +1 is the first row, so the surface is drawn flipped. The splat needs
284
+ // no flip because its map passes through the blur and resolve quads.
366
285
  this.$builtins.position = pb.getDevice().type === 'webgpu' ? pb.vec4(this.hitWarped.x, pb.neg(this.hitWarped.y), 0, 1) : pb.vec4(this.hitWarped, 0, 1);
367
286
  });
368
287
  },
369
288
  fragment (pb) {
370
289
  this.$outputs.outColor = pb.vec4();
371
290
  pb.main(function() {
372
- // Outside the footprint nothing is written at all, so the texel keeps
373
- // reading as "no surface" rather than as a surface at height zero.
291
+ // Outside the footprint nothing is written, so the texel reads "no surface".
374
292
  this.$if(pb.lessThan(this.$inputs.inside, 0.5), function() {
375
293
  pb.discard();
376
294
  });
@@ -383,9 +301,8 @@ const MAX_FADE_FRACTION = 0.9;
383
301
  /**
384
302
  * Shader for the caustic map blur.
385
303
  *
386
- * Four bilinear taps at the texel corners average a 2x2 neighbourhood. The
387
- * weights sum to one, so the "calm water reads 1.0" normalisation established by
388
- * the accumulation pass survives any number of iterations.
304
+ * Four bilinear taps at the texel corners average a 2x2 neighbourhood; weights
305
+ * sum to one, preserving the "calm water reads 1.0" normalisation.
389
306
  *
390
307
  * @internal
391
308
  */ function createCausticBlurShader() {
@@ -413,15 +330,12 @@ const MAX_FADE_FRACTION = 0.9;
413
330
  /**
414
331
  * Shader for the temporal resolve.
415
332
  *
416
- * Reprojection is exact and matrix-free. Both frames parameterise the map as an
417
- * orthographic slice perpendicular to the light, so a texel maps to a world
418
- * point with two scaled axes, and that point maps into the previous frame with
419
- * two dot products. Photons travel along the light and the slice is normal to
420
- * it, so the plane the point is taken on does not matter.
333
+ * Reprojection is exact and matrix-free: both frames are orthographic slices
334
+ * perpendicular to the light, so a texel maps to the world with two scaled axes
335
+ * and into the previous frame with two dot products.
421
336
  *
422
337
  * Map NDC relates to texture coordinate by the receiver's `ndc * 0.5 + 0.5`
423
- * (see `ShaderHelper.calculateWaterCaustic`), which is what makes this
424
- * independent of the clip-space y flip the splat has to apply.
338
+ * (see `ShaderHelper.calculateWaterCaustic`).
425
339
  *
426
340
  * @internal
427
341
  */ function createCausticResolveShader() {
@@ -448,10 +362,8 @@ const MAX_FADE_FRACTION = 0.9;
448
362
  pb.main(function() {
449
363
  this.$l.texel = this.causticResolveParams.y;
450
364
  this.$l.current = pb.textureSampleLevel(this.causticCurrent, this.$inputs.uv, 0).x;
451
- // Range the current frame supports around this texel. The reprojected
452
- // value is clamped into it, which is what lets a long blend coexist with
453
- // an animated pattern: where the waves have moved on, the neighbourhood
454
- // no longer covers the old value.
365
+ // Neighbourhood range of the current frame; history is clamped into it
366
+ // so a long blend can follow an animated pattern.
455
367
  this.$l.mn = this.current;
456
368
  this.$l.mx = this.current;
457
369
  for(let dy = -1; dy <= 1; dy++){
@@ -464,10 +376,8 @@ const MAX_FADE_FRACTION = 0.9;
464
376
  this.mx = pb.max(this.mx, this.s);
465
377
  }
466
378
  }
467
- // This texel's world point on the slice, then where the previous frame
468
- // put that same point. Texels are laid out in warped space, so this
469
- // leaves it to reach the world and re-enters it to land on the history:
470
- // the two frames may differ in slice and in warp strength alike.
379
+ // This texel's world point, then its place in the previous frame; the
380
+ // frames may differ in slice and warp strength.
471
381
  this.$l.warpedNDC = pb.sub(pb.mul(this.$inputs.uv, 2), pb.vec2(1));
472
382
  this.$l.ndc = ShaderHelper.unwarpCausticNDC(this, this.warpedNDC, this.causticResolveParams.z);
473
383
  this.$l.radius = pb.div(pb.vec2(1), pb.vec2(this.causticFrameX.w, this.causticFrameY.w));
@@ -491,11 +401,9 @@ const MAX_FADE_FRACTION = 0.9;
491
401
  * One photon is emitted per texel of a uniform grid laid out on the same
492
402
  * orthographic light-space slice the map covers. Each photon is refracted at the
493
403
  * water surface, intersected with the scene (or a horizontal focal plane) below
494
- * it, and splatted additively at the hit position. Because the grid and the map
495
- * share a parameterisation, and the mean deflection of a flat surface is removed
496
- * from the refracted direction, calm water maps photon `(i,j)` onto texel
497
- * `(i,j)` and the map converges to a uniform 1.0 - which the receiver treats as
498
- * "no caustics".
404
+ * it, and splatted additively at the hit position. With the flat-surface
405
+ * deflection removed, calm water maps photon `(i,j)` onto texel `(i,j)` and the
406
+ * map converges to a uniform 1.0 - which the receiver treats as "no caustics".
499
407
  *
500
408
  * @internal
501
409
  */ class WaterCausticsRenderer {
@@ -504,22 +412,13 @@ const MAX_FADE_FRACTION = 0.9;
504
412
  /** One per water slot, differing only in which channel they let through. */ _heightStates;
505
413
  _heightParams;
506
414
  /** The surface grid the height map is rasterised with; built once. */ _heightLayout;
507
- _heightVertexBuffer;
508
- _heightIndexBuffer;
509
415
  _heightIndexCount;
510
416
  _blurProgram;
511
417
  _blurBindGroup;
512
418
  _resolveProgram;
513
419
  _resolveBindGroup;
514
420
  _resolveStates;
515
- /**
516
- * Slice parameters the last map committed as history was built with, per
517
- * camera.
518
- *
519
- * Per camera because the history textures are, and this has to describe the
520
- * exact map the resolve is about to reproject; one shared entry would make a
521
- * second viewport reproject through the first one's slice.
522
- */ _prevSlices;
421
+ /** Slice parameters of the last map committed as history, per camera. */ _prevSlices;
523
422
  _resolveParams;
524
423
  _photonLayout;
525
424
  /** Photons the buffer can serve; it grows and is never rebuilt smaller. */ _photonCapacity;
@@ -556,8 +455,6 @@ const MAX_FADE_FRACTION = 0.9;
556
455
  this._heightStates = [];
557
456
  this._heightParams = new Vector4();
558
457
  this._heightLayout = null;
559
- this._heightVertexBuffer = null;
560
- this._heightIndexBuffer = null;
561
458
  this._heightIndexCount = 0;
562
459
  this._blurProgram = null;
563
460
  this._blurBindGroup = null;
@@ -618,9 +515,8 @@ const MAX_FADE_FRACTION = 0.9;
618
515
  return info.renderable && info.filterable ? 'r16f' : 'rgba16f';
619
516
  }
620
517
  /**
621
- * Storage format for the displaced surface height map. One channel per water
622
- * slot, so always four wide; half floats carry a wave height to well under a
623
- * millimetre at any plausible level.
518
+ * Storage format for the displaced surface height map: one channel per water
519
+ * slot.
624
520
  */ static getHeightMapFormat() {
625
521
  return 'rgba16f';
626
522
  }
@@ -659,8 +555,7 @@ const MAX_FADE_FRACTION = 0.9;
659
555
  const device = ctx.device;
660
556
  this._updateUniforms(ctx, waters, light, map.width);
661
557
  const sceneDepth = this._anySceneDepth(waters) ? this._resolveSceneDepth(ctx, light) : null;
662
- // The height map first: it depends on the slice alone, and the receiver
663
- // needs it whatever the splat produces.
558
+ // The height map first; it depends on the slice alone.
664
559
  device.pushDeviceStates();
665
560
  device.setFramebuffer(createFramebuffer(heights));
666
561
  device.setViewport(null);
@@ -673,15 +568,12 @@ const MAX_FADE_FRACTION = 0.9;
673
568
  device.popDeviceStates();
674
569
  device.pushDeviceStates();
675
570
  device.setFramebuffer(createFramebuffer(map));
676
- // The viewport and scissor survive a framebuffer change, and this pass runs
677
- // straight after the shadow maps, whose targets are a different size.
571
+ // Viewport and scissor survive a framebuffer change; reset them.
678
572
  device.setViewport(null);
679
573
  device.setScissor(null);
680
574
  device.clearFrameBuffer(Vector4.zero(), null, null);
681
575
  device.setRenderStates(this._getSplatStates(device));
682
- // One draw per water body, accumulating into the one map. They share the
683
- // slice, so a receiver still resolves the pattern with a single lookup; the
684
- // footprint, surface height and wave generator are per-draw state.
576
+ // One draw per water body, accumulating into the one shared-slice map.
685
577
  for(let i = 0; i < waters.length; i++){
686
578
  this._splatOne(device, ctx, waters[i], i, map, sceneDepth);
687
579
  }
@@ -690,14 +582,12 @@ const MAX_FADE_FRACTION = 0.9;
690
582
  }
691
583
  /**
692
584
  * Writes one body's displaced surface height into its channel of the height
693
- * map. Per body for the same reason the splat is: each has its own wave
694
- * generator, rest level and footprint.
585
+ * map.
695
586
  * @internal
696
587
  */ _renderHeightsOne(device, ctx, water, index) {
697
588
  const material = water.material;
698
589
  const waveGenerator = material.waveGenerator;
699
- // The grid covers this body's footprint, like the photon grid does; a body
700
- // the map does not reach has nothing to draw.
590
+ // The grid covers this body's footprint.
701
591
  this._updateGridBounds(water);
702
592
  if (!(this._gridFraction > 0)) {
703
593
  return;
@@ -754,8 +644,6 @@ const MAX_FADE_FRACTION = 0.9;
754
644
  }
755
645
  const vertexBuffer = device.createVertexBuffer('position_f32', ordinals);
756
646
  const indexBuffer = device.createIndexBuffer(indices);
757
- this._heightVertexBuffer = vertexBuffer;
758
- this._heightIndexBuffer = indexBuffer;
759
647
  this._heightLayout = device.createVertexLayout({
760
648
  vertexBuffers: [
761
649
  {
@@ -769,16 +657,13 @@ const MAX_FADE_FRACTION = 0.9;
769
657
  /** @internal */ _splatOne(device, ctx, water, index, map, sceneDepth) {
770
658
  const material = water.material;
771
659
  const waveGenerator = material.waveGenerator;
772
- // Photons are laid out over this body's own footprint, so the grid bounds
773
- // and the fraction of the map they cover are per body, not per map.
660
+ // Photons are laid out over this body's own footprint.
774
661
  this._updateGridBounds(water);
775
662
  if (!(this._gridFraction > 0)) {
776
663
  return;
777
664
  }
778
665
  const photonGrid = this._resolvePhotonGrid(material.causticsPhotonResolution, map.width);
779
666
  this._updatePhotonLayout(device, photonGrid);
780
- // Scene-depth intersection is per material, but the program is per draw, so
781
- // a body that has it off simply compiles the flat variant.
782
667
  const useSceneDepth = !!sceneDepth && material.causticsSceneDepth;
783
668
  const splat = this._getSplatProgram(device, waveGenerator, useSceneDepth);
784
669
  // A bind group per slot; see _getSplatBindGroup.
@@ -788,8 +673,6 @@ const MAX_FADE_FRACTION = 0.9;
788
673
  bindGroup.setValue('causticSceneMatrixInv', sceneDepth.matrixInv);
789
674
  this._sceneDepthParams.setXYZW(sceneDepth.layer, sceneDepth.size, 0, 0);
790
675
  bindGroup.setValue('causticSceneParams', this._sceneDepthParams);
791
- // Point sampling: the stored value is a depth, and averaging two depths
792
- // across a silhouette names a surface that is not there.
793
676
  bindGroup.setTexture('causticSceneDepth', sceneDepth.texture, fetchSampler('clamp_nearest_nomip'));
794
677
  }
795
678
  bindGroup.setValue('causticFrameX', this._uniforms.frameX);
@@ -800,10 +683,8 @@ const MAX_FADE_FRACTION = 0.9;
800
683
  bindGroup.setValue('causticLightDir', this._uniforms.lightDir);
801
684
  bindGroup.setValue('causticRegion', material.region);
802
685
  bindGroup.setValue('causticGridBounds', this._gridBounds);
803
- // A photon grid denser than the map deposits more than one photon per texel;
804
- // scale the deposit so a flat surface still integrates to exactly 1. The
805
- // grid fraction is part of that: concentrating the same photons onto a
806
- // smaller part of the map raises the density there by exactly its inverse.
686
+ // Scale the deposit by covered texels per photon so a flat surface
687
+ // integrates to exactly 1.
807
688
  const photonWeight = this._gridFraction * map.width * map.height / (photonGrid * photonGrid);
808
689
  this._splatParams.setXYZW(this._warp, material.causticsDepth, WATER_ETA, photonWeight);
809
690
  bindGroup.setValue('causticSplatParams', this._splatParams);
@@ -817,12 +698,8 @@ const MAX_FADE_FRACTION = 0.9;
817
698
  device.draw('point-list', 0, this._photonCount);
818
699
  }
819
700
  /**
820
- * Blur and temporal resolve, which run once over the finished map rather than
821
- * per water body.
822
- *
823
- * The settings come from one body's material because the map is one texture.
824
- * The largest body supplies them, which is the one the viewer is most likely
825
- * under.
701
+ * Blur and temporal resolve, run once over the finished map. Settings come
702
+ * from the largest body's material.
826
703
  * @internal
827
704
  */ _postProcess(device, material, map, scratch, history, ctx, createFramebuffer) {
828
705
  // Ping-pong between the two targets. The count is rounded up to an even
@@ -833,19 +710,15 @@ const MAX_FADE_FRACTION = 0.9;
833
710
  const dst = i % 2 === 0 ? scratch : map;
834
711
  this._blur(device, src, createFramebuffer(dst));
835
712
  }
836
- // The blur always lands back in `map`, so `scratch` is free to take the
837
- // resolve. Doing it the other way round would make the resolve read and
838
- // write one texture.
713
+ // The blur lands back in `map`, so `scratch` takes the resolve.
839
714
  const strength = material.causticsTemporalStrength;
840
715
  if (strength <= 0) {
841
- // Nothing is committed as history this frame, so the recorded slice has to
842
- // keep describing whichever map the history slot still holds.
716
+ // No history committed; keep the recorded slice matching the history slot.
843
717
  return map;
844
718
  }
845
719
  const camera = ctx.camera;
846
720
  const previous = this._prevSlices.get(camera);
847
- // No history on the first frame, after a resize, or after the pass was off
848
- // for a while. The current map stands on its own and becomes the history.
721
+ // Without history the current map stands on its own and becomes the history.
849
722
  const resolved = history && previous ? scratch : map;
850
723
  if (resolved === scratch) {
851
724
  this._resolve(device, map, history, previous, strength, createFramebuffer(scratch));
@@ -858,23 +731,19 @@ const MAX_FADE_FRACTION = 0.9;
858
731
  * this light cannot supply one.
859
732
  * @internal
860
733
  */ _resolveSceneDepth(ctx, light) {
861
- // WebGPU only. Reading a raw depth out of the shadow map needs a texel
862
- // fetch, and GLSL ES 3.0 does not allow one on a shadow-sampler texture.
863
- // WebGL2 keeps the focal plane.
734
+ // WebGPU only: GLSL ES 3.0 forbids texelFetch on a shadow-sampler texture.
864
735
  if (ctx.device.type !== 'webgpu') {
736
+ warnUnsupported('Water caustics on scene depth (WaterMaterial.causticsSceneDepth)', 'WebGPU', 'caustics are projected onto the flat plane at causticsDepth');
865
737
  return null;
866
738
  }
867
739
  const params = ctx.shadowMapInfo?.get(light);
868
740
  const shadowMap = params?.shadowMap;
869
- // Only the cascaded native-depth path. A packed RGBA map stores an encoded
870
- // distance rather than a sampled depth, and a non-array map has no cascade
871
- // to select; both fall back to the plane.
741
+ // Only cascaded native-depth maps; others fall back to the plane.
872
742
  if (!params || !shadowMap?.isTexture2DArray() || !shadowMap.isDepth()) {
873
743
  return null;
874
744
  }
875
- // The cascade that covers the slice most tightly: the smallest one whose
876
- // split distance clears the slice's reach. Falling off the end means the
877
- // slice outruns the shadow map and the last cascade is all there is.
745
+ // The smallest cascade whose split distance clears the slice's reach, else
746
+ // the last.
878
747
  const count = Math.max(1, Math.min(params.numShadowCascades, 4));
879
748
  const reach = Math.max(1, this._sliceReach);
880
749
  let layer = count - 1;
@@ -884,8 +753,7 @@ const MAX_FADE_FRACTION = 0.9;
884
753
  break;
885
754
  }
886
755
  }
887
- // shadowMatrices holds each cascade's view-projection transposed, four vec4
888
- // rows per cascade, which is the layout the lighting pass dots against.
756
+ // shadowMatrices holds each cascade's view-projection transposed.
889
757
  this._sceneMatrixT.set(params.shadowMatrices.subarray(layer * 16, layer * 16 + 16));
890
758
  Matrix4x4.transpose(this._sceneMatrixT, this._sceneMatrix);
891
759
  Matrix4x4.invert(this._sceneMatrix, this._sceneMatrixInv);
@@ -902,8 +770,7 @@ const MAX_FADE_FRACTION = 0.9;
902
770
  *
903
771
  * Auto solves the grid for a fixed density in photons per covered map texel:
904
772
  * the grid covers `gridFraction` of the map, so the target is reached at
905
- * `grid = size * sqrt(target * gridFraction)`. Fixing the grid instead leaves
906
- * the density swinging with the water's footprint.
773
+ * `grid = size * sqrt(target * gridFraction)`.
907
774
  *
908
775
  * @param requested - Material setting; 0 asks for the automatic size.
909
776
  * @param mapSize - Edge length of the caustic map.
@@ -989,13 +856,9 @@ const MAX_FADE_FRACTION = 0.9;
989
856
  }
990
857
  this._heightPrograms.clear();
991
858
  this._heightStates.length = 0;
992
- // The layout does not own its buffers; see _updatePhotonLayout.
859
+ // The layout does not own its buffers.
993
860
  this._heightLayout?.dispose();
994
861
  this._heightLayout = null;
995
- this._heightVertexBuffer?.dispose();
996
- this._heightVertexBuffer = null;
997
- this._heightIndexBuffer?.dispose();
998
- this._heightIndexBuffer = null;
999
862
  this._heightIndexCount = 0;
1000
863
  this._blurBindGroup?.dispose();
1001
864
  this._blurBindGroup = null;
@@ -1008,8 +871,6 @@ const MAX_FADE_FRACTION = 0.9;
1008
871
  this._blurProgram = null;
1009
872
  this._photonLayout?.dispose();
1010
873
  this._photonLayout = null;
1011
- // Disposing the layout releases its vertex array object, not the buffer it
1012
- // was built over.
1013
874
  this._photonBuffer?.dispose();
1014
875
  this._photonBuffer = null;
1015
876
  this._photonCapacity = 0;
@@ -1033,15 +894,12 @@ const MAX_FADE_FRACTION = 0.9;
1033
894
  * Recomputes the orthographic light-space slice the map covers.
1034
895
  * @internal
1035
896
  */ _updateUniforms(ctx, waters, light, mapSize) {
1036
- // Slice-wide settings come from the first body, which the caller has sorted
1037
- // largest first. They describe the map, not the water, so one body has to
1038
- // win: a range or a warp per body would ask one texture for two resolutions.
897
+ // Slice-wide settings come from the first (largest) body.
1039
898
  const material = waters[0].material;
1040
899
  const dir = this._tmpDir;
1041
900
  dir.set(light.directionAndCutoff.xyz());
1042
901
  dir.inplaceNormalize();
1043
- // Two axes perpendicular to the light direction. Any pair will do; picking a
1044
- // world axis that is not parallel to the light keeps the cross products stable.
902
+ // Two axes perpendicular to the light, seeded by a non-parallel world axis.
1045
903
  const seed = Math.abs(dir.y) > 0.99 ? Vector3.axisPZ() : Vector3.axisPY();
1046
904
  Vector3.cross(seed, dir, this._tmpRight);
1047
905
  this._tmpRight.inplaceNormalize();
@@ -1058,13 +916,7 @@ const MAX_FADE_FRACTION = 0.9;
1058
916
  // The component along the light direction is arbitrary: both the projection
1059
917
  // into the map and the photon sweep are invariant along it.
1060
918
  const alongDir = Vector3.dot(center, dir);
1061
- // Water region in light space. A world-axis-aligned rectangle projects to a
1062
- // parallelogram here, so its bounds come from the four corners; the map is a
1063
- // box in these coordinates, which is why the fit is against the AABB.
1064
- //
1065
- // Over every body at once, so the slice covers all of them. Bodies far
1066
- // apart make the union mostly empty, but `causticsRange` already bounds how
1067
- // much of that reaches the map.
919
+ // Light-space AABB of all bodies' regions, from their four corners.
1068
920
  let regionMinR = Infinity;
1069
921
  let regionMaxR = -Infinity;
1070
922
  let regionMinU = Infinity;
@@ -1099,20 +951,15 @@ const MAX_FADE_FRACTION = 0.9;
1099
951
  regionMaxU = Math.max(regionMaxU, v);
1100
952
  }
1101
953
  }
1102
- // Fit the slice to what the water can actually cast into, instead of always
1103
- // spending the map on a camera-centred square of `range`, which leaves most
1104
- // of it permanently zero for a pool far smaller than the range.
1105
- // `causticsRange` becomes a cap on how far from the camera the map reaches
1106
- // rather than its literal half-extent.
954
+ // Fit the slice to the water; `causticsRange` caps how far from the camera
955
+ // the map reaches.
1107
956
  let loR = Math.max(regionMinR, cameraRight - range);
1108
957
  let hiR = Math.min(regionMaxR, cameraRight + range);
1109
958
  let loU = Math.max(regionMinU, cameraUp - range);
1110
959
  let hiU = Math.min(regionMaxU, cameraUp + range);
1111
960
  if (hiR <= loR || hiU <= loU) {
1112
- // The water casts nowhere within range. Nothing will be drawn, but the
1113
- // slice still has to be finite: the receiver projects through it whatever
1114
- // the map holds, and the grid bounds below have to come out empty rather
1115
- // than inverted.
961
+ // No water within range; keep the slice finite so the grid bounds come
962
+ // out empty rather than inverted.
1116
963
  loR = cameraRight - range;
1117
964
  hiR = cameraRight + range;
1118
965
  loU = cameraUp - range;
@@ -1120,27 +967,18 @@ const MAX_FADE_FRACTION = 0.9;
1120
967
  }
1121
968
  let halfR = Math.max(MIN_SLICE_EXTENT, (hiR - loR) * 0.5);
1122
969
  let halfU = Math.max(MIN_SLICE_EXTENT, (hiU - loU) * 0.5);
1123
- // The map is square, so an anisotropic slice gives its two axes different
1124
- // world texel sizes, which reads as directional smearing. Capping the ratio
1125
- // keeps most of the fit while bounding that, and bounds the same asymmetry
1126
- // in the edge fade, whose band is a share of each axis.
970
+ // Cap the aspect ratio to bound directional smearing on the square map.
1127
971
  halfR = Math.max(halfR, halfU / MAX_SLICE_ASPECT);
1128
972
  halfU = Math.max(halfU, halfR / MAX_SLICE_ASPECT);
1129
- // Quantise so the extent holds still while the camera moves: texel snapping
1130
- // below only stops the map crawling if the texel size itself is stable, and
1131
- // a continuously shrinking slice resamples the temporal history every frame.
1132
- // The two common cases - a pool wholly inside the range, a sea wholly
1133
- // covering it - sit on a fixed level and never change at all.
973
+ // Quantise so the texel size holds still while the camera moves, which the
974
+ // centre snapping and the temporal history rely on.
1134
975
  halfR = Math.min(range, quantiseSliceExtent(halfR, range));
1135
976
  halfU = Math.min(range, quantiseSliceExtent(halfU, range));
1136
- // Warping only pays where the range, not the water, is what bounds the map.
1137
- // A slice already fitted to a pool has every texel on water the camera can
1138
- // see, and concentrating them further would just blur its far side.
977
+ // Warp only when the range, not the water, bounds the map.
1139
978
  const rangeLimited = regionMinR < cameraRight - range || regionMaxR > cameraRight + range || regionMinU < cameraUp - range || regionMaxU > cameraUp + range;
1140
979
  const warp = rangeLimited ? material.causticsWarp : 0;
1141
- // Snap the centre so the map does not crawl as the camera moves. Whole
1142
- // texels when the map is linear, which is exact; a coarse ladder under a
1143
- // warp, where no shift is exact and the goal is instead to shift rarely.
980
+ // Snap the centre so the map does not crawl: whole texels when linear, a
981
+ // coarse ladder under a warp.
1144
982
  const snapR = warp > 0 ? halfR / WARPED_CENTER_SNAP_DIVISIONS : 2 * halfR / mapSize;
1145
983
  const snapU = warp > 0 ? halfU / WARPED_CENTER_SNAP_DIVISIONS : 2 * halfU / mapSize;
1146
984
  const centerR = Math.round((loR + hiR) * 0.5 / snapR) * snapR;
@@ -1151,34 +989,24 @@ const MAX_FADE_FRACTION = 0.9;
1151
989
  u.frameY.setXYZW(up.x, up.y, up.z, 1 / halfU);
1152
990
  u.center.setXYZW(center.x, center.y, center.z, waterLevel);
1153
991
  u.lightDir.setXYZW(dir.x, dir.y, dir.z, 1 / Math.max(-dir.y, MIN_SUN_ELEVATION));
1154
- // Edge fade, expressed to the receiver as the distance from the map centre
1155
- // the fade begins at, in units of the map half-extent. Resolving it here
1156
- // keeps the shader free of the extents and lets the auto width put a floor
1157
- // in meters under a band the map fraction alone would collapse.
1158
- //
1159
- // Only a border the range put there needs fading. Where the fit stopped at
1160
- // the water instead, the border already coincides with the region the
1161
- // receiver gates on, so a band there is at best a no-op and at worst eats
1162
- // caustics from the last few meters of a pool the map now fits exactly.
992
+ // Edge fade start, in units of the map half-extent. Only a border set by the
993
+ // range is faded; a border fitted to the water coincides with the region.
1163
994
  const minHalf = Math.min(halfR, halfU);
1164
995
  const fadeDistance = !rangeLimited ? 0 : material.causticsFadeDistance > 0 ? material.causticsFadeDistance : Math.max(AUTO_FADE_MIN_DISTANCE, AUTO_FADE_FRACTION * minHalf);
1165
996
  const fadeFraction = Math.max(MIN_FADE_FRACTION, Math.min(MAX_FADE_FRACTION, fadeDistance / minHalf));
1166
997
  u.params.setXYZW(material.causticsIntensity, material.causticsDepth, material.causticsDefocus, 1 - fadeFraction);
1167
- u.extinction.setXYZW(0, 0, 0, warp);
998
+ u.extinction.setXYZW(material.causticsMinDepth, 0, 0, warp);
1168
999
  this._warp = warp;
1169
- // Retained for the per-body grid fit, which runs per draw rather than here.
1000
+ // Retained for the per-body grid fit.
1170
1001
  this._sliceRight = right;
1171
1002
  this._sliceUp = up;
1172
1003
  this._sliceCenterR = centerR;
1173
1004
  this._sliceCenterU = centerU;
1174
1005
  this._sliceHalfR = halfR;
1175
1006
  this._sliceHalfU = halfU;
1176
- // How far the slice reaches from the camera, which is what decides how
1177
- // coarse a shadow cascade has to be to contain it.
1007
+ // Slice reach from the camera, for shadow cascade selection.
1178
1008
  this._sliceReach = Math.hypot(halfR, halfU);
1179
- // The per-body table the receiver searches. Footprint and surface height
1180
- // decide which body is above a given point; the medium is what attenuates
1181
- // the light once one is.
1009
+ // Per-body table the receiver searches: footprint, medium and surface height.
1182
1010
  u.waterCount = Math.min(waters.length, MAX_CAUSTIC_WATERS);
1183
1011
  for(let i = 0; i < u.waterCount; i++){
1184
1012
  const body = waters[i];
@@ -1197,13 +1025,9 @@ const MAX_FADE_FRACTION = 0.9;
1197
1025
  /**
1198
1026
  * Map-NDC bounds of one body's footprint, so its photon grid covers just that.
1199
1027
  *
1200
- * Projecting into the slice is invariant along the light and the slice is
1201
- * normal to it, so a point on the water plane and the photon it launches share
1202
- * a map position: the body's light-space bounds carry over directly. Clamped
1203
- * to the map, because photons outside it are rasterised away and the fraction
1204
- * has to describe what is left. Warped, because the grid is laid out in the
1205
- * space the map's texels live in - the warp is monotone and separable, so
1206
- * warping the interval's ends gives the warped interval exactly.
1028
+ * The body's light-space bounds carry over directly since the projection is
1029
+ * invariant along the light. Clamped to the map, then warped; the warp is
1030
+ * monotone and separable, so warping the interval's ends is exact.
1207
1031
  * @internal
1208
1032
  */ _updateGridBounds(water) {
1209
1033
  const region = water.material.region;
@@ -1240,12 +1064,8 @@ const MAX_FADE_FRACTION = 0.9;
1240
1064
  maxU = Math.max(maxU, v);
1241
1065
  }
1242
1066
  const warp = this._warp;
1243
- // Clamped to the map plus a margin, but never past the water itself. Where
1244
- // the footprint ends inside the map the bound stays on the water, because a
1245
- // photon launched off the water is killed by the region test and would only
1246
- // dilute the weight the fraction below hands out. Where the water carries on
1247
- // past the border, the margin lets the displaced surface throw photons back
1248
- // in across it - see GRID_BORDER_MARGIN.
1067
+ // Clamped to the map plus a margin (see GRID_BORDER_MARGIN), but never past
1068
+ // the water itself.
1249
1069
  const m = 1 + GRID_BORDER_MARGIN;
1250
1070
  const lo = (v)=>Math.max(-1.15, Math.min(m, v));
1251
1071
  const minX = warpCausticNDC(lo((minR - this._sliceCenterR) / this._sliceHalfR), warp);
@@ -1253,9 +1073,8 @@ const MAX_FADE_FRACTION = 0.9;
1253
1073
  const minY = warpCausticNDC(lo((minU - this._sliceCenterU) / this._sliceHalfU), warp);
1254
1074
  const maxY = warpCausticNDC(lo((maxU - this._sliceCenterU) / this._sliceHalfU), warp);
1255
1075
  this._gridBounds.setXYZW(minX, minY, maxX, maxY);
1256
- // Share of the map the grid covers, which is what keeps calm water at 1.0:
1257
- // the same photon count spread over a smaller area has to deposit
1258
- // proportionally less each.
1076
+ // Share of the map the grid covers; scales the photon weight to keep calm
1077
+ // water at 1.0.
1259
1078
  this._gridFraction = Math.max(0, (maxX - minX) * (maxY - minY) / 4);
1260
1079
  }
1261
1080
  /** @internal */ _updatePhotonLayout(device, gridSize) {
@@ -1263,10 +1082,7 @@ const MAX_FADE_FRACTION = 0.9;
1263
1082
  if (this._photonCapacity >= this._photonCount) {
1264
1083
  return;
1265
1084
  }
1266
- // Grow only, and never rebuild for a size change.
1267
- //
1268
- // The buffer holds each photon's ordinal, and the shader divides that
1269
- // by the grid size to place it.
1085
+ // Grow only. The buffer holds each photon's ordinal.
1270
1086
  const capacity = Math.max(this._photonCount, this._photonCapacity * 2);
1271
1087
  const indices = new Float32Array(capacity);
1272
1088
  for(let i = 0; i < capacity; i++){
@@ -1303,8 +1119,7 @@ const MAX_FADE_FRACTION = 0.9;
1303
1119
  states = device.createRenderStateSet();
1304
1120
  states.useRasterizerState().setCullMode('none');
1305
1121
  states.useDepthState().enableTest(false).enableWrite(false);
1306
- // Plain overwrite into this slot's channel only. Blending would add the
1307
- // heights wherever the surface overlaps itself in the light's view.
1122
+ // Plain overwrite into this slot's channel only.
1308
1123
  states.useColorState().setColorMask(slot === 0, slot === 1, slot === 2, slot === 3);
1309
1124
  this._heightStates[slot] = states;
1310
1125
  }