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