| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306 |
- // geomx adds some geometry functions needed by rasterx
- // Copyright 2017 by the rasterx Authors. All rights reserved.
- // Created: 2/12/2017 by S.R.Wiley
- package rasterx
- import (
- "fmt"
- "math"
- "golang.org/x/image/math/fixed"
- )
- // Invert returns the point inverted around the origin
- func Invert(v fixed.Point26_6) fixed.Point26_6 {
- return fixed.Point26_6{X: -v.X, Y: -v.Y}
- }
- // turnStarboard90 returns the vector 90 degrees starboard (right in direction heading)
- func turnStarboard90(v fixed.Point26_6) fixed.Point26_6 {
- return fixed.Point26_6{X: -v.Y, Y: v.X}
- }
- // turnPort90 returns the vector 90 degrees port (left in direction heading)
- func turnPort90(v fixed.Point26_6) fixed.Point26_6 {
- return fixed.Point26_6{X: v.Y, Y: -v.X}
- }
- // DotProd returns the inner product of p and q
- func DotProd(p fixed.Point26_6, q fixed.Point26_6) fixed.Int52_12 {
- return fixed.Int52_12(int64(p.X)*int64(q.X) + int64(p.Y)*int64(q.Y))
- }
- // Length is the distance from the origin of the point
- func Length(v fixed.Point26_6) fixed.Int26_6 {
- vx, vy := float64(v.X), float64(v.Y)
- return fixed.Int26_6(math.Sqrt(vx*vx + vy*vy))
- }
- //PathCommand is the type for the path command token
- type PathCommand fixed.Int26_6
- // Human readable path constants
- const (
- PathMoveTo PathCommand = iota
- PathLineTo
- PathQuadTo
- PathCubicTo
- PathClose
- )
- // A Path starts with a PathCommand value followed by zero to three fixed
- // int points.
- type Path []fixed.Int26_6
- // ToSVGPath returns a string representation of the path
- func (p Path) ToSVGPath() string {
- s := ""
- for i := 0; i < len(p); {
- if i != 0 {
- s += " "
- }
- switch PathCommand(p[i]) {
- case PathMoveTo:
- s += fmt.Sprintf("M%4.3f,%4.3f", float32(p[i+1])/64, float32(p[i+2])/64)
- i += 3
- case PathLineTo:
- s += fmt.Sprintf("L%4.3f,%4.3f", float32(p[i+1])/64, float32(p[i+2])/64)
- i += 3
- case PathQuadTo:
- s += fmt.Sprintf("Q%4.3f,%4.3f,%4.3f,%4.3f", float32(p[i+1])/64, float32(p[i+2])/64,
- float32(p[i+3])/64, float32(p[i+4])/64)
- i += 5
- case PathCubicTo:
- s += "C" + fmt.Sprintf("C%4.3f,%4.3f,%4.3f,%4.3f,%4.3f,%4.3f", float32(p[i+1])/64, float32(p[i+2])/64,
- float32(p[i+3])/64, float32(p[i+4])/64, float32(p[i+5])/64, float32(p[i+6])/64)
- i += 7
- case PathClose:
- s += "Z"
- i++
- default:
- panic("freetype/rasterx: bad pather")
- }
- }
- return s
- }
- // String returns a readable representation of a Path.
- func (p Path) String() string {
- return p.ToSVGPath()
- }
- // Clear zeros the path slice
- func (p *Path) Clear() {
- *p = (*p)[:0]
- }
- // Start starts a new curve at the given point.
- func (p *Path) Start(a fixed.Point26_6) {
- *p = append(*p, fixed.Int26_6(PathMoveTo), a.X, a.Y)
- }
- // Line adds a linear segment to the current curve.
- func (p *Path) Line(b fixed.Point26_6) {
- *p = append(*p, fixed.Int26_6(PathLineTo), b.X, b.Y)
- }
- // QuadBezier adds a quadratic segment to the current curve.
- func (p *Path) QuadBezier(b, c fixed.Point26_6) {
- *p = append(*p, fixed.Int26_6(PathQuadTo), b.X, b.Y, c.X, c.Y)
- }
- // CubeBezier adds a cubic segment to the current curve.
- func (p *Path) CubeBezier(b, c, d fixed.Point26_6) {
- *p = append(*p, fixed.Int26_6(PathCubicTo), b.X, b.Y, c.X, c.Y, d.X, d.Y)
- }
- // Stop joins the ends of the path
- func (p *Path) Stop(closeLoop bool) {
- if closeLoop {
- *p = append(*p, fixed.Int26_6(PathClose))
- }
- }
- // AddTo adds the Path p to q.
- func (p Path) AddTo(q Adder) {
- for i := 0; i < len(p); {
- switch PathCommand(p[i]) {
- case PathMoveTo:
- q.Stop(false) // Fixes issues #1 by described by Djadala; implicit close if currently in path.
- q.Start(fixed.Point26_6{X: p[i+1], Y: p[i+2]})
- i += 3
- case PathLineTo:
- q.Line(fixed.Point26_6{X: p[i+1], Y: p[i+2]})
- i += 3
- case PathQuadTo:
- q.QuadBezier(fixed.Point26_6{X: p[i+1], Y: p[i+2]}, fixed.Point26_6{X: p[i+3], Y: p[i+4]})
- i += 5
- case PathCubicTo:
- q.CubeBezier(fixed.Point26_6{X: p[i+1], Y: p[i+2]},
- fixed.Point26_6{X: p[i+3], Y: p[i+4]}, fixed.Point26_6{X: p[i+5], Y: p[i+6]})
- i += 7
- case PathClose:
- q.Stop(true)
- i++
- default:
- panic("AddTo: bad path")
- }
- }
- q.Stop(false)
- }
- // ToLength scales the point to the length indicated by ln
- func ToLength(p fixed.Point26_6, ln fixed.Int26_6) (q fixed.Point26_6) {
- if ln == 0 || (p.X == 0 && p.Y == 0) {
- return
- }
- pX, pY := float64(p.X), float64(p.Y)
- lnF := float64(ln)
- pLen := math.Sqrt(pX*pX + pY*pY)
- qX, qY := pX*lnF/pLen, pY*lnF/pLen
- q.X, q.Y = fixed.Int26_6(qX), fixed.Int26_6(qY)
- return
- }
- // ClosestPortside returns the closest of p1 or p2 on the port side of the
- // line from the bow to the stern. (port means left side of the direction you are heading)
- // isIntersecting is just convienice to reduce code, and if false returns false, because p1 and p2 are not valid
- func ClosestPortside(bow, stern, p1, p2 fixed.Point26_6, isIntersecting bool) (xt fixed.Point26_6, intersects bool) {
- if isIntersecting == false {
- return
- }
- dir := bow.Sub(stern)
- dp1 := p1.Sub(stern)
- dp2 := p2.Sub(stern)
- cp1 := dir.X*dp1.Y - dp1.X*dir.Y
- cp2 := dir.X*dp2.Y - dp2.X*dir.Y
- switch {
- case cp1 < 0 && cp2 < 0:
- return
- case cp1 < 0 && cp2 >= 0:
- return p2, true
- case cp1 >= 0 && cp2 < 0:
- return p1, true
- default: // both points on port side
- dirdot := DotProd(dir, dir)
- // calculate vector rejections of dp1 and dp2 onto dir
- h1 := dp1.Sub(dir.Mul(fixed.Int26_6((DotProd(dp1, dir) << 6) / dirdot)))
- h2 := dp2.Sub(dir.Mul(fixed.Int26_6((DotProd(dp2, dir) << 6) / dirdot)))
- // return point with smallest vector rejection; i.e. closest to dir line
- if (h1.X*h1.X + h1.Y*h1.Y) > (h2.X*h2.X + h2.Y*h2.Y) {
- return p2, true
- }
- return p1, true
- }
- }
- // RadCurvature returns the curvature of a Bezier curve end point,
- // given an end point, the two adjacent control points and the degree.
- // The sign of the value indicates if the center of the osculating circle
- // is left or right (port or starboard) of the curve in the forward direction.
- func RadCurvature(p0, p1, p2 fixed.Point26_6, dm fixed.Int52_12) fixed.Int26_6 {
- a, b := p2.Sub(p1), p1.Sub(p0)
- abdot, bbdot := DotProd(a, b), DotProd(b, b)
- h := a.Sub(b.Mul(fixed.Int26_6((abdot << 6) / bbdot))) // h is the vector rejection of a onto b
- if h.X == 0 && h.Y == 0 { // points are co-linear
- return 0
- }
- radCurve := fixed.Int26_6((fixed.Int52_12(a.X*a.X+a.Y*a.Y) * dm / fixed.Int52_12(Length(h)<<6)) >> 6)
- if a.X*b.Y > b.X*a.Y { // xprod sign
- return radCurve
- }
- return -radCurve
- }
- // CircleCircleIntersection calculates the points of intersection of
- // two circles or returns with intersects == false if no such points exist.
- func CircleCircleIntersection(ct, cl fixed.Point26_6, rt, rl fixed.Int26_6) (xt1, xt2 fixed.Point26_6, intersects bool) {
- dc := cl.Sub(ct)
- d := Length(dc)
- // Check for solvability.
- if d > (rt + rl) {
- return // No solution. Circles do not intersect.
- }
- // check if d < abs(rt-rl)
- if da := rt - rl; (da > 0 && d < da) || (da < 0 && d < -da) {
- return // No solution. One circle is contained by the other.
- }
- rlf, rtf, df := float64(rl), float64(rt), float64(d)
- af := (rtf*rtf - rlf*rlf + df*df) / df / 2.0
- hfd := math.Sqrt(rtf*rtf-af*af) / df
- afd := af / df
- rOffx, rOffy := float64(-dc.Y)*hfd, float64(dc.X)*hfd
- p2x := float64(ct.X) + float64(dc.X)*afd
- p2y := float64(ct.Y) + float64(dc.Y)*afd
- xt1x, xt1y := p2x+rOffx, p2y+rOffy
- xt2x, xt2y := p2x-rOffx, p2y-rOffy
- return fixed.Point26_6{X: fixed.Int26_6(xt1x), Y: fixed.Int26_6(xt1y)},
- fixed.Point26_6{X: fixed.Int26_6(xt2x), Y: fixed.Int26_6(xt2y)}, true
- }
- // CalcIntersect calculates the points of intersection of two fixed point lines
- // and panics if the determinate is zero. You have been warned.
- func CalcIntersect(a1, a2, b1, b2 fixed.Point26_6) (x fixed.Point26_6) {
- da, db, ds := a2.Sub(a1), b2.Sub(b1), a1.Sub(b1)
- det := float32(da.X*db.Y - db.X*da.Y) // Determinate
- t := float32(ds.Y*db.X-ds.X*db.Y) / det
- x = a1.Add(fixed.Point26_6{X: fixed.Int26_6(float32(da.X) * t), Y: fixed.Int26_6(float32(da.Y) * t)})
- return
- }
- // RayCircleIntersection calculates the points of intersection of
- // a ray starting at s2 passing through s1 and a circle in fixed point.
- // Returns intersects == false if no solution is possible. If two
- // solutions are possible, the point closest to s2 is returned
- func RayCircleIntersection(s1, s2, c fixed.Point26_6, r fixed.Int26_6) (x fixed.Point26_6, intersects bool) {
- fx, fy, intersects := RayCircleIntersectionF(float64(s1.X), float64(s1.Y),
- float64(s2.X), float64(s2.Y), float64(c.X), float64(c.Y), float64(r))
- return fixed.Point26_6{X: fixed.Int26_6(fx),
- Y: fixed.Int26_6(fy)}, intersects
- }
- // RayCircleIntersectionF calculates in floating point the points of intersection of
- // a ray starting at s2 passing through s1 and a circle in fixed point.
- // Returns intersects == false if no solution is possible. If two
- // solutions are possible, the point closest to s2 is returned
- func RayCircleIntersectionF(s1X, s1Y, s2X, s2Y, cX, cY, r float64) (x, y float64, intersects bool) {
- n := s2X - cX // Calculating using 64* rather than divide
- m := s2Y - cY
- e := s2X - s1X
- d := s2Y - s1Y
- // Quadratic normal form coefficients
- A, B, C := e*e+d*d, -2*(e*n+m*d), n*n+m*m-r*r
- D := B*B - 4*A*C
- if D <= 0 {
- return // No intersection or is tangent
- }
- D = math.Sqrt(D)
- t1, t2 := (-B+D)/(2*A), (-B-D)/(2*A)
- p1OnSide := t1 > 0
- p2OnSide := t2 > 0
- switch {
- case p1OnSide && p2OnSide:
- if t2 < t1 { // both on ray, use closest to s2
- t1 = t2
- }
- case p2OnSide: // Only p2 on ray
- t1 = t2
- case p1OnSide: // only p1 on ray
- default: // Neither solution is on the ray
- return
- }
- return (n - e*t1) + cX, (m - d*t1) + cY, true
- }
|