Skip to content

Commit

Permalink
Fix panic with improperly sorted self-intersections, fixes panic but …
Browse files Browse the repository at this point in the history
…fails 3 tests, fixes #272
  • Loading branch information
tdewolff committed Jan 26, 2024
1 parent ed23c86 commit 9c17082
Show file tree
Hide file tree
Showing 3 changed files with 44 additions and 14 deletions.
9 changes: 9 additions & 0 deletions path_intersection.go
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,9 @@ func (p *Path) Settle(fillRule FillRule) *Path {
if 0 < len(zp) {
ps = p.Split() // otherwise, keep unflattened path
}
//for i, z := range zp {
// fmt.Println(i, z)
//}
//for i := range zp {
// fmt.Println(i, zp[i])
//}
Expand Down Expand Up @@ -196,6 +199,12 @@ func (p *Path) Settle(fillRule FillRule) *Path {

// cut path at intersections
paths, segs := cut(p, zp)
//for i, z := range zp[1:] {
// if z.Seg < zp[i].Seg || z.Seg == zp[i].Seg && !Equal(z.T, zp[i].T) && z.T < zp[i].T {
// fmt.Println(i, "bad", zp[i], z, zp[i].Less(z))
// }
//}
//fmt.Println(len(paths), len(zp))

// build up linked nodes between the intersections
// reverse direction for clock-wise path to ensure one of both paths goes outwards
Expand Down
15 changes: 15 additions & 0 deletions path_intersection_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -775,6 +775,21 @@ func TestIntersections(t *testing.T) {
{Point{2.0, 1.0}, 3, 0.5, math.Pi, false, true, false},
{Point{1.0, 1.0}, 4, 0.0, 1.5 * math.Pi, true, false, false},
}},

// bugs
{"M67.89174682452696 63.79390646055095L67.89174682452696 63.91890646055095L59.89174682452683 50.06250000000001", "M68.10825317547533 63.79390646055193L67.89174682452919 63.91890646055186M67.89174682452672 63.918906460550865L59.891746824526074 50.06250000000021", []PathIntersection{
{Point{67.89174682452696, 63.918906460551284}, 1, 1.0, 90.0 * math.Pi / 180.0, false, false, true},
{Point{67.89174682452696, 63.918906460553146}, 1, 1.0, 90.0 * math.Pi / 180.0, false, false, true},
{Point{67.89174682452696, 63.91890646055095}, 2, 0.0, 240.0 * math.Pi / 180.0, false, false, true},
{Point{67.89174682452793, 63.918906460552606}, 2, 0.0, 240.0 * math.Pi / 180.0, false, false, true},
{Point{59.89174682452683, 50.06250000000001}, 2, 1.0, 240.0 * math.Pi / 180.0, false, false, true},
}, []PathIntersection{
{Point{67.89174682452696, 63.918906460553146}, 1, 1.0, 150.0 * math.Pi / 180.0, false, false, true},
{Point{67.89174682452696, 63.918906460551284}, 3, 0.0, 240.0 * math.Pi / 180.0, false, false, true},
{Point{67.89174682452793, 63.918906460552606}, 1, 1.0, 150.0 * math.Pi / 180.0, false, false, true},
{Point{67.89174682452696, 63.91890646055095}, 3, 0.0, 240.0 * math.Pi / 180.0, false, false, true},
{Point{59.89174682452683, 50.06250000000001}, 3, 1.0, 240.0 * math.Pi / 180.0, false, false, true},
}},
}
origEpsilon := Epsilon
for _, tt := range tts {
Expand Down
34 changes: 20 additions & 14 deletions path_intersection_util.go
Original file line number Diff line number Diff line change
Expand Up @@ -242,11 +242,12 @@ func pathIntersectionNodes(p, q *Path, zp, zq []PathIntersection) []PathIntersec
}

func cut(p *Path, zs []PathIntersection) ([]*Path, subpathIndexer) {
// zs must be sorted
if len(zs) == 0 {
return []*Path{p}, newSubpathIndexer(p)
}

j := 0 // index into intersections
j := 0 // index into zs
k := 0 // index into ps
seg := 0 // segment count
var ps []*Path
Expand Down Expand Up @@ -391,9 +392,10 @@ func cutSegment(start Point, d []float64, t float64) (*Path, *Path) {
// PathIntersection is an intersection of a path.
// Intersection is either tangent or secant. Tangent intersections may be Parallel. Secant intersections either go into the other path (Into is set) or the other path goes into this path (Into is not set).
// Possible types of intersections:
// - Crossing anywhere: Tangent=false, Parallel=false
// - Touching anywhere: Tangent=true, Parallel=false, Into is invalid
// - Parallel onwards: Tangent=false, Parallel=true, Into is invalid
// - Crossing anywhere: Tangent=false, Parallel=false
// - Touching anywhere: Tangent=true, Parallel=false, Into is invalid
// - Parallel onwards: Tangent=false, Parallel=true, Into is invalid
//
// NB: Tangent may also be true for non-closing paths when touching its endpoints
type PathIntersection struct {
Point // coordinate of intersection
Expand All @@ -409,13 +411,14 @@ type PathIntersection struct {
func (z PathIntersection) Less(o PathIntersection) bool {
ti := float64(z.Seg) + z.T
tj := float64(o.Seg) + o.T
if Equal(ti, tj) {
// Q crosses P twice at the same point, Q must be at a tangent intersections, since
// all secant and parallel tangent intersections have been removed with Settle.
// Choose the parallel-end first and then the parallel-start
return !z.Parallel
}
return ti < tj
// TODO: this generates panics
//if Equal(ti, tj) {
// // Q crosses P twice at the same point, Q must be at a tangent intersections, since
// // all secant and parallel tangent intersections have been removed with Settle.
// // Choose the parallel-end first and then the parallel-start
// return !z.Parallel
//}
return z.Seg < o.Seg || ti < tj
}

func (z PathIntersection) Equals(o PathIntersection) bool {
Expand Down Expand Up @@ -908,9 +911,10 @@ func intersectionSegment(zs Intersections, a0 Point, a []float64, b0 Point, b []
// Intersection is an intersection between two path segments, e.g. Line x Line.
// Note that intersection is tangent also when it is one of the endpoints, in which case it may be tangent for this segment but we should double check when converting to a PathIntersection as it may or may not cross depending on the adjacent segment(s). Also, the Into value at tangent intersections at endpoints should be interpreted as if the paths were extended and the path would go into the left-hand side of the other path.
// Possible types of intersections:
// - Crossing not at endpoint: Tangent=false, Aligned=false
// - Touching not at endpoint: Tangent=true, Aligned=true, Into is invalid
// - Touching at endpoint: Tangent=true, may be aligned for (partly) overlapping paths
// - Crossing not at endpoint: Tangent=false, Aligned=false
// - Touching not at endpoint: Tangent=true, Aligned=true, Into is invalid
// - Touching at endpoint: Tangent=true, may be aligned for (partly) overlapping paths
//
// NB: for quad/cube/ellipse aligned angles at the endpoint for non-overlapping curves are deviated slightly to correctly calculate the value for Into, and will thus not be aligned
type Intersection struct {
Point // coordinate of intersection
Expand Down Expand Up @@ -976,6 +980,8 @@ func (zs Intersections) HasTangent() bool {
}

func (zs Intersections) add(pos Point, ta, tb, dira, dirb float64, tangent bool) Intersections {
ta = math.Max(0.0, math.Min(1.0, ta))
tb = math.Max(0.0, math.Min(1.0, tb))
return append(zs, Intersection{pos, [2]float64{ta, tb}, [2]float64{dira, dirb}, tangent})
}

Expand Down

0 comments on commit 9c17082

Please sign in to comment.