@woosh/meep-engine 2.168.1 → 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/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/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/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 +3 -5
- 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
|
@@ -468,6 +468,40 @@ class List {
|
|
|
468
468
|
return this.data.slice(start, end);
|
|
469
469
|
}
|
|
470
470
|
|
|
471
|
+
/**
|
|
472
|
+
* Whether every element satisfies the condition. Vacuously true when empty,
|
|
473
|
+
* matching Array.prototype.every.
|
|
474
|
+
*
|
|
475
|
+
* @param {function(element:T):boolean} condition
|
|
476
|
+
* @returns {boolean}
|
|
477
|
+
*/
|
|
478
|
+
every(condition) {
|
|
479
|
+
const l = this.length;
|
|
480
|
+
const data = this.data;
|
|
481
|
+
|
|
482
|
+
for (let i = 0; i < l; i++) {
|
|
483
|
+
if (!condition(data[i])) {
|
|
484
|
+
return false;
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
return true;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
/**
|
|
492
|
+
* Iterate the list's elements, so `for...of` and spread work on it the way
|
|
493
|
+
* they already do on HashMap, HashSet and Deque.
|
|
494
|
+
*
|
|
495
|
+
* @returns {Iterator<T>}
|
|
496
|
+
*/
|
|
497
|
+
* [Symbol.iterator]() {
|
|
498
|
+
const data = this.data;
|
|
499
|
+
|
|
500
|
+
for (let i = 0; i < this.length; i++) {
|
|
501
|
+
yield data[i];
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
|
|
471
505
|
/**
|
|
472
506
|
*
|
|
473
507
|
* @param {function(element:T):boolean} condition
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SortedListProjection.d.ts","sourceRoot":"","sources":["../../../../../src/core/collection/list/SortedListProjection.js"],"names":[],"mappings":"AAGA;;GAEG;AACH;IACI;;;;OAIG;IACH,mBAHW,KAAK,CAAC,CAAC,qBACE,CAAC,QAAC,CAAC,KAAE,MAAM,EAoB9B;IAjBG;;;OAGG;IACH,OAFU,KAAK,CAAC,CAAC,CAEC;IAElB;;;OAGG;IACH,QAFU,KAAK,CAAC,CAAC,CAEO;IAExB;;;OAGG;IACH,mBAFmB,CAAC,QAAE,CAAC,KAAG,MAAM,CAEJ;IAGhC,aAIC;IAED,eAEC;IAED;;;OAGG;IACH,mBAFW,CAAC,
|
|
1
|
+
{"version":3,"file":"SortedListProjection.d.ts","sourceRoot":"","sources":["../../../../../src/core/collection/list/SortedListProjection.js"],"names":[],"mappings":"AAGA;;GAEG;AACH;IACI;;;;OAIG;IACH,mBAHW,KAAK,CAAC,CAAC,qBACE,CAAC,QAAC,CAAC,KAAE,MAAM,EAoB9B;IAjBG;;;OAGG;IACH,OAFU,KAAK,CAAC,CAAC,CAEC;IAElB;;;OAGG;IACH,QAFU,KAAK,CAAC,CAAC,CAEO;IAExB;;;OAGG;IACH,mBAFmB,CAAC,QAAE,CAAC,KAAG,MAAM,CAEJ;IAGhC,aAIC;IAED,eAEC;IAED;;;OAGG;IACH,mBAFW,CAAC,QASX;IAGD,cAUC;CAEJ;iBAnEgB,WAAW"}
|
|
@@ -45,9 +45,12 @@ export class SortedListProjection {
|
|
|
45
45
|
* @param {T} el
|
|
46
46
|
*/
|
|
47
47
|
handleAddition(el) {
|
|
48
|
+
// binarySearchLowIndex returns the index of the largest element <= el,
|
|
49
|
+
// or -1 when el sorts below everything. The insertion point is one past
|
|
50
|
+
// that in both cases.
|
|
48
51
|
const where = binarySearchLowIndex(this.output.data, el, this.comparator);
|
|
49
52
|
|
|
50
|
-
this.output.insert(where, el);
|
|
53
|
+
this.output.insert(where + 1, el);
|
|
51
54
|
}
|
|
52
55
|
|
|
53
56
|
|
|
@@ -14,11 +14,34 @@ export class BiMap<K, V> {
|
|
|
14
14
|
*/
|
|
15
15
|
backward: Map<V, K>;
|
|
16
16
|
/**
|
|
17
|
+
* Note the argument order: the pair is stored as value-then-key, which is
|
|
18
|
+
* how the only in-tree consumer (EncodingBinaryBuffer) calls it. The
|
|
19
|
+
* parameter names used to claim the opposite, which made every accessor
|
|
20
|
+
* below look inverted.
|
|
17
21
|
*
|
|
22
|
+
* @param {V} value
|
|
18
23
|
* @param {K} key
|
|
24
|
+
*/
|
|
25
|
+
add(value: V, key: K): void;
|
|
26
|
+
/**
|
|
27
|
+
* Remove a pair by its value.
|
|
28
|
+
*
|
|
19
29
|
* @param {V} value
|
|
30
|
+
* @returns {boolean} whether a pair was removed
|
|
31
|
+
*/
|
|
32
|
+
deleteByValue(value: V): boolean;
|
|
33
|
+
/**
|
|
34
|
+
* Remove a pair by its key.
|
|
35
|
+
*
|
|
36
|
+
* @param {K} key
|
|
37
|
+
* @returns {boolean} whether a pair was removed
|
|
38
|
+
*/
|
|
39
|
+
deleteByKey(key: K): boolean;
|
|
40
|
+
/**
|
|
41
|
+
* @returns {number}
|
|
20
42
|
*/
|
|
21
|
-
|
|
43
|
+
get size(): number;
|
|
44
|
+
clear(): void;
|
|
22
45
|
/**
|
|
23
46
|
*
|
|
24
47
|
* @param {V} value
|
|
@@ -32,6 +55,6 @@ export class BiMap<K, V> {
|
|
|
32
55
|
*/
|
|
33
56
|
getValueByKey(address: K): V | undefined;
|
|
34
57
|
get: (address: any) => any;
|
|
35
|
-
set: (
|
|
58
|
+
set: (value: any, key: any) => void;
|
|
36
59
|
}
|
|
37
60
|
//# sourceMappingURL=BiMap.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BiMap.d.ts","sourceRoot":"","sources":["../../../../../src/core/collection/map/BiMap.js"],"names":[],"mappings":"AAAA;;;GAGG;AACH;IAEI;;;OAGG;IACH,SAFU,IAAI,CAAC,EAAE,CAAC,CAAC,CAEC;IACpB;;;OAGG;IACH,UAFU,IAAI,CAAC,EAAE,CAAC,CAAC,CAEE;IAGrB
|
|
1
|
+
{"version":3,"file":"BiMap.d.ts","sourceRoot":"","sources":["../../../../../src/core/collection/map/BiMap.js"],"names":[],"mappings":"AAAA;;;GAGG;AACH;IAEI;;;OAGG;IACH,SAFU,IAAI,CAAC,EAAE,CAAC,CAAC,CAEC;IACpB;;;OAGG;IACH,UAFU,IAAI,CAAC,EAAE,CAAC,CAAC,CAEE;IAGrB;;;;;;;;OAQG;IACH,WAHW,CAAC,OACD,CAAC,QAKX;IAED;;;;;OAKG;IACH,qBAHW,CAAC,GACC,OAAO,CAanB;IAED;;;;;OAKG;IACH,iBAHW,CAAC,GACC,OAAO,CAanB;IAED;;OAEG;IACH,mBAEC;IAED,cAGC;IAED;;;;OAIG;IACH,qBAHW,CAAC,GACC,CAAC,GAAC,SAAS,CAIvB;IAED;;;;OAIG;IACH,uBAHW,CAAC,GACC,CAAC,GAAC,SAAS,CAIvB;IAGL,2BAAmB;IACnB,oCAAmB;CAHlB"}
|
|
@@ -17,13 +17,67 @@ export class BiMap {
|
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
|
+
* Note the argument order: the pair is stored as value-then-key, which is
|
|
21
|
+
* how the only in-tree consumer (EncodingBinaryBuffer) calls it. The
|
|
22
|
+
* parameter names used to claim the opposite, which made every accessor
|
|
23
|
+
* below look inverted.
|
|
20
24
|
*
|
|
25
|
+
* @param {V} value
|
|
21
26
|
* @param {K} key
|
|
27
|
+
*/
|
|
28
|
+
add(value, key) {
|
|
29
|
+
this.forward.set(key, value);
|
|
30
|
+
this.backward.set(value, key);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Remove a pair by its value.
|
|
35
|
+
*
|
|
22
36
|
* @param {V} value
|
|
37
|
+
* @returns {boolean} whether a pair was removed
|
|
38
|
+
*/
|
|
39
|
+
deleteByValue(value) {
|
|
40
|
+
const key = this.backward.get(value);
|
|
41
|
+
|
|
42
|
+
if (key === undefined) {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
this.backward.delete(value);
|
|
47
|
+
this.forward.delete(key);
|
|
48
|
+
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Remove a pair by its key.
|
|
54
|
+
*
|
|
55
|
+
* @param {K} key
|
|
56
|
+
* @returns {boolean} whether a pair was removed
|
|
57
|
+
*/
|
|
58
|
+
deleteByKey(key) {
|
|
59
|
+
const value = this.forward.get(key);
|
|
60
|
+
|
|
61
|
+
if (value === undefined) {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
this.forward.delete(key);
|
|
66
|
+
this.backward.delete(value);
|
|
67
|
+
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* @returns {number}
|
|
23
73
|
*/
|
|
24
|
-
|
|
25
|
-
this.forward.
|
|
26
|
-
|
|
74
|
+
get size() {
|
|
75
|
+
return this.forward.size;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
clear() {
|
|
79
|
+
this.forward.clear();
|
|
80
|
+
this.backward.clear();
|
|
27
81
|
}
|
|
28
82
|
|
|
29
83
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"IsAnything.d.ts","sourceRoot":"","sources":["../../../../../src/core/debug/matchers/IsAnything.js"],"names":[],"mappings":"AAEA;IAGI,8BAIC;IAND,gBAAO;IAQP,
|
|
1
|
+
{"version":3,"file":"IsAnything.d.ts","sourceRoot":"","sources":["../../../../../src/core/debug/matchers/IsAnything.js"],"names":[],"mappings":"AAEA;IAGI,8BAIC;IAND,gBAAO;IAQP,uDAMC;IAED,mCAEC;CACJ;4BAtB2B,mBAAmB"}
|
|
@@ -10,7 +10,11 @@ export class IsAnything extends BaseMatcher {
|
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
matches(item, mismatch_description) {
|
|
13
|
-
|
|
13
|
+
// "anything" accepts everything; it is the identity of a conjunction and
|
|
14
|
+
// the placeholder for an argument whose value does not matter. Returning
|
|
15
|
+
// false made it match nothing at all -- and, since nothing() is defined
|
|
16
|
+
// as not(anything()), made that match everything.
|
|
17
|
+
return true;
|
|
14
18
|
}
|
|
15
19
|
|
|
16
20
|
describeTo(description) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Signal.d.ts","sourceRoot":"","sources":["../../../../../src/core/events/signal/Signal.js"],"names":[],"mappings":"AAMA;;;;;;;GAOG;AACH;IACI;;;;OAIG;IACH,iBAAqB;IAErB;;;;OAIG;IACH,cAAU;IAEV;;;OAGG;IACH,YAFU,MAAM,CAED;IAWf;;;OAGG;IACH,yBAEC;IAfD;;;OAGG;IACH,sBAEC;IAYD;;;;OAIG;IACH,cAHW,MAAM;;KAAY,GAChB,IAAI,CAIhB;IAED;;;;OAIG;IACH,gBAHW,MAAM;;KAAY,GAChB,IAAI,CAIhB;IAED;;;;OAIG;IACH,gBAHW,MAAM;;KAAY,SAClB,OAAO,QAQjB;IAED;;;;OAIG;IACH,cAHW,MAAM;;KAAY,GAChB,OAAO,CAInB;IAGD;;;;;OAKG;IACH,4CAFa,OAAO,CAcnB;IAED,aAEC;IAED,eAEC;IAED;;;OAGG;IACH,eAFa,OAAO,CAInB;IAED;;;;OAIG;IACH,yCAQC;IAED;;;;OAIG;IACH,sCAOC;IA8BD;;;;;OAKG;IACH,oCAFa,OAAO,CA2CnB;IAED;;;;;;;;OAQG;IACH,kBAEC;IAqDD;;;;;;;;;;;;;;OAcG;IACH,WAZY,QAAQ,EAAE,CAAC,CAkBtB;IAED;;;OAGG;IACH,+BA2CC;IAGD;;;OAGG;IACH,4BA6CC;IAED;;;;OAIG;IACH,sCA4CC;IAED;;;;OAIG;IACH,4CA2CC;IAED;;;;;OAKG;IACH,oDA2CC;IAED;;;;;;OAMG;IACH,4DA2CC;IAED;;;;;;;OAOG;IACH,oEA2CC;IAED;;;;;;;;OAQG;IACH,4EA2CC;IAED;;;;;;;;;;OAUG;IACH,4FA2CC;IAED;;;;OAIG;IACH,aAHW,MAAM,GACJ,MAAM,
|
|
1
|
+
{"version":3,"file":"Signal.d.ts","sourceRoot":"","sources":["../../../../../src/core/events/signal/Signal.js"],"names":[],"mappings":"AAMA;;;;;;;GAOG;AACH;IACI;;;;OAIG;IACH,iBAAqB;IAErB;;;;OAIG;IACH,cAAU;IAEV;;;OAGG;IACH,YAFU,MAAM,CAED;IAWf;;;OAGG;IACH,yBAEC;IAfD;;;OAGG;IACH,sBAEC;IAYD;;;;OAIG;IACH,cAHW,MAAM;;KAAY,GAChB,IAAI,CAIhB;IAED;;;;OAIG;IACH,gBAHW,MAAM;;KAAY,GAChB,IAAI,CAIhB;IAED;;;;OAIG;IACH,gBAHW,MAAM;;KAAY,SAClB,OAAO,QAQjB;IAED;;;;OAIG;IACH,cAHW,MAAM;;KAAY,GAChB,OAAO,CAInB;IAGD;;;;;OAKG;IACH,4CAFa,OAAO,CAcnB;IAED,aAEC;IAED,eAEC;IAED;;;OAGG;IACH,eAFa,OAAO,CAInB;IAED;;;;OAIG;IACH,yCAQC;IAED;;;;OAIG;IACH,sCAOC;IA8BD;;;;;OAKG;IACH,oCAFa,OAAO,CA2CnB;IAED;;;;;;;;OAQG;IACH,kBAEC;IAqDD;;;;;;;;;;;;;;OAcG;IACH,WAZY,QAAQ,EAAE,CAAC,CAkBtB;IAED;;;OAGG;IACH,+BA2CC;IAGD;;;OAGG;IACH,4BA6CC;IAED;;;;OAIG;IACH,sCA4CC;IAED;;;;OAIG;IACH,4CA2CC;IAED;;;;;OAKG;IACH,oDA2CC;IAED;;;;;;OAMG;IACH,4DA2CC;IAED;;;;;;;OAOG;IACH,oEA2CC;IAED;;;;;;;;OAQG;IACH,4EA2CC;IAED;;;;;;;;;;OAUG;IACH,4FA2CC;IAED;;;;OAIG;IACH,aAHW,MAAM,GACJ,MAAM,CAelB;IAIL;;;OAGG;IACH,mBAFU,OAAO,CAEQ;;CAPxB"}
|
|
@@ -798,7 +798,9 @@ export class Signal {
|
|
|
798
798
|
const result = new Signal();
|
|
799
799
|
|
|
800
800
|
function handler() {
|
|
801
|
-
|
|
801
|
+
// spread: dispatch takes varargs, so passing the arguments object
|
|
802
|
+
// forwards it as a single argument to every subscriber
|
|
803
|
+
result.dispatch(...arguments);
|
|
802
804
|
}
|
|
803
805
|
|
|
804
806
|
this.add(handler);
|
|
@@ -0,0 +1,433 @@
|
|
|
1
|
+
# Polygon triangulation — design note
|
|
2
|
+
|
|
3
|
+
Status: **LANDED** · 2026-08-01
|
|
4
|
+
Follows [`../../MANIFOLD_COMPARISON_2026_08_01.md`](../../MANIFOLD_COMPARISON_2026_08_01.md) §4.1.
|
|
5
|
+
|
|
6
|
+
> **Outcome.** Built as designed. What it cost, and what it turned up, is in §7 —
|
|
7
|
+
> including two defects in *existing* meep primitives that the work exposed and
|
|
8
|
+
> that are now fixed, and one place where the plan above was simply wrong.
|
|
9
|
+
|
|
10
|
+
meep has no general polygon triangulator. This note records the research behind
|
|
11
|
+
the one being added: the algorithm choice, and — for every component it needs —
|
|
12
|
+
the answer to *"does meep already have this, and is this how meep does it?"*
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## 1. What the callers actually need
|
|
17
|
+
|
|
18
|
+
Surveyed every site in meep + app that triangulates or wants to:
|
|
19
|
+
|
|
20
|
+
| Site | Today | n | Holes | Needs |
|
|
21
|
+
| --- | --- | --- | --- | --- |
|
|
22
|
+
| [`bt_face_triangulate`](../../3d/topology/struct/binary/io/face/bt_face_triangulate.js) | fan from the first loop vertex, documented as convex-only | 4–12 | no | correct output for concave faces |
|
|
23
|
+
| [`bt_mesh_triangulate`](../../3d/topology/struct/binary/io/bt_mesh_triangulate.js) | applies the above to every face unconditionally | — | no | same |
|
|
24
|
+
| [`bt_mesh_close_boundary_holes`](../../3d/topology/struct/binary/io/bt_mesh_close_boundary_holes.js) | Steiner vertex at the loop centroid + fan | 3–500 | no | a fill without a Steiner point, for non-convex loops |
|
|
25
|
+
| [`navmesh_build_topology`](../../../../engine/navigation/mesh/build/navmesh_build_topology.js):97,108 | *explicit TODO*: "identify flat areas and re-triangulate them to remove unnecessary triangles" | 10³–10⁴ | **yes** (obstacle footprints) | the general case |
|
|
26
|
+
| [`clip_soup_against_overhangs`](../../../../engine/navigation/mesh/build/clip_soup_against_overhangs.js):378 | fan, over convex pieces from `polygon2_clip_halfplane` | ≤ 8 | no | **correct as-is** — every piece is a half-plane intersection, hence convex. Listed only so nobody "fixes" it. |
|
|
27
|
+
| future: `Extrude` / `Revolve` / `Slice` / `Project`, glyph outlines (`opentype.js` is already a dependency), cross-sections | — | any | yes | the general case |
|
|
28
|
+
|
|
29
|
+
Two conclusions:
|
|
30
|
+
|
|
31
|
+
1. The dominant call is **tiny n, no holes** (`bt_face_triangulate`). The
|
|
32
|
+
asymptotics of the algorithm barely matter there; correctness and a small
|
|
33
|
+
constant do.
|
|
34
|
+
2. The demanding call is **large n with holes** (navmesh flat regions). That one
|
|
35
|
+
is offline.
|
|
36
|
+
|
|
37
|
+
Nothing in the survey needs an incremental/dynamic triangulation, and nothing
|
|
38
|
+
needs Steiner points. That rules a lot of design space out immediately.
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## 2. Algorithm choice
|
|
43
|
+
|
|
44
|
+
### Candidates
|
|
45
|
+
|
|
46
|
+
| | complexity | status structure needed | predicate set | output |
|
|
47
|
+
| --- | --- | --- | --- | --- |
|
|
48
|
+
| **Monotone decomposition sweep** (Lee–Preparata; what manifold uses) | O(n log n) | ordered dictionary keyed on "edge x at sweep y" | orient2d **+ inexact edge/horizontal-line comparisons** | arbitrary order |
|
|
49
|
+
| **Ear clipping** (earcut-family) | O(n²), ~O(n log n) with z-order | none — a doubly-linked ring | **orient2d only** | ears, in cut order |
|
|
50
|
+
| **Constrained Delaunay** | O(n log n) | ordered dictionary + flip machinery | orient2d + incircle | best quality |
|
|
51
|
+
| **Seidel trapezoidation** | O(n log* n) | trapezoid DAG | orient2d | arbitrary order |
|
|
52
|
+
|
|
53
|
+
### Decision: ear clipping, with exact `orient2d`
|
|
54
|
+
|
|
55
|
+
Three reasons, in order of weight.
|
|
56
|
+
|
|
57
|
+
**(a) meep already ships the one predicate ear clipping needs, and nothing
|
|
58
|
+
else's predicate set is satisfiable.** `robust-predicates@3.0.2` is a
|
|
59
|
+
first-class dependency (`package.json`), already whitelisted in the root
|
|
60
|
+
`jest.conf.json` `transformIgnorePatterns`, and already used in four places —
|
|
61
|
+
[`orient2d_indexed.js`](../convex-hull/orient2d_indexed.js) for the 2D hull,
|
|
62
|
+
[`orient3d_robust.js`](../../3d/plane/orient3d_robust.js),
|
|
63
|
+
[`in_sphere3d_robust.js`](../../3d/sphere/in_sphere3d_robust.js),
|
|
64
|
+
[`TetrahedralMesh.js`](../../3d/tetrahedra/TetrahedralMesh.js).
|
|
65
|
+
|
|
66
|
+
Every decision an ear clipper makes reduces to `orient2d`:
|
|
67
|
+
|
|
68
|
+
- *is vertex v convex?* → `orient2d(prev, v, next)`
|
|
69
|
+
- *is point p inside triangle (a,b,c)?* → three `orient2d` calls
|
|
70
|
+
- *is the candidate diagonal locally valid?* → two `orient2d` calls
|
|
71
|
+
|
|
72
|
+
So the whole algorithm can be made **exact**, with no epsilon anywhere in the
|
|
73
|
+
combinatorial decisions. That is not true of the sweep: its status structure
|
|
74
|
+
compares edges by their x-coordinate at the current sweep y, which is a
|
|
75
|
+
division. You can reformulate it into orient2d tests, but the reformulation is
|
|
76
|
+
subtle and a comparator that is merely *nearly* consistent corrupts an ordered
|
|
77
|
+
dictionary silently.
|
|
78
|
+
|
|
79
|
+
**(b) meep has no ordered dictionary, and adding one is the wrong first move.**
|
|
80
|
+
`core/collection` has `BinaryHeap`, `FibonacciHeap`, `IndexedFloatMinHeap`,
|
|
81
|
+
`Uint32Heap`, `Uint32Map`, `PairUint32Map`, `HashMap`, `HashSet`, `RingBuffer`,
|
|
82
|
+
`Deque`, `union_find`, `RowFirstTable`, `binarySearchLowIndex/HighIndex` — no
|
|
83
|
+
balanced BST, no skip list, no ordered set. The sweep needs one. Building a
|
|
84
|
+
red-black tree *and* a sweep triangulator on top of it, in one change, means the
|
|
85
|
+
first bug is ambiguous between the two. Ear clipping needs no new collection at
|
|
86
|
+
all: its ring is two `Uint32Array`s.
|
|
87
|
+
|
|
88
|
+
**(c) Ear clipping's output is directly consumable by meep's topology surgery.**
|
|
89
|
+
`bt_face_triangulate`'s existing primitive is "cut triangle (l_prev, l_curr,
|
|
90
|
+
l_next) off the face by inserting one edge" — that *is* an ear cut. Today it
|
|
91
|
+
always picks the first ear whether or not it is one. The fix is to pick a real
|
|
92
|
+
ear. A sweep would emit an arbitrary triangle set that then has to be
|
|
93
|
+
re-sequenced into diagonal insertions.
|
|
94
|
+
|
|
95
|
+
### What we give up, and the upgrade path
|
|
96
|
+
|
|
97
|
+
Ear clipping is O(n²) in the worst case and produces sliver-heavy
|
|
98
|
+
triangulations. Both are accepted, explicitly:
|
|
99
|
+
|
|
100
|
+
- **Time.** The z-order acceleration (§3.6) makes the point-in-ear test local
|
|
101
|
+
rather than global, which is what turns the practical cost into ~O(n log n).
|
|
102
|
+
Per [`feedback_reuse_over_micro_opt`], the right response if this ever shows
|
|
103
|
+
up in a profile is a measurement, not a pre-emptive rewrite. The bench hook is
|
|
104
|
+
the navmesh flat-region path, which is the only large-n caller and is offline.
|
|
105
|
+
- **Quality.** Deliberately *not* solved inside the triangulator. Adding a
|
|
106
|
+
"quality mode" flag would be exactly the `X = null means auto` sentinel
|
|
107
|
+
pattern that is rejected in this codebase ([`feedback_uniform_flow`]). The
|
|
108
|
+
triangulator has one behaviour: produce a valid triangulation. A caller that
|
|
109
|
+
wants Delaunay quality gets a **separate** `tri2_mesh_flip_to_delaunay` pass
|
|
110
|
+
(§6) built on `incircle` from the same `robust-predicates` package — the exact
|
|
111
|
+
2D analogue of the existing `in_sphere3d_robust.js`, and independently
|
|
112
|
+
testable.
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
## 3. Component-by-component meep audit
|
|
117
|
+
|
|
118
|
+
For each piece: *does meep already have it? is this how meep does it?*
|
|
119
|
+
|
|
120
|
+
### 3.1 Exact orientation predicate — **reuse, verbatim**
|
|
121
|
+
|
|
122
|
+
`orient2d` from `robust-predicates`, wrapped the way meep already wraps it.
|
|
123
|
+
[`orient2d_indexed.js`](../convex-hull/orient2d_indexed.js) is the existing
|
|
124
|
+
precedent: it takes a flat `points` array plus three *indices*, which is exactly
|
|
125
|
+
the call shape the triangulator wants (the ring stores point indices). **Reused
|
|
126
|
+
unchanged.** No new vendor import site — per [`feedback_confine_vendor_imports`]
|
|
127
|
+
the `robust-predicates` import stays inside the two existing wrapper files.
|
|
128
|
+
|
|
129
|
+
### 3.2 The vertex ring — **BinaryTopology-flavoured, not object nodes**
|
|
130
|
+
|
|
131
|
+
earcut uses a linked list of JS objects (`{i, x, y, prev, next, z, prevZ,
|
|
132
|
+
nextZ, steiner}`). That is not how meep does it. Every comparable meep
|
|
133
|
+
structure is flat typed arrays with integer handles and a `NULL_POINTER`
|
|
134
|
+
sentinel — `BinaryElementPool`, `BinaryTopology`, `RowFirstTable`, `bvh8`.
|
|
135
|
+
|
|
136
|
+
So the ring is:
|
|
137
|
+
|
|
138
|
+
```
|
|
139
|
+
ring_next : Uint32Array // node -> node
|
|
140
|
+
ring_prev : Uint32Array // node -> node
|
|
141
|
+
ring_point : Uint32Array // node -> index into `points`
|
|
142
|
+
ring_z : Uint32Array // node -> 2D Morton code (§3.6)
|
|
143
|
+
ring_zprev : Uint32Array
|
|
144
|
+
ring_znext : Uint32Array
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Node ids are dense `[0, total_vertex_count)`; removal is unlinking, never
|
|
148
|
+
compaction. `NULL_POINTER = 0xFFFFFFFF` is imported from
|
|
149
|
+
[`BinaryTopology.js`](../../3d/topology/struct/binary/BinaryTopology.js) rather
|
|
150
|
+
than redeclared — it is already the codebase-wide sentinel.
|
|
151
|
+
|
|
152
|
+
Buffers are module-static and grown on demand, matching the
|
|
153
|
+
`_cur` / `_tmp` / `_side` + `ensure_capacity` pattern in
|
|
154
|
+
[`clip_soup_against_overhangs.js`](../../../../engine/navigation/mesh/build/clip_soup_against_overhangs.js).
|
|
155
|
+
The triangulator is therefore **not re-entrant**, same as
|
|
156
|
+
[`bt_mesh_simplify_by_error`](../../3d/topology/struct/binary/io/bt_mesh_simplify_by_error.js)
|
|
157
|
+
(whose `_degree` / `_vboundary` caches are module-level and documented as such).
|
|
158
|
+
That is stated in the JSDoc, not guarded against.
|
|
159
|
+
|
|
160
|
+
### 3.3 Signed area / winding — **reuse**
|
|
161
|
+
|
|
162
|
+
[`polygon2_signed_area`](polygon2_signed_area.js) already exists and already
|
|
163
|
+
handles the "does the contour repeat its first point" case. Contour orientation
|
|
164
|
+
(outer CCW, holes CW) is decided with it. [`tri2_signed_area`](../triangle/tri2_signed_area.js)
|
|
165
|
+
is reused by the tests for area conservation.
|
|
166
|
+
|
|
167
|
+
### 3.4 Hole elimination — **bridging, no new structure**
|
|
168
|
+
|
|
169
|
+
Holes are eliminated the standard way: for each hole, find the outer-contour
|
|
170
|
+
vertex that is mutually visible from the hole's rightmost vertex, and splice the
|
|
171
|
+
hole into the outer ring through a doubled bridge edge. Requires only:
|
|
172
|
+
|
|
173
|
+
- a ray cast to the right from the hole's rightmost point against outer edges —
|
|
174
|
+
plain arithmetic, no structure;
|
|
175
|
+
- a visibility tie-break — `orient2d` again;
|
|
176
|
+
- sorting the holes by their rightmost x so that bridges never cross.
|
|
177
|
+
|
|
178
|
+
**Sorting**: meep has `array_sort_quick`, `array_quick_sort_by_comparator`,
|
|
179
|
+
`array_quick_sort_by_lookup_map`, `array_quick_sort_by_lookup_uint`. Hole count
|
|
180
|
+
is small (typically < 100); the comparator variant is used rather than a
|
|
181
|
+
hand-rolled sort, per [`feedback_reuse_over_micro_opt`].
|
|
182
|
+
|
|
183
|
+
### 3.5 Degenerate-input policy — **sentinel return, never throw, never swallow**
|
|
184
|
+
|
|
185
|
+
Real input is not ε-valid: welding produces self-touching contours, imported
|
|
186
|
+
geometry has duplicate and collinear points, navmesh footprints can be zero-area.
|
|
187
|
+
[`feedback_no_silent_catch`] says a function with a legitimate "can be expected
|
|
188
|
+
to fail" mode returns a sentinel; [`feedback_no_defensive_guards`] says do not
|
|
189
|
+
guard can't-happen conditions.
|
|
190
|
+
|
|
191
|
+
Applied:
|
|
192
|
+
|
|
193
|
+
- **Preconditions** (finite coordinates, `contour_count >= 1`, each contour
|
|
194
|
+
`>= 3` vertices, `contour_ends` non-decreasing) are `assert.*` — free in
|
|
195
|
+
production ([`feedback_assert_stripped`]) and documented in JSDoc.
|
|
196
|
+
- **Expected failure** (the polygon is self-intersecting / partially
|
|
197
|
+
degenerate and cannot be fully triangulated) is reported by the **return
|
|
198
|
+
value**: the number of triangles actually written. A caller that cares
|
|
199
|
+
compares it against the exact expected count `V + 2H − 2`, or measures area
|
|
200
|
+
conservation with the companion `polygon2_triangulation_deviation`.
|
|
201
|
+
- **No throw, no try/catch, no silent zero.** A partially-triangulated result is
|
|
202
|
+
still the best available answer and the caller is told how much it got.
|
|
203
|
+
|
|
204
|
+
Three recovery tiers are implemented, in this order, because without them the
|
|
205
|
+
behaviour on real welded meshes is "returns almost nothing":
|
|
206
|
+
|
|
207
|
+
1. plain ear clipping;
|
|
208
|
+
2. *cure local intersections* — when no ear is found, remove a vertex whose
|
|
209
|
+
incident edges cross a neighbour's, then retry;
|
|
210
|
+
3. *split* — find a pair of non-adjacent ring vertices whose diagonal is valid,
|
|
211
|
+
cut the ring in two, and triangulate each half.
|
|
212
|
+
|
|
213
|
+
These are not bandaids ([`feedback_correctness_no_bandaid`]): they are the
|
|
214
|
+
defined behaviour for input outside the ε-valid contract, each has its own
|
|
215
|
+
tests, and none of them weakens an assertion about valid input.
|
|
216
|
+
|
|
217
|
+
### 3.6 Z-order acceleration — **reuse `split_by_2`**
|
|
218
|
+
|
|
219
|
+
The point-in-ear test is the hot loop. earcut restricts it to points whose 2D
|
|
220
|
+
Morton code lies in the ear's bounding-box code range. meep already has
|
|
221
|
+
[`split_by_2`](../../../binary/split_by_2.js) (bit interleave, libmorton-derived)
|
|
222
|
+
and already uses it for 2D Morton in `SpatialHashGrid` and `StaticR2Tree`, plus
|
|
223
|
+
a full `v3_morton_encode*` family under [`../../3d/morton/`](../../3d/morton/).
|
|
224
|
+
There is no `v2_morton_encode` file, so one is added next to `split_by_2` rather
|
|
225
|
+
than inlining the interleave — filling the gap in the existing family instead of
|
|
226
|
+
growing a private copy.
|
|
227
|
+
|
|
228
|
+
Acceleration is enabled above a vertex-count threshold. This is *not* a mode
|
|
229
|
+
flag on the public API — it is an internal branch on `n`, invisible to callers,
|
|
230
|
+
and both paths are asserted to produce the same triangle count in the tests.
|
|
231
|
+
|
|
232
|
+
### 3.7 3D → 2D projection — **axis-aligned drop, never a rotation**
|
|
233
|
+
|
|
234
|
+
Faces are 3D. The projection to 2D must not perturb coordinates, or the exact
|
|
235
|
+
predicates stop being exact for the *original* geometry. So: drop the axis with
|
|
236
|
+
the largest |normal| component and keep the other two verbatim, flipping the
|
|
237
|
+
winding when that component is negative. Same choice manifold makes
|
|
238
|
+
(`GetAxisAlignedProjection`).
|
|
239
|
+
|
|
240
|
+
meep has [`v3_orthonormal_matrix_from_normal`](../../vec3/v3_orthonormal_matrix_from_normal.js)
|
|
241
|
+
— used by `clip_soup_against_overhangs` — but that is a *rotation*, and rotating
|
|
242
|
+
introduces rounding into every coordinate. It is deliberately **not** used here.
|
|
243
|
+
The axis-drop helper is new (`v3_project_to_dominant_plane`), which is the
|
|
244
|
+
honest answer to "does meep already have this": it has a related thing that is
|
|
245
|
+
wrong for this job.
|
|
246
|
+
|
|
247
|
+
### 3.8 Output buffer convention — **`(result, result_offset, …)` first**
|
|
248
|
+
|
|
249
|
+
Matches the bvh3 query convention recorded in [`project_bvh3_review_2026_06`]
|
|
250
|
+
and the `support(result, result_offset, …)` / `vertex_read_coordinate(out, offset, v)`
|
|
251
|
+
shape used throughout `AbstractShape3D` and `BinaryTopology`.
|
|
252
|
+
|
|
253
|
+
---
|
|
254
|
+
|
|
255
|
+
## 4. API
|
|
256
|
+
|
|
257
|
+
One general function. A single-contour caller passes `contour_count = 1`; there
|
|
258
|
+
is no convenience overload, because two entry points for one operation is the
|
|
259
|
+
kind of API surface [`feedback_uniform_flow`] rejects.
|
|
260
|
+
|
|
261
|
+
```js
|
|
262
|
+
/**
|
|
263
|
+
* @param {Uint32Array} result 3 point-indices per triangle
|
|
264
|
+
* @param {number} result_offset
|
|
265
|
+
* @param {ArrayLike<number>} points flat (x, y), stride 2
|
|
266
|
+
* @param {ArrayLike<number>} contour_ends exclusive end vertex index per contour
|
|
267
|
+
* @param {number} contour_count
|
|
268
|
+
* @returns {number} triangles written; < V + 2H - 2 means the input could not be
|
|
269
|
+
* fully triangulated
|
|
270
|
+
*/
|
|
271
|
+
polygon2_triangulate(result, result_offset, points, contour_ends, contour_count)
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
`contour_ends` rather than earcut's `holeIndices` (contour starts) so that
|
|
275
|
+
contour `i` is uniformly `[i === 0 ? 0 : ends[i - 1], ends[i])` — no special case
|
|
276
|
+
for the first contour.
|
|
277
|
+
|
|
278
|
+
Contour 0 is the outer boundary; 1..n are holes. Winding is **not** a
|
|
279
|
+
precondition: the implementation normalises it from `polygon2_signed_area`, so
|
|
280
|
+
callers cannot get it subtly wrong. (This is the one place where inferring
|
|
281
|
+
rather than demanding is right: winding is derivable from the data, so demanding
|
|
282
|
+
it would be redundant state the caller can contradict.)
|
|
283
|
+
|
|
284
|
+
Companions:
|
|
285
|
+
|
|
286
|
+
```js
|
|
287
|
+
polygon2_triangulation_deviation(result, result_offset, triangle_count, points, contour_ends, contour_count)
|
|
288
|
+
```
|
|
289
|
+
→ relative area error `|Σ|tri| − |polygon||/|polygon|`; `0` for a perfect
|
|
290
|
+
triangulation. Used by the tests as the primary invariant and available to
|
|
291
|
+
callers that need to validate untrusted input.
|
|
292
|
+
|
|
293
|
+
```js
|
|
294
|
+
polygon3_triangulate(result, result_offset, points3, contour_ends, contour_count, nx, ny, nz)
|
|
295
|
+
```
|
|
296
|
+
→ projects with §3.7 and delegates. This is what `bt_face_triangulate` calls.
|
|
297
|
+
|
|
298
|
+
---
|
|
299
|
+
|
|
300
|
+
## 5. Testing plan
|
|
301
|
+
|
|
302
|
+
Per house rules: black-box, no mocks or spies, no assertions on call counts or
|
|
303
|
+
internal paths, verbose and explicit over test frameworks, reproducible, nothing
|
|
304
|
+
vacuous.
|
|
305
|
+
|
|
306
|
+
The triangulator is unusually well suited to **invariant** testing, which is the
|
|
307
|
+
only kind that is honest here — asserting a specific triangle list would bake in
|
|
308
|
+
one valid answer out of many and break on any legitimate change.
|
|
309
|
+
|
|
310
|
+
Invariants asserted for every case:
|
|
311
|
+
|
|
312
|
+
1. **Triangle count** is exactly `V + 2H − 2`.
|
|
313
|
+
2. **Area conservation** — `Σ|signed area of each output triangle|` equals
|
|
314
|
+
`|polygon area − Σ hole areas|` to a tight relative tolerance.
|
|
315
|
+
3. **Consistent orientation** — every output triangle is CCW.
|
|
316
|
+
4. **No overlap** — pairwise, for small cases: triangle interiors are disjoint
|
|
317
|
+
(sampled at each triangle's incenter, which must lie in exactly one).
|
|
318
|
+
5. **Index validity** — every emitted index is in `[0, V)`.
|
|
319
|
+
6. **Coverage** — a grid of sample points inside the polygon and outside every
|
|
320
|
+
hole is covered by exactly one triangle.
|
|
321
|
+
|
|
322
|
+
Case families:
|
|
323
|
+
|
|
324
|
+
- squares, L-shapes, combs, spirals, stars (concave), zero-area spikes;
|
|
325
|
+
- squares with 1 / 2 / nested holes; a hole touching the boundary at a vertex;
|
|
326
|
+
- collinear runs; duplicate consecutive points; a contour that repeats its first
|
|
327
|
+
point; a hole wound the "wrong" way (must still work — §4);
|
|
328
|
+
- the two accelerated/non-accelerated paths agreeing on a large input;
|
|
329
|
+
- **corpus** — `polygon2_triangulate.corpus.js`, a checked-in list of inputs
|
|
330
|
+
that have ever failed, replayed as tests. Manifold's most transferable
|
|
331
|
+
practice: `test/polygons/polygon_corpus.txt` is 2.9 MB of exactly this.
|
|
332
|
+
- **fuzz** — a *seeded* generator (`seededRandom` from `core/math/random`, so
|
|
333
|
+
every failure is reproducible from its seed) producing random contours,
|
|
334
|
+
asserting the invariants that survive on invalid input: never throws, never
|
|
335
|
+
hangs, never emits an out-of-range index, and the deviation is reported
|
|
336
|
+
honestly.
|
|
337
|
+
|
|
338
|
+
---
|
|
339
|
+
|
|
340
|
+
## 6. What landed
|
|
341
|
+
|
|
342
|
+
| File | What |
|
|
343
|
+
| --- | --- |
|
|
344
|
+
| [`polygon2_triangulate.js`](polygon2_triangulate.js) | the triangulator |
|
|
345
|
+
| [`polygon2_triangulation_deviation.js`](polygon2_triangulation_deviation.js) | relative area error of a result |
|
|
346
|
+
| [`polygon2_is_counter_clockwise.js`](polygon2_is_counter_clockwise.js) | exact winding test (see §7.1) |
|
|
347
|
+
| [`../v2_morton_encode.js`](../v2_morton_encode.js) | 2D Morton code, filling the gap next to `v3_morton_encode*` |
|
|
348
|
+
| [`../../3d/polygon/polygon3_triangulate.js`](../../3d/polygon/polygon3_triangulate.js) | planar 3D adapter, axis-aligned projection |
|
|
349
|
+
| [`../../3d/polygon/polygon3_compute_normal.js`](../../3d/polygon/polygon3_compute_normal.js) | Newell normal for an arbitrary polygon |
|
|
350
|
+
| [`polygon2_triangulate.corpus.js`](polygon2_triangulate.corpus.js) | recorded failures, replayed as tests |
|
|
351
|
+
| [`bt_face_triangulate.js`](../../3d/topology/struct/binary/io/face/bt_face_triangulate.js) | rewritten onto the triangulator |
|
|
352
|
+
|
|
353
|
+
Also fixed, because the work depended on them and they were wrong: §7.1 and §7.2.
|
|
354
|
+
|
|
355
|
+
## 7. What the work turned up
|
|
356
|
+
|
|
357
|
+
### 7.1 The plan's winding test was not exact — `polygon2_signed_area` cannot be
|
|
358
|
+
|
|
359
|
+
The design above says every combinatorial decision is exact. The first
|
|
360
|
+
implementation broke that promise in its very first step: it picked each
|
|
361
|
+
contour's winding from `polygon2_signed_area`, a shoelace sum. A shoelace sum
|
|
362
|
+
adds terms of magnitude `O(|p|²)` to reach an answer that is the area, so its
|
|
363
|
+
*sign* is rounding noise whenever the polygon is small relative to its distance
|
|
364
|
+
from the origin. A 1e-9-sided square at (1, 1) has a true area of 1e-18 against
|
|
365
|
+
per-term rounding of ~1e-16. The ring came out backwards, every corner looked
|
|
366
|
+
reflex, and the triangulator returned nothing.
|
|
367
|
+
|
|
368
|
+
Fixed by [`polygon2_is_counter_clockwise`](polygon2_is_counter_clockwise.js):
|
|
369
|
+
one exact `orient2d` at the contour's lexicographically smallest vertex, which
|
|
370
|
+
is necessarily on the convex hull, so the turn there is the contour's own. No
|
|
371
|
+
summation, no cancellation, magnitude-independent.
|
|
372
|
+
|
|
373
|
+
The lesson generalises: **use the signed area when you want the area, and the
|
|
374
|
+
predicate when you want the winding.** They are not the same question.
|
|
375
|
+
|
|
376
|
+
### 7.2 Two existing meep primitives had the same cancellation defect
|
|
377
|
+
|
|
378
|
+
Both were on the path of the new code, so both had to be right.
|
|
379
|
+
|
|
380
|
+
- **`tri2_signed_area`** computed the expanded determinant of absolute
|
|
381
|
+
coordinates. Its consumers are mostly *sign* tests — the Polyanya funnel
|
|
382
|
+
(`bt_mesh_face_find_path_polyanya`), the atlas parameterisation validity check
|
|
383
|
+
— which is the worst possible exposure: a cancelled sign is a wrong path or a
|
|
384
|
+
wrongly-rejected chart, not a slightly wrong number. Now differences-first.
|
|
385
|
+
- **`polygon2_signed_area`** had the same shape and now subtracts the first
|
|
386
|
+
vertex before accumulating. As a bonus the special-case closing term
|
|
387
|
+
disappeared: both edges at the reference vertex contribute zero, so the
|
|
388
|
+
open-vs-closed contour branch is gone.
|
|
389
|
+
|
|
390
|
+
Neither changes any API. Both are strictly better conditioned, and both now have
|
|
391
|
+
specs that fail on the old implementation.
|
|
392
|
+
|
|
393
|
+
This is the same defect class as `tri3_compute_signed_volume` in
|
|
394
|
+
[the manifold comparison](../../MANIFOLD_COMPARISON_2026_08_01.md) §3.1, which
|
|
395
|
+
remains open.
|
|
396
|
+
|
|
397
|
+
### 7.3 `V + 2H − 2` is an upper bound, not an equality
|
|
398
|
+
|
|
399
|
+
The first spec asserted equality and failed on two hole cases. The triangulator
|
|
400
|
+
was right: bridging a hole can leave three collinear corners, and absorbing them
|
|
401
|
+
is better output than emitting a zero-area triangle. Area conservation plus
|
|
402
|
+
"every input vertex is used" is the honest invariant; the exact count is asserted
|
|
403
|
+
only for hole-free shapes whose vertices are all genuine corners.
|
|
404
|
+
|
|
405
|
+
### 7.4 The sentinel had to be propagated, not assumed away
|
|
406
|
+
|
|
407
|
+
`filter_points` returns a collapsed-ring sentinel. The recovery pass took it as a
|
|
408
|
+
node id and walked it — an infinite loop on a 20-point coarse-grid soup (corpus
|
|
409
|
+
entry 2). Fixed by making the recovery pass accept and propagate the sentinel,
|
|
410
|
+
and stop once the ring holds fewer than three nodes. That second condition is a
|
|
411
|
+
genuine termination condition, not a guard: with fewer than three nodes there is
|
|
412
|
+
no triangle to cure and nothing left to walk.
|
|
413
|
+
|
|
414
|
+
### 7.5 Replaying an ear sequence as topology surgery needs the ring, not the handles
|
|
415
|
+
|
|
416
|
+
`bt_face_triangulate` cuts each ear by moving two existing corners onto the new
|
|
417
|
+
face and giving the shrunken polygon a *fresh* corner in the ear's predecessor's
|
|
418
|
+
place. The first version replayed the triangulator's output against the corner
|
|
419
|
+
handles collected up front, which go stale the moment a cut happens — producing
|
|
420
|
+
2-corner faces. It now walks the same index ring the dry run uses and swaps each
|
|
421
|
+
replaced handle as it goes.
|
|
422
|
+
|
|
423
|
+
## 8. Deliberately out of scope
|
|
424
|
+
|
|
425
|
+
- **Delaunay quality** (`tri2_mesh_flip_to_delaunay` via `incircle`). Separate
|
|
426
|
+
pass, separate change; the note above explains why it must not be a flag.
|
|
427
|
+
- **Steiner points / refinement.** No caller needs them.
|
|
428
|
+
- **2D polygon boolean and offsetting.** Different problem, much larger; §4.2 of
|
|
429
|
+
the comparison doc.
|
|
430
|
+
- **`bt_mesh_close_boundary_holes` rework.** It currently adds a centroid
|
|
431
|
+
Steiner vertex, which is a *different* output (n triangles instead of n−2) and
|
|
432
|
+
is what its tetrahedralisation consumer wants. Switching it is a behaviour
|
|
433
|
+
change with its own consumer study, tracked separately.
|