1
+ """
2
+ axisall
3
+
4
+ Author: Timothy Moore
5
+
6
+ Defines a class for handling axis-aligned two-dimensional lines
7
+ segments. This class simplifies intermediary calculations in
8
+ SAT and similiar algorithms.
9
+
10
+ These are 2dimensional axis-aligned objects
11
+ https://en.wikipedia.org/wiki/Axis-aligned_object
12
+ """
13
+
14
+ import math
15
+
16
+ class AxisAlignedLine (object ):
17
+ """
18
+ Define an axis aligned line.
19
+
20
+ This class provides functions related to axis aligned lines as well as
21
+ acting as a convienent container for them. In this context, an axis
22
+ aligned line is a two-dimensional line that is defined by an axis and
23
+ length on that axis, rather than two points. When working with two lines
24
+ defined as such that have the same axis, many calculations are
25
+ simplified.
26
+
27
+ .. note::
28
+
29
+ Though it requires the same amount of memory as a simple representation of
30
+ a 2 dimensional line (4 numerics), it cannot describe all types of lines.
31
+ All lines that can be defined this way intersect (0, 0).
32
+
33
+ .. note::
34
+
35
+ `min` and `max` are referring to nearness to negative and positive infinity,
36
+ respectively. The absolute value of `min` may be larger than that of `max`.
37
+
38
+ .. note::
39
+
40
+ AxisAlignedLines are an intermediary operation, so offsets should be baked
41
+ into them.
42
+
43
+ :ivar axis: the axis this line is on
44
+ :vartype axis: :class:`pygorithm.geometry.vector2.Vector2`
45
+ :ivar min: the point closest to negative infinity
46
+ :vartype min: :class:`numbers.Number`
47
+ :ivar max: the point closest to positive infinity
48
+ :vartype max: :class:`numbers.Number`
49
+ """
50
+
51
+ def __init__ (self , axis , point1 , point2 ):
52
+ """
53
+ Construct an axis aligned line with the appropriate min and max.
54
+
55
+ :param axis: axis this line is on (for bookkeeping only, may be None)
56
+ :type axis: :class:`pygorithm.geometry.vector2.Vector2`
57
+ :param point1: one point on this line
58
+ :type point1: :class:`numbers.Number`
59
+ :param point2: a different point on this line
60
+ :type point2: :class:`numbers.Number`
61
+ """
62
+
63
+ self .axis = axis
64
+ self .min = min (point1 , point2 )
65
+ self .max = max (point1 , point2 )
66
+
67
+ @staticmethod
68
+ def intersects (line1 , line2 ):
69
+ """
70
+ Determine if the two lines intersect
71
+
72
+ Determine if the two lines are touching, if they are overlapping, or if
73
+ they are disjoint. Lines are touching if they share only one end point,
74
+ whereas they are overlapping if they share infinitely many points.
75
+
76
+ .. note::
77
+
78
+ It is rarely faster to check intersection before finding intersection if
79
+ you will need the minimum translation vector, since they do mostly
80
+ the same operations.
81
+
82
+ .. tip::
83
+
84
+ This will never return ``True, True``
85
+
86
+ :param line1: the first line
87
+ :type line1: :class:`pygorithm.geometry.axisall.AxisAlignedLine`
88
+ :param line2: the second line
89
+ :type line2: :class:`pygorithm.geometry.axisall.AxisAlignedLine`
90
+ :returns: (touching, overlapping)
91
+ :rtype: (bool, bool)
92
+ """
93
+
94
+ if math .isclose (line1 .max , line2 .min ):
95
+ return True , False
96
+ elif math .isclose (line1 .min , line2 .max ):
97
+ return True , False
98
+ elif line1 .max < line2 .min :
99
+ return False , False
100
+ elif line1 .min > line2 .max :
101
+ return False , False
102
+
103
+ return False , True
104
+
105
+ @staticmethod
106
+ def find_intersection (line1 , line2 ):
107
+ """
108
+ Calculate the MTV between line1 and line2 to move line1
109
+
110
+ Determine if the two lines are touching and/or overlapping and then
111
+ returns the minimum translation vector to move line 1 along axis. If the
112
+ result is negative, it means line 1 should be moved in the opposite
113
+ direction of the axis by the magnitude of the result.
114
+
115
+
116
+ Returns `true, (None, touch_point_numeric, touch_point_numeric)` if the lines are touching
117
+ and not overlapping.
118
+
119
+ .. note::
120
+
121
+ Ensure your program correctly handles `true, (None, numeric, numeric)`
122
+
123
+
124
+ :param line1: the first line
125
+ :type line1: :class:`pygorithm.geometry.axisall.AxisAlignedLine`
126
+ :param line2: the second line
127
+ :type line2: :class:`pygorithm.geometry.axisall.AxisAlignedLine`
128
+ :returns: (touching, (mtv against 1, intersection min, intersection max))
129
+ :rtype: (bool, (:class:`numbers.Number` or None, :class:`numbers.Number`, :class:`numbers.Number`) or None)
130
+ """
131
+
132
+ if math .isclose (line1 .max , line2 .min ):
133
+ return True , (None , line2 .min , line2 .min )
134
+ elif math .isclose (line1 .min , line2 .max ):
135
+ return True , (None , line1 .min , line1 .min )
136
+ elif line1 .max < line2 .min or line2 .max < line1 .min :
137
+ return False , None
138
+ else :
139
+ opt_1 = line2 .min - line1 .max
140
+ opt_2 = line2 .max - line1 .min
141
+
142
+ res_min = max (line1 .min , line2 .min )
143
+ res_max = min (line1 .max , line2 .max )
144
+
145
+ if abs (opt_1 ) < abs (opt_2 ):
146
+ return True , (opt_1 , res_min , res_max )
147
+ else :
148
+ return True , (opt_2 , res_min , res_max )
149
+
150
+ @staticmethod
151
+ def contains_point (line , point ):
152
+ """
153
+ Determine if the line contains the specified point.
154
+
155
+ The point must be defined the same way as min and max.
156
+
157
+ .. tip::
158
+
159
+ It is not possible for both returned booleans to be `True`.
160
+
161
+ :param line: the line
162
+ :type line: :class:`pygorithm.geometry.axisall.AxisAlignedLine`
163
+ :param point: the point
164
+ :type point: :class:`numbers.Number`
165
+ :returns: (if the point is an edge of the line, if the point is contained by the line)
166
+ :rtype: (bool, bool)
167
+ """
168
+
169
+ if math .isclose (line .min , point ) or math .isclose (line .max , point ):
170
+ return True , False
171
+ elif point < line .min or point > line .max :
172
+ return False , False
173
+ else :
174
+ return False , True
175
+
176
+ def __repr__ (self ):
177
+ """
178
+ Create an unambiguous representation of this axis aligned
179
+ line.
180
+
181
+ Example:
182
+
183
+ .. code-block:: python
184
+
185
+ from pygorithm.geometry import axisall
186
+
187
+ aal = axisall.AxisAlignedLine(None, 3, 5)
188
+
189
+ # prints AxisAlignedLine(axis=None, min=3, max=5)
190
+ print(repr(aal))
191
+
192
+ :returns: un-ambiguous representation of this line
193
+ :rtype: string
194
+ """
195
+
196
+ return "AxisAlignedLine(axis={}, min={}, max={})" .format (repr (self .axis ), self .min , self .max )
197
+
198
+ def __str__ (self ):
199
+ """
200
+ Create a human-readable representation of this axis aligned line.
201
+
202
+ Example:
203
+
204
+ .. code-block:: python
205
+
206
+ from pygorithm.geometry import axisall
207
+
208
+ aal = axisall.AxisAlignedLine(None, 0.7071234, 0.7071234)
209
+
210
+ # prints axisall(along None from 0.707 to 0.707)
211
+ print(aal)
212
+
213
+ :returns: human-readable representation of this line
214
+ :rtype: string
215
+ """
216
+
217
+ pretty_min = round (self .min * 1000 ) / 1000
218
+ if pretty_min == math .floor (pretty_min ):
219
+ pretty_min = math .floor (pretty_min )
220
+
221
+ pretty_max = round (self .max * 1000 ) / 1000
222
+ if pretty_max == math .floor (pretty_max ):
223
+ pretty_max = math .floor (pretty_max )
224
+
225
+ return "axisall(along {} from {} to {})" .format (str (self .axis ), pretty_min , pretty_max )
0 commit comments