RayIntersections returns the intersections of a path with a ray starting at (x,y) to (∞,y). An intersection is tangent only when it is at (x,y), i.e. the start of the ray. The parameter T along the ray is zero at the start but NaN otherwise. Intersections are sorted along the ray. This function runs
(x, y float64)
| 22 | // along the ray is zero at the start but NaN otherwise. Intersections are sorted along the ray. |
| 23 | // This function runs in O(n) with n the number of path segments. |
| 24 | func (p *Path) RayIntersections(x, y float64) []Intersection { |
| 25 | var start, end Point |
| 26 | var zs []Intersection |
| 27 | for i := 0; i < len(p.d); { |
| 28 | cmd := p.d[i] |
| 29 | switch cmd { |
| 30 | case MoveToCmd: |
| 31 | end = Point{p.d[i+1], p.d[i+2]} |
| 32 | case LineToCmd, CloseCmd: |
| 33 | end = Point{p.d[i+1], p.d[i+2]} |
| 34 | ymin := math.Min(start.Y, end.Y) |
| 35 | ymax := math.Max(start.Y, end.Y) |
| 36 | xmax := math.Max(start.X, end.X) |
| 37 | if Interval(y, ymin, ymax) && x <= xmax+Epsilon { |
| 38 | zs = intersectionLineLine(zs, Point{x, y}, Point{xmax + 1.0, y}, start, end) |
| 39 | } |
| 40 | case QuadToCmd: |
| 41 | cp := Point{p.d[i+1], p.d[i+2]} |
| 42 | end = Point{p.d[i+3], p.d[i+4]} |
| 43 | ymin := math.Min(math.Min(start.Y, end.Y), cp.Y) |
| 44 | ymax := math.Max(math.Max(start.Y, end.Y), cp.Y) |
| 45 | xmax := math.Max(math.Max(start.X, end.X), cp.X) |
| 46 | if Interval(y, ymin, ymax) && x <= xmax+Epsilon { |
| 47 | zs = intersectionLineQuad(zs, Point{x, y}, Point{xmax + 1.0, y}, start, cp, end) |
| 48 | } |
| 49 | case CubeToCmd: |
| 50 | cp1 := Point{p.d[i+1], p.d[i+2]} |
| 51 | cp2 := Point{p.d[i+3], p.d[i+4]} |
| 52 | end = Point{p.d[i+5], p.d[i+6]} |
| 53 | ymin := math.Min(math.Min(start.Y, end.Y), math.Min(cp1.Y, cp2.Y)) |
| 54 | ymax := math.Max(math.Max(start.Y, end.Y), math.Max(cp1.Y, cp2.Y)) |
| 55 | xmax := math.Max(math.Max(start.X, end.X), math.Max(cp1.X, cp2.X)) |
| 56 | if Interval(y, ymin, ymax) && x <= xmax+Epsilon { |
| 57 | zs = intersectionLineCube(zs, Point{x, y}, Point{xmax + 1.0, y}, start, cp1, cp2, end) |
| 58 | } |
| 59 | case ArcToCmd: |
| 60 | rx, ry, phi := p.d[i+1], p.d[i+2], p.d[i+3] |
| 61 | large, sweep := toArcFlags(p.d[i+4]) |
| 62 | end = Point{p.d[i+5], p.d[i+6]} |
| 63 | cx, cy, theta0, theta1 := ellipseToCenter(start.X, start.Y, rx, ry, phi, large, sweep, end.X, end.Y) |
| 64 | if Interval(y, cy-math.Max(rx, ry), cy+math.Max(rx, ry)) && x <= cx+math.Max(rx, ry)+Epsilon { |
| 65 | zs = intersectionLineEllipse(zs, Point{x, y}, Point{cx + rx + 1.0, y}, Point{cx, cy}, Point{rx, ry}, phi, theta0, theta1) |
| 66 | } |
| 67 | } |
| 68 | i += cmdLen(cmd) |
| 69 | start = end |
| 70 | } |
| 71 | for i := range zs { |
| 72 | if zs[i].T[0] != 0.0 { |
| 73 | zs[i].T[0] = math.NaN() |
| 74 | } |
| 75 | } |
| 76 | sort.SliceStable(zs, func(i, j int) bool { |
| 77 | if Equal(zs[i].X, zs[j].X) { |
| 78 | return false |
| 79 | } |
| 80 | return zs[i].X < zs[j].X |
| 81 | }) |
no test coverage detected