@babylonjs/loaders 9.12.1 → 9.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/BVH/bvhFileLoader.d.ts +4 -67
- package/BVH/bvhFileLoader.js +6 -130
- package/BVH/bvhFileLoader.js.map +1 -1
- package/BVH/bvhFileLoader.pure.d.ts +75 -0
- package/BVH/bvhFileLoader.pure.js +154 -0
- package/BVH/bvhFileLoader.pure.js.map +1 -0
- package/BVH/bvhFileLoader.types.d.ts +11 -0
- package/BVH/bvhFileLoader.types.js +2 -0
- package/BVH/bvhFileLoader.types.js.map +1 -0
- package/BVH/pure.d.ts +3 -0
- package/BVH/pure.js +5 -0
- package/BVH/pure.js.map +1 -0
- package/FBX/fbxFileLoader.d.ts +4 -192
- package/FBX/fbxFileLoader.js +6 -2437
- package/FBX/fbxFileLoader.js.map +1 -1
- package/FBX/fbxFileLoader.pure.d.ts +190 -0
- package/FBX/fbxFileLoader.pure.js +2451 -0
- package/FBX/fbxFileLoader.pure.js.map +1 -0
- package/FBX/fbxFileLoader.types.d.ts +11 -0
- package/FBX/fbxFileLoader.types.js +2 -0
- package/FBX/fbxFileLoader.types.js.map +1 -0
- package/FBX/pure.d.ts +3 -0
- package/FBX/pure.js +4 -0
- package/FBX/pure.js.map +1 -0
- package/OBJ/objFileLoader.d.ts +4 -134
- package/OBJ/objFileLoader.js +6 -315
- package/OBJ/objFileLoader.js.map +1 -1
- package/OBJ/objFileLoader.pure.d.ts +132 -0
- package/OBJ/objFileLoader.pure.js +329 -0
- package/OBJ/objFileLoader.pure.js.map +1 -0
- package/OBJ/objFileLoader.types.d.ts +11 -0
- package/OBJ/objFileLoader.types.js +2 -0
- package/OBJ/objFileLoader.types.js.map +1 -0
- package/OBJ/pure.d.ts +5 -0
- package/OBJ/pure.js +6 -0
- package/OBJ/pure.js.map +1 -0
- package/OBJ/solidParser.js +4 -3
- package/OBJ/solidParser.js.map +1 -1
- package/SPLAT/gaussianSplattingBlockAllocator.d.ts +137 -0
- package/SPLAT/gaussianSplattingBlockAllocator.js +555 -0
- package/SPLAT/gaussianSplattingBlockAllocator.js.map +1 -0
- package/SPLAT/gaussianSplattingDownloadManager.d.ts +109 -0
- package/SPLAT/gaussianSplattingDownloadManager.js +241 -0
- package/SPLAT/gaussianSplattingDownloadManager.js.map +1 -0
- package/SPLAT/gaussianSplattingResidencyController.d.ts +133 -0
- package/SPLAT/gaussianSplattingResidencyController.js +222 -0
- package/SPLAT/gaussianSplattingResidencyController.js.map +1 -0
- package/SPLAT/gaussianSplattingStream.d.ts +190 -9
- package/SPLAT/gaussianSplattingStream.js +623 -69
- package/SPLAT/gaussianSplattingStream.js.map +1 -1
- package/SPLAT/gaussianSplattingWorkBuffer.d.ts +56 -0
- package/SPLAT/gaussianSplattingWorkBuffer.js +215 -13
- package/SPLAT/gaussianSplattingWorkBuffer.js.map +1 -1
- package/SPLAT/gaussianSplattingWorkBufferShaders.d.ts +15 -0
- package/SPLAT/gaussianSplattingWorkBufferShaders.js +82 -0
- package/SPLAT/gaussianSplattingWorkBufferShaders.js.map +1 -1
- package/SPLAT/pure.d.ts +5 -0
- package/SPLAT/pure.js +7 -0
- package/SPLAT/pure.js.map +1 -0
- package/SPLAT/sog.d.ts +2 -93
- package/SPLAT/sog.js +4 -420
- package/SPLAT/sog.js.map +1 -1
- package/SPLAT/sog.pure.d.ts +102 -0
- package/SPLAT/sog.pure.js +514 -0
- package/SPLAT/sog.pure.js.map +1 -0
- package/SPLAT/splatFileLoader.d.ts +5 -106
- package/SPLAT/splatFileLoader.js +9 -636
- package/SPLAT/splatFileLoader.js.map +1 -1
- package/SPLAT/splatFileLoader.pure.d.ts +104 -0
- package/SPLAT/splatFileLoader.pure.js +650 -0
- package/SPLAT/splatFileLoader.pure.js.map +1 -0
- package/SPLAT/splatFileLoader.types.d.ts +11 -0
- package/SPLAT/splatFileLoader.types.js +2 -0
- package/SPLAT/splatFileLoader.types.js.map +1 -0
- package/STL/pure.d.ts +2 -0
- package/STL/pure.js +3 -0
- package/STL/pure.js.map +1 -0
- package/STL/stlFileLoader.d.ts +5 -76
- package/STL/stlFileLoader.js +7 -236
- package/STL/stlFileLoader.js.map +1 -1
- package/STL/stlFileLoader.pure.d.ts +73 -0
- package/STL/stlFileLoader.pure.js +249 -0
- package/STL/stlFileLoader.pure.js.map +1 -0
- package/STL/stlFileLoader.types.d.ts +10 -0
- package/STL/stlFileLoader.types.js +2 -0
- package/STL/stlFileLoader.types.js.map +1 -0
- package/glTF/1.0/pure.d.ts +3 -0
- package/glTF/1.0/pure.js +4 -0
- package/glTF/1.0/pure.js.map +1 -0
- package/glTF/2.0/Extensions/EXT_lights_area.d.ts +4 -39
- package/glTF/2.0/Extensions/EXT_lights_area.js +6 -91
- package/glTF/2.0/Extensions/EXT_lights_area.js.map +1 -1
- package/glTF/2.0/Extensions/EXT_lights_area.pure.d.ts +38 -0
- package/glTF/2.0/Extensions/EXT_lights_area.pure.js +106 -0
- package/glTF/2.0/Extensions/EXT_lights_area.pure.js.map +1 -0
- package/glTF/2.0/Extensions/EXT_lights_area.types.d.ts +9 -0
- package/glTF/2.0/Extensions/EXT_lights_area.types.js +2 -0
- package/glTF/2.0/Extensions/EXT_lights_area.types.js.map +1 -0
- package/glTF/2.0/Extensions/EXT_lights_ies.d.ts +4 -39
- package/glTF/2.0/Extensions/EXT_lights_ies.js +6 -81
- package/glTF/2.0/Extensions/EXT_lights_ies.js.map +1 -1
- package/glTF/2.0/Extensions/EXT_lights_ies.pure.d.ts +38 -0
- package/glTF/2.0/Extensions/EXT_lights_ies.pure.js +96 -0
- package/glTF/2.0/Extensions/EXT_lights_ies.pure.js.map +1 -0
- package/glTF/2.0/Extensions/EXT_lights_ies.types.d.ts +9 -0
- package/glTF/2.0/Extensions/EXT_lights_ies.types.js +2 -0
- package/glTF/2.0/Extensions/EXT_lights_ies.types.js.map +1 -0
- package/glTF/2.0/Extensions/EXT_lights_image_based.d.ts +4 -46
- package/glTF/2.0/Extensions/EXT_lights_image_based.js +6 -113
- package/glTF/2.0/Extensions/EXT_lights_image_based.js.map +1 -1
- package/glTF/2.0/Extensions/EXT_lights_image_based.pure.d.ts +37 -0
- package/glTF/2.0/Extensions/EXT_lights_image_based.pure.js +128 -0
- package/glTF/2.0/Extensions/EXT_lights_image_based.pure.js.map +1 -0
- package/glTF/2.0/Extensions/EXT_lights_image_based.types.d.ts +17 -0
- package/glTF/2.0/Extensions/EXT_lights_image_based.types.js +2 -0
- package/glTF/2.0/Extensions/EXT_lights_image_based.types.js.map +1 -0
- package/glTF/2.0/Extensions/EXT_mesh_gpu_instancing.d.ts +5 -37
- package/glTF/2.0/Extensions/EXT_mesh_gpu_instancing.js +7 -92
- package/glTF/2.0/Extensions/EXT_mesh_gpu_instancing.js.map +1 -1
- package/glTF/2.0/Extensions/EXT_mesh_gpu_instancing.pure.d.ts +35 -0
- package/glTF/2.0/Extensions/EXT_mesh_gpu_instancing.pure.js +106 -0
- package/glTF/2.0/Extensions/EXT_mesh_gpu_instancing.pure.js.map +1 -0
- package/glTF/2.0/Extensions/EXT_mesh_gpu_instancing.types.d.ts +9 -0
- package/glTF/2.0/Extensions/EXT_mesh_gpu_instancing.types.js +2 -0
- package/glTF/2.0/Extensions/EXT_mesh_gpu_instancing.types.js.map +1 -0
- package/glTF/2.0/Extensions/EXT_meshopt_compression.d.ts +4 -37
- package/glTF/2.0/Extensions/EXT_meshopt_compression.js +6 -48
- package/glTF/2.0/Extensions/EXT_meshopt_compression.js.map +1 -1
- package/glTF/2.0/Extensions/EXT_meshopt_compression.pure.d.ts +36 -0
- package/glTF/2.0/Extensions/EXT_meshopt_compression.pure.js +63 -0
- package/glTF/2.0/Extensions/EXT_meshopt_compression.pure.js.map +1 -0
- package/glTF/2.0/Extensions/EXT_meshopt_compression.types.d.ts +9 -0
- package/glTF/2.0/Extensions/EXT_meshopt_compression.types.js +2 -0
- package/glTF/2.0/Extensions/EXT_meshopt_compression.types.js.map +1 -0
- package/glTF/2.0/Extensions/EXT_texture_avif.d.ts +4 -32
- package/glTF/2.0/Extensions/EXT_texture_avif.js +6 -36
- package/glTF/2.0/Extensions/EXT_texture_avif.js.map +1 -1
- package/glTF/2.0/Extensions/EXT_texture_avif.pure.d.ts +31 -0
- package/glTF/2.0/Extensions/EXT_texture_avif.pure.js +51 -0
- package/glTF/2.0/Extensions/EXT_texture_avif.pure.js.map +1 -0
- package/glTF/2.0/Extensions/EXT_texture_avif.types.d.ts +9 -0
- package/glTF/2.0/Extensions/EXT_texture_avif.types.js +2 -0
- package/glTF/2.0/Extensions/EXT_texture_avif.types.js.map +1 -0
- package/glTF/2.0/Extensions/EXT_texture_webp.d.ts +4 -31
- package/glTF/2.0/Extensions/EXT_texture_webp.js +6 -35
- package/glTF/2.0/Extensions/EXT_texture_webp.js.map +1 -1
- package/glTF/2.0/Extensions/EXT_texture_webp.pure.d.ts +30 -0
- package/glTF/2.0/Extensions/EXT_texture_webp.pure.js +50 -0
- package/glTF/2.0/Extensions/EXT_texture_webp.pure.js.map +1 -0
- package/glTF/2.0/Extensions/EXT_texture_webp.types.d.ts +9 -0
- package/glTF/2.0/Extensions/EXT_texture_webp.types.js +2 -0
- package/glTF/2.0/Extensions/EXT_texture_webp.types.js.map +1 -0
- package/glTF/2.0/Extensions/ExtrasAsMetadata.d.ts +4 -51
- package/glTF/2.0/Extensions/ExtrasAsMetadata.js +6 -71
- package/glTF/2.0/Extensions/ExtrasAsMetadata.js.map +1 -1
- package/glTF/2.0/Extensions/ExtrasAsMetadata.pure.d.ts +50 -0
- package/glTF/2.0/Extensions/ExtrasAsMetadata.pure.js +85 -0
- package/glTF/2.0/Extensions/ExtrasAsMetadata.pure.js.map +1 -0
- package/glTF/2.0/Extensions/ExtrasAsMetadata.types.d.ts +9 -0
- package/glTF/2.0/Extensions/ExtrasAsMetadata.types.js +2 -0
- package/glTF/2.0/Extensions/ExtrasAsMetadata.types.js.map +1 -0
- package/glTF/2.0/Extensions/KHR_animation_pointer.d.ts +5 -45
- package/glTF/2.0/Extensions/KHR_animation_pointer.js +7 -75
- package/glTF/2.0/Extensions/KHR_animation_pointer.js.map +1 -1
- package/glTF/2.0/Extensions/KHR_animation_pointer.pure.d.ts +43 -0
- package/glTF/2.0/Extensions/KHR_animation_pointer.pure.js +89 -0
- package/glTF/2.0/Extensions/KHR_animation_pointer.pure.js.map +1 -0
- package/glTF/2.0/Extensions/KHR_animation_pointer.types.d.ts +9 -0
- package/glTF/2.0/Extensions/KHR_animation_pointer.types.js +2 -0
- package/glTF/2.0/Extensions/KHR_animation_pointer.types.js.map +1 -0
- package/glTF/2.0/Extensions/KHR_draco_mesh_compression.d.ts +4 -45
- package/glTF/2.0/Extensions/KHR_draco_mesh_compression.js +6 -90
- package/glTF/2.0/Extensions/KHR_draco_mesh_compression.js.map +1 -1
- package/glTF/2.0/Extensions/KHR_draco_mesh_compression.pure.d.ts +44 -0
- package/glTF/2.0/Extensions/KHR_draco_mesh_compression.pure.js +105 -0
- package/glTF/2.0/Extensions/KHR_draco_mesh_compression.pure.js.map +1 -0
- package/glTF/2.0/Extensions/KHR_draco_mesh_compression.types.d.ts +9 -0
- package/glTF/2.0/Extensions/KHR_draco_mesh_compression.types.js +2 -0
- package/glTF/2.0/Extensions/KHR_draco_mesh_compression.types.js.map +1 -0
- package/glTF/2.0/Extensions/KHR_interactivity/pure.d.ts +4 -0
- package/glTF/2.0/Extensions/KHR_interactivity/pure.js +5 -0
- package/glTF/2.0/Extensions/KHR_interactivity/pure.js.map +1 -0
- package/glTF/2.0/Extensions/KHR_interactivity.d.ts +4 -36
- package/glTF/2.0/Extensions/KHR_interactivity.js +6 -148
- package/glTF/2.0/Extensions/KHR_interactivity.js.map +1 -1
- package/glTF/2.0/Extensions/KHR_interactivity.pure.d.ts +35 -0
- package/glTF/2.0/Extensions/KHR_interactivity.pure.js +163 -0
- package/glTF/2.0/Extensions/KHR_interactivity.pure.js.map +1 -0
- package/glTF/2.0/Extensions/KHR_interactivity.types.d.ts +9 -0
- package/glTF/2.0/Extensions/KHR_interactivity.types.js +2 -0
- package/glTF/2.0/Extensions/KHR_interactivity.types.js.map +1 -0
- package/glTF/2.0/Extensions/KHR_lights_punctual.d.ts +4 -39
- package/glTF/2.0/Extensions/KHR_lights_punctual.js +6 -89
- package/glTF/2.0/Extensions/KHR_lights_punctual.js.map +1 -1
- package/glTF/2.0/Extensions/KHR_lights_punctual.pure.d.ts +38 -0
- package/glTF/2.0/Extensions/KHR_lights_punctual.pure.js +104 -0
- package/glTF/2.0/Extensions/KHR_lights_punctual.pure.js.map +1 -0
- package/glTF/2.0/Extensions/KHR_lights_punctual.types.d.ts +9 -0
- package/glTF/2.0/Extensions/KHR_lights_punctual.types.js +2 -0
- package/glTF/2.0/Extensions/KHR_lights_punctual.types.js.map +1 -0
- package/glTF/2.0/Extensions/KHR_materials_anisotropy.d.ts +4 -40
- package/glTF/2.0/Extensions/KHR_materials_anisotropy.js +6 -63
- package/glTF/2.0/Extensions/KHR_materials_anisotropy.js.map +1 -1
- package/glTF/2.0/Extensions/KHR_materials_anisotropy.pure.d.ts +39 -0
- package/glTF/2.0/Extensions/KHR_materials_anisotropy.pure.js +78 -0
- package/glTF/2.0/Extensions/KHR_materials_anisotropy.pure.js.map +1 -0
- package/glTF/2.0/Extensions/KHR_materials_anisotropy.types.d.ts +9 -0
- package/glTF/2.0/Extensions/KHR_materials_anisotropy.types.js +2 -0
- package/glTF/2.0/Extensions/KHR_materials_anisotropy.types.js.map +1 -0
- package/glTF/2.0/Extensions/KHR_materials_clearcoat.d.ts +4 -41
- package/glTF/2.0/Extensions/KHR_materials_clearcoat.js +6 -77
- package/glTF/2.0/Extensions/KHR_materials_clearcoat.js.map +1 -1
- package/glTF/2.0/Extensions/KHR_materials_clearcoat.pure.d.ts +40 -0
- package/glTF/2.0/Extensions/KHR_materials_clearcoat.pure.js +92 -0
- package/glTF/2.0/Extensions/KHR_materials_clearcoat.pure.js.map +1 -0
- package/glTF/2.0/Extensions/KHR_materials_clearcoat.types.d.ts +9 -0
- package/glTF/2.0/Extensions/KHR_materials_clearcoat.types.js +2 -0
- package/glTF/2.0/Extensions/KHR_materials_clearcoat.types.js.map +1 -0
- package/glTF/2.0/Extensions/KHR_materials_coat.d.ts +4 -47
- package/glTF/2.0/Extensions/KHR_materials_coat.js +6 -117
- package/glTF/2.0/Extensions/KHR_materials_coat.js.map +1 -1
- package/glTF/2.0/Extensions/KHR_materials_coat.pure.d.ts +46 -0
- package/glTF/2.0/Extensions/KHR_materials_coat.pure.js +132 -0
- package/glTF/2.0/Extensions/KHR_materials_coat.pure.js.map +1 -0
- package/glTF/2.0/Extensions/KHR_materials_coat.types.d.ts +9 -0
- package/glTF/2.0/Extensions/KHR_materials_coat.types.js +2 -0
- package/glTF/2.0/Extensions/KHR_materials_coat.types.js.map +1 -0
- package/glTF/2.0/Extensions/KHR_materials_diffuse_roughness.d.ts +4 -41
- package/glTF/2.0/Extensions/KHR_materials_diffuse_roughness.js +6 -56
- package/glTF/2.0/Extensions/KHR_materials_diffuse_roughness.js.map +1 -1
- package/glTF/2.0/Extensions/KHR_materials_diffuse_roughness.pure.d.ts +40 -0
- package/glTF/2.0/Extensions/KHR_materials_diffuse_roughness.pure.js +71 -0
- package/glTF/2.0/Extensions/KHR_materials_diffuse_roughness.pure.js.map +1 -0
- package/glTF/2.0/Extensions/KHR_materials_diffuse_roughness.types.d.ts +9 -0
- package/glTF/2.0/Extensions/KHR_materials_diffuse_roughness.types.js +2 -0
- package/glTF/2.0/Extensions/KHR_materials_diffuse_roughness.types.js.map +1 -0
- package/glTF/2.0/Extensions/KHR_materials_diffuse_transmission.d.ts +4 -41
- package/glTF/2.0/Extensions/KHR_materials_diffuse_transmission.js +6 -71
- package/glTF/2.0/Extensions/KHR_materials_diffuse_transmission.js.map +1 -1
- package/glTF/2.0/Extensions/KHR_materials_diffuse_transmission.pure.d.ts +40 -0
- package/glTF/2.0/Extensions/KHR_materials_diffuse_transmission.pure.js +86 -0
- package/glTF/2.0/Extensions/KHR_materials_diffuse_transmission.pure.js.map +1 -0
- package/glTF/2.0/Extensions/KHR_materials_diffuse_transmission.types.d.ts +9 -0
- package/glTF/2.0/Extensions/KHR_materials_diffuse_transmission.types.js +2 -0
- package/glTF/2.0/Extensions/KHR_materials_diffuse_transmission.types.js.map +1 -0
- package/glTF/2.0/Extensions/KHR_materials_dispersion.d.ts +4 -41
- package/glTF/2.0/Extensions/KHR_materials_dispersion.js +6 -54
- package/glTF/2.0/Extensions/KHR_materials_dispersion.js.map +1 -1
- package/glTF/2.0/Extensions/KHR_materials_dispersion.pure.d.ts +40 -0
- package/glTF/2.0/Extensions/KHR_materials_dispersion.pure.js +69 -0
- package/glTF/2.0/Extensions/KHR_materials_dispersion.pure.js.map +1 -0
- package/glTF/2.0/Extensions/KHR_materials_dispersion.types.d.ts +9 -0
- package/glTF/2.0/Extensions/KHR_materials_dispersion.types.js +2 -0
- package/glTF/2.0/Extensions/KHR_materials_dispersion.types.js.map +1 -0
- package/glTF/2.0/Extensions/KHR_materials_emissive_strength.d.ts +4 -40
- package/glTF/2.0/Extensions/KHR_materials_emissive_strength.js +6 -45
- package/glTF/2.0/Extensions/KHR_materials_emissive_strength.js.map +1 -1
- package/glTF/2.0/Extensions/KHR_materials_emissive_strength.pure.d.ts +39 -0
- package/glTF/2.0/Extensions/KHR_materials_emissive_strength.pure.js +60 -0
- package/glTF/2.0/Extensions/KHR_materials_emissive_strength.pure.js.map +1 -0
- package/glTF/2.0/Extensions/KHR_materials_emissive_strength.types.d.ts +9 -0
- package/glTF/2.0/Extensions/KHR_materials_emissive_strength.types.js +2 -0
- package/glTF/2.0/Extensions/KHR_materials_emissive_strength.types.js.map +1 -0
- package/glTF/2.0/Extensions/KHR_materials_fuzz.d.ts +4 -41
- package/glTF/2.0/Extensions/KHR_materials_fuzz.js +6 -75
- package/glTF/2.0/Extensions/KHR_materials_fuzz.js.map +1 -1
- package/glTF/2.0/Extensions/KHR_materials_fuzz.pure.d.ts +40 -0
- package/glTF/2.0/Extensions/KHR_materials_fuzz.pure.js +90 -0
- package/glTF/2.0/Extensions/KHR_materials_fuzz.pure.js.map +1 -0
- package/glTF/2.0/Extensions/KHR_materials_fuzz.types.d.ts +9 -0
- package/glTF/2.0/Extensions/KHR_materials_fuzz.types.js +2 -0
- package/glTF/2.0/Extensions/KHR_materials_fuzz.types.js.map +1 -0
- package/glTF/2.0/Extensions/KHR_materials_ior.d.ts +4 -44
- package/glTF/2.0/Extensions/KHR_materials_ior.js +6 -52
- package/glTF/2.0/Extensions/KHR_materials_ior.js.map +1 -1
- package/glTF/2.0/Extensions/KHR_materials_ior.pure.d.ts +43 -0
- package/glTF/2.0/Extensions/KHR_materials_ior.pure.js +67 -0
- package/glTF/2.0/Extensions/KHR_materials_ior.pure.js.map +1 -0
- package/glTF/2.0/Extensions/KHR_materials_ior.types.d.ts +9 -0
- package/glTF/2.0/Extensions/KHR_materials_ior.types.js +2 -0
- package/glTF/2.0/Extensions/KHR_materials_ior.types.js.map +1 -0
- package/glTF/2.0/Extensions/KHR_materials_iridescence.d.ts +4 -40
- package/glTF/2.0/Extensions/KHR_materials_iridescence.js +6 -66
- package/glTF/2.0/Extensions/KHR_materials_iridescence.js.map +1 -1
- package/glTF/2.0/Extensions/KHR_materials_iridescence.pure.d.ts +39 -0
- package/glTF/2.0/Extensions/KHR_materials_iridescence.pure.js +81 -0
- package/glTF/2.0/Extensions/KHR_materials_iridescence.pure.js.map +1 -0
- package/glTF/2.0/Extensions/KHR_materials_iridescence.types.d.ts +9 -0
- package/glTF/2.0/Extensions/KHR_materials_iridescence.types.js +2 -0
- package/glTF/2.0/Extensions/KHR_materials_iridescence.types.js.map +1 -0
- package/glTF/2.0/Extensions/KHR_materials_pbrSpecularGlossiness.d.ts +4 -40
- package/glTF/2.0/Extensions/KHR_materials_pbrSpecularGlossiness.js +6 -72
- package/glTF/2.0/Extensions/KHR_materials_pbrSpecularGlossiness.js.map +1 -1
- package/glTF/2.0/Extensions/KHR_materials_pbrSpecularGlossiness.pure.d.ts +39 -0
- package/glTF/2.0/Extensions/KHR_materials_pbrSpecularGlossiness.pure.js +87 -0
- package/glTF/2.0/Extensions/KHR_materials_pbrSpecularGlossiness.pure.js.map +1 -0
- package/glTF/2.0/Extensions/KHR_materials_pbrSpecularGlossiness.types.d.ts +9 -0
- package/glTF/2.0/Extensions/KHR_materials_pbrSpecularGlossiness.types.js +2 -0
- package/glTF/2.0/Extensions/KHR_materials_pbrSpecularGlossiness.types.js.map +1 -0
- package/glTF/2.0/Extensions/KHR_materials_sheen.d.ts +4 -41
- package/glTF/2.0/Extensions/KHR_materials_sheen.js +6 -71
- package/glTF/2.0/Extensions/KHR_materials_sheen.js.map +1 -1
- package/glTF/2.0/Extensions/KHR_materials_sheen.pure.d.ts +40 -0
- package/glTF/2.0/Extensions/KHR_materials_sheen.pure.js +86 -0
- package/glTF/2.0/Extensions/KHR_materials_sheen.pure.js.map +1 -0
- package/glTF/2.0/Extensions/KHR_materials_sheen.types.d.ts +9 -0
- package/glTF/2.0/Extensions/KHR_materials_sheen.types.js +2 -0
- package/glTF/2.0/Extensions/KHR_materials_sheen.types.js.map +1 -0
- package/glTF/2.0/Extensions/KHR_materials_specular.d.ts +4 -40
- package/glTF/2.0/Extensions/KHR_materials_specular.js +6 -74
- package/glTF/2.0/Extensions/KHR_materials_specular.js.map +1 -1
- package/glTF/2.0/Extensions/KHR_materials_specular.pure.d.ts +39 -0
- package/glTF/2.0/Extensions/KHR_materials_specular.pure.js +89 -0
- package/glTF/2.0/Extensions/KHR_materials_specular.pure.js.map +1 -0
- package/glTF/2.0/Extensions/KHR_materials_specular.types.d.ts +9 -0
- package/glTF/2.0/Extensions/KHR_materials_specular.types.js +2 -0
- package/glTF/2.0/Extensions/KHR_materials_specular.types.js.map +1 -0
- package/glTF/2.0/Extensions/KHR_materials_transmission.d.ts +4 -40
- package/glTF/2.0/Extensions/KHR_materials_transmission.js +6 -70
- package/glTF/2.0/Extensions/KHR_materials_transmission.js.map +1 -1
- package/glTF/2.0/Extensions/KHR_materials_transmission.pure.d.ts +39 -0
- package/glTF/2.0/Extensions/KHR_materials_transmission.pure.js +85 -0
- package/glTF/2.0/Extensions/KHR_materials_transmission.pure.js.map +1 -0
- package/glTF/2.0/Extensions/KHR_materials_transmission.types.d.ts +9 -0
- package/glTF/2.0/Extensions/KHR_materials_transmission.types.js +2 -0
- package/glTF/2.0/Extensions/KHR_materials_transmission.types.js.map +1 -0
- package/glTF/2.0/Extensions/KHR_materials_unlit.d.ts +4 -40
- package/glTF/2.0/Extensions/KHR_materials_unlit.js +6 -64
- package/glTF/2.0/Extensions/KHR_materials_unlit.js.map +1 -1
- package/glTF/2.0/Extensions/KHR_materials_unlit.pure.d.ts +39 -0
- package/glTF/2.0/Extensions/KHR_materials_unlit.pure.js +79 -0
- package/glTF/2.0/Extensions/KHR_materials_unlit.pure.js.map +1 -0
- package/glTF/2.0/Extensions/KHR_materials_unlit.types.d.ts +9 -0
- package/glTF/2.0/Extensions/KHR_materials_unlit.types.js +2 -0
- package/glTF/2.0/Extensions/KHR_materials_unlit.types.js.map +1 -0
- package/glTF/2.0/Extensions/KHR_materials_variants.d.ts +4 -110
- package/glTF/2.0/Extensions/KHR_materials_variants.js +6 -260
- package/glTF/2.0/Extensions/KHR_materials_variants.js.map +1 -1
- package/glTF/2.0/Extensions/KHR_materials_variants.pure.d.ts +97 -0
- package/glTF/2.0/Extensions/KHR_materials_variants.pure.js +275 -0
- package/glTF/2.0/Extensions/KHR_materials_variants.pure.js.map +1 -0
- package/glTF/2.0/Extensions/KHR_materials_variants.types.d.ts +29 -0
- package/glTF/2.0/Extensions/KHR_materials_variants.types.js +2 -0
- package/glTF/2.0/Extensions/KHR_materials_variants.types.js.map +1 -0
- package/glTF/2.0/Extensions/KHR_materials_volume.d.ts +4 -41
- package/glTF/2.0/Extensions/KHR_materials_volume.js +6 -77
- package/glTF/2.0/Extensions/KHR_materials_volume.js.map +1 -1
- package/glTF/2.0/Extensions/KHR_materials_volume.pure.d.ts +40 -0
- package/glTF/2.0/Extensions/KHR_materials_volume.pure.js +92 -0
- package/glTF/2.0/Extensions/KHR_materials_volume.pure.js.map +1 -0
- package/glTF/2.0/Extensions/KHR_materials_volume.types.d.ts +9 -0
- package/glTF/2.0/Extensions/KHR_materials_volume.types.js +2 -0
- package/glTF/2.0/Extensions/KHR_materials_volume.types.js.map +1 -0
- package/glTF/2.0/Extensions/KHR_materials_volume_scatter.d.ts +4 -43
- package/glTF/2.0/Extensions/KHR_materials_volume_scatter.js +6 -113
- package/glTF/2.0/Extensions/KHR_materials_volume_scatter.js.map +1 -1
- package/glTF/2.0/Extensions/KHR_materials_volume_scatter.pure.d.ts +42 -0
- package/glTF/2.0/Extensions/KHR_materials_volume_scatter.pure.js +128 -0
- package/glTF/2.0/Extensions/KHR_materials_volume_scatter.pure.js.map +1 -0
- package/glTF/2.0/Extensions/KHR_materials_volume_scatter.types.d.ts +9 -0
- package/glTF/2.0/Extensions/KHR_materials_volume_scatter.types.js +2 -0
- package/glTF/2.0/Extensions/KHR_materials_volume_scatter.types.js.map +1 -0
- package/glTF/2.0/Extensions/KHR_mesh_quantization.d.ts +4 -27
- package/glTF/2.0/Extensions/KHR_mesh_quantization.js +6 -20
- package/glTF/2.0/Extensions/KHR_mesh_quantization.js.map +1 -1
- package/glTF/2.0/Extensions/KHR_mesh_quantization.pure.d.ts +26 -0
- package/glTF/2.0/Extensions/KHR_mesh_quantization.pure.js +35 -0
- package/glTF/2.0/Extensions/KHR_mesh_quantization.pure.js.map +1 -0
- package/glTF/2.0/Extensions/KHR_mesh_quantization.types.d.ts +9 -0
- package/glTF/2.0/Extensions/KHR_mesh_quantization.types.js +2 -0
- package/glTF/2.0/Extensions/KHR_mesh_quantization.types.js.map +1 -0
- package/glTF/2.0/Extensions/KHR_node_hoverability.d.ts +4 -29
- package/glTF/2.0/Extensions/KHR_node_hoverability.js +6 -187
- package/glTF/2.0/Extensions/KHR_node_hoverability.js.map +1 -1
- package/glTF/2.0/Extensions/KHR_node_hoverability.pure.d.ts +28 -0
- package/glTF/2.0/Extensions/KHR_node_hoverability.pure.js +202 -0
- package/glTF/2.0/Extensions/KHR_node_hoverability.pure.js.map +1 -0
- package/glTF/2.0/Extensions/KHR_node_hoverability.types.d.ts +9 -0
- package/glTF/2.0/Extensions/KHR_node_hoverability.types.js +2 -0
- package/glTF/2.0/Extensions/KHR_node_hoverability.types.js.map +1 -0
- package/glTF/2.0/Extensions/KHR_node_selectability.d.ts +4 -28
- package/glTF/2.0/Extensions/KHR_node_selectability.js +6 -121
- package/glTF/2.0/Extensions/KHR_node_selectability.js.map +1 -1
- package/glTF/2.0/Extensions/KHR_node_selectability.pure.d.ts +27 -0
- package/glTF/2.0/Extensions/KHR_node_selectability.pure.js +136 -0
- package/glTF/2.0/Extensions/KHR_node_selectability.pure.js.map +1 -0
- package/glTF/2.0/Extensions/KHR_node_selectability.types.d.ts +9 -0
- package/glTF/2.0/Extensions/KHR_node_selectability.types.js +2 -0
- package/glTF/2.0/Extensions/KHR_node_selectability.types.js.map +1 -0
- package/glTF/2.0/Extensions/KHR_node_visibility.d.ts +4 -28
- package/glTF/2.0/Extensions/KHR_node_visibility.js +6 -64
- package/glTF/2.0/Extensions/KHR_node_visibility.js.map +1 -1
- package/glTF/2.0/Extensions/KHR_node_visibility.pure.d.ts +27 -0
- package/glTF/2.0/Extensions/KHR_node_visibility.pure.js +79 -0
- package/glTF/2.0/Extensions/KHR_node_visibility.pure.js.map +1 -0
- package/glTF/2.0/Extensions/KHR_node_visibility.types.d.ts +9 -0
- package/glTF/2.0/Extensions/KHR_node_visibility.types.js +2 -0
- package/glTF/2.0/Extensions/KHR_node_visibility.types.js.map +1 -0
- package/glTF/2.0/Extensions/KHR_texture_basisu.d.ts +4 -31
- package/glTF/2.0/Extensions/KHR_texture_basisu.js +6 -35
- package/glTF/2.0/Extensions/KHR_texture_basisu.js.map +1 -1
- package/glTF/2.0/Extensions/KHR_texture_basisu.pure.d.ts +30 -0
- package/glTF/2.0/Extensions/KHR_texture_basisu.pure.js +50 -0
- package/glTF/2.0/Extensions/KHR_texture_basisu.pure.js.map +1 -0
- package/glTF/2.0/Extensions/KHR_texture_basisu.types.d.ts +9 -0
- package/glTF/2.0/Extensions/KHR_texture_basisu.types.js +2 -0
- package/glTF/2.0/Extensions/KHR_texture_basisu.types.js.map +1 -0
- package/glTF/2.0/Extensions/KHR_texture_transform.d.ts +4 -35
- package/glTF/2.0/Extensions/KHR_texture_transform.js +6 -56
- package/glTF/2.0/Extensions/KHR_texture_transform.js.map +1 -1
- package/glTF/2.0/Extensions/KHR_texture_transform.pure.d.ts +34 -0
- package/glTF/2.0/Extensions/KHR_texture_transform.pure.js +71 -0
- package/glTF/2.0/Extensions/KHR_texture_transform.pure.js.map +1 -0
- package/glTF/2.0/Extensions/KHR_texture_transform.types.d.ts +9 -0
- package/glTF/2.0/Extensions/KHR_texture_transform.types.js +2 -0
- package/glTF/2.0/Extensions/KHR_texture_transform.types.js.map +1 -0
- package/glTF/2.0/Extensions/KHR_xmp_json_ld.d.ts +4 -37
- package/glTF/2.0/Extensions/KHR_xmp_json_ld.js +6 -45
- package/glTF/2.0/Extensions/KHR_xmp_json_ld.js.map +1 -1
- package/glTF/2.0/Extensions/KHR_xmp_json_ld.pure.d.ts +36 -0
- package/glTF/2.0/Extensions/KHR_xmp_json_ld.pure.js +60 -0
- package/glTF/2.0/Extensions/KHR_xmp_json_ld.pure.js.map +1 -0
- package/glTF/2.0/Extensions/KHR_xmp_json_ld.types.d.ts +9 -0
- package/glTF/2.0/Extensions/KHR_xmp_json_ld.types.js +2 -0
- package/glTF/2.0/Extensions/KHR_xmp_json_ld.types.js.map +1 -0
- package/glTF/2.0/Extensions/MSFT_audio_emitter.d.ts +5 -54
- package/glTF/2.0/Extensions/MSFT_audio_emitter.js +7 -226
- package/glTF/2.0/Extensions/MSFT_audio_emitter.js.map +1 -1
- package/glTF/2.0/Extensions/MSFT_audio_emitter.pure.d.ts +52 -0
- package/glTF/2.0/Extensions/MSFT_audio_emitter.pure.js +240 -0
- package/glTF/2.0/Extensions/MSFT_audio_emitter.pure.js.map +1 -0
- package/glTF/2.0/Extensions/MSFT_audio_emitter.types.d.ts +9 -0
- package/glTF/2.0/Extensions/MSFT_audio_emitter.types.js +2 -0
- package/glTF/2.0/Extensions/MSFT_audio_emitter.types.js.map +1 -0
- package/glTF/2.0/Extensions/MSFT_lod.d.ts +4 -102
- package/glTF/2.0/Extensions/MSFT_lod.js +6 -335
- package/glTF/2.0/Extensions/MSFT_lod.js.map +1 -1
- package/glTF/2.0/Extensions/MSFT_lod.pure.d.ts +96 -0
- package/glTF/2.0/Extensions/MSFT_lod.pure.js +350 -0
- package/glTF/2.0/Extensions/MSFT_lod.pure.js.map +1 -0
- package/glTF/2.0/Extensions/MSFT_lod.types.d.ts +14 -0
- package/glTF/2.0/Extensions/MSFT_lod.types.js +2 -0
- package/glTF/2.0/Extensions/MSFT_lod.types.js.map +1 -0
- package/glTF/2.0/Extensions/MSFT_minecraftMesh.d.ts +6 -27
- package/glTF/2.0/Extensions/MSFT_minecraftMesh.js +8 -40
- package/glTF/2.0/Extensions/MSFT_minecraftMesh.js.map +1 -1
- package/glTF/2.0/Extensions/MSFT_minecraftMesh.pure.d.ts +24 -0
- package/glTF/2.0/Extensions/MSFT_minecraftMesh.pure.js +53 -0
- package/glTF/2.0/Extensions/MSFT_minecraftMesh.pure.js.map +1 -0
- package/glTF/2.0/Extensions/MSFT_minecraftMesh.types.d.ts +9 -0
- package/glTF/2.0/Extensions/MSFT_minecraftMesh.types.js +2 -0
- package/glTF/2.0/Extensions/MSFT_minecraftMesh.types.js.map +1 -0
- package/glTF/2.0/Extensions/MSFT_sRGBFactors.d.ts +6 -27
- package/glTF/2.0/Extensions/MSFT_sRGBFactors.js +8 -38
- package/glTF/2.0/Extensions/MSFT_sRGBFactors.js.map +1 -1
- package/glTF/2.0/Extensions/MSFT_sRGBFactors.pure.d.ts +24 -0
- package/glTF/2.0/Extensions/MSFT_sRGBFactors.pure.js +51 -0
- package/glTF/2.0/Extensions/MSFT_sRGBFactors.pure.js.map +1 -0
- package/glTF/2.0/Extensions/MSFT_sRGBFactors.types.d.ts +9 -0
- package/glTF/2.0/Extensions/MSFT_sRGBFactors.types.js +2 -0
- package/glTF/2.0/Extensions/MSFT_sRGBFactors.types.js.map +1 -0
- package/glTF/2.0/Extensions/pure.d.ts +45 -0
- package/glTF/2.0/Extensions/pure.js +47 -0
- package/glTF/2.0/Extensions/pure.js.map +1 -0
- package/glTF/2.0/glTFLoader.d.ts +1 -471
- package/glTF/2.0/glTFLoader.js +3 -2539
- package/glTF/2.0/glTFLoader.js.map +1 -1
- package/glTF/2.0/glTFLoader.pure.d.ts +476 -0
- package/glTF/2.0/glTFLoader.pure.js +2563 -0
- package/glTF/2.0/glTFLoader.pure.js.map +1 -0
- package/glTF/2.0/pure.d.ts +8 -0
- package/glTF/2.0/pure.js +10 -0
- package/glTF/2.0/pure.js.map +1 -0
- package/glTF/glTFFileLoader.d.ts +5 -551
- package/glTF/glTFFileLoader.js +6 -996
- package/glTF/glTFFileLoader.js.map +1 -1
- package/glTF/glTFFileLoader.pure.d.ts +562 -0
- package/glTF/glTFFileLoader.pure.js +1024 -0
- package/glTF/glTFFileLoader.pure.js.map +1 -0
- package/glTF/glTFFileLoader.types.d.ts +11 -0
- package/glTF/glTFFileLoader.types.js +2 -0
- package/glTF/glTFFileLoader.types.js.map +1 -0
- package/glTF/pure.d.ts +6 -0
- package/glTF/pure.js +8 -0
- package/glTF/pure.js.map +1 -0
- package/package.json +56 -4
- package/pure.d.ts +8 -0
- package/pure.js +10 -0
- package/pure.js.map +1 -0
|
@@ -3,14 +3,24 @@ import { Logger } from "@babylonjs/core/Misc/logger.js";
|
|
|
3
3
|
import { Tools } from "@babylonjs/core/Misc/tools.js";
|
|
4
4
|
import { Vector3, Matrix } from "@babylonjs/core/Maths/math.vector.js";
|
|
5
5
|
import { Color4 } from "@babylonjs/core/Maths/math.color.js";
|
|
6
|
+
import { Frustum } from "@babylonjs/core/Maths/math.frustum.js";
|
|
7
|
+
import { Plane } from "@babylonjs/core/Maths/math.plane.js";
|
|
6
8
|
import { Camera } from "@babylonjs/core/Cameras/camera.js";
|
|
7
9
|
import { BoundingInfo } from "@babylonjs/core/Culling/boundingInfo.js";
|
|
8
10
|
import { CreateLineSystem } from "@babylonjs/core/Meshes/Builders/linesBuilder.js";
|
|
9
11
|
import { VertexBuffer } from "@babylonjs/core/Buffers/buffer.js";
|
|
10
|
-
import { ParseSogMetaAsTextures } from "./sog.js";
|
|
12
|
+
import { ParseSogMetaAsTextures } from "./sog.pure.js";
|
|
11
13
|
import { GaussianSplattingWorkBuffer } from "./gaussianSplattingWorkBuffer.js";
|
|
14
|
+
import { GaussianSplattingDownloadManager } from "./gaussianSplattingDownloadManager.js";
|
|
15
|
+
import { GaussianSplattingResidencyController } from "./gaussianSplattingResidencyController.js";
|
|
12
16
|
// tan(22.5deg): reference half-FOV for a 45-degree vertical FOV, used for FOV compensation (matches PlayCanvas).
|
|
13
17
|
const RefTanHalfFov = Math.tan((22.5 * Math.PI) / 180);
|
|
18
|
+
// Sentinel "file" ids for the residency controller's pinned (never-evicted) allocations.
|
|
19
|
+
const PaddingFileId = -2;
|
|
20
|
+
const EnvironmentFileId = -1;
|
|
21
|
+
// Approximate bytes per resident splat used to convert a memory budget (MB) to a splat budget: the four
|
|
22
|
+
// work-buffer textures cost 16+16+16+4 = 52 bytes on the GPU, plus ~32 bytes of CPU position/sort data.
|
|
23
|
+
const BytesPerResidentSplat = 84;
|
|
14
24
|
// Scratch objects reused by the per-frame optimal-LOD evaluation (avoids per-call allocations).
|
|
15
25
|
const TmpInvWorld = new Matrix();
|
|
16
26
|
const TmpLocalCamera = new Vector3();
|
|
@@ -98,20 +108,55 @@ export class GaussianSplattingStream extends GaussianSplattingMesh {
|
|
|
98
108
|
this._lodUpdateInterval = 4;
|
|
99
109
|
this._lodUpdateDistance = 0.5;
|
|
100
110
|
this._maxDetailLod = 0;
|
|
111
|
+
// Frustum LOD bias: when enabled, nodes outside the camera frustum are rendered at their coarsest LOD.
|
|
112
|
+
this._frustumCulling = true;
|
|
113
|
+
// Reused world-space frustum planes and view-projection scratch matrix (avoids per-frame allocation).
|
|
114
|
+
this._frustumPlanes = [
|
|
115
|
+
new Plane(0, 0, 0, 0),
|
|
116
|
+
new Plane(0, 0, 0, 0),
|
|
117
|
+
new Plane(0, 0, 0, 0),
|
|
118
|
+
new Plane(0, 0, 0, 0),
|
|
119
|
+
new Plane(0, 0, 0, 0),
|
|
120
|
+
new Plane(0, 0, 0, 0),
|
|
121
|
+
];
|
|
122
|
+
this._cullViewProj = new Matrix();
|
|
101
123
|
// GPU work buffer holding all decoded splats; created once the total capacity is known.
|
|
102
124
|
this._workBuffer = null;
|
|
103
|
-
//
|
|
104
|
-
|
|
125
|
+
// True once GPU position readback has been validated against a CPU decode (see _probeReadbackAsync). While
|
|
126
|
+
// false, positions are decoded on the CPU from the means images; once validated, every SOG image uses the
|
|
127
|
+
// fast direct upload and positions are read back from the work buffer (non-blocking).
|
|
128
|
+
this._useGpuPositionReadback = false;
|
|
129
|
+
// Whether the engine reports GPU readback support (candidate to validate on the first decode).
|
|
130
|
+
this._readbackCandidate = false;
|
|
131
|
+
// Set once the one-time readback validation has run (success or failure).
|
|
132
|
+
this._readbackProbed = false;
|
|
133
|
+
// Residency controller: owns the work-buffer slot allocator, per-file blocks, and eviction cooldowns.
|
|
134
|
+
this._residency = null;
|
|
105
135
|
// Splat count of each source file (learned from its metadata before allocation).
|
|
106
136
|
this._fileCounts = new Map();
|
|
107
137
|
// Cached SOG metadata per file so on-demand decodes don't refetch the meta.json.
|
|
108
138
|
this._fileMeta = new Map();
|
|
109
|
-
// Files whose splats have been GPU-decoded into the work buffer.
|
|
139
|
+
// Files whose splats have been fully GPU-decoded into the work buffer (render-safe).
|
|
110
140
|
this._decodedFiles = new Set();
|
|
111
141
|
// Files whose decode is currently in flight (dedupes concurrent requests).
|
|
112
142
|
this._loadingFiles = new Set();
|
|
113
143
|
// FIFO of file ids waiting to be decoded (drained under a per-frame budget).
|
|
114
144
|
this._decodeQueue = [];
|
|
145
|
+
// Per-file reference count: number of leaf nodes whose active LOD renders, or whose pending target points
|
|
146
|
+
// at, each file. At zero, a decoded file is scheduled for eviction and a still-downloading file is cancelled.
|
|
147
|
+
this._fileRefs = new Map();
|
|
148
|
+
// Files whose in-flight decode was cancelled; checked at decode checkpoints to bail out cooperatively.
|
|
149
|
+
this._cancelledDecodes = new Set();
|
|
150
|
+
// Eviction streaming config: enabled only when a budget smaller than the full dataset is configured.
|
|
151
|
+
this._evictionEnabled = false;
|
|
152
|
+
this._residentBudget = 0;
|
|
153
|
+
this._evictionCooldownFrames = 100;
|
|
154
|
+
// Serializes the allocate -> decode -> readback critical section so a defrag relayout (which runs inside it)
|
|
155
|
+
// never overlaps another file's decode writing the work buffer, which would corrupt the moved data.
|
|
156
|
+
this._decodeGate = Promise.resolve();
|
|
157
|
+
// Reusable scratch for the (rare) defrag relayout, to avoid per-relayout allocations during streaming.
|
|
158
|
+
this._relayoutOldOffsets = new Map();
|
|
159
|
+
this._relayoutSrcIndex = null;
|
|
115
160
|
// Global range covered by the environment file (always rendered), or null until it loads.
|
|
116
161
|
this._environmentRange = null;
|
|
117
162
|
// Unzipped environment bundle contents, retained between count-gathering and decode.
|
|
@@ -173,9 +218,29 @@ export class GaussianSplattingStream extends GaussianSplattingMesh {
|
|
|
173
218
|
if (options.maxDetailLod !== undefined) {
|
|
174
219
|
this._maxDetailLod = Math.max(0, Math.floor(options.maxDetailLod));
|
|
175
220
|
}
|
|
221
|
+
if (options.frustumCulling !== undefined) {
|
|
222
|
+
this._frustumCulling = options.frustumCulling;
|
|
223
|
+
}
|
|
176
224
|
if (options.debugLodSource) {
|
|
177
225
|
this._debugLodSource = options.debugLodSource;
|
|
178
226
|
}
|
|
227
|
+
if (options.evictionCooldownFrames !== undefined) {
|
|
228
|
+
this._evictionCooldownFrames = Math.max(0, Math.floor(options.evictionCooldownFrames));
|
|
229
|
+
}
|
|
230
|
+
// Resolve the resident-splat budget from the splat-count and/or memory-size options (smaller wins).
|
|
231
|
+
let budget = 0;
|
|
232
|
+
if (options.maxResidentSplats !== undefined && options.maxResidentSplats > 0) {
|
|
233
|
+
budget = Math.floor(options.maxResidentSplats);
|
|
234
|
+
}
|
|
235
|
+
if (options.memoryBudgetMb !== undefined && options.memoryBudgetMb > 0) {
|
|
236
|
+
const fromMB = Math.floor((options.memoryBudgetMb * 1024 * 1024) / BytesPerResidentSplat);
|
|
237
|
+
budget = budget > 0 ? Math.min(budget, fromMB) : fromMB;
|
|
238
|
+
}
|
|
239
|
+
this._residentBudget = budget;
|
|
240
|
+
this._downloadManager = new GaussianSplattingDownloadManager({
|
|
241
|
+
maxConcurrent: options.maxConcurrentDownloads,
|
|
242
|
+
maxRetries: options.maxDownloadRetries,
|
|
243
|
+
});
|
|
179
244
|
// PlayCanvas SOG data is authored with a flipped Y; match the standard SOG loader.
|
|
180
245
|
this.scaling.y *= -1;
|
|
181
246
|
// PlayCanvas SOG LOD scenes are authored Z-up; rotate into Babylon's Y-up convention.
|
|
@@ -193,6 +258,88 @@ export class GaussianSplattingStream extends GaussianSplattingMesh {
|
|
|
193
258
|
getClassName() {
|
|
194
259
|
return "GaussianSplattingStream";
|
|
195
260
|
}
|
|
261
|
+
/**
|
|
262
|
+
* Resolves once the scene is fully streamed and displayed for the current camera: a LOD re-evaluation has
|
|
263
|
+
* run for the current point of view, every reachable LOD file has finished downloading and decoding (no
|
|
264
|
+
* downloads, decodes, or queued work remain), and the depth sort for the resulting splats has been applied
|
|
265
|
+
* and rendered. Intended for deterministic automated testing and screenshot/image comparison.
|
|
266
|
+
*
|
|
267
|
+
* Streaming and settling require rendered frames. If an external render loop is already running, this waits
|
|
268
|
+
* on it passively; otherwise (e.g. when awaited inside an async `createScene` before the host starts its
|
|
269
|
+
* render loop) it drives `scene.render()` itself until settled, so it never deadlocks.
|
|
270
|
+
*
|
|
271
|
+
* Note: the promise only resolves while the camera is still — if the camera keeps moving, the target LODs
|
|
272
|
+
* (and the depth sort) keep changing and the stream never settles. Position the camera, then await this.
|
|
273
|
+
* @param stableFrames number of consecutive settled frames to require before resolving (defaults to 3), so
|
|
274
|
+
* the final sorted frame is actually on screen
|
|
275
|
+
* @returns a promise that resolves when loading and rendering are complete for the current view
|
|
276
|
+
*/
|
|
277
|
+
async whenSettledAsync(stableFrames = 3) {
|
|
278
|
+
if (this._disposed) {
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
// Re-evaluate LODs immediately so the target levels reflect the current camera before we wait.
|
|
282
|
+
this._forceLodUpdate = true;
|
|
283
|
+
const required = Math.max(1, stableFrames);
|
|
284
|
+
const scene = this._scene;
|
|
285
|
+
let stable = 0;
|
|
286
|
+
const isSettled = () => {
|
|
287
|
+
if (this._isLoadingIdle() && this._isDepthSortSettled) {
|
|
288
|
+
return ++stable >= required;
|
|
289
|
+
}
|
|
290
|
+
stable = 0;
|
|
291
|
+
return false;
|
|
292
|
+
};
|
|
293
|
+
// An external render loop is already driving frames: observe it passively.
|
|
294
|
+
if (scene.getEngine().activeRenderLoops.length > 0) {
|
|
295
|
+
await new Promise((resolve) => {
|
|
296
|
+
let observer = null;
|
|
297
|
+
observer = scene.onAfterRenderObservable.add(() => {
|
|
298
|
+
if (this._disposed || isSettled()) {
|
|
299
|
+
if (observer) {
|
|
300
|
+
scene.onAfterRenderObservable.remove(observer);
|
|
301
|
+
observer = null;
|
|
302
|
+
}
|
|
303
|
+
resolve();
|
|
304
|
+
}
|
|
305
|
+
});
|
|
306
|
+
});
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
// No render loop yet (e.g. awaited inside createScene): drive rendering ourselves so the streaming
|
|
310
|
+
// decodes and depth sort can progress, yielding between frames so async downloads/readbacks resolve.
|
|
311
|
+
// Wrap each render in beginFrame/endFrame exactly like the engine's own render loop: on WebGPU,
|
|
312
|
+
// endFrame submits the frame's command buffers and presents the swapchain, so a bare scene.render()
|
|
313
|
+
// would leave the acquired swapchain texture to be destroyed at the frame boundary before its command
|
|
314
|
+
// buffer is submitted ("destroyed texture used in a submit").
|
|
315
|
+
const engine = scene.getEngine();
|
|
316
|
+
const requestFrame = globalThis.requestAnimationFrame;
|
|
317
|
+
while (!this._disposed) {
|
|
318
|
+
engine.beginFrame();
|
|
319
|
+
scene.render();
|
|
320
|
+
engine.endFrame();
|
|
321
|
+
if (isSettled()) {
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
// eslint-disable-next-line no-await-in-loop
|
|
325
|
+
await new Promise((resolve) => {
|
|
326
|
+
if (typeof requestFrame === "function") {
|
|
327
|
+
requestFrame(() => resolve());
|
|
328
|
+
}
|
|
329
|
+
else {
|
|
330
|
+
setTimeout(resolve, 16);
|
|
331
|
+
}
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* Whether the base layer is ready and there is no streaming work in flight (nothing queued for decode, no
|
|
337
|
+
* decode running, and no downloads pending).
|
|
338
|
+
* @returns true when no loading work remains
|
|
339
|
+
*/
|
|
340
|
+
_isLoadingIdle() {
|
|
341
|
+
return this._baseLayerReady && this._decodeQueue.length === 0 && this._loadingFiles.size === 0 && this._downloadManager.isIdle;
|
|
342
|
+
}
|
|
196
343
|
/**
|
|
197
344
|
* Finest (most detailed) LOD level any node is allowed to render. `0` allows full detail (level 0);
|
|
198
345
|
* `1` caps detail at the next-coarser level, and so on. Nodes already coarser than this cap (by
|
|
@@ -217,6 +364,23 @@ export class GaussianSplattingStream extends GaussianSplattingMesh {
|
|
|
217
364
|
get maxLodLevel() {
|
|
218
365
|
return Math.max(0, this._metadata.lodLevels - 1);
|
|
219
366
|
}
|
|
367
|
+
/**
|
|
368
|
+
* When true (default), nodes whose bounding box is outside the camera frustum are biased to the coarsest
|
|
369
|
+
* LOD instead of being hidden. They stay in the sort/render set (their off-screen splats are clipped), so
|
|
370
|
+
* turning the camera toward them shows low detail immediately with no invisible frames, then refines.
|
|
371
|
+
* Changes take effect in real time.
|
|
372
|
+
*/
|
|
373
|
+
get frustumCulling() {
|
|
374
|
+
return this._frustumCulling;
|
|
375
|
+
}
|
|
376
|
+
set frustumCulling(value) {
|
|
377
|
+
if (this._frustumCulling === value) {
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
this._frustumCulling = value;
|
|
381
|
+
// Re-evaluate LODs next frame so the off-screen bias is applied/removed immediately.
|
|
382
|
+
this._forceLodUpdate = true;
|
|
383
|
+
}
|
|
220
384
|
/**
|
|
221
385
|
* When true, renders a wireframe box per LOD node, colored by the LOD level selected by {@link debugLodSource}.
|
|
222
386
|
*/
|
|
@@ -258,6 +422,9 @@ export class GaussianSplattingStream extends GaussianSplattingMesh {
|
|
|
258
422
|
this._lodObserver = null;
|
|
259
423
|
}
|
|
260
424
|
this._clearDebugDisplay();
|
|
425
|
+
this._downloadManager.dispose();
|
|
426
|
+
this._residency?.dispose();
|
|
427
|
+
this._residency = null;
|
|
261
428
|
this._workBuffer?.dispose();
|
|
262
429
|
this._workBuffer = null;
|
|
263
430
|
super.dispose(doNotRecurse);
|
|
@@ -287,7 +454,7 @@ export class GaussianSplattingStream extends GaussianSplattingMesh {
|
|
|
287
454
|
const tanHalfH = tanHalfV * aspect;
|
|
288
455
|
const fovScale = Math.min(tanHalfV, tanHalfH) / RefTanHalfFov;
|
|
289
456
|
// Transform the camera into the mesh's local space (where the node bounds live).
|
|
290
|
-
this.computeWorldMatrix(
|
|
457
|
+
this.computeWorldMatrix(false).invertToRef(TmpInvWorld);
|
|
291
458
|
const localCamera = Vector3.TransformCoordinatesToRef(camera.globalPosition, TmpInvWorld, TmpLocalCamera);
|
|
292
459
|
const px = localCamera.x;
|
|
293
460
|
const py = localCamera.y;
|
|
@@ -340,6 +507,13 @@ export class GaussianSplattingStream extends GaussianSplattingMesh {
|
|
|
340
507
|
else if (optimalLod > rangeMax) {
|
|
341
508
|
optimalLod = rangeMax;
|
|
342
509
|
}
|
|
510
|
+
// Frustum-based LOD bias: nodes outside the camera frustum are pushed to the coarsest allowed
|
|
511
|
+
// level instead of being hidden. They stay in the render/sort set (their splats are off-screen
|
|
512
|
+
// and clipped anyway), so when the camera turns to include them they are already present at low
|
|
513
|
+
// detail with no invisible frames, then refine to the distance-optimal level.
|
|
514
|
+
if (this._frustumCulling && node.inFrustum === false) {
|
|
515
|
+
optimalLod = rangeMax;
|
|
516
|
+
}
|
|
343
517
|
node.optimalLod = optimalLod;
|
|
344
518
|
}
|
|
345
519
|
}
|
|
@@ -510,6 +684,9 @@ export class GaussianSplattingStream extends GaussianSplattingMesh {
|
|
|
510
684
|
node.baseLod = levels[levels.length - 1];
|
|
511
685
|
node.activeLod = undefined;
|
|
512
686
|
node.lodCooldown = 0;
|
|
687
|
+
node.inFrustum = true;
|
|
688
|
+
// Local-space bounds for the per-node frustum test; the mesh world matrix is applied per evaluation.
|
|
689
|
+
node.cullBounds = new BoundingInfo(Vector3.FromArray(node.bound.min), Vector3.FromArray(node.bound.max));
|
|
513
690
|
this._leafNodes.push(node);
|
|
514
691
|
}
|
|
515
692
|
/**
|
|
@@ -524,27 +701,44 @@ export class GaussianSplattingStream extends GaussianSplattingMesh {
|
|
|
524
701
|
if (this._disposed) {
|
|
525
702
|
return;
|
|
526
703
|
}
|
|
527
|
-
// Phase 2:
|
|
704
|
+
// Phase 2: learn the full dataset size (padding + environment + every LOD file). The work buffer is
|
|
705
|
+
// sized to this unless a smaller budget enables eviction-based streaming.
|
|
528
706
|
// Index 0 is reserved as a never-decoded padding splat: the sort worker and index buffer pad unused
|
|
529
707
|
// slots with index 0, and leaving that slot zeroed (center.w = 0 => zero covariance, alpha 0) makes
|
|
530
708
|
// the padding invisible instead of ghosting a copy of the first real splat.
|
|
531
|
-
let
|
|
709
|
+
let fullCapacity = 1;
|
|
532
710
|
if (envCount > 0) {
|
|
533
|
-
|
|
534
|
-
capacity += envCount;
|
|
711
|
+
fullCapacity += envCount;
|
|
535
712
|
}
|
|
536
713
|
for (const fileId of fileIds) {
|
|
537
714
|
const count = this._fileCounts.get(fileId);
|
|
538
|
-
if (count
|
|
539
|
-
|
|
715
|
+
if (count !== undefined && count > 0) {
|
|
716
|
+
fullCapacity += count;
|
|
540
717
|
}
|
|
541
|
-
this._fileBaseSplat.set(fileId, capacity);
|
|
542
|
-
capacity += count;
|
|
543
718
|
}
|
|
544
|
-
if (
|
|
719
|
+
if (fullCapacity <= 1) {
|
|
545
720
|
return;
|
|
546
721
|
}
|
|
722
|
+
// Eviction streams the dataset through a fixed budget; only enabled when that budget is below the full set.
|
|
723
|
+
this._evictionEnabled = this._residentBudget > 0 && this._residentBudget < fullCapacity;
|
|
724
|
+
const capacity = this._evictionEnabled ? Math.max(this._residentBudget, 1) : fullCapacity;
|
|
725
|
+
this._residency = new GaussianSplattingResidencyController(capacity, this._evictionCooldownFrames, (file) => this._onFileEvicted(file));
|
|
726
|
+
// Pin splat 0 as the invisible padding splat, then the environment (always rendered) — neither is evicted.
|
|
727
|
+
this._residency.pin(PaddingFileId, 1);
|
|
728
|
+
if (envCount > 0) {
|
|
729
|
+
const envOffset = this._residency.pin(EnvironmentFileId, envCount);
|
|
730
|
+
if (envOffset !== null) {
|
|
731
|
+
this._environmentRange = { offset: envOffset, count: envCount };
|
|
732
|
+
}
|
|
733
|
+
else {
|
|
734
|
+
Logger.Warn("GaussianSplattingStream: environment does not fit the memory budget; skipping it.");
|
|
735
|
+
this._environmentFiles = null;
|
|
736
|
+
}
|
|
737
|
+
}
|
|
547
738
|
this._workBuffer = new GaussianSplattingWorkBuffer(this._scene, capacity);
|
|
739
|
+
// GPU readback is only enabled after it is validated against a CPU decode on the first file (see
|
|
740
|
+
// _probeReadbackAsync); until then positions are decoded on the CPU so there is always a correct result.
|
|
741
|
+
this._readbackCandidate = this._workBuffer.supportsAsyncCentersReadback;
|
|
548
742
|
const splatPositions = new Float32Array(capacity * 4);
|
|
549
743
|
const textures = this._workBuffer.textures;
|
|
550
744
|
this._setExternalWorkBuffer(textures[0], textures[1], textures[2], textures[3], splatPositions, capacity);
|
|
@@ -559,7 +753,7 @@ export class GaussianSplattingStream extends GaussianSplattingMesh {
|
|
|
559
753
|
const baseFiles = new Set();
|
|
560
754
|
for (const node of this._leafNodes) {
|
|
561
755
|
const entry = node.lods[String(node.baseLod)];
|
|
562
|
-
if (entry && this.
|
|
756
|
+
if (entry && this._fileCounts.has(entry.file)) {
|
|
563
757
|
baseFiles.add(entry.file);
|
|
564
758
|
}
|
|
565
759
|
}
|
|
@@ -606,7 +800,7 @@ export class GaussianSplattingStream extends GaussianSplattingMesh {
|
|
|
606
800
|
if (this._metadata.environment) {
|
|
607
801
|
try {
|
|
608
802
|
const url = this._rootUrl + this._metadata.environment;
|
|
609
|
-
const buffer =
|
|
803
|
+
const buffer = await this._downloadManager.loadFileAsync(url);
|
|
610
804
|
const files = await this._unzipAsync(new Uint8Array(buffer));
|
|
611
805
|
const metaBytes = files.get("meta.json");
|
|
612
806
|
if (metaBytes) {
|
|
@@ -629,8 +823,8 @@ export class GaussianSplattingStream extends GaussianSplattingMesh {
|
|
|
629
823
|
try {
|
|
630
824
|
const metaUrl = this._rootUrl + relativePath;
|
|
631
825
|
const subRootUrl = metaUrl.substring(0, metaUrl.lastIndexOf("/") + 1);
|
|
632
|
-
const
|
|
633
|
-
const sogData = JSON.parse(
|
|
826
|
+
const metaBuffer = await this._downloadManager.loadFileAsync(metaUrl);
|
|
827
|
+
const sogData = JSON.parse(new TextDecoder().decode(new Uint8Array(metaBuffer)));
|
|
634
828
|
this._fileCounts.set(fileId, GaussianSplattingStream._GetSplatCount(sogData));
|
|
635
829
|
this._fileMeta.set(fileId, { sogData, subRootUrl });
|
|
636
830
|
}
|
|
@@ -670,6 +864,95 @@ export class GaussianSplattingStream extends GaussianSplattingMesh {
|
|
|
670
864
|
});
|
|
671
865
|
}
|
|
672
866
|
}
|
|
867
|
+
/**
|
|
868
|
+
* Writes a decoded splat range's positions into the shared buffer, expands the bounds, and incrementally
|
|
869
|
+
* patches the sort worker.
|
|
870
|
+
* @param positions stride-4 positions for the range
|
|
871
|
+
* @param base first splat index of the range in the work buffer
|
|
872
|
+
* @param count number of splats in the range
|
|
873
|
+
*/
|
|
874
|
+
_applyPositions(positions, base, count) {
|
|
875
|
+
this._splatPositions.set(positions, base * 4);
|
|
876
|
+
this._updateBounds(positions, count);
|
|
877
|
+
// Incrementally patch only this range in the sort worker (avoids the full position-buffer re-copy).
|
|
878
|
+
this._postWorkerPositionsRange(base, count);
|
|
879
|
+
}
|
|
880
|
+
/**
|
|
881
|
+
* One-time validation of GPU position readback: reads a sample of the just-decoded range back from the work
|
|
882
|
+
* buffer and compares it to the CPU-decoded positions. Enables {@link _useGpuPositionReadback} only on an
|
|
883
|
+
* exact (within float tolerance) match, so an unsupported or incorrect readback (e.g. a backend without the
|
|
884
|
+
* required texture usage, or an orientation mismatch) safely keeps the CPU decode path.
|
|
885
|
+
* @param base first splat index of the validated range
|
|
886
|
+
* @param count number of splats in the range
|
|
887
|
+
* @param cpuPositions the CPU-decoded stride-4 positions for the range (ground truth)
|
|
888
|
+
*/
|
|
889
|
+
async _probeReadbackAsync(base, count, cpuPositions) {
|
|
890
|
+
this._readbackProbed = true;
|
|
891
|
+
if (!this._workBuffer) {
|
|
892
|
+
return;
|
|
893
|
+
}
|
|
894
|
+
const sampleCount = Math.min(count, 1024);
|
|
895
|
+
let ok = false;
|
|
896
|
+
try {
|
|
897
|
+
const gpu = await this._workBuffer.readCentersRangeAsync(base, sampleCount);
|
|
898
|
+
if (this._disposed) {
|
|
899
|
+
return;
|
|
900
|
+
}
|
|
901
|
+
if (gpu && gpu.length >= sampleCount * 4) {
|
|
902
|
+
ok = true;
|
|
903
|
+
for (let i = 0; i < sampleCount && ok; i++) {
|
|
904
|
+
for (let j = 0; j < 3; j++) {
|
|
905
|
+
const a = gpu[i * 4 + j];
|
|
906
|
+
const b = cpuPositions[i * 4 + j];
|
|
907
|
+
if (Math.abs(a - b) > 1e-2 * (1 + Math.abs(b))) {
|
|
908
|
+
ok = false;
|
|
909
|
+
break;
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
catch {
|
|
916
|
+
ok = false;
|
|
917
|
+
}
|
|
918
|
+
this._useGpuPositionReadback = ok;
|
|
919
|
+
Logger.Log(ok
|
|
920
|
+
? "GaussianSplattingStream: GPU position readback validated; streamed LOD positions are read back from the GPU."
|
|
921
|
+
: "GaussianSplattingStream: GPU position readback unavailable; decoding LOD positions on the CPU.");
|
|
922
|
+
}
|
|
923
|
+
/**
|
|
924
|
+
* Resolves the decoded positions for a splat range and applies them. Once GPU readback has been validated,
|
|
925
|
+
* positions are read back from the work buffer (non-blocking) and `pack.positions` is empty; otherwise the
|
|
926
|
+
* CPU-decoded `pack.positions` are used, and — on the first such decode — the GPU readback is validated
|
|
927
|
+
* against them so subsequent decodes can use the fast path.
|
|
928
|
+
* @param pack the parsed SOG pack (its `positions` is populated only on the CPU path)
|
|
929
|
+
* @param base first splat index of the range in the work buffer
|
|
930
|
+
* @param count number of splats in the range
|
|
931
|
+
* @returns whether positions were applied
|
|
932
|
+
*/
|
|
933
|
+
async _applyDecodedPositionsAsync(pack, base, count) {
|
|
934
|
+
if (this._useGpuPositionReadback && this._workBuffer) {
|
|
935
|
+
const positions = await this._workBuffer.readCentersRangeAsync(base, count);
|
|
936
|
+
if (this._disposed) {
|
|
937
|
+
return false;
|
|
938
|
+
}
|
|
939
|
+
if (positions && this._splatPositions) {
|
|
940
|
+
this._applyPositions(positions, base, count);
|
|
941
|
+
return true;
|
|
942
|
+
}
|
|
943
|
+
// Validated readback unexpectedly returned nothing; fall through to the (likely empty) CPU positions.
|
|
944
|
+
}
|
|
945
|
+
const cpu = pack.positions.length >= count * 4 ? pack.positions.subarray(0, count * 4) : null;
|
|
946
|
+
if (!cpu || !this._splatPositions) {
|
|
947
|
+
return false;
|
|
948
|
+
}
|
|
949
|
+
this._applyPositions(cpu, base, count);
|
|
950
|
+
// First CPU decode while readback is a candidate: validate it so later decodes can use the fast path.
|
|
951
|
+
if (!this._readbackProbed && this._readbackCandidate) {
|
|
952
|
+
await this._probeReadbackAsync(base, count, cpu);
|
|
953
|
+
}
|
|
954
|
+
return true;
|
|
955
|
+
}
|
|
673
956
|
/**
|
|
674
957
|
* Decodes the always-on environment bundle into its work-buffer block and activates its range.
|
|
675
958
|
*/
|
|
@@ -679,7 +962,7 @@ export class GaussianSplattingStream extends GaussianSplattingMesh {
|
|
|
679
962
|
}
|
|
680
963
|
const range = this._environmentRange;
|
|
681
964
|
try {
|
|
682
|
-
const parsed = await ParseSogMetaAsTextures(this._environmentFiles, "", this._scene);
|
|
965
|
+
const parsed = await ParseSogMetaAsTextures(this._environmentFiles, "", this._scene, !this._useGpuPositionReadback, this._downloadManager);
|
|
683
966
|
const pack = parsed.sogTextures;
|
|
684
967
|
if (!pack) {
|
|
685
968
|
return;
|
|
@@ -692,9 +975,10 @@ export class GaussianSplattingStream extends GaussianSplattingMesh {
|
|
|
692
975
|
if (this._disposed) {
|
|
693
976
|
return;
|
|
694
977
|
}
|
|
695
|
-
this.
|
|
696
|
-
this.
|
|
697
|
-
|
|
978
|
+
await this._applyDecodedPositionsAsync(pack, range.offset, range.count);
|
|
979
|
+
if (this._disposed) {
|
|
980
|
+
return;
|
|
981
|
+
}
|
|
698
982
|
this._refreshActiveRanges();
|
|
699
983
|
}
|
|
700
984
|
finally {
|
|
@@ -709,52 +993,197 @@ export class GaussianSplattingStream extends GaussianSplattingMesh {
|
|
|
709
993
|
/**
|
|
710
994
|
* Loads one LOD source file as GPU textures, decodes it into its fixed work-buffer block, records its
|
|
711
995
|
* CPU centers for sorting, frees the source textures, then promotes any nodes that were waiting for it.
|
|
712
|
-
* Concurrent or repeat requests for the same file are ignored.
|
|
996
|
+
* Concurrent or repeat requests for the same file are ignored. If the file is cancelled mid-flight
|
|
997
|
+
* (because every node that wanted it retargeted), the decode bails cooperatively at the next checkpoint.
|
|
713
998
|
* @param fileId file index to decode
|
|
714
999
|
*/
|
|
715
1000
|
async _decodeFileAsync(fileId) {
|
|
716
|
-
if (this._decodedFiles.has(fileId) || this._loadingFiles.has(fileId)) {
|
|
1001
|
+
if (this._decodedFiles.has(fileId) || this._loadingFiles.has(fileId) || !this._residency) {
|
|
717
1002
|
return;
|
|
718
1003
|
}
|
|
719
1004
|
const meta = this._fileMeta.get(fileId);
|
|
720
|
-
const base = this._fileBaseSplat.get(fileId);
|
|
721
1005
|
const count = this._fileCounts.get(fileId);
|
|
722
|
-
if (!meta ||
|
|
1006
|
+
if (!meta || count === undefined) {
|
|
723
1007
|
return;
|
|
724
1008
|
}
|
|
725
1009
|
this._loadingFiles.add(fileId);
|
|
1010
|
+
this._cancelledDecodes.delete(fileId);
|
|
1011
|
+
let allocated = false;
|
|
726
1012
|
try {
|
|
727
|
-
const parsed = await ParseSogMetaAsTextures(meta.sogData, meta.subRootUrl, this._scene);
|
|
1013
|
+
const parsed = await ParseSogMetaAsTextures(meta.sogData, meta.subRootUrl, this._scene, !this._useGpuPositionReadback, this._downloadManager, fileId);
|
|
728
1014
|
const pack = parsed.sogTextures;
|
|
729
1015
|
if (!pack) {
|
|
730
1016
|
return;
|
|
731
1017
|
}
|
|
1018
|
+
// Serialize the allocate -> decode -> readback section: a relayout runs only inside it (see
|
|
1019
|
+
// _relayoutAndAllocateAsync), so it never moves a file whose decode has not finished writing.
|
|
1020
|
+
const release = await this._acquireDecodeGateAsync();
|
|
732
1021
|
try {
|
|
733
|
-
if (this._disposed || !this._workBuffer) {
|
|
1022
|
+
if (this._disposed || !this._workBuffer || this._cancelledDecodes.has(fileId)) {
|
|
1023
|
+
return;
|
|
1024
|
+
}
|
|
1025
|
+
let base = this._residency.allocate(fileId, count);
|
|
1026
|
+
if (base === null) {
|
|
1027
|
+
// Defragment the work buffer to reclaim fragmented free space, then retry.
|
|
1028
|
+
base = await this._relayoutAndAllocateAsync(fileId, count);
|
|
1029
|
+
}
|
|
1030
|
+
if (base === null) {
|
|
1031
|
+
// No room even after evicting and compacting: refuse and keep nodes on their current LOD.
|
|
1032
|
+
// A file cancelled mid-flight isn't a budget problem, so don't warn for it.
|
|
1033
|
+
if (!this._cancelledDecodes.has(fileId)) {
|
|
1034
|
+
Logger.Warn(`GaussianSplattingStream: resident memory budget full; skipping LOD file ${fileId}.`);
|
|
1035
|
+
}
|
|
1036
|
+
return;
|
|
1037
|
+
}
|
|
1038
|
+
allocated = true;
|
|
1039
|
+
if (this._disposed || !this._workBuffer || this._cancelledDecodes.has(fileId)) {
|
|
734
1040
|
return;
|
|
735
1041
|
}
|
|
736
1042
|
await this._workBuffer.decodeAsync(pack, base);
|
|
1043
|
+
if (this._disposed || this._cancelledDecodes.has(fileId)) {
|
|
1044
|
+
return;
|
|
1045
|
+
}
|
|
1046
|
+
await this._applyDecodedPositionsAsync(pack, base, count);
|
|
737
1047
|
if (this._disposed) {
|
|
738
1048
|
return;
|
|
739
1049
|
}
|
|
740
|
-
this._splatPositions.set(pack.positions.subarray(0, count * 4), base * 4);
|
|
741
|
-
this._updateBounds(pack.positions, count);
|
|
742
1050
|
this._decodedFiles.add(fileId);
|
|
743
|
-
this._notifyWorkerNewData();
|
|
744
1051
|
// Promote any nodes that can now reach their desired LOD via this newly decoded file.
|
|
745
1052
|
if (this._applyDesiredLods()) {
|
|
746
1053
|
this._refreshActiveRanges();
|
|
747
1054
|
}
|
|
748
1055
|
}
|
|
749
1056
|
finally {
|
|
750
|
-
// Always release the GPU source textures (the decode pass is the only consumer).
|
|
751
1057
|
GaussianSplattingStream._DisposePack(pack);
|
|
1058
|
+
release();
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
catch (e) {
|
|
1062
|
+
// A cancelled file rejects its downloads on purpose — swallow that; re-throw genuine failures.
|
|
1063
|
+
if (!this._cancelledDecodes.has(fileId)) {
|
|
1064
|
+
throw e;
|
|
752
1065
|
}
|
|
753
1066
|
}
|
|
754
1067
|
finally {
|
|
1068
|
+
// If a slot was allocated but the decode did not complete (cancelled/disposed), release it.
|
|
1069
|
+
if (allocated && !this._decodedFiles.has(fileId)) {
|
|
1070
|
+
this._residency.free(fileId);
|
|
1071
|
+
}
|
|
755
1072
|
this._loadingFiles.delete(fileId);
|
|
1073
|
+
this._cancelledDecodes.delete(fileId);
|
|
756
1074
|
}
|
|
757
1075
|
}
|
|
1076
|
+
/**
|
|
1077
|
+
* Acquires the decode gate (a simple async mutex). Resolves once any prior holder releases, returning a
|
|
1078
|
+
* release function the caller must invoke in a `finally`.
|
|
1079
|
+
* @returns the release function
|
|
1080
|
+
*/
|
|
1081
|
+
async _acquireDecodeGateAsync() {
|
|
1082
|
+
const previous = this._decodeGate;
|
|
1083
|
+
let release;
|
|
1084
|
+
this._decodeGate = new Promise((resolve) => {
|
|
1085
|
+
release = resolve;
|
|
1086
|
+
});
|
|
1087
|
+
await previous;
|
|
1088
|
+
return release;
|
|
1089
|
+
}
|
|
1090
|
+
/**
|
|
1091
|
+
* Defragments the work buffer to make room for a file that did not fit, then allocates its slot. Runs the
|
|
1092
|
+
* compaction + GPU relayout atomically inside a single `onBeforeRender` so no inconsistent CPU/GPU layout
|
|
1093
|
+
* is ever rendered. Returns the new slot offset, or null if even compaction cannot free enough contiguous
|
|
1094
|
+
* space (the caller refuses the upgrade).
|
|
1095
|
+
* @param fileId file to allocate after compaction
|
|
1096
|
+
* @param count splats the file needs
|
|
1097
|
+
* @returns the allocated offset, or null
|
|
1098
|
+
*/
|
|
1099
|
+
async _relayoutAndAllocateAsync(fileId, count) {
|
|
1100
|
+
if (!this._residency || !this._workBuffer) {
|
|
1101
|
+
return null;
|
|
1102
|
+
}
|
|
1103
|
+
// Even a perfect compaction cannot help if the total free space is below what is needed.
|
|
1104
|
+
if (this._residency.freeSize < count) {
|
|
1105
|
+
return null;
|
|
1106
|
+
}
|
|
1107
|
+
return await new Promise((resolve) => {
|
|
1108
|
+
const attempt = () => {
|
|
1109
|
+
// Bail out (no relayout) if the file was cancelled while we waited for the shader to be ready,
|
|
1110
|
+
// so rapidly-changing targets don't trigger an expensive compaction for a file no longer needed.
|
|
1111
|
+
if (this._disposed || !this._residency || !this._workBuffer || this._cancelledDecodes.has(fileId)) {
|
|
1112
|
+
resolve(null);
|
|
1113
|
+
return;
|
|
1114
|
+
}
|
|
1115
|
+
if (!this._workBuffer.isRelayoutReady()) {
|
|
1116
|
+
this._scene.onBeforeRenderObservable.addOnce(attempt);
|
|
1117
|
+
return;
|
|
1118
|
+
}
|
|
1119
|
+
this._performRelayout();
|
|
1120
|
+
resolve(this._residency.allocate(fileId, count));
|
|
1121
|
+
};
|
|
1122
|
+
this._scene.onBeforeRenderObservable.addOnce(attempt);
|
|
1123
|
+
});
|
|
1124
|
+
}
|
|
1125
|
+
/**
|
|
1126
|
+
* Compacts the resident set and relocates the corresponding GPU textures and CPU positions to the new
|
|
1127
|
+
* layout. Must run at a frame-safe point with the work buffer's relayout shader ready.
|
|
1128
|
+
*/
|
|
1129
|
+
_performRelayout() {
|
|
1130
|
+
if (!this._residency || !this._workBuffer || !this._splatPositions) {
|
|
1131
|
+
return;
|
|
1132
|
+
}
|
|
1133
|
+
const oldOffsets = this._relayoutOldOffsets;
|
|
1134
|
+
oldOffsets.clear();
|
|
1135
|
+
for (const block of this._residency.getResidentBlocks()) {
|
|
1136
|
+
oldOffsets.set(block.file, block.offset);
|
|
1137
|
+
}
|
|
1138
|
+
const moves = this._residency.compact();
|
|
1139
|
+
if (moves.length === 0) {
|
|
1140
|
+
return;
|
|
1141
|
+
}
|
|
1142
|
+
const capacity = this._residency.capacity;
|
|
1143
|
+
if (!this._relayoutSrcIndex || this._relayoutSrcIndex.length !== capacity) {
|
|
1144
|
+
this._relayoutSrcIndex = new Float32Array(capacity);
|
|
1145
|
+
}
|
|
1146
|
+
const srcIndexByDst = this._relayoutSrcIndex;
|
|
1147
|
+
srcIndexByDst.fill(-1);
|
|
1148
|
+
const resident = this._residency.getResidentBlocks();
|
|
1149
|
+
// Destination->source splat index map for the GPU relayout pass.
|
|
1150
|
+
for (const block of resident) {
|
|
1151
|
+
const oldOffset = oldOffsets.get(block.file);
|
|
1152
|
+
for (let k = 0; k < block.count; k++) {
|
|
1153
|
+
srcIndexByDst[block.offset + k] = oldOffset + k;
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
// GPU: relocate the decoded textures in place (same texture instances).
|
|
1157
|
+
this._workBuffer.relayoutSync(srcIndexByDst);
|
|
1158
|
+
// CPU positions: compaction only ever moves a block to a lower offset, so copying in place in ascending
|
|
1159
|
+
// new-offset order is safe (a block's source is never overwritten by an earlier move). This avoids a
|
|
1160
|
+
// full capacity*4 scratch buffer.
|
|
1161
|
+
const positions = this._splatPositions;
|
|
1162
|
+
resident.sort((a, b) => a.offset - b.offset);
|
|
1163
|
+
for (const block of resident) {
|
|
1164
|
+
const oldOffset = oldOffsets.get(block.file);
|
|
1165
|
+
if (oldOffset !== block.offset) {
|
|
1166
|
+
positions.copyWithin(block.offset * 4, oldOffset * 4, (oldOffset + block.count) * 4);
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
// Update the environment offset (it may have moved), re-post to the sort worker, and refresh ranges.
|
|
1170
|
+
if (this._environmentRange) {
|
|
1171
|
+
const envOffset = this._residency.offset(EnvironmentFileId);
|
|
1172
|
+
if (envOffset !== undefined) {
|
|
1173
|
+
this._environmentRange.offset = envOffset;
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
this._notifyWorkerNewData();
|
|
1177
|
+
this._refreshActiveRanges();
|
|
1178
|
+
}
|
|
1179
|
+
/**
|
|
1180
|
+
* Drops a file evicted by the residency controller from the decoded set so it will be re-decoded on demand.
|
|
1181
|
+
* The file had no remaining references, so no node was rendering or downloading it.
|
|
1182
|
+
* @param fileId evicted file index
|
|
1183
|
+
*/
|
|
1184
|
+
_onFileEvicted(fileId) {
|
|
1185
|
+
this._decodedFiles.delete(fileId);
|
|
1186
|
+
}
|
|
758
1187
|
/**
|
|
759
1188
|
* Snaps a desired LOD level to the nearest level the node provides, while never selecting a level finer
|
|
760
1189
|
* than {@link maxDetailLod} (i.e. with an index below the cap). Ties prefer the finer allowed level. If
|
|
@@ -793,41 +1222,125 @@ export class GaussianSplattingStream extends GaussianSplattingMesh {
|
|
|
793
1222
|
}
|
|
794
1223
|
/**
|
|
795
1224
|
* Applies each node's {@link ISOGLODNode.targetLevel}: switches a node to its target level when that
|
|
796
|
-
* level's file is already decoded, otherwise
|
|
797
|
-
* nothing ever disappears). Nodes within their post-switch cooldown are
|
|
1225
|
+
* level's file is already decoded, otherwise records a pending download request for the file and leaves
|
|
1226
|
+
* the node on its current LOD (so nothing ever disappears). Nodes within their post-switch cooldown are
|
|
1227
|
+
* left untouched to damp oscillation (and keep their existing pending request).
|
|
1228
|
+
*
|
|
1229
|
+
* Each node tracks the single file it currently needs but lacks ({@link ISOGLODNode.pendingFile}). When a
|
|
1230
|
+
* node's target changes before that file finished downloading, the old file's reference is released; if no
|
|
1231
|
+
* other node still needs it, its queued/in-flight download is cancelled (see {@link _releaseFileRef}).
|
|
798
1232
|
* @returns true when at least one node changed LOD (callers should refresh the active ranges)
|
|
799
1233
|
*/
|
|
800
1234
|
_applyDesiredLods() {
|
|
801
1235
|
let dirty = false;
|
|
802
1236
|
for (const node of this._leafNodes) {
|
|
1237
|
+
// Nodes in cooldown keep their current LOD and their existing pending request untouched.
|
|
803
1238
|
if (node.lodCooldown && node.lodCooldown > 0) {
|
|
804
1239
|
continue;
|
|
805
1240
|
}
|
|
806
1241
|
const desired = node.targetLevel ?? node.baseLod;
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
1242
|
+
let newPending;
|
|
1243
|
+
if (desired !== node.activeLod) {
|
|
1244
|
+
const entry = node.lods[String(desired)];
|
|
1245
|
+
if (entry) {
|
|
1246
|
+
if (this._decodedFiles.has(entry.file)) {
|
|
1247
|
+
this._switchActiveFile(node, entry.file);
|
|
1248
|
+
node.activeLod = desired;
|
|
1249
|
+
node.lodCooldown = this._lodCooldownFrames;
|
|
1250
|
+
dirty = true;
|
|
1251
|
+
}
|
|
1252
|
+
else {
|
|
1253
|
+
newPending = entry.file;
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
818
1256
|
}
|
|
819
|
-
|
|
820
|
-
|
|
1257
|
+
// Reconcile this node's pending-download reference against its (possibly changed) target.
|
|
1258
|
+
if (node.pendingFile !== newPending) {
|
|
1259
|
+
if (node.pendingFile !== undefined) {
|
|
1260
|
+
this._releaseFileRef(node.pendingFile);
|
|
1261
|
+
}
|
|
1262
|
+
if (newPending !== undefined) {
|
|
1263
|
+
this._acquirePendingFile(newPending);
|
|
1264
|
+
}
|
|
1265
|
+
node.pendingFile = newPending;
|
|
821
1266
|
}
|
|
822
1267
|
}
|
|
823
1268
|
return dirty;
|
|
824
1269
|
}
|
|
825
1270
|
/**
|
|
826
|
-
*
|
|
827
|
-
* the
|
|
828
|
-
*
|
|
829
|
-
*
|
|
830
|
-
*
|
|
1271
|
+
* Moves a node's resident reference from its previous active file to the one it now renders, so the file
|
|
1272
|
+
* count that keeps a block in the work buffer stays accurate (and cancels any pending eviction of the new
|
|
1273
|
+
* file). The new file is already decoded.
|
|
1274
|
+
* @param node leaf node switching its rendered file
|
|
1275
|
+
* @param file the file the node now renders from
|
|
1276
|
+
*/
|
|
1277
|
+
_switchActiveFile(node, file) {
|
|
1278
|
+
if (node.activeFile === file) {
|
|
1279
|
+
return;
|
|
1280
|
+
}
|
|
1281
|
+
if (node.activeFile !== undefined) {
|
|
1282
|
+
this._releaseFileRef(node.activeFile);
|
|
1283
|
+
}
|
|
1284
|
+
this._acquireFileRef(file);
|
|
1285
|
+
node.activeFile = file;
|
|
1286
|
+
}
|
|
1287
|
+
/**
|
|
1288
|
+
* Adds a reference to a file (active render or pending download), cancelling any scheduled eviction.
|
|
1289
|
+
* @param fileId file index
|
|
1290
|
+
*/
|
|
1291
|
+
_acquireFileRef(fileId) {
|
|
1292
|
+
const refs = (this._fileRefs.get(fileId) ?? 0) + 1;
|
|
1293
|
+
this._fileRefs.set(fileId, refs);
|
|
1294
|
+
if (refs === 1) {
|
|
1295
|
+
// Referenced again before its eviction cooldown elapsed: keep it resident.
|
|
1296
|
+
this._residency?.cancelEviction(fileId);
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
/**
|
|
1300
|
+
* Records that a node needs a not-yet-decoded file, bumping its reference count and queueing the decode.
|
|
1301
|
+
* @param fileId file index the node now targets
|
|
1302
|
+
*/
|
|
1303
|
+
_acquirePendingFile(fileId) {
|
|
1304
|
+
this._acquireFileRef(fileId);
|
|
1305
|
+
this._enqueueDecode(fileId);
|
|
1306
|
+
}
|
|
1307
|
+
/**
|
|
1308
|
+
* Releases a node's reference to a file. When the last reference is dropped: a decoded file is scheduled
|
|
1309
|
+
* for eviction (when streaming under a budget), and a still-downloading file has its queued decode dropped
|
|
1310
|
+
* and any in-flight download cancelled.
|
|
1311
|
+
* @param fileId file index the node no longer references
|
|
1312
|
+
*/
|
|
1313
|
+
_releaseFileRef(fileId) {
|
|
1314
|
+
const refs = (this._fileRefs.get(fileId) ?? 0) - 1;
|
|
1315
|
+
if (refs > 0) {
|
|
1316
|
+
this._fileRefs.set(fileId, refs);
|
|
1317
|
+
return;
|
|
1318
|
+
}
|
|
1319
|
+
this._fileRefs.delete(fileId);
|
|
1320
|
+
if (this._decodedFiles.has(fileId)) {
|
|
1321
|
+
// No node renders it anymore: schedule eviction (only when streaming under a budget).
|
|
1322
|
+
if (this._evictionEnabled) {
|
|
1323
|
+
this._residency?.scheduleEviction(fileId);
|
|
1324
|
+
}
|
|
1325
|
+
return;
|
|
1326
|
+
}
|
|
1327
|
+
// Still downloading/queued: drop the queued decode and cancel any in-flight download.
|
|
1328
|
+
const queueIndex = this._decodeQueue.indexOf(fileId);
|
|
1329
|
+
if (queueIndex !== -1) {
|
|
1330
|
+
this._decodeQueue.splice(queueIndex, 1);
|
|
1331
|
+
}
|
|
1332
|
+
if (this._loadingFiles.has(fileId)) {
|
|
1333
|
+
// Flag the in-flight decode to bail at its next checkpoint and cancel its image downloads.
|
|
1334
|
+
this._cancelledDecodes.add(fileId);
|
|
1335
|
+
this._downloadManager.cancelGroup(fileId);
|
|
1336
|
+
}
|
|
1337
|
+
}
|
|
1338
|
+
/**
|
|
1339
|
+
* Per-frame LOD streaming loop. Ticks cooldowns and pumps the decode queue every frame, and runs the
|
|
1340
|
+
* cheap per-node frustum test every frame so the off-screen LOD bias tracks camera rotation. The LOD
|
|
1341
|
+
* re-evaluation is throttled to at most every {@link _lodUpdateInterval} frames once the camera has
|
|
1342
|
+
* translated far enough, but also runs immediately whenever a node enters/leaves the frustum (so its
|
|
1343
|
+
* detail upgrades/downgrades promptly) or a cap change forces it. Active ranges rebuild on any LOD change.
|
|
831
1344
|
*/
|
|
832
1345
|
_onLodFrame() {
|
|
833
1346
|
if (this._disposed || !this._baseLayerReady) {
|
|
@@ -838,29 +1351,70 @@ export class GaussianSplattingStream extends GaussianSplattingMesh {
|
|
|
838
1351
|
node.lodCooldown--;
|
|
839
1352
|
}
|
|
840
1353
|
}
|
|
1354
|
+
// Tick eviction cooldowns: unreferenced files are freed once their cooldown elapses (budgeted streaming).
|
|
1355
|
+
if (this._evictionEnabled) {
|
|
1356
|
+
this._residency?.tick();
|
|
1357
|
+
}
|
|
841
1358
|
// In-flight/queued decodes still progress every frame.
|
|
842
1359
|
this._pumpDecodeQueue();
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
1360
|
+
// Per-node frustum test runs every frame (cheap) so the off-screen LOD bias tracks camera rotation,
|
|
1361
|
+
// not just the translation that gates the throttled LOD re-evaluation below.
|
|
1362
|
+
const frustumChanged = this._updateNodeFrustum();
|
|
1363
|
+
let runLodEval = this._forceLodUpdate || frustumChanged;
|
|
1364
|
+
if (!runLodEval && ++this._framesSinceLodUpdate >= this._lodUpdateInterval) {
|
|
848
1365
|
const camera = this._scene.activeCamera;
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
if (
|
|
852
|
-
|
|
1366
|
+
const threshold = this._lodUpdateDistance;
|
|
1367
|
+
if (!camera || Vector3.DistanceSquared(camera.globalPosition, this._lastLodCamPos) >= threshold * threshold) {
|
|
1368
|
+
if (camera) {
|
|
1369
|
+
this._lastLodCamPos.copyFrom(camera.globalPosition);
|
|
853
1370
|
}
|
|
854
|
-
|
|
1371
|
+
runLodEval = true;
|
|
855
1372
|
}
|
|
856
1373
|
}
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
this.
|
|
1374
|
+
if (runLodEval) {
|
|
1375
|
+
this._forceLodUpdate = false;
|
|
1376
|
+
this._framesSinceLodUpdate = 0;
|
|
1377
|
+
this.evaluateOptimalLods(this._scene.activeCamera);
|
|
1378
|
+
this._computeTargetLevels();
|
|
1379
|
+
if (this._applyDesiredLods()) {
|
|
1380
|
+
this._refreshActiveRanges();
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1384
|
+
/**
|
|
1385
|
+
* Updates each leaf node's {@link ISOGLODNode.inFrustum} flag from a per-node frustum test against the
|
|
1386
|
+
* active camera. When {@link frustumCulling} is disabled (or there is no camera) every node is marked
|
|
1387
|
+
* in-frustum. Bounds are static (from the LOD tree), so flags are valid for all nodes regardless of
|
|
1388
|
+
* decode state. Returns true when any node's in-frustum state changed (so the LOD bias must be re-applied).
|
|
1389
|
+
* @returns whether any node's in-frustum state changed
|
|
1390
|
+
*/
|
|
1391
|
+
_updateNodeFrustum() {
|
|
1392
|
+
const camera = this._scene.activeCamera;
|
|
1393
|
+
let changed = false;
|
|
1394
|
+
if (!this._frustumCulling || !camera) {
|
|
1395
|
+
for (const node of this._leafNodes) {
|
|
1396
|
+
if (node.inFrustum === false) {
|
|
1397
|
+
node.inFrustum = true;
|
|
1398
|
+
changed = true;
|
|
1399
|
+
}
|
|
1400
|
+
}
|
|
1401
|
+
return changed;
|
|
1402
|
+
}
|
|
1403
|
+
// World-space frustum planes from the current view-projection, tested against each node's world AABB.
|
|
1404
|
+
// force=false uses the renderId/sync fast-path (still recomputes when the transform actually changed),
|
|
1405
|
+
// avoiding a full world-matrix recompute every frame for the per-node frustum test.
|
|
1406
|
+
const world = this.computeWorldMatrix(false);
|
|
1407
|
+
camera.getViewMatrix().multiplyToRef(camera.getProjectionMatrix(), this._cullViewProj);
|
|
1408
|
+
Frustum.GetPlanesToRef(this._cullViewProj, this._frustumPlanes);
|
|
1409
|
+
for (const node of this._leafNodes) {
|
|
1410
|
+
node.cullBounds.update(world);
|
|
1411
|
+
const inFrustum = node.cullBounds.isInFrustum(this._frustumPlanes);
|
|
1412
|
+
if (inFrustum !== node.inFrustum) {
|
|
1413
|
+
node.inFrustum = inFrustum;
|
|
1414
|
+
changed = true;
|
|
1415
|
+
}
|
|
863
1416
|
}
|
|
1417
|
+
return changed;
|
|
864
1418
|
}
|
|
865
1419
|
/**
|
|
866
1420
|
* Reads the splat count from SOG metadata.
|
|
@@ -919,7 +1473,7 @@ export class GaussianSplattingStream extends GaussianSplattingMesh {
|
|
|
919
1473
|
if (!entry) {
|
|
920
1474
|
continue;
|
|
921
1475
|
}
|
|
922
|
-
const base = this.
|
|
1476
|
+
const base = this._residency?.offset(entry.file);
|
|
923
1477
|
if (base === undefined) {
|
|
924
1478
|
continue;
|
|
925
1479
|
}
|