-
Notifications
You must be signed in to change notification settings - Fork 0
/
Rotated_Rect.py
298 lines (257 loc) · 12 KB
/
Rotated_Rect.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
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
import pygame
from math_utils import distance
import math
import base
from Circle import Circle
# start of Rotated_Rect class
class Rotated_Rect(pygame.Rect):
# make a metaclass which wraps a lot of functions just like move is defined below.
# functions to be wrapped - move,inflate
# Think about them - clamp,clip,union,fit,contains,collide
# the metaclass can probably also change the __str__ of the class.
# About drawing - use pygame.transform.rotate to get a rotated surface to
# be drawed.
def __init__(self, leftTop_or_center, w_h, angle, centered_coords=False, color=None):
''' Angle in degrees.
leftTop_or_center, w_h -> both are 2-element tuples or list
centered_coords -> whether the (left,top) is actually intended to be (center_x,center_y) or not.
I.e. if centered_coords==True the (left,top) co-ordinates are used as (center_x,center_y) co-ordinates of the Rotated_Rect'''
self.angle = angle
self.color = color
try:
left = leftTop_or_center[0]
top = leftTop_or_center[1]
except:
raise AttributeError(
"leftTop_or_center must be of format (left,top)")
try:
width = w_h[0]
height = w_h[1]
except:
raise AttributeError("w_h must be of format (width,height)")
if centered_coords:
left = left - width / 2
top = top - height / 2
super(Rotated_Rect, self).__init__((left, top), (width, height))
def __copy__(self):
return Rotated_Rect((self.left, self.top), (self.width, self.height), self.angle)
def __repr__(self):
return "{0.__class__.__name__}((top={0.top}, left={0.left}), (width={0.width}, height={0.height}), angle={0.angle}, color={0.color})".format(self)
@classmethod
def create_from_rect(cls, another_rect, angle):
return cls((another_rect.left, another_rect.top), (another_rect.width, another_rect.height), angle)
def get_rect(self):
return pygame.Rect((self.left, self.top), (self.width, self.height))
# most in_place versions retained from super-class
def move(self, x, y):
return self.__copy__().move_ip(x, y)
def inflate(self, x, y):
return self.__copy__().inflate_ip(x, y)
def normalize(self):
# not sure what this one does - ask others
return self.__copy__().normalize_ip()
def rotate_ip(self, some_angle):
self.angle += some_angle
def rotate(self, some_angle):
return self.__copy__().rotate_ip(some_angle)
# generalise the parameters to accept x1,y1,x2,y2,rot_angle
def rotate_point_about_center(self, x, y, theta=None):
''' x,y -> Absolute co-ords of the point to be rotated wrt to the rectangles center.
self.centerx,self.centery are internally substracted from them.
theta -> angle (in degrees) by which the point is to be rotated. Default value is self.angle.
Returns absolute positions of the points after rotation. '''
if theta is None:
theta = self.angle
return self.rotate_point(x, y, self.centerx, self.centery, theta)
@staticmethod
# to be allowed as rotate_point(class)
def rotate_point(x, y, origin_x, origin_y, theta):
''' x,y -> Absolute co-ords of the point to be rotated. origin_x,origin_y are internally substracted from them.
(origin_x,origin_y) -> Absolute co-ords of the origin wrt to which the point will be rotated
theta -> angle (in degrees) by which the point is to be rotated.
Returns absolute positions of the points after rotation. '''
import math
x_diff = x - origin_x
y_diff = y - origin_y
# -y_diff because of the inverted nature of y co-ordinate system in pygame
y_diff = -y_diff
r = math.sqrt(x_diff ** 2 + y_diff ** 2)
initial_angle = math.atan2(y_diff, x_diff)
final_angle = initial_angle + math.radians(theta)
rotated_x_diff = r * math.cos(final_angle)
rotated_y_diff = r * math.sin(final_angle)
rotated_y_diff = -rotated_y_diff # - used for same reason as above
rotated_x = rotated_x_diff + origin_x
rotated_y = rotated_y_diff + origin_y
return (rotated_x, rotated_y)
def rotate_point_polar(self, r, initial_angle, theta=None):
''' theta and initial_angle in radians.
initial_angle ->angle r makes with horizontal.
theta -> angle to be rotated by. '''
if theta is None:
theta = self.angle
final_angle = initial_angle + theta
rotated_x = r * math.cos(final_angle)
rotated_y = r * math.sin(final_angle)
return (rotated_x, rotated_y)
@property
def bottomleft_rotated(self):
temp = self.bottomleft
return self.rotate_point_about_center(temp[0], temp[1])
@property
def topleft_rotated(self):
temp = self.topleft
return self.rotate_point_about_center(temp[0], temp[1])
@property
def bottomright_rotated(self):
temp = self.bottomright
return self.rotate_point_about_center(temp[0], temp[1])
@property
def topright_rotated(self):
temp = self.topright
return self.rotate_point_about_center(temp[0], temp[1])
@property
def midleft_rotated(self):
temp = self.midleft
return self.rotate_point_about_center(temp[0], temp[1])
@property
def midright_rotated(self):
temp = self.midright
return self.rotate_point_about_center(temp[0], temp[1])
@property
def midtop_rotated(self):
temp = self.midtop
return self.rotate_point_about_center(temp[0], temp[1])
@property
def midbottom_rotated(self):
temp = self.midbottom
return self.rotate_point_about_center(temp[0], temp[1])
# related to bounding box
@property
def bbox(self):
list_x = [self.topleft_rotated[0], self.topright_rotated[0],
self.bottomleft_rotated[0], self.bottomright_rotated[0]]
list_y = [self.topleft_rotated[1], self.topright_rotated[1],
self.bottomleft_rotated[1], self.bottomright_rotated[1]]
min_x = math.floor(min(list_x))
max_x = math.ceil(max(list_x))
min_y = math.floor(min(list_y))
max_y = math.ceil(max(list_y))
return pygame.Rect((min_x, min_y), (max_x - min_x, max_y - min_y))
# corners
@property
def corners(self):
''' List of actual (rotated) corners. '''
return [self.topleft_rotated, self.topright_rotated, self.bottomright_rotated, self.bottomleft_rotated]
@property
def corners_relative(self):
return [(temp[0] - self.centerx, temp[1] - self.centery) for temp in self.corners]
# circles related to the rect
def get_circumcircle(self):
Circle.get_circumcircle_from_rect(self.bbox)
def get_incircle(self):
# get_rect used as incircle doesnt depend upon theta, same for rot and
# non-rot rect
Circle.get_incircle_from_rect(self.get_rect())
# collision events
def collide_point(self, *args):
''' collidepoint(x,y) ->
collidepoint((x,y)) -> '''
temp_len = len(args)
temp_x = None
temp_y = None
if temp_len == 2:
temp_x = args[0]
temp_y = args[1]
elif temp_len == 1:
if len(args[0]) == 2:
temp_x, temp_y = args[0]
else:
raise TypeError("Arguments are of incorrect type")
else:
raise TypeError("Arguments are of incorrect type")
if temp_x is None or temp_y is None:
raise TypeError("argument must contain two numbers")
return self.get_rect().collidepoint(self.rotate_point_about_center(temp_x, temp_y, -self.angle))
def collide_rect(self, rect):
''' Tests if it collides with rect, which should be instance of rect class. '''
# First test:whether bbox collides with rect. If not, no collision.
if not self.bbox.colliderect(rect):
return False
else:
# Todo: Else sure test: Need to do line class first. Test if any line
# of titedRect collides with any line of (instead, the whole) rect.
pass
# draw functions
def blit(self, surface, (x, y)=(None, None), color=None, clip_rect=None, width=1, use_antialiasing=False, blend=True):
''' Draws this Circle on surface. It must be of width and height greater than or equal to its bbox.
(x,y)->co-ordinates whether the center of the Rotated_Rect should be drawn on the surface.
width argument doesnt work with antialiasing. width=0 makes it filled.
antialiasing may break in later versions as the backend pygame.gfxdraw is experimental.
color falls back first to Circle.color (if present) and then to blue color.'''
if not ((x, y) == (None, None)):
sprite_to_draw = self.__copy__()
sprite_to_draw.center = (x, y)
else:
sprite_to_draw = self
# pygame.Rect does this, which is used in clear_rect
x, y = int(x), int(y)
if not color:
if self.color:
color = self.color
else:
color = pygame.Color("blue")
if clip_rect:
# Set clip region
surface.set_clip(clip_rect)
iter_corners = [(temp[0] + x, temp[1] + y)
for temp in sprite_to_draw.corners_relative]
if not use_antialiasing:
pygame.draw.polygon(surface, color, iter_corners, width)
else:
pygame.draw.aalines(surface, color, True, iter_corners, blend)
if clip_rect:
# Remove clip region
surface.set_clip(None)
def draw(self):
tmp_dirty_sprite = {
"sprite": self,
"shape": "Rotated_Rect",
"bbox": self.bbox,
"config": {
"leftTop_or_center": (self.left, self.top),
"w_h": (self.w, self.h),
"angle": self.angle,
"color": self.color
}
}
for tmp_old_sprite in self.container_obj.list_rendered_sprite:
# if any of them is eqv. to new dirty sprite, mark old one as
# still_valid
if tmp_old_sprite == tmp_dirty_sprite:
# puts "still_valid" entry in the old_dirty_sprite_dict
tmp_old_sprite["still_valid"] = True
break
else:
# i.e. if no match found, list it as dirty sprite
self.container_obj.list_dirty_sprite.append(tmp_dirty_sprite)
def get_surface(self, surface=None, flags=0):
''' Returns a new surface with dimensions of (Rotated_Rect.bbox.width,Rotated_Rect.bbox.width) and other properties as surface.
This Rotated_Rect can be safely drawn on it.'''
if surface:
return pygame.Surface((self.bbox.width, self.bbox.height), flags, surface)
else:
return pygame.Surface((self.bbox.width, self.bbox.height), flags)
def get_surface_drawn(self, (x, y)=(None, None), color=None, width=1, use_antialiasing=False, blend=True, surface=None, flags=0):
''' Returns a surface which has the Rotated_Rect drwan on it. '''
temp_surf = self.get_surface(surface, flags)
self.draw(temp_surf, (x, y), color, width, use_antialiasing, blend)
return temp_surf
def collide_Rotated_Rect(self, another_Rotated_Rect):
'''Checks whether this Rotated_Rect collides with another_titltedrect, which must be a instance of titledRect. '''
# Rotates both Rotated_Rect by -another_Rotated_Rect.angle. So, another_Rotated_Rect becomes a Rect called temp_Rect.
# Then check if the rotated version of this Rotated_Rect
# (temp_Rotated_Rect) collides with the obtained Rect.
temp_Rotated_Rect = self.rotate(-another_Rotated_Rect.angle)
temp_Rect = another_Rotated_Rect.get_rect()
return temp_Rotated_Rect.colliderect(temp_Rect)