@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.
Files changed (685) hide show
  1. package/build/bundle-worker-image-decoder.js +1 -1
  2. package/build/bundle-worker-terrain.js +1 -1
  3. package/package.json +1 -1
  4. package/src/core/binary/BinaryBuffer.d.ts.map +1 -1
  5. package/src/core/binary/BinaryBuffer.js +14 -2
  6. package/src/core/binary/BitSet.d.ts +16 -0
  7. package/src/core/binary/BitSet.d.ts.map +1 -1
  8. package/src/core/binary/BitSet.js +159 -49
  9. package/src/core/binary/allocator/OffsetAllocator.d.ts +48 -0
  10. package/src/core/binary/allocator/OffsetAllocator.d.ts.map +1 -1
  11. package/src/core/binary/allocator/OffsetAllocator.js +208 -9
  12. package/src/core/binary/compression/compress_bytes.d.ts +20 -0
  13. package/src/core/binary/compression/compress_bytes.d.ts.map +1 -0
  14. package/src/core/binary/compression/compress_bytes.js +35 -0
  15. package/src/core/binary/compression/decompress_bytes_into.d.ts +30 -0
  16. package/src/core/binary/compression/decompress_bytes_into.d.ts.map +1 -0
  17. package/src/core/binary/compression/decompress_bytes_into.js +87 -0
  18. package/src/core/binary/compression/deflate_decompress_bound.d.ts +27 -0
  19. package/src/core/binary/compression/deflate_decompress_bound.d.ts.map +1 -0
  20. package/src/core/binary/compression/deflate_decompress_bound.js +32 -0
  21. package/src/core/bvh2/bvh3/BVH.d.ts.map +1 -1
  22. package/src/core/bvh2/bvh3/BVH.js +14 -1
  23. package/src/core/cache/FrequencySketch.d.ts +3 -1
  24. package/src/core/cache/FrequencySketch.d.ts.map +1 -1
  25. package/src/core/cache/FrequencySketch.js +10 -4
  26. package/src/core/cache/wtinylfu/CacheWTinylfu.d.ts.map +1 -1
  27. package/src/core/cache/wtinylfu/CacheWTinylfu.js +24 -2
  28. package/src/core/geom/3d/quadric/quadric_attribute_add.d.ts +24 -0
  29. package/src/core/geom/3d/quadric/quadric_attribute_add.d.ts.map +1 -0
  30. package/src/core/geom/3d/quadric/quadric_attribute_add.js +31 -0
  31. package/src/core/geom/3d/quadric/quadric_attribute_reduce.d.ts +46 -0
  32. package/src/core/geom/3d/quadric/quadric_attribute_reduce.d.ts.map +1 -0
  33. package/src/core/geom/3d/quadric/quadric_attribute_reduce.js +104 -0
  34. package/src/core/geom/3d/quadric/quadric_attribute_set_from_triangle.d.ts +62 -0
  35. package/src/core/geom/3d/quadric/quadric_attribute_set_from_triangle.d.ts.map +1 -0
  36. package/src/core/geom/3d/quadric/quadric_attribute_set_from_triangle.js +158 -0
  37. package/src/core/geom/3d/quadric/quadric_attribute_size.d.ts +21 -0
  38. package/src/core/geom/3d/quadric/quadric_attribute_size.d.ts.map +1 -0
  39. package/src/core/geom/3d/quadric/quadric_attribute_size.js +24 -0
  40. package/src/core/geom/3d/quadric/quadric_attribute_solve.d.ts +34 -0
  41. package/src/core/geom/3d/quadric/quadric_attribute_solve.d.ts.map +1 -0
  42. package/src/core/geom/3d/quadric/quadric_attribute_solve.js +55 -0
  43. package/src/core/geom/3d/quadric/quadric_attribute_sub.d.ts +28 -0
  44. package/src/core/geom/3d/quadric/quadric_attribute_sub.d.ts.map +1 -0
  45. package/src/core/geom/3d/quadric/quadric_attribute_sub.js +35 -0
  46. package/src/core/geom/3d/topology/struct/binary/BinaryDataBlock.d.ts +15 -0
  47. package/src/core/geom/3d/topology/struct/binary/BinaryDataBlock.d.ts.map +1 -1
  48. package/src/core/geom/3d/topology/struct/binary/BinaryDataBlock.js +35 -0
  49. package/src/core/geom/3d/topology/struct/binary/BtWedgeSet.d.ts +93 -0
  50. package/src/core/geom/3d/topology/struct/binary/BtWedgeSet.d.ts.map +1 -0
  51. package/src/core/geom/3d/topology/struct/binary/BtWedgeSet.js +128 -0
  52. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_compute_vertex_quadrics.d.ts +20 -0
  53. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_compute_vertex_quadrics.d.ts.map +1 -1
  54. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_compute_vertex_quadrics.js +69 -4
  55. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_compute_wedge_attribute_quadrics.d.ts +38 -0
  56. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_compute_wedge_attribute_quadrics.d.ts.map +1 -0
  57. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_compute_wedge_attribute_quadrics.js +113 -0
  58. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_simplify.d.ts +93 -17
  59. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_simplify.d.ts.map +1 -1
  60. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_simplify.js +930 -69
  61. package/src/core/geom/3d/topology/struct/binary/io/bt_wedges_live_count.d.ts +22 -0
  62. package/src/core/geom/3d/topology/struct/binary/io/bt_wedges_live_count.d.ts.map +1 -0
  63. package/src/core/geom/3d/topology/struct/binary/io/bt_wedges_live_count.js +46 -0
  64. package/src/core/geom/3d/topology/struct/binary/io/bt_wedges_per_vertex.d.ts +19 -0
  65. package/src/core/geom/3d/topology/struct/binary/io/bt_wedges_per_vertex.d.ts.map +1 -0
  66. package/src/core/geom/3d/topology/struct/binary/io/bt_wedges_per_vertex.js +49 -0
  67. package/src/core/geom/3d/topology/struct/binary/query/bt_edge_collapse_fold_limit.d.ts +25 -0
  68. package/src/core/geom/3d/topology/struct/binary/query/bt_edge_collapse_fold_limit.d.ts.map +1 -0
  69. package/src/core/geom/3d/topology/struct/binary/query/bt_edge_collapse_fold_limit.js +228 -0
  70. package/src/core/geom/3d/topology/struct/binary/query/bt_edge_collapse_would_fold.d.ts +13 -0
  71. package/src/core/geom/3d/topology/struct/binary/query/bt_edge_collapse_would_fold.d.ts.map +1 -1
  72. package/src/core/geom/3d/topology/struct/binary/query/bt_edge_collapse_would_fold.js +10 -128
  73. package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_build_face_bvh.d.ts.map +1 -1
  74. package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_build_face_bvh.js +11 -1
  75. package/src/core/graph/layout/layered/BoxLayoutOptions.d.ts +63 -0
  76. package/src/core/graph/layout/layered/BoxLayoutOptions.d.ts.map +1 -0
  77. package/src/core/graph/layout/layered/BoxLayoutOptions.js +80 -0
  78. package/src/core/{model/node-graph/visual/layout → graph/layout/layered}/LayoutBoxGraph.d.ts +35 -0
  79. package/src/core/graph/layout/layered/LayoutBoxGraph.d.ts.map +1 -0
  80. package/src/core/{model/node-graph/visual/layout → graph/layout/layered}/LayoutBoxGraph.js +53 -1
  81. package/src/core/graph/layout/layered/layout_assign_coordinates.d.ts.map +1 -0
  82. package/src/core/{model/node-graph/visual/layout → graph/layout/layered}/layout_assign_coordinates.js +72 -68
  83. package/src/core/graph/layout/layered/layout_assign_layers.d.ts.map +1 -0
  84. package/src/core/{model/node-graph/visual/layout → graph/layout/layered}/layout_assign_layers.js +4 -2
  85. package/src/core/graph/layout/layered/layout_break_cycles.d.ts.map +1 -0
  86. package/src/core/graph/layout/layered/layout_insert_lanes.d.ts.map +1 -0
  87. package/src/core/graph/layout/layered/layout_order_layers.d.ts.map +1 -0
  88. package/src/core/graph/layout/layered/layout_orient_links.d.ts.map +1 -0
  89. package/src/core/graph/mn_graph_collapse_weighted_edge.js +6 -6
  90. package/src/core/math/lookup/ParameterLookupTable.d.ts.map +1 -1
  91. package/src/core/math/lookup/ParameterLookupTable.js +10 -5
  92. package/src/core/math/noise/sdnoise.d.ts.map +1 -1
  93. package/src/core/math/noise/sdnoise.js +16 -10
  94. package/src/core/model/node-graph/visual/layout/NodeGraphLayoutOptions.d.ts +7 -51
  95. package/src/core/model/node-graph/visual/layout/NodeGraphLayoutOptions.d.ts.map +1 -1
  96. package/src/core/model/node-graph/visual/layout/NodeGraphLayoutOptions.js +8 -69
  97. package/src/core/model/node-graph/visual/layout/layout_build_problem.js +1 -1
  98. package/src/core/model/node-graph/visual/layout/layout_node_graph.js +6 -6
  99. package/src/core/model/node-graph/visual/layout/route_grid_build.js +1 -1
  100. package/src/core/model/object/ImmutableObjectPool.d.ts.map +1 -1
  101. package/src/core/model/object/ImmutableObjectPool.js +17 -7
  102. package/src/core/process/executor/ConcurrentExecutor.d.ts +1 -5
  103. package/src/core/process/executor/ConcurrentExecutor.d.ts.map +1 -1
  104. package/src/core/process/executor/ConcurrentExecutor.js +95 -1
  105. package/src/core/process/task/task_find_dependency_cycle.d.ts +17 -0
  106. package/src/core/process/task/task_find_dependency_cycle.d.ts.map +1 -0
  107. package/src/core/process/task/task_find_dependency_cycle.js +81 -0
  108. package/src/core/wfc/WFCCellChoice.d.ts +17 -0
  109. package/src/core/wfc/WFCCellChoice.d.ts.map +1 -0
  110. package/src/core/wfc/WFCCellChoice.js +36 -0
  111. package/src/core/wfc/WFCLattice.d.ts +105 -0
  112. package/src/core/wfc/WFCLattice.d.ts.map +1 -0
  113. package/src/core/wfc/WFCLattice.js +235 -0
  114. package/src/core/wfc/WFCRuleTable.d.ts +172 -0
  115. package/src/core/wfc/WFCRuleTable.d.ts.map +1 -0
  116. package/src/core/wfc/WFCRuleTable.js +334 -0
  117. package/src/core/wfc/WFCState.d.ts +273 -0
  118. package/src/core/wfc/WFCState.d.ts.map +1 -0
  119. package/src/core/wfc/WFCState.js +552 -0
  120. package/src/core/wfc/WFCStatus.d.ts +11 -0
  121. package/src/core/wfc/WFCStatus.d.ts.map +1 -0
  122. package/src/core/wfc/WFCStatus.js +28 -0
  123. package/src/core/wfc/WFCTileWeights.d.ts +48 -0
  124. package/src/core/wfc/WFCTileWeights.d.ts.map +1 -0
  125. package/src/core/wfc/WFCTileWeights.js +86 -0
  126. package/src/core/wfc/WFC_UNRESOLVED.d.ts +12 -0
  127. package/src/core/wfc/WFC_UNRESOLVED.d.ts.map +1 -0
  128. package/src/core/wfc/WFC_UNRESOLVED.js +11 -0
  129. package/src/core/wfc/wfc_estimate_bytes.d.ts +24 -0
  130. package/src/core/wfc/wfc_estimate_bytes.d.ts.map +1 -0
  131. package/src/core/wfc/wfc_estimate_bytes.js +47 -0
  132. package/src/core/wfc/wfc_rule_table_tile_components.d.ts +27 -0
  133. package/src/core/wfc/wfc_rule_table_tile_components.d.ts.map +1 -0
  134. package/src/core/wfc/wfc_rule_table_tile_components.js +80 -0
  135. package/src/core/wfc/wfc_solve.d.ts +21 -0
  136. package/src/core/wfc/wfc_solve.d.ts.map +1 -0
  137. package/src/core/wfc/wfc_solve.js +154 -0
  138. package/src/core/wfc/wfc_state_apply_boundary.d.ts +18 -0
  139. package/src/core/wfc/wfc_state_apply_boundary.d.ts.map +1 -0
  140. package/src/core/wfc/wfc_state_apply_boundary.js +61 -0
  141. package/src/core/wfc/wfc_state_assign.d.ts +11 -0
  142. package/src/core/wfc/wfc_state_assign.d.ts.map +1 -0
  143. package/src/core/wfc/wfc_state_assign.js +36 -0
  144. package/src/core/wfc/wfc_state_ban_bits.d.ts +17 -0
  145. package/src/core/wfc/wfc_state_ban_bits.d.ts.map +1 -0
  146. package/src/core/wfc/wfc_state_ban_bits.js +81 -0
  147. package/src/core/wfc/wfc_state_collapse.d.ts +12 -0
  148. package/src/core/wfc/wfc_state_collapse.d.ts.map +1 -0
  149. package/src/core/wfc/wfc_state_collapse.js +73 -0
  150. package/src/core/wfc/wfc_state_flush_dirty.d.ts +12 -0
  151. package/src/core/wfc/wfc_state_flush_dirty.d.ts.map +1 -0
  152. package/src/core/wfc/wfc_state_flush_dirty.js +39 -0
  153. package/src/core/wfc/wfc_state_propagate.d.ts +14 -0
  154. package/src/core/wfc/wfc_state_propagate.d.ts.map +1 -0
  155. package/src/core/wfc/wfc_state_propagate.js +155 -0
  156. package/src/core/wfc/wfc_state_read.d.ts +11 -0
  157. package/src/core/wfc/wfc_state_read.d.ts.map +1 -0
  158. package/src/core/wfc/wfc_state_read.js +38 -0
  159. package/src/core/wfc/wfc_state_restrict.d.ts +18 -0
  160. package/src/core/wfc/wfc_state_restrict.d.ts.map +1 -0
  161. package/src/core/wfc/wfc_state_restrict.js +39 -0
  162. package/src/core/wfc/wfc_state_set_eligible.d.ts +24 -0
  163. package/src/core/wfc/wfc_state_set_eligible.d.ts.map +1 -0
  164. package/src/core/wfc/wfc_state_set_eligible.js +49 -0
  165. package/src/core/wfc/wfc_state_undo_to.d.ts +13 -0
  166. package/src/core/wfc/wfc_state_undo_to.d.ts.map +1 -0
  167. package/src/core/wfc/wfc_state_undo_to.js +84 -0
  168. package/src/core/wfc/wfc_words_per_cell.d.ts +11 -0
  169. package/src/core/wfc/wfc_words_per_cell.d.ts.map +1 -0
  170. package/src/core/wfc/wfc_words_per_cell.js +16 -0
  171. package/src/engine/ecs/EntityComponentDataset.d.ts +8 -0
  172. package/src/engine/ecs/EntityComponentDataset.d.ts.map +1 -1
  173. package/src/engine/ecs/EntityComponentDataset.js +37 -14
  174. package/src/engine/ecs/fow/FogOfWarRevealerSystem.d.ts +8 -1
  175. package/src/engine/ecs/fow/FogOfWarRevealerSystem.d.ts.map +1 -1
  176. package/src/engine/ecs/fow/FogOfWarRevealerSystem.js +37 -2
  177. package/src/engine/ecs/guid/UUID.d.ts.map +1 -1
  178. package/src/engine/ecs/guid/UUID.js +45 -17
  179. package/src/engine/ecs/ik/OneBoneSurfaceAlignmentSolver.d.ts.map +1 -1
  180. package/src/engine/ecs/ik/OneBoneSurfaceAlignmentSolver.js +3 -1
  181. package/src/engine/ecs/terrain/ecs/cling/ClingToTerrainSystem.d.ts.map +1 -1
  182. package/src/engine/ecs/terrain/ecs/cling/ClingToTerrainSystem.js +366 -360
  183. package/src/engine/graphics/texture/sampler/resize/sampler2d_scale_down_linear.d.ts.map +1 -1
  184. package/src/engine/graphics/texture/sampler/resize/sampler2d_scale_down_linear.js +24 -10
  185. package/src/engine/graphics3/DebugDrawSystem.d.ts +7 -5
  186. package/src/engine/graphics3/DebugDrawSystem.d.ts.map +1 -1
  187. package/src/engine/graphics3/DebugDrawSystem.js +206 -203
  188. package/src/engine/graphics3/DecalSystem.d.ts +4 -5
  189. package/src/engine/graphics3/DecalSystem.d.ts.map +1 -1
  190. package/src/engine/graphics3/DecalSystem.js +5 -5
  191. package/src/engine/graphics3/FogOfWarSystem.d.ts +7 -12
  192. package/src/engine/graphics3/FogOfWarSystem.d.ts.map +1 -1
  193. package/src/engine/graphics3/FogOfWarSystem.js +8 -12
  194. package/src/engine/graphics3/prefab/prefab_instantiate.js +230 -230
  195. package/src/engine/grid/grid2transform/GridPosition2TransformSystem.d.ts.map +1 -1
  196. package/src/engine/grid/grid2transform/GridPosition2TransformSystem.js +24 -5
  197. package/src/engine/interpolation/Interpoland.d.ts +7 -1
  198. package/src/engine/interpolation/Interpoland.d.ts.map +1 -1
  199. package/src/engine/interpolation/Interpoland.js +8 -1
  200. package/src/engine/interpolation/InterpolationSystem.d.ts.map +1 -1
  201. package/src/engine/interpolation/InterpolationSystem.js +3 -0
  202. package/src/engine/interpolation/TransformPoseSerializationAdapter.d.ts +5 -3
  203. package/src/engine/interpolation/TransformPoseSerializationAdapter.d.ts.map +1 -1
  204. package/src/engine/interpolation/TransformPoseSerializationAdapter.js +14 -3
  205. package/src/engine/interpolation/pose_interpoland.d.ts.map +1 -1
  206. package/src/engine/interpolation/pose_interpoland.js +2 -0
  207. package/src/engine/network/orchestrator/NetworkPeer.d.ts.map +1 -1
  208. package/src/engine/network/orchestrator/NetworkPeer.js +15 -10
  209. package/src/engine/physics/cloth/ecs/ClothWorld.d.ts.map +1 -1
  210. package/src/engine/physics/cloth/ecs/ClothWorld.js +17 -0
  211. package/src/engine/physics/mls-mpm/MLS_MPM.d.ts.map +1 -1
  212. package/src/engine/physics/mls-mpm/MLS_MPM.js +14 -0
  213. package/src/engine/sound/sopra/definition/clip/AbstractAudioClip.d.ts +2 -1
  214. package/src/engine/sound/sopra/definition/clip/AbstractAudioClip.d.ts.map +1 -1
  215. package/src/engine/sound/sopra/definition/clip/AbstractAudioClip.js +1 -1
  216. package/src/engine/sound/sopra/definition/clip/BlendContainerAudioClip.d.ts +1 -0
  217. package/src/engine/sound/sopra/definition/clip/BlendContainerAudioClip.d.ts.map +1 -1
  218. package/src/engine/sound/sopra/definition/clip/RandomContainerAudioClip.d.ts +1 -0
  219. package/src/engine/sound/sopra/definition/clip/RandomContainerAudioClip.d.ts.map +1 -1
  220. package/src/engine/sound/sopra/definition/clip/SampleAudioClip.d.ts +2 -1
  221. package/src/engine/sound/sopra/definition/clip/SampleAudioClip.d.ts.map +1 -1
  222. package/src/engine/sound/sopra/definition/clip/SampleAudioClip.js +12 -3
  223. package/src/engine/sound/sopra/definition/clip/SequenceContainerAudioClip.d.ts +1 -0
  224. package/src/engine/sound/sopra/definition/clip/SequenceContainerAudioClip.d.ts.map +1 -1
  225. package/src/engine/sound/sopra/definition/clip/SwitchContainerAudioClip.d.ts +1 -0
  226. package/src/engine/sound/sopra/definition/clip/SwitchContainerAudioClip.d.ts.map +1 -1
  227. package/src/engine/sound/sopra/runtime/EventInstance.d.ts.map +1 -1
  228. package/src/engine/sound/sopra/runtime/EventInstance.js +4 -1
  229. package/src/format/image/avif/av1/decode/decode_coefficients.d.ts.map +1 -1
  230. package/src/format/image/avif/av1/decode/decode_coefficients.js +248 -259
  231. package/src/format/image/avif/av1/decode/read_golomb.d.ts +21 -0
  232. package/src/format/image/avif/av1/decode/read_golomb.d.ts.map +1 -0
  233. package/src/format/image/avif/av1/decode/read_golomb.js +49 -0
  234. package/src/format/image/avif/av1/filter/loop_restoration.js +461 -451
  235. package/src/format/image/avif/av1/util/round2.d.ts +19 -2
  236. package/src/format/image/avif/av1/util/round2.d.ts.map +1 -1
  237. package/src/format/image/avif/av1/util/round2.js +51 -27
  238. package/src/format/image/avif/heif/parse_avif_file.js +24 -0
  239. package/src/shade/RENDERER_CONTRACT.md +12 -7
  240. package/src/shade/playground/caldera/README.md +529 -0
  241. package/src/shade/playground/caldera/caldera_format.d.ts +151 -0
  242. package/src/shade/playground/caldera/caldera_format.d.ts.map +1 -0
  243. package/src/shade/playground/caldera/caldera_format.js +241 -0
  244. package/src/shade/playground/caldera/index.html +126 -0
  245. package/src/shade/playground/caldera/main.d.ts.map +1 -0
  246. package/src/shade/playground/caldera/main.js +1565 -0
  247. package/src/shade/playground/mesh_row_ceiling/README.md +85 -0
  248. package/src/shade/playground/mesh_row_ceiling/index.html +31 -0
  249. package/src/shade/playground/mesh_row_ceiling/main.d.ts +2 -0
  250. package/src/shade/playground/mesh_row_ceiling/main.d.ts.map +1 -0
  251. package/src/shade/playground/mesh_row_ceiling/main.js +389 -0
  252. package/src/shade/playground/vgeo_runtime/README.md +5 -3
  253. package/src/shade/playground/vgeo_runtime/main.js +3 -2
  254. package/src/shade/playground/vgeo_runtime/sample_asset.d.ts +3 -3
  255. package/src/shade/playground/vgeo_runtime/sample_asset.d.ts.map +1 -1
  256. package/src/shade/playground/vgeo_runtime/sample_asset.js +3 -3
  257. package/src/shade/playground/vgeo_scene/README.md +103 -24
  258. package/src/shade/playground/vgeo_scene/main.js +525 -24
  259. package/src/shade/renderer/GraphicsContext.d.ts.map +1 -1
  260. package/src/shade/renderer/GraphicsContext.js +6 -0
  261. package/src/shade/renderer/Renderer.d.ts +2 -0
  262. package/src/shade/renderer/Renderer.d.ts.map +1 -1
  263. package/src/shade/renderer/Renderer.js +23 -0
  264. package/src/shade/renderer/animation/skinning/GPUMeshSkinningContext.d.ts.map +1 -1
  265. package/src/shade/renderer/animation/skinning/GPUMeshSkinningContext.js +100 -8
  266. package/src/shade/renderer/buffer/table/GPUTypedTable.d.ts.map +1 -1
  267. package/src/shade/renderer/buffer/table/GPUTypedTable.js +14 -0
  268. package/src/shade/renderer/extension/RENDER_EXTENSION_DESIGN.md +69 -33
  269. package/src/shade/renderer/extension/RenderExtension.d.ts +15 -18
  270. package/src/shade/renderer/extension/RenderExtension.d.ts.map +1 -1
  271. package/src/shade/renderer/extension/RenderExtension.js +15 -19
  272. package/src/shade/renderer/extension/RenderExtensionRegistry.d.ts +5 -5
  273. package/src/shade/renderer/extension/RenderExtensionRegistry.d.ts.map +1 -1
  274. package/src/shade/renderer/extension/RenderExtensionRegistry.js +29 -166
  275. package/src/shade/renderer/geometry/GPUGeometryManager.d.ts.map +1 -1
  276. package/src/shade/renderer/geometry/GPUGeometryManager.js +73 -16
  277. package/src/shade/renderer/geometry/bvh/GPUGeometryBVHManager.d.ts.map +1 -1
  278. package/src/shade/renderer/geometry/bvh/GPUGeometryBVHManager.js +18 -3
  279. package/src/shade/renderer/geometry/meshlet/COMPACTION_PLAN_2026_09_16.md +663 -0
  280. package/src/shade/renderer/geometry/meshlet/GPUMeshletManager.d.ts +100 -5
  281. package/src/shade/renderer/geometry/meshlet/GPUMeshletManager.d.ts.map +1 -1
  282. package/src/shade/renderer/geometry/meshlet/GPUMeshletManager.js +688 -81
  283. package/src/shade/renderer/geometry/virtual/ATTRIBUTE_PLAN_2026_09_18.md +540 -0
  284. package/src/shade/renderer/geometry/virtual/VGEO_FORMAT.md +155 -64
  285. package/src/shade/renderer/geometry/virtual/VIRTUAL_GEOMETRY_DESIGN.md +76 -16
  286. package/src/shade/renderer/geometry/virtual/VIRTUAL_GEOMETRY_PLAN.md +3 -3
  287. package/src/shade/renderer/geometry/virtual/WEDGE_PLAN_2026_09_18.md +448 -0
  288. package/src/shade/renderer/geometry/virtual/build/VGeoBuildOptions.d.ts +44 -7
  289. package/src/shade/renderer/geometry/virtual/build/VGeoBuildOptions.d.ts.map +1 -1
  290. package/src/shade/renderer/geometry/virtual/build/VGeoBuildOptions.js +48 -9
  291. package/src/shade/renderer/geometry/virtual/build/level/VGeoContribution.d.ts +40 -0
  292. package/src/shade/renderer/geometry/virtual/build/level/VGeoContribution.d.ts.map +1 -0
  293. package/src/shade/renderer/geometry/virtual/build/level/VGeoContribution.js +24 -0
  294. package/src/shade/renderer/geometry/virtual/build/level/VGeoFrontier.d.ts +91 -0
  295. package/src/shade/renderer/geometry/virtual/build/level/VGeoFrontier.d.ts.map +1 -0
  296. package/src/shade/renderer/geometry/virtual/build/level/VGeoFrontier.js +91 -0
  297. package/src/shade/renderer/geometry/virtual/build/level/VGeoGroupRepack.d.ts +24 -0
  298. package/src/shade/renderer/geometry/virtual/build/level/VGeoGroupRepack.d.ts.map +1 -0
  299. package/src/shade/renderer/geometry/virtual/build/level/VGeoGroupRepack.js +25 -0
  300. package/src/shade/renderer/geometry/virtual/build/level/VGeoGroupStep.d.ts +77 -0
  301. package/src/shade/renderer/geometry/virtual/build/level/VGeoGroupStep.d.ts.map +1 -0
  302. package/src/shade/renderer/geometry/virtual/build/level/VGeoGroupStep.js +84 -0
  303. package/src/shade/renderer/geometry/virtual/build/level/VGeoLevelOptions.d.ts +19 -6
  304. package/src/shade/renderer/geometry/virtual/build/level/VGeoLevelOptions.d.ts.map +1 -1
  305. package/src/shade/renderer/geometry/virtual/build/level/VGeoLevelOptions.js +20 -7
  306. package/src/shade/renderer/geometry/virtual/build/level/vgeo_build_cluster.d.ts +19 -0
  307. package/src/shade/renderer/geometry/virtual/build/level/vgeo_build_cluster.d.ts.map +1 -0
  308. package/src/shade/renderer/geometry/virtual/build/level/vgeo_build_cluster.js +58 -0
  309. package/src/shade/renderer/geometry/virtual/build/level/vgeo_build_cluster_graph.d.ts +18 -0
  310. package/src/shade/renderer/geometry/virtual/build/level/vgeo_build_cluster_graph.d.ts.map +1 -0
  311. package/src/shade/renderer/geometry/virtual/build/level/vgeo_build_cluster_graph.js +116 -0
  312. package/src/shade/renderer/geometry/virtual/build/level/vgeo_build_group_step.d.ts +18 -0
  313. package/src/shade/renderer/geometry/virtual/build/level/vgeo_build_group_step.d.ts.map +1 -0
  314. package/src/shade/renderer/geometry/virtual/build/level/vgeo_build_group_step.js +545 -0
  315. package/src/shade/renderer/geometry/virtual/build/level/vgeo_build_leaf_frontier.d.ts +16 -0
  316. package/src/shade/renderer/geometry/virtual/build/level/vgeo_build_leaf_frontier.d.ts.map +1 -0
  317. package/src/shade/renderer/geometry/virtual/build/level/vgeo_build_leaf_frontier.js +110 -0
  318. package/src/shade/renderer/geometry/virtual/build/level/vgeo_build_levels.d.ts +2 -27
  319. package/src/shade/renderer/geometry/virtual/build/level/vgeo_build_levels.d.ts.map +1 -1
  320. package/src/shade/renderer/geometry/virtual/build/level/vgeo_build_levels.js +120 -1564
  321. package/src/shade/renderer/geometry/virtual/build/level/vgeo_compute_group_boundary.d.ts +26 -0
  322. package/src/shade/renderer/geometry/virtual/build/level/vgeo_compute_group_boundary.d.ts.map +1 -0
  323. package/src/shade/renderer/geometry/virtual/build/level/vgeo_compute_group_boundary.js +123 -0
  324. package/src/shade/renderer/geometry/virtual/build/level/vgeo_error_bands.d.ts +26 -0
  325. package/src/shade/renderer/geometry/virtual/build/level/vgeo_error_bands.d.ts.map +1 -0
  326. package/src/shade/renderer/geometry/virtual/build/level/vgeo_error_bands.js +78 -0
  327. package/src/shade/renderer/geometry/virtual/build/level/vgeo_partition_into_groups.d.ts +24 -0
  328. package/src/shade/renderer/geometry/virtual/build/level/vgeo_partition_into_groups.d.ts.map +1 -0
  329. package/src/shade/renderer/geometry/virtual/build/level/vgeo_partition_into_groups.js +337 -0
  330. package/src/shade/renderer/geometry/virtual/build/level/vgeo_run_round.d.ts +29 -0
  331. package/src/shade/renderer/geometry/virtual/build/level/vgeo_run_round.d.ts.map +1 -0
  332. package/src/shade/renderer/geometry/virtual/build/level/vgeo_run_round.js +147 -0
  333. package/src/shade/renderer/geometry/virtual/build/level/vgeo_stitch_frontier.d.ts +37 -0
  334. package/src/shade/renderer/geometry/virtual/build/level/vgeo_stitch_frontier.d.ts.map +1 -0
  335. package/src/shade/renderer/geometry/virtual/build/level/vgeo_stitch_frontier.js +243 -0
  336. package/src/shade/renderer/geometry/virtual/build/level/vgeo_try_repack.d.ts +29 -0
  337. package/src/shade/renderer/geometry/virtual/build/level/vgeo_try_repack.d.ts.map +1 -0
  338. package/src/shade/renderer/geometry/virtual/build/level/vgeo_try_repack.js +117 -0
  339. package/src/shade/renderer/geometry/virtual/build/mesh/VGEO_CREASE_ANGLE.d.ts +46 -0
  340. package/src/shade/renderer/geometry/virtual/build/mesh/VGEO_CREASE_ANGLE.d.ts.map +1 -0
  341. package/src/shade/renderer/geometry/virtual/build/mesh/VGEO_DEFAULT_ATTRIBUTE_WEIGHT_RATIOS.d.ts +74 -0
  342. package/src/shade/renderer/geometry/virtual/build/mesh/VGEO_DEFAULT_ATTRIBUTE_WEIGHT_RATIOS.d.ts.map +1 -0
  343. package/src/shade/renderer/geometry/virtual/build/mesh/VGEO_DEFAULT_ATTRIBUTE_WEIGHT_RATIOS.js +78 -0
  344. package/src/shade/renderer/geometry/virtual/build/mesh/VirtualGeometryMesh.d.ts +12 -7
  345. package/src/shade/renderer/geometry/virtual/build/mesh/VirtualGeometryMesh.d.ts.map +1 -1
  346. package/src/shade/renderer/geometry/virtual/build/mesh/VirtualGeometryMesh.js +33 -11
  347. package/src/shade/renderer/geometry/virtual/build/mesh/vgeo_attribute_vector_layout.d.ts +58 -0
  348. package/src/shade/renderer/geometry/virtual/build/mesh/vgeo_attribute_vector_layout.d.ts.map +1 -0
  349. package/src/shade/renderer/geometry/virtual/build/mesh/vgeo_attribute_vector_layout.js +79 -0
  350. package/src/shade/renderer/geometry/virtual/build/mesh/vgeo_attribute_vector_read.d.ts +39 -0
  351. package/src/shade/renderer/geometry/virtual/build/mesh/vgeo_attribute_vector_read.d.ts.map +1 -0
  352. package/src/shade/renderer/geometry/virtual/build/mesh/vgeo_attribute_vector_read.js +68 -0
  353. package/src/shade/renderer/geometry/virtual/build/mesh/vgeo_attribute_vector_write.d.ts +53 -0
  354. package/src/shade/renderer/geometry/virtual/build/mesh/vgeo_attribute_vector_write.d.ts.map +1 -0
  355. package/src/shade/renderer/geometry/virtual/build/mesh/vgeo_attribute_vector_write.js +243 -0
  356. package/src/shade/renderer/geometry/virtual/build/mesh/vgeo_compute_vertex_normals.d.ts +32 -7
  357. package/src/shade/renderer/geometry/virtual/build/mesh/vgeo_compute_vertex_normals.d.ts.map +1 -1
  358. package/src/shade/renderer/geometry/virtual/build/mesh/vgeo_compute_vertex_normals.js +243 -24
  359. package/src/shade/renderer/geometry/virtual/build/mesh/vgeo_compute_vertex_tangents.d.ts +12 -8
  360. package/src/shade/renderer/geometry/virtual/build/mesh/vgeo_compute_vertex_tangents.d.ts.map +1 -1
  361. package/src/shade/renderer/geometry/virtual/build/mesh/vgeo_compute_vertex_tangents.js +58 -50
  362. package/src/shade/renderer/geometry/virtual/build/mesh/vgeo_normalize_source.d.ts +5 -0
  363. package/src/shade/renderer/geometry/virtual/build/mesh/vgeo_normalize_source.d.ts.map +1 -1
  364. package/src/shade/renderer/geometry/virtual/build/mesh/vgeo_normalize_source.js +13 -1
  365. package/src/shade/renderer/geometry/virtual/build/mesh/vgeo_resolve_attribute_weights.d.ts +38 -0
  366. package/src/shade/renderer/geometry/virtual/build/mesh/vgeo_resolve_attribute_weights.d.ts.map +1 -0
  367. package/src/shade/renderer/geometry/virtual/build/mesh/vgeo_resolve_attribute_weights.js +81 -0
  368. package/src/shade/renderer/geometry/virtual/build/partition/VGeoFaceGraph.d.ts +23 -5
  369. package/src/shade/renderer/geometry/virtual/build/partition/VGeoFaceGraph.d.ts.map +1 -1
  370. package/src/shade/renderer/geometry/virtual/build/partition/VGeoFaceGraph.js +28 -21
  371. package/src/shade/renderer/geometry/virtual/build/partition/vgeo_split_face_set.js +1 -1
  372. package/src/shade/renderer/geometry/virtual/build/partition/vgeo_split_face_set_packed.d.ts.map +1 -1
  373. package/src/shade/renderer/geometry/virtual/build/partition/vgeo_split_face_set_packed.js +224 -60
  374. package/src/shade/renderer/geometry/virtual/build/submesh/VGeoGatheredFaces.d.ts +15 -0
  375. package/src/shade/renderer/geometry/virtual/build/submesh/VGeoGatheredFaces.d.ts.map +1 -1
  376. package/src/shade/renderer/geometry/virtual/build/submesh/VGeoGatheredFaces.js +17 -0
  377. package/src/shade/renderer/geometry/virtual/build/submesh/VGeoSubmesh.d.ts +17 -0
  378. package/src/shade/renderer/geometry/virtual/build/submesh/VGeoSubmesh.d.ts.map +1 -1
  379. package/src/shade/renderer/geometry/virtual/build/submesh/VGeoSubmesh.js +19 -0
  380. package/src/shade/renderer/geometry/virtual/build/submesh/vgeo_extract_submesh.d.ts +10 -1
  381. package/src/shade/renderer/geometry/virtual/build/submesh/vgeo_extract_submesh.d.ts.map +1 -1
  382. package/src/shade/renderer/geometry/virtual/build/submesh/vgeo_extract_submesh.js +103 -9
  383. package/src/shade/renderer/geometry/virtual/build/submesh/vgeo_gather_faces.d.ts +14 -2
  384. package/src/shade/renderer/geometry/virtual/build/submesh/vgeo_gather_faces.d.ts.map +1 -1
  385. package/src/shade/renderer/geometry/virtual/build/submesh/vgeo_gather_faces.js +38 -14
  386. package/src/shade/renderer/geometry/virtual/build/vgeo_build.d.ts +6 -2
  387. package/src/shade/renderer/geometry/virtual/build/vgeo_build.d.ts.map +1 -1
  388. package/src/shade/renderer/geometry/virtual/build/vgeo_build.js +27 -4
  389. package/src/shade/renderer/geometry/virtual/build/vgeo_subtree_bounds.d.ts +32 -0
  390. package/src/shade/renderer/geometry/virtual/build/vgeo_subtree_bounds.d.ts.map +1 -0
  391. package/src/shade/renderer/geometry/virtual/build/vgeo_subtree_bounds.js +93 -0
  392. package/src/shade/renderer/geometry/virtual/format/VGeoWriteOptions.d.ts +20 -6
  393. package/src/shade/renderer/geometry/virtual/format/VGeoWriteOptions.d.ts.map +1 -1
  394. package/src/shade/renderer/geometry/virtual/format/VGeoWriteOptions.js +22 -8
  395. package/src/shade/renderer/geometry/virtual/format/attribute/vgeo_layers_copy_corner.d.ts +16 -0
  396. package/src/shade/renderer/geometry/virtual/format/attribute/vgeo_layers_copy_corner.d.ts.map +1 -0
  397. package/src/shade/renderer/geometry/virtual/format/attribute/{vgeo_layers_copy_vertex.js → vgeo_layers_copy_corner.js} +8 -8
  398. package/src/shade/renderer/geometry/virtual/format/attribute/vgeo_layers_declare.d.ts +18 -7
  399. package/src/shade/renderer/geometry/virtual/format/attribute/vgeo_layers_declare.d.ts.map +1 -1
  400. package/src/shade/renderer/geometry/virtual/format/attribute/vgeo_layers_declare.js +19 -8
  401. package/src/shade/renderer/geometry/virtual/format/attribute/vgeo_layers_like.d.ts +3 -3
  402. package/src/shade/renderer/geometry/virtual/format/attribute/vgeo_layers_like.js +3 -3
  403. package/src/shade/renderer/geometry/virtual/format/attribute/vgeo_mesh_wedges.d.ts +27 -0
  404. package/src/shade/renderer/geometry/virtual/format/attribute/vgeo_mesh_wedges.d.ts.map +1 -0
  405. package/src/shade/renderer/geometry/virtual/format/attribute/vgeo_mesh_wedges.js +97 -0
  406. package/src/shade/renderer/geometry/virtual/format/cluster/VGEO_CLUSTER_OFFSET.d.ts +7 -3
  407. package/src/shade/renderer/geometry/virtual/format/cluster/VGEO_CLUSTER_OFFSET.d.ts.map +1 -1
  408. package/src/shade/renderer/geometry/virtual/format/cluster/VGEO_CLUSTER_OFFSET.js +21 -11
  409. package/src/shade/renderer/geometry/virtual/format/cluster/VGEO_CLUSTER_OFFSET_V4.d.ts +21 -0
  410. package/src/shade/renderer/geometry/virtual/format/cluster/VGEO_CLUSTER_OFFSET_V4.d.ts.map +1 -0
  411. package/src/shade/renderer/geometry/virtual/format/cluster/VGEO_CLUSTER_OFFSET_V4.js +41 -0
  412. package/src/shade/renderer/geometry/virtual/format/dag/VirtualGeometryCluster.d.ts +19 -1
  413. package/src/shade/renderer/geometry/virtual/format/dag/VirtualGeometryCluster.d.ts.map +1 -1
  414. package/src/shade/renderer/geometry/virtual/format/dag/VirtualGeometryCluster.js +20 -1
  415. package/src/shade/renderer/geometry/virtual/format/frame/VGEO_CODEC_DEFLATE.d.ts +25 -0
  416. package/src/shade/renderer/geometry/virtual/format/frame/VGEO_CODEC_DEFLATE.d.ts.map +1 -0
  417. package/src/shade/renderer/geometry/virtual/format/frame/VGEO_CODEC_DEFLATE.js +24 -0
  418. package/src/shade/renderer/geometry/virtual/format/frame/VGEO_CODEC_LZ4.d.ts +5 -0
  419. package/src/shade/renderer/geometry/virtual/format/frame/VGEO_CODEC_LZ4.d.ts.map +1 -1
  420. package/src/shade/renderer/geometry/virtual/format/frame/VGEO_CODEC_LZ4.js +5 -0
  421. package/src/shade/renderer/geometry/virtual/format/frame/vgeo_codec_name.d.ts.map +1 -1
  422. package/src/shade/renderer/geometry/virtual/format/frame/vgeo_codec_name.js +5 -0
  423. package/src/shade/renderer/geometry/virtual/format/frame/vgeo_decode_frame_blob.d.ts +10 -3
  424. package/src/shade/renderer/geometry/virtual/format/frame/vgeo_decode_frame_blob.d.ts.map +1 -1
  425. package/src/shade/renderer/geometry/virtual/format/frame/vgeo_decode_frame_blob.js +37 -3
  426. package/src/shade/renderer/geometry/virtual/format/frame/vgeo_encode_frame_blob.d.ts +21 -13
  427. package/src/shade/renderer/geometry/virtual/format/frame/vgeo_encode_frame_blob.d.ts.map +1 -1
  428. package/src/shade/renderer/geometry/virtual/format/frame/vgeo_encode_frame_blob.js +37 -13
  429. package/src/shade/renderer/geometry/virtual/format/frame/vgeo_frame_decode_bound.d.ts +8 -2
  430. package/src/shade/renderer/geometry/virtual/format/frame/vgeo_frame_decode_bound.d.ts.map +1 -1
  431. package/src/shade/renderer/geometry/virtual/format/frame/vgeo_frame_decode_bound.js +14 -2
  432. package/src/shade/renderer/geometry/virtual/format/header/VGEO_FORMAT_VERSION.d.ts +18 -0
  433. package/src/shade/renderer/geometry/virtual/format/header/VGEO_FORMAT_VERSION.d.ts.map +1 -1
  434. package/src/shade/renderer/geometry/virtual/format/header/VGEO_FORMAT_VERSION.js +19 -1
  435. package/src/shade/renderer/geometry/virtual/format/header/VGEO_HEADER_OFFSET.js +6 -0
  436. package/src/shade/renderer/geometry/virtual/format/header/VGEO_MAX_LEVEL_COUNT.d.ts +21 -0
  437. package/src/shade/renderer/geometry/virtual/format/header/VGEO_MAX_LEVEL_COUNT.d.ts.map +1 -0
  438. package/src/shade/renderer/geometry/virtual/format/header/VGEO_MAX_LEVEL_COUNT.js +20 -0
  439. package/src/shade/renderer/geometry/virtual/format/header/VGEO_MIN_READER_VERSION.d.ts +9 -0
  440. package/src/shade/renderer/geometry/virtual/format/header/VGEO_MIN_READER_VERSION.d.ts.map +1 -1
  441. package/src/shade/renderer/geometry/virtual/format/header/VGEO_MIN_READER_VERSION.js +10 -1
  442. package/src/shade/renderer/geometry/virtual/format/page/VGEO_PAGE_OFFSET.d.ts +1 -2
  443. package/src/shade/renderer/geometry/virtual/format/page/VGEO_PAGE_OFFSET.js +10 -3
  444. package/src/shade/renderer/geometry/virtual/format/page/VGEO_PAGE_OFFSET_V5.d.ts +20 -0
  445. package/src/shade/renderer/geometry/virtual/format/page/VGEO_PAGE_OFFSET_V5.d.ts.map +1 -0
  446. package/src/shade/renderer/geometry/virtual/format/page/VGEO_PAGE_OFFSET_V5.js +43 -0
  447. package/src/shade/renderer/geometry/virtual/format/payload/vgeo_encode_cluster_payload.d.ts +4 -2
  448. package/src/shade/renderer/geometry/virtual/format/payload/vgeo_encode_cluster_payload.d.ts.map +1 -1
  449. package/src/shade/renderer/geometry/virtual/format/payload/vgeo_encode_cluster_payload.js +8 -4
  450. package/src/shade/renderer/geometry/virtual/format/read/VGeoContainerReader.d.ts.map +1 -1
  451. package/src/shade/renderer/geometry/virtual/format/read/VGeoContainerReader.js +2 -2
  452. package/src/shade/renderer/geometry/virtual/format/read/VGeoDirectory.d.ts +6 -4
  453. package/src/shade/renderer/geometry/virtual/format/read/VGeoDirectory.d.ts.map +1 -1
  454. package/src/shade/renderer/geometry/virtual/format/read/VGeoDirectory.js +6 -4
  455. package/src/shade/renderer/geometry/virtual/format/read/VGeoPage.d.ts +13 -2
  456. package/src/shade/renderer/geometry/virtual/format/read/VGeoPage.d.ts.map +1 -1
  457. package/src/shade/renderer/geometry/virtual/format/read/VGeoPage.js +13 -2
  458. package/src/shade/renderer/geometry/virtual/format/read/VGeoPageFetcherCore.d.ts +5 -1
  459. package/src/shade/renderer/geometry/virtual/format/read/VGeoPageFetcherCore.d.ts.map +1 -1
  460. package/src/shade/renderer/geometry/virtual/format/read/VGeoPageFetcherCore.js +14 -1
  461. package/src/shade/renderer/geometry/virtual/format/read/vgeo_page_worker_serve.d.ts.map +1 -1
  462. package/src/shade/renderer/geometry/virtual/format/read/vgeo_page_worker_serve.js +1 -0
  463. package/src/shade/renderer/geometry/virtual/format/read/vgeo_read_directory.d.ts +4 -1
  464. package/src/shade/renderer/geometry/virtual/format/read/vgeo_read_directory.d.ts.map +1 -1
  465. package/src/shade/renderer/geometry/virtual/format/read/vgeo_read_directory.js +53 -11
  466. package/src/shade/renderer/geometry/virtual/format/read/vgeo_read_header.d.ts.map +1 -1
  467. package/src/shade/renderer/geometry/virtual/format/read/vgeo_read_header.js +7 -3
  468. package/src/shade/renderer/geometry/virtual/format/read/vgeo_read_page.d.ts +12 -2
  469. package/src/shade/renderer/geometry/virtual/format/read/vgeo_read_page.d.ts.map +1 -1
  470. package/src/shade/renderer/geometry/virtual/format/read/vgeo_read_page.js +111 -33
  471. package/src/shade/renderer/geometry/virtual/format/vgeo_validate_container.d.ts +6 -2
  472. package/src/shade/renderer/geometry/virtual/format/vgeo_validate_container.d.ts.map +1 -1
  473. package/src/shade/renderer/geometry/virtual/format/vgeo_validate_container.js +112 -42
  474. package/src/shade/renderer/geometry/virtual/format/vgeo_write_container.d.ts +2 -2
  475. package/src/shade/renderer/geometry/virtual/format/vgeo_write_container.d.ts.map +1 -1
  476. package/src/shade/renderer/geometry/virtual/format/vgeo_write_container.js +51 -21
  477. package/src/shade/renderer/geometry/virtual/runtime/CULL_PLAN_2026_09_16.md +385 -0
  478. package/src/shade/renderer/geometry/virtual/runtime/VGeoPageFetcher.d.ts.map +1 -1
  479. package/src/shade/renderer/geometry/virtual/runtime/VGeoPageFetcher.js +9 -0
  480. package/src/shade/renderer/geometry/virtual/runtime/VGeoResidency.d.ts.map +1 -1
  481. package/src/shade/renderer/geometry/virtual/runtime/VGeoResidency.js +14 -1
  482. package/src/shade/renderer/geometry/virtual/runtime/cut/chunk_vgeo_cut_access.d.ts.map +1 -1
  483. package/src/shade/renderer/geometry/virtual/runtime/cut/chunk_vgeo_cut_access.js +34 -0
  484. package/src/shade/renderer/geometry/virtual/runtime/cut/chunk_vgeo_cut_arena.d.ts.map +1 -1
  485. package/src/shade/renderer/geometry/virtual/runtime/cut/chunk_vgeo_cut_arena.js +18 -0
  486. package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_traverse.d.ts.map +1 -1
  487. package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_traverse.js +47 -19
  488. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_CLUSTER_ROW_OFFSET.d.ts +1 -0
  489. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_CLUSTER_ROW_OFFSET.js +22 -4
  490. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_CLUSTER_ROW_WORDS.d.ts +7 -2
  491. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_CLUSTER_ROW_WORDS.d.ts.map +1 -1
  492. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_CLUSTER_ROW_WORDS.js +8 -3
  493. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_CUT_STAT.d.ts +1 -0
  494. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_CUT_STAT.js +10 -0
  495. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_CUT_STAT_COUNT.js +1 -1
  496. package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_page_slot_words.d.ts +13 -11
  497. package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_page_slot_words.d.ts.map +1 -1
  498. package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_page_slot_words.js +13 -11
  499. package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_write_page_block.d.ts.map +1 -1
  500. package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_write_page_block.js +4 -0
  501. package/src/shade/renderer/global_illumination/probe/octahedral/GPUProbeAtlas.d.ts.map +1 -1
  502. package/src/shade/renderer/global_illumination/probe/octahedral/GPUProbeAtlas.js +12 -3
  503. package/src/shade/renderer/global_illumination/sharc/shader/shader_sharc_evict.d.ts.map +1 -1
  504. package/src/shade/renderer/global_illumination/sharc/shader/shader_sharc_evict.js +3 -4
  505. package/src/shade/renderer/gpu_primitive/indirect/IndirectComputeCommandBuildPass.d.ts +6 -2
  506. package/src/shade/renderer/gpu_primitive/indirect/IndirectComputeCommandBuildPass.d.ts.map +1 -1
  507. package/src/shade/renderer/gpu_primitive/indirect/IndirectComputeCommandBuildPass.js +90 -12
  508. package/src/shade/renderer/gpu_primitive/indirect/WEBGPU_GUARANTEED_MAX_COMPUTE_WORKGROUPS_PER_DIMENSION.d.ts +22 -0
  509. package/src/shade/renderer/gpu_primitive/indirect/WEBGPU_GUARANTEED_MAX_COMPUTE_WORKGROUPS_PER_DIMENSION.d.ts.map +1 -0
  510. package/src/shade/renderer/gpu_primitive/indirect/WEBGPU_GUARANTEED_MAX_COMPUTE_WORKGROUPS_PER_DIMENSION.js +21 -0
  511. package/src/shade/renderer/light/environment/rgbe/RGBE_ReadPixels_RLE.d.ts.map +1 -1
  512. package/src/shade/renderer/light/environment/rgbe/RGBE_ReadPixels_RLE.js +7 -1
  513. package/src/shade/renderer/lightmap/GPULightMap.d.ts.map +1 -1
  514. package/src/shade/renderer/lightmap/GPULightMap.js +349 -336
  515. package/src/shade/renderer/loader/gltf/load_gltf.d.ts.map +1 -1
  516. package/src/shade/renderer/loader/gltf/load_gltf.js +1137 -1128
  517. package/src/shade/renderer/particles/DESIGN.md +14 -5
  518. package/src/shade/renderer/particles/GPUParticleSystem.d.ts +25 -10
  519. package/src/shade/renderer/particles/GPUParticleSystem.d.ts.map +1 -1
  520. package/src/shade/renderer/particles/GPUParticleSystem.js +182 -38
  521. package/src/shade/renderer/particles/graph/groups/simplex_reference.d.ts.map +1 -1
  522. package/src/shade/renderer/particles/graph/groups/simplex_reference.js +16 -10
  523. package/src/shade/renderer/particles/graph_particles.d.ts +4 -2
  524. package/src/shade/renderer/particles/graph_particles.d.ts.map +1 -1
  525. package/src/shade/renderer/particles/graph_particles.js +8 -2
  526. package/src/shade/renderer/particles/optimizer/particle_vm_semantics.d.ts.map +1 -1
  527. package/src/shade/renderer/particles/optimizer/particle_vm_semantics.js +8 -2
  528. package/src/shade/renderer/particles/optimizer/pass_fold_constants.d.ts.map +1 -1
  529. package/src/shade/renderer/particles/optimizer/pass_fold_constants.js +10 -4
  530. package/src/shade/renderer/particles/shaders/shader_particle_emit.d.ts.map +1 -1
  531. package/src/shade/renderer/particles/shaders/shader_particle_emit.js +13 -8
  532. package/src/shade/renderer/particles/shaders/shader_particle_init_free_list.d.ts +31 -0
  533. package/src/shade/renderer/particles/shaders/shader_particle_init_free_list.d.ts.map +1 -0
  534. package/src/shade/renderer/particles/shaders/shader_particle_init_free_list.js +62 -0
  535. package/src/shade/renderer/particles/shaders/shader_particle_simulate.d.ts.map +1 -1
  536. package/src/shade/renderer/particles/shaders/shader_particle_simulate.js +3 -6
  537. package/src/shade/renderer/particles/vm/ParticleVMReference.d.ts +13 -0
  538. package/src/shade/renderer/particles/vm/ParticleVMReference.d.ts.map +1 -1
  539. package/src/shade/renderer/particles/vm/ParticleVMReference.js +95 -17
  540. package/src/shade/renderer/rasterize/bucket/prepare_meshlet_draw_commands_by_material.d.ts.map +1 -1
  541. package/src/shade/renderer/rasterize/bucket/prepare_meshlet_draw_commands_by_material.js +4 -12
  542. package/src/shade/renderer/rasterize/bucket/rasterization_has_opaque_bucket.d.ts +21 -0
  543. package/src/shade/renderer/rasterize/bucket/rasterization_has_opaque_bucket.d.ts.map +1 -0
  544. package/src/shade/renderer/rasterize/bucket/rasterization_has_opaque_bucket.js +28 -0
  545. package/src/shade/renderer/rasterize/bucket/shader_read_out_bucket.d.ts +0 -2
  546. package/src/shade/renderer/rasterize/bucket/shader_read_out_bucket.d.ts.map +1 -1
  547. package/src/shade/renderer/rasterize/bucket/shader_read_out_bucket.js +4 -5
  548. package/src/shade/renderer/rasterize/cull/hzb/shader_hzb_mesh_filter_2way.d.ts.map +1 -1
  549. package/src/shade/renderer/rasterize/cull/hzb/shader_hzb_mesh_filter_2way.js +5 -3
  550. package/src/shade/renderer/rasterize/cull/hzb/shader_hzb_meshlet_filter_1way.d.ts.map +1 -1
  551. package/src/shade/renderer/rasterize/cull/hzb/shader_hzb_meshlet_filter_1way.js +11 -4
  552. package/src/shade/renderer/rasterize/cull/hzb/shader_hzb_meshlet_filter_1way_full.d.ts.map +1 -1
  553. package/src/shade/renderer/rasterize/cull/hzb/shader_hzb_meshlet_filter_1way_full.js +4 -3
  554. package/src/shade/renderer/rasterize/expand/bucket/meshlet/graph_sort_meshlets_by_bucket_id.d.ts.map +1 -1
  555. package/src/shade/renderer/rasterize/expand/bucket/meshlet/graph_sort_meshlets_by_bucket_id.js +3 -0
  556. package/src/shade/renderer/rasterize/expand/bucket/meshlet/shader_meshlets_count_by_rasterization_bucket.d.ts.map +1 -1
  557. package/src/shade/renderer/rasterize/expand/bucket/meshlet/shader_meshlets_count_by_rasterization_bucket.js +94 -90
  558. package/src/shade/renderer/rasterize/expand/bucket/meshlet/shader_meshlets_extract_bucket.d.ts.map +1 -1
  559. package/src/shade/renderer/rasterize/expand/bucket/meshlet/shader_meshlets_extract_bucket.js +13 -5
  560. package/src/shade/renderer/rasterize/expand/bucket/meshlet/shader_meshlets_rasterization_bucket_sort.d.ts.map +1 -1
  561. package/src/shade/renderer/rasterize/expand/bucket/meshlet/shader_meshlets_rasterization_bucket_sort.js +96 -89
  562. package/src/shade/renderer/rasterize/expand/graph_meshlets_filter_one_way.d.ts.map +1 -1
  563. package/src/shade/renderer/rasterize/expand/graph_meshlets_filter_one_way.js +3 -1
  564. package/src/shade/renderer/rasterize/expand/graph_meshlets_filter_two_way.d.ts.map +1 -1
  565. package/src/shade/renderer/rasterize/expand/graph_meshlets_filter_two_way.js +4 -1
  566. package/src/shade/renderer/rasterize/expand/mesh/shader_mesh_expand_to_meshlets_prefix.d.ts.map +1 -1
  567. package/src/shade/renderer/rasterize/expand/mesh/shader_mesh_expand_to_meshlets_prefix.js +100 -89
  568. package/src/shade/renderer/rasterize/expand/mesh/shader_prefix_sum_to_command.d.ts.map +1 -1
  569. package/src/shade/renderer/rasterize/expand/mesh/shader_prefix_sum_to_command.js +31 -7
  570. package/src/shade/renderer/rasterize/expand/pass0/pass_mesh_cull.d.ts.map +1 -1
  571. package/src/shade/renderer/rasterize/expand/pass0/pass_mesh_cull.js +1 -7
  572. package/src/shade/renderer/rasterize/expand/pass1/filter_meshlets.2-way.compute.d.ts.map +1 -1
  573. package/src/shade/renderer/rasterize/expand/pass1/filter_meshlets.2-way.compute.js +161 -153
  574. package/src/shade/renderer/rasterize/fast/graph_extract_meshes_from_bucket.d.ts.map +1 -1
  575. package/src/shade/renderer/rasterize/fast/graph_extract_meshes_from_bucket.js +2 -7
  576. package/src/shade/renderer/rasterize/fast/graph_rasterize_scene_fast.d.ts.map +1 -1
  577. package/src/shade/renderer/rasterize/fast/graph_rasterize_scene_fast.js +19 -2
  578. package/src/shade/renderer/rasterize/standard/graph_rasterize_partial_opaque.d.ts.map +1 -1
  579. package/src/shade/renderer/rasterize/standard/graph_rasterize_partial_opaque.js +4 -8
  580. package/src/shade/renderer/rasterize/standard/graph_rasterize_remaining_opaque.d.ts.map +1 -1
  581. package/src/shade/renderer/rasterize/standard/graph_rasterize_remaining_opaque.js +5 -8
  582. package/src/shade/renderer/rasterize/standard/graph_rasterize_scene.d.ts.map +1 -1
  583. package/src/shade/renderer/rasterize/standard/graph_rasterize_scene.js +19 -2
  584. package/src/shade/renderer/shader/type/WebGPUStruct.d.ts.map +1 -1
  585. package/src/shade/renderer/shader/type/WebGPUStruct.js +12 -0
  586. package/src/shade/renderer/shadow/map/DEFAULT_SHADOWMAP_UPDATE_BUDGET.d.ts +25 -0
  587. package/src/shade/renderer/shadow/map/DEFAULT_SHADOWMAP_UPDATE_BUDGET.d.ts.map +1 -0
  588. package/src/shade/renderer/shadow/map/DEFAULT_SHADOWMAP_UPDATE_BUDGET.js +24 -0
  589. package/src/shade/renderer/shadow/map/GPUSceneShadowmapContext.d.ts +10 -0
  590. package/src/shade/renderer/shadow/map/GPUSceneShadowmapContext.d.ts.map +1 -1
  591. package/src/shade/renderer/shadow/map/GPUSceneShadowmapContext.js +30 -21
  592. package/src/shade/renderer/texture/mipmaps/WebGPUMipmapGenerator.d.ts +8 -2
  593. package/src/shade/renderer/texture/mipmaps/WebGPUMipmapGenerator.d.ts.map +1 -1
  594. package/src/shade/renderer/texture/mipmaps/WebGPUMipmapGenerator.js +41 -6
  595. package/src/shade/wgsl/emulator/WGSLJavaScriptCompiler.js +2383 -2356
  596. package/src/view/tooltip/DomTooltipManager.d.ts +7 -0
  597. package/src/view/tooltip/DomTooltipManager.d.ts.map +1 -1
  598. package/src/view/tooltip/DomTooltipManager.js +22 -0
  599. package/src/view/tooltip/DomTooltipObserver.d.ts.map +1 -1
  600. package/src/view/tooltip/DomTooltipObserver.js +13 -3
  601. package/src/core/model/node-graph/visual/layout/LayoutBoxGraph.d.ts.map +0 -1
  602. package/src/core/model/node-graph/visual/layout/layout_assign_coordinates.d.ts.map +0 -1
  603. package/src/core/model/node-graph/visual/layout/layout_assign_layers.d.ts.map +0 -1
  604. package/src/core/model/node-graph/visual/layout/layout_break_cycles.d.ts.map +0 -1
  605. package/src/core/model/node-graph/visual/layout/layout_insert_lanes.d.ts.map +0 -1
  606. package/src/core/model/node-graph/visual/layout/layout_order_layers.d.ts.map +0 -1
  607. package/src/core/model/node-graph/visual/layout/layout_orient_links.d.ts.map +0 -1
  608. package/src/shade/playground/vgeo_viewer/cut_geometry.d.ts +0 -61
  609. package/src/shade/playground/vgeo_viewer/cut_geometry.d.ts.map +0 -1
  610. package/src/shade/playground/vgeo_viewer/main.d.ts.map +0 -1
  611. package/src/shade/playground/vgeo_viewer/sample_asset.d.ts +0 -18
  612. package/src/shade/playground/vgeo_viewer/sample_asset.d.ts.map +0 -1
  613. package/src/shade/playground/vgeo_viewer/select_cut.d.ts +0 -173
  614. package/src/shade/playground/vgeo_viewer/select_cut.d.ts.map +0 -1
  615. package/src/shade/renderer/geometry/virtual/build/VGeoFaceGraph.d.ts +0 -52
  616. package/src/shade/renderer/geometry/virtual/build/VGeoFaceGraph.d.ts.map +0 -1
  617. package/src/shade/renderer/geometry/virtual/build/VGeoGatheredFaces.d.ts +0 -24
  618. package/src/shade/renderer/geometry/virtual/build/VGeoGatheredFaces.d.ts.map +0 -1
  619. package/src/shade/renderer/geometry/virtual/build/VGeoLevel.d.ts +0 -32
  620. package/src/shade/renderer/geometry/virtual/build/VGeoLevel.d.ts.map +0 -1
  621. package/src/shade/renderer/geometry/virtual/build/VGeoLevelOptions.d.ts +0 -59
  622. package/src/shade/renderer/geometry/virtual/build/VGeoLevelOptions.d.ts.map +0 -1
  623. package/src/shade/renderer/geometry/virtual/build/VGeoLevelSummary.d.ts +0 -44
  624. package/src/shade/renderer/geometry/virtual/build/VGeoLevelSummary.d.ts.map +0 -1
  625. package/src/shade/renderer/geometry/virtual/build/VGeoSubmesh.d.ts +0 -20
  626. package/src/shade/renderer/geometry/virtual/build/VGeoSubmesh.d.ts.map +0 -1
  627. package/src/shade/renderer/geometry/virtual/build/VirtualGeometryMesh.d.ts +0 -73
  628. package/src/shade/renderer/geometry/virtual/build/VirtualGeometryMesh.d.ts.map +0 -1
  629. package/src/shade/renderer/geometry/virtual/build/vgeo_assemble_pages.d.ts +0 -13
  630. package/src/shade/renderer/geometry/virtual/build/vgeo_assemble_pages.d.ts.map +0 -1
  631. package/src/shade/renderer/geometry/virtual/build/vgeo_bounding_sphere.d.ts +0 -12
  632. package/src/shade/renderer/geometry/virtual/build/vgeo_bounding_sphere.d.ts.map +0 -1
  633. package/src/shade/renderer/geometry/virtual/build/vgeo_build_levels.d.ts +0 -36
  634. package/src/shade/renderer/geometry/virtual/build/vgeo_build_levels.d.ts.map +0 -1
  635. package/src/shade/renderer/geometry/virtual/build/vgeo_build_page_nodes.d.ts +0 -13
  636. package/src/shade/renderer/geometry/virtual/build/vgeo_build_page_nodes.d.ts.map +0 -1
  637. package/src/shade/renderer/geometry/virtual/build/vgeo_build_root_group.d.ts +0 -19
  638. package/src/shade/renderer/geometry/virtual/build/vgeo_build_root_group.d.ts.map +0 -1
  639. package/src/shade/renderer/geometry/virtual/build/vgeo_compute_vertex_normals.d.ts +0 -29
  640. package/src/shade/renderer/geometry/virtual/build/vgeo_compute_vertex_normals.d.ts.map +0 -1
  641. package/src/shade/renderer/geometry/virtual/build/vgeo_compute_vertex_tangents.d.ts +0 -31
  642. package/src/shade/renderer/geometry/virtual/build/vgeo_compute_vertex_tangents.d.ts.map +0 -1
  643. package/src/shade/renderer/geometry/virtual/build/vgeo_containing_sphere.d.ts +0 -18
  644. package/src/shade/renderer/geometry/virtual/build/vgeo_containing_sphere.d.ts.map +0 -1
  645. package/src/shade/renderer/geometry/virtual/build/vgeo_extract_submesh.d.ts +0 -14
  646. package/src/shade/renderer/geometry/virtual/build/vgeo_extract_submesh.d.ts.map +0 -1
  647. package/src/shade/renderer/geometry/virtual/build/vgeo_gather_faces.d.ts +0 -12
  648. package/src/shade/renderer/geometry/virtual/build/vgeo_gather_faces.d.ts.map +0 -1
  649. package/src/shade/renderer/geometry/virtual/build/vgeo_graph_components.d.ts +0 -14
  650. package/src/shade/renderer/geometry/virtual/build/vgeo_graph_components.d.ts.map +0 -1
  651. package/src/shade/renderer/geometry/virtual/build/vgeo_narrow_indices.d.ts +0 -11
  652. package/src/shade/renderer/geometry/virtual/build/vgeo_narrow_indices.d.ts.map +0 -1
  653. package/src/shade/renderer/geometry/virtual/build/vgeo_normalize_source.d.ts +0 -46
  654. package/src/shade/renderer/geometry/virtual/build/vgeo_normalize_source.d.ts.map +0 -1
  655. package/src/shade/renderer/geometry/virtual/build/vgeo_page_node_count.d.ts +0 -22
  656. package/src/shade/renderer/geometry/virtual/build/vgeo_page_node_count.d.ts.map +0 -1
  657. package/src/shade/renderer/geometry/virtual/build/vgeo_partition_graph.d.ts +0 -29
  658. package/src/shade/renderer/geometry/virtual/build/vgeo_partition_graph.d.ts.map +0 -1
  659. package/src/shade/renderer/geometry/virtual/build/vgeo_sphere_inflate_to_contain.d.ts +0 -9
  660. package/src/shade/renderer/geometry/virtual/build/vgeo_sphere_inflate_to_contain.d.ts.map +0 -1
  661. package/src/shade/renderer/geometry/virtual/build/vgeo_split_face_set.d.ts +0 -10
  662. package/src/shade/renderer/geometry/virtual/build/vgeo_split_face_set.d.ts.map +0 -1
  663. package/src/shade/renderer/geometry/virtual/build/vgeo_split_face_set_packed.d.ts +0 -15
  664. package/src/shade/renderer/geometry/virtual/build/vgeo_split_face_set_packed.d.ts.map +0 -1
  665. package/src/shade/renderer/geometry/virtual/format/attribute/vgeo_layers_copy_vertex.d.ts +0 -16
  666. package/src/shade/renderer/geometry/virtual/format/attribute/vgeo_layers_copy_vertex.d.ts.map +0 -1
  667. package/src/shade/renderer/loader/gltf/fix_up_material_sides.d.ts +0 -10
  668. package/src/shade/renderer/loader/gltf/fix_up_material_sides.d.ts.map +0 -1
  669. package/src/shade/renderer/rasterize/expand/graph_triangles_filter_by_material_side.d.ts +0 -17
  670. package/src/shade/renderer/rasterize/expand/graph_triangles_filter_by_material_side.d.ts.map +0 -1
  671. package/src/shade/renderer/rasterize/expand/shader_filter_triangle_by_material_side.d.ts +0 -3
  672. package/src/shade/renderer/rasterize/expand/shader_filter_triangle_by_material_side.d.ts.map +0 -1
  673. package/src/shade/renderer/rasterize/forward/chunk_material_side_should_draw.d.ts +0 -8
  674. package/src/shade/renderer/rasterize/forward/chunk_material_side_should_draw.d.ts.map +0 -1
  675. /package/src/core/{model/node-graph/visual/layout → graph/layout/layered}/layout_assign_coordinates.d.ts +0 -0
  676. /package/src/core/{model/node-graph/visual/layout → graph/layout/layered}/layout_assign_layers.d.ts +0 -0
  677. /package/src/core/{model/node-graph/visual/layout → graph/layout/layered}/layout_break_cycles.d.ts +0 -0
  678. /package/src/core/{model/node-graph/visual/layout → graph/layout/layered}/layout_break_cycles.js +0 -0
  679. /package/src/core/{model/node-graph/visual/layout → graph/layout/layered}/layout_insert_lanes.d.ts +0 -0
  680. /package/src/core/{model/node-graph/visual/layout → graph/layout/layered}/layout_insert_lanes.js +0 -0
  681. /package/src/core/{model/node-graph/visual/layout → graph/layout/layered}/layout_order_layers.d.ts +0 -0
  682. /package/src/core/{model/node-graph/visual/layout → graph/layout/layered}/layout_order_layers.js +0 -0
  683. /package/src/core/{model/node-graph/visual/layout → graph/layout/layered}/layout_orient_links.d.ts +0 -0
  684. /package/src/core/{model/node-graph/visual/layout → graph/layout/layered}/layout_orient_links.js +0 -0
  685. /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
- sphere_compute_bounding_of_2_spheres
7
- } from "../../../../../../core/geom/3d/sphere/sphere_compute_bounding_of_2_spheres.js";
8
- import { BinaryTopology, NULL_POINTER, } from "../../../../../../core/geom/3d/topology/struct/binary/BinaryTopology.js";
9
- import {
10
- bt_mesh_compute_vertex_quadrics
11
- } from "../../../../../../core/geom/3d/topology/struct/binary/io/bt_mesh_compute_vertex_quadrics.js";
12
- import {
13
- bt_mesh_from_indexed_geometry
14
- } from "../../../../../../core/geom/3d/topology/struct/binary/io/bt_mesh_from_indexed_geometry.js";
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
- * The bands a round tries, in the order it tries them.
15
+ * Build the cluster DAG: leaves out of the source mesh, then merge steps until one cluster is left.
1378
16
  *
1379
- * Each is one error *octave*: `[e, e · growth]` for each distinct error the frontier holds, finest
1380
- * first. An octave rather than a running threshold is what bounds how far apart two clusters in one
1381
- * group can be — a group shares a single parent error, so merging a nearly-exact cluster with a
1382
- * badly-approximated one drags the exact one up to the other's error for nothing. It is also what
1383
- * lets a cluster *wait*: geometry outside the octave is simply not this round's business.
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 last band is the whole frontier. It is the escape hatch, not the plan: a frontier of two
1386
- * clusters an order of magnitude apart in error has no octave holding both, and merging them at a
1387
- * wide error span still beats never reaching a root.
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
- * @param {number[]} errors ascending, as {@link order_by_error} returns
1390
- * @param {number} growth
1391
- * @param {number} min_fraction least share of the frontier a band may cover
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
- * Every group is speculative and judged on its own. One that comes back with no fewer clusters than
1456
- * it consumed is simply dropped — its clusters return to the frontier, to be grouped differently by
1457
- * a later round — rather than failing the round for everyone else. A group that cannot be
1458
- * simplified is no different in kind from a group that could not be formed, and the frontier already
1459
- * knows what to do with those: nothing, for now.
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
- * The round as a whole is accepted only when it *strictly* shrinks the frontier. Failing that it
1462
- * widens the band, and failing that it grows the group — small groups starve the simplifier, and a
1463
- * wider band gives a fine region coarser neighbours to merge with.
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
- * @param {VGeoFrontier} frontier
1466
- * @param {number} level of the parents this round would produce
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 = build_leaf_frontier(mesh);
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 many as the container can name`
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 = run_round(frontier, level, options);
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(frontier.mesh, frontier.cluster_faces[index]);
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 = stitch_frontier(contributions, frontier.mesh.layers);
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];