@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,7 +1,15 @@
1
1
  import { assert } from "../../../../../../assert.js";
2
2
  import { Uint32Heap } from "../../../../../../collection/heap/Uint32Heap.js";
3
3
  import { Quadric3 } from "../../../../quadric/Quadric3.js";
4
+ import { QUADRIC3_SIZE } from "../../../../quadric/QUADRIC3_SIZE.js";
5
+ import { quadric3_add } from "../../../../quadric/quadric3_add.js";
6
+ import { quadric3_evaluate } from "../../../../quadric/quadric3_evaluate.js";
7
+ import { quadric_attribute_add } from "../../../../quadric/quadric_attribute_add.js";
8
+ import { quadric_attribute_reduce } from "../../../../quadric/quadric_attribute_reduce.js";
9
+ import { quadric_attribute_size } from "../../../../quadric/quadric_attribute_size.js";
10
+ import { quadric_attribute_solve } from "../../../../quadric/quadric_attribute_solve.js";
4
11
  import { NULL_POINTER } from "../BinaryTopology.js";
12
+ import { bt_edge_collapse_fold_limit } from "../query/bt_edge_collapse_fold_limit.js";
5
13
  import { bt_edge_collapse_would_fold } from "../query/bt_edge_collapse_would_fold.js";
6
14
  import { bt_loop_kill } from "./bt_loop_kill.js";
7
15
  import { bt_mesh_compute_vertex_quadrics } from "./bt_mesh_compute_vertex_quadrics.js";
@@ -17,6 +25,73 @@ const scratch_coord_a = new Float32Array(3);
17
25
  const scratch_coord_b = new Float32Array(3);
18
26
  const scratch_target = new Float32Array(3);
19
27
  const scratch_combined_q = new Quadric3();
28
+ const scratch_anchor = new Float32Array(3);
29
+ const scratch_candidate = new Float32Array(3);
30
+ const scratch_placement = new Float32Array(3);
31
+
32
+ /**
33
+ * How much of the way to the fold limit a constrained placement is allowed to travel.
34
+ *
35
+ * The limit itself is where a surviving triangle reaches *zero* area, which is refused along with an
36
+ * outright flip and for the same reason: a zero-area triangle has no normal, so the quadrics built
37
+ * from it afterwards are meaningless and every later collapse around it is priced off nothing. So
38
+ * stopping short of the limit is a statement about the mesh rather than about arithmetic.
39
+ *
40
+ * Half, because the feasible interval along the segment is `[0, limit)` and its midpoint is the
41
+ * placement furthest from both ends of it — from the anchor, which is the placement this was trying
42
+ * to improve on, and from the degeneracy. Nothing is given up by stopping there: swept against 0.9
43
+ * and 0.99 over Zorah's 479k-triangle `mesh0[0]`, all three build the *same* 34 levels to the *same*
44
+ * root error of `1.0445e+3`, and differ only in cluster count, by 0.3% and in no particular
45
+ * direction (11,511 / 11,498 / 11,532). The choice is free on everything that was measured, so it
46
+ * goes to the end that cannot produce a sliver.
47
+ * @type {number}
48
+ */
49
+ const FOLD_LIMIT_TRAVEL = 0.5;
50
+
51
+ /**
52
+ * The pair's summed attribute quadric, and its reduction. Grown to whatever channel count the
53
+ * current call carries and reused across every candidate it scores — `evaluate_edge_collapse` runs
54
+ * once per heap insert and again for every neighbour of every collapse, and an allocation there is
55
+ * an allocation per edge per round.
56
+ * @type {Float64Array}
57
+ */
58
+ let scratch_attribute_sum = new Float64Array(0);
59
+
60
+ /**
61
+ * @type {Float64Array}
62
+ */
63
+ const scratch_attribute_reduced = new Float64Array(QUADRIC3_SIZE);
64
+
65
+ /**
66
+ * One wedge group's reduction, on its way into the sum over groups.
67
+ * @type {Float64Array}
68
+ */
69
+ const scratch_group_reduced = new Float64Array(QUADRIC3_SIZE);
70
+
71
+ /**
72
+ * The wedges of a candidate's two endpoints, the survivor's first, and the local union-find that
73
+ * groups them. Only the multi-wedge path touches these, which on smooth geometry is never.
74
+ * @type {Uint32Array}
75
+ */
76
+ let scratch_wedge_ids = new Uint32Array(0);
77
+
78
+ /**
79
+ * @type {Uint32Array}
80
+ */
81
+ let scratch_wedge_group = new Uint32Array(0);
82
+
83
+ /**
84
+ * The corners of the faces a collapse would destroy, as `(at a, at b)` pairs. Two on a manifold
85
+ * edge, which is why it starts small and grows.
86
+ * @type {Uint32Array}
87
+ */
88
+ let scratch_dying_loops = new Uint32Array(8);
89
+
90
+ /**
91
+ * The fitted attribute record, on its way onto the survivor.
92
+ * @type {Float64Array}
93
+ */
94
+ let scratch_attribute_values = new Float64Array(0);
20
95
 
21
96
  /**
22
97
  * Count the number of still-live faces attached to an edge via its radial loop cycle.
@@ -52,6 +127,143 @@ function count_edge_faces(mesh, edge_id) {
52
127
  return count;
53
128
  }
54
129
 
130
+ /**
131
+ * What a collapse of this pair costs with its surviving vertex at `(x, y, z)`.
132
+ *
133
+ * The two geometric quadrics say how far the placement is from the surface the pair was cut out of;
134
+ * the reduced attribute quadric, when there is one, says what the channels still cost once each has
135
+ * been given its best value there — in units of length, because the weights put them there.
136
+ *
137
+ * It is exactly the function the placement minimizes — quadric evaluation is linear in the quadric,
138
+ * so the sum `optimize` was handed evaluates to the sum of the two terms below — which is why
139
+ * constraining the placement can only ever raise this, never lower it. Read at the placement
140
+ * actually chosen, so the queue's order is a statement about the collapses that will happen rather
141
+ * than about ones that will not.
142
+ *
143
+ * @param {Quadric3} q1
144
+ * @param {Quadric3} q2
145
+ * @param {boolean} has_attributes whether {@link scratch_attribute_reduced} holds this pair's
146
+ * @param {number} x
147
+ * @param {number} y
148
+ * @param {number} z
149
+ * @returns {number}
150
+ */
151
+ function placement_cost(q1, q2, has_attributes, x, y, z) {
152
+ // Cost can drift slightly negative for near-zero values due to fp noise.
153
+ const geometric = Math.abs(q1.evaluate(x, y, z) + q2.evaluate(x, y, z));
154
+
155
+ if (!has_attributes) {
156
+ return geometric;
157
+ }
158
+
159
+ return geometric + Math.abs(quadric3_evaluate(scratch_attribute_reduced, 0, x, y, z));
160
+ }
161
+
162
+ /**
163
+ * Move `out_target` to the cheapest placement near it that turns no surviving triangle over, or
164
+ * report that the pair has none.
165
+ *
166
+ * **A placement that folds is not an impossible collapse, and this is where the two stop being
167
+ * conflated.** What the quadric hands back is the unconstrained minimum of a convex cost. Keeping
168
+ * every surviving triangle facing the way it did is a *constraint* on that minimum — a linear one,
169
+ * as {@link bt_edge_collapse_fold_limit} derives, so the placements that satisfy it are a convex set
170
+ * and the constrained minimum is the thing this should be choosing. Testing the unconstrained
171
+ * answer and abandoning the collapse when it fails is the classic way of getting that wrong, and it
172
+ * is expensive: measured over one 479k-triangle Zorah primitive, 12% to 15% of every candidate
173
+ * collapse scored was discarded that way — and it is not a tail, it is the whole build, which is how
174
+ * a level of the DAG that could still have been halved came to be reported as having no coarser
175
+ * form at all.
176
+ *
177
+ * The constrained minimum lies on the boundary whenever the unconstrained one is outside, and the
178
+ * boundary is reached along a segment: the cost is convex with its minimum at `out_target`, so along
179
+ * a segment ending there it falls monotonically, and the best placement on that segment is simply
180
+ * the furthest one the fold limit allows. That makes this exact in one dimension rather than a
181
+ * search — what is heuristic is only the choice of where to measure *from*.
182
+ *
183
+ * Three anchors, and they are the three points the surface already passes through: either endpoint,
184
+ * or the middle of the edge. An anchor that folds on its own is no anchor and is skipped; when all
185
+ * three do, the pair really has nothing this can offer and the collapse is refused.
186
+ *
187
+ * @param {BinaryTopology} mesh
188
+ * @param {number} v1
189
+ * @param {number} v2
190
+ * @param {Quadric3} q1
191
+ * @param {Quadric3} q2
192
+ * @param {boolean} has_attributes
193
+ * @param {Float32Array|number[]} out_target the wanted placement, replaced by the chosen one
194
+ * @returns {boolean} `false` when every anchor folds, and the collapse has no placement at all
195
+ */
196
+ function constrain_placement(mesh, v1, v2, q1, q2, has_attributes, out_target) {
197
+ if (!bt_edge_collapse_would_fold(mesh, v1, v2, out_target)) {
198
+ return true;
199
+ }
200
+
201
+ mesh.vertex_read_coordinate(scratch_coord_a, 0, v1);
202
+ mesh.vertex_read_coordinate(scratch_coord_b, 0, v2);
203
+
204
+ // Tracked separately rather than read off `best`, which cannot say the difference between "no
205
+ // anchor held" and "one did, at a price that overflowed" — a quadric carrying the planes of a
206
+ // few hundred thousand source triangles is not far off it.
207
+ let placed = false;
208
+
209
+ let best = Number.POSITIVE_INFINITY;
210
+
211
+ for (let anchor = 0; anchor < 3; anchor++) {
212
+ if (anchor === 0) {
213
+ scratch_anchor.set(scratch_coord_a);
214
+ } else if (anchor === 1) {
215
+ scratch_anchor.set(scratch_coord_b);
216
+ } else {
217
+ scratch_anchor[0] = (scratch_coord_a[0] + scratch_coord_b[0]) * 0.5;
218
+ scratch_anchor[1] = (scratch_coord_a[1] + scratch_coord_b[1]) * 0.5;
219
+ scratch_anchor[2] = (scratch_coord_a[2] + scratch_coord_b[2]) * 0.5;
220
+ }
221
+
222
+ const limit = bt_edge_collapse_fold_limit(mesh, v1, v2, scratch_anchor, out_target);
223
+
224
+ if (limit < 0) {
225
+ continue;
226
+ }
227
+
228
+ const travel = limit * FOLD_LIMIT_TRAVEL;
229
+
230
+ scratch_candidate[0] = scratch_anchor[0] + (out_target[0] - scratch_anchor[0]) * travel;
231
+ scratch_candidate[1] = scratch_anchor[1] + (out_target[1] - scratch_anchor[1]) * travel;
232
+ scratch_candidate[2] = scratch_anchor[2] + (out_target[2] - scratch_anchor[2]) * travel;
233
+
234
+ if (bt_edge_collapse_would_fold(mesh, v1, v2, scratch_candidate)) {
235
+ /*
236
+ The limit is exact in real arithmetic and this is not real arithmetic: a fan whose
237
+ triangles are already slivers can put the crossing and the anchor within a few ulps of
238
+ each other, and half of nothing is still nothing. The anchor is feasible by
239
+ construction, so fall back to it rather than to a search for how much less to travel.
240
+ */
241
+ scratch_candidate.set(scratch_anchor);
242
+ }
243
+
244
+ const cost = placement_cost(
245
+ q1, q2, has_attributes,
246
+ scratch_candidate[0], scratch_candidate[1], scratch_candidate[2]
247
+ );
248
+
249
+ if (!placed || cost < best) {
250
+ placed = true;
251
+ best = cost;
252
+ scratch_placement.set(scratch_candidate);
253
+ }
254
+ }
255
+
256
+ if (!placed) {
257
+ return false;
258
+ }
259
+
260
+ out_target[0] = scratch_placement[0];
261
+ out_target[1] = scratch_placement[1];
262
+ out_target[2] = scratch_placement[2];
263
+
264
+ return true;
265
+ }
266
+
55
267
  /**
56
268
  * Evaluate a potential edge collapse: choose the optimal surviving vertex position
57
269
  * and return the quadric-based cost. Returns Infinity if the edge cannot be collapsed.
@@ -60,10 +272,17 @@ function count_edge_faces(mesh, edge_id) {
60
272
  * @param {number} edge_id
61
273
  * @param {Quadric3[]} quadratics
62
274
  * @param {Set<number>} restricted_vertices
275
+ * @param {{values: Float64Array, components: number, quadrics: Float64Array, weights: Float64Array}|null}
276
+ * attributes the channels the collapse carries, or `null` to carry none
277
+ * @param {BtWedgeSet|null} wedges which corners of each vertex shade as one
63
278
  * @param {Float32Array|number[]} out_target Target position is written here.
64
- * @returns {number}
279
+ * @returns {number} the price at the placement written, which is the placement the collapse would
280
+ * actually use — so the queue this feeds is ordered on what the collapses cost rather than on
281
+ * what they would have cost somewhere they are not allowed to go
65
282
  */
66
- function evaluate_edge_collapse(mesh, edge_id, quadratics, restricted_vertices, out_target) {
283
+ function evaluate_edge_collapse(
284
+ mesh, edge_id, quadratics, restricted_vertices, attributes, wedges, out_target
285
+ ) {
67
286
  const v1 = mesh.edge_read_vertex1(edge_id);
68
287
  const v2 = mesh.edge_read_vertex2(edge_id);
69
288
 
@@ -89,6 +308,12 @@ function evaluate_edge_collapse(mesh, edge_id, quadratics, restricted_vertices,
89
308
  return Number.POSITIVE_INFINITY;
90
309
  }
91
310
 
311
+ const has_attributes = attributes !== null;
312
+
313
+ if (has_attributes) {
314
+ reduce_pair_attributes(mesh, v1, v2, attributes, wedges, scratch_attribute_reduced);
315
+ }
316
+
92
317
  if (v1_restricted) {
93
318
  mesh.vertex_read_coordinate(out_target, 0, v1);
94
319
  } else if (v2_restricted) {
@@ -97,6 +322,22 @@ function evaluate_edge_collapse(mesh, edge_id, quadratics, restricted_vertices,
97
322
  scratch_combined_q.copy(q1);
98
323
  scratch_combined_q.add(q2);
99
324
 
325
+ if (has_attributes) {
326
+ /*
327
+ The channels decide where the vertex goes, and this is where they do it. `A` and `b` of
328
+ the reduced attribute quadric are what is left of them after the exact solve for their
329
+ own values, so a collapse across a crease or a texture seam is pulled away from the
330
+ placement that would smear it.
331
+
332
+ Only `optimize` reads this sum. Its weight slot is now a plane count plus a triangle
333
+ count, which is not a quantity, and nothing downstream takes a distance from it -- the
334
+ cost below is the two pure geometric quadrics, evaluated separately.
335
+ */
336
+ quadric3_add(
337
+ scratch_combined_q, 0, scratch_combined_q, 0, scratch_attribute_reduced, 0
338
+ );
339
+ }
340
+
100
341
  if (!scratch_combined_q.optimize(out_target)) {
101
342
  // Singular combined tensor: fall back to the midpoint
102
343
  mesh.vertex_read_coordinate(scratch_coord_a, 0, v1);
@@ -108,25 +349,45 @@ function evaluate_edge_collapse(mesh, edge_id, quadratics, restricted_vertices,
108
349
  }
109
350
  }
110
351
 
111
- const tx = out_target[0];
112
- const ty = out_target[1];
113
- const tz = out_target[2];
352
+ if (v1_restricted || v2_restricted) {
353
+ /*
354
+ A pin takes the placement with it, so there is no segment to slide along and a fold here is
355
+ final. Refused with the price rather than by the caller, so the queue never holds an edge
356
+ whose cost describes a collapse that will not be performed.
357
+ */
358
+ if (bt_edge_collapse_would_fold(mesh, v1, v2, out_target)) {
359
+ return Number.POSITIVE_INFINITY;
360
+ }
361
+ } else if (!constrain_placement(mesh, v1, v2, q1, q2, has_attributes, out_target)) {
362
+ return Number.POSITIVE_INFINITY;
363
+ }
114
364
 
115
- // Cost can drift slightly negative for near-zero values due to fp noise.
116
- return Math.abs(q1.evaluate(tx, ty, tz) + q2.evaluate(tx, ty, tz));
365
+ return placement_cost(
366
+ q1, q2, has_attributes, out_target[0], out_target[1], out_target[2]
367
+ );
117
368
  }
118
369
 
119
370
  /**
120
371
  * Re-score every edge in the disk cycle of `survivor` that is currently in the heap.
121
372
  * Edges that were never inserted (e.g. both endpoints restricted) are left untouched.
122
373
  *
374
+ * Left untouched including the ones that were left out because they had no placement at the time,
375
+ * which is a real limit and a deliberate one: an edge is excluded when *both* its fans leave it
376
+ * nowhere to go, and a collapse one edge away is unlikely to change that. Re-admitting them here was
377
+ * built and measured — it moved a 479k-triangle Zorah primitive from 34 levels to 33, and cost 60%
378
+ * of the build time to do it.
379
+ *
123
380
  * @param {BinaryTopology} mesh
124
381
  * @param {number} survivor
125
382
  * @param {Quadric3[]} quadratics
126
383
  * @param {Set<number>} restricted_vertices
384
+ * @param {{values: Float64Array, components: number, quadrics: Float64Array, weights: Float64Array}|null} attributes
385
+ * @param {BtWedgeSet|null} wedges
127
386
  * @param {Uint32Heap} heap
128
387
  */
129
- function update_neighbor_costs(mesh, survivor, quadratics, restricted_vertices, heap) {
388
+ function update_neighbor_costs(
389
+ mesh, survivor, quadratics, restricted_vertices, attributes, wedges, heap
390
+ ) {
130
391
  let curr_e = mesh.vertex_read_edge(survivor);
131
392
 
132
393
  if (curr_e === NULL_POINTER) {
@@ -148,7 +409,10 @@ function update_neighbor_costs(mesh, survivor, quadratics, restricted_vertices,
148
409
  const idx = heap.find_index_by_id(curr_e);
149
410
 
150
411
  if (idx !== -1) {
151
- const cost = evaluate_edge_collapse(mesh, curr_e, quadratics, restricted_vertices, scratch_target);
412
+ const cost = evaluate_edge_collapse(
413
+ mesh, curr_e, quadratics, restricted_vertices, attributes, wedges, scratch_target
414
+ );
415
+
152
416
  heap.__update_score_by_index(idx, cost);
153
417
  }
154
418
 
@@ -156,6 +420,504 @@ function update_neighbor_costs(mesh, survivor, quadratics, restricted_vertices,
156
420
  } while (curr_e !== start_e && curr_e !== NULL_POINTER);
157
421
  }
158
422
 
423
+ /**
424
+ * The corners of every face a collapse of `(a, b)` would destroy, as pairs — the corner at `a`
425
+ * followed by the corner at `b` — written into {@link scratch_dying_loops}.
426
+ *
427
+ * **The faces a collapse destroys are not only the ones on the edge it names.** They are the ones
428
+ * holding *both* endpoints: the edge's own, the ones on any parallel edge {@link bt_edge_kill_parallels}
429
+ * clears first, and the ones {@link bt_vertex_fuse_duplicate_edges} finds degenerate afterwards. That
430
+ * is {@link collapse_kills_protected_face}'s contract, written down there and true here for the same
431
+ * reason — and a triangle holding both endpoints has an edge between them, so walking the disk cycle
432
+ * for edges to `b` finds all of them.
433
+ *
434
+ * Walking one radial cycle instead would miss the parallel edges, and a wedge whose only dying face
435
+ * is on one of those would be adopted by the survivor rather than merged into it: two corner groups
436
+ * that are one piece of surface, left as two, which is a crease where the mesh has none.
437
+ *
438
+ * @param {BinaryTopology} mesh
439
+ * @param {number} a
440
+ * @param {number} b
441
+ * @returns {number} how many pairs were written
442
+ */
443
+ function collect_dying_faces(mesh, a, b) {
444
+ let count = 0;
445
+
446
+ let edge = mesh.vertex_read_edge(a);
447
+
448
+ if (edge === NULL_POINTER) {
449
+ return 0;
450
+ }
451
+
452
+ const start_edge = edge;
453
+ const face_pool = mesh.faces;
454
+
455
+ do {
456
+ const at_v1 = mesh.edge_read_vertex1(edge) === a;
457
+
458
+ const other = at_v1 ? mesh.edge_read_vertex2(edge) : mesh.edge_read_vertex1(edge);
459
+
460
+ if (other === b) {
461
+ const first_loop = mesh.edge_read_loop(edge);
462
+
463
+ let loop = first_loop;
464
+
465
+ while (loop !== NULL_POINTER) {
466
+ const face = mesh.loop_read_face(loop);
467
+
468
+ if (face !== NULL_POINTER && face_pool.is_allocated(face)) {
469
+ const next_loop = mesh.loop_read_next(loop);
470
+
471
+ const at_a = mesh.loop_read_vertex(loop) === a ? loop : next_loop;
472
+
473
+ if (scratch_dying_loops.length < (count + 1) * 2) {
474
+ const grown = new Uint32Array(Math.max(8, (count + 1) * 4));
475
+
476
+ grown.set(scratch_dying_loops);
477
+
478
+ scratch_dying_loops = grown;
479
+ }
480
+
481
+ scratch_dying_loops[count * 2] = at_a;
482
+ scratch_dying_loops[count * 2 + 1] = at_a === loop ? next_loop : loop;
483
+
484
+ count++;
485
+ }
486
+
487
+ loop = mesh.loop_read_radial_next(loop);
488
+
489
+ if (loop === first_loop) {
490
+ break;
491
+ }
492
+ }
493
+ }
494
+
495
+ edge = at_v1 ? mesh.edge_read_v1_disk_next(edge) : mesh.edge_read_v2_disk_next(edge);
496
+ } while (edge !== start_edge && edge !== NULL_POINTER);
497
+
498
+ return count;
499
+ }
500
+
501
+ /**
502
+ * Which wedges of a collapse's two endpoints would become one, and the reduced attribute quadric
503
+ * that follows from it.
504
+ *
505
+ * **A live face is what says two corners shade as one.** Each face on the edge holds one corner at
506
+ * each endpoint, and that face is about to die into the survivor — so whatever those two corners
507
+ * were, they are one corner's worth of surface afterwards. Everything else the two vertices carry
508
+ * stays apart. That rule needs no tolerance and no comparison of values, which matters: two
509
+ * neighbours on a smoothly curved surface have genuinely different normals, so a rule that merged
510
+ * only equal values would invent a crease at every collapse a sphere has.
511
+ *
512
+ * Each resulting group is reduced separately and the reductions summed, because each group solves
513
+ * for its own value ({@link quadric_attribute_reduce} is the substitution of that solve). Summing
514
+ * the quadrics first and reducing once would be the claim that all of them share one value, which
515
+ * is the thing a seam denies.
516
+ *
517
+ * **A collapse with no face to destroy is a fuse of two points, not a simplification of a surface**,
518
+ * and it is treated as one group. Nothing survives to hold the corners apart, and the alternative
519
+ * leaves the victim's accumulated history on a wedge that may have no corners left to carry it. Rare,
520
+ * and measured rather than assumed: 6 of 6,195 collapses over a 12,288-triangle sphere's whole DAG.
521
+ *
522
+ * @param {BinaryTopology} mesh
523
+ * @param {number} v1
524
+ * @param {number} v2
525
+ * @param {{values: Float64Array, components: number, quadrics: Float64Array, weights: Float64Array}} attributes
526
+ * @param {BtWedgeSet} wedges
527
+ * @param {Float64Array} out `QUADRIC3_SIZE` elements, overwritten
528
+ */
529
+ function reduce_pair_attributes(mesh, v1, v2, attributes, wedges, out) {
530
+ const components = attributes.components;
531
+ const size = quadric_attribute_size(components);
532
+
533
+ const quadrics = attributes.quadrics;
534
+
535
+ const first_1 = wedges.first_of_vertex[v1];
536
+ const first_2 = wedges.first_of_vertex[v2];
537
+
538
+ /*
539
+ One wedge each, which is every vertex of a mesh with no seams and the overwhelming majority of
540
+ one that has them. They can only end up in the same group, so the answer is the sum of the two
541
+ reduced once -- exactly the arithmetic this had before wedges existed, kept on the path that
542
+ runs once per edge per round.
543
+ */
544
+ if (
545
+ first_1 !== NULL_POINTER && wedges.next_of_wedge[first_1] === NULL_POINTER
546
+ && first_2 !== NULL_POINTER && wedges.next_of_wedge[first_2] === NULL_POINTER
547
+ ) {
548
+ quadric_attribute_add(
549
+ scratch_attribute_sum, 0,
550
+ quadrics, first_1 * size, quadrics, first_2 * size,
551
+ components
552
+ );
553
+
554
+ quadric_attribute_reduce(out, 0, scratch_attribute_sum, 0, components);
555
+
556
+ return;
557
+ }
558
+
559
+ let count = 0;
560
+
561
+ for (let w = first_1; w !== NULL_POINTER; w = wedges.next_of_wedge[w]) {
562
+ count++;
563
+ }
564
+
565
+ for (let w = first_2; w !== NULL_POINTER; w = wedges.next_of_wedge[w]) {
566
+ count++;
567
+ }
568
+
569
+ if (scratch_wedge_ids.length < count) {
570
+ scratch_wedge_ids = new Uint32Array(count);
571
+ scratch_wedge_group = new Uint32Array(count);
572
+ }
573
+
574
+ let n = 0;
575
+
576
+ for (let w = first_1; w !== NULL_POINTER; w = wedges.next_of_wedge[w]) {
577
+ scratch_wedge_ids[n] = w;
578
+ scratch_wedge_group[n] = n;
579
+ n++;
580
+ }
581
+
582
+ const first_count = n;
583
+
584
+ for (let w = first_2; w !== NULL_POINTER; w = wedges.next_of_wedge[w]) {
585
+ scratch_wedge_ids[n] = w;
586
+ scratch_wedge_group[n] = n;
587
+ n++;
588
+ }
589
+
590
+ const dying = collect_dying_faces(mesh, v1, v2);
591
+
592
+ for (let pair = 0; pair < dying; pair++) {
593
+ local_union(
594
+ local_index(wedges.find(wedges.of_loop[scratch_dying_loops[pair * 2]]), n),
595
+ local_index(wedges.find(wedges.of_loop[scratch_dying_loops[pair * 2 + 1]]), n)
596
+ );
597
+ }
598
+
599
+ if (dying === 0 && first_count > 0) {
600
+ /*
601
+ Whatever the second vertex holds joins the first's first wedge, and the first's *other*
602
+ wedges are left alone -- which is exactly what {@link carry_wedges} does when the edge has no
603
+ face, and the two have to agree or the price of a collapse is not the price of the collapse
604
+ that happens. (Which of the pair survives is decided after this runs, so a swapped pair
605
+ scores the mirror of what it will do; the shape of the partition is the same either way and
606
+ this is a heuristic over 0.1% of collapses.)
607
+ */
608
+ for (let i = first_count; i < n; i++) {
609
+ local_union(0, i);
610
+ }
611
+ }
612
+
613
+ out.fill(0, 0, QUADRIC3_SIZE);
614
+
615
+ for (let group = 0; group < n; group++) {
616
+ if (local_find(group) !== group) {
617
+ continue;
618
+ }
619
+
620
+ let members = 0;
621
+
622
+ for (let i = 0; i < n; i++) {
623
+ if (local_find(i) !== group) {
624
+ continue;
625
+ }
626
+
627
+ const from = scratch_wedge_ids[i] * size;
628
+
629
+ if (members === 0) {
630
+ for (let element = 0; element < size; element++) {
631
+ scratch_attribute_sum[element] = quadrics[from + element];
632
+ }
633
+ } else {
634
+ quadric_attribute_add(
635
+ scratch_attribute_sum, 0, scratch_attribute_sum, 0, quadrics, from, components
636
+ );
637
+ }
638
+
639
+ members++;
640
+ }
641
+
642
+ quadric_attribute_reduce(scratch_group_reduced, 0, scratch_attribute_sum, 0, components);
643
+
644
+ quadric3_add(out, 0, out, 0, scratch_group_reduced, 0);
645
+ }
646
+ }
647
+
648
+ /**
649
+ * Where a wedge id sits in {@link scratch_wedge_ids}, or `-1`.
650
+ *
651
+ * A scan rather than a map: it is over the wedges of two vertices, which is two on a crease and one
652
+ * on everything else.
653
+ *
654
+ * @param {number} wedge
655
+ * @param {number} count
656
+ * @returns {number}
657
+ */
658
+ function local_index(wedge, count) {
659
+ for (let i = 0; i < count; i++) {
660
+ if (scratch_wedge_ids[i] === wedge) {
661
+ return i;
662
+ }
663
+ }
664
+
665
+ return -1;
666
+ }
667
+
668
+ /**
669
+ * @param {number} index
670
+ * @returns {number}
671
+ */
672
+ function local_find(index) {
673
+ let root = index;
674
+
675
+ while (scratch_wedge_group[root] !== root) {
676
+ root = scratch_wedge_group[root];
677
+ }
678
+
679
+ return root;
680
+ }
681
+
682
+ /**
683
+ * @param {number} a index into {@link scratch_wedge_ids}, or `-1` for a wedge not among them
684
+ * @param {number} b
685
+ */
686
+ function local_union(a, b) {
687
+ if (a < 0 || b < 0) {
688
+ return;
689
+ }
690
+
691
+ const root_a = local_find(a);
692
+ const root_b = local_find(b);
693
+
694
+ if (root_a !== root_b) {
695
+ scratch_wedge_group[root_b] = root_a;
696
+ }
697
+ }
698
+
699
+ /**
700
+ * Move the survivor's attribute record onto the value the pair between them stands for, when there
701
+ * is no field to fit one from.
702
+ *
703
+ * The fallback, not the rule. A vertex whose every incident triangle was a sliver -- or which has
704
+ * none left at all -- has an attribute quadric of no weight, and a quadric of no weight has no field
705
+ * to evaluate. Blending is what is left, and it is a reasonable answer rather than a good one: it is
706
+ * confined to the values that went in, where a fit is not, and it says nothing about where the
707
+ * vertex ended up.
708
+ *
709
+ * Blended by accumulated quadric weight, which is how much source surface each endpoint speaks for:
710
+ * a vertex that a dozen collapses have folded into already represents a dozen vertices' worth of the
711
+ * original, and taking half its word against a vertex that represents one would let whichever side
712
+ * of a merge happened to be shorter of history pull the value across.
713
+ *
714
+ * Two endpoints that have accumulated nothing have no claim over one another, and split it.
715
+ *
716
+ * @param {Float64Array} values indexed by wedge id, `components` elements each
717
+ * @param {number} components
718
+ * @param {number} survivor wedge
719
+ * @param {number} victim wedge
720
+ * @param {number} survivor_weight
721
+ * @param {number} victim_weight
722
+ */
723
+ function blend_attributes(values, components, survivor, victim, survivor_weight, victim_weight) {
724
+ const total = survivor_weight + victim_weight;
725
+
726
+ const survivor_share = total > 0 ? survivor_weight / total : 0.5;
727
+ const victim_share = 1 - survivor_share;
728
+
729
+ const survivor_base = survivor * components;
730
+ const victim_base = victim * components;
731
+
732
+ for (let component = 0; component < components; component++) {
733
+ values[survivor_base + component] =
734
+ values[survivor_base + component] * survivor_share
735
+ + values[victim_base + component] * victim_share;
736
+ }
737
+ }
738
+
739
+ /**
740
+ * Merge the pair's wedges, and — where there are values riding along — decide what the survivor
741
+ * carries and give it the victim's accumulated fields.
742
+ *
743
+ * **The merge happens whether or not anything is being carried.** A wedge set is a statement about
744
+ * which corners of the result belong together, and a set that stops tracking collapses is a set
745
+ * whose ids go stale: the victim's loops arrive at the survivor still naming the victim's wedge, and
746
+ * every consumer downstream reads a seam where there is none. Measured, because it got past a
747
+ * byte-identity gate and was caught by an unrelated assertion instead: a round over a mesh with no
748
+ * channels at all cut clusters of 132 vertices against a ceiling of 128.
749
+ *
750
+ * Once per **wedge**, because a vertex has one position and not one appearance. The survivor comes
751
+ * out of this holding its own wedges, whichever of the victim's merged into them, and whichever of
752
+ * the victim's did not — those simply move, still carrying what they always did.
753
+ * {@link reduce_pair_attributes} is the rule for which is which, and it is the same walk.
754
+ *
755
+ * Every wedge that ends up on the survivor is fitted afresh, not only the merged ones: the vertex
756
+ * moved, and a field is evaluated *at a position*.
757
+ *
758
+ * A merged wedge's quadric absorbs its partner's first, so the fit it is then asked for is over
759
+ * *everything* the pair has ever stood for — which is what makes the answer a deviation from the
760
+ * source rather than from the step before it, exactly as the geometric quadric's is.
761
+ *
762
+ * Then the value: the position it is being moved to, plugged into the field those accumulated
763
+ * triangles describe. Where there is no field — a wedge every one of whose triangles was a sliver,
764
+ * or which has no triangles left — {@link blend_attributes} is the fallback, applied at the merge so
765
+ * that a wedge with nothing to fit from still ends up between the two values rather than on one of
766
+ * them. A successful fit overwrites it. (Two victim wedges merging into one survivor wedge, which
767
+ * takes a non-manifold edge, blend the second against the first's result; it is a fallback inside a
768
+ * fallback and it is confined to the values that went in either way.)
769
+ *
770
+ * **A restricted survivor is given the victim's fields and none of its values.** Its position does
771
+ * not move, but the fit at that unchanged position would still come out different, because `W`, `G`
772
+ * and `D` under it have grown. That would break the one thing pinning is for: a vertex two groups
773
+ * share must carry byte-identical values in both. It keeps the quadric so that a *later* step, one
774
+ * that is free to move it, fits from the whole history rather than from the fragment it started
775
+ * with. A wedge it *adopts* is a wedge the other group never had, carrying corners that are this
776
+ * group's alone, so it is no exception to that.
777
+ *
778
+ * @param {BinaryTopology} mesh
779
+ * @param {BtWedgeSet} wedges
780
+ * @param {{values: Float64Array, components: number, quadrics: Float64Array, weights: Float64Array}|null} attributes
781
+ * @param {number} survivor
782
+ * @param {number} victim
783
+ * @param {boolean} survivor_restricted
784
+ * @param {number} survivor_weight geometric quadric weight, for the fallback
785
+ * @param {number} victim_weight
786
+ * @param {Float32Array|number[]} target where the survivor is being moved to
787
+ */
788
+ function carry_wedges(
789
+ mesh, wedges, attributes, survivor, victim, survivor_restricted,
790
+ survivor_weight, victim_weight, target
791
+ ) {
792
+ const dying = collect_dying_faces(mesh, survivor, victim);
793
+
794
+ for (let pair = 0; pair < dying; pair++) {
795
+ absorb_wedge(
796
+ wedges, attributes,
797
+ wedges.find(wedges.of_loop[scratch_dying_loops[pair * 2]]),
798
+ wedges.find(wedges.of_loop[scratch_dying_loops[pair * 2 + 1]]),
799
+ survivor_restricted, survivor_weight, victim_weight
800
+ );
801
+ }
802
+
803
+ if (dying === 0 && wedges.first_of_vertex[survivor] !== NULL_POINTER) {
804
+ // A fuse of two points rather than a simplification of a surface; see
805
+ // {@link reduce_pair_attributes}, whose cost model this has to agree with.
806
+ const into = wedges.find(wedges.first_of_vertex[survivor]);
807
+
808
+ for (
809
+ let wedge = wedges.first_of_vertex[victim];
810
+ wedge !== NULL_POINTER;
811
+ wedge = wedges.next_of_wedge[wedge]
812
+ ) {
813
+ absorb_wedge(
814
+ wedges, attributes, into, wedges.find(wedge),
815
+ survivor_restricted, survivor_weight, victim_weight
816
+ );
817
+ }
818
+ }
819
+
820
+ // The survivor's own list, less whatever merged into something else, then the victim's on top of
821
+ // it. The loops themselves are not touched: a corner's wedge id goes stale and `find` resolves
822
+ // it, which is the whole reason nothing has to go looking for a corner in order to move it.
823
+ relist_wedges(wedges, survivor, survivor);
824
+ relist_wedges(wedges, victim, survivor);
825
+
826
+ if (survivor_restricted || attributes === null) {
827
+ return;
828
+ }
829
+
830
+ const components = attributes.components;
831
+
832
+ for (
833
+ let wedge = wedges.first_of_vertex[survivor];
834
+ wedge !== NULL_POINTER;
835
+ wedge = wedges.next_of_wedge[wedge]
836
+ ) {
837
+ const fitted = quadric_attribute_solve(
838
+ scratch_attribute_values, 0,
839
+ attributes.quadrics, wedge * quadric_attribute_size(components), components,
840
+ attributes.weights,
841
+ target[0], target[1], target[2]
842
+ );
843
+
844
+ if (fitted) {
845
+ attributes.values.set(
846
+ scratch_attribute_values.subarray(0, components), wedge * components
847
+ );
848
+ }
849
+ }
850
+ }
851
+
852
+ /**
853
+ * Make `other` part of `wedge`: its accumulated field, and — for the case where there turns out to
854
+ * be no field to fit from — its value.
855
+ *
856
+ * @param {BtWedgeSet} wedges
857
+ * @param {{values: Float64Array, components: number, quadrics: Float64Array, weights: Float64Array}|null} attributes
858
+ * @param {number} wedge root that survives, the survivor's
859
+ * @param {number} other root that does not, the victim's
860
+ * @param {boolean} survivor_restricted
861
+ * @param {number} survivor_weight
862
+ * @param {number} victim_weight
863
+ */
864
+ function absorb_wedge(
865
+ wedges, attributes, wedge, other, survivor_restricted, survivor_weight, victim_weight
866
+ ) {
867
+ if (!wedges.merge(wedge, other)) {
868
+ // Already one wedge. A manifold edge's two faces name the same pair twice, which is the
869
+ // common case rather than an oddity, so this is the guard that keeps a quadric from being
870
+ // counted into itself.
871
+ return;
872
+ }
873
+
874
+ if (attributes === null) {
875
+ return;
876
+ }
877
+
878
+ const components = attributes.components;
879
+ const size = quadric_attribute_size(components);
880
+
881
+ quadric_attribute_add(
882
+ attributes.quadrics, wedge * size,
883
+ attributes.quadrics, wedge * size,
884
+ attributes.quadrics, other * size,
885
+ components
886
+ );
887
+
888
+ if (!survivor_restricted) {
889
+ blend_attributes(
890
+ attributes.values, components, wedge, other, survivor_weight, victim_weight
891
+ );
892
+ }
893
+ }
894
+
895
+ /**
896
+ * Move every wedge of `from` onto `to`, dropping the ones that are no longer their own root.
897
+ *
898
+ * `from === to` is a compaction in place, which is what the survivor needs: its own wedges can have
899
+ * merged into each other, through a victim wedge that touched both.
900
+ *
901
+ * @param {BtWedgeSet} wedges
902
+ * @param {number} from vertex
903
+ * @param {number} to vertex
904
+ */
905
+ function relist_wedges(wedges, from, to) {
906
+ let wedge = wedges.first_of_vertex[from];
907
+
908
+ wedges.detach_all(from);
909
+
910
+ while (wedge !== NULL_POINTER) {
911
+ const next = wedges.next_of_wedge[wedge];
912
+
913
+ if (wedges.find(wedge) === wedge) {
914
+ wedges.attach(wedge, to);
915
+ }
916
+
917
+ wedge = next;
918
+ }
919
+ }
920
+
159
921
  /**
160
922
  * Would collapsing this edge destroy a face the caller asked to keep?
161
923
  *
@@ -228,9 +990,24 @@ function collapse_kills_protected_face(mesh, edge_id, protected_faces) {
228
990
  * removed. If the target cannot be reached (e.g. too many restricted vertices),
229
991
  * simplification stops early without failing.
230
992
  *
231
- * A collapse that would turn a surviving triangle inside out is refused however cheap the quadric
232
- * says it is ({@link bt_edge_collapse_would_fold}) -- the quadric cannot see a fold, because a flat
233
- * region scores zero for every placement in its plane.
993
+ * No collapse ever turns a surviving triangle inside out, and the quadric cannot help with that: it
994
+ * cannot see a fold at all, because a flat region scores zero for every placement in its plane. So
995
+ * the placement is *constrained* rather than merely checked. Where the quadric's own answer folds,
996
+ * the collapse is re-placed at the best point that does not — see {@link constrain_placement}, and
997
+ * {@link bt_edge_collapse_fold_limit} for why that point can be found exactly instead of hunted for.
998
+ * A collapse is refused only when the pair has no such placement anywhere, which is a statement
999
+ * about the geometry rather than about the quadric's preference.
1000
+ *
1001
+ * Every edge is priced at the placement it would actually use, including the constrained ones, and
1002
+ * the queue is ordered on that. It is the expensive way round — the constraint costs a walk of both
1003
+ * the pair's fans, and pricing happens once per edge at the start and again for every neighbour of
1004
+ * every collapse, which measures at 1.4x to 2.5x the build time of the alternative. The alternative
1005
+ * is to order on the unconstrained price, which is a genuine lower bound ({@link placement_cost}),
1006
+ * and settle only on the way out of the queue. That was built and measured, and it is not here: the
1007
+ * greedy order it produces is approximate, and on a pinwheel whose whole rim is pinned it stopped at
1008
+ * 28 faces against a target of 21 — which is precisely the shape a vgeo group step hands this, since
1009
+ * such a step pins its entire outer boundary. A cheaper order that cannot finish a pinned
1010
+ * simplification is no bargain.
234
1011
  *
235
1012
  * **Topology is not preserved**, and cannot be here. The other standard guard, the *link condition*
236
1013
  * ({@link bt_edge_collapse_breaks_link_condition}), refuses collapses that weld together two parts
@@ -246,21 +1023,36 @@ function collapse_kills_protected_face(mesh, edge_id, protected_faces) {
246
1023
  * At the end of the call the mesh is cleaned up so that no dangling loops, edges,
247
1024
  * or vertices remain in the topology.
248
1025
  *
249
- * The return value is the **object-space distance** the surface moved: the largest, over every
250
- * vertex, of that vertex's accumulated quadric evaluated where the vertex ended up, normalized by
251
- * the quadric's weight. Because a survivor inherits its victim's quadric, this measures deviation
252
- * from the mesh as it stood when the call began — not merely from the step before.
1026
+ * The return value is the **object-space distance** the surface moved: the largest reading taken at
1027
+ * any collapse the call performed, a collapse being read by evaluating *each* of its two endpoints'
1028
+ * accumulated quadrics at the placement the survivor is about to take, each normalized by its own
1029
+ * weight. Because a survivor inherits its victim's quadric, every such reading is a deviation from
1030
+ * the mesh as it stood when the call began — not merely from the step before.
253
1031
  *
254
- * It is taken *before* faceless vertices are cleaned away, and that is not an implementation detail.
255
- * A patch that is consumed entirely leaves a vertex behind that belongs to no face; that vertex is
256
- * the only thing still holding the quadric of everything that used to be there, and reading the mesh
257
- * after cleanup would find no trace of it. A soup of separate triangles simplifies by *deleting*
258
- * them, and measuring survivors alone reports the deletion of ninety per cent of a model as costing
259
- * nothing at all.
1032
+ * **At the collapses, and per endpoint, and both of those are what keep it from diluting.** The
1033
+ * weight is what makes the value a length ({@link Quadric3#distance}) and it is also a divisor that
1034
+ * grows every time a vertex absorbs another. A vertex that flattens a spike and then spends the rest
1035
+ * of the call swallowing the plane around it ends up standing mostly for planes it sits exactly on,
1036
+ * and a single reading taken at the end divides the spike it destroyed by all of them — measured on
1037
+ * a unit spike at the centre of a pinned grid, the same spike came back at 0.64 on a 7-vertex grid
1038
+ * and at 0.040 on a 33-vertex one, which is the reported error falling as the surface grows.
1039
+ * Reading at every collapse catches the spike while the quadric is still mostly about it. Reading
1040
+ * the two endpoints apart rather than summed stops a small victim's residual being averaged into a
1041
+ * large survivor's untouched history, which is that same dilution one collapse at a time.
260
1042
  *
261
- * A caller that needs deviation from something *older* than this call's input — a mesh several
262
- * simplification passes back — supplies its own quadrics through
263
- * {@link bt_mesh_simplify_with_quadrics} and keeps them across passes.
1043
+ * Nothing is given up by no longer scanning the survivors, and it is a domination rather than a
1044
+ * coincidence. What the scan would read off a survivor is its *merged* quadric at the placement its
1045
+ * last collapse chose — and a merged quadric's reading is the weight-weighted mean of its two parts'
1046
+ * readings at that same point, so it can never exceed the larger of the two this already took there.
1047
+ * Every value the scan could report is therefore one this maximum has seen or beaten, and the
1048
+ * maximum holds the earlier and sharper readings besides. A patch consumed entirely, which leaves
1049
+ * behind a faceless vertex that the cleanup then sweeps, is read at the collapse that consumed it
1050
+ * instead of in the window between the last collapse and the sweep.
1051
+ *
1052
+ * So what comes back is what *this call's* collapses cost. A caller carrying quadrics across calls
1053
+ * through {@link bt_mesh_simplify_with_quadrics} carries the maximum across them too — and the
1054
+ * deviation a vertex arrives already carrying is, by construction, a reading that the call which
1055
+ * placed it there has already returned.
264
1056
  *
265
1057
  * `protected_faces` names faces that must still be there when the call returns. Pinning vertices
266
1058
  * does not by itself protect the *edges* between them: a collapse elsewhere in the fan can take away
@@ -316,7 +1108,48 @@ export function bt_mesh_simplify(
316
1108
  * @param {number} target_face_count Desired face count after simplification.
317
1109
  * @param {Set<number>} restricted_vertices Vertices that must not move or be removed.
318
1110
  * @param {Set<number>} protected_faces Faces that must not be removed.
1111
+ * `attributes` is per-vertex data that travels *through* the collapse rather than being re-derived
1112
+ * from the result. Whatever a merged vertex is to carry has to be decided here, from the values
1113
+ * being merged: the mesh a collapse leaves behind is a different surface, and a value read off it
1114
+ * afterwards describes that one rather than the one the caller started with.
1115
+ *
1116
+ * It is carried per **wedge** — a set of corners at one vertex that shade as one — so a hard edge
1117
+ * or a texture seam survives a collapse instead of being averaged away. Which of the pair's wedges
1118
+ * become one is decided by the faces the collapse destroys, in {@link reduce_pair_attributes}; a
1119
+ * mesh with no seams has one wedge per vertex, every collapse merges the pair, and all of this is
1120
+ * the arithmetic it always was.
1121
+ *
1122
+ * It is **fitted**, not inherited and not averaged. Each channel varies affinely over a triangle, so
1123
+ * every triangle a wedge has ever absorbed says what that channel should be at any position; the
1124
+ * survivor takes the value that best explains all of them at once, at the position it is being moved
1125
+ * to ({@link quadric_attribute_solve}). Those same accumulated fields also reach the *placement* and
1126
+ * the cost, through {@link quadric_attribute_reduce} -- so a collapse that would smear a crease or a
1127
+ * texture seam is both moved away from it and priced for it. A vertex with no field to fit from
1128
+ * falls back to a weighted blend, and a **restricted** survivor is left exactly as it is, for the
1129
+ * same reason its position is.
1130
+ *
1131
+ * The values themselves mean nothing here. A caller whose record holds a direction or a flag rather
1132
+ * than a quantity has to say so afterwards, over the array this leaves behind.
1133
+ *
1134
+ * What the call **returns** is still the pure geometric quadric's answer, evaluated at wherever the
1135
+ * higher-dimensional metric put each vertex. Solving in one metric and reporting in the other is
1136
+ * what keeps the number an object-space length (`VIRTUAL_GEOMETRY_DESIGN.md` §3).
1137
+ *
319
1138
  * @param {Quadric3[]} quadratics indexed by vertex id, one per allocated vertex; mutated in place
1139
+ * @param {{values: Float64Array, components: number, quadrics: Float64Array, weights: Float64Array}|null} [attributes]
1140
+ * per-wedge records to carry through the collapses, with the attribute quadrics that fit them
1141
+ * ({@link bt_mesh_compute_wedge_attribute_quadrics}) and the per-channel weights those were
1142
+ * built with. `values` and `quadrics` are indexed by **wedge id**;
1143
+ * {@link bt_wedges_per_vertex} is the set where that is the vertex id. `null` to carry nothing,
1144
+ * which is what every caller but the virtual-geometry builder does. {@link bt_mesh_simplify_by_error}
1145
+ * is not among them at all — it is a separate implementation with its own cost function, and so
1146
+ * is untouched by any of this, which matters because it simplifies to a *tolerance* that an
1147
+ * attribute term would quietly move.
1148
+ * @param {BtWedgeSet|null} [wedges] which corners of each vertex shade as one, **mutated in place**
1149
+ * as the collapses merge them. Required when `attributes` is given, and useful without it: a
1150
+ * caller that carries no values may still care which corners of the result belong together, and
1151
+ * a set that is not maintained is a set whose ids go stale — corners of one vertex would resolve
1152
+ * to different wedges and every consumer downstream would see a seam that is not there.
320
1153
  * @returns {number} object-space distance the surface moved, `0` when no collapse was performed
321
1154
  */
322
1155
  export function bt_mesh_simplify_with_quadrics(
@@ -324,7 +1157,9 @@ export function bt_mesh_simplify_with_quadrics(
324
1157
  target_face_count,
325
1158
  restricted_vertices,
326
1159
  protected_faces,
327
- quadratics
1160
+ quadratics,
1161
+ attributes = null,
1162
+ wedges = null
328
1163
  ) {
329
1164
  assert.defined(mesh, 'mesh');
330
1165
  assert.notNull(mesh, 'mesh');
@@ -341,10 +1176,34 @@ export function bt_mesh_simplify_with_quadrics(
341
1176
  // Tracked from here on rather than re-counted, so progress costs nothing per iteration.
342
1177
  let current_face_count = face_pool.count();
343
1178
 
1179
+ /**
1180
+ * The largest object-space distance any collapse so far has put the surface from the one the
1181
+ * quadrics remember. See {@link bt_mesh_simplify} for why it is a running maximum over the
1182
+ * collapses rather than one sweep of the survivors at the end.
1183
+ * @type {number}
1184
+ */
1185
+ let deviation = 0;
1186
+
344
1187
  if (current_face_count <= target_face_count) {
345
1188
  return 0;
346
1189
  }
347
1190
 
1191
+ if (attributes !== null) {
1192
+ assert.notNull(wedges, 'wedges, which carrying attributes requires');
1193
+
1194
+ const size = quadric_attribute_size(attributes.components);
1195
+
1196
+ // Grown, never shrunk: a build is thousands of calls over the same handful of channel
1197
+ // counts, and every one of them would otherwise allocate.
1198
+ if (scratch_attribute_sum.length < size) {
1199
+ scratch_attribute_sum = new Float64Array(size);
1200
+ }
1201
+
1202
+ if (scratch_attribute_values.length < attributes.components) {
1203
+ scratch_attribute_values = new Float64Array(attributes.components);
1204
+ }
1205
+ }
1206
+
348
1207
  const edge_pool = mesh.edges;
349
1208
  const heap = new Uint32Heap(edge_pool.size);
350
1209
 
@@ -357,7 +1216,9 @@ export function bt_mesh_simplify_with_quadrics(
357
1216
  continue;
358
1217
  }
359
1218
 
360
- const cost = evaluate_edge_collapse(mesh, e, quadratics, restricted_vertices, scratch_target);
1219
+ const cost = evaluate_edge_collapse(
1220
+ mesh, e, quadratics, restricted_vertices, attributes, wedges, scratch_target
1221
+ );
361
1222
 
362
1223
  if (cost === Number.POSITIVE_INFINITY) {
363
1224
  continue;
@@ -407,20 +1268,24 @@ export function bt_mesh_simplify_with_quadrics(
407
1268
  continue;
408
1269
  }
409
1270
 
410
- const cost = evaluate_edge_collapse(mesh, edge_id, quadratics, restricted_vertices, scratch_target);
1271
+ const cost = evaluate_edge_collapse(
1272
+ mesh, edge_id, quadratics, restricted_vertices, attributes, wedges, scratch_target
1273
+ );
411
1274
 
412
1275
  if (cost === Number.POSITIVE_INFINITY) {
413
1276
  continue;
414
1277
  }
415
1278
 
416
- // `evaluate_edge_collapse` has just written the placement into `scratch_target`, which is
417
- // what this needs and why it is tested here rather than beside the link condition above.
418
- // Quadric cost is blind to the fold: a flat region scores zero for every placement in its
419
- // plane, so nothing in the price stops a one-ring that is not star-shaped about the target
420
- // from turning inside out.
421
- if (bt_edge_collapse_would_fold(mesh, v1, v2, scratch_target)) {
422
- continue;
423
- }
1279
+ /*
1280
+ An invariant now, not a filter. `evaluate_edge_collapse` has just written the placement
1281
+ into `scratch_target`, and it returns a finite price only for a placement it has itself
1282
+ confirmed folds nothing -- so a fold reaching this point is this file being wrong, which is
1283
+ what an assertion is for and what a `continue` would have hidden.
1284
+ */
1285
+ assert.notOk(
1286
+ bt_edge_collapse_would_fold(mesh, v1, v2, scratch_target),
1287
+ 'the placement a finite collapse price was quoted for must not fold'
1288
+ );
424
1289
 
425
1290
  const face_deaths = count_edge_faces(mesh, edge_id);
426
1291
 
@@ -433,6 +1298,32 @@ export function bt_mesh_simplify_with_quadrics(
433
1298
  const pre_survivor = mesh.edge_read_vertex1(edge_id);
434
1299
  const pre_victim = mesh.edge_read_vertex2(edge_id);
435
1300
 
1301
+ const target_x = scratch_target[0];
1302
+ const target_y = scratch_target[1];
1303
+ const target_z = scratch_target[2];
1304
+
1305
+ // Priced before the accumulation below, which is the merge that would dilute it, and each
1306
+ // endpoint against its own weight for the same reason.
1307
+ deviation = Math.max(
1308
+ deviation,
1309
+ quadratics[pre_survivor].distance(target_x, target_y, target_z),
1310
+ quadratics[pre_victim].distance(target_x, target_y, target_z)
1311
+ );
1312
+
1313
+ // Before the accumulation below, which is what makes the blend fallback weigh the two
1314
+ // endpoints' own histories rather than the survivor's already carrying the victim's. And
1315
+ // before the topology mutates, which is what lets the faces about to die say which corners
1316
+ // shade as one.
1317
+ if (wedges !== null) {
1318
+ carry_wedges(
1319
+ mesh, wedges, attributes, pre_survivor, pre_victim,
1320
+ restricted_vertices.has(pre_survivor),
1321
+ quadratics[pre_survivor].weight,
1322
+ quadratics[pre_victim].weight,
1323
+ scratch_target
1324
+ );
1325
+ }
1326
+
436
1327
  // Accumulate victim's quadric onto the survivor before the topology mutates.
437
1328
  quadratics[pre_survivor].add(quadratics[pre_victim]);
438
1329
  quadratics[pre_victim] = null;
@@ -453,14 +1344,11 @@ export function bt_mesh_simplify_with_quadrics(
453
1344
  // are still intact.
454
1345
  current_face_count -= bt_vertex_fuse_duplicate_edges(mesh, survivor);
455
1346
 
456
- update_neighbor_costs(mesh, survivor, quadratics, restricted_vertices, heap);
1347
+ update_neighbor_costs(
1348
+ mesh, survivor, quadratics, restricted_vertices, attributes, wedges, heap
1349
+ );
457
1350
  }
458
1351
 
459
- // Measured here, before the cleanup below, because a vertex left behind by a patch that was
460
- // consumed whole is faceless and about to be removed — and it is the only thing still carrying
461
- // the quadric of the geometry that used to be there.
462
- const deviation = measure_deviation(mesh, quadratics);
463
-
464
1352
  // Drop any loose loops/edges/verts produced during collapses, preserving
465
1353
  // restricted vertices even if they ended up isolated.
466
1354
  cleanup_faceless_preserving_restricted(mesh, restricted_vertices);
@@ -468,33 +1356,6 @@ export function bt_mesh_simplify_with_quadrics(
468
1356
  return deviation;
469
1357
  }
470
1358
 
471
- /**
472
- * The largest distance any vertex now sits from the surface its quadric remembers.
473
- *
474
- * @param {BinaryTopology} mesh
475
- * @param {Quadric3[]} quadratics
476
- * @returns {number}
477
- */
478
- function measure_deviation(mesh, quadratics) {
479
- const vertex_pool = mesh.vertices;
480
-
481
- let result = 0;
482
-
483
- for (let v = 0; v < vertex_pool.size; v++) {
484
- if (!vertex_pool.is_allocated(v) || quadratics[v] === null || quadratics[v] === undefined) {
485
- continue;
486
- }
487
-
488
- mesh.vertex_read_coordinate(scratch_coord_a, 0, v);
489
-
490
- result = Math.max(
491
- result, quadratics[v].distance(scratch_coord_a[0], scratch_coord_a[1], scratch_coord_a[2])
492
- );
493
- }
494
-
495
- return result;
496
- }
497
-
498
1359
  /**
499
1360
  * Variant of bt_mesh_cleanup_faceless_references that never releases a restricted
500
1361
  * vertex even when it has no remaining edges.