Skip to content

Commit 06ae7bf

Browse files
authored
Merge pull request #58 from Tjstretchalot/master
Add SAT collision detection
2 parents 04c254e + b521d96 commit 06ae7bf

File tree

9 files changed

+2787
-6
lines changed

9 files changed

+2787
-6
lines changed

docs/Geometry.rst

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
========
2+
Geometry
3+
========
4+
5+
Some geometrical shapes and operations
6+
7+
Quick Start Guide
8+
-----------------
9+
10+
.. code-block:: python
11+
12+
# import the required shapes and structures
13+
from pygorithm.geometry import polygon2
14+
from pygorithm.geometry import vector2
15+
16+
# create a regular polygon
17+
poly1 = polygon2.Polygon2.from_regular(5, 5)
18+
19+
# create a polygon from tuple (x, y) - note that the polygon must be convex
20+
# and the points must be clockwise
21+
poly2 = polygon2.Polygon2(points=[ (0, 0), (1, 0), (1, 1), (0, 1) ])
22+
23+
# create a polygon from vector2s.
24+
poly3 = polygon2.Polygon2(points=[ vector2.Vector2(0, 0),
25+
vector2.Vector2(1, 1),
26+
vector2.Vector2(2, 0) ])
27+
28+
# create a polygon by rotating another polygon
29+
poly4 = poly3.rotate(0.2)
30+
poly5 = poly3.rotate(degrees = 30)
31+
32+
33+
# check intersection
34+
intrs, mtv = polygon2.Polygon2.find_intersection(poly1, poly2, (0, 0), (1, 0))
35+
36+
if intrs:
37+
mtv_dist = mtv[0]
38+
mtv_vec = mtv[1]
39+
print('They intersect. The best way to push poly1 is {} units along {}'.format(mtv_dist, mtv_vec))
40+
else:
41+
print('No intersection')
42+
43+
Features
44+
--------
45+
46+
* Structures available:
47+
- Vector2 (vector2)
48+
- Line2 (line2)
49+
- AxisAlignedLine (axisall)
50+
51+
* Shapes available:
52+
- Concave Polygons (polygon2)
53+
54+
* Algorithms available:
55+
- Separating Axis Theorem (polygon2)
56+
57+
Vector2
58+
-------
59+
60+
.. autoclass:: pygorithm.geometry.vector2.Vector2
61+
:members:
62+
:special-members:
63+
64+
Line2
65+
-----
66+
67+
.. autoclass:: pygorithm.geometry.line2.Line2
68+
:members:
69+
:special-members:
70+
71+
Axis-Aligned Line
72+
-----------------
73+
74+
.. autoclass:: pygorithm.geometry.axisall.AxisAlignedLine
75+
:members:
76+
:special-members:
77+
78+
Concave Polygon
79+
---------------
80+
81+
.. autoclass:: pygorithm.geometry.polygon2.Polygon2
82+
:members:
83+
:special-members:
84+
85+
86+
87+
88+
89+
90+
91+

docs/conf.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@
4242
u'Omkar Pathak', 'manual'),
4343
]
4444

45+
# Auto-Doc options
46+
autodoc_member_order = 'bysource' # alternatively 'alphabetical' (default) or 'groupwise'
4547

4648
# -- Options for manual page output --------------------------------------------
4749

docs/index.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,14 @@ Quick Links
2525
Data_Structure
2626
DynamicP
2727
Fibonacci
28+
Geometry
2829
Greedy
2930
Math
3031
Pathfinding
3132
Searching
3233
Sorting
3334
strings
3435

35-
3636
Quick Start Guide
3737
-----------------
3838

pygorithm/geometry/__init__.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,16 @@
11
"""
2-
Collection of special geometry functions
2+
Collection of geometry examples
33
"""
4+
from . import vector2
5+
from . import axisall
6+
from . import line2
7+
from . import polygon2
48
from . import rect_broad_phase
59

610
__all__ = [
11+
'vector2',
12+
'axisall',
13+
'line2',
14+
'polygon2',
715
'rect_broad_phase'
8-
]
16+
]

pygorithm/geometry/axisall.py

Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
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

Comments
 (0)