diff --git a/doc/python/magnum.math.rst b/doc/python/magnum.math.rst index feeacb7..3a13ca6 100644 --- a/doc/python/magnum.math.rst +++ b/doc/python/magnum.math.rst @@ -322,6 +322,8 @@ :raise ValueError: If either of the quaternions is not normalized .. py:function:: magnum.Quaternion.rotation(angle: magnum.Rad, normalized_axis: magnum.Vector3) :raise ValueError: If :p:`normalized_axis` is not normalized +.. py:function:: magnum.Quaternion.rotation(normalized_from: magnum.Vector3, normalized_to: magnum.Vector3) + :raise ValueError: If either of the vectors is not normalized .. py:function:: magnum.Quaterniond.rotation(angle: magnum.Rad, normalized_axis: magnum.Vector3d) :raise ValueError: If :p:`normalized_axis` is not normalized .. py:function:: magnum.Quaternion.reflection diff --git a/doc/python/pages/changelog.rst b/doc/python/pages/changelog.rst index 4c31378..1efa02d 100644 --- a/doc/python/pages/changelog.rst +++ b/doc/python/pages/changelog.rst @@ -68,8 +68,8 @@ Changelog - Exposed :ref:`Color3.from_xyz()`, :ref:`Color3.from_linear_rgb_int()`, :ref:`Color3.to_xyz()`, :ref:`Color3.to_linear_rgb_int()` and equivalent APIs on :ref:`Color4` -- Exposed new :ref:`Quaternion.reflection()` and - :ref:`Quaternion.reflect_vector()` APIs +- Exposed new :ref:`Quaternion.rotation()`, :ref:`Quaternion.reflection()` + and :ref:`Quaternion.reflect_vector()` APIs - Exposed :ref:`gl.Context` and its platform-specific subclasses for EGL, WGL and GLX - Exposed :ref:`gl.Framebuffer.attach_texture()` and missing sRGB, depth diff --git a/src/python/magnum/math.cpp b/src/python/magnum/math.cpp index ea40ebe..673c0ed 100644 --- a/src/python/magnum/math.cpp +++ b/src/python/magnum/math.cpp @@ -348,6 +348,13 @@ template void quaternion(py::module_& m, py::class_& c) { } return T::rotation(Math::Rad(angle), normalizedAxis); }, "Rotation quaternion", py::arg("angle"), py::arg("normalized_axis")) + .def_static("rotation", [](const Math::Vector3& normalizedFrom, const Math::Vector3& normalizedTo) { + if(!normalizedFrom.isNormalized() || !normalizedTo.isNormalized()) { + PyErr_Format(PyExc_ValueError, "vectors %S and %S are not normalized", py::cast(normalizedFrom).ptr(), py::cast(normalizedTo).ptr()); + throw py::error_already_set{}; + } + return T::rotation(normalizedFrom, normalizedTo); + }, "Quaternion rotating from a vector to another", py::arg("normalized_from"), py::arg("normalized_to")) .def_static("reflection", [](const Math::Vector3& normal) { if(!normal.isNormalized()) { PyErr_Format(PyExc_ValueError, "normal %S is not normalized", py::cast(normal).ptr()); diff --git a/src/python/magnum/test/test_math.py b/src/python/magnum/test/test_math.py index 0b104e4..7af36d3 100644 --- a/src/python/magnum/test/test_math.py +++ b/src/python/magnum/test/test_math.py @@ -1451,15 +1451,26 @@ class Quaternion_(unittest.TestCase): self.assertEqual(a, Quaternion((0.382683, 0.0, 0.0), 0.92388)) self.assertEqual(a.to_matrix(), Matrix4.rotation_x(Deg(45.0)).rotation_scaling()) - b = Quaternion.from_matrix(Matrix4.rotation_x(Deg(45.0)).rotation_scaling()) - self.assertEqual(b, Quaternion((0.382683, 0.0, 0.0), 0.92388)) + # Same as in QuaternionTest::rotationTwoVectors() + b = Vector3(1.0/math.sqrt3) + c = Vector3(Vector2(1.0/math.sqrt2), 0.0) + d = Quaternion.rotation(b, c) + self.assertEqual(d, Quaternion((-0.214186, 0.214186, 0.0), 0.953021)) + self.assertEqual(d.transform_vector(b), c) - c = Quaternion.reflection(Vector3.x_axis()) - self.assertEqual(c, Quaternion((1.0, 0.0, 0.0), 0.0)) + e = Quaternion.from_matrix(Matrix4.rotation_x(Deg(45.0)).rotation_scaling()) + self.assertEqual(e, Quaternion((0.382683, 0.0, 0.0), 0.92388)) + + f = Quaternion.reflection(Vector3.x_axis()) + self.assertEqual(f, Quaternion((1.0, 0.0, 0.0), 0.0)) def test_static_methods_invalid(self): with self.assertRaisesRegex(ValueError, "axis Vector\\(2, 0, 1\\) is not normalized"): Quaternion.rotation(Deg(35.0), Vector3(2.0, 0.0, 1.0)) + with self.assertRaisesRegex(ValueError, "vectors Vector\\(2, 0, 0\\) and Vector\\(0, 1, 0\\) are not normalized"): + Quaternion.rotation(Vector3(2.0, 0.0, 0.0), Vector3(0.0, 1.0, 0.0)) + with self.assertRaisesRegex(ValueError, "vectors Vector\\(1, 0, 0\\) and Vector\\(0, 2, 0\\) are not normalized"): + Quaternion.rotation(Vector3(1.0, 0.0, 0.0), Vector3(0.0, 2.0, 0.0)) with self.assertRaisesRegex(ValueError, "normal Vector\\(2, 0, 1\\) is not normalized"): Quaternion.reflection(Vector3(2.0, 0.0, 1.0)) with self.assertRaisesRegex(ValueError, """the matrix is not a rotation: