@woosh/meep-engine 3.0.2 → 3.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +188 -188
- package/README.md +152 -145
- package/editor/Editor.js +598 -598
- package/editor/EditorCameraNavigation.js +205 -205
- package/editor/EditorKeyMap.js +182 -182
- package/editor/SelectionVisualizer.js +127 -127
- package/editor/actions/concrete/ActionInvalidateSampler.js +27 -27
- package/editor/actions/concrete/PatchTerrainHeightAction.js +85 -85
- package/editor/camera/camera_framing.js +104 -104
- package/editor/clipboard.js +184 -184
- package/editor/ecs/component/editors/ObservedIntegerEditor.js +22 -22
- package/editor/ecs/component/editors/ObservedStringEditor.js +34 -34
- package/editor/ecs/component/editors/common/two_way_sync.js +50 -50
- package/editor/ecs/component/editors/ecs/GridObstacleEditor.js +19 -19
- package/editor/ecs/component/editors/ecs/ParameterLookupTableEditor.js +35 -35
- package/editor/ecs/component/editors/ecs/ParameterTrackEditor.js +17 -17
- package/editor/ecs/component/editors/ecs/SGMeshEditor.js +24 -24
- package/editor/ecs/component/editors/ecs/ShadedGeometryEditor.js +33 -33
- package/editor/ecs/component/editors/ecs/Trail3DEditor.js +45 -45
- package/editor/ecs/component/editors/ecs/buildGridObstaclePreview.js +19 -19
- package/editor/ecs/component/editors/ecs/terrain/TerrainEditor.js +72 -72
- package/editor/ecs/component/editors/shade/MeshletGeometryEditor.js +30 -30
- package/editor/ecs/component/editors/shade/ShadeImageEditor.js +70 -70
- package/editor/ecs/component/editors/shade/ShadeMaterialEditor.js +34 -34
- package/editor/ecs/component/editors/shade/ShadeTextureEditor.js +46 -46
- package/editor/ecs/component/editors/shade/StandardShadeMaterialEditor.js +24 -24
- package/editor/ecs/component/prototypeObjectEditor.js +265 -265
- package/editor/ecs/component/registerBasicTypeEditors.js +100 -100
- package/editor/ecs/component/registerEngineComponentEditors.js +95 -95
- package/editor/entity_world_bounds.js +27 -27
- package/editor/particles/lut_ops.js +148 -148
- package/editor/particles/particleEditEvents.js +12 -12
- package/editor/particles/rebuildParticleEmitter.js +31 -31
- package/editor/persistence/EditorLayoutStore.js +71 -71
- package/editor/persistence/SceneMetadata.js +52 -52
- package/editor/persistence/SceneSlotManager.js +144 -144
- package/editor/persistence/buildNewSceneDataset.js +67 -67
- package/editor/persistence/files.js +68 -68
- package/editor/persistence/scene_import.js +143 -143
- package/editor/persistence/scene_serialization.js +79 -79
- package/editor/persistence/string_buffer_codec.js +41 -41
- package/editor/process/GridDisplayProcess.js +125 -125
- package/editor/process/SymbolicDisplayProcess.js +106 -108
- package/editor/process/symbolic/CameraSymbolicDisplay.js +146 -146
- package/editor/process/symbolic/LightSymbolicDisplay.js +172 -172
- package/editor/process/symbolic/PathSymbolicDisplay.js +118 -118
- package/editor/process/symbolic/PositionedMarkerSymbolicDisplay.js +79 -79
- package/editor/process/symbolic/SoundEmitterSymbolicDisplay.js +57 -57
- package/editor/process/symbolic/SymbolicDisplay.js +62 -62
- package/editor/process/symbolic/gizmo_draw_wire_box_oriented.js +85 -85
- package/editor/process/symbolic/gizmo_draw_wire_circle.js +53 -53
- package/editor/prototypeEditorShell.js +156 -156
- package/editor/selection/editor_pick.js +130 -130
- package/editor/selection/screen_rect_frustum.js +119 -119
- package/editor/style/controls.scss +168 -168
- package/editor/style/inspector.scss +223 -223
- package/editor/templates/entity_templates.js +167 -167
- package/editor/tools/GridPaintTool.js +273 -273
- package/editor/tools/SelectionTool.js +307 -307
- package/editor/tools/TransformToolV2.js +378 -378
- package/editor/tools/engine/ToolEngine.js +188 -188
- package/editor/tools/paint/TerrainTexturePaintTool.js +250 -250
- package/editor/tools/v2/BlenderCameraOrientationGizmo.js +501 -501
- package/editor/tools/v2/TransformControls.js +971 -971
- package/editor/tools/v2/handle/gizmo_drag_plane.js +141 -141
- package/editor/tools/v2/handle/gizmo_draw.js +399 -399
- package/editor/tools/v2/handle/gizmo_handles.js +364 -364
- package/editor/tools/v2/handle/gizmo_ray_shapes.js +239 -239
- package/editor/view/ecs/HierarchicalEntityListView.js +339 -339
- package/editor/view/ecs/components/DatGuiController.js +2 -2
- package/editor/view/ecs/components/common/AutoCanvasView.js +100 -100
- package/editor/view/ecs/components/common/TextController.js +59 -59
- package/editor/view/library/MeshLibraryView.js +175 -175
- package/editor/view/library/mesh_library_drag.js +63 -63
- package/editor/view/library/model_thumbnail_source.js +86 -86
- package/editor/view/makeEntityDecorators.js +125 -125
- package/editor/view/node-graph/NodeGraphSelection.js +89 -89
- package/editor/view/node-graph/NodeView.js +211 -211
- package/editor/view/node-graph/actions/ConnectionDeleteAction.js +36 -36
- package/editor/view/node-graph/actions/NodesMoveAction.js +41 -41
- package/editor/view/node-graph/connection_wire_geometry.js +107 -107
- package/editor/view/particles/ColorLutGradientView.js +291 -291
- package/editor/view/particles/ScalarLutCurveView.js +279 -279
- package/editor/view/prepareMeshLibrary.js +188 -188
- package/editor/view/shell/SceneTreePanelView.js +119 -119
- package/editor/view/v2/SplitView.js +2 -2
- package/package.json +94 -94
- package/samples/generation/generators/interactive/mir_generator_place_buff_objects.js +362 -362
- package/src/DUPLICATION_AUDIT_2026_08_07/00_core_reuse_surface.md +738 -738
- package/src/DUPLICATION_AUDIT_2026_08_07/A_core_math_geom.md +79 -79
- package/src/DUPLICATION_AUDIT_2026_08_07/B_core_data.md +93 -93
- package/src/DUPLICATION_AUDIT_2026_08_07/C_gfx_render.md +80 -80
- package/src/DUPLICATION_AUDIT_2026_08_07/D_gfx_features.md +164 -164
- package/src/DUPLICATION_AUDIT_2026_08_07/E_physics.md +69 -69
- package/src/DUPLICATION_AUDIT_2026_08_07/F_sound.md +102 -102
- package/src/DUPLICATION_AUDIT_2026_08_07/G_net_save.md +129 -129
- package/src/DUPLICATION_AUDIT_2026_08_07/H_engine_misc.md +72 -72
- package/src/DUPLICATION_AUDIT_2026_08_07/I_generation_view.md +74 -74
- package/src/DUPLICATION_AUDIT_2026_08_07/K_crosscutting.md +169 -169
- package/src/DUPLICATION_AUDIT_2026_08_07.md +254 -254
- package/src/DUPLICATION_AUDIT_REMEDIATION.md +250 -250
- package/src/REVIEW_2026_08_06/adv-core.md +511 -511
- package/src/REVIEW_2026_08_06/adv-gfx.md +480 -480
- package/src/REVIEW_2026_08_06/adv-view.md +446 -446
- package/src/REVIEW_2026_08_06/api-core.md +296 -296
- package/src/REVIEW_2026_08_06/api-engine.md +195 -195
- package/src/REVIEW_2026_08_06/bvh-phys-sound-net.md +507 -507
- package/src/REVIEW_2026_08_06/core-binary.md +348 -348
- package/src/REVIEW_2026_08_06/core-collection.md +376 -376
- package/src/REVIEW_2026_08_06/core-math.md +221 -221
- package/src/REVIEW_2026_08_06/core-process.md +266 -266
- package/src/REVIEW_2026_08_06/core-science.md +223 -223
- package/src/REVIEW_2026_08_06/engine-ai-nav.md +279 -279
- package/src/REVIEW_2026_08_06/engine-animation.md +167 -167
- package/src/REVIEW_2026_08_06/engine-asset.md +166 -166
- package/src/REVIEW_2026_08_06/engine-ecs.md +920 -920
- package/src/REVIEW_2026_08_06/engine-input.md +197 -197
- package/src/REVIEW_2026_08_06/engine-misc.md +248 -248
- package/src/REVIEW_2026_08_06/engine-terrain.md +52 -52
- package/src/REVIEW_2026_08_06/generation.md +176 -176
- package/src/REVIEW_2026_08_06/geom-2d.md +434 -434
- package/src/REVIEW_2026_08_06/geom-3d.md +117 -117
- package/src/REVIEW_2026_08_06/gfx-gi.md +143 -143
- package/src/REVIEW_2026_08_06/gfx-render.md +740 -740
- package/src/REVIEW_2026_08_06/gfx-texture.md +690 -690
- package/src/REVIEW_2026_08_06/prior-criticals.md +49 -49
- package/src/REVIEW_2026_08_06/prior-highs.md +138 -138
- package/src/REVIEW_2026_08_06/test-gaps.md +364 -364
- package/src/REVIEW_2026_08_06/view.md +667 -667
- package/src/REVIEW_2026_08_06.md +610 -610
- package/src/avif/codec/dec/avif_dec.cpp +233 -233
- package/src/avif/codec/dec/avif_dec.js +16 -16
- package/src/core/binary/BitImage2.js +246 -246
- package/src/core/binary/align_32.js +21 -21
- package/src/core/binary/data_view/DataType2DataViewReaders.js +23 -23
- package/src/core/binary/data_view/DataType2DataViewWriters.js +25 -25
- package/src/core/binary/hash/XXH128Hash.js +55 -55
- package/src/core/binary/hash/XXH3_SECRET.js +25 -25
- package/src/core/binary/hash/xxh3_128.js +341 -341
- package/src/core/binary/hash/xxh3_64.js +241 -241
- package/src/core/binary/hash/xxh3_common.js +476 -476
- package/src/core/binary/lz4/LZ4_MAX_INPUT_SIZE.js +9 -9
- package/src/core/binary/lz4/lz4_compress_block.js +385 -385
- package/src/core/binary/lz4/lz4_compress_bound.js +25 -25
- package/src/core/binary/lz4/lz4_decompress_block.js +139 -139
- package/src/core/binary/lz4/lz4_decompress_bound.js +30 -30
- package/src/core/binary/meshopt/MESHOPT_GROUP_SIZE.js +18 -18
- package/src/core/binary/meshopt/MeshoptCompressionFilter.js +30 -30
- package/src/core/binary/meshopt/MeshoptCompressionMode.js +21 -21
- package/src/core/binary/meshopt/__meshopt_test_streams.js +237 -237
- package/src/core/binary/meshopt/meshopt_decode.js +73 -73
- package/src/core/binary/meshopt/meshopt_decode_byte_group.js +67 -67
- package/src/core/binary/meshopt/meshopt_decode_data_block.js +59 -59
- package/src/core/binary/meshopt/meshopt_decode_filter_color.js +69 -69
- package/src/core/binary/meshopt/meshopt_decode_filter_exponential.js +43 -43
- package/src/core/binary/meshopt/meshopt_decode_filter_octahedral.js +60 -60
- package/src/core/binary/meshopt/meshopt_decode_filter_quaternion.js +71 -71
- package/src/core/binary/meshopt/meshopt_decode_index_buffer.js +294 -294
- package/src/core/binary/meshopt/meshopt_decode_index_sequence.js +115 -115
- package/src/core/binary/meshopt/meshopt_decode_vertex_buffer.js +381 -381
- package/src/core/binary/meshopt/meshopt_read_uint_var.js +59 -59
- package/src/core/binary/reinterpret_float32_as_uint32.js +13 -13
- package/src/core/binary/reinterpret_uint32_as_float32.js +14 -14
- package/src/core/binary/type/DataType2TypedArrayConstructorMapping.js +35 -35
- package/src/core/bvh2/bvh3/ebvh_geometry_query_any_triangle_ray.js +149 -149
- package/src/core/bvh2/bvh3/ebvh_sort_for_traversal_depth_first.js +96 -96
- package/src/core/bvh2/bvh3/query/bvh_query_depth_range_in_frustum.js +101 -101
- package/src/core/bvh2/bvh3/query/bvh_query_leaves_generic.js +77 -77
- package/src/core/bvh2/bvh3/query/bvh_query_leaves_ray.js +90 -90
- package/src/core/bvh2/bvh3/query/bvh_query_leaves_ray_segment.js +94 -94
- package/src/core/bvh2/bvh3/query/bvh_query_user_data_generic.js +86 -86
- package/src/core/bvh2/bvh3/query/bvh_query_user_data_intersects_capsule.js +101 -101
- package/src/core/bvh2/bvh3/query/bvh_query_user_data_intersects_sphere.js +92 -92
- package/src/core/bvh2/bvh3/query/bvh_query_user_data_ray.js +96 -96
- package/src/core/bvh2/bvh3/query/bvh_query_user_data_ray_segment.js +100 -100
- package/src/core/bvh2/visual/BVHGeometry.js +131 -131
- package/src/core/bvh8/BVH8.js +436 -436
- package/src/core/bvh8/aabb3_round_to_float32.js +22 -22
- package/src/core/bvh8/build/BVH8Converter.js +588 -588
- package/src/core/bvh8/build/TriangleCluster.js +122 -122
- package/src/core/bvh8/build/aabb3_compute_merge_cost.js +28 -28
- package/src/core/bvh8/build/aabb3_from_triangle_by_index.js +17 -17
- package/src/core/bvh8/build/bvh8_build_for_geometry.js +303 -303
- package/src/core/bvh8/build/bvh8_from_proxy.js +256 -256
- package/src/core/bvh8/build/byte.js +9 -9
- package/src/core/bvh8/build/encode_bounds_e.js +11 -11
- package/src/core/bvh8/bvh8_convert_to_dot.js +132 -132
- package/src/core/bvh8/bvh8_count_primitives.js +98 -98
- package/src/core/bvh8/bvh8_geometry_validate.js +138 -138
- package/src/core/bvh8/bvh8_geometry_validate_indirect.js +166 -166
- package/src/core/bvh8/bvh8_get_node_bounds.js +34 -34
- package/src/core/bvh8/bvh8_get_node_child_bounds.js +52 -52
- package/src/core/bvh8/bvh8_node_child_surface_area.js +18 -18
- package/src/core/bvh8/bvh8_node_count_triangles.js +27 -27
- package/src/core/bvh8/bvh8_quality.js +72 -72
- package/src/core/bvh8/bvh8_validate_structure.js +87 -87
- package/src/core/clipboard/obtainClipBoard.js +29 -29
- package/src/core/clipboard/safeClipboardReadText.js +55 -55
- package/src/core/clipboard/safeClipboardWriteText.js +23 -23
- package/src/core/collection/array/typed/typed_array_value_denormalize.js +52 -52
- package/src/core/collection/array/typed/typed_array_value_normalize.js +51 -51
- package/src/core/collection/union-find/union_find.js +79 -79
- package/src/core/color/YCoCg/rgb_to_ycocg.js +20 -20
- package/src/core/color/YCoCg/ycocg_to_rgb.js +17 -17
- package/src/core/color/colormap/MAGMA_LUT.js +26 -26
- package/src/core/color/illuminant/D65_TRISTIMULUS_XYZ.js +19 -19
- package/src/core/color/lab/lab_apply_hunt_adjustment.js +27 -27
- package/src/core/color/lab/lab_distance_hyab.js +23 -23
- package/src/core/color/lab/xyz_to_lab.js +62 -62
- package/src/core/color/operations/color_darken.js +24 -24
- package/src/core/color/operations/color_desaturate.js +24 -24
- package/src/core/color/operations/color_lighten.js +24 -24
- package/src/core/color/operations/color_saturate.js +24 -24
- package/src/core/color/operations/color_scale_okhsv_channel.js +45 -45
- package/src/core/color/tonemap/tonemap_aces.js +41 -41
- package/src/core/color/ycxcz/xyz_to_ycxcz.js +28 -28
- package/src/core/color/ycxcz/ycxcz_to_xyz.js +19 -19
- package/src/core/dom/isImageBitmap.js +12 -12
- package/src/core/function/frameThrottle.js +23 -23
- package/src/core/geom/2d/aabb/aabb2_array_combine.js +7 -7
- package/src/core/geom/2d/aabb/aabb2_compute_area.js +7 -7
- package/src/core/geom/2d/aabb/aabb2_compute_center_from_multiple.js +7 -7
- package/src/core/geom/2d/aabb/aabb2_compute_overlap.js +7 -7
- package/src/core/geom/2d/aabb/aabb2_from_v2_array.js +7 -7
- package/src/core/geom/2d/aabb/aabb2_intersects_point.js +7 -7
- package/src/core/geom/2d/aabb/aabb2_overlap_exists.js +7 -7
- package/src/core/geom/2d/circle/circle_compute_circle_intersection.js +7 -7
- package/src/core/geom/2d/circle/circle_compute_circle_penetration.js +7 -7
- package/src/core/geom/2d/circle/circle_intersects_point.js +7 -7
- package/src/core/geom/2d/compute_polygon_area_2d.js +7 -7
- package/src/core/geom/2d/convex-hull/fixed_convex_hull_humus.js +7 -7
- package/src/core/geom/2d/convex-hull/fixed_convex_hull_relaxation.js +7 -7
- package/src/core/geom/2d/convex-hull/orientation3_array.js +7 -7
- package/src/core/geom/2d/intersect_ray_2d.js +7 -7
- package/src/core/geom/2d/line/line2_distance_to_point_sqr.js +7 -7
- package/src/core/geom/2d/line/line2_segment_intersection_fraction_2d.js +44 -44
- package/src/core/geom/2d/line/line_segment_compute_line_segment_intersection_2d.js +7 -7
- package/src/core/geom/2d/line/line_segment_compute_line_segment_intersection_array_2d.js +7 -7
- package/src/core/geom/2d/line/line_segment_compute_line_segment_intersection_vectors_2d.js +7 -7
- package/src/core/geom/2d/line/line_segment_compute_line_segment_intersection_vectors_array_2d.js +7 -7
- package/src/core/geom/2d/line/line_segment_intersection_fraction_2d.js +7 -7
- package/src/core/geom/2d/line/line_segment_line_segment_intersection_exists_2d.js +7 -7
- package/src/core/geom/2d/line/segment2_intersects_segment2_2d.js +25 -25
- package/src/core/geom/2d/line/segment2_segment_compute_intersection_array_2d.js +46 -46
- package/src/core/geom/2d/line/segment2_segment_intersection_fractions.js +52 -52
- package/src/core/geom/2d/oriented-box/min_area_rect_2d.js +69 -69
- package/src/core/geom/2d/polygon/TRIANGULATION_DESIGN.md +433 -433
- package/src/core/geom/2d/polygon/polygon2_clip_axis_halfplane.js +51 -51
- package/src/core/geom/2d/polygon/polygon2_clip_halfplane.js +50 -50
- package/src/core/geom/2d/polygon/polygon2_is_counter_clockwise.js +84 -84
- package/src/core/geom/2d/polygon/polygon2_signed_area.js +52 -52
- package/src/core/geom/2d/polygon/polygon2_triangulate.corpus.js +86 -86
- package/src/core/geom/2d/polygon/polygon2_triangulate.js +1280 -1280
- package/src/core/geom/2d/polygon/polygon2_triangulation_deviation.js +77 -77
- package/src/core/geom/2d/quad-tree/qt_collect_by_circle.js +39 -39
- package/src/core/geom/2d/quad-tree/qt_match_data_by_circle.js +101 -101
- package/src/core/geom/2d/quad-tree/qt_query_data_nearest_to_point.js +7 -7
- package/src/core/geom/2d/rectangle_to_aabb.js +7 -7
- package/src/core/geom/2d/spline_bezier2_2d.js +7 -7
- package/src/core/geom/2d/triangle/tri2_rasterize_conservative.js +99 -99
- package/src/core/geom/2d/triangle/tri2_signed_area.js +35 -35
- package/src/core/geom/2d/triangle2d_compute_area.js +7 -7
- package/src/core/geom/2d/triangle2d_contains_point.js +7 -7
- package/src/core/geom/2d/triangle2d_get_barycentric.js +7 -7
- package/src/core/geom/2d/v2_morton_encode.js +23 -23
- package/src/core/geom/3d/aabb/aabb3_array_intersects_frustum_degree.js +7 -7
- package/src/core/geom/3d/aabb/aabb3_box_surface_area_2.js +7 -7
- package/src/core/geom/3d/aabb/aabb3_build_frustum.js +5 -5
- package/src/core/geom/3d/aabb/aabb3_closest_point_on_surface.js +57 -57
- package/src/core/geom/3d/aabb/aabb3_combined_surface_area.js +7 -7
- package/src/core/geom/3d/aabb/aabb3_compute_half_surface_area.js +7 -7
- package/src/core/geom/3d/aabb/aabb3_compute_projected_area.js +7 -7
- package/src/core/geom/3d/aabb/aabb3_compute_surface_area.js +7 -7
- package/src/core/geom/3d/aabb/aabb3_detailed_volume_intersection.js +7 -7
- package/src/core/geom/3d/aabb/aabb3_estimate_projected_area2.js +7 -7
- package/src/core/geom/3d/aabb/aabb3_from_threejs_geometry.js +7 -7
- package/src/core/geom/3d/aabb/aabb3_from_triangle_group.js +46 -46
- package/src/core/geom/3d/aabb/aabb3_intersects_frustum_degree.js +7 -7
- package/src/core/geom/3d/aabb/aabb3_intersects_line_segment.js +7 -7
- package/src/core/geom/3d/aabb/aabb3_near_distance_to_intersection_ray_segment.js +7 -7
- package/src/core/geom/3d/aabb/aabb3_nearest_point_on_surface.js +7 -7
- package/src/core/geom/3d/aabb/aabb3_ray_segment_entry_distance.js +95 -95
- package/src/core/geom/3d/aabb/aabb3_raycast.js +7 -7
- package/src/core/geom/3d/aabb/aabb3_transform_oriented.js +93 -93
- package/src/core/geom/3d/aabb/aabb3_transform_oriented_inverse.js +82 -82
- package/src/core/geom/3d/aabb/compute_aabb_from_points.js +7 -7
- package/src/core/geom/3d/aabb/compute_triangle_group_aabb3.js +7 -7
- package/src/core/geom/3d/atlas/ATLAS_PORT_PLAN.md +486 -486
- package/src/core/geom/3d/atlas/BFF_PLAN.md +174 -174
- package/src/core/geom/3d/atlas/Chart.js +77 -77
- package/src/core/geom/3d/atlas/REVIEW_02.md +392 -392
- package/src/core/geom/3d/atlas/atlas_bench_lib.js +315 -315
- package/src/core/geom/3d/atlas/atlas_chart_type.js +16 -16
- package/src/core/geom/3d/atlas/atlas_compute_charts.js +188 -188
- package/src/core/geom/3d/atlas/atlas_extract_chart.js +107 -107
- package/src/core/geom/3d/atlas/atlas_generate.js +50 -50
- package/src/core/geom/3d/atlas/atlas_test_fixtures.js +266 -266
- package/src/core/geom/3d/atlas/atlas_write_chart_uvs.js +36 -36
- package/src/core/geom/3d/atlas/data/atlas_build_edge_dihedrals.js +62 -62
- package/src/core/geom/3d/atlas/data/atlas_build_edge_lengths.js +33 -33
- package/src/core/geom/3d/atlas/data/atlas_build_face_areas.js +50 -50
- package/src/core/geom/3d/atlas/io/atlas_mesh_weld_colocals.js +39 -39
- package/src/core/geom/3d/atlas/io/bt_loop_uv.js +35 -35
- package/src/core/geom/3d/atlas/pack/ChartPlacement.js +46 -46
- package/src/core/geom/3d/atlas/param/atlas_chart_parameterize_bff.js +269 -269
- package/src/core/geom/3d/atlas/param/atlas_chart_parameterize_lscm.js +209 -209
- package/src/core/geom/3d/atlas/param/atlas_chart_parameterize_ortho.js +38 -38
- package/src/core/geom/3d/atlas/param/atlas_chart_stretch_metric.js +86 -86
- package/src/core/geom/3d/atlas/param/atlas_piecewise_resegment.js +183 -183
- package/src/core/geom/3d/atlas/param/fit_plane_basis.js +66 -66
- package/src/core/geom/3d/atlas/segment/RegionGraph.js +40 -40
- package/src/core/geom/3d/atlas/segment/atlas_build_region_graph.js +136 -136
- package/src/core/geom/3d/atlas/segment/atlas_chart_roundness_delta.js +30 -30
- package/src/core/geom/3d/atlas/segment/atlas_compute_planar_regions.js +77 -77
- package/src/core/geom/3d/atlas/segment/atlas_merge_charts.js +116 -116
- package/src/core/geom/3d/atlas/segment/atlas_segment_charts.js +271 -271
- package/src/core/geom/3d/atlas/segment/atlas_segment_charts_atomic.js +191 -191
- package/src/core/geom/3d/box/box3_projected_half_extent.js +35 -35
- package/src/core/geom/3d/capsule/capsule_intersects_aabb3_closed.js +67 -67
- package/src/core/geom/3d/capsule/capsule_intersects_aabb3_iterative.js +137 -137
- package/src/core/geom/3d/compute_circle_bounding_box.js +7 -7
- package/src/core/geom/3d/cone/computeConeBoundingBox.js +7 -7
- package/src/core/geom/3d/cone/computeConePlaneSide.js +7 -7
- package/src/core/geom/3d/cone/compute_bounding_cone_of_2_cones.js +7 -7
- package/src/core/geom/3d/cylinder/cylinder3_raycast.d.ts +38 -0
- package/src/core/geom/3d/cylinder/cylinder3_raycast.d.ts.map +1 -0
- package/src/core/geom/3d/cylinder/cylinder3_raycast.js +105 -0
- package/src/core/geom/3d/equirectangular/equirectangular_direction_to_uv.js +18 -18
- package/src/core/geom/3d/equirectangular/equirectangular_uv_to_direction.js +24 -24
- package/src/core/geom/3d/frustum/clipping_volume_matrix4_project.js +7 -7
- package/src/core/geom/3d/frustum/frustum3_computeNearestPointToPoint.js +7 -7
- package/src/core/geom/3d/frustum/frustum3_compute_closest_point.js +117 -117
- package/src/core/geom/3d/frustum/frustum_matrix4_project.js +7 -7
- package/src/core/geom/3d/frustum/hexahedron_from_aabb.js +7 -7
- package/src/core/geom/3d/frustum/read_frustum_corner.js +17 -17
- package/src/core/geom/3d/frustum/read_three_planes_to_array.js +7 -7
- package/src/core/geom/3d/frustum/slice_frustum_linear_to_points.js +7 -7
- package/src/core/geom/3d/gjk/GJK_REVIEW_NOTES.md +146 -146
- package/src/core/geom/3d/gjk/gjk.js +430 -430
- package/src/core/geom/3d/gjk/gjk_epa_penetration.js +520 -520
- package/src/core/geom/3d/gjk/minkowski_support.js +71 -71
- package/src/core/geom/3d/gjk/mpr.js +368 -368
- package/src/core/geom/3d/line/line3_closest_points_segment_segment.js +7 -7
- package/src/core/geom/3d/line/line3_compute_nearest_point_to_point.js +7 -7
- package/src/core/geom/3d/line/line3_compute_point_segment_point_distance_unfolded.js +64 -64
- package/src/core/geom/3d/line/line3_compute_segment_closest_point_to_aabb3_t.js +153 -153
- package/src/core/geom/3d/line/line3_compute_segment_nearest_point_to_aabb3_t.js +7 -7
- package/src/core/geom/3d/line/line3_compute_segment_nearest_point_to_point_t.js +7 -7
- package/src/core/geom/3d/line/line3_compute_segment_segment_closest_points.js +88 -88
- package/src/core/geom/3d/line/line3_segment_segment_closest_point_pair.js +71 -71
- package/src/core/geom/3d/line/segment3_point_distance_to_segments.js +25 -25
- package/src/core/geom/3d/mat4/allocate_m4.js +7 -7
- package/src/core/geom/3d/mat4/apply_mat4_transform_to_direction_v3_array.js +7 -7
- package/src/core/geom/3d/mat4/apply_mat4_transform_to_v3_array.js +7 -7
- package/src/core/geom/3d/mat4/compose_matrix4_array.js +7 -7
- package/src/core/geom/3d/mat4/decompose_matrix_4_array.js +7 -7
- package/src/core/geom/3d/mat4/eulerAnglesFromMatrix.js +7 -7
- package/src/core/geom/3d/mat4/m4_compose.js +22 -22
- package/src/core/geom/3d/mat4/m4_compute_normal_matrix3.js +7 -7
- package/src/core/geom/3d/mat4/m4_decompose.js +64 -64
- package/src/core/geom/3d/mat4/m4_decompose_array.js +157 -157
- package/src/core/geom/3d/mat4/m4_from_rotation_translation_scale.js +32 -32
- package/src/core/geom/3d/mat4/m4_from_rotation_translation_scale_scalar.js +67 -67
- package/src/core/geom/3d/mat4/m4_linear_matrix3.js +29 -29
- package/src/core/geom/3d/mat4/m4_look_at.js +96 -96
- package/src/core/geom/3d/mat4/m4_make_rotation_x.js +47 -47
- package/src/core/geom/3d/mat4/m4_make_rotation_y.js +47 -47
- package/src/core/geom/3d/mat4/m4_make_rotation_z.js +47 -47
- package/src/core/geom/3d/mat4/m4_normal_matrix3.js +39 -39
- package/src/core/geom/3d/mat4/m4_perspective.js +66 -66
- package/src/core/geom/3d/mat4/m4_transpose.js +48 -48
- package/src/core/geom/3d/morton/mortonEncode_LUT.js +7 -7
- package/src/core/geom/3d/morton/mortonEncode_magicbits.js +7 -7
- package/src/core/geom/3d/normal/hemioct/encode_unit3_hemioct.js +5 -5
- package/src/core/geom/3d/normal/hemioct/encode_unit_to_hemioct.js +28 -28
- package/src/core/geom/3d/normal/lambert_azimuth/decode_azimuth_to_unit.js +30 -30
- package/src/core/geom/3d/normal/lambert_azimuth/encode_unit_to_azimuth.js +19 -19
- package/src/core/geom/3d/normal/spherical/decode_spheremap_to_unit.js +38 -38
- package/src/core/geom/3d/normal/spherical/encode_unit_to_spheremap.js +30 -30
- package/src/core/geom/3d/plane/is_point_within_planes.js +7 -7
- package/src/core/geom/3d/plane/orient3d_fast_point.js +47 -47
- package/src/core/geom/3d/plane/plane3_compute_convex_3_plane_intersection.js +7 -7
- package/src/core/geom/3d/plane/plane3_compute_plane_intersection.js +7 -7
- package/src/core/geom/3d/plane/plane3_intersect_plane.js +7 -7
- package/src/core/geom/3d/plane/plane3_three_compute_convex_3_plane_intersection.js +7 -7
- package/src/core/geom/3d/point/point3_raycast.d.ts +33 -0
- package/src/core/geom/3d/point/point3_raycast.d.ts.map +1 -0
- package/src/core/geom/3d/point/point3_raycast.js +47 -0
- package/src/core/geom/3d/polygon/polygon3_compute_normal.js +60 -60
- package/src/core/geom/3d/polygon/polygon3_triangulate.js +94 -94
- package/src/core/geom/3d/polyhedron/convex_polyhedron3_raycast.d.ts +40 -0
- package/src/core/geom/3d/polyhedron/convex_polyhedron3_raycast.d.ts.map +1 -0
- package/src/core/geom/3d/polyhedron/convex_polyhedron3_raycast.js +94 -0
- package/src/core/geom/3d/quadric/QUADRIC3_SIZE.js +14 -14
- package/src/core/geom/3d/quadric/Quadric3.js +282 -282
- package/src/core/geom/3d/quadric/quadric3_add.js +33 -33
- package/src/core/geom/3d/quadric/quadric3_clear.js +25 -25
- package/src/core/geom/3d/quadric/quadric3_copy.js +27 -27
- package/src/core/geom/3d/quadric/quadric3_distance.js +36 -36
- package/src/core/geom/3d/quadric/quadric3_evaluate.js +25 -25
- package/src/core/geom/3d/quadric/quadric3_optimize.js +45 -45
- package/src/core/geom/3d/quadric/quadric3_scale.js +32 -32
- package/src/core/geom/3d/quadric/quadric3_set_from_plane_through_point.js +30 -30
- package/src/core/geom/3d/quadric/quadric3_set_from_vector4.js +35 -35
- package/src/core/geom/3d/quadric/quadric3_sub.js +36 -36
- package/src/core/geom/3d/quadric/quadric3_to_tensor_m3.js +28 -28
- package/src/core/geom/3d/quadric/quadric3_to_tensor_m3_inverse.js +56 -56
- package/src/core/geom/3d/quadric/quadric3_to_vector3.js +19 -19
- package/src/core/geom/3d/quaternion/quat3_createFromAxisAngle.js +7 -7
- package/src/core/geom/3d/quaternion/quat3_integrate.js +62 -62
- package/src/core/geom/3d/quaternion/quat3_multiply.js +31 -31
- package/src/core/geom/3d/quaternion/quat3_nlerp.js +72 -72
- package/src/core/geom/3d/quaternion/quat3_to_matrix3.js +69 -69
- package/src/core/geom/3d/quaternion/quat_decode_from_uint32.js +7 -7
- package/src/core/geom/3d/quaternion/quat_encode_to_uint32.js +7 -7
- package/src/core/geom/3d/ray/ray3_array_compose.js +7 -7
- package/src/core/geom/3d/ray/ray3_compute_nearest_point_to_point.js +7 -7
- package/src/core/geom/3d/ray/ray3_distance_to_point.js +7 -7
- package/src/core/geom/3d/ray/ray3_shift_origin_along_direction.js +7 -7
- package/src/core/geom/3d/shape/ConvexHullShape3D.d.ts +17 -0
- package/src/core/geom/3d/shape/ConvexHullShape3D.d.ts.map +1 -1
- package/src/core/geom/3d/shape/ConvexHullShape3D.js +43 -0
- package/src/core/geom/3d/shape/CylinderShape3D.d.ts +1 -0
- package/src/core/geom/3d/shape/CylinderShape3D.d.ts.map +1 -1
- package/src/core/geom/3d/shape/CylinderShape3D.js +21 -0
- package/src/core/geom/3d/shape/PointShape3D.d.ts +7 -0
- package/src/core/geom/3d/shape/PointShape3D.d.ts.map +1 -1
- package/src/core/geom/3d/shape/PointShape3D.js +88 -63
- package/src/core/geom/3d/shape/PosedShape3D.d.ts +6 -0
- package/src/core/geom/3d/shape/PosedShape3D.d.ts.map +1 -1
- package/src/core/geom/3d/shape/PosedShape3D.js +27 -0
- package/src/core/geom/3d/shape/TransformedShape3D.d.ts +10 -0
- package/src/core/geom/3d/shape/TransformedShape3D.d.ts.map +1 -1
- package/src/core/geom/3d/shape/TransformedShape3D.js +13 -0
- package/src/core/geom/3d/shape/Triangle3D.d.ts +1 -0
- package/src/core/geom/3d/shape/Triangle3D.d.ts.map +1 -1
- package/src/core/geom/3d/shape/Triangle3D.js +26 -0
- package/src/core/geom/3d/shape/UnionShape3D.d.ts +6 -0
- package/src/core/geom/3d/shape/UnionShape3D.d.ts.map +1 -1
- package/src/core/geom/3d/shape/UnionShape3D.js +7 -0
- package/src/core/geom/3d/shape/UnitCubeShape3D.js +45 -45
- package/src/core/geom/3d/shape/UnitSphereShape3D.js +44 -44
- package/src/core/geom/3d/shape/shape3_mesh_from_geometry.js +64 -64
- package/src/core/geom/3d/shape/shape_mesh_from_geometry.js +7 -7
- package/src/core/geom/3d/shape/util/compute_signed_distance_gradient_by_sampling.js +51 -51
- package/src/core/geom/3d/shape/util/shape3d_voxelize_to_grid.js +7 -7
- package/src/core/geom/3d/sphere/compute_bounding_sphere_of_2_spheres.js +7 -7
- package/src/core/geom/3d/sphere/harmonics/sh3_convolve_with_zh.js +53 -53
- package/src/core/geom/3d/sphere/harmonics/sh3_ggx_zh.js +30 -30
- package/src/core/geom/3d/sphere/harmonics/sh3_rotate_bl.js +7 -7
- package/src/core/geom/3d/sphere/harmonics/sh_compute_K.js +7 -7
- package/src/core/geom/3d/sphere/sphere_array_intersects_point.js +7 -7
- package/src/core/geom/3d/sphere/sphere_array_intersects_ray.js +7 -7
- package/src/core/geom/3d/sphere/sphere_intersects_point.js +7 -7
- package/src/core/geom/3d/sphere/sphere_matrix4_transform.js +5 -5
- package/src/core/geom/3d/sphere/sphere_projected_sphere_radius_sqr.js +7 -7
- package/src/core/geom/3d/sphere/sphere_radius_sqr_from_v3_array_transformed.js +7 -7
- package/src/core/geom/3d/sphere/spherical_from_cartesian.js +7 -7
- package/src/core/geom/3d/tetrahedra/compute_tetrahedral_mesh_from_surface.js +174 -174
- package/src/core/geom/3d/tetrahedra/compute_tetrahedron_quality.js +7 -7
- package/src/core/geom/3d/tetrahedra/compute_tetrahedron_volume.js +7 -7
- package/src/core/geom/3d/tetrahedra/delaunay/fill_in_a_cavity.js +7 -7
- package/src/core/geom/3d/tetrahedra/graphics/build_tetrahedral_mesh_geometry.js +39 -39
- package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_build_vertex_to_tets_map.js +48 -48
- package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_carve_outside_surface.js +173 -173
- package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_compute_tet_quality.js +22 -22
- package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_find_tets_around_edge.js +146 -146
- package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_flip_23.js +232 -232
- package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_flip_32.js +255 -255
- package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_improve_quality.js +387 -387
- package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_smooth_vertex.js +140 -140
- package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_tet_get_neighbours.js +7 -7
- package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_vertex_is_boundary.js +97 -97
- package/src/core/geom/3d/tetrahedra/tetrahedron_compute_quality.js +66 -66
- package/src/core/geom/3d/tetrahedra/triangle/triangle_decode_depth.js +59 -59
- package/src/core/geom/3d/tetrahedra/triangle/triangle_rasterized_depth_sample_index.js +54 -54
- package/src/core/geom/3d/tetrahedra/validate_neighbour.js +70 -70
- package/src/core/geom/3d/topology/struct/binary/BinaryDataBlock.js +314 -314
- package/src/core/geom/3d/topology/struct/binary/BinaryDataLayer.js +364 -364
- package/src/core/geom/3d/topology/struct/binary/BinaryElementPool.js +567 -567
- package/src/core/geom/3d/topology/struct/binary/BinaryTopology.js +1085 -1085
- package/src/core/geom/3d/topology/struct/binary/io/bt_boundary_loop.js +73 -73
- package/src/core/geom/3d/topology/struct/binary/io/bt_loop_kill.js +35 -35
- package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_append.js +119 -119
- package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_close_boundary_holes.js +84 -84
- package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_compact.js +172 -172
- package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_compute_face_normals.js +56 -56
- package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_compute_vertex_quadratics.js +7 -7
- package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_compute_vertex_quadrics.js +227 -227
- package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_face_decouple.js +161 -161
- package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_face_island_erode.js +490 -490
- package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_fill_small_holes.js +120 -120
- package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_from_indexed_geometry.js +120 -120
- package/src/core/geom/3d/topology/struct/binary/io/edge/bt_edge_collapse.js +52 -52
- package/src/core/geom/3d/topology/struct/binary/io/edge/bt_edge_create.js +115 -115
- package/src/core/geom/3d/topology/struct/binary/io/edge/bt_edge_flip.js +142 -142
- package/src/core/geom/3d/topology/struct/binary/io/edge/bt_edge_get_or_create.js +21 -21
- package/src/core/geom/3d/topology/struct/binary/io/edge/bt_edge_kill_parallels.js +92 -92
- package/src/core/geom/3d/topology/struct/binary/io/edge/bt_edge_split.js +179 -179
- package/src/core/geom/3d/topology/struct/binary/io/edge/bt_edge_swap_vertex_slots.js +28 -28
- package/src/core/geom/3d/topology/struct/binary/io/edge/bt_kill_only_edge.js +8 -8
- package/src/core/geom/3d/topology/struct/binary/io/edge/bt_mesh_fuse_duplicate_edges.js +83 -83
- package/src/core/geom/3d/topology/struct/binary/io/edge/bt_mesh_kill_short_edges.js +89 -89
- package/src/core/geom/3d/topology/struct/binary/io/face/bt_face_kill.js +25 -25
- package/src/core/geom/3d/topology/struct/binary/io/face/bt_face_poke.js +127 -127
- package/src/core/geom/3d/topology/struct/binary/io/face/bt_face_triangulate.js +268 -268
- package/src/core/geom/3d/topology/struct/binary/io/face/bt_kill_only_face.js +8 -8
- package/src/core/geom/3d/topology/struct/binary/io/face/bt_mesh_kill_degenerate_faces.js +72 -72
- package/src/core/geom/3d/topology/struct/binary/io/vertex/bt_kill_only_vert.js +7 -7
- package/src/core/geom/3d/topology/struct/binary/io/vertex/bt_merge_verts_by_distance.js +7 -7
- package/src/core/geom/3d/topology/struct/binary/io/vertex/bt_mesh_relative_merge_distance.js +48 -48
- package/src/core/geom/3d/topology/struct/binary/io/vertex/bt_mesh_split_pinched_vertices.js +256 -256
- package/src/core/geom/3d/topology/struct/binary/io/vertex/bt_mesh_vertex_merge_distance.js +77 -77
- package/src/core/geom/3d/topology/struct/binary/io/vertex/bt_vert_fuse_duplicate_edges.js +7 -7
- package/src/core/geom/3d/topology/struct/binary/io/vertex/bt_vert_kill.js +7 -7
- package/src/core/geom/3d/topology/struct/binary/io/vertex/bt_vertex_fuse_duplicate_edges.js +148 -148
- package/src/core/geom/3d/topology/struct/binary/io/vertex/bt_vertex_kill_only.js +7 -7
- package/src/core/geom/3d/topology/struct/binary/query/bt_collect_boundary_segments.js +29 -29
- package/src/core/geom/3d/topology/struct/binary/query/bt_edge_collapse_breaks_link_condition.js +107 -107
- package/src/core/geom/3d/topology/struct/binary/query/bt_edge_flip_is_legal.js +41 -41
- package/src/core/geom/3d/topology/struct/binary/query/bt_edge_flip_would_fold.js +120 -120
- package/src/core/geom/3d/topology/struct/binary/query/bt_edge_get.js +42 -42
- package/src/core/geom/3d/topology/struct/binary/query/bt_edge_is_boundary.js +20 -20
- package/src/core/geom/3d/topology/struct/binary/query/bt_edge_read_diamond.js +96 -96
- package/src/core/geom/3d/topology/struct/binary/query/bt_face_compute_newell_normal.js +72 -72
- package/src/core/geom/3d/topology/struct/binary/query/bt_face_get_attached_faces.js +7 -7
- package/src/core/geom/3d/topology/struct/binary/query/bt_face_get_edge_adjacent_faces.js +39 -39
- package/src/core/geom/3d/topology/struct/binary/query/bt_face_get_edge_adjacent_faces_weighted.js +93 -93
- package/src/core/geom/3d/topology/struct/binary/query/bt_face_get_neighbour_faces.js +7 -7
- package/src/core/geom/3d/topology/struct/binary/query/bt_face_get_shared_loop.js +48 -48
- package/src/core/geom/3d/topology/struct/binary/query/bt_face_island_flood_fill.js +45 -45
- package/src/core/geom/3d/topology/struct/binary/query/bt_face_read_triangle.js +66 -66
- package/src/core/geom/3d/topology/struct/binary/query/bt_faces_shared_loop.js +7 -7
- package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_assert_valid.js +44 -44
- package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_build_boundary_euclidean_distance_field.js +32 -32
- package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_compute_face_islands.js +53 -53
- package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_face_find_path.js +703 -703
- package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_sample_interior_grid_points.js +181 -181
- package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_segment_penetrates_surface.js +133 -133
- package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_surface_ray_parity.js +82 -82
- package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_validate.js +599 -599
- package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_walk_boundary_loops.js +108 -108
- package/src/core/geom/3d/topology/struct/binary/query/bt_query_edge_is_boundary.js +7 -7
- package/src/core/geom/3d/triangle/computeTriangleClosestPointToPointBarycentric.js +7 -7
- package/src/core/geom/3d/triangle/computeTriangleRayIntersection.js +7 -7
- package/src/core/geom/3d/triangle/computeTriangleRayIntersectionBarycentric.js +7 -7
- package/src/core/geom/3d/triangle/computeTriangleRayIntersectionBarycentricEdge.js +7 -7
- package/src/core/geom/3d/triangle/computeTriangleRayIntersectionBarycentricGeometry.js +7 -7
- package/src/core/geom/3d/triangle/compute_triangle_area_3d.js +7 -7
- package/src/core/geom/3d/triangle/compute_triangle_normal.js +7 -7
- package/src/core/geom/3d/triangle/rayTriangleIntersection.js +7 -7
- package/src/core/geom/3d/triangle/tri3_closest_point.js +65 -65
- package/src/core/geom/3d/triangle/tri3_closest_point_barycentric.js +171 -171
- package/src/core/geom/3d/triangle/tri3_flatten_isometric.js +79 -79
- package/src/core/geom/3d/triangle/tri3_ray_intersection.js +160 -160
- package/src/core/geom/3d/triangle/tri3_ray_intersection_barycentric.js +96 -96
- package/src/core/geom/3d/triangle/tri3_raycast.d.ts +42 -0
- package/src/core/geom/3d/triangle/tri3_raycast.d.ts.map +1 -0
- package/src/core/geom/3d/triangle/tri3_raycast.js +94 -0
- package/src/core/geom/3d/triangle/triangle3_incenter.js +7 -7
- package/src/core/geom/3d/triangle/triangle_compute_plane_side.js +7 -7
- package/src/core/geom/3d/triangle/triangle_compute_signed_volume.js +7 -7
- package/src/core/geom/3d/triangle/triangle_intersects_clipping_volume.js +7 -7
- package/src/core/geom/3d/triangle/triangle_mesh_compute_signed_volume.js +7 -7
- package/src/core/geom/3d/triangle/v3_compute_triangle_normal.js +7 -7
- package/src/core/geom/3d/util/make_justified_point_grid.js +7 -7
- package/src/core/geom/GEOMETRY_REVIEW_002_API_ISSUES.md +381 -381
- package/src/core/geom/GEOMETRY_REVIEW_002_NAMING.md +684 -684
- package/src/core/geom/MANIFOLD_COMPARISON_2026_08_01.md +554 -554
- package/src/core/geom/mat3/m3_cm_from_translation.js +7 -7
- package/src/core/geom/mat3/m3_cm_invert.js +7 -7
- package/src/core/geom/mat3/m3_make_rotation.js +5 -5
- package/src/core/geom/mat3/m3_make_rotation_TBN.js +31 -31
- package/src/core/geom/mat3/m3_multiply_vec3.js +7 -7
- package/src/core/geom/packing/computeBoundingSphereOfSpheres.js +7 -7
- package/src/core/geom/packing/max-rect/cost/costByBestShortSide.js +7 -7
- package/src/core/geom/packing/max-rect/cost/costByRemainingArea.js +7 -7
- package/src/core/geom/packing/max-rect/cutArea.js +7 -7
- package/src/core/geom/packing/max-rect/findBestContainer.js +7 -7
- package/src/core/geom/packing/max-rect/packMaxRectangles.js +7 -7
- package/src/core/geom/packing/max-rect/packOneBox.js +7 -7
- package/src/core/geom/packing/max-rect/removeRedundantBoxes.js +7 -7
- package/src/core/geom/packing/max-rect/removeRedundantBoxesArray.js +7 -7
- package/src/core/geom/random/randomPointInBox.js +7 -7
- package/src/core/geom/random/randomPointInCircle.js +7 -7
- package/src/core/geom/random/randomPointInPoint.js +7 -7
- package/src/core/geom/random/randomPointInSphere.js +7 -7
- package/src/core/geom/random/randomPointOnBox.js +7 -7
- package/src/core/geom/random/randomPointOnSphere.js +7 -7
- package/src/core/geom/random/random_orthonormal_basis.js +31 -31
- package/src/core/geom/vec/vector_axpy.js +15 -15
- package/src/core/geom/vec/vector_axpy_offset.js +20 -20
- package/src/core/geom/vec/vector_copy.js +7 -7
- package/src/core/geom/vec/vector_dot_offset.js +25 -25
- package/src/core/geom/vec/vector_scale_array.js +7 -7
- package/src/core/geom/vec2/v2_rotate.js +21 -21
- package/src/core/geom/vec2/v2_rotate_cos_sin.js +21 -21
- package/src/core/geom/vec3/v3_angle_atan2_between.js +7 -7
- package/src/core/geom/vec3/v3_angle_between.js +7 -7
- package/src/core/geom/vec3/v3_angle_cos_between.js +7 -7
- package/src/core/geom/vec3/v3_array_add_in_place.js +7 -7
- package/src/core/geom/vec3/v3_array_centroid.js +7 -7
- package/src/core/geom/vec3/v3_array_copy.js +7 -7
- package/src/core/geom/vec3/v3_array_displace_in_direction.js +7 -7
- package/src/core/geom/vec3/v3_array_equals.js +7 -7
- package/src/core/geom/vec3/v3_array_immediate_add.js +7 -7
- package/src/core/geom/vec3/v3_array_matrix4_rotate.js +7 -7
- package/src/core/geom/vec3/v3_array_normalize.js +7 -7
- package/src/core/geom/vec3/v3_array_scale.js +7 -7
- package/src/core/geom/vec3/v3_compute_interior_angle.js +7 -7
- package/src/core/geom/vec3/v3_cotangent.js +33 -33
- package/src/core/geom/vec3/v3_dot_array_array.js +7 -7
- package/src/core/geom/vec3/v3_matrix3_multiply.js +30 -30
- package/src/core/geom/vec3/v3_matrix3_rotate.js +49 -49
- package/src/core/geom/vec3/v3_matrix4_rotate.js +7 -7
- package/src/core/geom/vec3/v3_multiply.js +23 -23
- package/src/core/geom/vec3/v3_multiply_scalar.js +7 -7
- package/src/core/geom/vec3/v3_orthonormal_matrix_from_normal.js +49 -49
- package/src/core/geom/vec3/v3_quat3_apply.js +7 -7
- package/src/core/geom/vec3/v3_quat3_apply_inverse.js +7 -7
- package/src/core/geom/vec3/v3_quaternion_apply.js +39 -39
- package/src/core/geom/vec3/v3_quaternion_apply_inverse.js +41 -41
- package/src/core/geom/vec3/v3_reflect.js +24 -24
- package/src/core/geom/vec3/v3_scale_array.js +18 -18
- package/src/core/geom/vec3/v3_triple_cross.js +45 -45
- package/src/core/geom/vec3/v3_triple_cross_product.js +7 -7
- package/src/core/geom/vec3/v3_uniform_sample_cone.js +21 -21
- package/src/core/geom/vec4/v4_multiply_mat4.js +7 -7
- package/src/core/graph/csr/csr_graph_build_distance_field.js +112 -112
- package/src/core/graph/csr/csr_graph_find_edge.js +25 -25
- package/src/core/graph/csr/csr_graph_find_shortest_path.js +105 -105
- package/src/core/graph/csr/csr_graph_from_undirected_edge_list.js +94 -94
- package/src/core/graph/graph_k_means_cluster.js +250 -250
- package/src/core/graph/layout/CircleLayout.js +1 -1
- package/src/core/graph/layout/box/BoxLayouter.js +1 -1
- package/src/core/graph/layout/graph_peel_topological_layer.js +45 -45
- package/src/core/graph/metis/BinaryTopologyFaceGraph.js +49 -49
- package/src/core/graph/metis/bt_mesh_build_face_graph.js +111 -111
- package/src/core/graph/metis/cluster_mesh_metis.js +137 -137
- package/src/core/graph/metis/native/bisection/bisect_graph.js +100 -100
- package/src/core/graph/metis/native/bisection/compute_2way_params.js +86 -86
- package/src/core/graph/metis/native/bisection/fm_2way.js +297 -297
- package/src/core/graph/metis/native/bisection/grow_bisection.js +137 -137
- package/src/core/graph/metis/native/bisection/split_graph_two_way.js +119 -119
- package/src/core/graph/metis/native/coarsen/coarsen_graph.js +94 -94
- package/src/core/graph/metis/native/coarsen/create_coarse_graph.js +158 -158
- package/src/core/graph/metis/native/coarsen/match_shem.js +175 -175
- package/src/core/graph/metis/native/initial/initial_kway_bfs.js +122 -122
- package/src/core/graph/metis/native/initial/initial_kway_recursive_bisection.js +170 -170
- package/src/core/graph/metis/native/metis_partition_kway.js +126 -126
- package/src/core/graph/metis/native/refine/RefinementScratch.js +53 -53
- package/src/core/graph/metis/native/refine/fm_kway.js +487 -487
- package/src/core/graph/metis/native/refine/project_kway.js +43 -43
- package/src/core/graph/metis/native/refine/refine_kway.js +43 -43
- package/src/core/lang/reactive/pegjs/parser.js +3458 -3458
- package/src/core/math/complex/complex_horner_eval.js +31 -31
- package/src/core/math/computeWholeDivisorLow.js +33 -33
- package/src/core/math/f32_next_down.js +24 -24
- package/src/core/math/f32_next_up.js +27 -27
- package/src/core/math/hash/hash_mix2.js +22 -22
- package/src/core/math/hash/hash_mix3.js +19 -19
- package/src/core/math/linalg/cg/cg_solve.js +106 -106
- package/src/core/math/linalg/cg/cg_solve_normal_equations.js +59 -59
- package/src/core/math/linalg/cubic_residual_times_derivative_accumulate.js +43 -43
- package/src/core/math/linalg/eigen/matrix_householder_in_place.js +122 -122
- package/src/core/math/linalg/eigen/matrix_qr_in_place.js +190 -190
- package/src/core/math/linalg/eigen/matrix_symmetric_3x3_eigen.js +117 -117
- package/src/core/math/linalg/givens/givens_apply_rows.js +28 -28
- package/src/core/math/linalg/givens/givens_apply_rows_offset.js +30 -30
- package/src/core/math/linalg/givens/givens_rotation_coefficients.js +39 -39
- package/src/core/math/linalg/lu_factor_linear_system.js +108 -108
- package/src/core/math/linalg/lu_solve_linear_system.js +53 -53
- package/src/core/math/linalg/polynomial_add_into.js +29 -29
- package/src/core/math/linalg/polynomial_complex_roots_aberth_ehrlich.js +159 -159
- package/src/core/math/linalg/polynomial_cubic_derivative_eval.js +16 -16
- package/src/core/math/linalg/polynomial_cubic_horner_eval.js +19 -19
- package/src/core/math/linalg/polynomial_cubic_second_derivative_eval.js +14 -14
- package/src/core/math/linalg/polynomial_multiply.js +41 -41
- package/src/core/math/linalg/polynomial_real_roots_in_interval.js +211 -211
- package/src/core/math/linalg/polynomial_root_bound_cauchy.js +30 -30
- package/src/core/math/linalg/polynomial_scale_into.js +17 -17
- package/src/core/math/linalg/polynomial_sub_into.js +29 -29
- package/src/core/math/linalg/solve_linear_system_GEPP_2x2.js +96 -96
- package/src/core/math/linalg/sor_optimal_omega.js +42 -42
- package/src/core/math/linalg/sparse/SparseMatrixCSR.js +88 -88
- package/src/core/math/linalg/sparse/spm_matvec.js +22 -22
- package/src/core/math/linalg/sparse/spm_matvec_transpose.js +29 -29
- package/src/core/math/lookup/LUT_HEATMAP.js +30 -30
- package/src/core/math/lookup/ParameterLookupTable.js +495 -495
- package/src/core/math/lookup/ParameterLookupTableFlags.js +6 -6
- package/src/core/math/noise/curl_noise_3d.js +18 -18
- package/src/core/math/physics/brdf/cone_cosine_from_roughness.js +28 -28
- package/src/core/math/physics/brdf/reflection_sample_weight.js +48 -48
- package/src/core/math/physics/kinematics/computeInterceptPoint.js +79 -79
- package/src/core/math/random/randomSeed.js +16 -16
- package/src/core/math/random/random_pick_weighted_index.js +26 -26
- package/src/core/math/round_half_away_from_zero.js +13 -13
- package/src/core/math/spline/spline3_hermite_apply_transform.js +107 -107
- package/src/core/math/spline/spline3_hermite_intersection_spline3_hermite_1d.js +125 -125
- package/src/core/math/spline/spline3_hermite_intersects_spline3_hermite.js +50 -50
- package/src/core/math/spline/spline3_hermite_reverse.js +49 -49
- package/src/core/math/spline/spline3_hermite_to_monomial.js +37 -37
- package/src/core/math/statistics/hammersley_sequence.js +53 -53
- package/src/core/model/reactive/model/util/createRandomReactiveExpression.js +185 -185
- package/src/core/path/convertPathToURL.js +107 -107
- package/src/core/process/delay.js +16 -16
- package/src/core/science/units/MEASUREMENT_UNITS_SCIENTIFIC.js +87 -87
- package/src/core/science/units/MEASUREMENT_UNITS_SI.js +17 -17
- package/src/core/science/units/NamedUnit.js +48 -48
- package/src/core/science/units/UNIT_DIMENSION_MAPPING.js +29 -29
- package/src/core/science/units/UnitDimension.js +28 -28
- package/src/core/science/units/format_quantity.js +74 -74
- package/src/core/science/units/magnitude_prefix.js +127 -127
- package/src/core/science/units/si/AMPERE.js +13 -13
- package/src/core/science/units/si/CANDELA.js +13 -13
- package/src/core/science/units/si/DIMENSIONLESS.js +14 -14
- package/src/core/science/units/si/KELVIN.js +13 -13
- package/src/core/science/units/si/KILOGRAM.js +13 -13
- package/src/core/science/units/si/METER.js +13 -13
- package/src/core/science/units/si/MOLE.js +13 -13
- package/src/core/science/units/si/SECOND.js +13 -13
- package/src/core/science/units/si/derived/BECQUEREL.js +16 -16
- package/src/core/science/units/si/derived/COULOMB.js +13 -13
- package/src/core/science/units/si/derived/FARAD.js +13 -13
- package/src/core/science/units/si/derived/GRAY.js +16 -16
- package/src/core/science/units/si/derived/HENRY.js +13 -13
- package/src/core/science/units/si/derived/HERTZ.js +16 -16
- package/src/core/science/units/si/derived/JOULE.js +13 -13
- package/src/core/science/units/si/derived/KATAL.js +13 -13
- package/src/core/science/units/si/derived/LUMEN.js +14 -14
- package/src/core/science/units/si/derived/LUX.js +13 -13
- package/src/core/science/units/si/derived/NEWTON.js +13 -13
- package/src/core/science/units/si/derived/OHM.js +13 -13
- package/src/core/science/units/si/derived/PASCAL.js +13 -13
- package/src/core/science/units/si/derived/SIEMENS.js +13 -13
- package/src/core/science/units/si/derived/SIEVERT.js +16 -16
- package/src/core/science/units/si/derived/TESLA.js +13 -13
- package/src/core/science/units/si/derived/VOLT.js +13 -13
- package/src/core/science/units/si/derived/WATT.js +13 -13
- package/src/core/science/units/si/derived/WEBER.js +13 -13
- package/src/core/science/units/unit_matrix_to_string.js +403 -403
- package/src/engine/Engine.js +726 -726
- package/src/engine/EngineHarness.js +590 -590
- package/src/engine/REVIEW_2026_07_03.md +211 -211
- package/src/engine/animation/AnimatedActions.js +81 -81
- package/src/engine/animation/AnimationUtils.js +252 -252
- package/src/engine/animation/async/TimeSeries.js +300 -300
- package/src/engine/animation/curve/animation_curve_binary_codec.js +60 -60
- package/src/engine/animation/curve/draw/canvas_point_to_curve.js +22 -22
- package/src/engine/animation/curve/draw/curve_point_to_canvas.js +38 -38
- package/src/engine/animation/curve/draw/position_canvas_to_curve.js +22 -22
- package/src/engine/animation/curve/draw/position_curve_to_canvas.js +22 -22
- package/src/engine/asset/GameAssetType.d.ts +0 -1
- package/src/engine/asset/GameAssetType.js +0 -1
- package/src/engine/asset/load_model_scene_bundle.js +27 -27
- package/src/engine/asset/loaders/font/FontAsset.js +21 -21
- package/src/engine/asset/loaders/font/FontAssetLoader.js +20 -20
- package/src/engine/asset/loaders/gltf_test_fixtures.js +358 -358
- package/src/engine/asset/loaders/image/ImageBitmapAssetLoader.js +52 -52
- package/src/engine/asset/loaders/image/ImageRGBADataLoader.js +94 -94
- package/src/engine/asset/loaders/image/png/filter/png_filter_unFilterAverage.js +59 -59
- package/src/engine/asset/loaders/image/png/filter/png_filter_unFilterNone.js +55 -55
- package/src/engine/asset/loaders/image/png/filter/png_filter_unFilterPaeth.js +74 -74
- package/src/engine/asset/loaders/image/png/filter/png_filter_unFilterSub.js +34 -34
- package/src/engine/asset/loaders/image/png/filter/png_filter_unFilterUp.js +46 -46
- package/src/engine/control/first-person/DESIGN.md +640 -640
- package/src/engine/control/first-person/DESIGN_EXTENSIONS.md +563 -563
- package/src/engine/control/first-person/FirstPersonMotionPhase.js +134 -134
- package/src/engine/control/first-person/FirstPersonPlayerControllerSystem.js +2045 -2045
- package/src/engine/control/first-person/mastery/DecisionPoint.js +30 -30
- package/src/engine/control/first-person/math/Spring.js +71 -71
- package/src/engine/control/first-person/math/computeJumpFromApex.js +23 -23
- package/src/engine/control/first-person/math/computeLRCBreathRate.js +41 -41
- package/src/engine/control/first-person/math/computeMassRatios.js +44 -44
- package/src/engine/control/first-person/math/criticallyDampedSpring.js +34 -34
- package/src/engine/control/first-person/math/dampedSpringStep.js +72 -72
- package/src/engine/control/first-person/math/jumpDynamics.js +108 -108
- package/src/engine/control/first-person/math/stepTowards.js +20 -20
- package/src/engine/control/first-person/pose/FirstPersonPose.js +137 -137
- package/src/engine/control/first-person/pose/FirstPersonPosture.js +27 -27
- package/src/engine/control/first-person/test/buildTestPlayer.js +36 -36
- package/src/engine/ecs/ASYNC_SYSTEMS_PROPOSAL.md +531 -531
- package/src/engine/ecs/EntityManager.d.ts +7 -3
- package/src/engine/ecs/EntityManager.d.ts.map +1 -1
- package/src/engine/ecs/EntityManager.js +10 -5
- package/src/engine/ecs/HIERARCHY_REVIEW.md +717 -717
- package/src/engine/ecs/async/SystemWorkerHost.js +329 -329
- package/src/engine/ecs/async/SystemWorkerLoopback.js +143 -143
- package/src/engine/ecs/async/WORKER_SYSTEM_PLAN.md +246 -246
- package/src/engine/ecs/async/WorkerSystem.js +702 -702
- package/src/engine/ecs/async/WorkerSystemProtocol.js +150 -150
- package/src/engine/ecs/fow/FogOfWar.js +707 -707
- package/src/engine/ecs/fow/FogOfWarEdgeMode.js +40 -40
- package/src/engine/ecs/fow/fog_of_war_mask_world_bounds.js +24 -24
- package/src/engine/ecs/fow/fog_of_war_uv_transform.js +30 -30
- package/src/engine/ecs/gui/hud/testHudEdgeStick.js +132 -134
- package/src/engine/ecs/hierarchy/EntityChildIndex.js +140 -140
- package/src/engine/ecs/ik/IKProblem.js +42 -42
- package/src/engine/ecs/ik/InverseKinematicsSystem.js +193 -193
- package/src/engine/ecs/ik/OneBoneSurfaceAlignmentSolver.js +134 -134
- package/src/engine/ecs/ik/TwoBoneInverseKinematicsSolver.js +97 -97
- package/src/engine/ecs/ik/ik_bone_chain.js +85 -85
- package/src/engine/ecs/ik/ik_probe_terrain_contact.js +119 -119
- package/src/engine/ecs/parent/EntityNode.d.ts +1 -1
- package/src/engine/ecs/parent/EntityNode.js +2 -2
- package/src/engine/ecs/parent/ParentEntitySystem.js +255 -255
- package/src/engine/ecs/speaker/VoiceSystem.d.ts +2 -2
- package/src/engine/ecs/speaker/VoiceSystem.d.ts.map +1 -1
- package/src/engine/ecs/speaker/VoiceSystem.js +13 -7
- package/src/engine/ecs/storage/binary/SERIALIZATION_COVERAGE.md +269 -269
- package/src/engine/ecs/storage/binary/object-v2/BinaryObjectDeSerializer.js +315 -315
- package/src/engine/ecs/storage/binary/object-v2/BinaryObjectSerializer.js +331 -331
- package/src/engine/ecs/storage/binary/object-v2/DESIGN.md +199 -199
- package/src/engine/ecs/storage/binary/object-v2/ObjectBinaryFormat.js +57 -57
- package/src/engine/ecs/storage/binary/object-v2/ObjectSerializationAdapter.js +56 -56
- package/src/engine/ecs/storage/binary/object-v2/README.md +110 -110
- package/src/engine/ecs/storage/binary/object-v2/objectStringCodec.js +48 -48
- package/src/engine/ecs/storage/binary/object-v2/serializerTestHarness.js +44 -44
- package/src/engine/ecs/storage/populateEngineSerializationRegistry.js +265 -265
- package/src/engine/ecs/terrain/TerrainClouds.js +97 -97
- package/src/engine/ecs/terrain/ecs/StubTerrainBuildWorker.js +65 -65
- package/src/engine/ecs/terrain/ecs/Terrain.d.ts +4 -0
- package/src/engine/ecs/terrain/ecs/Terrain.d.ts.map +1 -1
- package/src/engine/ecs/terrain/ecs/Terrain.js +790 -790
- package/src/engine/ecs/terrain/ecs/TerrainSystem.js +209 -209
- package/src/engine/ecs/terrain/ecs/cling/ClingToTerrainSystem.js +316 -316
- package/src/engine/ecs/terrain/ecs/layers/TerrainLayers.js +416 -416
- package/src/engine/ecs/terrain/ecs/splat/SplatMapping.js +525 -525
- package/src/engine/ecs/terrain/overlay/TerrainOverlay.js +302 -302
- package/src/engine/ecs/terrain/tiles/TerrainTile.js +561 -561
- package/src/engine/ecs/terrain/tiles/TerrainTileManager.js +896 -896
- package/src/engine/ecs/terrain/tiles/tile_geometry_to_meshlets.js +36 -36
- package/src/engine/ecs/terrain/util/loadVisibleTerrainTiles.js +129 -129
- package/src/engine/ecs/transform/Transform64.js +814 -814
- package/src/engine/ecs/transform/t64_copy_from_transform.js +35 -35
- package/src/engine/ecs/transform/t64_equals_transform.js +55 -55
- package/src/engine/ecs/transform/t64_look_rotation.js +36 -36
- package/src/engine/ecs/transform/t64_transform_direction.js +25 -25
- package/src/engine/ecs/transform/transform_copy_from_t64.js +28 -28
- package/src/engine/ecs/transform-attachment/TransformAttachmentSystem.js +345 -345
- package/src/engine/ecs/transform-attachment/transform_attachment_find_descendant_by_name.js +65 -65
- package/src/engine/ecs/transform-attachment/transform_attachment_parent_of.js +24 -24
- package/src/engine/ecs/util/hideEntityGracefully.js +208 -208
- package/src/engine/graphics/CONTEXT_LOSS_RECOVERY_PLAN.md +446 -446
- package/src/engine/graphics/FULL_SCREEN_QUAD_VERTEX_SHADER.js +14 -14
- package/src/engine/graphics/FrameThrottle.js +2 -2
- package/src/engine/graphics/ecs/animation/animator/graph/AnimationState.js +207 -207
- package/src/engine/graphics/ecs/animation/animator/graph/AnimationTransition.js +151 -151
- package/src/engine/graphics/ecs/animation/animator/graph/definition/serialization/AnimationGraphDefinitionAssetLoader.js +22 -22
- package/src/engine/graphics/ecs/camera/Camera.js +165 -165
- package/src/engine/graphics/ecs/camera/camera_active_frustum.js +25 -25
- package/src/engine/graphics/ecs/camera/camera_find_active.js +28 -28
- package/src/engine/graphics/ecs/camera/camera_traverse_active.js +18 -18
- package/src/engine/graphics/ecs/camera/pp/PerfectPanner.js +170 -170
- package/src/engine/graphics/ecs/camera/serialization/CameraSerializationAdapter.js +68 -68
- package/src/engine/graphics/ecs/camera/topdown/TopDownCameraController.js +296 -296
- package/src/engine/graphics/ecs/decal/v2/Decal.js +220 -220
- package/src/engine/graphics/ecs/decal/v2/DecalSerializationAdapter.js +56 -56
- package/src/engine/graphics/ecs/decal/v2/DecalSerializationUpgrader_0_1.js +38 -38
- package/src/engine/graphics/ecs/light/shadow/compute_view_frustum_aabb_in_space.js +62 -62
- package/src/engine/graphics/ecs/light/shadow/extend_shadow_camera_near_for_casters.js +77 -77
- package/src/engine/graphics/ecs/make_bvh_depth_range_computer.js +33 -33
- package/src/engine/graphics/ecs/mesh-v2/DeferredBoundsQueue.js +88 -88
- package/src/engine/graphics/ecs/mesh-v2/ShadedGeometry.js +428 -428
- package/src/engine/graphics/ecs/mesh-v2/aggregate/SGMesh.js +303 -303
- package/src/engine/graphics/ecs/mesh-v2/aggregate/SGMeshSystem.d.ts +1 -1
- package/src/engine/graphics/ecs/mesh-v2/aggregate/serialization/SGMeshSerializationAdapter.js +39 -39
- package/src/engine/graphics/ecs/mesh-v2/sg_hierarchy_compute_bounding_box_via_parent_entity.js +36 -36
- package/src/engine/graphics/ecs/path/tube/build/GeometryStream.js +94 -94
- package/src/engine/graphics/ecs/path/tube/build/build_stream_catmullrom.js +310 -310
- package/src/engine/graphics/ecs/path/tube/build/build_stream_linear.js +73 -73
- package/src/engine/graphics/ecs/path/tube/build/computeFrenetFrames.js +199 -199
- package/src/engine/graphics/ecs/path/tube/build/makeTubeStream.js +217 -217
- package/src/engine/graphics/ecs/path/tube/build/make_cap.js +340 -340
- package/src/engine/graphics/ecs/path/tube/build/make_ring_faces.js +40 -40
- package/src/engine/graphics/ecs/path/tube/build/make_ring_vertices.js +153 -153
- package/src/engine/graphics/ecs/render_layers_compute_depth_range.js +64 -64
- package/src/engine/graphics/ecs/trail3d/Trail3D.js +483 -483
- package/src/engine/graphics/ecs/trail3d/Trail3DFlags.js +26 -26
- package/src/engine/graphics/ecs/trail3d/advance_trail.js +42 -42
- package/src/engine/graphics/ecs/trail3d/make_gradient_stroke.js +58 -58
- package/src/engine/graphics/ecs/trail3d/seed_trail_stroke.js +90 -90
- package/src/engine/graphics/ecs/trail3d/seed_trail_tube.js +75 -75
- package/src/engine/graphics/ecs/trail3d/trail_head_position.js +19 -19
- package/src/engine/graphics/ecs/water/Water.js +104 -104
- package/src/engine/graphics/geometry/AttributeData.js +84 -84
- package/src/engine/graphics/geometry/buffered/geometry_compute_vertex_normals_indexed.js +49 -49
- package/src/engine/graphics/geometry/buffered/query/GeometrySpatialQueryAccelerator.js +288 -288
- package/src/engine/graphics/geometry/buffered/query/nearest_point_on_triangle_record.js +87 -87
- package/src/engine/graphics/geometry/buffered/read_triangle_vertices.js +102 -102
- package/src/engine/graphics/geometry/bvh/buffered/bvh32_from_buffered_geometry.js +52 -52
- package/src/engine/graphics/geometry/bvh/buffered/bvh32_from_indexed_geometry.js +20 -20
- package/src/engine/graphics/geometry/bvh/buffered/bvh32_from_unindexed_geometry.js +19 -19
- package/src/engine/graphics/geometry/optimization/geometry_optimize_uv_tension.js +135 -135
- package/src/engine/graphics/impostors/voxel/README.md +149 -149
- package/src/engine/graphics/impostors/voxel/VoxelImpostorDescription.js +141 -141
- package/src/engine/graphics/particles/node-based/codegen/glsl/getTypeByteSize.js +20 -20
- package/src/engine/graphics/particles/node-based/nodes/FloatConstant.d.ts +2 -0
- package/src/engine/graphics/particles/node-based/nodes/FloatConstant.d.ts.map +1 -1
- package/src/engine/graphics/particles/node-based/nodes/ReadFloatUniform.d.ts +2 -0
- package/src/engine/graphics/particles/node-based/nodes/ReadFloatUniform.d.ts.map +1 -1
- package/src/engine/graphics/particles/node-based/nodes/ReadVelocity.d.ts +2 -0
- package/src/engine/graphics/particles/node-based/nodes/ReadVelocity.d.ts.map +1 -1
- package/src/engine/graphics/particles/node-based/nodes/ShaderNode.d.ts +1 -2
- package/src/engine/graphics/particles/node-based/nodes/ShaderNode.d.ts.map +1 -1
- package/src/engine/graphics/particles/node-based/nodes/Vector3Constant.d.ts +2 -0
- package/src/engine/graphics/particles/node-based/nodes/Vector3Constant.d.ts.map +1 -1
- package/src/engine/graphics/particles/node-based/nodes/Vector3Merge.d.ts +2 -0
- package/src/engine/graphics/particles/node-based/nodes/Vector3Merge.d.ts.map +1 -1
- package/src/engine/graphics/particles/node-based/nodes/Vector3Split.d.ts +2 -0
- package/src/engine/graphics/particles/node-based/nodes/Vector3Split.d.ts.map +1 -1
- package/src/engine/graphics/particles/node-based/nodes/WriteVelocity.d.ts +2 -0
- package/src/engine/graphics/particles/node-based/nodes/WriteVelocity.d.ts.map +1 -1
- package/src/engine/graphics/particles/node-based/nodes/attribute/ReadVector3Attribute.d.ts +2 -0
- package/src/engine/graphics/particles/node-based/nodes/attribute/ReadVector3Attribute.d.ts.map +1 -1
- package/src/engine/graphics/particles/node-based/nodes/attribute/WriteVector3Attribute.d.ts +2 -0
- package/src/engine/graphics/particles/node-based/nodes/attribute/WriteVector3Attribute.d.ts.map +1 -1
- package/src/engine/graphics/particles/node-based/nodes/math/AddFloatNode.d.ts +2 -0
- package/src/engine/graphics/particles/node-based/nodes/math/AddFloatNode.d.ts.map +1 -1
- package/src/engine/graphics/particles/node-based/nodes/math/Vector3Add.d.ts +2 -0
- package/src/engine/graphics/particles/node-based/nodes/math/Vector3Add.d.ts.map +1 -1
- package/src/engine/graphics/particles/node-based/nodes/math/Vector3Divide.d.ts +2 -0
- package/src/engine/graphics/particles/node-based/nodes/math/Vector3Divide.d.ts.map +1 -1
- package/src/engine/graphics/particles/node-based/nodes/math/Vector3Multiply.d.ts +2 -0
- package/src/engine/graphics/particles/node-based/nodes/math/Vector3Multiply.d.ts.map +1 -1
- package/src/engine/graphics/particles/node-based/nodes/math/Vector3Subtract.d.ts +2 -0
- package/src/engine/graphics/particles/node-based/nodes/math/Vector3Subtract.d.ts.map +1 -1
- package/src/engine/graphics/particles/node-based/nodes/noise/CurlNoiseNode.d.ts +2 -0
- package/src/engine/graphics/particles/node-based/nodes/noise/CurlNoiseNode.d.ts.map +1 -1
- package/src/engine/graphics/particles/node-based/nodes/particle_data_type_component_count.js +45 -45
- package/src/engine/graphics/particles/particular/engine/ParticularEngine.js +229 -229
- package/src/engine/graphics/particles/particular/engine/emitter/ParticleEmitter.d.ts +2 -10
- package/src/engine/graphics/particles/particular/engine/emitter/ParticleEmitter.d.ts.map +1 -1
- package/src/engine/graphics/particles/particular/engine/emitter/ParticleEmitter.js +1149 -1149
- package/src/engine/graphics/particles/particular/engine/emitter/ParticlePool.js +472 -472
- package/src/engine/graphics/particles/particular/engine/parameter/ParameterLookupTable.js +2 -2
- package/src/engine/graphics/particles/particular/engine/parameter/ParameterLookupTableFlags.js +2 -2
- package/src/engine/graphics/particles/particular/engine/parameter/ParameterSet.d.ts.map +1 -1
- package/src/engine/graphics/particles/particular/engine/parameter/ParameterSet.js +3 -5
- package/src/engine/graphics/particles/particular/engine/shader/ShaderManager.js +135 -135
- package/src/engine/graphics/particles/particular/engine/utils/distribute_points_on_indexed_triangles.js +168 -168
- package/src/engine/graphics/render/forward_plus/plugin/forwardPlusMaterialMarker.js +18 -18
- package/src/engine/graphics/render/forward_plus/read_frustum_corner.js +2 -2
- package/src/engine/graphics/render/frame_graph/FRAME_GRAPH_RECOMMENDATIONS.md +219 -219
- package/src/engine/graphics/render/frame_graph/FrameGraph.js +900 -900
- package/src/engine/graphics/render/frame_graph/FrameGraphScope.js +72 -72
- package/src/engine/graphics/render/frame_graph/FrameGraphState.js +39 -39
- package/src/engine/graphics/render/frame_graph/ResourceEntry.js +96 -96
- package/src/engine/graphics/render/gizmo/GizmoShapeBuffer.js +105 -105
- package/src/engine/graphics/render/gizmo/GizmoShapeRenderingInterface.js +162 -162
- package/src/engine/graphics/render/layers/RenderLayer.js +95 -95
- package/src/engine/graphics/sh3/build_injected_shader.js +56 -56
- package/src/engine/graphics/sh3/grid_dilate_average.js +175 -175
- package/src/engine/graphics/sh3/path_tracer/BufferedGeometryBVH.js +506 -506
- package/src/engine/graphics/sh3/path_tracer/PathTracedMesh.js +161 -161
- package/src/engine/graphics/sh3/path_tracer/PathTracedRenderer.js +205 -205
- package/src/engine/graphics/sh3/path_tracer/PathTracedScene.js +698 -698
- package/src/engine/graphics/sh3/path_tracer/REVIEW_001_ACTION_PLAN.md +490 -490
- package/src/engine/graphics/sh3/path_tracer/geometry/compute_triangle_group_aabb3.js +2 -2
- package/src/engine/graphics/sh3/path_tracer/make_sky_hosek.js +45 -45
- package/src/engine/graphics/sh3/path_tracer/populate_path_traced_scene_from_ecd.js +73 -73
- package/src/engine/graphics/sh3/path_tracer/ray_hit_apply_transform.js +32 -32
- package/src/engine/graphics/sh3/path_tracer/texture/apply_texture_clamping_to_coordinate.js +28 -28
- package/src/engine/graphics/sh3/path_tracer/texture/sample_material.js +142 -142
- package/src/engine/graphics/sh3/sky/hosek/render_hosek_sky_to_equirectangular.js +70 -70
- package/src/engine/graphics/sh3/sky/hosek/sky_hosek_compute_irradiance_by_direction.js +388 -388
- package/src/engine/graphics/shaders/chunks/glsl_octahedral_impostor.js +277 -277
- package/src/engine/graphics/shaders/chunks/glsl_voxel_impostor_splat.js +57 -57
- package/src/engine/graphics/shadows/vsm.glsl.js +43 -43
- package/src/engine/graphics/texture/3d/scs3d_sample_linear3.js +81 -81
- package/src/engine/graphics/texture/EnvironmentTextureProjection.js +15 -15
- package/src/engine/graphics/texture/channel_count_to_format_prefix.js +26 -26
- package/src/engine/graphics/texture/isImageBitmap.js +2 -2
- package/src/engine/graphics/texture/reflection/convolve_equirectangular_reflection.js +189 -189
- package/src/engine/graphics/texture/reflection/equirectangular_reflection_roughness.js +51 -51
- package/src/engine/graphics/texture/sampler/HarmonicDiffusionGrid.js +145 -145
- package/src/engine/graphics/texture/sampler/Sampler2DArray.js +172 -172
- package/src/engine/graphics/texture/sampler/compare/FLIP_IMPLEMENTATION_PLAN.md +466 -466
- package/src/engine/graphics/texture/sampler/compare/flip/FlipParameters.js +18 -18
- package/src/engine/graphics/texture/sampler/compare/flip/FlipPooling.js +33 -33
- package/src/engine/graphics/texture/sampler/compare/flip/flip_build_feature_kernel.js +147 -147
- package/src/engine/graphics/texture/sampler/compare/flip/flip_build_spatial_kernel.js +177 -177
- package/src/engine/graphics/texture/sampler/compare/flip/flip_compute_color_difference.js +104 -104
- package/src/engine/graphics/texture/sampler/compare/flip/flip_compute_feature_difference.js +76 -76
- package/src/engine/graphics/texture/sampler/compare/flip/flip_compute_pixels_per_degree.js +34 -34
- package/src/engine/graphics/texture/sampler/compare/flip/flip_constants.js +77 -77
- package/src/engine/graphics/texture/sampler/compare/flip/flip_filter_feature.js +48 -48
- package/src/engine/graphics/texture/sampler/compare/flip/flip_filter_spatial.js +86 -86
- package/src/engine/graphics/texture/sampler/compare/flip/flip_pool.js +71 -71
- package/src/engine/graphics/texture/sampler/compare/flip/flip_srgb_to_ycxcz.js +55 -55
- package/src/engine/graphics/texture/sampler/compare/flip/sampler2d_flip_ldr.js +80 -80
- package/src/engine/graphics/texture/sampler/filter/SeparableKernel.js +82 -82
- package/src/engine/graphics/texture/sampler/filter/sampler2d_convolve_separable.js +122 -122
- package/src/engine/graphics/texture/sampler/resize/sampler2d_scale_down_lanczos.js +148 -148
- package/src/engine/graphics/texture/sampler/sampler2d_channel_apply_lut.js +82 -82
- package/src/engine/graphics/texture/sampler/sampler2d_combine.js +67 -67
- package/src/engine/graphics/texture/sampler/sampler2d_linear_f16_to_srgb_u8.js +54 -54
- package/src/engine/graphics/texture/sampler/sampler2d_sample_equirectangular_direction.js +63 -63
- package/src/engine/graphics/texture/sampler/sampler2d_srgb_to_linear.js +33 -33
- package/src/engine/graphics/texture/sampler/sampler2d_srgb_u8_to_linear_f16.js +68 -68
- package/src/engine/graphics/texture/shade_image_preview.js +103 -103
- package/src/engine/graphics/trail/TrailSpawnMode.js +20 -20
- package/src/engine/graphics/trail/tube/TubeX.js +573 -573
- package/src/engine/graphics/trail/tube/TubeXMaterialSpec.js +58 -58
- package/src/engine/graphics/trail/tube/simulator/TubeXFixedPhysicsSimulator.js +34 -34
- package/src/engine/graphics/trail/tube/tube_attributes_spec.js +69 -69
- package/src/engine/graphics/trail/tube/tube_frame.js +83 -83
- package/src/engine/graphics3/AnimationGraphSystem3.js +329 -329
- package/src/engine/graphics3/AnimationSystem3.js +352 -352
- package/src/engine/graphics3/CameraSystem3.js +170 -170
- package/src/engine/graphics3/DecalSystem3.js +441 -441
- package/src/engine/graphics3/FogOfWarSystem3.js +265 -265
- package/src/engine/graphics3/GraphicsEngine3.js +776 -776
- package/src/engine/graphics3/HighlightSystem3.js +241 -241
- package/src/engine/graphics3/LightSystem3.js +211 -211
- package/src/engine/graphics3/MeshSystem3.js +646 -646
- package/src/engine/graphics3/ParticleEmitterSystem3.js +289 -289
- package/src/engine/graphics3/PathDisplaySystem3.js +309 -309
- package/src/engine/graphics3/PickingQuery.js +48 -48
- package/src/engine/graphics3/PickingResult.js +47 -47
- package/src/engine/graphics3/PickingSystem3.js +273 -273
- package/src/engine/graphics3/ShadeCameraAdapter.js +55 -55
- package/src/engine/graphics3/ShadedGeometrySystem3.js +169 -169
- package/src/engine/graphics3/TerrainSystem3.js +566 -566
- package/src/engine/graphics3/TooltipComponentSystem3.js +328 -328
- package/src/engine/graphics3/Trail3DSystem3.js +221 -221
- package/src/engine/graphics3/WaterSystem3.js +293 -293
- package/src/engine/graphics3/animation/ClipPlayback.js +84 -84
- package/src/engine/graphics3/animation/wrap_clip_time.js +23 -23
- package/src/engine/graphics3/animation/write_graph_playback.js +48 -48
- package/src/engine/graphics3/camera_sync_from_transform.js +29 -29
- package/src/engine/graphics3/debug/gizmo_view_from_camera.js +48 -48
- package/src/engine/graphics3/decal/DECAL_CLUSTER.js +61 -61
- package/src/engine/graphics3/decal/DECAL_GPU_RECORD.js +117 -117
- package/src/engine/graphics3/decal/DecalAtlas.js +194 -194
- package/src/engine/graphics3/decal/DecalAtlasFill.js +32 -32
- package/src/engine/graphics3/decal/GPUDecalRenderer.js +360 -360
- package/src/engine/graphics3/decal/chunk_decal_intersects_frustum.js +59 -59
- package/src/engine/graphics3/decal/chunk_decal_sampling_info.js +57 -57
- package/src/engine/graphics3/decal/chunk_decal_surface_frame.js +54 -54
- package/src/engine/graphics3/decal/decal_atlas_border.js +69 -69
- package/src/engine/graphics3/decal/decal_gpu_records.js +120 -120
- package/src/engine/graphics3/decal/decal_material_samplers.js +109 -109
- package/src/engine/graphics3/decal/graph_build_decal_clusters.js +212 -212
- package/src/engine/graphics3/decal/shader_cluster_assign_decals.js +207 -207
- package/src/engine/graphics3/decal/shader_cull_decals_frustum.js +108 -108
- package/src/engine/graphics3/highlight/GPUHighlightRenderer.js +151 -151
- package/src/engine/graphics3/highlight/HIGHLIGHT_OUTLINE.js +23 -23
- package/src/engine/graphics3/highlight/chunk_highlight_lookup.js +53 -53
- package/src/engine/graphics3/highlight/pack_highlight_color.js +44 -44
- package/src/engine/graphics3/highlight/pack_highlight_table.js +74 -74
- package/src/engine/graphics3/highlight/shader_highlight_outline.js +150 -150
- package/src/engine/graphics3/load_cube_environment.js +68 -68
- package/src/engine/graphics3/make_default_environment.js +107 -107
- package/src/engine/graphics3/particles/particle_gpu_records.js +221 -221
- package/src/engine/graphics3/path/build_tube_path_streams.js +98 -98
- package/src/engine/graphics3/path/tube_stream_write_geometry.js +90 -90
- package/src/engine/graphics3/preview/ModelPreviewFraming.js +35 -35
- package/src/engine/graphics3/preview/make_model_preview_scene.js +154 -154
- package/src/engine/graphics3/preview/make_model_thumbnail.js +48 -48
- package/src/engine/graphics3/preview/model_preview_framing.js +70 -70
- package/src/engine/graphics3/shade_bundle_bounds.js +55 -55
- package/src/engine/graphics3/shade_camera_projection_ray.js +77 -77
- package/src/engine/graphics3/shade_node_to_entity_composition.js +84 -84
- package/src/engine/graphics3/terrain/GPUTerrainSplatRenderer.js +481 -481
- package/src/engine/graphics3/terrain/chunk_terrain_grid_overlay.js +61 -61
- package/src/engine/graphics3/terrain/chunk_terrain_splat_mix.js +66 -66
- package/src/engine/graphics3/terrain/pack_terrain_row_table.js +53 -53
- package/src/engine/graphics3/terrain/shader_terrain_splat.js +215 -215
- package/src/engine/graphics3/trail/trail_tube_write_geometry.js +158 -158
- package/src/engine/grid/grid2transform/GridPosition2TransformSystem.d.ts +2 -2
- package/src/engine/grid/grid2transform/GridPosition2TransformSystem.d.ts.map +1 -1
- package/src/engine/input/GAMEPAD_API_PROPOSAL.md +717 -717
- package/src/engine/input/analog/apply_radial_dead_zone.js +38 -38
- package/src/engine/input/devices/GamepadDevice.js +466 -466
- package/src/engine/input/devices/ManualInputDevice.js +152 -152
- package/src/engine/input/devices/PointerCoordinates.js +26 -26
- package/src/engine/input/devices/events/GamepadEvents.js +10 -10
- package/src/engine/input/devices/gamepad/GamepadAxes.js +19 -19
- package/src/engine/input/devices/gamepad/GamepadButtons.js +29 -29
- package/src/engine/input/devices/gamepad/GamepadCoordinates.js +17 -17
- package/src/engine/input/devices/gamepad/GamepadDeviceLayout.js +323 -323
- package/src/engine/input/devices/gamepad/GamepadHandle.js +286 -286
- package/src/engine/input/devices/gamepad/GamepadLayout.js +160 -160
- package/src/engine/input/devices/gamepad/GamepadStandardMapping.js +31 -31
- package/src/engine/input/devices/gamepad/GamepadStick.js +169 -169
- package/src/engine/input/devices/gamepad/GamepadStickCalibration.js +119 -119
- package/src/engine/input/devices/gamepad/GamepadStickCalibrator.js +250 -250
- package/src/engine/input/devices/gamepad/gamepad_layout_database.js +144 -144
- package/src/engine/input/devices/gamepad/gamepad_read_hat_axis.js +61 -61
- package/src/engine/input/devices/gamepad/gamepad_write_dpad_vector.js +30 -30
- package/src/engine/input/devices/gamepad/prototypeGamepadTester.js +2124 -2124
- package/src/engine/input/devices/mouse/MouseButtons.js +22 -22
- package/src/engine/input/ecs/components/Input.js +118 -118
- package/src/engine/input/ecs/components/InputController.js +78 -78
- package/src/engine/input/ecs/ism/ISM_DESIGN.md +1523 -1523
- package/src/engine/input/ecs/ism/ISM_IMPLEMENTATION_PLAN.md +627 -627
- package/src/engine/input/ecs/ism/InputActionEvent.js +40 -40
- package/src/engine/input/ecs/ism/InputBinding.js +71 -71
- package/src/engine/input/ecs/ism/InputCoordinateKind.js +25 -25
- package/src/engine/input/ecs/ism/InputCoordinateSource.js +86 -86
- package/src/engine/input/ecs/ism/InputDirection.js +33 -33
- package/src/engine/input/ecs/ism/InputMapProgram.js +236 -236
- package/src/engine/input/ecs/ism/InputMapSystem.js +976 -976
- package/src/engine/input/ecs/ism/InputOcclusion.js +31 -31
- package/src/engine/input/ecs/ism/InputSource.js +67 -67
- package/src/engine/input/ecs/ism/InputSourceTable.js +107 -107
- package/src/engine/input/ecs/ism/coordinate/InputCoordinateChannel.js +93 -93
- package/src/engine/input/ecs/ism/coordinate/InputCoordinateChannelType.js +11 -11
- package/src/engine/input/ecs/ism/coordinate/InputCoordinateDevice.js +153 -153
- package/src/engine/input/ecs/ism/coordinate/InputCoordinateSwitches.js +160 -160
- package/src/engine/input/ecs/ism/device/GAMEPAD_INPUT_DEVICE_ID.js +9 -9
- package/src/engine/input/ecs/ism/device/GamepadInputDeviceAdapter.js +88 -88
- package/src/engine/input/ecs/ism/device/InputDeviceAdapter.js +72 -72
- package/src/engine/input/ecs/ism/device/KEYBOARD_INPUT_DEVICE_ID.js +9 -9
- package/src/engine/input/ecs/ism/device/KeyboardInputDeviceAdapter.js +63 -63
- package/src/engine/input/ecs/ism/device/ManualInputDeviceAdapter.js +55 -55
- package/src/engine/input/ecs/ism/device/POINTER_INPUT_DEVICE_ID.js +9 -9
- package/src/engine/input/ecs/ism/device/PointerInputDeviceAdapter.js +160 -160
- package/src/engine/input/ecs/ism/map/InputBinding.js +88 -88
- package/src/engine/input/ecs/ism/map/InputCoordinateBinding.js +102 -102
- package/src/engine/input/ecs/ism/map/InputLayer.js +220 -220
- package/src/engine/input/ecs/ism/map/InputMap.js +331 -331
- package/src/engine/input/ecs/ism/prototypeInputMap.js +309 -309
- package/src/engine/input/ecs/ism/trigger/InputTrigger.js +148 -148
- package/src/engine/input/ecs/ism/trigger/InputTriggerChord.js +173 -173
- package/src/engine/input/ecs/ism/trigger/InputTriggerGamepadButton.js +37 -37
- package/src/engine/input/ecs/ism/trigger/InputTriggerHold.js +176 -176
- package/src/engine/input/ecs/ism/trigger/InputTriggerKey.js +39 -39
- package/src/engine/input/ecs/ism/trigger/InputTriggerMouseButton.js +38 -38
- package/src/engine/input/ecs/ism/trigger/InputTriggerRelease.js +120 -120
- package/src/engine/input/ecs/ism/trigger/InputTriggerSequence.js +315 -315
- package/src/engine/input/ecs/ism/trigger/InputTriggerSwitch.js +105 -105
- package/src/engine/input/ecs/ism/trigger/InputTriggerType.js +17 -17
- package/src/engine/input/ecs/systems/InputControllerSystem.js +253 -253
- package/src/engine/input/ecs/systems/InputSystem.js +162 -162
- package/src/engine/intelligence/behavior/primitive/FailingBehaviorSerializationAdapter.js +25 -25
- package/src/engine/intelligence/behavior/primitive/SucceedingBehaviorSerializationAdapter.js +26 -26
- package/src/engine/intelligence/behavior/util/AsynchronousDelayAction.js +55 -55
- package/src/engine/interpolation/BinaryInterpolationAdapter.js +56 -56
- package/src/engine/interpolation/Interpoland.js +49 -49
- package/src/engine/interpolation/Interpolated.js +149 -149
- package/src/engine/interpolation/PoseInterpolationAdapter.js +61 -61
- package/src/engine/interpolation/TransformPoseSerializationAdapter.js +57 -57
- package/src/engine/interpolation/pose_interpoland.js +27 -27
- package/src/engine/navigation/ecs/path_following/PathFollower.js +222 -222
- package/src/engine/navigation/mesh/NavigationMesh.js +145 -145
- package/src/engine/navigation/mesh/OVERLAPPING_SURFACES_2026_08_25.md +661 -0
- package/src/engine/navigation/mesh/PATHFINDING_PLAN.md +229 -229
- package/src/engine/navigation/mesh/build/clip_soup_against_overhangs.d.ts.map +1 -1
- package/src/engine/navigation/mesh/build/clip_soup_against_overhangs.js +628 -520
- package/src/engine/network/CONGESTION_CONTROL.md +87 -87
- package/src/engine/network/README.md +52 -52
- package/src/engine/network/adapters/QuaternionInterpolationAdapter.js +43 -43
- package/src/engine/network/adapters/TransformInterpolationAdapter.js +84 -84
- package/src/engine/network/adapters/TransformReplicationAdapter.js +87 -87
- package/src/engine/network/adapters/Vector3InterpolationAdapter.js +46 -46
- package/src/engine/network/convertPathToURL.js +2 -2
- package/src/engine/network/core/MalformedPacketError.js +26 -26
- package/src/engine/network/core/quantize/quantize_float.js +73 -73
- package/src/engine/network/core/quantize/quantize_position.js +65 -65
- package/src/engine/network/core/sequence/ack_bitfield.js +77 -77
- package/src/engine/network/core/sequence/seq16.js +69 -69
- package/src/engine/network/core/sequence/seq32.js +73 -73
- package/src/engine/network/ecs/NetworkSystem.js +84 -84
- package/src/engine/network/ecs/components/NetworkIdentity.js +73 -73
- package/src/engine/network/ecs/owner_authorization.js +55 -55
- package/src/engine/network/ecs/serialization/NetworkIdentitySerializationAdapter.js +64 -64
- package/src/engine/network/replication/ScopeFilter.js +71 -71
- package/src/engine/network/sim/ActionLog.js +152 -152
- package/src/engine/network/sim/SimAction.js +273 -273
- package/src/engine/network/sim/Snapshotter.js +222 -222
- package/src/engine/network/state/FrameBufferRing.js +168 -168
- package/src/engine/network/state/InputRing.js +134 -134
- package/src/engine/network/state/PriorityAccumulator.js +180 -180
- package/src/engine/network/time/TimeDilation.js +62 -62
- package/src/engine/network/transport/Channel.js +336 -336
- package/src/engine/network/transport/Transport.js +203 -203
- package/src/engine/network/transport/adapters/NodeUDPTransport.js +186 -186
- package/src/engine/network/transport/adapters/WebRTCDataChannelTransport.js +105 -105
- package/src/engine/network/transport/fragments/fragment_send.js +147 -147
- package/src/engine/network/transport/fragments/packet_size.js +101 -101
- package/src/engine/network/xhr.js +23 -23
- package/src/engine/physics/BULLET_REVIEW.md +945 -945
- package/src/engine/physics/CANNON_REVIEW.md +1300 -1300
- package/src/engine/physics/CONSTRAINT_SOLVER_BENCH_LOG.md +208 -208
- package/src/engine/physics/CONSTRAINT_SOLVER_IMPROVEMENTS_PLAN.md +364 -364
- package/src/engine/physics/INTEPOLATION_SYSTEM_PLAN.md +287 -287
- package/src/engine/physics/JOLT_REVIEW.md +913 -913
- package/src/engine/physics/PLAN.md +1120 -1094
- package/src/engine/physics/RAPIER_REVIEW.md +934 -934
- package/src/engine/physics/REVIEW_001_ACTION_PLAN.md +642 -642
- package/src/engine/physics/REVIEW_002.md +151 -151
- package/src/engine/physics/REVIEW_003.md +166 -166
- package/src/engine/physics/broadphase/compute_fat_world_aabb.js +97 -97
- package/src/engine/physics/constraint/DofMode.js +35 -35
- package/src/engine/physics/constraint/solve_constraints.js +1 -1
- package/src/engine/physics/contact/combine_material.js +35 -35
- package/src/engine/physics/ecs/BodyKind.js +29 -29
- package/src/engine/physics/ecs/Collider.js +177 -177
- package/src/engine/physics/ecs/ColliderFlags.js +15 -15
- package/src/engine/physics/ecs/ColliderSerializationAdapter.js +37 -37
- package/src/engine/physics/ecs/Joint.js +475 -475
- package/src/engine/physics/ecs/JointSerializationAdapter.js +72 -72
- package/src/engine/physics/ecs/PhysicsSystem.d.ts +3 -0
- package/src/engine/physics/ecs/PhysicsSystem.d.ts.map +1 -1
- package/src/engine/physics/ecs/PhysicsSystem.js +7 -2
- package/src/engine/physics/ecs/RigidBodySerializationUpgrader_0_1.js +37 -37
- package/src/engine/physics/ecs/SleepState.js +12 -12
- package/src/engine/physics/ecs/find_non_finite_physics_state.js +76 -76
- package/src/engine/physics/fluid/FluidField.js +867 -867
- package/src/engine/physics/fluid/FluidSimulator.js +832 -832
- package/src/engine/physics/fluid/REVIEW_02_PLAN.md +243 -243
- package/src/engine/physics/fluid/SliceVisualiser.js +190 -190
- package/src/engine/physics/fluid/ecs/FluidComponent.js +239 -239
- package/src/engine/physics/fluid/ecs/FluidEffectorsComponent.js +89 -89
- package/src/engine/physics/fluid/ecs/FluidObstacle.js +97 -97
- package/src/engine/physics/fluid/ecs/FluidObstacleSystem.js +348 -348
- package/src/engine/physics/fluid/ecs/FluidSystem.js +142 -142
- package/src/engine/physics/fluid/ecs/FluidWorkerCore.js +123 -123
- package/src/engine/physics/fluid/ecs/WorkerFluidSystem.js +395 -395
- package/src/engine/physics/fluid/ecs/fluid.worker.js +12 -12
- package/src/engine/physics/fluid/ecs/fluid_build_world_to_grid.js +32 -32
- package/src/engine/physics/fluid/ecs/fluid_gather_visible_effectors.js +47 -47
- package/src/engine/physics/fluid/ecs/fluid_reanchor_field.js +85 -85
- package/src/engine/physics/fluid/ecs/fluid_sync_effectors_from_transform.js +16 -16
- package/src/engine/physics/fluid/ecs/fluid_worker_protocol.js +111 -111
- package/src/engine/physics/fluid/ecs/makeFluidWorker.js +18 -18
- package/src/engine/physics/fluid/effector/AbstractFluidEffector.js +81 -81
- package/src/engine/physics/fluid/effector/GlobalFluidEffector.js +202 -202
- package/src/engine/physics/fluid/effector/ImpulseFluidEffector.js +257 -257
- package/src/engine/physics/fluid/effector/WakeFluidEffector.js +405 -405
- package/src/engine/physics/fluid/prototype.js +126 -126
- package/src/engine/physics/fluid/solver/v3_grid_advect_maccormack_scalar.js +143 -143
- package/src/engine/physics/fluid/solver/v3_grid_advect_maccormack_velocity.js +162 -162
- package/src/engine/physics/fluid/solver/v3_grid_advect_sl_velocity.js +107 -107
- package/src/engine/physics/fluid/solver/v3_grid_apply_vorticity_confinement.js +131 -131
- package/src/engine/physics/fluid/solver/v3_grid_compute_solid_neighbour_mask.js +90 -90
- package/src/engine/physics/fluid/solver/v3_grid_limit8_at_indices.js +38 -38
- package/src/engine/physics/fluid/solver/v3_grid_patch_edges_constant.js +77 -77
- package/src/engine/physics/fluid/solver/v3_grid_patch_edges_uniform.js +113 -113
- package/src/engine/physics/fluid/solver/v3_grid_sample_scalar_masked.js +92 -92
- package/src/engine/physics/fluid/solver/v3_grid_shift_in_place.js +107 -107
- package/src/engine/physics/fluid/solver/v3_grid_solve_pressure.js +135 -135
- package/src/engine/physics/fluid/solver/v3_grid_solve_pressure_pcg.js +511 -511
- package/src/engine/physics/fluid/solver/v3_grid_subtract_pressure_gradient.js +108 -108
- package/src/engine/physics/fluid/solver/v3_mac_advect_maccormack_velocity.js +317 -317
- package/src/engine/physics/fluid/solver/v3_mac_advect_scalar.js +236 -236
- package/src/engine/physics/fluid/solver/v3_mac_advect_sl_velocity.js +217 -217
- package/src/engine/physics/fluid/solver/v3_mac_apply_vorticity_confinement.js +174 -174
- package/src/engine/physics/fluid/solver/v3_mac_clip_trace.js +95 -95
- package/src/engine/physics/fluid/solver/v3_mac_compute_divergence.js +77 -77
- package/src/engine/physics/fluid/solver/v3_mac_compute_face_solid.js +131 -131
- package/src/engine/physics/fluid/solver/v3_mac_subtract_pressure_gradient.js +104 -104
- package/src/engine/physics/inertia/world_inverse_inertia.js +160 -160
- package/src/engine/physics/integration/integrate_position.js +97 -97
- package/src/engine/physics/narrowphase/box_box_manifold.js +681 -681
- package/src/engine/physics/narrowphase/box_triangle_contact.js +906 -906
- package/src/engine/physics/narrowphase/capsule_triangle_contact.js +395 -395
- package/src/engine/physics/narrowphase/compute_penetration.js +369 -369
- package/src/engine/physics/narrowphase/decomposition/decompose_to_triangles.js +55 -55
- package/src/engine/physics/narrowphase/decomposition/triangle_buffer_layout.js +37 -37
- package/src/engine/physics/narrowphase/mesh_mesh_tet_manifold.js +117 -117
- package/src/engine/physics/narrowphase/narrowphase_step.js +1738 -1738
- package/src/engine/physics/narrowphase/reduce_manifold_contacts.js +74 -74
- package/src/engine/physics/narrowphase/refine_ray_hit.d.ts +12 -11
- package/src/engine/physics/narrowphase/refine_ray_hit.d.ts.map +1 -1
- package/src/engine/physics/narrowphase/refine_ray_hit.js +228 -36
- package/src/engine/physics/narrowphase/sphere_sphere_contact.js +52 -52
- package/src/engine/physics/narrowphase/sphere_triangle_contact.js +138 -138
- package/src/engine/physics/persistence/solver_caches.js +309 -309
- package/src/engine/physics/queries/overlap_shape.js +187 -187
- package/src/engine/physics/queries/raycast.d.ts +7 -4
- package/src/engine/physics/queries/raycast.d.ts.map +1 -1
- package/src/engine/physics/queries/raycast.js +11 -5
- package/src/engine/physics/queries/shape_cast.js +417 -417
- package/src/engine/physics/solver/apply_impulse_to_body.js +86 -86
- package/src/engine/physics/solver/friction_cone.js +37 -37
- package/src/engine/physics/solver/solve_contacts.js +1 -1
- package/src/engine/platform/WebEnginePlatform.d.ts.map +1 -1
- package/src/engine/platform/WebEnginePlatform.js +1 -2
- package/src/engine/scene/SerializedScene.d.ts +2 -2
- package/src/engine/scene/SerializedScene.d.ts.map +1 -1
- package/src/engine/scene/transitionToScene.js +121 -121
- package/src/engine/simulation/Ticker.d.ts.map +1 -1
- package/src/engine/simulation/Ticker.js +33 -0
- package/src/engine/sound/ecs/audio/LiveEmitterSet.js +345 -345
- package/src/engine/sound/ecs/audio/SpatialAudioIndex.js +140 -140
- package/src/engine/sound/simulation/AcousticSimulator.js +646 -646
- package/src/engine/sound/simulation/IMPLEMENTATION_PLAN.md +432 -432
- package/src/engine/sound/simulation/PROBE_PLACEMENT.md +250 -250
- package/src/engine/sound/simulation/PROBE_TRANSFER_RESEARCH.md +322 -322
- package/src/engine/sound/simulation/README.md +277 -277
- package/src/engine/sound/simulation/REFLECTION_IR_PLAN.md +261 -261
- package/src/engine/sound/simulation/VISIBILITY_GRAPH_PLAN.md +121 -121
- package/src/engine/sound/simulation/configureAcousticSimulation.js +48 -48
- package/src/engine/sound/simulation/core/AcousticMedium.js +113 -113
- package/src/engine/sound/simulation/core/AcousticMediumSerializationAdapter.js +27 -27
- package/src/engine/sound/simulation/core/AcousticOccluderIndex.js +312 -312
- package/src/engine/sound/simulation/core/AcousticRegionIndex.js +159 -159
- package/src/engine/sound/simulation/core/AcousticSolution.js +90 -90
- package/src/engine/sound/simulation/core/AcousticSourceState.js +109 -109
- package/src/engine/sound/simulation/core/AcousticVolumeRegion.js +73 -73
- package/src/engine/sound/simulation/core/OcclusionSolver.js +112 -112
- package/src/engine/sound/simulation/core/RayHit.js +50 -50
- package/src/engine/sound/simulation/core/VolumeField.js +149 -149
- package/src/engine/sound/simulation/definition/AcousticDirectivity.js +114 -114
- package/src/engine/sound/simulation/definition/AcousticDirectivitySerializationAdapter.js +30 -30
- package/src/engine/sound/simulation/definition/AcousticMaterial.js +122 -122
- package/src/engine/sound/simulation/definition/AcousticMaterialSerializationAdapter.js +32 -32
- package/src/engine/sound/simulation/ecs/AcousticBody.js +48 -48
- package/src/engine/sound/simulation/ecs/AcousticBodySerializationAdapter.js +34 -34
- package/src/engine/sound/simulation/ecs/AcousticProbeFieldSystem.js +64 -64
- package/src/engine/sound/simulation/ecs/AcousticSimulationSystem.js +75 -75
- package/src/engine/sound/simulation/ecs/AcousticVolume.js +74 -74
- package/src/engine/sound/simulation/ecs/AcousticVolumeSerializationAdapter.js +37 -37
- package/src/engine/sound/simulation/ecs/AcousticVolumeSystem.js +66 -66
- package/src/engine/sound/simulation/probe/AcousticProbeField.js +578 -578
- package/src/engine/sound/simulation/probe/AcousticProbeFieldSerializationAdapter.js +60 -60
- package/src/engine/sound/simulation/probe/ProbeFieldPathingSolution.js +64 -64
- package/src/engine/sound/simulation/probe/ProbeVisibilityRecheck.js +132 -132
- package/src/engine/sound/simulation/probe/acoustic_probe_transfer.js +103 -103
- package/src/engine/sound/simulation/probe/acoustic_trace_energy.js +136 -136
- package/src/engine/sound/simulation/probe/bakeProbeField.js +169 -169
- package/src/engine/sound/simulation/probe/bakeProbeReflectors.js +110 -110
- package/src/engine/sound/simulation/probe/bakeReverbBands.js +110 -110
- package/src/engine/sound/simulation/probe/bakeVisibility.js +148 -148
- package/src/engine/sound/simulation/probe/echogram_band_rt60.js +83 -83
- package/src/engine/sound/simulation/probe/probe_delaunay_edges.js +215 -215
- package/src/engine/sound/simulation/probe/probe_densify_portals.js +140 -140
- package/src/engine/sound/simulation/probe/probe_detect_portals.js +196 -196
- package/src/engine/sound/simulation/probe/probe_edge_key.js +38 -38
- package/src/engine/sound/simulation/probe/probe_edge_visible.js +41 -41
- package/src/engine/sound/simulation/probe/probe_place_sdf_cover.js +82 -82
- package/src/engine/sound/simulation/probe/probe_refine_connectivity.js +181 -181
- package/src/engine/sound/simulation/probe/probe_sdf_grid.js +171 -171
- package/src/engine/sound/simulation/probe/probe_visibility_augment.js +146 -146
- package/src/engine/sound/simulation/probe/probe_visibility_bridge_components.js +167 -167
- package/src/engine/sound/simulation/render/FoaProbeReverbRenderer.js +354 -354
- package/src/engine/sound/simulation/render/ProbeReverbRenderer.js +231 -231
- package/src/engine/sound/simulation/render/foaReverbImpulseResponse.js +105 -105
- package/src/engine/sound/simulation/render/reverbImpulseResponse.js +66 -66
- package/src/engine/sound/simulation/render/reverb_band_tails.js +93 -93
- package/src/engine/sound/simulation/serialization/populateAcousticSerializationRegistry.js +25 -25
- package/src/engine/sound/sopra/README.md +643 -643
- package/src/engine/sound/sopra/SopraEngine.js +432 -432
- package/src/engine/sound/sopra/asset/AssetManagerBufferProvider.js +71 -71
- package/src/engine/sound/sopra/asset/BufferProvider.js +29 -29
- package/src/engine/sound/sopra/asset/StubBufferProvider.js +58 -58
- package/src/engine/sound/sopra/definition/BusDefinition.js +142 -142
- package/src/engine/sound/sopra/definition/BusDefinitionSerializationAdapter.js +54 -54
- package/src/engine/sound/sopra/definition/DuckingRule.js +106 -106
- package/src/engine/sound/sopra/definition/DuckingRuleSerializationAdapter.js +31 -31
- package/src/engine/sound/sopra/definition/EventDescription.js +262 -262
- package/src/engine/sound/sopra/definition/EventDescriptionSerializationAdapter.js +55 -55
- package/src/engine/sound/sopra/definition/MixerSnapshot.js +83 -83
- package/src/engine/sound/sopra/definition/MixerSnapshotSerializationAdapter.js +39 -39
- package/src/engine/sound/sopra/definition/ParameterDefinition.js +117 -117
- package/src/engine/sound/sopra/definition/ParameterDefinitionSerializationAdapter.js +31 -31
- package/src/engine/sound/sopra/definition/VoiceStealMode.js +18 -18
- package/src/engine/sound/sopra/definition/clip/AbstractAudioClip.js +109 -109
- package/src/engine/sound/sopra/definition/clip/BlendContainerAudioClip.js +185 -185
- package/src/engine/sound/sopra/definition/clip/BlendContainerAudioClipSerializationAdapter.js +59 -59
- package/src/engine/sound/sopra/definition/clip/ContainerAudioClip.js +128 -128
- package/src/engine/sound/sopra/definition/clip/RandomContainerAudioClip.js +226 -226
- package/src/engine/sound/sopra/definition/clip/RandomContainerAudioClipSerializationAdapter.js +54 -54
- package/src/engine/sound/sopra/definition/clip/SampleAudioClip.js +191 -191
- package/src/engine/sound/sopra/definition/clip/SampleAudioClipSerializationAdapter.js +39 -39
- package/src/engine/sound/sopra/definition/clip/SequenceContainerAudioClip.js +79 -79
- package/src/engine/sound/sopra/definition/clip/SequenceContainerAudioClipSerializationAdapter.js +42 -42
- package/src/engine/sound/sopra/definition/clip/SilenceAudioClip.js +77 -77
- package/src/engine/sound/sopra/definition/clip/SilenceAudioClipSerializationAdapter.js +27 -27
- package/src/engine/sound/sopra/definition/clip/SwitchContainerAudioClip.d.ts +5 -0
- package/src/engine/sound/sopra/definition/clip/SwitchContainerAudioClip.d.ts.map +1 -1
- package/src/engine/sound/sopra/definition/clip/SwitchContainerAudioClip.js +125 -125
- package/src/engine/sound/sopra/definition/clip/SwitchContainerAudioClipSerializationAdapter.js +41 -41
- package/src/engine/sound/sopra/definition/effect/AbstractAudioEffect.js +24 -24
- package/src/engine/sound/sopra/definition/effect/CompressorEffect.js +120 -120
- package/src/engine/sound/sopra/definition/effect/CompressorEffectSerializationAdapter.js +31 -31
- package/src/engine/sound/sopra/definition/effect/EqEffect.js +128 -128
- package/src/engine/sound/sopra/definition/effect/EqEffectSerializationAdapter.js +29 -29
- package/src/engine/sound/sopra/definition/effect/ReverbEffect.js +113 -113
- package/src/engine/sound/sopra/definition/effect/ReverbEffectSerializationAdapter.js +25 -25
- package/src/engine/sound/sopra/runtime/BusGraph.js +231 -231
- package/src/engine/sound/sopra/runtime/ParameterStore.js +98 -98
- package/src/engine/sound/sopra/runtime/SopraPlaybackContext.js +68 -68
- package/src/engine/sound/sopra/runtime/Voice.js +145 -145
- package/src/engine/sound/sopra/runtime/VoiceManager.js +136 -136
- package/src/engine/sound/sopra/runtime/VoicePool.js +17 -17
- package/src/engine/sound/sopra/serialization/populateSopraSerializationRegistry.js +42 -42
- package/src/engine/sound/sopra/serialization/sopraJSON.js +99 -99
- package/src/engine/sound/sopra/serialization/sopraSerializationHarness.js +49 -49
- package/src/engine/sound/sopra/util/MockAudioContext.js +224 -224
- package/src/engine/sound/sopra/util/fadeOutAndStop.js +60 -60
- package/src/engine/ui/DraggableAspect.js +115 -115
- package/src/engine/ui/apply_pointer_tilt_3d.js +50 -50
- package/src/generation/COORDINATES.md +54 -54
- package/src/generation/GridGenerationResource.js +24 -24
- package/src/generation/GridTaskGroup.js +214 -214
- package/src/generation/REVIEW_01_ACTION_PLAN.md +628 -628
- package/src/generation/automata/CaveGeneratorCellularAutomata.js +79 -79
- package/src/generation/automata/CellularAutomata.js +22 -22
- package/src/generation/compute_writes_union.js +25 -25
- package/src/generation/filtering/CellFilter.js +117 -117
- package/src/generation/filtering/core/CellFilterBinaryOperation.js +37 -37
- package/src/generation/filtering/core/CellFilterOperationTertiary.js +43 -43
- package/src/generation/filtering/core/CellFilterUnaryOperation.js +29 -29
- package/src/generation/filtering/numeric/process/computeCellFilterSobel.js +36 -36
- package/src/generation/filtering/numeric/process/computeFilterSurfaceNormal.js +42 -42
- package/src/generation/filtering/numeric/util/populateSampler2DFromCellFilter.js +18 -18
- package/src/generation/filtering/numeric/util/write_cell_filter_to_sampler2d.js +47 -47
- package/src/generation/filtering/util/cell_filter_weights_pick.js +54 -54
- package/src/generation/grid/GridData.js +233 -233
- package/src/generation/grid/coords/grid_to_texel.js +10 -10
- package/src/generation/grid/coords/texel_to_grid.js +10 -10
- package/src/generation/grid/generation/GridTaskApplyActionToCells.js +90 -90
- package/src/generation/grid/generation/GridTaskDensityMarkerDistribution.js +388 -388
- package/src/generation/grid/generation/GridTaskExecuteRuleTimes.js +127 -127
- package/src/generation/grid/generation/GridTaskSequence.js +58 -58
- package/src/generation/grid/generation/NoopGridTaskGenerator.js +12 -12
- package/src/generation/grid/generation/discrete/GridTaskActionRuleSet.js +48 -48
- package/src/generation/grid/generation/discrete/GridTaskCellularAutomata.js +146 -146
- package/src/generation/grid/generation/discrete/GridTaskConnectRooms.js +540 -540
- package/src/generation/grid/generation/discrete/layer/GridTaskBuildSourceDistanceMap.js +222 -222
- package/src/generation/grid/generation/grid/GridTaskAddNodesFixed.js +44 -44
- package/src/generation/grid/generation/road/GridTaskGenerateRoads.js +614 -614
- package/src/generation/markers/GridCellActionPlaceMarker.js +244 -244
- package/src/generation/markers/GridCellActionPlaceMarkerGroup.js +86 -86
- package/src/generation/markers/actions/terrain/MarkerNodeActionPaintTerrain.js +266 -266
- package/src/generation/markers/emitter/MarkerNodeEmitterGridCellAction.js +49 -49
- package/src/generation/placement/GridCellActionTransformNearbyMarkers.js +120 -120
- package/src/generation/placement/action/GridCellAction.js +45 -45
- package/src/generation/placement/action/GridCellActionPlaceTags.js +140 -140
- package/src/generation/placement/action/GridCellActionWriteFilterToLayer.js +89 -89
- package/src/generation/placement/action/random/CellActionSelectRandom.js +69 -69
- package/src/generation/placement/action/random/weighted/CellActionSelectWeightedRandom.js +91 -91
- package/src/generation/placement/action/util/GridCellActionSequence.js +61 -61
- package/src/generation/placement/action/util/GridCellDisplacedAction.js +61 -61
- package/src/generation/rules/cell/CellMatcherRemovalPreservesConnectivity.js +183 -183
- package/src/generation/theme/ThemeEngine.js +666 -666
- package/src/shade/RENDERER_CONTRACT.md +568 -0
- package/src/shade/descriptor/texture/format/gpu_texture_format_info.js +267 -267
- package/src/shade/device/ShadeDeviceFailure.js +104 -104
- package/src/shade/device/ShadeDeviceFailureReason.js +41 -41
- package/src/shade/device/mock/IMPLEMENTATION_PLAN.md +255 -255
- package/src/shade/device/mock/REVIEW_2026_08_22.md +527 -527
- package/src/shade/device/mock/SoftwareGPUBindGroup.js +23 -23
- package/src/shade/device/mock/SoftwareGPUBindGroupLayout.js +22 -22
- package/src/shade/device/mock/SoftwareGPUError.js +63 -63
- package/src/shade/device/mock/SoftwareGPUPipeline.js +75 -75
- package/src/shade/device/mock/SoftwareGPUQuerySet.js +113 -113
- package/src/shade/device/mock/SoftwareGPUSampler.js +30 -30
- package/src/shade/device/mock/SoftwareGPUShaderModule.js +32 -32
- package/src/shade/device/mock/SoftwareGPUTextureView.js +22 -22
- package/src/shade/device/mock/SoftwareGPUUncapturedErrorEvent.js +23 -23
- package/src/shade/device/mock/normalize_dynamic_offsets.js +26 -26
- package/src/shade/device/mock/texture_copy.js +287 -287
- package/src/shade/device/timing/GPUTimer.js +285 -285
- package/src/shade/device/timing/GPUTimerArray.js +205 -205
- package/src/shade/playground/add_random_lights.js +49 -49
- package/src/shade/playground/add_random_meshes.js +83 -83
- package/src/shade/playground/animation_prototype.js +508 -508
- package/src/shade/playground/animation_prototype_skinning.js +647 -647
- package/src/shade/playground/animation_prototype_stress.js +765 -765
- package/src/shade/playground/enable_picker.js +59 -59
- package/src/shade/playground/gltf_skinned_prototype.js +560 -560
- package/src/shade/playground/gltf_skinned_stress_prototype.js +387 -387
- package/src/shade/playground/gltf_viewer.js +282 -282
- package/src/shade/playground/load_default_environment.js +19 -19
- package/src/shade/playground/main.js +2727 -2727
- package/src/shade/playground/make_animated_instances.js +117 -117
- package/src/shade/playground/prototype_virtual_texture.js +274 -274
- package/src/shade/playground/prototype_volumetrics_01.js +206 -206
- package/src/shade/playground/sample.js +234 -234
- package/src/shade/playground/sample_load_gltf.js +102 -102
- package/src/shade/playground/spread_node3d_grid.js +19 -19
- package/src/shade/renderer/Renderer.js +2436 -2436
- package/src/shade/renderer/ShadeIndirectLightingMode.js +14 -14
- package/src/shade/renderer/animation/ShadeAnimationChannel.js +414 -414
- package/src/shade/renderer/animation/compute_skinned_mesh_clip_bounding_sphere.js +531 -531
- package/src/shade/renderer/animation/pose/PosePlayback.js +55 -55
- package/src/shade/renderer/animation/pose/pose_evaluate_local.js +179 -179
- package/src/shade/renderer/animation/pose/pose_evaluate_world.js +76 -76
- package/src/shade/renderer/animation/skinning/GPUMeshSkinningContext.js +1024 -1024
- package/src/shade/renderer/animation/skinning/SKINNING_BINDING_STRUCT.js +79 -79
- package/src/shade/renderer/animation/skinning/chunk_skin_blend_mesh_local.js +56 -56
- package/src/shade/renderer/binding/BindGroupManager.js +282 -282
- package/src/shade/renderer/binding/adapt_cache_capacity.js +25 -25
- package/src/shade/renderer/buffer/GPUBufferAllocator.js +116 -116
- package/src/shade/renderer/buffer/buffer_pool_ops.js +123 -123
- package/src/shade/renderer/buffer/table/GPU_DATABASE_PAGE_SIZE.js +9 -9
- package/src/shade/renderer/buffer/table/GPU_DATABASE_UPLOAD_BUFFER_LIMIT.js +8 -8
- package/src/shade/renderer/buffer/table/PageBufferPool.js +71 -71
- package/src/shade/renderer/buffer/table/PageSlotAllocator.js +95 -95
- package/src/shade/renderer/camera/Camera.js +281 -281
- package/src/shade/renderer/camera/GPUCameraContext.js +233 -233
- package/src/shade/renderer/camera/orbital/README.md +84 -84
- package/src/shade/renderer/deferred/shader_composite_volumetrics.js +75 -75
- package/src/shade/renderer/dynamic/DYNAMIC_MESH_VERTEX.js +25 -25
- package/src/shade/renderer/dynamic/GPUDynamicMeshRenderer.js +342 -342
- package/src/shade/renderer/dynamic/GPUDynamicMeshResidency.js +167 -167
- package/src/shade/renderer/dynamic/shader_dynamic_mesh.js +170 -170
- package/src/shade/renderer/extension/FrameContext.js +263 -263
- package/src/shade/renderer/extension/FramePhase.js +104 -104
- package/src/shade/renderer/extension/FrameRecord.js +126 -126
- package/src/shade/renderer/extension/GBufferTextures.js +83 -83
- package/src/shade/renderer/extension/PresentTarget.js +34 -34
- package/src/shade/renderer/extension/RENDER_EXTENSION_DESIGN.md +918 -918
- package/src/shade/renderer/extension/RenderExtension.js +69 -69
- package/src/shade/renderer/extension/RenderExtensionRegistry.js +319 -319
- package/src/shade/renderer/extension/SceneColor.js +40 -40
- package/src/shade/renderer/extension/ViewTextures.js +78 -78
- package/src/shade/renderer/fow/GPUFogOfWarRenderer.js +182 -182
- package/src/shade/renderer/geometry/Geometry.js +608 -608
- package/src/shade/renderer/geometry/geometry_build_from_meshlet_geometry.js +342 -342
- package/src/shade/renderer/geometry/meshlet_geometry_transfer.js +41 -41
- package/src/shade/renderer/geometry/primitives/make_box_geometry.js +153 -153
- package/src/shade/renderer/geometry/primitives/make_cylinder_geometry.js +240 -240
- package/src/shade/renderer/geometry/primitives/make_plane_geometry.js +145 -145
- package/src/shade/renderer/geometry/primitives/make_polyhedron_geometry.js +327 -327
- package/src/shade/renderer/geometry/primitives/make_torus_geometry.js +107 -107
- package/src/shade/renderer/geometry/primitives/make_torus_knot_geometry.js +165 -165
- package/src/shade/renderer/geometry/virtual/VGEO_FORMAT.md +607 -607
- package/src/shade/renderer/geometry/virtual/build/VGeoBuildOptions.js +66 -66
- package/src/shade/renderer/geometry/virtual/build/VGeoBuildResult.js +32 -32
- package/src/shade/renderer/geometry/virtual/build/VGeoFaceGraph.js +103 -103
- package/src/shade/renderer/geometry/virtual/build/VGeoGatheredFaces.js +25 -25
- package/src/shade/renderer/geometry/virtual/build/VGeoLevel.js +33 -33
- package/src/shade/renderer/geometry/virtual/build/VGeoLevelOptions.js +62 -62
- package/src/shade/renderer/geometry/virtual/build/VGeoLevelSummary.js +49 -49
- package/src/shade/renderer/geometry/virtual/build/VGeoSubmesh.js +20 -20
- package/src/shade/renderer/geometry/virtual/build/VirtualGeometryMesh.js +214 -214
- package/src/shade/renderer/geometry/virtual/build/gltf/GltfByteSource.js +122 -122
- package/src/shade/renderer/geometry/virtual/build/gltf/GltfCollectReport.js +29 -29
- package/src/shade/renderer/geometry/virtual/build/gltf/GltfDocument.js +210 -210
- package/src/shade/renderer/geometry/virtual/build/gltf/GltfGeometry.js +48 -48
- package/src/shade/renderer/geometry/virtual/build/gltf/GltfGeometryInstance.js +25 -25
- package/src/shade/renderer/geometry/virtual/build/gltf/GltfGeometrySelection.js +28 -28
- package/src/shade/renderer/geometry/virtual/build/gltf/gltf_collect_geometries.js +296 -296
- package/src/shade/renderer/geometry/virtual/build/gltf/gltf_open_document.js +166 -166
- package/src/shade/renderer/geometry/virtual/build/gltf/gltf_read_geometry.js +199 -199
- package/src/shade/renderer/geometry/virtual/build/vgeo_assemble_pages.js +173 -173
- package/src/shade/renderer/geometry/virtual/build/vgeo_bounding_sphere.js +28 -28
- package/src/shade/renderer/geometry/virtual/build/vgeo_build.js +200 -200
- package/src/shade/renderer/geometry/virtual/build/vgeo_build_levels.js +1786 -1786
- package/src/shade/renderer/geometry/virtual/build/vgeo_build_page_nodes.js +157 -157
- package/src/shade/renderer/geometry/virtual/build/vgeo_build_root_group.js +26 -26
- package/src/shade/renderer/geometry/virtual/build/vgeo_compute_vertex_normals.js +75 -75
- package/src/shade/renderer/geometry/virtual/build/vgeo_compute_vertex_tangents.js +143 -143
- package/src/shade/renderer/geometry/virtual/build/vgeo_containing_sphere.js +44 -44
- package/src/shade/renderer/geometry/virtual/build/vgeo_extract_submesh.js +41 -41
- package/src/shade/renderer/geometry/virtual/build/vgeo_gather_faces.js +71 -71
- package/src/shade/renderer/geometry/virtual/build/vgeo_graph_components.js +123 -123
- package/src/shade/renderer/geometry/virtual/build/vgeo_narrow_indices.js +26 -26
- package/src/shade/renderer/geometry/virtual/build/vgeo_normalize_source.js +112 -112
- package/src/shade/renderer/geometry/virtual/build/vgeo_partition_graph.js +56 -56
- package/src/shade/renderer/geometry/virtual/build/vgeo_sphere_inflate_to_contain.js +70 -70
- package/src/shade/renderer/geometry/virtual/build/vgeo_split_face_set.js +187 -187
- package/src/shade/renderer/geometry/virtual/build/vgeo_split_face_set_packed.js +207 -207
- package/src/shade/renderer/geometry/virtual/format/VGEO_DEFECT.js +18 -18
- package/src/shade/renderer/geometry/virtual/format/VGeoDefect.js +38 -38
- package/src/shade/renderer/geometry/virtual/format/VGeoWriteOptions.js +64 -64
- package/src/shade/renderer/geometry/virtual/format/attribute/VGEO_ATTRIBUTE.js +13 -13
- package/src/shade/renderer/geometry/virtual/format/attribute/VGEO_ATTRIBUTES.js +50 -50
- package/src/shade/renderer/geometry/virtual/format/attribute/vgeo_attribute_allocate.js +28 -28
- package/src/shade/renderer/geometry/virtual/format/attribute/vgeo_attribute_mask.js +23 -23
- package/src/shade/renderer/geometry/virtual/format/attribute/vgeo_attributes_read.js +31 -31
- package/src/shade/renderer/geometry/virtual/format/attribute/vgeo_layers_copy_vertex.js +32 -32
- package/src/shade/renderer/geometry/virtual/format/attribute/vgeo_layers_declare.js +46 -46
- package/src/shade/renderer/geometry/virtual/format/attribute/vgeo_layers_like.js +19 -19
- package/src/shade/renderer/geometry/virtual/format/attribute/vgeo_layers_test_fixtures.js +27 -27
- package/src/shade/renderer/geometry/virtual/format/checksum/vgeo_asset_id_preimage.js +62 -62
- package/src/shade/renderer/geometry/virtual/format/checksum/vgeo_compute_asset_id.js +13 -13
- package/src/shade/renderer/geometry/virtual/format/checksum/vgeo_compute_directory_block_checksum.js +41 -41
- package/src/shade/renderer/geometry/virtual/format/checksum/vgeo_compute_frame_checksum.js +41 -41
- package/src/shade/renderer/geometry/virtual/format/checksum/vgeo_compute_header_checksum.js +25 -25
- package/src/shade/renderer/geometry/virtual/format/cluster/VGEO_CLUSTER_OFFSET.js +20 -20
- package/src/shade/renderer/geometry/virtual/format/cluster/VGEO_CLUSTER_RECORD_SIZE.js +5 -5
- package/src/shade/renderer/geometry/virtual/format/cluster/VGEO_MAX_CHILD_REF.js +5 -5
- package/src/shade/renderer/geometry/virtual/format/cluster/VGEO_MAX_CLUSTER_TRIANGLE_COUNT.js +5 -5
- package/src/shade/renderer/geometry/virtual/format/cluster/VGEO_MAX_CLUSTER_VERTEX_COUNT.js +5 -5
- package/src/shade/renderer/geometry/virtual/format/cluster/VGEO_NO_CHILD.js +5 -5
- package/src/shade/renderer/geometry/virtual/format/cluster/VGEO_NO_GROUP.js +5 -5
- package/src/shade/renderer/geometry/virtual/format/cluster/VGEO_SAME_PAGE.js +6 -6
- package/src/shade/renderer/geometry/virtual/format/dag/VirtualGeometryCluster.js +81 -81
- package/src/shade/renderer/geometry/virtual/format/dag/VirtualGeometryGroup.js +37 -37
- package/src/shade/renderer/geometry/virtual/format/dag/VirtualGeometryPageNode.js +55 -55
- package/src/shade/renderer/geometry/virtual/format/directory/VGEO_DIRECTORY_BLOCK_RECORDS.js +5 -5
- package/src/shade/renderer/geometry/virtual/format/directory/VGEO_DIRECTORY_FLAG_ROOT_PAGE.js +5 -5
- package/src/shade/renderer/geometry/virtual/format/directory/VGEO_DIRECTORY_RECORD_SIZE.js +5 -5
- package/src/shade/renderer/geometry/virtual/format/directory/vgeo_directory_block_count.js +12 -12
- package/src/shade/renderer/geometry/virtual/format/frame/VGEO_CHILD_OFFSET.js +11 -11
- package/src/shade/renderer/geometry/virtual/format/frame/VGEO_CHILD_TABLE_ENTRY_SIZE.js +5 -5
- package/src/shade/renderer/geometry/virtual/format/frame/VGEO_CODEC_LZ4.js +11 -11
- package/src/shade/renderer/geometry/virtual/format/frame/VGEO_CODEC_RAW.js +5 -5
- package/src/shade/renderer/geometry/virtual/format/frame/VGEO_FRAME_ALIGNMENT.js +5 -5
- package/src/shade/renderer/geometry/virtual/format/frame/VGEO_FRAME_HEADER_SIZE.js +5 -5
- package/src/shade/renderer/geometry/virtual/format/frame/VGEO_FRAME_OFFSET.js +13 -13
- package/src/shade/renderer/geometry/virtual/format/frame/VGEO_PAGE_MAGIC.js +5 -5
- package/src/shade/renderer/geometry/virtual/format/frame/VGeoFrameBlob.js +21 -21
- package/src/shade/renderer/geometry/virtual/format/frame/VGeoFrameExtent.js +59 -59
- package/src/shade/renderer/geometry/virtual/format/frame/vgeo_codec_name.js +27 -27
- package/src/shade/renderer/geometry/virtual/format/frame/vgeo_decode_frame_blob.js +84 -84
- package/src/shade/renderer/geometry/virtual/format/frame/vgeo_encode_frame_blob.js +56 -56
- package/src/shade/renderer/geometry/virtual/format/frame/vgeo_frame_decode_bound.js +35 -35
- package/src/shade/renderer/geometry/virtual/format/frame/vgeo_scan_frames.js +74 -74
- package/src/shade/renderer/geometry/virtual/format/header/VGEO_FORMAT_VERSION.js +6 -6
- package/src/shade/renderer/geometry/virtual/format/header/VGEO_HASH_ALGO_XXH3.js +5 -5
- package/src/shade/renderer/geometry/virtual/format/header/VGEO_HEADER_OFFSET.js +31 -31
- package/src/shade/renderer/geometry/virtual/format/header/VGEO_HEADER_SIZE.js +5 -5
- package/src/shade/renderer/geometry/virtual/format/header/VGEO_LEVEL_OFFSET.js +20 -20
- package/src/shade/renderer/geometry/virtual/format/header/VGEO_LEVEL_RECORD_SIZE.js +9 -9
- package/src/shade/renderer/geometry/virtual/format/header/VGEO_MAGIC.js +5 -5
- package/src/shade/renderer/geometry/virtual/format/header/VGEO_MIN_READER_VERSION.js +5 -5
- package/src/shade/renderer/geometry/virtual/format/header/VGEO_PAYLOAD_ENCODING_SHADE_MESHLET_V1.js +5 -5
- package/src/shade/renderer/geometry/virtual/format/header/VGeoLevelRecord.js +41 -41
- package/src/shade/renderer/geometry/virtual/format/page/VGEO_GROUP_RECORD_SIZE.js +5 -5
- package/src/shade/renderer/geometry/virtual/format/page/VGEO_MAX_GROUP_CLUSTER_COUNT.js +6 -6
- package/src/shade/renderer/geometry/virtual/format/page/VGEO_METRIC_OFFSET.js +21 -21
- package/src/shade/renderer/geometry/virtual/format/page/VGEO_METRIC_SIZE.js +5 -5
- package/src/shade/renderer/geometry/virtual/format/page/VGEO_NODE_FANOUT.js +9 -9
- package/src/shade/renderer/geometry/virtual/format/page/VGEO_NODE_FLAG_LEAF.js +5 -5
- package/src/shade/renderer/geometry/virtual/format/page/VGEO_NODE_OFFSET.js +10 -10
- package/src/shade/renderer/geometry/virtual/format/page/VGEO_NODE_RECORD_SIZE.js +5 -5
- package/src/shade/renderer/geometry/virtual/format/page/VGEO_PAGE_HEADER_SIZE.js +5 -5
- package/src/shade/renderer/geometry/virtual/format/page/VGEO_PAGE_OFFSET.js +16 -16
- package/src/shade/renderer/geometry/virtual/format/payload/VGEO_ENCODING_FLAGS_MASK.js +6 -6
- package/src/shade/renderer/geometry/virtual/format/payload/VGeoEncodedPayload.js +18 -18
- package/src/shade/renderer/geometry/virtual/format/payload/vgeo_encode_cluster_payload.js +200 -200
- package/src/shade/renderer/geometry/virtual/format/payload/vgeo_payload_attribute_offset.js +39 -39
- package/src/shade/renderer/geometry/virtual/format/payload/vgeo_payload_size.js +20 -20
- package/src/shade/renderer/geometry/virtual/format/prototypeVGEOFormatViewer.js +2063 -2063
- package/src/shade/renderer/geometry/virtual/format/vgeo_validate_container.js +1622 -1622
- package/src/shade/renderer/geometry/virtual/format/vgeo_write_container.js +485 -485
- package/src/shade/renderer/gpu_primitive/sort/ffx/graph_radix_sort.js +115 -115
- package/src/shade/renderer/gpu_primitive/sort/one_sweep/verify_radix_sort_one_sweep.js +110 -110
- package/src/shade/renderer/light/environment/octahedral_resample_kernel.js +50 -50
- package/src/shade/renderer/light/environment/octahedral_uv_to_direction.js +30 -30
- package/src/shade/renderer/light/environment/resample_cube_to_octahedral.js +157 -157
- package/src/shade/renderer/light/make_sunlight.js +35 -35
- package/src/shade/renderer/light/model/io/light_record_point.js +39 -39
- package/src/shade/renderer/loader/gltf/GLTF_PRIMITIVE_MODE.js +23 -23
- package/src/shade/renderer/loader/gltf/MESHOPT_COMPRESSION_PLAN.md +793 -793
- package/src/shade/renderer/loader/gltf/ext/GltfBufferViewExtension.js +54 -54
- package/src/shade/renderer/loader/gltf/ext/GltfBufferViewExtensionSet.js +109 -109
- package/src/shade/renderer/loader/gltf/ext/gltf_apply_buffer_view_extensions.js +69 -69
- package/src/shade/renderer/loader/gltf/ext/gltf_buffer_view_extensions.js +22 -22
- package/src/shade/renderer/loader/gltf/ext/gltf_geometry_extensions.js +39 -39
- package/src/shade/renderer/loader/gltf/ext/meshopt/MeshoptBufferViewExtension.js +301 -301
- package/src/shade/renderer/loader/gltf/gltf_component_type.js +223 -223
- package/src/shade/renderer/loader/gltf/gltf_node_world_matrices.js +137 -137
- package/src/shade/renderer/loader/gltf/gltf_parse_container.js +241 -241
- package/src/shade/renderer/loader/gltf/gltf_read_accessor.js +294 -294
- package/src/shade/renderer/loader/gltf/gltf_used_image_indices.js +49 -49
- package/src/shade/renderer/loader/gltf/load_gltf.js +1253 -1253
- package/src/shade/renderer/loader/gltf/tiny-gltf.js +646 -646
- package/src/shade/renderer/loader/usd/UsdError.js +33 -33
- package/src/shade/renderer/loader/usd/UsdExtensionRegistry.js +69 -69
- package/src/shade/renderer/loader/usd/load_usd.js +226 -226
- package/src/shade/renderer/loader/usd/parse_usda.js +931 -931
- package/src/shade/renderer/loader/usd/unpack_usdz.js +106 -106
- package/src/shade/renderer/loader/usd/usd_build_scene.js +658 -658
- package/src/shade/renderer/loader/usd/usd_compose_transform.js +258 -258
- package/src/shade/renderer/loader/usd/usd_decode_image.js +384 -384
- package/src/shade/renderer/loader/usd/usd_lz4.js +182 -182
- package/src/shade/renderer/loader/usd/usd_triangulate.js +159 -159
- package/src/shade/renderer/material/ShadeMaterial.d.ts +2 -2
- package/src/shade/renderer/material/ShadeMaterial.js +2 -2
- package/src/shade/renderer/material/standard/fragment_gbuffer.js +334 -334
- package/src/shade/renderer/material/standard/fragment_gbuffer_vt.js +169 -169
- package/src/shade/renderer/material/standard/material_gbuffer_vertex.js +45 -45
- package/src/shade/renderer/material/standard_material_to_struct.js +23 -23
- package/src/shade/renderer/particles/prototypeParticleSystem.js +450 -450
- package/src/shade/renderer/particles/runtime/spawn_request_encoding.js +52 -52
- package/src/shade/renderer/pool/TransientResourcePool.js +392 -392
- package/src/shade/renderer/postprocess/bloom/bloom_chain_resolution.js +29 -29
- package/src/shade/renderer/postprocess/bloom/graph_bloom_downsample.js +109 -109
- package/src/shade/renderer/postprocess/bloom/graph_postprocess_bloom.js +155 -155
- package/src/shade/renderer/postprocess/cas/shader_ffx_rcas.js +114 -114
- package/src/shade/renderer/postprocess/dof/DOF_AUTOFOCUS_PARAMS_STRUCT.js +25 -25
- package/src/shade/renderer/postprocess/dof/DOF_FOCUS_STATE_STRUCT.js +23 -23
- package/src/shade/renderer/postprocess/dof/DOF_PRESETS.js +27 -27
- package/src/shade/renderer/postprocess/dof/chunk_dof_coc_band_diameter.js +41 -41
- package/src/shade/renderer/postprocess/dof/chunk_dof_coc_band_radius.js +32 -32
- package/src/shade/renderer/postprocess/dof/chunk_dof_coc_coefficient.js +26 -26
- package/src/shade/renderer/postprocess/dof/compute_dof_autofocus_params.js +64 -64
- package/src/shade/renderer/postprocess/dof/compute_dof_uniform.js +54 -54
- package/src/shade/renderer/postprocess/dof/gather/DOF2_CONFIG.js +91 -91
- package/src/shade/renderer/postprocess/dof/gather/DOF2_PRESETS.js +9 -9
- package/src/shade/renderer/postprocess/dof/gather/DOF2_SETTINGS_STRUCT.js +66 -66
- package/src/shade/renderer/postprocess/dof/gather/DOF2_TEXTURE_FORMAT.js +22 -22
- package/src/shade/renderer/postprocess/dof/gather/DepthOfFieldU.js +292 -292
- package/src/shade/renderer/postprocess/dof/gather/chunk_dof2_coc_to_near_far.js +25 -25
- package/src/shade/renderer/postprocess/dof/gather/chunk_dof2_gather_weight.js +30 -30
- package/src/shade/renderer/postprocess/dof/gather/compute_dof2_uniform.js +54 -54
- package/src/shade/renderer/postprocess/dof/gather/graph_postprocess_dof2.js +194 -194
- package/src/shade/renderer/postprocess/dof/gather/shader_dof2_fill.js +68 -68
- package/src/shade/renderer/postprocess/dof/gather/shader_dof2_gather_far.js +140 -140
- package/src/shade/renderer/postprocess/dof/gather/shader_dof2_gather_near.js +142 -142
- package/src/shade/renderer/postprocess/dof/gather/shader_dof2_recombine.js +99 -99
- package/src/shade/renderer/postprocess/dof/gather/shader_dof2_setup.js +102 -102
- package/src/shade/renderer/postprocess/dof/gather/shader_dof2_tile_dilate.js +75 -75
- package/src/shade/renderer/postprocess/dof/gather/shader_dof2_tile_max.js +73 -73
- package/src/shade/renderer/postprocess/dof/raymarch/DOF_SETTINGS_STRUCT.js +49 -49
- package/src/shade/renderer/postprocess/dof/raymarch/DepthOfField.js +298 -298
- package/src/shade/renderer/postprocess/dof/raymarch/graph_postprocess_dof.js +244 -244
- package/src/shade/renderer/postprocess/dof/raymarch/shader_dof_composite.js +91 -91
- package/src/shade/renderer/postprocess/dof/raymarch/shader_dof_firefly_cleanup.js +61 -61
- package/src/shade/renderer/postprocess/dof/raymarch/shader_dof_prefilter.js +90 -90
- package/src/shade/renderer/postprocess/dof/raymarch/shader_dof_raymarch_compute.js +267 -267
- package/src/shade/renderer/postprocess/dof/raymarch/shader_dof_slice_compute.js +271 -271
- package/src/shade/renderer/postprocess/dof/raymarch/shader_dof_tile_dilate.js +70 -70
- package/src/shade/renderer/postprocess/dof/raymarch/shader_dof_tile_max.js +72 -72
- package/src/shade/renderer/postprocess/dof/shader_dof_autofocus.js +99 -99
- package/src/shade/renderer/postprocess/gtao/generate_magic_square_noise.js +51 -51
- package/src/shade/renderer/postprocess/motion_blur/MotionBlur.js +56 -56
- package/src/shade/renderer/postprocess/upscale-dither/NOTES.md +78 -78
- package/src/shade/renderer/postprocess/upscale-dither/UpscaleDither.js +95 -95
- package/src/shade/renderer/postprocess/upscale-dither/fragment_shader_upscale_dither.js +204 -204
- package/src/shade/renderer/rasterize/bucket/construct_primitive_state.js +58 -58
- package/src/shade/renderer/rasterize/fast/graph_rasterize_scene_fast.js +230 -230
- package/src/shade/renderer/rasterize/native/graph_rasterize_meshes_alpha_tested.js +278 -278
- package/src/shade/renderer/rasterize/native/oit/chunk_material_side_should_draw.js +26 -26
- package/src/shade/renderer/rasterize/native/oit/shader_oit_resolve_moments_ibl.js +178 -178
- package/src/shade/renderer/rasterize/standard/graph_rasterize_partial_opaque.js +235 -235
- package/src/shade/renderer/rasterize/standard/graph_rasterize_remaining_opaque.js +208 -208
- package/src/shade/renderer/rasterize/standard/graph_rasterize_scene.js +206 -206
- package/src/shade/renderer/scene/DynamicMesh.js +70 -70
- package/src/shade/renderer/scene/DynamicMeshBatch.js +90 -90
- package/src/shade/renderer/scene/GPUSceneContext.js +901 -901
- package/src/shade/renderer/scene/SceneNodeBinding.js +56 -56
- package/src/shade/renderer/scene/SkinnedMesh.js +55 -55
- package/src/shade/renderer/scene/TransformAuthority.js +33 -33
- package/src/shade/renderer/scene/optimization/deduplicate_geometries.js +38 -38
- package/src/shade/renderer/scene/optimization/deduplicate_images.js +93 -93
- package/src/shade/renderer/scene/optimization/geometry/internal/match_refit_validate.js +127 -127
- package/src/shade/renderer/scene/optimization/geometry/internal/match_solve_transform.js +492 -492
- package/src/shade/renderer/scene/optimization/optimize_texture_set.js +81 -81
- package/src/shade/renderer/scene/optimization/source_to_sampler2d.js +21 -21
- package/src/shade/renderer/scene/serialization/STANDARD_MATERIAL_SERIALIZED_BYTE_SIZE.js +12 -12
- package/src/shade/renderer/shader/chunk/geometry/quaternion/dual/chunk_dual_quat_transform_point.js +26 -26
- package/src/shade/renderer/shader/chunk/noise/chunk_interleaved_gradient_noise.js +20 -20
- package/src/shade/renderer/shader/chunk/spherical_harmonics/dering/chunk_sh2_dering_optimize_positive.js +61 -61
- package/src/shade/renderer/shader/chunk/texture/chunk_texture_filter_fxaa_lite.js +225 -225
- package/src/shade/renderer/shader/chunk/texture/dither/chunk_dither_color_8bit_gradient_noise.js +25 -25
- package/src/shade/renderer/shadow/map/DirectionalLightShadowmap.js +384 -384
- package/src/shade/renderer/shadow/map/PointLightShadowmap.js +105 -105
- package/src/shade/renderer/shadow/map/SpotLightShadowmap.js +63 -63
- package/src/shade/renderer/shadow/map/TetrahedronPointLightShadowmap.js +89 -89
- package/src/shade/renderer/shadow/map/shadowmap_update_score.js +232 -232
- package/src/shade/renderer/texture/GPUTextureAllocator.js +85 -85
- package/src/shade/renderer/texture/texture_pool_ops.js +171 -171
- package/src/shade/renderer/texture/virtual/PageTableQuadAllocator.js +253 -253
- package/src/shade/renderer/texture/virtual/README.md +276 -276
- package/src/shade/renderer/texture/virtual/VTPageTable.js +330 -330
- package/src/shade/renderer/texture/virtual/VTPhysicalCache.js +349 -349
- package/src/shade/renderer/texture/virtual/VT_CONSTANTS.js +154 -154
- package/src/shade/renderer/texture/virtual/VT_STACK_INFO_STRUCT.js +20 -20
- package/src/shade/renderer/texture/virtual/VirtualTextureManager.js +1276 -1276
- package/src/shade/renderer/texture/virtual/VirtualTextureStack.js +106 -106
- package/src/shade/renderer/texture/virtual/build_chunk_vt_lookup.js +150 -150
- package/src/shade/renderer/texture/virtual/build_shader_vt_feedback.js +232 -232
- package/src/shade/renderer/texture/virtual/chunk_vt_request_key.js +28 -28
- package/src/shade/renderer/texture/virtual/source/VTSourceImage.js +207 -207
- package/src/shade/renderer/texture/virtual/source/VTSourceProcedural.js +135 -135
- package/src/shade/renderer/texture/virtual/source/VTSourceTiled.js +132 -132
- package/src/shade/renderer/water/GPUWaterRenderer.js +167 -167
- package/src/shade/renderer/water/shader_water.js +246 -246
- package/src/shade/renderer/water/water_height_texture_from_sampler.js +56 -56
- package/src/shade/vitest.setup.mjs +89 -89
- package/src/shade/wgsl/validate_wgsl_source.js +316 -316
- package/src/view/controller/controls/NumericIntervalControl.js +90 -90
- package/src/view/controller/controls/Vector2Control.js +83 -83
- package/src/view/controller/controls/Vector3Control.js +90 -90
- package/src/view/elements/Group.js +56 -56
- package/src/view/elements/drawCheckers.js +33 -33
- package/src/view/elements/windrose/WindRoseDiagram.js +369 -369
- package/src/view/graphics/WebGLContextFailureView.js +76 -76
- package/src/view/layout/DockPanel.js +51 -51
- package/src/view/layout/SplitView.js +365 -365
- package/src/view/layout/show_popup_menu.js +81 -81
- package/src/view/minimap/MinimapCanvasView.js +411 -411
- package/src/view/minimap/MinimapImageCache.js +88 -88
- package/src/view/minimap/MinimapMarkerCollection.d.ts +0 -1
- package/src/view/minimap/MinimapMarkerCollection.d.ts.map +1 -1
- package/src/view/minimap/MinimapMarkerCollection.js +180 -180
- package/src/view/minimap/dom/MinimapCameraView.js +206 -206
- package/src/view/minimap/minimap_focus_area.js +97 -97
- package/editor/process/symbolic/SocketsSymbolicDisplay.js +0 -192
- package/src/core/geom/3d/topology/struct/binary/io/moved_tests.txt +0 -37
- package/src/engine/ecs/attachment/Attachment.d.ts +0 -40
- package/src/engine/ecs/attachment/Attachment.d.ts.map +0 -1
- package/src/engine/ecs/attachment/Attachment.js +0 -57
- package/src/engine/ecs/attachment/AttachmentBinding.d.ts +0 -28
- package/src/engine/ecs/attachment/AttachmentBinding.d.ts.map +0 -1
- package/src/engine/ecs/attachment/AttachmentBinding.js +0 -41
- package/src/engine/ecs/attachment/AttachmentSystem.d.ts +0 -61
- package/src/engine/ecs/attachment/AttachmentSystem.d.ts.map +0 -1
- package/src/engine/ecs/attachment/AttachmentSystem.js +0 -307
- package/src/engine/ecs/attachment/BoneAttachmentBinding.d.ts +0 -27
- package/src/engine/ecs/attachment/BoneAttachmentBinding.d.ts.map +0 -1
- package/src/engine/ecs/attachment/BoneAttachmentBinding.js +0 -69
- package/src/engine/ecs/attachment/TransformAttachmentBinding.d.ts +0 -4
- package/src/engine/ecs/attachment/TransformAttachmentBinding.d.ts.map +0 -1
- package/src/engine/ecs/attachment/TransformAttachmentBinding.js +0 -51
- package/src/engine/ecs/sockets/AttachmentSocket.d.ts +0 -32
- package/src/engine/ecs/sockets/AttachmentSocket.d.ts.map +0 -1
- package/src/engine/ecs/sockets/AttachmentSocket.js +0 -60
- package/src/engine/ecs/sockets/AttachmentSocketType.d.ts +0 -6
- package/src/engine/ecs/sockets/AttachmentSocketType.d.ts.map +0 -1
- package/src/engine/ecs/sockets/AttachmentSocketType.js +0 -8
- package/src/engine/ecs/sockets/AttachmentSockets.d.ts +0 -57
- package/src/engine/ecs/sockets/AttachmentSockets.d.ts.map +0 -1
- package/src/engine/ecs/sockets/AttachmentSockets.js +0 -130
- package/src/engine/ecs/sockets/BoneAttachmentSocket.d.ts +0 -29
- package/src/engine/ecs/sockets/BoneAttachmentSocket.d.ts.map +0 -1
- package/src/engine/ecs/sockets/BoneAttachmentSocket.js +0 -56
- package/src/engine/ecs/sockets/serialization/AttachmentSocketsAssetLoader.d.ts +0 -6
- package/src/engine/ecs/sockets/serialization/AttachmentSocketsAssetLoader.d.ts.map +0 -1
- package/src/engine/ecs/sockets/serialization/AttachmentSocketsAssetLoader.js +0 -19
- package/src/shade/renderer/animation/_dump_resolve.d.mts +0 -2
- package/src/shade/renderer/animation/_dump_resolve.d.mts.map +0 -1
- package/src/shade/renderer/animation/_dump_resolve.mjs +0 -5
|
@@ -1,1786 +1,1786 @@
|
|
|
1
|
-
import { assert } from "../../../../../core/assert.js";
|
|
2
|
-
import { aabb3_from_v3_array } from "../../../../../core/geom/3d/aabb/aabb3_from_v3_array.js";
|
|
3
|
-
import { v3_morton_encode_bounded } from "../../../../../core/geom/3d/morton/v3_morton_encode_bounded.js";
|
|
4
|
-
import { Quadric3 } from "../../../../../core/geom/3d/quadric/Quadric3.js";
|
|
5
|
-
import {
|
|
6
|
-
sphere_compute_bounding_of_2_spheres
|
|
7
|
-
} from "../../../../../core/geom/3d/sphere/sphere_compute_bounding_of_2_spheres.js";
|
|
8
|
-
import { BinaryTopology, NULL_POINTER, } from "../../../../../core/geom/3d/topology/struct/binary/BinaryTopology.js";
|
|
9
|
-
import {
|
|
10
|
-
bt_mesh_compute_vertex_quadrics
|
|
11
|
-
} from "../../../../../core/geom/3d/topology/struct/binary/io/bt_mesh_compute_vertex_quadrics.js";
|
|
12
|
-
import {
|
|
13
|
-
bt_mesh_from_indexed_geometry
|
|
14
|
-
} from "../../../../../core/geom/3d/topology/struct/binary/io/bt_mesh_from_indexed_geometry.js";
|
|
15
|
-
import {
|
|
16
|
-
bt_mesh_simplify_with_quadrics
|
|
17
|
-
} from "../../../../../core/geom/3d/topology/struct/binary/io/bt_mesh_simplify.js";
|
|
18
|
-
import { bt_edge_get } from "../../../../../core/geom/3d/topology/struct/binary/query/bt_edge_get.js";
|
|
19
|
-
import { bt_mesh_validate } from "../../../../../core/geom/3d/topology/struct/binary/query/bt_mesh_validate.js";
|
|
20
|
-
import { bt_mesh_build_face_graph } from "../../../../../core/graph/metis/bt_mesh_build_face_graph.js";
|
|
21
|
-
import { vgeo_encode_cluster_payload } from "../format/payload/vgeo_encode_cluster_payload.js";
|
|
22
|
-
import { vgeo_layers_copy_vertex } from "../format/attribute/vgeo_layers_copy_vertex.js";
|
|
23
|
-
import { vgeo_layers_like } from "../format/attribute/vgeo_layers_like.js";
|
|
24
|
-
import { VGEO_MAX_CLUSTER_TRIANGLE_COUNT } from "../format/cluster/VGEO_MAX_CLUSTER_TRIANGLE_COUNT.js";
|
|
25
|
-
import { VGEO_MAX_CLUSTER_VERTEX_COUNT } from "../format/cluster/VGEO_MAX_CLUSTER_VERTEX_COUNT.js";
|
|
26
|
-
import { VGEO_MAX_GROUP_CLUSTER_COUNT } from "../format/page/VGEO_MAX_GROUP_CLUSTER_COUNT.js";
|
|
27
|
-
import { VirtualGeometryCluster } from "../format/dag/VirtualGeometryCluster.js";
|
|
28
|
-
import { VirtualGeometryGroup } from "../format/dag/VirtualGeometryGroup.js";
|
|
29
|
-
import { vgeo_bounding_sphere } from "./vgeo_bounding_sphere.js";
|
|
30
|
-
import { vgeo_compute_vertex_normals } from "./vgeo_compute_vertex_normals.js";
|
|
31
|
-
import { vgeo_compute_vertex_tangents } from "./vgeo_compute_vertex_tangents.js";
|
|
32
|
-
import { vgeo_containing_sphere } from "./vgeo_containing_sphere.js";
|
|
33
|
-
import { vgeo_extract_submesh } from "./vgeo_extract_submesh.js";
|
|
34
|
-
import { vgeo_gather_faces } from "./vgeo_gather_faces.js";
|
|
35
|
-
import { vgeo_graph_components } from "./vgeo_graph_components.js";
|
|
36
|
-
import { vgeo_narrow_indices } from "./vgeo_narrow_indices.js";
|
|
37
|
-
import { vgeo_partition_graph } from "./vgeo_partition_graph.js";
|
|
38
|
-
import { vgeo_split_face_set } from "./vgeo_split_face_set.js";
|
|
39
|
-
import { vgeo_split_face_set_packed } from "./vgeo_split_face_set_packed.js";
|
|
40
|
-
import { VGeoFaceGraph } from "./VGeoFaceGraph.js";
|
|
41
|
-
import { VGeoLevel } from "./VGeoLevel.js";
|
|
42
|
-
import { VirtualGeometryMesh } from "./VirtualGeometryMesh.js";
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Build the cluster DAG: leaves out of the source mesh, then merge steps until one cluster is left.
|
|
46
|
-
*
|
|
47
|
-
* The unit of work is a **round**, not a level sweep. A round holds a *frontier* — every cluster that
|
|
48
|
-
* nothing has merged yet — selects a band of it by error, groups the band, locks every vertex the
|
|
49
|
-
* group shares with anything outside it, halves the group's interior, and re-cuts the result into
|
|
50
|
-
* new clusters, which become the parents of every cluster in the group. Locked perimeters plus a
|
|
51
|
-
* group-uniform metric is the whole reason independent per-cluster LOD decisions come out
|
|
52
|
-
* crack-free.
|
|
53
|
-
*
|
|
54
|
-
* **The band is why this is not a level sweep.** Sweeping whole levels pushes every cluster up one
|
|
55
|
-
* step per level whatever its error, so a region that is nearly exact is coarsened in lockstep with
|
|
56
|
-
* one that is not, and a level ends up spanning an order of magnitude of error. The cut the runtime
|
|
57
|
-
* takes is a threshold on error, so a level that spans an order of magnitude is a page band that
|
|
58
|
-
* spans one too: installing it brings clusters the cut does not want, and the ones it does want are
|
|
59
|
-
* scattered across bands. Selecting the lowest-error band instead lets a fine region catch up before
|
|
60
|
-
* the coarse one moves, and bands come out error-uniform. On geometry whose error is already uniform
|
|
61
|
-
* the band is the whole frontier and this degenerates to the sweep, which is the correct behaviour
|
|
62
|
-
* for that case rather than a special case for it.
|
|
63
|
-
*
|
|
64
|
-
* **A cluster the round cannot use simply stays.** It is not pushed up for the sake of symmetry, and
|
|
65
|
-
* it does not stall the round for anyone else — which is what lets a lone island wait, untouched and
|
|
66
|
-
* at zero error, until the geometry it will eventually merge with has come down to meet it.
|
|
67
|
-
*
|
|
68
|
-
* **Error is conformance to the source, not a sum of steps.** Vertex quadrics are built once, from
|
|
69
|
-
* the source triangles, and carried through every round: a survivor absorbs its victim's quadric at
|
|
70
|
-
* every collapse, so a vertex twelve rounds up still holds the planes of every source triangle
|
|
71
|
-
* folded into it. Evaluating that at its current position measures the deviation from what the
|
|
72
|
-
* artist authored, directly, and normalizing by the quadric's accumulated weight makes it an
|
|
73
|
-
* object-space length rather than a cost whose scale depends on how many planes it happens to hold.
|
|
74
|
-
* That length is what the runtime projects into pixels.
|
|
75
|
-
*
|
|
76
|
-
* **Termination is convergence to one root, not a stall.** Groups bundle across connected components
|
|
77
|
-
* once connectivity runs out, and a group small enough to fit one cluster is merged losslessly, so a
|
|
78
|
-
* frontier of separate islands still comes together. A build that cannot reach a single root throws:
|
|
79
|
-
* the runtime starts its cut at the root, and "several roots" is not a shape it can start from.
|
|
80
|
-
*
|
|
81
|
-
* @author Alex Goldring
|
|
82
|
-
* @copyright Company Named Limited (c) 2026
|
|
83
|
-
*/
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* Fixed so that the same frontier always groups the same way.
|
|
87
|
-
* @readonly
|
|
88
|
-
* @type {number}
|
|
89
|
-
*/
|
|
90
|
-
const GROUP_SEED = 0x6D05;
|
|
91
|
-
|
|
92
|
-
const scratch_sphere = new Float32Array(4);
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* Every cluster nothing has merged yet, and the geometry they currently stand for.
|
|
96
|
-
*
|
|
97
|
-
* One mesh holds the whole frontier so that a group can be extracted with its surroundings intact —
|
|
98
|
-
* the lock set is "every vertex a face outside this group touches", and that question has no answer
|
|
99
|
-
* in a mesh that holds the group alone.
|
|
100
|
-
*/
|
|
101
|
-
class VGeoFrontier {
|
|
102
|
-
/**
|
|
103
|
-
* @type {VirtualGeometryMesh}
|
|
104
|
-
*/
|
|
105
|
-
mesh = null;
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* @type {VirtualGeometryCluster[]}
|
|
109
|
-
*/
|
|
110
|
-
clusters = [];
|
|
111
|
-
|
|
112
|
-
/**
|
|
113
|
-
* `cluster_faces[i]` are the faces of {@link mesh} that cluster `i` owns.
|
|
114
|
-
* @type {number[][]}
|
|
115
|
-
*/
|
|
116
|
-
cluster_faces = [];
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
* One accumulated quadric per vertex of {@link mesh}, carried unbroken from the source mesh.
|
|
120
|
-
*
|
|
121
|
-
* This is what makes the error mean "how far this is from the geometry the artist authored"
|
|
122
|
-
* rather than "how far this is from the frontier it was simplified out of". A survivor absorbs
|
|
123
|
-
* its victim's quadric at every collapse, at every round, so a vertex twelve rounds up still
|
|
124
|
-
* carries the planes of every source triangle that was ever folded into it, and evaluating its
|
|
125
|
-
* quadric at its current position measures the deviation from the source directly. Composing
|
|
126
|
-
* per-round residuals instead only *bounds* that, by the triangle inequality, and the bound
|
|
127
|
-
* loosens with every round: measured on a unit sphere, the same ninety-triangle root read 0.36
|
|
128
|
-
* built from 3k source triangles and 13.3 built from 24k, where it is the same approximation and
|
|
129
|
-
* reads 0.0315 either way once the quadrics are carried.
|
|
130
|
-
*
|
|
131
|
-
* @type {Quadric3[]}
|
|
132
|
-
*/
|
|
133
|
-
quadrics = [];
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
/**
|
|
137
|
-
* Everything one group step produced, before it is committed. Kept separate so that a round which
|
|
138
|
-
* fails to make progress can be discarded without having mutated the frontier.
|
|
139
|
-
*/
|
|
140
|
-
class GroupStep {
|
|
141
|
-
/**
|
|
142
|
-
* The frontier clusters this group holds.
|
|
143
|
-
* @type {VirtualGeometryCluster[]}
|
|
144
|
-
*/
|
|
145
|
-
child_clusters = [];
|
|
146
|
-
|
|
147
|
-
/**
|
|
148
|
-
* @type {VirtualGeometryCluster[]}
|
|
149
|
-
*/
|
|
150
|
-
parent_clusters = [];
|
|
151
|
-
|
|
152
|
-
/**
|
|
153
|
-
* Parallel to {@link parent_clusters}: the geometry each parent was cut from, in terms of the
|
|
154
|
-
* frontier, so the next frontier can be stitched back together.
|
|
155
|
-
* @type {VGeoContribution[]}
|
|
156
|
-
*/
|
|
157
|
-
parent_geometry = [];
|
|
158
|
-
|
|
159
|
-
/**
|
|
160
|
-
* The value both sides of this step store: the object-space distance from the *source* surface
|
|
161
|
-
* that this step's geometry now stands at, floored by what its children already carried.
|
|
162
|
-
* @type {number}
|
|
163
|
-
*/
|
|
164
|
-
error = 0;
|
|
165
|
-
|
|
166
|
-
/**
|
|
167
|
-
* `frontier vertex id -> that vertex's quadric after this step`, for every vertex that survived
|
|
168
|
-
* it. Held rather than written straight back because a step is speculative until the round
|
|
169
|
-
* accepts it, and because two steps sharing a pinned boundary vertex must have their gains
|
|
170
|
-
* combined rather than one overwriting the other.
|
|
171
|
-
* @type {Map<number, Quadric3>}
|
|
172
|
-
*/
|
|
173
|
-
quadrics = new Map();
|
|
174
|
-
|
|
175
|
-
/**
|
|
176
|
-
* The LOD-metric sphere both sides of this step store.
|
|
177
|
-
* @type {Float32Array}
|
|
178
|
-
*/
|
|
179
|
-
sphere = new Float32Array(4);
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
/**
|
|
183
|
-
* Several leaves re-cut into one, with nothing simplified — see {@link build_repack}.
|
|
184
|
-
*/
|
|
185
|
-
class GroupRepack {
|
|
186
|
-
/**
|
|
187
|
-
* The leaves this removes from the DAG.
|
|
188
|
-
* @type {VirtualGeometryCluster[]}
|
|
189
|
-
*/
|
|
190
|
-
replaced = [];
|
|
191
|
-
|
|
192
|
-
/**
|
|
193
|
-
* The leaves that take their place -- always fewer.
|
|
194
|
-
* @type {VirtualGeometryCluster[]}
|
|
195
|
-
*/
|
|
196
|
-
clusters = [];
|
|
197
|
-
|
|
198
|
-
/**
|
|
199
|
-
* Parallel to {@link clusters}.
|
|
200
|
-
* @type {VGeoContribution[]}
|
|
201
|
-
*/
|
|
202
|
-
geometry = [];
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
/**
|
|
206
|
-
* One cluster's geometry expressed against the frontier it came out of, which is what lets the next
|
|
207
|
-
* frontier be stitched from a mixture of freshly simplified clusters and untouched ones.
|
|
208
|
-
*
|
|
209
|
-
* @typedef {object} VGeoContribution
|
|
210
|
-
* @property {Float32Array} positions
|
|
211
|
-
* @property {Uint32Array} indices local to {@link positions}
|
|
212
|
-
* @property {Uint32Array} local_vertex_ids ids in the mesh {@link topology} belongs to
|
|
213
|
-
* @property {Uint32Array} source_vertex_ids the same vertices, named in the frontier's mesh
|
|
214
|
-
* @property {BinaryTopology} topology the mesh {@link local_vertex_ids} name vertices of, whose
|
|
215
|
-
* vertex data block holds their attributes
|
|
216
|
-
*/
|
|
217
|
-
|
|
218
|
-
/**
|
|
219
|
-
* Encode a gathered face set into a cluster record.
|
|
220
|
-
*
|
|
221
|
-
* @param {VGeoGatheredFaces} gathered already within both meshlet caps
|
|
222
|
-
* @param {number} level
|
|
223
|
-
* @param {(BinaryDataLayer|null)[]} layers of the mesh it was gathered from
|
|
224
|
-
* @returns {VirtualGeometryCluster}
|
|
225
|
-
*/
|
|
226
|
-
function cluster_from_gathered(gathered, level, layers) {
|
|
227
|
-
const payload = vgeo_encode_cluster_payload(
|
|
228
|
-
gathered.positions,
|
|
229
|
-
vgeo_narrow_indices(gathered),
|
|
230
|
-
gathered.vertex_ids,
|
|
231
|
-
layers
|
|
232
|
-
);
|
|
233
|
-
|
|
234
|
-
const cluster = new VirtualGeometryCluster();
|
|
235
|
-
|
|
236
|
-
cluster.level = level;
|
|
237
|
-
cluster.vertex_count = gathered.vertex_ids.length;
|
|
238
|
-
cluster.triangle_count = gathered.indices.length / 3;
|
|
239
|
-
cluster.encoding_flags = payload.encoding_flags;
|
|
240
|
-
cluster.payload = payload.bytes;
|
|
241
|
-
cluster.bounds_sphere = vgeo_bounding_sphere(gathered.positions);
|
|
242
|
-
|
|
243
|
-
// the box needs no inflation, unlike the sphere above: the extremes are float32 values already,
|
|
244
|
-
// so min and max of them are exactly representable and the stored box contains the stored
|
|
245
|
-
// points by construction
|
|
246
|
-
aabb3_from_v3_array(cluster.aabb, gathered.positions, gathered.positions.length);
|
|
247
|
-
|
|
248
|
-
return cluster;
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
/**
|
|
252
|
-
* Build a cluster out of a face set: gather it, bound it, encode its payload.
|
|
253
|
-
*
|
|
254
|
-
* @param {VirtualGeometryMesh} mesh
|
|
255
|
-
* @param {number[]} face_ids
|
|
256
|
-
* @param {number} level
|
|
257
|
-
* @returns {{cluster: VirtualGeometryCluster, gathered: VGeoGatheredFaces}}
|
|
258
|
-
*/
|
|
259
|
-
function build_cluster(mesh, face_ids, level) {
|
|
260
|
-
const gathered = vgeo_gather_faces(mesh, face_ids);
|
|
261
|
-
|
|
262
|
-
return { cluster: cluster_from_gathered(gathered, level, mesh.layers), gathered };
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
/**
|
|
266
|
-
* Cut a mesh into leaf clusters — the frontier every round starts from.
|
|
267
|
-
*
|
|
268
|
-
* @param {VirtualGeometryMesh} mesh
|
|
269
|
-
* @returns {VGeoFrontier}
|
|
270
|
-
*/
|
|
271
|
-
function build_leaf_frontier(mesh) {
|
|
272
|
-
const face_graph = new VGeoFaceGraph(mesh.topology, bt_mesh_build_face_graph(mesh.topology));
|
|
273
|
-
|
|
274
|
-
const all = [];
|
|
275
|
-
|
|
276
|
-
for (let vertex = 0; vertex < face_graph.graph.vertex_count; vertex++) {
|
|
277
|
-
all.push(vertex);
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
const parts = vgeo_split_face_set(
|
|
281
|
-
face_graph, all, VGEO_MAX_CLUSTER_TRIANGLE_COUNT, VGEO_MAX_CLUSTER_VERTEX_COUNT
|
|
282
|
-
);
|
|
283
|
-
|
|
284
|
-
const frontier = new VGeoFrontier();
|
|
285
|
-
|
|
286
|
-
frontier.mesh = mesh;
|
|
287
|
-
|
|
288
|
-
// Built once, here, from the source triangles. Nothing rebuilds them afterwards — every later
|
|
289
|
-
// round inherits these and adds to them, which is the whole point.
|
|
290
|
-
frontier.quadrics = bt_mesh_compute_vertex_quadrics(mesh.topology);
|
|
291
|
-
|
|
292
|
-
for (const part of parts) {
|
|
293
|
-
const face_ids = part.map((dense) => face_graph.vertex_face_ids[dense]);
|
|
294
|
-
|
|
295
|
-
face_ids.sort((a, b) => a - b);
|
|
296
|
-
|
|
297
|
-
const built = build_cluster(mesh, face_ids, 0);
|
|
298
|
-
|
|
299
|
-
// A leaf's own bounds are its LOD-metric sphere, and it deviates from nothing.
|
|
300
|
-
built.cluster.self_sphere = built.cluster.bounds_sphere.slice();
|
|
301
|
-
built.cluster.self_error = 0;
|
|
302
|
-
|
|
303
|
-
frontier.clusters.push(built.cluster);
|
|
304
|
-
frontier.cluster_faces.push(face_ids);
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
return frontier;
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
/**
|
|
311
|
-
* How much of the space two clusters would waste by being merged, as a factor in `(0, 1]`.
|
|
312
|
-
*
|
|
313
|
-
* The ratio of what they occupy separately to what the sphere containing both would occupy. Two
|
|
314
|
-
* clusters side by side score near one; two clusters at opposite ends of the model score near zero,
|
|
315
|
-
* because the sphere over both is mostly empty. Scale-free by construction — it compares a volume
|
|
316
|
-
* to a volume, so it needs no notion of how big the model is or how far apart "far" is, which is
|
|
317
|
-
* exactly what a distance threshold would have needed and could never have been given.
|
|
318
|
-
*
|
|
319
|
-
* The group's sphere is what the runtime projects to decide the whole group's fate, so a group
|
|
320
|
-
* spread across empty space is one that gets refined from much further away than its geometry
|
|
321
|
-
* warrants. Cutting edge weight by this steers the partitioner away from those merges without ever
|
|
322
|
-
* forbidding one — when nothing better is available, a poor merge is still better than none.
|
|
323
|
-
*
|
|
324
|
-
* @param {Float32Array} a bounding sphere, centre then radius
|
|
325
|
-
* @param {Float32Array} b
|
|
326
|
-
* @returns {number}
|
|
327
|
-
*/
|
|
328
|
-
function compactness(a, b) {
|
|
329
|
-
sphere_compute_bounding_of_2_spheres(scratch_sphere, a, b);
|
|
330
|
-
|
|
331
|
-
const merged = scratch_sphere[3];
|
|
332
|
-
|
|
333
|
-
if (merged <= 0) {
|
|
334
|
-
return 1;
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
// Volumes, up to the common factor of 4/3 pi, which cancels.
|
|
338
|
-
const separate = a[3] * a[3] * a[3] + b[3] * b[3] * b[3];
|
|
339
|
-
|
|
340
|
-
return Math.min(1, separate / (merged * merged * merged));
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
/**
|
|
344
|
-
* The cluster adjacency graph of a frontier: clusters are the vertices, shared mesh edges are the
|
|
345
|
-
* edges, and an edge weighs as much boundary as the two clusters share, discounted by how much empty
|
|
346
|
-
* space merging them would enclose.
|
|
347
|
-
*
|
|
348
|
-
* @param {VGeoFrontier} frontier
|
|
349
|
-
* @param {VGeoFaceGraph} face_graph of `frontier.mesh`
|
|
350
|
-
* @returns {{edge_addresses: Uint32Array, adjacency: Uint32Array, edge_weights: Float32Array}}
|
|
351
|
-
*/
|
|
352
|
-
function build_cluster_graph(frontier, face_graph) {
|
|
353
|
-
const cluster_of_face = new Int32Array(frontier.mesh.topology.faces.size).fill(-1);
|
|
354
|
-
|
|
355
|
-
frontier.cluster_faces.forEach((face_ids, cluster) => {
|
|
356
|
-
for (const face of face_ids) {
|
|
357
|
-
cluster_of_face[face] = cluster;
|
|
358
|
-
}
|
|
359
|
-
});
|
|
360
|
-
|
|
361
|
-
const cluster_count = frontier.clusters.length;
|
|
362
|
-
|
|
363
|
-
/**
|
|
364
|
-
* `cluster -> (neighbour cluster -> shared boundary)`.
|
|
365
|
-
* @type {Map<number, number>[]}
|
|
366
|
-
*/
|
|
367
|
-
const neighbours = [];
|
|
368
|
-
|
|
369
|
-
for (let i = 0; i < cluster_count; i++) {
|
|
370
|
-
neighbours.push(new Map());
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
const graph = face_graph.graph;
|
|
374
|
-
|
|
375
|
-
for (let vertex = 0; vertex < graph.vertex_count; vertex++) {
|
|
376
|
-
const cluster = cluster_of_face[face_graph.vertex_face_ids[vertex]];
|
|
377
|
-
|
|
378
|
-
for (let i = graph.edge_addresses[vertex]; i < graph.edge_addresses[vertex + 1]; i++) {
|
|
379
|
-
const other_cluster = cluster_of_face[face_graph.vertex_face_ids[graph.adjacency[i]]];
|
|
380
|
-
|
|
381
|
-
if (other_cluster === cluster) {
|
|
382
|
-
continue;
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
const bucket = neighbours[cluster];
|
|
386
|
-
|
|
387
|
-
bucket.set(other_cluster, (bucket.get(other_cluster) ?? 0) + graph.edge_weights[i]);
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
const edge_addresses = new Uint32Array(cluster_count + 1);
|
|
392
|
-
const adjacency = [];
|
|
393
|
-
const edge_weights = [];
|
|
394
|
-
|
|
395
|
-
for (let cluster = 0; cluster < cluster_count; cluster++) {
|
|
396
|
-
edge_addresses[cluster] = adjacency.length;
|
|
397
|
-
|
|
398
|
-
const entries = Array.from(neighbours[cluster].entries()).sort((a, b) => a[0] - b[0]);
|
|
399
|
-
|
|
400
|
-
for (const [other, weight] of entries) {
|
|
401
|
-
adjacency.push(other);
|
|
402
|
-
|
|
403
|
-
edge_weights.push(weight * compactness(
|
|
404
|
-
frontier.clusters[cluster].bounds_sphere, frontier.clusters[other].bounds_sphere
|
|
405
|
-
));
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
edge_addresses[cluster_count] = adjacency.length;
|
|
410
|
-
|
|
411
|
-
return {
|
|
412
|
-
edge_addresses,
|
|
413
|
-
adjacency: Uint32Array.from(adjacency),
|
|
414
|
-
edge_weights: Float32Array.from(edge_weights),
|
|
415
|
-
};
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
/**
|
|
419
|
-
* Bundle clusters that have no one to merge with along a Morton curve.
|
|
420
|
-
*
|
|
421
|
-
* A cluster alone in its connected component cannot be coarsened: halving its triangles leaves one
|
|
422
|
-
* cluster where there was one cluster, and the DAG stands still. The only way it ever reaches a root
|
|
423
|
-
* is bundled with geometry it shares no edge with, and the only thing left to choose them by is
|
|
424
|
-
* where they are. That is a real cost — a cluster spanning two islands carries a sphere containing
|
|
425
|
-
* both — which is why this is the fallback and adjacency is the rule.
|
|
426
|
-
*
|
|
427
|
-
* @param {VGeoFrontier} frontier
|
|
428
|
-
* @param {number[]} loose cluster indices, each alone in its component
|
|
429
|
-
* @param {number} group_size
|
|
430
|
-
* @returns {number[][]} groups of at least two; a single leftover is returned to the frontier
|
|
431
|
-
*/
|
|
432
|
-
function bundle_loose_clusters(frontier, loose, group_size) {
|
|
433
|
-
if (loose.length < 2) {
|
|
434
|
-
return [];
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
const bounds = new Float32Array([
|
|
438
|
-
Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY,
|
|
439
|
-
Number.NEGATIVE_INFINITY, Number.NEGATIVE_INFINITY, Number.NEGATIVE_INFINITY,
|
|
440
|
-
]);
|
|
441
|
-
|
|
442
|
-
for (const cluster of loose) {
|
|
443
|
-
const centre = frontier.clusters[cluster].bounds_sphere;
|
|
444
|
-
|
|
445
|
-
for (let axis = 0; axis < 3; axis++) {
|
|
446
|
-
bounds[axis] = Math.min(bounds[axis], centre[axis]);
|
|
447
|
-
bounds[axis + 3] = Math.max(bounds[axis + 3], centre[axis]);
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
/**
|
|
452
|
-
* @type {Map<number, number>}
|
|
453
|
-
*/
|
|
454
|
-
const codes = new Map();
|
|
455
|
-
|
|
456
|
-
for (const cluster of loose) {
|
|
457
|
-
const centre = frontier.clusters[cluster].bounds_sphere;
|
|
458
|
-
|
|
459
|
-
codes.set(cluster, v3_morton_encode_bounded(centre[0], centre[1], centre[2], bounds));
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
const order = loose.slice();
|
|
463
|
-
|
|
464
|
-
order.sort((a, b) => (codes.get(a) - codes.get(b)) || (a - b));
|
|
465
|
-
|
|
466
|
-
const result = [];
|
|
467
|
-
|
|
468
|
-
let bundle = [];
|
|
469
|
-
let triangles = 0;
|
|
470
|
-
let vertices = 0;
|
|
471
|
-
let packable = true;
|
|
472
|
-
|
|
473
|
-
for (const index of order) {
|
|
474
|
-
const cluster = frontier.clusters[index];
|
|
475
|
-
|
|
476
|
-
// Leaves small enough to end up in one cluster between them are worth bundling past the
|
|
477
|
-
// group size: what happens to them is a re-cut at no error, not a simplification, so a
|
|
478
|
-
// bigger bundle costs nothing in fidelity and is the difference between a scene of small
|
|
479
|
-
// parts filling its clusters and running them at a tenth of capacity. Anything else stops at
|
|
480
|
-
// the group size, where the size is a real trade against LOD lockstep.
|
|
481
|
-
const packs = packable
|
|
482
|
-
&& cluster.child_group === undefined
|
|
483
|
-
&& triangles + cluster.triangle_count <= VGEO_MAX_CLUSTER_TRIANGLE_COUNT
|
|
484
|
-
&& vertices + cluster.vertex_count <= VGEO_MAX_CLUSTER_VERTEX_COUNT;
|
|
485
|
-
|
|
486
|
-
if (bundle.length >= group_size && !packs) {
|
|
487
|
-
result.push(bundle);
|
|
488
|
-
|
|
489
|
-
bundle = [];
|
|
490
|
-
triangles = 0;
|
|
491
|
-
vertices = 0;
|
|
492
|
-
packable = true;
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
bundle.push(index);
|
|
496
|
-
|
|
497
|
-
triangles += cluster.triangle_count;
|
|
498
|
-
vertices += cluster.vertex_count;
|
|
499
|
-
|
|
500
|
-
packable = packable && cluster.child_group === undefined;
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
if (bundle.length === 1 && result.length > 0) {
|
|
504
|
-
// A bundle of one merges nothing. Handing it to the bundle before it costs one cluster of
|
|
505
|
-
// spatial spread and is the difference between the frontier shrinking and it standing still.
|
|
506
|
-
result[result.length - 1].push(bundle[0]);
|
|
507
|
-
} else if (bundle.length > 1) {
|
|
508
|
-
result.push(bundle);
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
return result.filter((group) => group.length >= 2);
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
/**
|
|
515
|
-
* Fold every one-cluster piece into the adjacent piece it shares the most boundary with.
|
|
516
|
-
*
|
|
517
|
-
* The partitioner produces these constantly — it optimizes cut, not contiguity, so splitting its
|
|
518
|
-
* parts into connected pieces routinely leaves a cluster standing on its own. Such a cluster is not
|
|
519
|
-
* out of options, though: it sits inside a component that has plenty of neighbours, and the right
|
|
520
|
-
* answer is the nearest of them, not the proximity pool. Letting it fall through to the pool is how
|
|
521
|
-
* a lone shell sixty metres away ends up merged with a fragment of something else entirely.
|
|
522
|
-
*
|
|
523
|
-
* One forward pass is enough. Folding only ever grows a piece and empties the one it came from, so
|
|
524
|
-
* it cannot turn a piece of two into a piece of one — the singletons are exactly the ones present
|
|
525
|
-
* when the pass starts, and a singleton that has already absorbed another is no longer one.
|
|
526
|
-
*
|
|
527
|
-
* @param {number[][]} pieces of a single connected component of the band, each non-empty; every
|
|
528
|
-
* cluster in a singleton piece therefore has a neighbour in some other piece here
|
|
529
|
-
* @param {object} cluster_graph as {@link build_cluster_graph} returns
|
|
530
|
-
* @returns {number[][]} non-empty pieces, with no singletons
|
|
531
|
-
*/
|
|
532
|
-
function absorb_singleton_pieces(pieces, cluster_graph) {
|
|
533
|
-
if (pieces.length < 2) {
|
|
534
|
-
return pieces;
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
/**
|
|
538
|
-
* `cluster -> index of the piece holding it`.
|
|
539
|
-
* @type {Map<number, number>}
|
|
540
|
-
*/
|
|
541
|
-
const piece_of = new Map();
|
|
542
|
-
|
|
543
|
-
const alive = pieces.map((piece) => piece.slice());
|
|
544
|
-
|
|
545
|
-
alive.forEach((piece, index) => {
|
|
546
|
-
for (const cluster of piece) {
|
|
547
|
-
piece_of.set(cluster, index);
|
|
548
|
-
}
|
|
549
|
-
});
|
|
550
|
-
|
|
551
|
-
for (let index = 0; index < alive.length; index++) {
|
|
552
|
-
if (alive[index].length !== 1) {
|
|
553
|
-
continue;
|
|
554
|
-
}
|
|
555
|
-
|
|
556
|
-
const cluster = alive[index][0];
|
|
557
|
-
|
|
558
|
-
/**
|
|
559
|
-
* `piece index -> boundary shared with this cluster`.
|
|
560
|
-
* @type {Map<number, number>}
|
|
561
|
-
*/
|
|
562
|
-
const shared = new Map();
|
|
563
|
-
|
|
564
|
-
for (let i = cluster_graph.edge_addresses[cluster]; i < cluster_graph.edge_addresses[cluster + 1]; i++) {
|
|
565
|
-
const target = piece_of.get(cluster_graph.adjacency[i]);
|
|
566
|
-
|
|
567
|
-
if (target === undefined || target === index) {
|
|
568
|
-
continue;
|
|
569
|
-
}
|
|
570
|
-
|
|
571
|
-
shared.set(target, (shared.get(target) ?? 0) + cluster_graph.edge_weights[i]);
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
let best = -1;
|
|
575
|
-
let best_weight = -1;
|
|
576
|
-
|
|
577
|
-
// Sorted, so that two pieces sharing the same boundary length resolve the same way every
|
|
578
|
-
// time rather than however the adjacency happened to be walked.
|
|
579
|
-
for (const [target, weight] of Array.from(shared.entries()).sort((a, b) => a[0] - b[0])) {
|
|
580
|
-
if (weight > best_weight) {
|
|
581
|
-
best = target;
|
|
582
|
-
best_weight = weight;
|
|
583
|
-
}
|
|
584
|
-
}
|
|
585
|
-
|
|
586
|
-
assert.notEqual(
|
|
587
|
-
best, -1,
|
|
588
|
-
'a lone cluster inside a connected component must border another piece of it'
|
|
589
|
-
);
|
|
590
|
-
|
|
591
|
-
alive[best].push(cluster);
|
|
592
|
-
alive[best].sort((a, b) => a - b);
|
|
593
|
-
|
|
594
|
-
piece_of.set(cluster, best);
|
|
595
|
-
|
|
596
|
-
alive[index] = [];
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
return alive.filter((piece) => piece.length > 0);
|
|
600
|
-
}
|
|
601
|
-
|
|
602
|
-
/**
|
|
603
|
-
* Partition a band of the frontier into groups of roughly `group_size`.
|
|
604
|
-
*
|
|
605
|
-
* The partition runs **inside each connected component** of the cluster graph, not across the band as
|
|
606
|
-
* a whole. Partitioning globally and splitting the disconnected parts afterwards looks equivalent and
|
|
607
|
-
* is not: a scene assembled from thousands of separate shells has a cluster graph in thousands of
|
|
608
|
-
* pieces, and a balanced global partition draws each part from several of them, so the connectivity
|
|
609
|
-
* split then shreds nearly every group down to a single cluster.
|
|
610
|
-
*
|
|
611
|
-
* What is left over after that — every cluster alone in its component — is bundled by proximity
|
|
612
|
-
* instead, because for those clusters there is no adjacency left to respect and no other way to the
|
|
613
|
-
* root.
|
|
614
|
-
*
|
|
615
|
-
* @param {VGeoFrontier} frontier
|
|
616
|
-
* @param {number[]} band cluster indices this round may consume, ascending
|
|
617
|
-
* @param {object} cluster_graph as {@link build_cluster_graph} returns
|
|
618
|
-
* @param {number} group_size
|
|
619
|
-
* @returns {number[][]} groups of at least two clusters each; clusters left out stay in the frontier
|
|
620
|
-
*/
|
|
621
|
-
function partition_into_groups(frontier, band, cluster_graph, group_size) {
|
|
622
|
-
const membership = new Int32Array(frontier.clusters.length).fill(-1);
|
|
623
|
-
|
|
624
|
-
for (const cluster of band) {
|
|
625
|
-
membership[cluster] = 0;
|
|
626
|
-
}
|
|
627
|
-
|
|
628
|
-
const components = vgeo_graph_components(
|
|
629
|
-
cluster_graph.edge_addresses, cluster_graph.adjacency, band, membership, 0
|
|
630
|
-
);
|
|
631
|
-
|
|
632
|
-
const result = [];
|
|
633
|
-
|
|
634
|
-
/**
|
|
635
|
-
* Clusters with nobody adjacent to merge with.
|
|
636
|
-
* @type {number[]}
|
|
637
|
-
*/
|
|
638
|
-
const loose = [];
|
|
639
|
-
|
|
640
|
-
let token = 0;
|
|
641
|
-
|
|
642
|
-
/**
|
|
643
|
-
* @param {number[]} piece
|
|
644
|
-
*/
|
|
645
|
-
function accept(piece) {
|
|
646
|
-
if (piece.length < 2) {
|
|
647
|
-
loose.push(piece[0]);
|
|
648
|
-
|
|
649
|
-
return;
|
|
650
|
-
}
|
|
651
|
-
|
|
652
|
-
// The format caps a group at 4096 clusters. Nothing the size policy produces comes near it,
|
|
653
|
-
// but the cap is hard, so the chunking is here rather than assumed away.
|
|
654
|
-
for (let start = 0; start < piece.length; start += VGEO_MAX_GROUP_CLUSTER_COUNT) {
|
|
655
|
-
const chunk = piece.slice(start, start + VGEO_MAX_GROUP_CLUSTER_COUNT);
|
|
656
|
-
|
|
657
|
-
if (chunk.length < 2) {
|
|
658
|
-
loose.push(chunk[0]);
|
|
659
|
-
|
|
660
|
-
continue;
|
|
661
|
-
}
|
|
662
|
-
|
|
663
|
-
result.push(chunk);
|
|
664
|
-
}
|
|
665
|
-
}
|
|
666
|
-
|
|
667
|
-
for (const component of components) {
|
|
668
|
-
if (component.length < 2) {
|
|
669
|
-
// Alone in its component: no adjacency left anywhere, so proximity is the only rule that
|
|
670
|
-
// can still place it.
|
|
671
|
-
loose.push(component[0]);
|
|
672
|
-
|
|
673
|
-
continue;
|
|
674
|
-
}
|
|
675
|
-
|
|
676
|
-
if (component.length <= group_size) {
|
|
677
|
-
accept(component);
|
|
678
|
-
|
|
679
|
-
continue;
|
|
680
|
-
}
|
|
681
|
-
|
|
682
|
-
/**
|
|
683
|
-
* @type {number[][]}
|
|
684
|
-
*/
|
|
685
|
-
const pieces = [];
|
|
686
|
-
|
|
687
|
-
const part_count = Math.ceil(component.length / group_size);
|
|
688
|
-
|
|
689
|
-
token++;
|
|
690
|
-
|
|
691
|
-
for (const cluster of component) {
|
|
692
|
-
membership[cluster] = token;
|
|
693
|
-
}
|
|
694
|
-
|
|
695
|
-
const local_index = new Map();
|
|
696
|
-
|
|
697
|
-
component.forEach((cluster, index) => local_index.set(cluster, index));
|
|
698
|
-
|
|
699
|
-
const local_addresses = new Uint32Array(component.length + 1);
|
|
700
|
-
const local_adjacency = [];
|
|
701
|
-
const local_weights = [];
|
|
702
|
-
|
|
703
|
-
component.forEach((cluster, index) => {
|
|
704
|
-
local_addresses[index] = local_adjacency.length;
|
|
705
|
-
|
|
706
|
-
for (let i = cluster_graph.edge_addresses[cluster]; i < cluster_graph.edge_addresses[cluster + 1]; i++) {
|
|
707
|
-
const other = cluster_graph.adjacency[i];
|
|
708
|
-
|
|
709
|
-
if (membership[other] !== token) {
|
|
710
|
-
continue;
|
|
711
|
-
}
|
|
712
|
-
|
|
713
|
-
local_adjacency.push(local_index.get(other));
|
|
714
|
-
local_weights.push(cluster_graph.edge_weights[i]);
|
|
715
|
-
}
|
|
716
|
-
});
|
|
717
|
-
|
|
718
|
-
local_addresses[component.length] = local_adjacency.length;
|
|
719
|
-
|
|
720
|
-
const assignment = vgeo_partition_graph(
|
|
721
|
-
component.length,
|
|
722
|
-
part_count,
|
|
723
|
-
local_addresses,
|
|
724
|
-
Uint32Array.from(local_adjacency),
|
|
725
|
-
Float32Array.from(local_weights),
|
|
726
|
-
GROUP_SEED
|
|
727
|
-
);
|
|
728
|
-
|
|
729
|
-
const parts = [];
|
|
730
|
-
|
|
731
|
-
for (let i = 0; i < part_count; i++) {
|
|
732
|
-
parts.push([]);
|
|
733
|
-
}
|
|
734
|
-
|
|
735
|
-
component.forEach((cluster, index) => {
|
|
736
|
-
parts[assignment[index]].push(cluster);
|
|
737
|
-
});
|
|
738
|
-
|
|
739
|
-
for (const part of parts) {
|
|
740
|
-
if (part.length === 0) {
|
|
741
|
-
continue;
|
|
742
|
-
}
|
|
743
|
-
|
|
744
|
-
token++;
|
|
745
|
-
|
|
746
|
-
for (const cluster of part) {
|
|
747
|
-
membership[cluster] = token;
|
|
748
|
-
}
|
|
749
|
-
|
|
750
|
-
for (const piece of vgeo_graph_components(
|
|
751
|
-
cluster_graph.edge_addresses, cluster_graph.adjacency, part, membership, token
|
|
752
|
-
)) {
|
|
753
|
-
pieces.push(piece);
|
|
754
|
-
}
|
|
755
|
-
}
|
|
756
|
-
|
|
757
|
-
for (const piece of absorb_singleton_pieces(pieces, cluster_graph)) {
|
|
758
|
-
accept(piece);
|
|
759
|
-
}
|
|
760
|
-
}
|
|
761
|
-
|
|
762
|
-
for (const bundle of bundle_loose_clusters(frontier, loose, group_size)) {
|
|
763
|
-
result.push(bundle);
|
|
764
|
-
}
|
|
765
|
-
|
|
766
|
-
result.sort((a, b) => a[0] - b[0]);
|
|
767
|
-
|
|
768
|
-
return result;
|
|
769
|
-
}
|
|
770
|
-
|
|
771
|
-
/**
|
|
772
|
-
* Does any face outside this group use the edge?
|
|
773
|
-
*
|
|
774
|
-
* @param {BinaryTopology} topology
|
|
775
|
-
* @param {number} edge
|
|
776
|
-
* @param {Int32Array} group_of_face
|
|
777
|
-
* @param {number} group_index
|
|
778
|
-
* @returns {boolean}
|
|
779
|
-
*/
|
|
780
|
-
function edge_touches_outside(topology, edge, group_of_face, group_index) {
|
|
781
|
-
const first_loop = topology.edge_read_loop(edge);
|
|
782
|
-
|
|
783
|
-
if (first_loop === NULL_POINTER) {
|
|
784
|
-
return false;
|
|
785
|
-
}
|
|
786
|
-
|
|
787
|
-
let loop = first_loop;
|
|
788
|
-
|
|
789
|
-
do {
|
|
790
|
-
const face = topology.loop_read_face(loop);
|
|
791
|
-
|
|
792
|
-
if (face !== NULL_POINTER && group_of_face[face] !== group_index) {
|
|
793
|
-
return true;
|
|
794
|
-
}
|
|
795
|
-
|
|
796
|
-
loop = topology.loop_read_radial_next(loop);
|
|
797
|
-
} while (loop !== first_loop && loop !== NULL_POINTER);
|
|
798
|
-
|
|
799
|
-
return false;
|
|
800
|
-
}
|
|
801
|
-
|
|
802
|
-
/**
|
|
803
|
-
* Does any face outside this group use the vertex? Walks the vertex's disk cycle and each edge's
|
|
804
|
-
* radial cycle, so the answer is local to the vertex rather than a scan of the frontier.
|
|
805
|
-
*
|
|
806
|
-
* @param {BinaryTopology} topology
|
|
807
|
-
* @param {number} vertex
|
|
808
|
-
* @param {Int32Array} group_of_face
|
|
809
|
-
* @param {number} group_index
|
|
810
|
-
* @returns {boolean}
|
|
811
|
-
*/
|
|
812
|
-
function vertex_touches_outside(topology, vertex, group_of_face, group_index) {
|
|
813
|
-
const first_edge = topology.vertex_read_edge(vertex);
|
|
814
|
-
|
|
815
|
-
if (first_edge === NULL_POINTER) {
|
|
816
|
-
return false;
|
|
817
|
-
}
|
|
818
|
-
|
|
819
|
-
let edge = first_edge;
|
|
820
|
-
|
|
821
|
-
do {
|
|
822
|
-
if (edge_touches_outside(topology, edge, group_of_face, group_index)) {
|
|
823
|
-
return true;
|
|
824
|
-
}
|
|
825
|
-
|
|
826
|
-
edge = (topology.edge_read_vertex1(edge) === vertex)
|
|
827
|
-
? topology.edge_read_v1_disk_next(edge)
|
|
828
|
-
: topology.edge_read_v2_disk_next(edge);
|
|
829
|
-
} while (edge !== first_edge && edge !== NULL_POINTER);
|
|
830
|
-
|
|
831
|
-
return false;
|
|
832
|
-
}
|
|
833
|
-
|
|
834
|
-
/**
|
|
835
|
-
* Which vertices a group must not move, and which boundary edges must survive its simplification.
|
|
836
|
-
*
|
|
837
|
-
* A vertex is locked when any face outside the group touches it — not merely when it sits on a
|
|
838
|
-
* shared *edge*. The weaker rule leaks: two groups can meet at a single non-manifold vertex with no
|
|
839
|
-
* edge between them, and each would then move that vertex somewhere else, tearing the surface at a
|
|
840
|
-
* point no boundary walk would have flagged.
|
|
841
|
-
*
|
|
842
|
-
* "Outside" includes frontier clusters this round left alone, which is what keeps a band's seam with
|
|
843
|
-
* the rest of the frontier from opening.
|
|
844
|
-
*
|
|
845
|
-
* @param {BinaryTopology} topology
|
|
846
|
-
* @param {number[]} group_face_ids
|
|
847
|
-
* @param {Int32Array} group_of_face one entry per face slot, holding the group each face belongs to
|
|
848
|
-
* @param {number} group_index
|
|
849
|
-
* @returns {{locked: Set<number>, polyline: number[][]}} locked vertex ids, and the `[v1, v2]` pairs
|
|
850
|
-
* of every edge this group shares with the outside
|
|
851
|
-
*/
|
|
852
|
-
function compute_group_boundary(topology, group_face_ids, group_of_face, group_index) {
|
|
853
|
-
const vertices = new Set();
|
|
854
|
-
const edges = new Set();
|
|
855
|
-
|
|
856
|
-
for (const face of group_face_ids) {
|
|
857
|
-
const first_loop = topology.face_read_loop(face);
|
|
858
|
-
|
|
859
|
-
let loop = first_loop;
|
|
860
|
-
|
|
861
|
-
do {
|
|
862
|
-
vertices.add(topology.loop_read_vertex(loop));
|
|
863
|
-
edges.add(topology.loop_read_edge(loop));
|
|
864
|
-
|
|
865
|
-
loop = topology.loop_read_next(loop);
|
|
866
|
-
} while (loop !== first_loop);
|
|
867
|
-
}
|
|
868
|
-
|
|
869
|
-
const locked = new Set();
|
|
870
|
-
|
|
871
|
-
for (const vertex of vertices) {
|
|
872
|
-
if (vertex_touches_outside(topology, vertex, group_of_face, group_index)) {
|
|
873
|
-
locked.add(vertex);
|
|
874
|
-
}
|
|
875
|
-
}
|
|
876
|
-
|
|
877
|
-
const polyline = [];
|
|
878
|
-
|
|
879
|
-
for (const edge of edges) {
|
|
880
|
-
if (!edge_touches_outside(topology, edge, group_of_face, group_index)) {
|
|
881
|
-
continue;
|
|
882
|
-
}
|
|
883
|
-
|
|
884
|
-
polyline.push([topology.edge_read_vertex1(edge), topology.edge_read_vertex2(edge)]);
|
|
885
|
-
}
|
|
886
|
-
|
|
887
|
-
return { locked, polyline };
|
|
888
|
-
}
|
|
889
|
-
|
|
890
|
-
/**
|
|
891
|
-
* Largest error any of a group's clusters carries -- the floor a step above them must respect.
|
|
892
|
-
*
|
|
893
|
-
* @param {VirtualGeometryCluster[]} clusters
|
|
894
|
-
* @returns {number}
|
|
895
|
-
*/
|
|
896
|
-
function max_child_error(clusters) {
|
|
897
|
-
let result = 0;
|
|
898
|
-
|
|
899
|
-
for (const cluster of clusters) {
|
|
900
|
-
result = Math.max(result, cluster.self_error);
|
|
901
|
-
}
|
|
902
|
-
|
|
903
|
-
return result;
|
|
904
|
-
}
|
|
905
|
-
|
|
906
|
-
/**
|
|
907
|
-
* Leaves with room to spare between them, re-cut into fewer and fuller leaves.
|
|
908
|
-
*
|
|
909
|
-
* This is not a level of the DAG and must not become one. Nothing is simplified, so the new clusters
|
|
910
|
-
* carry the same error their inputs did -- zero -- and a step whose parent error equals its child
|
|
911
|
-
* error is a step the cut rule can never take: a cluster is drawn only while `project(parent) > tau`,
|
|
912
|
-
* and a parent at zero error projects to zero unconditionally. The leaves under it would be stored,
|
|
913
|
-
* paged, and never once selected. So the re-cut *replaces* them; they were a way of cutting this
|
|
914
|
-
* geometry up, and now there is a better one.
|
|
915
|
-
*
|
|
916
|
-
* It is what makes a scene of small separate parts tractable. A thousand twelve-triangle bolts are a
|
|
917
|
-
* thousand clusters at eight per cent fill, and no simplification is what they need -- they need to
|
|
918
|
-
* be cut up differently. Repacking is bounded by the grouping that proposed it, so what lands in one
|
|
919
|
-
* cluster is what the adjacency and proximity rules already judged to belong together.
|
|
920
|
-
*
|
|
921
|
-
* Only leaves. Above them the same re-cut would orphan a subtree, since a coarser cluster cannot be
|
|
922
|
-
* deleted -- something hangs off it.
|
|
923
|
-
*
|
|
924
|
-
* @param {VGeoFrontier} frontier
|
|
925
|
-
* @param {number[]} group_clusters cluster indices
|
|
926
|
-
* @param {number} parent_target how many clusters the group is aiming to become
|
|
927
|
-
* @returns {GroupRepack|undefined} `undefined` when the group must be simplified instead
|
|
928
|
-
*/
|
|
929
|
-
function try_repack(frontier, group_clusters, parent_target) {
|
|
930
|
-
const face_ids = [];
|
|
931
|
-
|
|
932
|
-
for (const cluster of group_clusters) {
|
|
933
|
-
if (frontier.clusters[cluster].child_group !== undefined) {
|
|
934
|
-
return undefined;
|
|
935
|
-
}
|
|
936
|
-
|
|
937
|
-
for (const face of frontier.cluster_faces[cluster]) {
|
|
938
|
-
face_ids.push(face);
|
|
939
|
-
}
|
|
940
|
-
}
|
|
941
|
-
|
|
942
|
-
// Only when the target is already met -- there is nothing to simplify, so the reduction has to
|
|
943
|
-
// come from re-cutting. A group over its target has real geometry to lose and belongs on the
|
|
944
|
-
// simplification path.
|
|
945
|
-
if (face_ids.length > parent_target * VGEO_MAX_CLUSTER_TRIANGLE_COUNT) {
|
|
946
|
-
return undefined;
|
|
947
|
-
}
|
|
948
|
-
|
|
949
|
-
face_ids.sort((a, b) => a - b);
|
|
950
|
-
|
|
951
|
-
const submesh = vgeo_extract_submesh(frontier.mesh, face_ids);
|
|
952
|
-
|
|
953
|
-
const face_graph = new VGeoFaceGraph(
|
|
954
|
-
submesh.mesh.topology, bt_mesh_build_face_graph(submesh.mesh.topology)
|
|
955
|
-
);
|
|
956
|
-
|
|
957
|
-
/**
|
|
958
|
-
*
|
|
959
|
-
* @type {number[]}
|
|
960
|
-
*/
|
|
961
|
-
const all = [];
|
|
962
|
-
|
|
963
|
-
const vertex_count = face_graph.graph.vertex_count;
|
|
964
|
-
for (let vertex = 0; vertex < vertex_count; vertex++) {
|
|
965
|
-
all.push(vertex);
|
|
966
|
-
}
|
|
967
|
-
|
|
968
|
-
const parts = vgeo_split_face_set_packed(
|
|
969
|
-
face_graph, all, VGEO_MAX_CLUSTER_TRIANGLE_COUNT, VGEO_MAX_CLUSTER_VERTEX_COUNT
|
|
970
|
-
);
|
|
971
|
-
|
|
972
|
-
if (parts.length >= group_clusters.length) {
|
|
973
|
-
// The vertex ceiling bit before the triangle one, and re-cutting bought nothing.
|
|
974
|
-
return undefined;
|
|
975
|
-
}
|
|
976
|
-
|
|
977
|
-
const repack = new GroupRepack();
|
|
978
|
-
|
|
979
|
-
repack.replaced = group_clusters.map((cluster) => frontier.clusters[cluster]);
|
|
980
|
-
|
|
981
|
-
for (const part of parts) {
|
|
982
|
-
const part_face_ids = part.map((dense) => face_graph.vertex_face_ids[dense]);
|
|
983
|
-
|
|
984
|
-
part_face_ids.sort((a, b) => a - b);
|
|
985
|
-
|
|
986
|
-
const built = build_cluster(submesh.mesh, part_face_ids, 0);
|
|
987
|
-
|
|
988
|
-
// A leaf's own bounds are its LOD-metric sphere, and it deviates from nothing.
|
|
989
|
-
built.cluster.self_sphere = built.cluster.bounds_sphere.slice();
|
|
990
|
-
built.cluster.self_error = 0;
|
|
991
|
-
|
|
992
|
-
repack.clusters.push(built.cluster);
|
|
993
|
-
|
|
994
|
-
repack.geometry.push({
|
|
995
|
-
positions: built.gathered.positions,
|
|
996
|
-
indices: built.gathered.indices,
|
|
997
|
-
local_vertex_ids: built.gathered.vertex_ids,
|
|
998
|
-
source_vertex_ids: Uint32Array.from(
|
|
999
|
-
built.gathered.vertex_ids, (local) => submesh.source_vertex_ids[local]
|
|
1000
|
-
),
|
|
1001
|
-
topology: submesh.mesh.topology,
|
|
1002
|
-
});
|
|
1003
|
-
}
|
|
1004
|
-
|
|
1005
|
-
return repack;
|
|
1006
|
-
}
|
|
1007
|
-
|
|
1008
|
-
/**
|
|
1009
|
-
* Simplify one group and cut the result into the parent clusters of every cluster in it.
|
|
1010
|
-
*
|
|
1011
|
-
* @param {VGeoFrontier} frontier
|
|
1012
|
-
* @param {number[]} group_clusters cluster indices
|
|
1013
|
-
* @param {Int32Array} group_of_face one entry per face slot, holding the group each face belongs to
|
|
1014
|
-
* @param {number} group_index
|
|
1015
|
-
* @param {number} level of the parents this step produces
|
|
1016
|
-
* @param {VGeoLevelOptions} options
|
|
1017
|
-
* @returns {GroupStep|undefined} `undefined` when the group has no coarser form at all, and the
|
|
1018
|
-
* round should leave its clusters alone
|
|
1019
|
-
*/
|
|
1020
|
-
function build_group_step(frontier, group_clusters, group_of_face, group_index, level, options) {
|
|
1021
|
-
/**
|
|
1022
|
-
*
|
|
1023
|
-
* @type {number[]}
|
|
1024
|
-
*/
|
|
1025
|
-
const face_ids = [];
|
|
1026
|
-
|
|
1027
|
-
for (const cluster of group_clusters) {
|
|
1028
|
-
for (const face of frontier.cluster_faces[cluster]) {
|
|
1029
|
-
face_ids.push(face);
|
|
1030
|
-
}
|
|
1031
|
-
}
|
|
1032
|
-
|
|
1033
|
-
face_ids.sort((a, b) => a - b);
|
|
1034
|
-
|
|
1035
|
-
const child_clusters = group_clusters.map((cluster) => frontier.clusters[cluster]);
|
|
1036
|
-
|
|
1037
|
-
const boundary = compute_group_boundary(
|
|
1038
|
-
frontier.mesh.topology, face_ids, group_of_face, group_index
|
|
1039
|
-
);
|
|
1040
|
-
|
|
1041
|
-
/**
|
|
1042
|
-
* Extract the group, simplify it to `target_face_count`, and cut the result into clusters.
|
|
1043
|
-
*
|
|
1044
|
-
* @param {number} target_face_count
|
|
1045
|
-
* @returns {{submesh: VGeoSubmesh, face_graph: VGeoFaceGraph, parts: number[][],
|
|
1046
|
-
* quadrics: Quadric3[], error: number}}
|
|
1047
|
-
*/
|
|
1048
|
-
function simplify_to(target_face_count) {
|
|
1049
|
-
const submesh = vgeo_extract_submesh(frontier.mesh, face_ids);
|
|
1050
|
-
|
|
1051
|
-
// Cloned, not borrowed: the simplifier mutates these, and this whole attempt may be thrown
|
|
1052
|
-
// away — by the polyline check below, by the round rejecting it, or by a wider band being
|
|
1053
|
-
// tried instead. The frontier's own quadrics must survive all of that untouched.
|
|
1054
|
-
const submesh_source_vertex_ids = submesh.source_vertex_ids;
|
|
1055
|
-
const source_vertex_id_count = submesh_source_vertex_ids.length;
|
|
1056
|
-
|
|
1057
|
-
/**
|
|
1058
|
-
*
|
|
1059
|
-
* @type {Quadric3[]}
|
|
1060
|
-
*/
|
|
1061
|
-
const quadrics = new Array(source_vertex_id_count);
|
|
1062
|
-
|
|
1063
|
-
for (let i = 0; i < source_vertex_id_count; i++) {
|
|
1064
|
-
const source = submesh_source_vertex_ids[i];
|
|
1065
|
-
quadrics[i] = frontier.quadrics[source].clone();
|
|
1066
|
-
}
|
|
1067
|
-
|
|
1068
|
-
/**
|
|
1069
|
-
* `source vertex id -> submesh local id`, for translating the lock set and the polyline.
|
|
1070
|
-
* @type {Map<number, number>}
|
|
1071
|
-
*/
|
|
1072
|
-
const local_of_source = new Map();
|
|
1073
|
-
|
|
1074
|
-
for (let local = 0; local < source_vertex_id_count; local++) {
|
|
1075
|
-
const source = submesh_source_vertex_ids[local];
|
|
1076
|
-
local_of_source.set(source, local);
|
|
1077
|
-
}
|
|
1078
|
-
|
|
1079
|
-
/**
|
|
1080
|
-
*
|
|
1081
|
-
* @type {Set<number>}
|
|
1082
|
-
*/
|
|
1083
|
-
const locked = new Set();
|
|
1084
|
-
|
|
1085
|
-
for (const vertex of boundary.locked) {
|
|
1086
|
-
const local = local_of_source.get(vertex);
|
|
1087
|
-
|
|
1088
|
-
if (local !== undefined) {
|
|
1089
|
-
locked.add(local);
|
|
1090
|
-
}
|
|
1091
|
-
}
|
|
1092
|
-
|
|
1093
|
-
// The faces carrying the group's boundary edges. Locking the endpoints keeps them where they
|
|
1094
|
-
// are; protecting these faces is what keeps an edge *between* two locked vertices from
|
|
1095
|
-
// losing the only face that holds it — see the polyline check below, which is the same
|
|
1096
|
-
// guarantee stated as an assertion.
|
|
1097
|
-
|
|
1098
|
-
/**
|
|
1099
|
-
*
|
|
1100
|
-
* @type {Set<number>}
|
|
1101
|
-
*/
|
|
1102
|
-
const protected_faces = new Set();
|
|
1103
|
-
|
|
1104
|
-
for (const [v1, v2] of boundary.polyline) {
|
|
1105
|
-
const edge = bt_edge_get(
|
|
1106
|
-
submesh.mesh.topology, local_of_source.get(v1), local_of_source.get(v2)
|
|
1107
|
-
);
|
|
1108
|
-
|
|
1109
|
-
if (edge === NULL_POINTER) {
|
|
1110
|
-
continue;
|
|
1111
|
-
}
|
|
1112
|
-
|
|
1113
|
-
const first_loop = submesh.mesh.topology.edge_read_loop(edge);
|
|
1114
|
-
|
|
1115
|
-
let loop = first_loop;
|
|
1116
|
-
|
|
1117
|
-
do {
|
|
1118
|
-
const face = submesh.mesh.topology.loop_read_face(loop);
|
|
1119
|
-
|
|
1120
|
-
if (face !== NULL_POINTER) {
|
|
1121
|
-
protected_faces.add(face);
|
|
1122
|
-
}
|
|
1123
|
-
|
|
1124
|
-
loop = submesh.mesh.topology.loop_read_radial_next(loop);
|
|
1125
|
-
} while (loop !== first_loop && loop !== NULL_POINTER);
|
|
1126
|
-
}
|
|
1127
|
-
|
|
1128
|
-
// The simplifier measures this itself, and has to: a cluster consumed whole leaves a
|
|
1129
|
-
// vertex behind that belongs to no face and is about to be swept away, and that vertex is
|
|
1130
|
-
// the only thing still holding the quadric of what used to be there. Measuring survivors
|
|
1131
|
-
// from out here would report a soup of separate triangles losing most of itself as free.
|
|
1132
|
-
const deviation = bt_mesh_simplify_with_quadrics(
|
|
1133
|
-
submesh.mesh.topology, target_face_count, locked, protected_faces, quadrics
|
|
1134
|
-
);
|
|
1135
|
-
|
|
1136
|
-
const defects = bt_mesh_validate(submesh.mesh.topology);
|
|
1137
|
-
|
|
1138
|
-
if (defects.length > 0) {
|
|
1139
|
-
throw new Error(
|
|
1140
|
-
`simplifying a group of ${group_clusters.length} clusters left the mesh inconsistent: `
|
|
1141
|
-
+ defects.slice(0, 4).map((defect) => defect.detail).join('; ')
|
|
1142
|
-
);
|
|
1143
|
-
}
|
|
1144
|
-
|
|
1145
|
-
// The design asks for this explicitly: pinning a vertex does not by itself protect the
|
|
1146
|
-
// *edges* between pinned vertices, and a face-kill path can strip one. A missing boundary
|
|
1147
|
-
// edge is a crack between two groups that will never close, so the build stops here rather
|
|
1148
|
-
// than shipping it.
|
|
1149
|
-
for (const [v1, v2] of boundary.polyline) {
|
|
1150
|
-
const local_1 = local_of_source.get(v1);
|
|
1151
|
-
const local_2 = local_of_source.get(v2);
|
|
1152
|
-
|
|
1153
|
-
if (
|
|
1154
|
-
local_1 === undefined
|
|
1155
|
-
|| local_2 === undefined
|
|
1156
|
-
|| bt_edge_get(submesh.mesh.topology, local_1, local_2) === NULL_POINTER
|
|
1157
|
-
) {
|
|
1158
|
-
throw new Error(
|
|
1159
|
-
`group boundary edge (${v1}, ${v2}) did not survive simplification of a group of `
|
|
1160
|
-
+ `${group_clusters.length} clusters`
|
|
1161
|
-
);
|
|
1162
|
-
}
|
|
1163
|
-
}
|
|
1164
|
-
|
|
1165
|
-
/*
|
|
1166
|
-
The step moved the geometry, so the frame that describes it has to move with it — but only
|
|
1167
|
-
where the step was free to move anything. `locked` is the group's perimeter, the vertices
|
|
1168
|
-
every neighbouring group also carries, and it is what a cut leans on to put two levels of
|
|
1169
|
-
detail side by side without a crack. Derived here, a perimeter vertex would get the average
|
|
1170
|
-
of this group's faces alone while its neighbour got the average of that group's, and the
|
|
1171
|
-
seam that is watertight in geometry would show as a crease in shading.
|
|
1172
|
-
*/
|
|
1173
|
-
vgeo_compute_vertex_normals(submesh.mesh, locked);
|
|
1174
|
-
vgeo_compute_vertex_tangents(submesh.mesh, locked);
|
|
1175
|
-
|
|
1176
|
-
const face_graph = new VGeoFaceGraph(
|
|
1177
|
-
submesh.mesh.topology, bt_mesh_build_face_graph(submesh.mesh.topology)
|
|
1178
|
-
);
|
|
1179
|
-
|
|
1180
|
-
const all = [];
|
|
1181
|
-
|
|
1182
|
-
for (let vertex = 0; vertex < face_graph.graph.vertex_count; vertex++) {
|
|
1183
|
-
all.push(vertex);
|
|
1184
|
-
}
|
|
1185
|
-
|
|
1186
|
-
// Packed: this is a group, so its extent is already the group's, and one cluster per
|
|
1187
|
-
// surviving island would undo the merge the group exists to make.
|
|
1188
|
-
const parts = vgeo_split_face_set_packed(
|
|
1189
|
-
face_graph, all, VGEO_MAX_CLUSTER_TRIANGLE_COUNT, VGEO_MAX_CLUSTER_VERTEX_COUNT
|
|
1190
|
-
);
|
|
1191
|
-
|
|
1192
|
-
return { submesh, face_graph, parts, quadrics, error: deviation };
|
|
1193
|
-
}
|
|
1194
|
-
|
|
1195
|
-
// Aim at a number of *clusters*, not a fraction of the triangles. Halving the triangle count is
|
|
1196
|
-
// a means; halving the cluster count is the end, and asking for it directly is what keeps
|
|
1197
|
-
// parents full. Where the children are already full the two coincide exactly. Where they are
|
|
1198
|
-
// not, this simplifies less — because the reduction is coming from packing the geometry better
|
|
1199
|
-
// rather than from destroying it, which is the outcome to prefer.
|
|
1200
|
-
const parent_target = Math.max(
|
|
1201
|
-
1, Math.ceil(group_clusters.length * options.simplification_ratio)
|
|
1202
|
-
);
|
|
1203
|
-
|
|
1204
|
-
const outcome = simplify_to(Math.min(
|
|
1205
|
-
parent_target * VGEO_MAX_CLUSTER_TRIANGLE_COUNT,
|
|
1206
|
-
// A group of under-filled clusters can already be under the cluster-count target, in which
|
|
1207
|
-
// case nothing would be simplified and the step would record no error — a step the cut rule
|
|
1208
|
-
// cannot take. Falling back to the triangle ratio keeps it a real simplification.
|
|
1209
|
-
Math.max(2, Math.round(face_ids.length * options.simplification_ratio))
|
|
1210
|
-
));
|
|
1211
|
-
|
|
1212
|
-
if (outcome.parts.length === 0) {
|
|
1213
|
-
// Some small closed shells have no coarser form under edge collapse at all — a tetrahedron
|
|
1214
|
-
// loses two faces to the collapse and its other two to the fuse that follows, leaving
|
|
1215
|
-
// nothing. Nothing to build a parent from, so the group is refused and its clusters go back
|
|
1216
|
-
// to the frontier to be grouped differently.
|
|
1217
|
-
return undefined;
|
|
1218
|
-
}
|
|
1219
|
-
|
|
1220
|
-
const submesh = outcome.submesh;
|
|
1221
|
-
const submesh_face_graph = outcome.face_graph;
|
|
1222
|
-
const parts = outcome.parts;
|
|
1223
|
-
|
|
1224
|
-
const step = new GroupStep();
|
|
1225
|
-
|
|
1226
|
-
step.child_clusters = child_clusters;
|
|
1227
|
-
|
|
1228
|
-
// Floored by what the children already carried. The accumulated quadric is a deviation from the
|
|
1229
|
-
// source and nothing forces it to rise when the vertex carrying the previous maximum is the one
|
|
1230
|
-
// collapsed away — but a parent that claimed to be closer to the source than its own child would
|
|
1231
|
-
// make the cut oscillate, so monotonicity is imposed here rather than hoped for.
|
|
1232
|
-
step.error = Math.max(outcome.error, max_child_error(child_clusters));
|
|
1233
|
-
|
|
1234
|
-
submesh.source_vertex_ids.forEach((source, local) => {
|
|
1235
|
-
if (outcome.quadrics[local] !== null) {
|
|
1236
|
-
step.quadrics.set(source, outcome.quadrics[local]);
|
|
1237
|
-
}
|
|
1238
|
-
});
|
|
1239
|
-
|
|
1240
|
-
const parent_points = [];
|
|
1241
|
-
|
|
1242
|
-
for (const part of parts) {
|
|
1243
|
-
const part_face_ids = part.map((dense) => submesh_face_graph.vertex_face_ids[dense]);
|
|
1244
|
-
|
|
1245
|
-
part_face_ids.sort((a, b) => a - b);
|
|
1246
|
-
|
|
1247
|
-
const built = build_cluster(submesh.mesh, part_face_ids, level);
|
|
1248
|
-
|
|
1249
|
-
step.parent_clusters.push(built.cluster);
|
|
1250
|
-
|
|
1251
|
-
step.parent_geometry.push({
|
|
1252
|
-
positions: built.gathered.positions,
|
|
1253
|
-
indices: built.gathered.indices,
|
|
1254
|
-
local_vertex_ids: built.gathered.vertex_ids,
|
|
1255
|
-
source_vertex_ids: Uint32Array.from(
|
|
1256
|
-
built.gathered.vertex_ids, (local) => submesh.source_vertex_ids[local]
|
|
1257
|
-
),
|
|
1258
|
-
topology: submesh.mesh.topology,
|
|
1259
|
-
});
|
|
1260
|
-
|
|
1261
|
-
parent_points.push(built.gathered.positions);
|
|
1262
|
-
}
|
|
1263
|
-
|
|
1264
|
-
let point_count = 0;
|
|
1265
|
-
|
|
1266
|
-
for (const points of parent_points) {
|
|
1267
|
-
point_count += points.length;
|
|
1268
|
-
}
|
|
1269
|
-
|
|
1270
|
-
const points = new Float32Array(point_count);
|
|
1271
|
-
|
|
1272
|
-
let cursor = 0;
|
|
1273
|
-
|
|
1274
|
-
for (const part of parent_points) {
|
|
1275
|
-
points.set(part, cursor);
|
|
1276
|
-
|
|
1277
|
-
cursor += part.length;
|
|
1278
|
-
}
|
|
1279
|
-
|
|
1280
|
-
// One sphere for the whole step, containing both what it consumed and what it produced. Sharing
|
|
1281
|
-
// it is what makes every sibling project the same value, and containing the children is what
|
|
1282
|
-
// makes the pair monotonic.
|
|
1283
|
-
step.sphere = vgeo_containing_sphere(
|
|
1284
|
-
step.child_clusters.map((cluster) => cluster.self_sphere),
|
|
1285
|
-
points
|
|
1286
|
-
);
|
|
1287
|
-
|
|
1288
|
-
return step;
|
|
1289
|
-
}
|
|
1290
|
-
|
|
1291
|
-
/**
|
|
1292
|
-
* Stitch a round's output back into one mesh: freshly simplified clusters, and the clusters the
|
|
1293
|
-
* round left alone, side by side.
|
|
1294
|
-
*
|
|
1295
|
-
* Vertices are unified by the id they had in the frontier below, never by position: a vertex two
|
|
1296
|
-
* groups share is pinned, so both carry it unmoved and unmistakably, while a vertex only one group
|
|
1297
|
-
* holds is its own even if a collapse happened to place it exactly where another landed. An
|
|
1298
|
-
* untouched cluster names its vertices in that same space, so it stitches to its simplified
|
|
1299
|
-
* neighbour along exactly the boundary that neighbour was forbidden to move.
|
|
1300
|
-
*
|
|
1301
|
-
* @param {VGeoContribution[]} contributions parallel to the clusters of the frontier being built
|
|
1302
|
-
* @param {(BinaryDataLayer|null)[]} template_layers the channels the frontier below carries, which
|
|
1303
|
-
* the frontier being stitched carries too
|
|
1304
|
-
* @returns {{mesh: VirtualGeometryMesh, cluster_faces: number[][], source_vertex_ids: Uint32Array}}
|
|
1305
|
-
* the last of which names, for every vertex of the new mesh, the vertex of the old one it came
|
|
1306
|
-
* from — which is how its accumulated quadric follows it across the seam
|
|
1307
|
-
*/
|
|
1308
|
-
function stitch_frontier(contributions, template_layers) {
|
|
1309
|
-
/**
|
|
1310
|
-
* `source vertex id -> index in the frontier being built`.
|
|
1311
|
-
* @type {Map<number, number>}
|
|
1312
|
-
*/
|
|
1313
|
-
const index_of_source = new Map();
|
|
1314
|
-
|
|
1315
|
-
const positions = [];
|
|
1316
|
-
const indices = [];
|
|
1317
|
-
const cluster_faces = [];
|
|
1318
|
-
|
|
1319
|
-
/**
|
|
1320
|
-
* `[mesh the vertex came from, its id there]` per new vertex, so its attribute record can be
|
|
1321
|
-
* copied across once the topology that receives it exists.
|
|
1322
|
-
* @type {[BinaryTopology, number][]}
|
|
1323
|
-
*/
|
|
1324
|
-
const attribute_sources = [];
|
|
1325
|
-
|
|
1326
|
-
let face_cursor = 0;
|
|
1327
|
-
|
|
1328
|
-
for (const geometry of contributions) {
|
|
1329
|
-
const remap = new Uint32Array(geometry.local_vertex_ids.length);
|
|
1330
|
-
|
|
1331
|
-
geometry.local_vertex_ids.forEach((local, i) => {
|
|
1332
|
-
const source = geometry.source_vertex_ids[i];
|
|
1333
|
-
|
|
1334
|
-
let target = index_of_source.get(source);
|
|
1335
|
-
|
|
1336
|
-
if (target === undefined) {
|
|
1337
|
-
target = positions.length / 3;
|
|
1338
|
-
|
|
1339
|
-
index_of_source.set(source, target);
|
|
1340
|
-
|
|
1341
|
-
positions.push(
|
|
1342
|
-
geometry.positions[i * 3],
|
|
1343
|
-
geometry.positions[i * 3 + 1],
|
|
1344
|
-
geometry.positions[i * 3 + 2]
|
|
1345
|
-
);
|
|
1346
|
-
|
|
1347
|
-
attribute_sources.push([geometry.topology, local]);
|
|
1348
|
-
}
|
|
1349
|
-
|
|
1350
|
-
remap[i] = target;
|
|
1351
|
-
});
|
|
1352
|
-
|
|
1353
|
-
const triangle_count = geometry.indices.length / 3;
|
|
1354
|
-
|
|
1355
|
-
const faces = [];
|
|
1356
|
-
|
|
1357
|
-
for (let i = 0; i < geometry.indices.length; i++) {
|
|
1358
|
-
indices.push(remap[geometry.indices[i]]);
|
|
1359
|
-
}
|
|
1360
|
-
|
|
1361
|
-
for (let i = 0; i < triangle_count; i++) {
|
|
1362
|
-
faces.push(face_cursor + i);
|
|
1363
|
-
}
|
|
1364
|
-
|
|
1365
|
-
face_cursor += triangle_count;
|
|
1366
|
-
|
|
1367
|
-
cluster_faces.push(faces);
|
|
1368
|
-
}
|
|
1369
|
-
|
|
1370
|
-
const topology = new BinaryTopology();
|
|
1371
|
-
|
|
1372
|
-
// Before the vertices: adding a layer widens every record already in the block, and a frontier
|
|
1373
|
-
// holds every vertex of a level.
|
|
1374
|
-
const layers = vgeo_layers_like(topology, template_layers);
|
|
1375
|
-
|
|
1376
|
-
bt_mesh_from_indexed_geometry(topology, Uint32Array.from(indices), Float32Array.from(positions));
|
|
1377
|
-
|
|
1378
|
-
attribute_sources.forEach(([source, local], target) => {
|
|
1379
|
-
vgeo_layers_copy_vertex(topology, source, local, target);
|
|
1380
|
-
});
|
|
1381
|
-
|
|
1382
|
-
const mesh = new VirtualGeometryMesh(topology, layers);
|
|
1383
|
-
|
|
1384
|
-
const source_vertex_ids = new Uint32Array(index_of_source.size);
|
|
1385
|
-
|
|
1386
|
-
for (const [source, target] of index_of_source) {
|
|
1387
|
-
source_vertex_ids[target] = source;
|
|
1388
|
-
}
|
|
1389
|
-
|
|
1390
|
-
return { mesh, cluster_faces, source_vertex_ids };
|
|
1391
|
-
}
|
|
1392
|
-
|
|
1393
|
-
/**
|
|
1394
|
-
* The frontier's clusters ordered by error, which is what a band is a slice of.
|
|
1395
|
-
*
|
|
1396
|
-
* @param {VGeoFrontier} frontier
|
|
1397
|
-
* @returns {{order: number[], errors: number[]}} cluster indices ascending by error, and their
|
|
1398
|
-
* errors in the same order
|
|
1399
|
-
*/
|
|
1400
|
-
function order_by_error(frontier) {
|
|
1401
|
-
const order = frontier.clusters.map((cluster, index) => index);
|
|
1402
|
-
|
|
1403
|
-
order.sort(
|
|
1404
|
-
(a, b) => (frontier.clusters[a].self_error - frontier.clusters[b].self_error) || (a - b)
|
|
1405
|
-
);
|
|
1406
|
-
|
|
1407
|
-
return { order, errors: order.map((index) => frontier.clusters[index].self_error) };
|
|
1408
|
-
}
|
|
1409
|
-
|
|
1410
|
-
/**
|
|
1411
|
-
* The bands a round tries, in the order it tries them.
|
|
1412
|
-
*
|
|
1413
|
-
* Each is one error *octave*: `[e, e · growth]` for each distinct error the frontier holds, finest
|
|
1414
|
-
* first. An octave rather than a running threshold is what bounds how far apart two clusters in one
|
|
1415
|
-
* group can be — a group shares a single parent error, so merging a nearly-exact cluster with a
|
|
1416
|
-
* badly-approximated one drags the exact one up to the other's error for nothing. It is also what
|
|
1417
|
-
* lets a cluster *wait*: geometry outside the octave is simply not this round's business.
|
|
1418
|
-
*
|
|
1419
|
-
* The last band is the whole frontier. It is the escape hatch, not the plan: a frontier of two
|
|
1420
|
-
* clusters an order of magnitude apart in error has no octave holding both, and merging them at a
|
|
1421
|
-
* wide error span still beats never reaching a root.
|
|
1422
|
-
*
|
|
1423
|
-
* @param {number[]} errors ascending, as {@link order_by_error} returns
|
|
1424
|
-
* @param {number} growth
|
|
1425
|
-
* @param {number} min_fraction least share of the frontier a band may cover
|
|
1426
|
-
* @returns {{start: number, end: number}[]} half-open ranges into the error order, deduplicated
|
|
1427
|
-
*/
|
|
1428
|
-
function error_bands(errors, growth, min_fraction) {
|
|
1429
|
-
/**
|
|
1430
|
-
* @param {number} value
|
|
1431
|
-
* @returns {number} first position whose error exceeds `value`
|
|
1432
|
-
*/
|
|
1433
|
-
function upper_bound(value) {
|
|
1434
|
-
let low = 0;
|
|
1435
|
-
let high = errors.length;
|
|
1436
|
-
|
|
1437
|
-
while (low < high) {
|
|
1438
|
-
const middle = (low + high) >> 1;
|
|
1439
|
-
|
|
1440
|
-
if (errors[middle] <= value) {
|
|
1441
|
-
low = middle + 1;
|
|
1442
|
-
} else {
|
|
1443
|
-
high = middle;
|
|
1444
|
-
}
|
|
1445
|
-
}
|
|
1446
|
-
|
|
1447
|
-
return low;
|
|
1448
|
-
}
|
|
1449
|
-
|
|
1450
|
-
const result = [];
|
|
1451
|
-
const seen = new Set();
|
|
1452
|
-
|
|
1453
|
-
/**
|
|
1454
|
-
* @param {number} start
|
|
1455
|
-
* @param {number} end
|
|
1456
|
-
*/
|
|
1457
|
-
function add(start, end) {
|
|
1458
|
-
const key = `${start}:${end}`;
|
|
1459
|
-
|
|
1460
|
-
if (end - start < 2 || seen.has(key)) {
|
|
1461
|
-
return;
|
|
1462
|
-
}
|
|
1463
|
-
|
|
1464
|
-
seen.add(key);
|
|
1465
|
-
|
|
1466
|
-
result.push({ start, end });
|
|
1467
|
-
}
|
|
1468
|
-
|
|
1469
|
-
const minimum = Math.max(2, Math.ceil(errors.length * min_fraction));
|
|
1470
|
-
|
|
1471
|
-
let cursor = 0;
|
|
1472
|
-
|
|
1473
|
-
while (cursor < errors.length) {
|
|
1474
|
-
const low = errors[cursor];
|
|
1475
|
-
|
|
1476
|
-
add(cursor, Math.max(upper_bound(low * growth), Math.min(errors.length, cursor + minimum)));
|
|
1477
|
-
|
|
1478
|
-
cursor = upper_bound(low);
|
|
1479
|
-
}
|
|
1480
|
-
|
|
1481
|
-
add(0, errors.length);
|
|
1482
|
-
|
|
1483
|
-
return result;
|
|
1484
|
-
}
|
|
1485
|
-
|
|
1486
|
-
/**
|
|
1487
|
-
* One round: pick a band, group it, simplify each group, and report what it produced.
|
|
1488
|
-
*
|
|
1489
|
-
* Every group is speculative and judged on its own. One that comes back with no fewer clusters than
|
|
1490
|
-
* it consumed is simply dropped — its clusters return to the frontier, to be grouped differently by
|
|
1491
|
-
* a later round — rather than failing the round for everyone else. A group that cannot be
|
|
1492
|
-
* simplified is no different in kind from a group that could not be formed, and the frontier already
|
|
1493
|
-
* knows what to do with those: nothing, for now.
|
|
1494
|
-
*
|
|
1495
|
-
* The round as a whole is accepted only when it *strictly* shrinks the frontier. Failing that it
|
|
1496
|
-
* widens the band, and failing that it grows the group — small groups starve the simplifier, and a
|
|
1497
|
-
* wider band gives a fine region coarser neighbours to merge with.
|
|
1498
|
-
*
|
|
1499
|
-
* @param {VGeoFrontier} frontier
|
|
1500
|
-
* @param {number} level of the parents this round would produce
|
|
1501
|
-
* @param {VGeoLevelOptions} options
|
|
1502
|
-
* @returns {{steps: GroupStep[], repacks: GroupRepack[], merged: Set<number>}|undefined} what to
|
|
1503
|
-
* commit and which frontier clusters it consumed; `undefined` when nothing this round could try
|
|
1504
|
-
* made the frontier smaller
|
|
1505
|
-
*/
|
|
1506
|
-
function run_round(frontier, level, options) {
|
|
1507
|
-
const face_graph = new VGeoFaceGraph(
|
|
1508
|
-
frontier.mesh.topology, bt_mesh_build_face_graph(frontier.mesh.topology)
|
|
1509
|
-
);
|
|
1510
|
-
|
|
1511
|
-
const cluster_graph = build_cluster_graph(frontier, face_graph);
|
|
1512
|
-
|
|
1513
|
-
const ordered = order_by_error(frontier);
|
|
1514
|
-
|
|
1515
|
-
const bands = error_bands(
|
|
1516
|
-
ordered.errors, options.error_band_growth, options.error_band_min_fraction
|
|
1517
|
-
);
|
|
1518
|
-
|
|
1519
|
-
let group_size = options.group_size;
|
|
1520
|
-
|
|
1521
|
-
for (; ;) {
|
|
1522
|
-
for (const range of bands) {
|
|
1523
|
-
const band = ordered.order.slice(range.start, range.end);
|
|
1524
|
-
|
|
1525
|
-
band.sort((a, b) => a - b);
|
|
1526
|
-
|
|
1527
|
-
const groups = partition_into_groups(frontier, band, cluster_graph, group_size);
|
|
1528
|
-
|
|
1529
|
-
if (groups.length === 0) {
|
|
1530
|
-
continue;
|
|
1531
|
-
}
|
|
1532
|
-
|
|
1533
|
-
const group_of_face = new Int32Array(frontier.mesh.topology.faces.size).fill(-1);
|
|
1534
|
-
|
|
1535
|
-
groups.forEach((group, group_index) => {
|
|
1536
|
-
for (const cluster of group) {
|
|
1537
|
-
for (const face of frontier.cluster_faces[cluster]) {
|
|
1538
|
-
group_of_face[face] = group_index;
|
|
1539
|
-
}
|
|
1540
|
-
}
|
|
1541
|
-
});
|
|
1542
|
-
|
|
1543
|
-
const steps = [];
|
|
1544
|
-
const repacks = [];
|
|
1545
|
-
|
|
1546
|
-
let consumed = 0;
|
|
1547
|
-
let produced = 0;
|
|
1548
|
-
|
|
1549
|
-
const merged = new Set();
|
|
1550
|
-
|
|
1551
|
-
groups.forEach((group, group_index) => {
|
|
1552
|
-
const parent_target = Math.max(
|
|
1553
|
-
1, Math.ceil(group.length * options.simplification_ratio)
|
|
1554
|
-
);
|
|
1555
|
-
|
|
1556
|
-
const repack = try_repack(frontier, group, parent_target);
|
|
1557
|
-
|
|
1558
|
-
if (repack !== undefined) {
|
|
1559
|
-
repacks.push(repack);
|
|
1560
|
-
|
|
1561
|
-
consumed += group.length;
|
|
1562
|
-
produced += repack.clusters.length;
|
|
1563
|
-
|
|
1564
|
-
for (const cluster of group) {
|
|
1565
|
-
merged.add(cluster);
|
|
1566
|
-
}
|
|
1567
|
-
|
|
1568
|
-
return;
|
|
1569
|
-
}
|
|
1570
|
-
|
|
1571
|
-
const step = build_group_step(
|
|
1572
|
-
frontier, group, group_of_face, group_index, level, options
|
|
1573
|
-
);
|
|
1574
|
-
|
|
1575
|
-
if (step === undefined || step.parent_clusters.length >= group.length) {
|
|
1576
|
-
// Refused. Nothing has been mutated — the step worked on a copy of the geometry
|
|
1577
|
-
// and a copy of the quadrics — so its clusters simply stay where they are.
|
|
1578
|
-
return;
|
|
1579
|
-
}
|
|
1580
|
-
|
|
1581
|
-
steps.push(step);
|
|
1582
|
-
|
|
1583
|
-
consumed += group.length;
|
|
1584
|
-
produced += step.parent_clusters.length;
|
|
1585
|
-
|
|
1586
|
-
for (const cluster of group) {
|
|
1587
|
-
merged.add(cluster);
|
|
1588
|
-
}
|
|
1589
|
-
});
|
|
1590
|
-
|
|
1591
|
-
if (produced < consumed) {
|
|
1592
|
-
return { steps, repacks, merged };
|
|
1593
|
-
}
|
|
1594
|
-
}
|
|
1595
|
-
|
|
1596
|
-
// No band made progress. Growing the group gives the simplifier more interior to work with
|
|
1597
|
-
// relative to its locked perimeter, which is the thing that stalls first.
|
|
1598
|
-
if (group_size >= frontier.clusters.length) {
|
|
1599
|
-
return undefined;
|
|
1600
|
-
}
|
|
1601
|
-
|
|
1602
|
-
group_size = Math.min(frontier.clusters.length, group_size * 2);
|
|
1603
|
-
}
|
|
1604
|
-
}
|
|
1605
|
-
|
|
1606
|
-
const scratch_quadric = new Quadric3();
|
|
1607
|
-
|
|
1608
|
-
/**
|
|
1609
|
-
* Build the whole DAG, from a source mesh to a single root.
|
|
1610
|
-
*
|
|
1611
|
-
* @param {VirtualGeometryMesh} mesh the normalized source; consumed, not copied
|
|
1612
|
-
* @param {VGeoLevelOptions} options
|
|
1613
|
-
* @returns {VGeoLevel[]} leaves first; the last level holds exactly one cluster, the root
|
|
1614
|
-
*/
|
|
1615
|
-
export function vgeo_build_levels(mesh, options) {
|
|
1616
|
-
assert.isPositiveInteger(options.group_size, 'options.group_size');
|
|
1617
|
-
assert.greaterThan(options.group_size, 1, 'options.group_size');
|
|
1618
|
-
assert.greaterThan(options.simplification_ratio, 0, 'options.simplification_ratio');
|
|
1619
|
-
assert.lessThan(options.simplification_ratio, 1, 'options.simplification_ratio');
|
|
1620
|
-
assert.greaterThan(options.error_band_growth, 1, 'options.error_band_growth');
|
|
1621
|
-
assert.greaterThan(options.error_band_min_fraction, 0, 'options.error_band_min_fraction');
|
|
1622
|
-
assert.lessThanOrEqual(options.error_band_min_fraction, 1, 'options.error_band_min_fraction');
|
|
1623
|
-
assert.isPositiveInteger(options.max_levels, 'options.max_levels');
|
|
1624
|
-
|
|
1625
|
-
let frontier = build_leaf_frontier(mesh);
|
|
1626
|
-
|
|
1627
|
-
const leaves = new VGeoLevel();
|
|
1628
|
-
|
|
1629
|
-
leaves.index = 0;
|
|
1630
|
-
leaves.clusters = frontier.clusters.slice();
|
|
1631
|
-
|
|
1632
|
-
const levels = [leaves];
|
|
1633
|
-
|
|
1634
|
-
while (frontier.clusters.length > 1) {
|
|
1635
|
-
if (levels.length >= options.max_levels) {
|
|
1636
|
-
throw new Error(
|
|
1637
|
-
`the DAG still holds ${frontier.clusters.length} clusters after ${levels.length} `
|
|
1638
|
-
+ `levels, which is as many as the container can name`
|
|
1639
|
-
);
|
|
1640
|
-
}
|
|
1641
|
-
|
|
1642
|
-
const level = levels.length;
|
|
1643
|
-
|
|
1644
|
-
const round = run_round(frontier, level, options);
|
|
1645
|
-
|
|
1646
|
-
if (round === undefined) {
|
|
1647
|
-
throw new Error(
|
|
1648
|
-
`the DAG stopped converging with ${frontier.clusters.length} clusters left: no `
|
|
1649
|
-
+ `grouping of the frontier produced fewer clusters than it consumed, so the build `
|
|
1650
|
-
+ `has no single root to start a cut from`
|
|
1651
|
-
);
|
|
1652
|
-
}
|
|
1653
|
-
|
|
1654
|
-
const next = new VGeoLevel();
|
|
1655
|
-
|
|
1656
|
-
next.index = level;
|
|
1657
|
-
|
|
1658
|
-
/**
|
|
1659
|
-
* @type {VGeoContribution[]}
|
|
1660
|
-
*/
|
|
1661
|
-
const contributions = [];
|
|
1662
|
-
|
|
1663
|
-
const clusters = [];
|
|
1664
|
-
|
|
1665
|
-
// Untouched clusters first, in the order they already had, then this round's output. All of
|
|
1666
|
-
// them name their vertices in the frontier that is being replaced, which is what lets them
|
|
1667
|
-
// be stitched into one mesh.
|
|
1668
|
-
frontier.clusters.forEach((cluster, index) => {
|
|
1669
|
-
if (round.merged.has(index)) {
|
|
1670
|
-
return;
|
|
1671
|
-
}
|
|
1672
|
-
|
|
1673
|
-
const gathered = vgeo_gather_faces(frontier.mesh, frontier.cluster_faces[index]);
|
|
1674
|
-
|
|
1675
|
-
clusters.push(cluster);
|
|
1676
|
-
|
|
1677
|
-
contributions.push({
|
|
1678
|
-
positions: gathered.positions,
|
|
1679
|
-
indices: gathered.indices,
|
|
1680
|
-
local_vertex_ids: gathered.vertex_ids,
|
|
1681
|
-
source_vertex_ids: gathered.vertex_ids,
|
|
1682
|
-
topology: frontier.mesh.topology,
|
|
1683
|
-
});
|
|
1684
|
-
});
|
|
1685
|
-
|
|
1686
|
-
if (round.repacks.length > 0) {
|
|
1687
|
-
const replaced = new Set();
|
|
1688
|
-
|
|
1689
|
-
for (const repack of round.repacks) {
|
|
1690
|
-
for (const leaf of repack.replaced) {
|
|
1691
|
-
replaced.add(leaf);
|
|
1692
|
-
}
|
|
1693
|
-
}
|
|
1694
|
-
|
|
1695
|
-
// One pass over the leaf level for the whole round. Per repack it would be a scan of
|
|
1696
|
-
// every leaf in the asset, and a scene of small parts is nothing but repacks.
|
|
1697
|
-
leaves.clusters = leaves.clusters.filter((cluster) => !replaced.has(cluster));
|
|
1698
|
-
|
|
1699
|
-
for (const repack of round.repacks) {
|
|
1700
|
-
repack.clusters.forEach((cluster, index) => {
|
|
1701
|
-
leaves.clusters.push(cluster);
|
|
1702
|
-
|
|
1703
|
-
clusters.push(cluster);
|
|
1704
|
-
contributions.push(repack.geometry[index]);
|
|
1705
|
-
});
|
|
1706
|
-
}
|
|
1707
|
-
}
|
|
1708
|
-
|
|
1709
|
-
for (const step of round.steps) {
|
|
1710
|
-
const group = new VirtualGeometryGroup();
|
|
1711
|
-
|
|
1712
|
-
// The group sits one band below the parents it produced, which is what makes page order
|
|
1713
|
-
// by descending level a topological order of the DAG.
|
|
1714
|
-
group.level = level - 1;
|
|
1715
|
-
group.clusters = step.child_clusters;
|
|
1716
|
-
|
|
1717
|
-
for (const cluster of step.child_clusters) {
|
|
1718
|
-
cluster.parent_error = step.error;
|
|
1719
|
-
cluster.parent_sphere = step.sphere;
|
|
1720
|
-
}
|
|
1721
|
-
|
|
1722
|
-
step.parent_clusters.forEach((parent, index) => {
|
|
1723
|
-
parent.self_error = step.error;
|
|
1724
|
-
parent.self_sphere = step.sphere;
|
|
1725
|
-
parent.child_group = group;
|
|
1726
|
-
|
|
1727
|
-
next.clusters.push(parent);
|
|
1728
|
-
clusters.push(parent);
|
|
1729
|
-
contributions.push(step.parent_geometry[index]);
|
|
1730
|
-
});
|
|
1731
|
-
|
|
1732
|
-
levels[level - 1].groups.push(group);
|
|
1733
|
-
}
|
|
1734
|
-
|
|
1735
|
-
if (next.clusters.length > 0) {
|
|
1736
|
-
levels.push(next);
|
|
1737
|
-
}
|
|
1738
|
-
|
|
1739
|
-
// Fold every step's quadric gains back into the frontier's own set, before it is re-indexed.
|
|
1740
|
-
//
|
|
1741
|
-
// Gains, not results: two steps that share a pinned boundary vertex each started from this
|
|
1742
|
-
// vertex's quadric and each accumulated their own victims onto it, so the two results
|
|
1743
|
-
// overlap by exactly the quadric they both began with. Adding them whole would count the
|
|
1744
|
-
// source planes under that vertex twice and inflate every error above it.
|
|
1745
|
-
const updated = frontier.quadrics.slice();
|
|
1746
|
-
|
|
1747
|
-
for (const step of round.steps) {
|
|
1748
|
-
for (const [vertex, quadric] of step.quadrics) {
|
|
1749
|
-
|
|
1750
|
-
scratch_quadric.copy(quadric);
|
|
1751
|
-
scratch_quadric.sub(frontier.quadrics[vertex]);
|
|
1752
|
-
|
|
1753
|
-
const next = updated[vertex].clone();
|
|
1754
|
-
next.add(scratch_quadric);
|
|
1755
|
-
|
|
1756
|
-
updated[vertex] = next;
|
|
1757
|
-
|
|
1758
|
-
}
|
|
1759
|
-
}
|
|
1760
|
-
|
|
1761
|
-
const stitched = stitch_frontier(contributions, frontier.mesh.layers);
|
|
1762
|
-
|
|
1763
|
-
frontier = new VGeoFrontier();
|
|
1764
|
-
|
|
1765
|
-
frontier.mesh = stitched.mesh;
|
|
1766
|
-
frontier.clusters = clusters;
|
|
1767
|
-
frontier.cluster_faces = stitched.cluster_faces;
|
|
1768
|
-
frontier.quadrics = Array.from(
|
|
1769
|
-
stitched.source_vertex_ids, (source) => updated[source]
|
|
1770
|
-
);
|
|
1771
|
-
}
|
|
1772
|
-
|
|
1773
|
-
const top = levels[levels.length - 1];
|
|
1774
|
-
|
|
1775
|
-
assert.equal(
|
|
1776
|
-
top.clusters.length, 1,
|
|
1777
|
-
'the topmost level must hold the single root the frontier converged to'
|
|
1778
|
-
);
|
|
1779
|
-
|
|
1780
|
-
assert.equal(
|
|
1781
|
-
top.clusters[0], frontier.clusters[0],
|
|
1782
|
-
'the cluster the frontier converged to must be the one the topmost level holds'
|
|
1783
|
-
);
|
|
1784
|
-
|
|
1785
|
-
return levels;
|
|
1786
|
-
}
|
|
1
|
+
import { assert } from "../../../../../core/assert.js";
|
|
2
|
+
import { aabb3_from_v3_array } from "../../../../../core/geom/3d/aabb/aabb3_from_v3_array.js";
|
|
3
|
+
import { v3_morton_encode_bounded } from "../../../../../core/geom/3d/morton/v3_morton_encode_bounded.js";
|
|
4
|
+
import { Quadric3 } from "../../../../../core/geom/3d/quadric/Quadric3.js";
|
|
5
|
+
import {
|
|
6
|
+
sphere_compute_bounding_of_2_spheres
|
|
7
|
+
} from "../../../../../core/geom/3d/sphere/sphere_compute_bounding_of_2_spheres.js";
|
|
8
|
+
import { BinaryTopology, NULL_POINTER, } from "../../../../../core/geom/3d/topology/struct/binary/BinaryTopology.js";
|
|
9
|
+
import {
|
|
10
|
+
bt_mesh_compute_vertex_quadrics
|
|
11
|
+
} from "../../../../../core/geom/3d/topology/struct/binary/io/bt_mesh_compute_vertex_quadrics.js";
|
|
12
|
+
import {
|
|
13
|
+
bt_mesh_from_indexed_geometry
|
|
14
|
+
} from "../../../../../core/geom/3d/topology/struct/binary/io/bt_mesh_from_indexed_geometry.js";
|
|
15
|
+
import {
|
|
16
|
+
bt_mesh_simplify_with_quadrics
|
|
17
|
+
} from "../../../../../core/geom/3d/topology/struct/binary/io/bt_mesh_simplify.js";
|
|
18
|
+
import { bt_edge_get } from "../../../../../core/geom/3d/topology/struct/binary/query/bt_edge_get.js";
|
|
19
|
+
import { bt_mesh_validate } from "../../../../../core/geom/3d/topology/struct/binary/query/bt_mesh_validate.js";
|
|
20
|
+
import { bt_mesh_build_face_graph } from "../../../../../core/graph/metis/bt_mesh_build_face_graph.js";
|
|
21
|
+
import { vgeo_encode_cluster_payload } from "../format/payload/vgeo_encode_cluster_payload.js";
|
|
22
|
+
import { vgeo_layers_copy_vertex } from "../format/attribute/vgeo_layers_copy_vertex.js";
|
|
23
|
+
import { vgeo_layers_like } from "../format/attribute/vgeo_layers_like.js";
|
|
24
|
+
import { VGEO_MAX_CLUSTER_TRIANGLE_COUNT } from "../format/cluster/VGEO_MAX_CLUSTER_TRIANGLE_COUNT.js";
|
|
25
|
+
import { VGEO_MAX_CLUSTER_VERTEX_COUNT } from "../format/cluster/VGEO_MAX_CLUSTER_VERTEX_COUNT.js";
|
|
26
|
+
import { VGEO_MAX_GROUP_CLUSTER_COUNT } from "../format/page/VGEO_MAX_GROUP_CLUSTER_COUNT.js";
|
|
27
|
+
import { VirtualGeometryCluster } from "../format/dag/VirtualGeometryCluster.js";
|
|
28
|
+
import { VirtualGeometryGroup } from "../format/dag/VirtualGeometryGroup.js";
|
|
29
|
+
import { vgeo_bounding_sphere } from "./vgeo_bounding_sphere.js";
|
|
30
|
+
import { vgeo_compute_vertex_normals } from "./vgeo_compute_vertex_normals.js";
|
|
31
|
+
import { vgeo_compute_vertex_tangents } from "./vgeo_compute_vertex_tangents.js";
|
|
32
|
+
import { vgeo_containing_sphere } from "./vgeo_containing_sphere.js";
|
|
33
|
+
import { vgeo_extract_submesh } from "./vgeo_extract_submesh.js";
|
|
34
|
+
import { vgeo_gather_faces } from "./vgeo_gather_faces.js";
|
|
35
|
+
import { vgeo_graph_components } from "./vgeo_graph_components.js";
|
|
36
|
+
import { vgeo_narrow_indices } from "./vgeo_narrow_indices.js";
|
|
37
|
+
import { vgeo_partition_graph } from "./vgeo_partition_graph.js";
|
|
38
|
+
import { vgeo_split_face_set } from "./vgeo_split_face_set.js";
|
|
39
|
+
import { vgeo_split_face_set_packed } from "./vgeo_split_face_set_packed.js";
|
|
40
|
+
import { VGeoFaceGraph } from "./VGeoFaceGraph.js";
|
|
41
|
+
import { VGeoLevel } from "./VGeoLevel.js";
|
|
42
|
+
import { VirtualGeometryMesh } from "./VirtualGeometryMesh.js";
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Build the cluster DAG: leaves out of the source mesh, then merge steps until one cluster is left.
|
|
46
|
+
*
|
|
47
|
+
* The unit of work is a **round**, not a level sweep. A round holds a *frontier* — every cluster that
|
|
48
|
+
* nothing has merged yet — selects a band of it by error, groups the band, locks every vertex the
|
|
49
|
+
* group shares with anything outside it, halves the group's interior, and re-cuts the result into
|
|
50
|
+
* new clusters, which become the parents of every cluster in the group. Locked perimeters plus a
|
|
51
|
+
* group-uniform metric is the whole reason independent per-cluster LOD decisions come out
|
|
52
|
+
* crack-free.
|
|
53
|
+
*
|
|
54
|
+
* **The band is why this is not a level sweep.** Sweeping whole levels pushes every cluster up one
|
|
55
|
+
* step per level whatever its error, so a region that is nearly exact is coarsened in lockstep with
|
|
56
|
+
* one that is not, and a level ends up spanning an order of magnitude of error. The cut the runtime
|
|
57
|
+
* takes is a threshold on error, so a level that spans an order of magnitude is a page band that
|
|
58
|
+
* spans one too: installing it brings clusters the cut does not want, and the ones it does want are
|
|
59
|
+
* scattered across bands. Selecting the lowest-error band instead lets a fine region catch up before
|
|
60
|
+
* the coarse one moves, and bands come out error-uniform. On geometry whose error is already uniform
|
|
61
|
+
* the band is the whole frontier and this degenerates to the sweep, which is the correct behaviour
|
|
62
|
+
* for that case rather than a special case for it.
|
|
63
|
+
*
|
|
64
|
+
* **A cluster the round cannot use simply stays.** It is not pushed up for the sake of symmetry, and
|
|
65
|
+
* it does not stall the round for anyone else — which is what lets a lone island wait, untouched and
|
|
66
|
+
* at zero error, until the geometry it will eventually merge with has come down to meet it.
|
|
67
|
+
*
|
|
68
|
+
* **Error is conformance to the source, not a sum of steps.** Vertex quadrics are built once, from
|
|
69
|
+
* the source triangles, and carried through every round: a survivor absorbs its victim's quadric at
|
|
70
|
+
* every collapse, so a vertex twelve rounds up still holds the planes of every source triangle
|
|
71
|
+
* folded into it. Evaluating that at its current position measures the deviation from what the
|
|
72
|
+
* artist authored, directly, and normalizing by the quadric's accumulated weight makes it an
|
|
73
|
+
* object-space length rather than a cost whose scale depends on how many planes it happens to hold.
|
|
74
|
+
* That length is what the runtime projects into pixels.
|
|
75
|
+
*
|
|
76
|
+
* **Termination is convergence to one root, not a stall.** Groups bundle across connected components
|
|
77
|
+
* once connectivity runs out, and a group small enough to fit one cluster is merged losslessly, so a
|
|
78
|
+
* frontier of separate islands still comes together. A build that cannot reach a single root throws:
|
|
79
|
+
* the runtime starts its cut at the root, and "several roots" is not a shape it can start from.
|
|
80
|
+
*
|
|
81
|
+
* @author Alex Goldring
|
|
82
|
+
* @copyright Company Named Limited (c) 2026
|
|
83
|
+
*/
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Fixed so that the same frontier always groups the same way.
|
|
87
|
+
* @readonly
|
|
88
|
+
* @type {number}
|
|
89
|
+
*/
|
|
90
|
+
const GROUP_SEED = 0x6D05;
|
|
91
|
+
|
|
92
|
+
const scratch_sphere = new Float32Array(4);
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Every cluster nothing has merged yet, and the geometry they currently stand for.
|
|
96
|
+
*
|
|
97
|
+
* One mesh holds the whole frontier so that a group can be extracted with its surroundings intact —
|
|
98
|
+
* the lock set is "every vertex a face outside this group touches", and that question has no answer
|
|
99
|
+
* in a mesh that holds the group alone.
|
|
100
|
+
*/
|
|
101
|
+
class VGeoFrontier {
|
|
102
|
+
/**
|
|
103
|
+
* @type {VirtualGeometryMesh}
|
|
104
|
+
*/
|
|
105
|
+
mesh = null;
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* @type {VirtualGeometryCluster[]}
|
|
109
|
+
*/
|
|
110
|
+
clusters = [];
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* `cluster_faces[i]` are the faces of {@link mesh} that cluster `i` owns.
|
|
114
|
+
* @type {number[][]}
|
|
115
|
+
*/
|
|
116
|
+
cluster_faces = [];
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* One accumulated quadric per vertex of {@link mesh}, carried unbroken from the source mesh.
|
|
120
|
+
*
|
|
121
|
+
* This is what makes the error mean "how far this is from the geometry the artist authored"
|
|
122
|
+
* rather than "how far this is from the frontier it was simplified out of". A survivor absorbs
|
|
123
|
+
* its victim's quadric at every collapse, at every round, so a vertex twelve rounds up still
|
|
124
|
+
* carries the planes of every source triangle that was ever folded into it, and evaluating its
|
|
125
|
+
* quadric at its current position measures the deviation from the source directly. Composing
|
|
126
|
+
* per-round residuals instead only *bounds* that, by the triangle inequality, and the bound
|
|
127
|
+
* loosens with every round: measured on a unit sphere, the same ninety-triangle root read 0.36
|
|
128
|
+
* built from 3k source triangles and 13.3 built from 24k, where it is the same approximation and
|
|
129
|
+
* reads 0.0315 either way once the quadrics are carried.
|
|
130
|
+
*
|
|
131
|
+
* @type {Quadric3[]}
|
|
132
|
+
*/
|
|
133
|
+
quadrics = [];
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Everything one group step produced, before it is committed. Kept separate so that a round which
|
|
138
|
+
* fails to make progress can be discarded without having mutated the frontier.
|
|
139
|
+
*/
|
|
140
|
+
class GroupStep {
|
|
141
|
+
/**
|
|
142
|
+
* The frontier clusters this group holds.
|
|
143
|
+
* @type {VirtualGeometryCluster[]}
|
|
144
|
+
*/
|
|
145
|
+
child_clusters = [];
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* @type {VirtualGeometryCluster[]}
|
|
149
|
+
*/
|
|
150
|
+
parent_clusters = [];
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Parallel to {@link parent_clusters}: the geometry each parent was cut from, in terms of the
|
|
154
|
+
* frontier, so the next frontier can be stitched back together.
|
|
155
|
+
* @type {VGeoContribution[]}
|
|
156
|
+
*/
|
|
157
|
+
parent_geometry = [];
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* The value both sides of this step store: the object-space distance from the *source* surface
|
|
161
|
+
* that this step's geometry now stands at, floored by what its children already carried.
|
|
162
|
+
* @type {number}
|
|
163
|
+
*/
|
|
164
|
+
error = 0;
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* `frontier vertex id -> that vertex's quadric after this step`, for every vertex that survived
|
|
168
|
+
* it. Held rather than written straight back because a step is speculative until the round
|
|
169
|
+
* accepts it, and because two steps sharing a pinned boundary vertex must have their gains
|
|
170
|
+
* combined rather than one overwriting the other.
|
|
171
|
+
* @type {Map<number, Quadric3>}
|
|
172
|
+
*/
|
|
173
|
+
quadrics = new Map();
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* The LOD-metric sphere both sides of this step store.
|
|
177
|
+
* @type {Float32Array}
|
|
178
|
+
*/
|
|
179
|
+
sphere = new Float32Array(4);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Several leaves re-cut into one, with nothing simplified — see {@link build_repack}.
|
|
184
|
+
*/
|
|
185
|
+
class GroupRepack {
|
|
186
|
+
/**
|
|
187
|
+
* The leaves this removes from the DAG.
|
|
188
|
+
* @type {VirtualGeometryCluster[]}
|
|
189
|
+
*/
|
|
190
|
+
replaced = [];
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* The leaves that take their place -- always fewer.
|
|
194
|
+
* @type {VirtualGeometryCluster[]}
|
|
195
|
+
*/
|
|
196
|
+
clusters = [];
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Parallel to {@link clusters}.
|
|
200
|
+
* @type {VGeoContribution[]}
|
|
201
|
+
*/
|
|
202
|
+
geometry = [];
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* One cluster's geometry expressed against the frontier it came out of, which is what lets the next
|
|
207
|
+
* frontier be stitched from a mixture of freshly simplified clusters and untouched ones.
|
|
208
|
+
*
|
|
209
|
+
* @typedef {object} VGeoContribution
|
|
210
|
+
* @property {Float32Array} positions
|
|
211
|
+
* @property {Uint32Array} indices local to {@link positions}
|
|
212
|
+
* @property {Uint32Array} local_vertex_ids ids in the mesh {@link topology} belongs to
|
|
213
|
+
* @property {Uint32Array} source_vertex_ids the same vertices, named in the frontier's mesh
|
|
214
|
+
* @property {BinaryTopology} topology the mesh {@link local_vertex_ids} name vertices of, whose
|
|
215
|
+
* vertex data block holds their attributes
|
|
216
|
+
*/
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Encode a gathered face set into a cluster record.
|
|
220
|
+
*
|
|
221
|
+
* @param {VGeoGatheredFaces} gathered already within both meshlet caps
|
|
222
|
+
* @param {number} level
|
|
223
|
+
* @param {(BinaryDataLayer|null)[]} layers of the mesh it was gathered from
|
|
224
|
+
* @returns {VirtualGeometryCluster}
|
|
225
|
+
*/
|
|
226
|
+
function cluster_from_gathered(gathered, level, layers) {
|
|
227
|
+
const payload = vgeo_encode_cluster_payload(
|
|
228
|
+
gathered.positions,
|
|
229
|
+
vgeo_narrow_indices(gathered),
|
|
230
|
+
gathered.vertex_ids,
|
|
231
|
+
layers
|
|
232
|
+
);
|
|
233
|
+
|
|
234
|
+
const cluster = new VirtualGeometryCluster();
|
|
235
|
+
|
|
236
|
+
cluster.level = level;
|
|
237
|
+
cluster.vertex_count = gathered.vertex_ids.length;
|
|
238
|
+
cluster.triangle_count = gathered.indices.length / 3;
|
|
239
|
+
cluster.encoding_flags = payload.encoding_flags;
|
|
240
|
+
cluster.payload = payload.bytes;
|
|
241
|
+
cluster.bounds_sphere = vgeo_bounding_sphere(gathered.positions);
|
|
242
|
+
|
|
243
|
+
// the box needs no inflation, unlike the sphere above: the extremes are float32 values already,
|
|
244
|
+
// so min and max of them are exactly representable and the stored box contains the stored
|
|
245
|
+
// points by construction
|
|
246
|
+
aabb3_from_v3_array(cluster.aabb, gathered.positions, gathered.positions.length);
|
|
247
|
+
|
|
248
|
+
return cluster;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Build a cluster out of a face set: gather it, bound it, encode its payload.
|
|
253
|
+
*
|
|
254
|
+
* @param {VirtualGeometryMesh} mesh
|
|
255
|
+
* @param {number[]} face_ids
|
|
256
|
+
* @param {number} level
|
|
257
|
+
* @returns {{cluster: VirtualGeometryCluster, gathered: VGeoGatheredFaces}}
|
|
258
|
+
*/
|
|
259
|
+
function build_cluster(mesh, face_ids, level) {
|
|
260
|
+
const gathered = vgeo_gather_faces(mesh, face_ids);
|
|
261
|
+
|
|
262
|
+
return { cluster: cluster_from_gathered(gathered, level, mesh.layers), gathered };
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Cut a mesh into leaf clusters — the frontier every round starts from.
|
|
267
|
+
*
|
|
268
|
+
* @param {VirtualGeometryMesh} mesh
|
|
269
|
+
* @returns {VGeoFrontier}
|
|
270
|
+
*/
|
|
271
|
+
function build_leaf_frontier(mesh) {
|
|
272
|
+
const face_graph = new VGeoFaceGraph(mesh.topology, bt_mesh_build_face_graph(mesh.topology));
|
|
273
|
+
|
|
274
|
+
const all = [];
|
|
275
|
+
|
|
276
|
+
for (let vertex = 0; vertex < face_graph.graph.vertex_count; vertex++) {
|
|
277
|
+
all.push(vertex);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const parts = vgeo_split_face_set(
|
|
281
|
+
face_graph, all, VGEO_MAX_CLUSTER_TRIANGLE_COUNT, VGEO_MAX_CLUSTER_VERTEX_COUNT
|
|
282
|
+
);
|
|
283
|
+
|
|
284
|
+
const frontier = new VGeoFrontier();
|
|
285
|
+
|
|
286
|
+
frontier.mesh = mesh;
|
|
287
|
+
|
|
288
|
+
// Built once, here, from the source triangles. Nothing rebuilds them afterwards — every later
|
|
289
|
+
// round inherits these and adds to them, which is the whole point.
|
|
290
|
+
frontier.quadrics = bt_mesh_compute_vertex_quadrics(mesh.topology);
|
|
291
|
+
|
|
292
|
+
for (const part of parts) {
|
|
293
|
+
const face_ids = part.map((dense) => face_graph.vertex_face_ids[dense]);
|
|
294
|
+
|
|
295
|
+
face_ids.sort((a, b) => a - b);
|
|
296
|
+
|
|
297
|
+
const built = build_cluster(mesh, face_ids, 0);
|
|
298
|
+
|
|
299
|
+
// A leaf's own bounds are its LOD-metric sphere, and it deviates from nothing.
|
|
300
|
+
built.cluster.self_sphere = built.cluster.bounds_sphere.slice();
|
|
301
|
+
built.cluster.self_error = 0;
|
|
302
|
+
|
|
303
|
+
frontier.clusters.push(built.cluster);
|
|
304
|
+
frontier.cluster_faces.push(face_ids);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
return frontier;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* How much of the space two clusters would waste by being merged, as a factor in `(0, 1]`.
|
|
312
|
+
*
|
|
313
|
+
* The ratio of what they occupy separately to what the sphere containing both would occupy. Two
|
|
314
|
+
* clusters side by side score near one; two clusters at opposite ends of the model score near zero,
|
|
315
|
+
* because the sphere over both is mostly empty. Scale-free by construction — it compares a volume
|
|
316
|
+
* to a volume, so it needs no notion of how big the model is or how far apart "far" is, which is
|
|
317
|
+
* exactly what a distance threshold would have needed and could never have been given.
|
|
318
|
+
*
|
|
319
|
+
* The group's sphere is what the runtime projects to decide the whole group's fate, so a group
|
|
320
|
+
* spread across empty space is one that gets refined from much further away than its geometry
|
|
321
|
+
* warrants. Cutting edge weight by this steers the partitioner away from those merges without ever
|
|
322
|
+
* forbidding one — when nothing better is available, a poor merge is still better than none.
|
|
323
|
+
*
|
|
324
|
+
* @param {Float32Array} a bounding sphere, centre then radius
|
|
325
|
+
* @param {Float32Array} b
|
|
326
|
+
* @returns {number}
|
|
327
|
+
*/
|
|
328
|
+
function compactness(a, b) {
|
|
329
|
+
sphere_compute_bounding_of_2_spheres(scratch_sphere, a, b);
|
|
330
|
+
|
|
331
|
+
const merged = scratch_sphere[3];
|
|
332
|
+
|
|
333
|
+
if (merged <= 0) {
|
|
334
|
+
return 1;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// Volumes, up to the common factor of 4/3 pi, which cancels.
|
|
338
|
+
const separate = a[3] * a[3] * a[3] + b[3] * b[3] * b[3];
|
|
339
|
+
|
|
340
|
+
return Math.min(1, separate / (merged * merged * merged));
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* The cluster adjacency graph of a frontier: clusters are the vertices, shared mesh edges are the
|
|
345
|
+
* edges, and an edge weighs as much boundary as the two clusters share, discounted by how much empty
|
|
346
|
+
* space merging them would enclose.
|
|
347
|
+
*
|
|
348
|
+
* @param {VGeoFrontier} frontier
|
|
349
|
+
* @param {VGeoFaceGraph} face_graph of `frontier.mesh`
|
|
350
|
+
* @returns {{edge_addresses: Uint32Array, adjacency: Uint32Array, edge_weights: Float32Array}}
|
|
351
|
+
*/
|
|
352
|
+
function build_cluster_graph(frontier, face_graph) {
|
|
353
|
+
const cluster_of_face = new Int32Array(frontier.mesh.topology.faces.size).fill(-1);
|
|
354
|
+
|
|
355
|
+
frontier.cluster_faces.forEach((face_ids, cluster) => {
|
|
356
|
+
for (const face of face_ids) {
|
|
357
|
+
cluster_of_face[face] = cluster;
|
|
358
|
+
}
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
const cluster_count = frontier.clusters.length;
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* `cluster -> (neighbour cluster -> shared boundary)`.
|
|
365
|
+
* @type {Map<number, number>[]}
|
|
366
|
+
*/
|
|
367
|
+
const neighbours = [];
|
|
368
|
+
|
|
369
|
+
for (let i = 0; i < cluster_count; i++) {
|
|
370
|
+
neighbours.push(new Map());
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
const graph = face_graph.graph;
|
|
374
|
+
|
|
375
|
+
for (let vertex = 0; vertex < graph.vertex_count; vertex++) {
|
|
376
|
+
const cluster = cluster_of_face[face_graph.vertex_face_ids[vertex]];
|
|
377
|
+
|
|
378
|
+
for (let i = graph.edge_addresses[vertex]; i < graph.edge_addresses[vertex + 1]; i++) {
|
|
379
|
+
const other_cluster = cluster_of_face[face_graph.vertex_face_ids[graph.adjacency[i]]];
|
|
380
|
+
|
|
381
|
+
if (other_cluster === cluster) {
|
|
382
|
+
continue;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
const bucket = neighbours[cluster];
|
|
386
|
+
|
|
387
|
+
bucket.set(other_cluster, (bucket.get(other_cluster) ?? 0) + graph.edge_weights[i]);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
const edge_addresses = new Uint32Array(cluster_count + 1);
|
|
392
|
+
const adjacency = [];
|
|
393
|
+
const edge_weights = [];
|
|
394
|
+
|
|
395
|
+
for (let cluster = 0; cluster < cluster_count; cluster++) {
|
|
396
|
+
edge_addresses[cluster] = adjacency.length;
|
|
397
|
+
|
|
398
|
+
const entries = Array.from(neighbours[cluster].entries()).sort((a, b) => a[0] - b[0]);
|
|
399
|
+
|
|
400
|
+
for (const [other, weight] of entries) {
|
|
401
|
+
adjacency.push(other);
|
|
402
|
+
|
|
403
|
+
edge_weights.push(weight * compactness(
|
|
404
|
+
frontier.clusters[cluster].bounds_sphere, frontier.clusters[other].bounds_sphere
|
|
405
|
+
));
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
edge_addresses[cluster_count] = adjacency.length;
|
|
410
|
+
|
|
411
|
+
return {
|
|
412
|
+
edge_addresses,
|
|
413
|
+
adjacency: Uint32Array.from(adjacency),
|
|
414
|
+
edge_weights: Float32Array.from(edge_weights),
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* Bundle clusters that have no one to merge with along a Morton curve.
|
|
420
|
+
*
|
|
421
|
+
* A cluster alone in its connected component cannot be coarsened: halving its triangles leaves one
|
|
422
|
+
* cluster where there was one cluster, and the DAG stands still. The only way it ever reaches a root
|
|
423
|
+
* is bundled with geometry it shares no edge with, and the only thing left to choose them by is
|
|
424
|
+
* where they are. That is a real cost — a cluster spanning two islands carries a sphere containing
|
|
425
|
+
* both — which is why this is the fallback and adjacency is the rule.
|
|
426
|
+
*
|
|
427
|
+
* @param {VGeoFrontier} frontier
|
|
428
|
+
* @param {number[]} loose cluster indices, each alone in its component
|
|
429
|
+
* @param {number} group_size
|
|
430
|
+
* @returns {number[][]} groups of at least two; a single leftover is returned to the frontier
|
|
431
|
+
*/
|
|
432
|
+
function bundle_loose_clusters(frontier, loose, group_size) {
|
|
433
|
+
if (loose.length < 2) {
|
|
434
|
+
return [];
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
const bounds = new Float32Array([
|
|
438
|
+
Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY,
|
|
439
|
+
Number.NEGATIVE_INFINITY, Number.NEGATIVE_INFINITY, Number.NEGATIVE_INFINITY,
|
|
440
|
+
]);
|
|
441
|
+
|
|
442
|
+
for (const cluster of loose) {
|
|
443
|
+
const centre = frontier.clusters[cluster].bounds_sphere;
|
|
444
|
+
|
|
445
|
+
for (let axis = 0; axis < 3; axis++) {
|
|
446
|
+
bounds[axis] = Math.min(bounds[axis], centre[axis]);
|
|
447
|
+
bounds[axis + 3] = Math.max(bounds[axis + 3], centre[axis]);
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* @type {Map<number, number>}
|
|
453
|
+
*/
|
|
454
|
+
const codes = new Map();
|
|
455
|
+
|
|
456
|
+
for (const cluster of loose) {
|
|
457
|
+
const centre = frontier.clusters[cluster].bounds_sphere;
|
|
458
|
+
|
|
459
|
+
codes.set(cluster, v3_morton_encode_bounded(centre[0], centre[1], centre[2], bounds));
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
const order = loose.slice();
|
|
463
|
+
|
|
464
|
+
order.sort((a, b) => (codes.get(a) - codes.get(b)) || (a - b));
|
|
465
|
+
|
|
466
|
+
const result = [];
|
|
467
|
+
|
|
468
|
+
let bundle = [];
|
|
469
|
+
let triangles = 0;
|
|
470
|
+
let vertices = 0;
|
|
471
|
+
let packable = true;
|
|
472
|
+
|
|
473
|
+
for (const index of order) {
|
|
474
|
+
const cluster = frontier.clusters[index];
|
|
475
|
+
|
|
476
|
+
// Leaves small enough to end up in one cluster between them are worth bundling past the
|
|
477
|
+
// group size: what happens to them is a re-cut at no error, not a simplification, so a
|
|
478
|
+
// bigger bundle costs nothing in fidelity and is the difference between a scene of small
|
|
479
|
+
// parts filling its clusters and running them at a tenth of capacity. Anything else stops at
|
|
480
|
+
// the group size, where the size is a real trade against LOD lockstep.
|
|
481
|
+
const packs = packable
|
|
482
|
+
&& cluster.child_group === undefined
|
|
483
|
+
&& triangles + cluster.triangle_count <= VGEO_MAX_CLUSTER_TRIANGLE_COUNT
|
|
484
|
+
&& vertices + cluster.vertex_count <= VGEO_MAX_CLUSTER_VERTEX_COUNT;
|
|
485
|
+
|
|
486
|
+
if (bundle.length >= group_size && !packs) {
|
|
487
|
+
result.push(bundle);
|
|
488
|
+
|
|
489
|
+
bundle = [];
|
|
490
|
+
triangles = 0;
|
|
491
|
+
vertices = 0;
|
|
492
|
+
packable = true;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
bundle.push(index);
|
|
496
|
+
|
|
497
|
+
triangles += cluster.triangle_count;
|
|
498
|
+
vertices += cluster.vertex_count;
|
|
499
|
+
|
|
500
|
+
packable = packable && cluster.child_group === undefined;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
if (bundle.length === 1 && result.length > 0) {
|
|
504
|
+
// A bundle of one merges nothing. Handing it to the bundle before it costs one cluster of
|
|
505
|
+
// spatial spread and is the difference between the frontier shrinking and it standing still.
|
|
506
|
+
result[result.length - 1].push(bundle[0]);
|
|
507
|
+
} else if (bundle.length > 1) {
|
|
508
|
+
result.push(bundle);
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
return result.filter((group) => group.length >= 2);
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
/**
|
|
515
|
+
* Fold every one-cluster piece into the adjacent piece it shares the most boundary with.
|
|
516
|
+
*
|
|
517
|
+
* The partitioner produces these constantly — it optimizes cut, not contiguity, so splitting its
|
|
518
|
+
* parts into connected pieces routinely leaves a cluster standing on its own. Such a cluster is not
|
|
519
|
+
* out of options, though: it sits inside a component that has plenty of neighbours, and the right
|
|
520
|
+
* answer is the nearest of them, not the proximity pool. Letting it fall through to the pool is how
|
|
521
|
+
* a lone shell sixty metres away ends up merged with a fragment of something else entirely.
|
|
522
|
+
*
|
|
523
|
+
* One forward pass is enough. Folding only ever grows a piece and empties the one it came from, so
|
|
524
|
+
* it cannot turn a piece of two into a piece of one — the singletons are exactly the ones present
|
|
525
|
+
* when the pass starts, and a singleton that has already absorbed another is no longer one.
|
|
526
|
+
*
|
|
527
|
+
* @param {number[][]} pieces of a single connected component of the band, each non-empty; every
|
|
528
|
+
* cluster in a singleton piece therefore has a neighbour in some other piece here
|
|
529
|
+
* @param {object} cluster_graph as {@link build_cluster_graph} returns
|
|
530
|
+
* @returns {number[][]} non-empty pieces, with no singletons
|
|
531
|
+
*/
|
|
532
|
+
function absorb_singleton_pieces(pieces, cluster_graph) {
|
|
533
|
+
if (pieces.length < 2) {
|
|
534
|
+
return pieces;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
/**
|
|
538
|
+
* `cluster -> index of the piece holding it`.
|
|
539
|
+
* @type {Map<number, number>}
|
|
540
|
+
*/
|
|
541
|
+
const piece_of = new Map();
|
|
542
|
+
|
|
543
|
+
const alive = pieces.map((piece) => piece.slice());
|
|
544
|
+
|
|
545
|
+
alive.forEach((piece, index) => {
|
|
546
|
+
for (const cluster of piece) {
|
|
547
|
+
piece_of.set(cluster, index);
|
|
548
|
+
}
|
|
549
|
+
});
|
|
550
|
+
|
|
551
|
+
for (let index = 0; index < alive.length; index++) {
|
|
552
|
+
if (alive[index].length !== 1) {
|
|
553
|
+
continue;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
const cluster = alive[index][0];
|
|
557
|
+
|
|
558
|
+
/**
|
|
559
|
+
* `piece index -> boundary shared with this cluster`.
|
|
560
|
+
* @type {Map<number, number>}
|
|
561
|
+
*/
|
|
562
|
+
const shared = new Map();
|
|
563
|
+
|
|
564
|
+
for (let i = cluster_graph.edge_addresses[cluster]; i < cluster_graph.edge_addresses[cluster + 1]; i++) {
|
|
565
|
+
const target = piece_of.get(cluster_graph.adjacency[i]);
|
|
566
|
+
|
|
567
|
+
if (target === undefined || target === index) {
|
|
568
|
+
continue;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
shared.set(target, (shared.get(target) ?? 0) + cluster_graph.edge_weights[i]);
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
let best = -1;
|
|
575
|
+
let best_weight = -1;
|
|
576
|
+
|
|
577
|
+
// Sorted, so that two pieces sharing the same boundary length resolve the same way every
|
|
578
|
+
// time rather than however the adjacency happened to be walked.
|
|
579
|
+
for (const [target, weight] of Array.from(shared.entries()).sort((a, b) => a[0] - b[0])) {
|
|
580
|
+
if (weight > best_weight) {
|
|
581
|
+
best = target;
|
|
582
|
+
best_weight = weight;
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
assert.notEqual(
|
|
587
|
+
best, -1,
|
|
588
|
+
'a lone cluster inside a connected component must border another piece of it'
|
|
589
|
+
);
|
|
590
|
+
|
|
591
|
+
alive[best].push(cluster);
|
|
592
|
+
alive[best].sort((a, b) => a - b);
|
|
593
|
+
|
|
594
|
+
piece_of.set(cluster, best);
|
|
595
|
+
|
|
596
|
+
alive[index] = [];
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
return alive.filter((piece) => piece.length > 0);
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
/**
|
|
603
|
+
* Partition a band of the frontier into groups of roughly `group_size`.
|
|
604
|
+
*
|
|
605
|
+
* The partition runs **inside each connected component** of the cluster graph, not across the band as
|
|
606
|
+
* a whole. Partitioning globally and splitting the disconnected parts afterwards looks equivalent and
|
|
607
|
+
* is not: a scene assembled from thousands of separate shells has a cluster graph in thousands of
|
|
608
|
+
* pieces, and a balanced global partition draws each part from several of them, so the connectivity
|
|
609
|
+
* split then shreds nearly every group down to a single cluster.
|
|
610
|
+
*
|
|
611
|
+
* What is left over after that — every cluster alone in its component — is bundled by proximity
|
|
612
|
+
* instead, because for those clusters there is no adjacency left to respect and no other way to the
|
|
613
|
+
* root.
|
|
614
|
+
*
|
|
615
|
+
* @param {VGeoFrontier} frontier
|
|
616
|
+
* @param {number[]} band cluster indices this round may consume, ascending
|
|
617
|
+
* @param {object} cluster_graph as {@link build_cluster_graph} returns
|
|
618
|
+
* @param {number} group_size
|
|
619
|
+
* @returns {number[][]} groups of at least two clusters each; clusters left out stay in the frontier
|
|
620
|
+
*/
|
|
621
|
+
function partition_into_groups(frontier, band, cluster_graph, group_size) {
|
|
622
|
+
const membership = new Int32Array(frontier.clusters.length).fill(-1);
|
|
623
|
+
|
|
624
|
+
for (const cluster of band) {
|
|
625
|
+
membership[cluster] = 0;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
const components = vgeo_graph_components(
|
|
629
|
+
cluster_graph.edge_addresses, cluster_graph.adjacency, band, membership, 0
|
|
630
|
+
);
|
|
631
|
+
|
|
632
|
+
const result = [];
|
|
633
|
+
|
|
634
|
+
/**
|
|
635
|
+
* Clusters with nobody adjacent to merge with.
|
|
636
|
+
* @type {number[]}
|
|
637
|
+
*/
|
|
638
|
+
const loose = [];
|
|
639
|
+
|
|
640
|
+
let token = 0;
|
|
641
|
+
|
|
642
|
+
/**
|
|
643
|
+
* @param {number[]} piece
|
|
644
|
+
*/
|
|
645
|
+
function accept(piece) {
|
|
646
|
+
if (piece.length < 2) {
|
|
647
|
+
loose.push(piece[0]);
|
|
648
|
+
|
|
649
|
+
return;
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
// The format caps a group at 4096 clusters. Nothing the size policy produces comes near it,
|
|
653
|
+
// but the cap is hard, so the chunking is here rather than assumed away.
|
|
654
|
+
for (let start = 0; start < piece.length; start += VGEO_MAX_GROUP_CLUSTER_COUNT) {
|
|
655
|
+
const chunk = piece.slice(start, start + VGEO_MAX_GROUP_CLUSTER_COUNT);
|
|
656
|
+
|
|
657
|
+
if (chunk.length < 2) {
|
|
658
|
+
loose.push(chunk[0]);
|
|
659
|
+
|
|
660
|
+
continue;
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
result.push(chunk);
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
for (const component of components) {
|
|
668
|
+
if (component.length < 2) {
|
|
669
|
+
// Alone in its component: no adjacency left anywhere, so proximity is the only rule that
|
|
670
|
+
// can still place it.
|
|
671
|
+
loose.push(component[0]);
|
|
672
|
+
|
|
673
|
+
continue;
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
if (component.length <= group_size) {
|
|
677
|
+
accept(component);
|
|
678
|
+
|
|
679
|
+
continue;
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
/**
|
|
683
|
+
* @type {number[][]}
|
|
684
|
+
*/
|
|
685
|
+
const pieces = [];
|
|
686
|
+
|
|
687
|
+
const part_count = Math.ceil(component.length / group_size);
|
|
688
|
+
|
|
689
|
+
token++;
|
|
690
|
+
|
|
691
|
+
for (const cluster of component) {
|
|
692
|
+
membership[cluster] = token;
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
const local_index = new Map();
|
|
696
|
+
|
|
697
|
+
component.forEach((cluster, index) => local_index.set(cluster, index));
|
|
698
|
+
|
|
699
|
+
const local_addresses = new Uint32Array(component.length + 1);
|
|
700
|
+
const local_adjacency = [];
|
|
701
|
+
const local_weights = [];
|
|
702
|
+
|
|
703
|
+
component.forEach((cluster, index) => {
|
|
704
|
+
local_addresses[index] = local_adjacency.length;
|
|
705
|
+
|
|
706
|
+
for (let i = cluster_graph.edge_addresses[cluster]; i < cluster_graph.edge_addresses[cluster + 1]; i++) {
|
|
707
|
+
const other = cluster_graph.adjacency[i];
|
|
708
|
+
|
|
709
|
+
if (membership[other] !== token) {
|
|
710
|
+
continue;
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
local_adjacency.push(local_index.get(other));
|
|
714
|
+
local_weights.push(cluster_graph.edge_weights[i]);
|
|
715
|
+
}
|
|
716
|
+
});
|
|
717
|
+
|
|
718
|
+
local_addresses[component.length] = local_adjacency.length;
|
|
719
|
+
|
|
720
|
+
const assignment = vgeo_partition_graph(
|
|
721
|
+
component.length,
|
|
722
|
+
part_count,
|
|
723
|
+
local_addresses,
|
|
724
|
+
Uint32Array.from(local_adjacency),
|
|
725
|
+
Float32Array.from(local_weights),
|
|
726
|
+
GROUP_SEED
|
|
727
|
+
);
|
|
728
|
+
|
|
729
|
+
const parts = [];
|
|
730
|
+
|
|
731
|
+
for (let i = 0; i < part_count; i++) {
|
|
732
|
+
parts.push([]);
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
component.forEach((cluster, index) => {
|
|
736
|
+
parts[assignment[index]].push(cluster);
|
|
737
|
+
});
|
|
738
|
+
|
|
739
|
+
for (const part of parts) {
|
|
740
|
+
if (part.length === 0) {
|
|
741
|
+
continue;
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
token++;
|
|
745
|
+
|
|
746
|
+
for (const cluster of part) {
|
|
747
|
+
membership[cluster] = token;
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
for (const piece of vgeo_graph_components(
|
|
751
|
+
cluster_graph.edge_addresses, cluster_graph.adjacency, part, membership, token
|
|
752
|
+
)) {
|
|
753
|
+
pieces.push(piece);
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
for (const piece of absorb_singleton_pieces(pieces, cluster_graph)) {
|
|
758
|
+
accept(piece);
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
for (const bundle of bundle_loose_clusters(frontier, loose, group_size)) {
|
|
763
|
+
result.push(bundle);
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
result.sort((a, b) => a[0] - b[0]);
|
|
767
|
+
|
|
768
|
+
return result;
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
/**
|
|
772
|
+
* Does any face outside this group use the edge?
|
|
773
|
+
*
|
|
774
|
+
* @param {BinaryTopology} topology
|
|
775
|
+
* @param {number} edge
|
|
776
|
+
* @param {Int32Array} group_of_face
|
|
777
|
+
* @param {number} group_index
|
|
778
|
+
* @returns {boolean}
|
|
779
|
+
*/
|
|
780
|
+
function edge_touches_outside(topology, edge, group_of_face, group_index) {
|
|
781
|
+
const first_loop = topology.edge_read_loop(edge);
|
|
782
|
+
|
|
783
|
+
if (first_loop === NULL_POINTER) {
|
|
784
|
+
return false;
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
let loop = first_loop;
|
|
788
|
+
|
|
789
|
+
do {
|
|
790
|
+
const face = topology.loop_read_face(loop);
|
|
791
|
+
|
|
792
|
+
if (face !== NULL_POINTER && group_of_face[face] !== group_index) {
|
|
793
|
+
return true;
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
loop = topology.loop_read_radial_next(loop);
|
|
797
|
+
} while (loop !== first_loop && loop !== NULL_POINTER);
|
|
798
|
+
|
|
799
|
+
return false;
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
/**
|
|
803
|
+
* Does any face outside this group use the vertex? Walks the vertex's disk cycle and each edge's
|
|
804
|
+
* radial cycle, so the answer is local to the vertex rather than a scan of the frontier.
|
|
805
|
+
*
|
|
806
|
+
* @param {BinaryTopology} topology
|
|
807
|
+
* @param {number} vertex
|
|
808
|
+
* @param {Int32Array} group_of_face
|
|
809
|
+
* @param {number} group_index
|
|
810
|
+
* @returns {boolean}
|
|
811
|
+
*/
|
|
812
|
+
function vertex_touches_outside(topology, vertex, group_of_face, group_index) {
|
|
813
|
+
const first_edge = topology.vertex_read_edge(vertex);
|
|
814
|
+
|
|
815
|
+
if (first_edge === NULL_POINTER) {
|
|
816
|
+
return false;
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
let edge = first_edge;
|
|
820
|
+
|
|
821
|
+
do {
|
|
822
|
+
if (edge_touches_outside(topology, edge, group_of_face, group_index)) {
|
|
823
|
+
return true;
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
edge = (topology.edge_read_vertex1(edge) === vertex)
|
|
827
|
+
? topology.edge_read_v1_disk_next(edge)
|
|
828
|
+
: topology.edge_read_v2_disk_next(edge);
|
|
829
|
+
} while (edge !== first_edge && edge !== NULL_POINTER);
|
|
830
|
+
|
|
831
|
+
return false;
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
/**
|
|
835
|
+
* Which vertices a group must not move, and which boundary edges must survive its simplification.
|
|
836
|
+
*
|
|
837
|
+
* A vertex is locked when any face outside the group touches it — not merely when it sits on a
|
|
838
|
+
* shared *edge*. The weaker rule leaks: two groups can meet at a single non-manifold vertex with no
|
|
839
|
+
* edge between them, and each would then move that vertex somewhere else, tearing the surface at a
|
|
840
|
+
* point no boundary walk would have flagged.
|
|
841
|
+
*
|
|
842
|
+
* "Outside" includes frontier clusters this round left alone, which is what keeps a band's seam with
|
|
843
|
+
* the rest of the frontier from opening.
|
|
844
|
+
*
|
|
845
|
+
* @param {BinaryTopology} topology
|
|
846
|
+
* @param {number[]} group_face_ids
|
|
847
|
+
* @param {Int32Array} group_of_face one entry per face slot, holding the group each face belongs to
|
|
848
|
+
* @param {number} group_index
|
|
849
|
+
* @returns {{locked: Set<number>, polyline: number[][]}} locked vertex ids, and the `[v1, v2]` pairs
|
|
850
|
+
* of every edge this group shares with the outside
|
|
851
|
+
*/
|
|
852
|
+
function compute_group_boundary(topology, group_face_ids, group_of_face, group_index) {
|
|
853
|
+
const vertices = new Set();
|
|
854
|
+
const edges = new Set();
|
|
855
|
+
|
|
856
|
+
for (const face of group_face_ids) {
|
|
857
|
+
const first_loop = topology.face_read_loop(face);
|
|
858
|
+
|
|
859
|
+
let loop = first_loop;
|
|
860
|
+
|
|
861
|
+
do {
|
|
862
|
+
vertices.add(topology.loop_read_vertex(loop));
|
|
863
|
+
edges.add(topology.loop_read_edge(loop));
|
|
864
|
+
|
|
865
|
+
loop = topology.loop_read_next(loop);
|
|
866
|
+
} while (loop !== first_loop);
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
const locked = new Set();
|
|
870
|
+
|
|
871
|
+
for (const vertex of vertices) {
|
|
872
|
+
if (vertex_touches_outside(topology, vertex, group_of_face, group_index)) {
|
|
873
|
+
locked.add(vertex);
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
const polyline = [];
|
|
878
|
+
|
|
879
|
+
for (const edge of edges) {
|
|
880
|
+
if (!edge_touches_outside(topology, edge, group_of_face, group_index)) {
|
|
881
|
+
continue;
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
polyline.push([topology.edge_read_vertex1(edge), topology.edge_read_vertex2(edge)]);
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
return { locked, polyline };
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
/**
|
|
891
|
+
* Largest error any of a group's clusters carries -- the floor a step above them must respect.
|
|
892
|
+
*
|
|
893
|
+
* @param {VirtualGeometryCluster[]} clusters
|
|
894
|
+
* @returns {number}
|
|
895
|
+
*/
|
|
896
|
+
function max_child_error(clusters) {
|
|
897
|
+
let result = 0;
|
|
898
|
+
|
|
899
|
+
for (const cluster of clusters) {
|
|
900
|
+
result = Math.max(result, cluster.self_error);
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
return result;
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
/**
|
|
907
|
+
* Leaves with room to spare between them, re-cut into fewer and fuller leaves.
|
|
908
|
+
*
|
|
909
|
+
* This is not a level of the DAG and must not become one. Nothing is simplified, so the new clusters
|
|
910
|
+
* carry the same error their inputs did -- zero -- and a step whose parent error equals its child
|
|
911
|
+
* error is a step the cut rule can never take: a cluster is drawn only while `project(parent) > tau`,
|
|
912
|
+
* and a parent at zero error projects to zero unconditionally. The leaves under it would be stored,
|
|
913
|
+
* paged, and never once selected. So the re-cut *replaces* them; they were a way of cutting this
|
|
914
|
+
* geometry up, and now there is a better one.
|
|
915
|
+
*
|
|
916
|
+
* It is what makes a scene of small separate parts tractable. A thousand twelve-triangle bolts are a
|
|
917
|
+
* thousand clusters at eight per cent fill, and no simplification is what they need -- they need to
|
|
918
|
+
* be cut up differently. Repacking is bounded by the grouping that proposed it, so what lands in one
|
|
919
|
+
* cluster is what the adjacency and proximity rules already judged to belong together.
|
|
920
|
+
*
|
|
921
|
+
* Only leaves. Above them the same re-cut would orphan a subtree, since a coarser cluster cannot be
|
|
922
|
+
* deleted -- something hangs off it.
|
|
923
|
+
*
|
|
924
|
+
* @param {VGeoFrontier} frontier
|
|
925
|
+
* @param {number[]} group_clusters cluster indices
|
|
926
|
+
* @param {number} parent_target how many clusters the group is aiming to become
|
|
927
|
+
* @returns {GroupRepack|undefined} `undefined` when the group must be simplified instead
|
|
928
|
+
*/
|
|
929
|
+
function try_repack(frontier, group_clusters, parent_target) {
|
|
930
|
+
const face_ids = [];
|
|
931
|
+
|
|
932
|
+
for (const cluster of group_clusters) {
|
|
933
|
+
if (frontier.clusters[cluster].child_group !== undefined) {
|
|
934
|
+
return undefined;
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
for (const face of frontier.cluster_faces[cluster]) {
|
|
938
|
+
face_ids.push(face);
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
// Only when the target is already met -- there is nothing to simplify, so the reduction has to
|
|
943
|
+
// come from re-cutting. A group over its target has real geometry to lose and belongs on the
|
|
944
|
+
// simplification path.
|
|
945
|
+
if (face_ids.length > parent_target * VGEO_MAX_CLUSTER_TRIANGLE_COUNT) {
|
|
946
|
+
return undefined;
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
face_ids.sort((a, b) => a - b);
|
|
950
|
+
|
|
951
|
+
const submesh = vgeo_extract_submesh(frontier.mesh, face_ids);
|
|
952
|
+
|
|
953
|
+
const face_graph = new VGeoFaceGraph(
|
|
954
|
+
submesh.mesh.topology, bt_mesh_build_face_graph(submesh.mesh.topology)
|
|
955
|
+
);
|
|
956
|
+
|
|
957
|
+
/**
|
|
958
|
+
*
|
|
959
|
+
* @type {number[]}
|
|
960
|
+
*/
|
|
961
|
+
const all = [];
|
|
962
|
+
|
|
963
|
+
const vertex_count = face_graph.graph.vertex_count;
|
|
964
|
+
for (let vertex = 0; vertex < vertex_count; vertex++) {
|
|
965
|
+
all.push(vertex);
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
const parts = vgeo_split_face_set_packed(
|
|
969
|
+
face_graph, all, VGEO_MAX_CLUSTER_TRIANGLE_COUNT, VGEO_MAX_CLUSTER_VERTEX_COUNT
|
|
970
|
+
);
|
|
971
|
+
|
|
972
|
+
if (parts.length >= group_clusters.length) {
|
|
973
|
+
// The vertex ceiling bit before the triangle one, and re-cutting bought nothing.
|
|
974
|
+
return undefined;
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
const repack = new GroupRepack();
|
|
978
|
+
|
|
979
|
+
repack.replaced = group_clusters.map((cluster) => frontier.clusters[cluster]);
|
|
980
|
+
|
|
981
|
+
for (const part of parts) {
|
|
982
|
+
const part_face_ids = part.map((dense) => face_graph.vertex_face_ids[dense]);
|
|
983
|
+
|
|
984
|
+
part_face_ids.sort((a, b) => a - b);
|
|
985
|
+
|
|
986
|
+
const built = build_cluster(submesh.mesh, part_face_ids, 0);
|
|
987
|
+
|
|
988
|
+
// A leaf's own bounds are its LOD-metric sphere, and it deviates from nothing.
|
|
989
|
+
built.cluster.self_sphere = built.cluster.bounds_sphere.slice();
|
|
990
|
+
built.cluster.self_error = 0;
|
|
991
|
+
|
|
992
|
+
repack.clusters.push(built.cluster);
|
|
993
|
+
|
|
994
|
+
repack.geometry.push({
|
|
995
|
+
positions: built.gathered.positions,
|
|
996
|
+
indices: built.gathered.indices,
|
|
997
|
+
local_vertex_ids: built.gathered.vertex_ids,
|
|
998
|
+
source_vertex_ids: Uint32Array.from(
|
|
999
|
+
built.gathered.vertex_ids, (local) => submesh.source_vertex_ids[local]
|
|
1000
|
+
),
|
|
1001
|
+
topology: submesh.mesh.topology,
|
|
1002
|
+
});
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
return repack;
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
/**
|
|
1009
|
+
* Simplify one group and cut the result into the parent clusters of every cluster in it.
|
|
1010
|
+
*
|
|
1011
|
+
* @param {VGeoFrontier} frontier
|
|
1012
|
+
* @param {number[]} group_clusters cluster indices
|
|
1013
|
+
* @param {Int32Array} group_of_face one entry per face slot, holding the group each face belongs to
|
|
1014
|
+
* @param {number} group_index
|
|
1015
|
+
* @param {number} level of the parents this step produces
|
|
1016
|
+
* @param {VGeoLevelOptions} options
|
|
1017
|
+
* @returns {GroupStep|undefined} `undefined` when the group has no coarser form at all, and the
|
|
1018
|
+
* round should leave its clusters alone
|
|
1019
|
+
*/
|
|
1020
|
+
function build_group_step(frontier, group_clusters, group_of_face, group_index, level, options) {
|
|
1021
|
+
/**
|
|
1022
|
+
*
|
|
1023
|
+
* @type {number[]}
|
|
1024
|
+
*/
|
|
1025
|
+
const face_ids = [];
|
|
1026
|
+
|
|
1027
|
+
for (const cluster of group_clusters) {
|
|
1028
|
+
for (const face of frontier.cluster_faces[cluster]) {
|
|
1029
|
+
face_ids.push(face);
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
face_ids.sort((a, b) => a - b);
|
|
1034
|
+
|
|
1035
|
+
const child_clusters = group_clusters.map((cluster) => frontier.clusters[cluster]);
|
|
1036
|
+
|
|
1037
|
+
const boundary = compute_group_boundary(
|
|
1038
|
+
frontier.mesh.topology, face_ids, group_of_face, group_index
|
|
1039
|
+
);
|
|
1040
|
+
|
|
1041
|
+
/**
|
|
1042
|
+
* Extract the group, simplify it to `target_face_count`, and cut the result into clusters.
|
|
1043
|
+
*
|
|
1044
|
+
* @param {number} target_face_count
|
|
1045
|
+
* @returns {{submesh: VGeoSubmesh, face_graph: VGeoFaceGraph, parts: number[][],
|
|
1046
|
+
* quadrics: Quadric3[], error: number}}
|
|
1047
|
+
*/
|
|
1048
|
+
function simplify_to(target_face_count) {
|
|
1049
|
+
const submesh = vgeo_extract_submesh(frontier.mesh, face_ids);
|
|
1050
|
+
|
|
1051
|
+
// Cloned, not borrowed: the simplifier mutates these, and this whole attempt may be thrown
|
|
1052
|
+
// away — by the polyline check below, by the round rejecting it, or by a wider band being
|
|
1053
|
+
// tried instead. The frontier's own quadrics must survive all of that untouched.
|
|
1054
|
+
const submesh_source_vertex_ids = submesh.source_vertex_ids;
|
|
1055
|
+
const source_vertex_id_count = submesh_source_vertex_ids.length;
|
|
1056
|
+
|
|
1057
|
+
/**
|
|
1058
|
+
*
|
|
1059
|
+
* @type {Quadric3[]}
|
|
1060
|
+
*/
|
|
1061
|
+
const quadrics = new Array(source_vertex_id_count);
|
|
1062
|
+
|
|
1063
|
+
for (let i = 0; i < source_vertex_id_count; i++) {
|
|
1064
|
+
const source = submesh_source_vertex_ids[i];
|
|
1065
|
+
quadrics[i] = frontier.quadrics[source].clone();
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
/**
|
|
1069
|
+
* `source vertex id -> submesh local id`, for translating the lock set and the polyline.
|
|
1070
|
+
* @type {Map<number, number>}
|
|
1071
|
+
*/
|
|
1072
|
+
const local_of_source = new Map();
|
|
1073
|
+
|
|
1074
|
+
for (let local = 0; local < source_vertex_id_count; local++) {
|
|
1075
|
+
const source = submesh_source_vertex_ids[local];
|
|
1076
|
+
local_of_source.set(source, local);
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
/**
|
|
1080
|
+
*
|
|
1081
|
+
* @type {Set<number>}
|
|
1082
|
+
*/
|
|
1083
|
+
const locked = new Set();
|
|
1084
|
+
|
|
1085
|
+
for (const vertex of boundary.locked) {
|
|
1086
|
+
const local = local_of_source.get(vertex);
|
|
1087
|
+
|
|
1088
|
+
if (local !== undefined) {
|
|
1089
|
+
locked.add(local);
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
// The faces carrying the group's boundary edges. Locking the endpoints keeps them where they
|
|
1094
|
+
// are; protecting these faces is what keeps an edge *between* two locked vertices from
|
|
1095
|
+
// losing the only face that holds it — see the polyline check below, which is the same
|
|
1096
|
+
// guarantee stated as an assertion.
|
|
1097
|
+
|
|
1098
|
+
/**
|
|
1099
|
+
*
|
|
1100
|
+
* @type {Set<number>}
|
|
1101
|
+
*/
|
|
1102
|
+
const protected_faces = new Set();
|
|
1103
|
+
|
|
1104
|
+
for (const [v1, v2] of boundary.polyline) {
|
|
1105
|
+
const edge = bt_edge_get(
|
|
1106
|
+
submesh.mesh.topology, local_of_source.get(v1), local_of_source.get(v2)
|
|
1107
|
+
);
|
|
1108
|
+
|
|
1109
|
+
if (edge === NULL_POINTER) {
|
|
1110
|
+
continue;
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
const first_loop = submesh.mesh.topology.edge_read_loop(edge);
|
|
1114
|
+
|
|
1115
|
+
let loop = first_loop;
|
|
1116
|
+
|
|
1117
|
+
do {
|
|
1118
|
+
const face = submesh.mesh.topology.loop_read_face(loop);
|
|
1119
|
+
|
|
1120
|
+
if (face !== NULL_POINTER) {
|
|
1121
|
+
protected_faces.add(face);
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
loop = submesh.mesh.topology.loop_read_radial_next(loop);
|
|
1125
|
+
} while (loop !== first_loop && loop !== NULL_POINTER);
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
// The simplifier measures this itself, and has to: a cluster consumed whole leaves a
|
|
1129
|
+
// vertex behind that belongs to no face and is about to be swept away, and that vertex is
|
|
1130
|
+
// the only thing still holding the quadric of what used to be there. Measuring survivors
|
|
1131
|
+
// from out here would report a soup of separate triangles losing most of itself as free.
|
|
1132
|
+
const deviation = bt_mesh_simplify_with_quadrics(
|
|
1133
|
+
submesh.mesh.topology, target_face_count, locked, protected_faces, quadrics
|
|
1134
|
+
);
|
|
1135
|
+
|
|
1136
|
+
const defects = bt_mesh_validate(submesh.mesh.topology);
|
|
1137
|
+
|
|
1138
|
+
if (defects.length > 0) {
|
|
1139
|
+
throw new Error(
|
|
1140
|
+
`simplifying a group of ${group_clusters.length} clusters left the mesh inconsistent: `
|
|
1141
|
+
+ defects.slice(0, 4).map((defect) => defect.detail).join('; ')
|
|
1142
|
+
);
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
// The design asks for this explicitly: pinning a vertex does not by itself protect the
|
|
1146
|
+
// *edges* between pinned vertices, and a face-kill path can strip one. A missing boundary
|
|
1147
|
+
// edge is a crack between two groups that will never close, so the build stops here rather
|
|
1148
|
+
// than shipping it.
|
|
1149
|
+
for (const [v1, v2] of boundary.polyline) {
|
|
1150
|
+
const local_1 = local_of_source.get(v1);
|
|
1151
|
+
const local_2 = local_of_source.get(v2);
|
|
1152
|
+
|
|
1153
|
+
if (
|
|
1154
|
+
local_1 === undefined
|
|
1155
|
+
|| local_2 === undefined
|
|
1156
|
+
|| bt_edge_get(submesh.mesh.topology, local_1, local_2) === NULL_POINTER
|
|
1157
|
+
) {
|
|
1158
|
+
throw new Error(
|
|
1159
|
+
`group boundary edge (${v1}, ${v2}) did not survive simplification of a group of `
|
|
1160
|
+
+ `${group_clusters.length} clusters`
|
|
1161
|
+
);
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
/*
|
|
1166
|
+
The step moved the geometry, so the frame that describes it has to move with it — but only
|
|
1167
|
+
where the step was free to move anything. `locked` is the group's perimeter, the vertices
|
|
1168
|
+
every neighbouring group also carries, and it is what a cut leans on to put two levels of
|
|
1169
|
+
detail side by side without a crack. Derived here, a perimeter vertex would get the average
|
|
1170
|
+
of this group's faces alone while its neighbour got the average of that group's, and the
|
|
1171
|
+
seam that is watertight in geometry would show as a crease in shading.
|
|
1172
|
+
*/
|
|
1173
|
+
vgeo_compute_vertex_normals(submesh.mesh, locked);
|
|
1174
|
+
vgeo_compute_vertex_tangents(submesh.mesh, locked);
|
|
1175
|
+
|
|
1176
|
+
const face_graph = new VGeoFaceGraph(
|
|
1177
|
+
submesh.mesh.topology, bt_mesh_build_face_graph(submesh.mesh.topology)
|
|
1178
|
+
);
|
|
1179
|
+
|
|
1180
|
+
const all = [];
|
|
1181
|
+
|
|
1182
|
+
for (let vertex = 0; vertex < face_graph.graph.vertex_count; vertex++) {
|
|
1183
|
+
all.push(vertex);
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
// Packed: this is a group, so its extent is already the group's, and one cluster per
|
|
1187
|
+
// surviving island would undo the merge the group exists to make.
|
|
1188
|
+
const parts = vgeo_split_face_set_packed(
|
|
1189
|
+
face_graph, all, VGEO_MAX_CLUSTER_TRIANGLE_COUNT, VGEO_MAX_CLUSTER_VERTEX_COUNT
|
|
1190
|
+
);
|
|
1191
|
+
|
|
1192
|
+
return { submesh, face_graph, parts, quadrics, error: deviation };
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
// Aim at a number of *clusters*, not a fraction of the triangles. Halving the triangle count is
|
|
1196
|
+
// a means; halving the cluster count is the end, and asking for it directly is what keeps
|
|
1197
|
+
// parents full. Where the children are already full the two coincide exactly. Where they are
|
|
1198
|
+
// not, this simplifies less — because the reduction is coming from packing the geometry better
|
|
1199
|
+
// rather than from destroying it, which is the outcome to prefer.
|
|
1200
|
+
const parent_target = Math.max(
|
|
1201
|
+
1, Math.ceil(group_clusters.length * options.simplification_ratio)
|
|
1202
|
+
);
|
|
1203
|
+
|
|
1204
|
+
const outcome = simplify_to(Math.min(
|
|
1205
|
+
parent_target * VGEO_MAX_CLUSTER_TRIANGLE_COUNT,
|
|
1206
|
+
// A group of under-filled clusters can already be under the cluster-count target, in which
|
|
1207
|
+
// case nothing would be simplified and the step would record no error — a step the cut rule
|
|
1208
|
+
// cannot take. Falling back to the triangle ratio keeps it a real simplification.
|
|
1209
|
+
Math.max(2, Math.round(face_ids.length * options.simplification_ratio))
|
|
1210
|
+
));
|
|
1211
|
+
|
|
1212
|
+
if (outcome.parts.length === 0) {
|
|
1213
|
+
// Some small closed shells have no coarser form under edge collapse at all — a tetrahedron
|
|
1214
|
+
// loses two faces to the collapse and its other two to the fuse that follows, leaving
|
|
1215
|
+
// nothing. Nothing to build a parent from, so the group is refused and its clusters go back
|
|
1216
|
+
// to the frontier to be grouped differently.
|
|
1217
|
+
return undefined;
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
const submesh = outcome.submesh;
|
|
1221
|
+
const submesh_face_graph = outcome.face_graph;
|
|
1222
|
+
const parts = outcome.parts;
|
|
1223
|
+
|
|
1224
|
+
const step = new GroupStep();
|
|
1225
|
+
|
|
1226
|
+
step.child_clusters = child_clusters;
|
|
1227
|
+
|
|
1228
|
+
// Floored by what the children already carried. The accumulated quadric is a deviation from the
|
|
1229
|
+
// source and nothing forces it to rise when the vertex carrying the previous maximum is the one
|
|
1230
|
+
// collapsed away — but a parent that claimed to be closer to the source than its own child would
|
|
1231
|
+
// make the cut oscillate, so monotonicity is imposed here rather than hoped for.
|
|
1232
|
+
step.error = Math.max(outcome.error, max_child_error(child_clusters));
|
|
1233
|
+
|
|
1234
|
+
submesh.source_vertex_ids.forEach((source, local) => {
|
|
1235
|
+
if (outcome.quadrics[local] !== null) {
|
|
1236
|
+
step.quadrics.set(source, outcome.quadrics[local]);
|
|
1237
|
+
}
|
|
1238
|
+
});
|
|
1239
|
+
|
|
1240
|
+
const parent_points = [];
|
|
1241
|
+
|
|
1242
|
+
for (const part of parts) {
|
|
1243
|
+
const part_face_ids = part.map((dense) => submesh_face_graph.vertex_face_ids[dense]);
|
|
1244
|
+
|
|
1245
|
+
part_face_ids.sort((a, b) => a - b);
|
|
1246
|
+
|
|
1247
|
+
const built = build_cluster(submesh.mesh, part_face_ids, level);
|
|
1248
|
+
|
|
1249
|
+
step.parent_clusters.push(built.cluster);
|
|
1250
|
+
|
|
1251
|
+
step.parent_geometry.push({
|
|
1252
|
+
positions: built.gathered.positions,
|
|
1253
|
+
indices: built.gathered.indices,
|
|
1254
|
+
local_vertex_ids: built.gathered.vertex_ids,
|
|
1255
|
+
source_vertex_ids: Uint32Array.from(
|
|
1256
|
+
built.gathered.vertex_ids, (local) => submesh.source_vertex_ids[local]
|
|
1257
|
+
),
|
|
1258
|
+
topology: submesh.mesh.topology,
|
|
1259
|
+
});
|
|
1260
|
+
|
|
1261
|
+
parent_points.push(built.gathered.positions);
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
let point_count = 0;
|
|
1265
|
+
|
|
1266
|
+
for (const points of parent_points) {
|
|
1267
|
+
point_count += points.length;
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1270
|
+
const points = new Float32Array(point_count);
|
|
1271
|
+
|
|
1272
|
+
let cursor = 0;
|
|
1273
|
+
|
|
1274
|
+
for (const part of parent_points) {
|
|
1275
|
+
points.set(part, cursor);
|
|
1276
|
+
|
|
1277
|
+
cursor += part.length;
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1280
|
+
// One sphere for the whole step, containing both what it consumed and what it produced. Sharing
|
|
1281
|
+
// it is what makes every sibling project the same value, and containing the children is what
|
|
1282
|
+
// makes the pair monotonic.
|
|
1283
|
+
step.sphere = vgeo_containing_sphere(
|
|
1284
|
+
step.child_clusters.map((cluster) => cluster.self_sphere),
|
|
1285
|
+
points
|
|
1286
|
+
);
|
|
1287
|
+
|
|
1288
|
+
return step;
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1291
|
+
/**
|
|
1292
|
+
* Stitch a round's output back into one mesh: freshly simplified clusters, and the clusters the
|
|
1293
|
+
* round left alone, side by side.
|
|
1294
|
+
*
|
|
1295
|
+
* Vertices are unified by the id they had in the frontier below, never by position: a vertex two
|
|
1296
|
+
* groups share is pinned, so both carry it unmoved and unmistakably, while a vertex only one group
|
|
1297
|
+
* holds is its own even if a collapse happened to place it exactly where another landed. An
|
|
1298
|
+
* untouched cluster names its vertices in that same space, so it stitches to its simplified
|
|
1299
|
+
* neighbour along exactly the boundary that neighbour was forbidden to move.
|
|
1300
|
+
*
|
|
1301
|
+
* @param {VGeoContribution[]} contributions parallel to the clusters of the frontier being built
|
|
1302
|
+
* @param {(BinaryDataLayer|null)[]} template_layers the channels the frontier below carries, which
|
|
1303
|
+
* the frontier being stitched carries too
|
|
1304
|
+
* @returns {{mesh: VirtualGeometryMesh, cluster_faces: number[][], source_vertex_ids: Uint32Array}}
|
|
1305
|
+
* the last of which names, for every vertex of the new mesh, the vertex of the old one it came
|
|
1306
|
+
* from — which is how its accumulated quadric follows it across the seam
|
|
1307
|
+
*/
|
|
1308
|
+
function stitch_frontier(contributions, template_layers) {
|
|
1309
|
+
/**
|
|
1310
|
+
* `source vertex id -> index in the frontier being built`.
|
|
1311
|
+
* @type {Map<number, number>}
|
|
1312
|
+
*/
|
|
1313
|
+
const index_of_source = new Map();
|
|
1314
|
+
|
|
1315
|
+
const positions = [];
|
|
1316
|
+
const indices = [];
|
|
1317
|
+
const cluster_faces = [];
|
|
1318
|
+
|
|
1319
|
+
/**
|
|
1320
|
+
* `[mesh the vertex came from, its id there]` per new vertex, so its attribute record can be
|
|
1321
|
+
* copied across once the topology that receives it exists.
|
|
1322
|
+
* @type {[BinaryTopology, number][]}
|
|
1323
|
+
*/
|
|
1324
|
+
const attribute_sources = [];
|
|
1325
|
+
|
|
1326
|
+
let face_cursor = 0;
|
|
1327
|
+
|
|
1328
|
+
for (const geometry of contributions) {
|
|
1329
|
+
const remap = new Uint32Array(geometry.local_vertex_ids.length);
|
|
1330
|
+
|
|
1331
|
+
geometry.local_vertex_ids.forEach((local, i) => {
|
|
1332
|
+
const source = geometry.source_vertex_ids[i];
|
|
1333
|
+
|
|
1334
|
+
let target = index_of_source.get(source);
|
|
1335
|
+
|
|
1336
|
+
if (target === undefined) {
|
|
1337
|
+
target = positions.length / 3;
|
|
1338
|
+
|
|
1339
|
+
index_of_source.set(source, target);
|
|
1340
|
+
|
|
1341
|
+
positions.push(
|
|
1342
|
+
geometry.positions[i * 3],
|
|
1343
|
+
geometry.positions[i * 3 + 1],
|
|
1344
|
+
geometry.positions[i * 3 + 2]
|
|
1345
|
+
);
|
|
1346
|
+
|
|
1347
|
+
attribute_sources.push([geometry.topology, local]);
|
|
1348
|
+
}
|
|
1349
|
+
|
|
1350
|
+
remap[i] = target;
|
|
1351
|
+
});
|
|
1352
|
+
|
|
1353
|
+
const triangle_count = geometry.indices.length / 3;
|
|
1354
|
+
|
|
1355
|
+
const faces = [];
|
|
1356
|
+
|
|
1357
|
+
for (let i = 0; i < geometry.indices.length; i++) {
|
|
1358
|
+
indices.push(remap[geometry.indices[i]]);
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1361
|
+
for (let i = 0; i < triangle_count; i++) {
|
|
1362
|
+
faces.push(face_cursor + i);
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1365
|
+
face_cursor += triangle_count;
|
|
1366
|
+
|
|
1367
|
+
cluster_faces.push(faces);
|
|
1368
|
+
}
|
|
1369
|
+
|
|
1370
|
+
const topology = new BinaryTopology();
|
|
1371
|
+
|
|
1372
|
+
// Before the vertices: adding a layer widens every record already in the block, and a frontier
|
|
1373
|
+
// holds every vertex of a level.
|
|
1374
|
+
const layers = vgeo_layers_like(topology, template_layers);
|
|
1375
|
+
|
|
1376
|
+
bt_mesh_from_indexed_geometry(topology, Uint32Array.from(indices), Float32Array.from(positions));
|
|
1377
|
+
|
|
1378
|
+
attribute_sources.forEach(([source, local], target) => {
|
|
1379
|
+
vgeo_layers_copy_vertex(topology, source, local, target);
|
|
1380
|
+
});
|
|
1381
|
+
|
|
1382
|
+
const mesh = new VirtualGeometryMesh(topology, layers);
|
|
1383
|
+
|
|
1384
|
+
const source_vertex_ids = new Uint32Array(index_of_source.size);
|
|
1385
|
+
|
|
1386
|
+
for (const [source, target] of index_of_source) {
|
|
1387
|
+
source_vertex_ids[target] = source;
|
|
1388
|
+
}
|
|
1389
|
+
|
|
1390
|
+
return { mesh, cluster_faces, source_vertex_ids };
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1393
|
+
/**
|
|
1394
|
+
* The frontier's clusters ordered by error, which is what a band is a slice of.
|
|
1395
|
+
*
|
|
1396
|
+
* @param {VGeoFrontier} frontier
|
|
1397
|
+
* @returns {{order: number[], errors: number[]}} cluster indices ascending by error, and their
|
|
1398
|
+
* errors in the same order
|
|
1399
|
+
*/
|
|
1400
|
+
function order_by_error(frontier) {
|
|
1401
|
+
const order = frontier.clusters.map((cluster, index) => index);
|
|
1402
|
+
|
|
1403
|
+
order.sort(
|
|
1404
|
+
(a, b) => (frontier.clusters[a].self_error - frontier.clusters[b].self_error) || (a - b)
|
|
1405
|
+
);
|
|
1406
|
+
|
|
1407
|
+
return { order, errors: order.map((index) => frontier.clusters[index].self_error) };
|
|
1408
|
+
}
|
|
1409
|
+
|
|
1410
|
+
/**
|
|
1411
|
+
* The bands a round tries, in the order it tries them.
|
|
1412
|
+
*
|
|
1413
|
+
* Each is one error *octave*: `[e, e · growth]` for each distinct error the frontier holds, finest
|
|
1414
|
+
* first. An octave rather than a running threshold is what bounds how far apart two clusters in one
|
|
1415
|
+
* group can be — a group shares a single parent error, so merging a nearly-exact cluster with a
|
|
1416
|
+
* badly-approximated one drags the exact one up to the other's error for nothing. It is also what
|
|
1417
|
+
* lets a cluster *wait*: geometry outside the octave is simply not this round's business.
|
|
1418
|
+
*
|
|
1419
|
+
* The last band is the whole frontier. It is the escape hatch, not the plan: a frontier of two
|
|
1420
|
+
* clusters an order of magnitude apart in error has no octave holding both, and merging them at a
|
|
1421
|
+
* wide error span still beats never reaching a root.
|
|
1422
|
+
*
|
|
1423
|
+
* @param {number[]} errors ascending, as {@link order_by_error} returns
|
|
1424
|
+
* @param {number} growth
|
|
1425
|
+
* @param {number} min_fraction least share of the frontier a band may cover
|
|
1426
|
+
* @returns {{start: number, end: number}[]} half-open ranges into the error order, deduplicated
|
|
1427
|
+
*/
|
|
1428
|
+
function error_bands(errors, growth, min_fraction) {
|
|
1429
|
+
/**
|
|
1430
|
+
* @param {number} value
|
|
1431
|
+
* @returns {number} first position whose error exceeds `value`
|
|
1432
|
+
*/
|
|
1433
|
+
function upper_bound(value) {
|
|
1434
|
+
let low = 0;
|
|
1435
|
+
let high = errors.length;
|
|
1436
|
+
|
|
1437
|
+
while (low < high) {
|
|
1438
|
+
const middle = (low + high) >> 1;
|
|
1439
|
+
|
|
1440
|
+
if (errors[middle] <= value) {
|
|
1441
|
+
low = middle + 1;
|
|
1442
|
+
} else {
|
|
1443
|
+
high = middle;
|
|
1444
|
+
}
|
|
1445
|
+
}
|
|
1446
|
+
|
|
1447
|
+
return low;
|
|
1448
|
+
}
|
|
1449
|
+
|
|
1450
|
+
const result = [];
|
|
1451
|
+
const seen = new Set();
|
|
1452
|
+
|
|
1453
|
+
/**
|
|
1454
|
+
* @param {number} start
|
|
1455
|
+
* @param {number} end
|
|
1456
|
+
*/
|
|
1457
|
+
function add(start, end) {
|
|
1458
|
+
const key = `${start}:${end}`;
|
|
1459
|
+
|
|
1460
|
+
if (end - start < 2 || seen.has(key)) {
|
|
1461
|
+
return;
|
|
1462
|
+
}
|
|
1463
|
+
|
|
1464
|
+
seen.add(key);
|
|
1465
|
+
|
|
1466
|
+
result.push({ start, end });
|
|
1467
|
+
}
|
|
1468
|
+
|
|
1469
|
+
const minimum = Math.max(2, Math.ceil(errors.length * min_fraction));
|
|
1470
|
+
|
|
1471
|
+
let cursor = 0;
|
|
1472
|
+
|
|
1473
|
+
while (cursor < errors.length) {
|
|
1474
|
+
const low = errors[cursor];
|
|
1475
|
+
|
|
1476
|
+
add(cursor, Math.max(upper_bound(low * growth), Math.min(errors.length, cursor + minimum)));
|
|
1477
|
+
|
|
1478
|
+
cursor = upper_bound(low);
|
|
1479
|
+
}
|
|
1480
|
+
|
|
1481
|
+
add(0, errors.length);
|
|
1482
|
+
|
|
1483
|
+
return result;
|
|
1484
|
+
}
|
|
1485
|
+
|
|
1486
|
+
/**
|
|
1487
|
+
* One round: pick a band, group it, simplify each group, and report what it produced.
|
|
1488
|
+
*
|
|
1489
|
+
* Every group is speculative and judged on its own. One that comes back with no fewer clusters than
|
|
1490
|
+
* it consumed is simply dropped — its clusters return to the frontier, to be grouped differently by
|
|
1491
|
+
* a later round — rather than failing the round for everyone else. A group that cannot be
|
|
1492
|
+
* simplified is no different in kind from a group that could not be formed, and the frontier already
|
|
1493
|
+
* knows what to do with those: nothing, for now.
|
|
1494
|
+
*
|
|
1495
|
+
* The round as a whole is accepted only when it *strictly* shrinks the frontier. Failing that it
|
|
1496
|
+
* widens the band, and failing that it grows the group — small groups starve the simplifier, and a
|
|
1497
|
+
* wider band gives a fine region coarser neighbours to merge with.
|
|
1498
|
+
*
|
|
1499
|
+
* @param {VGeoFrontier} frontier
|
|
1500
|
+
* @param {number} level of the parents this round would produce
|
|
1501
|
+
* @param {VGeoLevelOptions} options
|
|
1502
|
+
* @returns {{steps: GroupStep[], repacks: GroupRepack[], merged: Set<number>}|undefined} what to
|
|
1503
|
+
* commit and which frontier clusters it consumed; `undefined` when nothing this round could try
|
|
1504
|
+
* made the frontier smaller
|
|
1505
|
+
*/
|
|
1506
|
+
function run_round(frontier, level, options) {
|
|
1507
|
+
const face_graph = new VGeoFaceGraph(
|
|
1508
|
+
frontier.mesh.topology, bt_mesh_build_face_graph(frontier.mesh.topology)
|
|
1509
|
+
);
|
|
1510
|
+
|
|
1511
|
+
const cluster_graph = build_cluster_graph(frontier, face_graph);
|
|
1512
|
+
|
|
1513
|
+
const ordered = order_by_error(frontier);
|
|
1514
|
+
|
|
1515
|
+
const bands = error_bands(
|
|
1516
|
+
ordered.errors, options.error_band_growth, options.error_band_min_fraction
|
|
1517
|
+
);
|
|
1518
|
+
|
|
1519
|
+
let group_size = options.group_size;
|
|
1520
|
+
|
|
1521
|
+
for (; ;) {
|
|
1522
|
+
for (const range of bands) {
|
|
1523
|
+
const band = ordered.order.slice(range.start, range.end);
|
|
1524
|
+
|
|
1525
|
+
band.sort((a, b) => a - b);
|
|
1526
|
+
|
|
1527
|
+
const groups = partition_into_groups(frontier, band, cluster_graph, group_size);
|
|
1528
|
+
|
|
1529
|
+
if (groups.length === 0) {
|
|
1530
|
+
continue;
|
|
1531
|
+
}
|
|
1532
|
+
|
|
1533
|
+
const group_of_face = new Int32Array(frontier.mesh.topology.faces.size).fill(-1);
|
|
1534
|
+
|
|
1535
|
+
groups.forEach((group, group_index) => {
|
|
1536
|
+
for (const cluster of group) {
|
|
1537
|
+
for (const face of frontier.cluster_faces[cluster]) {
|
|
1538
|
+
group_of_face[face] = group_index;
|
|
1539
|
+
}
|
|
1540
|
+
}
|
|
1541
|
+
});
|
|
1542
|
+
|
|
1543
|
+
const steps = [];
|
|
1544
|
+
const repacks = [];
|
|
1545
|
+
|
|
1546
|
+
let consumed = 0;
|
|
1547
|
+
let produced = 0;
|
|
1548
|
+
|
|
1549
|
+
const merged = new Set();
|
|
1550
|
+
|
|
1551
|
+
groups.forEach((group, group_index) => {
|
|
1552
|
+
const parent_target = Math.max(
|
|
1553
|
+
1, Math.ceil(group.length * options.simplification_ratio)
|
|
1554
|
+
);
|
|
1555
|
+
|
|
1556
|
+
const repack = try_repack(frontier, group, parent_target);
|
|
1557
|
+
|
|
1558
|
+
if (repack !== undefined) {
|
|
1559
|
+
repacks.push(repack);
|
|
1560
|
+
|
|
1561
|
+
consumed += group.length;
|
|
1562
|
+
produced += repack.clusters.length;
|
|
1563
|
+
|
|
1564
|
+
for (const cluster of group) {
|
|
1565
|
+
merged.add(cluster);
|
|
1566
|
+
}
|
|
1567
|
+
|
|
1568
|
+
return;
|
|
1569
|
+
}
|
|
1570
|
+
|
|
1571
|
+
const step = build_group_step(
|
|
1572
|
+
frontier, group, group_of_face, group_index, level, options
|
|
1573
|
+
);
|
|
1574
|
+
|
|
1575
|
+
if (step === undefined || step.parent_clusters.length >= group.length) {
|
|
1576
|
+
// Refused. Nothing has been mutated — the step worked on a copy of the geometry
|
|
1577
|
+
// and a copy of the quadrics — so its clusters simply stay where they are.
|
|
1578
|
+
return;
|
|
1579
|
+
}
|
|
1580
|
+
|
|
1581
|
+
steps.push(step);
|
|
1582
|
+
|
|
1583
|
+
consumed += group.length;
|
|
1584
|
+
produced += step.parent_clusters.length;
|
|
1585
|
+
|
|
1586
|
+
for (const cluster of group) {
|
|
1587
|
+
merged.add(cluster);
|
|
1588
|
+
}
|
|
1589
|
+
});
|
|
1590
|
+
|
|
1591
|
+
if (produced < consumed) {
|
|
1592
|
+
return { steps, repacks, merged };
|
|
1593
|
+
}
|
|
1594
|
+
}
|
|
1595
|
+
|
|
1596
|
+
// No band made progress. Growing the group gives the simplifier more interior to work with
|
|
1597
|
+
// relative to its locked perimeter, which is the thing that stalls first.
|
|
1598
|
+
if (group_size >= frontier.clusters.length) {
|
|
1599
|
+
return undefined;
|
|
1600
|
+
}
|
|
1601
|
+
|
|
1602
|
+
group_size = Math.min(frontier.clusters.length, group_size * 2);
|
|
1603
|
+
}
|
|
1604
|
+
}
|
|
1605
|
+
|
|
1606
|
+
const scratch_quadric = new Quadric3();
|
|
1607
|
+
|
|
1608
|
+
/**
|
|
1609
|
+
* Build the whole DAG, from a source mesh to a single root.
|
|
1610
|
+
*
|
|
1611
|
+
* @param {VirtualGeometryMesh} mesh the normalized source; consumed, not copied
|
|
1612
|
+
* @param {VGeoLevelOptions} options
|
|
1613
|
+
* @returns {VGeoLevel[]} leaves first; the last level holds exactly one cluster, the root
|
|
1614
|
+
*/
|
|
1615
|
+
export function vgeo_build_levels(mesh, options) {
|
|
1616
|
+
assert.isPositiveInteger(options.group_size, 'options.group_size');
|
|
1617
|
+
assert.greaterThan(options.group_size, 1, 'options.group_size');
|
|
1618
|
+
assert.greaterThan(options.simplification_ratio, 0, 'options.simplification_ratio');
|
|
1619
|
+
assert.lessThan(options.simplification_ratio, 1, 'options.simplification_ratio');
|
|
1620
|
+
assert.greaterThan(options.error_band_growth, 1, 'options.error_band_growth');
|
|
1621
|
+
assert.greaterThan(options.error_band_min_fraction, 0, 'options.error_band_min_fraction');
|
|
1622
|
+
assert.lessThanOrEqual(options.error_band_min_fraction, 1, 'options.error_band_min_fraction');
|
|
1623
|
+
assert.isPositiveInteger(options.max_levels, 'options.max_levels');
|
|
1624
|
+
|
|
1625
|
+
let frontier = build_leaf_frontier(mesh);
|
|
1626
|
+
|
|
1627
|
+
const leaves = new VGeoLevel();
|
|
1628
|
+
|
|
1629
|
+
leaves.index = 0;
|
|
1630
|
+
leaves.clusters = frontier.clusters.slice();
|
|
1631
|
+
|
|
1632
|
+
const levels = [leaves];
|
|
1633
|
+
|
|
1634
|
+
while (frontier.clusters.length > 1) {
|
|
1635
|
+
if (levels.length >= options.max_levels) {
|
|
1636
|
+
throw new Error(
|
|
1637
|
+
`the DAG still holds ${frontier.clusters.length} clusters after ${levels.length} `
|
|
1638
|
+
+ `levels, which is as many as the container can name`
|
|
1639
|
+
);
|
|
1640
|
+
}
|
|
1641
|
+
|
|
1642
|
+
const level = levels.length;
|
|
1643
|
+
|
|
1644
|
+
const round = run_round(frontier, level, options);
|
|
1645
|
+
|
|
1646
|
+
if (round === undefined) {
|
|
1647
|
+
throw new Error(
|
|
1648
|
+
`the DAG stopped converging with ${frontier.clusters.length} clusters left: no `
|
|
1649
|
+
+ `grouping of the frontier produced fewer clusters than it consumed, so the build `
|
|
1650
|
+
+ `has no single root to start a cut from`
|
|
1651
|
+
);
|
|
1652
|
+
}
|
|
1653
|
+
|
|
1654
|
+
const next = new VGeoLevel();
|
|
1655
|
+
|
|
1656
|
+
next.index = level;
|
|
1657
|
+
|
|
1658
|
+
/**
|
|
1659
|
+
* @type {VGeoContribution[]}
|
|
1660
|
+
*/
|
|
1661
|
+
const contributions = [];
|
|
1662
|
+
|
|
1663
|
+
const clusters = [];
|
|
1664
|
+
|
|
1665
|
+
// Untouched clusters first, in the order they already had, then this round's output. All of
|
|
1666
|
+
// them name their vertices in the frontier that is being replaced, which is what lets them
|
|
1667
|
+
// be stitched into one mesh.
|
|
1668
|
+
frontier.clusters.forEach((cluster, index) => {
|
|
1669
|
+
if (round.merged.has(index)) {
|
|
1670
|
+
return;
|
|
1671
|
+
}
|
|
1672
|
+
|
|
1673
|
+
const gathered = vgeo_gather_faces(frontier.mesh, frontier.cluster_faces[index]);
|
|
1674
|
+
|
|
1675
|
+
clusters.push(cluster);
|
|
1676
|
+
|
|
1677
|
+
contributions.push({
|
|
1678
|
+
positions: gathered.positions,
|
|
1679
|
+
indices: gathered.indices,
|
|
1680
|
+
local_vertex_ids: gathered.vertex_ids,
|
|
1681
|
+
source_vertex_ids: gathered.vertex_ids,
|
|
1682
|
+
topology: frontier.mesh.topology,
|
|
1683
|
+
});
|
|
1684
|
+
});
|
|
1685
|
+
|
|
1686
|
+
if (round.repacks.length > 0) {
|
|
1687
|
+
const replaced = new Set();
|
|
1688
|
+
|
|
1689
|
+
for (const repack of round.repacks) {
|
|
1690
|
+
for (const leaf of repack.replaced) {
|
|
1691
|
+
replaced.add(leaf);
|
|
1692
|
+
}
|
|
1693
|
+
}
|
|
1694
|
+
|
|
1695
|
+
// One pass over the leaf level for the whole round. Per repack it would be a scan of
|
|
1696
|
+
// every leaf in the asset, and a scene of small parts is nothing but repacks.
|
|
1697
|
+
leaves.clusters = leaves.clusters.filter((cluster) => !replaced.has(cluster));
|
|
1698
|
+
|
|
1699
|
+
for (const repack of round.repacks) {
|
|
1700
|
+
repack.clusters.forEach((cluster, index) => {
|
|
1701
|
+
leaves.clusters.push(cluster);
|
|
1702
|
+
|
|
1703
|
+
clusters.push(cluster);
|
|
1704
|
+
contributions.push(repack.geometry[index]);
|
|
1705
|
+
});
|
|
1706
|
+
}
|
|
1707
|
+
}
|
|
1708
|
+
|
|
1709
|
+
for (const step of round.steps) {
|
|
1710
|
+
const group = new VirtualGeometryGroup();
|
|
1711
|
+
|
|
1712
|
+
// The group sits one band below the parents it produced, which is what makes page order
|
|
1713
|
+
// by descending level a topological order of the DAG.
|
|
1714
|
+
group.level = level - 1;
|
|
1715
|
+
group.clusters = step.child_clusters;
|
|
1716
|
+
|
|
1717
|
+
for (const cluster of step.child_clusters) {
|
|
1718
|
+
cluster.parent_error = step.error;
|
|
1719
|
+
cluster.parent_sphere = step.sphere;
|
|
1720
|
+
}
|
|
1721
|
+
|
|
1722
|
+
step.parent_clusters.forEach((parent, index) => {
|
|
1723
|
+
parent.self_error = step.error;
|
|
1724
|
+
parent.self_sphere = step.sphere;
|
|
1725
|
+
parent.child_group = group;
|
|
1726
|
+
|
|
1727
|
+
next.clusters.push(parent);
|
|
1728
|
+
clusters.push(parent);
|
|
1729
|
+
contributions.push(step.parent_geometry[index]);
|
|
1730
|
+
});
|
|
1731
|
+
|
|
1732
|
+
levels[level - 1].groups.push(group);
|
|
1733
|
+
}
|
|
1734
|
+
|
|
1735
|
+
if (next.clusters.length > 0) {
|
|
1736
|
+
levels.push(next);
|
|
1737
|
+
}
|
|
1738
|
+
|
|
1739
|
+
// Fold every step's quadric gains back into the frontier's own set, before it is re-indexed.
|
|
1740
|
+
//
|
|
1741
|
+
// Gains, not results: two steps that share a pinned boundary vertex each started from this
|
|
1742
|
+
// vertex's quadric and each accumulated their own victims onto it, so the two results
|
|
1743
|
+
// overlap by exactly the quadric they both began with. Adding them whole would count the
|
|
1744
|
+
// source planes under that vertex twice and inflate every error above it.
|
|
1745
|
+
const updated = frontier.quadrics.slice();
|
|
1746
|
+
|
|
1747
|
+
for (const step of round.steps) {
|
|
1748
|
+
for (const [vertex, quadric] of step.quadrics) {
|
|
1749
|
+
|
|
1750
|
+
scratch_quadric.copy(quadric);
|
|
1751
|
+
scratch_quadric.sub(frontier.quadrics[vertex]);
|
|
1752
|
+
|
|
1753
|
+
const next = updated[vertex].clone();
|
|
1754
|
+
next.add(scratch_quadric);
|
|
1755
|
+
|
|
1756
|
+
updated[vertex] = next;
|
|
1757
|
+
|
|
1758
|
+
}
|
|
1759
|
+
}
|
|
1760
|
+
|
|
1761
|
+
const stitched = stitch_frontier(contributions, frontier.mesh.layers);
|
|
1762
|
+
|
|
1763
|
+
frontier = new VGeoFrontier();
|
|
1764
|
+
|
|
1765
|
+
frontier.mesh = stitched.mesh;
|
|
1766
|
+
frontier.clusters = clusters;
|
|
1767
|
+
frontier.cluster_faces = stitched.cluster_faces;
|
|
1768
|
+
frontier.quadrics = Array.from(
|
|
1769
|
+
stitched.source_vertex_ids, (source) => updated[source]
|
|
1770
|
+
);
|
|
1771
|
+
}
|
|
1772
|
+
|
|
1773
|
+
const top = levels[levels.length - 1];
|
|
1774
|
+
|
|
1775
|
+
assert.equal(
|
|
1776
|
+
top.clusters.length, 1,
|
|
1777
|
+
'the topmost level must hold the single root the frontier converged to'
|
|
1778
|
+
);
|
|
1779
|
+
|
|
1780
|
+
assert.equal(
|
|
1781
|
+
top.clusters[0], frontier.clusters[0],
|
|
1782
|
+
'the cluster the frontier converged to must be the one the topmost level holds'
|
|
1783
|
+
);
|
|
1784
|
+
|
|
1785
|
+
return levels;
|
|
1786
|
+
}
|