geometry.py 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. #!/usr/bin/env python
  2. #
  3. # This code is part of Grandalf
  4. # Copyright (C) 2008 Axel Tillequin (bdcht3@gmail.com) and others
  5. # published under GPLv2 license or EPLv1 license
  6. # Contributor(s): Axel Tillequin, Fabio Zadrozny
  7. from .poset import *
  8. from .dot import *
  9. from math import atan2, sqrt
  10. from random import SystemRandom
  11. # ------------------------------------------------------------------------------
  12. def intersect2lines(xy1, xy2, xy3, xy4):
  13. (x1, y1) = xy1
  14. (x2, y2) = xy2
  15. (x3, y3) = xy3
  16. (x4, y4) = xy4
  17. b = (x2 - x1, y2 - y1)
  18. d = (x4 - x3, y4 - y3)
  19. det = b[0] * d[1] - b[1] * d[0]
  20. if det == 0:
  21. return None
  22. c = (x3 - x1, y3 - y1)
  23. t = float(c[0] * b[1] - c[1] * b[0]) / (det * 1.0)
  24. if t < 0.0 or t > 1.0:
  25. return None
  26. t = float(c[0] * d[1] - c[1] * d[0]) / (det * 1.0)
  27. if t < 0.0 or t > 1.0:
  28. return None
  29. x = x1 + t * b[0]
  30. y = y1 + t * b[1]
  31. return (x, y)
  32. # ------------------------------------------------------------------------------
  33. # intersectR returns the intersection point between the Rectangle
  34. # (w,h) that characterize the view object and the line that goes
  35. # from the views' object center to the 'topt' point.
  36. def intersectR(view, topt):
  37. # we compute intersection in local views' coord:
  38. # center of view is obviously :
  39. x1, y1 = 0, 0
  40. # endpoint in view's coord:
  41. x2, y2 = topt[0] - view.xy[0], topt[1] - view.xy[1]
  42. # bounding box:
  43. bbx2 = view.w // 2
  44. bbx1 = -bbx2
  45. bby2 = view.h // 2
  46. bby1 = -bby2
  47. # all 4 segments of the bb:
  48. S = [
  49. ((x1, y1), (x2, y2), (bbx1, bby1), (bbx2, bby1)),
  50. ((x1, y1), (x2, y2), (bbx2, bby1), (bbx2, bby2)),
  51. ((x1, y1), (x2, y2), (bbx1, bby2), (bbx2, bby2)),
  52. ((x1, y1), (x2, y2), (bbx1, bby2), (bbx1, bby1)),
  53. ]
  54. # check intersection with each seg:
  55. for segs in S:
  56. xy = intersect2lines(*segs)
  57. if xy != None:
  58. x, y = xy
  59. # return global coord:
  60. x += view.xy[0]
  61. y += view.xy[1]
  62. return (x, y)
  63. # there can't be no intersection unless the endpoint was
  64. # inside the bb !
  65. raise ValueError(
  66. "no intersection found (point inside ?!). view: %s topt: %s" % (view, topt)
  67. )
  68. # ------------------------------------------------------------------------------
  69. def getangle(p1, p2):
  70. x1, y1 = p1
  71. x2, y2 = p2
  72. theta = atan2(y2 - y1, x2 - x1)
  73. return theta
  74. # ------------------------------------------------------------------------------
  75. def median_wh(views):
  76. mw = [v.w for v in views]
  77. mh = [v.h for v in views]
  78. mw.sort()
  79. mh.sort()
  80. return (mw[len(mw) // 2], mh[len(mh) // 2])
  81. # ------------------------------------------------------------------------------
  82. try:
  83. from numpy import array, matrix, cos, sin
  84. has_numpy = True
  85. except ImportError:
  86. has_numpy = False
  87. from math import cos, sin, pi
  88. from .linalg import array, matrix
  89. # rand_ortho1 returns a numpy.array representing
  90. # a random normalized n-dimension vector orthogonal to (1,1,1,...,1).
  91. def rand_ortho1(n):
  92. r = SystemRandom()
  93. pos = [r.random() for x in range(n)]
  94. s = sum(pos)
  95. v = array(pos, dtype=float) - (s / float(n))
  96. norm = sqrt(sum(v * v))
  97. return v / norm
  98. # ------------------------------------------------------------------------------
  99. # intersectC returns the intersection point between the Circle
  100. # of radius r and centered on views' position with the line
  101. # to the 'topt' point.
  102. def intersectC(view, r, topt):
  103. theta = getangle(view.xy, topt)
  104. x = int(cos(theta) * r)
  105. y = int(sin(theta) * r)
  106. return (x, y)
  107. # ------------------------------------------------------------------------------
  108. # setcurve returns the spline curve that path through the list of points P.
  109. # The spline curve is a list of cubic bezier curves (nurbs) that have
  110. # matching tangents at their extreme points.
  111. # The method considered here is taken from "The NURBS book" (Les A. Piegl,
  112. # Wayne Tiller, Springer, 1997) and implements a local interpolation rather
  113. # than a global interpolation.
  114. def setcurve(e, pts, tgs=None):
  115. P = list(map(array, pts))
  116. n = len(P)
  117. # tangent estimation
  118. if tgs:
  119. assert len(tgs) == n
  120. T = list(map(array, tgs))
  121. Q = [P[k + 1] - P[k] for k in range(0, n - 1)]
  122. else:
  123. Q, T = tangents(P, n)
  124. splines = []
  125. for k in range(n - 1):
  126. t = T[k] + T[k + 1]
  127. a = 16.0 - (t.dot(t))
  128. b = 12.0 * (Q[k].dot(t))
  129. c = -36.0 * Q[k].dot(Q[k])
  130. D = (b * b) - 4.0 * a * c
  131. assert D >= 0
  132. sd = sqrt(D)
  133. s1, s2 = (-b - sd) / (2.0 * a), (-b + sd) / (2.0 * a)
  134. s = s2
  135. if s1 >= 0:
  136. s = s1
  137. C0 = tuple(P[k])
  138. C1 = tuple(P[k] + (s / 3.0) * T[k])
  139. C2 = tuple(P[k + 1] - (s / 3.0) * T[k + 1])
  140. C3 = tuple(P[k + 1])
  141. splines.append([C0, C1, C2, C3])
  142. return splines
  143. # ------------------------------------------------------------------------------
  144. def tangents(P, n):
  145. assert n >= 2
  146. Q = []
  147. T = []
  148. for k in range(0, n - 1):
  149. q = P[k + 1] - P[k]
  150. t = q / sqrt(q.dot(q))
  151. Q.append(q)
  152. T.append(t)
  153. T.append(t)
  154. return (Q, T)
  155. # ------------------------------------------------------------------------------
  156. def setroundcorner(e, pts):
  157. P = list(map(array, pts))
  158. n = len(P)
  159. Q, T = tangents(P, n)
  160. c0 = P[0]
  161. t0 = T[0]
  162. k0 = 0
  163. splines = []
  164. k = 1
  165. while k < n:
  166. z = abs(t0[0] * T[k][1] - (t0[1] * T[k][0]))
  167. if z < 1.0e-6:
  168. k += 1
  169. continue
  170. if (k - 1) > k0:
  171. splines.append([c0, P[k - 1]])
  172. if (k + 1) < n:
  173. splines.extend(setcurve(e, [P[k - 1], P[k + 1]], tgs=[T[k - 1], T[k + 1]]))
  174. else:
  175. splines.extend(setcurve(e, [P[k - 1], P[k]], tgs=[T[k - 1], T[k]]))
  176. break
  177. if (k + 2) < n:
  178. c0 = P[k + 1]
  179. t0 = T[k + 1]
  180. k0 = k + 1
  181. k += 2
  182. else:
  183. break
  184. return splines or [[P[0], P[-1]]]
  185. # ------------------------------------------------------------------------------
  186. def new_point_at_distance(pt, distance, angle):
  187. # angle in radians
  188. distance = float(distance)
  189. x, y = pt[0], pt[1]
  190. x += distance * cos(angle)
  191. y += distance * sin(angle)
  192. return x, y