|
| 1 | +# Near-Parallel Edge Detection Fix |
| 2 | + |
| 3 | +## Problem |
| 4 | + |
| 5 | +The `sliceIntoLayers` method was failing to generate segments for edges nearly parallel to the slice plane (< 1° angle). This caused gaps in layer slicing (0.60mm - 5.8mm) which prevented closed path formation in 3D printing applications. |
| 6 | + |
| 7 | +## Root Cause |
| 8 | + |
| 9 | +The original implementation used exact floating-point equality checks: |
| 10 | +```coffeescript |
| 11 | +continue if startDist is 0 and endDist is 0 |
| 12 | +if startDist is 0 |
| 13 | +if endDist is 0 |
| 14 | +``` |
| 15 | + |
| 16 | +For edges nearly parallel to the plane, the distances to the plane (`startDist` and `endDist`) are extremely small but non-zero (e.g., 1e-15). These edges were incorrectly skipped, creating gaps in the generated segment chains. |
| 17 | + |
| 18 | +## Solution |
| 19 | + |
| 20 | +Implemented an **angle-aware adaptive epsilon** approach: |
| 21 | + |
| 22 | +### 1. Calculate Edge Angle |
| 23 | +```coffeescript |
| 24 | +edgeDir = edgeVector.clone().divideScalar(edgeLength) |
| 25 | +dotWithNormal = Math.abs(edgeDir.dot(planeNormal)) |
| 26 | +``` |
| 27 | + |
| 28 | +The dot product between the edge direction and plane normal indicates how parallel the edge is to the plane: |
| 29 | +- `dotWithNormal ≈ 0`: Edge is parallel to plane |
| 30 | +- `dotWithNormal ≈ 1`: Edge is perpendicular to plane |
| 31 | + |
| 32 | +### 2. Adaptive Epsilon Calculation |
| 33 | +```coffeescript |
| 34 | +baseEpsilon = Math.max(1e-10, edgeLength * 1e-9) |
| 35 | +angleFactor = if dotWithNormal < 0.02 then 100.0 else 1.0 |
| 36 | +epsilon = baseEpsilon * angleFactor |
| 37 | +``` |
| 38 | + |
| 39 | +- **Base epsilon**: Scales with edge length to handle floating-point precision errors |
| 40 | +- **Angle factor**: For near-parallel edges (< ~1° angle), increase epsilon by 100x |
| 41 | +- **Final epsilon**: Product of base and angle factor |
| 42 | + |
| 43 | +### 3. Epsilon-Based Comparisons |
| 44 | +```coffeescript |
| 45 | +absStartDist = Math.abs(startDist) |
| 46 | +absEndDist = Math.abs(endDist) |
| 47 | + |
| 48 | +# Skip edges entirely in plane |
| 49 | +continue if absStartDist < epsilon and absEndDist < epsilon |
| 50 | + |
| 51 | +# Check if edge crosses or touches plane |
| 52 | +if absStartDist < epsilon |
| 53 | + # Start point on or very near plane |
| 54 | +if absEndDist < epsilon |
| 55 | + # End point on or very near plane |
| 56 | +``` |
| 57 | + |
| 58 | +## Test Coverage |
| 59 | + |
| 60 | +Added 4 comprehensive test cases: |
| 61 | + |
| 62 | +1. **Near-parallel edges**: Edges with vertices within 2e-7 of plane |
| 63 | +2. **Very small distances**: Tests 1e-10 precision floating-point errors |
| 64 | +3. **Duplicate detection**: Ensures epsilon tolerance doesn't create duplicates |
| 65 | +4. **Long edges**: Tests 200+ unit edges with scaled epsilon |
| 66 | + |
| 67 | +All 507 tests pass (503 existing + 4 new). |
| 68 | + |
| 69 | +## Impact |
| 70 | + |
| 71 | +This fix ensures: |
| 72 | +- ✅ Edges nearly parallel to slice plane are correctly detected |
| 73 | +- ✅ Long edges with accumulated floating-point errors are handled |
| 74 | +- ✅ Backward compatible - existing functionality unchanged |
| 75 | +- ✅ Resolves gaps in layer slicing for complex geometries (e.g., Benchy model) |
| 76 | + |
| 77 | +## Example |
| 78 | + |
| 79 | +```javascript |
| 80 | +const Polytree = require('@jgphilpott/polytree'); |
| 81 | + |
| 82 | +// Create geometry with near-parallel edges |
| 83 | +const geometry = createGeometryWithNearParallelEdges(); |
| 84 | +const mesh = new THREE.Mesh(geometry, material); |
| 85 | + |
| 86 | +// Slice at Z=1.0 - now correctly handles near-parallel edges |
| 87 | +const layers = Polytree.sliceIntoLayers(mesh, 0.2, 0, 2); |
| 88 | +``` |
| 89 | + |
| 90 | +Before fix: Missing segments, gaps in paths |
| 91 | +After fix: Complete segment chains, closed paths |
| 92 | + |
| 93 | +## References |
| 94 | + |
| 95 | +- Issue analysis: [polyslice PR #69 comment](https://github.com/jgphilpott/polyslice/pull/69#issuecomment-3538692023) |
| 96 | +- Performance report: POLYTREE_PERFORMANCE_REPORT.md in polyslice PR #69 |
| 97 | +- Benchmark analysis: BENCHMARK_SUMMARY.md in polyslice PR #69 |
0 commit comments