From 7c4521b227e2897384c1006aa37c7cdea0d41ba5 Mon Sep 17 00:00:00 2001 From: Thomas Friedel Date: Wed, 10 Apr 2019 11:48:22 +0200 Subject: [PATCH] added a function to check if a point is in a path --- README.md | 2 +- canvas.go | 6 +++++ path2d.go | 58 ++++++++++++++++++++++++++++++++++++++++++++++++ paths.go | 29 +++++++++--------------- triangulation.go | 18 ++++++++------- 5 files changed, 85 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index 6d4deb8..8c6ea14 100644 --- a/README.md +++ b/README.md @@ -119,11 +119,11 @@ These features *should* work just like their HTML5 counterparts, but there are l - shadowColor - shadowOffset(X/Y) - shadowBlur +- isPointInPath # Missing features - globalCompositeOperation -- isPointInPath - isPointInStroke - textBaseline hanging and ideographic (currently work just like top and bottom) - full self intersecting polygon support diff --git a/canvas.go b/canvas.go index 642b5ac..e0e8894 100644 --- a/canvas.go +++ b/canvas.go @@ -454,3 +454,9 @@ func (cv *Canvas) SetShadowOffset(x, y float64) { func (cv *Canvas) SetShadowBlur(r float64) { cv.state.shadowBlur = r } + +// IsPointInPath returns true if the point is in the current +// path according to the given rule +func (cv *Canvas) IsPointInPath(x, y float64, rule pathRule) bool { + return cv.path.IsPointInPath(x, y, rule) +} diff --git a/path2d.go b/path2d.go index 510b0af..1fa36b9 100644 --- a/path2d.go +++ b/path2d.go @@ -286,3 +286,61 @@ func (p *Path2D) Rect(x, y, w, h float64) { // func (p *Path2D) Ellipse(...) { // } + +func runSubPaths(path *Path2D, fn func(subPath []pathPoint) bool) { + start := 0 + for i, p := range path.p { + if p.flags&pathMove == 0 { + continue + } + if i >= start+3 { + if fn(path.p[start:i]) { + return + } + } + start = i + } + if len(path.p) >= start+3 { + fn(path.p[start:]) + } +} + +type pathRule uint8 + +// Path rule constants. See https://en.wikipedia.org/wiki/Nonzero-rule +// and https://en.wikipedia.org/wiki/Even%E2%80%93odd_rule +const ( + NonZero pathRule = iota + EvenOdd +) + +// IsPointInPath returns true if the point is in the path according +// to the given rule +func (p *Path2D) IsPointInPath(x, y float64, rule pathRule) bool { + inside := false + runSubPaths(p, func(sp []pathPoint) bool { + num := 0 + prev := sp[len(sp)-1].pos + for _, pt := range p.p { + r, dir := pointIsRightOfLine(prev, pt.pos, vec{x, y}) + prev = pt.pos + if !r { + continue + } + if dir { + num++ + } else { + num-- + } + } + + if rule == NonZero { + inside = num != 0 + } else { + inside = num%2 == 0 + } + + return inside + }) + return inside +} diff --git a/paths.go b/paths.go index 5d29505..ae2a985 100644 --- a/paths.go +++ b/paths.go @@ -338,7 +338,11 @@ func (cv *Canvas) FillPath(path *Path2D) { } var triBuf [500][2]float64 - tris := buildFillTriangles(path, triBuf[:0]) + tris := triBuf[:0] + runSubPaths(path, func(sp []pathPoint) bool { + tris = appendSubPathTriangles(tris, sp) + return false + }) if len(tris) == 0 { return } @@ -349,23 +353,6 @@ func (cv *Canvas) FillPath(path *Path2D) { cv.b.Fill(&stl, tris) } -func buildFillTriangles(path *Path2D, tris [][2]float64) [][2]float64 { - start := 0 - for i, p := range path.p { - if p.flags&pathMove == 0 { - continue - } - if i >= start+3 { - tris = appendSubPathTriangles(tris, path.p[start:i]) - } - start = i - } - if len(path.p) >= start+3 { - tris = appendSubPathTriangles(tris, path.p[start:]) - } - return tris -} - func appendSubPathTriangles(tris [][2]float64, path []pathPoint) [][2]float64 { last := path[len(path)-1] if last.flags&pathIsConvex != 0 { @@ -397,7 +384,11 @@ func (cv *Canvas) clip(path *Path2D) { } var triBuf [500][2]float64 - tris := buildFillTriangles(path, triBuf[:0]) + tris := triBuf[:0] + runSubPaths(path, func(sp []pathPoint) bool { + tris = appendSubPathTriangles(tris, sp) + return false + }) if len(tris) == 0 { return } diff --git a/triangulation.go b/triangulation.go index 8d7c302..f9ddf24 100644 --- a/triangulation.go +++ b/triangulation.go @@ -5,20 +5,22 @@ import ( "sort" ) -func pointIsRightOfLine(a, b, p vec) bool { +func pointIsRightOfLine(a, b, p vec) (bool, bool) { if a[1] == b[1] { - return false + return false, false } + dir := false if a[1] > b[1] { a, b = b, a + dir = !dir } if p[1] < a[1] || p[1] > b[1] { - return false + return false, false } v := b.sub(a) r := (p[1] - a[1]) / v[1] x := a[0] + r*v[0] - return p[0] > x + return p[0] > x, dir } func triangleContainsPoint(a, b, c, p vec) bool { @@ -38,13 +40,13 @@ func triangleContainsPoint(a, b, c, p vec) bool { // check whether the point is to the right of each triangle line. // if the total is 1, it is inside the triangle count := 0 - if pointIsRightOfLine(a, b, p) { + if r, _ := pointIsRightOfLine(a, b, p); r { count++ } - if pointIsRightOfLine(b, c, p) { + if r, _ := pointIsRightOfLine(b, c, p); r { count++ } - if pointIsRightOfLine(c, a, p) { + if r, _ := pointIsRightOfLine(c, a, p); r { count++ } return count == 1 @@ -54,7 +56,7 @@ func polygonContainsPoint(polygon []vec, p vec) bool { a := polygon[len(polygon)-1] count := 0 for _, b := range polygon { - if pointIsRightOfLine(a, b, p) { + if r, _ := pointIsRightOfLine(a, b, p); r { count++ } a = b