1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 """
23 Contains various geometry related classes and functions
24 """
25
26
27 import math
28 import preferences
29
31 """
32 This may just end up being replaced by a tuple
33 """
35 """
36 Brief Description
37 @type x: int
38 @param x: Placeholder
39 @type y: int
40 @param y: Placeholder
41 """
42 self.x = x
43 self.y = y
44
45
47 """
48 Base class for shapes
49 """
51 self.__center = None
52 self.friction = 1.0
53 self.restitution = 0.0
54 self.damage = 0.0
55
56 - def adjust(self, x, y, i = -1):
57 """
58 Brief Description
59 @type x: int
60 @param x: Placeholder
61 @type y: int
62 @param y: Placeholder
63 @type i: int
64 @param i: index of the point to adjust. -1 adjusts the last point
65 """
66 i = -1
67 raise NameError("Shape::adjust must be overridden in a subclass")
68
69
71 """
72 @rtype: (int, int, int, int)
73 @return: a bounding box int the form x, y, x2, y2 where x2 is the
74 x-coordinate of the right edge, y2 as the bottom edge, and so on.
75 """
76 raise NameError("Shape::boundingBox must be overridden in a subclass")
77
79 """
80 @rtype: Point
81 @return: The center of the shape
82 """
83 return self.__center
84
86 self.__center = center
87
89 """
90 @type p: Point
91 @param p: point to test
92 @rtype: bool
93 @return: True if p intersects the shape, False otherwise
94 """
95 raise NameError("Shape::intersects must be overridden in a subclass")
96
97 - def shift(self, xoffset, yoffset):
98 """
99 Moves the shape around by x and y
100 """
101 raise NameError("Shape::shift must be overridden in a subclass")
102
104 """
105 @rtype: Point[]
106 @return: List of points that define the center of handles for resizing
107 the shape
108 """
109 raise NameError("Shape::getHandles must be overridden in a subclass")
110
111
113
115 """
116 @type points: point[]
117 @param points: the points that make up the polygon
118 """
119 Shape.__init__(self)
120 self.__points = points
121 self.__concave = False
122 if points != None:
123 if len(self.__points) < 3:
124 log.warning("Polygon constructed with fewer than three points."+
125 " This will not end well.")
126 else:
127 self.__concave = self.__calcConcave()
128 self.calcCenter()
129 else:
130 self.__points = [Point(0, 0)]
131
132 points = None
133
135 """
136 Brief Description
137 @type p: Point
138 @param p: the point to add
139 """
140 self.__points.append(p)
141 self.__concave = self.__calcConcave()
142 self.calcCenter()
143
145 """
146 @rtype: Point[]
147 @return: A copy of the polygon's points
148 """
149 return self.__points[:]
150
152 if len(self.__points) > 2:
153 return self.__points[-2]
154 else:
155 return None
156
158 del self.__points[-1]
159
161 """
162 Rearranges the points of the shape so that they are in counter-clockwise
163 order.
164 """
165
166 self.__points.sort(key=lambda p: math.atan2(p.x - self.getCenter().x,
167 p.y - self.getCenter().y), reverse=True)
168
170 """
171 Recalculates the center of the shape. This center is simply the average
172 of the x and y coordinates of all the points
173 """
174 l = len(self.__points)
175 if l == 0:
176 return
177 totalx = 0
178 totaly = 0
179 for p in self.__points:
180 totalx += p.x
181 totaly += p.y
182 center = Point(totalx / float(l), totaly / float(l))
183 self.setCenter(center)
184
186 """
187 @rtype: bool
188 @return: True if the shape is convex
189 """
190 if len(self.__points) <= 2:
191 return False
192 else:
193 return not self.__concave
194
196 """
197 @rtype: bool
198 @return: True if the shape is convex
199 """
200
201 return self.__concave
202
204 if len(self.__points) <= 2:
205
206 return True
207 elif len(self.__points) == 3:
208
209 return False
210
211
212
213 positiveFlag = negativeFlag = False
214 for i in range(len(self.__points)):
215 j = (i + 1) % len(self.__points)
216 k = (i + 2) % len(self.__points)
217 z = ((self.__points[j].x - self.__points[i].x)
218 * (self.__points[k].y - self.__points[j].y))
219 z = z - ((self.__points[j].y - self.__points[i].y)
220 * (self.__points[k].x - self.__points[j].x))
221 if z < 0:
222 positiveFlag = True
223 elif z > 0:
224 negativeFlag = True
225 if positiveFlag and negativeFlag:
226 return True
227 return False
228
229 - def adjust(self, x, y, i = -1):
235
237
238
239 rightVectors = []
240 for i in range(len(self.__points) - 1):
241 dx = self.__points[i + 1].x - self.__points[i].x
242 dy = self.__points[i + 1].y - self.__points[i].y
243 rightVectors.append(Point(dy, -dx))
244
245 dx = self.__points[0].x - self.__points[-1].x
246 dy = self.__points[0].y - self.__points[-1].y
247 rightVectors.append(Point(dy, -dx))
248
249 for i, v in enumerate(rightVectors):
250
251
252 x = (p.x - self.__points[i].x) * v.x
253 y = (p.y - self.__points[i].y) * v.y
254
255
256
257 if (x + y) >= 0:
258 return False
259 return True
260
261 - def shift(self, xoffset, yoffset):
262 for p in self.__points:
263 p.x = p.x + xoffset
264 p.y = p.y + yoffset
265 self.calcCenter()
266
269
270
272
274 Shape.__init__(self)
275 self.__radius = radius
276 self.__snapFactor = snap
277 snap = -1
278
279 - def adjust(self, x, y, i = -1):
280 c = self.getCenter()
281 newRadius = math.sqrt((c.x - x)**2 + (c.y - y)**2)
282 if self.__snapFactor != -1:
283 newRadius = round(newRadius / self.__snapFactor) \
284 * self.__snapFactor
285 if newRadius > 0:
286 self.__radius = newRadius
287
289 self.__snapFactor = snap
290
292 c = self.getCenter()
293 return (c.x - self.__radius, c.x + self.__radius, self.__radius * 2,
294 self.__radius * 2)
295
297 """
298 @rtype: int
299 @return: the radius of the circle
300 """
301 return self.__radius
302
304 c = self.getCenter()
305 dx = p.x - c.x
306 dy = p.y - c.y
307 if ((dx ** 2.0) + (dy ** 2.0)) < (self.__radius ** 2.0):
308 return True
309 else:
310 return False
311
312 - def shift(self, xoffset, yoffset):
315
322
323
325 """
326 @type p: Point
327 @param p: the point to test
328 @type h: Point
329 @param h: the center of the handle
330 @rtype: bool
331 @return: True if the point is inside the handle, False if not
332 """
333 s = preferences.visual["handle_size"]
334 if p.x < h.x - s:
335 return False
336 if p.x > h.x + s:
337 return False
338 if p.y < h.y - s:
339 return False
340 if p.y > h.y + s:
341 return False
342 return True
343
344
346 """
347 Simple sanity check on the intersects code
348 """
349 p = Polygon([
350 Point(1, 1),
351 Point(4, 2),
352 Point(2, 4)
353 ])
354 assert p.intersects(Point(2, 2)) == True
355 assert p.intersects(Point(2, 1)) == False
356
357 p = Polygon([
358 Point(1, 1),
359 Point(3, 1),
360 Point(1, 3),
361 Point(3, 3)
362 ])
363 assert p.intersects(Point(2, 2)) == True
364 assert p.intersects(Point(0, 0)) == False
365
366 if __name__ == "__main__":
367 unittest()
368