xyz.py 5.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. # coding: utf-8
  2. from math import sqrt
  3. from math import degrees
  4. from math import acos
  5. from synergine2.simulation import SubjectMechanism, Subjects, Subject
  6. from synergine2.simulation import Simulation as BaseSimulation
  7. """
  8. Positions are exprimed as tuple: (x, y, z) Considering start point at top left:
  9. Z
  10. #
  11. #
  12. #
  13. #-------------> X
  14. |.
  15. | .
  16. | .
  17. |
  18. Y
  19. """
  20. COLLECTION_XYZ = 'COLLECTION_XYZ'
  21. NORTH = 11
  22. NORTH_EST = 12
  23. EST = 15
  24. SOUTH_EST = 18
  25. SOUTH = 17
  26. SOUTH_WEST = 16
  27. WEST = 13
  28. NORTH_WEST = 10
  29. DIRECTIONS = (
  30. NORTH,
  31. NORTH_EST,
  32. EST,
  33. SOUTH_EST,
  34. SOUTH,
  35. SOUTH_WEST,
  36. WEST,
  37. NORTH_WEST,
  38. )
  39. DIRECTION_FROM_NORTH_DEGREES = {
  40. (0, 22.5): NORTH,
  41. (22.5, 67): NORTH_EST,
  42. (67, 112.5): EST,
  43. (112.5, 157.5): SOUTH_EST,
  44. (157.5, 202.5): SOUTH,
  45. (202.5, 247.5): SOUTH_WEST,
  46. (247.5, 292.5): WEST,
  47. (292.5, 337.5): NORTH_WEST,
  48. (337.5, 360): NORTH,
  49. (337.5, 0): NORTH
  50. }
  51. DIRECTION_SLIGHTLY = {
  52. NORTH: (NORTH_WEST, NORTH, NORTH_EST),
  53. NORTH_EST: (NORTH, NORTH_EST, EST),
  54. EST: (NORTH_EST, EST, SOUTH_EST),
  55. SOUTH_EST: (EST, SOUTH_EST, SOUTH),
  56. SOUTH: (SOUTH_EST, SOUTH, SOUTH_WEST),
  57. SOUTH_WEST: (SOUTH, SOUTH_WEST, WEST),
  58. WEST: (SOUTH_WEST, WEST, NORTH_WEST),
  59. NORTH_WEST: (WEST, NORTH_WEST, NORTH),
  60. }
  61. DIRECTION_MODIFIERS = {
  62. NORTH_WEST: (-1, -1, 0),
  63. NORTH: (0, -1, 0),
  64. NORTH_EST: (1, -1, 0),
  65. WEST: (-1, 0, 0),
  66. EST: (1, 0, 0),
  67. SOUTH_WEST: (-1, 1, 0),
  68. SOUTH: (0, 1, 0),
  69. SOUTH_EST: (1, 1, 0),
  70. }
  71. def get_degree_from_north(a, b):
  72. if a == b:
  73. return 0
  74. ax, ay = a[0], a[1]
  75. bx, by = b[0], b[1]
  76. Dx, Dy = ax, ay - 1
  77. ab = sqrt((bx - ax) ** 2 + (by - ay) ** 2)
  78. aD = sqrt((Dx - ax) ** 2 + (Dy - ay) ** 2)
  79. Db = sqrt((bx - Dx) ** 2 + (by - Dy) ** 2)
  80. degs = degrees(acos((ab ** 2 + aD ** 2 - Db ** 2) / (2 * ab * aD)))
  81. if bx < ax:
  82. return 360 - degs
  83. return degs
  84. class XYZSubjectMixinMetaClass(type):
  85. def __init__(cls, name, parents, attribs):
  86. super().__init__(name, parents, attribs)
  87. collections = getattr(cls, "collections", [])
  88. if COLLECTION_XYZ not in collections:
  89. collections.append(COLLECTION_XYZ)
  90. class XYZSubjectMixin(object, metaclass=XYZSubjectMixinMetaClass):
  91. def __init__(self, *args, **kwargs):
  92. """
  93. :param position: tuple with (x, y, z)
  94. """
  95. self._position = kwargs.pop('position')
  96. self.previous_direction = None
  97. super().__init__(*args, **kwargs)
  98. @property
  99. def position(self):
  100. return self._position
  101. @position.setter
  102. def position(self, value):
  103. self._position = value
  104. class ProximityMixin(object):
  105. distance = 1
  106. feel_collections = [COLLECTION_XYZ]
  107. direction_round_decimals = 0
  108. distance_round_decimals = 2
  109. def get_for_position(
  110. self,
  111. position,
  112. simulation: 'Simulation',
  113. exclude_subject: Subject=None,
  114. ):
  115. subjects = []
  116. for feel_collection in self.feel_collections:
  117. # TODO: Optimiser en calculant directement les positions alentours et
  118. # en regardant si elles sont occupés dans subjects.xyz par un subject
  119. # etant dans fell_collection
  120. for subject in simulation.collections.get(feel_collection, []):
  121. if subject == exclude_subject:
  122. continue
  123. distance = round(
  124. self.get_distance_of(
  125. position=position,
  126. subject=subject,
  127. ),
  128. self.distance_round_decimals,
  129. )
  130. if distance <= self.distance and self.acceptable_subject(subject):
  131. direction = round(
  132. get_degree_from_north(
  133. position,
  134. subject.position,
  135. ),
  136. self.direction_round_decimals,
  137. )
  138. subjects.append({
  139. 'subject': subject,
  140. 'direction': direction,
  141. 'distance': distance,
  142. })
  143. return subjects
  144. @classmethod
  145. def get_distance_of(cls, position, subject: XYZSubjectMixin):
  146. from synergine2.xyz_utils import get_distance_between_points # cyclic import
  147. return get_distance_between_points(
  148. position,
  149. subject.position,
  150. )
  151. def acceptable_subject(self, subject: Subject) -> bool:
  152. pass
  153. class ProximitySubjectMechanism(ProximityMixin, SubjectMechanism):
  154. def run(self):
  155. return self.get_for_position(
  156. position=self.subject.position,
  157. simulation=self.simulation,
  158. exclude_subject=self.subject,
  159. )
  160. class XYZSubjects(Subjects):
  161. def __init__(self, *args, **kwargs):
  162. super().__init__(*args, **kwargs)
  163. # TODO: accept multiple subjects as same position
  164. # TODO: init xyz with given list
  165. self.xyz = {}
  166. def remove(self, value: XYZSubjectMixin):
  167. super().remove(value)
  168. del self.xyz[value.position]
  169. def append(self, p_object: XYZSubjectMixin):
  170. super().append(p_object)
  171. self.xyz[p_object.position] = p_object
  172. class Simulation(BaseSimulation):
  173. accepted_subject_class = XYZSubjects
  174. def get_direction_from_north_degree(degree: float):
  175. for range, direction in DIRECTION_FROM_NORTH_DEGREES.items():
  176. if range[0] <= degree <= range[1]:
  177. return direction
  178. raise Exception('Degree {} our of range ({})'.format(
  179. degree,
  180. DIRECTION_FROM_NORTH_DEGREES,
  181. ))