@zephyr3d/scene 0.9.26 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (506) hide show
  1. package/README.md +60 -27
  2. package/dist/animation/animation_bank.js +634 -0
  3. package/dist/animation/animation_bank.js.map +1 -0
  4. package/dist/animation/animation_bank_gpu.js +256 -0
  5. package/dist/animation/animation_bank_gpu.js.map +1 -0
  6. package/dist/animation/animationset.js +146 -30
  7. package/dist/animation/animationset.js.map +1 -1
  8. package/dist/animation/cloth/gpu_cloth_component.js +406 -0
  9. package/dist/animation/cloth/gpu_cloth_component.js.map +1 -0
  10. package/dist/animation/cloth/gpu_cloth_system.js +332 -202
  11. package/dist/animation/cloth/gpu_cloth_system.js.map +1 -1
  12. package/dist/animation/gpu_animation_driver.js +289 -0
  13. package/dist/animation/gpu_animation_driver.js.map +1 -0
  14. package/dist/animation/joint_dynamics/collision.js +112 -1
  15. package/dist/animation/joint_dynamics/collision.js.map +1 -1
  16. package/dist/animation/joint_dynamics/controller.js +6 -0
  17. package/dist/animation/joint_dynamics/controller.js.map +1 -1
  18. package/dist/animation/joint_dynamics/joint_dynamics_system.js +11 -0
  19. package/dist/animation/joint_dynamics/joint_dynamics_system.js.map +1 -1
  20. package/dist/animation/joint_dynamics/solver.js +50 -12
  21. package/dist/animation/joint_dynamics/solver.js.map +1 -1
  22. package/dist/animation/joint_dynamics/types.js.map +1 -1
  23. package/dist/animation/skeleton.js +300 -56
  24. package/dist/animation/skeleton.js.map +1 -1
  25. package/dist/animation/skin_palette_atlas.js +252 -0
  26. package/dist/animation/skin_palette_atlas.js.map +1 -0
  27. package/dist/animation/spring/multi_chain_spring_system.js +298 -38
  28. package/dist/animation/spring/multi_chain_spring_system.js.map +1 -1
  29. package/dist/animation/spring/spring_chain.js +2 -0
  30. package/dist/animation/spring/spring_chain.js.map +1 -1
  31. package/dist/animation/spring/spring_collider.js +159 -21
  32. package/dist/animation/spring/spring_collider.js.map +1 -1
  33. package/dist/animation/spring/spring_particle.js +1 -0
  34. package/dist/animation/spring/spring_particle.js.map +1 -1
  35. package/dist/animation/spring/spring_solver.js +173 -0
  36. package/dist/animation/spring/spring_solver.js.map +1 -0
  37. package/dist/animation/spring/spring_system.js +249 -24
  38. package/dist/animation/spring/spring_system.js.map +1 -1
  39. package/dist/animation/spring_modifier.js +32 -1
  40. package/dist/animation/spring_modifier.js.map +1 -1
  41. package/dist/app/app.js +26 -0
  42. package/dist/app/app.js.map +1 -1
  43. package/dist/app/engine.js +86 -15
  44. package/dist/app/engine.js.map +1 -1
  45. package/dist/app/gamepad.js +191 -0
  46. package/dist/app/gamepad.js.map +1 -0
  47. package/dist/app/inputactions.js +271 -0
  48. package/dist/app/inputactions.js.map +1 -0
  49. package/dist/app/inputmgr.js +331 -22
  50. package/dist/app/inputmgr.js.map +1 -1
  51. package/dist/app/loading.js +138 -0
  52. package/dist/app/loading.js.map +1 -0
  53. package/dist/app/pipelinecache.js +209 -0
  54. package/dist/app/pipelinecache.js.map +1 -0
  55. package/dist/app/runtimescript.js +25 -0
  56. package/dist/app/runtimescript.js.map +1 -1
  57. package/dist/app/scriptingsystem.js +54 -0
  58. package/dist/app/scriptingsystem.js.map +1 -1
  59. package/dist/app/scriptregistry.js +7 -2
  60. package/dist/app/scriptregistry.js.map +1 -1
  61. package/dist/asset/asset_manifest.js +59 -0
  62. package/dist/asset/asset_manifest.js.map +1 -0
  63. package/dist/asset/asset_preload.js +258 -0
  64. package/dist/asset/asset_preload.js.map +1 -0
  65. package/dist/asset/assetmanager.js +117 -65
  66. package/dist/asset/assetmanager.js.map +1 -1
  67. package/dist/asset/loaders/ktx2/basis/LICENSE +201 -0
  68. package/dist/asset/loaders/ktx2/basis/basis_transcoder.js +2 -0
  69. package/dist/asset/loaders/ktx2/basis/basis_transcoder.wasm +0 -0
  70. package/dist/asset/loaders/ktx2/basis_location.js +17 -0
  71. package/dist/asset/loaders/ktx2/basis_location.js.map +1 -0
  72. package/dist/asset/loaders/ktx2/ktx2_loader.js +88 -0
  73. package/dist/asset/loaders/ktx2/ktx2_loader.js.map +1 -0
  74. package/dist/asset/loaders/ktx2/ktx2_targets.js +118 -0
  75. package/dist/asset/loaders/ktx2/ktx2_targets.js.map +1 -0
  76. package/dist/asset/loaders/ktx2/transcoder_pool.js +259 -0
  77. package/dist/asset/loaders/ktx2/transcoder_pool.js.map +1 -0
  78. package/dist/asset/mesh_lod_builder.js +104 -0
  79. package/dist/asset/mesh_lod_builder.js.map +1 -0
  80. package/dist/asset/mesh_settings.js +111 -0
  81. package/dist/asset/mesh_settings.js.map +1 -0
  82. package/dist/asset/meshopt_decoder.js +18 -0
  83. package/dist/asset/meshopt_decoder.js.map +1 -0
  84. package/dist/asset/meshopt_simplifier.js +26 -0
  85. package/dist/asset/meshopt_simplifier.js.map +1 -0
  86. package/dist/asset/model.js +177 -3
  87. package/dist/asset/model.js.map +1 -1
  88. package/dist/asset/texture_settings.js +231 -0
  89. package/dist/asset/texture_settings.js.map +1 -0
  90. package/dist/asset/zmsh_binary.js +131 -0
  91. package/dist/asset/zmsh_binary.js.map +1 -0
  92. package/dist/asset/zsdf_binary.js +111 -0
  93. package/dist/asset/zsdf_binary.js.map +1 -0
  94. package/dist/blitter/bilateralblur.js +1 -2
  95. package/dist/blitter/bilateralblur.js.map +1 -1
  96. package/dist/blitter/gaussianblur.js +1 -6
  97. package/dist/blitter/gaussianblur.js.map +1 -1
  98. package/dist/camera/base.js +31 -10
  99. package/dist/camera/base.js.map +1 -1
  100. package/dist/camera/camera.js +178 -200
  101. package/dist/camera/camera.js.map +1 -1
  102. package/dist/camera/follow.js +342 -0
  103. package/dist/camera/follow.js.map +1 -0
  104. package/dist/camera/fps.js +9 -7
  105. package/dist/camera/fps.js.map +1 -1
  106. package/dist/camera/orbit.js +46 -19
  107. package/dist/camera/orbit.js.map +1 -1
  108. package/dist/index.d.ts +30475 -19865
  109. package/dist/index.js +67 -12
  110. package/dist/index.js.map +1 -1
  111. package/dist/material/blinn.js +3 -0
  112. package/dist/material/blinn.js.map +1 -1
  113. package/dist/material/eye.js +1 -1
  114. package/dist/material/eye.js.map +1 -1
  115. package/dist/material/hair.js +2 -3
  116. package/dist/material/hair.js.map +1 -1
  117. package/dist/material/hairstrand.js +235 -235
  118. package/dist/material/hairstrand.js.map +1 -1
  119. package/dist/material/lambert.js +4 -1
  120. package/dist/material/lambert.js.map +1 -1
  121. package/dist/material/material.js +362 -300
  122. package/dist/material/material.js.map +1 -1
  123. package/dist/material/meshmaterial.js +118 -45
  124. package/dist/material/meshmaterial.js.map +1 -1
  125. package/dist/material/mixins/lightmodel/blinnphong.js +1 -1
  126. package/dist/material/mixins/lightmodel/blinnphong.js.map +1 -1
  127. package/dist/material/mixins/lightmodel/lambert.js +1 -1
  128. package/dist/material/mixins/lightmodel/lambert.js.map +1 -1
  129. package/dist/material/mixins/lightmodel/pbrblueprintmixin.js +34 -61
  130. package/dist/material/mixins/lightmodel/pbrblueprintmixin.js.map +1 -1
  131. package/dist/material/mixins/lightmodel/pbrmetallicroughness.js +16 -2
  132. package/dist/material/mixins/lightmodel/pbrmetallicroughness.js.map +1 -1
  133. package/dist/material/mixins/lightmodel/pbrspecularglossness.js +2 -1
  134. package/dist/material/mixins/lightmodel/pbrspecularglossness.js.map +1 -1
  135. package/dist/material/mixins/lit.js +223 -83
  136. package/dist/material/mixins/lit.js.map +1 -1
  137. package/dist/material/mixins/pbr/common.js +31 -206
  138. package/dist/material/mixins/pbr/common.js.map +1 -1
  139. package/dist/material/mtoon.js +1 -1
  140. package/dist/material/mtoon.js.map +1 -1
  141. package/dist/material/pbrmr.js +3 -0
  142. package/dist/material/pbrmr.js.map +1 -1
  143. package/dist/material/pbrsg.js +3 -0
  144. package/dist/material/pbrsg.js.map +1 -1
  145. package/dist/material/sdf_debug.js +78 -0
  146. package/dist/material/sdf_debug.js.map +1 -0
  147. package/dist/material/shader/helper.js +733 -190
  148. package/dist/material/shader/helper.js.map +1 -1
  149. package/dist/material/shader/terrain_blend.js +204 -43
  150. package/dist/material/shader/terrain_blend.js.map +1 -1
  151. package/dist/material/skin.js +331 -297
  152. package/dist/material/skin.js.map +1 -1
  153. package/dist/material/skinprofile.js +1058 -0
  154. package/dist/material/skinprofile.js.map +1 -0
  155. package/dist/material/sss.js +353 -0
  156. package/dist/material/sss.js.map +1 -0
  157. package/dist/material/sss_kernel.js +265 -0
  158. package/dist/material/sss_kernel.js.map +1 -0
  159. package/dist/material/sssprofile.js +879 -0
  160. package/dist/material/sssprofile.js.map +1 -0
  161. package/dist/material/subsurfaceprofile.js +4 -8
  162. package/dist/material/subsurfaceprofile.js.map +1 -1
  163. package/dist/material/terrain-cm.js +464 -188
  164. package/dist/material/terrain-cm.js.map +1 -1
  165. package/dist/material/unlit.js +3 -0
  166. package/dist/material/unlit.js.map +1 -1
  167. package/dist/material/water.js +2009 -201
  168. package/dist/material/water.js.map +1 -1
  169. package/dist/node_modules/meshoptimizer/LICENSE +21 -0
  170. package/dist/node_modules/meshoptimizer/meshopt_decoder.js +197 -0
  171. package/dist/node_modules/meshoptimizer/meshopt_decoder.js.map +1 -0
  172. package/dist/node_modules/meshoptimizer/meshopt_simplifier.js +677 -0
  173. package/dist/node_modules/meshoptimizer/meshopt_simplifier.js.map +1 -0
  174. package/dist/physics/character.js +226 -0
  175. package/dist/physics/character.js.map +1 -0
  176. package/dist/physics/collider.js +209 -0
  177. package/dist/physics/collider.js.map +1 -0
  178. package/dist/physics/events.js +79 -0
  179. package/dist/physics/events.js.map +1 -0
  180. package/dist/physics/joint.js +272 -0
  181. package/dist/physics/joint.js.map +1 -0
  182. package/dist/physics/node_physics.js +234 -0
  183. package/dist/physics/node_physics.js.map +1 -0
  184. package/dist/physics/part.js +91 -0
  185. package/dist/physics/part.js.map +1 -0
  186. package/dist/physics/rigid_body.js +379 -0
  187. package/dist/physics/rigid_body.js.map +1 -0
  188. package/dist/physics/vehicle.js +335 -0
  189. package/dist/physics/vehicle.js.map +1 -0
  190. package/dist/physics/world.js +25 -0
  191. package/dist/physics/world.js.map +1 -0
  192. package/dist/posteffect/bloom.js +49 -14
  193. package/dist/posteffect/bloom.js.map +1 -1
  194. package/dist/posteffect/motionblur.js +668 -63
  195. package/dist/posteffect/motionblur.js.map +1 -1
  196. package/dist/posteffect/posteffect.js +12 -1
  197. package/dist/posteffect/posteffect.js.map +1 -1
  198. package/dist/posteffect/postsss.js +572 -0
  199. package/dist/posteffect/postsss.js.map +1 -0
  200. package/dist/posteffect/sao.js +48 -50
  201. package/dist/posteffect/sao.js.map +1 -1
  202. package/dist/posteffect/skinsss.js +605 -355
  203. package/dist/posteffect/skinsss.js.map +1 -1
  204. package/dist/posteffect/ssgi.js +91 -162
  205. package/dist/posteffect/ssgi.js.map +1 -1
  206. package/dist/posteffect/ssr.js +3 -11
  207. package/dist/posteffect/ssr.js.map +1 -1
  208. package/dist/posteffect/sss.js +34 -15
  209. package/dist/posteffect/sss.js.map +1 -1
  210. package/dist/posteffect/taa.js +1 -0
  211. package/dist/posteffect/taa.js.map +1 -1
  212. package/dist/render/abuffer_oit.js +40 -39
  213. package/dist/render/abuffer_oit.js.map +1 -1
  214. package/dist/render/clipmap.js +328 -62
  215. package/dist/render/clipmap.js.map +1 -1
  216. package/dist/render/cluster_light.js +958 -224
  217. package/dist/render/cluster_light.js.map +1 -1
  218. package/dist/render/cull_visitor.js +26 -5
  219. package/dist/render/cull_visitor.js.map +1 -1
  220. package/dist/render/debug_views.js +37 -0
  221. package/dist/render/debug_views.js.map +1 -0
  222. package/dist/render/depthpass.js +5 -1
  223. package/dist/render/depthpass.js.map +1 -1
  224. package/dist/render/drawable_mixin.js +53 -6
  225. package/dist/render/drawable_mixin.js.map +1 -1
  226. package/dist/render/dualdepthpeeling_oit.js +53 -53
  227. package/dist/render/dualdepthpeeling_oit.js.map +1 -1
  228. package/dist/render/envlight.js +196 -113
  229. package/dist/render/envlight.js.map +1 -1
  230. package/dist/render/fbm_wavegenerator.js +8 -13
  231. package/dist/render/fbm_wavegenerator.js.map +1 -1
  232. package/dist/render/fft_wavegenerator.js +268 -136
  233. package/dist/render/fft_wavegenerator.js.map +1 -1
  234. package/dist/render/gerstner_wavegenerator.js +113 -28
  235. package/dist/render/gerstner_wavegenerator.js.map +1 -1
  236. package/dist/render/gi/probe_volume.js +1334 -0
  237. package/dist/render/gi/probe_volume.js.map +1 -0
  238. package/dist/render/gi/scene_gi.js +282 -0
  239. package/dist/render/gi/scene_gi.js.map +1 -0
  240. package/dist/render/gi/voxel_atlas.js +198 -0
  241. package/dist/render/gi/voxel_atlas.js.map +1 -0
  242. package/dist/render/gi/voxel_lighting.js +652 -0
  243. package/dist/render/gi/voxel_lighting.js.map +1 -0
  244. package/dist/render/gi/voxel_scene.js +642 -0
  245. package/dist/render/gi/voxel_scene.js.map +1 -0
  246. package/dist/render/gi/voxelize_pass.js +223 -0
  247. package/dist/render/gi/voxelize_pass.js.map +1 -0
  248. package/dist/render/globalbindgroup_allocator.js +3 -2
  249. package/dist/render/globalbindgroup_allocator.js.map +1 -1
  250. package/dist/render/gpu_picking.js +9 -36
  251. package/dist/render/gpu_picking.js.map +1 -1
  252. package/dist/render/hzb.js +80 -22
  253. package/dist/render/hzb.js.map +1 -1
  254. package/dist/render/instance_culling.js +499 -0
  255. package/dist/render/instance_culling.js.map +1 -0
  256. package/dist/render/lightpass.js +23 -9
  257. package/dist/render/lightpass.js.map +1 -1
  258. package/dist/render/lod.js +46 -0
  259. package/dist/render/lod.js.map +1 -0
  260. package/dist/render/oit.js +11 -11
  261. package/dist/render/oit.js.map +1 -1
  262. package/dist/render/primitive.js +301 -9
  263. package/dist/render/primitive.js.map +1 -1
  264. package/dist/render/primitive_readback.js +209 -0
  265. package/dist/render/primitive_readback.js.map +1 -0
  266. package/dist/render/render_queue.js +198 -11
  267. package/dist/render/render_queue.js.map +1 -1
  268. package/dist/render/renderer.js +14 -7
  269. package/dist/render/renderer.js.map +1 -1
  270. package/dist/render/rendergraph/blackboard.js +15 -1
  271. package/dist/render/rendergraph/blackboard.js.map +1 -1
  272. package/dist/render/rendergraph/executor.js +136 -0
  273. package/dist/render/rendergraph/executor.js.map +1 -1
  274. package/dist/render/rendergraph/forward_plus_builder.js +585 -59
  275. package/dist/render/rendergraph/forward_plus_builder.js.map +1 -1
  276. package/dist/render/rendergraph/frame_resource_requirements.js +1 -0
  277. package/dist/render/rendergraph/frame_resource_requirements.js.map +1 -1
  278. package/dist/render/rendergraph/history_resources.js +8 -1
  279. package/dist/render/rendergraph/history_resources.js.map +1 -1
  280. package/dist/render/rendergraph/scene_render_context.js +31 -2
  281. package/dist/render/rendergraph/scene_render_context.js.map +1 -1
  282. package/dist/render/rendergraph/types.js.map +1 -1
  283. package/dist/render/renderpass.js +28 -5
  284. package/dist/render/renderpass.js.map +1 -1
  285. package/dist/render/sdf/box_allocator.js +98 -0
  286. package/dist/render/sdf/box_allocator.js.map +1 -0
  287. package/dist/render/sdf/global_sdf.js +865 -0
  288. package/dist/render/sdf/global_sdf.js.map +1 -0
  289. package/dist/render/sdf/scene_sdf.js +562 -0
  290. package/dist/render/sdf/scene_sdf.js.map +1 -0
  291. package/dist/render/sdf/sdf_atlas.js +227 -0
  292. package/dist/render/sdf/sdf_atlas.js.map +1 -0
  293. package/dist/render/sdf/sdf_programs.js +42 -0
  294. package/dist/render/sdf/sdf_programs.js.map +1 -0
  295. package/dist/render/sdf/sdf_view.js +199 -0
  296. package/dist/render/sdf/sdf_view.js.map +1 -0
  297. package/dist/render/sdf/waterline.js +610 -0
  298. package/dist/render/sdf/waterline.js.map +1 -0
  299. package/dist/render/shadow_mask_pass.js +142 -25
  300. package/dist/render/shadow_mask_pass.js.map +1 -1
  301. package/dist/render/shore_wave_generator.js +1193 -0
  302. package/dist/render/shore_wave_generator.js.map +1 -0
  303. package/dist/render/sky.js +516 -86
  304. package/dist/render/sky.js.map +1 -1
  305. package/dist/render/transmission_thickness_pass.js +477 -0
  306. package/dist/render/transmission_thickness_pass.js.map +1 -0
  307. package/dist/render/underwater.js +524 -0
  308. package/dist/render/underwater.js.map +1 -0
  309. package/dist/render/virtualtexture/virtual_texture.js +1214 -0
  310. package/dist/render/virtualtexture/virtual_texture.js.map +1 -0
  311. package/dist/render/volumetric_cloud/cloud_common.js +472 -0
  312. package/dist/render/volumetric_cloud/cloud_common.js.map +1 -0
  313. package/dist/render/volumetric_cloud/cloud_noise.js +189 -0
  314. package/dist/render/volumetric_cloud/cloud_noise.js.map +1 -0
  315. package/dist/render/volumetric_cloud/debug_view.js +131 -0
  316. package/dist/render/volumetric_cloud/debug_view.js.map +1 -0
  317. package/dist/render/volumetric_cloud/resources.js +380 -0
  318. package/dist/render/volumetric_cloud/resources.js.map +1 -0
  319. package/dist/render/volumetric_cloud/sky_cache.js +380 -0
  320. package/dist/render/volumetric_cloud/sky_cache.js.map +1 -0
  321. package/dist/render/volumetric_cloud/volumetric_cloud.js +318 -0
  322. package/dist/render/volumetric_cloud/volumetric_cloud.js.map +1 -0
  323. package/dist/render/water_caustics.js +1195 -0
  324. package/dist/render/water_caustics.js.map +1 -0
  325. package/dist/render/water_foam.js +627 -0
  326. package/dist/render/water_foam.js.map +1 -0
  327. package/dist/render/water_interaction.js +1795 -0
  328. package/dist/render/water_interaction.js.map +1 -0
  329. package/dist/render/weightedblended_oit.js +36 -36
  330. package/dist/render/weightedblended_oit.js.map +1 -1
  331. package/dist/scene/basesprite.js +9 -4
  332. package/dist/scene/basesprite.js.map +1 -1
  333. package/dist/scene/batchgroup.js +108 -12
  334. package/dist/scene/batchgroup.js.map +1 -1
  335. package/dist/scene/environment.js +28 -7
  336. package/dist/scene/environment.js.map +1 -1
  337. package/dist/scene/foliage.js +1020 -0
  338. package/dist/scene/foliage.js.map +1 -0
  339. package/dist/scene/foliage_buckets.js +147 -0
  340. package/dist/scene/foliage_buckets.js.map +1 -0
  341. package/dist/scene/foliage_mask.js +358 -0
  342. package/dist/scene/foliage_mask.js.map +1 -0
  343. package/dist/scene/hair.js +15 -4
  344. package/dist/scene/hair.js.map +1 -1
  345. package/dist/scene/light.js +179 -13
  346. package/dist/scene/light.js.map +1 -1
  347. package/dist/scene/mesh.js +327 -45
  348. package/dist/scene/mesh.js.map +1 -1
  349. package/dist/scene/meshdrawable.js +6 -0
  350. package/dist/scene/meshdrawable.js.map +1 -1
  351. package/dist/scene/msdftext.js +9 -3
  352. package/dist/scene/msdftext.js.map +1 -1
  353. package/dist/scene/msdftextsprite.js +9 -3
  354. package/dist/scene/msdftextsprite.js.map +1 -1
  355. package/dist/scene/particlesys.js +8 -2
  356. package/dist/scene/particlesys.js.map +1 -1
  357. package/dist/scene/physics_settings.js +157 -0
  358. package/dist/scene/physics_settings.js.map +1 -0
  359. package/dist/scene/scene.js +190 -44
  360. package/dist/scene/scene.js.map +1 -1
  361. package/dist/scene/scene_node.js +147 -6
  362. package/dist/scene/scene_node.js.map +1 -1
  363. package/dist/scene/terrain-cm/bladegrassmaterial.js +735 -0
  364. package/dist/scene/terrain-cm/bladegrassmaterial.js.map +1 -0
  365. package/dist/scene/terrain-cm/grass.js +881 -42
  366. package/dist/scene/terrain-cm/grass.js.map +1 -1
  367. package/dist/scene/terrain-cm/grass_gpu.js +929 -0
  368. package/dist/scene/terrain-cm/grass_gpu.js.map +1 -0
  369. package/dist/scene/terrain-cm/grassmaterial.js +180 -9
  370. package/dist/scene/terrain-cm/grassmaterial.js.map +1 -1
  371. package/dist/scene/terrain-cm/terrain-cm.js +465 -36
  372. package/dist/scene/terrain-cm/terrain-cm.js.map +1 -1
  373. package/dist/scene/terrain-cm/terrain_gi_albedo.js +257 -0
  374. package/dist/scene/terrain-cm/terrain_gi_albedo.js.map +1 -0
  375. package/dist/scene/terrain-cm/terrain_rvt.js +395 -0
  376. package/dist/scene/terrain-cm/terrain_rvt.js.map +1 -0
  377. package/dist/scene/update_queue.js +54 -0
  378. package/dist/scene/update_queue.js.map +1 -0
  379. package/dist/scene/water.js +997 -96
  380. package/dist/scene/water.js.map +1 -1
  381. package/dist/scene/water_surface_sampler.js +324 -0
  382. package/dist/scene/water_surface_sampler.js.map +1 -0
  383. package/dist/scene/wind.js +317 -0
  384. package/dist/scene/wind.js.map +1 -0
  385. package/dist/shaders/atmosphere.js +388 -166
  386. package/dist/shaders/atmosphere.js.map +1 -1
  387. package/dist/shaders/fog.js +100 -32
  388. package/dist/shaders/fog.js.map +1 -1
  389. package/dist/shaders/gi.js +365 -0
  390. package/dist/shaders/gi.js.map +1 -0
  391. package/dist/shaders/ltc_rect.js +202 -0
  392. package/dist/shaders/ltc_rect.js.map +1 -0
  393. package/dist/shaders/misc.js +1 -10
  394. package/dist/shaders/misc.js.map +1 -1
  395. package/dist/shaders/noise.js +5 -1
  396. package/dist/shaders/noise.js.map +1 -1
  397. package/dist/shaders/probes.js +309 -0
  398. package/dist/shaders/probes.js.map +1 -0
  399. package/dist/shaders/sdf.js +537 -0
  400. package/dist/shaders/sdf.js.map +1 -0
  401. package/dist/shaders/shadow.js +104 -70
  402. package/dist/shaders/shadow.js.map +1 -1
  403. package/dist/shaders/skin_brdf.js +243 -0
  404. package/dist/shaders/skin_brdf.js.map +1 -0
  405. package/dist/shaders/ssr.js +6 -159
  406. package/dist/shaders/ssr.js.map +1 -1
  407. package/dist/shaders/temporal.js +38 -27
  408. package/dist/shaders/temporal.js.map +1 -1
  409. package/dist/shaders/water.js +40 -87
  410. package/dist/shaders/water.js.map +1 -1
  411. package/dist/shaders/water_medium.js +42 -0
  412. package/dist/shaders/water_medium.js.map +1 -0
  413. package/dist/shadow/dom.js +110 -19
  414. package/dist/shadow/dom.js.map +1 -1
  415. package/dist/shadow/esm.js +3 -3
  416. package/dist/shadow/esm.js.map +1 -1
  417. package/dist/shadow/pcf_opt.js +15 -48
  418. package/dist/shadow/pcf_opt.js.map +1 -1
  419. package/dist/shadow/pcf_pd.js +15 -53
  420. package/dist/shadow/pcf_pd.js.map +1 -1
  421. package/dist/shadow/pcss.js +34 -9
  422. package/dist/shadow/pcss.js.map +1 -1
  423. package/dist/shadow/shadow_impl.js +3 -8
  424. package/dist/shadow/shadow_impl.js.map +1 -1
  425. package/dist/shadow/shadow_region.js +87 -49
  426. package/dist/shadow/shadow_region.js.map +1 -1
  427. package/dist/shadow/shadowmapper.js +93 -53
  428. package/dist/shadow/shadowmapper.js.map +1 -1
  429. package/dist/shadow/ssm.js +18 -72
  430. package/dist/shadow/ssm.js.map +1 -1
  431. package/dist/shadow/vsm.js +3 -3
  432. package/dist/shadow/vsm.js.map +1 -1
  433. package/dist/shapes/shape.js +44 -1
  434. package/dist/shapes/shape.js.map +1 -1
  435. package/dist/utility/buoyancy.js +287 -0
  436. package/dist/utility/buoyancy.js.map +1 -0
  437. package/dist/utility/physical.js +10 -1
  438. package/dist/utility/physical.js.map +1 -1
  439. package/dist/utility/pmrem.js +17 -43
  440. package/dist/utility/pmrem.js.map +1 -1
  441. package/dist/utility/sdf/bake.js +435 -0
  442. package/dist/utility/sdf/bake.js.map +1 -0
  443. package/dist/utility/sdf/bvh.js +576 -0
  444. package/dist/utility/sdf/bvh.js.map +1 -0
  445. package/dist/utility/serialization/manager.js +36 -6
  446. package/dist/utility/serialization/manager.js.map +1 -1
  447. package/dist/utility/serialization/scene/animation.js +77 -7
  448. package/dist/utility/serialization/scene/animation.js.map +1 -1
  449. package/dist/utility/serialization/scene/batch.js +11 -4
  450. package/dist/utility/serialization/scene/batch.js.map +1 -1
  451. package/dist/utility/serialization/scene/camera.js +140 -279
  452. package/dist/utility/serialization/scene/camera.js.map +1 -1
  453. package/dist/utility/serialization/scene/cloth.js +25 -0
  454. package/dist/utility/serialization/scene/cloth.js.map +1 -0
  455. package/dist/utility/serialization/scene/common.js +14 -6
  456. package/dist/utility/serialization/scene/common.js.map +1 -1
  457. package/dist/utility/serialization/scene/foliage.js +821 -0
  458. package/dist/utility/serialization/scene/foliage.js.map +1 -0
  459. package/dist/utility/serialization/scene/hair.js +11 -4
  460. package/dist/utility/serialization/scene/hair.js.map +1 -1
  461. package/dist/utility/serialization/scene/light.js +115 -7
  462. package/dist/utility/serialization/scene/light.js.map +1 -1
  463. package/dist/utility/serialization/scene/material.js +388 -220
  464. package/dist/utility/serialization/scene/material.js.map +1 -1
  465. package/dist/utility/serialization/scene/mesh.js +69 -4
  466. package/dist/utility/serialization/scene/mesh.js.map +1 -1
  467. package/dist/utility/serialization/scene/node.js +140 -5
  468. package/dist/utility/serialization/scene/node.js.map +1 -1
  469. package/dist/utility/serialization/scene/node_physics.js +1318 -0
  470. package/dist/utility/serialization/scene/node_physics.js.map +1 -0
  471. package/dist/utility/serialization/scene/particle.js +11 -4
  472. package/dist/utility/serialization/scene/particle.js.map +1 -1
  473. package/dist/utility/serialization/scene/physics.js +120 -0
  474. package/dist/utility/serialization/scene/physics.js.map +1 -0
  475. package/dist/utility/serialization/scene/primitive.js +124 -10
  476. package/dist/utility/serialization/scene/primitive.js.map +1 -1
  477. package/dist/utility/serialization/scene/scene.js +1687 -51
  478. package/dist/utility/serialization/scene/scene.js.map +1 -1
  479. package/dist/utility/serialization/scene/sprite.js +11 -4
  480. package/dist/utility/serialization/scene/sprite.js.map +1 -1
  481. package/dist/utility/serialization/scene/terrain.js +163 -33
  482. package/dist/utility/serialization/scene/terrain.js.map +1 -1
  483. package/dist/utility/serialization/scene/text.js +11 -4
  484. package/dist/utility/serialization/scene/text.js.map +1 -1
  485. package/dist/utility/serialization/scene/water.js +2473 -114
  486. package/dist/utility/serialization/scene/water.js.map +1 -1
  487. package/dist/utility/serialization/types.js.map +1 -1
  488. package/dist/utility/shprojector.js +97 -109
  489. package/dist/utility/shprojector.js.map +1 -1
  490. package/dist/utility/textures/foam.js +170 -0
  491. package/dist/utility/textures/foam.js.map +1 -0
  492. package/dist/utility/textures/ggxlut.js +17 -43
  493. package/dist/utility/textures/ggxlut.js.map +1 -1
  494. package/dist/utility/textures/ltcdata.js +4 -4
  495. package/dist/utility/textures/ltcdata.js.map +1 -1
  496. package/dist/utility/textures/ltclut.js +16 -6
  497. package/dist/utility/textures/ltclut.js.map +1 -1
  498. package/dist/utility/unsupported.js +24 -0
  499. package/dist/utility/unsupported.js.map +1 -0
  500. package/dist/values.js +13 -1
  501. package/dist/values.js.map +1 -1
  502. package/package.json +25 -12
  503. package/src/asset/loaders/ktx2/basis/LICENSE +201 -0
  504. package/src/asset/loaders/ktx2/basis/basis_transcoder.js +2 -0
  505. package/src/asset/loaders/ktx2/basis/basis_transcoder.wasm +0 -0
  506. package/src/asset/meshoptimizer/LICENSE +21 -0
@@ -1,18 +1,20 @@
1
- import { Disposable, DRef, AABB, ClipState, nextPowerOf2, DWeakRef } from '@zephyr3d/base';
2
- import '../../values.js';
3
- import '../../render/render_queue.js';
1
+ import { Disposable, DRef, AABB, Vector3, Vector4, ClipState, DWeakRef, nextPowerOf2 } from '@zephyr3d/base';
2
+ import { RENDER_PASS_TYPE_SHADOWMAP } from '../../values.js';
4
3
  import { getDevice } from '../../app/api.js';
4
+ import { warnUnsupported } from '../../utility/unsupported.js';
5
+ import '../../render/render_queue.js';
5
6
  import { Primitive } from '../../render/primitive.js';
6
7
  import '@zephyr3d/device';
7
8
  import '../../material/shader/helper.js';
8
9
  import '../../material/lambert.js';
9
10
  import '../../material/blinn.js';
10
11
  import '../../material/unlit.js';
12
+ import '../../material/meshmaterial.js';
11
13
  import '../../material/particle.js';
12
14
  import '../../material/subsurfaceprofile.js';
13
- import '../../material/skin.js';
15
+ import '../../material/sssprofile.js';
16
+ import '../../material/sss.js';
14
17
  import '../../material/eye.js';
15
- import '../../material/meshmaterial.js';
16
18
  import '../../material/hair.js';
17
19
  import '../../material/hairstrand.js';
18
20
  import '../../material/grassmaterial.js';
@@ -29,25 +31,60 @@ import '../../render/lightpass.js';
29
31
  import '../../render/sky.js';
30
32
  import '../../render/clipmap.js';
31
33
  import '../../render/envlight.js';
34
+ import '../../render/lod.js';
32
35
  import '../../render/dualdepthpeeling_oit.js';
33
36
  import '../../shaders/atmosphere.js';
37
+ import '../../render/water_interaction.js';
34
38
  import '../../render/rendergraph/scene_render_context.js';
39
+ import '../../render/sdf/scene_sdf.js';
40
+ import '../../render/sdf/global_sdf.js';
41
+ import '../../render/gi/voxelize_pass.js';
35
42
  import { ClipmapGrassMaterial } from './grassmaterial.js';
43
+ import { GrassGpuPlacement, grassHash, GrassOcclusionMode, GRASS_CULL_PLANES } from './grass_gpu.js';
44
+ import { DirectionalLight } from '../light.js';
45
+ import { ClipmapBladeGrassMaterial, createBladeIndices, BLADE_LOD_INDEX_COUNTS } from './bladegrassmaterial.js';
36
46
 
37
47
  const INSTANCE_BYTES = 4 * 4;
38
48
  /** Number of placement cells along each axis of a grass tile */ const TILE_CELLS = 64;
39
49
  /** Default number of placement cells per density map texel along each axis */ const DEFAULT_CELLS_PER_TEXEL = 2;
40
50
  const MAX_CELLS_PER_TEXEL = 8;
51
+ /** Default distance from the camera beyond which grass is not drawn */ const DEFAULT_DRAW_DISTANCE = 150;
52
+ /** Default fraction of blades kept at the draw distance */ const DEFAULT_FAR_DENSITY = 0.25;
53
+ /**
54
+ * Number of the nearest cascades of the sun's shadow map procedural blades cast shadows into.
55
+ * Farther cascades have texels too coarse for single blades.
56
+ */ const GRASS_SHADOW_CASCADES = 2;
57
+ /** Default shape and clumping of procedural blades, see GrassBladeShape */ const DEFAULT_BLADE_SHAPE = [
58
+ 1.5,
59
+ 0.3,
60
+ 0.08,
61
+ 0.2,
62
+ 0.25,
63
+ 0.2,
64
+ 0.15,
65
+ 0.1,
66
+ 1.9,
67
+ 0.48,
68
+ 0.2,
69
+ 0.3,
70
+ 0.2,
71
+ 0.5,
72
+ 0.15,
73
+ 80
74
+ ];
75
+ /**
76
+ * Default width of procedural blades
77
+ * @internal
78
+ */ const DEFAULT_BLADE_WIDTH = 0.08;
41
79
  /**
42
- * Deterministic 2D integer hash, returns a value in [0, 1).
43
- * Grass placement derives everything (jitter, survival threshold, rotation)
44
- * from this hash so that blade positions are stable across edits and reloads.
45
- */ function hashCell(x, z, seed) {
46
- let h = Math.imul(x, 0x27d4eb2d) ^ Math.imul(z, 0x165667b1) ^ Math.imul(seed, 0x9e3779b9) | 0;
47
- h = Math.imul(h ^ h >>> 15, 0x85ebca6b);
48
- h = Math.imul(h ^ h >>> 13, 0xc2b2ae35);
49
- h ^= h >>> 16;
50
- return (h >>> 0) / 4294967296;
80
+ * Default height of procedural blades
81
+ * @internal
82
+ */ const DEFAULT_BLADE_HEIGHT = 1.5;
83
+ function distanceSqToAABB(x, y, z, aabb) {
84
+ const dx = Math.max(aabb.minPoint.x - x, 0, x - aabb.maxPoint.x);
85
+ const dy = Math.max(aabb.minPoint.y - y, 0, y - aabb.maxPoint.y);
86
+ const dz = Math.max(aabb.minPoint.z - z, 0, z - aabb.maxPoint.z);
87
+ return dx * dx + dy * dy + dz * dz;
51
88
  }
52
89
  /**
53
90
  * Grass blade instance buffer for a single tile
@@ -121,10 +158,15 @@ const MAX_CELLS_PER_TEXEL = 8;
121
158
  * @public
122
159
  */ class GrassLayer extends Disposable {
123
160
  static _indexBuffer = new DRef();
161
+ static _bladeIndexBuffer = new DRef();
162
+ static _bladeVertexBuffer = new DRef();
124
163
  static _cullAABB = new AABB();
164
+ static _cameraPos = new Vector3();
125
165
  static _visibleTiles = [];
126
166
  static _instanceData = new Float32Array(TILE_CELLS * TILE_CELLS * 4);
167
+ _kind;
127
168
  _material;
169
+ _bladeShape;
128
170
  _seed;
129
171
  _bladeWidth;
130
172
  _bladeHeight;
@@ -137,19 +179,33 @@ const MAX_CELLS_PER_TEXEL = 8;
137
179
  _tilesX;
138
180
  _tilesZ;
139
181
  _numBlades;
182
+ _drawDistance;
183
+ _farDensity;
184
+ /** Set on WebGPU with indirect draw: blades are placed on the GPU instead of in CPU tiles */ _gpu;
185
+ /** Number of shadow maps the blades were placed for by the last updatePerCamera() call */ _numShadowTargets;
140
186
  /**
141
187
  * Creates an instance of GrassLayer
142
188
  * @param terrain - Clipmap terrain object
143
189
  * @param seed - Seed for deterministic blade placement, usually the layer index
144
190
  * @param bladeWidth - Grass blade width
145
191
  * @param bladeHeight - Grass blade height
146
- * @param albedoMap - Albedo texture for the blade
147
- */ constructor(terrain, seed, bladeWidth, bladeHeight, albedoMap){
192
+ * @param albedoMap - Albedo texture for the blade, card layers only
193
+ * @param kind - Textured cards, or procedural blades (drawn on WebGPU only)
194
+ */ constructor(terrain, seed, bladeWidth, bladeHeight, albedoMap, kind = 'card'){
148
195
  super();
149
- this._material = new DRef(new ClipmapGrassMaterial(terrain));
150
- this._material.get().albedoTexture = albedoMap ?? null;
151
- if (albedoMap) {
152
- this._material.get().setTextureSize(albedoMap.width, albedoMap.height);
196
+ this._kind = kind;
197
+ this._bladeShape = new Float32Array(DEFAULT_BLADE_SHAPE);
198
+ if (kind === 'blade') {
199
+ const material = new ClipmapBladeGrassMaterial(terrain);
200
+ material.colorMap = albedoMap ?? null;
201
+ this._material = new DRef(material);
202
+ } else {
203
+ const material = new ClipmapGrassMaterial(terrain);
204
+ material.albedoTexture = albedoMap ?? null;
205
+ if (albedoMap) {
206
+ material.setTextureSize(albedoMap.width, albedoMap.height);
207
+ }
208
+ this._material = new DRef(material);
153
209
  }
154
210
  this._seed = seed;
155
211
  this._bladeWidth = bladeWidth;
@@ -163,27 +219,402 @@ const MAX_CELLS_PER_TEXEL = 8;
163
219
  this._tilesX = 0;
164
220
  this._tilesZ = 0;
165
221
  this._numBlades = 0;
222
+ this._drawDistance = 0;
223
+ this._farDensity = 1;
224
+ this._numShadowTargets = 0;
225
+ if (!GrassGpuPlacement.isSupported()) {
226
+ this._gpu = null;
227
+ if (kind === 'blade') {
228
+ warnUnsupported("Procedural grass blades (grass layer kind 'blade')", 'WebGPU with indirect draw', 'blade layers are not drawn');
229
+ }
230
+ } else if (kind === 'blade') {
231
+ this._gpu = new GrassGpuPlacement('blade', GrassLayer._getBladeVertexBuffer(), GrassLayer._getBladeIndexBuffer(), BLADE_LOD_INDEX_COUNTS);
232
+ } else {
233
+ this._gpu = new GrassGpuPlacement('card', this._baseVertexBuffer.get(), GrassLayer._getIndexBuffer());
234
+ }
235
+ if (kind === 'blade') {
236
+ this._bladeShape[0] = bladeHeight;
237
+ this._bladeShape[2] = bladeWidth;
238
+ this.bladeMaterial.lodDistance = this._bladeShape[15];
239
+ }
240
+ this._gpu?.setDensity(this._densityWidth, this._densityHeight, this._densityMap);
241
+ this.drawDistance = DEFAULT_DRAW_DISTANCE;
242
+ this.farDensity = DEFAULT_FAR_DENSITY;
166
243
  this.updateTileGrid();
167
244
  }
245
+ /**
246
+ * Distance from the camera beyond which blades of this layer are not drawn. Blades shrink
247
+ * into the ground over the last quarter of it. Zero draws the layer at any distance.
248
+ */ get drawDistance() {
249
+ return this._drawDistance;
250
+ }
251
+ set drawDistance(val) {
252
+ val = Math.max(0, Number(val) || 0);
253
+ if (val !== this._drawDistance) {
254
+ this._drawDistance = val;
255
+ this._material.get().setDrawDistance(val);
256
+ }
257
+ }
258
+ /**
259
+ * Fraction of the blades of this layer still drawn at the draw distance, 1 to draw them all.
260
+ *
261
+ * From a quarter of the draw distance on, blades thin out towards this fraction, each leaving
262
+ * at its own fixed distance, and the remaining ones widen to keep the ground covered. Saves
263
+ * most of the distant grass geometry where blades are placed on the GPU (WebGPU); elsewhere
264
+ * the dropped blades are still drawn, only shrunk to nothing. Has no effect without a draw
265
+ * distance, nor on blade layers, which thin out by their detail levels (see
266
+ * {@link GrassLayer.lodDistance}).
267
+ */ get farDensity() {
268
+ return this._farDensity;
269
+ }
270
+ set farDensity(val) {
271
+ val = Math.min(1, Math.max(0.01, Number(val) || 0));
272
+ if (val !== this._farDensity) {
273
+ this._farDensity = val;
274
+ this._material.get().setFarDensity(val);
275
+ }
276
+ }
277
+ /** Kind of the blades of this layer, fixed at creation */ get kind() {
278
+ return this._kind;
279
+ }
168
280
  /** @internal */ updateMaterial() {
169
281
  this._material.get().uniformChanged();
170
282
  }
171
283
  /**
172
- * Sets the albedo texture of grass blades in this layer
284
+ * Sets the albedo texture of grass blades in this layer: the card texture of card layers, the
285
+ * color map of blade layers (see {@link GrassLayer.colorMap})
173
286
  * @param albedoMap - Albedo texture to set
174
287
  */ setAlbedoMap(albedoMap) {
175
- this._material.get().albedoTexture = albedoMap;
176
- if (albedoMap) {
177
- this._material.get().setTextureSize(albedoMap.width, albedoMap.height);
288
+ const material = this._material.get();
289
+ if (material instanceof ClipmapGrassMaterial) {
290
+ material.albedoTexture = albedoMap;
291
+ if (albedoMap) {
292
+ material.setTextureSize(albedoMap.width, albedoMap.height);
293
+ }
294
+ } else {
295
+ material.colorMap = albedoMap;
178
296
  }
179
297
  }
180
298
  /**
181
299
  * Gets the albedo texture of grass blades in this layer
182
- * @returns - Albedo texture of grass blades in this layer
300
+ * @returns - The card texture of card layers, the color map of blade layers
183
301
  */ getAlbedoMap() {
184
- return this._material.get().albedoTexture;
302
+ const material = this._material.get();
303
+ return material instanceof ClipmapGrassMaterial ? material.albedoTexture : material.colorMap;
304
+ }
305
+ /** @internal */ get bladeMaterial() {
306
+ const material = this._material.get();
307
+ return material instanceof ClipmapBladeGrassMaterial ? material : null;
308
+ }
309
+ /** @internal */ setShape(index, value) {
310
+ if (Number.isFinite(value)) {
311
+ this._bladeShape[index] = value;
312
+ }
313
+ }
314
+ /**
315
+ * How much blade heights vary around the layer's blade height, as a fraction of it.
316
+ * Blade layers only.
317
+ */ get heightRandomness() {
318
+ return this._bladeShape[1];
319
+ }
320
+ set heightRandomness(val) {
321
+ this.setShape(1, Math.min(1, Math.max(0, val)));
322
+ }
323
+ /** How much blade widths vary around the layer's blade width, as a fraction of it. Blade layers only. */ get widthRandomness() {
324
+ return this._bladeShape[3];
325
+ }
326
+ set widthRandomness(val) {
327
+ this.setShape(3, Math.min(1, Math.max(0, val)));
328
+ }
329
+ /**
330
+ * How far the blades lean over: 0 stands them upright, 1 lays their tips on the ground.
331
+ * Blade layers only.
332
+ */ get tilt() {
333
+ return this._bladeShape[4];
334
+ }
335
+ set tilt(val) {
336
+ this.setShape(4, Math.min(1, Math.max(0, val)));
337
+ }
338
+ /** How much the lean varies from blade to blade. Blade layers only. */ get tiltRandomness() {
339
+ return this._bladeShape[5];
340
+ }
341
+ set tiltRandomness(val) {
342
+ this.setShape(5, Math.min(1, Math.max(0, val)));
343
+ }
344
+ /**
345
+ * How much the blades arch: 0 keeps them straight, higher values bow the middle up so the tips
346
+ * droop. Blade layers only.
347
+ */ get bend() {
348
+ return this._bladeShape[6];
349
+ }
350
+ set bend(val) {
351
+ this.setShape(6, Math.min(1, Math.max(-1, val)));
352
+ }
353
+ /** How much the arch varies from blade to blade. Blade layers only. */ get bendRandomness() {
354
+ return this._bladeShape[7];
355
+ }
356
+ set bendRandomness(val) {
357
+ this.setShape(7, Math.min(1, Math.max(0, val)));
358
+ }
359
+ /**
360
+ * Typical size of a clump of blades, in world units. Blades of a clump share their height,
361
+ * direction and color, so the field breaks up into patches this big. Blade layers only.
362
+ */ get clumpSize() {
363
+ return this._bladeShape[8];
364
+ }
365
+ set clumpSize(val) {
366
+ this.setShape(8, Math.max(0.01, val));
367
+ }
368
+ /**
369
+ * How much the height changes from clump to clump: 0 keeps every patch the same height,
370
+ * higher values give taller and shorter patches. Blade layers only.
371
+ */ get clumpHeightVariation() {
372
+ return this._bladeShape[9];
373
+ }
374
+ set clumpHeightVariation(val) {
375
+ this.setShape(9, Math.min(1, Math.max(0, val)));
376
+ }
377
+ /**
378
+ * How much the blades of a clump gather toward its center: 0 spreads them evenly, 1 bunches
379
+ * them into tufts. Blade layers only.
380
+ */ get clumpPull() {
381
+ return this._bladeShape[10];
382
+ }
383
+ set clumpPull(val) {
384
+ this.setShape(10, Math.min(1, Math.max(0, val)));
385
+ }
386
+ /**
387
+ * How much the blades of a clump lean the same way: 0 lets each blade face its own random way,
388
+ * 1 combs the whole patch in one direction. Blade layers only.
389
+ */ get clumpSameDirection() {
390
+ return this._bladeShape[11];
391
+ }
392
+ set clumpSameDirection(val) {
393
+ this.setShape(11, Math.min(1, Math.max(0, val)));
394
+ }
395
+ /**
396
+ * How much the blades lean out from the center of their clump, so each patch splays open like a
397
+ * tuft. Blade layers only.
398
+ */ get clumpFaceAway() {
399
+ return this._bladeShape[12];
400
+ }
401
+ set clumpFaceAway(val) {
402
+ this.setShape(12, Math.min(1, Math.max(0, val)));
403
+ }
404
+ /**
405
+ * How much the brightness changes from clump to clump, giving the field a patchy look.
406
+ * Blade layers only.
407
+ */ get clumpColorVariation() {
408
+ return this.bladeMaterial?.clumpColorVariation ?? 0;
409
+ }
410
+ set clumpColorVariation(val) {
411
+ const material = this.bladeMaterial;
412
+ if (material) {
413
+ material.clumpColorVariation = val;
414
+ }
415
+ }
416
+ /**
417
+ * Optional color texture of the blades: V runs from the root (0) to the tip (1), U picks a
418
+ * column per clump. Replaces the root and tip colors when set. Blade layers only.
419
+ */ get colorMap() {
420
+ return this.bladeMaterial?.colorMap ?? null;
421
+ }
422
+ set colorMap(val) {
423
+ const material = this.bladeMaterial;
424
+ if (material) {
425
+ material.colorMap = val;
426
+ }
427
+ }
428
+ /**
429
+ * How much the ambient light darkens toward the root, where the neighboring blades hide the
430
+ * sky: 0 lights the whole blade evenly, 1 leaves the root black. Blade layers only.
431
+ */ get rootOcclusion() {
432
+ return this.bladeMaterial?.rootOcclusion ?? 0;
433
+ }
434
+ set rootOcclusion(val) {
435
+ const material = this.bladeMaterial;
436
+ if (material) {
437
+ material.rootOcclusion = val;
438
+ }
439
+ }
440
+ /**
441
+ * How much the blades narrow toward the tip: 0 keeps them wide to the end, 1 narrows them evenly
442
+ * to a point. Blade layers only.
443
+ */ get taper() {
444
+ return this.bladeMaterial?.taper ?? 0;
445
+ }
446
+ set taper(val) {
447
+ const material = this.bladeMaterial;
448
+ if (material) {
449
+ material.taper = val;
450
+ }
451
+ }
452
+ /**
453
+ * Spends more of each blade's vertices near the tip, where it curves most. 1 spaces them
454
+ * evenly; higher values give smoother drooping tips. Blade layers only.
455
+ */ get tipDetail() {
456
+ return this.bladeMaterial?.tipDetail ?? 1;
457
+ }
458
+ set tipDetail(val) {
459
+ const material = this.bladeMaterial;
460
+ if (material) {
461
+ material.tipDetail = val;
462
+ }
463
+ }
464
+ /**
465
+ * How much the blade normals tilt outward across the width, so flat blades shade as if
466
+ * curved. 0 shades them flat. Blade layers only.
467
+ */ get roundness() {
468
+ return this.bladeMaterial?.roundness ?? 0;
469
+ }
470
+ set roundness(val) {
471
+ const material = this.bladeMaterial;
472
+ if (material) {
473
+ material.roundness = val;
474
+ }
475
+ }
476
+ /**
477
+ * How much blades seen edge-on widen toward the viewer, keeping fields full from low
478
+ * angles. 0 disables it. Blade layers only.
479
+ */ get viewThickening() {
480
+ return this.bladeMaterial?.viewThickening ?? 0;
481
+ }
482
+ set viewThickening(val) {
483
+ const material = this.bladeMaterial;
484
+ if (material) {
485
+ material.viewThickening = val;
486
+ }
487
+ }
488
+ /**
489
+ * Distance at which the blades start shading as one surface per clump, which calms the
490
+ * glitter of distant fields. Blade layers only.
491
+ */ get farNormalStart() {
492
+ return this.bladeMaterial?.farNormalStart ?? 0;
493
+ }
494
+ set farNormalStart(val) {
495
+ const material = this.bladeMaterial;
496
+ if (material) {
497
+ material.farNormalStart = val;
498
+ }
499
+ }
500
+ /**
501
+ * Distance beyond which the blades shade fully as one surface per clump. Blade layers only.
502
+ */ get farNormalEnd() {
503
+ return this.bladeMaterial?.farNormalEnd ?? 0;
504
+ }
505
+ set farNormalEnd(val) {
506
+ const material = this.bladeMaterial;
507
+ if (material) {
508
+ material.farNormalEnd = val;
509
+ }
510
+ }
511
+ /**
512
+ * How dull the highlights of distant blades get, reached at the far normal end distance. Blade layers only.
513
+ */ get farRoughness() {
514
+ return this.bladeMaterial?.farRoughness ?? 0;
515
+ }
516
+ set farRoughness(val) {
517
+ const material = this.bladeMaterial;
518
+ if (material) {
519
+ material.farRoughness = val;
520
+ }
521
+ }
522
+ /**
523
+ * Minimum width of a blade on screen, in pixels: distant blades are widened to it instead of
524
+ * thinning below a pixel, which makes them flicker. Larger values calm the flicker more but make
525
+ * distant fields look thicker. 0 disables it. Blade layers only.
526
+ */ get minScreenWidth() {
527
+ return this.bladeMaterial?.minScreenWidth ?? 0;
528
+ }
529
+ set minScreenWidth(val) {
530
+ const material = this.bladeMaterial;
531
+ if (material) {
532
+ material.minScreenWidth = val;
533
+ }
534
+ }
535
+ /**
536
+ * How much the wind turns the blades to lean downwind, per unit of wind push: 0 keeps their own
537
+ * directions, higher values comb the field along the wind. Blade layers only.
538
+ */ get windFacing() {
539
+ return this._bladeShape[13];
540
+ }
541
+ set windFacing(val) {
542
+ this.setShape(13, Math.min(4, Math.max(0, val)));
543
+ }
544
+ /**
545
+ * How far the wind lays the blades over, per unit of wind push: 0 leaves them standing, higher
546
+ * values flatten them in strong gusts.
547
+ */ get windLean() {
548
+ return this._material.get().windLean;
549
+ }
550
+ set windLean(val) {
551
+ this._material.get().windLean = val;
552
+ }
553
+ /** How far the blade tips bob in the wind, as a fraction of their height per unit of wind push */ get swayAmplitude() {
554
+ return this._material.get().swayAmplitude;
555
+ }
556
+ set swayAmplitude(val) {
557
+ const material = this._material.get();
558
+ material.swayAmplitude = val;
559
+ this.setShape(14, material.swayAmplitude);
560
+ }
561
+ /**
562
+ * Distance from the camera at which blades switch to their low detail level: a quarter as many
563
+ * blades, each four times as wide and with fewer vertices, blending in over the last quarter of this
564
+ * distance. 0 keeps every blade at high detail up to the draw distance. Blade layers only.
565
+ */ get lodDistance() {
566
+ return this._bladeShape[15];
185
567
  }
186
- /** How many grass blades are currently generated in this layer */ get numBlades() {
568
+ set lodDistance(val) {
569
+ val = Math.max(0, Number(val) || 0);
570
+ this.setShape(15, val);
571
+ const material = this.bladeMaterial;
572
+ if (material) {
573
+ material.lodDistance = val;
574
+ }
575
+ }
576
+ /** How fast the blades bob in the wind, in radians per second */ get swaySpeed() {
577
+ return this._material.get().swaySpeed;
578
+ }
579
+ set swaySpeed(val) {
580
+ this._material.get().swaySpeed = val;
581
+ }
582
+ /** Color at the root of the blades. Blade layers only. */ get rootColor() {
583
+ return this.bladeMaterial?.rootColor ?? Vector4.one();
584
+ }
585
+ set rootColor(val) {
586
+ const material = this.bladeMaterial;
587
+ if (material) {
588
+ material.rootColor = val;
589
+ }
590
+ }
591
+ /** Color at the tip of the blades. Blade layers only. */ get tipColor() {
592
+ return this.bladeMaterial?.tipColor ?? Vector4.one();
593
+ }
594
+ set tipColor(val) {
595
+ const material = this.bladeMaterial;
596
+ if (material) {
597
+ material.tipColor = val;
598
+ }
599
+ }
600
+ /**
601
+ * Color of the light the blades let through when lit from behind, strongest at the tips, so a
602
+ * field glows against a low sun. Black lets no light through. Blade layers only.
603
+ */ get transmissionColor() {
604
+ return this.bladeMaterial?.transmissionColor ?? Vector4.zero();
605
+ }
606
+ set transmissionColor(val) {
607
+ const material = this.bladeMaterial;
608
+ if (material) {
609
+ material.transmissionColor = val;
610
+ }
611
+ }
612
+ /**
613
+ * How many grass blades are currently generated in this layer.
614
+ *
615
+ * Only counts the CPU placement path. Where blades are placed on the GPU (WebGPU) they are
616
+ * generated around the camera every frame and this stays 0.
617
+ */ get numBlades() {
187
618
  return this._numBlades;
188
619
  }
189
620
  /**
@@ -237,6 +668,13 @@ const MAX_CELLS_PER_TEXEL = 8;
237
668
  * @param maxTexelX - Maximum x texel of the region (exclusive)
238
669
  * @param maxTexelZ - Maximum z texel of the region (exclusive)
239
670
  */ updateDensityRegion(minTexelX, minTexelZ, maxTexelX, maxTexelZ) {
671
+ if (this._kind === 'blade' && !this._gpu) {
672
+ return;
673
+ }
674
+ if (this._gpu) {
675
+ this._gpu.updateDensityRegion(this._densityMap, this._densityWidth, Math.max(0, minTexelX), Math.max(0, minTexelZ), Math.min(this._densityWidth, maxTexelX), Math.min(this._densityHeight, maxTexelZ));
676
+ return;
677
+ }
240
678
  const k = this._cellsPerTexel;
241
679
  // expand by one texel to cover the bilinear sampling footprint
242
680
  const cx0 = Math.max(0, (minTexelX - 1) * k);
@@ -265,6 +703,13 @@ const MAX_CELLS_PER_TEXEL = 8;
265
703
  this._tiles.clear();
266
704
  this._numBlades = 0;
267
705
  this.updateTileGrid();
706
+ if (this._gpu) {
707
+ this._gpu.setDensity(this._densityWidth, this._densityHeight, this._densityMap);
708
+ return;
709
+ }
710
+ if (this._kind === 'blade') {
711
+ return;
712
+ }
268
713
  for(let tz = 0; tz < this._tilesZ; tz++){
269
714
  for(let tx = 0; tx < this._tilesX; tx++){
270
715
  this.generateTile(tx, tz);
@@ -291,10 +736,16 @@ const MAX_CELLS_PER_TEXEL = 8;
291
736
  if (width !== this._bladeWidth || height !== this._bladeHeight) {
292
737
  this._bladeWidth = width;
293
738
  this._bladeHeight = height;
739
+ if (this._kind === 'blade') {
740
+ this._bladeShape[0] = height;
741
+ this._bladeShape[2] = width;
742
+ return;
743
+ }
294
744
  this._baseVertexBuffer.set(this.createBaseVertexBuffer(this._bladeWidth, this._bladeHeight));
295
745
  for (const tile of this._tiles.values()){
296
746
  tile.setBaseVertexBuffer(this._baseVertexBuffer.get());
297
747
  }
748
+ this._gpu?.setBaseVertexBuffer(this._baseVertexBuffer.get());
298
749
  }
299
750
  }
300
751
  /** @internal */ updateTileGrid() {
@@ -354,14 +805,15 @@ const MAX_CELLS_PER_TEXEL = 8;
354
805
  let count = 0;
355
806
  for(let cz = cz0; cz < cz1; cz++){
356
807
  for(let cx = cx0; cx < cx1; cx++){
357
- const u = (cx + hashCell(cx, cz, seed)) / cw;
358
- const v = (cz + hashCell(cx, cz, seed + 1)) / ch;
359
- if (this.sampleDensity(u, v) > hashCell(cx, cz, seed + 2)) {
360
- const angle = hashCell(cx, cz, seed + 3) * Math.PI * 2;
808
+ const u = (cx + grassHash(cx, cz, seed)) / cw;
809
+ const v = (cz + grassHash(cx, cz, seed + 1)) / ch;
810
+ if (this.sampleDensity(u, v) > grassHash(cx, cz, seed + 2)) {
811
+ const angle = grassHash(cx, cz, seed + 3) * Math.PI * 2;
361
812
  data[count * 4 + 0] = u;
362
813
  data[count * 4 + 1] = v;
363
- data[count * 4 + 2] = Math.sin(angle);
364
- data[count * 4 + 3] = Math.cos(angle);
814
+ data[count * 4 + 2] = angle;
815
+ // Density LOD hash, see ClipmapGrassMaterial
816
+ data[count * 4 + 3] = grassHash(cx, cz, seed + 4);
365
817
  count++;
366
818
  }
367
819
  }
@@ -405,6 +857,22 @@ const MAX_CELLS_PER_TEXEL = 8;
405
857
  }
406
858
  return this._indexBuffer.get();
407
859
  }
860
+ /** @internal */ static _getBladeIndexBuffer() {
861
+ if (!this._bladeIndexBuffer.get()) {
862
+ this._bladeIndexBuffer.set(getDevice().createIndexBuffer(createBladeIndices()));
863
+ }
864
+ return this._bladeIndexBuffer.get();
865
+ }
866
+ /**
867
+ * Procedural blades read nothing from their vertex buffer, but a draw still needs a vertex
868
+ * layout, which one vertex establishes
869
+ * @internal
870
+ */ static _getBladeVertexBuffer() {
871
+ if (!this._bladeVertexBuffer.get()) {
872
+ this._bladeVertexBuffer.set(getDevice().createVertexBuffer('position_f32x3', new Float32Array(3)));
873
+ }
874
+ return this._bladeVertexBuffer.get();
875
+ }
408
876
  /** @internal */ createBaseVertexBuffer(bladeWidth, bladeHeight) {
409
877
  const device = getDevice();
410
878
  const r = bladeWidth * 0.5;
@@ -478,7 +946,50 @@ const MAX_CELLS_PER_TEXEL = 8;
478
946
  'tex0_f32x2'
479
947
  ], vertices);
480
948
  }
949
+ /**
950
+ * Places the blades seen from a camera on the GPU path; nothing to do on the CPU one.
951
+ * @param shadowCullPlanes - For each shadow map the blades cast shadows into, the planes
952
+ * bounding where its casters can be (see GrassRenderer); blade layers only
953
+ * @param shadowFade - View depths over which the shadow casting blades thin out
954
+ * @internal
955
+ */ updatePerCamera(camera, terrain, occlusionMode, shadowCullPlanes, shadowFade) {
956
+ if (!this._gpu) {
957
+ return;
958
+ }
959
+ const wind = terrain.scene?.env.wind.shaderParams ?? null;
960
+ const place = (target, mode, planes, fade)=>this._gpu.generate(camera, terrain, this._seed, this._cellsPerTexel, this._drawDistance, this._bladeWidth, this._bladeHeight, mode, this._farDensity, this._bladeShape, wind, target, planes, fade);
961
+ place(0, occlusionMode, null, null);
962
+ // Target i + 1 holds the casters of shadow map i. Blades hidden behind the terrain from the
963
+ // camera still cast shadows, so these skip the occlusion culling.
964
+ const numShadowTargets = this._kind === 'blade' ? shadowCullPlanes.length : 0;
965
+ for(let i = 0; i < numShadowTargets; i++){
966
+ place(i + 1, GrassOcclusionMode.Off, shadowCullPlanes[i], shadowFade);
967
+ }
968
+ this._gpu.trimTargets(numShadowTargets + 1);
969
+ this._numShadowTargets = numShadowTargets;
970
+ }
971
+ /**
972
+ * Draws the shadow casters of the shadow map the blades were placed for by the last
973
+ * updatePerCamera() call
974
+ * @param shadowMap - Index of the shadow map
975
+ * @internal
976
+ */ drawShadow(ctx, shadowMap) {
977
+ if (shadowMap < this._numShadowTargets) {
978
+ this.drawGpu(ctx, shadowMap + 1);
979
+ }
980
+ }
981
+ /** @internal */ setOcclusionDebug(val) {
982
+ this._material.get().occlusionDebug = val;
983
+ }
481
984
  /** @internal */ draw(ctx, region, minY, maxY) {
985
+ if (this._kind === 'blade' && !this._gpu) {
986
+ return;
987
+ }
988
+ if (this._gpu) {
989
+ this.drawGpu(ctx, 0);
990
+ return;
991
+ }
992
+ const wind = ctx.scene?.env.wind ?? null;
482
993
  const visible = GrassLayer._visibleTiles;
483
994
  visible.length = 0;
484
995
  const camera = ctx.camera;
@@ -489,6 +1000,10 @@ const MAX_CELLS_PER_TEXEL = 8;
489
1000
  const rz = region.y;
490
1001
  const rw = region.z - region.x;
491
1002
  const rh = region.w - region.y;
1003
+ const cameraPos = camera.getWorldPosition(GrassLayer._cameraPos);
1004
+ const maxDistSq = this._drawDistance > 0 ? this._drawDistance * this._drawDistance : Infinity;
1005
+ // The wind can lay the cards over by up to their height
1006
+ const windPad = wind && wind.strength > 0 ? this._bladeHeight : 0;
492
1007
  for (const [key, tile] of this._tiles){
493
1008
  const tx = key % this._tilesX;
494
1009
  const tz = (key - tx) / this._tilesX;
@@ -496,8 +1011,11 @@ const MAX_CELLS_PER_TEXEL = 8;
496
1011
  const v0 = tz * TILE_CELLS / cellsH;
497
1012
  const u1 = Math.min(1, (tx + 1) * TILE_CELLS / cellsW);
498
1013
  const v1 = Math.min(1, (tz + 1) * TILE_CELLS / cellsH);
499
- cullAABB.minPoint.setXYZ(rx + u0 * rw, minY, rz + v0 * rh);
500
- cullAABB.maxPoint.setXYZ(rx + u1 * rw, maxY, rz + v1 * rh);
1014
+ cullAABB.minPoint.setXYZ(rx + u0 * rw - windPad, minY, rz + v0 * rh - windPad);
1015
+ cullAABB.maxPoint.setXYZ(rx + u1 * rw + windPad, maxY, rz + v1 * rh + windPad);
1016
+ if (distanceSqToAABB(cameraPos.x, cameraPos.y, cameraPos.z, cullAABB) > maxDistSq) {
1017
+ continue;
1018
+ }
501
1019
  const clipState = camera.clipMask ? cullAABB.getClipStateWithFrustumMask(camera.frustum, camera.clipMask) : cullAABB.getClipStateWithFrustum(camera.frustum);
502
1020
  if (clipState !== ClipState.NOT_CLIPPED) {
503
1021
  visible.push(tile);
@@ -506,15 +1024,41 @@ const MAX_CELLS_PER_TEXEL = 8;
506
1024
  if (visible.length === 0) {
507
1025
  return;
508
1026
  }
509
- this._material.get().apply(ctx);
510
- for(let pass = 0; pass < this._material.get().numPasses; pass++){
511
- this._material.get().bind(ctx.device, pass);
1027
+ const material = this._material.get();
1028
+ material.prepareDraw(wind);
1029
+ material.apply(ctx);
1030
+ for(let pass = 0; pass < material.numPasses; pass++){
1031
+ material.bind(ctx.device, pass);
512
1032
  for (const tile of visible){
513
1033
  tile.draw();
514
1034
  }
515
1035
  }
516
1036
  visible.length = 0;
517
1037
  }
1038
+ /** Draws the blades placed on the GPU into a target, see GrassGpuPlacement */ drawGpu(ctx, target) {
1039
+ // From the scene, not ctx.env: the depth pass clears ctx.env, and the depth prepass and the
1040
+ // light pass must bend the grass by the same wind or the light pass's equal depth test
1041
+ // rejects it
1042
+ const wind = ctx.scene?.env.wind ?? null;
1043
+ const gpu = this._gpu;
1044
+ const material = this._material.get();
1045
+ if (material instanceof ClipmapBladeGrassMaterial) {
1046
+ const instances = gpu.getInstanceBuffer(target);
1047
+ if (!instances) {
1048
+ return;
1049
+ }
1050
+ material.setInstanceBuffer(instances, gpu.getLowListBase(target));
1051
+ // The camera the blades were placed for, also in the shadow map passes
1052
+ material.prepareDraw(ctx.camera, wind, ctx.renderPass.type === RENDER_PASS_TYPE_SHADOWMAP ? 0 : ctx.renderHeight);
1053
+ } else {
1054
+ material.prepareDraw(wind);
1055
+ }
1056
+ material.apply(ctx);
1057
+ for(let pass = 0; pass < material.numPasses; pass++){
1058
+ material.bind(ctx.device, pass);
1059
+ gpu.draw(target);
1060
+ }
1061
+ }
518
1062
  /** @internal */ onDispose() {
519
1063
  super.onDispose();
520
1064
  this._material.dispose();
@@ -523,14 +1067,43 @@ const MAX_CELLS_PER_TEXEL = 8;
523
1067
  }
524
1068
  this._tiles.clear();
525
1069
  this._baseVertexBuffer.dispose();
1070
+ this._gpu?.dispose();
526
1071
  }
527
1072
  }
528
1073
  /**
529
1074
  * Grass renderer for clipmap terrain
530
1075
  * @public
531
1076
  */ class GrassRenderer extends Disposable {
1077
+ static _lightDir = new Vector3();
1078
+ static _forward = new Vector3();
1079
+ static _eye = new Vector3();
1080
+ static _center = new Vector3();
1081
+ static _tmp = new Vector3();
1082
+ static _edge0 = new Vector3();
1083
+ static _edge1 = new Vector3();
1084
+ static _normal = new Vector3();
1085
+ static _corners = Array.from({
1086
+ length: 8
1087
+ }, ()=>new Vector3());
1088
+ static _faceDots = [
1089
+ 0,
1090
+ 0,
1091
+ 0,
1092
+ 0,
1093
+ 0,
1094
+ 0
1095
+ ];
1096
+ static _facePlane = new Float32Array(4);
1097
+ static _shadowCullPlanes = [];
532
1098
  _terrain;
533
1099
  _layers;
1100
+ _occlusionCulling;
1101
+ _occlusionDebug;
1102
+ _suspendOcclusionCulling;
1103
+ _castShadow;
1104
+ _shadowFadeFraction;
1105
+ /** View depths over which the blades casting shadows thin out, see calcShadowCullPlanes */ _shadowFade;
1106
+ /** The light whose shadow map casters the blades were last placed for */ _shadowLight;
534
1107
  /**
535
1108
  * Creates an instance of GrassRenderer
536
1109
  * @param terrain - Clipmap terrain object
@@ -538,12 +1111,226 @@ const MAX_CELLS_PER_TEXEL = 8;
538
1111
  super();
539
1112
  this._terrain = new DWeakRef(terrain);
540
1113
  this._layers = [];
1114
+ this._occlusionCulling = true;
1115
+ this._occlusionDebug = false;
1116
+ this._suspendOcclusionCulling = false;
1117
+ this._castShadow = true;
1118
+ this._shadowFadeFraction = 0.5;
1119
+ this._shadowFade = null;
1120
+ this._shadowLight = new DWeakRef();
1121
+ }
1122
+ /**
1123
+ * Whether to skip grass hidden behind the terrain itself (WebGPU only).
1124
+ *
1125
+ * Tested against the terrain height field every frame, so it never culls grass that is
1126
+ * visible, but it does not see occluders other than the terrain.
1127
+ */ get occlusionCulling() {
1128
+ return this._occlusionCulling;
1129
+ }
1130
+ set occlusionCulling(val) {
1131
+ this._occlusionCulling = !!val;
1132
+ }
1133
+ /**
1134
+ * Draws the grass the terrain occlusion culling would skip in red instead of skipping it
1135
+ */ get occlusionDebug() {
1136
+ return this._occlusionDebug;
1137
+ }
1138
+ set occlusionDebug(val) {
1139
+ this._occlusionDebug = !!val;
1140
+ for (const layer of this._layers){
1141
+ layer.setOcclusionDebug(this._occlusionDebug);
1142
+ }
1143
+ }
1144
+ /**
1145
+ * Pauses the terrain occlusion culling while the height map is being edited, since the
1146
+ * height pyramid it relies on is only rebuilt afterwards.
1147
+ * @internal
1148
+ */ get suspendOcclusionCulling() {
1149
+ return this._suspendOcclusionCulling;
1150
+ }
1151
+ set suspendOcclusionCulling(val) {
1152
+ this._suspendOcclusionCulling = !!val;
541
1153
  }
542
1154
  /** @internal */ updateMaterial() {
543
1155
  for (const layer of this._layers){
544
1156
  layer.updateMaterial();
545
1157
  }
546
1158
  }
1159
+ /**
1160
+ * Whether procedural blades cast shadows from the sun into the nearest cascades of its shadow
1161
+ * map (WebGPU only). Also needs the terrain to cast shadows.
1162
+ */ get castShadow() {
1163
+ return this._castShadow;
1164
+ }
1165
+ set castShadow(val) {
1166
+ this._castShadow = !!val;
1167
+ }
1168
+ /**
1169
+ * Fraction of the last shadow cascade the blades cast shadows into over which their shadows
1170
+ * fade out toward its far end, blade by blade. 0 ends them on a line at the far end.
1171
+ */ get shadowFadeFraction() {
1172
+ return this._shadowFadeFraction;
1173
+ }
1174
+ set shadowFadeFraction(val) {
1175
+ this._shadowFadeFraction = Math.min(1, Math.max(0, Number(val) || 0));
1176
+ }
1177
+ /** @internal */ updatePerCamera(camera) {
1178
+ const terrain = this._terrain.get();
1179
+ if (terrain) {
1180
+ const mode = !this._occlusionCulling || this._suspendOcclusionCulling ? GrassOcclusionMode.Off : this._occlusionDebug ? GrassOcclusionMode.Debug : GrassOcclusionMode.Cull;
1181
+ const shadowCullPlanes = this.calcShadowCullPlanes(camera, terrain);
1182
+ for (const layer of this._layers){
1183
+ layer.updatePerCamera(camera, terrain, mode, shadowCullPlanes, this._shadowFade);
1184
+ }
1185
+ }
1186
+ }
1187
+ /**
1188
+ * Culling planes of the blades casting shadows into each of the nearest cascades of the sun's
1189
+ * shadow map, and remembers the sun as the light the blades were placed for.
1190
+ */ calcShadowCullPlanes(camera, terrain) {
1191
+ this._shadowLight.set(null);
1192
+ this._shadowFade = null;
1193
+ const scene = terrain.scene;
1194
+ const sun = scene ? DirectionalLight.getSunLight(scene) : null;
1195
+ if (!this._castShadow || !terrain.castShadow || !sun || !sun.castShadow || !GrassGpuPlacement.isSupported() || !this._layers.some((layer)=>layer.kind === 'blade')) {
1196
+ return [];
1197
+ }
1198
+ this._shadowLight.set(sun);
1199
+ const distances = sun.shadow.getCascadeDistances(camera);
1200
+ const numCascades = Math.min(GRASS_SHADOW_CASCADES, distances.length - 1);
1201
+ // The blades thin out over the far part of the last cascade they cast shadows into, so the
1202
+ // shadowed ground does not end on a line at its far split
1203
+ const fadeEnd = distances[numCascades];
1204
+ const fadeStart = fadeEnd - (fadeEnd - distances[numCascades - 1]) * this._shadowFadeFraction;
1205
+ this._shadowFade = fadeEnd > fadeStart ? [
1206
+ fadeStart,
1207
+ fadeEnd
1208
+ ] : null;
1209
+ // Direction the light travels in
1210
+ const lightDir = GrassRenderer._lightDir;
1211
+ lightDir.set(sun.directionAndCutoff.xyz());
1212
+ lightDir.inplaceNormalize();
1213
+ const m = camera.worldMatrix;
1214
+ // Camera forward, the view axis the split distances are measured along
1215
+ const forward = GrassRenderer._forward.setXYZ(-m.m02, -m.m12, -m.m22).inplaceNormalize();
1216
+ const eye = camera.getWorldPosition(GrassRenderer._eye);
1217
+ const perspective = camera.isPerspective();
1218
+ const nearCorners = camera.frustum.corners;
1219
+ const corners = GrassRenderer._corners;
1220
+ const center = GrassRenderer._center;
1221
+ const tmp = GrassRenderer._tmp;
1222
+ const result = [];
1223
+ for(let i = 0; i < numCascades; i++){
1224
+ // Corners of the slice, indexed as the frustum corners: x << 2 | y << 1 | far. Each lies on
1225
+ // the ray through the matching near plane corner, at the split distance along the view axis.
1226
+ center.setXYZ(0, 0, 0);
1227
+ for(let c = 0; c < 8; c++){
1228
+ const near = nearCorners[c & 6];
1229
+ const d = distances[i + (c & 1)];
1230
+ Vector3.sub(near, eye, tmp);
1231
+ const depth = Vector3.dot(tmp, forward);
1232
+ if (perspective) {
1233
+ Vector3.add(eye, Vector3.scale(tmp, d / depth, tmp), corners[c]);
1234
+ } else {
1235
+ Vector3.add(near, Vector3.scale(forward, d - depth, tmp), corners[c]);
1236
+ }
1237
+ Vector3.add(center, corners[c], center);
1238
+ }
1239
+ center.scaleBy(1 / 8);
1240
+ // Faces of the slice: the four corners sharing one bit, normals pointing inward
1241
+ const faceDots = GrassRenderer._faceDots;
1242
+ const planes = GrassRenderer._shadowCullPlanes[i] ??= new Float32Array(GRASS_CULL_PLANES * 4);
1243
+ let numPlanes = 0;
1244
+ const addPlane = (a, b, c)=>{
1245
+ if (numPlanes < GRASS_CULL_PLANES) {
1246
+ if (GrassRenderer.planeThrough(a, b, c, center, planes, numPlanes * 4)) {
1247
+ numPlanes++;
1248
+ }
1249
+ }
1250
+ };
1251
+ for(let face = 0; face < 6; face++){
1252
+ const bit = 1 << (face >> 1);
1253
+ const side = face & 1 ? bit : 0;
1254
+ const others = [
1255
+ 0,
1256
+ 1,
1257
+ 2,
1258
+ 3
1259
+ ].map((k)=>{
1260
+ // The two free bits of the face, in order
1261
+ const free = [
1262
+ 1,
1263
+ 2,
1264
+ 4
1265
+ ].filter((b)=>b !== bit);
1266
+ return side | (k & 1 ? free[0] : 0) | (k & 2 ? free[1] : 0);
1267
+ });
1268
+ GrassRenderer.planeThrough(corners[others[0]], corners[others[1]], corners[others[3]], center, GrassRenderer._facePlane, 0);
1269
+ const facePlane = GrassRenderer._facePlane;
1270
+ faceDots[face] = facePlane[0] * lightDir.x + facePlane[1] * lightDir.y + facePlane[2] * lightDir.z;
1271
+ // Faces the light travels away from, or along, bound the swept slice
1272
+ if (faceDots[face] <= 0 && numPlanes < GRASS_CULL_PLANES) {
1273
+ planes.set(facePlane, numPlanes * 4);
1274
+ numPlanes++;
1275
+ }
1276
+ }
1277
+ // Silhouette edges: shared by a face kept and a face dropped. Two faces are adjacent when
1278
+ // they fix different corner bits; their edge is the two corners with both bits fixed.
1279
+ for(let fa = 0; fa < 6; fa++){
1280
+ for(let fb = fa + 1; fb < 6; fb++){
1281
+ const bitA = 1 << (fa >> 1);
1282
+ const bitB = 1 << (fb >> 1);
1283
+ const keptA = faceDots[fa] <= 0;
1284
+ const keptB = faceDots[fb] <= 0;
1285
+ if (bitA === bitB || keptA === keptB) {
1286
+ continue;
1287
+ }
1288
+ const fixed = (fa & 1 ? bitA : 0) | (fb & 1 ? bitB : 0);
1289
+ const freeBit = 7 & ~bitA & ~bitB;
1290
+ const a = corners[fixed];
1291
+ const b = corners[fixed | freeBit];
1292
+ // The third point extends the edge along the light, scaled by the edge length for
1293
+ Vector3.add(a, Vector3.scale(lightDir, Vector3.distance(a, b), tmp), tmp);
1294
+ addPlane(a, b, tmp);
1295
+ }
1296
+ }
1297
+ // The rest are planes everything is inside of
1298
+ for(let k = numPlanes; k < GRASS_CULL_PLANES; k++){
1299
+ planes.set([
1300
+ 0,
1301
+ 0,
1302
+ 0,
1303
+ 1e30
1304
+ ], k * 4);
1305
+ }
1306
+ result.push(planes);
1307
+ }
1308
+ return result;
1309
+ }
1310
+ /**
1311
+ * Writes the plane through three points, normal turned toward an inside point, as (a, b, c, d)
1312
+ * at an offset of an array
1313
+ * @returns false if the points are degenerate
1314
+ */ static planeThrough(p0, p1, p2, inside, out, offset) {
1315
+ const e0 = Vector3.sub(p1, p0, GrassRenderer._edge0);
1316
+ const e1 = Vector3.sub(p2, p0, GrassRenderer._edge1);
1317
+ const n = Vector3.cross(e0, e1, GrassRenderer._normal);
1318
+ const len = n.magnitude;
1319
+ if (len < 1e-12) {
1320
+ return false;
1321
+ }
1322
+ n.scaleBy(1 / len);
1323
+ let d = -Vector3.dot(n, p0);
1324
+ if (Vector3.dot(n, inside) + d < 0) {
1325
+ n.scaleBy(-1);
1326
+ d = -d;
1327
+ }
1328
+ out[offset] = n.x;
1329
+ out[offset + 1] = n.y;
1330
+ out[offset + 2] = n.z;
1331
+ out[offset + 3] = d;
1332
+ return true;
1333
+ }
547
1334
  /** How many grass blades */ get numGrassBlades() {
548
1335
  return this._layers.reduce((sum, layer)=>sum + layer.numBlades, 0);
549
1336
  }
@@ -561,10 +1348,12 @@ const MAX_CELLS_PER_TEXEL = 8;
561
1348
  * Adds a grass layer
562
1349
  * @param bladeWidth - Width of grass blades in this layer
563
1350
  * @param bladeHeight - Height of grass blades in this layer
564
- * @param albedoMap - Albedo texture of grass blades in this layer
1351
+ * @param albedoMap - Albedo texture of grass blades in this layer, card layers only
1352
+ * @param kind - Textured cards (default), or procedural blades drawn on WebGPU only
565
1353
  * @returns Index of the added grass layer
566
- */ addLayer(bladeWidth, bladeHeight, albedoMap) {
567
- const layer = new GrassLayer(this._terrain.get(), this._layers.length, bladeWidth, bladeHeight, albedoMap);
1354
+ */ addLayer(bladeWidth, bladeHeight, albedoMap, kind = 'card') {
1355
+ const layer = new GrassLayer(this._terrain.get(), this._layers.length, bladeWidth, bladeHeight, albedoMap, kind);
1356
+ layer.setOcclusionDebug(this._occlusionDebug);
568
1357
  this._layers.push(layer);
569
1358
  return this._layers.length - 1;
570
1359
  }
@@ -588,6 +1377,44 @@ const MAX_CELLS_PER_TEXEL = 8;
588
1377
  }
589
1378
  }
590
1379
  /**
1380
+ * Gets the draw distance of the grass layer at given index
1381
+ * @param layer - Index of the grass layer
1382
+ * @returns Distance from the camera beyond which the layer is not drawn, 0 for unlimited
1383
+ */ getDrawDistance(layer) {
1384
+ return this._layers[layer]?.drawDistance ?? 0;
1385
+ }
1386
+ /**
1387
+ * Sets the draw distance of the grass layer at given index
1388
+ * @param layer - Index of the grass layer
1389
+ * @param distance - Distance from the camera beyond which the layer is not drawn, 0 for unlimited
1390
+ */ setDrawDistance(layer, distance) {
1391
+ const grassLayer = this._layers[layer];
1392
+ if (grassLayer) {
1393
+ grassLayer.drawDistance = distance;
1394
+ } else {
1395
+ console.error(`Invalid grass layer: ${layer}`);
1396
+ }
1397
+ }
1398
+ /**
1399
+ * Gets the fraction of blades kept at the draw distance for the grass layer at given index
1400
+ * @param layer - Index of the grass layer
1401
+ * @returns Fraction of blades drawn at the draw distance, see GrassLayer.farDensity
1402
+ */ getFarDensity(layer) {
1403
+ return this._layers[layer]?.farDensity ?? 1;
1404
+ }
1405
+ /**
1406
+ * Sets the fraction of blades kept at the draw distance for the grass layer at given index
1407
+ * @param layer - Index of the grass layer
1408
+ * @param farDensity - Fraction of blades drawn at the draw distance, see GrassLayer.farDensity
1409
+ */ setFarDensity(layer, farDensity) {
1410
+ const grassLayer = this._layers[layer];
1411
+ if (grassLayer) {
1412
+ grassLayer.farDensity = farDensity;
1413
+ } else {
1414
+ console.error(`Invalid grass layer: ${layer}`);
1415
+ }
1416
+ }
1417
+ /**
591
1418
  * Gets width of the grass blades in the grass layer at given index
592
1419
  * @param layer - Index of the grass layer
593
1420
  * @returns Width of the grass blades in the layer
@@ -615,6 +1442,17 @@ const MAX_CELLS_PER_TEXEL = 8;
615
1442
  }
616
1443
  }
617
1444
  /** @internal */ draw(ctx) {
1445
+ if (ctx.renderPass.type === RENDER_PASS_TYPE_SHADOWMAP) {
1446
+ // Only into the cascades of the light the blades were placed for
1447
+ const light = ctx.renderPass.light;
1448
+ if (light && light === this._shadowLight.get()) {
1449
+ const cascade = ctx.shadowMapInfo?.get(light)?.cascadeIndex ?? 0;
1450
+ for (const layer of this._layers){
1451
+ layer.drawShadow(ctx, cascade);
1452
+ }
1453
+ }
1454
+ return;
1455
+ }
618
1456
  const bv = this._terrain.get().getWorldBoundingVolume().toAABB();
619
1457
  const minY = bv.minPoint.y;
620
1458
  const maxY = bv.maxPoint.y;
@@ -625,11 +1463,12 @@ const MAX_CELLS_PER_TEXEL = 8;
625
1463
  onDispose() {
626
1464
  super.onDispose();
627
1465
  this._terrain.dispose();
1466
+ this._shadowLight.dispose();
628
1467
  for (const layer of this._layers){
629
1468
  layer.dispose();
630
1469
  }
631
1470
  }
632
1471
  }
633
1472
 
634
- export { GrassInstances, GrassLayer, GrassRenderer };
1473
+ export { DEFAULT_BLADE_HEIGHT, DEFAULT_BLADE_WIDTH, GrassInstances, GrassLayer, GrassRenderer };
635
1474
  //# sourceMappingURL=grass.js.map