Mercurial > parpg-source
diff 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 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bGrease/geometry.py Mon Sep 05 15:00:34 2011 +0200 @@ -0,0 +1,600 @@ +__version__ = "$Id$" +__docformat__ = "reStructuredText" + +import operator +import math +import ctypes + +class Vec2d(ctypes.Structure): + """2d vector class, supports vector and scalar operators, + and also provides a bunch of high level functions + """ + __slots__ = ['x', 'y'] + + @classmethod + def from_param(cls, arg): + return cls(arg) + + def __init__(self, x_or_pair, y = None): + + if y == None: + self.x = x_or_pair[0] + self.y = x_or_pair[1] + else: + self.x = x_or_pair + self.y = y + + def __len__(self): + return 2 + + def __getitem__(self, key): + if key == 0: + return self.x + elif key == 1: + return self.y + else: + raise IndexError("Invalid subscript "+str(key)+" to Vec2d") + + def __setitem__(self, key, value): + if key == 0: + self.x = value + elif key == 1: + self.y = value + else: + raise IndexError("Invalid subscript "+str(key)+" to Vec2d") + + # String representaion (for debugging) + def __repr__(self): + return 'Vec2d(%s, %s)' % (self.x, self.y) + + # Comparison + def __eq__(self, other): + if hasattr(other, "__getitem__") and len(other) == 2: + return self.x == other[0] and self.y == other[1] + else: + return False + + def __ne__(self, other): + if hasattr(other, "__getitem__") and len(other) == 2: + return self.x != other[0] or self.y != other[1] + else: + return True + + def __nonzero__(self): + return self.x or self.y + + # Generic operator handlers + def _o2(self, other, f): + "Any two-operator operation where the left operand is a Vec2d" + if isinstance(other, Vec2d): + return Vec2d(f(self.x, other.x), + f(self.y, other.y)) + elif (hasattr(other, "__getitem__")): + return Vec2d(f(self.x, other[0]), + f(self.y, other[1])) + else: + return Vec2d(f(self.x, other), + f(self.y, other)) + + def _r_o2(self, other, f): + "Any two-operator operation where the right operand is a Vec2d" + if (hasattr(other, "__getitem__")): + return Vec2d(f(other[0], self.x), + f(other[1], self.y)) + else: + return Vec2d(f(other, self.x), + f(other, self.y)) + + def _io(self, other, f): + "inplace operator" + if (hasattr(other, "__getitem__")): + self.x = f(self.x, other[0]) + self.y = f(self.y, other[1]) + else: + self.x = f(self.x, other) + self.y = f(self.y, other) + return self + + # Addition + def __add__(self, other): + if isinstance(other, Vec2d): + return Vec2d(self.x + other.x, self.y + other.y) + elif hasattr(other, "__getitem__"): + return Vec2d(self.x + other[0], self.y + other[1]) + else: + return Vec2d(self.x + other, self.y + other) + __radd__ = __add__ + + def __iadd__(self, other): + if isinstance(other, Vec2d): + self.x += other.x + self.y += other.y + elif hasattr(other, "__getitem__"): + self.x += other[0] + self.y += other[1] + else: + self.x += other + self.y += other + return self + + # Subtraction + def __sub__(self, other): + if isinstance(other, Vec2d): + return Vec2d(self.x - other.x, self.y - other.y) + elif (hasattr(other, "__getitem__")): + return Vec2d(self.x - other[0], self.y - other[1]) + else: + return Vec2d(self.x - other, self.y - other) + def __rsub__(self, other): + if isinstance(other, Vec2d): + return Vec2d(other.x - self.x, other.y - self.y) + if (hasattr(other, "__getitem__")): + return Vec2d(other[0] - self.x, other[1] - self.y) + else: + return Vec2d(other - self.x, other - self.y) + def __isub__(self, other): + if isinstance(other, Vec2d): + self.x -= other.x + self.y -= other.y + elif (hasattr(other, "__getitem__")): + self.x -= other[0] + self.y -= other[1] + else: + self.x -= other + self.y -= other + return self + + # Multiplication + def __mul__(self, other): + if isinstance(other, Vec2d): + return Vec2d(self.x*other.y, self.y*other.y) + if (hasattr(other, "__getitem__")): + return Vec2d(self.x*other[0], self.y*other[1]) + else: + return Vec2d(self.x*other, self.y*other) + __rmul__ = __mul__ + + def __imul__(self, other): + if isinstance(other, Vec2d): + self.x *= other.x + self.y *= other.y + elif (hasattr(other, "__getitem__")): + self.x *= other[0] + self.y *= other[1] + else: + self.x *= other + self.y *= other + return self + + # Division + def __div__(self, other): + return self._o2(other, operator.div) + def __rdiv__(self, other): + return self._r_o2(other, operator.div) + def __idiv__(self, other): + return self._io(other, operator.div) + + def __floordiv__(self, other): + return self._o2(other, operator.floordiv) + def __rfloordiv__(self, other): + return self._r_o2(other, operator.floordiv) + def __ifloordiv__(self, other): + return self._io(other, operator.floordiv) + + def __truediv__(self, other): + return self._o2(other, operator.truediv) + def __rtruediv__(self, other): + return self._r_o2(other, operator.truediv) + def __itruediv__(self, other): + return self._io(other, operator.floordiv) + + # Modulo + def __mod__(self, other): + return self._o2(other, operator.mod) + def __rmod__(self, other): + return self._r_o2(other, operator.mod) + + def __divmod__(self, other): + return self._o2(other, divmod) + def __rdivmod__(self, other): + return self._r_o2(other, divmod) + + # Exponentation + def __pow__(self, other): + return self._o2(other, operator.pow) + def __rpow__(self, other): + return self._r_o2(other, operator.pow) + + # Bitwise operators + def __lshift__(self, other): + return self._o2(other, operator.lshift) + def __rlshift__(self, other): + return self._r_o2(other, operator.lshift) + + def __rshift__(self, other): + return self._o2(other, operator.rshift) + def __rrshift__(self, other): + return self._r_o2(other, operator.rshift) + + def __and__(self, other): + return self._o2(other, operator.and_) + __rand__ = __and__ + + def __or__(self, other): + return self._o2(other, operator.or_) + __ror__ = __or__ + + def __xor__(self, other): + return self._o2(other, operator.xor) + __rxor__ = __xor__ + + # Unary operations + def __neg__(self): + return Vec2d(operator.neg(self.x), operator.neg(self.y)) + + def __pos__(self): + return Vec2d(operator.pos(self.x), operator.pos(self.y)) + + def __abs__(self): + return Vec2d(abs(self.x), abs(self.y)) + + def __invert__(self): + return Vec2d(-self.x, -self.y) + + # vectory functions + def get_length_sqrd(self): + """Get the squared length of the vector. + It is more efficent to use this method instead of first call + get_length() or access .length and then do a sqrt(). + + :return: The squared length + """ + return self.x**2 + self.y**2 + + def get_length(self): + """Get the length of the vector. + + :return: The length + """ + return math.sqrt(self.x**2 + self.y**2) + def __setlength(self, value): + length = self.get_length() + self.x *= value/length + self.y *= value/length + length = property(get_length, __setlength, doc = """Gets or sets the magnitude of the vector""") + + def rotate(self, angle_degrees): + """Rotate the vector by angle_degrees degrees clockwise.""" + radians = -math.radians(angle_degrees) + cos = math.cos(radians) + sin = math.sin(radians) + x = self.x*cos - self.y*sin + y = self.x*sin + self.y*cos + self.x = x + self.y = y + + def rotated(self, angle_degrees): + """Create and return a new vector by rotating this vector by + angle_degrees degrees clockwise. + + :return: Rotated vector + """ + radians = -math.radians(angle_degrees) + cos = math.cos(radians) + sin = math.sin(radians) + x = self.x*cos - self.y*sin + y = self.x*sin + self.y*cos + return Vec2d(x, y) + + def get_angle(self): + if (self.get_length_sqrd() == 0): + return 0 + return math.degrees(math.atan2(self.y, self.x)) + def __setangle(self, angle_degrees): + self.x = self.length + self.y = 0 + self.rotate(angle_degrees) + angle = property(get_angle, __setangle, doc="""Gets or sets the angle of a vector""") + + def get_angle_between(self, other): + """Get the angle between the vector and the other in degrees + + :return: The angle + """ + cross = self.x*other[1] - self.y*other[0] + dot = self.x*other[0] + self.y*other[1] + return math.degrees(math.atan2(cross, dot)) + + def normalized(self): + """Get a normalized copy of the vector + + :return: A normalized vector + """ + length = self.length + if length != 0: + return self/length + return Vec2d(self) + + def normalize_return_length(self): + """Normalize the vector and return its length before the normalization + + :return: The length before the normalization + """ + length = self.length + if length != 0: + self.x /= length + self.y /= length + return length + + def perpendicular(self): + return Vec2d(-self.y, self.x) + + def perpendicular_normal(self): + length = self.length + if length != 0: + return Vec2d(-self.y/length, self.x/length) + return Vec2d(self) + + def dot(self, other): + """The dot product between the vector and other vector + v1.dot(v2) -> v1.x*v2.x + v1.y*v2.y + + :return: The dot product + """ + return float(self.x*other[0] + self.y*other[1]) + + def get_distance(self, other): + """The distance between the vector and other vector + + :return: The distance + """ + return math.sqrt((self.x - other[0])**2 + (self.y - other[1])**2) + + def get_dist_sqrd(self, other): + """The squared distance between the vector and other vector + It is more efficent to use this method than to call get_distance() + first and then do a sqrt() on the result. + + :return: The squared distance + """ + return (self.x - other[0])**2 + (self.y - other[1])**2 + + def projection(self, other): + other_length_sqrd = other[0]*other[0] + other[1]*other[1] + projected_length_times_other_length = self.dot(other) + return other*(projected_length_times_other_length/other_length_sqrd) + + def cross(self, other): + """The cross product between the vector and other vector + v1.cross(v2) -> v1.x*v2.y - v2.y-v1.x + + :return: The cross product + """ + return self.x*other[1] - self.y*other[0] + + def interpolate_to(self, other, range): + return Vec2d(self.x + (other[0] - self.x)*range, self.y + (other[1] - self.y)*range) + + def convert_to_basis(self, x_vector, y_vector): + return Vec2d(self.dot(x_vector)/x_vector.get_length_sqrd(), self.dot(y_vector)/y_vector.get_length_sqrd()) + + # Extra functions, mainly for chipmunk + def cpvrotate(self, other): + return Vec2d(self.x*other.x - self.y*other.y, self.x*other.y + self.y*other.x) + def cpvunrotate(self, other): + return Vec2d(self.x*other.x + self.y*other.y, self.y*other.x - self.x*other.y) + + # Pickle, does not work atm. + def __getstate__(self): + return [self.x, self.y] + + def __setstate__(self, dict): + self.x, self.y = dict + def __newobj__(cls, *args): + return cls.__new__(cls, *args) +Vec2d._fields_ = [ + ('x', ctypes.c_double), + ('y', ctypes.c_double), + ] + + +class Vec2dArray(list): + + def __init__(self, iterable=()): + list.__init__(self, (Vec2d(i) for i in iterable)) + + def __setitem__(self, index, value): + list.__setitem__(self, index, Vec2d(value)) + + def append(self, value): + """Append a vector to the array""" + list.append(self, Vec2d(value)) + + def insert(self, index, value): + """Insert a vector into the array""" + list.insert(self, index, Vec2d(value)) + + def transform(self, offset=Vec2d(0,0), angle=0, scale=1.0): + """Return a new transformed Vec2dArray""" + offset = Vec2d(offset) + angle = math.radians(-angle) + rot_vec = Vec2d(math.cos(angle), math.sin(angle)) + xformed = Vec2dArray() + for vec in self: + xformed.append(vec.cpvrotate(rot_vec) * scale + offset) + return xformed + + def segments(self, closed=True): + """Generate arrays of line segments connecting adjacent vetices + in this array, exploding the shape into it's constituent segments + """ + if len(self) >= 2: + last = self[0] + for vert in self[1:]: + yield Vec2dArray((last, vert)) + last = vert + if closed: + yield Vec2dArray((last, self[0])) + elif self and closed: + yield Vec2dArray((self[0], self[0])) + + + +class Rect(ctypes.Structure): + """Simple rectangle. Will gain more functionality as needed""" + _fields_ = [ + ('left', ctypes.c_double), + ('top', ctypes.c_double), + ('right', ctypes.c_double), + ('bottom', ctypes.c_double), + ] + + def __init__(self, rect_or_left, bottom=None, right=None, top=None): + if bottom is not None: + assert right is not None and top is not None, "No enough arguments to Rect" + self.left = rect_or_left + self.bottom = bottom + self.right = right + self.top = top + else: + self.left = rect_or_left.left + self.bottom = rect_or_left.bottom + self.right = rect_or_left.right + self.top = rect_or_left.top + + @property + def width(self): + """Rectangle width""" + return self.right - self.left + + @property + def height(self): + """Rectangle height""" + return self.top - self.bottom + + +######################################################################## +## Unit Testing ## +######################################################################## +if __name__ == "__main__": + + import unittest + import pickle + + #################################################################### + class UnitTestVec2d(unittest.TestCase): + + def setUp(self): + pass + + def testCreationAndAccess(self): + v = Vec2d(111, 222) + self.assert_(v.x == 111 and v.y == 222) + v.x = 333 + v[1] = 444 + self.assert_(v[0] == 333 and v[1] == 444) + + def testMath(self): + v = Vec2d(111,222) + self.assertEqual(v + 1, Vec2d(112, 223)) + self.assert_(v - 2 == [109, 220]) + self.assert_(v * 3 == (333, 666)) + self.assert_(v / 2.0 == Vec2d(55.5, 111)) + #self.assert_(v / 2 == (55, 111)) # Not supported since this is a c_float structure in the bottom + self.assert_(v ** Vec2d(2, 3) == [12321, 10941048]) + self.assert_(v + [-11, 78] == Vec2d(100, 300)) + #self.assert_(v / [11,2] == [10,111]) # Not supported since this is a c_float structure in the bottom + + def testReverseMath(self): + v = Vec2d(111, 222) + self.assert_(1 + v == Vec2d(112, 223)) + self.assert_(2 - v == [-109, -220]) + self.assert_(3 * v == (333, 666)) + #self.assert_([222,999] / v == [2,4]) # Not supported since this is a c_float structure in the bottom + self.assert_([111, 222] ** Vec2d(2, 3) == [12321, 10941048]) + self.assert_([-11, 78] + v == Vec2d(100, 300)) + + def testUnary(self): + v = Vec2d(111, 222) + v = -v + self.assert_(v == [-111, -222]) + v = abs(v) + self.assert_(v == [111, 222]) + + def testLength(self): + v = Vec2d(3,4) + self.assert_(v.length == 5) + self.assert_(v.get_length_sqrd() == 25) + self.assert_(v.normalize_return_length() == 5) + self.assertAlmostEquals(v.length, 1) + v.length = 5 + self.assert_(v == Vec2d(3, 4)) + v2 = Vec2d(10, -2) + self.assert_(v.get_distance(v2) == (v - v2).get_length()) + + def testAngles(self): + v = Vec2d(0, 3) + self.assertEquals(v.angle, 90) + v2 = Vec2d(v) + v.rotate(-90) + self.assertEqual(v.get_angle_between(v2), 90) + v2.angle -= 90 + self.assertEqual(v.length, v2.length) + self.assertEquals(v2.angle, 0) + self.assertEqual(v2, [3, 0]) + self.assert_((v - v2).length < .00001) + self.assertEqual(v.length, v2.length) + v2.rotate(300) + self.assertAlmostEquals(v.get_angle_between(v2), -60, 5) # Allow a little more error than usual (floats..) + v2.rotate(v2.get_angle_between(v)) + angle = v.get_angle_between(v2) + self.assertAlmostEquals(v.get_angle_between(v2), 0) + + def testHighLevel(self): + basis0 = Vec2d(5.0, 0) + basis1 = Vec2d(0, .5) + v = Vec2d(10, 1) + self.assert_(v.convert_to_basis(basis0, basis1) == [2, 2]) + self.assert_(v.projection(basis0) == (10, 0)) + self.assert_(basis0.dot(basis1) == 0) + + def testCross(self): + lhs = Vec2d(1, .5) + rhs = Vec2d(4, 6) + self.assert_(lhs.cross(rhs) == 4) + + def testComparison(self): + int_vec = Vec2d(3, -2) + flt_vec = Vec2d(3.0, -2.0) + zero_vec = Vec2d(0, 0) + self.assert_(int_vec == flt_vec) + self.assert_(int_vec != zero_vec) + self.assert_((flt_vec == zero_vec) == False) + self.assert_((flt_vec != int_vec) == False) + self.assert_(int_vec == (3, -2)) + self.assert_(int_vec != [0, 0]) + self.assert_(int_vec != 5) + self.assert_(int_vec != [3, -2, -5]) + + def testInplace(self): + inplace_vec = Vec2d(5, 13) + inplace_ref = inplace_vec + inplace_src = Vec2d(inplace_vec) + inplace_vec *= .5 + inplace_vec += .5 + inplace_vec /= (3, 6) + inplace_vec += Vec2d(-1, -1) + alternate = (inplace_src*.5 + .5)/Vec2d(3, 6) + [-1, -1] + self.assertEquals(inplace_vec, inplace_ref) + self.assertEquals(inplace_vec, alternate) + + def testPickle(self): + return # pickling does not work atm + testvec = Vec2d(5, .3) + testvec_str = pickle.dumps(testvec) + loaded_vec = pickle.loads(testvec_str) + self.assertEquals(testvec, loaded_vec) + + #################################################################### + unittest.main() +