Mercurial > parpg-source
comparison bGrease/geometry.py @ 41:ff3e395abf91
Renamed grease to bGrease (Basic Grease) to get rid of conflicts with an already installed grease.
author | KarstenBock@gmx.net |
---|---|
date | Mon, 05 Sep 2011 15:00:34 +0200 |
parents | grease/geometry.py@bc88f7d5ca8b |
children | a6bbb732b27b |
comparison
equal
deleted
inserted
replaced
40:2e3ab06a2f47 | 41:ff3e395abf91 |
---|---|
1 __version__ = "$Id$" | |
2 __docformat__ = "reStructuredText" | |
3 | |
4 import operator | |
5 import math | |
6 import ctypes | |
7 | |
8 class Vec2d(ctypes.Structure): | |
9 """2d vector class, supports vector and scalar operators, | |
10 and also provides a bunch of high level functions | |
11 """ | |
12 __slots__ = ['x', 'y'] | |
13 | |
14 @classmethod | |
15 def from_param(cls, arg): | |
16 return cls(arg) | |
17 | |
18 def __init__(self, x_or_pair, y = None): | |
19 | |
20 if y == None: | |
21 self.x = x_or_pair[0] | |
22 self.y = x_or_pair[1] | |
23 else: | |
24 self.x = x_or_pair | |
25 self.y = y | |
26 | |
27 def __len__(self): | |
28 return 2 | |
29 | |
30 def __getitem__(self, key): | |
31 if key == 0: | |
32 return self.x | |
33 elif key == 1: | |
34 return self.y | |
35 else: | |
36 raise IndexError("Invalid subscript "+str(key)+" to Vec2d") | |
37 | |
38 def __setitem__(self, key, value): | |
39 if key == 0: | |
40 self.x = value | |
41 elif key == 1: | |
42 self.y = value | |
43 else: | |
44 raise IndexError("Invalid subscript "+str(key)+" to Vec2d") | |
45 | |
46 # String representaion (for debugging) | |
47 def __repr__(self): | |
48 return 'Vec2d(%s, %s)' % (self.x, self.y) | |
49 | |
50 # Comparison | |
51 def __eq__(self, other): | |
52 if hasattr(other, "__getitem__") and len(other) == 2: | |
53 return self.x == other[0] and self.y == other[1] | |
54 else: | |
55 return False | |
56 | |
57 def __ne__(self, other): | |
58 if hasattr(other, "__getitem__") and len(other) == 2: | |
59 return self.x != other[0] or self.y != other[1] | |
60 else: | |
61 return True | |
62 | |
63 def __nonzero__(self): | |
64 return self.x or self.y | |
65 | |
66 # Generic operator handlers | |
67 def _o2(self, other, f): | |
68 "Any two-operator operation where the left operand is a Vec2d" | |
69 if isinstance(other, Vec2d): | |
70 return Vec2d(f(self.x, other.x), | |
71 f(self.y, other.y)) | |
72 elif (hasattr(other, "__getitem__")): | |
73 return Vec2d(f(self.x, other[0]), | |
74 f(self.y, other[1])) | |
75 else: | |
76 return Vec2d(f(self.x, other), | |
77 f(self.y, other)) | |
78 | |
79 def _r_o2(self, other, f): | |
80 "Any two-operator operation where the right operand is a Vec2d" | |
81 if (hasattr(other, "__getitem__")): | |
82 return Vec2d(f(other[0], self.x), | |
83 f(other[1], self.y)) | |
84 else: | |
85 return Vec2d(f(other, self.x), | |
86 f(other, self.y)) | |
87 | |
88 def _io(self, other, f): | |
89 "inplace operator" | |
90 if (hasattr(other, "__getitem__")): | |
91 self.x = f(self.x, other[0]) | |
92 self.y = f(self.y, other[1]) | |
93 else: | |
94 self.x = f(self.x, other) | |
95 self.y = f(self.y, other) | |
96 return self | |
97 | |
98 # Addition | |
99 def __add__(self, other): | |
100 if isinstance(other, Vec2d): | |
101 return Vec2d(self.x + other.x, self.y + other.y) | |
102 elif hasattr(other, "__getitem__"): | |
103 return Vec2d(self.x + other[0], self.y + other[1]) | |
104 else: | |
105 return Vec2d(self.x + other, self.y + other) | |
106 __radd__ = __add__ | |
107 | |
108 def __iadd__(self, other): | |
109 if isinstance(other, Vec2d): | |
110 self.x += other.x | |
111 self.y += other.y | |
112 elif hasattr(other, "__getitem__"): | |
113 self.x += other[0] | |
114 self.y += other[1] | |
115 else: | |
116 self.x += other | |
117 self.y += other | |
118 return self | |
119 | |
120 # Subtraction | |
121 def __sub__(self, other): | |
122 if isinstance(other, Vec2d): | |
123 return Vec2d(self.x - other.x, self.y - other.y) | |
124 elif (hasattr(other, "__getitem__")): | |
125 return Vec2d(self.x - other[0], self.y - other[1]) | |
126 else: | |
127 return Vec2d(self.x - other, self.y - other) | |
128 def __rsub__(self, other): | |
129 if isinstance(other, Vec2d): | |
130 return Vec2d(other.x - self.x, other.y - self.y) | |
131 if (hasattr(other, "__getitem__")): | |
132 return Vec2d(other[0] - self.x, other[1] - self.y) | |
133 else: | |
134 return Vec2d(other - self.x, other - self.y) | |
135 def __isub__(self, other): | |
136 if isinstance(other, Vec2d): | |
137 self.x -= other.x | |
138 self.y -= other.y | |
139 elif (hasattr(other, "__getitem__")): | |
140 self.x -= other[0] | |
141 self.y -= other[1] | |
142 else: | |
143 self.x -= other | |
144 self.y -= other | |
145 return self | |
146 | |
147 # Multiplication | |
148 def __mul__(self, other): | |
149 if isinstance(other, Vec2d): | |
150 return Vec2d(self.x*other.y, self.y*other.y) | |
151 if (hasattr(other, "__getitem__")): | |
152 return Vec2d(self.x*other[0], self.y*other[1]) | |
153 else: | |
154 return Vec2d(self.x*other, self.y*other) | |
155 __rmul__ = __mul__ | |
156 | |
157 def __imul__(self, other): | |
158 if isinstance(other, Vec2d): | |
159 self.x *= other.x | |
160 self.y *= other.y | |
161 elif (hasattr(other, "__getitem__")): | |
162 self.x *= other[0] | |
163 self.y *= other[1] | |
164 else: | |
165 self.x *= other | |
166 self.y *= other | |
167 return self | |
168 | |
169 # Division | |
170 def __div__(self, other): | |
171 return self._o2(other, operator.div) | |
172 def __rdiv__(self, other): | |
173 return self._r_o2(other, operator.div) | |
174 def __idiv__(self, other): | |
175 return self._io(other, operator.div) | |
176 | |
177 def __floordiv__(self, other): | |
178 return self._o2(other, operator.floordiv) | |
179 def __rfloordiv__(self, other): | |
180 return self._r_o2(other, operator.floordiv) | |
181 def __ifloordiv__(self, other): | |
182 return self._io(other, operator.floordiv) | |
183 | |
184 def __truediv__(self, other): | |
185 return self._o2(other, operator.truediv) | |
186 def __rtruediv__(self, other): | |
187 return self._r_o2(other, operator.truediv) | |
188 def __itruediv__(self, other): | |
189 return self._io(other, operator.floordiv) | |
190 | |
191 # Modulo | |
192 def __mod__(self, other): | |
193 return self._o2(other, operator.mod) | |
194 def __rmod__(self, other): | |
195 return self._r_o2(other, operator.mod) | |
196 | |
197 def __divmod__(self, other): | |
198 return self._o2(other, divmod) | |
199 def __rdivmod__(self, other): | |
200 return self._r_o2(other, divmod) | |
201 | |
202 # Exponentation | |
203 def __pow__(self, other): | |
204 return self._o2(other, operator.pow) | |
205 def __rpow__(self, other): | |
206 return self._r_o2(other, operator.pow) | |
207 | |
208 # Bitwise operators | |
209 def __lshift__(self, other): | |
210 return self._o2(other, operator.lshift) | |
211 def __rlshift__(self, other): | |
212 return self._r_o2(other, operator.lshift) | |
213 | |
214 def __rshift__(self, other): | |
215 return self._o2(other, operator.rshift) | |
216 def __rrshift__(self, other): | |
217 return self._r_o2(other, operator.rshift) | |
218 | |
219 def __and__(self, other): | |
220 return self._o2(other, operator.and_) | |
221 __rand__ = __and__ | |
222 | |
223 def __or__(self, other): | |
224 return self._o2(other, operator.or_) | |
225 __ror__ = __or__ | |
226 | |
227 def __xor__(self, other): | |
228 return self._o2(other, operator.xor) | |
229 __rxor__ = __xor__ | |
230 | |
231 # Unary operations | |
232 def __neg__(self): | |
233 return Vec2d(operator.neg(self.x), operator.neg(self.y)) | |
234 | |
235 def __pos__(self): | |
236 return Vec2d(operator.pos(self.x), operator.pos(self.y)) | |
237 | |
238 def __abs__(self): | |
239 return Vec2d(abs(self.x), abs(self.y)) | |
240 | |
241 def __invert__(self): | |
242 return Vec2d(-self.x, -self.y) | |
243 | |
244 # vectory functions | |
245 def get_length_sqrd(self): | |
246 """Get the squared length of the vector. | |
247 It is more efficent to use this method instead of first call | |
248 get_length() or access .length and then do a sqrt(). | |
249 | |
250 :return: The squared length | |
251 """ | |
252 return self.x**2 + self.y**2 | |
253 | |
254 def get_length(self): | |
255 """Get the length of the vector. | |
256 | |
257 :return: The length | |
258 """ | |
259 return math.sqrt(self.x**2 + self.y**2) | |
260 def __setlength(self, value): | |
261 length = self.get_length() | |
262 self.x *= value/length | |
263 self.y *= value/length | |
264 length = property(get_length, __setlength, doc = """Gets or sets the magnitude of the vector""") | |
265 | |
266 def rotate(self, angle_degrees): | |
267 """Rotate the vector by angle_degrees degrees clockwise.""" | |
268 radians = -math.radians(angle_degrees) | |
269 cos = math.cos(radians) | |
270 sin = math.sin(radians) | |
271 x = self.x*cos - self.y*sin | |
272 y = self.x*sin + self.y*cos | |
273 self.x = x | |
274 self.y = y | |
275 | |
276 def rotated(self, angle_degrees): | |
277 """Create and return a new vector by rotating this vector by | |
278 angle_degrees degrees clockwise. | |
279 | |
280 :return: Rotated vector | |
281 """ | |
282 radians = -math.radians(angle_degrees) | |
283 cos = math.cos(radians) | |
284 sin = math.sin(radians) | |
285 x = self.x*cos - self.y*sin | |
286 y = self.x*sin + self.y*cos | |
287 return Vec2d(x, y) | |
288 | |
289 def get_angle(self): | |
290 if (self.get_length_sqrd() == 0): | |
291 return 0 | |
292 return math.degrees(math.atan2(self.y, self.x)) | |
293 def __setangle(self, angle_degrees): | |
294 self.x = self.length | |
295 self.y = 0 | |
296 self.rotate(angle_degrees) | |
297 angle = property(get_angle, __setangle, doc="""Gets or sets the angle of a vector""") | |
298 | |
299 def get_angle_between(self, other): | |
300 """Get the angle between the vector and the other in degrees | |
301 | |
302 :return: The angle | |
303 """ | |
304 cross = self.x*other[1] - self.y*other[0] | |
305 dot = self.x*other[0] + self.y*other[1] | |
306 return math.degrees(math.atan2(cross, dot)) | |
307 | |
308 def normalized(self): | |
309 """Get a normalized copy of the vector | |
310 | |
311 :return: A normalized vector | |
312 """ | |
313 length = self.length | |
314 if length != 0: | |
315 return self/length | |
316 return Vec2d(self) | |
317 | |
318 def normalize_return_length(self): | |
319 """Normalize the vector and return its length before the normalization | |
320 | |
321 :return: The length before the normalization | |
322 """ | |
323 length = self.length | |
324 if length != 0: | |
325 self.x /= length | |
326 self.y /= length | |
327 return length | |
328 | |
329 def perpendicular(self): | |
330 return Vec2d(-self.y, self.x) | |
331 | |
332 def perpendicular_normal(self): | |
333 length = self.length | |
334 if length != 0: | |
335 return Vec2d(-self.y/length, self.x/length) | |
336 return Vec2d(self) | |
337 | |
338 def dot(self, other): | |
339 """The dot product between the vector and other vector | |
340 v1.dot(v2) -> v1.x*v2.x + v1.y*v2.y | |
341 | |
342 :return: The dot product | |
343 """ | |
344 return float(self.x*other[0] + self.y*other[1]) | |
345 | |
346 def get_distance(self, other): | |
347 """The distance between the vector and other vector | |
348 | |
349 :return: The distance | |
350 """ | |
351 return math.sqrt((self.x - other[0])**2 + (self.y - other[1])**2) | |
352 | |
353 def get_dist_sqrd(self, other): | |
354 """The squared distance between the vector and other vector | |
355 It is more efficent to use this method than to call get_distance() | |
356 first and then do a sqrt() on the result. | |
357 | |
358 :return: The squared distance | |
359 """ | |
360 return (self.x - other[0])**2 + (self.y - other[1])**2 | |
361 | |
362 def projection(self, other): | |
363 other_length_sqrd = other[0]*other[0] + other[1]*other[1] | |
364 projected_length_times_other_length = self.dot(other) | |
365 return other*(projected_length_times_other_length/other_length_sqrd) | |
366 | |
367 def cross(self, other): | |
368 """The cross product between the vector and other vector | |
369 v1.cross(v2) -> v1.x*v2.y - v2.y-v1.x | |
370 | |
371 :return: The cross product | |
372 """ | |
373 return self.x*other[1] - self.y*other[0] | |
374 | |
375 def interpolate_to(self, other, range): | |
376 return Vec2d(self.x + (other[0] - self.x)*range, self.y + (other[1] - self.y)*range) | |
377 | |
378 def convert_to_basis(self, x_vector, y_vector): | |
379 return Vec2d(self.dot(x_vector)/x_vector.get_length_sqrd(), self.dot(y_vector)/y_vector.get_length_sqrd()) | |
380 | |
381 # Extra functions, mainly for chipmunk | |
382 def cpvrotate(self, other): | |
383 return Vec2d(self.x*other.x - self.y*other.y, self.x*other.y + self.y*other.x) | |
384 def cpvunrotate(self, other): | |
385 return Vec2d(self.x*other.x + self.y*other.y, self.y*other.x - self.x*other.y) | |
386 | |
387 # Pickle, does not work atm. | |
388 def __getstate__(self): | |
389 return [self.x, self.y] | |
390 | |
391 def __setstate__(self, dict): | |
392 self.x, self.y = dict | |
393 def __newobj__(cls, *args): | |
394 return cls.__new__(cls, *args) | |
395 Vec2d._fields_ = [ | |
396 ('x', ctypes.c_double), | |
397 ('y', ctypes.c_double), | |
398 ] | |
399 | |
400 | |
401 class Vec2dArray(list): | |
402 | |
403 def __init__(self, iterable=()): | |
404 list.__init__(self, (Vec2d(i) for i in iterable)) | |
405 | |
406 def __setitem__(self, index, value): | |
407 list.__setitem__(self, index, Vec2d(value)) | |
408 | |
409 def append(self, value): | |
410 """Append a vector to the array""" | |
411 list.append(self, Vec2d(value)) | |
412 | |
413 def insert(self, index, value): | |
414 """Insert a vector into the array""" | |
415 list.insert(self, index, Vec2d(value)) | |
416 | |
417 def transform(self, offset=Vec2d(0,0), angle=0, scale=1.0): | |
418 """Return a new transformed Vec2dArray""" | |
419 offset = Vec2d(offset) | |
420 angle = math.radians(-angle) | |
421 rot_vec = Vec2d(math.cos(angle), math.sin(angle)) | |
422 xformed = Vec2dArray() | |
423 for vec in self: | |
424 xformed.append(vec.cpvrotate(rot_vec) * scale + offset) | |
425 return xformed | |
426 | |
427 def segments(self, closed=True): | |
428 """Generate arrays of line segments connecting adjacent vetices | |
429 in this array, exploding the shape into it's constituent segments | |
430 """ | |
431 if len(self) >= 2: | |
432 last = self[0] | |
433 for vert in self[1:]: | |
434 yield Vec2dArray((last, vert)) | |
435 last = vert | |
436 if closed: | |
437 yield Vec2dArray((last, self[0])) | |
438 elif self and closed: | |
439 yield Vec2dArray((self[0], self[0])) | |
440 | |
441 | |
442 | |
443 class Rect(ctypes.Structure): | |
444 """Simple rectangle. Will gain more functionality as needed""" | |
445 _fields_ = [ | |
446 ('left', ctypes.c_double), | |
447 ('top', ctypes.c_double), | |
448 ('right', ctypes.c_double), | |
449 ('bottom', ctypes.c_double), | |
450 ] | |
451 | |
452 def __init__(self, rect_or_left, bottom=None, right=None, top=None): | |
453 if bottom is not None: | |
454 assert right is not None and top is not None, "No enough arguments to Rect" | |
455 self.left = rect_or_left | |
456 self.bottom = bottom | |
457 self.right = right | |
458 self.top = top | |
459 else: | |
460 self.left = rect_or_left.left | |
461 self.bottom = rect_or_left.bottom | |
462 self.right = rect_or_left.right | |
463 self.top = rect_or_left.top | |
464 | |
465 @property | |
466 def width(self): | |
467 """Rectangle width""" | |
468 return self.right - self.left | |
469 | |
470 @property | |
471 def height(self): | |
472 """Rectangle height""" | |
473 return self.top - self.bottom | |
474 | |
475 | |
476 ######################################################################## | |
477 ## Unit Testing ## | |
478 ######################################################################## | |
479 if __name__ == "__main__": | |
480 | |
481 import unittest | |
482 import pickle | |
483 | |
484 #################################################################### | |
485 class UnitTestVec2d(unittest.TestCase): | |
486 | |
487 def setUp(self): | |
488 pass | |
489 | |
490 def testCreationAndAccess(self): | |
491 v = Vec2d(111, 222) | |
492 self.assert_(v.x == 111 and v.y == 222) | |
493 v.x = 333 | |
494 v[1] = 444 | |
495 self.assert_(v[0] == 333 and v[1] == 444) | |
496 | |
497 def testMath(self): | |
498 v = Vec2d(111,222) | |
499 self.assertEqual(v + 1, Vec2d(112, 223)) | |
500 self.assert_(v - 2 == [109, 220]) | |
501 self.assert_(v * 3 == (333, 666)) | |
502 self.assert_(v / 2.0 == Vec2d(55.5, 111)) | |
503 #self.assert_(v / 2 == (55, 111)) # Not supported since this is a c_float structure in the bottom | |
504 self.assert_(v ** Vec2d(2, 3) == [12321, 10941048]) | |
505 self.assert_(v + [-11, 78] == Vec2d(100, 300)) | |
506 #self.assert_(v / [11,2] == [10,111]) # Not supported since this is a c_float structure in the bottom | |
507 | |
508 def testReverseMath(self): | |
509 v = Vec2d(111, 222) | |
510 self.assert_(1 + v == Vec2d(112, 223)) | |
511 self.assert_(2 - v == [-109, -220]) | |
512 self.assert_(3 * v == (333, 666)) | |
513 #self.assert_([222,999] / v == [2,4]) # Not supported since this is a c_float structure in the bottom | |
514 self.assert_([111, 222] ** Vec2d(2, 3) == [12321, 10941048]) | |
515 self.assert_([-11, 78] + v == Vec2d(100, 300)) | |
516 | |
517 def testUnary(self): | |
518 v = Vec2d(111, 222) | |
519 v = -v | |
520 self.assert_(v == [-111, -222]) | |
521 v = abs(v) | |
522 self.assert_(v == [111, 222]) | |
523 | |
524 def testLength(self): | |
525 v = Vec2d(3,4) | |
526 self.assert_(v.length == 5) | |
527 self.assert_(v.get_length_sqrd() == 25) | |
528 self.assert_(v.normalize_return_length() == 5) | |
529 self.assertAlmostEquals(v.length, 1) | |
530 v.length = 5 | |
531 self.assert_(v == Vec2d(3, 4)) | |
532 v2 = Vec2d(10, -2) | |
533 self.assert_(v.get_distance(v2) == (v - v2).get_length()) | |
534 | |
535 def testAngles(self): | |
536 v = Vec2d(0, 3) | |
537 self.assertEquals(v.angle, 90) | |
538 v2 = Vec2d(v) | |
539 v.rotate(-90) | |
540 self.assertEqual(v.get_angle_between(v2), 90) | |
541 v2.angle -= 90 | |
542 self.assertEqual(v.length, v2.length) | |
543 self.assertEquals(v2.angle, 0) | |
544 self.assertEqual(v2, [3, 0]) | |
545 self.assert_((v - v2).length < .00001) | |
546 self.assertEqual(v.length, v2.length) | |
547 v2.rotate(300) | |
548 self.assertAlmostEquals(v.get_angle_between(v2), -60, 5) # Allow a little more error than usual (floats..) | |
549 v2.rotate(v2.get_angle_between(v)) | |
550 angle = v.get_angle_between(v2) | |
551 self.assertAlmostEquals(v.get_angle_between(v2), 0) | |
552 | |
553 def testHighLevel(self): | |
554 basis0 = Vec2d(5.0, 0) | |
555 basis1 = Vec2d(0, .5) | |
556 v = Vec2d(10, 1) | |
557 self.assert_(v.convert_to_basis(basis0, basis1) == [2, 2]) | |
558 self.assert_(v.projection(basis0) == (10, 0)) | |
559 self.assert_(basis0.dot(basis1) == 0) | |
560 | |
561 def testCross(self): | |
562 lhs = Vec2d(1, .5) | |
563 rhs = Vec2d(4, 6) | |
564 self.assert_(lhs.cross(rhs) == 4) | |
565 | |
566 def testComparison(self): | |
567 int_vec = Vec2d(3, -2) | |
568 flt_vec = Vec2d(3.0, -2.0) | |
569 zero_vec = Vec2d(0, 0) | |
570 self.assert_(int_vec == flt_vec) | |
571 self.assert_(int_vec != zero_vec) | |
572 self.assert_((flt_vec == zero_vec) == False) | |
573 self.assert_((flt_vec != int_vec) == False) | |
574 self.assert_(int_vec == (3, -2)) | |
575 self.assert_(int_vec != [0, 0]) | |
576 self.assert_(int_vec != 5) | |
577 self.assert_(int_vec != [3, -2, -5]) | |
578 | |
579 def testInplace(self): | |
580 inplace_vec = Vec2d(5, 13) | |
581 inplace_ref = inplace_vec | |
582 inplace_src = Vec2d(inplace_vec) | |
583 inplace_vec *= .5 | |
584 inplace_vec += .5 | |
585 inplace_vec /= (3, 6) | |
586 inplace_vec += Vec2d(-1, -1) | |
587 alternate = (inplace_src*.5 + .5)/Vec2d(3, 6) + [-1, -1] | |
588 self.assertEquals(inplace_vec, inplace_ref) | |
589 self.assertEquals(inplace_vec, alternate) | |
590 | |
591 def testPickle(self): | |
592 return # pickling does not work atm | |
593 testvec = Vec2d(5, .3) | |
594 testvec_str = pickle.dumps(testvec) | |
595 loaded_vec = pickle.loads(testvec_str) | |
596 self.assertEquals(testvec, loaded_vec) | |
597 | |
598 #################################################################### | |
599 unittest.main() | |
600 |