From 3ac9bb4bde3f6c3b71f71700d902d22006e71ef8 Mon Sep 17 00:00:00 2001 From: Anton Soloviev Date: Thu, 15 Oct 2026 13:06:00 -0700 Subject: [PATCH] Convert vectors to nonorthogonal bases using both coordinates --- pymunk/tests/test_vec2d.py | 28 ++++++++++++++++++++++++++++ pymunk/vec2d.py | 12 ++++++++++-- 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/pymunk/tests/test_vec2d.py b/pymunk/tests/test_vec2d.py index 8aaab99b..ca75cc80 100644 --- a/pymunk/tests/test_vec2d.py +++ b/pymunk/tests/test_vec2d.py @@ -28,3 +28,31 @@ def testComparison(self) -> None: self.assertTrue(int_vec != (0, 0)) self.assertTrue(int_vec != 5) # type: ignore self.assertTrue(int_vec != (3, -2, -5)) # type: ignore + + def testNonorthogonalBasis(self) -> None: + bases = [ + ((32, 16), (-32, 16)), + ((2, 1), (1, 3)), + ((1, 3), (2, 1)), + ((-2, -1), (1, 3)), + ] + for x_axis, y_axis in bases: + for expected in [(1, 0), (0, 1), (3, -2), (-0.5, 1.5)]: + with self.subTest(basis=(x_axis, y_axis), expected=expected): + world = Vec2d(*x_axis) * expected[0] + Vec2d(*y_axis) * expected[1] + actual = world.convert_to_basis(x_axis, y_axis) + self.assertAlmostEqual(actual.x, expected[0]) + self.assertAlmostEqual(actual.y, expected[1]) + + def testOrthogonalBasis(self) -> None: + for x_axis, y_axis in [((5, 0), (0, 0.5)), ((3, 4), (-4, 3))]: + with self.subTest(basis=(x_axis, y_axis)): + expected = Vec2d(2, -3) + world = Vec2d(*x_axis) * expected.x + Vec2d(*y_axis) * expected.y + self.assertEqual(world.convert_to_basis(x_axis, y_axis), expected) + + def testDependentBasis(self) -> None: + for x_axis, y_axis in [((0, 0), (0, 1)), ((1, 2), (2, 4))]: + with self.subTest(basis=(x_axis, y_axis)): + with self.assertRaises(ZeroDivisionError): + Vec2d(3, 4).convert_to_basis(x_axis, y_axis) diff --git a/pymunk/vec2d.py b/pymunk/vec2d.py index 4e417398..54202491 100644 --- a/pymunk/vec2d.py +++ b/pymunk/vec2d.py @@ -571,13 +571,21 @@ def convert_to_basis( ) -> "Vec2d": """Convert the vector to a new basis defined by the given x and y vectors. + The basis vectors must be linearly independent, but need not be + perpendicular. + >>> Vec2d(10, 1).convert_to_basis((5, 0), (0, 0.5)) Vec2d(2.0, 2.0) + >>> Vec2d(32, 16).convert_to_basis((32, 16), (-32, 16)) + Vec2d(1.0, 0.0) + + :raises ZeroDivisionError: If the basis vectors are linearly dependent. """ assert len(x_vector) == 2 assert len(y_vector) == 2 - x = self.dot(x_vector) / Vec2d(*x_vector).length_squared - y = self.dot(y_vector) / Vec2d(*y_vector).length_squared + determinant = x_vector[0] * y_vector[1] - x_vector[1] * y_vector[0] + x = (self.x * y_vector[1] - self.y * y_vector[0]) / determinant + y = (x_vector[0] * self.y - x_vector[1] * self.x) / determinant return Vec2d(x, y) @property