123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218 |
- # coding: utf-8
- import typing
- from math import acos
- from math import degrees
- from math import sqrt
-
- from synergine2.exceptions import SynergineException
- from synergine2.share import shared
- from synergine2.simulation import Subject
-
- """
-
- Positions are exprimed as tuple: (x, y, z) Considering start point at top left:
-
- Z
- #
- #
- #
- #-------------> X
- |.
- | .
- | .
- |
- Y
- """
-
- COLLECTION_XYZ = 'COLLECTION_XYZ'
-
- NORTH = 11
- NORTH_EST = 12
- EST = 15
- SOUTH_EST = 18
- SOUTH = 17
- SOUTH_WEST = 16
- WEST = 13
- NORTH_WEST = 10
-
- DIRECTIONS = (
- NORTH,
- NORTH_EST,
- EST,
- SOUTH_EST,
- SOUTH,
- SOUTH_WEST,
- WEST,
- NORTH_WEST,
- )
-
- DIRECTION_FROM_NORTH_DEGREES = {
- (0, 22.5): NORTH,
- (22.5, 67): NORTH_EST,
- (67, 112.5): EST,
- (112.5, 157.5): SOUTH_EST,
- (157.5, 202.5): SOUTH,
- (202.5, 247.5): SOUTH_WEST,
- (247.5, 292.5): WEST,
- (292.5, 337.5): NORTH_WEST,
- (337.5, 360): NORTH,
- (337.5, 0): NORTH
- }
-
- DIRECTION_SLIGHTLY = {
- NORTH: (NORTH_WEST, NORTH, NORTH_EST),
- NORTH_EST: (NORTH, NORTH_EST, EST),
- EST: (NORTH_EST, EST, SOUTH_EST),
- SOUTH_EST: (EST, SOUTH_EST, SOUTH),
- SOUTH: (SOUTH_EST, SOUTH, SOUTH_WEST),
- SOUTH_WEST: (SOUTH, SOUTH_WEST, WEST),
- WEST: (SOUTH_WEST, WEST, NORTH_WEST),
- NORTH_WEST: (WEST, NORTH_WEST, NORTH),
- }
-
- DIRECTION_MODIFIERS = {
- NORTH_WEST: (-1, -1, 0),
- NORTH: (0, -1, 0),
- NORTH_EST: (1, -1, 0),
- WEST: (-1, 0, 0),
- EST: (1, 0, 0),
- SOUTH_WEST: (-1, 1, 0),
- SOUTH: (0, 1, 0),
- SOUTH_EST: (1, 1, 0),
- }
-
-
- class XYZException(SynergineException):
- pass
-
-
- class PositionNotPossible(XYZException):
- pass
-
-
- def get_degree_from_north(a, b):
- if a == b:
- return 0
-
- ax, ay = a[0], a[1]
- bx, by = b[0], b[1]
- Dx, Dy = ax, ay - 1
- ab = sqrt((bx - ax) ** 2 + (by - ay) ** 2)
- aD = sqrt((Dx - ax) ** 2 + (Dy - ay) ** 2)
- Db = sqrt((bx - Dx) ** 2 + (by - Dy) ** 2)
-
- degs = degrees(acos((ab ** 2 + aD ** 2 - Db ** 2) / (2 * ab * aD)))
- if bx < ax:
- return 360 - degs
- return degs
-
-
- class XYZSubjectMixinMetaClass(type):
- def __init__(cls, name, parents, attribs):
- super().__init__(name, parents, attribs)
- collections = getattr(cls, "start_collections", [])
- if COLLECTION_XYZ not in collections:
- collections.append(COLLECTION_XYZ)
-
-
- class XYZSubjectMixin(object, metaclass=XYZSubjectMixinMetaClass):
- position = shared.create_self('position', lambda: (0, 0, 0))
-
- def __init__(self, *args, **kwargs):
- """
- :param position: tuple with (x, y, z)
- """
- position = None
- try:
- position = kwargs.pop('position')
- except KeyError:
- pass
-
- self.previous_direction = None # TODO: shared
- super().__init__(*args, **kwargs)
-
- if position:
- self.position = position
-
-
- class ProximityMixin(object):
- distance = 1
- feel_collections = [COLLECTION_XYZ]
- direction_round_decimals = 0
- distance_round_decimals = 2
-
- def have_to_check_position_is_possible(self) -> bool:
- return True
-
- def get_for_position(
- self,
- position,
- simulation: 'XYZSimulation',
- exclude_subject: Subject=None,
- ):
- subjects = []
- for feel_collection in self.feel_collections:
- # TODO: Optimiser en calculant directement les positions alentours et
- # en regardant si elles sont occupés dans subjects.xyz par un subject
- # etant dans fell_collection
- for subject_id in simulation.collections.get(feel_collection, []):
- subject = simulation.get_or_create_subject(subject_id)
- if subject.id == exclude_subject.id:
- continue
-
- if self.have_to_check_position_is_possible() and not simulation.is_possible_position(subject.position):
- continue
-
- distance = round(
- self.get_distance_of(
- position=position,
- subject=subject,
- ),
- self.distance_round_decimals,
- )
- if distance <= self.distance and self.acceptable_subject(subject):
- direction = round(
- get_degree_from_north(
- position,
- subject.position,
- ),
- self.direction_round_decimals,
- )
- subjects.append({
- 'subject_id': subject.id,
- 'direction': direction,
- 'distance': distance,
- })
-
- return subjects
-
- @classmethod
- def get_distance_of(cls, position, subject: XYZSubjectMixin):
- from synergine2_xyz.utils import get_distance_between_points # cyclic import
- return get_distance_between_points(
- position,
- subject.position,
- )
-
- def acceptable_subject(self, subject: Subject) -> bool:
- return True
-
-
- def get_direction_from_north_degree(degree: float):
- for range, direction in DIRECTION_FROM_NORTH_DEGREES.items():
- if range[0] <= degree <= range[1]:
- return direction
- raise Exception('Degree {} out of range ({})'.format(
- degree,
- DIRECTION_FROM_NORTH_DEGREES,
- ))
-
-
- def get_neighbor_positions(position: typing.Tuple[int, int]) -> typing.List[typing.Tuple[int, int]]:
- neighbors = []
-
- for modifier_x, modifier_y, modifier_z in DIRECTION_MODIFIERS.values():
- neighbors.append((position[0] + modifier_x, position[1] + modifier_y))
-
- return neighbors
|