From a55a7e6009a8f780605be7728604bb2f17bc0510 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Wed, 24 Apr 2019 18:42:06 +0200 Subject: [PATCH] python: expose Quaternion. --- src/python/magnum/__init__.py | 2 + src/python/magnum/magnum.cpp | 4 - src/python/magnum/math.cpp | 117 ++++++++++++++++++++++++++++ src/python/magnum/test/test_math.py | 50 ++++++++++++ 4 files changed, 169 insertions(+), 4 deletions(-) diff --git a/src/python/magnum/__init__.py b/src/python/magnum/__init__.py index 7b0a107..df831e1 100644 --- a/src/python/magnum/__init__.py +++ b/src/python/magnum/__init__.py @@ -43,4 +43,6 @@ __all__ = [ 'Matrix3x2d', 'Matrix3x3d', 'Matrix3x4d', 'Matrix4x2d', 'Matrix4x3d', 'Matrix4x4d', 'Matrix3', 'Matrix4', 'Matrix3d', 'Matrix4d', + + 'Quaternion', 'Quaterniond' ] diff --git a/src/python/magnum/magnum.cpp b/src/python/magnum/magnum.cpp index b235dcb..ba1c434 100644 --- a/src/python/magnum/magnum.cpp +++ b/src/python/magnum/magnum.cpp @@ -34,8 +34,4 @@ PYBIND11_MODULE(_magnum, m) { py::module math = m.def_submodule("math"); magnum::math(m, math); - magnum::mathVectorFloat(m, math); - magnum::mathVectorIntegral(m, math); - magnum::mathMatrixFloat(m); - magnum::mathMatrixDouble(m); } diff --git a/src/python/magnum/math.cpp b/src/python/magnum/math.cpp index f12391b..00bb92e 100644 --- a/src/python/magnum/math.cpp +++ b/src/python/magnum/math.cpp @@ -29,6 +29,7 @@ #include #include #include +#include #include "magnum/bootstrap.h" #include "magnum/math.h" @@ -153,6 +154,110 @@ template void boolVector(py::class_& c) { c.def_static("__len__", []() { return int(T::Size); }, lenDocstring); } +template void quaternion(py::module& m, py::class_& c) { + /* + Missing APIs: + + Type + construction from different types + */ + + m + .def("dot", static_cast(&Math::dot), + "Dot product between two quaternions") + .def("angle", [](const T& a, const T& b) { + return Radd(Math::angle(a, b)); + }, "Angle between normalized quaternions") + .def("lerp", static_cast(&Math::lerp), + "Linear interpolation of two quaternions", py::arg("normalized_a"), py::arg("normalized_b"), py::arg("t")) + .def("lerp_shortest_path", static_cast(&Math::lerpShortestPath), + "Linear shortest-path interpolation of two quaternions", py::arg("normalized_a"), py::arg("normalized_b"), py::arg("t")) + .def("slerp", static_cast(&Math::slerp), + "Spherical linear interpolation of two quaternions", py::arg("normalized_a"), py::arg("normalized_b"), py::arg("t")) + .def("slerp_shortest_path", static_cast(&Math::slerpShortestPath), + "Spherical linear shortest-path interpolation of two quaternions", py::arg("normalized_a"), py::arg("normalized_b"), py::arg("t")) + ; + + c + /* Constructors */ + .def_static("rotation", [](Radd angle, const Math::Vector3& axis) { + return T::rotation(Math::Rad(angle), axis); + }, "Rotation quaternion") + .def_static("from_matrix", &T::fromMatrix, + "Create a quaternion from rotation matrix") + .def_static("zero_init", []() { + return T{Math::ZeroInit}; + }, "Construct a zero-initialized quaternion") + .def_static("identity_init", []() { + return T{Math::IdentityInit}; + }, "Construct an identity quaternion") + .def(py::init(), "Default constructor") + .def(py::init&, typename T::Type>(), + "Construct from a vector and a scalar") + .def(py::init([](const std::pair, typename T::Type>& value) { + return T{{std::get<0>(value.first), std::get<1>(value.first), std::get<2>(value.first)}, value.second}; + }), "Construct from a tuple") + .def(py::init&>(), + "Construct from a vector") + + /* Comparison */ + .def(py::self == py::self, "Equality comparison") + .def(py::self != py::self, "Non-equality comparison") + + /* Operators */ + .def(-py::self, "Negated quaternion") + .def(py::self += py::self, "Add and assign a quaternion") + .def(py::self + py::self, "Add a quaternion") + .def(py::self -= py::self, "Subtract and assign a quaternion") + .def(py::self - py::self, "Subtract a quaternion") + .def(py::self *= typename T::Type{}, "Multiply with a scalar and assign") + .def(py::self * typename T::Type{}, "Multiply with a scalar") + .def(py::self /= typename T::Type{}, "Divide with a scalar and assign") + .def(py::self / typename T::Type{}, "Divide with a scalar") + .def(py::self * py::self, "Multiply with a quaternion") + .def(typename T::Type{} * py::self, "Multiply a scalar with a quaternion") + .def(typename T::Type{} / py::self, "Divide a quaternion with a scalar and invert") + + /* Member functions */ + .def("is_normalized", &T::isNormalized, + "Whether the quaternion is normalized") + .def("angle", [](const T& self) { + return Radd(self.angle()); + }, "Rotation angle of a unit quaternion") + .def("axis", &T::axis, + "Rotation axis of a unit quaternion") + .def("to_matrix", &T::toMatrix, + "Convert to a rotation matrix") + .def("dot", &T::dot, + "Dot product of the quaternion") + .def("length", &T::length, + "Quaternion length") + .def("normalized", &T::normalized, + "Normalized quaternion (of unit length)") + .def("conjugated", &T::conjugated, + "Conjugated quaternion") + .def("inverted", &T::inverted, + "Inverted quaternion") + .def("inverted_normalized", &T::invertedNormalized, + "Inverted normalized quaternion") + .def("transform_vector", &T::transformVector, + "Rotate a vector with a quaternion") + .def("transform_vector_normalized", &T::transformVectorNormalized, + "Rotate a vector with a normalized quaternion") + + /* Properties */ + .def_property("vector", + static_cast(T::*)() const>(&T::vector), + [](T& self, const Math::Vector3& value) { self.vector() = value; }, + "Vector part") + .def_property("scalar", + static_cast(&T::scalar), + [](T& self, typename T::Type value) { self.scalar() = value; }, + "Scalar part") + + .def("__repr__", repr, "Object representation"); +} + } void math(py::module& root, py::module& m) { @@ -190,6 +295,18 @@ void math(py::module& root, py::module& m) { m.attr("sqrt_half") = Constantsd::sqrtHalf(); m.attr("nan") = Constantsd::nan(); m.attr("inf") = Constantsd::inf(); + + /* These are needed for the quaternion, so register them before */ + magnum::mathVectorFloat(root, m); + magnum::mathVectorIntegral(root, m); + magnum::mathMatrixFloat(root); + magnum::mathMatrixDouble(root); + + /* Quaternion */ + py::class_ quaternion_(root, "Quaternion", "Float quaternion"); + py::class_ quaterniond(root, "Quaterniond", "Double quaternion"); + quaternion(m, quaternion_); + quaternion(m, quaterniond); } } diff --git a/src/python/magnum/test/test_math.py b/src/python/magnum/test/test_math.py index e2a3651..863d79f 100644 --- a/src/python/magnum/test/test_math.py +++ b/src/python/magnum/test/test_math.py @@ -448,3 +448,53 @@ class Matrix4_(unittest.TestCase): def test_methods(self): self.assertEqual(Matrix4.rotation_x(Deg(45.0)).inverted(), Matrix4.rotation_x(Deg(-45.0))) + +class Quaternion_(unittest.TestCase): + def test_init(self): + a = Quaternion() + self.assertEqual(a.vector, Vector3(0.0, 0.0, 0.0)) + self.assertEqual(a.scalar, 1.0) + + b = Quaternion.identity_init() + self.assertEqual(b.vector, Vector3(0.0, 0.0, 0.0)) + self.assertEqual(b.scalar, 1.0) + + c = Quaternion.zero_init() + self.assertEqual(c.vector, Vector3(0.0, 0.0, 0.0)) + self.assertEqual(c.scalar, 0.0) + + d = Quaternion(Vector3(1.0, 2.0, 3.0), 4.0) + self.assertEqual(d.vector, Vector3(1.0, 2.0, 3.0)) + self.assertEqual(d.scalar, 4.0) + + e = Quaternion(((1.0, 2.0, 3.0), 4.0)) + self.assertEqual(e.vector, Vector3(1.0, 2.0, 3.0)) + self.assertEqual(e.scalar, 4.0) + + def test_static_methods(self): + a = Quaternion.rotation(Deg(45.0), Vector3.x_axis()) + 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()) + + def test_methods(self): + a = Quaternion.rotation(Deg(45.0), Vector3.x_axis()) + self.assertEqual(a.inverted(), + Quaternion.rotation(Deg(45.0), -Vector3.x_axis())) + self.assertAlmostEqual(float(Deg(a.angle())), float(Deg(45.0)), 4) + + def test_functions(self): + a = math.angle(Quaterniond.rotation(Deg(45.0), Vector3d.x_axis()), + Quaterniond.rotation(Deg(75.0), Vector3d.x_axis())) + self.assertEqual(Deg(a), Deg(15.0)) + + def test_properties(self): + a = Quaternion() + a.vector = (1.0, 2.0, 3.0) + a.scalar = 4.0 + self.assertEqual(a.vector, Vector3(1.0, 2.0, 3.0)) + self.assertEqual(a.scalar, 4.0) + self.assertEqual(a, Quaternion((1.0, 2.0, 3.0), 4.0)) + + def test_repr(self): + a = Quaternion.rotation(Deg(45.0), Vector3.x_axis()) + self.assertEqual(repr(a), 'Quaternion({0.382683, 0, 0}, 0.92388)')