@woosh/meep-engine 2.168.0 → 2.169.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/README.md +2 -2
- package/build/bundle-worker-image-decoder.js +1 -1
- package/package.json +1 -1
- package/src/REVIEW_2026_08_06/adv-core.md +511 -0
- package/src/REVIEW_2026_08_06/adv-gfx.md +480 -0
- package/src/REVIEW_2026_08_06/adv-view.md +446 -0
- package/src/REVIEW_2026_08_06/api-core.md +296 -0
- package/src/REVIEW_2026_08_06/api-engine.md +195 -0
- package/src/REVIEW_2026_08_06/bvh-phys-sound-net.md +507 -0
- package/src/REVIEW_2026_08_06/core-binary.md +348 -0
- package/src/REVIEW_2026_08_06/core-collection.md +376 -0
- package/src/REVIEW_2026_08_06/core-math.md +221 -0
- package/src/REVIEW_2026_08_06/core-process.md +266 -0
- package/src/REVIEW_2026_08_06/core-science.md +223 -0
- package/src/REVIEW_2026_08_06/engine-ai-nav.md +279 -0
- package/src/REVIEW_2026_08_06/engine-animation.md +167 -0
- package/src/REVIEW_2026_08_06/engine-asset.md +166 -0
- package/src/REVIEW_2026_08_06/engine-ecs.md +920 -0
- package/src/REVIEW_2026_08_06/engine-input.md +197 -0
- package/src/REVIEW_2026_08_06/engine-misc.md +248 -0
- package/src/REVIEW_2026_08_06/engine-terrain.md +52 -0
- package/src/REVIEW_2026_08_06/generation.md +176 -0
- package/src/REVIEW_2026_08_06/geom-2d.md +434 -0
- package/src/REVIEW_2026_08_06/geom-3d.md +117 -0
- package/src/REVIEW_2026_08_06/gfx-gi.md +143 -0
- package/src/REVIEW_2026_08_06/gfx-render.md +740 -0
- package/src/REVIEW_2026_08_06/gfx-texture.md +690 -0
- package/src/REVIEW_2026_08_06/prior-criticals.md +49 -0
- package/src/REVIEW_2026_08_06/prior-highs.md +138 -0
- package/src/REVIEW_2026_08_06/test-gaps.md +364 -0
- package/src/REVIEW_2026_08_06/view.md +667 -0
- package/src/REVIEW_2026_08_06.md +571 -0
- package/src/core/binary/BinaryBuffer.d.ts.map +1 -1
- package/src/core/binary/BinaryBuffer.js +12 -1
- package/src/core/binary/BitImage2.d.ts +5 -1
- package/src/core/binary/BitImage2.d.ts.map +1 -1
- package/src/core/binary/BitImage2.js +5 -1
- package/src/core/bvh8/build/NodeProxy.d.ts.map +1 -1
- package/src/core/bvh8/build/NodeProxy.js +314 -308
- package/src/core/collection/list/List.d.ts +15 -0
- package/src/core/collection/list/List.d.ts.map +1 -1
- package/src/core/collection/list/List.js +34 -0
- package/src/core/collection/list/SortedListProjection.d.ts.map +1 -1
- package/src/core/collection/list/SortedListProjection.js +4 -1
- package/src/core/collection/map/BiMap.d.ts +25 -2
- package/src/core/collection/map/BiMap.d.ts.map +1 -1
- package/src/core/collection/map/BiMap.js +57 -3
- package/src/core/debug/matchers/IsAnything.d.ts.map +1 -1
- package/src/core/debug/matchers/IsAnything.js +5 -1
- package/src/core/events/signal/Signal.d.ts.map +1 -1
- package/src/core/events/signal/Signal.js +3 -1
- package/src/core/geom/2d/polygon/TRIANGULATION_DESIGN.md +433 -0
- package/src/core/geom/2d/polygon/polygon2_is_counter_clockwise.d.ts +31 -0
- package/src/core/geom/2d/polygon/polygon2_is_counter_clockwise.d.ts.map +1 -0
- package/src/core/geom/2d/polygon/polygon2_is_counter_clockwise.js +84 -0
- package/src/core/geom/2d/polygon/polygon2_signed_area.d.ts +24 -2
- package/src/core/geom/2d/polygon/polygon2_signed_area.d.ts.map +1 -1
- package/src/core/geom/2d/polygon/polygon2_signed_area.js +52 -34
- package/src/core/geom/2d/polygon/polygon2_triangulate.corpus.d.ts +28 -0
- package/src/core/geom/2d/polygon/polygon2_triangulate.corpus.d.ts.map +1 -0
- package/src/core/geom/2d/polygon/polygon2_triangulate.corpus.js +86 -0
- package/src/core/geom/2d/polygon/polygon2_triangulate.d.ts +40 -0
- package/src/core/geom/2d/polygon/polygon2_triangulate.d.ts.map +1 -0
- package/src/core/geom/2d/polygon/polygon2_triangulate.js +1280 -0
- package/src/core/geom/2d/polygon/polygon2_triangulation_deviation.d.ts +28 -0
- package/src/core/geom/2d/polygon/polygon2_triangulation_deviation.d.ts.map +1 -0
- package/src/core/geom/2d/polygon/polygon2_triangulation_deviation.js +77 -0
- package/src/core/geom/2d/triangle/tri2_rasterize_conservative.d.ts +6 -3
- package/src/core/geom/2d/triangle/tri2_rasterize_conservative.d.ts.map +1 -1
- package/src/core/geom/2d/triangle/tri2_rasterize_conservative.js +26 -13
- package/src/core/geom/2d/triangle/tri2_signed_area.d.ts +17 -1
- package/src/core/geom/2d/triangle/tri2_signed_area.d.ts.map +1 -1
- package/src/core/geom/2d/triangle/tri2_signed_area.js +35 -23
- package/src/core/geom/2d/v2_morton_encode.d.ts +20 -0
- package/src/core/geom/2d/v2_morton_encode.d.ts.map +1 -0
- package/src/core/geom/2d/v2_morton_encode.js +23 -0
- package/src/core/geom/3d/atlas/segment/atlas_merge_charts.d.ts.map +1 -1
- package/src/core/geom/3d/atlas/segment/atlas_merge_charts.js +4 -1
- package/src/core/geom/3d/polygon/polygon3_compute_normal.d.ts +27 -0
- package/src/core/geom/3d/polygon/polygon3_compute_normal.d.ts.map +1 -0
- package/src/core/geom/3d/polygon/polygon3_compute_normal.js +60 -0
- package/src/core/geom/3d/polygon/polygon3_triangulate.d.ts +34 -0
- package/src/core/geom/3d/polygon/polygon3_triangulate.d.ts.map +1 -0
- package/src/core/geom/3d/polygon/polygon3_triangulate.js +94 -0
- package/src/core/geom/3d/quaternion/quat3_multiply.d.ts +8 -2
- package/src/core/geom/3d/quaternion/quat3_multiply.d.ts.map +1 -1
- package/src/core/geom/3d/quaternion/quat3_multiply.js +8 -2
- package/src/core/geom/3d/shape/ConvexHullShape3D.d.ts.map +1 -1
- package/src/core/geom/3d/shape/ConvexHullShape3D.js +32 -2
- package/src/core/geom/3d/topology/struct/binary/io/face/bt_face_triangulate.d.ts +23 -6
- package/src/core/geom/3d/topology/struct/binary/io/face/bt_face_triangulate.d.ts.map +1 -1
- package/src/core/geom/3d/topology/struct/binary/io/face/bt_face_triangulate.js +268 -124
- package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_assert_valid.d.ts +17 -0
- package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_assert_valid.d.ts.map +1 -0
- package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_assert_valid.js +44 -0
- package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_count_edge_faces.d.ts +20 -0
- package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_count_edge_faces.d.ts.map +1 -0
- package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_count_edge_faces.js +40 -0
- package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_is_closed.d.ts +19 -0
- package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_is_closed.d.ts.map +1 -0
- package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_is_closed.js +34 -0
- package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_is_manifold.d.ts +22 -0
- package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_is_manifold.d.ts.map +1 -0
- package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_is_manifold.js +37 -0
- package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_validate.d.ts +87 -0
- package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_validate.d.ts.map +1 -0
- package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_validate.js +599 -0
- package/src/core/geom/3d/triangle/tri3_compute_signed_volume.d.ts +32 -0
- package/src/core/geom/3d/triangle/tri3_compute_signed_volume.d.ts.map +1 -1
- package/src/core/geom/3d/triangle/tri3_compute_signed_volume.js +66 -7
- package/src/core/geom/3d/triangle/tri3_mesh_compute_signed_volume.d.ts +22 -1
- package/src/core/geom/3d/triangle/tri3_mesh_compute_signed_volume.d.ts.map +1 -1
- package/src/core/geom/3d/triangle/tri3_mesh_compute_signed_volume.js +83 -38
- package/src/core/geom/MANIFOLD_COMPARISON_2026_08_01.md +554 -0
- package/src/core/geom/vec3/v3_cotangent.d.ts +6 -2
- package/src/core/geom/vec3/v3_cotangent.d.ts.map +1 -1
- package/src/core/geom/vec3/v3_cotangent.js +6 -2
- package/src/core/geom/vec3/v3_cross.d.ts +6 -2
- package/src/core/geom/vec3/v3_cross.d.ts.map +1 -1
- package/src/core/geom/vec3/v3_cross.js +6 -2
- package/src/core/geom/vec3/v3_multiply.d.ts +6 -2
- package/src/core/geom/vec3/v3_multiply.d.ts.map +1 -1
- package/src/core/geom/vec3/v3_multiply.js +6 -2
- package/src/core/geom/vec3/v3_subtract.d.ts +6 -2
- package/src/core/geom/vec3/v3_subtract.d.ts.map +1 -1
- package/src/core/geom/vec3/v3_subtract.js +6 -2
- package/src/core/localization/Localization.js +1 -1
- package/src/core/math/random/randomSeed.d.ts +13 -0
- package/src/core/math/random/randomSeed.d.ts.map +1 -0
- package/src/core/math/random/randomSeed.js +16 -0
- package/src/core/math/spline/spline3_hermite_apply_transform.d.ts +50 -0
- package/src/core/math/spline/spline3_hermite_apply_transform.d.ts.map +1 -0
- package/src/core/math/spline/spline3_hermite_apply_transform.js +107 -0
- package/src/core/math/spline/spline3_hermite_reverse.d.ts +34 -0
- package/src/core/math/spline/spline3_hermite_reverse.d.ts.map +1 -0
- package/src/core/math/spline/spline3_hermite_reverse.js +49 -0
- package/src/core/math/statistics/hammersley_sequence.d.ts +17 -4
- package/src/core/math/statistics/hammersley_sequence.d.ts.map +1 -1
- package/src/core/math/statistics/hammersley_sequence.js +25 -16
- package/src/core/process/Future.d.ts.map +1 -1
- package/src/core/process/Future.js +12 -0
- package/src/core/process/PromiseWatcher.d.ts.map +1 -1
- package/src/core/process/PromiseWatcher.js +9 -2
- package/src/core/process/executor/ConcurrentExecutor.d.ts.map +1 -1
- package/src/core/process/executor/ConcurrentExecutor.js +12 -2
- package/src/core/process/worker/WorkerProxy.d.ts.map +1 -1
- package/src/core/process/worker/WorkerProxy.js +5 -2
- package/src/core/process/worker/extractTransferables.d.ts.map +1 -1
- package/src/core/process/worker/extractTransferables.js +4 -1
- package/src/engine/Engine.d.ts +12 -12
- package/src/engine/Engine.d.ts.map +1 -1
- package/src/engine/Engine.js +149 -10
- package/src/engine/EngineHarness.d.ts.map +1 -1
- package/src/engine/EngineHarness.js +4 -1
- package/src/engine/asset/loaders/ArrayBufferLoader.d.ts.map +1 -1
- package/src/engine/asset/loaders/ArrayBufferLoader.js +7 -0
- package/src/engine/ecs/speaker/lines/sets/LineSetDescription.d.ts.map +1 -1
- package/src/engine/ecs/speaker/lines/sets/LineSetDescription.js +3 -1
- package/src/engine/ecs/terrain/ecs/TerrainSystem.d.ts.map +1 -1
- package/src/engine/ecs/terrain/ecs/TerrainSystem.js +4 -1
- package/src/engine/ecs/terrain/tiles/TerrainTileManager.d.ts +29 -0
- package/src/engine/ecs/terrain/tiles/TerrainTileManager.d.ts.map +1 -1
- package/src/engine/ecs/terrain/tiles/TerrainTileManager.js +84 -23
- package/src/engine/graphics/CONTEXT_LOSS_RECOVERY_PLAN.md +446 -0
- package/src/engine/graphics/GraphicsEngine.d.ts +56 -7
- package/src/engine/graphics/GraphicsEngine.d.ts.map +1 -1
- package/src/engine/graphics/GraphicsEngine.js +196 -24
- package/src/engine/graphics/context/WebGLContextFailureReason.d.ts +9 -0
- package/src/engine/graphics/context/WebGLContextFailureReason.d.ts.map +1 -0
- package/src/engine/graphics/context/WebGLContextFailureReason.js +17 -0
- package/src/engine/graphics/context/WebGLContextMonitor.d.ts +106 -0
- package/src/engine/graphics/context/WebGLContextMonitor.d.ts.map +1 -0
- package/src/engine/graphics/context/WebGLContextMonitor.js +341 -0
- package/src/engine/graphics/context/WebGLContextState.d.ts +14 -0
- package/src/engine/graphics/context/WebGLContextState.d.ts.map +1 -0
- package/src/engine/graphics/context/WebGLContextState.js +24 -0
- package/src/engine/graphics/context/testWebGLContextLoss.d.ts +2 -0
- package/src/engine/graphics/context/testWebGLContextLoss.d.ts.map +1 -0
- package/src/engine/graphics/context/testWebGLContextLoss.js +696 -0
- package/src/engine/graphics/ecs/camera/serialization/CameraSerializationAdapter.d.ts.map +1 -1
- package/src/engine/graphics/ecs/camera/serialization/CameraSerializationAdapter.js +68 -32
- package/src/engine/graphics/ecs/camera/serialization/CameraSerializationUpgrader_0_1.d.ts +14 -0
- package/src/engine/graphics/ecs/camera/serialization/CameraSerializationUpgrader_0_1.d.ts.map +1 -0
- package/src/engine/graphics/ecs/camera/serialization/CameraSerializationUpgrader_0_1.js +41 -0
- package/src/engine/graphics/ecs/decal/v2/FPDecalSystem.js +1 -1
- package/src/engine/graphics/ecs/mesh-v2/ShadedGeometrySystem.d.ts.map +1 -1
- package/src/engine/graphics/ecs/mesh-v2/ShadedGeometrySystem.js +5 -0
- package/src/engine/graphics/ecs/trail2d/Trail2D.d.ts +5 -1
- package/src/engine/graphics/ecs/trail2d/Trail2D.d.ts.map +1 -1
- package/src/engine/graphics/ecs/trail2d/Trail2D.js +15 -0
- package/src/engine/graphics/ecs/trail2d/Trail2DSystem.d.ts +1 -1
- package/src/engine/graphics/ecs/trail2d/Trail2DSystem.d.ts.map +1 -1
- package/src/engine/graphics/ecs/trail2d/Trail2DSystem.js +2 -1
- package/src/engine/graphics/ecs/trail3d/Trail3D.d.ts +4 -1
- package/src/engine/graphics/ecs/trail3d/Trail3D.d.ts.map +1 -1
- package/src/engine/graphics/ecs/trail3d/Trail3D.js +488 -476
- package/src/engine/graphics/ecs/trail3d/Trail3DSystem.d.ts +1 -1
- package/src/engine/graphics/ecs/trail3d/Trail3DSystem.d.ts.map +1 -1
- package/src/engine/graphics/ecs/trail3d/Trail3DSystem.js +280 -253
- package/src/engine/graphics/geometry/MikkT/BuildNeighborsFast.d.ts.map +1 -1
- package/src/engine/graphics/geometry/MikkT/BuildNeighborsFast.js +3 -2
- package/src/engine/graphics/geometry/MikkT/GenerateSharedVerticesIndexList.d.ts.map +1 -1
- package/src/engine/graphics/geometry/MikkT/GenerateSharedVerticesIndexList.js +3 -1
- package/src/engine/graphics/geometry/computeMeshSurfaceArea.js +1 -1
- package/src/engine/graphics/load_and_set_cubemap_v0.d.ts +5 -0
- package/src/engine/graphics/load_and_set_cubemap_v0.d.ts.map +1 -1
- package/src/engine/graphics/load_and_set_cubemap_v0.js +26 -21
- package/src/engine/graphics/particles/particular/group/ParticleGroup.d.ts.map +1 -1
- package/src/engine/graphics/particles/particular/group/ParticleGroup.js +19 -8
- package/src/engine/graphics/particles/particular/group/optimizeCommandQueue.js +1 -1
- package/src/engine/graphics/texture/atlas/AtlasLookupTexture.d.ts.map +1 -1
- package/src/engine/graphics/texture/atlas/AtlasLookupTexture.js +10 -3
- package/src/engine/graphics/texture/atlas/TextureAtlas.d.ts.map +1 -1
- package/src/engine/graphics/texture/atlas/TextureAtlas.js +4 -1
- package/src/engine/graphics/texture/atlas/gpu/WebGLTextureAtlas.d.ts.map +1 -1
- package/src/engine/graphics/texture/atlas/gpu/WebGLTextureAtlas.js +11 -0
- package/src/engine/graphics/texture/cubemap/load_environment_map.d.ts.map +1 -1
- package/src/engine/graphics/texture/cubemap/load_environment_map.js +5 -2
- package/src/engine/graphics/texture/sampler/distance/computeSignedDistanceField_Chamfer.d.ts.map +1 -1
- package/src/engine/graphics/texture/sampler/distance/computeSignedDistanceField_Chamfer.js +26 -18
- package/src/engine/graphics/texture/sampler/filter/sampler2d_blur_gaussian.d.ts +15 -2
- package/src/engine/graphics/texture/sampler/filter/sampler2d_blur_gaussian.d.ts.map +1 -1
- package/src/engine/graphics/texture/sampler/filter/sampler2d_blur_gaussian.js +29 -3
- package/src/engine/graphics/texture/sampler/resize/sampler2d_scale_down_lanczos.d.ts +11 -1
- package/src/engine/graphics/texture/sampler/resize/sampler2d_scale_down_lanczos.d.ts.map +1 -1
- package/src/engine/graphics/texture/sampler/resize/sampler2d_scale_down_lanczos.js +158 -171
- package/src/engine/graphics/texture/sampler/sampler2d_paint.js +1 -1
- package/src/engine/graphics/texture/sampler/util/bitSet2Sampler2D.js +2 -2
- package/src/engine/input/ecs/util/TopDownCameraControllerHelper.d.ts.map +1 -1
- package/src/engine/input/ecs/util/TopDownCameraControllerHelper.js +10 -3
- package/src/engine/navigation/ecs/components/PathSerializationUpgrader_2_3.d.ts +14 -0
- package/src/engine/navigation/ecs/components/PathSerializationUpgrader_2_3.d.ts.map +1 -0
- package/src/engine/navigation/ecs/components/PathSerializationUpgrader_2_3.js +38 -0
- package/src/engine/network/orchestrator/ServerAuthoritativeClient.d.ts.map +1 -1
- package/src/engine/network/orchestrator/ServerAuthoritativeClient.js +433 -425
- package/src/engine/physics/constraint/solve_constraints.d.ts +4 -1
- package/src/engine/physics/constraint/solve_constraints.d.ts.map +1 -1
- package/src/engine/physics/constraint/solve_constraints.js +38 -13
- package/src/engine/physics/contact/ManifoldStore.d.ts +15 -3
- package/src/engine/physics/contact/ManifoldStore.d.ts.map +1 -1
- package/src/engine/physics/contact/ManifoldStore.js +15 -3
- package/src/engine/physics/ecs/Joint.d.ts +7 -4
- package/src/engine/physics/ecs/Joint.d.ts.map +1 -1
- package/src/engine/physics/ecs/Joint.js +7 -4
- package/src/engine/physics/ecs/PhysicsSystem.d.ts +14 -0
- package/src/engine/physics/ecs/PhysicsSystem.d.ts.map +1 -1
- package/src/engine/physics/ecs/PhysicsSystem.js +16 -0
- package/src/engine/physics/fluid/ecs/FluidObstacleSystem.d.ts +4 -4
- package/src/engine/physics/fluid/ecs/FluidSystem.d.ts +3 -3
- package/src/engine/physics/narrowphase/convex_convex_manifold.d.ts +15 -7
- package/src/engine/physics/narrowphase/convex_convex_manifold.d.ts.map +1 -1
- package/src/engine/physics/narrowphase/convex_convex_manifold.js +33 -10
- package/src/engine/physics/narrowphase/refine_ray_concave.d.ts +6 -2
- package/src/engine/physics/narrowphase/refine_ray_concave.d.ts.map +1 -1
- package/src/engine/physics/narrowphase/refine_ray_concave.js +6 -2
- package/src/engine/physics/narrowphase/refine_ray_hit.d.ts +6 -2
- package/src/engine/physics/narrowphase/refine_ray_hit.d.ts.map +1 -1
- package/src/engine/physics/narrowphase/refine_ray_hit.js +6 -2
- package/src/engine/physics/queries/raycast.d.ts.map +1 -1
- package/src/engine/physics/queries/raycast.js +11 -4
- package/src/engine/save/storage/IndexedDBStorage.d.ts.map +1 -1
- package/src/engine/save/storage/IndexedDBStorage.js +21 -3
- package/src/engine/simulation/Ticker.d.ts +12 -0
- package/src/engine/simulation/Ticker.d.ts.map +1 -1
- package/src/engine/simulation/Ticker.js +18 -0
- package/src/engine/sound/simulation/AcousticSimulator.d.ts.map +1 -1
- package/src/engine/sound/simulation/AcousticSimulator.js +9 -1
- package/src/engine/sound/simulation/core/VolumeField.d.ts +6 -2
- package/src/engine/sound/simulation/core/VolumeField.d.ts.map +1 -1
- package/src/engine/sound/simulation/core/VolumeField.js +6 -2
- package/src/engine/sound/simulation/probe/AcousticProbeField.d.ts +6 -2
- package/src/engine/sound/simulation/probe/AcousticProbeField.d.ts.map +1 -1
- package/src/engine/sound/simulation/probe/AcousticProbeField.js +6 -2
- package/src/engine/sound/simulation/probe/acoustic_probe_transfer.d.ts +6 -2
- package/src/engine/sound/simulation/probe/acoustic_probe_transfer.d.ts.map +1 -1
- package/src/engine/sound/simulation/probe/acoustic_probe_transfer.js +6 -2
- package/src/engine/sound/simulation/probe/bakeProbeReflectors.d.ts +3 -1
- package/src/engine/sound/simulation/probe/bakeProbeReflectors.d.ts.map +1 -1
- package/src/engine/sound/simulation/probe/bakeProbeReflectors.js +3 -1
- package/src/engine/ui/GUIEngine.d.ts.map +1 -1
- package/src/engine/ui/GUIEngine.js +8 -1
- package/src/generation/theme/TerrainTheme.d.ts.map +1 -1
- package/src/generation/theme/TerrainTheme.js +2 -1
- package/src/generation/theme/ThemeEngine.d.ts.map +1 -1
- package/src/generation/theme/ThemeEngine.js +6 -5
- package/src/view/ViewGroup.d.ts.map +1 -1
- package/src/view/ViewGroup.js +9 -7
- package/src/view/graphics/WebGLContextFailureView.d.ts +19 -0
- package/src/view/graphics/WebGLContextFailureView.d.ts.map +1 -0
- package/src/view/graphics/WebGLContextFailureView.js +76 -0
- package/src/view/minimap/Minimap.d.ts +8 -0
- package/src/view/minimap/Minimap.d.ts.map +1 -1
- package/src/view/minimap/Minimap.js +16 -3
- package/src/view/minimap/dom/MinimapMarkerView.d.ts.map +1 -1
- package/src/view/minimap/dom/MinimapMarkerView.js +3 -1
|
@@ -0,0 +1,599 @@
|
|
|
1
|
+
import { NULL_POINTER } from "../BinaryTopology.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Which kind of element a defect was found on. Typed constant (no stringly-typed
|
|
5
|
+
* states), mirroring the four pools of {@link BinaryTopology}.
|
|
6
|
+
* @readonly
|
|
7
|
+
*/
|
|
8
|
+
export const BT_ELEMENT_TYPE = Object.freeze({
|
|
9
|
+
VERTEX: 0,
|
|
10
|
+
EDGE: 1,
|
|
11
|
+
LOOP: 2,
|
|
12
|
+
FACE: 3,
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Structural defects {@link bt_mesh_validate} can report. Each value names a
|
|
17
|
+
* broken invariant of {@link BinaryTopology}, not a symptom.
|
|
18
|
+
* @readonly
|
|
19
|
+
*/
|
|
20
|
+
export const BT_DEFECT = Object.freeze({
|
|
21
|
+
/** A handle points at a pool slot that is not allocated (dangling reference). */
|
|
22
|
+
DANGLING_REFERENCE: 0,
|
|
23
|
+
/** A cycle (disk / radial / face-loop) does not return to its start within the pool's size. */
|
|
24
|
+
CYCLE_NOT_CLOSED: 1,
|
|
25
|
+
/** `prev` is not the inverse of `next` somewhere on a cycle. */
|
|
26
|
+
CYCLE_LINK_ASYMMETRIC: 2,
|
|
27
|
+
/** An edge is missing from the disk cycle of one of its vertices. */
|
|
28
|
+
EDGE_NOT_IN_DISK: 3,
|
|
29
|
+
/** An edge sits on the disk cycle of a vertex that is not one of its endpoints. */
|
|
30
|
+
DISK_EDGE_VERTEX_MISMATCH: 4,
|
|
31
|
+
/** A loop is missing from the radial cycle of the edge it claims. */
|
|
32
|
+
LOOP_NOT_IN_RADIAL: 5,
|
|
33
|
+
/** A loop on an edge's radial cycle does not claim that edge. */
|
|
34
|
+
RADIAL_LOOP_EDGE_MISMATCH: 6,
|
|
35
|
+
/** A loop on a face's loop cycle does not claim that face. */
|
|
36
|
+
FACE_LOOP_FACE_MISMATCH: 7,
|
|
37
|
+
/** A face's loop cycle is shorter than three corners. */
|
|
38
|
+
FACE_TOO_FEW_CORNERS: 8,
|
|
39
|
+
/** The same vertex appears twice on one face's loop cycle. */
|
|
40
|
+
FACE_VERTEX_REPEATED: 9,
|
|
41
|
+
/** A loop's edge does not connect that loop's vertex to the next loop's vertex. */
|
|
42
|
+
LOOP_EDGE_ENDPOINT_MISMATCH: 10,
|
|
43
|
+
/** An allocated loop is not reachable from any allocated face. */
|
|
44
|
+
LOOP_ORPHANED: 11,
|
|
45
|
+
/** A vertex with incident edges has a NULL edge pointer, or vice versa. */
|
|
46
|
+
VERTEX_EDGE_POINTER_INCONSISTENT: 12,
|
|
47
|
+
/** A coordinate or normal component is not finite. */
|
|
48
|
+
NON_FINITE_VALUE: 13,
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Bit set in `edge_seen` once an edge has been reached through its v1 disk slot.
|
|
53
|
+
* @type {number}
|
|
54
|
+
*/
|
|
55
|
+
const SEEN_V1 = 1;
|
|
56
|
+
/**
|
|
57
|
+
* Bit set in `edge_seen` once an edge has been reached through its v2 disk slot.
|
|
58
|
+
* @type {number}
|
|
59
|
+
*/
|
|
60
|
+
const SEEN_V2 = 2;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* @param {object[]} out
|
|
64
|
+
* @param {number} code one of {@link BT_DEFECT}
|
|
65
|
+
* @param {number} element_type one of {@link BT_ELEMENT_TYPE}
|
|
66
|
+
* @param {number} element
|
|
67
|
+
* @param {string} detail
|
|
68
|
+
*/
|
|
69
|
+
function record(out, code, element_type, element, detail) {
|
|
70
|
+
out.push({ code, element_type, element, detail });
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* True when `id` is a live slot of `pool`, i.e. safe to dereference.
|
|
75
|
+
* @param {BinaryElementPool} pool
|
|
76
|
+
* @param {number} id
|
|
77
|
+
* @returns {boolean}
|
|
78
|
+
*/
|
|
79
|
+
function is_live(pool, id) {
|
|
80
|
+
return id !== NULL_POINTER && id < pool.size && pool.is_allocated(id);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Check every handle an element stores actually points at a live slot of the
|
|
85
|
+
* right pool. Runs first so the cycle walks below can dereference freely.
|
|
86
|
+
*
|
|
87
|
+
* @param {BinaryTopology} mesh
|
|
88
|
+
* @param {object[]} out
|
|
89
|
+
*/
|
|
90
|
+
function check_references(mesh, out) {
|
|
91
|
+
const vertices = mesh.vertices;
|
|
92
|
+
const edges = mesh.edges;
|
|
93
|
+
const loops = mesh.loops;
|
|
94
|
+
const faces = mesh.faces;
|
|
95
|
+
|
|
96
|
+
for (let v = 0; v < vertices.size; v++) {
|
|
97
|
+
if (!vertices.is_allocated(v)) {
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const e = mesh.vertex_read_edge(v);
|
|
102
|
+
|
|
103
|
+
if (e !== NULL_POINTER && !is_live(edges, e)) {
|
|
104
|
+
record(out, BT_DEFECT.DANGLING_REFERENCE, BT_ELEMENT_TYPE.VERTEX, v,
|
|
105
|
+
`vertex ${v} points at edge ${e}, which is not allocated`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
for (let e = 0; e < edges.size; e++) {
|
|
110
|
+
if (!edges.is_allocated(e)) {
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const v1 = mesh.edge_read_vertex1(e);
|
|
115
|
+
const v2 = mesh.edge_read_vertex2(e);
|
|
116
|
+
|
|
117
|
+
if (!is_live(vertices, v1)) {
|
|
118
|
+
record(out, BT_DEFECT.DANGLING_REFERENCE, BT_ELEMENT_TYPE.EDGE, e,
|
|
119
|
+
`edge ${e} vertex1 = ${v1}, which is not allocated`);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (!is_live(vertices, v2)) {
|
|
123
|
+
record(out, BT_DEFECT.DANGLING_REFERENCE, BT_ELEMENT_TYPE.EDGE, e,
|
|
124
|
+
`edge ${e} vertex2 = ${v2}, which is not allocated`);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const l = mesh.edge_read_loop(e);
|
|
128
|
+
|
|
129
|
+
if (l !== NULL_POINTER && !is_live(loops, l)) {
|
|
130
|
+
record(out, BT_DEFECT.DANGLING_REFERENCE, BT_ELEMENT_TYPE.EDGE, e,
|
|
131
|
+
`edge ${e} points at loop ${l}, which is not allocated`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
for (let l = 0; l < loops.size; l++) {
|
|
136
|
+
if (!loops.is_allocated(l)) {
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const v = mesh.loop_read_vertex(l);
|
|
141
|
+
const e = mesh.loop_read_edge(l);
|
|
142
|
+
const f = mesh.loop_read_face(l);
|
|
143
|
+
|
|
144
|
+
if (!is_live(vertices, v)) {
|
|
145
|
+
record(out, BT_DEFECT.DANGLING_REFERENCE, BT_ELEMENT_TYPE.LOOP, l,
|
|
146
|
+
`loop ${l} vertex = ${v}, which is not allocated`);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (!is_live(edges, e)) {
|
|
150
|
+
record(out, BT_DEFECT.DANGLING_REFERENCE, BT_ELEMENT_TYPE.LOOP, l,
|
|
151
|
+
`loop ${l} edge = ${e}, which is not allocated`);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (f !== NULL_POINTER && !is_live(faces, f)) {
|
|
155
|
+
record(out, BT_DEFECT.DANGLING_REFERENCE, BT_ELEMENT_TYPE.LOOP, l,
|
|
156
|
+
`loop ${l} face = ${f}, which is not allocated`);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const links = [
|
|
160
|
+
['radial_next', mesh.loop_read_radial_next(l)],
|
|
161
|
+
['radial_prev', mesh.loop_read_radial_prev(l)],
|
|
162
|
+
['next', mesh.loop_read_next(l)],
|
|
163
|
+
['prev', mesh.loop_read_prev(l)],
|
|
164
|
+
];
|
|
165
|
+
|
|
166
|
+
for (let i = 0; i < links.length; i++) {
|
|
167
|
+
const name = links[i][0];
|
|
168
|
+
const target = links[i][1];
|
|
169
|
+
|
|
170
|
+
if (!is_live(loops, target)) {
|
|
171
|
+
record(out, BT_DEFECT.DANGLING_REFERENCE, BT_ELEMENT_TYPE.LOOP, l,
|
|
172
|
+
`loop ${l} ${name} = ${target}, which is not allocated`);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
for (let f = 0; f < faces.size; f++) {
|
|
178
|
+
if (!faces.is_allocated(f)) {
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const l = mesh.face_read_loop(f);
|
|
183
|
+
|
|
184
|
+
if (!is_live(loops, l)) {
|
|
185
|
+
record(out, BT_DEFECT.DANGLING_REFERENCE, BT_ELEMENT_TYPE.FACE, f,
|
|
186
|
+
`face ${f} points at loop ${l}, which is not allocated`);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Walk every vertex's disk cycle and check it is a closed doubly-linked ring
|
|
193
|
+
* whose members all touch that vertex, then check that every allocated edge was
|
|
194
|
+
* reached through both of its endpoints.
|
|
195
|
+
*
|
|
196
|
+
* Self-loops (`vertex1 === vertex2`) occupy the v1 slot only and their
|
|
197
|
+
* v2_disk_link is unspecified, so they are expected on exactly one side. See the
|
|
198
|
+
* disk cycle convention documented on {@link BinaryTopology}.
|
|
199
|
+
*
|
|
200
|
+
* @param {BinaryTopology} mesh
|
|
201
|
+
* @param {object[]} out
|
|
202
|
+
*/
|
|
203
|
+
function check_disk_cycles(mesh, out) {
|
|
204
|
+
const vertices = mesh.vertices;
|
|
205
|
+
const edges = mesh.edges;
|
|
206
|
+
|
|
207
|
+
const edge_seen = new Uint8Array(edges.size);
|
|
208
|
+
|
|
209
|
+
for (let v = 0; v < vertices.size; v++) {
|
|
210
|
+
if (!vertices.is_allocated(v)) {
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const start = mesh.vertex_read_edge(v);
|
|
215
|
+
|
|
216
|
+
if (start === NULL_POINTER || !is_live(edges, start)) {
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
let e = start;
|
|
221
|
+
// The cycle cannot legally be longer than the pool, so this bounds a walk over
|
|
222
|
+
// corrupt links without hiding the corruption -- it is reported below.
|
|
223
|
+
let steps = 0;
|
|
224
|
+
let closed = false;
|
|
225
|
+
|
|
226
|
+
do {
|
|
227
|
+
const v1 = mesh.edge_read_vertex1(e);
|
|
228
|
+
const v2 = mesh.edge_read_vertex2(e);
|
|
229
|
+
|
|
230
|
+
const is_v1 = v1 === v;
|
|
231
|
+
|
|
232
|
+
if (!is_v1 && v2 !== v) {
|
|
233
|
+
record(out, BT_DEFECT.DISK_EDGE_VERTEX_MISMATCH, BT_ELEMENT_TYPE.EDGE, e,
|
|
234
|
+
`edge ${e} is on vertex ${v}'s disk cycle but connects ${v1} and ${v2}`);
|
|
235
|
+
break;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
edge_seen[e] |= is_v1 ? SEEN_V1 : SEEN_V2;
|
|
239
|
+
|
|
240
|
+
const next = is_v1 ? mesh.edge_read_v1_disk_next(e) : mesh.edge_read_v2_disk_next(e);
|
|
241
|
+
|
|
242
|
+
if (!is_live(edges, next)) {
|
|
243
|
+
record(out, BT_DEFECT.CYCLE_NOT_CLOSED, BT_ELEMENT_TYPE.VERTEX, v,
|
|
244
|
+
`vertex ${v} disk cycle leaves the pool at edge ${e} (next = ${next})`);
|
|
245
|
+
break;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// prev of next, taken on next's slot for v, must come back to e
|
|
249
|
+
const next_is_v1 = mesh.edge_read_vertex1(next) === v;
|
|
250
|
+
const back = next_is_v1 ? mesh.edge_read_v1_disk_prev(next) : mesh.edge_read_v2_disk_prev(next);
|
|
251
|
+
|
|
252
|
+
if (back !== e) {
|
|
253
|
+
record(out, BT_DEFECT.CYCLE_LINK_ASYMMETRIC, BT_ELEMENT_TYPE.VERTEX, v,
|
|
254
|
+
`vertex ${v} disk cycle: next(${e}) = ${next} but prev(${next}) = ${back}`);
|
|
255
|
+
break;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
e = next;
|
|
259
|
+
steps++;
|
|
260
|
+
|
|
261
|
+
if (e === start) {
|
|
262
|
+
closed = true;
|
|
263
|
+
break;
|
|
264
|
+
}
|
|
265
|
+
} while (steps <= edges.size);
|
|
266
|
+
|
|
267
|
+
if (!closed && steps > edges.size) {
|
|
268
|
+
record(out, BT_DEFECT.CYCLE_NOT_CLOSED, BT_ELEMENT_TYPE.VERTEX, v,
|
|
269
|
+
`vertex ${v} disk cycle did not return to edge ${start} within ${edges.size} steps`);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
for (let e = 0; e < edges.size; e++) {
|
|
274
|
+
if (!edges.is_allocated(e)) {
|
|
275
|
+
continue;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const v1 = mesh.edge_read_vertex1(e);
|
|
279
|
+
const v2 = mesh.edge_read_vertex2(e);
|
|
280
|
+
|
|
281
|
+
if (!is_live(vertices, v1) || !is_live(vertices, v2)) {
|
|
282
|
+
// already reported by check_references
|
|
283
|
+
continue;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
if ((edge_seen[e] & SEEN_V1) === 0) {
|
|
287
|
+
record(out, BT_DEFECT.EDGE_NOT_IN_DISK, BT_ELEMENT_TYPE.EDGE, e,
|
|
288
|
+
`edge ${e} is not on the disk cycle of its vertex1 (${v1})`);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if (v1 !== v2 && (edge_seen[e] & SEEN_V2) === 0) {
|
|
292
|
+
record(out, BT_DEFECT.EDGE_NOT_IN_DISK, BT_ELEMENT_TYPE.EDGE, e,
|
|
293
|
+
`edge ${e} is not on the disk cycle of its vertex2 (${v2})`);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Walk every edge's radial cycle and check it is a closed doubly-linked ring of
|
|
300
|
+
* loops that all claim that edge, then check every allocated loop was reached
|
|
301
|
+
* through the edge it claims.
|
|
302
|
+
*
|
|
303
|
+
* @param {BinaryTopology} mesh
|
|
304
|
+
* @param {object[]} out
|
|
305
|
+
*/
|
|
306
|
+
function check_radial_cycles(mesh, out) {
|
|
307
|
+
const edges = mesh.edges;
|
|
308
|
+
const loops = mesh.loops;
|
|
309
|
+
|
|
310
|
+
const loop_seen = new Uint8Array(loops.size);
|
|
311
|
+
|
|
312
|
+
for (let e = 0; e < edges.size; e++) {
|
|
313
|
+
if (!edges.is_allocated(e)) {
|
|
314
|
+
continue;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
const start = mesh.edge_read_loop(e);
|
|
318
|
+
|
|
319
|
+
if (start === NULL_POINTER || !is_live(loops, start)) {
|
|
320
|
+
continue;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
let l = start;
|
|
324
|
+
let steps = 0;
|
|
325
|
+
let closed = false;
|
|
326
|
+
|
|
327
|
+
do {
|
|
328
|
+
if (mesh.loop_read_edge(l) !== e) {
|
|
329
|
+
record(out, BT_DEFECT.RADIAL_LOOP_EDGE_MISMATCH, BT_ELEMENT_TYPE.LOOP, l,
|
|
330
|
+
`loop ${l} is on edge ${e}'s radial cycle but claims edge ${mesh.loop_read_edge(l)}`);
|
|
331
|
+
break;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
loop_seen[l] = 1;
|
|
335
|
+
|
|
336
|
+
const next = mesh.loop_read_radial_next(l);
|
|
337
|
+
|
|
338
|
+
if (!is_live(loops, next)) {
|
|
339
|
+
record(out, BT_DEFECT.CYCLE_NOT_CLOSED, BT_ELEMENT_TYPE.EDGE, e,
|
|
340
|
+
`edge ${e} radial cycle leaves the pool at loop ${l} (radial_next = ${next})`);
|
|
341
|
+
break;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
const back = mesh.loop_read_radial_prev(next);
|
|
345
|
+
|
|
346
|
+
if (back !== l) {
|
|
347
|
+
record(out, BT_DEFECT.CYCLE_LINK_ASYMMETRIC, BT_ELEMENT_TYPE.EDGE, e,
|
|
348
|
+
`edge ${e} radial cycle: radial_next(${l}) = ${next} but radial_prev(${next}) = ${back}`);
|
|
349
|
+
break;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
l = next;
|
|
353
|
+
steps++;
|
|
354
|
+
|
|
355
|
+
if (l === start) {
|
|
356
|
+
closed = true;
|
|
357
|
+
break;
|
|
358
|
+
}
|
|
359
|
+
} while (steps <= loops.size);
|
|
360
|
+
|
|
361
|
+
if (!closed && steps > loops.size) {
|
|
362
|
+
record(out, BT_DEFECT.CYCLE_NOT_CLOSED, BT_ELEMENT_TYPE.EDGE, e,
|
|
363
|
+
`edge ${e} radial cycle did not return to loop ${start} within ${loops.size} steps`);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
for (let l = 0; l < loops.size; l++) {
|
|
368
|
+
if (!loops.is_allocated(l)) {
|
|
369
|
+
continue;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
const e = mesh.loop_read_edge(l);
|
|
373
|
+
|
|
374
|
+
if (!is_live(edges, e)) {
|
|
375
|
+
// already reported by check_references
|
|
376
|
+
continue;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
if (loop_seen[l] === 0) {
|
|
380
|
+
record(out, BT_DEFECT.LOOP_NOT_IN_RADIAL, BT_ELEMENT_TYPE.LOOP, l,
|
|
381
|
+
`loop ${l} claims edge ${e} but is not on that edge's radial cycle`);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Walk every face's loop cycle. Checks closure, `prev`/`next` symmetry, that
|
|
388
|
+
* every corner claims the face, that the face has at least three corners with no
|
|
389
|
+
* repeated vertex, and that each corner's edge spans that corner's vertex and the
|
|
390
|
+
* next corner's vertex. Finally reports allocated loops no face can reach.
|
|
391
|
+
*
|
|
392
|
+
* @param {BinaryTopology} mesh
|
|
393
|
+
* @param {object[]} out
|
|
394
|
+
*/
|
|
395
|
+
function check_face_cycles(mesh, out) {
|
|
396
|
+
const loops = mesh.loops;
|
|
397
|
+
const faces = mesh.faces;
|
|
398
|
+
|
|
399
|
+
const loop_seen = new Uint8Array(loops.size);
|
|
400
|
+
|
|
401
|
+
// Reused across faces so a fan of small faces does not allocate per face.
|
|
402
|
+
const cycle_vertices = [];
|
|
403
|
+
|
|
404
|
+
for (let f = 0; f < faces.size; f++) {
|
|
405
|
+
if (!faces.is_allocated(f)) {
|
|
406
|
+
continue;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
const start = mesh.face_read_loop(f);
|
|
410
|
+
|
|
411
|
+
if (!is_live(loops, start)) {
|
|
412
|
+
continue;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
cycle_vertices.length = 0;
|
|
416
|
+
|
|
417
|
+
let l = start;
|
|
418
|
+
let steps = 0;
|
|
419
|
+
let closed = false;
|
|
420
|
+
let broken = false;
|
|
421
|
+
|
|
422
|
+
do {
|
|
423
|
+
if (mesh.loop_read_face(l) !== f) {
|
|
424
|
+
record(out, BT_DEFECT.FACE_LOOP_FACE_MISMATCH, BT_ELEMENT_TYPE.LOOP, l,
|
|
425
|
+
`loop ${l} is on face ${f}'s loop cycle but claims face ${mesh.loop_read_face(l)}`);
|
|
426
|
+
broken = true;
|
|
427
|
+
break;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
loop_seen[l] = 1;
|
|
431
|
+
cycle_vertices.push(mesh.loop_read_vertex(l));
|
|
432
|
+
|
|
433
|
+
const next = mesh.loop_read_next(l);
|
|
434
|
+
|
|
435
|
+
if (!is_live(loops, next)) {
|
|
436
|
+
record(out, BT_DEFECT.CYCLE_NOT_CLOSED, BT_ELEMENT_TYPE.FACE, f,
|
|
437
|
+
`face ${f} loop cycle leaves the pool at loop ${l} (next = ${next})`);
|
|
438
|
+
broken = true;
|
|
439
|
+
break;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
const back = mesh.loop_read_prev(next);
|
|
443
|
+
|
|
444
|
+
if (back !== l) {
|
|
445
|
+
record(out, BT_DEFECT.CYCLE_LINK_ASYMMETRIC, BT_ELEMENT_TYPE.FACE, f,
|
|
446
|
+
`face ${f} loop cycle: next(${l}) = ${next} but prev(${next}) = ${back}`);
|
|
447
|
+
broken = true;
|
|
448
|
+
break;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// the corner's edge must span this corner and the next one
|
|
452
|
+
const e = mesh.loop_read_edge(l);
|
|
453
|
+
const v_here = mesh.loop_read_vertex(l);
|
|
454
|
+
const v_next = mesh.loop_read_vertex(next);
|
|
455
|
+
|
|
456
|
+
if (is_live(mesh.edges, e)) {
|
|
457
|
+
const e1 = mesh.edge_read_vertex1(e);
|
|
458
|
+
const e2 = mesh.edge_read_vertex2(e);
|
|
459
|
+
|
|
460
|
+
const spans = (e1 === v_here && e2 === v_next) || (e1 === v_next && e2 === v_here);
|
|
461
|
+
|
|
462
|
+
if (!spans) {
|
|
463
|
+
record(out, BT_DEFECT.LOOP_EDGE_ENDPOINT_MISMATCH, BT_ELEMENT_TYPE.LOOP, l,
|
|
464
|
+
`loop ${l} spans vertices ${v_here}->${v_next} but its edge ${e} connects ${e1} and ${e2}`);
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
l = next;
|
|
469
|
+
steps++;
|
|
470
|
+
|
|
471
|
+
if (l === start) {
|
|
472
|
+
closed = true;
|
|
473
|
+
break;
|
|
474
|
+
}
|
|
475
|
+
} while (steps <= loops.size);
|
|
476
|
+
|
|
477
|
+
if (!closed && !broken) {
|
|
478
|
+
record(out, BT_DEFECT.CYCLE_NOT_CLOSED, BT_ELEMENT_TYPE.FACE, f,
|
|
479
|
+
`face ${f} loop cycle did not return to loop ${start} within ${loops.size} steps`);
|
|
480
|
+
continue;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
if (broken) {
|
|
484
|
+
continue;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
if (cycle_vertices.length < 3) {
|
|
488
|
+
record(out, BT_DEFECT.FACE_TOO_FEW_CORNERS, BT_ELEMENT_TYPE.FACE, f,
|
|
489
|
+
`face ${f} has ${cycle_vertices.length} corner(s)`);
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
for (let i = 0; i < cycle_vertices.length; i++) {
|
|
493
|
+
for (let j = i + 1; j < cycle_vertices.length; j++) {
|
|
494
|
+
if (cycle_vertices[i] === cycle_vertices[j]) {
|
|
495
|
+
record(out, BT_DEFECT.FACE_VERTEX_REPEATED, BT_ELEMENT_TYPE.FACE, f,
|
|
496
|
+
`face ${f} visits vertex ${cycle_vertices[i]} at corners ${i} and ${j}`);
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
for (let l = 0; l < loops.size; l++) {
|
|
503
|
+
if (!loops.is_allocated(l)) {
|
|
504
|
+
continue;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
if (loop_seen[l] === 0) {
|
|
508
|
+
record(out, BT_DEFECT.LOOP_ORPHANED, BT_ELEMENT_TYPE.LOOP, l,
|
|
509
|
+
`loop ${l} is allocated but no face's loop cycle reaches it`);
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
/**
|
|
515
|
+
* Vertex coordinates and normals, and face normals, must be finite. A NaN here
|
|
516
|
+
* survives every topological check and only surfaces much later as an empty
|
|
517
|
+
* bounding box or a black triangle.
|
|
518
|
+
*
|
|
519
|
+
* @param {BinaryTopology} mesh
|
|
520
|
+
* @param {object[]} out
|
|
521
|
+
*/
|
|
522
|
+
function check_finite_values(mesh, out) {
|
|
523
|
+
const vertices = mesh.vertices;
|
|
524
|
+
const faces = mesh.faces;
|
|
525
|
+
|
|
526
|
+
const scratch = new Float32Array(3);
|
|
527
|
+
|
|
528
|
+
for (let v = 0; v < vertices.size; v++) {
|
|
529
|
+
if (!vertices.is_allocated(v)) {
|
|
530
|
+
continue;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
mesh.vertex_read_coordinate(scratch, 0, v);
|
|
534
|
+
|
|
535
|
+
if (!Number.isFinite(scratch[0]) || !Number.isFinite(scratch[1]) || !Number.isFinite(scratch[2])) {
|
|
536
|
+
record(out, BT_DEFECT.NON_FINITE_VALUE, BT_ELEMENT_TYPE.VERTEX, v,
|
|
537
|
+
`vertex ${v} coordinate is (${scratch[0]}, ${scratch[1]}, ${scratch[2]})`);
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
for (let f = 0; f < faces.size; f++) {
|
|
542
|
+
if (!faces.is_allocated(f)) {
|
|
543
|
+
continue;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
mesh.face_read_normal(scratch, 0, f);
|
|
547
|
+
|
|
548
|
+
if (!Number.isFinite(scratch[0]) || !Number.isFinite(scratch[1]) || !Number.isFinite(scratch[2])) {
|
|
549
|
+
record(out, BT_DEFECT.NON_FINITE_VALUE, BT_ELEMENT_TYPE.FACE, f,
|
|
550
|
+
`face ${f} normal is (${scratch[0]}, ${scratch[1]}, ${scratch[2]})`);
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
/**
|
|
556
|
+
* Check every structural invariant of a {@link BinaryTopology} and return the
|
|
557
|
+
* defects found. An empty result means the mesh is internally consistent: every
|
|
558
|
+
* handle resolves, every disk / radial / face-loop cycle is a closed
|
|
559
|
+
* doubly-linked ring, every element agrees with the elements that reference it,
|
|
560
|
+
* and every stored coordinate is finite.
|
|
561
|
+
*
|
|
562
|
+
* This is the counterpart to manifold's `IsManifold()` -- the invariant checker
|
|
563
|
+
* that makes a pile of topology-surgery primitives trustworthy. `BinaryTopology`
|
|
564
|
+
* has ~40 mutators (`bt_edge_collapse`, `bt_vert_splice`, `bt_radial_loop_remove`,
|
|
565
|
+
* `bt_face_poke`, ...), each of which can scribble a cycle; without this, nothing
|
|
566
|
+
* notices until a much later operation reads garbage. Run it in specs after any
|
|
567
|
+
* mutation, and in prototypes after any pipeline stage.
|
|
568
|
+
*
|
|
569
|
+
* What it deliberately does NOT check, because `BinaryTopology` permits all of
|
|
570
|
+
* them by design: wire edges (no incident face), boundary edges (one face),
|
|
571
|
+
* non-manifold edges (three or more faces), n-gon faces, disconnected islands.
|
|
572
|
+
* For those, see {@link bt_mesh_is_manifold} and {@link bt_mesh_is_closed}.
|
|
573
|
+
*
|
|
574
|
+
* O(V + E + L + F) except for the repeated-vertex check, which is quadratic in a
|
|
575
|
+
* single face's corner count (faces are small; a 1000-gon would be the anomaly,
|
|
576
|
+
* not the cost).
|
|
577
|
+
*
|
|
578
|
+
* Cycle walks are bounded by the corresponding pool's size. That bound is the
|
|
579
|
+
* theoretical maximum of a legal cycle, so hitting it is itself a defect and is
|
|
580
|
+
* reported as {@link BT_DEFECT.CYCLE_NOT_CLOSED} rather than swallowed.
|
|
581
|
+
*
|
|
582
|
+
* @param {BinaryTopology} mesh
|
|
583
|
+
* @returns {{code:number, element_type:number, element:number, detail:string}[]}
|
|
584
|
+
* defects found, in discovery order; empty when the mesh is consistent
|
|
585
|
+
*
|
|
586
|
+
* @author Alex Goldring
|
|
587
|
+
* @copyright Company Named Limited (c) 2026
|
|
588
|
+
*/
|
|
589
|
+
export function bt_mesh_validate(mesh) {
|
|
590
|
+
const out = [];
|
|
591
|
+
|
|
592
|
+
check_references(mesh, out);
|
|
593
|
+
check_disk_cycles(mesh, out);
|
|
594
|
+
check_radial_cycles(mesh, out);
|
|
595
|
+
check_face_cycles(mesh, out);
|
|
596
|
+
check_finite_values(mesh, out);
|
|
597
|
+
|
|
598
|
+
return out;
|
|
599
|
+
}
|
|
@@ -1,6 +1,38 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Compute signed volume of a tetrahedron with the last point at the origin
|
|
3
3
|
* Triangle is defined by points A,B,C
|
|
4
|
+
*
|
|
5
|
+
* Evaluated as `p · ((q - p) × (r - p))` where `(p, q, r)` is the cyclic
|
|
6
|
+
* rotation of `(a, b, c)` that puts the vertex nearest the origin first. Both
|
|
7
|
+
* steps are exact rewrites of `a · (b × c)`: a cyclic rotation leaves a 3×3
|
|
8
|
+
* determinant unchanged, and the two extra terms the edge form introduces,
|
|
9
|
+
* `p · (q × p)` and `p · (p × r)`, are identically zero.
|
|
10
|
+
*
|
|
11
|
+
* The rotation is not cosmetic — the two forms fail in opposite regimes, and
|
|
12
|
+
* picking the apex is what makes the result well conditioned in both. The
|
|
13
|
+
* error behaves like `eps · |p| · |q - p| · |r - p|`:
|
|
14
|
+
*
|
|
15
|
+
* - the expanded determinant of absolute coordinates loses everything for a
|
|
16
|
+
* SMALL triangle FAR from the origin. Summed over a unit cube translated to
|
|
17
|
+
* (1e6, 1e6, 1e6) it returned 256, and −262144 at 1e7;
|
|
18
|
+
* - anchoring unconditionally on `a` instead loses everything for a LARGE
|
|
19
|
+
* triangle reaching IN toward the origin, where `|b - a|` and `|c - a|` are
|
|
20
|
+
* both as large as `|a|`. `(1e8, 1e8, 1e8), (1, 0, 0), (0, 1, 0)` returned
|
|
21
|
+
* 0 for a true volume of 16666666.67, and flipped the sign of a needle
|
|
22
|
+
* tetrahedron often enough to break callers using this as an orientation
|
|
23
|
+
* predicate.
|
|
24
|
+
*
|
|
25
|
+
* Anchoring on the vertex nearest the origin is exact in both, and measured at
|
|
26
|
+
* a worst relative error of 8e-13 over 400k triangles with each vertex drawn
|
|
27
|
+
* from an independent magnitude decade up to 1e9.
|
|
28
|
+
*
|
|
29
|
+
* Distance is compared by largest absolute component rather than by squared
|
|
30
|
+
* length, which is cheaper and cannot overflow for coordinates above ~1e154.
|
|
31
|
+
*
|
|
32
|
+
* A caller summing over a whole mesh should still subtract a reference point,
|
|
33
|
+
* as {@link tri3_mesh_compute_signed_volume} does: this conditions each term,
|
|
34
|
+
* but only the caller can stop the terms themselves from being enormous.
|
|
35
|
+
*
|
|
4
36
|
* @see "EFFICIENT FEATURE EXTRACTION FOR 2D/3D OBJECTS IN MESH REPRESENTATION" by Cha Zhang and Tsuhan Chen
|
|
5
37
|
* @param {number} ax
|
|
6
38
|
* @param {number} ay
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tri3_compute_signed_volume.d.ts","sourceRoot":"","sources":["../../../../../../src/core/geom/3d/triangle/tri3_compute_signed_volume.js"],"names":[],"mappings":"AAEA
|
|
1
|
+
{"version":3,"file":"tri3_compute_signed_volume.d.ts","sourceRoot":"","sources":["../../../../../../src/core/geom/3d/triangle/tri3_compute_signed_volume.js"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AACH,+CAXW,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,GACL,MAAM,CA4CjB"}
|