Skip to content

Commit

Permalink
Path intersections: prevent numerical issues by first gridsnapping al…
Browse files Browse the repository at this point in the history
…l coordinates to 2*Epsilon grid
  • Loading branch information
tdewolff committed Nov 11, 2024
1 parent 8d95f4a commit 7285c02
Show file tree
Hide file tree
Showing 3 changed files with 18 additions and 8 deletions.
4 changes: 0 additions & 4 deletions path_decimate.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,6 @@ import (
"math"
)

func snap(v, d float64) float64 {
return math.Round(v/d) * d
}

// Gridsnap snaps all vertices to a grid with the given spacing. This will significantly reduce numerical issues e.g. for path boolean operations. This operation is in-place.
func (p *Path) Gridsnap(spacing float64) *Path {
for i := 0; i < len(p.d); {
Expand Down
12 changes: 8 additions & 4 deletions path_intersection.go
Original file line number Diff line number Diff line change
Expand Up @@ -914,6 +914,8 @@ func addIntersections(queue *SweepEvents, handled map[SweepPointPair]struct{}, z
// clean up intersections outside one of the segments, this may happen for nearly parallel
// lines for example
for i := 0; i < len(zs); i++ {
zs[i].Point = zs[i].Point.Gridsnap(2.0 * Epsilon) // prevent numerical issues

if z := zs[i]; !a.vertical && !Interval(z.X, a.X, a.other.X) || a.vertical && !Interval(z.Y, a.Y, a.other.Y) || !b.vertical && !Interval(z.X, b.X, b.other.X) || b.vertical && !Interval(z.Y, b.Y, b.other.Y) { //z.X < a.X || z.X < b.X || a.other.X < z.X || b.other.X < z.X {
fmt.Println("WARNING: removing intersection", zs[i], "between", a, b)
zs = append(zs[:i], zs[i+1:]...)
Expand Down Expand Up @@ -1133,8 +1135,9 @@ func bentleyOttmann(ps, qs Paths, op pathOp, fillRule FillRule) *Path {
ps = append(ps, split[1:]...)
}
}
for i, p := range ps {
ps[i] = p.Flatten(Tolerance)
for i := range ps {
ps[i] = ps[i].Flatten(Tolerance)
ps[i] = ps[i].Gridsnap(2.0 * Epsilon) // prevent numerical issues
}
if qs != nil {
for i, iMax := 0, len(qs); i < iMax; i++ {
Expand All @@ -1144,8 +1147,9 @@ func bentleyOttmann(ps, qs Paths, op pathOp, fillRule FillRule) *Path {
qs = append(qs, split[1:]...)
}
}
for i, q := range qs {
qs[i] = q.Flatten(Tolerance)
for i := range qs {
qs[i] = qs[i].Flatten(Tolerance)
qs[i] = qs[i].Gridsnap(2.0 * Epsilon) // prevent numerical issues
}
}

Expand Down
10 changes: 10 additions & 0 deletions util.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,11 @@ func angleBetweenExclusive(theta, lower, upper float64) bool {
return false
}

// snap "gridsnaps" the floating point to a grid of the given spacing
func snap(val, spacing float64) float64 {
return math.Round(val/spacing) * spacing
}

////////////////////////////////////////////////////////////////

type numEps float64
Expand Down Expand Up @@ -341,6 +346,11 @@ func (p Point) Interpolate(q Point, t float64) Point {
return Point{(1-t)*p.X + t*q.X, (1-t)*p.Y + t*q.Y}
}

// Gridsnap snaps point to a grid with the given spacing.
func (p Point) Gridsnap(spacing float64) Point {
return Point{snap(p.X, spacing), snap(p.Y, spacing)}
}

// String returns the string representation of a point, such as "(x,y)".
func (p Point) String() string {
return fmt.Sprintf("(%g,%g)", p.X, p.Y)
Expand Down

0 comments on commit 7285c02

Please sign in to comment.