Skip to content

Commit

Permalink
Path intersections: fix bugs related to line-line intersection code
Browse files Browse the repository at this point in the history
  • Loading branch information
tdewolff committed Jan 5, 2025
1 parent 082824f commit 9b1ab02
Show file tree
Hide file tree
Showing 2 changed files with 113 additions and 59 deletions.
20 changes: 13 additions & 7 deletions path_intersection_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -166,9 +166,9 @@ func TestIntersectionLineLineBentleyOttmann(t *testing.T) {
// tangent
{"M2 0L2 3", "M2 2L3 2", []Point{{2.0, 2.0}}},
{"M2 0L2 2", "M2 2L3 2", nil},
{"L2 2", "M0 4L2 2", []Point{{2.0, 2.0}}},
{"L10 5", "M0 10L10 5", []Point{{10.0, 5.0}}},
{"M10 5L20 10", "M10 5L20 0", []Point{{10.0, 5.0}}},
{"L2 2", "M0 4L2 2", nil},
{"L10 5", "M0 10L10 5", nil},
{"M10 5L20 10", "M10 5L20 0", nil},

// parallel
{"L2 2", "L2 2", nil},
Expand Down Expand Up @@ -198,26 +198,32 @@ func TestIntersectionLineLineBentleyOttmann(t *testing.T) {

// bugs
{"M21.590990257669734 18.40900974233027L22.651650429449557 17.348349570550447", "M21.23743686707646 18.762563132923542L21.590990257669738 18.409009742330266",
[]Point{{21.590990257669738, 18.40900974233027}},
nil, //[]Point{{21.590990257669738, 18.40900974233027}},
}, // almost colinear
{"M-0.1997406229376793 158.88740153238177L-0.1997406229376793 296.9999999925494", "M-0.1997406229376793 158.88740153238177L-0.19974062293732664 158.8874019079834",
[]Point{{-0.1997406229376793, 158.88740153238177}},
nil,
}, // #287
{"M-0.1997406229376793 158.88740153238177L-0.1997406229376793 296.9999999925494", "M-0.19999999999964735 20.77454766193328L-0.19974062293732664 158.8874019079834",
[]Point{{-0.1997406229376793, 158.88740172019808}},
}, // #287
{"M-0.1997406229376793 158.88740153238177L-0.19974062293732664 158.8874019079834", "M-0.19999999999964735 20.77454766193328L-0.19974062293732664 158.8874019079834",
[]Point{{-0.19974062293732664, 158.8874019079834}},
nil, //[]Point{{-0.19974062293732664, 158.8874019079834}},
}, // almost colinear, #287
{"M162.43449681368278 -9.999996185876771L162.43449681368278 -9.99998551284069", "M162.2344968136828 -9.99998551284069L162.43449681368278 -9.999985512840682",
nil,
}, // almost colinear, #287
{"M0.7814851602550552,0.3987574923859699L0.7814861805182336,0.39875653588924026", "M0.7814852358775772,0.39875773815916654L0.7814861805182336,0.39875653588924026",
[]Point{{0.7814861805182336, 0.39875653588924026}},
nil, //[]Point{{0.7814861805182336, 0.39875653588924026}},
}, // almost colinear
{"M0.6187340865555582,7.030136875251485L0.6203785454666688,7.0296030922171955", "M0.6187340865555582,7.030136875251485L0.6189178552813777,7.03007722485377",
nil,
}, // colinear
{"M3.937495009359532,6.968745009359532L3.937495009359532,6.968745009361169", "M3.93359874,6.96874501L3.937504990640468,6.968745009359532",
nil,
},
{"M0.7099618031467739,7.781251248018358L0.7099618032015841,7.781251247660366", "M0.70995969,7.78125125L0.709962185160117,7.781251247660117",
[]Point{{0.7099618031467739, 7.781251248018357}}, // weird!
},
}
for _, tt := range tts {
t.Run(fmt.Sprint(tt.line1, "x", tt.line2), func(t *testing.T) {
Expand Down
152 changes: 100 additions & 52 deletions path_intersection_util.go
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,38 @@ func (zs Intersections) add(pos Point, ta, tb, dira, dirb float64, tangent, same
return append(zs, Intersection{pos, [2]float64{ta, tb}, [2]float64{dira, dirb}, tangent, same})
}

func correctIntersection(z, aMin, aMax, bMin, bMax Point) Point {
if z.X < aMin.X {
//fmt.Println("CORRECT 1:", a0, a1, "--", b0, b1)
z.X = aMin.X
} else if aMax.X < z.X {
//fmt.Println("CORRECT 2:", a0, a1, "--", b0, b1)
z.X = aMax.X
}
if z.X < bMin.X {
//fmt.Println("CORRECT 3:", a0, a1, "--", b0, b1)
z.X = bMin.X
} else if bMax.X < z.X {
//fmt.Println("CORRECT 4:", a0, a1, "--", b0, b1)
z.X = bMax.X
}
if z.Y < aMin.Y {
//fmt.Println("CORRECT 5:", a0, a1, "--", b0, b1)
z.Y = aMin.Y
} else if aMax.Y < z.Y {
//fmt.Println("CORRECT 6:", a0, a1, "--", b0, b1)
z.Y = aMax.Y
}
if z.Y < bMin.Y {
//fmt.Println("CORRECT 7:", a0, a1, "--", b0, b1)
z.Y = bMin.Y
} else if bMax.Y < z.Y {
//fmt.Println("CORRECT 8:", a0, a1, "--", b0, b1)
z.Y = bMax.Y
}
return z
}

// F. Antonio, "Faster Line Segment Intersection", Graphics Gems III, 1992
func intersectionLineLineBentleyOttmann(zs []Point, a0, a1, b0, b1 Point) []Point {
// fast line-line intersection code, with additional constraints for the BentleyOttmann code:
Expand All @@ -169,11 +201,17 @@ func intersectionLineLineBentleyOttmann(zs []Point, a0, a1, b0, b1 Point) []Poin
if a1.X < b0.X || b1.X < a0.X {
return zs
}
aMinY, aMaxY := math.Min(a0.Y, a1.Y), math.Max(a0.Y, a1.Y)
bMinY, bMaxY := math.Min(b0.Y, b1.Y), math.Max(b0.Y, b1.Y)
if aMaxY < bMinY || bMaxY < aMinY {

aMin, aMax, bMin, bMax := a0, a1, b0, b1
if a1.Y < a0.Y {
aMin.Y, aMax.Y = aMax.Y, aMin.Y
}
if b1.Y < b0.Y {
bMin.Y, bMax.Y = bMax.Y, bMin.Y
}
if aMax.Y < bMin.Y || bMax.Y < aMin.Y {
return zs
} else if (a1.X == b0.X || b1.X == a0.X) && (aMaxY == bMinY || bMaxY == aMinY) {
} else if (aMax.X == bMin.X || bMax.X == aMin.X) && (aMax.Y == bMin.Y || bMax.Y == aMin.Y) {
return zs
}

Expand Down Expand Up @@ -205,60 +243,70 @@ func intersectionLineLineBentleyOttmann(zs []Point, a0, a1, b0, b1 Point) []Poin
}
}
}
} else {
anum := C.PerpDot(B)
if 0.0 < denom {
if anum < Epsilon || denom+Epsilon < anum {
return zs
}
} else if anum < denom-Epsilon || Epsilon < anum {
return zs
}

anum := C.PerpDot(B)
if 0.0 < denom {
if anum < 0.0 || denom < anum {
return zs
}
} else if anum < denom || 0.0 < anum {
return zs
}

bnum := A.PerpDot(C)
if 0.0 < denom {
if bnum < Epsilon || denom+Epsilon < bnum {
return zs
}
} else if bnum < denom-Epsilon || Epsilon < bnum {
bnum := A.PerpDot(C)
if 0.0 < denom {
if bnum < 0.0 || denom < bnum {
return zs
}
} else if bnum < denom || 0.0 < bnum {
return zs
}

// ta is snapped to 0.0 or 1.0 if very close
ta := anum / denom // in [0,1]
if Equal(ta, 0.0) || ta < 0.0 {
ta = 0.0
} else if Equal(ta, 1.0) || 1.0 < ta {
ta = 1.0
}
z := a0.Interpolate(a1, ta)
zs = append(zs, z)
}

// correct for numerical errors, make sure that the intersection is within the limits of
// each segment, and also that all split segments (ie. (a0,z), (z,a1), (b0,z), (z,b1)) are
// increasing (ie. go left-to-right or if vertical bottom-to-top)
for i := range zs {
z := &zs[i]
if z.X < a0.X {
z.X = a0.X
} else if a1.X < z.X {
z.X = a1.X
}
if z.X < b0.X {
z.X = b0.X
} else if b1.X < z.X {
z.X = b1.X
}
if z.Y < aMinY {
z.Y = aMinY
} else if aMaxY < z.Y {
z.Y = aMaxY
}
if z.Y < bMinY {
z.Y = bMinY
} else if bMaxY < z.Y {
z.Y = bMaxY
// ta is snapped to 0.0 or 1.0 if very close
ta := anum / denom // in [0,1]
if Equal(ta, 0.0) {
ta = 0.0
} else if Equal(ta, 1.0) {
ta = 1.0
}

z := a0.Interpolate(a1, ta)
z = correctIntersection(z, aMin, aMax, bMin, bMax)
if z != a0 && z != a1 || z != b0 && z != b1 {
// not at endpoints for both
if a0 != b0 && z != a0 && z != b0 && b0.Sub(z).PerpDot(z.Sub(a0)) == 0.0 {
a, c, m := a0.X, b0.X, z.X
if math.Abs(z.Sub(a0).X) < math.Abs(z.Sub(a0).Y) {
// mostly vertical
a, c, m = a0.Y, b0.Y, z.Y
}

if a != c && (a < m) == (c < m) {
if a < m && a < c || m < a && c < a {
zs = append(zs, b0)
} else {
zs = append(zs, a0)
}
}
zs = append(zs, z)
} else if a1 != b1 && z != a1 && z != b1 && z.Sub(b1).PerpDot(a1.Sub(z)) == 0.0 {
b, d, m := a1.X, b1.X, z.X
if math.Abs(z.Sub(a1).X) < math.Abs(z.Sub(a1).Y) {
// mostly vertical
b, d, m = a1.Y, b1.Y, z.Y
}

if b != d && (b < m) == (d < m) {
if b < m && b < d || m < b && d < b {
zs = append(zs, b1)
} else {
zs = append(zs, a1)
}
}
} else {
zs = append(zs, z)
}
}
return zs
Expand Down

0 comments on commit 9b1ab02

Please sign in to comment.