geometry.py 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  1. from __future__ import division
  2. from math import atan2, pi
  3. import time
  4. class Positionable(object):
  5. """
  6. Parent class for any object with a position.
  7. """
  8. def __init__(self, x=None, y=None):
  9. self.x = x
  10. self.y = y
  11. def __str__(self):
  12. return '<%s at (%s, %s)>' % (self.__class__.__name__, self.x, self.y)
  13. def set_position(self, x, y):
  14. self.x = x
  15. self.y = y
  16. def get_position(self):
  17. return self.x, self.y
  18. @property
  19. def xy(self):
  20. """
  21. Shortcut getter for (x, y) position.
  22. """
  23. return self.x, self.y
  24. def distance_to(self, positionable):
  25. """
  26. Calculate the Pythagorian distance from this positionable to another.
  27. """
  28. x, y = positionable.get_position()
  29. return ((x - self.x) ** 2 + (y - self.y) ** 2) ** .5
  30. class MovingPositionable(Positionable):
  31. """
  32. Parent class for positionable objects that need movement calculations. For
  33. these calculations, the previous position is also saved.
  34. """
  35. def __init__(self, x=None, y=None):
  36. super(MovingPositionable, self).__init__(x, y)
  37. self.prev = Positionable(x, y)
  38. def set_position(self, x, y):
  39. """
  40. Set a new position and save the current position as the precious
  41. position. If no previous position has been set, set is to the new
  42. position so that the movement is zero.
  43. """
  44. if self.x is None or self.y is None:
  45. self.prev.set_position(x, y)
  46. else:
  47. self.prev.set_position(self.x, self.y)
  48. Positionable.set_position(self, x, y)
  49. def get_previous_position(self):
  50. return self.prev
  51. def rotation_around(self, center):
  52. """
  53. Calculate rotation of this positionable relative to a center
  54. positionable.
  55. """
  56. cx, cy = center.get_position()
  57. px, py = self.prev.get_position()
  58. prev_angle = atan2(px - cx, py - cy)
  59. current_angle = atan2(self.x - cx, self.y - cy)
  60. rotation = current_angle - prev_angle
  61. if rotation >= pi:
  62. return 2 * pi - rotation
  63. if rotation <= -pi:
  64. return -2 * pi - rotation
  65. return rotation
  66. def translation(self):
  67. """
  68. Calculate the movement relative to the last position as a vector
  69. positionable.
  70. """
  71. px, py = self.prev.get_position()
  72. return Positionable(self.x - px, self.y - py)
  73. def movement_distance(self):
  74. return self.distance_to(self.prev)
  75. class AcceleratedPositionable(MovingPositionable):
  76. """
  77. Parent class for positionable objects that need acceleration calculations.
  78. For these calculations, timestamps are saved for both the current and
  79. previous position.
  80. """
  81. def __init__(self, x=None, y=None):
  82. super(AcceleratedPositionable, self).__init__(x, y)
  83. self.prev_timestamp = self.current_timestamp = None
  84. def set_position(self, x, y):
  85. MovingPositionable.set_position(self, x, y)
  86. if self.current_timestamp is None:
  87. self.prev_timestamp = self.current_timestamp = time.time()
  88. return
  89. self.prev_timestamp = self.current_timestamp
  90. self.current_timestamp = time.time()
  91. def movement_time(self):
  92. return self.timestamp - self.prev_timestamp
  93. def acceleration(self):
  94. """
  95. Calculate the acceleration in pixels/second.
  96. """
  97. return self.movement_distance() / self.movement_time()
  98. class Surface(Positionable):
  99. """
  100. Interface class for surfaces with a position. Defines a function
  101. 'contains', which calculates whether a position is located in the surface.
  102. """
  103. def contains(self, point):
  104. raise NotImplemented
  105. class RectangularSurface(Surface):
  106. """
  107. Rectangle, represented by a left-top position (x, y) and a size (width,
  108. height).
  109. """
  110. def __init__(self, x, y, width, height):
  111. super(RectangularSurface, self).__init__(x, y)
  112. self.set_size(width, height)
  113. def __str__(self):
  114. return '<%s at (%s, %s) size=(%s, %s)>' \
  115. % (self.__class__.__name__, self.x, self.y, self.width,
  116. self.height)
  117. def set_size(self, width, height):
  118. self.width = width
  119. self.height = height
  120. def get_size(self):
  121. return self.width, self.height
  122. def contains(self, point):
  123. x, y = point.get_position()
  124. return self.x <= x <= self.x + self.width \
  125. and self.y <= y <= self.y + self.height
  126. class CircularSurface(Surface):
  127. """
  128. Circle, represented by a center position (x, y) and a radius.
  129. """
  130. def __init__(self, x, y, radius):
  131. super(CircularSurface, self).__init__(x, y)
  132. self.set_radius(radius)
  133. def __str__(self):
  134. return '<%s at (%s, %s) size=(%s, %s)>' \
  135. % (self.__class__.__name__, self.x, self.y, self.width,
  136. self.height)
  137. def set_radius(self, radius):
  138. self.radius = radius
  139. def get_radius(self):
  140. return self.radius
  141. def contains(self, point):
  142. return self.distance_to(point) <= self.radius