forked from PascalLeMerrer/Bug-Arena
-
Notifications
You must be signed in to change notification settings - Fork 1
/
cshape.py
177 lines (154 loc) · 6.52 KB
/
cshape.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
import math
from cocos.euclid import Vector2
from point import Point
class OrientableRectShape(object):
"""
Implements the Cshape interface that uses rectangles with a possible rotation.
Distance is not the euclidean distance but the rectangular or max-min
distance, max( min(x0 - x1), min(y0 - y1) : (xi, yi) in recti )
Good if actors rotate.
Look at Cshape for other class and methods documentation.
"""
def __init__(self, center, half_width, half_height, angle):
"""
:Parameters:
`center` : euclid.Vector2
rectangle center
`half_width` : float
half width of rectangle
`half_height` : float
half height of rectangle
'angle' : float
orientation of the rectangle, in degrees
"""
self.half_width = half_width
self.half_height = half_height
self.center = center
self.update_position()
self.rotate(angle)
def update_position(self):
self.unrotated_A = Point(self.center.x - self.half_width, self.center.y + self.half_height)
self.unrotated_B = Point(self.center.x + self.half_width, self.center.y + self.half_height)
self.unrotated_C = Point(self.center.x + self.half_width, self.center.y - self.half_height)
self.unrotated_D = Point(self.center.x - self.half_width, self.center.y - self.half_height)
def move_by(self, dx, dy):
''' moves the shape
dx distance in pixels along horizontal axis
dy distance in pixels along vertical axis
'''
self.unrotated_A.x += dx
self.unrotated_B.x += dx
self.unrotated_C.x += dx
self.unrotated_D.x += dx
self.unrotated_A.y += dy
self.unrotated_B.y += dy
self.unrotated_C.y += dy
self.unrotated_D.y += dy
self.center.x += dx
self.center.y += dy
self.A.x += dx
self.B.x += dx
self.C.x += dx
self.D.x += dx
self.A.y += dy
self.B.y += dy
self.C.y += dy
self.D.y += dy
def rotate(self, angle):
"""
:Parameters:
'angle': float
the new rotation of the shape, in degrees
"""
self.angle = angle
rad = math.radians(angle)
self.A = self.unrotated_A.rotate_about(self.center, rad)
self.B = self.unrotated_B.rotate_about(self.center, rad)
self.C = self.unrotated_C.rotate_about(self.center, rad)
self.D = self.unrotated_D.rotate_about(self.center, rad)
def _get_triangle_area(self, A,B,C):
"""
:Parameters:
'A' : Point
'B' : Point
'C' : Point
A, B, C must be in clockwise order
:rtype: float
the double of the area of the ABC triangle
"""
return (C.x*B.y-B.x*C.y)-(C.x*A.y-A.x*C.y)+(B.x*A.y-A.x*B.y)
def _get_square_distance(self, p1, p2):
"""
:Parameters:
'p1' : Point - first point
'p2' : Point - second point
:rtype: float
the square of the distance between p1 and p2
"""
return (p1.x - p2.x)**2 + (p1.y - p2.y)**2
def overlaps(self, other):
if self == other:
return False
return (self.touches(other.center)
or self.touches(other.A)
or self.touches(other.B)
or self.touches(other.C)
or self.touches(other.D)
or other.touches(self.center)
or other.touches(self.A)
or other.touches(self.B)
or other.touches(self.C)
or other.touches(self.D))
def distance(self, other):
"""
TODO: find some optimization
"""
square_distance = min(self._get_square_distance(self.A, other.A),
self._get_square_distance(self.A, other.B),
self._get_square_distance(self.A, other.C),
self._get_square_distance(self.A, other.D),
self._get_square_distance(self.B, other.A),
self._get_square_distance(self.B, other.B),
self._get_square_distance(self.B, other.C),
self._get_square_distance(self.B, other.D),
self._get_square_distance(self.C, other.A),
self._get_square_distance(self.C, other.B),
self._get_square_distance(self.C, other.C),
self._get_square_distance(self.C, other.D),
self._get_square_distance(self.D, other.A),
self._get_square_distance(self.D, other.B),
self._get_square_distance(self.D, other.C),
self._get_square_distance(self.D, other.D))
if square_distance < 0.0:
result = 0.0
else:
result = math.sqrt(square_distance)
return result
def near_than(self, other, near_distance):
return self.distance(other) <= near_distance
def touches_point(self, x, y):
P = Point(x, y)
return self.touches(P)
def touches(self, P):
return (self._get_triangle_area(self.A, self.B, P) > 0
and self._get_triangle_area(self.B, self.C, P) > 0
and self._get_triangle_area(self.C, self.D, P) > 0
and self._get_triangle_area(self.D, self.A, P) > 0)
def fits_in_box(self, packed_box):
minmax = self.minmax()
return (packed_box[0] <= minmax[0]
and packed_box[1] >= minmax[1]
and packed_box[2] <= minmax[2]
and packed_box[3] >= minmax[3])
def minmax(self):
return (min(self.A.x, self.B.x, self.C.x, self.D.x),
max(self.A.x, self.B.x, self.C.x, self.D.x),
min(self.A.y, self.B.y, self.C.y, self.D.y),
max(self.A.y, self.B.y, self.C.y, self.D.y))
def copy(self):
return OrientableRectShape(Vector2(self.center.x, self.center.y),
self.half_width,
self.half_height,
self.angle)
def __repr__(self):
return self.A.__repr__() + " " + self.B.__repr__() + " " + self.C.__repr__() + " " + self.D.__repr__()