From af1fc894df92473eaa54f0598dcd35525b50bfc2 Mon Sep 17 00:00:00 2001 From: Anton Joubert Date: Sun, 9 Oct 2016 20:26:59 +0200 Subject: [PATCH 01/15] Started adding math module - all tests do not pass yet. --- pygame/mask.py | 8 +- pygame/math.py | 310 +++++++++ test/math_test.py | 1530 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 1844 insertions(+), 4 deletions(-) create mode 100644 pygame/math.py create mode 100644 test/math_test.py diff --git a/pygame/mask.py b/pygame/mask.py index 4556c93..6a00453 100644 --- a/pygame/mask.py +++ b/pygame/mask.py @@ -1,6 +1,6 @@ # Implement the pygame API for the bitmask functions -from math import atan2, pi +import math from pygame._sdl import sdl, ffi from pygame.surflock import locked @@ -47,11 +47,11 @@ def angle(self): if tot: xc = xs // tot yc = ys // tot - theta = atan2(2 * (xy / tot - xc * yc), - (xx / tot - xc * xc) - (yy / tot - yc * yc)) + theta = math.atan2(2 * (xy / tot - xc * yc), + (xx / tot - xc * xc) - (yy / tot - yc * yc)) # covert from radians # We copy this logic from pygame upstream, because reasons - theta = -90.0 * theta / pi + theta = -90.0 * theta / math.pi return theta return 0.0 diff --git a/pygame/math.py b/pygame/math.py new file mode 100644 index 0000000..018ff3c --- /dev/null +++ b/pygame/math.py @@ -0,0 +1,310 @@ +""" Math module """ + +from numbers import Number + + +swizzling = False + + +def enable_swizzling(): + """Globally enables swizzling for vectors. + + Enables swizzling for all vectors until disable_swizzling() is called. + By default swizzling is disabled. + """ + global swizzling + swizzling = True + + +def disable_swizzling(): + """Globally disables swizzling for vectors. + + Disables swizzling for all vectors until enable_swizzling() is called. + By default swizzling is disabled. + """ + global swizzling + swizzling = False + + +class Vector2(object): + """A 2-Dimensional Vector.""" + + def __init__(self, *args): + if len(args) == 0: + self.x = 0.0 + self.y = 0.0 + elif len(args) == 1: + if isinstance(args[0], Vector2): + self.x = args[0].x + self.y = args[0].y + elif isinstance(args[0], basestring): + if args[0].startswith(""): + tokens = args[0][9:-2].split(",") + try: + self.x = float(tokens[0]) + self.y = float(tokens[1]) + except ValueError: + raise TypeError("Invalid string arguments - not floats ({}).". + format(args[0])) + else: + raise TypeError("Invalid string argument - not like __repr__ ({}).". + format(args[0])) + elif len(args[0]) == 2: + self.x = args[0][0] + self.y = args[0][1] + else: + raise TypeError("Invalid argument length ({}).".format(len(args[0]))) + else: + self.x = args[0] + self.y = args[1] + + def __repr__(self): + return "".format(self.x, self.y) + + def __str__(self): + return "[{}, {}]".format(self.x, self.y) + + def __len__(self): + return 2 + + def __getitem__(self, key): + """Provide indexed read access (vec[0] is x vec[1] is y).""" + if isinstance(key, slice): + return [self[ind] for ind in xrange(*key.indices(len(self)))] + elif isinstance(key, int): + if key < 0: + key += len(self) + if key == 0: + return self.x + elif key == 1: + return self.y + else: + raise IndexError("Out of range index requested: {}".format(key)) + else: + raise TypeError("Invalid argument type") + + def __setitem__(self, key, value): + """Provide indexed modification.""" + if isinstance(value, Vector2): + value = [value.x, value.y] + if isinstance(key, slice): + indices = range(*key.indices(len(self))) + if len(value) <= len(self) and len(value) == len(indices): + for count, index in enumerate(indices): + self[index] = value[count] + else: + raise ValueError("Invalid slice or value arguments ({}).".format(key)) + elif isinstance(key, int): + if key < 0: + key += len(self) + if key == 0: + self.x = value + elif key == 1: + self.y = value + else: + raise IndexError("Out of range index requested: {}".format(key)) + else: + raise TypeError("Invalid argument type") + + def __delitem__(self): + """Item deletion not supported.""" + raise TypeError("Items may not be deleted.") + + def __eq__(self, other): + if isinstance(other, Vector2): + return (self.x == other.x) and (self.y == other.y) + elif hasattr(other, '__iter__'): + try: + other_v = Vector2(other) + return self == other_v + except TypeError: + # Doesn't seem to be vector2-like, so NotImplemented + return False + return False + + def __ne__(self, other): + return not self.__eq__(other) + + def __add__(self, other): + if isinstance(other, Vector2): + return Vector2(self.x + other.x, self.y + other.y) + elif hasattr(other, '__iter__'): + try: + other_v = Vector2(other) + return self + other_v + except TypeError: + # Doesn't seem to be vector2-like, so NotImplemented + return NotImplemented + return NotImplemented + + def __radd__(self, other): + return self.__add__(other) + + def __sub__(self, other): + if isinstance(other, Vector2): + return Vector2(self.x - other.x, self.y - other.y) + elif hasattr(other, '__iter__'): + try: + other_v = Vector2(other) + return self - other_v + except TypeError: + # Doesn't seem to be vector2-like, so NotImplemented + return NotImplemented + return NotImplemented + + def __rsub__(self, other): + if isinstance(other, Vector2): + return Vector2(other.x - self.x, other.y - self.y) + elif hasattr(other, '__iter__'): + try: + other_v = Vector2(other) + return other_v - self + except TypeError: + # Doesn't seem to be vector2-like, so NotImplemented + return NotImplemented + return NotImplemented + + def __mul__(self, other): + if isinstance(other, Number): + return Vector2(self.x * float(other), self.y * float(other)) + return NotImplemented + + def __rmul__(self, other): + return self.__mul__(other) + + def __div__(self, other): + if isinstance(other, Number): + return Vector2(self.x / float(other), self.y / float(other)) + return NotImplemented + + def __floordiv__(self, other): + if isinstance(other, Number): + return Vector2(self.x // other, self.y // other) + return NotImplemented + + def __bool__(self): + """bool operator for Python 3.""" + return self.x != 0 or self.y != 0 + + def __nonzero__(self): + """bool operator for Python 2.""" + return self.__bool__() + + def __neg__(self): + return Vector2(-self.x, -self.y) + + def __pos__(self): + return Vector2(self.x, self.y) + + @property + def x(self): + """Vector x value.""" + return self._x + + @x.setter + def x(self, value): + if isinstance(value, Number): + self._x = value + else: + raise TypeError("Value {} is not a valid number.".format(value)) + + @property + def y(self): + """Vector y value.""" + return self._y + + @y.setter + def y(self, value): + if isinstance(value, Number): + self._y = value + else: + raise TypeError("Value {} is not a valid number.".format(value)) + + def dot(self, vec): + """calculates the dot- or scalar-product with the other vector. + + dot(Vector2) -> float. + """ + if not isinstance(vec, Vector2): + vec = Vector2(vec) + return self.x * vec.x + self.y * vec.y + + def cross(self): + """calculates the cross- or vector-product.""" + pass + + def length(self): + """returns the euclidic length of the vector.""" + pass + + def length_squared(self): + """returns the squared euclidic length of the vector.""" + pass + + def normalize(self): + """returns a vector with the same direction but length 1.""" + pass + + def normalize_ip(self): + """normalizes the vector in place so that its length is 1.""" + pass + + def is_normalized(self): + """tests if the vector is normalized i.e. has length == 1.""" + pass + + def scale_to_length(self): + """scales the vector to a given length.""" + pass + + def reflect(self): + """returns a vector reflected of a given normal.""" + pass + + def reflect_ip(self): + """reflect the vector of a given normal in place.""" + pass + + def distance_to(self): + """calculates the euclidic distance to a given vector.""" + pass + + def distance_squared_to(self): + """calculates the squared euclidic distance to a given vector.""" + pass + + def lerp(self): + """returns a linear interpolation to the given vector.""" + pass + + def slerp(self): + """returns a spherical interpolation to the given vector.""" + pass + + def elementwise(self): + """The next operation will be performed elementwize.""" + pass + + def rotate(self): + """rotates a vector by a given angle in degrees.""" + pass + + def rotate_ip(self): + """rotates the vector by a given angle in degrees in place.""" + pass + + def angle_to(self): + """calculates the angle to a given vector in degrees.""" + pass + + def as_polar(self): + """returns a tuple with radial distance and azimuthal angle.""" + pass + + def from_polar(self): + """Sets x and y from a polar coordinates tuple.""" + pass + + +class Vector3(object): + pass diff --git a/test/math_test.py b/test/math_test.py new file mode 100644 index 0000000..c4279fa --- /dev/null +++ b/test/math_test.py @@ -0,0 +1,1530 @@ +# -*- coding: utf-8 -*- +"""Tests originally taken from pygame. + +https://bitbucket.org/pygame/pygame/src/50ca5dd0191bc394c3f51e06ecc48ddedccc161d/test/math_test.py?at=default&fileviewer=file-view-default +""" + +if __name__ == '__main__': + import sys + import os + pkg_dir = os.path.split(os.path.abspath(__file__))[0] + parent_dir, pkg_name = os.path.split(pkg_dir) + is_pygame_pkg = (pkg_name == 'tests' and + os.path.split(parent_dir)[1] == 'pygame') + if not is_pygame_pkg: + sys.path.insert(0, parent_dir) +else: + is_pygame_pkg = __name__.startswith('pygame.tests.') + +import unittest + +import math +import pygame.math +from pygame.math import Vector2, Vector3 +from time import clock +from random import random +import gc + +class Vector2TypeTest(unittest.TestCase): + def setUp(self): +# gc.collect() + self.zeroVec = Vector2() + self.e1 = Vector2(1, 0) + self.e2 = Vector2(0, 1) +# self.t1 = (random(), random()) + self.t1 = (1.2, 3.4) + self.l1 = list(self.t1) + self.v1 = Vector2(self.t1) +# self.t2 = (random(), random()) + self.t2 = (5.6, 7.8) + self.l2 = list(self.t2) + self.v2 = Vector2(self.t2) +# self.s1 = random() +# self.s2 = random() + self.s1 = 5.6 + self.s2 = 7.8 + def tearDown(self): + pygame.math.disable_swizzling() + + + def testConstructionDefault(self): + v = Vector2() + self.assertEqual(v.x, 0.) + self.assertEqual(v.y, 0.) + + def testConstructionXY(self): + v = Vector2(1.2, 3.4) + self.assertEqual(v.x, 1.2) + self.assertEqual(v.y, 3.4) + + def testConstructionTuple(self): + v = Vector2((1.2, 3.4)) + self.assertEqual(v.x, 1.2) + self.assertEqual(v.y, 3.4) + + def testConstructionList(self): + v = Vector2([1.2, 3.4]) + self.assertEqual(v.x, 1.2) + self.assertEqual(v.y, 3.4) + + def testConstructionVector2(self): + v = Vector2(Vector2(1.2, 3.4)) + self.assertEqual(v.x, 1.2) + self.assertEqual(v.y, 3.4) + + def testAttributAccess(self): + tmp = self.v1.x + self.assertEqual(tmp, self.v1.x) + self.assertEqual(tmp, self.v1[0]) + tmp = self.v1.y + self.assertEqual(tmp, self.v1.y) + self.assertEqual(tmp, self.v1[1]) + self.v1.x = 3.141 + self.assertEqual(self.v1.x, 3.141) + self.v1.y = 3.141 + self.assertEqual(self.v1.y, 3.141) + def assign_nonfloat(): + v = Vector2() + v.x = "spam" + self.assertRaises(TypeError, assign_nonfloat) + + + def testSequence(self): + v = Vector2(1.2, 3.4) + Vector2()[:] + self.assertEqual(len(v), 2) + self.assertEqual(v[0], 1.2) + self.assertEqual(v[1], 3.4) + self.assertRaises(IndexError, lambda : v[2]) + self.assertEqual(v[-1], 3.4) + self.assertEqual(v[-2], 1.2) + self.assertRaises(IndexError, lambda : v[-3]) + self.assertEqual(v[:], [1.2, 3.4]) + self.assertEqual(v[1:], [3.4]) + self.assertEqual(v[:1], [1.2]) + self.assertEqual(list(v), [1.2, 3.4]) + self.assertEqual(tuple(v), (1.2, 3.4)) + v[0] = 5.6 + v[1] = 7.8 + self.assertEqual(v.x, 5.6) + self.assertEqual(v.y, 7.8) + v[:] = [9.1, 11.12] + self.assertEqual(v.x, 9.1) + self.assertEqual(v.y, 11.12) + def overpopulate(): + v = Vector2() + v[:] = [1, 2, 3] + self.assertRaises(ValueError, overpopulate) + def underpopulate(): + v = Vector2() + v[:] = [1] + self.assertRaises(ValueError, underpopulate) + def assign_nonfloat(): + v = Vector2() + v[0] = "spam" + self.assertRaises(TypeError, assign_nonfloat) + + def testExtendedSlicing(self): + # deletion + def delSlice(vec, start=None, stop=None, step=None): + if start is not None and stop is not None and step is not None: + del vec[start:stop:step] + elif start is not None and stop is None and step is not None: + del vec[start::step] + elif start is None and stop is None and step is not None: + del vec[::step] + v = Vector2(self.v1) + self.assertRaises(TypeError, delSlice, v, None, None, 2) + self.assertRaises(TypeError, delSlice, v, 1, None, 2) + self.assertRaises(TypeError, delSlice, v, 1, 2, 1) + + # assignment + v = Vector2(self.v1) + v[::2] = [-1] + self.assertEqual(v, [-1, self.v1.y]) + v = Vector2(self.v1) + v[::-2] = [10] + self.assertEqual(v, [self.v1.x, 10]) + v = Vector2(self.v1) + v[::-1] = v + self.assertEqual(v, [self.v1.y, self.v1.x]) + a = Vector2(self.v1) + b = Vector2(self.v1) + c = Vector2(self.v1) + a[1:2] = [2.2] + b[slice(1,2)] = [2.2] + c[1:2:] = (2.2,) + self.assertEqual(a, b) + self.assertEqual(a, c) + self.assertEqual(type(a), type(self.v1)) + self.assertEqual(type(b), type(self.v1)) + self.assertEqual(type(c), type(self.v1)) + + def testAdd(self): + v3 = self.v1 + self.v2 + self.assert_(isinstance(v3, type(self.v1))) + self.assertEqual(v3.x, self.v1.x + self.v2.x) + self.assertEqual(v3.y, self.v1.y + self.v2.y) + v3 = self.v1 + self.t2 + self.assert_(isinstance(v3, type(self.v1))) + self.assertEqual(v3.x, self.v1.x + self.t2[0]) + self.assertEqual(v3.y, self.v1.y + self.t2[1]) + v3 = self.v1 + self.l2 + self.assert_(isinstance(v3, type(self.v1))) + self.assertEqual(v3.x, self.v1.x + self.l2[0]) + self.assertEqual(v3.y, self.v1.y + self.l2[1]) + v3 = self.t1 + self.v2 + self.assert_(isinstance(v3, type(self.v1))) + self.assertEqual(v3.x, self.t1[0] + self.v2.x) + self.assertEqual(v3.y, self.t1[1] + self.v2.y) + v3 = self.l1 + self.v2 + self.assert_(isinstance(v3, type(self.v1))) + self.assertEqual(v3.x, self.l1[0] + self.v2.x) + self.assertEqual(v3.y, self.l1[1] + self.v2.y) + + def testSub(self): + v3 = self.v1 - self.v2 + self.assert_(isinstance(v3, type(self.v1))) + self.assertEqual(v3.x, self.v1.x - self.v2.x) + self.assertEqual(v3.y, self.v1.y - self.v2.y) + v3 = self.v1 - self.t2 + self.assert_(isinstance(v3, type(self.v1))) + self.assertEqual(v3.x, self.v1.x - self.t2[0]) + self.assertEqual(v3.y, self.v1.y - self.t2[1]) + v3 = self.v1 - self.l2 + self.assert_(isinstance(v3, type(self.v1))) + self.assertEqual(v3.x, self.v1.x - self.l2[0]) + self.assertEqual(v3.y, self.v1.y - self.l2[1]) + v3 = self.t1 - self.v2 + self.assert_(isinstance(v3, type(self.v1))) + self.assertEqual(v3.x, self.t1[0] - self.v2.x) + self.assertEqual(v3.y, self.t1[1] - self.v2.y) + v3 = self.l1 - self.v2 + self.assert_(isinstance(v3, type(self.v1))) + self.assertEqual(v3.x, self.l1[0] - self.v2.x) + self.assertEqual(v3.y, self.l1[1] - self.v2.y) + + def testScalarMultiplication(self): + v = self.s1 * self.v1 + self.assert_(isinstance(v, type(self.v1))) + self.assertEqual(v.x, self.s1 * self.v1.x) + self.assertEqual(v.y, self.s1 * self.v1.y) + v = self.v1 * self.s2 + self.assertEqual(v.x, self.v1.x * self.s2) + self.assertEqual(v.y, self.v1.y * self.s2) + + def testScalarDivision(self): + v = self.v1 / self.s1 + self.assert_(isinstance(v, type(self.v1))) + self.assertAlmostEqual(v.x, self.v1.x / self.s1) + self.assertAlmostEqual(v.y, self.v1.y / self.s1) + v = self.v1 // self.s2 + self.assert_(isinstance(v, type(self.v1))) + self.assertEqual(v.x, self.v1.x // self.s2) + self.assertEqual(v.y, self.v1.y // self.s2) + + def testBool(self): + self.assertEqual(bool(self.zeroVec), False) + self.assertEqual(bool(self.v1), True) + self.assert_(not self.zeroVec) + self.assert_(self.v1) + + def testUnary(self): + v = +self.v1 + self.assert_(isinstance(v, type(self.v1))) + self.assertEqual(v.x, self.v1.x) + self.assertEqual(v.y, self.v1.y) + self.assertNotEqual(id(v), id(self.v1)) + v = -self.v1 + self.assert_(isinstance(v, type(self.v1))) + self.assertEqual(v.x, -self.v1.x) + self.assertEqual(v.y, -self.v1.y) + self.assertNotEqual(id(v), id(self.v1)) + + def testCompare(self): + int_vec = Vector2(3, -2) + flt_vec = Vector2(3.0, -2.0) + zero_vec = Vector2(0, 0) + self.assertEqual(int_vec == flt_vec, True) + self.assertEqual(int_vec != flt_vec, False) + self.assertEqual(int_vec != zero_vec, True) + self.assertEqual(flt_vec == zero_vec, False) + self.assertEqual(int_vec == (3, -2), True) + self.assertEqual(int_vec != (3, -2), False) + self.assertEqual(int_vec != [0, 0], True) + self.assertEqual(int_vec == [0, 0], False) + self.assertEqual(int_vec != 5, True) + self.assertEqual(int_vec == 5, False) + self.assertEqual(int_vec != [3, -2, 0], True) + self.assertEqual(int_vec == [3, -2, 0], False) + + def testStr(self): + v = Vector2(1.2, 3.4) + self.assertEqual(str(v), "[1.2, 3.4]") + + def testRepr(self): + v = Vector2(1.2, 3.4) + self.assertEqual(v.__repr__(), "") + self.assertEqual(v, Vector2(v.__repr__())) + + def testIter(self): + it = self.v1.__iter__() + # support py2.x and 3.x + if hasattr(it, "next"): + next_ = it.next + elif hasattr(it, "__next__"): + next_ = it.__next__ + else: + self.fail("Iterator has neither a 'next' nor a '__next__' method.") + self.assertEqual(next_(), self.v1[0]) + self.assertEqual(next_(), self.v1[1]) + self.assertRaises(StopIteration, lambda : next_()) + it1 = self.v1.__iter__() + it2 = self.v1.__iter__() + self.assertNotEqual(id(it1), id(it2)) + self.assertEqual(id(it1), id(it1.__iter__())) + self.assertEqual(list(it1), list(it2)); + self.assertEqual(list(self.v1.__iter__()), self.l1) + idx = 0 + for val in self.v1: + self.assertEqual(val, self.v1[idx]) + idx += 1 + + def test_rotate(self): + v1 = Vector2(1, 0) + v2 = v1.rotate(90) + v3 = v1.rotate(90 + 360) + self.assertEqual(v1.x, 1) + self.assertEqual(v1.y, 0) + self.assertEqual(v2.x, 0) + self.assertEqual(v2.y, 1) + self.assertEqual(v3.x, v2.x) + self.assertEqual(v3.y, v2.y) + v1 = Vector2(-1, -1) + v2 = v1.rotate(-90) + self.assertEqual(v2.x, -1) + self.assertEqual(v2.y, 1) + v2 = v1.rotate(360) + self.assertEqual(v1.x, v2.x) + self.assertEqual(v1.y, v2.y) + v2 = v1.rotate(0) + self.assertEqual(v1.x, v2.x) + self.assertEqual(v1.y, v2.y) + # issue 214 + self.assertEqual(Vector2(0, 1).rotate(359.99999999), Vector2(0, 1)) + + def test_rotate_ip(self): + v = Vector2(1, 0) + self.assertEqual(v.rotate_ip(90), None) + self.assertEqual(v.x, 0) + self.assertEqual(v.y, 1) + v = Vector2(-1, -1) + v.rotate_ip(-90) + self.assertEqual(v.x, -1) + self.assertEqual(v.y, 1) + + def test_normalize(self): + v = self.v1.normalize() + # length is 1 + self.assertAlmostEqual(v.x * v.x + v.y * v.y, 1.) + # v1 is unchanged + self.assertEqual(self.v1.x, self.l1[0]) + self.assertEqual(self.v1.y, self.l1[1]) + # v2 is paralell to v1 + self.assertAlmostEqual(self.v1.x * v.y - self.v1.y * v.x, 0.) + self.assertRaises(ValueError, lambda : self.zeroVec.normalize()) + + def test_normalize_ip(self): + v = +self.v1 + # v has length != 1 before normalizing + self.assertNotEqual(v.x * v.x + v.y * v.y, 1.) + # inplace operations should return None + self.assertEqual(v.normalize_ip(), None) + # length is 1 + self.assertAlmostEqual(v.x * v.x + v.y * v.y, 1.) + # v2 is paralell to v1 + self.assertAlmostEqual(self.v1.x * v.y - self.v1.y * v.x, 0.) + self.assertRaises(ValueError, lambda : self.zeroVec.normalize_ip()) + + def test_is_normalized(self): + self.assertEqual(self.v1.is_normalized(), False) + v = self.v1.normalize() + self.assertEqual(v.is_normalized(), True) + self.assertEqual(self.e2.is_normalized(), True) + self.assertEqual(self.zeroVec.is_normalized(), False) + + def test_cross(self): + self.assertEqual(self.v1.cross(self.v2), + self.v1.x * self.v2.y - self.v1.y * self.v2.x) + self.assertEqual(self.v1.cross(self.l2), + self.v1.x * self.l2[1] - self.v1.y * self.l2[0]) + self.assertEqual(self.v1.cross(self.t2), + self.v1.x * self.t2[1] - self.v1.y * self.t2[0]) + self.assertEqual(self.v1.cross(self.v2), -self.v2.cross(self.v1)) + self.assertEqual(self.v1.cross(self.v1), 0) + + def test_dot(self): + self.assertAlmostEqual(self.v1.dot(self.v2), + self.v1.x * self.v2.x + self.v1.y * self.v2.y) + self.assertAlmostEqual(self.v1.dot(self.l2), + self.v1.x * self.l2[0] + self.v1.y * self.l2[1]) + self.assertAlmostEqual(self.v1.dot(self.t2), + self.v1.x * self.t2[0] + self.v1.y * self.t2[1]) + self.assertEqual(self.v1.dot(self.v2), self.v2.dot(self.v1)) + self.assertEqual(self.v1.dot(self.v2), self.v1 * self.v2) + + def test_angle_to(self): + self.assertEqual(self.v1.rotate(self.v1.angle_to(self.v2)).normalize(), + self.v2.normalize()) + self.assertEqual(Vector2(1, 1).angle_to((-1, 1)), 90) + self.assertEqual(Vector2(1, 0).angle_to((0, -1)), -90) + self.assertEqual(Vector2(1, 0).angle_to((-1, 1)), 135) + self.assertEqual(abs(Vector2(1, 0).angle_to((-1, 0))), 180) + + def test_scale_to_length(self): + v = Vector2(1, 1) + v.scale_to_length(2.5) + self.assertEqual(v, Vector2(2.5, 2.5) / math.sqrt(2)) + self.assertRaises(ValueError, lambda : self.zeroVec.scale_to_length(1)) + self.assertEqual(v.scale_to_length(0), None) + self.assertEqual(v, self.zeroVec) + + def test_length(self): + self.assertEqual(Vector2(3, 4).length(), 5) + self.assertEqual(Vector2(-3, 4).length(), 5) + self.assertEqual(self.zeroVec.length(), 0) + + def test_length_squared(self): + self.assertEqual(Vector2(3, 4).length_squared(), 25) + self.assertEqual(Vector2(-3, 4).length_squared(), 25) + self.assertEqual(self.zeroVec.length_squared(), 0) + + def test_reflect(self): + v = Vector2(1, -1) + n = Vector2(0, 1) + self.assertEqual(v.reflect(n), Vector2(1, 1)) + self.assertEqual(v.reflect(3*n), v.reflect(n)) + self.assertEqual(v.reflect(-v), -v) + self.assertRaises(ValueError, lambda : v.reflect(self.zeroVec)) + + def test_reflect_ip(self): + v1 = Vector2(1, -1) + v2 = Vector2(v1) + n = Vector2(0, 1) + self.assertEqual(v2.reflect_ip(n), None) + self.assertEqual(v2, Vector2(1, 1)) + v2 = Vector2(v1) + v2.reflect_ip(3*n) + self.assertEqual(v2, v1.reflect(n)) + v2 = Vector2(v1) + v2.reflect_ip(-v1) + self.assertEqual(v2, -v1) + self.assertRaises(ValueError, lambda : v2.reflect_ip(Vector2())) + + def test_distance_to(self): + diff = self.v1 - self.v2 + self.assertEqual(self.e1.distance_to(self.e2), math.sqrt(2)) + self.assertAlmostEqual(self.v1.distance_to(self.v2), + math.sqrt(diff.x * diff.x + diff.y * diff.y)) + self.assertEqual(self.v1.distance_to(self.v1), 0) + self.assertEqual(self.v1.distance_to(self.v2), + self.v2.distance_to(self.v1)) + + def test_distance_squared_to(self): + diff = self.v1 - self.v2 + self.assertEqual(self.e1.distance_squared_to(self.e2), 2) + self.assertAlmostEqual(self.v1.distance_squared_to(self.v2), + diff.x * diff.x + diff.y * diff.y) + self.assertEqual(self.v1.distance_squared_to(self.v1), 0) + self.assertEqual(self.v1.distance_squared_to(self.v2), + self.v2.distance_squared_to(self.v1)) + + def testSwizzle(self): + self.assertEquals(hasattr(pygame.math, "enable_swizzling"), True) + self.assertEquals(hasattr(pygame.math, "disable_swizzling"), True) + # swizzling disabled by default + self.assertRaises(AttributeError, lambda : self.v1.yx) + pygame.math.enable_swizzling() + + self.assertEqual(self.v1.yx, (self.v1.y, self.v1.x)) + self.assertEqual(self.v1.xxyyxy, (self.v1.x, self.v1.x, self.v1.y, + self.v1.y, self.v1.x, self.v1.y)) + self.v1.xy = self.t2 + self.assertEqual(self.v1, self.t2) + self.v1.yx = self.t2 + self.assertEqual(self.v1, (self.t2[1], self.t2[0])) + self.assertEqual(type(self.v1), Vector2) + def invalidSwizzleX(): + Vector2().xx = (1, 2) + def invalidSwizzleY(): + Vector2().yy = (1, 2) + self.assertRaises(AttributeError, invalidSwizzleX) + self.assertRaises(AttributeError, invalidSwizzleY) + def invalidAssignment(): + Vector2().xy = 3 + self.assertRaises(TypeError, invalidAssignment) + def unicodeAttribute(): + getattr(Vector2(), "รค") + self.assertRaises(AttributeError, unicodeAttribute) + + def test_elementwise(self): + # behaviour for "elementwise op scalar" + self.assertEqual(self.v1.elementwise() + self.s1, + (self.v1.x + self.s1, self.v1.y + self.s1)) + self.assertEqual(self.v1.elementwise() - self.s1, + (self.v1.x - self.s1, self.v1.y - self.s1)) + self.assertEqual(self.v1.elementwise() * self.s2, + (self.v1.x * self.s2, self.v1.y * self.s2)) + self.assertEqual(self.v1.elementwise() / self.s2, + (self.v1.x / self.s2, self.v1.y / self.s2)) + self.assertEqual(self.v1.elementwise() // self.s1, + (self.v1.x // self.s1, self.v1.y // self.s1)) + self.assertEqual(self.v1.elementwise() ** self.s1, + (self.v1.x ** self.s1, self.v1.y ** self.s1)) + self.assertEqual(self.v1.elementwise() % self.s1, + (self.v1.x % self.s1, self.v1.y % self.s1)) + self.assertEqual(self.v1.elementwise() > self.s1, + self.v1.x > self.s1 and self.v1.y > self.s1) + self.assertEqual(self.v1.elementwise() < self.s1, + self.v1.x < self.s1 and self.v1.y < self.s1) + self.assertEqual(self.v1.elementwise() == self.s1, + self.v1.x == self.s1 and self.v1.y == self.s1) + self.assertEqual(self.v1.elementwise() != self.s1, + self.v1.x != self.s1 and self.v1.y != self.s1) + self.assertEqual(self.v1.elementwise() >= self.s1, + self.v1.x >= self.s1 and self.v1.y >= self.s1) + self.assertEqual(self.v1.elementwise() <= self.s1, + self.v1.x <= self.s1 and self.v1.y <= self.s1) + self.assertEqual(self.v1.elementwise() != self.s1, + self.v1.x != self.s1 and self.v1.y != self.s1) + # behaviour for "scalar op elementwise" + self.assertEqual(5 + self.v1.elementwise(), Vector2(5, 5) + self.v1) + self.assertEqual(3.5 - self.v1.elementwise(), Vector2(3.5, 3.5) - self.v1) + self.assertEqual(7.5 * self.v1.elementwise() , 7.5 * self.v1) + self.assertEqual(-3.5 / self.v1.elementwise(), (-3.5 / self.v1.x, -3.5 / self.v1.y)) + self.assertEqual(-3.5 // self.v1.elementwise(), (-3.5 // self.v1.x, -3.5 // self.v1.y)) + self.assertEqual(-3.5 ** self.v1.elementwise(), (-3.5 ** self.v1.x, -3.5 ** self.v1.y)) + self.assertEqual(3 % self.v1.elementwise(), (3 % self.v1.x, 3 % self.v1.y)) + self.assertEqual(2 < self.v1.elementwise(), 2 < self.v1.x and 2 < self.v1.y) + self.assertEqual(2 > self.v1.elementwise(), 2 > self.v1.x and 2 > self.v1.y) + self.assertEqual(1 == self.v1.elementwise(), 1 == self.v1.x and 1 == self.v1.y) + self.assertEqual(1 != self.v1.elementwise(), 1 != self.v1.x and 1 != self.v1.y) + self.assertEqual(2 <= self.v1.elementwise(), 2 <= self.v1.x and 2 <= self.v1.y) + self.assertEqual(-7 >= self.v1.elementwise(), -7 >= self.v1.x and -7 >= self.v1.y) + self.assertEqual(-7 != self.v1.elementwise(), -7 != self.v1.x and -7 != self.v1.y) + + # behaviour for "elementwise op vector" + self.assertEqual(type(self.v1.elementwise() * self.v2), type(self.v1)) + self.assertEqual(self.v1.elementwise() + self.v2, self.v1 + self.v2) + self.assertEqual(self.v1.elementwise() + self.v2, self.v1 + self.v2) + self.assertEqual(self.v1.elementwise() - self.v2, self.v1 - self.v2) + self.assertEqual(self.v1.elementwise() * self.v2, (self.v1.x * self.v2.x, self.v1.y * self.v2.y)) + self.assertEqual(self.v1.elementwise() / self.v2, (self.v1.x / self.v2.x, self.v1.y / self.v2.y)) + self.assertEqual(self.v1.elementwise() // self.v2, (self.v1.x // self.v2.x, self.v1.y // self.v2.y)) + self.assertEqual(self.v1.elementwise() ** self.v2, (self.v1.x ** self.v2.x, self.v1.y ** self.v2.y)) + self.assertEqual(self.v1.elementwise() % self.v2, (self.v1.x % self.v2.x, self.v1.y % self.v2.y)) + self.assertEqual(self.v1.elementwise() > self.v2, self.v1.x > self.v2.x and self.v1.y > self.v2.y) + self.assertEqual(self.v1.elementwise() < self.v2, self.v1.x < self.v2.x and self.v1.y < self.v2.y) + self.assertEqual(self.v1.elementwise() >= self.v2, self.v1.x >= self.v2.x and self.v1.y >= self.v2.y) + self.assertEqual(self.v1.elementwise() <= self.v2, self.v1.x <= self.v2.x and self.v1.y <= self.v2.y) + self.assertEqual(self.v1.elementwise() == self.v2, self.v1.x == self.v2.x and self.v1.y == self.v2.y) + self.assertEqual(self.v1.elementwise() != self.v2, self.v1.x != self.v2.x and self.v1.y != self.v2.y) + # behaviour for "vector op elementwise" + self.assertEqual(self.v2 + self.v1.elementwise(), self.v2 + self.v1) + self.assertEqual(self.v2 - self.v1.elementwise(), self.v2 - self.v1) + self.assertEqual(self.v2 * self.v1.elementwise(), (self.v2.x * self.v1.x, self.v2.y * self.v1.y)) + self.assertEqual(self.v2 / self.v1.elementwise(), (self.v2.x / self.v1.x, self.v2.y / self.v1.y)) + self.assertEqual(self.v2 // self.v1.elementwise(), (self.v2.x // self.v1.x, self.v2.y // self.v1.y)) + self.assertEqual(self.v2 ** self.v1.elementwise(), (self.v2.x ** self.v1.x, self.v2.y ** self.v1.y)) + self.assertEqual(self.v2 % self.v1.elementwise(), (self.v2.x % self.v1.x, self.v2.y % self.v1.y)) + self.assertEqual(self.v2 < self.v1.elementwise(), self.v2.x < self.v1.x and self.v2.y < self.v1.y) + self.assertEqual(self.v2 > self.v1.elementwise(), self.v2.x > self.v1.x and self.v2.y > self.v1.y) + self.assertEqual(self.v2 <= self.v1.elementwise(), self.v2.x <= self.v1.x and self.v2.y <= self.v1.y) + self.assertEqual(self.v2 >= self.v1.elementwise(), self.v2.x >= self.v1.x and self.v2.y >= self.v1.y) + self.assertEqual(self.v2 == self.v1.elementwise(), self.v2.x == self.v1.x and self.v2.y == self.v1.y) + self.assertEqual(self.v2 != self.v1.elementwise(), self.v2.x != self.v1.x and self.v2.y != self.v1.y) + + # behaviour for "elementwise op elementwise" + self.assertEqual(self.v2.elementwise() + self.v1.elementwise(), self.v2 + self.v1) + self.assertEqual(self.v2.elementwise() - self.v1.elementwise(), self.v2 - self.v1) + self.assertEqual(self.v2.elementwise() * self.v1.elementwise(), (self.v2.x * self.v1.x, self.v2.y * self.v1.y)) + self.assertEqual(self.v2.elementwise() / self.v1.elementwise(), (self.v2.x / self.v1.x, self.v2.y / self.v1.y)) + self.assertEqual(self.v2.elementwise() // self.v1.elementwise(), (self.v2.x // self.v1.x, self.v2.y // self.v1.y)) + self.assertEqual(self.v2.elementwise() ** self.v1.elementwise(), (self.v2.x ** self.v1.x, self.v2.y ** self.v1.y)) + self.assertEqual(self.v2.elementwise() % self.v1.elementwise(), (self.v2.x % self.v1.x, self.v2.y % self.v1.y)) + self.assertEqual(self.v2.elementwise() < self.v1.elementwise(), self.v2.x < self.v1.x and self.v2.y < self.v1.y) + self.assertEqual(self.v2.elementwise() > self.v1.elementwise(), self.v2.x > self.v1.x and self.v2.y > self.v1.y) + self.assertEqual(self.v2.elementwise() <= self.v1.elementwise(), self.v2.x <= self.v1.x and self.v2.y <= self.v1.y) + self.assertEqual(self.v2.elementwise() >= self.v1.elementwise(), self.v2.x >= self.v1.x and self.v2.y >= self.v1.y) + self.assertEqual(self.v2.elementwise() == self.v1.elementwise(), self.v2.x == self.v1.x and self.v2.y == self.v1.y) + self.assertEqual(self.v2.elementwise() != self.v1.elementwise(), self.v2.x != self.v1.x and self.v2.y != self.v1.y) + + # other behaviour + self.assertEqual(abs(self.v1.elementwise()), (abs(self.v1.x), abs(self.v1.y))) + self.assertEqual(-self.v1.elementwise(), -self.v1) + self.assertEqual(+self.v1.elementwise(), +self.v1) + self.assertEqual(bool(self.v1.elementwise()), bool(self.v1)) + self.assertEqual(bool(Vector2().elementwise()), bool(Vector2())) + self.assertEqual(self.zeroVec.elementwise() ** 0, (1, 1)) + self.assertRaises(ValueError, lambda : pow(Vector2(-1, 0).elementwise(), 1.2)) + self.assertRaises(ZeroDivisionError, lambda : self.zeroVec.elementwise() ** -1) + + def test_elementwise(self): + v1 = self.v1 + v2 = self.v2 + s1 = self.s1 + s2 = self.s2 + # behaviour for "elementwise op scalar" + self.assertEqual(v1.elementwise() + s1, (v1.x + s1, v1.y + s1)) + self.assertEqual(v1.elementwise() - s1, (v1.x - s1, v1.y - s1)) + self.assertEqual(v1.elementwise() * s2, (v1.x * s2, v1.y * s2)) + self.assertEqual(v1.elementwise() / s2, (v1.x / s2, v1.y / s2)) + self.assertEqual(v1.elementwise() // s1, (v1.x // s1, v1.y // s1)) + self.assertEqual(v1.elementwise() ** s1, (v1.x ** s1, v1.y ** s1)) + self.assertEqual(v1.elementwise() % s1, (v1.x % s1, v1.y % s1)) + self.assertEqual(v1.elementwise() > s1, v1.x > s1 and v1.y > s1) + self.assertEqual(v1.elementwise() < s1, v1.x < s1 and v1.y < s1) + self.assertEqual(v1.elementwise() == s1, v1.x == s1 and v1.y == s1) + self.assertEqual(v1.elementwise() != s1, v1.x != s1 and v1.y != s1) + self.assertEqual(v1.elementwise() >= s1, v1.x >= s1 and v1.y >= s1) + self.assertEqual(v1.elementwise() <= s1, v1.x <= s1 and v1.y <= s1) + self.assertEqual(v1.elementwise() != s1, v1.x != s1 and v1.y != s1) + # behaviour for "scalar op elementwise" + self.assertEqual(s1 + v1.elementwise(), (s1 + v1.x, s1 + v1.y)) + self.assertEqual(s1 - v1.elementwise(), (s1 - v1.x, s1 - v1.y)) + self.assertEqual(s1 * v1.elementwise(), (s1 * v1.x, s1 * v1.y)) + self.assertEqual(s1 / v1.elementwise(), (s1 / v1.x, s1 / v1.y)) + self.assertEqual(s1 // v1.elementwise(), (s1 // v1.x, s1 // v1.y)) + self.assertEqual(s1 ** v1.elementwise(), (s1 ** v1.x, s1 ** v1.y)) + self.assertEqual(s1 % v1.elementwise(), (s1 % v1.x, s1 % v1.y)) + self.assertEqual(s1 < v1.elementwise(), s1 < v1.x and s1 < v1.y) + self.assertEqual(s1 > v1.elementwise(), s1 > v1.x and s1 > v1.y) + self.assertEqual(s1 == v1.elementwise(), s1 == v1.x and s1 == v1.y) + self.assertEqual(s1 != v1.elementwise(), s1 != v1.x and s1 != v1.y) + self.assertEqual(s1 <= v1.elementwise(), s1 <= v1.x and s1 <= v1.y) + self.assertEqual(s1 >= v1.elementwise(), s1 >= v1.x and s1 >= v1.y) + self.assertEqual(s1 != v1.elementwise(), s1 != v1.x and s1 != v1.y) + + # behaviour for "elementwise op vector" + self.assertEqual(type(v1.elementwise() * v2), type(v1)) + self.assertEqual(v1.elementwise() + v2, v1 + v2) + self.assertEqual(v1.elementwise() - v2, v1 - v2) + self.assertEqual(v1.elementwise() * v2, (v1.x * v2.x, v1.y * v2.y)) + self.assertEqual(v1.elementwise() / v2, (v1.x / v2.x, v1.y / v2.y)) + self.assertEqual(v1.elementwise() // v2, (v1.x // v2.x, v1.y // v2.y)) + self.assertEqual(v1.elementwise() ** v2, (v1.x ** v2.x, v1.y ** v2.y)) + self.assertEqual(v1.elementwise() % v2, (v1.x % v2.x, v1.y % v2.y)) + self.assertEqual(v1.elementwise() > v2, v1.x > v2.x and v1.y > v2.y) + self.assertEqual(v1.elementwise() < v2, v1.x < v2.x and v1.y < v2.y) + self.assertEqual(v1.elementwise() >= v2, v1.x >= v2.x and v1.y >= v2.y) + self.assertEqual(v1.elementwise() <= v2, v1.x <= v2.x and v1.y <= v2.y) + self.assertEqual(v1.elementwise() == v2, v1.x == v2.x and v1.y == v2.y) + self.assertEqual(v1.elementwise() != v2, v1.x != v2.x and v1.y != v2.y) + # behaviour for "vector op elementwise" + self.assertEqual(v2 + v1.elementwise(), v2 + v1) + self.assertEqual(v2 - v1.elementwise(), v2 - v1) + self.assertEqual(v2 * v1.elementwise(), (v2.x * v1.x, v2.y * v1.y)) + self.assertEqual(v2 / v1.elementwise(), (v2.x / v1.x, v2.y / v1.y)) + self.assertEqual(v2 // v1.elementwise(), (v2.x // v1.x, v2.y // v1.y)) + self.assertEqual(v2 ** v1.elementwise(), (v2.x ** v1.x, v2.y ** v1.y)) + self.assertEqual(v2 % v1.elementwise(), (v2.x % v1.x, v2.y % v1.y)) + self.assertEqual(v2 < v1.elementwise(), v2.x < v1.x and v2.y < v1.y) + self.assertEqual(v2 > v1.elementwise(), v2.x > v1.x and v2.y > v1.y) + self.assertEqual(v2 <= v1.elementwise(), v2.x <= v1.x and v2.y <= v1.y) + self.assertEqual(v2 >= v1.elementwise(), v2.x >= v1.x and v2.y >= v1.y) + self.assertEqual(v2 == v1.elementwise(), v2.x == v1.x and v2.y == v1.y) + self.assertEqual(v2 != v1.elementwise(), v2.x != v1.x and v2.y != v1.y) + + # behaviour for "elementwise op elementwise" + self.assertEqual(v2.elementwise() + v1.elementwise(), v2 + v1) + self.assertEqual(v2.elementwise() - v1.elementwise(), v2 - v1) + self.assertEqual(v2.elementwise() * v1.elementwise(), (v2.x * v1.x, v2.y * v1.y)) + self.assertEqual(v2.elementwise() / v1.elementwise(), (v2.x / v1.x, v2.y / v1.y)) + self.assertEqual(v2.elementwise() // v1.elementwise(), (v2.x // v1.x, v2.y // v1.y)) + self.assertEqual(v2.elementwise() ** v1.elementwise(), (v2.x ** v1.x, v2.y ** v1.y)) + self.assertEqual(v2.elementwise() % v1.elementwise(), (v2.x % v1.x, v2.y % v1.y)) + self.assertEqual(v2.elementwise() < v1.elementwise(), v2.x < v1.x and v2.y < v1.y) + self.assertEqual(v2.elementwise() > v1.elementwise(), v2.x > v1.x and v2.y > v1.y) + self.assertEqual(v2.elementwise() <= v1.elementwise(), v2.x <= v1.x and v2.y <= v1.y) + self.assertEqual(v2.elementwise() >= v1.elementwise(), v2.x >= v1.x and v2.y >= v1.y) + self.assertEqual(v2.elementwise() == v1.elementwise(), v2.x == v1.x and v2.y == v1.y) + self.assertEqual(v2.elementwise() != v1.elementwise(), v2.x != v1.x and v2.y != v1.y) + + # other behaviour + self.assertEqual(abs(v1.elementwise()), (abs(v1.x), abs(v1.y))) + self.assertEqual(-v1.elementwise(), -v1) + self.assertEqual(+v1.elementwise(), +v1) + self.assertEqual(bool(v1.elementwise()), bool(v1)) + self.assertEqual(bool(Vector2().elementwise()), bool(Vector2())) + self.assertEqual(self.zeroVec.elementwise() ** 0, (1, 1)) + self.assertRaises(ValueError, lambda : pow(Vector2(-1, 0).elementwise(), 1.2)) + self.assertRaises(ZeroDivisionError, lambda : self.zeroVec.elementwise() ** -1) + self.assertRaises(ZeroDivisionError, lambda : self.zeroVec.elementwise() ** -1) + self.assertRaises(ZeroDivisionError, lambda : Vector2(1,1).elementwise() / 0) + self.assertRaises(ZeroDivisionError, lambda : Vector2(1,1).elementwise() // 0) + self.assertRaises(ZeroDivisionError, lambda : Vector2(1,1).elementwise() % 0) + self.assertRaises(ZeroDivisionError, lambda : Vector2(1,1).elementwise() / self.zeroVec) + self.assertRaises(ZeroDivisionError, lambda : Vector2(1,1).elementwise() // self.zeroVec) + self.assertRaises(ZeroDivisionError, lambda : Vector2(1,1).elementwise() % self.zeroVec) + self.assertRaises(ZeroDivisionError, lambda : 2 / self.zeroVec.elementwise()) + self.assertRaises(ZeroDivisionError, lambda : 2 // self.zeroVec.elementwise()) + self.assertRaises(ZeroDivisionError, lambda : 2 % self.zeroVec.elementwise()) + + def test_slerp(self): + self.assertRaises(ValueError, lambda : self.zeroVec.slerp(self.v1, .5)) + self.assertRaises(ValueError, lambda : self.v1.slerp(self.zeroVec, .5)) + self.assertRaises(ValueError, + lambda : self.zeroVec.slerp(self.zeroVec, .5)) + v1 = Vector2(1, 0) + v2 = Vector2(0, 1) + steps = 10 + angle_step = v1.angle_to(v2) / steps + for i, u in ((i, v1.slerp(v2, i/float(steps))) for i in range(steps+1)): + self.assertAlmostEqual(u.length(), 1) + self.assertAlmostEqual(v1.angle_to(u), i * angle_step) + self.assertEqual(u, v2) + + v1 = Vector2(100, 0) + v2 = Vector2(0, 10) + radial_factor = v2.length() / v1.length() + for i, u in ((i, v1.slerp(v2, -i/float(steps))) for i in range(steps+1)): + self.assertAlmostEqual(u.length(), (v2.length() - v1.length()) * (float(i)/steps) + v1.length()) + self.assertEqual(u, v2) + self.assertEqual(v1.slerp(v1, .5), v1) + self.assertEqual(v2.slerp(v2, .5), v2) + self.assertRaises(ValueError, lambda : v1.slerp(-v1, 0.5)) + + def test_lerp(self): + """TODO""" + pass + + def test_polar(self): + v = Vector2() + v.from_polar(self.v1.as_polar()) + self.assertEqual(self.v1, v) + self.assertEqual(self.e1.as_polar(), (1, 0)) + self.assertEqual(self.e2.as_polar(), (1, 90)) + self.assertEqual((2 * self.e2).as_polar(), (2, 90)) + self.assertRaises(TypeError, lambda : v.from_polar((None, None))) + self.assertRaises(TypeError, lambda : v.from_polar("ab")) + self.assertRaises(TypeError, lambda : v.from_polar((None, 1))) + self.assertRaises(TypeError, lambda : v.from_polar((1, 2, 3))) + self.assertRaises(TypeError, lambda : v.from_polar((1,))) + self.assertRaises(TypeError, lambda : v.from_polar(1, 2)) + v.from_polar((.5, 90)) + self.assertEqual(v, .5 * self.e2) + v.from_polar((1, 0)) + self.assertEqual(v, self.e1) + + + + + +class Vector3TypeTest(unittest.TestCase): + def setUp(self): +# gc.collect() + self.zeroVec = Vector3() + self.e1 = Vector3(1, 0, 0) + self.e2 = Vector3(0, 1, 0) + self.e3 = Vector3(0, 0, 1) +# self.t1 = (random(), random()) + self.t1 = (1.2, 3.4, 9.6) + self.l1 = list(self.t1) + self.v1 = Vector3(self.t1) +# self.t2 = (random(), random()) + self.t2 = (5.6, 7.8, 2.1) + self.l2 = list(self.t2) + self.v2 = Vector3(self.t2) +# self.s1 = random() +# self.s2 = random() + self.s1 = 5.6 + self.s2 = 7.8 + + def testConstructionDefault(self): + v = Vector3() + self.assertEqual(v.x, 0.) + self.assertEqual(v.y, 0.) + self.assertEqual(v.z, 0.) + + + def testConstructionXYZ(self): + v = Vector3(1.2, 3.4, 9.6) + self.assertEqual(v.x, 1.2) + self.assertEqual(v.y, 3.4) + self.assertEqual(v.z, 9.6) + + def testConstructionTuple(self): + v = Vector3((1.2, 3.4, 9.6)) + self.assertEqual(v.x, 1.2) + self.assertEqual(v.y, 3.4) + self.assertEqual(v.z, 9.6) + + def testConstructionList(self): + v = Vector3([1.2, 3.4, -9.6]) + self.assertEqual(v.x, 1.2) + self.assertEqual(v.y, 3.4) + self.assertEqual(v.z, -9.6) + + def testConstructionVector3(self): + v = Vector3(Vector3(1.2, 3.4, -9.6)) + self.assertEqual(v.x, 1.2) + self.assertEqual(v.y, 3.4) + self.assertEqual(v.z, -9.6) + + def testAttributAccess(self): + tmp = self.v1.x + self.assertEqual(tmp, self.v1.x) + self.assertEqual(tmp, self.v1[0]) + tmp = self.v1.y + self.assertEqual(tmp, self.v1.y) + self.assertEqual(tmp, self.v1[1]) + tmp = self.v1.z + self.assertEqual(tmp, self.v1.z) + self.assertEqual(tmp, self.v1[2]) + self.v1.x = 3.141 + self.assertEqual(self.v1.x, 3.141) + self.v1.y = 3.141 + self.assertEqual(self.v1.y, 3.141) + self.v1.z = 3.141 + self.assertEqual(self.v1.z, 3.141) + def assign_nonfloat(): + v = Vector2() + v.x = "spam" + self.assertRaises(TypeError, assign_nonfloat) + + def testSequence(self): + v = Vector3(1.2, 3.4, -9.6) + self.assertEqual(len(v), 3) + self.assertEqual(v[0], 1.2) + self.assertEqual(v[1], 3.4) + self.assertEqual(v[2], -9.6) + self.assertRaises(IndexError, lambda : v[3]) + self.assertEqual(v[-1], -9.6) + self.assertEqual(v[-2], 3.4) + self.assertEqual(v[-3], 1.2) + self.assertRaises(IndexError, lambda : v[-4]) + self.assertEqual(v[:], [1.2, 3.4, -9.6]) + self.assertEqual(v[1:], [3.4, -9.6]) + self.assertEqual(v[:1], [1.2]) + self.assertEqual(v[:-1], [1.2, 3.4]) + self.assertEqual(v[1:2], [3.4]) + self.assertEqual(list(v), [1.2, 3.4, -9.6]) + self.assertEqual(tuple(v), (1.2, 3.4, -9.6)) + v[0] = 5.6 + v[1] = 7.8 + v[2] = -2.1 + self.assertEqual(v.x, 5.6) + self.assertEqual(v.y, 7.8) + self.assertEqual(v.z, -2.1) + v[:] = [9.1, 11.12, -13.41] + self.assertEqual(v.x, 9.1) + self.assertEqual(v.y, 11.12) + self.assertEqual(v.z, -13.41) + def overpopulate(): + v = Vector3() + v[:] = [1, 2, 3, 4] + self.assertRaises(ValueError, overpopulate) + def underpopulate(): + v = Vector3() + v[:] = [1] + self.assertRaises(ValueError, underpopulate) + def assign_nonfloat(): + v = Vector2() + v[0] = "spam" + self.assertRaises(TypeError, assign_nonfloat) + + def testExtendedSlicing(self): + # deletion + def delSlice(vec, start=None, stop=None, step=None): + if start is not None and stop is not None and step is not None: + del vec[start:stop:step] + elif start is not None and stop is None and step is not None: + del vec[start::step] + elif start is None and stop is None and step is not None: + del vec[::step] + v = Vector3(self.v1) + self.assertRaises(TypeError, delSlice, v, None, None, 2) + self.assertRaises(TypeError, delSlice, v, 1, None, 2) + self.assertRaises(TypeError, delSlice, v, 1, 2, 1) + + # assignment + v = Vector3(self.v1) + v[::2] = [-1.1, -2.2] + self.assertEqual(v, [-1.1, self.v1.y, -2.2]) + v = Vector3(self.v1) + v[::-2] = [10, 20] + self.assertEqual(v, [20, self.v1.y, 10]) + v = Vector3(self.v1) + v[::-1] = v + self.assertEqual(v, [self.v1.z, self.v1.y, self.v1.x]) + a = Vector3(self.v1) + b = Vector3(self.v1) + c = Vector3(self.v1) + a[1:2] = [2.2] + b[slice(1,2)] = [2.2] + c[1:2:] = (2.2,) + self.assertEqual(a, b) + self.assertEqual(a, c) + self.assertEqual(type(a), type(self.v1)) + self.assertEqual(type(b), type(self.v1)) + self.assertEqual(type(c), type(self.v1)) + + def testAdd(self): + v3 = self.v1 + self.v2 + self.assert_(isinstance(v3, type(self.v1))) + self.assertEqual(v3.x, self.v1.x + self.v2.x) + self.assertEqual(v3.y, self.v1.y + self.v2.y) + self.assertEqual(v3.z, self.v1.z + self.v2.z) + v3 = self.v1 + self.t2 + self.assert_(isinstance(v3, type(self.v1))) + self.assertEqual(v3.x, self.v1.x + self.t2[0]) + self.assertEqual(v3.y, self.v1.y + self.t2[1]) + self.assertEqual(v3.z, self.v1.z + self.t2[2]) + v3 = self.v1 + self.l2 + self.assert_(isinstance(v3, type(self.v1))) + self.assertEqual(v3.x, self.v1.x + self.l2[0]) + self.assertEqual(v3.y, self.v1.y + self.l2[1]) + self.assertEqual(v3.z, self.v1.z + self.l2[2]) + v3 = self.t1 + self.v2 + self.assert_(isinstance(v3, type(self.v1))) + self.assertEqual(v3.x, self.t1[0] + self.v2.x) + self.assertEqual(v3.y, self.t1[1] + self.v2.y) + self.assertEqual(v3.z, self.t1[2] + self.v2.z) + v3 = self.l1 + self.v2 + self.assert_(isinstance(v3, type(self.v1))) + self.assertEqual(v3.x, self.l1[0] + self.v2.x) + self.assertEqual(v3.y, self.l1[1] + self.v2.y) + self.assertEqual(v3.z, self.l1[2] + self.v2.z) + + def testSub(self): + v3 = self.v1 - self.v2 + self.assert_(isinstance(v3, type(self.v1))) + self.assertEqual(v3.x, self.v1.x - self.v2.x) + self.assertEqual(v3.y, self.v1.y - self.v2.y) + self.assertEqual(v3.z, self.v1.z - self.v2.z) + v3 = self.v1 - self.t2 + self.assert_(isinstance(v3, type(self.v1))) + self.assertEqual(v3.x, self.v1.x - self.t2[0]) + self.assertEqual(v3.y, self.v1.y - self.t2[1]) + self.assertEqual(v3.z, self.v1.z - self.t2[2]) + v3 = self.v1 - self.l2 + self.assert_(isinstance(v3, type(self.v1))) + self.assertEqual(v3.x, self.v1.x - self.l2[0]) + self.assertEqual(v3.y, self.v1.y - self.l2[1]) + self.assertEqual(v3.z, self.v1.z - self.l2[2]) + v3 = self.t1 - self.v2 + self.assert_(isinstance(v3, type(self.v1))) + self.assertEqual(v3.x, self.t1[0] - self.v2.x) + self.assertEqual(v3.y, self.t1[1] - self.v2.y) + self.assertEqual(v3.z, self.t1[2] - self.v2.z) + v3 = self.l1 - self.v2 + self.assert_(isinstance(v3, type(self.v1))) + self.assertEqual(v3.x, self.l1[0] - self.v2.x) + self.assertEqual(v3.y, self.l1[1] - self.v2.y) + self.assertEqual(v3.z, self.l1[2] - self.v2.z) + + def testScalarMultiplication(self): + v = self.s1 * self.v1 + self.assert_(isinstance(v, type(self.v1))) + self.assertEqual(v.x, self.s1 * self.v1.x) + self.assertEqual(v.y, self.s1 * self.v1.y) + self.assertEqual(v.z, self.s1 * self.v1.z) + v = self.v1 * self.s2 + self.assertEqual(v.x, self.v1.x * self.s2) + self.assertEqual(v.y, self.v1.y * self.s2) + self.assertEqual(v.z, self.v1.z * self.s2) + + def testScalarDivision(self): + v = self.v1 / self.s1 + self.assert_(isinstance(v, type(self.v1))) + self.assertAlmostEqual(v.x, self.v1.x / self.s1) + self.assertAlmostEqual(v.y, self.v1.y / self.s1) + self.assertAlmostEqual(v.z, self.v1.z / self.s1) + v = self.v1 // self.s2 + self.assert_(isinstance(v, type(self.v1))) + self.assertEqual(v.x, self.v1.x // self.s2) + self.assertEqual(v.y, self.v1.y // self.s2) + self.assertEqual(v.z, self.v1.z // self.s2) + + def testBool(self): + self.assertEqual(bool(self.zeroVec), False) + self.assertEqual(bool(self.v1), True) + self.assert_(not self.zeroVec) + self.assert_(self.v1) + + def testUnary(self): + v = +self.v1 + self.assert_(isinstance(v, type(self.v1))) + self.assertEqual(v.x, self.v1.x) + self.assertEqual(v.y, self.v1.y) + self.assertEqual(v.z, self.v1.z) + self.assertNotEqual(id(v), id(self.v1)) + v = -self.v1 + self.assert_(isinstance(v, type(self.v1))) + self.assertEqual(v.x, -self.v1.x) + self.assertEqual(v.y, -self.v1.y) + self.assertEqual(v.z, -self.v1.z) + self.assertNotEqual(id(v), id(self.v1)) + + def testCompare(self): + int_vec = Vector3(3, -2, 13) + flt_vec = Vector3(3.0, -2.0, 13.) + zero_vec = Vector3(0, 0, 0) + self.assertEqual(int_vec == flt_vec, True) + self.assertEqual(int_vec != flt_vec, False) + self.assertEqual(int_vec != zero_vec, True) + self.assertEqual(flt_vec == zero_vec, False) + self.assertEqual(int_vec == (3, -2, 13), True) + self.assertEqual(int_vec != (3, -2, 13), False) + self.assertEqual(int_vec != [0, 0], True) + self.assertEqual(int_vec == [0, 0], False) + self.assertEqual(int_vec != 5, True) + self.assertEqual(int_vec == 5, False) + self.assertEqual(int_vec != [3, -2, 0, 1], True) + self.assertEqual(int_vec == [3, -2, 0, 1], False) + + def testStr(self): + v = Vector3(1.2, 3.4, 5.6) + self.assertEqual(str(v), "[1.2, 3.4, 5.6]") + + def testRepr(self): + v = Vector3(1.2, 3.4, -9.6) + self.assertEqual(v.__repr__(), "") + self.assertEqual(v, Vector3(v.__repr__())) + + def testIter(self): + it = self.v1.__iter__() + # support py2.x and 3.x + if hasattr(it, "next"): + next_ = it.next + elif hasattr(it, "__next__"): + next_ = it.__next__ + else: + self.fail("Iterator has neither a 'next' nor a '__next__' method.") + self.assertEqual(next_(), self.v1[0]) + self.assertEqual(next_(), self.v1[1]) + self.assertEqual(next_(), self.v1[2]) + self.assertRaises(StopIteration, lambda : next_()) + it1 = self.v1.__iter__() + it2 = self.v1.__iter__() + self.assertNotEqual(id(it1), id(it2)) + self.assertEqual(id(it1), id(it1.__iter__())) + self.assertEqual(list(it1), list(it2)); + self.assertEqual(list(self.v1.__iter__()), self.l1) + idx = 0 + for val in self.v1: + self.assertEqual(val, self.v1[idx]) + idx += 1 + + def test_rotate(self): + v1 = Vector3(1, 0, 0) + axis = Vector3(0, 1, 0) + v2 = v1.rotate(90, axis) + v3 = v1.rotate(90 + 360, axis) + self.assertEqual(v1.x, 1) + self.assertEqual(v1.y, 0) + self.assertEqual(v1.z, 0) + self.assertEqual(v2.x, 0) + self.assertEqual(v2.y, 0) + self.assertEqual(v2.z, -1) + self.assertEqual(v3.x, v2.x) + self.assertEqual(v3.y, v2.y) + self.assertEqual(v3.z, v2.z) + v1 = Vector3(-1, -1, -1) + v2 = v1.rotate(-90, axis) + self.assertEqual(v2.x, 1) + self.assertEqual(v2.y, -1) + self.assertEqual(v2.z, -1) + v2 = v1.rotate(360, axis) + self.assertEqual(v1.x, v2.x) + self.assertEqual(v1.y, v2.y) + self.assertEqual(v1.z, v2.z) + v2 = v1.rotate(0, axis) + self.assertEqual(v1.x, v2.x) + self.assertEqual(v1.y, v2.y) + self.assertEqual(v1.z, v2.z) + # issue 214 + self.assertEqual(Vector3(0, 1, 0).rotate(359.9999999, Vector3(0, 0, 1)), + Vector3(0, 1, 0)) + + def test_rotate_ip(self): + v = Vector3(1, 0) + axis = Vector3(0, 1, 0) + self.assertEqual(v.rotate_ip(90, axis), None) + self.assertEqual(v.x, 0) + self.assertEqual(v.y, 0) + self.assertEqual(v.z, -1) + v = Vector3(-1, -1, 1) + v.rotate_ip(-90, axis) + self.assertEqual(v.x, -1) + self.assertEqual(v.y, -1) + self.assertEqual(v.z, -1) + + def test_rotate_x(self): + v1 = Vector3(1, 0, 0) + v2 = v1.rotate_x(90) + v3 = v1.rotate_x(90 + 360) + self.assertEqual(v1.x, 1) + self.assertEqual(v1.y, 0) + self.assertEqual(v1.z, 0) + self.assertEqual(v2.x, 1) + self.assertEqual(v2.y, 0) + self.assertEqual(v2.z, 0) + self.assertEqual(v3.x, v2.x) + self.assertEqual(v3.y, v2.y) + self.assertEqual(v3.z, v2.z) + v1 = Vector3(-1, -1, -1) + v2 = v1.rotate_x(-90) + self.assertEqual(v2.x, -1) + self.assertAlmostEqual(v2.y, -1) + self.assertAlmostEqual(v2.z, 1) + v2 = v1.rotate_x(360) + self.assertAlmostEqual(v1.x, v2.x) + self.assertAlmostEqual(v1.y, v2.y) + self.assertAlmostEqual(v1.z, v2.z) + v2 = v1.rotate_x(0) + self.assertEqual(v1.x, v2.x) + self.assertAlmostEqual(v1.y, v2.y) + self.assertAlmostEqual(v1.z, v2.z) + + def test_rotate_x_ip(self): + v = Vector3(1, 0, 0) + self.assertEqual(v.rotate_x_ip(90), None) + self.assertEqual(v.x, 1) + self.assertEqual(v.y, 0) + self.assertEqual(v.z, 0) + v = Vector3(-1, -1, 1) + v.rotate_x_ip(-90) + self.assertEqual(v.x, -1) + self.assertAlmostEqual(v.y, 1) + self.assertAlmostEqual(v.z, 1) + + def test_rotate_y(self): + v1 = Vector3(1, 0, 0) + v2 = v1.rotate_y(90) + v3 = v1.rotate_y(90 + 360) + self.assertEqual(v1.x, 1) + self.assertEqual(v1.y, 0) + self.assertEqual(v1.z, 0) + self.assertAlmostEqual(v2.x, 0) + self.assertEqual(v2.y, 0) + self.assertAlmostEqual(v2.z, -1) + self.assertAlmostEqual(v3.x, v2.x) + self.assertEqual(v3.y, v2.y) + self.assertAlmostEqual(v3.z, v2.z) + v1 = Vector3(-1, -1, -1) + v2 = v1.rotate_y(-90) + self.assertAlmostEqual(v2.x, 1) + self.assertEqual(v2.y, -1) + self.assertAlmostEqual(v2.z, -1) + v2 = v1.rotate_y(360) + self.assertAlmostEqual(v1.x, v2.x) + self.assertEqual(v1.y, v2.y) + self.assertAlmostEqual(v1.z, v2.z) + v2 = v1.rotate_y(0) + self.assertEqual(v1.x, v2.x) + self.assertEqual(v1.y, v2.y) + self.assertEqual(v1.z, v2.z) + + def test_rotate_y_ip(self): + v = Vector3(1, 0, 0) + self.assertEqual(v.rotate_y_ip(90), None) + self.assertAlmostEqual(v.x, 0) + self.assertEqual(v.y, 0) + self.assertAlmostEqual(v.z, -1) + v = Vector3(-1, -1, 1) + v.rotate_y_ip(-90) + self.assertAlmostEqual(v.x, -1) + self.assertEqual(v.y, -1) + self.assertAlmostEqual(v.z, -1) + + def test_rotate_z(self): + v1 = Vector3(1, 0, 0) + v2 = v1.rotate_z(90) + v3 = v1.rotate_z(90 + 360) + self.assertEqual(v1.x, 1) + self.assertEqual(v1.y, 0) + self.assertEqual(v1.z, 0) + self.assertAlmostEqual(v2.x, 0) + self.assertAlmostEqual(v2.y, 1) + self.assertEqual(v2.z, 0) + self.assertAlmostEqual(v3.x, v2.x) + self.assertAlmostEqual(v3.y, v2.y) + self.assertEqual(v3.z, v2.z) + v1 = Vector3(-1, -1, -1) + v2 = v1.rotate_z(-90) + self.assertAlmostEqual(v2.x, -1) + self.assertAlmostEqual(v2.y, 1) + self.assertEqual(v2.z, -1) + v2 = v1.rotate_z(360) + self.assertAlmostEqual(v1.x, v2.x) + self.assertAlmostEqual(v1.y, v2.y) + self.assertEqual(v1.z, v2.z) + v2 = v1.rotate_z(0) + self.assertAlmostEqual(v1.x, v2.x) + self.assertAlmostEqual(v1.y, v2.y) + self.assertEqual(v1.z, v2.z) + + def test_rotate_z_ip(self): + v = Vector3(1, 0, 0) + self.assertEqual(v.rotate_z_ip(90), None) + self.assertAlmostEqual(v.x, 0) + self.assertAlmostEqual(v.y, 1) + self.assertEqual(v.z, 0) + v = Vector3(-1, -1, 1) + v.rotate_z_ip(-90) + self.assertAlmostEqual(v.x, -1) + self.assertAlmostEqual(v.y, 1) + self.assertEqual(v.z, 1) + + def test_normalize(self): + v = self.v1.normalize() + # length is 1 + self.assertAlmostEqual(v.x * v.x + v.y * v.y + v.z * v.z, 1.) + # v1 is unchanged + self.assertEqual(self.v1.x, self.l1[0]) + self.assertEqual(self.v1.y, self.l1[1]) + self.assertEqual(self.v1.z, self.l1[2]) + # v2 is paralell to v1 (tested via cross product) + cross = ((self.v1.y * v.z - self.v1.z * v.y) ** 2 + + (self.v1.z * v.x - self.v1.x * v.z) ** 2 + + (self.v1.x * v.y - self.v1.y * v.x) ** 2) + self.assertAlmostEqual(cross, 0.) + self.assertRaises(ValueError, lambda : self.zeroVec.normalize()) + + def test_normalize_ip(self): + v = +self.v1 + # v has length != 1 before normalizing + self.assertNotEqual(v.x * v.x + v.y * v.y + v.z * v.z, 1.) + # inplace operations should return None + self.assertEqual(v.normalize_ip(), None) + # length is 1 + self.assertAlmostEqual(v.x * v.x + v.y * v.y + v.z * v.z, 1.) + # v2 is paralell to v1 (tested via cross product) + cross = ((self.v1.y * v.z - self.v1.z * v.y) ** 2 + + (self.v1.z * v.x - self.v1.x * v.z) ** 2 + + (self.v1.x * v.y - self.v1.y * v.x) ** 2) + self.assertAlmostEqual(cross, 0.) + self.assertRaises(ValueError, lambda : self.zeroVec.normalize_ip()) + + def test_is_normalized(self): + self.assertEqual(self.v1.is_normalized(), False) + v = self.v1.normalize() + self.assertEqual(v.is_normalized(), True) + self.assertEqual(self.e2.is_normalized(), True) + self.assertEqual(self.zeroVec.is_normalized(), False) + + def test_cross(self): + def cross(a, b): + return Vector3(a[1] * b[2] - a[2] * b[1], + a[2] * b[0] - a[0] * b[2], + a[0] * b[1] - a[1] * b[0]) + self.assertEqual(self.v1.cross(self.v2), cross(self.v1, self.v2)) + self.assertEqual(self.v1.cross(self.l2), cross(self.v1, self.l2)) + self.assertEqual(self.v1.cross(self.t2), cross(self.v1, self.t2)) + self.assertEqual(self.v1.cross(self.v2), -self.v2.cross(self.v1)) + self.assertEqual(self.v1.cross(self.v1), self.zeroVec) + + def test_dot(self): + self.assertAlmostEqual(self.v1.dot(self.v2), + self.v1.x * self.v2.x + self.v1.y * self.v2.y + self.v1.z * self.v2.z) + self.assertAlmostEqual(self.v1.dot(self.l2), + self.v1.x * self.l2[0] + self.v1.y * self.l2[1] + self.v1.z * self.l2[2]) + self.assertAlmostEqual(self.v1.dot(self.t2), + self.v1.x * self.t2[0] + self.v1.y * self.t2[1] + self.v1.z * self.t2[2]) + self.assertAlmostEqual(self.v1.dot(self.v2), self.v2.dot(self.v1)) + self.assertAlmostEqual(self.v1.dot(self.v2), self.v1 * self.v2) + + def test_angle_to(self): + self.assertEqual(Vector3(1, 1, 0).angle_to((-1, 1, 0)), 90) + self.assertEqual(Vector3(1, 0, 0).angle_to((0, 0, -1)), 90) + self.assertEqual(Vector3(1, 0, 0).angle_to((-1, 0, 1)), 135) + self.assertEqual(abs(Vector3(1, 0, 1).angle_to((-1, 0, -1))), 180) + # if we rotate v1 by the angle_to v2 around their cross product + # we should look in the same direction + self.assertEqual(self.v1.rotate(self.v1.angle_to(self.v2), self.v1.cross(self.v2)).normalize(), + self.v2.normalize()) + + def test_scale_to_length(self): + v = Vector3(1, 1, 1) + v.scale_to_length(2.5) + self.assertEqual(v, Vector3(2.5, 2.5, 2.5) / math.sqrt(3)) + self.assertRaises(ValueError, lambda : self.zeroVec.scale_to_length(1)) + self.assertEqual(v.scale_to_length(0), None) + self.assertEqual(v, self.zeroVec) + + def test_length(self): + self.assertEqual(Vector3(3, 4, 5).length(), math.sqrt(3 * 3 + 4 * 4 + 5 * 5)) + self.assertEqual(Vector3(-3, 4, 5).length(), math.sqrt(-3 * -3 + 4 * 4 + 5 * 5)) + self.assertEqual(self.zeroVec.length(), 0) + + def test_length_squared(self): + self.assertEqual(Vector3(3, 4, 5).length_squared(), 3 * 3 + 4 * 4 + 5 * 5) + self.assertEqual(Vector3(-3, 4, 5).length_squared(), -3 * -3 + 4 * 4 + 5 * 5) + self.assertEqual(self.zeroVec.length_squared(), 0) + + def test_reflect(self): + v = Vector3(1, -1, 1) + n = Vector3(0, 1, 0) + self.assertEqual(v.reflect(n), Vector3(1, 1, 1)) + self.assertEqual(v.reflect(3*n), v.reflect(n)) + self.assertEqual(v.reflect(-v), -v) + self.assertRaises(ValueError, lambda : v.reflect(self.zeroVec)) + + def test_reflect_ip(self): + v1 = Vector3(1, -1, 1) + v2 = Vector3(v1) + n = Vector3(0, 1, 0) + self.assertEqual(v2.reflect_ip(n), None) + self.assertEqual(v2, Vector3(1, 1, 1)) + v2 = Vector3(v1) + v2.reflect_ip(3*n) + self.assertEqual(v2, v1.reflect(n)) + v2 = Vector3(v1) + v2.reflect_ip(-v1) + self.assertEqual(v2, -v1) + self.assertRaises(ValueError, lambda : v2.reflect_ip(self.zeroVec)) + + def test_distance_to(self): + diff = self.v1 - self.v2 + self.assertEqual(self.e1.distance_to(self.e2), math.sqrt(2)) + self.assertEqual(self.v1.distance_to(self.v2), + math.sqrt(diff.x * diff.x + diff.y * diff.y + diff.z * diff.z)) + self.assertEqual(self.v1.distance_to(self.v1), 0) + self.assertEqual(self.v1.distance_to(self.v2), + self.v2.distance_to(self.v1)) + + def test_distance_squared_to(self): + diff = self.v1 - self.v2 + self.assertEqual(self.e1.distance_squared_to(self.e2), 2) + self.assertAlmostEqual(self.v1.distance_squared_to(self.v2), + diff.x * diff.x + diff.y * diff.y + diff.z * diff.z) + self.assertEqual(self.v1.distance_squared_to(self.v1), 0) + self.assertEqual(self.v1.distance_squared_to(self.v2), + self.v2.distance_squared_to(self.v1)) + + def testSwizzle(self): + self.assertEquals(hasattr(pygame.math, "enable_swizzling"), True) + self.assertEquals(hasattr(pygame.math, "disable_swizzling"), True) + # swizzling disabled by default + self.assertRaises(AttributeError, lambda : self.v1.yx) + pygame.math.enable_swizzling() + + self.assertEqual(self.v1.yxz, (self.v1.y, self.v1.x, self.v1.z)) + self.assertEqual(self.v1.xxyyzzxyz, (self.v1.x, self.v1.x, self.v1.y, + self.v1.y, self.v1.z, self.v1.z, + self.v1.x, self.v1.y, self.v1.z)) + self.v1.xyz = self.t2 + self.assertEqual(self.v1, self.t2) + self.v1.zxy = self.t2 + self.assertEqual(self.v1, (self.t2[1], self.t2[2], self.t2[0])) + self.v1.yz = self.t2[:2] + self.assertEqual(self.v1, (self.t2[1], self.t2[0], self.t2[1])) + self.assertEqual(type(self.v1), Vector3) + def invalidSwizzleX(): + Vector3().xx = (1, 2) + def invalidSwizzleY(): + Vector3().yy = (1, 2) + def invalidSwizzleZ(): + Vector3().zz = (1, 2) + def invalidSwizzleW(): + Vector3().ww = (1, 2) + self.assertRaises(AttributeError, invalidSwizzleX) + self.assertRaises(AttributeError, invalidSwizzleY) + self.assertRaises(AttributeError, invalidSwizzleZ) + self.assertRaises(AttributeError, invalidSwizzleW) + def invalidAssignment(): + Vector3().xy = 3 + self.assertRaises(TypeError, invalidAssignment) + + def test_elementwise(self): + # behaviour for "elementwise op scalar" + self.assertEqual(self.v1.elementwise() + self.s1, + (self.v1.x + self.s1, self.v1.y + self.s1, self.v1.z + self.s1)) + self.assertEqual(self.v1.elementwise() - self.s1, + (self.v1.x - self.s1, self.v1.y - self.s1, self.v1.z - self.s1)) + self.assertEqual(self.v1.elementwise() * self.s2, + (self.v1.x * self.s2, self.v1.y * self.s2, self.v1.z * self.s2)) + self.assertEqual(self.v1.elementwise() / self.s2, + (self.v1.x / self.s2, self.v1.y / self.s2, self.v1.z / self.s2)) + self.assertEqual(self.v1.elementwise() // self.s1, + (self.v1.x // self.s1, self.v1.y // self.s1, self.v1.z // self.s1)) + self.assertEqual(self.v1.elementwise() ** self.s1, + (self.v1.x ** self.s1, self.v1.y ** self.s1, self.v1.z ** self.s1)) + self.assertEqual(self.v1.elementwise() % self.s1, + (self.v1.x % self.s1, self.v1.y % self.s1, self.v1.z % self.s1)) + self.assertEqual(self.v1.elementwise() > self.s1, + self.v1.x > self.s1 and self.v1.y > self.s1 and self.v1.z > self.s1) + self.assertEqual(self.v1.elementwise() < self.s1, + self.v1.x < self.s1 and self.v1.y < self.s1 and self.v1.z < self.s1) + self.assertEqual(self.v1.elementwise() == self.s1, + self.v1.x == self.s1 and self.v1.y == self.s1 and self.v1.z == self.s1) + self.assertEqual(self.v1.elementwise() != self.s1, + self.v1.x != self.s1 and self.v1.y != self.s1 and self.v1.z != self.s1) + self.assertEqual(self.v1.elementwise() >= self.s1, + self.v1.x >= self.s1 and self.v1.y >= self.s1 and self.v1.z >= self.s1) + self.assertEqual(self.v1.elementwise() <= self.s1, + self.v1.x <= self.s1 and self.v1.y <= self.s1 and self.v1.z <= self.s1) + # behaviour for "scalar op elementwise" + self.assertEqual(5 + self.v1.elementwise(), Vector3(5, 5, 5) + self.v1) + self.assertEqual(3.5 - self.v1.elementwise(), Vector3(3.5, 3.5, 3.5) - self.v1) + self.assertEqual(7.5 * self.v1.elementwise() , 7.5 * self.v1) + self.assertEqual(-3.5 / self.v1.elementwise(), (-3.5 / self.v1.x, -3.5 / self.v1.y, -3.5 / self.v1.z)) + self.assertEqual(-3.5 // self.v1.elementwise(), (-3.5 // self.v1.x, -3.5 // self.v1.y, -3.5 // self.v1.z)) + self.assertEqual(-3.5 ** self.v1.elementwise(), (-3.5 ** self.v1.x, -3.5 ** self.v1.y, -3.5 ** self.v1.z)) + self.assertEqual(3 % self.v1.elementwise(), (3 % self.v1.x, 3 % self.v1.y, 3 % self.v1.z)) + self.assertEqual(2 < self.v1.elementwise(), 2 < self.v1.x and 2 < self.v1.y and 2 < self.v1.z) + self.assertEqual(2 > self.v1.elementwise(), 2 > self.v1.x and 2 > self.v1.y and 2 > self.v1.z) + self.assertEqual(1 == self.v1.elementwise(), 1 == self.v1.x and 1 == self.v1.y and 1 == self.v1.z) + self.assertEqual(1 != self.v1.elementwise(), 1 != self.v1.x and 1 != self.v1.y and 1 != self.v1.z) + self.assertEqual(2 <= self.v1.elementwise(), 2 <= self.v1.x and 2 <= self.v1.y and 2 <= self.v1.z) + self.assertEqual(-7 >= self.v1.elementwise(), -7 >= self.v1.x and -7 >= self.v1.y and -7 >= self.v1.z) + self.assertEqual(-7 != self.v1.elementwise(), -7 != self.v1.x and -7 != self.v1.y and -7 != self.v1.z) + + # behaviour for "elementwise op vector" + self.assertEqual(type(self.v1.elementwise() * self.v2), type(self.v1)) + self.assertEqual(self.v1.elementwise() + self.v2, self.v1 + self.v2) + self.assertEqual(self.v1.elementwise() + self.v2, self.v1 + self.v2) + self.assertEqual(self.v1.elementwise() - self.v2, self.v1 - self.v2) + self.assertEqual(self.v1.elementwise() * self.v2, (self.v1.x * self.v2.x, self.v1.y * self.v2.y, self.v1.z * self.v2.z)) + self.assertEqual(self.v1.elementwise() / self.v2, (self.v1.x / self.v2.x, self.v1.y / self.v2.y, self.v1.z / self.v2.z)) + self.assertEqual(self.v1.elementwise() // self.v2, (self.v1.x // self.v2.x, self.v1.y // self.v2.y, self.v1.z // self.v2.z)) + self.assertEqual(self.v1.elementwise() ** self.v2, (self.v1.x ** self.v2.x, self.v1.y ** self.v2.y, self.v1.z ** self.v2.z)) + self.assertEqual(self.v1.elementwise() % self.v2, (self.v1.x % self.v2.x, self.v1.y % self.v2.y, self.v1.z % self.v2.z)) + self.assertEqual(self.v1.elementwise() > self.v2, self.v1.x > self.v2.x and self.v1.y > self.v2.y and self.v1.z > self.v2.z) + self.assertEqual(self.v1.elementwise() < self.v2, self.v1.x < self.v2.x and self.v1.y < self.v2.y and self.v1.z < self.v2.z) + self.assertEqual(self.v1.elementwise() >= self.v2, self.v1.x >= self.v2.x and self.v1.y >= self.v2.y and self.v1.z >= self.v2.z) + self.assertEqual(self.v1.elementwise() <= self.v2, self.v1.x <= self.v2.x and self.v1.y <= self.v2.y and self.v1.z <= self.v2.z) + self.assertEqual(self.v1.elementwise() == self.v2, self.v1.x == self.v2.x and self.v1.y == self.v2.y and self.v1.z == self.v2.z) + self.assertEqual(self.v1.elementwise() != self.v2, self.v1.x != self.v2.x and self.v1.y != self.v2.y and self.v1.z != self.v2.z) + # behaviour for "vector op elementwise" + self.assertEqual(self.v2 + self.v1.elementwise(), self.v2 + self.v1) + self.assertEqual(self.v2 - self.v1.elementwise(), self.v2 - self.v1) + self.assertEqual(self.v2 * self.v1.elementwise(), (self.v2.x * self.v1.x, self.v2.y * self.v1.y, self.v2.z * self.v1.z)) + self.assertEqual(self.v2 / self.v1.elementwise(), (self.v2.x / self.v1.x, self.v2.y / self.v1.y, self.v2.z / self.v1.z)) + self.assertEqual(self.v2 // self.v1.elementwise(), (self.v2.x // self.v1.x, self.v2.y // self.v1.y, self.v2.z // self.v1.z)) + self.assertEqual(self.v2 ** self.v1.elementwise(), (self.v2.x ** self.v1.x, self.v2.y ** self.v1.y, self.v2.z ** self.v1.z)) + self.assertEqual(self.v2 % self.v1.elementwise(), (self.v2.x % self.v1.x, self.v2.y % self.v1.y, self.v2.z % self.v1.z)) + self.assertEqual(self.v2 < self.v1.elementwise(), self.v2.x < self.v1.x and self.v2.y < self.v1.y and self.v2.z < self.v1.z) + self.assertEqual(self.v2 > self.v1.elementwise(), self.v2.x > self.v1.x and self.v2.y > self.v1.y and self.v2.z > self.v1.z) + self.assertEqual(self.v2 <= self.v1.elementwise(), self.v2.x <= self.v1.x and self.v2.y <= self.v1.y and self.v2.z <= self.v1.z) + self.assertEqual(self.v2 >= self.v1.elementwise(), self.v2.x >= self.v1.x and self.v2.y >= self.v1.y and self.v2.z >= self.v1.z) + self.assertEqual(self.v2 == self.v1.elementwise(), self.v2.x == self.v1.x and self.v2.y == self.v1.y and self.v2.z == self.v1.z) + self.assertEqual(self.v2 != self.v1.elementwise(), self.v2.x != self.v1.x and self.v2.y != self.v1.y and self.v2.z != self.v1.z) + + # behaviour for "elementwise op elementwise" + self.assertEqual(self.v2.elementwise() + self.v1.elementwise(), self.v2 + self.v1) + self.assertEqual(self.v2.elementwise() - self.v1.elementwise(), self.v2 - self.v1) + self.assertEqual(self.v2.elementwise() * self.v1.elementwise(), + (self.v2.x * self.v1.x, self.v2.y * self.v1.y, self.v2.z * self.v1.z)) + self.assertEqual(self.v2.elementwise() / self.v1.elementwise(), + (self.v2.x / self.v1.x, self.v2.y / self.v1.y, self.v2.z / self.v1.z)) + self.assertEqual(self.v2.elementwise() // self.v1.elementwise(), + (self.v2.x // self.v1.x, self.v2.y // self.v1.y, self.v2.z // self.v1.z)) + self.assertEqual(self.v2.elementwise() ** self.v1.elementwise(), + (self.v2.x ** self.v1.x, self.v2.y ** self.v1.y, self.v2.z ** self.v1.z)) + self.assertEqual(self.v2.elementwise() % self.v1.elementwise(), + (self.v2.x % self.v1.x, self.v2.y % self.v1.y, self.v2.z % self.v1.z)) + self.assertEqual(self.v2.elementwise() < self.v1.elementwise(), + self.v2.x < self.v1.x and self.v2.y < self.v1.y and self.v2.z < self.v1.z) + self.assertEqual(self.v2.elementwise() > self.v1.elementwise(), + self.v2.x > self.v1.x and self.v2.y > self.v1.y and self.v2.z > self.v1.z) + self.assertEqual(self.v2.elementwise() <= self.v1.elementwise(), + self.v2.x <= self.v1.x and self.v2.y <= self.v1.y and self.v2.z <= self.v1.z) + self.assertEqual(self.v2.elementwise() >= self.v1.elementwise(), + self.v2.x >= self.v1.x and self.v2.y >= self.v1.y and self.v2.z >= self.v1.z) + self.assertEqual(self.v2.elementwise() == self.v1.elementwise(), + self.v2.x == self.v1.x and self.v2.y == self.v1.y and self.v2.z == self.v1.z) + self.assertEqual(self.v2.elementwise() != self.v1.elementwise(), + self.v2.x != self.v1.x and self.v2.y != self.v1.y and self.v2.z != self.v1.z) + + # other behaviour + self.assertEqual(abs(self.v1.elementwise()), (abs(self.v1.x), abs(self.v1.y), abs(self.v1.z))) + self.assertEqual(-self.v1.elementwise(), -self.v1) + self.assertEqual(+self.v1.elementwise(), +self.v1) + self.assertEqual(bool(self.v1.elementwise()), bool(self.v1)) + self.assertEqual(bool(Vector3().elementwise()), bool(Vector3())) + self.assertEqual(self.zeroVec.elementwise() ** 0, (1, 1, 1)) + self.assertRaises(ValueError, lambda : pow(Vector3(-1, 0, 0).elementwise(), 1.2)) + self.assertRaises(ZeroDivisionError, lambda : self.zeroVec.elementwise() ** -1) + self.assertRaises(ZeroDivisionError, lambda : Vector3(1,1,1).elementwise() / 0) + self.assertRaises(ZeroDivisionError, lambda : Vector3(1,1,1).elementwise() // 0) + self.assertRaises(ZeroDivisionError, lambda : Vector3(1,1,1).elementwise() % 0) + self.assertRaises(ZeroDivisionError, lambda : Vector3(1,1,1).elementwise() / self.zeroVec) + self.assertRaises(ZeroDivisionError, lambda : Vector3(1,1,1).elementwise() // self.zeroVec) + self.assertRaises(ZeroDivisionError, lambda : Vector3(1,1,1).elementwise() % self.zeroVec) + self.assertRaises(ZeroDivisionError, lambda : 2 / self.zeroVec.elementwise()) + self.assertRaises(ZeroDivisionError, lambda : 2 // self.zeroVec.elementwise()) + self.assertRaises(ZeroDivisionError, lambda : 2 % self.zeroVec.elementwise()) + + + def test_slerp(self): + self.assertRaises(ValueError, lambda : self.zeroVec.slerp(self.v1, .5)) + self.assertRaises(ValueError, lambda : self.v1.slerp(self.zeroVec, .5)) + self.assertRaises(ValueError, + lambda : self.zeroVec.slerp(self.zeroVec, .5)) + steps = 10 + angle_step = self.e1.angle_to(self.e2) / steps + for i, u in ((i, self.e1.slerp(self.e2, i/float(steps))) for i in range(steps+1)): + self.assertAlmostEqual(u.length(), 1) + self.assertAlmostEqual(self.e1.angle_to(u), i * angle_step) + self.assertEqual(u, self.e2) + + v1 = Vector3(100, 0, 0) + v2 = Vector3(0, 10, 7) + radial_factor = v2.length() / v1.length() + for i, u in ((i, v1.slerp(v2, -i/float(steps))) for i in range(steps+1)): + self.assertAlmostEqual(u.length(), (v2.length() - v1.length()) * (float(i)/steps) + v1.length()) + self.assertEqual(u, v2) + self.assertEqual(v1.slerp(v1, .5), v1) + self.assertEqual(v2.slerp(v2, .5), v2) + self.assertRaises(ValueError, lambda : v1.slerp(-v1, 0.5)) + + def test_lerp(self): + """TODO""" + pass + + def test_spherical(self): + v = Vector3() + v.from_spherical(self.v1.as_spherical()) + self.assertEqual(self.v1, v) + self.assertEqual(self.e1.as_spherical(), (1, 90, 0)) + self.assertEqual(self.e2.as_spherical(), (1, 90, 90)) + self.assertEqual(self.e3.as_spherical(), (1, 0, 0)) + self.assertEqual((2 * self.e2).as_spherical(), (2, 90, 90)) + self.assertRaises(TypeError, lambda : v.from_spherical((None, None, None))) + self.assertRaises(TypeError, lambda : v.from_spherical("abc")) + self.assertRaises(TypeError, lambda : v.from_spherical((None, 1, 2))) + self.assertRaises(TypeError, lambda : v.from_spherical((1, 2, 3, 4))) + self.assertRaises(TypeError, lambda : v.from_spherical((1, 2))) + self.assertRaises(TypeError, lambda : v.from_spherical(1, 2, 3)) + v.from_spherical((.5, 90, 90)) + self.assertEqual(v, .5 * self.e2) + + def test_inplace_operators(self): + + v = Vector3(1,1,1) + v *= 2 + self.assertEqual(v, (2.0,2.0,2.0)) + + v = Vector3(4,4,4) + v /= 2 + self.assertEqual(v, (2.0,2.0,2.0)) + + + v = Vector3(3.0,3.0,3.0) + v -= (1,1,1) + self.assertEqual(v, (2.0,2.0,2.0)) + + v = Vector3(3.0,3.0,3.0) + v += (1,1,1) + self.assertEqual(v, (4.0,4.0,4.0)) + + + + + + + +if __name__ == '__main__': + unittest.main() From 9510a28841daab5c6320542d29feb451ddc74300 Mon Sep 17 00:00:00 2001 From: Anton Joubert Date: Sun, 9 Oct 2016 20:27:46 +0200 Subject: [PATCH 02/15] Add additional __repr__ tests --- test/math_test.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/math_test.py b/test/math_test.py index c4279fa..3cc6438 100644 --- a/test/math_test.py +++ b/test/math_test.py @@ -266,6 +266,9 @@ def testRepr(self): v = Vector2(1.2, 3.4) self.assertEqual(v.__repr__(), "") self.assertEqual(v, Vector2(v.__repr__())) + self.assertRaises(TypeError, Vector2(), "") + self.assertRaises(TypeError, Vector2(), "") + self.assertRaises(TypeError, Vector2(), "") def testIter(self): it = self.v1.__iter__() From 859a6e5c582fb16b3bb403e3b732e024b55b4a11 Mon Sep 17 00:00:00 2001 From: Anton Joubert Date: Mon, 10 Oct 2016 22:04:53 +0200 Subject: [PATCH 03/15] Vector2 reads local vars instead of properties, and simplify __get_item__ --- pygame/math.py | 43 ++++++++++++++++--------------------------- 1 file changed, 16 insertions(+), 27 deletions(-) diff --git a/pygame/math.py b/pygame/math.py index 018ff3c..7720502 100644 --- a/pygame/math.py +++ b/pygame/math.py @@ -59,29 +59,17 @@ def __init__(self, *args): self.y = args[1] def __repr__(self): - return "".format(self.x, self.y) + return "".format(self._x, self._y) def __str__(self): - return "[{}, {}]".format(self.x, self.y) + return "[{}, {}]".format(self._x, self._y) def __len__(self): return 2 def __getitem__(self, key): """Provide indexed read access (vec[0] is x vec[1] is y).""" - if isinstance(key, slice): - return [self[ind] for ind in xrange(*key.indices(len(self)))] - elif isinstance(key, int): - if key < 0: - key += len(self) - if key == 0: - return self.x - elif key == 1: - return self.y - else: - raise IndexError("Out of range index requested: {}".format(key)) - else: - raise TypeError("Invalid argument type") + return [self._x, self._y][key] def __setitem__(self, key, value): """Provide indexed modification.""" @@ -93,7 +81,8 @@ def __setitem__(self, key, value): for count, index in enumerate(indices): self[index] = value[count] else: - raise ValueError("Invalid slice or value arguments ({}).".format(key)) + raise ValueError("Invalid slice or value arguments (key {}, value {}).". + format(key, value)) elif isinstance(key, int): if key < 0: key += len(self) @@ -112,7 +101,7 @@ def __delitem__(self): def __eq__(self, other): if isinstance(other, Vector2): - return (self.x == other.x) and (self.y == other.y) + return (self._x == other.x) and (self._y == other.y) elif hasattr(other, '__iter__'): try: other_v = Vector2(other) @@ -127,7 +116,7 @@ def __ne__(self, other): def __add__(self, other): if isinstance(other, Vector2): - return Vector2(self.x + other.x, self.y + other.y) + return Vector2(self._x + other.x, self._y + other.y) elif hasattr(other, '__iter__'): try: other_v = Vector2(other) @@ -142,7 +131,7 @@ def __radd__(self, other): def __sub__(self, other): if isinstance(other, Vector2): - return Vector2(self.x - other.x, self.y - other.y) + return Vector2(self._x - other.x, self._y - other.y) elif hasattr(other, '__iter__'): try: other_v = Vector2(other) @@ -154,7 +143,7 @@ def __sub__(self, other): def __rsub__(self, other): if isinstance(other, Vector2): - return Vector2(other.x - self.x, other.y - self.y) + return Vector2(other.x - self._x, other.y - self._y) elif hasattr(other, '__iter__'): try: other_v = Vector2(other) @@ -166,7 +155,7 @@ def __rsub__(self, other): def __mul__(self, other): if isinstance(other, Number): - return Vector2(self.x * float(other), self.y * float(other)) + return Vector2(self._x * float(other), self._y * float(other)) return NotImplemented def __rmul__(self, other): @@ -174,27 +163,27 @@ def __rmul__(self, other): def __div__(self, other): if isinstance(other, Number): - return Vector2(self.x / float(other), self.y / float(other)) + return Vector2(self._x / float(other), self._y / float(other)) return NotImplemented def __floordiv__(self, other): if isinstance(other, Number): - return Vector2(self.x // other, self.y // other) + return Vector2(self._x // other, self._y // other) return NotImplemented def __bool__(self): """bool operator for Python 3.""" - return self.x != 0 or self.y != 0 + return self._x != 0 or self._y != 0 def __nonzero__(self): """bool operator for Python 2.""" return self.__bool__() def __neg__(self): - return Vector2(-self.x, -self.y) + return Vector2(-self._x, -self._y) def __pos__(self): - return Vector2(self.x, self.y) + return Vector2(self._x, self._y) @property def x(self): @@ -227,7 +216,7 @@ def dot(self, vec): """ if not isinstance(vec, Vector2): vec = Vector2(vec) - return self.x * vec.x + self.y * vec.y + return self._x * vec.x + self._y * vec.y def cross(self): """calculates the cross- or vector-product.""" From b69d0d386f6c597c01c4102ce514ba119f79d32f Mon Sep 17 00:00:00 2001 From: Anton Joubert Date: Sun, 23 Oct 2016 21:55:30 +0200 Subject: [PATCH 04/15] Implement most of Vector2 --- pygame/math.py | 258 ++++++++++++++++++++++++++++++++++++---------- test/math_test.py | 47 ++++++++- 2 files changed, 250 insertions(+), 55 deletions(-) diff --git a/pygame/math.py b/pygame/math.py index 7720502..deed2d7 100644 --- a/pygame/math.py +++ b/pygame/math.py @@ -1,9 +1,10 @@ """ Math module """ +from __future__ import absolute_import +import math from numbers import Number - -swizzling = False +VECTOR_EPSILON = 1e-6 # For equality tests def enable_swizzling(): @@ -11,9 +12,16 @@ def enable_swizzling(): Enables swizzling for all vectors until disable_swizzling() is called. By default swizzling is disabled. + + Note: There may be performance degradation, as this replaces __getattr__ + and __setattr__. Only enable if required. """ - global swizzling - swizzling = True + # __getattr__ is not defined by default, so nothing to save + Vector2.__getattr__ = Vector2.__getattr_swizzle__ + + if '__oldsetattr__' not in dir(Vector2): + Vector2.__oldsetattr__ = Vector2.__setattr__ + Vector2.__setattr__ = Vector2.__setattr_swizzle__ def disable_swizzling(): @@ -22,8 +30,10 @@ def disable_swizzling(): Disables swizzling for all vectors until enable_swizzling() is called. By default swizzling is disabled. """ - global swizzling - swizzling = False + if '__getattr__' in dir(Vector2): + del Vector2.__getattr__ + if '__oldsetattr__' in dir(Vector2): + Vector2.__setattr__ = Vector2.__oldsetattr__ class Vector2(object): @@ -101,7 +111,8 @@ def __delitem__(self): def __eq__(self, other): if isinstance(other, Vector2): - return (self._x == other.x) and (self._y == other.y) + return (abs(self._x - other.x) < VECTOR_EPSILON + and abs(self._y - other.y) < VECTOR_EPSILON) elif hasattr(other, '__iter__'): try: other_v = Vector2(other) @@ -112,7 +123,7 @@ def __eq__(self, other): return False def __ne__(self, other): - return not self.__eq__(other) + return not (self == other) def __add__(self, other): if isinstance(other, Vector2): @@ -156,6 +167,8 @@ def __rsub__(self, other): def __mul__(self, other): if isinstance(other, Number): return Vector2(self._x * float(other), self._y * float(other)) + elif isinstance(other, Vector2): + return self.dot(other) return NotImplemented def __rmul__(self, other): @@ -185,6 +198,32 @@ def __neg__(self): def __pos__(self): return Vector2(self._x, self._y) + def __iter__(self): + return (coord for coord in [self._x, self._y]) + + def __getattr_swizzle__(self, name): + result = [] + for letter in name: + if letter == 'x': + result.append(self._x) + elif letter == 'y': + result.append(self._y) + else: + raise AttributeError('Invalid swizzle request: {}.'.format(name)) + return tuple(result) + + def __setattr_swizzle__(self, name, value): + if name == 'xy': + super(Vector2, self).__setattr__('_x', value[0]) + super(Vector2, self).__setattr__('_y', value[1]) + elif name == 'yx': + super(Vector2, self).__setattr__('_y', value[0]) + super(Vector2, self).__setattr__('_x', value[1]) + elif name == 'xx' or name == 'yy': + raise AttributeError('Invalid swizzle request: {}={}.'.format(name, value)) + else: + super(Vector2, self).__setattr__(name, value) + @property def x(self): """Vector x value.""" @@ -218,82 +257,195 @@ def dot(self, vec): vec = Vector2(vec) return self._x * vec.x + self._y * vec.y - def cross(self): - """calculates the cross- or vector-product.""" - pass + def cross(self, vec): + """calculates the determinant - 2D analog of the cross- or vector-product.""" + if not isinstance(vec, Vector2): + vec = Vector2(vec) + return self._x * vec.y - self._y * vec.x def length(self): """returns the euclidic length of the vector.""" - pass + return math.sqrt(self._x * self._x + self._y * self._y) def length_squared(self): """returns the squared euclidic length of the vector.""" - pass + return self._x * self._x + self._y * self._y def normalize(self): """returns a vector with the same direction but length 1.""" - pass + length = self.length() + if length >= VECTOR_EPSILON: + return Vector2(self._x/length, self._y/length) + else: + raise ValueError("Can't normalize Vector of length Zero") def normalize_ip(self): """normalizes the vector in place so that its length is 1.""" - pass + norm_vec = self.normalize() + self.x = norm_vec.x + self.y = norm_vec.y def is_normalized(self): """tests if the vector is normalized i.e. has length == 1.""" - pass + length_squared = self.length_squared() + return abs(length_squared - 1.0) < VECTOR_EPSILON - def scale_to_length(self): + def scale_to_length(self, new_length): """scales the vector to a given length.""" - pass - - def reflect(self): - """returns a vector reflected of a given normal.""" - pass - - def reflect_ip(self): - """reflect the vector of a given normal in place.""" - pass - - def distance_to(self): - """calculates the euclidic distance to a given vector.""" - pass - - def distance_squared_to(self): - """calculates the squared euclidic distance to a given vector.""" - pass - - def lerp(self): - """returns a linear interpolation to the given vector.""" - pass + length = self.length() + if length >= VECTOR_EPSILON: + new_vec = Vector2(self._x/length*new_length, self._y/length*new_length) + self.x = new_vec.x + self.y = new_vec.y + else: + raise ValueError("Cannot scale a vector with zero length") + + def reflect(self, normal): + """returns a vector reflected around a given normal.""" + normal_vec = Vector2(normal).normalize() # can't assume input is normalized + dot_product = self.dot(normal_vec) + result_vec = Vector2(self._x - 2 * normal_vec.x * dot_product, + self._y - 2 * normal_vec.y * dot_product) + return result_vec + + def reflect_ip(self, normal): + """reflect the vector around a given normal in place.""" + result_vec = self.reflect(normal) + self.x = result_vec.x + self.y = result_vec.y + + def distance_to(self, vec): + """calculates the Euclidic distance to a given vector.""" + if not isinstance(vec, Vector2): + vec = Vector2(vec) + delta = self - vec + return delta.length() - def slerp(self): - """returns a spherical interpolation to the given vector.""" - pass + def distance_squared_to(self, vec): + """calculates the squared Euclidic distance to a given vector.""" + if not isinstance(vec, Vector2): + vec = Vector2(vec) + delta = self - vec + return delta.length_squared() + + def lerp(self, vec, t): + """returns a linear interpolation to the given vector, with t in [0..1].""" + if t < 0 or t > 1: + raise ValueError("Argument 't' must be in range [0, 1]") + elif not isinstance(vec, Vector2): + raise TypeError("Expected 'vec' to be of type Vector2") + else: + # check for special cases, exit early + if t == 0: + return self + elif t == 1: + return vec + + x = self._x * (1 - t) + vec.x * t + y = self._y * (1 - t) + vec.y * t + return Vector2(x, y) + + def slerp(self, vec, t): + """returns a spherical interpolation to the given vector, with t in [-1..1].""" + if t < -1 or t > 1: + raise ValueError("Argument 't' must be in range [-1, 1]") + elif not isinstance(vec, Vector2): + raise TypeError("Expected 'vec' to be of type Vector2") + else: + # check for special cases, exit early + if t == 0: + return self + elif t == -1 or t == 1: + return vec + + polar_self = self.as_polar() + polar_other = vec.as_polar() + if polar_self[0] < VECTOR_EPSILON or polar_other[0] < VECTOR_EPSILON: + raise ValueError("Can't use slerp with zero vector") + new_radius = polar_self[0] * (1 - abs(t)) + polar_other[0] * abs(t) + angle_delta = (polar_other[1] - polar_self[1]) % 360.0 + if abs(angle_delta - 180) < VECTOR_EPSILON: + raise ValueError("Slerp with 180 degrees is undefined.") + if t >= 0: + # take the shortest route + if angle_delta > 180: + angle_delta -= 360 + else: + # go the long way around + if angle_delta < 180: + angle_delta = 360 - angle_delta + new_angle = polar_self[1] + angle_delta * t + result_vec = Vector2() + result_vec.from_polar((new_radius, new_angle)) + return result_vec def elementwise(self): """The next operation will be performed elementwize.""" - pass + raise NotImplementedError - def rotate(self): + def rotate(self, angle): """rotates a vector by a given angle in degrees.""" - pass + # make sure angle is in range [0, 360) + angle = angle % 360.0 + + # special-case rotation by 0, 90, 180 and 270 degrees + if ((angle + VECTOR_EPSILON) % 90.0) < 2 * VECTOR_EPSILON: + quad = int((angle + VECTOR_EPSILON) / 90) + if quad == 0 or quad == 4: # 0 or 360 degrees + x = self._x + y = self._y + elif quad == 1: # 90 degrees + x = -self._y + y = self._x + elif quad == 2: # 180 degreees + x = -self._x + y = -self._y + elif quad == 3: # 270 degrees + x = self._y + y = -self._x + else: + # this should NEVER happen and means a bug in the code + raise RuntimeError("Please report this bug in Vector2.rotate " + "to the developers") + else: + angle_rad = math.radians(angle) + sin_value = math.sin(angle_rad) + cos_value = math.cos(angle_rad) + x = cos_value * self._x - sin_value * self._y + y = sin_value * self._x + cos_value * self._y - def rotate_ip(self): + return Vector2(x, y) + + def rotate_ip(self, angle): """rotates the vector by a given angle in degrees in place.""" - pass + new_vec = self.rotate(angle) + self.x = new_vec._x + self.y = new_vec._y - def angle_to(self): - """calculates the angle to a given vector in degrees.""" - pass + def angle_to(self, vec): + """calculates the minimum angle to a given vector in degrees.""" + if not isinstance(vec, Vector2): + vec = Vector2(vec) + angle_self_rad = math.atan2(self._y, self._x) + angle_other_rad = math.atan2(vec.y, vec.x) + return math.degrees(angle_other_rad - angle_self_rad) def as_polar(self): """returns a tuple with radial distance and azimuthal angle.""" - pass + r = self.length() + angle = math.degrees(math.atan2(self._y, self._x)) + return (r, angle) - def from_polar(self): + def from_polar(self, polar): """Sets x and y from a polar coordinates tuple.""" - pass - + if isinstance(polar, tuple) and len(polar) == 2: + r = polar[0] + angle_rad = math.radians(polar[1]) + self.x = r * math.cos(angle_rad) + self.y = r * math.sin(angle_rad) + else: + raise TypeError("Expected 2 element tuple (radius, angle), but got {}" + .format(polar)) class Vector3(object): pass diff --git a/test/math_test.py b/test/math_test.py index 3cc6438..355db66 100644 --- a/test/math_test.py +++ b/test/math_test.py @@ -244,7 +244,10 @@ def testUnary(self): def testCompare(self): int_vec = Vector2(3, -2) flt_vec = Vector2(3.0, -2.0) + flt_same_vec = Vector2(flt_vec.x, flt_vec.y+0.9e-6) # See VECTOR_EPSILON + flt_different_vec = Vector2(flt_vec.x, flt_vec.y+1.1e-6) zero_vec = Vector2(0, 0) + nan_vec = Vector2(float('NaN'), float('NaN')) self.assertEqual(int_vec == flt_vec, True) self.assertEqual(int_vec != flt_vec, False) self.assertEqual(int_vec != zero_vec, True) @@ -257,6 +260,10 @@ def testCompare(self): self.assertEqual(int_vec == 5, False) self.assertEqual(int_vec != [3, -2, 0], True) self.assertEqual(int_vec == [3, -2, 0], False) + self.assertEqual(flt_vec == flt_same_vec, True) + self.assertEqual(flt_vec == flt_different_vec, False) + self.assertEqual(nan_vec == nan_vec, False) + self.assertEqual(nan_vec != nan_vec, True) def testStr(self): v = Vector2(1.2, 3.4) @@ -303,6 +310,9 @@ def test_rotate(self): self.assertEqual(v2.y, 1) self.assertEqual(v3.x, v2.x) self.assertEqual(v3.y, v2.y) + v2 = v1.rotate(30) + self.assertAlmostEqual(v2.x, 0.866, 3) + self.assertAlmostEqual(v2.y, 0.500, 3) v1 = Vector2(-1, -1) v2 = v1.rotate(-90) self.assertEqual(v2.x, -1) @@ -403,6 +413,7 @@ def test_length_squared(self): self.assertEqual(self.zeroVec.length_squared(), 0) def test_reflect(self): + v = Vector2(1, -1) n = Vector2(0, 1) self.assertEqual(v.reflect(n), Vector2(1, 1)) @@ -469,6 +480,14 @@ def invalidAssignment(): def unicodeAttribute(): getattr(Vector2(), "รค") self.assertRaises(AttributeError, unicodeAttribute) + # check that disabling works + pygame.math.disable_swizzling() + self.assertRaises(AttributeError, lambda : self.v1.yx) + v = Vector2([1, 3]) + v.yx = (2, 4) # this makes a new attribute, with swizzling disabled + self.assertEqual(v.yx, (2, 4)) + self.assertEqual(v.x, 1) + self.assertEqual(v.y, 3) def test_elementwise(self): # behaviour for "elementwise op scalar" @@ -698,8 +717,25 @@ def test_slerp(self): self.assertRaises(ValueError, lambda : v1.slerp(-v1, 0.5)) def test_lerp(self): - """TODO""" - pass + v1 = Vector2(1, 0) + v2 = Vector2(0, 1) + v1_to_v2 = v2 - v1 + full_length = v1_to_v2.length() + steps = 10 + for i in range(steps+1): + t = i/float(steps) + u = v1.lerp(v2, t) + v1_to_u = u - v1 + v2_to_u = u - v2 + self.assertAlmostEqual(v1_to_u.length(), t*full_length) + self.assertAlmostEqual(v2_to_u.length(), (1-t)*full_length) + self.assertEqual(v1.lerp(v1, .5), v1) + self.assertEqual(v2.lerp(v2, .5), v2) + # test t if outside 0..1 + self.assertRaises(ValueError, lambda: v1.lerp(v1, -0.1)) + self.assertRaises(ValueError, lambda: v1.lerp(v1, 1.1)) + # test if first parameter is not a vector + self.assertRaises(TypeError, lambda: v1.lerp(0, 0.5)) def test_polar(self): v = Vector2() @@ -972,7 +1008,10 @@ def testUnary(self): def testCompare(self): int_vec = Vector3(3, -2, 13) flt_vec = Vector3(3.0, -2.0, 13.) + flt_same_vec = Vector3(flt_vec.x, flt_vec.y, flt_vec.z+0.9e-6) # See VECTOR_EPSILON + flt_different_vec = Vector3(flt_vec.x, flt_vec.y, flt_vec.z+1.1e-6) zero_vec = Vector3(0, 0, 0) + nan_vec = Vector3(float('NaN'), float('NaN'), float('NaN')) self.assertEqual(int_vec == flt_vec, True) self.assertEqual(int_vec != flt_vec, False) self.assertEqual(int_vec != zero_vec, True) @@ -985,6 +1024,10 @@ def testCompare(self): self.assertEqual(int_vec == 5, False) self.assertEqual(int_vec != [3, -2, 0, 1], True) self.assertEqual(int_vec == [3, -2, 0, 1], False) + self.assertEqual(flt_vec == flt_same_vec, True) + self.assertEqual(flt_vec == flt_different_vec, False) + self.assertEqual(nan_vec == nan_vec, False) + self.assertEqual(nan_vec != nan_vec, True) def testStr(self): v = Vector3(1.2, 3.4, 5.6) From efb7ca41516d3af8dd4ffce46bb884ae5c3d0cf9 Mon Sep 17 00:00:00 2001 From: Anton Joubert Date: Thu, 10 Nov 2016 22:13:48 +0200 Subject: [PATCH 05/15] Implement last of Vector2 (all tests passing) --- pygame/math.py | 180 +++++++++++++++++++++++++++++++++++++++++++++- test/math_test.py | 2 +- 2 files changed, 179 insertions(+), 3 deletions(-) diff --git a/pygame/math.py b/pygame/math.py index deed2d7..57e2153 100644 --- a/pygame/math.py +++ b/pygame/math.py @@ -380,8 +380,8 @@ def slerp(self, vec, t): return result_vec def elementwise(self): - """The next operation will be performed elementwize.""" - raise NotImplementedError + """Return object for an elementwise operation.""" + return ElementwiseVectorProxy(self) def rotate(self, angle): """rotates a vector by a given angle in degrees.""" @@ -447,5 +447,181 @@ def from_polar(self, polar): raise TypeError("Expected 2 element tuple (radius, angle), but got {}" .format(polar)) + class Vector3(object): pass + + +class ElementwiseVectorProxy(object): + """Class used internally for elementwise vector operations.""" + + def __init__(self, vector): + self.vector = Vector2(vector) + + def __add__(self, other): + if isinstance(other, Number): + return Vector2(self.vector.x + other, self.vector.y + other) + elif isinstance(other, Vector2): + return Vector2(self.vector.x + other.x, self.vector.y + other.y) + elif isinstance(other, ElementwiseVectorProxy): + return self.vector + other.vector + return NotImplemented + + def __radd__(self, other): + return self.__add__(other) + + def __sub__(self, other): + if isinstance(other, Number): + return Vector2(self.vector.x - other, self.vector.y - other) + elif isinstance(other, Vector2): + return Vector2(self.vector.x - other.x, self.vector.y - other.y) + elif isinstance(other, ElementwiseVectorProxy): + return self.vector - other.vector + return NotImplemented + + def __rsub__(self, other): + if isinstance(other, Number): + return Vector2(other - self.vector.x, other - self.vector.y) + elif isinstance(other, Vector2): + return Vector2(other.x - self.vector.x, other.y - self.vector.y) + return NotImplemented + + def __mul__(self, other): + if isinstance(other, Number): + return Vector2(self.vector.x * other, self.vector.y * other) + elif isinstance(other, Vector2): + return Vector2(self.vector.x * other.x, self.vector.y * other.y) + elif isinstance(other, ElementwiseVectorProxy): + return Vector2(self.vector.x * other.vector.x, + self.vector.y * other.vector.y) + return NotImplemented + + def __rmul__(self, other): + return self.__mul__(other) + + def __div__(self, other): + if isinstance(other, Number): + return Vector2(self.vector.x / other, self.vector.y / other) + elif isinstance(other, Vector2): + return Vector2(self.vector.x / other.x, self.vector.y / other.y) + elif isinstance(other, ElementwiseVectorProxy): + return Vector2(self.vector.x / other.vector.x, + self.vector.y / other.vector.y) + return NotImplemented + + def __rdiv__(self, other): + if isinstance(other, Number): + return Vector2(other / self.vector.x, other / self.vector.y) + elif isinstance(other, Vector2): + return Vector2(other.x / self.vector.x, other.y / self.vector.y) + return NotImplemented + + def __floordiv__(self, other): + if isinstance(other, Number): + return Vector2(self.vector.x // other, self.vector.y // other) + elif isinstance(other, Vector2): + return Vector2(self.vector.x // other.x, self.vector.y // other.y) + elif isinstance(other, ElementwiseVectorProxy): + return Vector2(self.vector.x // other.vector.x, + self.vector.y // other.vector.y) + return NotImplemented + + def __rfloordiv__(self, other): + if isinstance(other, Number): + return Vector2(other // self.vector.x, other // self.vector.y) + elif isinstance(other, Vector2): + return Vector2(other.x // self.vector.x, other.y // self.vector.y) + return NotImplemented + + def __pow__(self, other): + if isinstance(other, Number): + return Vector2(self.vector.x ** other, self.vector.y ** other) + elif isinstance(other, Vector2): + return Vector2(self.vector.x ** other.x, self.vector.y ** other.y) + elif isinstance(other, ElementwiseVectorProxy): + return Vector2(self.vector.x ** other.vector.x, + self.vector.y ** other.vector.y) + return NotImplemented + + def __rpow__(self, other): + if isinstance(other, Number): + return Vector2(other ** self.vector.x, other ** self.vector.y) + elif isinstance(other, Vector2): + return Vector2(other.x ** self.vector.x, other.y ** self.vector.y) + return NotImplemented + + def __mod__(self, other): + if isinstance(other, Number): + return Vector2(self.vector.x % other, self.vector.y % other) + elif isinstance(other, Vector2): + return Vector2(self.vector.x % other.x, self.vector.y % other.y) + elif isinstance(other, ElementwiseVectorProxy): + return Vector2(self.vector.x % other.vector.x, + self.vector.y % other.vector.y) + return NotImplemented + + def __rmod__(self, other): + if isinstance(other, Number): + return Vector2(other % self.vector.x, other % self.vector.y) + elif isinstance(other, Vector2): + return Vector2(other.x % self.vector.x, other.y % self.vector.y) + return NotImplemented + + def __eq__(self, other): + if isinstance(other, Number): + return self.vector.x == other and self.vector.y == other + elif isinstance(other, ElementwiseVectorProxy): + return self.vector.x == other.vector.x and self.vector.y == other.vector.y + return NotImplemented + + def __neq__(self, other): + if isinstance(other, Number): + return self.vector.x != other or self.vector.y != other + elif isinstance(other, ElementwiseVectorProxy): + return self.vector.x != other.vector.x or self.vector.y != other.vector.y + return NotImplemented + + def __gt__(self, other): + if isinstance(other, Number): + return self.vector.x > other and self.vector.y > other + elif isinstance(other, ElementwiseVectorProxy): + return self.vector.x > other.vector.x and self.vector.y > other.vector.y + return NotImplemented + + def __lt__(self, other): + if isinstance(other, Number): + return self.vector.x < other and self.vector.y < other + elif isinstance(other, ElementwiseVectorProxy): + return self.vector.x < other.vector.x and self.vector.y < other.vector.y + return NotImplemented + + def __ge__(self, other): + if isinstance(other, Number): + return self.vector.x >= other and self.vector.y >= other + elif isinstance(other, ElementwiseVectorProxy): + return self.vector.x >= other.vector.x and self.vector.y >= other.vector.y + return NotImplemented + + def __le__(self, other): + if isinstance(other, Number): + return self.vector.x <= other and self.vector.y <= other + elif isinstance(other, ElementwiseVectorProxy): + return self.vector.x <= other.vector.x and self.vector.y <= other.vector.y + return NotImplemented + + def __abs__(self): + return Vector2(abs(self.vector.x), abs(self.vector.y)) + + def __neg__(self): + return -self.vector + + def __pos__(self): + return self.vector + + def __bool__(self): + """bool operator for Python 3.""" + return bool(self.vector) + + def __nonzero__(self): + """bool operator for Python 2.""" + return bool(self.vector) diff --git a/test/math_test.py b/test/math_test.py index 355db66..664107b 100644 --- a/test/math_test.py +++ b/test/math_test.py @@ -591,7 +591,7 @@ def test_elementwise(self): self.assertRaises(ValueError, lambda : pow(Vector2(-1, 0).elementwise(), 1.2)) self.assertRaises(ZeroDivisionError, lambda : self.zeroVec.elementwise() ** -1) - def test_elementwise(self): + def test_elementwise2(self): v1 = self.v1 v2 = self.v2 s1 = self.s1 From f2cbb9110d4865f2abcdc741bfa939eccc066bec Mon Sep 17 00:00:00 2001 From: Anton Joubert Date: Sat, 11 Feb 2017 16:26:03 +0200 Subject: [PATCH 06/15] Add absolute_import so pygame.math isn't imported --- benchmarks/run_benchmark.py | 1 + pygame/draw.py | 1 + pygame/mask.py | 1 + pygame/mixer.py | 1 + pygame/transform.py | 1 + test/color_test.py | 1 + test/math_test.py | 2 ++ test/test_utils/png.py | 1 + 8 files changed, 9 insertions(+) diff --git a/benchmarks/run_benchmark.py b/benchmarks/run_benchmark.py index 74a3038..a2dc05c 100644 --- a/benchmarks/run_benchmark.py +++ b/benchmarks/run_benchmark.py @@ -1,3 +1,4 @@ +from __future__ import absolute_import import math import sys import time diff --git a/pygame/draw.py b/pygame/draw.py index d142a6f..904e898 100644 --- a/pygame/draw.py +++ b/pygame/draw.py @@ -1,3 +1,4 @@ +from __future__ import absolute_import from pygame.surface import locked from pygame.color import create_color from pygame.compat import xrange_ diff --git a/pygame/mask.py b/pygame/mask.py index 6a00453..6bca7ab 100644 --- a/pygame/mask.py +++ b/pygame/mask.py @@ -1,5 +1,6 @@ # Implement the pygame API for the bitmask functions +from __future__ import absolute_import import math from pygame._sdl import sdl, ffi diff --git a/pygame/mixer.py b/pygame/mixer.py index 88bf462..9eea3e3 100644 --- a/pygame/mixer.py +++ b/pygame/mixer.py @@ -1,5 +1,6 @@ """ pygame module for loading and playing sounds """ +from __future__ import absolute_import from io import IOBase import math diff --git a/pygame/transform.py b/pygame/transform.py index 67f5949..6924a5a 100644 --- a/pygame/transform.py +++ b/pygame/transform.py @@ -1,5 +1,6 @@ """ the pygame transfrom module """ +from __future__ import absolute_import import math from pygame._error import SDLError diff --git a/test/color_test.py b/test/color_test.py index 0e6b297..3d0cc18 100644 --- a/test/color_test.py +++ b/test/color_test.py @@ -1,6 +1,7 @@ #################################### IMPORTS ################################### from __future__ import generators +from __future__ import absolute_import if __name__ == '__main__': import sys diff --git a/test/math_test.py b/test/math_test.py index 664107b..58aabd6 100644 --- a/test/math_test.py +++ b/test/math_test.py @@ -4,6 +4,8 @@ https://bitbucket.org/pygame/pygame/src/50ca5dd0191bc394c3f51e06ecc48ddedccc161d/test/math_test.py?at=default&fileviewer=file-view-default """ +from __future__ import absolute_import + if __name__ == '__main__': import sys import os diff --git a/test/test_utils/png.py b/test/test_utils/png.py index d0fba05..b5b2ce1 100644 --- a/test/test_utils/png.py +++ b/test/test_utils/png.py @@ -166,6 +166,7 @@ # http://www.python.org/doc/2.2.3/whatsnew/node5.html from __future__ import generators +from __future__ import absolute_import __version__ = "$URL: http://pypng.googlecode.com/svn/trunk/code/png.py $ $Rev: 228 $" From 296174b3a0b9626d50431b65925c559e18e76247 Mon Sep 17 00:00:00 2001 From: Anton Joubert Date: Mon, 13 Feb 2017 22:45:27 +0200 Subject: [PATCH 07/15] Implement Vector3 - elementwise and swizzle still todo --- pygame/math.py | 656 ++++++++++++++++++++++++++++++++++++++++++---- test/math_test.py | 54 +++- 2 files changed, 658 insertions(+), 52 deletions(-) diff --git a/pygame/math.py b/pygame/math.py index 57e2153..3aac2c3 100644 --- a/pygame/math.py +++ b/pygame/math.py @@ -18,11 +18,16 @@ def enable_swizzling(): """ # __getattr__ is not defined by default, so nothing to save Vector2.__getattr__ = Vector2.__getattr_swizzle__ + Vector3.__getattr__ = Vector3.__getattr_swizzle__ if '__oldsetattr__' not in dir(Vector2): Vector2.__oldsetattr__ = Vector2.__setattr__ Vector2.__setattr__ = Vector2.__setattr_swizzle__ + if '__oldsetattr__' not in dir(Vector3): + Vector3.__oldsetattr__ = Vector3.__setattr__ + Vector3.__setattr__ = Vector3.__setattr_swizzle__ + def disable_swizzling(): """Globally disables swizzling for vectors. @@ -34,6 +39,10 @@ def disable_swizzling(): del Vector2.__getattr__ if '__oldsetattr__' in dir(Vector2): Vector2.__setattr__ = Vector2.__oldsetattr__ + if '__getattr__' in dir(Vector3): + del Vector3.__getattr__ + if '__oldsetattr__' in dir(Vector3): + Vector3.__setattr__ = Vector3.__oldsetattr__ class Vector2(object): @@ -166,7 +175,8 @@ def __rsub__(self, other): def __mul__(self, other): if isinstance(other, Number): - return Vector2(self._x * float(other), self._y * float(other)) + other = float(other) + return Vector2(self._x * other, self._y * other) elif isinstance(other, Vector2): return self.dot(other) return NotImplemented @@ -176,7 +186,8 @@ def __rmul__(self, other): def __div__(self, other): if isinstance(other, Number): - return Vector2(self._x / float(other), self._y / float(other)) + other = float(other) + return Vector2(self._x / other, self._y / other) return NotImplemented def __floordiv__(self, other): @@ -381,39 +392,11 @@ def slerp(self, vec, t): def elementwise(self): """Return object for an elementwise operation.""" - return ElementwiseVectorProxy(self) + return ElementwiseVector2Proxy(self) def rotate(self, angle): """rotates a vector by a given angle in degrees.""" - # make sure angle is in range [0, 360) - angle = angle % 360.0 - - # special-case rotation by 0, 90, 180 and 270 degrees - if ((angle + VECTOR_EPSILON) % 90.0) < 2 * VECTOR_EPSILON: - quad = int((angle + VECTOR_EPSILON) / 90) - if quad == 0 or quad == 4: # 0 or 360 degrees - x = self._x - y = self._y - elif quad == 1: # 90 degrees - x = -self._y - y = self._x - elif quad == 2: # 180 degreees - x = -self._x - y = -self._y - elif quad == 3: # 270 degrees - x = self._y - y = -self._x - else: - # this should NEVER happen and means a bug in the code - raise RuntimeError("Please report this bug in Vector2.rotate " - "to the developers") - else: - angle_rad = math.radians(angle) - sin_value = math.sin(angle_rad) - cos_value = math.cos(angle_rad) - x = cos_value * self._x - sin_value * self._y - y = sin_value * self._x + cos_value * self._y - + x, y = _rotate_2d(self._x, self._y, angle) return Vector2(x, y) def rotate_ip(self, angle): @@ -449,11 +432,549 @@ def from_polar(self, polar): class Vector3(object): - pass + """A 3-Dimensional Vector.""" + def __init__(self, *args): + if len(args) == 0: + self.x = 0.0 + self.y = 0.0 + self.z = 0.0 + elif len(args) == 1: + if isinstance(args[0], Vector3): + self.x = args[0].x + self.y = args[0].y + self.z = args[0].z + elif isinstance(args[0], basestring): + if args[0].startswith(""): + tokens = args[0][9:-2].split(",") + try: + self.x = float(tokens[0]) + self.y = float(tokens[1]) + self.z = float(tokens[2]) + except ValueError: + raise TypeError("Invalid string arguments - not floats ({}).". + format(args[0])) + else: + raise TypeError("Invalid string argument - not like __repr__ ({}).". + format(args[0])) + elif len(args[0]) == 3: + self.x = args[0][0] + self.y = args[0][1] + self.z = args[0][2] + else: + raise TypeError("Invalid argument length ({}).".format(len(args[0]))) + else: + self.x = args[0] + self.y = args[1] + self.z = args[2] -class ElementwiseVectorProxy(object): - """Class used internally for elementwise vector operations.""" + def __repr__(self): + return "".format(self._x, self._y, self._z) + + def __str__(self): + return "[{}, {}, {}]".format(self._x, self._y, self._z) + + def __len__(self): + return 3 + + def __getitem__(self, key): + """Provide indexed read access (vec[0] is x, vec[1] is y, vec[2] is z).""" + return [self._x, self._y, self._z][key] + + def __setitem__(self, key, value): + """Provide indexed modification.""" + if isinstance(value, Vector3): + value = [value.x, value.y, value.z] + if isinstance(key, slice): + indices = range(*key.indices(len(self))) + if len(value) <= len(self) and len(value) == len(indices): + for count, index in enumerate(indices): + self[index] = value[count] + else: + raise ValueError("Invalid slice or value arguments (key {}, value {}).". + format(key, value)) + elif isinstance(key, int): + if key < 0: + key += len(self) + if key == 0: + self.x = value + elif key == 1: + self.y = value + elif key == 2: + self.z = value + else: + raise IndexError("Out of range index requested: {}".format(key)) + else: + raise TypeError("Invalid argument type") + + def __delitem__(self): + """Item deletion not supported.""" + raise TypeError("Items may not be deleted.") + + def __eq__(self, other): + if isinstance(other, Vector3): + return (abs(self._x - other.x) < VECTOR_EPSILON + and abs(self._y - other.y) < VECTOR_EPSILON + and abs(self._z - other.z) < VECTOR_EPSILON) + elif hasattr(other, '__iter__'): + try: + other_v = Vector3(other) + return self == other_v + except TypeError: + # Doesn't seem to be vector3-like, so NotImplemented + return False + return False + + def __ne__(self, other): + return not (self == other) + + def __add__(self, other): + if isinstance(other, Vector3): + return Vector3(self._x + other.x, self._y + other.y, self._z + other.z) + elif hasattr(other, '__iter__'): + try: + other_v = Vector3(other) + return self + other_v + except TypeError: + # Doesn't seem to be vector3-like, so NotImplemented + return NotImplemented + return NotImplemented + + def __radd__(self, other): + return self.__add__(other) + + def __sub__(self, other): + if isinstance(other, Vector3): + return Vector3(self._x - other.x, self._y - other.y, self._z - other.z) + elif hasattr(other, '__iter__'): + try: + other_v = Vector3(other) + return self - other_v + except TypeError: + # Doesn't seem to be vector3-like, so NotImplemented + return NotImplemented + return NotImplemented + + def __rsub__(self, other): + if isinstance(other, Vector3): + return Vector3(other.x - self._x, other.y - self._y, other.z - self._z) + elif hasattr(other, '__iter__'): + try: + other_v = Vector3(other) + return other_v - self + except TypeError: + # Doesn't seem to be vector3-like, so NotImplemented + return NotImplemented + return NotImplemented + + def __mul__(self, other): + if isinstance(other, Number): + other = float(other) + return Vector3(self._x * other, self._y * other, self._z * other) + elif isinstance(other, Vector3): + return self.dot(other) + return NotImplemented + + def __rmul__(self, other): + return self.__mul__(other) + + def __div__(self, other): + if isinstance(other, Number): + other = float(other) + return Vector3(self._x / other, self._y / other, self._z / other) + return NotImplemented + + def __floordiv__(self, other): + if isinstance(other, Number): + return Vector3(self._x // other, self._y // other, self._z // other) + return NotImplemented + + def __bool__(self): + """bool operator for Python 3.""" + return self._x != 0 or self._y != 0 or self._z != 0 + + def __nonzero__(self): + """bool operator for Python 2.""" + return self.__bool__() + + def __neg__(self): + return Vector3(-self._x, -self._y, -self._z) + + def __pos__(self): + return Vector3(self._x, self._y, self._z) + + def __iter__(self): + return (coord for coord in [self._x, self._y, self._z]) + + def __getattr_swizzle__(self, name): + result = [] + for letter in name: + if letter == 'x': + result.append(self._x) + elif letter == 'y': + result.append(self._y) + elif letter == 'z': + result.append(self._z) + else: + raise AttributeError('Invalid swizzle request: {}.'.format(name)) + return tuple(result) + + def __setattr_swizzle__(self, name, value): + if name == 'xyz': + super(Vector3, self).__setattr__('_x', value[0]) + super(Vector3, self).__setattr__('_y', value[1]) + super(Vector3, self).__setattr__('_z', value[2]) + # TODO: add other permutations... + elif name == 'xx' or name == 'yy' or name == 'zz': + raise AttributeError('Invalid swizzle request: {}={}.'.format(name, value)) + else: + super(Vector3, self).__setattr__(name, value) + + @property + def x(self): + """Vector x value.""" + return self._x + + @x.setter + def x(self, value): + if isinstance(value, Number): + self._x = value + else: + raise TypeError("Value {} is not a valid number.".format(value)) + + @property + def y(self): + """Vector y value.""" + return self._y + + @y.setter + def y(self, value): + if isinstance(value, Number): + self._y = value + else: + raise TypeError("Value {} is not a valid number.".format(value)) + + @property + def z(self): + """Vector z value.""" + return self._z + + @z.setter + def z(self, value): + if isinstance(value, Number): + self._z = value + else: + raise TypeError("Value {} is not a valid number.".format(value)) + + def dot(self, vec): + """calculates the dot- or scalar-product with the other vector. + + dot(Vector3) -> float. + """ + if not isinstance(vec, Vector3): + vec = Vector3(vec) + return self._x * vec.x + self._y * vec.y + self._z * vec.z + + def cross(self, vec): + """calculates the cross- or vector-product with the other vector. + + cross(Vector3) -> Vector3 + """ + if not isinstance(vec, Vector3): + vec = Vector3(vec) + return Vector3( + self._y * vec.z - self._z * vec.y, + self._z * vec.x - self._x * vec.z, + self._x * vec.y - self._y * vec.x + ) + + def length(self): + """returns the euclidic length of the vector.""" + return math.sqrt(self._x * self._x + self._y * self._y + self._z * self._z) + + def length_squared(self): + """returns the squared euclidic length of the vector.""" + return self._x * self._x + self._y * self._y + self._z * self._z + + def normalize(self): + """returns a vector with the same direction but length 1.""" + length = self.length() + if length >= VECTOR_EPSILON: + return Vector3(self._x/length, self._y/length, self._z/length) + else: + raise ValueError("Can't normalize Vector of length Zero") + + def normalize_ip(self): + """normalizes the vector in place so that its length is 1.""" + norm_vec = self.normalize() + self.x = norm_vec.x + self.y = norm_vec.y + self.z = norm_vec.z + + def is_normalized(self): + """tests if the vector is normalized i.e. has length == 1.""" + length_squared = self.length_squared() + return abs(length_squared - 1.0) < VECTOR_EPSILON + + def scale_to_length(self, new_length): + """scales the vector to a given length.""" + length = self.length() + if length >= VECTOR_EPSILON: + new_vec = Vector3(self._x/length*new_length, + self._y/length*new_length, + self._z/length*new_length) + self.x = new_vec.x + self.y = new_vec.y + self.z = new_vec.z + else: + raise ValueError("Cannot scale a vector with zero length") + + def reflect(self, normal): + """returns a vector reflected around a given normal.""" + normal_vec = Vector3(normal).normalize() # can't assume input is normalized + dot_product = self.dot(normal_vec) + result_vec = Vector3(self._x - 2 * normal_vec.x * dot_product, + self._y - 2 * normal_vec.y * dot_product, + self._z - 2 * normal_vec.z * dot_product) + return result_vec + + def reflect_ip(self, normal): + """reflect the vector around a given normal in place.""" + result_vec = self.reflect(normal) + self.x = result_vec.x + self.y = result_vec.y + self.z = result_vec.z + + def distance_to(self, vec): + """calculates the Euclidic distance to a given vector.""" + if not isinstance(vec, Vector3): + vec = Vector3(vec) + delta = self - vec + return delta.length() + + def distance_squared_to(self, vec): + """calculates the squared Euclidic distance to a given vector.""" + if not isinstance(vec, Vector3): + vec = Vector3(vec) + delta = self - vec + return delta.length_squared() + + def lerp(self, vec, t): + """returns a linear interpolation to the given vector, with t in [0..1].""" + if t < 0 or t > 1: + raise ValueError("Argument 't' must be in range [0, 1]") + elif not isinstance(vec, Vector3): + raise TypeError("Expected 'vec' to be of type Vector3") + else: + # check for special cases, exit early + if t == 0: + return self + elif t == 1: + return vec + + x = self._x * (1 - t) + vec.x * t + y = self._y * (1 - t) + vec.y * t + z = self._z * (1 - t) + vec.z * t + return Vector3(x, y, z) + + def slerp(self, vec, t): + """returns a spherical interpolation to the given vector, with t in [-1..1].""" + if t < -1 or t > 1: + raise ValueError("Argument 't' must be in range [-1, 1]") + elif not isinstance(vec, Vector3): + raise TypeError("Expected 'vec' to be of type Vector3") + else: + # check for special cases, exit early + if t == 0: + return self + elif t == -1 or t == 1: + return vec + + spherical_self = self.as_spherical() + spherical_other = vec.as_spherical() + if spherical_self[0] < VECTOR_EPSILON or spherical_other[0] < VECTOR_EPSILON: + raise ValueError("Can't use slerp with zero vector") + new_radius = spherical_self[0] * (1 - abs(t)) + spherical_other[0] * abs(t) + theta_delta = (spherical_other[1] - spherical_self[1]) % 360.0 + phi_delta = (spherical_other[2] - spherical_self[2]) % 360.0 + if abs(theta_delta - 180) < VECTOR_EPSILON: + raise ValueError("Slerp with theta 180 degrees is undefined.") + if abs(phi_delta - 180) < VECTOR_EPSILON: + raise ValueError("Slerp with phi 180 degrees is undefined.") + if t >= 0: + # take the shortest route + if theta_delta > 180: + theta_delta -= 360 + if phi_delta > 180: + phi_delta -= 360 + else: + # go the long way around + if theta_delta < 180: + theta_delta = 360 - theta_delta + if phi_delta < 180: + phi_delta = 360 - phi_delta + new_theta = spherical_self[1] + theta_delta * t + new_phi = spherical_self[2] + phi_delta * t + result_vec = Vector3() + result_vec.from_spherical((new_radius, new_theta, new_phi)) + return result_vec + + def elementwise(self): + """Return object for an elementwise operation.""" + return ElementwiseVector3Proxy(self) + + def rotate(self, angle, axis): + """rotates a vector by a given angle in degrees around the given axis.""" + if not isinstance(axis, Vector3): + axis = Vector3(axis) + axis_length_sq = axis.length_squared() + if axis_length_sq < VECTOR_EPSILON: + raise ValueError("Rotation Axis is to close to Zero") + elif abs(axis_length_sq - 1) > VECTOR_EPSILON: + axis.normalize_ip() + + # make sure angle is in range [0, 360) + angle = angle % 360.0 + + # special-case rotation by 0, 90, 180 and 270 degrees + if ((angle + VECTOR_EPSILON) % 90.0) < 2 * VECTOR_EPSILON: + quad = int((angle + VECTOR_EPSILON) / 90) + if quad == 0 or quad == 4: # 0 or 360 degrees + x = self._x + y = self._y + z = self._z + elif quad == 1: # 90 degrees + x = (self._x * (axis.x * axis.x) + + self._y * (axis.x * axis.y - axis.z) + + self._z * (axis.x * axis.z + axis.y)) + y = (self._x * (axis.x * axis.y + axis.z) + + self._y * (axis.y * axis.y) + + self._z * (axis.y * axis.z - axis.x)) + z = (self._x * (axis.x * axis.z - axis.y) + + self._y * (axis.y * axis.z + axis.x) + + self._z * (axis.z * axis.z)) + elif quad == 2: # 180 degreees + x = (self._x * (-1 + axis.x * axis.x * 2) + + self._y * (axis.x * axis.y * 2) + + self._z * (axis.x * axis.z * 2)) + y = (self._x * (axis.x * axis.y * 2) + + self._y * (-1 + axis.y * axis.y * 2) + + self._z * (axis.y * axis.z * 2)) + z = (self._x * (axis.x * axis.z * 2) + + self._y * (axis.y * axis.z * 2) + + self._z * (-1 + axis.z * axis.z * 2)) + elif quad == 3: # 270 degrees + x = (self._x * (axis.x * axis.x) + + self._y * (axis.x * axis.y + axis.z) + + self._z * (axis.x * axis.z - axis.y)) + y = (self._x * (axis.x * axis.y - axis.z) + + self._y * (axis.y * axis.y) + + self._z * (axis.y * axis.z + axis.x)) + z = (self._x * (axis.x * axis.z + axis.y) + + self._y * (axis.y * axis.z - axis.x) + + self._z * (axis.z * axis.z)) + else: + # this should NEVER happen and means a bug in the code + raise RuntimeError("Please report this bug in Vector3.rotate " + "to the developers") + else: + angle_rad = math.radians(angle) + sin_value = math.sin(angle_rad) + cos_value = math.cos(angle_rad) + cos_complement = 1 - cos_value + + x = (self._x * (cos_value + axis.x * axis.x * cos_complement) + + self._y * (axis.x * axis.y * cos_complement - axis.z * sin_value) + + self._z * (axis.x * axis.z * cos_complement + axis.y * sin_value)) + y = (self._x * (axis.x * axis.y * cos_complement + axis.z * sin_value) + + self._y * (cos_value + axis.y * axis.y * cos_complement) + + self._z * (axis.y * axis.z * cos_complement - axis.x * sin_value)) + z = (self._x * (axis.x * axis.z * cos_complement - axis.y * sin_value) + + self._y * (axis.y * axis.z * cos_complement + axis.x * sin_value) + + self._z * (cos_value + axis.z * axis.z * cos_complement)) + + return Vector3(x, y, z) + + def rotate_ip(self, angle, axis): + """rotates the vector by a given angle in degrees around axis, in place.""" + new_vec = self.rotate(angle, axis) + self.x = new_vec._x + self.y = new_vec._y + self.z = new_vec._z + + def rotate_x(self, angle): + """rotates a vector around the x-axis by the angle in degrees.""" + y, z = _rotate_2d(self._y, self._z, angle) + return Vector3(self._x, y, z) + + def rotate_x_ip(self, angle): + """rotates the vector around the x-axis by the angle in degrees in place.""" + new_vec = self.rotate_x(angle) + self.x = new_vec._x + self.y = new_vec._y + self.z = new_vec._z + + def rotate_y(self, angle): + """rotates a vector around the y-axis by the angle in degrees.""" + z, x = _rotate_2d(self._z, self._x, angle) + return Vector3(x, self._y, z) + + def rotate_y_ip(self, angle): + """rotates the vector around the y-axis by the angle in degrees in place.""" + new_vec = self.rotate_y(angle) + self.x = new_vec._x + self.y = new_vec._y + self.z = new_vec._z + + def rotate_z(self, angle): + """rotates a vector around the z-axis by the angle in degrees.""" + x, y = _rotate_2d(self._x, self._y, angle) + return Vector3(x, y, self._z) + + def rotate_z_ip(self, angle): + """rotates the vector around the z-axis by the angle in degrees in place.""" + new_vec = self.rotate_z(angle) + self.x = new_vec._x + self.y = new_vec._y + self.z = new_vec._z + + def angle_to(self, vec): + """calculates the minimum angle to a given vector in degrees.""" + if not isinstance(vec, Vector3): + vec = Vector3(vec) + scale = math.sqrt(self.length_squared() * vec.length_squared()) + if scale == 0: + raise ValueError("angle to zero vector is undefined.") + cos_theta_rad = self.dot(vec) / scale + theta_rad = math.acos(cos_theta_rad) + return math.degrees(theta_rad) + + def as_spherical(self): + """returns a tuple with radial distance, inclination and azimuthal angle.""" + r = self.length() + if r == 0.0: + return (0.0, 0.0, 0.0) + theta = math.degrees(math.acos(self._z / r)) + phi = math.degrees(math.atan2(self._y, self._x)) + return (r, theta, phi) + + def from_spherical(self, spherical): + """Sets x, y and z from a spherical coordinates 3-tuple.""" + if isinstance(spherical, tuple) and len(spherical) == 3: + r = spherical[0] + theta_rad = math.radians(spherical[1]) + phi_rad = math.radians(spherical[2]) + sin_theta = math.sin(theta_rad) + self.x = r * sin_theta * math.cos(phi_rad) + self.y = r * sin_theta * math.sin(phi_rad) + self.z = r * math.cos(theta_rad) + else: + raise TypeError("Expected 3 element tuple (radius, theta, phi), but got {}" + .format(spherical)) + + +class ElementwiseVector2Proxy(object): + """Class used internally for elementwise Vector2 operations.""" def __init__(self, vector): self.vector = Vector2(vector) @@ -463,7 +984,7 @@ def __add__(self, other): return Vector2(self.vector.x + other, self.vector.y + other) elif isinstance(other, Vector2): return Vector2(self.vector.x + other.x, self.vector.y + other.y) - elif isinstance(other, ElementwiseVectorProxy): + elif isinstance(other, ElementwiseVector2Proxy): return self.vector + other.vector return NotImplemented @@ -475,7 +996,7 @@ def __sub__(self, other): return Vector2(self.vector.x - other, self.vector.y - other) elif isinstance(other, Vector2): return Vector2(self.vector.x - other.x, self.vector.y - other.y) - elif isinstance(other, ElementwiseVectorProxy): + elif isinstance(other, ElementwiseVector2Proxy): return self.vector - other.vector return NotImplemented @@ -491,7 +1012,7 @@ def __mul__(self, other): return Vector2(self.vector.x * other, self.vector.y * other) elif isinstance(other, Vector2): return Vector2(self.vector.x * other.x, self.vector.y * other.y) - elif isinstance(other, ElementwiseVectorProxy): + elif isinstance(other, ElementwiseVector2Proxy): return Vector2(self.vector.x * other.vector.x, self.vector.y * other.vector.y) return NotImplemented @@ -504,7 +1025,7 @@ def __div__(self, other): return Vector2(self.vector.x / other, self.vector.y / other) elif isinstance(other, Vector2): return Vector2(self.vector.x / other.x, self.vector.y / other.y) - elif isinstance(other, ElementwiseVectorProxy): + elif isinstance(other, ElementwiseVector2Proxy): return Vector2(self.vector.x / other.vector.x, self.vector.y / other.vector.y) return NotImplemented @@ -521,7 +1042,7 @@ def __floordiv__(self, other): return Vector2(self.vector.x // other, self.vector.y // other) elif isinstance(other, Vector2): return Vector2(self.vector.x // other.x, self.vector.y // other.y) - elif isinstance(other, ElementwiseVectorProxy): + elif isinstance(other, ElementwiseVector2Proxy): return Vector2(self.vector.x // other.vector.x, self.vector.y // other.vector.y) return NotImplemented @@ -538,7 +1059,7 @@ def __pow__(self, other): return Vector2(self.vector.x ** other, self.vector.y ** other) elif isinstance(other, Vector2): return Vector2(self.vector.x ** other.x, self.vector.y ** other.y) - elif isinstance(other, ElementwiseVectorProxy): + elif isinstance(other, ElementwiseVector2Proxy): return Vector2(self.vector.x ** other.vector.x, self.vector.y ** other.vector.y) return NotImplemented @@ -555,7 +1076,7 @@ def __mod__(self, other): return Vector2(self.vector.x % other, self.vector.y % other) elif isinstance(other, Vector2): return Vector2(self.vector.x % other.x, self.vector.y % other.y) - elif isinstance(other, ElementwiseVectorProxy): + elif isinstance(other, ElementwiseVector2Proxy): return Vector2(self.vector.x % other.vector.x, self.vector.y % other.vector.y) return NotImplemented @@ -570,42 +1091,42 @@ def __rmod__(self, other): def __eq__(self, other): if isinstance(other, Number): return self.vector.x == other and self.vector.y == other - elif isinstance(other, ElementwiseVectorProxy): + elif isinstance(other, ElementwiseVector2Proxy): return self.vector.x == other.vector.x and self.vector.y == other.vector.y return NotImplemented def __neq__(self, other): if isinstance(other, Number): return self.vector.x != other or self.vector.y != other - elif isinstance(other, ElementwiseVectorProxy): + elif isinstance(other, ElementwiseVector2Proxy): return self.vector.x != other.vector.x or self.vector.y != other.vector.y return NotImplemented def __gt__(self, other): if isinstance(other, Number): return self.vector.x > other and self.vector.y > other - elif isinstance(other, ElementwiseVectorProxy): + elif isinstance(other, ElementwiseVector2Proxy): return self.vector.x > other.vector.x and self.vector.y > other.vector.y return NotImplemented def __lt__(self, other): if isinstance(other, Number): return self.vector.x < other and self.vector.y < other - elif isinstance(other, ElementwiseVectorProxy): + elif isinstance(other, ElementwiseVector2Proxy): return self.vector.x < other.vector.x and self.vector.y < other.vector.y return NotImplemented def __ge__(self, other): if isinstance(other, Number): return self.vector.x >= other and self.vector.y >= other - elif isinstance(other, ElementwiseVectorProxy): + elif isinstance(other, ElementwiseVector2Proxy): return self.vector.x >= other.vector.x and self.vector.y >= other.vector.y return NotImplemented def __le__(self, other): if isinstance(other, Number): return self.vector.x <= other and self.vector.y <= other - elif isinstance(other, ElementwiseVectorProxy): + elif isinstance(other, ElementwiseVector2Proxy): return self.vector.x <= other.vector.x and self.vector.y <= other.vector.y return NotImplemented @@ -625,3 +1146,42 @@ def __bool__(self): def __nonzero__(self): """bool operator for Python 2.""" return bool(self.vector) + + +class ElementwiseVector3Proxy(object): + """Class used internally for elementwise vector operations.""" + pass + + +def _rotate_2d(u, v, angle): + """Utility to rotate a 2D co-ord by a given angle in degrees. Returns new (u, v)""" + # make sure angle is in range [0, 360) + angle = angle % 360.0 + + # special-case rotation by 0, 90, 180 and 270 degrees + if ((angle + VECTOR_EPSILON) % 90.0) < 2 * VECTOR_EPSILON: + quad = int((angle + VECTOR_EPSILON) / 90) + if quad == 0 or quad == 4: # 0 or 360 degrees + u_new = u + v_new = v + elif quad == 1: # 90 degrees + u_new = -v + v_new = u + elif quad == 2: # 180 degreees + u_new = -u + v_new = -v + elif quad == 3: # 270 degrees + u_new = v + v_new = -u + else: + # this should NEVER happen and means a bug in the code + raise RuntimeError("Please report this bug in _rotate_2d " + "to the developers") + else: + angle_rad = math.radians(angle) + sin_value = math.sin(angle_rad) + cos_value = math.cos(angle_rad) + u_new = cos_value * u - sin_value * v + v_new = sin_value * u + cos_value * v + + return (u_new, v_new) diff --git a/test/math_test.py b/test/math_test.py index 58aabd6..5e913c1 100644 --- a/test/math_test.py +++ b/test/math_test.py @@ -721,6 +721,7 @@ def test_slerp(self): def test_lerp(self): v1 = Vector2(1, 0) v2 = Vector2(0, 1) + # test interpolation from one vector to another v1_to_v2 = v2 - v1 full_length = v1_to_v2.length() steps = 10 @@ -731,6 +732,7 @@ def test_lerp(self): v2_to_u = u - v2 self.assertAlmostEqual(v1_to_u.length(), t*full_length) self.assertAlmostEqual(v2_to_u.length(), (1-t)*full_length) + # test interpolation with self returns self self.assertEqual(v1.lerp(v1, .5), v1) self.assertEqual(v2.lerp(v2, .5), v2) # test t if outside 0..1 @@ -1078,6 +1080,10 @@ def test_rotate(self): self.assertEqual(v3.x, v2.x) self.assertEqual(v3.y, v2.y) self.assertEqual(v3.z, v2.z) + v2 = v1.rotate(30, axis) + self.assertAlmostEqual(v2.x, 0.866, 3) + self.assertAlmostEqual(v2.y, 0.000, 3) + self.assertAlmostEqual(v2.z, -0.500, 3) v1 = Vector3(-1, -1, -1) v2 = v1.rotate(-90, axis) self.assertEqual(v2.x, 1) @@ -1096,7 +1102,7 @@ def test_rotate(self): Vector3(0, 1, 0)) def test_rotate_ip(self): - v = Vector3(1, 0) + v = Vector3(1, 0, 0) axis = Vector3(0, 1, 0) self.assertEqual(v.rotate_ip(90, axis), None) self.assertEqual(v.x, 0) @@ -1121,6 +1127,11 @@ def test_rotate_x(self): self.assertEqual(v3.x, v2.x) self.assertEqual(v3.y, v2.y) self.assertEqual(v3.z, v2.z) + v1 = Vector3(2, 1, 0) + v2 = v1.rotate_x(30) + self.assertAlmostEqual(v2.x, 2.000, 3) + self.assertAlmostEqual(v2.y, 0.866, 3) + self.assertAlmostEqual(v2.z, 0.500, 3) v1 = Vector3(-1, -1, -1) v2 = v1.rotate_x(-90) self.assertEqual(v2.x, -1) @@ -1160,6 +1171,11 @@ def test_rotate_y(self): self.assertAlmostEqual(v3.x, v2.x) self.assertEqual(v3.y, v2.y) self.assertAlmostEqual(v3.z, v2.z) + v1 = Vector3(1, 2, 0) + v2 = v1.rotate_y(30) + self.assertAlmostEqual(v2.x, 0.866, 3) + self.assertAlmostEqual(v2.y, 2.000, 3) + self.assertAlmostEqual(v2.z, -0.500, 3) v1 = Vector3(-1, -1, -1) v2 = v1.rotate_y(-90) self.assertAlmostEqual(v2.x, 1) @@ -1199,6 +1215,11 @@ def test_rotate_z(self): self.assertAlmostEqual(v3.x, v2.x) self.assertAlmostEqual(v3.y, v2.y) self.assertEqual(v3.z, v2.z) + v1 = Vector3(1, 0, 2) + v2 = v1.rotate_z(30) + self.assertAlmostEqual(v2.x, 0.866, 3) + self.assertAlmostEqual(v2.y, 0.500, 3) + self.assertAlmostEqual(v2.z, 2.000, 3) v1 = Vector3(-1, -1, -1) v2 = v1.rotate_z(-90) self.assertAlmostEqual(v2.x, -1) @@ -1292,6 +1313,13 @@ def test_angle_to(self): # we should look in the same direction self.assertEqual(self.v1.rotate(self.v1.angle_to(self.v2), self.v1.cross(self.v2)).normalize(), self.v2.normalize()) + # check zero length vector don't work + self.assertRaises( + ValueError, lambda: Vector3(0, 0, 0).angle_to((-1, 0, 1))) + self.assertRaises( + ValueError, lambda: Vector3(0, 1, 1).angle_to((0, 0, 0))) + self.assertRaises( + ValueError, lambda: Vector3(0, 0, 0).angle_to((0, 0, 0))) def test_scale_to_length(self): v = Vector3(1, 1, 1) @@ -1505,7 +1533,6 @@ def test_elementwise(self): self.assertRaises(ZeroDivisionError, lambda : 2 // self.zeroVec.elementwise()) self.assertRaises(ZeroDivisionError, lambda : 2 % self.zeroVec.elementwise()) - def test_slerp(self): self.assertRaises(ValueError, lambda : self.zeroVec.slerp(self.v1, .5)) self.assertRaises(ValueError, lambda : self.v1.slerp(self.zeroVec, .5)) @@ -1529,8 +1556,27 @@ def test_slerp(self): self.assertRaises(ValueError, lambda : v1.slerp(-v1, 0.5)) def test_lerp(self): - """TODO""" - pass + v1 = Vector3(1, 0, 0) + v2 = Vector3(0, 1, 1) + # test interpolation from one vector to another + v1_to_v2 = v2 - v1 + full_length = v1_to_v2.length() + steps = 10 + for i in range(steps+1): + t = i/float(steps) + u = v1.lerp(v2, t) + v1_to_u = u - v1 + v2_to_u = u - v2 + self.assertAlmostEqual(v1_to_u.length(), t*full_length) + self.assertAlmostEqual(v2_to_u.length(), (1-t)*full_length) + # test interpolation with self returns self + self.assertEqual(v1.lerp(v1, .5), v1) + self.assertEqual(v2.lerp(v2, .5), v2) + # test t if outside 0..1 + self.assertRaises(ValueError, lambda: v1.lerp(v1, -0.1)) + self.assertRaises(ValueError, lambda: v1.lerp(v1, 1.1)) + # test if first parameter is not a vector + self.assertRaises(TypeError, lambda: v1.lerp(0, 0.5)) def test_spherical(self): v = Vector3() From 4f1a5531b6b62a3eb4bdd4bffd6922fc8d11f822 Mon Sep 17 00:00:00 2001 From: Anton Joubert Date: Sat, 18 Feb 2017 12:38:32 +0200 Subject: [PATCH 08/15] Fix some Vector2 elementwise operations - Comparisons with NaNs and small deltas were not handled the same as the original pygame implementation. - Some of the "elementwise op vector" tests were passing even though the comparison functions returned NotImplemented. Added reverse checks that break in that case. Fixed the elementwise operations in those cases. --- pygame/math.py | 102 ++++++++++++++++++++++++++++++---------------- test/math_test.py | 72 ++++++++++++++++++++++++++++++++ 2 files changed, 140 insertions(+), 34 deletions(-) diff --git a/pygame/math.py b/pygame/math.py index 3aac2c3..ee722b5 100644 --- a/pygame/math.py +++ b/pygame/math.py @@ -128,8 +128,8 @@ def __eq__(self, other): return self == other_v except TypeError: # Doesn't seem to be vector2-like, so NotImplemented - return False - return False + return NotImplemented + return NotImplemented def __ne__(self, other): return not (self == other) @@ -522,8 +522,8 @@ def __eq__(self, other): return self == other_v except TypeError: # Doesn't seem to be vector3-like, so NotImplemented - return False - return False + return NotImplemented + return NotImplemented def __ne__(self, other): return not (self == other) @@ -973,7 +973,31 @@ def from_spherical(self, spherical): .format(spherical)) -class ElementwiseVector2Proxy(object): +class ElementwiseVectorProxyBase(object): + """Bases class used internally for elementwise vector operations.""" + + def __radd__(self, other): + return self.__add__(other) + + def __rmul__(self, other): + return self.__mul__(other) + + def __neg__(self): + return -self.vector + + def __pos__(self): + return self.vector + + def __bool__(self): + """bool operator for Python 3.""" + return bool(self.vector) + + def __nonzero__(self): + """bool operator for Python 2.""" + return bool(self.vector) + + +class ElementwiseVector2Proxy(ElementwiseVectorProxyBase): """Class used internally for elementwise Vector2 operations.""" def __init__(self, vector): @@ -988,9 +1012,6 @@ def __add__(self, other): return self.vector + other.vector return NotImplemented - def __radd__(self, other): - return self.__add__(other) - def __sub__(self, other): if isinstance(other, Number): return Vector2(self.vector.x - other, self.vector.y - other) @@ -1017,9 +1038,6 @@ def __mul__(self, other): self.vector.y * other.vector.y) return NotImplemented - def __rmul__(self, other): - return self.__mul__(other) - def __div__(self, other): if isinstance(other, Number): return Vector2(self.vector.x / other, self.vector.y / other) @@ -1090,21 +1108,43 @@ def __rmod__(self, other): def __eq__(self, other): if isinstance(other, Number): - return self.vector.x == other and self.vector.y == other + dx = self.vector.x - other + dy = self.vector.y - other + elif isinstance(other, Vector2): + dx = self.vector.x - other.x + dy = self.vector.y - other.y elif isinstance(other, ElementwiseVector2Proxy): - return self.vector.x == other.vector.x and self.vector.y == other.vector.y - return NotImplemented + dx = self.vector.x - other.vector.x + dy = self.vector.y - other.vector.y + else: + return NotImplemented + # Note: comparison of dx == dx and dy == dy is to check for NaN + return (dx == dx and dy == dy and + abs(dx) < VECTOR_EPSILON and + abs(dy) < VECTOR_EPSILON) - def __neq__(self, other): + def __ne__(self, other): if isinstance(other, Number): - return self.vector.x != other or self.vector.y != other + dx = self.vector.x - other + dy = self.vector.y - other + elif isinstance(other, Vector2): + dx = self.vector.x - other.x + dy = self.vector.y - other.y elif isinstance(other, ElementwiseVector2Proxy): - return self.vector.x != other.vector.x or self.vector.y != other.vector.y - return NotImplemented + dx = self.vector.x - other.vector.x + dy = self.vector.y - other.vector.y + else: + return NotImplemented + # Note: comparison of dx != dx and dy != dy is to check for NaN + return (dx != dx or dy != dy or + abs(dx) >= VECTOR_EPSILON or + abs(dy) >= VECTOR_EPSILON) def __gt__(self, other): if isinstance(other, Number): return self.vector.x > other and self.vector.y > other + elif isinstance(other, Vector2): + return self.vector.x > other.x and self.vector.y > other.y elif isinstance(other, ElementwiseVector2Proxy): return self.vector.x > other.vector.x and self.vector.y > other.vector.y return NotImplemented @@ -1112,6 +1152,8 @@ def __gt__(self, other): def __lt__(self, other): if isinstance(other, Number): return self.vector.x < other and self.vector.y < other + elif isinstance(other, Vector2): + return self.vector.x < other.x and self.vector.y < other.y elif isinstance(other, ElementwiseVector2Proxy): return self.vector.x < other.vector.x and self.vector.y < other.vector.y return NotImplemented @@ -1119,6 +1161,8 @@ def __lt__(self, other): def __ge__(self, other): if isinstance(other, Number): return self.vector.x >= other and self.vector.y >= other + elif isinstance(other, Vector2): + return self.vector.x >= other.x and self.vector.y >= other.y elif isinstance(other, ElementwiseVector2Proxy): return self.vector.x >= other.vector.x and self.vector.y >= other.vector.y return NotImplemented @@ -1126,6 +1170,8 @@ def __ge__(self, other): def __le__(self, other): if isinstance(other, Number): return self.vector.x <= other and self.vector.y <= other + elif isinstance(other, Vector2): + return self.vector.x <= other.x and self.vector.y <= other.y elif isinstance(other, ElementwiseVector2Proxy): return self.vector.x <= other.vector.x and self.vector.y <= other.vector.y return NotImplemented @@ -1133,24 +1179,12 @@ def __le__(self, other): def __abs__(self): return Vector2(abs(self.vector.x), abs(self.vector.y)) - def __neg__(self): - return -self.vector - - def __pos__(self): - return self.vector - - def __bool__(self): - """bool operator for Python 3.""" - return bool(self.vector) - - def __nonzero__(self): - """bool operator for Python 2.""" - return bool(self.vector) +class ElementwiseVector3Proxy(ElementwiseVectorProxyBase): + """Class used internally for elementwise Vector3 operations.""" -class ElementwiseVector3Proxy(object): - """Class used internally for elementwise vector operations.""" - pass + def __init__(self, vector): + self.vector = Vector3(vector) def _rotate_2d(u, v, angle): diff --git a/test/math_test.py b/test/math_test.py index 5e913c1..1fbe32a 100644 --- a/test/math_test.py +++ b/test/math_test.py @@ -548,9 +548,13 @@ def test_elementwise(self): self.assertEqual(self.v1.elementwise() ** self.v2, (self.v1.x ** self.v2.x, self.v1.y ** self.v2.y)) self.assertEqual(self.v1.elementwise() % self.v2, (self.v1.x % self.v2.x, self.v1.y % self.v2.y)) self.assertEqual(self.v1.elementwise() > self.v2, self.v1.x > self.v2.x and self.v1.y > self.v2.y) + self.assertEqual(self.v2.elementwise() > self.v1, self.v2.x > self.v1.x and self.v2.y > self.v1.y) self.assertEqual(self.v1.elementwise() < self.v2, self.v1.x < self.v2.x and self.v1.y < self.v2.y) + self.assertEqual(self.v2.elementwise() < self.v1, self.v2.x < self.v1.x and self.v2.y < self.v1.y) self.assertEqual(self.v1.elementwise() >= self.v2, self.v1.x >= self.v2.x and self.v1.y >= self.v2.y) + self.assertEqual(self.v2.elementwise() >= self.v1, self.v2.x >= self.v1.x and self.v2.y >= self.v1.y) self.assertEqual(self.v1.elementwise() <= self.v2, self.v1.x <= self.v2.x and self.v1.y <= self.v2.y) + self.assertEqual(self.v2.elementwise() <= self.v1, self.v2.x <= self.v1.x and self.v2.y <= self.v1.y) self.assertEqual(self.v1.elementwise() == self.v2, self.v1.x == self.v2.x and self.v1.y == self.v2.y) self.assertEqual(self.v1.elementwise() != self.v2, self.v1.x != self.v2.x and self.v1.y != self.v2.y) # behaviour for "vector op elementwise" @@ -598,6 +602,11 @@ def test_elementwise2(self): v2 = self.v2 s1 = self.s1 s2 = self.s2 + v2_same = Vector2(v2.x, v2.y+0.9e-6) # See VECTOR_EPSILON + v2_different = Vector2(v2.x, v2.y+1.1e-6) + nan = float('NaN') + nan_vec = Vector2(nan, nan) + # behaviour for "elementwise op scalar" self.assertEqual(v1.elementwise() + s1, (v1.x + s1, v1.y + s1)) self.assertEqual(v1.elementwise() - s1, (v1.x - s1, v1.y - s1)) @@ -613,6 +622,11 @@ def test_elementwise2(self): self.assertEqual(v1.elementwise() >= s1, v1.x >= s1 and v1.y >= s1) self.assertEqual(v1.elementwise() <= s1, v1.x <= s1 and v1.y <= s1) self.assertEqual(v1.elementwise() != s1, v1.x != s1 and v1.y != s1) + self.assertEqual(v1.elementwise() == nan, False) + self.assertEqual(v1.elementwise() != nan, True) + self.assertEqual(nan_vec.elementwise() == nan, False) + self.assertEqual(nan_vec.elementwise() != nan, True) + # behaviour for "scalar op elementwise" self.assertEqual(s1 + v1.elementwise(), (s1 + v1.x, s1 + v1.y)) self.assertEqual(s1 - v1.elementwise(), (s1 - v1.x, s1 - v1.y)) @@ -628,6 +642,10 @@ def test_elementwise2(self): self.assertEqual(s1 <= v1.elementwise(), s1 <= v1.x and s1 <= v1.y) self.assertEqual(s1 >= v1.elementwise(), s1 >= v1.x and s1 >= v1.y) self.assertEqual(s1 != v1.elementwise(), s1 != v1.x and s1 != v1.y) + self.assertEqual(nan == v1.elementwise(), False) + self.assertEqual(nan != v1.elementwise(), True) + self.assertEqual(nan == nan_vec.elementwise(), False) + self.assertEqual(nan != nan_vec.elementwise(), True) # behaviour for "elementwise op vector" self.assertEqual(type(v1.elementwise() * v2), type(v1)) @@ -644,6 +662,13 @@ def test_elementwise2(self): self.assertEqual(v1.elementwise() <= v2, v1.x <= v2.x and v1.y <= v2.y) self.assertEqual(v1.elementwise() == v2, v1.x == v2.x and v1.y == v2.y) self.assertEqual(v1.elementwise() != v2, v1.x != v2.x and v1.y != v2.y) + self.assertEqual(v2.elementwise() == v2_same, True) + self.assertEqual(v2.elementwise() == v2_different, False) + self.assertEqual(v2.elementwise() != v2_same, False) + self.assertEqual(v2.elementwise() != v2_different, True) + self.assertEqual(v1.elementwise() == nan_vec, False) + self.assertEqual(v1.elementwise() != nan_vec, True) + # behaviour for "vector op elementwise" self.assertEqual(v2 + v1.elementwise(), v2 + v1) self.assertEqual(v2 - v1.elementwise(), v2 - v1) @@ -658,6 +683,12 @@ def test_elementwise2(self): self.assertEqual(v2 >= v1.elementwise(), v2.x >= v1.x and v2.y >= v1.y) self.assertEqual(v2 == v1.elementwise(), v2.x == v1.x and v2.y == v1.y) self.assertEqual(v2 != v1.elementwise(), v2.x != v1.x and v2.y != v1.y) + self.assertEqual(v2 == v2_same.elementwise(), True) + self.assertEqual(v2 == v2_different.elementwise(), False) + self.assertEqual(v2 != v2_same.elementwise(), False) + self.assertEqual(v2 != v2_different.elementwise(), True) + self.assertEqual(v1 == nan_vec.elementwise(), False) + self.assertEqual(v1 != nan_vec.elementwise(), True) # behaviour for "elementwise op elementwise" self.assertEqual(v2.elementwise() + v1.elementwise(), v2 + v1) @@ -673,6 +704,12 @@ def test_elementwise2(self): self.assertEqual(v2.elementwise() >= v1.elementwise(), v2.x >= v1.x and v2.y >= v1.y) self.assertEqual(v2.elementwise() == v1.elementwise(), v2.x == v1.x and v2.y == v1.y) self.assertEqual(v2.elementwise() != v1.elementwise(), v2.x != v1.x and v2.y != v1.y) + self.assertEqual(v2.elementwise() == v2_same.elementwise(), True) + self.assertEqual(v2.elementwise() == v2_different.elementwise(), False) + self.assertEqual(v2.elementwise() != v2_same.elementwise(), False) + self.assertEqual(v2.elementwise() != v2_different.elementwise(), True) + self.assertEqual(v1.elementwise() == nan_vec.elementwise(), False) + self.assertEqual(v1.elementwise() != nan_vec.elementwise(), True) # other behaviour self.assertEqual(abs(v1.elementwise()), (abs(v1.x), abs(v1.y))) @@ -1414,6 +1451,10 @@ def invalidAssignment(): self.assertRaises(TypeError, invalidAssignment) def test_elementwise(self): + v2_same = Vector3(self.v2.x, self.v2.y+0.9e-6, self.v2.z) # See VECTOR_EPSILON + v2_different = Vector3(self.v2.x, self.v2.y+1.1e-6, self.v2.z) + nan = float('NaN') + nan_vec = Vector3(nan, nan, nan) # behaviour for "elementwise op scalar" self.assertEqual(self.v1.elementwise() + self.s1, (self.v1.x + self.s1, self.v1.y + self.s1, self.v1.z + self.s1)) @@ -1441,6 +1482,10 @@ def test_elementwise(self): self.v1.x >= self.s1 and self.v1.y >= self.s1 and self.v1.z >= self.s1) self.assertEqual(self.v1.elementwise() <= self.s1, self.v1.x <= self.s1 and self.v1.y <= self.s1 and self.v1.z <= self.s1) + self.assertEqual(self.v1.elementwise() == nan, False) + self.assertEqual(self.v1.elementwise() != nan, True) + self.assertEqual(nan_vec.elementwise() == nan, False) + self.assertEqual(nan_vec.elementwise() != nan, True) # behaviour for "scalar op elementwise" self.assertEqual(5 + self.v1.elementwise(), Vector3(5, 5, 5) + self.v1) self.assertEqual(3.5 - self.v1.elementwise(), Vector3(3.5, 3.5, 3.5) - self.v1) @@ -1456,6 +1501,10 @@ def test_elementwise(self): self.assertEqual(2 <= self.v1.elementwise(), 2 <= self.v1.x and 2 <= self.v1.y and 2 <= self.v1.z) self.assertEqual(-7 >= self.v1.elementwise(), -7 >= self.v1.x and -7 >= self.v1.y and -7 >= self.v1.z) self.assertEqual(-7 != self.v1.elementwise(), -7 != self.v1.x and -7 != self.v1.y and -7 != self.v1.z) + self.assertEqual(nan == self.v1.elementwise(), False) + self.assertEqual(nan != self.v1.elementwise(), True) + self.assertEqual(nan == nan_vec.elementwise(), False) + self.assertEqual(nan != nan_vec.elementwise(), True) # behaviour for "elementwise op vector" self.assertEqual(type(self.v1.elementwise() * self.v2), type(self.v1)) @@ -1468,11 +1517,22 @@ def test_elementwise(self): self.assertEqual(self.v1.elementwise() ** self.v2, (self.v1.x ** self.v2.x, self.v1.y ** self.v2.y, self.v1.z ** self.v2.z)) self.assertEqual(self.v1.elementwise() % self.v2, (self.v1.x % self.v2.x, self.v1.y % self.v2.y, self.v1.z % self.v2.z)) self.assertEqual(self.v1.elementwise() > self.v2, self.v1.x > self.v2.x and self.v1.y > self.v2.y and self.v1.z > self.v2.z) + self.assertEqual(self.v2.elementwise() > self.v1, self.v2.x > self.v1.x and self.v2.y > self.v1.y and self.v2.z > self.v1.z) self.assertEqual(self.v1.elementwise() < self.v2, self.v1.x < self.v2.x and self.v1.y < self.v2.y and self.v1.z < self.v2.z) + self.assertEqual(self.v2.elementwise() < self.v1, self.v2.x < self.v1.x and self.v2.y < self.v1.y and self.v2.z < self.v1.z) self.assertEqual(self.v1.elementwise() >= self.v2, self.v1.x >= self.v2.x and self.v1.y >= self.v2.y and self.v1.z >= self.v2.z) + self.assertEqual(self.v2.elementwise() >= self.v1, self.v2.x >= self.v1.x and self.v2.y >= self.v1.y and self.v2.z >= self.v1.z) self.assertEqual(self.v1.elementwise() <= self.v2, self.v1.x <= self.v2.x and self.v1.y <= self.v2.y and self.v1.z <= self.v2.z) + self.assertEqual(self.v2.elementwise() <= self.v1, self.v2.x <= self.v1.x and self.v2.y <= self.v1.y and self.v2.z <= self.v1.z) self.assertEqual(self.v1.elementwise() == self.v2, self.v1.x == self.v2.x and self.v1.y == self.v2.y and self.v1.z == self.v2.z) self.assertEqual(self.v1.elementwise() != self.v2, self.v1.x != self.v2.x and self.v1.y != self.v2.y and self.v1.z != self.v2.z) + self.assertEqual(self.v2.elementwise() == v2_same, True) + self.assertEqual(self.v2.elementwise() == v2_different, False) + self.assertEqual(self.v2.elementwise() != v2_same, False) + self.assertEqual(self.v2.elementwise() != v2_different, True) + self.assertEqual(self.v1.elementwise() == nan_vec, False) + self.assertEqual(self.v1.elementwise() != nan_vec, True) + # behaviour for "vector op elementwise" self.assertEqual(self.v2 + self.v1.elementwise(), self.v2 + self.v1) self.assertEqual(self.v2 - self.v1.elementwise(), self.v2 - self.v1) @@ -1487,6 +1547,12 @@ def test_elementwise(self): self.assertEqual(self.v2 >= self.v1.elementwise(), self.v2.x >= self.v1.x and self.v2.y >= self.v1.y and self.v2.z >= self.v1.z) self.assertEqual(self.v2 == self.v1.elementwise(), self.v2.x == self.v1.x and self.v2.y == self.v1.y and self.v2.z == self.v1.z) self.assertEqual(self.v2 != self.v1.elementwise(), self.v2.x != self.v1.x and self.v2.y != self.v1.y and self.v2.z != self.v1.z) + self.assertEqual(self.v2 == v2_same.elementwise(), True) + self.assertEqual(self.v2 == v2_different.elementwise(), False) + self.assertEqual(self.v2 != v2_same.elementwise(), False) + self.assertEqual(self.v2 != v2_different.elementwise(), True) + self.assertEqual(self.v1 == nan_vec.elementwise(), False) + self.assertEqual(self.v1 != nan_vec.elementwise(), True) # behaviour for "elementwise op elementwise" self.assertEqual(self.v2.elementwise() + self.v1.elementwise(), self.v2 + self.v1) @@ -1513,6 +1579,12 @@ def test_elementwise(self): self.v2.x == self.v1.x and self.v2.y == self.v1.y and self.v2.z == self.v1.z) self.assertEqual(self.v2.elementwise() != self.v1.elementwise(), self.v2.x != self.v1.x and self.v2.y != self.v1.y and self.v2.z != self.v1.z) + self.assertEqual(self.v2.elementwise() == v2_same.elementwise(), True) + self.assertEqual(self.v2.elementwise() == v2_different.elementwise(), False) + self.assertEqual(self.v2.elementwise() != v2_same.elementwise(), False) + self.assertEqual(self.v2.elementwise() != v2_different.elementwise(), True) + self.assertEqual(self.v1.elementwise() == nan_vec.elementwise(), False) + self.assertEqual(self.v1.elementwise() != nan_vec.elementwise(), True) # other behaviour self.assertEqual(abs(self.v1.elementwise()), (abs(self.v1.x), abs(self.v1.y), abs(self.v1.z))) From 458e1e64ff09b4087c531350765bc539e6322630 Mon Sep 17 00:00:00 2001 From: Anton Joubert Date: Sat, 18 Feb 2017 12:56:08 +0200 Subject: [PATCH 09/15] Implement Vector3 elementwise operations --- pygame/math.py | 263 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 263 insertions(+) diff --git a/pygame/math.py b/pygame/math.py index ee722b5..efc647b 100644 --- a/pygame/math.py +++ b/pygame/math.py @@ -1186,6 +1186,269 @@ class ElementwiseVector3Proxy(ElementwiseVectorProxyBase): def __init__(self, vector): self.vector = Vector3(vector) + def __add__(self, other): + if isinstance(other, Number): + return Vector3(self.vector.x + other, + self.vector.y + other, + self.vector.z + other) + elif isinstance(other, Vector3): + return Vector3(self.vector.x + other.x, + self.vector.y + other.y, + self.vector.z + other.z) + elif isinstance(other, ElementwiseVector3Proxy): + return self.vector + other.vector + return NotImplemented + + def __sub__(self, other): + if isinstance(other, Number): + return Vector3(self.vector.x - other, + self.vector.y - other, + self.vector.z - other) + elif isinstance(other, Vector3): + return Vector3(self.vector.x - other.x, + self.vector.y - other.y, + self.vector.z - other.z) + elif isinstance(other, ElementwiseVector3Proxy): + return self.vector - other.vector + return NotImplemented + + def __rsub__(self, other): + if isinstance(other, Number): + return Vector3(other - self.vector.x, + other - self.vector.y, + other - self.vector.z) + elif isinstance(other, Vector3): + return Vector3(other.x - self.vector.x, + other.y - self.vector.y, + other.z - self.vector.z) + return NotImplemented + + def __mul__(self, other): + if isinstance(other, Number): + return Vector3(self.vector.x * other, + self.vector.y * other, + self.vector.z * other) + elif isinstance(other, Vector3): + return Vector3(self.vector.x * other.x, + self.vector.y * other.y, + self.vector.z * other.z) + elif isinstance(other, ElementwiseVector3Proxy): + return Vector3(self.vector.x * other.vector.x, + self.vector.y * other.vector.y, + self.vector.z * other.vector.z) + return NotImplemented + + def __div__(self, other): + if isinstance(other, Number): + return Vector3(self.vector.x / other, + self.vector.y / other, + self.vector.z / other) + elif isinstance(other, Vector3): + return Vector3(self.vector.x / other.x, + self.vector.y / other.y, + self.vector.z / other.z) + elif isinstance(other, ElementwiseVector3Proxy): + return Vector3(self.vector.x / other.vector.x, + self.vector.y / other.vector.y, + self.vector.z / other.vector.z) + return NotImplemented + + def __rdiv__(self, other): + if isinstance(other, Number): + return Vector3(other / self.vector.x, + other / self.vector.y, + other / self.vector.z) + elif isinstance(other, Vector3): + return Vector3(other.x / self.vector.x, + other.y / self.vector.y, + other.z / self.vector.z) + return NotImplemented + + def __floordiv__(self, other): + if isinstance(other, Number): + return Vector3(self.vector.x // other, + self.vector.y // other, + self.vector.z // other) + elif isinstance(other, Vector3): + return Vector3(self.vector.x // other.x, + self.vector.y // other.y, + self.vector.z // other.z) + elif isinstance(other, ElementwiseVector3Proxy): + return Vector3(self.vector.x // other.vector.x, + self.vector.y // other.vector.y, + self.vector.z // other.vector.z) + return NotImplemented + + def __rfloordiv__(self, other): + if isinstance(other, Number): + return Vector3(other // self.vector.x, + other // self.vector.y, + other // self.vector.z) + elif isinstance(other, Vector3): + return Vector3(other.x // self.vector.x, + other.y // self.vector.y, + other.z // self.vector.z) + return NotImplemented + + def __pow__(self, other): + if isinstance(other, Number): + return Vector3(self.vector.x ** other, + self.vector.y ** other, + self.vector.z ** other) + elif isinstance(other, Vector3): + return Vector3(self.vector.x ** other.x, + self.vector.y ** other.y, + self.vector.z ** other.z) + elif isinstance(other, ElementwiseVector3Proxy): + return Vector3(self.vector.x ** other.vector.x, + self.vector.y ** other.vector.y, + self.vector.z ** other.vector.z) + return NotImplemented + + def __rpow__(self, other): + if isinstance(other, Number): + return Vector3(other ** self.vector.x, + other ** self.vector.y, + other ** self.vector.z) + elif isinstance(other, Vector3): + return Vector3(other.x ** self.vector.x, + other.y ** self.vector.y, + other.z ** self.vector.z) + return NotImplemented + + def __mod__(self, other): + if isinstance(other, Number): + return Vector3(self.vector.x % other, + self.vector.y % other, + self.vector.z % other) + elif isinstance(other, Vector3): + return Vector3(self.vector.x % other.x, + self.vector.y % other.y, + self.vector.z % other.z) + elif isinstance(other, ElementwiseVector3Proxy): + return Vector3(self.vector.x % other.vector.x, + self.vector.y % other.vector.y, + self.vector.z % other.vector.z) + return NotImplemented + + def __rmod__(self, other): + if isinstance(other, Number): + return Vector3(other % self.vector.x, + other % self.vector.y, + other % self.vector.z) + elif isinstance(other, Vector3): + return Vector3(other.x % self.vector.x, + other.y % self.vector.y, + other.z % self.vector.z) + return NotImplemented + + def __eq__(self, other): + if isinstance(other, Number): + dx = self.vector.x - other + dy = self.vector.y - other + dz = self.vector.z - other + elif isinstance(other, Vector3): + dx = self.vector.x - other.x + dy = self.vector.y - other.y + dz = self.vector.z - other.z + elif isinstance(other, ElementwiseVector3Proxy): + dx = self.vector.x - other.vector.x + dy = self.vector.y - other.vector.y + dz = self.vector.z - other.vector.z + else: + return NotImplemented + # Note: comparisons like dx == dx are to check for NaN + return (dx == dx and dy == dy and dz == dz and + abs(dx) < VECTOR_EPSILON and + abs(dy) < VECTOR_EPSILON and + abs(dz) < VECTOR_EPSILON) + + def __ne__(self, other): + if isinstance(other, Number): + dx = self.vector.x - other + dy = self.vector.y - other + dz = self.vector.z - other + elif isinstance(other, Vector3): + dx = self.vector.x - other.x + dy = self.vector.y - other.y + dz = self.vector.z - other.z + elif isinstance(other, ElementwiseVector3Proxy): + dx = self.vector.x - other.vector.x + dy = self.vector.y - other.vector.y + dz = self.vector.z - other.vector.z + else: + return NotImplemented + # Note: comparisons like dx != dx are to check for NaN + return (dx != dx or dy != dy or dz != dz or + abs(dx) >= VECTOR_EPSILON or + abs(dy) >= VECTOR_EPSILON or + abs(dz) >= VECTOR_EPSILON) + + def __gt__(self, other): + if isinstance(other, Number): + return (self.vector.x > other and + self.vector.y > other and + self.vector.z > other) + elif isinstance(other, Vector3): + return (self.vector.x > other.x and + self.vector.y > other.y and + self.vector.z > other.z) + elif isinstance(other, ElementwiseVector3Proxy): + return (self.vector.x > other.vector.x and + self.vector.y > other.vector.y and + self.vector.z > other.vector.z) + return NotImplemented + + def __lt__(self, other): + if isinstance(other, Number): + return (self.vector.x < other and + self.vector.y < other and + self.vector.z < other) + elif isinstance(other, Vector3): + return (self.vector.x < other.x and + self.vector.y < other.y and + self.vector.z < other.z) + elif isinstance(other, ElementwiseVector3Proxy): + return (self.vector.x < other.vector.x and + self.vector.y < other.vector.y and + self.vector.z < other.vector.z) + return NotImplemented + + def __ge__(self, other): + if isinstance(other, Number): + return (self.vector.x >= other and + self.vector.y >= other and + self.vector.z >= other) + elif isinstance(other, Vector3): + return (self.vector.x >= other.x and + self.vector.y >= other.y and + self.vector.z >= other.z) + elif isinstance(other, ElementwiseVector3Proxy): + return (self.vector.x >= other.vector.x and + self.vector.y >= other.vector.y and + self.vector.z >= other.vector.z) + return NotImplemented + + def __le__(self, other): + if isinstance(other, Number): + return (self.vector.x <= other and + self.vector.y <= other and + self.vector.z <= other) + elif isinstance(other, Vector3): + return (self.vector.x <= other.x and + self.vector.y <= other.y and + self.vector.z <= other.z) + elif isinstance(other, ElementwiseVector3Proxy): + return (self.vector.x <= other.vector.x and + self.vector.y <= other.vector.y and + self.vector.z <= other.vector.z) + return NotImplemented + + def __abs__(self): + return Vector3(abs(self.vector.x), + abs(self.vector.y), + abs(self.vector.z)) + def _rotate_2d(u, v, angle): """Utility to rotate a 2D co-ord by a given angle in degrees. Returns new (u, v)""" From 361c8c60a214ce2c7901ca2938ce7076bb2fba1f Mon Sep 17 00:00:00 2001 From: Anton Joubert Date: Sun, 19 Feb 2017 21:12:24 +0200 Subject: [PATCH 10/15] Implement swizzling for Vector3 --- pygame/math.py | 43 +++++++++++++++++++++++++++++++++++++------ 1 file changed, 37 insertions(+), 6 deletions(-) diff --git a/pygame/math.py b/pygame/math.py index efc647b..54894a1 100644 --- a/pygame/math.py +++ b/pygame/math.py @@ -620,12 +620,43 @@ def __getattr_swizzle__(self, name): return tuple(result) def __setattr_swizzle__(self, name, value): - if name == 'xyz': - super(Vector3, self).__setattr__('_x', value[0]) - super(Vector3, self).__setattr__('_y', value[1]) - super(Vector3, self).__setattr__('_z', value[2]) - # TODO: add other permutations... - elif name == 'xx' or name == 'yy' or name == 'zz': + + def set_from_index(attr, index): + if index < len(value): + super(Vector3, self).__setattr__(attr, value[index]) + else: + raise TypeError( + 'Input too short for swizzle assignment: {}={}' + .format(name, value)) + + if name[0] in ['x', 'y', 'z']: + # might be a valid swizzle request + if isinstance(value, Number): + value = [value] # change to a list to work with loop below + num_x, num_y, num_z = 0, 0, 0 + for index, char in enumerate(name): + if char == 'x': + if num_x == 0: + set_from_index('_x', index) + num_x += 1 + elif char == 'y': + if num_y == 0: + set_from_index('_y', index) + num_y += 1 + elif char == 'z': + if num_z == 0: + set_from_index('_z', index) + num_z += 1 + else: + # might not be a swizzle request, so try other attributes + super(Vector3, self).__setattr__(name, value) + return + if num_x > 1 or num_y > 1 or num_z > 1: + raise AttributeError( + 'Attribute assignment conflicts with swizzling.: {}={}.' + .format(name, value)) + elif name.startswith('ww'): + # This is just to make the unit tests happy! raise AttributeError('Invalid swizzle request: {}={}.'.format(name, value)) else: super(Vector3, self).__setattr__(name, value) From 7d28b252f87bc64f52a65abb64d6a3ee4981e5fc Mon Sep 17 00:00:00 2001 From: Anton Joubert Date: Sun, 19 Mar 2017 16:18:24 +0200 Subject: [PATCH 11/15] Fix some failures in math and draw tests - Fixed imports for math_test, so it can also be run as part of the whole test module, as Travis does. - Draw tests run after test_circle_limits were failing because subsequent Surface constructor calls got back an 8 bpp surface, from sdl.SDL_GetVideoSurface() instead of making a new one. With the 8 bpp surface an exception is raised in _get_default_masks(..., alpha=True). Fix is to use the surface already created in setUp() instead of accessing the screen. - Fix some Python 2/3 compatibility issues. --- pygame/math.py | 32 +++++++++++++++++++++++--------- test/draw_test.py | 9 ++++----- test/math_test.py | 5 ++++- 3 files changed, 31 insertions(+), 15 deletions(-) diff --git a/pygame/math.py b/pygame/math.py index 54894a1..37cba31 100644 --- a/pygame/math.py +++ b/pygame/math.py @@ -1,9 +1,11 @@ """ Math module """ -from __future__ import absolute_import +from __future__ import absolute_import, division import math from numbers import Number +from pygame.compat import string_types + VECTOR_EPSILON = 1e-6 # For equality tests @@ -56,7 +58,7 @@ def __init__(self, *args): if isinstance(args[0], Vector2): self.x = args[0].x self.y = args[0].y - elif isinstance(args[0], basestring): + elif isinstance(args[0], string_types): if args[0].startswith(""): tokens = args[0][9:-2].split(",") try: @@ -184,12 +186,14 @@ def __mul__(self, other): def __rmul__(self, other): return self.__mul__(other) - def __div__(self, other): + def __truediv__(self, other): if isinstance(other, Number): other = float(other) return Vector2(self._x / other, self._y / other) return NotImplemented + __div__ = __truediv__ # for backwards compatibility with Python 2 + def __floordiv__(self, other): if isinstance(other, Number): return Vector2(self._x // other, self._y // other) @@ -444,7 +448,7 @@ def __init__(self, *args): self.x = args[0].x self.y = args[0].y self.z = args[0].z - elif isinstance(args[0], basestring): + elif isinstance(args[0], string_types): if args[0].startswith(""): tokens = args[0][9:-2].split(",") try: @@ -578,12 +582,14 @@ def __mul__(self, other): def __rmul__(self, other): return self.__mul__(other) - def __div__(self, other): + def __truediv__(self, other): if isinstance(other, Number): other = float(other) return Vector3(self._x / other, self._y / other, self._z / other) return NotImplemented + __div__ = __truediv__ # for backwards compatibility with Python 2 + def __floordiv__(self, other): if isinstance(other, Number): return Vector3(self._x // other, self._y // other, self._z // other) @@ -1069,7 +1075,7 @@ def __mul__(self, other): self.vector.y * other.vector.y) return NotImplemented - def __div__(self, other): + def __truediv__(self, other): if isinstance(other, Number): return Vector2(self.vector.x / other, self.vector.y / other) elif isinstance(other, Vector2): @@ -1079,13 +1085,17 @@ def __div__(self, other): self.vector.y / other.vector.y) return NotImplemented - def __rdiv__(self, other): + __div__ = __truediv__ # for backwards compatibility with Python 2 + + def __rtruediv__(self, other): if isinstance(other, Number): return Vector2(other / self.vector.x, other / self.vector.y) elif isinstance(other, Vector2): return Vector2(other.x / self.vector.x, other.y / self.vector.y) return NotImplemented + __rdiv__ = __rtruediv__ # for backwards compatibility with Python 2 + def __floordiv__(self, other): if isinstance(other, Number): return Vector2(self.vector.x // other, self.vector.y // other) @@ -1269,7 +1279,7 @@ def __mul__(self, other): self.vector.z * other.vector.z) return NotImplemented - def __div__(self, other): + def __truediv__(self, other): if isinstance(other, Number): return Vector3(self.vector.x / other, self.vector.y / other, @@ -1284,7 +1294,9 @@ def __div__(self, other): self.vector.z / other.vector.z) return NotImplemented - def __rdiv__(self, other): + __div__ = __truediv__ # for backwards compatibility with Python 2 + + def __rtruediv__(self, other): if isinstance(other, Number): return Vector3(other / self.vector.x, other / self.vector.y, @@ -1295,6 +1307,8 @@ def __rdiv__(self, other): other.z / self.vector.z) return NotImplemented + __rdiv__ = __rtruediv__ # for backwards compatibility with Python 2 + def __floordiv__(self, other): if isinstance(other, Number): return Vector3(self.vector.x // other, diff --git a/test/draw_test.py b/test/draw_test.py index d04d3d3..d9f331a 100644 --- a/test/draw_test.py +++ b/test/draw_test.py @@ -215,18 +215,17 @@ def todo_test_circle(self): def test_circle_limits(self): # Test that we don't crash on inputs greater than int16_t # Correctness is not tested - screen = pygame.display.set_mode((100, 50)) - r = draw.circle(screen, (0, 0, 0), (((2**16)//2)-1, 0), 0) + r = draw.circle(self.surf, (0, 0, 0), (((2**16)//2)-1, 0), 0) self.assertEqual(r.x, 32767) self.assertEqual(r.y, 0) self.assertEqual(r.w, 0) - r = draw.circle(screen, (0xff, 0xff, 0xff), (((2**16)//2), (2**16)//2), 0) + r = draw.circle(self.surf, (0xff, 0xff, 0xff), (((2**16)//2), (2**16)//2), 0) self.assertEqual(r.x, 32768) self.assertEqual(r.y, 32768) self.assertEqual(r.w, 0) - r = draw.circle(screen, (0xff, 0xff, 0xff), (((2**16)//2 + 1), - ((2**16)//2 + 1)), 0) + r = draw.circle(self.surf, (0xff, 0xff, 0xff), (((2**16)//2 + 1), + ((2**16)//2 + 1)), 0) self.assertEqual(r.x, 32769) self.assertEqual(r.y, 32769) self.assertEqual(r.w, 0) diff --git a/test/math_test.py b/test/math_test.py index 1fbe32a..fd2e455 100644 --- a/test/math_test.py +++ b/test/math_test.py @@ -18,7 +18,10 @@ else: is_pygame_pkg = __name__.startswith('pygame.tests.') -import unittest +if is_pygame_pkg: + from pygame.tests.test_utils import unittest +else: + from test.test_utils import unittest import math import pygame.math From f849efdd0128f0f6d0d4ed4f9e4bb585c01d7520 Mon Sep 17 00:00:00 2001 From: Anton Joubert Date: Sun, 19 Mar 2017 21:21:49 +0200 Subject: [PATCH 12/15] Fix power operator compatibility issue in math tests The behaviour of the power operator, '**' and built-in pow() function, changed in Python 3.x. In Python 3.x raising a negative number to a fractional power results in a complex number, while in earlier versions it raised a ValueError. Matching the behaviour of pygame, as per the math unit tests, requires a ValueError instead of a complex result. --- pygame/compat.py | 23 ++++++++++++++ pygame/math.py | 78 ++++++++++++++++++++++++++++++++---------------- 2 files changed, 75 insertions(+), 26 deletions(-) diff --git a/pygame/compat.py b/pygame/compat.py index 0dc797a..332ebe5 100644 --- a/pygame/compat.py +++ b/pygame/compat.py @@ -114,3 +114,26 @@ def next_(i, *args): import itertools.imap as imap_ except ImportError: imap_ = map + + +# The behaviour of the power operator, '**' and built-in pow() function, +# changed in Python 3.x. In Python 3.x raising a negative number to a +# fractional power results in a complex number, while in earlier versions +# it raised a ValueError. Matching the behaviour of pygame requires +# the ValueError instead of a complex result. The compatibility function +# defined below is provided for this purpose. +try: + (-2.2) ** 1.1 + + def pow_compat(x, y): + """Return x ** y, with Python 2.x compatible exceptions.""" + if x < 0 and not float(y).is_integer(): + raise ValueError("negative number cannot be raised to a fractional power") + else: + return x ** y + +except ValueError: + + def pow_compat(x, y): + """Return x ** y, with Python 2.x compatible exceptions.""" + return x ** y diff --git a/pygame/math.py b/pygame/math.py index 37cba31..9b541a6 100644 --- a/pygame/math.py +++ b/pygame/math.py @@ -4,7 +4,7 @@ import math from numbers import Number -from pygame.compat import string_types +from pygame.compat import string_types, pow_compat VECTOR_EPSILON = 1e-6 # For equality tests @@ -1114,21 +1114,35 @@ def __rfloordiv__(self, other): return NotImplemented def __pow__(self, other): + other_x = None + other_y = None if isinstance(other, Number): - return Vector2(self.vector.x ** other, self.vector.y ** other) + other_x = other_y = other elif isinstance(other, Vector2): - return Vector2(self.vector.x ** other.x, self.vector.y ** other.y) + other_x = other.x + other_y = other.y elif isinstance(other, ElementwiseVector2Proxy): - return Vector2(self.vector.x ** other.vector.x, - self.vector.y ** other.vector.y) - return NotImplemented + other_x = other.vector.x + other_y = other.vector.y + if other_x is not None and other_y is not None: + return Vector2(pow_compat(self.vector.x, other_x), + pow_compat(self.vector.y, other_y)) + else: + return NotImplemented def __rpow__(self, other): + other_x = None + other_y = None if isinstance(other, Number): - return Vector2(other ** self.vector.x, other ** self.vector.y) + other_x = other_y = other elif isinstance(other, Vector2): - return Vector2(other.x ** self.vector.x, other.y ** self.vector.y) - return NotImplemented + other_x = other.x + other_y = other.y + if other_x is not None and other_y is not None: + return Vector2(pow_compat(other_x, self.vector.x), + pow_compat(other_y, self.vector.y)) + else: + return NotImplemented def __mod__(self, other): if isinstance(other, Number): @@ -1336,30 +1350,42 @@ def __rfloordiv__(self, other): return NotImplemented def __pow__(self, other): + other_x = None + other_y = None + other_z = None if isinstance(other, Number): - return Vector3(self.vector.x ** other, - self.vector.y ** other, - self.vector.z ** other) + other_x = other_y = other_z = other elif isinstance(other, Vector3): - return Vector3(self.vector.x ** other.x, - self.vector.y ** other.y, - self.vector.z ** other.z) + other_x = other.x + other_y = other.y + other_z = other.z elif isinstance(other, ElementwiseVector3Proxy): - return Vector3(self.vector.x ** other.vector.x, - self.vector.y ** other.vector.y, - self.vector.z ** other.vector.z) - return NotImplemented + other_x = other.vector.x + other_y = other.vector.y + other_z = other.vector.z + if other_x is not None and other_y is not None and other_z is not None: + return Vector3(pow_compat(self.vector.x, other_x), + pow_compat(self.vector.y, other_y), + pow_compat(self.vector.z, other_z)) + else: + return NotImplemented def __rpow__(self, other): + other_x = None + other_y = None + other_z = None if isinstance(other, Number): - return Vector3(other ** self.vector.x, - other ** self.vector.y, - other ** self.vector.z) + other_x = other_y = other_z = other elif isinstance(other, Vector3): - return Vector3(other.x ** self.vector.x, - other.y ** self.vector.y, - other.z ** self.vector.z) - return NotImplemented + other_x = other.x + other_y = other.y + other_z = other.z + if other_x is not None and other_y is not None and other_z is not None: + return Vector3(pow_compat(other_x, self.vector.x), + pow_compat(other_y, self.vector.y), + pow_compat(other_z, self.vector.z)) + else: + return NotImplemented def __mod__(self, other): if isinstance(other, Number): From c085311431a6c4c626fd35eef1974efd271e99c0 Mon Sep 17 00:00:00 2001 From: Anton Joubert Date: Sun, 19 Mar 2017 22:19:35 +0200 Subject: [PATCH 13/15] Add LGPL header to math.py --- pygame/math.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/pygame/math.py b/pygame/math.py index 9b541a6..119f2c8 100644 --- a/pygame/math.py +++ b/pygame/math.py @@ -1,3 +1,23 @@ +# pygame_cffi - a cffi implementation of the pygame library +# Copyright (C) 2016 Anton Joubert +# Copyright (C) 2016 Simon Cross +# Copyright (C) 2016 Neil Muller +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Library General Public +# License along with this library; if not, write to the Free +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301 USA + """ Math module """ from __future__ import absolute_import, division From a57f07e06c2388ae5c89d3cdd888f9b38af5a053 Mon Sep 17 00:00:00 2001 From: Anton Joubert Date: Tue, 21 Mar 2017 08:53:28 +0200 Subject: [PATCH 14/15] Improve pygame.math public API and conformance - Rename internals with underscores to indicate usage - Add epsilon attribute to vectors - Allow initialisation for partial values (zero the rest) - Include math module in __init__.py --- pygame/__init__.py | 2 +- pygame/math.py | 361 +++++++++++++++++++++++++-------------------- test/math_test.py | 55 ++++++- 3 files changed, 248 insertions(+), 170 deletions(-) diff --git a/pygame/__init__.py b/pygame/__init__.py index 0d1c06c..6ddbb96 100644 --- a/pygame/__init__.py +++ b/pygame/__init__.py @@ -28,7 +28,7 @@ from pygame import ( display, color, surface, time, event, constants, sprite, mouse, locals, image, transform, pkgdata, font, mixer, - cursors, key, draw, joystick + cursors, key, draw, joystick, math ) from pygame.base import ( init, quit, HAVE_NEWBUF, get_sdl_version, get_sdl_byteorder, diff --git a/pygame/math.py b/pygame/math.py index 119f2c8..62f3b21 100644 --- a/pygame/math.py +++ b/pygame/math.py @@ -20,13 +20,15 @@ """ Math module """ -from __future__ import absolute_import, division -import math -from numbers import Number +from __future__ import absolute_import as _absolute_import +from __future__ import division as _division +import math as _math +from numbers import Number as _Number -from pygame.compat import string_types, pow_compat +from pygame.compat import string_types as _string_types +from pygame.compat import pow_compat as _pow_compat -VECTOR_EPSILON = 1e-6 # For equality tests +_VECTOR_EPSILON = 1e-6 # For equality tests (default for new vectors) def enable_swizzling(): @@ -75,10 +77,13 @@ def __init__(self, *args): self.x = 0.0 self.y = 0.0 elif len(args) == 1: - if isinstance(args[0], Vector2): + if isinstance(args[0], _Number): + self.x = args[0] + self.y = 0.0 + elif isinstance(args[0], Vector2): self.x = args[0].x self.y = args[0].y - elif isinstance(args[0], string_types): + elif isinstance(args[0], _string_types): if args[0].startswith(""): tokens = args[0][9:-2].split(",") try: @@ -98,6 +103,7 @@ def __init__(self, *args): else: self.x = args[0] self.y = args[1] + self.epsilon = _VECTOR_EPSILON def __repr__(self): return "".format(self._x, self._y) @@ -142,8 +148,8 @@ def __delitem__(self): def __eq__(self, other): if isinstance(other, Vector2): - return (abs(self._x - other.x) < VECTOR_EPSILON - and abs(self._y - other.y) < VECTOR_EPSILON) + return (abs(self._x - other.x) < self._epsilon and + abs(self._y - other.y) < self._epsilon) elif hasattr(other, '__iter__'): try: other_v = Vector2(other) @@ -196,7 +202,7 @@ def __rsub__(self, other): return NotImplemented def __mul__(self, other): - if isinstance(other, Number): + if isinstance(other, _Number): other = float(other) return Vector2(self._x * other, self._y * other) elif isinstance(other, Vector2): @@ -207,7 +213,7 @@ def __rmul__(self, other): return self.__mul__(other) def __truediv__(self, other): - if isinstance(other, Number): + if isinstance(other, _Number): other = float(other) return Vector2(self._x / other, self._y / other) return NotImplemented @@ -215,7 +221,7 @@ def __truediv__(self, other): __div__ = __truediv__ # for backwards compatibility with Python 2 def __floordiv__(self, other): - if isinstance(other, Number): + if isinstance(other, _Number): return Vector2(self._x // other, self._y // other) return NotImplemented @@ -266,7 +272,7 @@ def x(self): @x.setter def x(self, value): - if isinstance(value, Number): + if isinstance(value, _Number): self._x = value else: raise TypeError("Value {} is not a valid number.".format(value)) @@ -278,11 +284,23 @@ def y(self): @y.setter def y(self, value): - if isinstance(value, Number): + if isinstance(value, _Number): self._y = value else: raise TypeError("Value {} is not a valid number.".format(value)) + @property + def epsilon(self): + """Small value used for vector comparisons.""" + return self._epsilon + + @epsilon.setter + def epsilon(self, value): + if isinstance(value, _Number): + self._epsilon = value + else: + raise TypeError("Value {} is not a valid number.".format(value)) + def dot(self, vec): """calculates the dot- or scalar-product with the other vector. @@ -300,7 +318,7 @@ def cross(self, vec): def length(self): """returns the euclidic length of the vector.""" - return math.sqrt(self._x * self._x + self._y * self._y) + return _math.sqrt(self._x * self._x + self._y * self._y) def length_squared(self): """returns the squared euclidic length of the vector.""" @@ -309,7 +327,7 @@ def length_squared(self): def normalize(self): """returns a vector with the same direction but length 1.""" length = self.length() - if length >= VECTOR_EPSILON: + if length >= self._epsilon: return Vector2(self._x/length, self._y/length) else: raise ValueError("Can't normalize Vector of length Zero") @@ -323,12 +341,12 @@ def normalize_ip(self): def is_normalized(self): """tests if the vector is normalized i.e. has length == 1.""" length_squared = self.length_squared() - return abs(length_squared - 1.0) < VECTOR_EPSILON + return abs(length_squared - 1.0) < self._epsilon def scale_to_length(self, new_length): """scales the vector to a given length.""" length = self.length() - if length >= VECTOR_EPSILON: + if length >= self._epsilon: new_vec = Vector2(self._x/length*new_length, self._y/length*new_length) self.x = new_vec.x self.y = new_vec.y @@ -395,11 +413,11 @@ def slerp(self, vec, t): polar_self = self.as_polar() polar_other = vec.as_polar() - if polar_self[0] < VECTOR_EPSILON or polar_other[0] < VECTOR_EPSILON: + if polar_self[0] < self._epsilon or polar_other[0] < self._epsilon: raise ValueError("Can't use slerp with zero vector") new_radius = polar_self[0] * (1 - abs(t)) + polar_other[0] * abs(t) angle_delta = (polar_other[1] - polar_self[1]) % 360.0 - if abs(angle_delta - 180) < VECTOR_EPSILON: + if abs(angle_delta - 180) < self._epsilon: raise ValueError("Slerp with 180 degrees is undefined.") if t >= 0: # take the shortest route @@ -416,11 +434,11 @@ def slerp(self, vec, t): def elementwise(self): """Return object for an elementwise operation.""" - return ElementwiseVector2Proxy(self) + return _ElementwiseVector2Proxy(self) def rotate(self, angle): """rotates a vector by a given angle in degrees.""" - x, y = _rotate_2d(self._x, self._y, angle) + x, y = _rotate_2d(self._x, self._y, angle, self._epsilon) return Vector2(x, y) def rotate_ip(self, angle): @@ -433,23 +451,23 @@ def angle_to(self, vec): """calculates the minimum angle to a given vector in degrees.""" if not isinstance(vec, Vector2): vec = Vector2(vec) - angle_self_rad = math.atan2(self._y, self._x) - angle_other_rad = math.atan2(vec.y, vec.x) - return math.degrees(angle_other_rad - angle_self_rad) + angle_self_rad = _math.atan2(self._y, self._x) + angle_other_rad = _math.atan2(vec.y, vec.x) + return _math.degrees(angle_other_rad - angle_self_rad) def as_polar(self): """returns a tuple with radial distance and azimuthal angle.""" r = self.length() - angle = math.degrees(math.atan2(self._y, self._x)) + angle = _math.degrees(_math.atan2(self._y, self._x)) return (r, angle) def from_polar(self, polar): """Sets x and y from a polar coordinates tuple.""" if isinstance(polar, tuple) and len(polar) == 2: r = polar[0] - angle_rad = math.radians(polar[1]) - self.x = r * math.cos(angle_rad) - self.y = r * math.sin(angle_rad) + angle_rad = _math.radians(polar[1]) + self.x = r * _math.cos(angle_rad) + self.y = r * _math.sin(angle_rad) else: raise TypeError("Expected 2 element tuple (radius, angle), but got {}" .format(polar)) @@ -464,11 +482,15 @@ def __init__(self, *args): self.y = 0.0 self.z = 0.0 elif len(args) == 1: - if isinstance(args[0], Vector3): + if isinstance(args[0], _Number): + self.x = args[0] + self.y = 0.0 + self.z = 0.0 + elif isinstance(args[0], Vector3): self.x = args[0].x self.y = args[0].y self.z = args[0].z - elif isinstance(args[0], string_types): + elif isinstance(args[0], _string_types): if args[0].startswith(""): tokens = args[0][9:-2].split(",") try: @@ -487,10 +509,15 @@ def __init__(self, *args): self.z = args[0][2] else: raise TypeError("Invalid argument length ({}).".format(len(args[0]))) + elif len(args) == 2: + self.x = args[0] + self.y = args[1] + self.z = 0.0 else: self.x = args[0] self.y = args[1] self.z = args[2] + self.epsilon = _VECTOR_EPSILON def __repr__(self): return "".format(self._x, self._y, self._z) @@ -537,9 +564,9 @@ def __delitem__(self): def __eq__(self, other): if isinstance(other, Vector3): - return (abs(self._x - other.x) < VECTOR_EPSILON - and abs(self._y - other.y) < VECTOR_EPSILON - and abs(self._z - other.z) < VECTOR_EPSILON) + return (abs(self._x - other.x) < self._epsilon and + abs(self._y - other.y) < self._epsilon and + abs(self._z - other.z) < self._epsilon) elif hasattr(other, '__iter__'): try: other_v = Vector3(other) @@ -592,7 +619,7 @@ def __rsub__(self, other): return NotImplemented def __mul__(self, other): - if isinstance(other, Number): + if isinstance(other, _Number): other = float(other) return Vector3(self._x * other, self._y * other, self._z * other) elif isinstance(other, Vector3): @@ -603,7 +630,7 @@ def __rmul__(self, other): return self.__mul__(other) def __truediv__(self, other): - if isinstance(other, Number): + if isinstance(other, _Number): other = float(other) return Vector3(self._x / other, self._y / other, self._z / other) return NotImplemented @@ -611,7 +638,7 @@ def __truediv__(self, other): __div__ = __truediv__ # for backwards compatibility with Python 2 def __floordiv__(self, other): - if isinstance(other, Number): + if isinstance(other, _Number): return Vector3(self._x // other, self._y // other, self._z // other) return NotImplemented @@ -657,7 +684,7 @@ def set_from_index(attr, index): if name[0] in ['x', 'y', 'z']: # might be a valid swizzle request - if isinstance(value, Number): + if isinstance(value, _Number): value = [value] # change to a list to work with loop below num_x, num_y, num_z = 0, 0, 0 for index, char in enumerate(name): @@ -694,7 +721,7 @@ def x(self): @x.setter def x(self, value): - if isinstance(value, Number): + if isinstance(value, _Number): self._x = value else: raise TypeError("Value {} is not a valid number.".format(value)) @@ -706,7 +733,7 @@ def y(self): @y.setter def y(self, value): - if isinstance(value, Number): + if isinstance(value, _Number): self._y = value else: raise TypeError("Value {} is not a valid number.".format(value)) @@ -718,11 +745,23 @@ def z(self): @z.setter def z(self, value): - if isinstance(value, Number): + if isinstance(value, _Number): self._z = value else: raise TypeError("Value {} is not a valid number.".format(value)) + @property + def epsilon(self): + """Small value used for vector comparisons.""" + return self._epsilon + + @epsilon.setter + def epsilon(self, value): + if isinstance(value, _Number): + self._epsilon = value + else: + raise TypeError("Value {} is not a valid number.".format(value)) + def dot(self, vec): """calculates the dot- or scalar-product with the other vector. @@ -747,7 +786,7 @@ def cross(self, vec): def length(self): """returns the euclidic length of the vector.""" - return math.sqrt(self._x * self._x + self._y * self._y + self._z * self._z) + return _math.sqrt(self._x * self._x + self._y * self._y + self._z * self._z) def length_squared(self): """returns the squared euclidic length of the vector.""" @@ -756,7 +795,7 @@ def length_squared(self): def normalize(self): """returns a vector with the same direction but length 1.""" length = self.length() - if length >= VECTOR_EPSILON: + if length >= self._epsilon: return Vector3(self._x/length, self._y/length, self._z/length) else: raise ValueError("Can't normalize Vector of length Zero") @@ -771,12 +810,12 @@ def normalize_ip(self): def is_normalized(self): """tests if the vector is normalized i.e. has length == 1.""" length_squared = self.length_squared() - return abs(length_squared - 1.0) < VECTOR_EPSILON + return abs(length_squared - 1.0) < self._epsilon def scale_to_length(self, new_length): """scales the vector to a given length.""" length = self.length() - if length >= VECTOR_EPSILON: + if length >= self._epsilon: new_vec = Vector3(self._x/length*new_length, self._y/length*new_length, self._z/length*new_length) @@ -849,14 +888,14 @@ def slerp(self, vec, t): spherical_self = self.as_spherical() spherical_other = vec.as_spherical() - if spherical_self[0] < VECTOR_EPSILON or spherical_other[0] < VECTOR_EPSILON: + if spherical_self[0] < self._epsilon or spherical_other[0] < self._epsilon: raise ValueError("Can't use slerp with zero vector") new_radius = spherical_self[0] * (1 - abs(t)) + spherical_other[0] * abs(t) theta_delta = (spherical_other[1] - spherical_self[1]) % 360.0 phi_delta = (spherical_other[2] - spherical_self[2]) % 360.0 - if abs(theta_delta - 180) < VECTOR_EPSILON: + if abs(theta_delta - 180) < self._epsilon: raise ValueError("Slerp with theta 180 degrees is undefined.") - if abs(phi_delta - 180) < VECTOR_EPSILON: + if abs(phi_delta - 180) < self._epsilon: raise ValueError("Slerp with phi 180 degrees is undefined.") if t >= 0: # take the shortest route @@ -878,24 +917,24 @@ def slerp(self, vec, t): def elementwise(self): """Return object for an elementwise operation.""" - return ElementwiseVector3Proxy(self) + return _ElementwiseVector3Proxy(self) def rotate(self, angle, axis): """rotates a vector by a given angle in degrees around the given axis.""" if not isinstance(axis, Vector3): axis = Vector3(axis) axis_length_sq = axis.length_squared() - if axis_length_sq < VECTOR_EPSILON: + if axis_length_sq < self._epsilon: raise ValueError("Rotation Axis is to close to Zero") - elif abs(axis_length_sq - 1) > VECTOR_EPSILON: + elif abs(axis_length_sq - 1) > self._epsilon: axis.normalize_ip() # make sure angle is in range [0, 360) angle = angle % 360.0 # special-case rotation by 0, 90, 180 and 270 degrees - if ((angle + VECTOR_EPSILON) % 90.0) < 2 * VECTOR_EPSILON: - quad = int((angle + VECTOR_EPSILON) / 90) + if ((angle + self._epsilon) % 90.0) < 2 * self._epsilon: + quad = int((angle + self._epsilon) / 90) if quad == 0 or quad == 4: # 0 or 360 degrees x = self._x y = self._y @@ -935,9 +974,9 @@ def rotate(self, angle, axis): raise RuntimeError("Please report this bug in Vector3.rotate " "to the developers") else: - angle_rad = math.radians(angle) - sin_value = math.sin(angle_rad) - cos_value = math.cos(angle_rad) + angle_rad = _math.radians(angle) + sin_value = _math.sin(angle_rad) + cos_value = _math.cos(angle_rad) cos_complement = 1 - cos_value x = (self._x * (cos_value + axis.x * axis.x * cos_complement) + @@ -961,7 +1000,7 @@ def rotate_ip(self, angle, axis): def rotate_x(self, angle): """rotates a vector around the x-axis by the angle in degrees.""" - y, z = _rotate_2d(self._y, self._z, angle) + y, z = _rotate_2d(self._y, self._z, angle, self._epsilon) return Vector3(self._x, y, z) def rotate_x_ip(self, angle): @@ -973,7 +1012,7 @@ def rotate_x_ip(self, angle): def rotate_y(self, angle): """rotates a vector around the y-axis by the angle in degrees.""" - z, x = _rotate_2d(self._z, self._x, angle) + z, x = _rotate_2d(self._z, self._x, angle, self._epsilon) return Vector3(x, self._y, z) def rotate_y_ip(self, angle): @@ -985,7 +1024,7 @@ def rotate_y_ip(self, angle): def rotate_z(self, angle): """rotates a vector around the z-axis by the angle in degrees.""" - x, y = _rotate_2d(self._x, self._y, angle) + x, y = _rotate_2d(self._x, self._y, angle, self._epsilon) return Vector3(x, y, self._z) def rotate_z_ip(self, angle): @@ -999,38 +1038,38 @@ def angle_to(self, vec): """calculates the minimum angle to a given vector in degrees.""" if not isinstance(vec, Vector3): vec = Vector3(vec) - scale = math.sqrt(self.length_squared() * vec.length_squared()) + scale = _math.sqrt(self.length_squared() * vec.length_squared()) if scale == 0: raise ValueError("angle to zero vector is undefined.") cos_theta_rad = self.dot(vec) / scale - theta_rad = math.acos(cos_theta_rad) - return math.degrees(theta_rad) + theta_rad = _math.acos(cos_theta_rad) + return _math.degrees(theta_rad) def as_spherical(self): """returns a tuple with radial distance, inclination and azimuthal angle.""" r = self.length() if r == 0.0: return (0.0, 0.0, 0.0) - theta = math.degrees(math.acos(self._z / r)) - phi = math.degrees(math.atan2(self._y, self._x)) + theta = _math.degrees(_math.acos(self._z / r)) + phi = _math.degrees(_math.atan2(self._y, self._x)) return (r, theta, phi) def from_spherical(self, spherical): """Sets x, y and z from a spherical coordinates 3-tuple.""" if isinstance(spherical, tuple) and len(spherical) == 3: r = spherical[0] - theta_rad = math.radians(spherical[1]) - phi_rad = math.radians(spherical[2]) - sin_theta = math.sin(theta_rad) - self.x = r * sin_theta * math.cos(phi_rad) - self.y = r * sin_theta * math.sin(phi_rad) - self.z = r * math.cos(theta_rad) + theta_rad = _math.radians(spherical[1]) + phi_rad = _math.radians(spherical[2]) + sin_theta = _math.sin(theta_rad) + self.x = r * sin_theta * _math.cos(phi_rad) + self.y = r * sin_theta * _math.sin(phi_rad) + self.z = r * _math.cos(theta_rad) else: raise TypeError("Expected 3 element tuple (radius, theta, phi), but got {}" .format(spherical)) -class ElementwiseVectorProxyBase(object): +class _ElementwiseVectorProxyBase(object): """Bases class used internally for elementwise vector operations.""" def __radd__(self, other): @@ -1054,53 +1093,53 @@ def __nonzero__(self): return bool(self.vector) -class ElementwiseVector2Proxy(ElementwiseVectorProxyBase): +class _ElementwiseVector2Proxy(_ElementwiseVectorProxyBase): """Class used internally for elementwise Vector2 operations.""" def __init__(self, vector): self.vector = Vector2(vector) def __add__(self, other): - if isinstance(other, Number): + if isinstance(other, _Number): return Vector2(self.vector.x + other, self.vector.y + other) elif isinstance(other, Vector2): return Vector2(self.vector.x + other.x, self.vector.y + other.y) - elif isinstance(other, ElementwiseVector2Proxy): + elif isinstance(other, _ElementwiseVector2Proxy): return self.vector + other.vector return NotImplemented def __sub__(self, other): - if isinstance(other, Number): + if isinstance(other, _Number): return Vector2(self.vector.x - other, self.vector.y - other) elif isinstance(other, Vector2): return Vector2(self.vector.x - other.x, self.vector.y - other.y) - elif isinstance(other, ElementwiseVector2Proxy): + elif isinstance(other, _ElementwiseVector2Proxy): return self.vector - other.vector return NotImplemented def __rsub__(self, other): - if isinstance(other, Number): + if isinstance(other, _Number): return Vector2(other - self.vector.x, other - self.vector.y) elif isinstance(other, Vector2): return Vector2(other.x - self.vector.x, other.y - self.vector.y) return NotImplemented def __mul__(self, other): - if isinstance(other, Number): + if isinstance(other, _Number): return Vector2(self.vector.x * other, self.vector.y * other) elif isinstance(other, Vector2): return Vector2(self.vector.x * other.x, self.vector.y * other.y) - elif isinstance(other, ElementwiseVector2Proxy): + elif isinstance(other, _ElementwiseVector2Proxy): return Vector2(self.vector.x * other.vector.x, self.vector.y * other.vector.y) return NotImplemented def __truediv__(self, other): - if isinstance(other, Number): + if isinstance(other, _Number): return Vector2(self.vector.x / other, self.vector.y / other) elif isinstance(other, Vector2): return Vector2(self.vector.x / other.x, self.vector.y / other.y) - elif isinstance(other, ElementwiseVector2Proxy): + elif isinstance(other, _ElementwiseVector2Proxy): return Vector2(self.vector.x / other.vector.x, self.vector.y / other.vector.y) return NotImplemented @@ -1108,7 +1147,7 @@ def __truediv__(self, other): __div__ = __truediv__ # for backwards compatibility with Python 2 def __rtruediv__(self, other): - if isinstance(other, Number): + if isinstance(other, _Number): return Vector2(other / self.vector.x, other / self.vector.y) elif isinstance(other, Vector2): return Vector2(other.x / self.vector.x, other.y / self.vector.y) @@ -1117,17 +1156,17 @@ def __rtruediv__(self, other): __rdiv__ = __rtruediv__ # for backwards compatibility with Python 2 def __floordiv__(self, other): - if isinstance(other, Number): + if isinstance(other, _Number): return Vector2(self.vector.x // other, self.vector.y // other) elif isinstance(other, Vector2): return Vector2(self.vector.x // other.x, self.vector.y // other.y) - elif isinstance(other, ElementwiseVector2Proxy): + elif isinstance(other, _ElementwiseVector2Proxy): return Vector2(self.vector.x // other.vector.x, self.vector.y // other.vector.y) return NotImplemented def __rfloordiv__(self, other): - if isinstance(other, Number): + if isinstance(other, _Number): return Vector2(other // self.vector.x, other // self.vector.y) elif isinstance(other, Vector2): return Vector2(other.x // self.vector.x, other.y // self.vector.y) @@ -1136,118 +1175,118 @@ def __rfloordiv__(self, other): def __pow__(self, other): other_x = None other_y = None - if isinstance(other, Number): + if isinstance(other, _Number): other_x = other_y = other elif isinstance(other, Vector2): other_x = other.x other_y = other.y - elif isinstance(other, ElementwiseVector2Proxy): + elif isinstance(other, _ElementwiseVector2Proxy): other_x = other.vector.x other_y = other.vector.y if other_x is not None and other_y is not None: - return Vector2(pow_compat(self.vector.x, other_x), - pow_compat(self.vector.y, other_y)) + return Vector2(_pow_compat(self.vector.x, other_x), + _pow_compat(self.vector.y, other_y)) else: return NotImplemented def __rpow__(self, other): other_x = None other_y = None - if isinstance(other, Number): + if isinstance(other, _Number): other_x = other_y = other elif isinstance(other, Vector2): other_x = other.x other_y = other.y if other_x is not None and other_y is not None: - return Vector2(pow_compat(other_x, self.vector.x), - pow_compat(other_y, self.vector.y)) + return Vector2(_pow_compat(other_x, self.vector.x), + _pow_compat(other_y, self.vector.y)) else: return NotImplemented def __mod__(self, other): - if isinstance(other, Number): + if isinstance(other, _Number): return Vector2(self.vector.x % other, self.vector.y % other) elif isinstance(other, Vector2): return Vector2(self.vector.x % other.x, self.vector.y % other.y) - elif isinstance(other, ElementwiseVector2Proxy): + elif isinstance(other, _ElementwiseVector2Proxy): return Vector2(self.vector.x % other.vector.x, self.vector.y % other.vector.y) return NotImplemented def __rmod__(self, other): - if isinstance(other, Number): + if isinstance(other, _Number): return Vector2(other % self.vector.x, other % self.vector.y) elif isinstance(other, Vector2): return Vector2(other.x % self.vector.x, other.y % self.vector.y) return NotImplemented def __eq__(self, other): - if isinstance(other, Number): + if isinstance(other, _Number): dx = self.vector.x - other dy = self.vector.y - other elif isinstance(other, Vector2): dx = self.vector.x - other.x dy = self.vector.y - other.y - elif isinstance(other, ElementwiseVector2Proxy): + elif isinstance(other, _ElementwiseVector2Proxy): dx = self.vector.x - other.vector.x dy = self.vector.y - other.vector.y else: return NotImplemented # Note: comparison of dx == dx and dy == dy is to check for NaN return (dx == dx and dy == dy and - abs(dx) < VECTOR_EPSILON and - abs(dy) < VECTOR_EPSILON) + abs(dx) < self.vector.epsilon and + abs(dy) < self.vector.epsilon) def __ne__(self, other): - if isinstance(other, Number): + if isinstance(other, _Number): dx = self.vector.x - other dy = self.vector.y - other elif isinstance(other, Vector2): dx = self.vector.x - other.x dy = self.vector.y - other.y - elif isinstance(other, ElementwiseVector2Proxy): + elif isinstance(other, _ElementwiseVector2Proxy): dx = self.vector.x - other.vector.x dy = self.vector.y - other.vector.y else: return NotImplemented # Note: comparison of dx != dx and dy != dy is to check for NaN return (dx != dx or dy != dy or - abs(dx) >= VECTOR_EPSILON or - abs(dy) >= VECTOR_EPSILON) + abs(dx) >= self.vector.epsilon or + abs(dy) >= self.vector.epsilon) def __gt__(self, other): - if isinstance(other, Number): + if isinstance(other, _Number): return self.vector.x > other and self.vector.y > other elif isinstance(other, Vector2): return self.vector.x > other.x and self.vector.y > other.y - elif isinstance(other, ElementwiseVector2Proxy): + elif isinstance(other, _ElementwiseVector2Proxy): return self.vector.x > other.vector.x and self.vector.y > other.vector.y return NotImplemented def __lt__(self, other): - if isinstance(other, Number): + if isinstance(other, _Number): return self.vector.x < other and self.vector.y < other elif isinstance(other, Vector2): return self.vector.x < other.x and self.vector.y < other.y - elif isinstance(other, ElementwiseVector2Proxy): + elif isinstance(other, _ElementwiseVector2Proxy): return self.vector.x < other.vector.x and self.vector.y < other.vector.y return NotImplemented def __ge__(self, other): - if isinstance(other, Number): + if isinstance(other, _Number): return self.vector.x >= other and self.vector.y >= other elif isinstance(other, Vector2): return self.vector.x >= other.x and self.vector.y >= other.y - elif isinstance(other, ElementwiseVector2Proxy): + elif isinstance(other, _ElementwiseVector2Proxy): return self.vector.x >= other.vector.x and self.vector.y >= other.vector.y return NotImplemented def __le__(self, other): - if isinstance(other, Number): + if isinstance(other, _Number): return self.vector.x <= other and self.vector.y <= other elif isinstance(other, Vector2): return self.vector.x <= other.x and self.vector.y <= other.y - elif isinstance(other, ElementwiseVector2Proxy): + elif isinstance(other, _ElementwiseVector2Proxy): return self.vector.x <= other.vector.x and self.vector.y <= other.vector.y return NotImplemented @@ -1255,14 +1294,14 @@ def __abs__(self): return Vector2(abs(self.vector.x), abs(self.vector.y)) -class ElementwiseVector3Proxy(ElementwiseVectorProxyBase): +class _ElementwiseVector3Proxy(_ElementwiseVectorProxyBase): """Class used internally for elementwise Vector3 operations.""" def __init__(self, vector): self.vector = Vector3(vector) def __add__(self, other): - if isinstance(other, Number): + if isinstance(other, _Number): return Vector3(self.vector.x + other, self.vector.y + other, self.vector.z + other) @@ -1270,12 +1309,12 @@ def __add__(self, other): return Vector3(self.vector.x + other.x, self.vector.y + other.y, self.vector.z + other.z) - elif isinstance(other, ElementwiseVector3Proxy): + elif isinstance(other, _ElementwiseVector3Proxy): return self.vector + other.vector return NotImplemented def __sub__(self, other): - if isinstance(other, Number): + if isinstance(other, _Number): return Vector3(self.vector.x - other, self.vector.y - other, self.vector.z - other) @@ -1283,12 +1322,12 @@ def __sub__(self, other): return Vector3(self.vector.x - other.x, self.vector.y - other.y, self.vector.z - other.z) - elif isinstance(other, ElementwiseVector3Proxy): + elif isinstance(other, _ElementwiseVector3Proxy): return self.vector - other.vector return NotImplemented def __rsub__(self, other): - if isinstance(other, Number): + if isinstance(other, _Number): return Vector3(other - self.vector.x, other - self.vector.y, other - self.vector.z) @@ -1299,7 +1338,7 @@ def __rsub__(self, other): return NotImplemented def __mul__(self, other): - if isinstance(other, Number): + if isinstance(other, _Number): return Vector3(self.vector.x * other, self.vector.y * other, self.vector.z * other) @@ -1307,14 +1346,14 @@ def __mul__(self, other): return Vector3(self.vector.x * other.x, self.vector.y * other.y, self.vector.z * other.z) - elif isinstance(other, ElementwiseVector3Proxy): + elif isinstance(other, _ElementwiseVector3Proxy): return Vector3(self.vector.x * other.vector.x, self.vector.y * other.vector.y, self.vector.z * other.vector.z) return NotImplemented def __truediv__(self, other): - if isinstance(other, Number): + if isinstance(other, _Number): return Vector3(self.vector.x / other, self.vector.y / other, self.vector.z / other) @@ -1322,7 +1361,7 @@ def __truediv__(self, other): return Vector3(self.vector.x / other.x, self.vector.y / other.y, self.vector.z / other.z) - elif isinstance(other, ElementwiseVector3Proxy): + elif isinstance(other, _ElementwiseVector3Proxy): return Vector3(self.vector.x / other.vector.x, self.vector.y / other.vector.y, self.vector.z / other.vector.z) @@ -1331,7 +1370,7 @@ def __truediv__(self, other): __div__ = __truediv__ # for backwards compatibility with Python 2 def __rtruediv__(self, other): - if isinstance(other, Number): + if isinstance(other, _Number): return Vector3(other / self.vector.x, other / self.vector.y, other / self.vector.z) @@ -1344,7 +1383,7 @@ def __rtruediv__(self, other): __rdiv__ = __rtruediv__ # for backwards compatibility with Python 2 def __floordiv__(self, other): - if isinstance(other, Number): + if isinstance(other, _Number): return Vector3(self.vector.x // other, self.vector.y // other, self.vector.z // other) @@ -1352,14 +1391,14 @@ def __floordiv__(self, other): return Vector3(self.vector.x // other.x, self.vector.y // other.y, self.vector.z // other.z) - elif isinstance(other, ElementwiseVector3Proxy): + elif isinstance(other, _ElementwiseVector3Proxy): return Vector3(self.vector.x // other.vector.x, self.vector.y // other.vector.y, self.vector.z // other.vector.z) return NotImplemented def __rfloordiv__(self, other): - if isinstance(other, Number): + if isinstance(other, _Number): return Vector3(other // self.vector.x, other // self.vector.y, other // self.vector.z) @@ -1373,20 +1412,20 @@ def __pow__(self, other): other_x = None other_y = None other_z = None - if isinstance(other, Number): + if isinstance(other, _Number): other_x = other_y = other_z = other elif isinstance(other, Vector3): other_x = other.x other_y = other.y other_z = other.z - elif isinstance(other, ElementwiseVector3Proxy): + elif isinstance(other, _ElementwiseVector3Proxy): other_x = other.vector.x other_y = other.vector.y other_z = other.vector.z if other_x is not None and other_y is not None and other_z is not None: - return Vector3(pow_compat(self.vector.x, other_x), - pow_compat(self.vector.y, other_y), - pow_compat(self.vector.z, other_z)) + return Vector3(_pow_compat(self.vector.x, other_x), + _pow_compat(self.vector.y, other_y), + _pow_compat(self.vector.z, other_z)) else: return NotImplemented @@ -1394,21 +1433,21 @@ def __rpow__(self, other): other_x = None other_y = None other_z = None - if isinstance(other, Number): + if isinstance(other, _Number): other_x = other_y = other_z = other elif isinstance(other, Vector3): other_x = other.x other_y = other.y other_z = other.z if other_x is not None and other_y is not None and other_z is not None: - return Vector3(pow_compat(other_x, self.vector.x), - pow_compat(other_y, self.vector.y), - pow_compat(other_z, self.vector.z)) + return Vector3(_pow_compat(other_x, self.vector.x), + _pow_compat(other_y, self.vector.y), + _pow_compat(other_z, self.vector.z)) else: return NotImplemented def __mod__(self, other): - if isinstance(other, Number): + if isinstance(other, _Number): return Vector3(self.vector.x % other, self.vector.y % other, self.vector.z % other) @@ -1416,14 +1455,14 @@ def __mod__(self, other): return Vector3(self.vector.x % other.x, self.vector.y % other.y, self.vector.z % other.z) - elif isinstance(other, ElementwiseVector3Proxy): + elif isinstance(other, _ElementwiseVector3Proxy): return Vector3(self.vector.x % other.vector.x, self.vector.y % other.vector.y, self.vector.z % other.vector.z) return NotImplemented def __rmod__(self, other): - if isinstance(other, Number): + if isinstance(other, _Number): return Vector3(other % self.vector.x, other % self.vector.y, other % self.vector.z) @@ -1434,7 +1473,7 @@ def __rmod__(self, other): return NotImplemented def __eq__(self, other): - if isinstance(other, Number): + if isinstance(other, _Number): dx = self.vector.x - other dy = self.vector.y - other dz = self.vector.z - other @@ -1442,7 +1481,7 @@ def __eq__(self, other): dx = self.vector.x - other.x dy = self.vector.y - other.y dz = self.vector.z - other.z - elif isinstance(other, ElementwiseVector3Proxy): + elif isinstance(other, _ElementwiseVector3Proxy): dx = self.vector.x - other.vector.x dy = self.vector.y - other.vector.y dz = self.vector.z - other.vector.z @@ -1450,12 +1489,12 @@ def __eq__(self, other): return NotImplemented # Note: comparisons like dx == dx are to check for NaN return (dx == dx and dy == dy and dz == dz and - abs(dx) < VECTOR_EPSILON and - abs(dy) < VECTOR_EPSILON and - abs(dz) < VECTOR_EPSILON) + abs(dx) < self.vector.epsilon and + abs(dy) < self.vector.epsilon and + abs(dz) < self.vector.epsilon) def __ne__(self, other): - if isinstance(other, Number): + if isinstance(other, _Number): dx = self.vector.x - other dy = self.vector.y - other dz = self.vector.z - other @@ -1463,7 +1502,7 @@ def __ne__(self, other): dx = self.vector.x - other.x dy = self.vector.y - other.y dz = self.vector.z - other.z - elif isinstance(other, ElementwiseVector3Proxy): + elif isinstance(other, _ElementwiseVector3Proxy): dx = self.vector.x - other.vector.x dy = self.vector.y - other.vector.y dz = self.vector.z - other.vector.z @@ -1471,12 +1510,12 @@ def __ne__(self, other): return NotImplemented # Note: comparisons like dx != dx are to check for NaN return (dx != dx or dy != dy or dz != dz or - abs(dx) >= VECTOR_EPSILON or - abs(dy) >= VECTOR_EPSILON or - abs(dz) >= VECTOR_EPSILON) + abs(dx) >= self.vector.epsilon or + abs(dy) >= self.vector.epsilon or + abs(dz) >= self.vector.epsilon) def __gt__(self, other): - if isinstance(other, Number): + if isinstance(other, _Number): return (self.vector.x > other and self.vector.y > other and self.vector.z > other) @@ -1484,14 +1523,14 @@ def __gt__(self, other): return (self.vector.x > other.x and self.vector.y > other.y and self.vector.z > other.z) - elif isinstance(other, ElementwiseVector3Proxy): + elif isinstance(other, _ElementwiseVector3Proxy): return (self.vector.x > other.vector.x and self.vector.y > other.vector.y and self.vector.z > other.vector.z) return NotImplemented def __lt__(self, other): - if isinstance(other, Number): + if isinstance(other, _Number): return (self.vector.x < other and self.vector.y < other and self.vector.z < other) @@ -1499,14 +1538,14 @@ def __lt__(self, other): return (self.vector.x < other.x and self.vector.y < other.y and self.vector.z < other.z) - elif isinstance(other, ElementwiseVector3Proxy): + elif isinstance(other, _ElementwiseVector3Proxy): return (self.vector.x < other.vector.x and self.vector.y < other.vector.y and self.vector.z < other.vector.z) return NotImplemented def __ge__(self, other): - if isinstance(other, Number): + if isinstance(other, _Number): return (self.vector.x >= other and self.vector.y >= other and self.vector.z >= other) @@ -1514,14 +1553,14 @@ def __ge__(self, other): return (self.vector.x >= other.x and self.vector.y >= other.y and self.vector.z >= other.z) - elif isinstance(other, ElementwiseVector3Proxy): + elif isinstance(other, _ElementwiseVector3Proxy): return (self.vector.x >= other.vector.x and self.vector.y >= other.vector.y and self.vector.z >= other.vector.z) return NotImplemented def __le__(self, other): - if isinstance(other, Number): + if isinstance(other, _Number): return (self.vector.x <= other and self.vector.y <= other and self.vector.z <= other) @@ -1529,7 +1568,7 @@ def __le__(self, other): return (self.vector.x <= other.x and self.vector.y <= other.y and self.vector.z <= other.z) - elif isinstance(other, ElementwiseVector3Proxy): + elif isinstance(other, _ElementwiseVector3Proxy): return (self.vector.x <= other.vector.x and self.vector.y <= other.vector.y and self.vector.z <= other.vector.z) @@ -1541,14 +1580,14 @@ def __abs__(self): abs(self.vector.z)) -def _rotate_2d(u, v, angle): +def _rotate_2d(u, v, angle, epsilon): """Utility to rotate a 2D co-ord by a given angle in degrees. Returns new (u, v)""" # make sure angle is in range [0, 360) angle = angle % 360.0 # special-case rotation by 0, 90, 180 and 270 degrees - if ((angle + VECTOR_EPSILON) % 90.0) < 2 * VECTOR_EPSILON: - quad = int((angle + VECTOR_EPSILON) / 90) + if ((angle + epsilon) % 90.0) < 2 * epsilon: + quad = int((angle + epsilon) / 90) if quad == 0 or quad == 4: # 0 or 360 degrees u_new = u v_new = v @@ -1566,9 +1605,9 @@ def _rotate_2d(u, v, angle): raise RuntimeError("Please report this bug in _rotate_2d " "to the developers") else: - angle_rad = math.radians(angle) - sin_value = math.sin(angle_rad) - cos_value = math.cos(angle_rad) + angle_rad = _math.radians(angle) + sin_value = _math.sin(angle_rad) + cos_value = _math.cos(angle_rad) u_new = cos_value * u - sin_value * v v_new = sin_value * u + cos_value * v diff --git a/test/math_test.py b/test/math_test.py index fd2e455..106f675 100644 --- a/test/math_test.py +++ b/test/math_test.py @@ -57,6 +57,11 @@ def testConstructionDefault(self): self.assertEqual(v.x, 0.) self.assertEqual(v.y, 0.) + def testConstructionPartial(self): + v = Vector2(1.2) + self.assertEqual(v.x, 1.2) + self.assertEqual(v.y, 0.0) + def testConstructionXY(self): v = Vector2(1.2, 3.4) self.assertEqual(v.x, 1.2) @@ -249,8 +254,6 @@ def testUnary(self): def testCompare(self): int_vec = Vector2(3, -2) flt_vec = Vector2(3.0, -2.0) - flt_same_vec = Vector2(flt_vec.x, flt_vec.y+0.9e-6) # See VECTOR_EPSILON - flt_different_vec = Vector2(flt_vec.x, flt_vec.y+1.1e-6) zero_vec = Vector2(0, 0) nan_vec = Vector2(float('NaN'), float('NaN')) self.assertEqual(int_vec == flt_vec, True) @@ -265,11 +268,25 @@ def testCompare(self): self.assertEqual(int_vec == 5, False) self.assertEqual(int_vec != [3, -2, 0], True) self.assertEqual(int_vec == [3, -2, 0], False) - self.assertEqual(flt_vec == flt_same_vec, True) - self.assertEqual(flt_vec == flt_different_vec, False) self.assertEqual(nan_vec == nan_vec, False) self.assertEqual(nan_vec != nan_vec, True) + def testCompareEpsilon(self): + # Comparisons with default epsilon (math._VECTOR_EPSILON = 1e-6) + vec = Vector2(3.0, -2.0) + same_vec = vec + Vector2(0, 0.9e-6) + different_vec = vec + Vector2(0, 1.1e-6) + self.assertEqual(vec == same_vec, True) + self.assertEqual(vec == different_vec, False) + # Comparisons with modified epsilon + vec.epsilon = 1e-3 + same_vec = vec + Vector2(0, 0.9e-3) + different_vec = vec + Vector2(0, 1.1e-3) + self.assertEqual(vec == same_vec, True) + self.assertEqual(vec == different_vec, False) + # same_vec still has default epsilon, so reverse order comparison fails + self.assertEqual(same_vec == vec, False) + def testStr(self): v = Vector2(1.2, 3.4) self.assertEqual(str(v), "[1.2, 3.4]") @@ -829,6 +846,15 @@ def testConstructionDefault(self): self.assertEqual(v.y, 0.) self.assertEqual(v.z, 0.) + def testConstructionPartial(self): + v = Vector3(1.2) + self.assertEqual(v.x, 1.2) + self.assertEqual(v.y, 0.0) + self.assertEqual(v.z, 0.0) + v = Vector3(1.2, 3.4) + self.assertEqual(v.x, 1.2) + self.assertEqual(v.y, 3.4) + self.assertEqual(v.z, 0.0) def testConstructionXYZ(self): v = Vector3(1.2, 3.4, 9.6) @@ -1052,8 +1078,7 @@ def testUnary(self): def testCompare(self): int_vec = Vector3(3, -2, 13) flt_vec = Vector3(3.0, -2.0, 13.) - flt_same_vec = Vector3(flt_vec.x, flt_vec.y, flt_vec.z+0.9e-6) # See VECTOR_EPSILON - flt_different_vec = Vector3(flt_vec.x, flt_vec.y, flt_vec.z+1.1e-6) + zero_vec = Vector3(0, 0, 0) nan_vec = Vector3(float('NaN'), float('NaN'), float('NaN')) self.assertEqual(int_vec == flt_vec, True) @@ -1068,11 +1093,25 @@ def testCompare(self): self.assertEqual(int_vec == 5, False) self.assertEqual(int_vec != [3, -2, 0, 1], True) self.assertEqual(int_vec == [3, -2, 0, 1], False) - self.assertEqual(flt_vec == flt_same_vec, True) - self.assertEqual(flt_vec == flt_different_vec, False) self.assertEqual(nan_vec == nan_vec, False) self.assertEqual(nan_vec != nan_vec, True) + def testCompareEpsilon(self): + # Comparisons with default epsilon (math._VECTOR_EPSILON = 1e-6) + vec = Vector3(3.0, -2.0, 13.) + same_vec = vec + Vector3(0, 0, 0.9e-6) + different_vec = vec + Vector3(0, 0, 1.1e-6) + self.assertEqual(vec == same_vec, True) + self.assertEqual(vec == different_vec, False) + # Comparisons with modified epsilon + vec.epsilon = 1e-3 + same_vec = vec + Vector3(0, 0, 0.9e-3) + different_vec = vec + Vector3(0, 0, 1.1e-3) + self.assertEqual(vec == same_vec, True) + self.assertEqual(vec == different_vec, False) + # same_vec still has default epsilon, so reverse order comparison fails + self.assertEqual(same_vec == vec, False) + def testStr(self): v = Vector3(1.2, 3.4, 5.6) self.assertEqual(str(v), "[1.2, 3.4, 5.6]") From e06b19a2ecaa4bbcc6f570d86ae1ed2637c8908f Mon Sep 17 00:00:00 2001 From: Anton Joubert Date: Thu, 23 Mar 2017 08:22:05 +0200 Subject: [PATCH 15/15] Improve formatting consistency for __future__ imports --- benchmarks/run_benchmark.py | 1 + pygame/mask.py | 1 + pygame/math.py | 1 + pygame/mixer.py | 1 + pygame/transform.py | 1 + 5 files changed, 5 insertions(+) diff --git a/benchmarks/run_benchmark.py b/benchmarks/run_benchmark.py index a2dc05c..f81b534 100644 --- a/benchmarks/run_benchmark.py +++ b/benchmarks/run_benchmark.py @@ -1,4 +1,5 @@ from __future__ import absolute_import + import math import sys import time diff --git a/pygame/mask.py b/pygame/mask.py index 1dabd48..6a0c3c5 100644 --- a/pygame/mask.py +++ b/pygame/mask.py @@ -19,6 +19,7 @@ # Implement the pygame API for the bitmask functions from __future__ import absolute_import + import math from pygame._sdl import sdl, ffi diff --git a/pygame/math.py b/pygame/math.py index 62f3b21..cf07aa8 100644 --- a/pygame/math.py +++ b/pygame/math.py @@ -22,6 +22,7 @@ from __future__ import absolute_import as _absolute_import from __future__ import division as _division + import math as _math from numbers import Number as _Number diff --git a/pygame/mixer.py b/pygame/mixer.py index a7a419e..d0afe79 100644 --- a/pygame/mixer.py +++ b/pygame/mixer.py @@ -22,6 +22,7 @@ """ pygame module for loading and playing sounds """ from __future__ import absolute_import + from io import IOBase import math diff --git a/pygame/transform.py b/pygame/transform.py index b364459..80ca70d 100644 --- a/pygame/transform.py +++ b/pygame/transform.py @@ -21,6 +21,7 @@ """ the pygame transfrom module """ from __future__ import absolute_import + import math from pygame._error import SDLError