geometry.py 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  1. """
  2. This module has basic utilities for working with wx.Rects
  3. and Lines (which are tuples of wx.Points).
  4. """
  5. import math, wx
  6. def clipLineByRects(line, *rects):
  7. """
  8. Clips a line (e.g. an array of wx.Points) so it does
  9. not overlap any of the rects passed. The line must be
  10. the first parameter, but you may pass any number of rects.
  11. """
  12. result = line
  13. for rect in rects:
  14. for i in range(2):
  15. if rect.Contains(result[i]):
  16. intersection = lineRectIntersection(result, rect, excludeTrivial = True)
  17. if intersection:
  18. result[i] = intersection
  19. break
  20. return result
  21. def endPointProjectedFrom(line, angle, distance):
  22. """
  23. Projects an endpoint from the second wx.Point of a line at
  24. a given angle and distance. The angle should be given in radians.
  25. """
  26. length = lineLength(line)
  27. if length == 0: return line[1]
  28. # taken from http://mathforum.org/library/drmath/view/54146.html
  29. lengthRatio = distance / length
  30. x = line[1][0] - ((line[1][0] - line[0][0]) * math.cos(angle) - \
  31. (line[1][1] - line[0][1]) * math.sin(angle)) * lengthRatio
  32. y = line[1][1] - ((line[1][1] - line[0][1]) * math.cos(angle) + \
  33. (line[1][0] - line[0][0]) * math.sin(angle)) * lengthRatio
  34. return wx.Point(x, y)
  35. def pointsToRect(p1, p2):
  36. """
  37. Returns the smallest wx.Rect that encloses two points.
  38. """
  39. left = min(p1[0], p2[0])
  40. right = max(p1[0], p2[0])
  41. top = min(p1[1], p2[1])
  42. bottom = max(p1[1], p2[1])
  43. rect = wx.Rect(0, 0, 0, 0)
  44. rect.SetTopLeft((left, top))
  45. rect.SetBottomRight((right, bottom))
  46. return rect
  47. def rectToLines(rect):
  48. """
  49. Converts a wx.Rect into an array of lines
  50. (e.g. tuples of wx.Points)
  51. """
  52. topLeft = rect.GetTopLeft()
  53. topRight = rect.GetTopRight()
  54. bottomLeft = rect.GetBottomLeft()
  55. bottomRight = rect.GetBottomRight()
  56. return (topLeft, topRight), (topLeft, bottomLeft), (topRight, bottomRight), \
  57. (bottomLeft, bottomRight)
  58. def lineLength(line):
  59. """
  60. Returns the length of a line.
  61. """
  62. return math.sqrt((line[1][0] - line[0][0]) ** 2 + (line[1][1] - line[0][1]) ** 2)
  63. def lineRectIntersection(line, rect, excludeTrivial = False):
  64. """
  65. Returns a x,y pair corresponding to where a line and a
  66. wx.Rect intersect. If they do not intersect, then None
  67. is returned. This returns the first intersection it happens
  68. to find, not all of them.
  69. By default, it will immediately return an endpoint if one of
  70. them is inside the rectangle. The excludeTrivial prevents
  71. this behavior.
  72. """
  73. if not excludeTrivial:
  74. for i in range(2):
  75. if rect.Contains(line[i]):
  76. return line[i]
  77. # See Cohen-Sutherland Line-Clipping Algorithm
  78. # pylint: disable=invalid-name
  79. LEFT = 0b0001
  80. RIGHT = 0b0010
  81. BOTTOM = 0b0100
  82. TOP = 0b1000
  83. xmin, ymin = rect.GetTopLeft()
  84. xmax, ymax = rect.GetBottomRight()
  85. def computeCode(x, y):
  86. code = 0
  87. if x < xmin:
  88. code |= LEFT
  89. elif x > xmax:
  90. code |= RIGHT
  91. if y < ymin:
  92. code |= BOTTOM
  93. elif y > ymax:
  94. code |= TOP
  95. return code
  96. x0, y0 = line[0]
  97. x1, y1 = line[1]
  98. codeStart = computeCode(x0, y0)
  99. codeEnd = computeCode(x1, y1)
  100. if (codeStart & codeEnd) != 0:
  101. return None
  102. x, y = 0, 0
  103. while True:
  104. if (codeStart | codeEnd) == 0:
  105. return x, y
  106. elif not (codeStart & codeEnd) == 0:
  107. return None
  108. else:
  109. outsideCode = max(codeStart, codeEnd)
  110. # Checks for trivial cases with horizontal and vertical lines.
  111. if x1 == x0:
  112. if outsideCode & TOP != 0:
  113. return x1, ymax
  114. else:
  115. return x1, ymin
  116. if y1 == y0:
  117. if outsideCode & LEFT != 0:
  118. return xmin, y1
  119. else:
  120. return xmax, y1
  121. if outsideCode & TOP != 0:
  122. x, y = x0 + (x1 - x0) * (ymax - y0) / (y1 - y0), ymax
  123. elif outsideCode & BOTTOM != 0:
  124. x, y = x0 + (x1 - x0) * (ymin - y0) / (y1 - y0), ymin
  125. elif outsideCode & LEFT != 0:
  126. x, y = xmin, y0 + (y1 - y0) * (xmin - x0) / (x1 - x0)
  127. elif outsideCode & RIGHT != 0:
  128. x, y = xmax, y0 + (y1 - y0) * (xmax - x0) / (x1 - x0)
  129. if outsideCode == codeStart:
  130. x0, y0 = x, y
  131. codeStart = computeCode(x0, y0)
  132. else:
  133. x1, y1 = x, y
  134. codeEnd = computeCode(x1, y1)
  135. def lineIntersection(line1, line2):
  136. """
  137. Returns a wx.Point corresponding to where two line
  138. segments intersect. If they do not intersect, or they are parallel, then None
  139. is returned.
  140. """
  141. ax1,ay1,ax2,ay2 = line1[0][0],line1[0][1],line1[1][0],line1[1][1]
  142. bx1,by1,bx2,by2 = line2[0][0],line2[0][1],line2[1][0],line2[1][1]
  143. s1x = ax2-ax1
  144. s1y = ay2-ay1
  145. s2x = bx2-bx1
  146. s2y = by2-by1
  147. denominator = float(-s2x * s1y + s1x * s2y)
  148. if denominator == 0:
  149. #Collinear or Parallel returns none as in original
  150. return None
  151. s = (-s1y * (ax1 - bx1) + s1x * (ay1 - by1))
  152. if not 0 <= s <= denominator: return None
  153. t = ( s2x * (ay1 - by1) - s2y * (ax1 - bx1))
  154. if not 0 <= t <= denominator: return None
  155. t /= denominator
  156. ix = ax1 + (t * s1x)
  157. iy = ay1 + (t * s1y)
  158. return wx.Point(ix, iy)