@woosh/meep-engine 3.27.0 → 3.28.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/build/bundle-worker-terrain.js +1 -1
- package/package.json +1 -1
- package/src/core/geom/3d/mat4/m4_linear_determinant.d.ts +15 -0
- package/src/core/geom/3d/mat4/m4_linear_determinant.d.ts.map +1 -0
- package/src/core/geom/3d/mat4/m4_linear_determinant.js +24 -0
- package/src/core/geom/3d/mat4/m4_normal_matrix3.d.ts +4 -2
- package/src/core/geom/3d/mat4/m4_normal_matrix3.d.ts.map +1 -1
- package/src/core/geom/3d/mat4/m4_normal_matrix3.js +21 -11
- package/src/engine/Engine.d.ts.map +1 -1
- package/src/engine/Engine.js +38 -1
- package/src/engine/graphics3/GraphicsEngine.d.ts +5 -1
- package/src/engine/graphics3/GraphicsEngine.d.ts.map +1 -1
- package/src/engine/graphics3/GraphicsEngine.js +834 -830
- package/src/shade/RENDERER_CONTRACT.md +19 -10
- package/src/shade/device/timing/GPU_PROFILER_PROPOSAL_2026_08_28.md +1 -1
- package/src/shade/playground/vgeo_runtime/README.md +27 -13
- package/src/shade/playground/vgeo_runtime/main.js +18 -5
- package/src/shade/playground/vgeo_runtime/sample_asset.d.ts +18 -0
- package/src/shade/playground/vgeo_runtime/sample_asset.d.ts.map +1 -0
- package/src/shade/playground/{vgeo_viewer → vgeo_runtime}/sample_asset.js +9 -8
- package/src/shade/playground/vgeo_scene/README.md +408 -0
- package/src/shade/playground/vgeo_scene/index.html +144 -0
- package/src/shade/playground/vgeo_scene/main.d.ts +2 -0
- package/src/shade/playground/vgeo_scene/main.d.ts.map +1 -0
- package/src/shade/playground/vgeo_scene/main.js +1716 -0
- package/src/shade/renderer/Renderer.d.ts +4 -1
- package/src/shade/renderer/Renderer.d.ts.map +1 -1
- package/src/shade/renderer/Renderer.js +5 -2
- package/src/shade/renderer/extension/RENDER_EXTENSION_DESIGN.md +1 -2
- package/src/shade/renderer/geometry/meshlet/MeshletBatch.d.ts.map +1 -1
- package/src/shade/renderer/geometry/meshlet/MeshletBatch.js +3 -29
- package/src/shade/renderer/geometry/meshlet/encoding/ENCODED_ATTRIBUTE_DEFAULT_VALUES.d.ts +22 -0
- package/src/shade/renderer/geometry/meshlet/encoding/ENCODED_ATTRIBUTE_DEFAULT_VALUES.d.ts.map +1 -0
- package/src/shade/renderer/geometry/meshlet/encoding/ENCODED_ATTRIBUTE_DEFAULT_VALUES.js +49 -0
- package/src/shade/renderer/geometry/virtual/VGEO_FORMAT.md +4 -2
- package/src/shade/renderer/geometry/virtual/VIRTUAL_GEOMETRY_DESIGN.md +10 -8
- package/src/shade/renderer/geometry/virtual/VIRTUAL_GEOMETRY_PLAN.md +12 -11
- package/src/shade/renderer/geometry/virtual/build/VGeoBuildOptions.d.ts +1 -1
- package/src/shade/renderer/geometry/virtual/build/VGeoBuildOptions.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/build/VGeoBuildOptions.js +1 -1
- package/src/shade/renderer/geometry/virtual/build/gltf/gltf_open_document.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/build/gltf/gltf_open_document.js +5 -9
- package/src/shade/renderer/geometry/virtual/build/level/VGeoLevel.d.ts +32 -0
- package/src/shade/renderer/geometry/virtual/build/level/VGeoLevel.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/build/level/VGeoLevelOptions.d.ts +59 -0
- package/src/shade/renderer/geometry/virtual/build/level/VGeoLevelOptions.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/build/level/VGeoLevelSummary.d.ts +44 -0
- package/src/shade/renderer/geometry/virtual/build/level/VGeoLevelSummary.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/build/level/vgeo_build_levels.d.ts +36 -0
- package/src/shade/renderer/geometry/virtual/build/level/vgeo_build_levels.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/build/{vgeo_build_levels.js → level/vgeo_build_levels.js} +61 -95
- package/src/shade/renderer/geometry/virtual/build/level/vgeo_build_root_group.d.ts +19 -0
- package/src/shade/renderer/geometry/virtual/build/level/vgeo_build_root_group.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/build/{vgeo_build_root_group.js → level/vgeo_build_root_group.js} +1 -1
- package/src/shade/renderer/geometry/virtual/build/mesh/VirtualGeometryMesh.d.ts +73 -0
- package/src/shade/renderer/geometry/virtual/build/mesh/VirtualGeometryMesh.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/build/{VirtualGeometryMesh.js → mesh/VirtualGeometryMesh.js} +7 -7
- package/src/shade/renderer/geometry/virtual/build/mesh/vgeo_compute_vertex_normals.d.ts +29 -0
- package/src/shade/renderer/geometry/virtual/build/mesh/vgeo_compute_vertex_normals.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/build/{vgeo_compute_vertex_normals.js → mesh/vgeo_compute_vertex_normals.js} +3 -3
- package/src/shade/renderer/geometry/virtual/build/mesh/vgeo_compute_vertex_tangents.d.ts +31 -0
- package/src/shade/renderer/geometry/virtual/build/mesh/vgeo_compute_vertex_tangents.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/build/{vgeo_compute_vertex_tangents.js → mesh/vgeo_compute_vertex_tangents.js} +5 -5
- package/src/shade/renderer/geometry/virtual/build/mesh/vgeo_normalize_source.d.ts +46 -0
- package/src/shade/renderer/geometry/virtual/build/mesh/vgeo_normalize_source.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/build/{vgeo_normalize_source.js → mesh/vgeo_normalize_source.js} +9 -9
- package/src/shade/renderer/geometry/virtual/build/page/vgeo_assemble_pages.d.ts +13 -0
- package/src/shade/renderer/geometry/virtual/build/page/vgeo_assemble_pages.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/build/{vgeo_assemble_pages.js → page/vgeo_assemble_pages.js} +9 -9
- package/src/shade/renderer/geometry/virtual/build/page/vgeo_build_page_nodes.d.ts +13 -0
- package/src/shade/renderer/geometry/virtual/build/page/vgeo_build_page_nodes.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/build/{vgeo_build_page_nodes.js → page/vgeo_build_page_nodes.js} +4 -4
- package/src/shade/renderer/geometry/virtual/build/page/vgeo_page_node_count.d.ts +22 -0
- package/src/shade/renderer/geometry/virtual/build/page/vgeo_page_node_count.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/build/{vgeo_page_node_count.js → page/vgeo_page_node_count.js} +2 -2
- package/src/shade/renderer/geometry/virtual/build/partition/VGeoFaceGraph.d.ts +52 -0
- package/src/shade/renderer/geometry/virtual/build/partition/VGeoFaceGraph.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/build/{VGeoFaceGraph.js → partition/VGeoFaceGraph.js} +1 -1
- package/src/shade/renderer/geometry/virtual/build/partition/vgeo_graph_components.d.ts +14 -0
- package/src/shade/renderer/geometry/virtual/build/partition/vgeo_graph_components.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/build/partition/vgeo_partition_graph.d.ts +29 -0
- package/src/shade/renderer/geometry/virtual/build/partition/vgeo_partition_graph.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/build/{vgeo_partition_graph.js → partition/vgeo_partition_graph.js} +2 -2
- package/src/shade/renderer/geometry/virtual/build/partition/vgeo_partition_subgraph.d.ts +41 -0
- package/src/shade/renderer/geometry/virtual/build/partition/vgeo_partition_subgraph.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/build/partition/vgeo_partition_subgraph.js +92 -0
- package/src/shade/renderer/geometry/virtual/build/partition/vgeo_split_face_set.d.ts +10 -0
- package/src/shade/renderer/geometry/virtual/build/partition/vgeo_split_face_set.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/build/{vgeo_split_face_set.js → partition/vgeo_split_face_set.js} +12 -55
- package/src/shade/renderer/geometry/virtual/build/partition/vgeo_split_face_set_packed.d.ts +15 -0
- package/src/shade/renderer/geometry/virtual/build/partition/vgeo_split_face_set_packed.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/build/{vgeo_split_face_set_packed.js → partition/vgeo_split_face_set_packed.js} +3 -3
- package/src/shade/renderer/geometry/virtual/build/sphere/vgeo_bounding_sphere.d.ts +12 -0
- package/src/shade/renderer/geometry/virtual/build/sphere/vgeo_bounding_sphere.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/build/{vgeo_bounding_sphere.js → sphere/vgeo_bounding_sphere.js} +3 -3
- package/src/shade/renderer/geometry/virtual/build/sphere/vgeo_containing_sphere.d.ts +18 -0
- package/src/shade/renderer/geometry/virtual/build/sphere/vgeo_containing_sphere.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/build/{vgeo_containing_sphere.js → sphere/vgeo_containing_sphere.js} +3 -3
- package/src/shade/renderer/geometry/virtual/build/sphere/vgeo_sphere_inflate_to_contain.d.ts +9 -0
- package/src/shade/renderer/geometry/virtual/build/sphere/vgeo_sphere_inflate_to_contain.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/build/{vgeo_sphere_inflate_to_contain.js → sphere/vgeo_sphere_inflate_to_contain.js} +1 -1
- package/src/shade/renderer/geometry/virtual/build/submesh/VGeoGatheredFaces.d.ts +24 -0
- package/src/shade/renderer/geometry/virtual/build/submesh/VGeoGatheredFaces.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/build/submesh/VGeoSubmesh.d.ts +20 -0
- package/src/shade/renderer/geometry/virtual/build/submesh/VGeoSubmesh.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/build/submesh/vgeo_extract_submesh.d.ts +14 -0
- package/src/shade/renderer/geometry/virtual/build/submesh/vgeo_extract_submesh.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/build/{vgeo_extract_submesh.js → submesh/vgeo_extract_submesh.js} +5 -5
- package/src/shade/renderer/geometry/virtual/build/submesh/vgeo_gather_faces.d.ts +12 -0
- package/src/shade/renderer/geometry/virtual/build/submesh/vgeo_gather_faces.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/build/{vgeo_gather_faces.js → submesh/vgeo_gather_faces.js} +2 -2
- package/src/shade/renderer/geometry/virtual/build/submesh/vgeo_narrow_indices.d.ts +11 -0
- package/src/shade/renderer/geometry/virtual/build/submesh/vgeo_narrow_indices.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/build/{vgeo_narrow_indices.js → submesh/vgeo_narrow_indices.js} +3 -3
- package/src/shade/renderer/geometry/virtual/build/vgeo_build.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/build/vgeo_build.js +9 -11
- package/src/shade/renderer/geometry/virtual/format/attribute/vgeo_attributes_read.d.ts +8 -2
- package/src/shade/renderer/geometry/virtual/format/attribute/vgeo_attributes_read.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/format/attribute/vgeo_attributes_read.js +8 -2
- package/src/shade/renderer/geometry/virtual/format/page/VGEO_GROUP_OFFSET.d.ts +15 -0
- package/src/shade/renderer/geometry/virtual/format/page/VGEO_GROUP_OFFSET.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/format/page/VGEO_GROUP_OFFSET.js +15 -0
- package/src/shade/renderer/geometry/virtual/format/payload/vgeo_encode_cluster_payload.d.ts +28 -0
- package/src/shade/renderer/geometry/virtual/format/payload/vgeo_encode_cluster_payload.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/format/payload/vgeo_encode_cluster_payload.js +7 -3
- package/src/shade/renderer/geometry/virtual/format/read/vgeo_read_page.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/format/read/vgeo_read_page.js +9 -4
- package/src/shade/renderer/geometry/virtual/format/vgeo_validate_container.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/format/vgeo_validate_container.js +18 -5
- package/src/shade/renderer/geometry/virtual/format/vgeo_write_container.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/format/vgeo_write_container.js +9 -1
- package/src/shade/renderer/geometry/virtual/runtime/CUT_PLAN_2026_09_14.md +21 -2
- package/src/shade/renderer/geometry/virtual/runtime/GPUVirtualGeometryRuntime.d.ts +52 -2
- package/src/shade/renderer/geometry/virtual/runtime/GPUVirtualGeometryRuntime.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/runtime/GPUVirtualGeometryRuntime.js +97 -1
- package/src/shade/renderer/geometry/virtual/runtime/RUNTIME_PLAN.md +4 -4
- package/src/shade/renderer/geometry/virtual/runtime/VGeoFeedbackLoop.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/runtime/VGeoFeedbackLoop.js +10 -0
- package/src/shade/renderer/geometry/virtual/runtime/VGeoResidency.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/runtime/VGeoResidency.js +59 -34
- package/src/shade/renderer/geometry/virtual/runtime/cut/chunk_vgeo_cut_access.d.ts +8 -8
- package/src/shade/renderer/geometry/virtual/runtime/cut/chunk_vgeo_cut_access.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/runtime/cut/chunk_vgeo_cut_access.js +11 -88
- package/src/shade/renderer/geometry/virtual/runtime/cut/chunk_vgeo_cut_arena.d.ts +19 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/chunk_vgeo_cut_arena.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/chunk_vgeo_cut_arena.js +98 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/chunk_vgeo_cut_claim.d.ts +31 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/chunk_vgeo_cut_claim.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/chunk_vgeo_cut_claim.js +99 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/chunk_vgeo_cut_queue.d.ts +17 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/chunk_vgeo_cut_queue.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/chunk_vgeo_cut_queue.js +39 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_reseed.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_reseed.js +7 -63
- package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_seed.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_seed.js +13 -30
- package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_traverse.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_traverse.js +6 -52
- package/src/shade/renderer/loader/gltf/load_gltf.d.ts.map +1 -1
- package/src/shade/renderer/loader/gltf/load_gltf.js +0 -19
- package/src/shade/renderer/material/chunk_material_draw_side.d.ts +14 -0
- package/src/shade/renderer/material/chunk_material_draw_side.d.ts.map +1 -0
- package/src/shade/renderer/material/chunk_material_draw_side.js +34 -0
- package/src/shade/renderer/material/standard/fragment_gbuffer.d.ts.map +1 -1
- package/src/shade/renderer/material/standard/fragment_gbuffer.js +8 -1
- package/src/shade/renderer/path_tracer/shader/chunk_sample_material_data.d.ts.map +1 -1
- package/src/shade/renderer/path_tracer/shader/chunk_sample_material_data.js +13 -1
- package/src/shade/renderer/rasterize/bucket/construct_primitive_state.d.ts +19 -5
- package/src/shade/renderer/rasterize/bucket/construct_primitive_state.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/bucket/construct_primitive_state.js +23 -5
- package/src/shade/renderer/rasterize/bucket/filter_materials_by_bucket.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/bucket/filter_materials_by_bucket.js +12 -5
- package/src/shade/renderer/rasterize/bucket/rasterization_opaque_buckets.d.ts +22 -0
- package/src/shade/renderer/rasterize/bucket/rasterization_opaque_buckets.d.ts.map +1 -0
- package/src/shade/renderer/rasterize/bucket/rasterization_opaque_buckets.js +36 -0
- package/src/shade/renderer/rasterize/chunk_triangle_winding_is_reversed.d.ts +15 -0
- package/src/shade/renderer/rasterize/chunk_triangle_winding_is_reversed.d.ts.map +1 -0
- package/src/shade/renderer/rasterize/chunk_triangle_winding_is_reversed.js +35 -0
- package/src/shade/renderer/rasterize/expand/bucket/RASTERIZATION_BUCKET_LIMIT.d.ts +10 -1
- package/src/shade/renderer/rasterize/expand/bucket/RASTERIZATION_BUCKET_LIMIT.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/expand/bucket/RASTERIZATION_BUCKET_LIMIT.js +38 -29
- package/src/shade/renderer/rasterize/expand/bucket/chunk_rasterization_material_bucket.d.ts +5 -0
- package/src/shade/renderer/rasterize/expand/bucket/chunk_rasterization_material_bucket.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/expand/bucket/chunk_rasterization_material_bucket.js +63 -55
- package/src/shade/renderer/rasterize/expand/graph_meshlets_rasterize.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/expand/graph_meshlets_rasterize.js +1 -0
- package/src/shade/renderer/rasterize/fast/graph_rasterize_scene_fast.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/fast/graph_rasterize_scene_fast.js +44 -53
- package/src/shade/renderer/rasterize/forward/chunk_forward_shade_standard_fragment_brick4.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/forward/chunk_forward_shade_standard_fragment_brick4.js +6 -2
- package/src/shade/renderer/rasterize/forward/chunk_forward_shade_standard_fragment_ibl.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/forward/chunk_forward_shade_standard_fragment_ibl.js +6 -2
- package/src/shade/renderer/rasterize/forward/vertex_shader_chunk.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/forward/vertex_shader_chunk.js +19 -2
- package/src/shade/renderer/rasterize/native/avboit/AVBOIT_PLAN_2026_09_03.md +7 -4
- package/src/shade/renderer/rasterize/native/avboit/graph_rasterize_meshes_transparent_avboit.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/native/avboit/graph_rasterize_meshes_transparent_avboit.js +3 -2
- package/src/shade/renderer/rasterize/native/chunk_rasterize_triangle.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/native/chunk_rasterize_triangle.js +15 -4
- package/src/shade/renderer/rasterize/native/depth/shadow_rasterization_alpha_tested_pass_descriptor.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/native/depth/shadow_rasterization_alpha_tested_pass_descriptor.js +16 -5
- package/src/shade/renderer/rasterize/native/depth/shadow_rasterization_pass_descriptor.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/native/depth/shadow_rasterization_pass_descriptor.js +16 -5
- package/src/shade/renderer/rasterize/native/oit/graph_rasterize_meshes_transparent_oit.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/native/oit/graph_rasterize_meshes_transparent_oit.js +2 -1
- package/src/shade/renderer/rasterize/native/rasterization_shader_resources.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/native/rasterization_shader_resources.js +4 -0
- package/src/shade/renderer/rasterize/native/viz/viz_rasterization_alpha_tested_pass_descriptor.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/native/viz/viz_rasterization_alpha_tested_pass_descriptor.js +16 -7
- package/src/shade/renderer/rasterize/standard/graph_rasterize_partial_opaque.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/standard/graph_rasterize_partial_opaque.js +78 -88
- package/src/shade/renderer/rasterize/standard/graph_rasterize_remaining_opaque.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/standard/graph_rasterize_remaining_opaque.js +21 -31
- package/src/shade/renderer/scene/GPUSceneContext.d.ts.map +1 -1
- package/src/shade/renderer/scene/GPUSceneContext.js +2 -0
- package/src/shade/renderer/scene/NODE3D_STRUCT.d.ts +19 -0
- package/src/shade/renderer/scene/NODE3D_STRUCT.d.ts.map +1 -1
- package/src/shade/renderer/scene/NODE3D_STRUCT.js +34 -0
- package/src/shade/renderer/scene/chunk_node3d_flags.d.ts +9 -0
- package/src/shade/renderer/scene/chunk_node3d_flags.d.ts.map +1 -0
- package/src/shade/renderer/scene/chunk_node3d_flags.js +23 -0
- package/src/shade/renderer/scene/chunk_scene_database_access.d.ts.map +1 -1
- package/src/shade/renderer/scene/chunk_scene_database_access.js +15 -1
- package/src/shade/renderer/scene/rows/GPUSceneRows.d.ts.map +1 -1
- package/src/shade/renderer/scene/rows/GPUSceneRows.js +9 -1
- package/src/shade/renderer/shader/chunk/geometry/mat4/chunk_compute_normal_matrix_from_m4.d.ts +7 -0
- package/src/shade/renderer/shader/chunk/geometry/mat4/chunk_compute_normal_matrix_from_m4.d.ts.map +1 -1
- package/src/shade/renderer/shader/chunk/geometry/mat4/chunk_compute_normal_matrix_from_m4.js +16 -4
- package/src/shade/renderer/shader/chunk/geometry/mat4/chunk_mat4_linear_determinant.d.ts +12 -0
- package/src/shade/renderer/shader/chunk/geometry/mat4/chunk_mat4_linear_determinant.d.ts.map +1 -0
- package/src/shade/renderer/shader/chunk/geometry/mat4/chunk_mat4_linear_determinant.js +18 -0
- package/src/shade/renderer/shader/chunk/geometry/triangle/chunk_triangle_list_reverse_winding.d.ts +24 -0
- package/src/shade/renderer/shader/chunk/geometry/triangle/chunk_triangle_list_reverse_winding.d.ts.map +1 -0
- package/src/shade/renderer/shader/chunk/geometry/triangle/chunk_triangle_list_reverse_winding.js +33 -0
- package/src/shade/playground/vgeo_viewer/README.md +0 -150
- package/src/shade/playground/vgeo_viewer/cut_geometry.js +0 -460
- package/src/shade/playground/vgeo_viewer/index.html +0 -77
- package/src/shade/playground/vgeo_viewer/main.js +0 -1343
- package/src/shade/playground/vgeo_viewer/select_cut.js +0 -481
- package/src/shade/renderer/geometry/virtual/residency/VGeoResidencyManager.d.ts +0 -258
- package/src/shade/renderer/geometry/virtual/residency/VGeoResidencyManager.d.ts.map +0 -1
- package/src/shade/renderer/geometry/virtual/residency/VGeoResidencyOptions.d.ts +0 -49
- package/src/shade/renderer/geometry/virtual/residency/VGeoResidencyOptions.d.ts.map +0 -1
- package/src/shade/renderer/loader/gltf/fix_up_material_sides.js +0 -109
- package/src/shade/renderer/rasterize/expand/graph_triangles_filter_by_material_side.js +0 -78
- package/src/shade/renderer/rasterize/expand/shader_filter_triangle_by_material_side.js +0 -100
- package/src/shade/renderer/rasterize/forward/chunk_material_side_should_draw.js +0 -26
- package/src/shade/renderer/shader/chunk/jobs/chunk_job_system.d.ts +0 -3
- package/src/shade/renderer/shader/chunk/jobs/chunk_job_system.d.ts.map +0 -1
- /package/src/shade/renderer/geometry/virtual/build/{VGeoLevel.js → level/VGeoLevel.js} +0 -0
- /package/src/shade/renderer/geometry/virtual/build/{VGeoLevelOptions.js → level/VGeoLevelOptions.js} +0 -0
- /package/src/shade/renderer/geometry/virtual/build/{VGeoLevelSummary.js → level/VGeoLevelSummary.js} +0 -0
- /package/src/shade/renderer/geometry/virtual/build/{vgeo_graph_components.js → partition/vgeo_graph_components.js} +0 -0
- /package/src/shade/renderer/geometry/virtual/build/{VGeoGatheredFaces.js → submesh/VGeoGatheredFaces.js} +0 -0
- /package/src/shade/renderer/geometry/virtual/build/{VGeoSubmesh.js → submesh/VGeoSubmesh.js} +0 -0
|
@@ -1,1343 +0,0 @@
|
|
|
1
|
-
import Vector3 from "../../../core/geom/Vector3.js";
|
|
2
|
-
import { DEG_TO_RAD } from "../../../core/math/DEG_TO_RAD.js";
|
|
3
|
-
import { OrbitalCameraController } from "../../renderer/camera/orbital/index.js";
|
|
4
|
-
import { PerspectiveCamera } from "../../renderer/camera/PerspectiveCamera.js";
|
|
5
|
-
import { create_frame_loop } from "../../renderer/create_frame_loop.js";
|
|
6
|
-
import { DEFAULT_DEPTH_TEXTURE_VIEW } from "../../renderer/depth/DEFAULT_DEPTH_TEXTURE_VIEW.js";
|
|
7
|
-
import { DEPTH_BACKGROUND_VALUE } from "../../renderer/depth/DEPTH_BACKGROUND_VALUE.js";
|
|
8
|
-
import { GPUDynamicMeshRenderer } from "../../renderer/dynamic/GPUDynamicMeshRenderer.js";
|
|
9
|
-
import { FramePhase } from "../../renderer/extension/FramePhase.js";
|
|
10
|
-
import { RenderExtension } from "../../renderer/extension/RenderExtension.js";
|
|
11
|
-
import { SceneColor } from "../../renderer/extension/SceneColor.js";
|
|
12
|
-
import { ViewTextures } from "../../renderer/extension/ViewTextures.js";
|
|
13
|
-
import {
|
|
14
|
-
vgeo_codec_name
|
|
15
|
-
} from "../../renderer/geometry/virtual/format/frame/vgeo_codec_name.js";
|
|
16
|
-
import {
|
|
17
|
-
VGeoByteSource
|
|
18
|
-
} from "../../renderer/geometry/virtual/format/read/VGeoByteSource.js";
|
|
19
|
-
import {
|
|
20
|
-
VGeoContainerReader
|
|
21
|
-
} from "../../renderer/geometry/virtual/format/read/VGeoContainerReader.js";
|
|
22
|
-
import {
|
|
23
|
-
vgeo_fetch_byte_source
|
|
24
|
-
} from "../../renderer/geometry/virtual/format/read/vgeo_fetch_byte_source.js";
|
|
25
|
-
import {
|
|
26
|
-
VGeoReadOptions
|
|
27
|
-
} from "../../renderer/geometry/virtual/format/read/VGeoReadOptions.js";
|
|
28
|
-
import {
|
|
29
|
-
vgeo_audit_container
|
|
30
|
-
} from "../../renderer/geometry/virtual/format/read/tool/vgeo_audit_container.js";
|
|
31
|
-
import {
|
|
32
|
-
vgeo_install_all_pages
|
|
33
|
-
} from "../../renderer/geometry/virtual/format/read/tool/vgeo_install_all_pages.js";
|
|
34
|
-
import { make_sunlight } from "../../renderer/light/make_sunlight.js";
|
|
35
|
-
import { place_light } from "../../renderer/light/place_light.js";
|
|
36
|
-
import { EntityComponentDataset } from "../../../engine/ecs/EntityComponentDataset.js";
|
|
37
|
-
import { Renderer } from "../../renderer/Renderer.js";
|
|
38
|
-
import { DynamicMesh } from "../../renderer/scene/DynamicMesh.js";
|
|
39
|
-
import { DynamicMeshBatch } from "../../renderer/scene/DynamicMeshBatch.js";
|
|
40
|
-
import { Scene } from "../../renderer/scene/Scene.js";
|
|
41
|
-
import { VGEO_COLOR_MODE, vgeo_build_cut_geometry } from "./cut_geometry.js";
|
|
42
|
-
import { vgeo_sample_source } from "./sample_asset.js";
|
|
43
|
-
import { VGeoCut, VGeoCutView, vgeo_select_cut } from "./select_cut.js";
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* An interactive viewer for the `.vgeo` container.
|
|
47
|
-
*
|
|
48
|
-
* Point it at a file and it is opened *incrementally* — the header, the level table and the root
|
|
49
|
-
* page, and after that only the pages a cut actually asks for. What is drawn is not the asset but
|
|
50
|
-
* the **cut**: the clusters the container's own metric selects for where the camera is standing,
|
|
51
|
-
* coloured so the seams between them are visible.
|
|
52
|
-
*
|
|
53
|
-
* Two things are therefore on screen at once, and telling them apart is the point of the tool:
|
|
54
|
-
*
|
|
55
|
-
* - **The cut is frozen until you ask for a new one.** "Adapt to view" re-runs the traversal at
|
|
56
|
-
* wherever the camera is now; everything after that is you flying around a decision taken from
|
|
57
|
-
* somewhere else, which is the only way to see what a cut actually selected. Turn `follow the
|
|
58
|
-
* camera` on to watch it chase instead.
|
|
59
|
-
* - **A cut taken from a partly-arrived file is clamped, not wrong.** Where the page holding a
|
|
60
|
-
* refinement is missing, the coarser cluster is drawn and a want is recorded — a watertight
|
|
61
|
-
* surface either way. `colour by clamp` paints exactly those regions, and `stream wanted pages`
|
|
62
|
-
* fetches them and re-adapts until the count reaches zero.
|
|
63
|
-
*
|
|
64
|
-
* Drawing goes through Shade's **direct geometry path** — a {@link DynamicMesh} drawn straight from
|
|
65
|
-
* its attributes, no meshlet encode — because a cut is rebuilt from scratch every time it is
|
|
66
|
-
* adapted, and a meshlet build per adapt would price the tool out of being interactive. It is drawn
|
|
67
|
-
* *opaque* on that path ({@link GPUDynamicMeshRenderer.opaque}), so the depth buffer orders the
|
|
68
|
-
* cut's own triangles: a cut is a solid surface and nothing on the CPU can order one correctly.
|
|
69
|
-
*
|
|
70
|
-
* The reader under it is not a debug reader. It is `format/read/`, it is what a runtime would use,
|
|
71
|
-
* and everything above — the traversal, the clamp, the wants — is the page's own. See README.md.
|
|
72
|
-
*
|
|
73
|
-
* @author Alex Goldring
|
|
74
|
-
* @copyright Company Named Limited (c) 2026
|
|
75
|
-
*/
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* How many wanted pages one streaming round installs before the cut is retaken and the panel
|
|
79
|
-
* redrawn.
|
|
80
|
-
*
|
|
81
|
-
* Small on purpose. A round that installed every want would arrive as one step from coarse to final
|
|
82
|
-
* and show nothing of the process; this is what makes refinement visible while it happens, which is
|
|
83
|
-
* the only reason to look at a streaming reader rather than read its numbers.
|
|
84
|
-
*
|
|
85
|
-
* @type {number}
|
|
86
|
-
*/
|
|
87
|
-
const STREAM_BATCH = 8;
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* The head prefixes the panel offers, in bytes.
|
|
91
|
-
*
|
|
92
|
-
* §10 gives a reader two cold starts and this is the dial between them: the header alone, and then
|
|
93
|
-
* one exact range per page it decides it wants — or one speculative prefix that brings the head and
|
|
94
|
-
* whatever else it happens to cover. Both are correct and they cost very differently, and which is
|
|
95
|
-
* better depends entirely on how big the asset is against how much of it a view needs. Being able to
|
|
96
|
-
* flip between them on one file, and watch `read` and `requests` move, is most of what there is to
|
|
97
|
-
* learn about the front of this format.
|
|
98
|
-
*
|
|
99
|
-
* @type {[string, number][]}
|
|
100
|
-
*/
|
|
101
|
-
const HEAD_PREFIXES = [
|
|
102
|
-
['256 B — header only', 256],
|
|
103
|
-
['16 KB', 16 * 1024],
|
|
104
|
-
['64 KB — the default', 64 * 1024],
|
|
105
|
-
['256 KB', 256 * 1024],
|
|
106
|
-
['1 MB', 1024 * 1024],
|
|
107
|
-
];
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* @param {number} value
|
|
111
|
-
* @returns {string}
|
|
112
|
-
*/
|
|
113
|
-
function thousands(value) {
|
|
114
|
-
return value.toLocaleString('en-US');
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
/**
|
|
118
|
-
* @param {[string, string][]} entries
|
|
119
|
-
* @returns {string} one label/value row apiece
|
|
120
|
-
*/
|
|
121
|
-
function panel_table(entries) {
|
|
122
|
-
return entries
|
|
123
|
-
.map(([key, value]) => `<div class="row"><span class="muted">${key}</span><span>${value}</span></div>`)
|
|
124
|
-
.join('');
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
/**
|
|
128
|
-
* @param {number} bytes
|
|
129
|
-
* @returns {string}
|
|
130
|
-
*/
|
|
131
|
-
function human_bytes(bytes) {
|
|
132
|
-
if (bytes < 1024) {
|
|
133
|
-
return `${bytes} B`;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
if (bytes < 1024 * 1024) {
|
|
137
|
-
return `${(bytes / 1024).toFixed(1)} KB`;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
if (bytes < 1024 * 1024 * 1024) {
|
|
141
|
-
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
/**
|
|
148
|
-
* @param {string} text
|
|
149
|
-
* @returns {string}
|
|
150
|
-
*/
|
|
151
|
-
function escape_html(text) {
|
|
152
|
-
return String(text).replace(/[&<>]/g, (c) => ({ '&': '&', '<': '<', '>': '>' })[c]);
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
/**
|
|
156
|
-
* What a reader's quarantine amounts to, for the panel.
|
|
157
|
-
*
|
|
158
|
-
* A page that has failed its retries is not an error the viewer can act on — the clamped cut keeps
|
|
159
|
-
* that surface correct with a coarser ancestor — but it is the difference between an asset that is
|
|
160
|
-
* coarse because the camera just moved and one that is coarse because a page will never arrive, and
|
|
161
|
-
* those look identical on screen. So the count goes on the panel, with the first reason.
|
|
162
|
-
*
|
|
163
|
-
* @param {VGeoContainerReader} reader
|
|
164
|
-
* @returns {string}
|
|
165
|
-
*/
|
|
166
|
-
function quarantine_summary(reader) {
|
|
167
|
-
const count = reader.quarantined_page_count;
|
|
168
|
-
|
|
169
|
-
if (count === 0) {
|
|
170
|
-
return 'none';
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
const [page, reason] = reader.quarantine().next().value;
|
|
174
|
-
|
|
175
|
-
return `${thousands(count)} page(s) · page ${page}: ${escape_html(reason)}`;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
/**
|
|
179
|
-
* Everything the panel says about the resident set, in one pass over it.
|
|
180
|
-
*
|
|
181
|
-
* One pass, and over `resident_pages()` rather than `pages`, because the panel refreshes on every
|
|
182
|
-
* streaming round and `VGeoContainerReader.pages` is a snapshot that allocates an array and sorts
|
|
183
|
-
* it — 0.29 ms at 20,000 resident pages, and this used to ask for two of them.
|
|
184
|
-
*
|
|
185
|
-
* Storage: stored is what gets fetched and what sits on disk, decoded is what a resident page costs
|
|
186
|
-
* in RAM. The codec is per frame, so a file can genuinely be a mixture — the encoder stores a page
|
|
187
|
-
* raw whenever compressing it would not have shrunk it — and naming all of them is more honest than
|
|
188
|
-
* naming whichever one came first.
|
|
189
|
-
*
|
|
190
|
-
* Normals: worth saying outright because the alternative reads as a broken viewer. A source that
|
|
191
|
-
* carried no normals encodes the engine default everywhere, every cluster compresses to one constant
|
|
192
|
-
* word, and `colour by stored normal` comes out one flat colour across the whole model.
|
|
193
|
-
*
|
|
194
|
-
* @param {VGeoContainerReader} reader
|
|
195
|
-
* @returns {{storage: string, normals: string}}
|
|
196
|
-
*/
|
|
197
|
-
function page_summary(reader) {
|
|
198
|
-
let stored = 0;
|
|
199
|
-
let decoded = 0;
|
|
200
|
-
let per_vertex = 0;
|
|
201
|
-
let clusters = 0;
|
|
202
|
-
let pages = 0;
|
|
203
|
-
|
|
204
|
-
const codecs = new Set();
|
|
205
|
-
|
|
206
|
-
for (const page of reader.resident_pages()) {
|
|
207
|
-
stored += page.stored_size;
|
|
208
|
-
decoded += page.decoded_size;
|
|
209
|
-
per_vertex += page.per_vertex_normal_cluster_count;
|
|
210
|
-
clusters += page.cluster_count;
|
|
211
|
-
|
|
212
|
-
codecs.add(page.codec);
|
|
213
|
-
|
|
214
|
-
pages++;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
const codec = Array.from(codecs).map(vgeo_codec_name).join(' + ');
|
|
218
|
-
|
|
219
|
-
return {
|
|
220
|
-
storage: pages === 0
|
|
221
|
-
? '—'
|
|
222
|
-
: `${human_bytes(stored)} stored · ${human_bytes(decoded)} decoded`
|
|
223
|
-
+ ` · ${codec} ${(decoded / Math.max(stored, 1)).toFixed(2)}x`,
|
|
224
|
-
normals: `${thousands(per_vertex)} / ${thousands(clusters)} clusters`,
|
|
225
|
-
};
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
/**
|
|
229
|
-
* The viewer draws its clusters into the scene, at the phase a full-screen effect that should be
|
|
230
|
-
* tonemapped with the rest of the frame belongs at.
|
|
231
|
-
*/
|
|
232
|
-
class VGEOViewerExtension extends RenderExtension {
|
|
233
|
-
name = "vgeo viewer";
|
|
234
|
-
|
|
235
|
-
phase = FramePhase.AfterTransparency;
|
|
236
|
-
|
|
237
|
-
/**
|
|
238
|
-
* @type {VGeoViewer}
|
|
239
|
-
*/
|
|
240
|
-
#viewer;
|
|
241
|
-
|
|
242
|
-
/**
|
|
243
|
-
* @param {VGeoViewer} viewer
|
|
244
|
-
*/
|
|
245
|
-
constructor(viewer) {
|
|
246
|
-
super();
|
|
247
|
-
|
|
248
|
-
this.#viewer = viewer;
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
record(frame) {
|
|
252
|
-
this.#viewer.record(frame);
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
/**
|
|
257
|
-
* Clear the frame's depth, so that the cut is the only thing in it.
|
|
258
|
-
*
|
|
259
|
-
* **The renderer does not do this when there is nothing to rasterize.** Depth is cleared as a side
|
|
260
|
-
* effect of drawing the first opaque bucket, and this viewer's `Scene` holds a light and no meshes,
|
|
261
|
-
* so that draw never happens. The renderer also ping-pongs two depth textures frame to frame — so
|
|
262
|
-
* left alone, the cut is depth-tested against *its own depth from two frames ago*, and dissolves
|
|
263
|
-
* into holes wherever the camera has moved since. It reads as missing geometry and it is not.
|
|
264
|
-
*
|
|
265
|
-
* A real scene needs none of this and must not have it: the cut has to be occluded by whatever the
|
|
266
|
-
* scene put in the depth buffer, which is why the draw itself loads rather than clears.
|
|
267
|
-
*
|
|
268
|
-
* It runs immediately before the draw rather than at the top of the frame, so everything earlier
|
|
269
|
-
* that reads depth still sees the previous frame's. With nothing in the scene there is nothing for
|
|
270
|
-
* those passes to occlude, and the alternative is a second registration on another phase to save a
|
|
271
|
-
* result that is empty either way.
|
|
272
|
-
*
|
|
273
|
-
* @param {FrameGraph} graph
|
|
274
|
-
* @param {number} depth
|
|
275
|
-
* @returns {number} the depth handle to draw against
|
|
276
|
-
*/
|
|
277
|
-
function vgeo_graph_clear_depth(graph, depth) {
|
|
278
|
-
const data = {};
|
|
279
|
-
|
|
280
|
-
const pass = graph.add('vgeo/clear depth', data, (data, resources, context) => {
|
|
281
|
-
const texture = resources.get(data.depth);
|
|
282
|
-
|
|
283
|
-
// no draws — the clear is the load op, and ending the pass immediately is what performs it
|
|
284
|
-
context.encoder.beginRenderPass({
|
|
285
|
-
label: "vgeo/clear depth",
|
|
286
|
-
colorAttachments: [],
|
|
287
|
-
depthStencilAttachment: {
|
|
288
|
-
view: texture.obtainView(DEFAULT_DEPTH_TEXTURE_VIEW),
|
|
289
|
-
depthLoadOp: "clear",
|
|
290
|
-
depthStoreOp: "store",
|
|
291
|
-
depthClearValue: DEPTH_BACKGROUND_VALUE,
|
|
292
|
-
},
|
|
293
|
-
}).end();
|
|
294
|
-
});
|
|
295
|
-
|
|
296
|
-
data.depth = pass.write(depth);
|
|
297
|
-
|
|
298
|
-
return data.depth;
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
/**
|
|
302
|
-
* The viewer. One container at a time, one cut at a time, and the cut only changes when asked.
|
|
303
|
-
*/
|
|
304
|
-
class VGeoViewer {
|
|
305
|
-
/**
|
|
306
|
-
* @type {VGeoContainerReader|undefined}
|
|
307
|
-
*/
|
|
308
|
-
#reader = undefined;
|
|
309
|
-
|
|
310
|
-
/**
|
|
311
|
-
* The source the current container was opened over, kept so that changing a read option can
|
|
312
|
-
* re-open it rather than ask for the file again.
|
|
313
|
-
* @type {VGeoByteSource|undefined}
|
|
314
|
-
*/
|
|
315
|
-
#source = undefined;
|
|
316
|
-
|
|
317
|
-
/**
|
|
318
|
-
* @type {VGeoCutGeometry|undefined}
|
|
319
|
-
*/
|
|
320
|
-
#cut_geometry = undefined;
|
|
321
|
-
|
|
322
|
-
/**
|
|
323
|
-
* @type {VGeoCut}
|
|
324
|
-
*/
|
|
325
|
-
#cut = new VGeoCut();
|
|
326
|
-
|
|
327
|
-
/**
|
|
328
|
-
* @type {VGeoCutView}
|
|
329
|
-
*/
|
|
330
|
-
#view = new VGeoCutView();
|
|
331
|
-
|
|
332
|
-
/**
|
|
333
|
-
* @type {VGeoReadOptions}
|
|
334
|
-
*/
|
|
335
|
-
#read_options = new VGeoReadOptions();
|
|
336
|
-
|
|
337
|
-
/**
|
|
338
|
-
* Whether `?head=` named a prefix, in which case nothing here overrides it.
|
|
339
|
-
* @type {boolean}
|
|
340
|
-
*/
|
|
341
|
-
#head_pinned = false;
|
|
342
|
-
|
|
343
|
-
/**
|
|
344
|
-
* @type {DynamicMeshBatch}
|
|
345
|
-
*/
|
|
346
|
-
#batch = new DynamicMeshBatch();
|
|
347
|
-
|
|
348
|
-
/**
|
|
349
|
-
* @type {DynamicMesh|undefined}
|
|
350
|
-
*/
|
|
351
|
-
#mesh = undefined;
|
|
352
|
-
|
|
353
|
-
/**
|
|
354
|
-
* A cut is a solid surface, so it is drawn as one: opaque, writing depth, and ordered by the
|
|
355
|
-
* depth buffer rather than by anything this file does.
|
|
356
|
-
*
|
|
357
|
-
* @type {GPUDynamicMeshRenderer}
|
|
358
|
-
*/
|
|
359
|
-
#dynamic = new GPUDynamicMeshRenderer();
|
|
360
|
-
|
|
361
|
-
/**
|
|
362
|
-
* Where the camera stood when the current cut was taken, so the panel can say how far you have
|
|
363
|
-
* wandered from the decision you are looking at.
|
|
364
|
-
* @type {Float32Array}
|
|
365
|
-
*/
|
|
366
|
-
#cut_eye = new Float32Array(3);
|
|
367
|
-
|
|
368
|
-
/**
|
|
369
|
-
* @type {Float32Array}
|
|
370
|
-
*/
|
|
371
|
-
#eye = new Float32Array(3);
|
|
372
|
-
|
|
373
|
-
/**
|
|
374
|
-
* @type {boolean}
|
|
375
|
-
*/
|
|
376
|
-
#auto_adapt = false;
|
|
377
|
-
|
|
378
|
-
/**
|
|
379
|
-
* Whether a clamped cut chases its own wants until there are none left.
|
|
380
|
-
* @type {boolean}
|
|
381
|
-
*/
|
|
382
|
-
#auto_stream = true;
|
|
383
|
-
|
|
384
|
-
/**
|
|
385
|
-
* One streaming loop at a time — the loop retakes the cut between rounds, and two of them would
|
|
386
|
-
* be fetching against each other's want lists.
|
|
387
|
-
* @type {boolean}
|
|
388
|
-
*/
|
|
389
|
-
#streaming = false;
|
|
390
|
-
|
|
391
|
-
/**
|
|
392
|
-
* @type {VGEO_COLOR_MODE}
|
|
393
|
-
*/
|
|
394
|
-
#color_mode = VGEO_COLOR_MODE.Cluster;
|
|
395
|
-
|
|
396
|
-
/**
|
|
397
|
-
* @type {string}
|
|
398
|
-
*/
|
|
399
|
-
#status = 'drop a .vgeo file anywhere on the page';
|
|
400
|
-
|
|
401
|
-
/**
|
|
402
|
-
* @type {string}
|
|
403
|
-
*/
|
|
404
|
-
#file_label = '';
|
|
405
|
-
|
|
406
|
-
/**
|
|
407
|
-
* @type {number}
|
|
408
|
-
*/
|
|
409
|
-
#open_ms = 0;
|
|
410
|
-
|
|
411
|
-
/**
|
|
412
|
-
* Bytes the source had served by the time the container was open — the cold start, measured.
|
|
413
|
-
* @type {number}
|
|
414
|
-
*/
|
|
415
|
-
#open_bytes = 0;
|
|
416
|
-
|
|
417
|
-
/**
|
|
418
|
-
* @type {number}
|
|
419
|
-
*/
|
|
420
|
-
#build_ms = 0;
|
|
421
|
-
|
|
422
|
-
/**
|
|
423
|
-
* What the last {@link VGeoContainerReader.validate} came to, or `undefined` while it has never
|
|
424
|
-
* been asked for — which is the default and the ordinary state.
|
|
425
|
-
* @type {VGeoDefect[]|undefined}
|
|
426
|
-
*/
|
|
427
|
-
#defects = undefined;
|
|
428
|
-
|
|
429
|
-
/**
|
|
430
|
-
* @type {string}
|
|
431
|
-
*/
|
|
432
|
-
#validation_status = 'not run';
|
|
433
|
-
|
|
434
|
-
constructor() {
|
|
435
|
-
this.canvas = document.createElement('canvas');
|
|
436
|
-
this.renderer = new Renderer();
|
|
437
|
-
this.camera = new PerspectiveCamera();
|
|
438
|
-
this.scene = new Scene();
|
|
439
|
-
|
|
440
|
-
// the sun stands at an entity, so the scene names a dataset for it
|
|
441
|
-
this.scene.dataset = new EntityComponentDataset();
|
|
442
|
-
|
|
443
|
-
this.#dynamic.opaque = true;
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
/**
|
|
447
|
-
* @returns {VGeoContainerReader|undefined}
|
|
448
|
-
*/
|
|
449
|
-
get reader() {
|
|
450
|
-
return this.#reader;
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
/**
|
|
454
|
-
* @returns {VGeoCut}
|
|
455
|
-
*/
|
|
456
|
-
get cut() {
|
|
457
|
-
return this.#cut;
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
/**
|
|
461
|
-
* Whether a clamped cut chases its own wants until there are none left.
|
|
462
|
-
*
|
|
463
|
-
* Setting it does not start a run — a caller that wants one calls {@link stream} and holds the
|
|
464
|
-
* promise, which is what a scripted session needs in order to wait for the end of it.
|
|
465
|
-
*
|
|
466
|
-
* @param {boolean} value
|
|
467
|
-
*/
|
|
468
|
-
set_auto_stream(value) {
|
|
469
|
-
this.#auto_stream = value;
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
/**
|
|
473
|
-
* @returns {Promise<void>}
|
|
474
|
-
*/
|
|
475
|
-
async start() {
|
|
476
|
-
document.body.style.margin = '0';
|
|
477
|
-
document.body.style.overflow = 'hidden';
|
|
478
|
-
document.body.style.background = '#070b10';
|
|
479
|
-
|
|
480
|
-
this.canvas.style.position = 'absolute';
|
|
481
|
-
this.canvas.style.inset = '0';
|
|
482
|
-
this.canvas.style.outline = 'none';
|
|
483
|
-
|
|
484
|
-
// the orbital controller attaches a KeyboardDevice, and a canvas cannot receive key events
|
|
485
|
-
// without being focusable
|
|
486
|
-
this.canvas.tabIndex = 0;
|
|
487
|
-
|
|
488
|
-
document.body.appendChild(this.canvas);
|
|
489
|
-
|
|
490
|
-
this.canvas.focus();
|
|
491
|
-
|
|
492
|
-
await this.renderer.initialize({ context: this.canvas.getContext('webgpu') });
|
|
493
|
-
|
|
494
|
-
/*
|
|
495
|
-
Everything that would move the image for reasons unrelated to the cut is off. Automatic
|
|
496
|
-
exposure in particular: it adapts to whatever is on screen, so the same cluster would read a
|
|
497
|
-
different colour depending on how much of the model is in frame, which is exactly the signal
|
|
498
|
-
this tool is trying to show.
|
|
499
|
-
*/
|
|
500
|
-
this.renderer.feature_automatic_exposure_enabled = false;
|
|
501
|
-
this.renderer.feature_taa_enabled = true;
|
|
502
|
-
this.renderer.feature_bloom_enabled = false;
|
|
503
|
-
this.renderer.feature_ssr_enabled = false;
|
|
504
|
-
this.renderer.feature_ssao_enabled = true;
|
|
505
|
-
this.renderer.feature_shadows_enabled = false;
|
|
506
|
-
this.renderer.feature_lpv_enabled = false;
|
|
507
|
-
this.renderer.pixel_ratio = 1;
|
|
508
|
-
|
|
509
|
-
place_light(this.scene, make_sunlight({ intensity: 3 }));
|
|
510
|
-
|
|
511
|
-
this.camera.near = 0.05;
|
|
512
|
-
this.camera.far = 500;
|
|
513
|
-
this.camera.fov = 55 * DEG_TO_RAD;
|
|
514
|
-
this.camera.transform.setTranslation(0, 0.5, 2);
|
|
515
|
-
this.camera.transform.lookAt(Vector3.zero);
|
|
516
|
-
|
|
517
|
-
this.controller = new OrbitalCameraController(this.camera, this.canvas);
|
|
518
|
-
this.controller.distanceLimits.min = 1e-4;
|
|
519
|
-
this.controller.distanceLimits.max = 1e5;
|
|
520
|
-
|
|
521
|
-
this.#build_interface();
|
|
522
|
-
this.#fit_to_window();
|
|
523
|
-
|
|
524
|
-
window.addEventListener('resize', () => this.#fit_to_window());
|
|
525
|
-
|
|
526
|
-
this.renderer.add_extension(new VGEOViewerExtension(this));
|
|
527
|
-
|
|
528
|
-
create_frame_loop((dt) => this.#frame(dt));
|
|
529
|
-
|
|
530
|
-
const query = new URL(window.location.href).searchParams;
|
|
531
|
-
|
|
532
|
-
const head = query.get('head');
|
|
533
|
-
|
|
534
|
-
if (head !== null) {
|
|
535
|
-
this.#read_options.head_prefix_bytes = Number(head);
|
|
536
|
-
this.#head_pinned = true;
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
this.#read_options.validate = query.get('validate') !== null;
|
|
540
|
-
this.#read_options.verify_frames = query.get('verify') !== null;
|
|
541
|
-
|
|
542
|
-
const requested = query.get('file');
|
|
543
|
-
|
|
544
|
-
if (requested !== null) {
|
|
545
|
-
await this.load_url(requested);
|
|
546
|
-
} else if (query.get('sample') !== null) {
|
|
547
|
-
await this.open_sample();
|
|
548
|
-
}
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
/**
|
|
552
|
-
* @param {FrameContext} frame
|
|
553
|
-
*/
|
|
554
|
-
record(frame) {
|
|
555
|
-
const graph = frame.graph;
|
|
556
|
-
const scene = frame.get(SceneColor);
|
|
557
|
-
|
|
558
|
-
scene.color = this.#dynamic.graph_draw({
|
|
559
|
-
graph,
|
|
560
|
-
batch: this.#batch,
|
|
561
|
-
color: scene.color,
|
|
562
|
-
depth: vgeo_graph_clear_depth(graph, frame.get(ViewTextures).depth),
|
|
563
|
-
camera: frame.view.camera.buffer,
|
|
564
|
-
});
|
|
565
|
-
}
|
|
566
|
-
|
|
567
|
-
#fit_to_window() {
|
|
568
|
-
const width = window.innerWidth;
|
|
569
|
-
const height = window.innerHeight;
|
|
570
|
-
|
|
571
|
-
this.canvas.width = width;
|
|
572
|
-
this.canvas.height = height;
|
|
573
|
-
|
|
574
|
-
this.renderer.resize(width, height);
|
|
575
|
-
|
|
576
|
-
this.camera.aspect = this.renderer.aspect_ratio;
|
|
577
|
-
|
|
578
|
-
this.#view.screen_height = height;
|
|
579
|
-
this.#view.fov_y = this.camera.fov;
|
|
580
|
-
}
|
|
581
|
-
|
|
582
|
-
/**
|
|
583
|
-
* @param {number} dt seconds
|
|
584
|
-
*/
|
|
585
|
-
#frame(dt) {
|
|
586
|
-
this.controller.update(dt);
|
|
587
|
-
|
|
588
|
-
const position = this.camera.transform;
|
|
589
|
-
|
|
590
|
-
const moved = position.translation_x !== this.#eye[0]
|
|
591
|
-
|| position.translation_y !== this.#eye[1]
|
|
592
|
-
|| position.translation_z !== this.#eye[2];
|
|
593
|
-
|
|
594
|
-
this.#eye[0] = position.translation_x;
|
|
595
|
-
this.#eye[1] = position.translation_y;
|
|
596
|
-
this.#eye[2] = position.translation_z;
|
|
597
|
-
|
|
598
|
-
if (moved && this.#auto_adapt) {
|
|
599
|
-
this.adapt();
|
|
600
|
-
}
|
|
601
|
-
|
|
602
|
-
this.renderer.render(this.camera, this.scene, dt);
|
|
603
|
-
|
|
604
|
-
if (moved) {
|
|
605
|
-
this.#refresh_cut();
|
|
606
|
-
}
|
|
607
|
-
}
|
|
608
|
-
|
|
609
|
-
/**
|
|
610
|
-
* Re-render the cut readout alone.
|
|
611
|
-
*
|
|
612
|
-
* Every number under it moves as the camera does — the drift on every frame, and the whole lot on
|
|
613
|
-
* every adapt when `follow the camera` is on. Rebuilding the *whole* panel at that rate would
|
|
614
|
-
* also rebuild the controls, which is how a slider stops being draggable mid-drag; this section
|
|
615
|
-
* holds no controls, so replacing it is free of that.
|
|
616
|
-
*/
|
|
617
|
-
#refresh_cut() {
|
|
618
|
-
const host = this.panel.querySelector('#vgeo-cut');
|
|
619
|
-
|
|
620
|
-
if (host === null) {
|
|
621
|
-
return;
|
|
622
|
-
}
|
|
623
|
-
|
|
624
|
-
host.innerHTML = this.#cut_html();
|
|
625
|
-
}
|
|
626
|
-
|
|
627
|
-
/**
|
|
628
|
-
* The cut readout — the cluster count first, because it is the number the tool exists to show,
|
|
629
|
-
* and the clamp count second, because it is the one that says the picture is still arriving.
|
|
630
|
-
*
|
|
631
|
-
* @returns {string}
|
|
632
|
-
*/
|
|
633
|
-
#cut_html() {
|
|
634
|
-
const cut = this.#cut;
|
|
635
|
-
|
|
636
|
-
const clamped = cut.clamped === 0
|
|
637
|
-
? `<span class="good">0 — this is the cut the file selects</span>`
|
|
638
|
-
: `<span class="warn">${thousands(cut.clamped)} waiting on ${cut.wants.length} page(s)</span>`;
|
|
639
|
-
|
|
640
|
-
const rows = [
|
|
641
|
-
['triangles', thousands(cut.triangle_count)],
|
|
642
|
-
['clamped', clamped],
|
|
643
|
-
['groups', thousands(cut.groups_visited)],
|
|
644
|
-
['pages read', thousands(cut.pages_touched)],
|
|
645
|
-
['clusters tested', thousands(cut.clusters_tested)],
|
|
646
|
-
['culled subtrees', thousands(cut.culled)],
|
|
647
|
-
['select / build', `${cut.duration_ms.toFixed(1)} / ${this.#build_ms.toFixed(1)} ms`],
|
|
648
|
-
['camera drift', `${this.#drift().toExponential(2)} u`],
|
|
649
|
-
];
|
|
650
|
-
|
|
651
|
-
/*
|
|
652
|
-
A clamp whose child page is already resident is waiting on a page holding one of that
|
|
653
|
-
group's *other* parents (§10, activation) — so it is not counted by any want, and without
|
|
654
|
-
this row the panel reads as `20 waiting on 5 page(s)` while five pages would not have
|
|
655
|
-
settled all twenty. Only shown when it happens, which on a small asset is never.
|
|
656
|
-
*/
|
|
657
|
-
if (cut.clamped_on_activation > 0) {
|
|
658
|
-
rows.push([
|
|
659
|
-
'of those, on a parent',
|
|
660
|
-
`<span class="warn">${thousands(cut.clamped_on_activation)}</span>`
|
|
661
|
-
]);
|
|
662
|
-
}
|
|
663
|
-
|
|
664
|
-
if (cut.broken_child_refs > 0) {
|
|
665
|
-
rows.push(['broken child refs', `<span class="bad">${thousands(cut.broken_child_refs)}</span>`]);
|
|
666
|
-
}
|
|
667
|
-
|
|
668
|
-
return `<div class="count">${thousands(cut.clusters.length)}</div>`
|
|
669
|
-
+ `<div class="muted" style="margin-bottom:6px">clusters in the current cut</div>`
|
|
670
|
-
+ panel_table(rows);
|
|
671
|
-
}
|
|
672
|
-
|
|
673
|
-
/**
|
|
674
|
-
* How far the camera has moved since the cut on screen was selected.
|
|
675
|
-
* @returns {number}
|
|
676
|
-
*/
|
|
677
|
-
#drift() {
|
|
678
|
-
return Math.hypot(
|
|
679
|
-
this.#eye[0] - this.#cut_eye[0],
|
|
680
|
-
this.#eye[1] - this.#cut_eye[1],
|
|
681
|
-
this.#eye[2] - this.#cut_eye[2]
|
|
682
|
-
);
|
|
683
|
-
}
|
|
684
|
-
|
|
685
|
-
/**
|
|
686
|
-
* Take a fresh cut at wherever the camera is standing, from whatever the reader currently holds,
|
|
687
|
-
* and build the geometry for it.
|
|
688
|
-
*/
|
|
689
|
-
adapt() {
|
|
690
|
-
if (this.#reader === undefined) {
|
|
691
|
-
return;
|
|
692
|
-
}
|
|
693
|
-
|
|
694
|
-
// the frustum is derived from matrices the renderer refreshes during its own frame, and an
|
|
695
|
-
// adapt can land before one has run — a cut culled against last frame's planes would be a
|
|
696
|
-
// silent, hard-to-see wrong answer
|
|
697
|
-
this.camera.update();
|
|
698
|
-
|
|
699
|
-
this.#view.eye.set(this.#eye);
|
|
700
|
-
this.#view.screen_height = this.canvas.height;
|
|
701
|
-
this.#view.fov_y = this.camera.fov;
|
|
702
|
-
this.#view.frustum.set(this.camera.frustum);
|
|
703
|
-
|
|
704
|
-
this.#cut = vgeo_select_cut(this.#reader, this.#view);
|
|
705
|
-
|
|
706
|
-
const started = performance.now();
|
|
707
|
-
|
|
708
|
-
const previous = this.#cut_geometry;
|
|
709
|
-
|
|
710
|
-
this.#cut_geometry = vgeo_build_cut_geometry(this.#cut, this.#color_mode);
|
|
711
|
-
|
|
712
|
-
this.#build_ms = performance.now() - started;
|
|
713
|
-
|
|
714
|
-
this.#batch.clear();
|
|
715
|
-
|
|
716
|
-
// Residency is keyed on the geometry and deliberately survives leaving the batch, so the cut
|
|
717
|
-
// that was just replaced would hold its buffers forever otherwise — and this tool replaces
|
|
718
|
-
// one on every drag of the tau slider.
|
|
719
|
-
if (previous !== undefined) {
|
|
720
|
-
this.#dynamic.release(previous.geometry);
|
|
721
|
-
}
|
|
722
|
-
|
|
723
|
-
this.#mesh = DynamicMesh.from(this.#cut_geometry.geometry);
|
|
724
|
-
|
|
725
|
-
this.#batch.add(this.#mesh);
|
|
726
|
-
|
|
727
|
-
this.#cut_eye.set(this.#eye);
|
|
728
|
-
}
|
|
729
|
-
|
|
730
|
-
/**
|
|
731
|
-
* Fetch the pages the current cut is clamped against, a batch at a time, retaking the cut after
|
|
732
|
-
* each — until nothing is clamped, or the loop is turned off, or a round installs nothing.
|
|
733
|
-
*
|
|
734
|
-
* Note what is *not* here: any handling for the cut being wrong in the meantime. It is not. Every
|
|
735
|
-
* intermediate cut this loop draws is a complete watertight surface at some level of detail, so
|
|
736
|
-
* there is nothing to hide while it converges and nothing to reconcile when it does.
|
|
737
|
-
*
|
|
738
|
-
* @returns {Promise<void>}
|
|
739
|
-
*/
|
|
740
|
-
async stream() {
|
|
741
|
-
if (this.#streaming || this.#reader === undefined) {
|
|
742
|
-
return;
|
|
743
|
-
}
|
|
744
|
-
|
|
745
|
-
this.#streaming = true;
|
|
746
|
-
|
|
747
|
-
try {
|
|
748
|
-
while (this.#cut.wants.length > 0) {
|
|
749
|
-
const before = this.#reader.resident_page_count;
|
|
750
|
-
|
|
751
|
-
this.#status = `fetching ${Math.min(this.#cut.wants.length, STREAM_BATCH)}`
|
|
752
|
-
+ ` of ${this.#cut.wants.length} wanted page(s)`;
|
|
753
|
-
|
|
754
|
-
this.#refresh_panel();
|
|
755
|
-
|
|
756
|
-
await this.#reader.install_wants(this.#cut.wants, STREAM_BATCH);
|
|
757
|
-
|
|
758
|
-
// A round that installed nothing means every want is unfetchable, and continuing
|
|
759
|
-
// would be a spin against the same list forever.
|
|
760
|
-
if (this.#reader.resident_page_count === before) {
|
|
761
|
-
this.#status = 'the wanted pages could not be installed';
|
|
762
|
-
break;
|
|
763
|
-
}
|
|
764
|
-
|
|
765
|
-
this.#status = '';
|
|
766
|
-
|
|
767
|
-
this.adapt();
|
|
768
|
-
this.#refresh_panel();
|
|
769
|
-
|
|
770
|
-
if (!this.#auto_stream) {
|
|
771
|
-
break;
|
|
772
|
-
}
|
|
773
|
-
}
|
|
774
|
-
} catch (e) {
|
|
775
|
-
this.#status = String(e.message ?? e);
|
|
776
|
-
|
|
777
|
-
throw e;
|
|
778
|
-
} finally {
|
|
779
|
-
this.#streaming = false;
|
|
780
|
-
this.#refresh_panel();
|
|
781
|
-
}
|
|
782
|
-
}
|
|
783
|
-
|
|
784
|
-
/**
|
|
785
|
-
* Open a container over a byte source, and take the first cut.
|
|
786
|
-
*
|
|
787
|
-
* @param {VGeoByteSource} source
|
|
788
|
-
* @returns {Promise<void>}
|
|
789
|
-
*/
|
|
790
|
-
async open(source) {
|
|
791
|
-
this.#source = source;
|
|
792
|
-
this.#file_label = source.name;
|
|
793
|
-
this.#defects = undefined;
|
|
794
|
-
this.#validation_status = 'not run';
|
|
795
|
-
this.#status = `opening ${source.name}`;
|
|
796
|
-
|
|
797
|
-
source.reset_counters();
|
|
798
|
-
|
|
799
|
-
this.#refresh_panel();
|
|
800
|
-
|
|
801
|
-
const started = performance.now();
|
|
802
|
-
|
|
803
|
-
try {
|
|
804
|
-
this.#reader = await VGeoContainerReader.open(source, this.#read_options);
|
|
805
|
-
} catch (e) {
|
|
806
|
-
this.#reader = undefined;
|
|
807
|
-
this.#cut = new VGeoCut();
|
|
808
|
-
this.#cut_geometry = undefined;
|
|
809
|
-
this.#batch.clear();
|
|
810
|
-
this.#status = String(e.message ?? e);
|
|
811
|
-
this.#refresh_panel();
|
|
812
|
-
|
|
813
|
-
throw e;
|
|
814
|
-
}
|
|
815
|
-
|
|
816
|
-
this.#open_ms = performance.now() - started;
|
|
817
|
-
this.#open_bytes = source.bytes_read;
|
|
818
|
-
this.#status = '';
|
|
819
|
-
|
|
820
|
-
this.#frame_camera();
|
|
821
|
-
this.adapt();
|
|
822
|
-
this.#refresh_panel();
|
|
823
|
-
|
|
824
|
-
if (this.#auto_stream) {
|
|
825
|
-
await this.stream();
|
|
826
|
-
}
|
|
827
|
-
}
|
|
828
|
-
|
|
829
|
-
/**
|
|
830
|
-
* @param {Blob} blob a dropped `File`
|
|
831
|
-
* @returns {Promise<void>}
|
|
832
|
-
*/
|
|
833
|
-
open_blob(blob) {
|
|
834
|
-
return this.open(VGeoByteSource.from_blob(blob));
|
|
835
|
-
}
|
|
836
|
-
|
|
837
|
-
/**
|
|
838
|
-
* Build a container in the page and open it, so there is something to look at without a file.
|
|
839
|
-
*
|
|
840
|
-
* The source is over bytes already in memory, which changes nothing above it: the reader still
|
|
841
|
-
* opens on the head and installs a page at a time, and the panel's `read` still says what
|
|
842
|
-
* fraction of the container this view actually cost.
|
|
843
|
-
*
|
|
844
|
-
* @returns {Promise<void>}
|
|
845
|
-
*/
|
|
846
|
-
async open_sample() {
|
|
847
|
-
this.#status = 'building a sample asset';
|
|
848
|
-
this.#refresh_panel();
|
|
849
|
-
|
|
850
|
-
// yielded so the status paints before the builder takes the thread for a second
|
|
851
|
-
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
852
|
-
|
|
853
|
-
const sample = vgeo_sample_source();
|
|
854
|
-
|
|
855
|
-
/*
|
|
856
|
-
The default prefix is 64 KB, sized against a *default* build's pages of hundreds of KB. The
|
|
857
|
-
sample's are 8 KB, so even that prefix would swallow much of it and there would be little
|
|
858
|
-
left to stream — a demonstration of the head prefix rather than of the reader. Sized to the
|
|
859
|
-
sample's own pages instead, unless the address bar has already made the choice.
|
|
860
|
-
*/
|
|
861
|
-
if (!this.#head_pinned) {
|
|
862
|
-
this.#read_options.head_prefix_bytes = 16 * 1024;
|
|
863
|
-
}
|
|
864
|
-
|
|
865
|
-
await this.open(sample.source);
|
|
866
|
-
|
|
867
|
-
this.#status = `built in ${sample.build_ms.toFixed(0)} ms`;
|
|
868
|
-
this.#refresh_panel();
|
|
869
|
-
}
|
|
870
|
-
|
|
871
|
-
/**
|
|
872
|
-
* Open the current source again, from cold, under whatever the read options now say.
|
|
873
|
-
*
|
|
874
|
-
* Which is the point of it: the head prefix and frame verification are decisions taken at open
|
|
875
|
-
* time, and comparing two of them means comparing two opens. Nothing is re-fetched that the
|
|
876
|
-
* source would not have re-fetched anyway — a URL source really does go back to the network,
|
|
877
|
-
* which is what makes the cold-start number honest.
|
|
878
|
-
*
|
|
879
|
-
* @returns {Promise<void>}
|
|
880
|
-
*/
|
|
881
|
-
async reopen() {
|
|
882
|
-
if (this.#source === undefined) {
|
|
883
|
-
return;
|
|
884
|
-
}
|
|
885
|
-
|
|
886
|
-
await this.open(this.#source);
|
|
887
|
-
}
|
|
888
|
-
|
|
889
|
-
/**
|
|
890
|
-
* @param {string} url
|
|
891
|
-
* @returns {Promise<void>}
|
|
892
|
-
*/
|
|
893
|
-
async load_url(url) {
|
|
894
|
-
this.#status = `probing ${url}`;
|
|
895
|
-
this.#refresh_panel();
|
|
896
|
-
|
|
897
|
-
try {
|
|
898
|
-
await this.open(await vgeo_fetch_byte_source(url));
|
|
899
|
-
} catch (e) {
|
|
900
|
-
this.#status = `${url} — ${e.message ?? e}`;
|
|
901
|
-
this.#refresh_panel();
|
|
902
|
-
}
|
|
903
|
-
}
|
|
904
|
-
|
|
905
|
-
/**
|
|
906
|
-
* Run the whole-file audit, which is the one thing here that reads every byte.
|
|
907
|
-
*
|
|
908
|
-
* @returns {Promise<VGeoDefect[]>}
|
|
909
|
-
*/
|
|
910
|
-
async validate() {
|
|
911
|
-
if (this.#reader === undefined) {
|
|
912
|
-
return [];
|
|
913
|
-
}
|
|
914
|
-
|
|
915
|
-
this.#validation_status = 'reading the whole file…';
|
|
916
|
-
this.#refresh_panel();
|
|
917
|
-
|
|
918
|
-
const started = performance.now();
|
|
919
|
-
|
|
920
|
-
try {
|
|
921
|
-
this.#defects = await vgeo_audit_container(this.#reader);
|
|
922
|
-
} catch (e) {
|
|
923
|
-
this.#defects = undefined;
|
|
924
|
-
this.#validation_status = String(e.message ?? e);
|
|
925
|
-
this.#refresh_panel();
|
|
926
|
-
|
|
927
|
-
throw e;
|
|
928
|
-
}
|
|
929
|
-
|
|
930
|
-
this.#validation_status = `${(performance.now() - started).toFixed(0)} ms`;
|
|
931
|
-
this.#refresh_panel();
|
|
932
|
-
|
|
933
|
-
return this.#defects;
|
|
934
|
-
}
|
|
935
|
-
|
|
936
|
-
/**
|
|
937
|
-
* Install every page the directory lists — the opposite of what this reader is for, and what a
|
|
938
|
-
* question about the file rather than about a view of it wants.
|
|
939
|
-
*
|
|
940
|
-
* @returns {Promise<void>}
|
|
941
|
-
*/
|
|
942
|
-
async load_all_pages() {
|
|
943
|
-
if (this.#reader === undefined) {
|
|
944
|
-
return;
|
|
945
|
-
}
|
|
946
|
-
|
|
947
|
-
this.#status = 'installing every page';
|
|
948
|
-
this.#refresh_panel();
|
|
949
|
-
|
|
950
|
-
try {
|
|
951
|
-
await vgeo_install_all_pages(this.#reader, (done, total) => {
|
|
952
|
-
this.#status = `installing every page — ${done} / ${total}`;
|
|
953
|
-
});
|
|
954
|
-
|
|
955
|
-
this.#status = '';
|
|
956
|
-
} catch (e) {
|
|
957
|
-
// A page that could not be read is quarantined rather than fatal now, and the tool
|
|
958
|
-
// reports at the end rather than stopping at the first one — so every page that *could*
|
|
959
|
-
// be installed is here, and the message says how many could not.
|
|
960
|
-
this.#status = String(e.message ?? e);
|
|
961
|
-
}
|
|
962
|
-
|
|
963
|
-
this.adapt();
|
|
964
|
-
this.#refresh_panel();
|
|
965
|
-
}
|
|
966
|
-
|
|
967
|
-
/**
|
|
968
|
-
* Drop everything but the root page, so the file arrives again from cold.
|
|
969
|
-
*/
|
|
970
|
-
reset_residency() {
|
|
971
|
-
if (this.#reader === undefined) {
|
|
972
|
-
return;
|
|
973
|
-
}
|
|
974
|
-
|
|
975
|
-
this.#reader.evict_all();
|
|
976
|
-
|
|
977
|
-
this.adapt();
|
|
978
|
-
this.#refresh_panel();
|
|
979
|
-
|
|
980
|
-
if (this.#auto_stream) {
|
|
981
|
-
this.stream();
|
|
982
|
-
}
|
|
983
|
-
}
|
|
984
|
-
|
|
985
|
-
/**
|
|
986
|
-
* Put the camera where the whole asset is on screen, and scale the clip range to it — vgeo
|
|
987
|
-
* assets are authored at whatever scale the source used, and the Stanford models are a tenth of a
|
|
988
|
-
* unit across.
|
|
989
|
-
*/
|
|
990
|
-
#frame_camera() {
|
|
991
|
-
const sphere = this.#reader.header.object_sphere;
|
|
992
|
-
|
|
993
|
-
const radius = Math.max(sphere[3], 1e-6);
|
|
994
|
-
const center = new Vector3(sphere[0], sphere[1], sphere[2]);
|
|
995
|
-
|
|
996
|
-
const distance = radius / Math.tan(this.camera.fov * 0.5) * 1.15;
|
|
997
|
-
|
|
998
|
-
const eye = new Vector3(0.55, 0.42, 0.72);
|
|
999
|
-
|
|
1000
|
-
eye.normalize();
|
|
1001
|
-
eye.multiplyScalar(distance);
|
|
1002
|
-
eye.add(center);
|
|
1003
|
-
|
|
1004
|
-
this.camera.near = radius * 0.005;
|
|
1005
|
-
this.camera.far = radius * 200;
|
|
1006
|
-
|
|
1007
|
-
this.controller.distanceLimits.min = radius * 0.02;
|
|
1008
|
-
this.controller.distanceLimits.max = radius * 200;
|
|
1009
|
-
this.controller.movement_speed_scale = radius;
|
|
1010
|
-
|
|
1011
|
-
this.controller.look(eye, center);
|
|
1012
|
-
|
|
1013
|
-
this.#eye[0] = eye.x;
|
|
1014
|
-
this.#eye[1] = eye.y;
|
|
1015
|
-
this.#eye[2] = eye.z;
|
|
1016
|
-
}
|
|
1017
|
-
|
|
1018
|
-
#build_interface() {
|
|
1019
|
-
const drop = document.getElementById('vgeo-drop');
|
|
1020
|
-
|
|
1021
|
-
let drag_depth = 0;
|
|
1022
|
-
|
|
1023
|
-
window.addEventListener('dragenter', (event) => {
|
|
1024
|
-
event.preventDefault();
|
|
1025
|
-
drag_depth++;
|
|
1026
|
-
drop.classList.add('active');
|
|
1027
|
-
});
|
|
1028
|
-
|
|
1029
|
-
window.addEventListener('dragover', (event) => event.preventDefault());
|
|
1030
|
-
|
|
1031
|
-
window.addEventListener('dragleave', () => {
|
|
1032
|
-
drag_depth = Math.max(0, drag_depth - 1);
|
|
1033
|
-
|
|
1034
|
-
if (drag_depth === 0) {
|
|
1035
|
-
drop.classList.remove('active');
|
|
1036
|
-
}
|
|
1037
|
-
});
|
|
1038
|
-
|
|
1039
|
-
window.addEventListener('drop', async (event) => {
|
|
1040
|
-
event.preventDefault();
|
|
1041
|
-
|
|
1042
|
-
drag_depth = 0;
|
|
1043
|
-
drop.classList.remove('active');
|
|
1044
|
-
|
|
1045
|
-
const file = event.dataTransfer.files[0];
|
|
1046
|
-
|
|
1047
|
-
if (file === undefined) {
|
|
1048
|
-
return;
|
|
1049
|
-
}
|
|
1050
|
-
|
|
1051
|
-
await this.open_blob(file);
|
|
1052
|
-
});
|
|
1053
|
-
|
|
1054
|
-
this.panel = document.getElementById('vgeo-panel');
|
|
1055
|
-
|
|
1056
|
-
this.#refresh_panel();
|
|
1057
|
-
}
|
|
1058
|
-
|
|
1059
|
-
/**
|
|
1060
|
-
* @param {string} id
|
|
1061
|
-
* @param {function(HTMLElement): void} bind
|
|
1062
|
-
*/
|
|
1063
|
-
#wire(id, bind) {
|
|
1064
|
-
const element = this.panel.querySelector(`#${id}`);
|
|
1065
|
-
|
|
1066
|
-
if (element !== null) {
|
|
1067
|
-
bind(element);
|
|
1068
|
-
}
|
|
1069
|
-
}
|
|
1070
|
-
|
|
1071
|
-
/**
|
|
1072
|
-
* The container section: what the header said, and what has actually been read.
|
|
1073
|
-
*
|
|
1074
|
-
* The second half is the point of the whole exercise. `read` against `file` is the fraction of
|
|
1075
|
-
* the asset this view cost, and on anything larger than a toy it is a small one.
|
|
1076
|
-
*
|
|
1077
|
-
* @returns {string}
|
|
1078
|
-
*/
|
|
1079
|
-
#container_html() {
|
|
1080
|
-
const reader = this.#reader;
|
|
1081
|
-
const header = reader.header;
|
|
1082
|
-
const source = reader.source;
|
|
1083
|
-
|
|
1084
|
-
const summary = page_summary(reader);
|
|
1085
|
-
|
|
1086
|
-
/*
|
|
1087
|
-
Cumulative for the session, and deliberately allowed past the file's own size: evicting and
|
|
1088
|
-
re-streaming really does fetch those pages again, and the audit really does read every byte.
|
|
1089
|
-
Rendered as a multiple rather than a percentage once it passes one, because "151.8%" reads
|
|
1090
|
-
as a broken counter and "1.52× the file" reads as what it is.
|
|
1091
|
-
*/
|
|
1092
|
-
const read_share = source.byte_length === 0
|
|
1093
|
-
? ''
|
|
1094
|
-
: source.bytes_read < source.byte_length
|
|
1095
|
-
? ` (${(100 * source.bytes_read / source.byte_length).toFixed(1)}% of the file)`
|
|
1096
|
-
: ` (${(source.bytes_read / source.byte_length).toFixed(2)}× the file)`;
|
|
1097
|
-
|
|
1098
|
-
return panel_table([
|
|
1099
|
-
['file', `${escape_html(this.#file_label)} · ${human_bytes(source.byte_length)}`],
|
|
1100
|
-
['format', `v${header.format_version} · encoding ${header.payload_encoding}`],
|
|
1101
|
-
['asset id', header.asset_id.slice(0, 16)],
|
|
1102
|
-
['source', `${thousands(header.source_face_count)} tris / ${thousands(header.source_vertex_count)} verts`],
|
|
1103
|
-
['contents', `${thousands(header.total_clusters)} clusters · ${thousands(header.total_pages)} pages · ${header.levels.length} levels`],
|
|
1104
|
-
])
|
|
1105
|
-
+ `<h2>streaming</h2>`
|
|
1106
|
-
+ panel_table([
|
|
1107
|
-
['read so far', `${human_bytes(source.bytes_read)}${read_share}`],
|
|
1108
|
-
['requests', thousands(source.request_count)],
|
|
1109
|
-
['cold start', `${human_bytes(this.#open_bytes)} · ${this.#open_ms.toFixed(1)} ms`],
|
|
1110
|
-
['resident', `${thousands(reader.resident_page_count)} / ${thousands(header.total_pages)} pages · ${human_bytes(reader.resident_bytes)}`],
|
|
1111
|
-
['page storage', summary.storage],
|
|
1112
|
-
['per-vertex normals', summary.normals],
|
|
1113
|
-
['head prefix', reader.head_bytes === 0 ? 'released' : human_bytes(reader.head_bytes)],
|
|
1114
|
-
['quarantined', quarantine_summary(reader)],
|
|
1115
|
-
]);
|
|
1116
|
-
}
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
/**
|
|
1120
|
-
* @returns {string}
|
|
1121
|
-
*/
|
|
1122
|
-
#validation_html() {
|
|
1123
|
-
const defects = this.#defects;
|
|
1124
|
-
|
|
1125
|
-
const note = `<div class="muted" style="margin-bottom:4px">`
|
|
1126
|
-
+ `off by default — it reads the whole file</div>`;
|
|
1127
|
-
|
|
1128
|
-
if (defects === undefined) {
|
|
1129
|
-
return note
|
|
1130
|
-
+ `<div class="muted">${escape_html(this.#validation_status)}</div>`
|
|
1131
|
-
+ `<button id="vgeo-validate">validate the whole file</button>`;
|
|
1132
|
-
}
|
|
1133
|
-
|
|
1134
|
-
const verdict = defects.length === 0
|
|
1135
|
-
? `<div class="good">container valid — every invariant holds</div>`
|
|
1136
|
-
: `<div class="bad">${defects.length} defect(s)</div><ol class="bad">${
|
|
1137
|
-
defects.slice(0, 12).map((d) => `<li>${escape_html(d.toString())}</li>`).join('')
|
|
1138
|
-
}</ol>`;
|
|
1139
|
-
|
|
1140
|
-
return note
|
|
1141
|
-
+ verdict
|
|
1142
|
-
+ `<div class="row"><span class="muted">took</span><span>${escape_html(this.#validation_status)}</span></div>`
|
|
1143
|
-
+ `<button id="vgeo-validate">validate again</button>`;
|
|
1144
|
-
}
|
|
1145
|
-
|
|
1146
|
-
#refresh_panel() {
|
|
1147
|
-
const reader = this.#reader;
|
|
1148
|
-
|
|
1149
|
-
const header = [`<h1>.vgeo viewer</h1>`];
|
|
1150
|
-
|
|
1151
|
-
if (this.#status !== '') {
|
|
1152
|
-
header.push(`<div class="muted">${escape_html(this.#status)}</div>`);
|
|
1153
|
-
}
|
|
1154
|
-
|
|
1155
|
-
if (reader === undefined) {
|
|
1156
|
-
this.panel.innerHTML = header.join('')
|
|
1157
|
-
+ `<div class="muted" style="margin-top:8px">`
|
|
1158
|
-
+ `or open <code>?file=/some/asset.vgeo</code></div>`
|
|
1159
|
-
+ `<button id="vgeo-sample">build a sample asset</button>`;
|
|
1160
|
-
|
|
1161
|
-
this.#wire('vgeo-sample', (element) => {
|
|
1162
|
-
element.onclick = () => this.open_sample();
|
|
1163
|
-
});
|
|
1164
|
-
|
|
1165
|
-
return;
|
|
1166
|
-
}
|
|
1167
|
-
|
|
1168
|
-
this.panel.innerHTML = header.join('')
|
|
1169
|
-
+ `<h2>container</h2>${this.#container_html()}`
|
|
1170
|
-
+ `<button id="vgeo-stream" ${this.#cut.wants.length === 0 ? 'disabled' : ''}>`
|
|
1171
|
-
+ `stream ${this.#cut.wants.length} wanted page(s)</button>`
|
|
1172
|
-
+ `<label><input id="vgeo-auto-stream" type="checkbox" ${this.#auto_stream ? 'checked' : ''}> stream until nothing is clamped</label>`
|
|
1173
|
-
+ `<label><input id="vgeo-verify" type="checkbox" ${this.#read_options.verify_frames ? 'checked' : ''}> verify each frame's checksum</label>`
|
|
1174
|
-
+ `<button id="vgeo-load-all">install every page</button>`
|
|
1175
|
-
+ `<button id="vgeo-evict">evict everything but the root</button>`
|
|
1176
|
-
+ `<div class="row" style="margin-top:8px"><span class="muted">head prefix</span>`
|
|
1177
|
-
+ `<span class="muted">re-opens from cold</span></div>`
|
|
1178
|
-
+ `<select id="vgeo-head">`
|
|
1179
|
-
+ HEAD_PREFIXES.map(([label, size]) =>
|
|
1180
|
-
`<option value="${size}" ${size === this.#read_options.head_prefix_bytes ? 'selected' : ''}>${label}</option>`
|
|
1181
|
-
).join('')
|
|
1182
|
-
+ `</select>`
|
|
1183
|
-
+ `<h2>cut</h2>`
|
|
1184
|
-
+ `<div id="vgeo-cut">${this.#cut_html()}</div>`
|
|
1185
|
-
+ `<h2>selection</h2>`
|
|
1186
|
-
+ `<div class="row"><span class="muted">tau</span><span id="vgeo-tau-value">${this.#view.tau.toFixed(2)} px</span></div>`
|
|
1187
|
-
+ `<input id="vgeo-tau" type="range" min="-1.3" max="1.7" step="0.01" value="${Math.log10(this.#view.tau)}">`
|
|
1188
|
-
+ `<button id="vgeo-adapt">adapt to view</button>`
|
|
1189
|
-
+ `<label><input id="vgeo-auto" type="checkbox" ${this.#auto_adapt ? 'checked' : ''}> follow the camera</label>`
|
|
1190
|
-
+ `<label><input id="vgeo-cull" type="checkbox" ${this.#view.cull_enabled ? 'checked' : ''}> frustum cull while selecting</label>`
|
|
1191
|
-
+ `<h2>display</h2>`
|
|
1192
|
-
+ `<select id="vgeo-color">`
|
|
1193
|
-
+ Object.values(VGEO_COLOR_MODE).map((mode) =>
|
|
1194
|
-
`<option value="${mode}" ${mode === this.#color_mode ? 'selected' : ''}>colour by ${mode}</option>`
|
|
1195
|
-
).join('')
|
|
1196
|
-
+ `</select>`
|
|
1197
|
-
+ `<h2>validation</h2>${this.#validation_html()}`;
|
|
1198
|
-
|
|
1199
|
-
this.#wire('vgeo-adapt', (element) => {
|
|
1200
|
-
element.onclick = () => {
|
|
1201
|
-
this.adapt();
|
|
1202
|
-
this.#refresh_panel();
|
|
1203
|
-
};
|
|
1204
|
-
});
|
|
1205
|
-
|
|
1206
|
-
this.#wire('vgeo-stream', (element) => {
|
|
1207
|
-
element.onclick = () => this.stream();
|
|
1208
|
-
});
|
|
1209
|
-
|
|
1210
|
-
this.#wire('vgeo-auto-stream', (element) => {
|
|
1211
|
-
element.onchange = () => {
|
|
1212
|
-
this.set_auto_stream(element.checked);
|
|
1213
|
-
|
|
1214
|
-
if (element.checked) {
|
|
1215
|
-
this.stream();
|
|
1216
|
-
}
|
|
1217
|
-
};
|
|
1218
|
-
});
|
|
1219
|
-
|
|
1220
|
-
this.#wire('vgeo-verify', (element) => {
|
|
1221
|
-
// read per fetch, so this takes effect on the next page rather than on a reopen
|
|
1222
|
-
element.onchange = () => {
|
|
1223
|
-
this.#read_options.verify_frames = element.checked;
|
|
1224
|
-
};
|
|
1225
|
-
});
|
|
1226
|
-
|
|
1227
|
-
this.#wire('vgeo-head', (element) => {
|
|
1228
|
-
element.onchange = () => {
|
|
1229
|
-
this.#read_options.head_prefix_bytes = Number(element.value);
|
|
1230
|
-
|
|
1231
|
-
this.reopen();
|
|
1232
|
-
};
|
|
1233
|
-
});
|
|
1234
|
-
|
|
1235
|
-
this.#wire('vgeo-load-all', (element) => {
|
|
1236
|
-
element.onclick = () => this.load_all_pages();
|
|
1237
|
-
});
|
|
1238
|
-
|
|
1239
|
-
this.#wire('vgeo-evict', (element) => {
|
|
1240
|
-
element.onclick = () => this.reset_residency();
|
|
1241
|
-
});
|
|
1242
|
-
|
|
1243
|
-
this.#wire('vgeo-validate', (element) => {
|
|
1244
|
-
element.onclick = () => this.validate();
|
|
1245
|
-
});
|
|
1246
|
-
|
|
1247
|
-
this.#wire('vgeo-tau', (element) => {
|
|
1248
|
-
element.oninput = () => {
|
|
1249
|
-
this.#view.tau = Math.pow(10, Number(element.value));
|
|
1250
|
-
|
|
1251
|
-
this.adapt();
|
|
1252
|
-
this.#refresh_panel();
|
|
1253
|
-
|
|
1254
|
-
if (this.#auto_stream) {
|
|
1255
|
-
this.stream();
|
|
1256
|
-
}
|
|
1257
|
-
};
|
|
1258
|
-
});
|
|
1259
|
-
|
|
1260
|
-
this.#wire('vgeo-auto', (element) => {
|
|
1261
|
-
element.onchange = () => {
|
|
1262
|
-
this.#auto_adapt = element.checked;
|
|
1263
|
-
};
|
|
1264
|
-
});
|
|
1265
|
-
|
|
1266
|
-
this.#wire('vgeo-cull', (element) => {
|
|
1267
|
-
element.onchange = () => {
|
|
1268
|
-
this.#view.cull_enabled = element.checked;
|
|
1269
|
-
|
|
1270
|
-
this.adapt();
|
|
1271
|
-
this.#refresh_panel();
|
|
1272
|
-
};
|
|
1273
|
-
});
|
|
1274
|
-
|
|
1275
|
-
this.#wire('vgeo-color', (element) => {
|
|
1276
|
-
element.onchange = () => {
|
|
1277
|
-
this.#color_mode = element.value;
|
|
1278
|
-
|
|
1279
|
-
this.adapt();
|
|
1280
|
-
this.#refresh_panel();
|
|
1281
|
-
};
|
|
1282
|
-
});
|
|
1283
|
-
}
|
|
1284
|
-
}
|
|
1285
|
-
|
|
1286
|
-
const viewer = new VGeoViewer();
|
|
1287
|
-
|
|
1288
|
-
/*
|
|
1289
|
-
Everything a session might want to reach for, and the handles a scripted one needs. A page that can
|
|
1290
|
-
be driven from the console is one that can be driven from a headless browser, which is what makes
|
|
1291
|
-
this verifiable without a person watching it.
|
|
1292
|
-
*/
|
|
1293
|
-
Object.assign(globalThis, {
|
|
1294
|
-
__vgeo: viewer,
|
|
1295
|
-
|
|
1296
|
-
__open_url: (url) => viewer.load_url(url),
|
|
1297
|
-
__open_sample: () => viewer.open_sample().then(__state),
|
|
1298
|
-
__adapt: () => {
|
|
1299
|
-
viewer.adapt();
|
|
1300
|
-
|
|
1301
|
-
return __state();
|
|
1302
|
-
},
|
|
1303
|
-
__stream: () => viewer.stream().then(__state),
|
|
1304
|
-
__validate: () => viewer.validate().then((defects) => defects.map(String)),
|
|
1305
|
-
__state,
|
|
1306
|
-
});
|
|
1307
|
-
|
|
1308
|
-
/**
|
|
1309
|
-
* Everything a scripted session asks about, in one object.
|
|
1310
|
-
*
|
|
1311
|
-
* @returns {object}
|
|
1312
|
-
*/
|
|
1313
|
-
function __state() {
|
|
1314
|
-
const reader = viewer.reader;
|
|
1315
|
-
const cut = viewer.cut;
|
|
1316
|
-
|
|
1317
|
-
return {
|
|
1318
|
-
open: reader !== undefined,
|
|
1319
|
-
asset_id: reader?.header.asset_id,
|
|
1320
|
-
total_pages: reader?.header.total_pages,
|
|
1321
|
-
total_clusters: reader?.header.total_clusters,
|
|
1322
|
-
resident_pages: reader?.resident_page_count ?? 0,
|
|
1323
|
-
resident_bytes: reader?.resident_bytes ?? 0,
|
|
1324
|
-
head_bytes: reader?.head_bytes ?? 0,
|
|
1325
|
-
quarantined_pages: reader?.quarantined_page_count ?? 0,
|
|
1326
|
-
file_bytes: reader?.source.byte_length ?? 0,
|
|
1327
|
-
bytes_read: reader?.source.bytes_read ?? 0,
|
|
1328
|
-
requests: reader?.source.request_count ?? 0,
|
|
1329
|
-
clusters: cut.clusters.length,
|
|
1330
|
-
triangles: cut.triangle_count,
|
|
1331
|
-
clamped: cut.clamped,
|
|
1332
|
-
clamped_on_activation: cut.clamped_on_activation,
|
|
1333
|
-
wants: cut.wants.length,
|
|
1334
|
-
broken_child_refs: cut.broken_child_refs,
|
|
1335
|
-
};
|
|
1336
|
-
}
|
|
1337
|
-
|
|
1338
|
-
viewer.start().catch((e) => {
|
|
1339
|
-
console.error(e);
|
|
1340
|
-
|
|
1341
|
-
document.getElementById('vgeo-panel').innerHTML =
|
|
1342
|
-
`<h1>.vgeo viewer</h1><div class="bad">${e.message ?? e}</div>`;
|
|
1343
|
-
});
|