@woosh/meep-engine 3.28.0 → 3.30.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-image-decoder.js +1 -1
- package/build/bundle-worker-terrain.js +1 -1
- package/package.json +1 -1
- package/src/core/binary/BinaryBuffer.d.ts.map +1 -1
- package/src/core/binary/BinaryBuffer.js +14 -2
- package/src/core/binary/BitSet.d.ts +16 -0
- package/src/core/binary/BitSet.d.ts.map +1 -1
- package/src/core/binary/BitSet.js +159 -49
- package/src/core/binary/allocator/OffsetAllocator.d.ts +48 -0
- package/src/core/binary/allocator/OffsetAllocator.d.ts.map +1 -1
- package/src/core/binary/allocator/OffsetAllocator.js +208 -9
- package/src/core/binary/compression/compress_bytes.d.ts +20 -0
- package/src/core/binary/compression/compress_bytes.d.ts.map +1 -0
- package/src/core/binary/compression/compress_bytes.js +35 -0
- package/src/core/binary/compression/decompress_bytes_into.d.ts +30 -0
- package/src/core/binary/compression/decompress_bytes_into.d.ts.map +1 -0
- package/src/core/binary/compression/decompress_bytes_into.js +87 -0
- package/src/core/binary/compression/deflate_decompress_bound.d.ts +27 -0
- package/src/core/binary/compression/deflate_decompress_bound.d.ts.map +1 -0
- package/src/core/binary/compression/deflate_decompress_bound.js +32 -0
- package/src/core/bvh2/bvh3/BVH.d.ts.map +1 -1
- package/src/core/bvh2/bvh3/BVH.js +14 -1
- package/src/core/cache/FrequencySketch.d.ts +3 -1
- package/src/core/cache/FrequencySketch.d.ts.map +1 -1
- package/src/core/cache/FrequencySketch.js +10 -4
- package/src/core/cache/wtinylfu/CacheWTinylfu.d.ts.map +1 -1
- package/src/core/cache/wtinylfu/CacheWTinylfu.js +24 -2
- package/src/core/geom/3d/quadric/quadric_attribute_add.d.ts +24 -0
- package/src/core/geom/3d/quadric/quadric_attribute_add.d.ts.map +1 -0
- package/src/core/geom/3d/quadric/quadric_attribute_add.js +31 -0
- package/src/core/geom/3d/quadric/quadric_attribute_reduce.d.ts +46 -0
- package/src/core/geom/3d/quadric/quadric_attribute_reduce.d.ts.map +1 -0
- package/src/core/geom/3d/quadric/quadric_attribute_reduce.js +104 -0
- package/src/core/geom/3d/quadric/quadric_attribute_set_from_triangle.d.ts +62 -0
- package/src/core/geom/3d/quadric/quadric_attribute_set_from_triangle.d.ts.map +1 -0
- package/src/core/geom/3d/quadric/quadric_attribute_set_from_triangle.js +158 -0
- package/src/core/geom/3d/quadric/quadric_attribute_size.d.ts +21 -0
- package/src/core/geom/3d/quadric/quadric_attribute_size.d.ts.map +1 -0
- package/src/core/geom/3d/quadric/quadric_attribute_size.js +24 -0
- package/src/core/geom/3d/quadric/quadric_attribute_solve.d.ts +34 -0
- package/src/core/geom/3d/quadric/quadric_attribute_solve.d.ts.map +1 -0
- package/src/core/geom/3d/quadric/quadric_attribute_solve.js +55 -0
- package/src/core/geom/3d/quadric/quadric_attribute_sub.d.ts +28 -0
- package/src/core/geom/3d/quadric/quadric_attribute_sub.d.ts.map +1 -0
- package/src/core/geom/3d/quadric/quadric_attribute_sub.js +35 -0
- package/src/core/geom/3d/topology/struct/binary/BinaryDataBlock.d.ts +15 -0
- package/src/core/geom/3d/topology/struct/binary/BinaryDataBlock.d.ts.map +1 -1
- package/src/core/geom/3d/topology/struct/binary/BinaryDataBlock.js +35 -0
- package/src/core/geom/3d/topology/struct/binary/BtWedgeSet.d.ts +93 -0
- package/src/core/geom/3d/topology/struct/binary/BtWedgeSet.d.ts.map +1 -0
- package/src/core/geom/3d/topology/struct/binary/BtWedgeSet.js +128 -0
- package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_compute_vertex_quadrics.d.ts +20 -0
- package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_compute_vertex_quadrics.d.ts.map +1 -1
- package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_compute_vertex_quadrics.js +69 -4
- package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_compute_wedge_attribute_quadrics.d.ts +38 -0
- package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_compute_wedge_attribute_quadrics.d.ts.map +1 -0
- package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_compute_wedge_attribute_quadrics.js +113 -0
- package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_simplify.d.ts +93 -17
- package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_simplify.d.ts.map +1 -1
- package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_simplify.js +930 -69
- package/src/core/geom/3d/topology/struct/binary/io/bt_wedges_live_count.d.ts +22 -0
- package/src/core/geom/3d/topology/struct/binary/io/bt_wedges_live_count.d.ts.map +1 -0
- package/src/core/geom/3d/topology/struct/binary/io/bt_wedges_live_count.js +46 -0
- package/src/core/geom/3d/topology/struct/binary/io/bt_wedges_per_vertex.d.ts +19 -0
- package/src/core/geom/3d/topology/struct/binary/io/bt_wedges_per_vertex.d.ts.map +1 -0
- package/src/core/geom/3d/topology/struct/binary/io/bt_wedges_per_vertex.js +49 -0
- package/src/core/geom/3d/topology/struct/binary/query/bt_edge_collapse_fold_limit.d.ts +25 -0
- package/src/core/geom/3d/topology/struct/binary/query/bt_edge_collapse_fold_limit.d.ts.map +1 -0
- package/src/core/geom/3d/topology/struct/binary/query/bt_edge_collapse_fold_limit.js +228 -0
- package/src/core/geom/3d/topology/struct/binary/query/bt_edge_collapse_would_fold.d.ts +13 -0
- package/src/core/geom/3d/topology/struct/binary/query/bt_edge_collapse_would_fold.d.ts.map +1 -1
- package/src/core/geom/3d/topology/struct/binary/query/bt_edge_collapse_would_fold.js +10 -128
- package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_build_face_bvh.d.ts.map +1 -1
- package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_build_face_bvh.js +11 -1
- package/src/core/graph/layout/layered/BoxLayoutOptions.d.ts +63 -0
- package/src/core/graph/layout/layered/BoxLayoutOptions.d.ts.map +1 -0
- package/src/core/graph/layout/layered/BoxLayoutOptions.js +80 -0
- package/src/core/{model/node-graph/visual/layout → graph/layout/layered}/LayoutBoxGraph.d.ts +35 -0
- package/src/core/graph/layout/layered/LayoutBoxGraph.d.ts.map +1 -0
- package/src/core/{model/node-graph/visual/layout → graph/layout/layered}/LayoutBoxGraph.js +53 -1
- package/src/core/graph/layout/layered/layout_assign_coordinates.d.ts.map +1 -0
- package/src/core/{model/node-graph/visual/layout → graph/layout/layered}/layout_assign_coordinates.js +72 -68
- package/src/core/graph/layout/layered/layout_assign_layers.d.ts.map +1 -0
- package/src/core/{model/node-graph/visual/layout → graph/layout/layered}/layout_assign_layers.js +4 -2
- package/src/core/graph/layout/layered/layout_break_cycles.d.ts.map +1 -0
- package/src/core/graph/layout/layered/layout_insert_lanes.d.ts.map +1 -0
- package/src/core/graph/layout/layered/layout_order_layers.d.ts.map +1 -0
- package/src/core/graph/layout/layered/layout_orient_links.d.ts.map +1 -0
- package/src/core/graph/mn_graph_collapse_weighted_edge.js +6 -6
- package/src/core/math/lookup/ParameterLookupTable.d.ts.map +1 -1
- package/src/core/math/lookup/ParameterLookupTable.js +10 -5
- package/src/core/math/noise/sdnoise.d.ts.map +1 -1
- package/src/core/math/noise/sdnoise.js +16 -10
- package/src/core/model/node-graph/visual/layout/NodeGraphLayoutOptions.d.ts +7 -51
- package/src/core/model/node-graph/visual/layout/NodeGraphLayoutOptions.d.ts.map +1 -1
- package/src/core/model/node-graph/visual/layout/NodeGraphLayoutOptions.js +8 -69
- package/src/core/model/node-graph/visual/layout/layout_build_problem.js +1 -1
- package/src/core/model/node-graph/visual/layout/layout_node_graph.js +6 -6
- package/src/core/model/node-graph/visual/layout/route_grid_build.js +1 -1
- package/src/core/model/object/ImmutableObjectPool.d.ts.map +1 -1
- package/src/core/model/object/ImmutableObjectPool.js +17 -7
- package/src/core/process/executor/ConcurrentExecutor.d.ts +1 -5
- package/src/core/process/executor/ConcurrentExecutor.d.ts.map +1 -1
- package/src/core/process/executor/ConcurrentExecutor.js +95 -1
- package/src/core/process/task/task_find_dependency_cycle.d.ts +17 -0
- package/src/core/process/task/task_find_dependency_cycle.d.ts.map +1 -0
- package/src/core/process/task/task_find_dependency_cycle.js +81 -0
- package/src/core/wfc/WFCCellChoice.d.ts +17 -0
- package/src/core/wfc/WFCCellChoice.d.ts.map +1 -0
- package/src/core/wfc/WFCCellChoice.js +36 -0
- package/src/core/wfc/WFCLattice.d.ts +105 -0
- package/src/core/wfc/WFCLattice.d.ts.map +1 -0
- package/src/core/wfc/WFCLattice.js +235 -0
- package/src/core/wfc/WFCRuleTable.d.ts +172 -0
- package/src/core/wfc/WFCRuleTable.d.ts.map +1 -0
- package/src/core/wfc/WFCRuleTable.js +334 -0
- package/src/core/wfc/WFCState.d.ts +273 -0
- package/src/core/wfc/WFCState.d.ts.map +1 -0
- package/src/core/wfc/WFCState.js +552 -0
- package/src/core/wfc/WFCStatus.d.ts +11 -0
- package/src/core/wfc/WFCStatus.d.ts.map +1 -0
- package/src/core/wfc/WFCStatus.js +28 -0
- package/src/core/wfc/WFCTileWeights.d.ts +48 -0
- package/src/core/wfc/WFCTileWeights.d.ts.map +1 -0
- package/src/core/wfc/WFCTileWeights.js +86 -0
- package/src/core/wfc/WFC_UNRESOLVED.d.ts +12 -0
- package/src/core/wfc/WFC_UNRESOLVED.d.ts.map +1 -0
- package/src/core/wfc/WFC_UNRESOLVED.js +11 -0
- package/src/core/wfc/wfc_estimate_bytes.d.ts +24 -0
- package/src/core/wfc/wfc_estimate_bytes.d.ts.map +1 -0
- package/src/core/wfc/wfc_estimate_bytes.js +47 -0
- package/src/core/wfc/wfc_rule_table_tile_components.d.ts +27 -0
- package/src/core/wfc/wfc_rule_table_tile_components.d.ts.map +1 -0
- package/src/core/wfc/wfc_rule_table_tile_components.js +80 -0
- package/src/core/wfc/wfc_solve.d.ts +21 -0
- package/src/core/wfc/wfc_solve.d.ts.map +1 -0
- package/src/core/wfc/wfc_solve.js +154 -0
- package/src/core/wfc/wfc_state_apply_boundary.d.ts +18 -0
- package/src/core/wfc/wfc_state_apply_boundary.d.ts.map +1 -0
- package/src/core/wfc/wfc_state_apply_boundary.js +61 -0
- package/src/core/wfc/wfc_state_assign.d.ts +11 -0
- package/src/core/wfc/wfc_state_assign.d.ts.map +1 -0
- package/src/core/wfc/wfc_state_assign.js +36 -0
- package/src/core/wfc/wfc_state_ban_bits.d.ts +17 -0
- package/src/core/wfc/wfc_state_ban_bits.d.ts.map +1 -0
- package/src/core/wfc/wfc_state_ban_bits.js +81 -0
- package/src/core/wfc/wfc_state_collapse.d.ts +12 -0
- package/src/core/wfc/wfc_state_collapse.d.ts.map +1 -0
- package/src/core/wfc/wfc_state_collapse.js +73 -0
- package/src/core/wfc/wfc_state_flush_dirty.d.ts +12 -0
- package/src/core/wfc/wfc_state_flush_dirty.d.ts.map +1 -0
- package/src/core/wfc/wfc_state_flush_dirty.js +39 -0
- package/src/core/wfc/wfc_state_propagate.d.ts +14 -0
- package/src/core/wfc/wfc_state_propagate.d.ts.map +1 -0
- package/src/core/wfc/wfc_state_propagate.js +155 -0
- package/src/core/wfc/wfc_state_read.d.ts +11 -0
- package/src/core/wfc/wfc_state_read.d.ts.map +1 -0
- package/src/core/wfc/wfc_state_read.js +38 -0
- package/src/core/wfc/wfc_state_restrict.d.ts +18 -0
- package/src/core/wfc/wfc_state_restrict.d.ts.map +1 -0
- package/src/core/wfc/wfc_state_restrict.js +39 -0
- package/src/core/wfc/wfc_state_set_eligible.d.ts +24 -0
- package/src/core/wfc/wfc_state_set_eligible.d.ts.map +1 -0
- package/src/core/wfc/wfc_state_set_eligible.js +49 -0
- package/src/core/wfc/wfc_state_undo_to.d.ts +13 -0
- package/src/core/wfc/wfc_state_undo_to.d.ts.map +1 -0
- package/src/core/wfc/wfc_state_undo_to.js +84 -0
- package/src/core/wfc/wfc_words_per_cell.d.ts +11 -0
- package/src/core/wfc/wfc_words_per_cell.d.ts.map +1 -0
- package/src/core/wfc/wfc_words_per_cell.js +16 -0
- package/src/engine/ecs/EntityComponentDataset.d.ts +8 -0
- package/src/engine/ecs/EntityComponentDataset.d.ts.map +1 -1
- package/src/engine/ecs/EntityComponentDataset.js +37 -14
- package/src/engine/ecs/fow/FogOfWarRevealerSystem.d.ts +8 -1
- package/src/engine/ecs/fow/FogOfWarRevealerSystem.d.ts.map +1 -1
- package/src/engine/ecs/fow/FogOfWarRevealerSystem.js +37 -2
- package/src/engine/ecs/guid/UUID.d.ts.map +1 -1
- package/src/engine/ecs/guid/UUID.js +45 -17
- package/src/engine/ecs/ik/OneBoneSurfaceAlignmentSolver.d.ts.map +1 -1
- package/src/engine/ecs/ik/OneBoneSurfaceAlignmentSolver.js +3 -1
- package/src/engine/ecs/terrain/ecs/cling/ClingToTerrainSystem.d.ts.map +1 -1
- package/src/engine/ecs/terrain/ecs/cling/ClingToTerrainSystem.js +366 -360
- package/src/engine/graphics/texture/sampler/resize/sampler2d_scale_down_linear.d.ts.map +1 -1
- package/src/engine/graphics/texture/sampler/resize/sampler2d_scale_down_linear.js +24 -10
- package/src/engine/graphics3/DebugDrawSystem.d.ts +7 -5
- package/src/engine/graphics3/DebugDrawSystem.d.ts.map +1 -1
- package/src/engine/graphics3/DebugDrawSystem.js +206 -203
- package/src/engine/graphics3/DecalSystem.d.ts +4 -5
- package/src/engine/graphics3/DecalSystem.d.ts.map +1 -1
- package/src/engine/graphics3/DecalSystem.js +5 -5
- package/src/engine/graphics3/FogOfWarSystem.d.ts +7 -12
- package/src/engine/graphics3/FogOfWarSystem.d.ts.map +1 -1
- package/src/engine/graphics3/FogOfWarSystem.js +8 -12
- package/src/engine/graphics3/prefab/prefab_instantiate.js +230 -230
- package/src/engine/grid/grid2transform/GridPosition2TransformSystem.d.ts.map +1 -1
- package/src/engine/grid/grid2transform/GridPosition2TransformSystem.js +24 -5
- package/src/engine/interpolation/Interpoland.d.ts +7 -1
- package/src/engine/interpolation/Interpoland.d.ts.map +1 -1
- package/src/engine/interpolation/Interpoland.js +8 -1
- package/src/engine/interpolation/InterpolationSystem.d.ts.map +1 -1
- package/src/engine/interpolation/InterpolationSystem.js +3 -0
- package/src/engine/interpolation/TransformPoseSerializationAdapter.d.ts +5 -3
- package/src/engine/interpolation/TransformPoseSerializationAdapter.d.ts.map +1 -1
- package/src/engine/interpolation/TransformPoseSerializationAdapter.js +14 -3
- package/src/engine/interpolation/pose_interpoland.d.ts.map +1 -1
- package/src/engine/interpolation/pose_interpoland.js +2 -0
- package/src/engine/network/orchestrator/NetworkPeer.d.ts.map +1 -1
- package/src/engine/network/orchestrator/NetworkPeer.js +15 -10
- package/src/engine/physics/cloth/ecs/ClothWorld.d.ts.map +1 -1
- package/src/engine/physics/cloth/ecs/ClothWorld.js +17 -0
- package/src/engine/physics/mls-mpm/MLS_MPM.d.ts.map +1 -1
- package/src/engine/physics/mls-mpm/MLS_MPM.js +14 -0
- package/src/engine/sound/sopra/definition/clip/AbstractAudioClip.d.ts +2 -1
- package/src/engine/sound/sopra/definition/clip/AbstractAudioClip.d.ts.map +1 -1
- package/src/engine/sound/sopra/definition/clip/AbstractAudioClip.js +1 -1
- package/src/engine/sound/sopra/definition/clip/BlendContainerAudioClip.d.ts +1 -0
- package/src/engine/sound/sopra/definition/clip/BlendContainerAudioClip.d.ts.map +1 -1
- package/src/engine/sound/sopra/definition/clip/RandomContainerAudioClip.d.ts +1 -0
- package/src/engine/sound/sopra/definition/clip/RandomContainerAudioClip.d.ts.map +1 -1
- package/src/engine/sound/sopra/definition/clip/SampleAudioClip.d.ts +2 -1
- package/src/engine/sound/sopra/definition/clip/SampleAudioClip.d.ts.map +1 -1
- package/src/engine/sound/sopra/definition/clip/SampleAudioClip.js +12 -3
- package/src/engine/sound/sopra/definition/clip/SequenceContainerAudioClip.d.ts +1 -0
- package/src/engine/sound/sopra/definition/clip/SequenceContainerAudioClip.d.ts.map +1 -1
- package/src/engine/sound/sopra/definition/clip/SwitchContainerAudioClip.d.ts +1 -0
- package/src/engine/sound/sopra/definition/clip/SwitchContainerAudioClip.d.ts.map +1 -1
- package/src/engine/sound/sopra/runtime/EventInstance.d.ts.map +1 -1
- package/src/engine/sound/sopra/runtime/EventInstance.js +4 -1
- package/src/format/image/avif/av1/decode/decode_coefficients.d.ts.map +1 -1
- package/src/format/image/avif/av1/decode/decode_coefficients.js +248 -259
- package/src/format/image/avif/av1/decode/read_golomb.d.ts +21 -0
- package/src/format/image/avif/av1/decode/read_golomb.d.ts.map +1 -0
- package/src/format/image/avif/av1/decode/read_golomb.js +49 -0
- package/src/format/image/avif/av1/filter/loop_restoration.js +461 -451
- package/src/format/image/avif/av1/util/round2.d.ts +19 -2
- package/src/format/image/avif/av1/util/round2.d.ts.map +1 -1
- package/src/format/image/avif/av1/util/round2.js +51 -27
- package/src/format/image/avif/heif/parse_avif_file.js +24 -0
- package/src/shade/RENDERER_CONTRACT.md +12 -7
- package/src/shade/playground/caldera/README.md +529 -0
- package/src/shade/playground/caldera/caldera_format.d.ts +151 -0
- package/src/shade/playground/caldera/caldera_format.d.ts.map +1 -0
- package/src/shade/playground/caldera/caldera_format.js +241 -0
- package/src/shade/playground/caldera/index.html +126 -0
- package/src/shade/playground/caldera/main.d.ts.map +1 -0
- package/src/shade/playground/caldera/main.js +1565 -0
- package/src/shade/playground/mesh_row_ceiling/README.md +85 -0
- package/src/shade/playground/mesh_row_ceiling/index.html +31 -0
- package/src/shade/playground/mesh_row_ceiling/main.d.ts +2 -0
- package/src/shade/playground/mesh_row_ceiling/main.d.ts.map +1 -0
- package/src/shade/playground/mesh_row_ceiling/main.js +389 -0
- package/src/shade/playground/vgeo_runtime/README.md +5 -3
- package/src/shade/playground/vgeo_runtime/main.js +3 -2
- package/src/shade/playground/vgeo_runtime/sample_asset.d.ts +3 -3
- package/src/shade/playground/vgeo_runtime/sample_asset.d.ts.map +1 -1
- package/src/shade/playground/vgeo_runtime/sample_asset.js +3 -3
- package/src/shade/playground/vgeo_scene/README.md +103 -24
- package/src/shade/playground/vgeo_scene/main.js +525 -24
- package/src/shade/renderer/GraphicsContext.d.ts.map +1 -1
- package/src/shade/renderer/GraphicsContext.js +6 -0
- package/src/shade/renderer/Renderer.d.ts +2 -0
- package/src/shade/renderer/Renderer.d.ts.map +1 -1
- package/src/shade/renderer/Renderer.js +23 -0
- package/src/shade/renderer/animation/skinning/GPUMeshSkinningContext.d.ts.map +1 -1
- package/src/shade/renderer/animation/skinning/GPUMeshSkinningContext.js +100 -8
- package/src/shade/renderer/buffer/table/GPUTypedTable.d.ts.map +1 -1
- package/src/shade/renderer/buffer/table/GPUTypedTable.js +14 -0
- package/src/shade/renderer/extension/RENDER_EXTENSION_DESIGN.md +69 -33
- package/src/shade/renderer/extension/RenderExtension.d.ts +15 -18
- package/src/shade/renderer/extension/RenderExtension.d.ts.map +1 -1
- package/src/shade/renderer/extension/RenderExtension.js +15 -19
- package/src/shade/renderer/extension/RenderExtensionRegistry.d.ts +5 -5
- package/src/shade/renderer/extension/RenderExtensionRegistry.d.ts.map +1 -1
- package/src/shade/renderer/extension/RenderExtensionRegistry.js +29 -166
- package/src/shade/renderer/geometry/GPUGeometryManager.d.ts.map +1 -1
- package/src/shade/renderer/geometry/GPUGeometryManager.js +73 -16
- package/src/shade/renderer/geometry/bvh/GPUGeometryBVHManager.d.ts.map +1 -1
- package/src/shade/renderer/geometry/bvh/GPUGeometryBVHManager.js +18 -3
- package/src/shade/renderer/geometry/meshlet/COMPACTION_PLAN_2026_09_16.md +663 -0
- package/src/shade/renderer/geometry/meshlet/GPUMeshletManager.d.ts +100 -5
- package/src/shade/renderer/geometry/meshlet/GPUMeshletManager.d.ts.map +1 -1
- package/src/shade/renderer/geometry/meshlet/GPUMeshletManager.js +688 -81
- package/src/shade/renderer/geometry/virtual/ATTRIBUTE_PLAN_2026_09_18.md +540 -0
- package/src/shade/renderer/geometry/virtual/VGEO_FORMAT.md +155 -64
- package/src/shade/renderer/geometry/virtual/VIRTUAL_GEOMETRY_DESIGN.md +76 -16
- package/src/shade/renderer/geometry/virtual/VIRTUAL_GEOMETRY_PLAN.md +3 -3
- package/src/shade/renderer/geometry/virtual/WEDGE_PLAN_2026_09_18.md +448 -0
- package/src/shade/renderer/geometry/virtual/build/VGeoBuildOptions.d.ts +44 -7
- package/src/shade/renderer/geometry/virtual/build/VGeoBuildOptions.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/build/VGeoBuildOptions.js +48 -9
- package/src/shade/renderer/geometry/virtual/build/level/VGeoContribution.d.ts +40 -0
- package/src/shade/renderer/geometry/virtual/build/level/VGeoContribution.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/build/level/VGeoContribution.js +24 -0
- package/src/shade/renderer/geometry/virtual/build/level/VGeoFrontier.d.ts +91 -0
- package/src/shade/renderer/geometry/virtual/build/level/VGeoFrontier.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/build/level/VGeoFrontier.js +91 -0
- package/src/shade/renderer/geometry/virtual/build/level/VGeoGroupRepack.d.ts +24 -0
- package/src/shade/renderer/geometry/virtual/build/level/VGeoGroupRepack.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/build/level/VGeoGroupRepack.js +25 -0
- package/src/shade/renderer/geometry/virtual/build/level/VGeoGroupStep.d.ts +77 -0
- package/src/shade/renderer/geometry/virtual/build/level/VGeoGroupStep.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/build/level/VGeoGroupStep.js +84 -0
- package/src/shade/renderer/geometry/virtual/build/level/VGeoLevelOptions.d.ts +19 -6
- package/src/shade/renderer/geometry/virtual/build/level/VGeoLevelOptions.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/build/level/VGeoLevelOptions.js +20 -7
- package/src/shade/renderer/geometry/virtual/build/level/vgeo_build_cluster.d.ts +19 -0
- package/src/shade/renderer/geometry/virtual/build/level/vgeo_build_cluster.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/build/level/vgeo_build_cluster.js +58 -0
- package/src/shade/renderer/geometry/virtual/build/level/vgeo_build_cluster_graph.d.ts +18 -0
- package/src/shade/renderer/geometry/virtual/build/level/vgeo_build_cluster_graph.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/build/level/vgeo_build_cluster_graph.js +116 -0
- package/src/shade/renderer/geometry/virtual/build/level/vgeo_build_group_step.d.ts +18 -0
- package/src/shade/renderer/geometry/virtual/build/level/vgeo_build_group_step.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/build/level/vgeo_build_group_step.js +545 -0
- package/src/shade/renderer/geometry/virtual/build/level/vgeo_build_leaf_frontier.d.ts +16 -0
- package/src/shade/renderer/geometry/virtual/build/level/vgeo_build_leaf_frontier.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/build/level/vgeo_build_leaf_frontier.js +110 -0
- package/src/shade/renderer/geometry/virtual/build/level/vgeo_build_levels.d.ts +2 -27
- package/src/shade/renderer/geometry/virtual/build/level/vgeo_build_levels.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/build/level/vgeo_build_levels.js +120 -1564
- package/src/shade/renderer/geometry/virtual/build/level/vgeo_compute_group_boundary.d.ts +26 -0
- package/src/shade/renderer/geometry/virtual/build/level/vgeo_compute_group_boundary.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/build/level/vgeo_compute_group_boundary.js +123 -0
- package/src/shade/renderer/geometry/virtual/build/level/vgeo_error_bands.d.ts +26 -0
- package/src/shade/renderer/geometry/virtual/build/level/vgeo_error_bands.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/build/level/vgeo_error_bands.js +78 -0
- package/src/shade/renderer/geometry/virtual/build/level/vgeo_partition_into_groups.d.ts +24 -0
- package/src/shade/renderer/geometry/virtual/build/level/vgeo_partition_into_groups.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/build/level/vgeo_partition_into_groups.js +337 -0
- package/src/shade/renderer/geometry/virtual/build/level/vgeo_run_round.d.ts +29 -0
- package/src/shade/renderer/geometry/virtual/build/level/vgeo_run_round.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/build/level/vgeo_run_round.js +147 -0
- package/src/shade/renderer/geometry/virtual/build/level/vgeo_stitch_frontier.d.ts +37 -0
- package/src/shade/renderer/geometry/virtual/build/level/vgeo_stitch_frontier.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/build/level/vgeo_stitch_frontier.js +243 -0
- package/src/shade/renderer/geometry/virtual/build/level/vgeo_try_repack.d.ts +29 -0
- package/src/shade/renderer/geometry/virtual/build/level/vgeo_try_repack.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/build/level/vgeo_try_repack.js +117 -0
- package/src/shade/renderer/geometry/virtual/build/mesh/VGEO_CREASE_ANGLE.d.ts +46 -0
- package/src/shade/renderer/geometry/virtual/build/mesh/VGEO_CREASE_ANGLE.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/build/mesh/VGEO_DEFAULT_ATTRIBUTE_WEIGHT_RATIOS.d.ts +74 -0
- package/src/shade/renderer/geometry/virtual/build/mesh/VGEO_DEFAULT_ATTRIBUTE_WEIGHT_RATIOS.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/build/mesh/VGEO_DEFAULT_ATTRIBUTE_WEIGHT_RATIOS.js +78 -0
- package/src/shade/renderer/geometry/virtual/build/mesh/VirtualGeometryMesh.d.ts +12 -7
- package/src/shade/renderer/geometry/virtual/build/mesh/VirtualGeometryMesh.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/build/mesh/VirtualGeometryMesh.js +33 -11
- package/src/shade/renderer/geometry/virtual/build/mesh/vgeo_attribute_vector_layout.d.ts +58 -0
- package/src/shade/renderer/geometry/virtual/build/mesh/vgeo_attribute_vector_layout.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/build/mesh/vgeo_attribute_vector_layout.js +79 -0
- package/src/shade/renderer/geometry/virtual/build/mesh/vgeo_attribute_vector_read.d.ts +39 -0
- package/src/shade/renderer/geometry/virtual/build/mesh/vgeo_attribute_vector_read.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/build/mesh/vgeo_attribute_vector_read.js +68 -0
- package/src/shade/renderer/geometry/virtual/build/mesh/vgeo_attribute_vector_write.d.ts +53 -0
- package/src/shade/renderer/geometry/virtual/build/mesh/vgeo_attribute_vector_write.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/build/mesh/vgeo_attribute_vector_write.js +243 -0
- package/src/shade/renderer/geometry/virtual/build/mesh/vgeo_compute_vertex_normals.d.ts +32 -7
- package/src/shade/renderer/geometry/virtual/build/mesh/vgeo_compute_vertex_normals.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/build/mesh/vgeo_compute_vertex_normals.js +243 -24
- package/src/shade/renderer/geometry/virtual/build/mesh/vgeo_compute_vertex_tangents.d.ts +12 -8
- package/src/shade/renderer/geometry/virtual/build/mesh/vgeo_compute_vertex_tangents.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/build/mesh/vgeo_compute_vertex_tangents.js +58 -50
- package/src/shade/renderer/geometry/virtual/build/mesh/vgeo_normalize_source.d.ts +5 -0
- package/src/shade/renderer/geometry/virtual/build/mesh/vgeo_normalize_source.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/build/mesh/vgeo_normalize_source.js +13 -1
- package/src/shade/renderer/geometry/virtual/build/mesh/vgeo_resolve_attribute_weights.d.ts +38 -0
- package/src/shade/renderer/geometry/virtual/build/mesh/vgeo_resolve_attribute_weights.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/build/mesh/vgeo_resolve_attribute_weights.js +81 -0
- package/src/shade/renderer/geometry/virtual/build/partition/VGeoFaceGraph.d.ts +23 -5
- package/src/shade/renderer/geometry/virtual/build/partition/VGeoFaceGraph.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/build/partition/VGeoFaceGraph.js +28 -21
- package/src/shade/renderer/geometry/virtual/build/partition/vgeo_split_face_set.js +1 -1
- package/src/shade/renderer/geometry/virtual/build/partition/vgeo_split_face_set_packed.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/build/partition/vgeo_split_face_set_packed.js +224 -60
- package/src/shade/renderer/geometry/virtual/build/submesh/VGeoGatheredFaces.d.ts +15 -0
- package/src/shade/renderer/geometry/virtual/build/submesh/VGeoGatheredFaces.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/build/submesh/VGeoGatheredFaces.js +17 -0
- package/src/shade/renderer/geometry/virtual/build/submesh/VGeoSubmesh.d.ts +17 -0
- package/src/shade/renderer/geometry/virtual/build/submesh/VGeoSubmesh.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/build/submesh/VGeoSubmesh.js +19 -0
- package/src/shade/renderer/geometry/virtual/build/submesh/vgeo_extract_submesh.d.ts +10 -1
- package/src/shade/renderer/geometry/virtual/build/submesh/vgeo_extract_submesh.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/build/submesh/vgeo_extract_submesh.js +103 -9
- package/src/shade/renderer/geometry/virtual/build/submesh/vgeo_gather_faces.d.ts +14 -2
- package/src/shade/renderer/geometry/virtual/build/submesh/vgeo_gather_faces.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/build/submesh/vgeo_gather_faces.js +38 -14
- package/src/shade/renderer/geometry/virtual/build/vgeo_build.d.ts +6 -2
- package/src/shade/renderer/geometry/virtual/build/vgeo_build.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/build/vgeo_build.js +27 -4
- package/src/shade/renderer/geometry/virtual/build/vgeo_subtree_bounds.d.ts +32 -0
- package/src/shade/renderer/geometry/virtual/build/vgeo_subtree_bounds.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/build/vgeo_subtree_bounds.js +93 -0
- package/src/shade/renderer/geometry/virtual/format/VGeoWriteOptions.d.ts +20 -6
- package/src/shade/renderer/geometry/virtual/format/VGeoWriteOptions.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/format/VGeoWriteOptions.js +22 -8
- package/src/shade/renderer/geometry/virtual/format/attribute/vgeo_layers_copy_corner.d.ts +16 -0
- package/src/shade/renderer/geometry/virtual/format/attribute/vgeo_layers_copy_corner.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/format/attribute/{vgeo_layers_copy_vertex.js → vgeo_layers_copy_corner.js} +8 -8
- package/src/shade/renderer/geometry/virtual/format/attribute/vgeo_layers_declare.d.ts +18 -7
- package/src/shade/renderer/geometry/virtual/format/attribute/vgeo_layers_declare.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/format/attribute/vgeo_layers_declare.js +19 -8
- package/src/shade/renderer/geometry/virtual/format/attribute/vgeo_layers_like.d.ts +3 -3
- package/src/shade/renderer/geometry/virtual/format/attribute/vgeo_layers_like.js +3 -3
- package/src/shade/renderer/geometry/virtual/format/attribute/vgeo_mesh_wedges.d.ts +27 -0
- package/src/shade/renderer/geometry/virtual/format/attribute/vgeo_mesh_wedges.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/format/attribute/vgeo_mesh_wedges.js +97 -0
- package/src/shade/renderer/geometry/virtual/format/cluster/VGEO_CLUSTER_OFFSET.d.ts +7 -3
- package/src/shade/renderer/geometry/virtual/format/cluster/VGEO_CLUSTER_OFFSET.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/format/cluster/VGEO_CLUSTER_OFFSET.js +21 -11
- package/src/shade/renderer/geometry/virtual/format/cluster/VGEO_CLUSTER_OFFSET_V4.d.ts +21 -0
- package/src/shade/renderer/geometry/virtual/format/cluster/VGEO_CLUSTER_OFFSET_V4.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/format/cluster/VGEO_CLUSTER_OFFSET_V4.js +41 -0
- package/src/shade/renderer/geometry/virtual/format/dag/VirtualGeometryCluster.d.ts +19 -1
- package/src/shade/renderer/geometry/virtual/format/dag/VirtualGeometryCluster.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/format/dag/VirtualGeometryCluster.js +20 -1
- package/src/shade/renderer/geometry/virtual/format/frame/VGEO_CODEC_DEFLATE.d.ts +25 -0
- package/src/shade/renderer/geometry/virtual/format/frame/VGEO_CODEC_DEFLATE.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/format/frame/VGEO_CODEC_DEFLATE.js +24 -0
- package/src/shade/renderer/geometry/virtual/format/frame/VGEO_CODEC_LZ4.d.ts +5 -0
- package/src/shade/renderer/geometry/virtual/format/frame/VGEO_CODEC_LZ4.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/format/frame/VGEO_CODEC_LZ4.js +5 -0
- package/src/shade/renderer/geometry/virtual/format/frame/vgeo_codec_name.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/format/frame/vgeo_codec_name.js +5 -0
- package/src/shade/renderer/geometry/virtual/format/frame/vgeo_decode_frame_blob.d.ts +10 -3
- package/src/shade/renderer/geometry/virtual/format/frame/vgeo_decode_frame_blob.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/format/frame/vgeo_decode_frame_blob.js +37 -3
- package/src/shade/renderer/geometry/virtual/format/frame/vgeo_encode_frame_blob.d.ts +21 -13
- package/src/shade/renderer/geometry/virtual/format/frame/vgeo_encode_frame_blob.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/format/frame/vgeo_encode_frame_blob.js +37 -13
- package/src/shade/renderer/geometry/virtual/format/frame/vgeo_frame_decode_bound.d.ts +8 -2
- package/src/shade/renderer/geometry/virtual/format/frame/vgeo_frame_decode_bound.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/format/frame/vgeo_frame_decode_bound.js +14 -2
- package/src/shade/renderer/geometry/virtual/format/header/VGEO_FORMAT_VERSION.d.ts +18 -0
- package/src/shade/renderer/geometry/virtual/format/header/VGEO_FORMAT_VERSION.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/format/header/VGEO_FORMAT_VERSION.js +19 -1
- package/src/shade/renderer/geometry/virtual/format/header/VGEO_HEADER_OFFSET.js +6 -0
- package/src/shade/renderer/geometry/virtual/format/header/VGEO_MAX_LEVEL_COUNT.d.ts +21 -0
- package/src/shade/renderer/geometry/virtual/format/header/VGEO_MAX_LEVEL_COUNT.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/format/header/VGEO_MAX_LEVEL_COUNT.js +20 -0
- package/src/shade/renderer/geometry/virtual/format/header/VGEO_MIN_READER_VERSION.d.ts +9 -0
- package/src/shade/renderer/geometry/virtual/format/header/VGEO_MIN_READER_VERSION.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/format/header/VGEO_MIN_READER_VERSION.js +10 -1
- package/src/shade/renderer/geometry/virtual/format/page/VGEO_PAGE_OFFSET.d.ts +1 -2
- package/src/shade/renderer/geometry/virtual/format/page/VGEO_PAGE_OFFSET.js +10 -3
- package/src/shade/renderer/geometry/virtual/format/page/VGEO_PAGE_OFFSET_V5.d.ts +20 -0
- package/src/shade/renderer/geometry/virtual/format/page/VGEO_PAGE_OFFSET_V5.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/format/page/VGEO_PAGE_OFFSET_V5.js +43 -0
- package/src/shade/renderer/geometry/virtual/format/payload/vgeo_encode_cluster_payload.d.ts +4 -2
- 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 +8 -4
- package/src/shade/renderer/geometry/virtual/format/read/VGeoContainerReader.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/format/read/VGeoContainerReader.js +2 -2
- package/src/shade/renderer/geometry/virtual/format/read/VGeoDirectory.d.ts +6 -4
- package/src/shade/renderer/geometry/virtual/format/read/VGeoDirectory.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/format/read/VGeoDirectory.js +6 -4
- package/src/shade/renderer/geometry/virtual/format/read/VGeoPage.d.ts +13 -2
- package/src/shade/renderer/geometry/virtual/format/read/VGeoPage.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/format/read/VGeoPage.js +13 -2
- package/src/shade/renderer/geometry/virtual/format/read/VGeoPageFetcherCore.d.ts +5 -1
- package/src/shade/renderer/geometry/virtual/format/read/VGeoPageFetcherCore.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/format/read/VGeoPageFetcherCore.js +14 -1
- package/src/shade/renderer/geometry/virtual/format/read/vgeo_page_worker_serve.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/format/read/vgeo_page_worker_serve.js +1 -0
- package/src/shade/renderer/geometry/virtual/format/read/vgeo_read_directory.d.ts +4 -1
- package/src/shade/renderer/geometry/virtual/format/read/vgeo_read_directory.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/format/read/vgeo_read_directory.js +53 -11
- package/src/shade/renderer/geometry/virtual/format/read/vgeo_read_header.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/format/read/vgeo_read_header.js +7 -3
- package/src/shade/renderer/geometry/virtual/format/read/vgeo_read_page.d.ts +12 -2
- 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 +111 -33
- package/src/shade/renderer/geometry/virtual/format/vgeo_validate_container.d.ts +6 -2
- 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 +112 -42
- package/src/shade/renderer/geometry/virtual/format/vgeo_write_container.d.ts +2 -2
- 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 +51 -21
- package/src/shade/renderer/geometry/virtual/runtime/CULL_PLAN_2026_09_16.md +385 -0
- package/src/shade/renderer/geometry/virtual/runtime/VGeoPageFetcher.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/runtime/VGeoPageFetcher.js +9 -0
- package/src/shade/renderer/geometry/virtual/runtime/VGeoResidency.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/runtime/VGeoResidency.js +14 -1
- 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 +34 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/chunk_vgeo_cut_arena.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/runtime/cut/chunk_vgeo_cut_arena.js +18 -0
- 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 +47 -19
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_CLUSTER_ROW_OFFSET.d.ts +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_CLUSTER_ROW_OFFSET.js +22 -4
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_CLUSTER_ROW_WORDS.d.ts +7 -2
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_CLUSTER_ROW_WORDS.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_CLUSTER_ROW_WORDS.js +8 -3
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_CUT_STAT.d.ts +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_CUT_STAT.js +10 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_CUT_STAT_COUNT.js +1 -1
- package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_page_slot_words.d.ts +13 -11
- package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_page_slot_words.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_page_slot_words.js +13 -11
- package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_write_page_block.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_write_page_block.js +4 -0
- package/src/shade/renderer/global_illumination/probe/octahedral/GPUProbeAtlas.d.ts.map +1 -1
- package/src/shade/renderer/global_illumination/probe/octahedral/GPUProbeAtlas.js +12 -3
- package/src/shade/renderer/global_illumination/sharc/shader/shader_sharc_evict.d.ts.map +1 -1
- package/src/shade/renderer/global_illumination/sharc/shader/shader_sharc_evict.js +3 -4
- package/src/shade/renderer/gpu_primitive/indirect/IndirectComputeCommandBuildPass.d.ts +6 -2
- package/src/shade/renderer/gpu_primitive/indirect/IndirectComputeCommandBuildPass.d.ts.map +1 -1
- package/src/shade/renderer/gpu_primitive/indirect/IndirectComputeCommandBuildPass.js +90 -12
- package/src/shade/renderer/gpu_primitive/indirect/WEBGPU_GUARANTEED_MAX_COMPUTE_WORKGROUPS_PER_DIMENSION.d.ts +22 -0
- package/src/shade/renderer/gpu_primitive/indirect/WEBGPU_GUARANTEED_MAX_COMPUTE_WORKGROUPS_PER_DIMENSION.d.ts.map +1 -0
- package/src/shade/renderer/gpu_primitive/indirect/WEBGPU_GUARANTEED_MAX_COMPUTE_WORKGROUPS_PER_DIMENSION.js +21 -0
- package/src/shade/renderer/light/environment/rgbe/RGBE_ReadPixels_RLE.d.ts.map +1 -1
- package/src/shade/renderer/light/environment/rgbe/RGBE_ReadPixels_RLE.js +7 -1
- package/src/shade/renderer/lightmap/GPULightMap.d.ts.map +1 -1
- package/src/shade/renderer/lightmap/GPULightMap.js +349 -336
- package/src/shade/renderer/loader/gltf/load_gltf.d.ts.map +1 -1
- package/src/shade/renderer/loader/gltf/load_gltf.js +1137 -1128
- package/src/shade/renderer/particles/DESIGN.md +14 -5
- package/src/shade/renderer/particles/GPUParticleSystem.d.ts +25 -10
- package/src/shade/renderer/particles/GPUParticleSystem.d.ts.map +1 -1
- package/src/shade/renderer/particles/GPUParticleSystem.js +182 -38
- package/src/shade/renderer/particles/graph/groups/simplex_reference.d.ts.map +1 -1
- package/src/shade/renderer/particles/graph/groups/simplex_reference.js +16 -10
- package/src/shade/renderer/particles/graph_particles.d.ts +4 -2
- package/src/shade/renderer/particles/graph_particles.d.ts.map +1 -1
- package/src/shade/renderer/particles/graph_particles.js +8 -2
- package/src/shade/renderer/particles/optimizer/particle_vm_semantics.d.ts.map +1 -1
- package/src/shade/renderer/particles/optimizer/particle_vm_semantics.js +8 -2
- package/src/shade/renderer/particles/optimizer/pass_fold_constants.d.ts.map +1 -1
- package/src/shade/renderer/particles/optimizer/pass_fold_constants.js +10 -4
- package/src/shade/renderer/particles/shaders/shader_particle_emit.d.ts.map +1 -1
- package/src/shade/renderer/particles/shaders/shader_particle_emit.js +13 -8
- package/src/shade/renderer/particles/shaders/shader_particle_init_free_list.d.ts +31 -0
- package/src/shade/renderer/particles/shaders/shader_particle_init_free_list.d.ts.map +1 -0
- package/src/shade/renderer/particles/shaders/shader_particle_init_free_list.js +62 -0
- package/src/shade/renderer/particles/shaders/shader_particle_simulate.d.ts.map +1 -1
- package/src/shade/renderer/particles/shaders/shader_particle_simulate.js +3 -6
- package/src/shade/renderer/particles/vm/ParticleVMReference.d.ts +13 -0
- package/src/shade/renderer/particles/vm/ParticleVMReference.d.ts.map +1 -1
- package/src/shade/renderer/particles/vm/ParticleVMReference.js +95 -17
- package/src/shade/renderer/rasterize/bucket/prepare_meshlet_draw_commands_by_material.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/bucket/prepare_meshlet_draw_commands_by_material.js +4 -12
- package/src/shade/renderer/rasterize/bucket/rasterization_has_opaque_bucket.d.ts +21 -0
- package/src/shade/renderer/rasterize/bucket/rasterization_has_opaque_bucket.d.ts.map +1 -0
- package/src/shade/renderer/rasterize/bucket/rasterization_has_opaque_bucket.js +28 -0
- package/src/shade/renderer/rasterize/bucket/shader_read_out_bucket.d.ts +0 -2
- package/src/shade/renderer/rasterize/bucket/shader_read_out_bucket.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/bucket/shader_read_out_bucket.js +4 -5
- package/src/shade/renderer/rasterize/cull/hzb/shader_hzb_mesh_filter_2way.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/cull/hzb/shader_hzb_mesh_filter_2way.js +5 -3
- package/src/shade/renderer/rasterize/cull/hzb/shader_hzb_meshlet_filter_1way.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/cull/hzb/shader_hzb_meshlet_filter_1way.js +11 -4
- package/src/shade/renderer/rasterize/cull/hzb/shader_hzb_meshlet_filter_1way_full.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/cull/hzb/shader_hzb_meshlet_filter_1way_full.js +4 -3
- package/src/shade/renderer/rasterize/expand/bucket/meshlet/graph_sort_meshlets_by_bucket_id.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/expand/bucket/meshlet/graph_sort_meshlets_by_bucket_id.js +3 -0
- package/src/shade/renderer/rasterize/expand/bucket/meshlet/shader_meshlets_count_by_rasterization_bucket.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/expand/bucket/meshlet/shader_meshlets_count_by_rasterization_bucket.js +94 -90
- package/src/shade/renderer/rasterize/expand/bucket/meshlet/shader_meshlets_extract_bucket.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/expand/bucket/meshlet/shader_meshlets_extract_bucket.js +13 -5
- package/src/shade/renderer/rasterize/expand/bucket/meshlet/shader_meshlets_rasterization_bucket_sort.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/expand/bucket/meshlet/shader_meshlets_rasterization_bucket_sort.js +96 -89
- package/src/shade/renderer/rasterize/expand/graph_meshlets_filter_one_way.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/expand/graph_meshlets_filter_one_way.js +3 -1
- package/src/shade/renderer/rasterize/expand/graph_meshlets_filter_two_way.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/expand/graph_meshlets_filter_two_way.js +4 -1
- package/src/shade/renderer/rasterize/expand/mesh/shader_mesh_expand_to_meshlets_prefix.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/expand/mesh/shader_mesh_expand_to_meshlets_prefix.js +100 -89
- package/src/shade/renderer/rasterize/expand/mesh/shader_prefix_sum_to_command.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/expand/mesh/shader_prefix_sum_to_command.js +31 -7
- package/src/shade/renderer/rasterize/expand/pass0/pass_mesh_cull.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/expand/pass0/pass_mesh_cull.js +1 -7
- package/src/shade/renderer/rasterize/expand/pass1/filter_meshlets.2-way.compute.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/expand/pass1/filter_meshlets.2-way.compute.js +161 -153
- package/src/shade/renderer/rasterize/fast/graph_extract_meshes_from_bucket.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/fast/graph_extract_meshes_from_bucket.js +2 -7
- 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 +19 -2
- 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 +4 -8
- 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 +5 -8
- package/src/shade/renderer/rasterize/standard/graph_rasterize_scene.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/standard/graph_rasterize_scene.js +19 -2
- package/src/shade/renderer/shader/type/WebGPUStruct.d.ts.map +1 -1
- package/src/shade/renderer/shader/type/WebGPUStruct.js +12 -0
- package/src/shade/renderer/shadow/map/DEFAULT_SHADOWMAP_UPDATE_BUDGET.d.ts +25 -0
- package/src/shade/renderer/shadow/map/DEFAULT_SHADOWMAP_UPDATE_BUDGET.d.ts.map +1 -0
- package/src/shade/renderer/shadow/map/DEFAULT_SHADOWMAP_UPDATE_BUDGET.js +24 -0
- package/src/shade/renderer/shadow/map/GPUSceneShadowmapContext.d.ts +10 -0
- package/src/shade/renderer/shadow/map/GPUSceneShadowmapContext.d.ts.map +1 -1
- package/src/shade/renderer/shadow/map/GPUSceneShadowmapContext.js +30 -21
- package/src/shade/renderer/texture/mipmaps/WebGPUMipmapGenerator.d.ts +8 -2
- package/src/shade/renderer/texture/mipmaps/WebGPUMipmapGenerator.d.ts.map +1 -1
- package/src/shade/renderer/texture/mipmaps/WebGPUMipmapGenerator.js +41 -6
- package/src/shade/wgsl/emulator/WGSLJavaScriptCompiler.js +2383 -2356
- package/src/view/tooltip/DomTooltipManager.d.ts +7 -0
- package/src/view/tooltip/DomTooltipManager.d.ts.map +1 -1
- package/src/view/tooltip/DomTooltipManager.js +22 -0
- package/src/view/tooltip/DomTooltipObserver.d.ts.map +1 -1
- package/src/view/tooltip/DomTooltipObserver.js +13 -3
- package/src/core/model/node-graph/visual/layout/LayoutBoxGraph.d.ts.map +0 -1
- package/src/core/model/node-graph/visual/layout/layout_assign_coordinates.d.ts.map +0 -1
- package/src/core/model/node-graph/visual/layout/layout_assign_layers.d.ts.map +0 -1
- package/src/core/model/node-graph/visual/layout/layout_break_cycles.d.ts.map +0 -1
- package/src/core/model/node-graph/visual/layout/layout_insert_lanes.d.ts.map +0 -1
- package/src/core/model/node-graph/visual/layout/layout_order_layers.d.ts.map +0 -1
- package/src/core/model/node-graph/visual/layout/layout_orient_links.d.ts.map +0 -1
- package/src/shade/playground/vgeo_viewer/cut_geometry.d.ts +0 -61
- package/src/shade/playground/vgeo_viewer/cut_geometry.d.ts.map +0 -1
- package/src/shade/playground/vgeo_viewer/main.d.ts.map +0 -1
- package/src/shade/playground/vgeo_viewer/sample_asset.d.ts +0 -18
- package/src/shade/playground/vgeo_viewer/sample_asset.d.ts.map +0 -1
- package/src/shade/playground/vgeo_viewer/select_cut.d.ts +0 -173
- package/src/shade/playground/vgeo_viewer/select_cut.d.ts.map +0 -1
- package/src/shade/renderer/geometry/virtual/build/VGeoFaceGraph.d.ts +0 -52
- package/src/shade/renderer/geometry/virtual/build/VGeoFaceGraph.d.ts.map +0 -1
- package/src/shade/renderer/geometry/virtual/build/VGeoGatheredFaces.d.ts +0 -24
- package/src/shade/renderer/geometry/virtual/build/VGeoGatheredFaces.d.ts.map +0 -1
- package/src/shade/renderer/geometry/virtual/build/VGeoLevel.d.ts +0 -32
- package/src/shade/renderer/geometry/virtual/build/VGeoLevel.d.ts.map +0 -1
- package/src/shade/renderer/geometry/virtual/build/VGeoLevelOptions.d.ts +0 -59
- package/src/shade/renderer/geometry/virtual/build/VGeoLevelOptions.d.ts.map +0 -1
- package/src/shade/renderer/geometry/virtual/build/VGeoLevelSummary.d.ts +0 -44
- package/src/shade/renderer/geometry/virtual/build/VGeoLevelSummary.d.ts.map +0 -1
- package/src/shade/renderer/geometry/virtual/build/VGeoSubmesh.d.ts +0 -20
- package/src/shade/renderer/geometry/virtual/build/VGeoSubmesh.d.ts.map +0 -1
- package/src/shade/renderer/geometry/virtual/build/VirtualGeometryMesh.d.ts +0 -73
- package/src/shade/renderer/geometry/virtual/build/VirtualGeometryMesh.d.ts.map +0 -1
- package/src/shade/renderer/geometry/virtual/build/vgeo_assemble_pages.d.ts +0 -13
- package/src/shade/renderer/geometry/virtual/build/vgeo_assemble_pages.d.ts.map +0 -1
- package/src/shade/renderer/geometry/virtual/build/vgeo_bounding_sphere.d.ts +0 -12
- package/src/shade/renderer/geometry/virtual/build/vgeo_bounding_sphere.d.ts.map +0 -1
- package/src/shade/renderer/geometry/virtual/build/vgeo_build_levels.d.ts +0 -36
- package/src/shade/renderer/geometry/virtual/build/vgeo_build_levels.d.ts.map +0 -1
- package/src/shade/renderer/geometry/virtual/build/vgeo_build_page_nodes.d.ts +0 -13
- package/src/shade/renderer/geometry/virtual/build/vgeo_build_page_nodes.d.ts.map +0 -1
- package/src/shade/renderer/geometry/virtual/build/vgeo_build_root_group.d.ts +0 -19
- package/src/shade/renderer/geometry/virtual/build/vgeo_build_root_group.d.ts.map +0 -1
- package/src/shade/renderer/geometry/virtual/build/vgeo_compute_vertex_normals.d.ts +0 -29
- package/src/shade/renderer/geometry/virtual/build/vgeo_compute_vertex_normals.d.ts.map +0 -1
- package/src/shade/renderer/geometry/virtual/build/vgeo_compute_vertex_tangents.d.ts +0 -31
- package/src/shade/renderer/geometry/virtual/build/vgeo_compute_vertex_tangents.d.ts.map +0 -1
- package/src/shade/renderer/geometry/virtual/build/vgeo_containing_sphere.d.ts +0 -18
- package/src/shade/renderer/geometry/virtual/build/vgeo_containing_sphere.d.ts.map +0 -1
- package/src/shade/renderer/geometry/virtual/build/vgeo_extract_submesh.d.ts +0 -14
- package/src/shade/renderer/geometry/virtual/build/vgeo_extract_submesh.d.ts.map +0 -1
- package/src/shade/renderer/geometry/virtual/build/vgeo_gather_faces.d.ts +0 -12
- package/src/shade/renderer/geometry/virtual/build/vgeo_gather_faces.d.ts.map +0 -1
- package/src/shade/renderer/geometry/virtual/build/vgeo_graph_components.d.ts +0 -14
- package/src/shade/renderer/geometry/virtual/build/vgeo_graph_components.d.ts.map +0 -1
- package/src/shade/renderer/geometry/virtual/build/vgeo_narrow_indices.d.ts +0 -11
- package/src/shade/renderer/geometry/virtual/build/vgeo_narrow_indices.d.ts.map +0 -1
- package/src/shade/renderer/geometry/virtual/build/vgeo_normalize_source.d.ts +0 -46
- package/src/shade/renderer/geometry/virtual/build/vgeo_normalize_source.d.ts.map +0 -1
- package/src/shade/renderer/geometry/virtual/build/vgeo_page_node_count.d.ts +0 -22
- package/src/shade/renderer/geometry/virtual/build/vgeo_page_node_count.d.ts.map +0 -1
- package/src/shade/renderer/geometry/virtual/build/vgeo_partition_graph.d.ts +0 -29
- package/src/shade/renderer/geometry/virtual/build/vgeo_partition_graph.d.ts.map +0 -1
- package/src/shade/renderer/geometry/virtual/build/vgeo_sphere_inflate_to_contain.d.ts +0 -9
- package/src/shade/renderer/geometry/virtual/build/vgeo_sphere_inflate_to_contain.d.ts.map +0 -1
- package/src/shade/renderer/geometry/virtual/build/vgeo_split_face_set.d.ts +0 -10
- package/src/shade/renderer/geometry/virtual/build/vgeo_split_face_set.d.ts.map +0 -1
- package/src/shade/renderer/geometry/virtual/build/vgeo_split_face_set_packed.d.ts +0 -15
- package/src/shade/renderer/geometry/virtual/build/vgeo_split_face_set_packed.d.ts.map +0 -1
- package/src/shade/renderer/geometry/virtual/format/attribute/vgeo_layers_copy_vertex.d.ts +0 -16
- package/src/shade/renderer/geometry/virtual/format/attribute/vgeo_layers_copy_vertex.d.ts.map +0 -1
- package/src/shade/renderer/loader/gltf/fix_up_material_sides.d.ts +0 -10
- package/src/shade/renderer/loader/gltf/fix_up_material_sides.d.ts.map +0 -1
- package/src/shade/renderer/rasterize/expand/graph_triangles_filter_by_material_side.d.ts +0 -17
- package/src/shade/renderer/rasterize/expand/graph_triangles_filter_by_material_side.d.ts.map +0 -1
- package/src/shade/renderer/rasterize/expand/shader_filter_triangle_by_material_side.d.ts +0 -3
- package/src/shade/renderer/rasterize/expand/shader_filter_triangle_by_material_side.d.ts.map +0 -1
- package/src/shade/renderer/rasterize/forward/chunk_material_side_should_draw.d.ts +0 -8
- package/src/shade/renderer/rasterize/forward/chunk_material_side_should_draw.d.ts.map +0 -1
- /package/src/core/{model/node-graph/visual/layout → graph/layout/layered}/layout_assign_coordinates.d.ts +0 -0
- /package/src/core/{model/node-graph/visual/layout → graph/layout/layered}/layout_assign_layers.d.ts +0 -0
- /package/src/core/{model/node-graph/visual/layout → graph/layout/layered}/layout_break_cycles.d.ts +0 -0
- /package/src/core/{model/node-graph/visual/layout → graph/layout/layered}/layout_break_cycles.js +0 -0
- /package/src/core/{model/node-graph/visual/layout → graph/layout/layered}/layout_insert_lanes.d.ts +0 -0
- /package/src/core/{model/node-graph/visual/layout → graph/layout/layered}/layout_insert_lanes.js +0 -0
- /package/src/core/{model/node-graph/visual/layout → graph/layout/layered}/layout_order_layers.d.ts +0 -0
- /package/src/core/{model/node-graph/visual/layout → graph/layout/layered}/layout_order_layers.js +0 -0
- /package/src/core/{model/node-graph/visual/layout → graph/layout/layered}/layout_orient_links.d.ts +0 -0
- /package/src/core/{model/node-graph/visual/layout → graph/layout/layered}/layout_orient_links.js +0 -0
- /package/src/shade/playground/{vgeo_viewer → caldera}/main.d.ts +0 -0
|
@@ -1,1573 +1,68 @@
|
|
|
1
1
|
import { assert } from "../../../../../../core/assert.js";
|
|
2
|
-
import { aabb3_from_v3_array } from "../../../../../../core/geom/3d/aabb/aabb3_from_v3_array.js";
|
|
3
|
-
import { v3_morton_encode_bounded } from "../../../../../../core/geom/3d/morton/v3_morton_encode_bounded.js";
|
|
4
2
|
import { Quadric3 } from "../../../../../../core/geom/3d/quadric/Quadric3.js";
|
|
5
3
|
import {
|
|
6
|
-
|
|
7
|
-
} from "../../../../../../core/geom/3d/
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
|
|
11
|
-
} from "
|
|
12
|
-
import {
|
|
13
|
-
|
|
14
|
-
} from "
|
|
15
|
-
import {
|
|
16
|
-
bt_mesh_simplify_with_quadrics
|
|
17
|
-
} from "../../../../../../core/geom/3d/topology/struct/binary/io/bt_mesh_simplify.js";
|
|
18
|
-
import { bt_edge_get } from "../../../../../../core/geom/3d/topology/struct/binary/query/bt_edge_get.js";
|
|
19
|
-
import { bt_mesh_validate } from "../../../../../../core/geom/3d/topology/struct/binary/query/bt_mesh_validate.js";
|
|
20
|
-
import { bt_mesh_build_face_graph } from "../../../../../../core/graph/metis/bt_mesh_build_face_graph.js";
|
|
21
|
-
import { vgeo_encode_cluster_payload } from "../../format/payload/vgeo_encode_cluster_payload.js";
|
|
22
|
-
import { vgeo_layers_copy_vertex } from "../../format/attribute/vgeo_layers_copy_vertex.js";
|
|
23
|
-
import { vgeo_layers_like } from "../../format/attribute/vgeo_layers_like.js";
|
|
24
|
-
import { VGEO_MAX_CLUSTER_TRIANGLE_COUNT } from "../../format/cluster/VGEO_MAX_CLUSTER_TRIANGLE_COUNT.js";
|
|
25
|
-
import { VGEO_MAX_CLUSTER_VERTEX_COUNT } from "../../format/cluster/VGEO_MAX_CLUSTER_VERTEX_COUNT.js";
|
|
26
|
-
import { VGEO_MAX_GROUP_CLUSTER_COUNT } from "../../format/page/VGEO_MAX_GROUP_CLUSTER_COUNT.js";
|
|
27
|
-
import { VirtualGeometryCluster } from "../../format/dag/VirtualGeometryCluster.js";
|
|
28
|
-
import { VirtualGeometryGroup } from "../../format/dag/VirtualGeometryGroup.js";
|
|
29
|
-
import { vgeo_bounding_sphere } from "../sphere/vgeo_bounding_sphere.js";
|
|
30
|
-
import { vgeo_compute_vertex_normals } from "../mesh/vgeo_compute_vertex_normals.js";
|
|
31
|
-
import { vgeo_compute_vertex_tangents } from "../mesh/vgeo_compute_vertex_tangents.js";
|
|
32
|
-
import { vgeo_containing_sphere } from "../sphere/vgeo_containing_sphere.js";
|
|
33
|
-
import { vgeo_extract_submesh } from "../submesh/vgeo_extract_submesh.js";
|
|
34
|
-
import { vgeo_gather_faces } from "../submesh/vgeo_gather_faces.js";
|
|
35
|
-
import { vgeo_graph_components } from "../partition/vgeo_graph_components.js";
|
|
36
|
-
import { vgeo_narrow_indices } from "../submesh/vgeo_narrow_indices.js";
|
|
37
|
-
import { vgeo_partition_subgraph } from "../partition/vgeo_partition_subgraph.js";
|
|
38
|
-
import { vgeo_split_face_set } from "../partition/vgeo_split_face_set.js";
|
|
39
|
-
import { vgeo_split_face_set_packed } from "../partition/vgeo_split_face_set_packed.js";
|
|
40
|
-
import { VGeoFaceGraph } from "../partition/VGeoFaceGraph.js";
|
|
41
|
-
import { VGeoLevel } from "./VGeoLevel.js";
|
|
42
|
-
import { VirtualGeometryMesh } from "../mesh/VirtualGeometryMesh.js";
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Build the cluster DAG: leaves out of the source mesh, then merge steps until one cluster is left.
|
|
46
|
-
*
|
|
47
|
-
* The unit of work is a **round**, not a level sweep. A round holds a *frontier* — every cluster that
|
|
48
|
-
* nothing has merged yet — selects a band of it by error, groups the band, locks every vertex the
|
|
49
|
-
* group shares with anything outside it, halves the group's interior, and re-cuts the result into
|
|
50
|
-
* new clusters, which become the parents of every cluster in the group. Locked perimeters plus a
|
|
51
|
-
* group-uniform metric is the whole reason independent per-cluster LOD decisions come out
|
|
52
|
-
* crack-free.
|
|
53
|
-
*
|
|
54
|
-
* **The band is why this is not a level sweep.** Sweeping whole levels pushes every cluster up one
|
|
55
|
-
* step per level whatever its error, so a region that is nearly exact is coarsened in lockstep with
|
|
56
|
-
* one that is not, and a level ends up spanning an order of magnitude of error. The cut the runtime
|
|
57
|
-
* takes is a threshold on error, so a level that spans an order of magnitude is a page band that
|
|
58
|
-
* spans one too: installing it brings clusters the cut does not want, and the ones it does want are
|
|
59
|
-
* scattered across bands. Selecting the lowest-error band instead lets a fine region catch up before
|
|
60
|
-
* the coarse one moves, and bands come out error-uniform. On geometry whose error is already uniform
|
|
61
|
-
* the band is the whole frontier and this degenerates to the sweep, which is the correct behaviour
|
|
62
|
-
* for that case rather than a special case for it.
|
|
63
|
-
*
|
|
64
|
-
* **A cluster the round cannot use simply stays.** It is not pushed up for the sake of symmetry, and
|
|
65
|
-
* it does not stall the round for anyone else — which is what lets a lone island wait, untouched and
|
|
66
|
-
* at zero error, until the geometry it will eventually merge with has come down to meet it.
|
|
67
|
-
*
|
|
68
|
-
* **Error is conformance to the source, not a sum of steps.** Vertex quadrics are built once, from
|
|
69
|
-
* the source triangles, and carried through every round: a survivor absorbs its victim's quadric at
|
|
70
|
-
* every collapse, so a vertex twelve rounds up still holds the planes of every source triangle
|
|
71
|
-
* folded into it. Evaluating that at its current position measures the deviation from what the
|
|
72
|
-
* artist authored, directly, and normalizing by the quadric's accumulated weight makes it an
|
|
73
|
-
* object-space length rather than a cost whose scale depends on how many planes it happens to hold.
|
|
74
|
-
* That length is what the runtime projects into pixels.
|
|
75
|
-
*
|
|
76
|
-
* **Termination is convergence to one root, not a stall.** Groups bundle across connected components
|
|
77
|
-
* once connectivity runs out, and a group small enough to fit one cluster is merged losslessly, so a
|
|
78
|
-
* frontier of separate islands still comes together. A build that cannot reach a single root throws:
|
|
79
|
-
* the runtime starts its cut at the root, and "several roots" is not a shape it can start from.
|
|
80
|
-
*
|
|
81
|
-
* @author Alex Goldring
|
|
82
|
-
* @copyright Company Named Limited (c) 2026
|
|
83
|
-
*/
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* Fixed so that the same frontier always groups the same way.
|
|
87
|
-
* @readonly
|
|
88
|
-
* @type {number}
|
|
89
|
-
*/
|
|
90
|
-
const GROUP_SEED = 0x6D05;
|
|
91
|
-
|
|
92
|
-
const scratch_sphere = new Float32Array(4);
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* Every cluster nothing has merged yet, and the geometry they currently stand for.
|
|
96
|
-
*
|
|
97
|
-
* One mesh holds the whole frontier so that a group can be extracted with its surroundings intact —
|
|
98
|
-
* the lock set is "every vertex a face outside this group touches", and that question has no answer
|
|
99
|
-
* in a mesh that holds the group alone.
|
|
100
|
-
*/
|
|
101
|
-
class VGeoFrontier {
|
|
102
|
-
/**
|
|
103
|
-
* @type {VirtualGeometryMesh}
|
|
104
|
-
*/
|
|
105
|
-
mesh = null;
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* @type {VirtualGeometryCluster[]}
|
|
109
|
-
*/
|
|
110
|
-
clusters = [];
|
|
111
|
-
|
|
112
|
-
/**
|
|
113
|
-
* `cluster_faces[i]` are the faces of {@link mesh} that cluster `i` owns.
|
|
114
|
-
* @type {number[][]}
|
|
115
|
-
*/
|
|
116
|
-
cluster_faces = [];
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
* One accumulated quadric per vertex of {@link mesh}, carried unbroken from the source mesh.
|
|
120
|
-
*
|
|
121
|
-
* This is what makes the error mean "how far this is from the geometry the artist authored"
|
|
122
|
-
* rather than "how far this is from the frontier it was simplified out of". A survivor absorbs
|
|
123
|
-
* its victim's quadric at every collapse, at every round, so a vertex twelve rounds up still
|
|
124
|
-
* carries the planes of every source triangle that was ever folded into it, and evaluating its
|
|
125
|
-
* quadric at its current position measures the deviation from the source directly. Composing
|
|
126
|
-
* per-round residuals instead only *bounds* that, by the triangle inequality, and the bound
|
|
127
|
-
* loosens with every round: measured on a unit sphere, the same ninety-triangle root read 0.36
|
|
128
|
-
* built from 3k source triangles and 13.3 built from 24k, where it is the same approximation and
|
|
129
|
-
* reads 0.0315 either way once the quadrics are carried.
|
|
130
|
-
*
|
|
131
|
-
* @type {Quadric3[]}
|
|
132
|
-
*/
|
|
133
|
-
quadrics = [];
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
/**
|
|
137
|
-
* Everything one group step produced, before it is committed. Kept separate so that a round which
|
|
138
|
-
* fails to make progress can be discarded without having mutated the frontier.
|
|
139
|
-
*/
|
|
140
|
-
class GroupStep {
|
|
141
|
-
/**
|
|
142
|
-
* The frontier clusters this group holds.
|
|
143
|
-
* @type {VirtualGeometryCluster[]}
|
|
144
|
-
*/
|
|
145
|
-
child_clusters = [];
|
|
146
|
-
|
|
147
|
-
/**
|
|
148
|
-
* @type {VirtualGeometryCluster[]}
|
|
149
|
-
*/
|
|
150
|
-
parent_clusters = [];
|
|
151
|
-
|
|
152
|
-
/**
|
|
153
|
-
* Parallel to {@link parent_clusters}: the geometry each parent was cut from, in terms of the
|
|
154
|
-
* frontier, so the next frontier can be stitched back together.
|
|
155
|
-
* @type {VGeoContribution[]}
|
|
156
|
-
*/
|
|
157
|
-
parent_geometry = [];
|
|
158
|
-
|
|
159
|
-
/**
|
|
160
|
-
* The value both sides of this step store: the object-space distance from the *source* surface
|
|
161
|
-
* that this step's geometry now stands at, floored by what its children already carried.
|
|
162
|
-
* @type {number}
|
|
163
|
-
*/
|
|
164
|
-
error = 0;
|
|
165
|
-
|
|
166
|
-
/**
|
|
167
|
-
* `frontier vertex id -> that vertex's quadric after this step`, for every vertex that survived
|
|
168
|
-
* it. Held rather than written straight back because a step is speculative until the round
|
|
169
|
-
* accepts it, and because two steps sharing a pinned boundary vertex must have their gains
|
|
170
|
-
* combined rather than one overwriting the other.
|
|
171
|
-
* @type {Map<number, Quadric3>}
|
|
172
|
-
*/
|
|
173
|
-
quadrics = new Map();
|
|
174
|
-
|
|
175
|
-
/**
|
|
176
|
-
* The LOD-metric sphere both sides of this step store.
|
|
177
|
-
* @type {Float32Array}
|
|
178
|
-
*/
|
|
179
|
-
sphere = new Float32Array(4);
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
/**
|
|
183
|
-
* Several leaves re-cut into one, with nothing simplified — see {@link build_repack}.
|
|
184
|
-
*/
|
|
185
|
-
class GroupRepack {
|
|
186
|
-
/**
|
|
187
|
-
* The leaves this removes from the DAG.
|
|
188
|
-
* @type {VirtualGeometryCluster[]}
|
|
189
|
-
*/
|
|
190
|
-
replaced = [];
|
|
191
|
-
|
|
192
|
-
/**
|
|
193
|
-
* The leaves that take their place -- always fewer.
|
|
194
|
-
* @type {VirtualGeometryCluster[]}
|
|
195
|
-
*/
|
|
196
|
-
clusters = [];
|
|
197
|
-
|
|
198
|
-
/**
|
|
199
|
-
* Parallel to {@link clusters}.
|
|
200
|
-
* @type {VGeoContribution[]}
|
|
201
|
-
*/
|
|
202
|
-
geometry = [];
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
/**
|
|
206
|
-
* One cluster's geometry expressed against the frontier it came out of, which is what lets the next
|
|
207
|
-
* frontier be stitched from a mixture of freshly simplified clusters and untouched ones.
|
|
208
|
-
*
|
|
209
|
-
* @typedef {object} VGeoContribution
|
|
210
|
-
* @property {Float32Array} positions
|
|
211
|
-
* @property {Uint32Array} indices local to {@link positions}
|
|
212
|
-
* @property {Uint32Array} local_vertex_ids ids in the mesh {@link topology} belongs to
|
|
213
|
-
* @property {Uint32Array} source_vertex_ids the same vertices, named in the frontier's mesh
|
|
214
|
-
* @property {BinaryTopology} topology the mesh {@link local_vertex_ids} name vertices of, whose
|
|
215
|
-
* vertex data block holds their attributes
|
|
216
|
-
*/
|
|
217
|
-
|
|
218
|
-
/**
|
|
219
|
-
* Encode a gathered face set into a cluster record.
|
|
220
|
-
*
|
|
221
|
-
* @param {VGeoGatheredFaces} gathered already within both meshlet caps
|
|
222
|
-
* @param {number} level
|
|
223
|
-
* @param {(BinaryDataLayer|null)[]} layers of the mesh it was gathered from
|
|
224
|
-
* @returns {VirtualGeometryCluster}
|
|
225
|
-
*/
|
|
226
|
-
function cluster_from_gathered(gathered, level, layers) {
|
|
227
|
-
const payload = vgeo_encode_cluster_payload(
|
|
228
|
-
gathered.positions,
|
|
229
|
-
vgeo_narrow_indices(gathered),
|
|
230
|
-
gathered.vertex_ids,
|
|
231
|
-
layers
|
|
232
|
-
);
|
|
233
|
-
|
|
234
|
-
const cluster = new VirtualGeometryCluster();
|
|
235
|
-
|
|
236
|
-
cluster.level = level;
|
|
237
|
-
cluster.vertex_count = gathered.vertex_ids.length;
|
|
238
|
-
cluster.triangle_count = gathered.indices.length / 3;
|
|
239
|
-
cluster.encoding_flags = payload.encoding_flags;
|
|
240
|
-
cluster.payload = payload.bytes;
|
|
241
|
-
cluster.bounds_sphere = vgeo_bounding_sphere(gathered.positions);
|
|
242
|
-
|
|
243
|
-
// the box needs no inflation, unlike the sphere above: the extremes are float32 values already,
|
|
244
|
-
// so min and max of them are exactly representable and the stored box contains the stored
|
|
245
|
-
// points by construction
|
|
246
|
-
aabb3_from_v3_array(cluster.aabb, gathered.positions, gathered.positions.length);
|
|
247
|
-
|
|
248
|
-
return cluster;
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
/**
|
|
252
|
-
* Build a cluster out of a face set: gather it, bound it, encode its payload.
|
|
253
|
-
*
|
|
254
|
-
* @param {VirtualGeometryMesh} mesh
|
|
255
|
-
* @param {number[]} face_ids
|
|
256
|
-
* @param {number} level
|
|
257
|
-
* @returns {{cluster: VirtualGeometryCluster, gathered: VGeoGatheredFaces}}
|
|
258
|
-
*/
|
|
259
|
-
function build_cluster(mesh, face_ids, level) {
|
|
260
|
-
const gathered = vgeo_gather_faces(mesh, face_ids);
|
|
261
|
-
|
|
262
|
-
return { cluster: cluster_from_gathered(gathered, level, mesh.layers), gathered };
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
/**
|
|
266
|
-
* Cut a mesh into leaf clusters — the frontier every round starts from.
|
|
267
|
-
*
|
|
268
|
-
* @param {VirtualGeometryMesh} mesh
|
|
269
|
-
* @returns {VGeoFrontier}
|
|
270
|
-
*/
|
|
271
|
-
function build_leaf_frontier(mesh) {
|
|
272
|
-
const face_graph = new VGeoFaceGraph(mesh.topology, bt_mesh_build_face_graph(mesh.topology));
|
|
273
|
-
|
|
274
|
-
const all = [];
|
|
275
|
-
|
|
276
|
-
for (let vertex = 0; vertex < face_graph.graph.vertex_count; vertex++) {
|
|
277
|
-
all.push(vertex);
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
const parts = vgeo_split_face_set(
|
|
281
|
-
face_graph, all, VGEO_MAX_CLUSTER_TRIANGLE_COUNT, VGEO_MAX_CLUSTER_VERTEX_COUNT
|
|
282
|
-
);
|
|
283
|
-
|
|
284
|
-
const frontier = new VGeoFrontier();
|
|
285
|
-
|
|
286
|
-
frontier.mesh = mesh;
|
|
287
|
-
|
|
288
|
-
// Built once, here, from the source triangles. Nothing rebuilds them afterwards — every later
|
|
289
|
-
// round inherits these and adds to them, which is the whole point.
|
|
290
|
-
frontier.quadrics = bt_mesh_compute_vertex_quadrics(mesh.topology);
|
|
291
|
-
|
|
292
|
-
for (const part of parts) {
|
|
293
|
-
const face_ids = part.map((dense) => face_graph.vertex_face_ids[dense]);
|
|
294
|
-
|
|
295
|
-
face_ids.sort((a, b) => a - b);
|
|
296
|
-
|
|
297
|
-
const built = build_cluster(mesh, face_ids, 0);
|
|
298
|
-
|
|
299
|
-
// A leaf's own bounds are its LOD-metric sphere, and it deviates from nothing.
|
|
300
|
-
built.cluster.self_sphere = built.cluster.bounds_sphere.slice();
|
|
301
|
-
built.cluster.self_error = 0;
|
|
302
|
-
|
|
303
|
-
frontier.clusters.push(built.cluster);
|
|
304
|
-
frontier.cluster_faces.push(face_ids);
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
return frontier;
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
/**
|
|
311
|
-
* How much of the space two clusters would waste by being merged, as a factor in `(0, 1]`.
|
|
312
|
-
*
|
|
313
|
-
* The ratio of what they occupy separately to what the sphere containing both would occupy. Two
|
|
314
|
-
* clusters side by side score near one; two clusters at opposite ends of the model score near zero,
|
|
315
|
-
* because the sphere over both is mostly empty. Scale-free by construction — it compares a volume
|
|
316
|
-
* to a volume, so it needs no notion of how big the model is or how far apart "far" is, which is
|
|
317
|
-
* exactly what a distance threshold would have needed and could never have been given.
|
|
318
|
-
*
|
|
319
|
-
* The group's sphere is what the runtime projects to decide the whole group's fate, so a group
|
|
320
|
-
* spread across empty space is one that gets refined from much further away than its geometry
|
|
321
|
-
* warrants. Cutting edge weight by this steers the partitioner away from those merges without ever
|
|
322
|
-
* forbidding one — when nothing better is available, a poor merge is still better than none.
|
|
323
|
-
*
|
|
324
|
-
* @param {Float32Array} a bounding sphere, centre then radius
|
|
325
|
-
* @param {Float32Array} b
|
|
326
|
-
* @returns {number}
|
|
327
|
-
*/
|
|
328
|
-
function compactness(a, b) {
|
|
329
|
-
sphere_compute_bounding_of_2_spheres(scratch_sphere, a, b);
|
|
330
|
-
|
|
331
|
-
const merged = scratch_sphere[3];
|
|
332
|
-
|
|
333
|
-
if (merged <= 0) {
|
|
334
|
-
return 1;
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
// Volumes, up to the common factor of 4/3 pi, which cancels.
|
|
338
|
-
const separate = a[3] * a[3] * a[3] + b[3] * b[3] * b[3];
|
|
339
|
-
|
|
340
|
-
return Math.min(1, separate / (merged * merged * merged));
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
/**
|
|
344
|
-
* The cluster adjacency graph of a frontier: clusters are the vertices, shared mesh edges are the
|
|
345
|
-
* edges, and an edge weighs as much boundary as the two clusters share, discounted by how much empty
|
|
346
|
-
* space merging them would enclose.
|
|
347
|
-
*
|
|
348
|
-
* @param {VGeoFrontier} frontier
|
|
349
|
-
* @param {VGeoFaceGraph} face_graph of `frontier.mesh`
|
|
350
|
-
* @returns {{edge_addresses: Uint32Array, adjacency: Uint32Array, edge_weights: Float32Array}}
|
|
351
|
-
*/
|
|
352
|
-
function build_cluster_graph(frontier, face_graph) {
|
|
353
|
-
const cluster_of_face = new Int32Array(frontier.mesh.topology.faces.size).fill(-1);
|
|
354
|
-
|
|
355
|
-
frontier.cluster_faces.forEach((face_ids, cluster) => {
|
|
356
|
-
for (const face of face_ids) {
|
|
357
|
-
cluster_of_face[face] = cluster;
|
|
358
|
-
}
|
|
359
|
-
});
|
|
360
|
-
|
|
361
|
-
const cluster_count = frontier.clusters.length;
|
|
362
|
-
|
|
363
|
-
/**
|
|
364
|
-
* `cluster -> (neighbour cluster -> shared boundary)`.
|
|
365
|
-
* @type {Map<number, number>[]}
|
|
366
|
-
*/
|
|
367
|
-
const neighbours = [];
|
|
368
|
-
|
|
369
|
-
for (let i = 0; i < cluster_count; i++) {
|
|
370
|
-
neighbours.push(new Map());
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
const graph = face_graph.graph;
|
|
374
|
-
|
|
375
|
-
for (let vertex = 0; vertex < graph.vertex_count; vertex++) {
|
|
376
|
-
const cluster = cluster_of_face[face_graph.vertex_face_ids[vertex]];
|
|
377
|
-
|
|
378
|
-
for (let i = graph.edge_addresses[vertex]; i < graph.edge_addresses[vertex + 1]; i++) {
|
|
379
|
-
const other_cluster = cluster_of_face[face_graph.vertex_face_ids[graph.adjacency[i]]];
|
|
380
|
-
|
|
381
|
-
if (other_cluster === cluster) {
|
|
382
|
-
continue;
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
const bucket = neighbours[cluster];
|
|
386
|
-
|
|
387
|
-
bucket.set(other_cluster, (bucket.get(other_cluster) ?? 0) + graph.edge_weights[i]);
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
const edge_addresses = new Uint32Array(cluster_count + 1);
|
|
392
|
-
const adjacency = [];
|
|
393
|
-
const edge_weights = [];
|
|
394
|
-
|
|
395
|
-
for (let cluster = 0; cluster < cluster_count; cluster++) {
|
|
396
|
-
edge_addresses[cluster] = adjacency.length;
|
|
397
|
-
|
|
398
|
-
const entries = Array.from(neighbours[cluster].entries()).sort((a, b) => a[0] - b[0]);
|
|
399
|
-
|
|
400
|
-
for (const [other, weight] of entries) {
|
|
401
|
-
adjacency.push(other);
|
|
402
|
-
|
|
403
|
-
edge_weights.push(weight * compactness(
|
|
404
|
-
frontier.clusters[cluster].bounds_sphere, frontier.clusters[other].bounds_sphere
|
|
405
|
-
));
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
edge_addresses[cluster_count] = adjacency.length;
|
|
410
|
-
|
|
411
|
-
return {
|
|
412
|
-
edge_addresses,
|
|
413
|
-
adjacency: Uint32Array.from(adjacency),
|
|
414
|
-
edge_weights: Float32Array.from(edge_weights),
|
|
415
|
-
};
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
/**
|
|
419
|
-
* Bundle clusters that have no one to merge with along a Morton curve.
|
|
420
|
-
*
|
|
421
|
-
* A cluster alone in its connected component cannot be coarsened: halving its triangles leaves one
|
|
422
|
-
* cluster where there was one cluster, and the DAG stands still. The only way it ever reaches a root
|
|
423
|
-
* is bundled with geometry it shares no edge with, and the only thing left to choose them by is
|
|
424
|
-
* where they are. That is a real cost — a cluster spanning two islands carries a sphere containing
|
|
425
|
-
* both — which is why this is the fallback and adjacency is the rule.
|
|
426
|
-
*
|
|
427
|
-
* @param {VGeoFrontier} frontier
|
|
428
|
-
* @param {number[]} loose cluster indices, each alone in its component
|
|
429
|
-
* @param {number} group_size
|
|
430
|
-
* @returns {number[][]} groups of at least two; a single leftover is returned to the frontier
|
|
431
|
-
*/
|
|
432
|
-
function bundle_loose_clusters(frontier, loose, group_size) {
|
|
433
|
-
if (loose.length < 2) {
|
|
434
|
-
return [];
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
// Gathered rather than read through the cluster list twice: the bounds and the codes are the
|
|
438
|
-
// same points, and this is the shape `vgeo_split_face_set_packed` already packs in.
|
|
439
|
-
const centres = new Float32Array(loose.length * 3);
|
|
440
|
-
|
|
441
|
-
loose.forEach((cluster, index) => {
|
|
442
|
-
const sphere = frontier.clusters[cluster].bounds_sphere;
|
|
443
|
-
|
|
444
|
-
centres[index * 3] = sphere[0];
|
|
445
|
-
centres[index * 3 + 1] = sphere[1];
|
|
446
|
-
centres[index * 3 + 2] = sphere[2];
|
|
447
|
-
});
|
|
448
|
-
|
|
449
|
-
const bounds = new Float32Array(6);
|
|
450
|
-
|
|
451
|
-
aabb3_from_v3_array(bounds, centres, centres.length);
|
|
452
|
-
|
|
453
|
-
/**
|
|
454
|
-
* @type {Map<number, number>}
|
|
455
|
-
*/
|
|
456
|
-
const codes = new Map();
|
|
457
|
-
|
|
458
|
-
loose.forEach((cluster, index) => {
|
|
459
|
-
codes.set(cluster, v3_morton_encode_bounded(
|
|
460
|
-
centres[index * 3], centres[index * 3 + 1], centres[index * 3 + 2], bounds
|
|
461
|
-
));
|
|
462
|
-
});
|
|
463
|
-
|
|
464
|
-
const order = loose.slice();
|
|
465
|
-
|
|
466
|
-
order.sort((a, b) => (codes.get(a) - codes.get(b)) || (a - b));
|
|
467
|
-
|
|
468
|
-
const result = [];
|
|
469
|
-
|
|
470
|
-
let bundle = [];
|
|
471
|
-
let triangles = 0;
|
|
472
|
-
let vertices = 0;
|
|
473
|
-
let packable = true;
|
|
474
|
-
|
|
475
|
-
for (const index of order) {
|
|
476
|
-
const cluster = frontier.clusters[index];
|
|
477
|
-
|
|
478
|
-
// Leaves small enough to end up in one cluster between them are worth bundling past the
|
|
479
|
-
// group size: what happens to them is a re-cut at no error, not a simplification, so a
|
|
480
|
-
// bigger bundle costs nothing in fidelity and is the difference between a scene of small
|
|
481
|
-
// parts filling its clusters and running them at a tenth of capacity. Anything else stops at
|
|
482
|
-
// the group size, where the size is a real trade against LOD lockstep.
|
|
483
|
-
const packs = packable
|
|
484
|
-
&& cluster.child_group === undefined
|
|
485
|
-
&& triangles + cluster.triangle_count <= VGEO_MAX_CLUSTER_TRIANGLE_COUNT
|
|
486
|
-
&& vertices + cluster.vertex_count <= VGEO_MAX_CLUSTER_VERTEX_COUNT;
|
|
487
|
-
|
|
488
|
-
if (bundle.length >= group_size && !packs) {
|
|
489
|
-
result.push(bundle);
|
|
490
|
-
|
|
491
|
-
bundle = [];
|
|
492
|
-
triangles = 0;
|
|
493
|
-
vertices = 0;
|
|
494
|
-
packable = true;
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
bundle.push(index);
|
|
498
|
-
|
|
499
|
-
triangles += cluster.triangle_count;
|
|
500
|
-
vertices += cluster.vertex_count;
|
|
501
|
-
|
|
502
|
-
packable = packable && cluster.child_group === undefined;
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
if (bundle.length === 1 && result.length > 0) {
|
|
506
|
-
// A bundle of one merges nothing. Handing it to the bundle before it costs one cluster of
|
|
507
|
-
// spatial spread and is the difference between the frontier shrinking and it standing still.
|
|
508
|
-
result[result.length - 1].push(bundle[0]);
|
|
509
|
-
} else if (bundle.length > 1) {
|
|
510
|
-
result.push(bundle);
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
return result.filter((group) => group.length >= 2);
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
/**
|
|
517
|
-
* Fold every one-cluster piece into the adjacent piece it shares the most boundary with.
|
|
518
|
-
*
|
|
519
|
-
* The partitioner produces these constantly — it optimizes cut, not contiguity, so splitting its
|
|
520
|
-
* parts into connected pieces routinely leaves a cluster standing on its own. Such a cluster is not
|
|
521
|
-
* out of options, though: it sits inside a component that has plenty of neighbours, and the right
|
|
522
|
-
* answer is the nearest of them, not the proximity pool. Letting it fall through to the pool is how
|
|
523
|
-
* a lone shell sixty metres away ends up merged with a fragment of something else entirely.
|
|
524
|
-
*
|
|
525
|
-
* One forward pass is enough. Folding only ever grows a piece and empties the one it came from, so
|
|
526
|
-
* it cannot turn a piece of two into a piece of one — the singletons are exactly the ones present
|
|
527
|
-
* when the pass starts, and a singleton that has already absorbed another is no longer one.
|
|
528
|
-
*
|
|
529
|
-
* @param {number[][]} pieces of a single connected component of the band, each non-empty; every
|
|
530
|
-
* cluster in a singleton piece therefore has a neighbour in some other piece here
|
|
531
|
-
* @param {object} cluster_graph as {@link build_cluster_graph} returns
|
|
532
|
-
* @returns {number[][]} non-empty pieces, with no singletons
|
|
533
|
-
*/
|
|
534
|
-
function absorb_singleton_pieces(pieces, cluster_graph) {
|
|
535
|
-
if (pieces.length < 2) {
|
|
536
|
-
return pieces;
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
/**
|
|
540
|
-
* `cluster -> index of the piece holding it`.
|
|
541
|
-
* @type {Map<number, number>}
|
|
542
|
-
*/
|
|
543
|
-
const piece_of = new Map();
|
|
544
|
-
|
|
545
|
-
const alive = pieces.map((piece) => piece.slice());
|
|
546
|
-
|
|
547
|
-
alive.forEach((piece, index) => {
|
|
548
|
-
for (const cluster of piece) {
|
|
549
|
-
piece_of.set(cluster, index);
|
|
550
|
-
}
|
|
551
|
-
});
|
|
552
|
-
|
|
553
|
-
for (let index = 0; index < alive.length; index++) {
|
|
554
|
-
if (alive[index].length !== 1) {
|
|
555
|
-
continue;
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
const cluster = alive[index][0];
|
|
559
|
-
|
|
560
|
-
/**
|
|
561
|
-
* `piece index -> boundary shared with this cluster`.
|
|
562
|
-
* @type {Map<number, number>}
|
|
563
|
-
*/
|
|
564
|
-
const shared = new Map();
|
|
565
|
-
|
|
566
|
-
for (let i = cluster_graph.edge_addresses[cluster]; i < cluster_graph.edge_addresses[cluster + 1]; i++) {
|
|
567
|
-
const target = piece_of.get(cluster_graph.adjacency[i]);
|
|
568
|
-
|
|
569
|
-
if (target === undefined || target === index) {
|
|
570
|
-
continue;
|
|
571
|
-
}
|
|
572
|
-
|
|
573
|
-
shared.set(target, (shared.get(target) ?? 0) + cluster_graph.edge_weights[i]);
|
|
574
|
-
}
|
|
575
|
-
|
|
576
|
-
let best = -1;
|
|
577
|
-
let best_weight = -1;
|
|
578
|
-
|
|
579
|
-
// Sorted, so that two pieces sharing the same boundary length resolve the same way every
|
|
580
|
-
// time rather than however the adjacency happened to be walked.
|
|
581
|
-
for (const [target, weight] of Array.from(shared.entries()).sort((a, b) => a[0] - b[0])) {
|
|
582
|
-
if (weight > best_weight) {
|
|
583
|
-
best = target;
|
|
584
|
-
best_weight = weight;
|
|
585
|
-
}
|
|
586
|
-
}
|
|
587
|
-
|
|
588
|
-
assert.notEqual(
|
|
589
|
-
best, -1,
|
|
590
|
-
'a lone cluster inside a connected component must border another piece of it'
|
|
591
|
-
);
|
|
592
|
-
|
|
593
|
-
alive[best].push(cluster);
|
|
594
|
-
alive[best].sort((a, b) => a - b);
|
|
595
|
-
|
|
596
|
-
piece_of.set(cluster, best);
|
|
597
|
-
|
|
598
|
-
alive[index] = [];
|
|
599
|
-
}
|
|
600
|
-
|
|
601
|
-
return alive.filter((piece) => piece.length > 0);
|
|
602
|
-
}
|
|
603
|
-
|
|
604
|
-
/**
|
|
605
|
-
* Partition a band of the frontier into groups of roughly `group_size`.
|
|
606
|
-
*
|
|
607
|
-
* The partition runs **inside each connected component** of the cluster graph, not across the band as
|
|
608
|
-
* a whole. Partitioning globally and splitting the disconnected parts afterwards looks equivalent and
|
|
609
|
-
* is not: a scene assembled from thousands of separate shells has a cluster graph in thousands of
|
|
610
|
-
* pieces, and a balanced global partition draws each part from several of them, so the connectivity
|
|
611
|
-
* split then shreds nearly every group down to a single cluster.
|
|
612
|
-
*
|
|
613
|
-
* What is left over after that — every cluster alone in its component — is bundled by proximity
|
|
614
|
-
* instead, because for those clusters there is no adjacency left to respect and no other way to the
|
|
615
|
-
* root.
|
|
616
|
-
*
|
|
617
|
-
* @param {VGeoFrontier} frontier
|
|
618
|
-
* @param {number[]} band cluster indices this round may consume, ascending
|
|
619
|
-
* @param {object} cluster_graph as {@link build_cluster_graph} returns
|
|
620
|
-
* @param {number} group_size
|
|
621
|
-
* @returns {number[][]} groups of at least two clusters each; clusters left out stay in the frontier
|
|
622
|
-
*/
|
|
623
|
-
function partition_into_groups(frontier, band, cluster_graph, group_size) {
|
|
624
|
-
const membership = new Int32Array(frontier.clusters.length).fill(-1);
|
|
625
|
-
|
|
626
|
-
/**
|
|
627
|
-
* Dense rather than a `Map`, and hoisted out of the component loop so it is one allocation per
|
|
628
|
-
* level. See {@link vgeo_partition_subgraph} for why the density is load-bearing rather than a
|
|
629
|
-
* micro-optimisation.
|
|
630
|
-
* @type {Uint32Array}
|
|
631
|
-
*/
|
|
632
|
-
const local_index = new Uint32Array(frontier.clusters.length);
|
|
633
|
-
|
|
634
|
-
for (const cluster of band) {
|
|
635
|
-
membership[cluster] = 0;
|
|
636
|
-
}
|
|
637
|
-
|
|
638
|
-
const components = vgeo_graph_components(
|
|
639
|
-
cluster_graph.edge_addresses, cluster_graph.adjacency, band, membership, 0
|
|
640
|
-
);
|
|
641
|
-
|
|
642
|
-
const result = [];
|
|
643
|
-
|
|
644
|
-
/**
|
|
645
|
-
* Clusters with nobody adjacent to merge with.
|
|
646
|
-
* @type {number[]}
|
|
647
|
-
*/
|
|
648
|
-
const loose = [];
|
|
649
|
-
|
|
650
|
-
let token = 0;
|
|
651
|
-
|
|
652
|
-
/**
|
|
653
|
-
* @param {number[]} piece
|
|
654
|
-
*/
|
|
655
|
-
function accept(piece) {
|
|
656
|
-
if (piece.length < 2) {
|
|
657
|
-
loose.push(piece[0]);
|
|
658
|
-
|
|
659
|
-
return;
|
|
660
|
-
}
|
|
661
|
-
|
|
662
|
-
// The format caps a group at 4096 clusters. Nothing the size policy produces comes near it,
|
|
663
|
-
// but the cap is hard, so the chunking is here rather than assumed away.
|
|
664
|
-
for (let start = 0; start < piece.length; start += VGEO_MAX_GROUP_CLUSTER_COUNT) {
|
|
665
|
-
const chunk = piece.slice(start, start + VGEO_MAX_GROUP_CLUSTER_COUNT);
|
|
666
|
-
|
|
667
|
-
if (chunk.length < 2) {
|
|
668
|
-
loose.push(chunk[0]);
|
|
669
|
-
|
|
670
|
-
continue;
|
|
671
|
-
}
|
|
672
|
-
|
|
673
|
-
result.push(chunk);
|
|
674
|
-
}
|
|
675
|
-
}
|
|
676
|
-
|
|
677
|
-
for (const component of components) {
|
|
678
|
-
if (component.length < 2) {
|
|
679
|
-
// Alone in its component: no adjacency left anywhere, so proximity is the only rule that
|
|
680
|
-
// can still place it.
|
|
681
|
-
loose.push(component[0]);
|
|
682
|
-
|
|
683
|
-
continue;
|
|
684
|
-
}
|
|
685
|
-
|
|
686
|
-
if (component.length <= group_size) {
|
|
687
|
-
accept(component);
|
|
688
|
-
|
|
689
|
-
continue;
|
|
690
|
-
}
|
|
691
|
-
|
|
692
|
-
/**
|
|
693
|
-
* @type {number[][]}
|
|
694
|
-
*/
|
|
695
|
-
const pieces = [];
|
|
696
|
-
|
|
697
|
-
const part_count = Math.ceil(component.length / group_size);
|
|
698
|
-
|
|
699
|
-
token++;
|
|
700
|
-
|
|
701
|
-
for (const cluster of component) {
|
|
702
|
-
membership[cluster] = token;
|
|
703
|
-
}
|
|
704
|
-
|
|
705
|
-
const parts = vgeo_partition_subgraph(
|
|
706
|
-
cluster_graph, component, membership, token, part_count, GROUP_SEED, local_index
|
|
707
|
-
);
|
|
708
|
-
|
|
709
|
-
for (const part of parts) {
|
|
710
|
-
token++;
|
|
711
|
-
|
|
712
|
-
for (const cluster of part) {
|
|
713
|
-
membership[cluster] = token;
|
|
714
|
-
}
|
|
715
|
-
|
|
716
|
-
for (const piece of vgeo_graph_components(
|
|
717
|
-
cluster_graph.edge_addresses, cluster_graph.adjacency, part, membership, token
|
|
718
|
-
)) {
|
|
719
|
-
pieces.push(piece);
|
|
720
|
-
}
|
|
721
|
-
}
|
|
722
|
-
|
|
723
|
-
for (const piece of absorb_singleton_pieces(pieces, cluster_graph)) {
|
|
724
|
-
accept(piece);
|
|
725
|
-
}
|
|
726
|
-
}
|
|
727
|
-
|
|
728
|
-
for (const bundle of bundle_loose_clusters(frontier, loose, group_size)) {
|
|
729
|
-
result.push(bundle);
|
|
730
|
-
}
|
|
731
|
-
|
|
732
|
-
result.sort((a, b) => a[0] - b[0]);
|
|
733
|
-
|
|
734
|
-
return result;
|
|
735
|
-
}
|
|
736
|
-
|
|
737
|
-
/**
|
|
738
|
-
* Does any face outside this group use the edge?
|
|
739
|
-
*
|
|
740
|
-
* @param {BinaryTopology} topology
|
|
741
|
-
* @param {number} edge
|
|
742
|
-
* @param {Int32Array} group_of_face
|
|
743
|
-
* @param {number} group_index
|
|
744
|
-
* @returns {boolean}
|
|
745
|
-
*/
|
|
746
|
-
function edge_touches_outside(topology, edge, group_of_face, group_index) {
|
|
747
|
-
const first_loop = topology.edge_read_loop(edge);
|
|
748
|
-
|
|
749
|
-
if (first_loop === NULL_POINTER) {
|
|
750
|
-
return false;
|
|
751
|
-
}
|
|
752
|
-
|
|
753
|
-
let loop = first_loop;
|
|
754
|
-
|
|
755
|
-
do {
|
|
756
|
-
const face = topology.loop_read_face(loop);
|
|
757
|
-
|
|
758
|
-
if (face !== NULL_POINTER && group_of_face[face] !== group_index) {
|
|
759
|
-
return true;
|
|
760
|
-
}
|
|
761
|
-
|
|
762
|
-
loop = topology.loop_read_radial_next(loop);
|
|
763
|
-
} while (loop !== first_loop && loop !== NULL_POINTER);
|
|
764
|
-
|
|
765
|
-
return false;
|
|
766
|
-
}
|
|
767
|
-
|
|
768
|
-
/**
|
|
769
|
-
* Does any face outside this group use the vertex? Walks the vertex's disk cycle and each edge's
|
|
770
|
-
* radial cycle, so the answer is local to the vertex rather than a scan of the frontier.
|
|
771
|
-
*
|
|
772
|
-
* @param {BinaryTopology} topology
|
|
773
|
-
* @param {number} vertex
|
|
774
|
-
* @param {Int32Array} group_of_face
|
|
775
|
-
* @param {number} group_index
|
|
776
|
-
* @returns {boolean}
|
|
777
|
-
*/
|
|
778
|
-
function vertex_touches_outside(topology, vertex, group_of_face, group_index) {
|
|
779
|
-
const first_edge = topology.vertex_read_edge(vertex);
|
|
780
|
-
|
|
781
|
-
if (first_edge === NULL_POINTER) {
|
|
782
|
-
return false;
|
|
783
|
-
}
|
|
784
|
-
|
|
785
|
-
let edge = first_edge;
|
|
786
|
-
|
|
787
|
-
do {
|
|
788
|
-
if (edge_touches_outside(topology, edge, group_of_face, group_index)) {
|
|
789
|
-
return true;
|
|
790
|
-
}
|
|
791
|
-
|
|
792
|
-
edge = (topology.edge_read_vertex1(edge) === vertex)
|
|
793
|
-
? topology.edge_read_v1_disk_next(edge)
|
|
794
|
-
: topology.edge_read_v2_disk_next(edge);
|
|
795
|
-
} while (edge !== first_edge && edge !== NULL_POINTER);
|
|
796
|
-
|
|
797
|
-
return false;
|
|
798
|
-
}
|
|
799
|
-
|
|
800
|
-
/**
|
|
801
|
-
* Which vertices a group must not move, and which boundary edges must survive its simplification.
|
|
802
|
-
*
|
|
803
|
-
* A vertex is locked when any face outside the group touches it — not merely when it sits on a
|
|
804
|
-
* shared *edge*. The weaker rule leaks: two groups can meet at a single non-manifold vertex with no
|
|
805
|
-
* edge between them, and each would then move that vertex somewhere else, tearing the surface at a
|
|
806
|
-
* point no boundary walk would have flagged.
|
|
807
|
-
*
|
|
808
|
-
* "Outside" includes frontier clusters this round left alone, which is what keeps a band's seam with
|
|
809
|
-
* the rest of the frontier from opening.
|
|
810
|
-
*
|
|
811
|
-
* @param {BinaryTopology} topology
|
|
812
|
-
* @param {number[]} group_face_ids
|
|
813
|
-
* @param {Int32Array} group_of_face one entry per face slot, holding the group each face belongs to
|
|
814
|
-
* @param {number} group_index
|
|
815
|
-
* @returns {{locked: Set<number>, polyline: number[][]}} locked vertex ids, and the `[v1, v2]` pairs
|
|
816
|
-
* of every edge this group shares with the outside
|
|
817
|
-
*/
|
|
818
|
-
function compute_group_boundary(topology, group_face_ids, group_of_face, group_index) {
|
|
819
|
-
const vertices = new Set();
|
|
820
|
-
const edges = new Set();
|
|
821
|
-
|
|
822
|
-
for (const face of group_face_ids) {
|
|
823
|
-
const first_loop = topology.face_read_loop(face);
|
|
824
|
-
|
|
825
|
-
let loop = first_loop;
|
|
826
|
-
|
|
827
|
-
do {
|
|
828
|
-
vertices.add(topology.loop_read_vertex(loop));
|
|
829
|
-
edges.add(topology.loop_read_edge(loop));
|
|
830
|
-
|
|
831
|
-
loop = topology.loop_read_next(loop);
|
|
832
|
-
} while (loop !== first_loop);
|
|
833
|
-
}
|
|
834
|
-
|
|
835
|
-
const locked = new Set();
|
|
836
|
-
|
|
837
|
-
for (const vertex of vertices) {
|
|
838
|
-
if (vertex_touches_outside(topology, vertex, group_of_face, group_index)) {
|
|
839
|
-
locked.add(vertex);
|
|
840
|
-
}
|
|
841
|
-
}
|
|
842
|
-
|
|
843
|
-
const polyline = [];
|
|
844
|
-
|
|
845
|
-
for (const edge of edges) {
|
|
846
|
-
if (!edge_touches_outside(topology, edge, group_of_face, group_index)) {
|
|
847
|
-
continue;
|
|
848
|
-
}
|
|
849
|
-
|
|
850
|
-
polyline.push([topology.edge_read_vertex1(edge), topology.edge_read_vertex2(edge)]);
|
|
851
|
-
}
|
|
852
|
-
|
|
853
|
-
return { locked, polyline };
|
|
854
|
-
}
|
|
855
|
-
|
|
856
|
-
/**
|
|
857
|
-
* Largest error any of a group's clusters carries -- the floor a step above them must respect.
|
|
858
|
-
*
|
|
859
|
-
* @param {VirtualGeometryCluster[]} clusters
|
|
860
|
-
* @returns {number}
|
|
861
|
-
*/
|
|
862
|
-
function max_child_error(clusters) {
|
|
863
|
-
let result = 0;
|
|
864
|
-
|
|
865
|
-
for (const cluster of clusters) {
|
|
866
|
-
result = Math.max(result, cluster.self_error);
|
|
867
|
-
}
|
|
868
|
-
|
|
869
|
-
return result;
|
|
870
|
-
}
|
|
871
|
-
|
|
872
|
-
/**
|
|
873
|
-
* Leaves with room to spare between them, re-cut into fewer and fuller leaves.
|
|
874
|
-
*
|
|
875
|
-
* This is not a level of the DAG and must not become one. Nothing is simplified, so the new clusters
|
|
876
|
-
* carry the same error their inputs did -- zero -- and a step whose parent error equals its child
|
|
877
|
-
* error is a step the cut rule can never take: a cluster is drawn only while `project(parent) > tau`,
|
|
878
|
-
* and a parent at zero error projects to zero unconditionally. The leaves under it would be stored,
|
|
879
|
-
* paged, and never once selected. So the re-cut *replaces* them; they were a way of cutting this
|
|
880
|
-
* geometry up, and now there is a better one.
|
|
881
|
-
*
|
|
882
|
-
* It is what makes a scene of small separate parts tractable. A thousand twelve-triangle bolts are a
|
|
883
|
-
* thousand clusters at eight per cent fill, and no simplification is what they need -- they need to
|
|
884
|
-
* be cut up differently. Repacking is bounded by the grouping that proposed it, so what lands in one
|
|
885
|
-
* cluster is what the adjacency and proximity rules already judged to belong together.
|
|
886
|
-
*
|
|
887
|
-
* Only leaves. Above them the same re-cut would orphan a subtree, since a coarser cluster cannot be
|
|
888
|
-
* deleted -- something hangs off it.
|
|
889
|
-
*
|
|
890
|
-
* @param {VGeoFrontier} frontier
|
|
891
|
-
* @param {number[]} group_clusters cluster indices
|
|
892
|
-
* @param {number} parent_target how many clusters the group is aiming to become
|
|
893
|
-
* @returns {GroupRepack|undefined} `undefined` when the group must be simplified instead
|
|
894
|
-
*/
|
|
895
|
-
function try_repack(frontier, group_clusters, parent_target) {
|
|
896
|
-
const face_ids = [];
|
|
897
|
-
|
|
898
|
-
for (const cluster of group_clusters) {
|
|
899
|
-
if (frontier.clusters[cluster].child_group !== undefined) {
|
|
900
|
-
return undefined;
|
|
901
|
-
}
|
|
902
|
-
|
|
903
|
-
for (const face of frontier.cluster_faces[cluster]) {
|
|
904
|
-
face_ids.push(face);
|
|
905
|
-
}
|
|
906
|
-
}
|
|
907
|
-
|
|
908
|
-
// Only when the target is already met -- there is nothing to simplify, so the reduction has to
|
|
909
|
-
// come from re-cutting. A group over its target has real geometry to lose and belongs on the
|
|
910
|
-
// simplification path.
|
|
911
|
-
if (face_ids.length > parent_target * VGEO_MAX_CLUSTER_TRIANGLE_COUNT) {
|
|
912
|
-
return undefined;
|
|
913
|
-
}
|
|
914
|
-
|
|
915
|
-
face_ids.sort((a, b) => a - b);
|
|
916
|
-
|
|
917
|
-
const submesh = vgeo_extract_submesh(frontier.mesh, face_ids);
|
|
918
|
-
|
|
919
|
-
const face_graph = new VGeoFaceGraph(
|
|
920
|
-
submesh.mesh.topology, bt_mesh_build_face_graph(submesh.mesh.topology)
|
|
921
|
-
);
|
|
922
|
-
|
|
923
|
-
/**
|
|
924
|
-
*
|
|
925
|
-
* @type {number[]}
|
|
926
|
-
*/
|
|
927
|
-
const all = [];
|
|
928
|
-
|
|
929
|
-
const vertex_count = face_graph.graph.vertex_count;
|
|
930
|
-
for (let vertex = 0; vertex < vertex_count; vertex++) {
|
|
931
|
-
all.push(vertex);
|
|
932
|
-
}
|
|
933
|
-
|
|
934
|
-
const parts = vgeo_split_face_set_packed(
|
|
935
|
-
face_graph, all, VGEO_MAX_CLUSTER_TRIANGLE_COUNT, VGEO_MAX_CLUSTER_VERTEX_COUNT
|
|
936
|
-
);
|
|
937
|
-
|
|
938
|
-
if (parts.length >= group_clusters.length) {
|
|
939
|
-
// The vertex ceiling bit before the triangle one, and re-cutting bought nothing.
|
|
940
|
-
return undefined;
|
|
941
|
-
}
|
|
942
|
-
|
|
943
|
-
const repack = new GroupRepack();
|
|
944
|
-
|
|
945
|
-
repack.replaced = group_clusters.map((cluster) => frontier.clusters[cluster]);
|
|
946
|
-
|
|
947
|
-
for (const part of parts) {
|
|
948
|
-
const part_face_ids = part.map((dense) => face_graph.vertex_face_ids[dense]);
|
|
949
|
-
|
|
950
|
-
part_face_ids.sort((a, b) => a - b);
|
|
951
|
-
|
|
952
|
-
const built = build_cluster(submesh.mesh, part_face_ids, 0);
|
|
953
|
-
|
|
954
|
-
// A leaf's own bounds are its LOD-metric sphere, and it deviates from nothing.
|
|
955
|
-
built.cluster.self_sphere = built.cluster.bounds_sphere.slice();
|
|
956
|
-
built.cluster.self_error = 0;
|
|
957
|
-
|
|
958
|
-
repack.clusters.push(built.cluster);
|
|
959
|
-
|
|
960
|
-
repack.geometry.push({
|
|
961
|
-
positions: built.gathered.positions,
|
|
962
|
-
indices: built.gathered.indices,
|
|
963
|
-
local_vertex_ids: built.gathered.vertex_ids,
|
|
964
|
-
source_vertex_ids: Uint32Array.from(
|
|
965
|
-
built.gathered.vertex_ids, (local) => submesh.source_vertex_ids[local]
|
|
966
|
-
),
|
|
967
|
-
topology: submesh.mesh.topology,
|
|
968
|
-
});
|
|
969
|
-
}
|
|
970
|
-
|
|
971
|
-
return repack;
|
|
972
|
-
}
|
|
973
|
-
|
|
974
|
-
/**
|
|
975
|
-
* Simplify one group and cut the result into the parent clusters of every cluster in it.
|
|
976
|
-
*
|
|
977
|
-
* @param {VGeoFrontier} frontier
|
|
978
|
-
* @param {number[]} group_clusters cluster indices
|
|
979
|
-
* @param {Int32Array} group_of_face one entry per face slot, holding the group each face belongs to
|
|
980
|
-
* @param {number} group_index
|
|
981
|
-
* @param {number} level of the parents this step produces
|
|
982
|
-
* @param {VGeoLevelOptions} options
|
|
983
|
-
* @returns {GroupStep|undefined} `undefined` when the group has no coarser form at all, and the
|
|
984
|
-
* round should leave its clusters alone
|
|
985
|
-
*/
|
|
986
|
-
function build_group_step(frontier, group_clusters, group_of_face, group_index, level, options) {
|
|
987
|
-
/**
|
|
988
|
-
*
|
|
989
|
-
* @type {number[]}
|
|
990
|
-
*/
|
|
991
|
-
const face_ids = [];
|
|
992
|
-
|
|
993
|
-
for (const cluster of group_clusters) {
|
|
994
|
-
for (const face of frontier.cluster_faces[cluster]) {
|
|
995
|
-
face_ids.push(face);
|
|
996
|
-
}
|
|
997
|
-
}
|
|
998
|
-
|
|
999
|
-
face_ids.sort((a, b) => a - b);
|
|
1000
|
-
|
|
1001
|
-
const child_clusters = group_clusters.map((cluster) => frontier.clusters[cluster]);
|
|
1002
|
-
|
|
1003
|
-
const boundary = compute_group_boundary(
|
|
1004
|
-
frontier.mesh.topology, face_ids, group_of_face, group_index
|
|
1005
|
-
);
|
|
1006
|
-
|
|
1007
|
-
/**
|
|
1008
|
-
* Extract the group, simplify it to `target_face_count`, and cut the result into clusters.
|
|
1009
|
-
*
|
|
1010
|
-
* @param {number} target_face_count
|
|
1011
|
-
* @returns {{submesh: VGeoSubmesh, face_graph: VGeoFaceGraph, parts: number[][],
|
|
1012
|
-
* quadrics: Quadric3[], error: number}}
|
|
1013
|
-
*/
|
|
1014
|
-
function simplify_to(target_face_count) {
|
|
1015
|
-
const submesh = vgeo_extract_submesh(frontier.mesh, face_ids);
|
|
1016
|
-
|
|
1017
|
-
// Cloned, not borrowed: the simplifier mutates these, and this whole attempt may be thrown
|
|
1018
|
-
// away — by the polyline check below, by the round rejecting it, or by a wider band being
|
|
1019
|
-
// tried instead. The frontier's own quadrics must survive all of that untouched.
|
|
1020
|
-
const submesh_source_vertex_ids = submesh.source_vertex_ids;
|
|
1021
|
-
const source_vertex_id_count = submesh_source_vertex_ids.length;
|
|
1022
|
-
|
|
1023
|
-
/**
|
|
1024
|
-
*
|
|
1025
|
-
* @type {Quadric3[]}
|
|
1026
|
-
*/
|
|
1027
|
-
const quadrics = new Array(source_vertex_id_count);
|
|
1028
|
-
|
|
1029
|
-
for (let i = 0; i < source_vertex_id_count; i++) {
|
|
1030
|
-
const source = submesh_source_vertex_ids[i];
|
|
1031
|
-
quadrics[i] = frontier.quadrics[source].clone();
|
|
1032
|
-
}
|
|
1033
|
-
|
|
1034
|
-
/**
|
|
1035
|
-
* `source vertex id -> submesh local id`, for translating the lock set and the polyline.
|
|
1036
|
-
* @type {Map<number, number>}
|
|
1037
|
-
*/
|
|
1038
|
-
const local_of_source = new Map();
|
|
1039
|
-
|
|
1040
|
-
for (let local = 0; local < source_vertex_id_count; local++) {
|
|
1041
|
-
const source = submesh_source_vertex_ids[local];
|
|
1042
|
-
local_of_source.set(source, local);
|
|
1043
|
-
}
|
|
1044
|
-
|
|
1045
|
-
/**
|
|
1046
|
-
*
|
|
1047
|
-
* @type {Set<number>}
|
|
1048
|
-
*/
|
|
1049
|
-
const locked = new Set();
|
|
1050
|
-
|
|
1051
|
-
for (const vertex of boundary.locked) {
|
|
1052
|
-
const local = local_of_source.get(vertex);
|
|
1053
|
-
|
|
1054
|
-
if (local !== undefined) {
|
|
1055
|
-
locked.add(local);
|
|
1056
|
-
}
|
|
1057
|
-
}
|
|
1058
|
-
|
|
1059
|
-
// The faces carrying the group's boundary edges. Locking the endpoints keeps them where they
|
|
1060
|
-
// are; protecting these faces is what keeps an edge *between* two locked vertices from
|
|
1061
|
-
// losing the only face that holds it — see the polyline check below, which is the same
|
|
1062
|
-
// guarantee stated as an assertion.
|
|
1063
|
-
|
|
1064
|
-
/**
|
|
1065
|
-
*
|
|
1066
|
-
* @type {Set<number>}
|
|
1067
|
-
*/
|
|
1068
|
-
const protected_faces = new Set();
|
|
1069
|
-
|
|
1070
|
-
for (const [v1, v2] of boundary.polyline) {
|
|
1071
|
-
const edge = bt_edge_get(
|
|
1072
|
-
submesh.mesh.topology, local_of_source.get(v1), local_of_source.get(v2)
|
|
1073
|
-
);
|
|
1074
|
-
|
|
1075
|
-
if (edge === NULL_POINTER) {
|
|
1076
|
-
continue;
|
|
1077
|
-
}
|
|
1078
|
-
|
|
1079
|
-
const first_loop = submesh.mesh.topology.edge_read_loop(edge);
|
|
1080
|
-
|
|
1081
|
-
let loop = first_loop;
|
|
1082
|
-
|
|
1083
|
-
do {
|
|
1084
|
-
const face = submesh.mesh.topology.loop_read_face(loop);
|
|
1085
|
-
|
|
1086
|
-
if (face !== NULL_POINTER) {
|
|
1087
|
-
protected_faces.add(face);
|
|
1088
|
-
}
|
|
1089
|
-
|
|
1090
|
-
loop = submesh.mesh.topology.loop_read_radial_next(loop);
|
|
1091
|
-
} while (loop !== first_loop && loop !== NULL_POINTER);
|
|
1092
|
-
}
|
|
1093
|
-
|
|
1094
|
-
// The simplifier measures this itself, and has to: a cluster consumed whole leaves a
|
|
1095
|
-
// vertex behind that belongs to no face and is about to be swept away, and that vertex is
|
|
1096
|
-
// the only thing still holding the quadric of what used to be there. Measuring survivors
|
|
1097
|
-
// from out here would report a soup of separate triangles losing most of itself as free.
|
|
1098
|
-
const deviation = bt_mesh_simplify_with_quadrics(
|
|
1099
|
-
submesh.mesh.topology, target_face_count, locked, protected_faces, quadrics
|
|
1100
|
-
);
|
|
1101
|
-
|
|
1102
|
-
const defects = bt_mesh_validate(submesh.mesh.topology);
|
|
1103
|
-
|
|
1104
|
-
if (defects.length > 0) {
|
|
1105
|
-
throw new Error(
|
|
1106
|
-
`simplifying a group of ${group_clusters.length} clusters left the mesh inconsistent: `
|
|
1107
|
-
+ defects.slice(0, 4).map((defect) => defect.detail).join('; ')
|
|
1108
|
-
);
|
|
1109
|
-
}
|
|
1110
|
-
|
|
1111
|
-
// The design asks for this explicitly: pinning a vertex does not by itself protect the
|
|
1112
|
-
// *edges* between pinned vertices, and a face-kill path can strip one. A missing boundary
|
|
1113
|
-
// edge is a crack between two groups that will never close, so the build stops here rather
|
|
1114
|
-
// than shipping it.
|
|
1115
|
-
for (const [v1, v2] of boundary.polyline) {
|
|
1116
|
-
const local_1 = local_of_source.get(v1);
|
|
1117
|
-
const local_2 = local_of_source.get(v2);
|
|
1118
|
-
|
|
1119
|
-
if (
|
|
1120
|
-
local_1 === undefined
|
|
1121
|
-
|| local_2 === undefined
|
|
1122
|
-
|| bt_edge_get(submesh.mesh.topology, local_1, local_2) === NULL_POINTER
|
|
1123
|
-
) {
|
|
1124
|
-
throw new Error(
|
|
1125
|
-
`group boundary edge (${v1}, ${v2}) did not survive simplification of a group of `
|
|
1126
|
-
+ `${group_clusters.length} clusters`
|
|
1127
|
-
);
|
|
1128
|
-
}
|
|
1129
|
-
}
|
|
1130
|
-
|
|
1131
|
-
/*
|
|
1132
|
-
The step moved the geometry, so the frame that describes it has to move with it — but only
|
|
1133
|
-
where the step was free to move anything. `locked` is the group's perimeter, the vertices
|
|
1134
|
-
every neighbouring group also carries, and it is what a cut leans on to put two levels of
|
|
1135
|
-
detail side by side without a crack. Derived here, a perimeter vertex would get the average
|
|
1136
|
-
of this group's faces alone while its neighbour got the average of that group's, and the
|
|
1137
|
-
seam that is watertight in geometry would show as a crease in shading.
|
|
1138
|
-
*/
|
|
1139
|
-
vgeo_compute_vertex_normals(submesh.mesh, locked);
|
|
1140
|
-
vgeo_compute_vertex_tangents(submesh.mesh, locked);
|
|
1141
|
-
|
|
1142
|
-
const face_graph = new VGeoFaceGraph(
|
|
1143
|
-
submesh.mesh.topology, bt_mesh_build_face_graph(submesh.mesh.topology)
|
|
1144
|
-
);
|
|
1145
|
-
|
|
1146
|
-
const all = [];
|
|
1147
|
-
|
|
1148
|
-
for (let vertex = 0; vertex < face_graph.graph.vertex_count; vertex++) {
|
|
1149
|
-
all.push(vertex);
|
|
1150
|
-
}
|
|
1151
|
-
|
|
1152
|
-
// Packed: this is a group, so its extent is already the group's, and one cluster per
|
|
1153
|
-
// surviving island would undo the merge the group exists to make.
|
|
1154
|
-
const parts = vgeo_split_face_set_packed(
|
|
1155
|
-
face_graph, all, VGEO_MAX_CLUSTER_TRIANGLE_COUNT, VGEO_MAX_CLUSTER_VERTEX_COUNT
|
|
1156
|
-
);
|
|
1157
|
-
|
|
1158
|
-
return { submesh, face_graph, parts, quadrics, error: deviation };
|
|
1159
|
-
}
|
|
1160
|
-
|
|
1161
|
-
// Aim at a number of *clusters*, not a fraction of the triangles. Halving the triangle count is
|
|
1162
|
-
// a means; halving the cluster count is the end, and asking for it directly is what keeps
|
|
1163
|
-
// parents full. Where the children are already full the two coincide exactly. Where they are
|
|
1164
|
-
// not, this simplifies less — because the reduction is coming from packing the geometry better
|
|
1165
|
-
// rather than from destroying it, which is the outcome to prefer.
|
|
1166
|
-
const parent_target = Math.max(
|
|
1167
|
-
1, Math.ceil(group_clusters.length * options.simplification_ratio)
|
|
1168
|
-
);
|
|
1169
|
-
|
|
1170
|
-
const outcome = simplify_to(Math.min(
|
|
1171
|
-
parent_target * VGEO_MAX_CLUSTER_TRIANGLE_COUNT,
|
|
1172
|
-
// A group of under-filled clusters can already be under the cluster-count target, in which
|
|
1173
|
-
// case nothing would be simplified and the step would record no error — a step the cut rule
|
|
1174
|
-
// cannot take. Falling back to the triangle ratio keeps it a real simplification.
|
|
1175
|
-
Math.max(2, Math.round(face_ids.length * options.simplification_ratio))
|
|
1176
|
-
));
|
|
1177
|
-
|
|
1178
|
-
if (outcome.parts.length === 0) {
|
|
1179
|
-
// Some small closed shells have no coarser form under edge collapse at all — a tetrahedron
|
|
1180
|
-
// loses two faces to the collapse and its other two to the fuse that follows, leaving
|
|
1181
|
-
// nothing. Nothing to build a parent from, so the group is refused and its clusters go back
|
|
1182
|
-
// to the frontier to be grouped differently.
|
|
1183
|
-
return undefined;
|
|
1184
|
-
}
|
|
1185
|
-
|
|
1186
|
-
const submesh = outcome.submesh;
|
|
1187
|
-
const submesh_face_graph = outcome.face_graph;
|
|
1188
|
-
const parts = outcome.parts;
|
|
1189
|
-
|
|
1190
|
-
const step = new GroupStep();
|
|
1191
|
-
|
|
1192
|
-
step.child_clusters = child_clusters;
|
|
1193
|
-
|
|
1194
|
-
// Floored by what the children already carried. The accumulated quadric is a deviation from the
|
|
1195
|
-
// source and nothing forces it to rise when the vertex carrying the previous maximum is the one
|
|
1196
|
-
// collapsed away — but a parent that claimed to be closer to the source than its own child would
|
|
1197
|
-
// make the cut oscillate, so monotonicity is imposed here rather than hoped for.
|
|
1198
|
-
step.error = Math.max(outcome.error, max_child_error(child_clusters));
|
|
1199
|
-
|
|
1200
|
-
submesh.source_vertex_ids.forEach((source, local) => {
|
|
1201
|
-
if (outcome.quadrics[local] !== null) {
|
|
1202
|
-
step.quadrics.set(source, outcome.quadrics[local]);
|
|
1203
|
-
}
|
|
1204
|
-
});
|
|
1205
|
-
|
|
1206
|
-
const parent_points = [];
|
|
1207
|
-
|
|
1208
|
-
for (const part of parts) {
|
|
1209
|
-
const part_face_ids = part.map((dense) => submesh_face_graph.vertex_face_ids[dense]);
|
|
1210
|
-
|
|
1211
|
-
part_face_ids.sort((a, b) => a - b);
|
|
1212
|
-
|
|
1213
|
-
const built = build_cluster(submesh.mesh, part_face_ids, level);
|
|
1214
|
-
|
|
1215
|
-
step.parent_clusters.push(built.cluster);
|
|
1216
|
-
|
|
1217
|
-
step.parent_geometry.push({
|
|
1218
|
-
positions: built.gathered.positions,
|
|
1219
|
-
indices: built.gathered.indices,
|
|
1220
|
-
local_vertex_ids: built.gathered.vertex_ids,
|
|
1221
|
-
source_vertex_ids: Uint32Array.from(
|
|
1222
|
-
built.gathered.vertex_ids, (local) => submesh.source_vertex_ids[local]
|
|
1223
|
-
),
|
|
1224
|
-
topology: submesh.mesh.topology,
|
|
1225
|
-
});
|
|
1226
|
-
|
|
1227
|
-
parent_points.push(built.gathered.positions);
|
|
1228
|
-
}
|
|
1229
|
-
|
|
1230
|
-
let point_count = 0;
|
|
1231
|
-
|
|
1232
|
-
for (const points of parent_points) {
|
|
1233
|
-
point_count += points.length;
|
|
1234
|
-
}
|
|
1235
|
-
|
|
1236
|
-
const points = new Float32Array(point_count);
|
|
1237
|
-
|
|
1238
|
-
let cursor = 0;
|
|
1239
|
-
|
|
1240
|
-
for (const part of parent_points) {
|
|
1241
|
-
points.set(part, cursor);
|
|
1242
|
-
|
|
1243
|
-
cursor += part.length;
|
|
1244
|
-
}
|
|
1245
|
-
|
|
1246
|
-
// One sphere for the whole step, containing both what it consumed and what it produced. Sharing
|
|
1247
|
-
// it is what makes every sibling project the same value, and containing the children is what
|
|
1248
|
-
// makes the pair monotonic.
|
|
1249
|
-
step.sphere = vgeo_containing_sphere(
|
|
1250
|
-
step.child_clusters.map((cluster) => cluster.self_sphere),
|
|
1251
|
-
points
|
|
1252
|
-
);
|
|
1253
|
-
|
|
1254
|
-
return step;
|
|
1255
|
-
}
|
|
1256
|
-
|
|
1257
|
-
/**
|
|
1258
|
-
* Stitch a round's output back into one mesh: freshly simplified clusters, and the clusters the
|
|
1259
|
-
* round left alone, side by side.
|
|
1260
|
-
*
|
|
1261
|
-
* Vertices are unified by the id they had in the frontier below, never by position: a vertex two
|
|
1262
|
-
* groups share is pinned, so both carry it unmoved and unmistakably, while a vertex only one group
|
|
1263
|
-
* holds is its own even if a collapse happened to place it exactly where another landed. An
|
|
1264
|
-
* untouched cluster names its vertices in that same space, so it stitches to its simplified
|
|
1265
|
-
* neighbour along exactly the boundary that neighbour was forbidden to move.
|
|
1266
|
-
*
|
|
1267
|
-
* @param {VGeoContribution[]} contributions parallel to the clusters of the frontier being built
|
|
1268
|
-
* @param {(BinaryDataLayer|null)[]} template_layers the channels the frontier below carries, which
|
|
1269
|
-
* the frontier being stitched carries too
|
|
1270
|
-
* @returns {{mesh: VirtualGeometryMesh, cluster_faces: number[][], source_vertex_ids: Uint32Array}}
|
|
1271
|
-
* the last of which names, for every vertex of the new mesh, the vertex of the old one it came
|
|
1272
|
-
* from — which is how its accumulated quadric follows it across the seam
|
|
1273
|
-
*/
|
|
1274
|
-
function stitch_frontier(contributions, template_layers) {
|
|
1275
|
-
/**
|
|
1276
|
-
* `source vertex id -> index in the frontier being built`.
|
|
1277
|
-
* @type {Map<number, number>}
|
|
1278
|
-
*/
|
|
1279
|
-
const index_of_source = new Map();
|
|
1280
|
-
|
|
1281
|
-
const positions = [];
|
|
1282
|
-
const indices = [];
|
|
1283
|
-
const cluster_faces = [];
|
|
1284
|
-
|
|
1285
|
-
/**
|
|
1286
|
-
* `[mesh the vertex came from, its id there]` per new vertex, so its attribute record can be
|
|
1287
|
-
* copied across once the topology that receives it exists.
|
|
1288
|
-
* @type {[BinaryTopology, number][]}
|
|
1289
|
-
*/
|
|
1290
|
-
const attribute_sources = [];
|
|
1291
|
-
|
|
1292
|
-
let face_cursor = 0;
|
|
1293
|
-
|
|
1294
|
-
for (const geometry of contributions) {
|
|
1295
|
-
const remap = new Uint32Array(geometry.local_vertex_ids.length);
|
|
1296
|
-
|
|
1297
|
-
geometry.local_vertex_ids.forEach((local, i) => {
|
|
1298
|
-
const source = geometry.source_vertex_ids[i];
|
|
1299
|
-
|
|
1300
|
-
let target = index_of_source.get(source);
|
|
1301
|
-
|
|
1302
|
-
if (target === undefined) {
|
|
1303
|
-
target = positions.length / 3;
|
|
1304
|
-
|
|
1305
|
-
index_of_source.set(source, target);
|
|
1306
|
-
|
|
1307
|
-
positions.push(
|
|
1308
|
-
geometry.positions[i * 3],
|
|
1309
|
-
geometry.positions[i * 3 + 1],
|
|
1310
|
-
geometry.positions[i * 3 + 2]
|
|
1311
|
-
);
|
|
1312
|
-
|
|
1313
|
-
attribute_sources.push([geometry.topology, local]);
|
|
1314
|
-
}
|
|
1315
|
-
|
|
1316
|
-
remap[i] = target;
|
|
1317
|
-
});
|
|
1318
|
-
|
|
1319
|
-
const triangle_count = geometry.indices.length / 3;
|
|
1320
|
-
|
|
1321
|
-
const faces = [];
|
|
1322
|
-
|
|
1323
|
-
for (let i = 0; i < geometry.indices.length; i++) {
|
|
1324
|
-
indices.push(remap[geometry.indices[i]]);
|
|
1325
|
-
}
|
|
1326
|
-
|
|
1327
|
-
for (let i = 0; i < triangle_count; i++) {
|
|
1328
|
-
faces.push(face_cursor + i);
|
|
1329
|
-
}
|
|
1330
|
-
|
|
1331
|
-
face_cursor += triangle_count;
|
|
1332
|
-
|
|
1333
|
-
cluster_faces.push(faces);
|
|
1334
|
-
}
|
|
1335
|
-
|
|
1336
|
-
const topology = new BinaryTopology();
|
|
1337
|
-
|
|
1338
|
-
// Before the vertices: adding a layer widens every record already in the block, and a frontier
|
|
1339
|
-
// holds every vertex of a level.
|
|
1340
|
-
const layers = vgeo_layers_like(topology, template_layers);
|
|
1341
|
-
|
|
1342
|
-
bt_mesh_from_indexed_geometry(topology, Uint32Array.from(indices), Float32Array.from(positions));
|
|
1343
|
-
|
|
1344
|
-
attribute_sources.forEach(([source, local], target) => {
|
|
1345
|
-
vgeo_layers_copy_vertex(topology, source, local, target);
|
|
1346
|
-
});
|
|
1347
|
-
|
|
1348
|
-
const mesh = new VirtualGeometryMesh(topology, layers);
|
|
1349
|
-
|
|
1350
|
-
const source_vertex_ids = new Uint32Array(index_of_source.size);
|
|
1351
|
-
|
|
1352
|
-
for (const [source, target] of index_of_source) {
|
|
1353
|
-
source_vertex_ids[target] = source;
|
|
1354
|
-
}
|
|
1355
|
-
|
|
1356
|
-
return { mesh, cluster_faces, source_vertex_ids };
|
|
1357
|
-
}
|
|
1358
|
-
|
|
1359
|
-
/**
|
|
1360
|
-
* The frontier's clusters ordered by error, which is what a band is a slice of.
|
|
1361
|
-
*
|
|
1362
|
-
* @param {VGeoFrontier} frontier
|
|
1363
|
-
* @returns {{order: number[], errors: number[]}} cluster indices ascending by error, and their
|
|
1364
|
-
* errors in the same order
|
|
1365
|
-
*/
|
|
1366
|
-
function order_by_error(frontier) {
|
|
1367
|
-
const order = frontier.clusters.map((cluster, index) => index);
|
|
1368
|
-
|
|
1369
|
-
order.sort(
|
|
1370
|
-
(a, b) => (frontier.clusters[a].self_error - frontier.clusters[b].self_error) || (a - b)
|
|
1371
|
-
);
|
|
1372
|
-
|
|
1373
|
-
return { order, errors: order.map((index) => frontier.clusters[index].self_error) };
|
|
1374
|
-
}
|
|
4
|
+
quadric_attribute_size
|
|
5
|
+
} from "../../../../../../core/geom/3d/quadric/quadric_attribute_size.js";
|
|
6
|
+
import { VirtualGeometryGroup } from "../../format/dag/VirtualGeometryGroup.js";
|
|
7
|
+
import { vgeo_gather_faces } from "../submesh/vgeo_gather_faces.js";
|
|
8
|
+
import { VGeoFrontier } from "./VGeoFrontier.js";
|
|
9
|
+
import { VGeoLevel } from "./VGeoLevel.js";
|
|
10
|
+
import { vgeo_build_leaf_frontier } from "./vgeo_build_leaf_frontier.js";
|
|
11
|
+
import { vgeo_run_round } from "./vgeo_run_round.js";
|
|
12
|
+
import { vgeo_stitch_frontier } from "./vgeo_stitch_frontier.js";
|
|
1375
13
|
|
|
1376
14
|
/**
|
|
1377
|
-
*
|
|
15
|
+
* Build the cluster DAG: leaves out of the source mesh, then merge steps until one cluster is left.
|
|
1378
16
|
*
|
|
1379
|
-
*
|
|
1380
|
-
*
|
|
1381
|
-
* group
|
|
1382
|
-
*
|
|
1383
|
-
*
|
|
17
|
+
* The unit of work is a **round**, not a level sweep. A round holds a *frontier* — every cluster that
|
|
18
|
+
* nothing has merged yet — selects a band of it by error, groups the band, locks every vertex the
|
|
19
|
+
* group shares with anything outside it, halves the group's interior, and re-cuts the result into
|
|
20
|
+
* new clusters, which become the parents of every cluster in the group. Locked perimeters plus a
|
|
21
|
+
* group-uniform metric is the whole reason independent per-cluster LOD decisions come out
|
|
22
|
+
* crack-free.
|
|
1384
23
|
*
|
|
1385
|
-
* The
|
|
1386
|
-
*
|
|
1387
|
-
*
|
|
24
|
+
* **The band is why this is not a level sweep.** Sweeping whole levels pushes every cluster up one
|
|
25
|
+
* step per level whatever its error, so a region that is nearly exact is coarsened in lockstep with
|
|
26
|
+
* one that is not, and a level ends up spanning an order of magnitude of error. The cut the runtime
|
|
27
|
+
* takes is a threshold on error, so a level that spans an order of magnitude is a page band that
|
|
28
|
+
* spans one too: installing it brings clusters the cut does not want, and the ones it does want are
|
|
29
|
+
* scattered across bands. Selecting the lowest-error band instead lets a fine region catch up before
|
|
30
|
+
* the coarse one moves, and bands come out error-uniform. On geometry whose error is already uniform
|
|
31
|
+
* the band is the whole frontier and this degenerates to the sweep, which is the correct behaviour
|
|
32
|
+
* for that case rather than a special case for it.
|
|
1388
33
|
*
|
|
1389
|
-
*
|
|
1390
|
-
*
|
|
1391
|
-
*
|
|
1392
|
-
* @returns {{start: number, end: number}[]} half-open ranges into the error order, deduplicated
|
|
1393
|
-
*/
|
|
1394
|
-
function error_bands(errors, growth, min_fraction) {
|
|
1395
|
-
/**
|
|
1396
|
-
* @param {number} value
|
|
1397
|
-
* @returns {number} first position whose error exceeds `value`
|
|
1398
|
-
*/
|
|
1399
|
-
function upper_bound(value) {
|
|
1400
|
-
let low = 0;
|
|
1401
|
-
let high = errors.length;
|
|
1402
|
-
|
|
1403
|
-
while (low < high) {
|
|
1404
|
-
const middle = (low + high) >> 1;
|
|
1405
|
-
|
|
1406
|
-
if (errors[middle] <= value) {
|
|
1407
|
-
low = middle + 1;
|
|
1408
|
-
} else {
|
|
1409
|
-
high = middle;
|
|
1410
|
-
}
|
|
1411
|
-
}
|
|
1412
|
-
|
|
1413
|
-
return low;
|
|
1414
|
-
}
|
|
1415
|
-
|
|
1416
|
-
const result = [];
|
|
1417
|
-
const seen = new Set();
|
|
1418
|
-
|
|
1419
|
-
/**
|
|
1420
|
-
* @param {number} start
|
|
1421
|
-
* @param {number} end
|
|
1422
|
-
*/
|
|
1423
|
-
function add(start, end) {
|
|
1424
|
-
const key = `${start}:${end}`;
|
|
1425
|
-
|
|
1426
|
-
if (end - start < 2 || seen.has(key)) {
|
|
1427
|
-
return;
|
|
1428
|
-
}
|
|
1429
|
-
|
|
1430
|
-
seen.add(key);
|
|
1431
|
-
|
|
1432
|
-
result.push({ start, end });
|
|
1433
|
-
}
|
|
1434
|
-
|
|
1435
|
-
const minimum = Math.max(2, Math.ceil(errors.length * min_fraction));
|
|
1436
|
-
|
|
1437
|
-
let cursor = 0;
|
|
1438
|
-
|
|
1439
|
-
while (cursor < errors.length) {
|
|
1440
|
-
const low = errors[cursor];
|
|
1441
|
-
|
|
1442
|
-
add(cursor, Math.max(upper_bound(low * growth), Math.min(errors.length, cursor + minimum)));
|
|
1443
|
-
|
|
1444
|
-
cursor = upper_bound(low);
|
|
1445
|
-
}
|
|
1446
|
-
|
|
1447
|
-
add(0, errors.length);
|
|
1448
|
-
|
|
1449
|
-
return result;
|
|
1450
|
-
}
|
|
1451
|
-
|
|
1452
|
-
/**
|
|
1453
|
-
* One round: pick a band, group it, simplify each group, and report what it produced.
|
|
34
|
+
* **A cluster the round cannot use simply stays.** It is not pushed up for the sake of symmetry, and
|
|
35
|
+
* it does not stall the round for anyone else — which is what lets a lone island wait, untouched and
|
|
36
|
+
* at zero error, until the geometry it will eventually merge with has come down to meet it.
|
|
1454
37
|
*
|
|
1455
|
-
*
|
|
1456
|
-
*
|
|
1457
|
-
*
|
|
1458
|
-
*
|
|
1459
|
-
*
|
|
38
|
+
* **Error is conformance to the source, not a sum of steps.** Vertex quadrics are built once, from
|
|
39
|
+
* the source triangles, and carried through every round: a survivor absorbs its victim's quadric at
|
|
40
|
+
* every collapse, so a vertex twelve rounds up still holds the planes of every source triangle
|
|
41
|
+
* folded into it. Evaluating that at its current position measures the deviation from what the
|
|
42
|
+
* artist authored, directly, and normalizing by the quadric's accumulated weight makes it an
|
|
43
|
+
* object-space length rather than a cost whose scale depends on how many planes it happens to hold.
|
|
44
|
+
* That length is what the runtime projects into pixels.
|
|
1460
45
|
*
|
|
1461
|
-
*
|
|
1462
|
-
*
|
|
1463
|
-
*
|
|
46
|
+
* **Termination is convergence to one root, not a stall.** Groups bundle across connected components
|
|
47
|
+
* once connectivity runs out, and a group small enough to fit one cluster is merged losslessly, so a
|
|
48
|
+
* frontier of separate islands still comes together. A group whose re-cut wants as many clusters as
|
|
49
|
+
* it consumed is simplified further rather than dropped — a cluster is bounded by its vertices as
|
|
50
|
+
* well as by its triangles, and on geometry that has come apart it is the vertices that bind, so a
|
|
51
|
+
* triangle target alone cannot say whether the result will fit. A build that cannot reach a single
|
|
52
|
+
* root throws: the runtime starts its cut at the root, and "several roots" is not a shape it can
|
|
53
|
+
* start from.
|
|
54
|
+
*
|
|
55
|
+
* The round itself is `vgeo_run_round`: it builds the cluster graph (`vgeo_build_cluster_graph`),
|
|
56
|
+
* slices the frontier into error octaves (`vgeo_error_bands`), groups a band
|
|
57
|
+
* (`vgeo_partition_into_groups`), and asks each group for a re-cut (`vgeo_try_repack`) or a
|
|
58
|
+
* simplification (`vgeo_build_group_step`, which locks the perimeter through
|
|
59
|
+
* `vgeo_compute_group_boundary`). What is left here is what a round cannot do on its own: the leaf
|
|
60
|
+
* frontier it starts from (`vgeo_build_leaf_frontier`), committing a round's output into levels and
|
|
61
|
+
* groups, folding the quadric gains back, and stitching the next frontier (`vgeo_stitch_frontier`).
|
|
1464
62
|
*
|
|
1465
|
-
* @
|
|
1466
|
-
* @
|
|
1467
|
-
* @param {VGeoLevelOptions} options
|
|
1468
|
-
* @returns {{steps: GroupStep[], repacks: GroupRepack[], merged: Set<number>}|undefined} what to
|
|
1469
|
-
* commit and which frontier clusters it consumed; `undefined` when nothing this round could try
|
|
1470
|
-
* made the frontier smaller
|
|
63
|
+
* @author Alex Goldring
|
|
64
|
+
* @copyright Company Named Limited (c) 2026
|
|
1471
65
|
*/
|
|
1472
|
-
function run_round(frontier, level, options) {
|
|
1473
|
-
const face_graph = new VGeoFaceGraph(
|
|
1474
|
-
frontier.mesh.topology, bt_mesh_build_face_graph(frontier.mesh.topology)
|
|
1475
|
-
);
|
|
1476
|
-
|
|
1477
|
-
const cluster_graph = build_cluster_graph(frontier, face_graph);
|
|
1478
|
-
|
|
1479
|
-
const ordered = order_by_error(frontier);
|
|
1480
|
-
|
|
1481
|
-
const bands = error_bands(
|
|
1482
|
-
ordered.errors, options.error_band_growth, options.error_band_min_fraction
|
|
1483
|
-
);
|
|
1484
|
-
|
|
1485
|
-
let group_size = options.group_size;
|
|
1486
|
-
|
|
1487
|
-
for (; ;) {
|
|
1488
|
-
for (const range of bands) {
|
|
1489
|
-
const band = ordered.order.slice(range.start, range.end);
|
|
1490
|
-
|
|
1491
|
-
band.sort((a, b) => a - b);
|
|
1492
|
-
|
|
1493
|
-
const groups = partition_into_groups(frontier, band, cluster_graph, group_size);
|
|
1494
|
-
|
|
1495
|
-
if (groups.length === 0) {
|
|
1496
|
-
continue;
|
|
1497
|
-
}
|
|
1498
|
-
|
|
1499
|
-
const group_of_face = new Int32Array(frontier.mesh.topology.faces.size).fill(-1);
|
|
1500
|
-
|
|
1501
|
-
groups.forEach((group, group_index) => {
|
|
1502
|
-
for (const cluster of group) {
|
|
1503
|
-
for (const face of frontier.cluster_faces[cluster]) {
|
|
1504
|
-
group_of_face[face] = group_index;
|
|
1505
|
-
}
|
|
1506
|
-
}
|
|
1507
|
-
});
|
|
1508
|
-
|
|
1509
|
-
const steps = [];
|
|
1510
|
-
const repacks = [];
|
|
1511
|
-
|
|
1512
|
-
let consumed = 0;
|
|
1513
|
-
let produced = 0;
|
|
1514
|
-
|
|
1515
|
-
const merged = new Set();
|
|
1516
|
-
|
|
1517
|
-
groups.forEach((group, group_index) => {
|
|
1518
|
-
const parent_target = Math.max(
|
|
1519
|
-
1, Math.ceil(group.length * options.simplification_ratio)
|
|
1520
|
-
);
|
|
1521
|
-
|
|
1522
|
-
const repack = try_repack(frontier, group, parent_target);
|
|
1523
|
-
|
|
1524
|
-
if (repack !== undefined) {
|
|
1525
|
-
repacks.push(repack);
|
|
1526
|
-
|
|
1527
|
-
consumed += group.length;
|
|
1528
|
-
produced += repack.clusters.length;
|
|
1529
|
-
|
|
1530
|
-
for (const cluster of group) {
|
|
1531
|
-
merged.add(cluster);
|
|
1532
|
-
}
|
|
1533
|
-
|
|
1534
|
-
return;
|
|
1535
|
-
}
|
|
1536
|
-
|
|
1537
|
-
const step = build_group_step(
|
|
1538
|
-
frontier, group, group_of_face, group_index, level, options
|
|
1539
|
-
);
|
|
1540
|
-
|
|
1541
|
-
if (step === undefined || step.parent_clusters.length >= group.length) {
|
|
1542
|
-
// Refused. Nothing has been mutated — the step worked on a copy of the geometry
|
|
1543
|
-
// and a copy of the quadrics — so its clusters simply stay where they are.
|
|
1544
|
-
return;
|
|
1545
|
-
}
|
|
1546
|
-
|
|
1547
|
-
steps.push(step);
|
|
1548
|
-
|
|
1549
|
-
consumed += group.length;
|
|
1550
|
-
produced += step.parent_clusters.length;
|
|
1551
|
-
|
|
1552
|
-
for (const cluster of group) {
|
|
1553
|
-
merged.add(cluster);
|
|
1554
|
-
}
|
|
1555
|
-
});
|
|
1556
|
-
|
|
1557
|
-
if (produced < consumed) {
|
|
1558
|
-
return { steps, repacks, merged };
|
|
1559
|
-
}
|
|
1560
|
-
}
|
|
1561
|
-
|
|
1562
|
-
// No band made progress. Growing the group gives the simplifier more interior to work with
|
|
1563
|
-
// relative to its locked perimeter, which is the thing that stalls first.
|
|
1564
|
-
if (group_size >= frontier.clusters.length) {
|
|
1565
|
-
return undefined;
|
|
1566
|
-
}
|
|
1567
|
-
|
|
1568
|
-
group_size = Math.min(frontier.clusters.length, group_size * 2);
|
|
1569
|
-
}
|
|
1570
|
-
}
|
|
1571
66
|
|
|
1572
67
|
const scratch_quadric = new Quadric3();
|
|
1573
68
|
|
|
@@ -1576,9 +71,10 @@ const scratch_quadric = new Quadric3();
|
|
|
1576
71
|
*
|
|
1577
72
|
* @param {VirtualGeometryMesh} mesh the normalized source; consumed, not copied
|
|
1578
73
|
* @param {VGeoLevelOptions} options
|
|
74
|
+
* @param {Float64Array} [attribute_weights] see {@link vgeo_build_leaf_frontier}, which defaults it
|
|
1579
75
|
* @returns {VGeoLevel[]} leaves first; the last level holds exactly one cluster, the root
|
|
1580
76
|
*/
|
|
1581
|
-
export function vgeo_build_levels(mesh, options) {
|
|
77
|
+
export function vgeo_build_levels(mesh, options, attribute_weights) {
|
|
1582
78
|
assert.isPositiveInteger(options.group_size, 'options.group_size');
|
|
1583
79
|
assert.greaterThan(options.group_size, 1, 'options.group_size');
|
|
1584
80
|
assert.greaterThan(options.simplification_ratio, 0, 'options.simplification_ratio');
|
|
@@ -1588,7 +84,7 @@ export function vgeo_build_levels(mesh, options) {
|
|
|
1588
84
|
assert.lessThanOrEqual(options.error_band_min_fraction, 1, 'options.error_band_min_fraction');
|
|
1589
85
|
assert.isPositiveInteger(options.max_levels, 'options.max_levels');
|
|
1590
86
|
|
|
1591
|
-
let frontier =
|
|
87
|
+
let frontier = vgeo_build_leaf_frontier(mesh, attribute_weights);
|
|
1592
88
|
|
|
1593
89
|
const leaves = new VGeoLevel();
|
|
1594
90
|
|
|
@@ -1597,17 +93,22 @@ export function vgeo_build_levels(mesh, options) {
|
|
|
1597
93
|
|
|
1598
94
|
const levels = [leaves];
|
|
1599
95
|
|
|
96
|
+
// Constant for the whole build: the channel set is the source's, and the weights were resolved
|
|
97
|
+
// off its bounds before any pass had removed anything.
|
|
98
|
+
const attribute_layout = frontier.attribute_layout;
|
|
99
|
+
const resolved_attribute_weights = frontier.attribute_weights;
|
|
100
|
+
|
|
1600
101
|
while (frontier.clusters.length > 1) {
|
|
1601
102
|
if (levels.length >= options.max_levels) {
|
|
1602
103
|
throw new Error(
|
|
1603
104
|
`the DAG still holds ${frontier.clusters.length} clusters after ${levels.length} `
|
|
1604
|
-
+ `levels, which is as
|
|
105
|
+
+ `levels, which is as deep as a build is allowed to go`
|
|
1605
106
|
);
|
|
1606
107
|
}
|
|
1607
108
|
|
|
1608
109
|
const level = levels.length;
|
|
1609
110
|
|
|
1610
|
-
const round =
|
|
111
|
+
const round = vgeo_run_round(frontier, level, options);
|
|
1611
112
|
|
|
1612
113
|
if (round === undefined) {
|
|
1613
114
|
throw new Error(
|
|
@@ -1636,7 +137,9 @@ export function vgeo_build_levels(mesh, options) {
|
|
|
1636
137
|
return;
|
|
1637
138
|
}
|
|
1638
139
|
|
|
1639
|
-
const gathered = vgeo_gather_faces(
|
|
140
|
+
const gathered = vgeo_gather_faces(
|
|
141
|
+
frontier.mesh, frontier.cluster_faces[index], frontier.wedges
|
|
142
|
+
);
|
|
1640
143
|
|
|
1641
144
|
clusters.push(cluster);
|
|
1642
145
|
|
|
@@ -1645,6 +148,10 @@ export function vgeo_build_levels(mesh, options) {
|
|
|
1645
148
|
indices: gathered.indices,
|
|
1646
149
|
local_vertex_ids: gathered.vertex_ids,
|
|
1647
150
|
source_vertex_ids: gathered.vertex_ids,
|
|
151
|
+
attribute_ids: gathered.attribute_ids,
|
|
152
|
+
// Already the frontier's own ids: an untouched cluster is named in the space it is
|
|
153
|
+
// being stitched out of.
|
|
154
|
+
source_wedge_ids: gathered.wedge_ids,
|
|
1648
155
|
topology: frontier.mesh.topology,
|
|
1649
156
|
});
|
|
1650
157
|
});
|
|
@@ -1710,6 +217,15 @@ export function vgeo_build_levels(mesh, options) {
|
|
|
1710
217
|
// source planes under that vertex twice and inflate every error above it.
|
|
1711
218
|
const updated = frontier.quadrics.slice();
|
|
1712
219
|
|
|
220
|
+
const attribute_stride = frontier.attribute_layout.components > 0
|
|
221
|
+
? quadric_attribute_size(frontier.attribute_layout.components)
|
|
222
|
+
: 0;
|
|
223
|
+
|
|
224
|
+
// One copy of the whole set, then gains added into it in place. The attribute quadrics are
|
|
225
|
+
// one flat buffer rather than an object each, so this is a single `slice` where the
|
|
226
|
+
// geometric set above is an array of references and a `clone` per vertex that moved.
|
|
227
|
+
const updated_attributes = frontier.attribute_quadrics.slice();
|
|
228
|
+
|
|
1713
229
|
for (const step of round.steps) {
|
|
1714
230
|
for (const [vertex, quadric] of step.quadrics) {
|
|
1715
231
|
|
|
@@ -1722,9 +238,26 @@ export function vgeo_build_levels(mesh, options) {
|
|
|
1722
238
|
updated[vertex] = next;
|
|
1723
239
|
|
|
1724
240
|
}
|
|
241
|
+
|
|
242
|
+
step.attribute_source_wedge_ids.forEach((wedge, local) => {
|
|
243
|
+
// `live_wedges` is what says the wedge survived the step; the attribute buffer is
|
|
244
|
+
// handed over whole and has an entry for every slot the submesh ever had. A wedge
|
|
245
|
+
// that merged into another gave that one its quadric and has nothing left to fold.
|
|
246
|
+
if (!step.live_wedges.has(local)) {
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const base = wedge * attribute_stride;
|
|
251
|
+
const from = local * attribute_stride;
|
|
252
|
+
|
|
253
|
+
for (let i = 0; i < attribute_stride; i++) {
|
|
254
|
+
updated_attributes[base + i] +=
|
|
255
|
+
step.attribute_quadrics[from + i] - frontier.attribute_quadrics[base + i];
|
|
256
|
+
}
|
|
257
|
+
});
|
|
1725
258
|
}
|
|
1726
259
|
|
|
1727
|
-
const stitched =
|
|
260
|
+
const stitched = vgeo_stitch_frontier(contributions, frontier.mesh.layers);
|
|
1728
261
|
|
|
1729
262
|
frontier = new VGeoFrontier();
|
|
1730
263
|
|
|
@@ -1734,6 +267,29 @@ export function vgeo_build_levels(mesh, options) {
|
|
|
1734
267
|
frontier.quadrics = Array.from(
|
|
1735
268
|
stitched.source_vertex_ids, (source) => updated[source]
|
|
1736
269
|
);
|
|
270
|
+
|
|
271
|
+
frontier.attribute_layout = attribute_layout;
|
|
272
|
+
frontier.attribute_weights = resolved_attribute_weights;
|
|
273
|
+
frontier.wedges = stitched.wedges;
|
|
274
|
+
|
|
275
|
+
if (attribute_stride > 0) {
|
|
276
|
+
// Re-indexed by *wedge*, which the stitch has just handed out fresh ids for, exactly as
|
|
277
|
+
// the geometric set above is re-indexed by vertex.
|
|
278
|
+
const next_attributes = new Float64Array(
|
|
279
|
+
stitched.source_wedge_ids.length * attribute_stride
|
|
280
|
+
);
|
|
281
|
+
|
|
282
|
+
stitched.source_wedge_ids.forEach((source, target) => {
|
|
283
|
+
next_attributes.set(
|
|
284
|
+
updated_attributes.subarray(
|
|
285
|
+
source * attribute_stride, (source + 1) * attribute_stride
|
|
286
|
+
),
|
|
287
|
+
target * attribute_stride
|
|
288
|
+
);
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
frontier.attribute_quadrics = next_attributes;
|
|
292
|
+
}
|
|
1737
293
|
}
|
|
1738
294
|
|
|
1739
295
|
const top = levels[levels.length - 1];
|