From 9c170828cd659d097d3934c13de27d1937aeb76d Mon Sep 17 00:00:00 2001 From: Taco de Wolff Date: Fri, 26 Jan 2024 20:32:25 -0300 Subject: [PATCH] Fix panic with improperly sorted self-intersections, fixes panic but fails 3 tests, fixes #272 --- path_intersection.go | 9 +++++++++ path_intersection_test.go | 15 +++++++++++++++ path_intersection_util.go | 34 ++++++++++++++++++++-------------- 3 files changed, 44 insertions(+), 14 deletions(-) diff --git a/path_intersection.go b/path_intersection.go index d6b436b0..e91bbe49 100644 --- a/path_intersection.go +++ b/path_intersection.go @@ -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]) //} @@ -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 diff --git a/path_intersection_test.go b/path_intersection_test.go index acc43fec..c4608022 100644 --- a/path_intersection_test.go +++ b/path_intersection_test.go @@ -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 { diff --git a/path_intersection_util.go b/path_intersection_util.go index 304775fc..37733c75 100644 --- a/path_intersection_util.go +++ b/path_intersection_util.go @@ -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 @@ -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 @@ -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 { @@ -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 @@ -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}) }