Package horizons :: Package util :: Package shapes :: Module rect
[hide private]
[frames] | no frames]

Source Code for Module horizons.util.shapes.rect

  1  # ################################################### 
  2  # Copyright (C) 2008-2017 The Unknown Horizons Team 
  3  # team@unknown-horizons.org 
  4  # This file is part of Unknown Horizons. 
  5  # 
  6  # Unknown Horizons is free software; you can redistribute it and/or modify 
  7  # it under the terms of the GNU General Public License as published by 
  8  # the Free Software Foundation; either version 2 of the License, or 
  9  # (at your option) any later version. 
 10  # 
 11  # This program is distributed in the hope that it will be useful, 
 12  # but WITHOUT ANY WARRANTY; without even the implied warranty of 
 13  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
 14  # GNU General Public License for more details. 
 15  # 
 16  # You should have received a copy of the GNU General Public License 
 17  # along with this program; if not, write to the 
 18  # Free Software Foundation, Inc., 
 19  # 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA 
 20  # ################################################### 
 21   
 22  from horizons.util.python import Const 
 23   
 24  from . import Shape 
 25  from .point import Point 
26 27 28 -class Rect(Shape):
29 __slots__ = ('top', 'left', 'right', 'bottom', 'origin') 30
31 - def __init__(self, *args):
32 if len(args) == 2 and isinstance(args[0], Point) and isinstance(args[1], Point): #args: edge1, edge2 33 self.top = min(args[0].y, args[1].y) 34 self.left = min(args[0].x, args[1].x) 35 self.right = max(args[0].x, args[1].x) 36 self.bottom = max(args[0].y, args[1].y) 37 elif len(args) == 3 and isinstance(args[0], Point) and isinstance(args[1], int) and isinstance(args[2], int): #args: position, width, height 38 self.top = args[0].y 39 self.left = args[0].x 40 self.right = self.left + args[1] 41 self.bottom = self.top + args[2] 42 elif len(args) == 4 and isinstance(args[0], int) and isinstance(args[1], int) and isinstance(args[2], int) and isinstance(args[3], int): 43 self.top = min(args[1], args[3]) 44 self.left = min(args[0], args[2]) 45 self.right = max(args[0], args[2]) 46 self.bottom = max(args[1], args[3]) 47 48 else: 49 assert False 50 51 # Convenience attributes (can be used to make code more easy to read/understand) 52 self.origin = Point(self.left, self.top)
53 54 # NAMED CONSTRUCTORS: 55 56 @classmethod
57 - def init_from_borders(cls, left, top, right, bottom):
58 self = cls.__new__(cls) 59 self.left = left 60 self.top = top 61 self.right = right 62 self.bottom = bottom 63 self.origin = Point(self.left, self.top) 64 return self
65 66 @classmethod
67 - def init_from_topleft_and_size(cls, x, y, width, height):
68 self = cls.__new__(cls) 69 self.left = x 70 self.top = y 71 self.right = x + width - 1 72 self.bottom = y + height - 1 73 self.origin = Point(self.left, self.top) 74 return self
75 76 @classmethod
77 - def init_from_topleft_and_size_tuples(cls, coords, size):
78 self = cls.__new__(cls) 79 self.left = coords[0] 80 self.top = coords[1] 81 self.right = coords[0] + size[0] - 1 82 self.bottom = coords[1] + size[1] - 1 83 self.origin = Point(self.left, self.top) 84 return self
85 86 @classmethod
87 - def init_from_corners(cls, point1, point2):
88 """Init rect with 2 points interpreted as 2 corner points""" 89 self = cls.__new__(cls) 90 x_coords = [int(round(point1.x)), int(round(point2.x))] 91 x_coords.sort() 92 self.left = x_coords[0] 93 self.right = x_coords[1] 94 y_coords = [int(round(point1.y)), int(round(point2.y))] 95 y_coords.sort() 96 self.top = y_coords[0] 97 self.bottom = y_coords[1] 98 self.origin = Point(self.left, self.top) 99 return self
100 101 @property
102 - def height(self):
103 return self.bottom - self.top + 1
104 105 @property
106 - def width(self):
107 return self.right - self.left + 1
108
109 - def copy(self):
110 return Rect.init_from_borders(self.left, self.top, self.right, self.bottom)
111
112 - def get_radius_coordinates(self, radius, include_self=False):
113 """Returns list of all coordinates (as tuples), that are in the radius 114 This is a generator. 115 @param include_self: whether to include coords in self""" 116 # NOTE: this function has to be very fast, since it's blocking on building select 117 # therefore, the distance_to_tuple function is inlined manually. 118 """ 119 ALGORITHM: 120 Idea: 121 calculate the borders of the shape for every line (y-axis) to the left and the right 122 and fill it up later. 123 The borders are calculated this way: 124 Take a corner (here we use top right) and calculate a quarter of a circle (top right quarter). 125 This can be mirrored to every other corner. 126 Then there is only the space exactly above, below and left and right to the rect left. 127 Here, since we only got along one axis, we know that the border coords are right + radius, etc. 128 q.e.d. ;) 129 """ 130 borders = {} 131 132 # start with special case 133 134 # above, below 135 borders[self.top - radius] = (self.left, self.right) 136 borders[self.bottom + radius] = (self.left, self.right) 137 138 # left, right 139 for y in range(self.top, self.bottom + 1): 140 borders[y] = (self.left - radius, self.right + radius) 141 142 x = radius 143 radius_squared = radius ** 2 144 # calculate border for line y (y = 0 and y = radius are special cases handled above) 145 for y in range(1, radius): 146 test_val = radius_squared - y ** 2 147 # TODO: check if it's possible if x is decreased more than once here. 148 # if not, change the while to an if 149 while (x ** 2) > test_val: # this is equivalent to x^2 + y^2 > radius^2 150 x -= 1 151 152 # both sides are symmetrical, since it's a rect 153 borders[self.top - y] = (self.left - x, self.right + x) 154 borders[self.bottom + y] = (self.left - x, self.right + x) 155 156 if not include_self: 157 self_coords = frozenset(self.get_coordinates()) 158 for y, x_range in borders.items(): 159 if self.top <= y <= self.bottom: # we have to sort out the self_coords here 160 for x in range(x_range[0], x_range[1] + 1): 161 t = (x, y) 162 if t not in self_coords: 163 yield t 164 else: # coords of this rect cannot appear here 165 for x in range(x_range[0], x_range[1] + 1): 166 yield (x, y) 167 else: 168 for y, x_range in borders.items(): 169 for x in range(x_range[0], x_range[1] + 1): 170 yield (x, y)
171 172 @property
173 - def center(self):
174 """Returns the center point of the rect. 175 Implemented with integer division, which means the upper left is preferred.""" 176 return Point((self.right + self.left) // 2, (self.bottom + self.top) // 2)
177
178 - def __contains__(self, point):
179 return self.contains(point)
180
181 - def contains(self, point):
182 """ Returns if this rect (self) contains the point. 183 @param point: Point that is checked to be in this rect 184 @return: Returns whether the Point point is in this rect (self). 185 """ 186 return (self.left <= point.x <= self.right) and (self.top <= point.y <= self.bottom)
187
188 - def contains_without_border(self, point):
189 """Same as contains, see iter_without_border for difference""" 190 return (self.left <= point.x < self.right) and (self.top <= point.y < self.bottom)
191
192 - def contains_tuple(self, tup):
193 """Same as contains, but takes a tuple (x, y) as parameter (overloaded function)""" 194 return (self.left <= tup[0] <= self.right) and (self.top <= tup[1] <= self.bottom)
195
196 - def intersect(self, rect):
197 """ Returns a rect that is the intersection of this rect and the rect parameter. 198 @param rect: Rect that will be intersected with this rect. 199 @return: A Rect which is the intersection of self and rect or None if the intersection is empty. 200 """ 201 if not self.intersects(rect): 202 return None 203 return Rect(max(self.left, rect.left), max(self.top, rect.top), 204 min(self.right, rect.right), min(self.bottom, rect.bottom))
205
206 - def intersects(self, rect):
207 """ Returns if the rectangle intersects with the rect parameter. 208 @param rect: Rect that will be intersected with this rect. 209 @return: A bool. 210 """ 211 return not (rect.right < self.left or self.right < rect.left 212 or rect.bottom < self.top or self.bottom < rect.top)
213
214 - def get_corners(self):
215 """Returns corners of rect in this order: topleft topright bottomright bottomleft 216 @return: tuple of coord-tuples""" 217 return ((self.left, self.top), (self.right, self.top), 218 (self.right, self.bottom), (self.left, self.bottom))
219
220 - def get_surrounding(self, include_corners=True):
221 """Returns neighboring coords of the rect. 222 @param include_corners: whether to also move diagonally from the rect corners""" 223 # top and bottom 224 surrounding_top = self.top - 1 225 surrounding_bottom = self.bottom + 1 226 for x in range(self.left, self.right + 1): 227 yield (x, surrounding_bottom) 228 yield (x, surrounding_top) 229 # left and right 230 surrounding_left = self.left - 1 231 surrounding_right = self.right + 1 232 for y in range(self.top, self.bottom + 1): 233 yield (surrounding_left, y) 234 yield (surrounding_right, y) 235 236 if include_corners: 237 yield (self.left - 1, self.top - 1) 238 yield (self.right + 1, self.top - 1) 239 yield (self.left - 1, self.bottom + 1) 240 yield (self.right + 1, self.bottom + 1)
241
242 - def __str__(self):
243 return "Rect(o:({},{}),w:{},h:{})".format(self.left, self.top, self.width, self.height)
244
245 - def __eq__(self, other):
246 if not isinstance(other, Rect): 247 return False 248 return (self.top == other.top and self.left == other.left 249 and self.right == other.right and self.bottom == other.bottom)
250
251 - def __ne__(self, other):
252 return not self.__eq__(other)
253
254 - def __lt__(self, other):
255 if self.left != other.left: 256 return self.left < other.left 257 if self.top != other.top: 258 return self.top < other.top 259 if self.right != other.right: 260 return self.right < other.right 261 return self.bottom < other.bottom
262
263 - def __hash__(self):
264 return hash((self.top, self.right, self.bottom, self.left))
265
266 - def tuple_iter(self):
267 """Generates an iterator, that returns tuples""" 268 for x in range(self.left, self.right + 1): 269 for y in range(self.top, self.bottom + 1): 270 yield x, y
271
272 - def iter_without_border(self):
273 """There are 2 possible interpretations about what *width* means. 274 You can either include the last point in the area calculation or 275 just consider points without any extensions. 276 This method iterates over the points without extensions, while the 277 default iteration behavior in other methods is to include said area. 278 """ 279 for x in range(self.left, self.right): 280 for y in range(self.top, self.bottom): 281 yield Point(x, y)
282 283 @classmethod
284 - def get_surrounding_offsets(cls, size):
285 rect = cls.init_from_topleft_and_size_tuples((0, 0), size) 286 return list(rect.get_surrounding())
287
288 289 -class ConstRect(Const, Rect):
290 """An immutable Rect. 291 Can be used for manual const-only optimization""" 292 pass
293