geomx.go 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306
  1. // geomx adds some geometry functions needed by rasterx
  2. // Copyright 2017 by the rasterx Authors. All rights reserved.
  3. // Created: 2/12/2017 by S.R.Wiley
  4. package rasterx
  5. import (
  6. "fmt"
  7. "math"
  8. "golang.org/x/image/math/fixed"
  9. )
  10. // Invert returns the point inverted around the origin
  11. func Invert(v fixed.Point26_6) fixed.Point26_6 {
  12. return fixed.Point26_6{X: -v.X, Y: -v.Y}
  13. }
  14. // turnStarboard90 returns the vector 90 degrees starboard (right in direction heading)
  15. func turnStarboard90(v fixed.Point26_6) fixed.Point26_6 {
  16. return fixed.Point26_6{X: -v.Y, Y: v.X}
  17. }
  18. // turnPort90 returns the vector 90 degrees port (left in direction heading)
  19. func turnPort90(v fixed.Point26_6) fixed.Point26_6 {
  20. return fixed.Point26_6{X: v.Y, Y: -v.X}
  21. }
  22. // DotProd returns the inner product of p and q
  23. func DotProd(p fixed.Point26_6, q fixed.Point26_6) fixed.Int52_12 {
  24. return fixed.Int52_12(int64(p.X)*int64(q.X) + int64(p.Y)*int64(q.Y))
  25. }
  26. // Length is the distance from the origin of the point
  27. func Length(v fixed.Point26_6) fixed.Int26_6 {
  28. vx, vy := float64(v.X), float64(v.Y)
  29. return fixed.Int26_6(math.Sqrt(vx*vx + vy*vy))
  30. }
  31. //PathCommand is the type for the path command token
  32. type PathCommand fixed.Int26_6
  33. // Human readable path constants
  34. const (
  35. PathMoveTo PathCommand = iota
  36. PathLineTo
  37. PathQuadTo
  38. PathCubicTo
  39. PathClose
  40. )
  41. // A Path starts with a PathCommand value followed by zero to three fixed
  42. // int points.
  43. type Path []fixed.Int26_6
  44. // ToSVGPath returns a string representation of the path
  45. func (p Path) ToSVGPath() string {
  46. s := ""
  47. for i := 0; i < len(p); {
  48. if i != 0 {
  49. s += " "
  50. }
  51. switch PathCommand(p[i]) {
  52. case PathMoveTo:
  53. s += fmt.Sprintf("M%4.3f,%4.3f", float32(p[i+1])/64, float32(p[i+2])/64)
  54. i += 3
  55. case PathLineTo:
  56. s += fmt.Sprintf("L%4.3f,%4.3f", float32(p[i+1])/64, float32(p[i+2])/64)
  57. i += 3
  58. case PathQuadTo:
  59. s += fmt.Sprintf("Q%4.3f,%4.3f,%4.3f,%4.3f", float32(p[i+1])/64, float32(p[i+2])/64,
  60. float32(p[i+3])/64, float32(p[i+4])/64)
  61. i += 5
  62. case PathCubicTo:
  63. 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,
  64. float32(p[i+3])/64, float32(p[i+4])/64, float32(p[i+5])/64, float32(p[i+6])/64)
  65. i += 7
  66. case PathClose:
  67. s += "Z"
  68. i++
  69. default:
  70. panic("freetype/rasterx: bad pather")
  71. }
  72. }
  73. return s
  74. }
  75. // String returns a readable representation of a Path.
  76. func (p Path) String() string {
  77. return p.ToSVGPath()
  78. }
  79. // Clear zeros the path slice
  80. func (p *Path) Clear() {
  81. *p = (*p)[:0]
  82. }
  83. // Start starts a new curve at the given point.
  84. func (p *Path) Start(a fixed.Point26_6) {
  85. *p = append(*p, fixed.Int26_6(PathMoveTo), a.X, a.Y)
  86. }
  87. // Line adds a linear segment to the current curve.
  88. func (p *Path) Line(b fixed.Point26_6) {
  89. *p = append(*p, fixed.Int26_6(PathLineTo), b.X, b.Y)
  90. }
  91. // QuadBezier adds a quadratic segment to the current curve.
  92. func (p *Path) QuadBezier(b, c fixed.Point26_6) {
  93. *p = append(*p, fixed.Int26_6(PathQuadTo), b.X, b.Y, c.X, c.Y)
  94. }
  95. // CubeBezier adds a cubic segment to the current curve.
  96. func (p *Path) CubeBezier(b, c, d fixed.Point26_6) {
  97. *p = append(*p, fixed.Int26_6(PathCubicTo), b.X, b.Y, c.X, c.Y, d.X, d.Y)
  98. }
  99. // Stop joins the ends of the path
  100. func (p *Path) Stop(closeLoop bool) {
  101. if closeLoop {
  102. *p = append(*p, fixed.Int26_6(PathClose))
  103. }
  104. }
  105. // AddTo adds the Path p to q.
  106. func (p Path) AddTo(q Adder) {
  107. for i := 0; i < len(p); {
  108. switch PathCommand(p[i]) {
  109. case PathMoveTo:
  110. q.Stop(false) // Fixes issues #1 by described by Djadala; implicit close if currently in path.
  111. q.Start(fixed.Point26_6{X: p[i+1], Y: p[i+2]})
  112. i += 3
  113. case PathLineTo:
  114. q.Line(fixed.Point26_6{X: p[i+1], Y: p[i+2]})
  115. i += 3
  116. case PathQuadTo:
  117. q.QuadBezier(fixed.Point26_6{X: p[i+1], Y: p[i+2]}, fixed.Point26_6{X: p[i+3], Y: p[i+4]})
  118. i += 5
  119. case PathCubicTo:
  120. q.CubeBezier(fixed.Point26_6{X: p[i+1], Y: p[i+2]},
  121. fixed.Point26_6{X: p[i+3], Y: p[i+4]}, fixed.Point26_6{X: p[i+5], Y: p[i+6]})
  122. i += 7
  123. case PathClose:
  124. q.Stop(true)
  125. i++
  126. default:
  127. panic("AddTo: bad path")
  128. }
  129. }
  130. q.Stop(false)
  131. }
  132. // ToLength scales the point to the length indicated by ln
  133. func ToLength(p fixed.Point26_6, ln fixed.Int26_6) (q fixed.Point26_6) {
  134. if ln == 0 || (p.X == 0 && p.Y == 0) {
  135. return
  136. }
  137. pX, pY := float64(p.X), float64(p.Y)
  138. lnF := float64(ln)
  139. pLen := math.Sqrt(pX*pX + pY*pY)
  140. qX, qY := pX*lnF/pLen, pY*lnF/pLen
  141. q.X, q.Y = fixed.Int26_6(qX), fixed.Int26_6(qY)
  142. return
  143. }
  144. // ClosestPortside returns the closest of p1 or p2 on the port side of the
  145. // line from the bow to the stern. (port means left side of the direction you are heading)
  146. // isIntersecting is just convienice to reduce code, and if false returns false, because p1 and p2 are not valid
  147. func ClosestPortside(bow, stern, p1, p2 fixed.Point26_6, isIntersecting bool) (xt fixed.Point26_6, intersects bool) {
  148. if isIntersecting == false {
  149. return
  150. }
  151. dir := bow.Sub(stern)
  152. dp1 := p1.Sub(stern)
  153. dp2 := p2.Sub(stern)
  154. cp1 := dir.X*dp1.Y - dp1.X*dir.Y
  155. cp2 := dir.X*dp2.Y - dp2.X*dir.Y
  156. switch {
  157. case cp1 < 0 && cp2 < 0:
  158. return
  159. case cp1 < 0 && cp2 >= 0:
  160. return p2, true
  161. case cp1 >= 0 && cp2 < 0:
  162. return p1, true
  163. default: // both points on port side
  164. dirdot := DotProd(dir, dir)
  165. // calculate vector rejections of dp1 and dp2 onto dir
  166. h1 := dp1.Sub(dir.Mul(fixed.Int26_6((DotProd(dp1, dir) << 6) / dirdot)))
  167. h2 := dp2.Sub(dir.Mul(fixed.Int26_6((DotProd(dp2, dir) << 6) / dirdot)))
  168. // return point with smallest vector rejection; i.e. closest to dir line
  169. if (h1.X*h1.X + h1.Y*h1.Y) > (h2.X*h2.X + h2.Y*h2.Y) {
  170. return p2, true
  171. }
  172. return p1, true
  173. }
  174. }
  175. // RadCurvature returns the curvature of a Bezier curve end point,
  176. // given an end point, the two adjacent control points and the degree.
  177. // The sign of the value indicates if the center of the osculating circle
  178. // is left or right (port or starboard) of the curve in the forward direction.
  179. func RadCurvature(p0, p1, p2 fixed.Point26_6, dm fixed.Int52_12) fixed.Int26_6 {
  180. a, b := p2.Sub(p1), p1.Sub(p0)
  181. abdot, bbdot := DotProd(a, b), DotProd(b, b)
  182. h := a.Sub(b.Mul(fixed.Int26_6((abdot << 6) / bbdot))) // h is the vector rejection of a onto b
  183. if h.X == 0 && h.Y == 0 { // points are co-linear
  184. return 0
  185. }
  186. radCurve := fixed.Int26_6((fixed.Int52_12(a.X*a.X+a.Y*a.Y) * dm / fixed.Int52_12(Length(h)<<6)) >> 6)
  187. if a.X*b.Y > b.X*a.Y { // xprod sign
  188. return radCurve
  189. }
  190. return -radCurve
  191. }
  192. // CircleCircleIntersection calculates the points of intersection of
  193. // two circles or returns with intersects == false if no such points exist.
  194. func CircleCircleIntersection(ct, cl fixed.Point26_6, rt, rl fixed.Int26_6) (xt1, xt2 fixed.Point26_6, intersects bool) {
  195. dc := cl.Sub(ct)
  196. d := Length(dc)
  197. // Check for solvability.
  198. if d > (rt + rl) {
  199. return // No solution. Circles do not intersect.
  200. }
  201. // check if d < abs(rt-rl)
  202. if da := rt - rl; (da > 0 && d < da) || (da < 0 && d < -da) {
  203. return // No solution. One circle is contained by the other.
  204. }
  205. rlf, rtf, df := float64(rl), float64(rt), float64(d)
  206. af := (rtf*rtf - rlf*rlf + df*df) / df / 2.0
  207. hfd := math.Sqrt(rtf*rtf-af*af) / df
  208. afd := af / df
  209. rOffx, rOffy := float64(-dc.Y)*hfd, float64(dc.X)*hfd
  210. p2x := float64(ct.X) + float64(dc.X)*afd
  211. p2y := float64(ct.Y) + float64(dc.Y)*afd
  212. xt1x, xt1y := p2x+rOffx, p2y+rOffy
  213. xt2x, xt2y := p2x-rOffx, p2y-rOffy
  214. return fixed.Point26_6{X: fixed.Int26_6(xt1x), Y: fixed.Int26_6(xt1y)},
  215. fixed.Point26_6{X: fixed.Int26_6(xt2x), Y: fixed.Int26_6(xt2y)}, true
  216. }
  217. // CalcIntersect calculates the points of intersection of two fixed point lines
  218. // and panics if the determinate is zero. You have been warned.
  219. func CalcIntersect(a1, a2, b1, b2 fixed.Point26_6) (x fixed.Point26_6) {
  220. da, db, ds := a2.Sub(a1), b2.Sub(b1), a1.Sub(b1)
  221. det := float32(da.X*db.Y - db.X*da.Y) // Determinate
  222. t := float32(ds.Y*db.X-ds.X*db.Y) / det
  223. x = a1.Add(fixed.Point26_6{X: fixed.Int26_6(float32(da.X) * t), Y: fixed.Int26_6(float32(da.Y) * t)})
  224. return
  225. }
  226. // RayCircleIntersection calculates the points of intersection of
  227. // a ray starting at s2 passing through s1 and a circle in fixed point.
  228. // Returns intersects == false if no solution is possible. If two
  229. // solutions are possible, the point closest to s2 is returned
  230. func RayCircleIntersection(s1, s2, c fixed.Point26_6, r fixed.Int26_6) (x fixed.Point26_6, intersects bool) {
  231. fx, fy, intersects := RayCircleIntersectionF(float64(s1.X), float64(s1.Y),
  232. float64(s2.X), float64(s2.Y), float64(c.X), float64(c.Y), float64(r))
  233. return fixed.Point26_6{X: fixed.Int26_6(fx),
  234. Y: fixed.Int26_6(fy)}, intersects
  235. }
  236. // RayCircleIntersectionF calculates in floating point the points of intersection of
  237. // a ray starting at s2 passing through s1 and a circle in fixed point.
  238. // Returns intersects == false if no solution is possible. If two
  239. // solutions are possible, the point closest to s2 is returned
  240. func RayCircleIntersectionF(s1X, s1Y, s2X, s2Y, cX, cY, r float64) (x, y float64, intersects bool) {
  241. n := s2X - cX // Calculating using 64* rather than divide
  242. m := s2Y - cY
  243. e := s2X - s1X
  244. d := s2Y - s1Y
  245. // Quadratic normal form coefficients
  246. A, B, C := e*e+d*d, -2*(e*n+m*d), n*n+m*m-r*r
  247. D := B*B - 4*A*C
  248. if D <= 0 {
  249. return // No intersection or is tangent
  250. }
  251. D = math.Sqrt(D)
  252. t1, t2 := (-B+D)/(2*A), (-B-D)/(2*A)
  253. p1OnSide := t1 > 0
  254. p2OnSide := t2 > 0
  255. switch {
  256. case p1OnSide && p2OnSide:
  257. if t2 < t1 { // both on ray, use closest to s2
  258. t1 = t2
  259. }
  260. case p2OnSide: // Only p2 on ray
  261. t1 = t2
  262. case p1OnSide: // only p1 on ray
  263. default: // Neither solution is on the ray
  264. return
  265. }
  266. return (n - e*t1) + cX, (m - d*t1) + cY, true
  267. }