diff --git a/src/python/magnum/__init__.py b/src/python/magnum/__init__.py index 782e557..7b0a107 100644 --- a/src/python/magnum/__init__.py +++ b/src/python/magnum/__init__.py @@ -42,4 +42,5 @@ __all__ = [ 'Matrix2x2d', 'Matrix2x3d', 'Matrix2x4d', 'Matrix3x2d', 'Matrix3x3d', 'Matrix3x4d', 'Matrix4x2d', 'Matrix4x3d', 'Matrix4x4d', + 'Matrix3', 'Matrix4', 'Matrix3d', 'Matrix4d', ] diff --git a/src/python/magnum/math.matrix.h b/src/python/magnum/math.matrix.h index cd364af..8bf1ee5 100644 --- a/src/python/magnum/math.matrix.h +++ b/src/python/magnum/math.matrix.h @@ -27,9 +27,8 @@ #include #include - -#include "Magnum/Math/Matrix.h" -#include "Magnum/Math/Vector4.h" +#include +#include #include "magnum/math.h" @@ -149,7 +148,10 @@ template void matrices( py::class_>& matrix4x2, py::class_>& matrix4x3, - py::class_>& matrix4x4 + py::class_>& matrix4x4, + + py::class_, Math::Matrix3x3>& matrix3, + py::class_, Math::Matrix4x4>& matrix4 ) { /* Two-column matrices */ matrix2x2 @@ -330,6 +332,213 @@ template void matrices( rectangularMatrix(matrix4x3); rectangularMatrix(matrix4x4); matrix(matrix4x4); + + /* 3x3 transformation matrix */ + matrix3 + /* Constructors. The scaling() / rotation() are handled below + as they conflict with member functions. */ + .def_static("translation", static_cast(*)(const Math::Vector2&)>(&Math::Matrix3::translation), + "2D translation matrix") + .def_static("reflection", &Math::Matrix3::reflection, + "2D reflection matrix") + .def_static("shearing_x", &Math::Matrix3::shearingX, + "2D shearing matrix along the X axis", py::arg("amount")) + .def_static("shearing_y", &Math::Matrix3::shearingY, + "2D shearning matrix along the Y axis", py::arg("amount")) + .def_static("projection", &Math::Matrix3::projection, + "2D projection matrix", py::arg("size")) + .def_static("from", static_cast(*)(const Math::Matrix2x2&, const Math::Vector2&)>(&Math::Matrix3::from), + "Create a matrix from a rotation/scaling part and a translation part", + py::arg("rotation_scaling"), py::arg("translation")) + .def_static("zero_init", []() { + return Math::Matrix3{Math::ZeroInit}; + }, "Construct a zero-filled matrix") + .def_static("identity_init", [](T value) { + return Math::Matrix3{Math::IdentityInit, value}; + }, "Construct an identity matrix", py::arg("value") = T(1)) + .def(py::init(), "Default constructor") + .def(py::init(), "Construct a matrix with one value for all components") + .def(py::init&, const Math::Vector3&, const Math::Vector3&>(), + "Construct from column vectors") + .def(py::init([](const std::tuple, Math::Vector3, Math::Vector3>& value) { + return Math::Matrix3{std::get<0>(value), std::get<1>(value), std::get<2>(value)}; + }), "Construct from a column vector tuple") + + /* Member functions */ + .def("is_rigid_transformation", &Math::Matrix3::isRigidTransformation, + "Check whether the matrix represents a rigid transformation") + .def("rotation_scaling", &Math::Matrix3::rotationScaling, + "2D rotation and scaling part of the matrix") + .def("rotation_shear", &Math::Matrix3::rotationShear, + "2D rotation and shear part of the matrix") + .def("rotation_normalized", &Math::Matrix3::rotationNormalized, + "2D rotation part of the matrix assuming there is no scaling") + .def("scaling_squared", &Math::Matrix3::scalingSquared, + "Non-uniform scaling part of the matrix, squared") + .def("uniform_scaling_squared", &Math::Matrix3::uniformScalingSquared, + "Uniform scaling part of the matrix, squared") + .def("uniform_scaling", &Math::Matrix3::uniformScaling, + "Uniform scaling part of the matrix") + .def("inverted_rigid", &Math::Matrix3::invertedRigid, + "Inverted rigid transformation matrix") + .def("transform_vector", &Math::Matrix3::transformVector, + "Transform a 2D vector with the matrix") + .def("transform_point", &Math::Matrix3::transformPoint, + "Transform a 2D point with the matrix") + + /* Properties */ + .def_property("right", + static_cast(Math::Matrix3::*)() const>(&Math::Matrix3::right), + [](Math::Matrix3& self, const Math::Vector2& value) { self.right() = value; }, + "Right-pointing 2D vector") + .def_property("up", + static_cast(Math::Matrix3::*)() const>(&Math::Matrix3::up), + [](Math::Matrix3& self, const Math::Vector2& value) { self.up() = value; }, + "Up-pointing 2D vector") + + /* Static/member scaling(). Pybind doesn't support that natively, so + we create a scaling(*args, **kwargs) and dispatch ourselves. */ + .def_static("_sscaling", static_cast(*)(const Math::Vector2&)>(&Math::Matrix3::scaling), + "2D scaling matrix") + .def("_iscaling", static_cast(Math::Matrix3::*)() const>(&Math::Matrix3::scaling), + "Non-uniform scaling part of the matrix") + .def("scaling", [matrix3](py::args args, py::kwargs kwargs) { + if(py::len(args) && py::isinstance>(args[0])) { + return matrix3.attr("_iscaling")(*args, **kwargs); + } else { + return matrix3.attr("_sscaling")(*args, **kwargs); + } + }) + + /* Static/member rotation(). Pybind doesn't support that natively, so + we create a rotation(*args, **kwargs) and dispatch ourselves. */ + .def_static("_srotation", [](Radd angle) { + return Math::Matrix3::rotation(Math::Rad(angle)); + }, "2D rotation matrix") + .def("_irotation", static_cast(Math::Matrix3::*)() const>(&Math::Matrix3::rotation), + "2D rotation part of the matrix") + .def("rotation", [matrix3](py::args args, py::kwargs kwargs) { + if(py::len(args) && py::isinstance>(args[0])) { + return matrix3.attr("_irotation")(*args, **kwargs); + } else { + return matrix3.attr("_srotation")(*args, **kwargs); + } + }); + + /* 4x4 transformation matrix */ + matrix4 + /* Constructors. The scaling() / rotation() are handled below + as they conflict with member functions. */ + .def_static("translation", static_cast(*)(const Math::Vector3&)>(&Math::Matrix4::translation), + "3D translation matrix") + .def_static("rotation_x", [](Radd angle) { + return Math::Matrix4::rotationX(Math::Rad(angle)); + }, "3D rotation matrix around the X axis") + .def_static("rotation_y", [](Radd angle) { + return Math::Matrix4::rotationY(Math::Rad(angle)); + }, "3D rotation matrix around the Y axis") + .def_static("rotation_z", [](Radd angle) { + return Math::Matrix4::rotationZ(Math::Rad(angle)); + }, "3D rotation matrix around the Z axis") + .def_static("reflection", &Math::Matrix4::reflection, + "3D reflection matrix") + .def_static("shearing_xy", &Math::Matrix4::shearingXY, + "3D shearing matrix along the XY plane", py::arg("amountx"), py::arg("amounty")) + .def_static("shearing_xz", &Math::Matrix4::shearingXZ, + "3D shearning matrix along the XZ plane", py::arg("amountx"), py::arg("amountz")) + .def_static("shearing_yz", &Math::Matrix4::shearingYZ, + "3D shearing matrix along the YZ plane", py::arg("amounty"), py::arg("amountz")) + .def_static("orthographic_projection", &Math::Matrix4::orthographicProjection, + "3D orthographic projection matrix", py::arg("size"), py::arg("near"), py::arg("far")) + .def_static("perspective_projection", + static_cast(*)(const Math::Vector2&, T, T)>(&Math::Matrix4::perspectiveProjection), + "3D perspective projection matrix", py::arg("size"), py::arg("near"), py::arg("far")) + .def_static("perspective_projection", [](Radd fov, T aspectRatio, T near, T far) { + return Math::Matrix4::perspectiveProjection(Math::Rad(fov), aspectRatio, near, far); + }, "3D perspective projection matrix", py::arg("fov"), py::arg("aspect_ratio"), py::arg("near"), py::arg("far")) + .def_static("look_at", &Math::Matrix4::lookAt, + "Matrix oriented towards a specific point", py::arg("eye"), py::arg("target"), py::arg("up")) + .def_static("from", static_cast(*)(const Math::Matrix3x3&, const Math::Vector3&)>(&Math::Matrix4::from), + "Create a matrix from a rotation/scaling part and a translation part", + py::arg("rotation_scaling"), py::arg("translation")) + .def_static("zero_init", []() { + return Math::Matrix4{Math::ZeroInit}; + }, "Construct a zero-filled matrix") + .def_static("identity_init", [](T value) { + return Math::Matrix4{Math::IdentityInit, value}; + }, "Construct an identity matrix", py::arg("value") = T(1)) + .def(py::init(), "Default constructor") + .def(py::init(), "Construct a matrix with one value for all components") + .def(py::init&, const Math::Vector4&, const Math::Vector4&, const Math::Vector4&>(), + "Construct from column vectors") + .def(py::init([](const std::tuple, Math::Vector4, Math::Vector4, Math::Vector4>& value) { + return Math::Matrix4{std::get<0>(value), std::get<1>(value), std::get<2>(value), std::get<3>(value)}; + }), "Construct from a column vector tuple") + + /* Member functions */ + .def("is_rigid_transformation", &Math::Matrix4::isRigidTransformation, + "Check whether the matrix represents a rigid transformation") + .def("rotation_scaling", &Math::Matrix4::rotationScaling, + "3D rotation and scaling part of the matrix") + .def("rotation_shear", &Math::Matrix4::rotationShear, + "3D rotation and shear part of the matrix") + .def("rotation_normalized", &Math::Matrix4::rotationNormalized, + "3D rotation part of the matrix assuming there is no scaling") + .def("scaling_squared", &Math::Matrix4::scalingSquared, + "Non-uniform scaling part of the matrix, squared") + .def("uniform_scaling_squared", &Math::Matrix4::uniformScalingSquared, + "Uniform scaling part of the matrix, squared") + .def("uniform_scaling", &Math::Matrix4::uniformScaling, + "Uniform scaling part of the matrix") + .def("inverted_rigid", &Math::Matrix4::invertedRigid, + "Inverted rigid transformation matrix") + .def("transform_vector", &Math::Matrix4::transformVector, + "Transform a 3D vector with the matrix") + .def("transform_point", &Math::Matrix4::transformPoint, + "Transform a 3D point with the matrix") + + /* Properties */ + .def_property("right", + static_cast(Math::Matrix4::*)() const>(&Math::Matrix4::right), + [](Math::Matrix4& self, const Math::Vector3& value) { self.right() = value; }, + "Right-pointing 3D vector") + .def_property("up", + static_cast(Math::Matrix4::*)() const>(&Math::Matrix4::up), + [](Math::Matrix4& self, const Math::Vector3& value) { self.up() = value; }, + "Up-pointing 3D vector") + .def_property("backward", + static_cast(Math::Matrix4::*)() const>(&Math::Matrix4::backward), + [](Math::Matrix4& self, const Math::Vector3& value) { self.backward() = value; }, + "Backward-pointing 3D vector") + + /* Static/member scaling(). Pybind doesn't support that natively, so + we create a scaling(*args, **kwargs) and dispatch ourselves. */ + .def_static("_sscaling", static_cast(*)(const Math::Vector3&)>(&Math::Matrix4::scaling), + "3D scaling matrix") + .def("_iscaling", static_cast(Math::Matrix4::*)() const>(&Math::Matrix4::scaling), + "Non-uniform scaling part of the matrix") + .def("scaling", [matrix4](py::args args, py::kwargs kwargs) { + if(py::len(args) && py::isinstance>(args[0])) { + return matrix4.attr("_iscaling")(*args, **kwargs); + } else { + return matrix4.attr("_sscaling")(*args, **kwargs); + } + }) + + /* Static/member rotation(). Pybind doesn't support that natively, so + we create a rotation(*args, **kwargs) and dispatch ourselves. */ + .def_static("_srotation", [](Radd angle, const Math::Vector3& axis) { + return Math::Matrix4::rotation(Math::Rad(angle), axis); + }, "3D rotation matrix around arbitrary axis") + .def("_irotation", static_cast(Math::Matrix4::*)() const>(&Math::Matrix4::rotation), + "3D rotation part of the matrix") + .def("rotation", [matrix4](py::args args, py::kwargs kwargs) { + if(py::len(args) && py::isinstance>(args[0])) { + return matrix4.attr("_irotation")(*args, **kwargs); + } else { + return matrix4.attr("_srotation")(*args, **kwargs); + } + }); } } diff --git a/src/python/magnum/math.matrixdouble.cpp b/src/python/magnum/math.matrixdouble.cpp index 047a1bb..9b44cc1 100644 --- a/src/python/magnum/math.matrixdouble.cpp +++ b/src/python/magnum/math.matrixdouble.cpp @@ -40,10 +40,14 @@ void mathMatrixDouble(py::module& root) { py::class_ matrix4x3d{root, "Matrix4x3d", "4x3 double matrix"}; py::class_ matrix4x4d{root, "Matrix4x4d", "4x4 double matrix"}; + py::class_ matrix3d{root, "Matrix3d", "2D double transformation matrix"}; + py::class_ matrix4d{root, "Matrix4d", "3D double transformation matrix"}; + matrices( matrix2x2d, matrix2x3d, matrix2x4d, matrix3x2d, matrix3x3d, matrix3x4d, - matrix4x2d, matrix4x3d, matrix4x4d); + matrix4x2d, matrix4x3d, matrix4x4d, + matrix3d, matrix4d); } } diff --git a/src/python/magnum/math.matrixfloat.cpp b/src/python/magnum/math.matrixfloat.cpp index 2abc750..d0ce98a 100644 --- a/src/python/magnum/math.matrixfloat.cpp +++ b/src/python/magnum/math.matrixfloat.cpp @@ -40,10 +40,14 @@ void mathMatrixFloat(py::module& root) { py::class_ matrix4x3{root, "Matrix4x3", "4x3 float matrix"}; py::class_ matrix4x4{root, "Matrix4x4", "4x4 float matrix"}; + py::class_ matrix3{root, "Matrix3", "2D float transformation matrix"}; + py::class_ matrix4{root, "Matrix4", "3D float transformation matrix"}; + matrices( matrix2x2, matrix2x3, matrix2x4, matrix3x2, matrix3x3, matrix3x4, - matrix4x2, matrix4x3, matrix4x4); + matrix4x2, matrix4x3, matrix4x4, + matrix3, matrix4); } } diff --git a/src/python/magnum/test/test_math.py b/src/python/magnum/test/test_math.py index c71e5b4..e2a3651 100644 --- a/src/python/magnum/test/test_math.py +++ b/src/python/magnum/test/test_math.py @@ -345,3 +345,106 @@ class Matrix(unittest.TestCase): self.assertEqual(repr(a), 'Matrix(1, 4,\n' ' 2, 5,\n' ' 3, 6)') + +class Matrix3_(unittest.TestCase): + def test_init(self): + a = Matrix3() + self.assertEqual(a[0], Vector3.x_axis()) + self.assertEqual(a[1], Vector3.y_axis()) + self.assertEqual(a[2], Vector3.z_axis()) + + b = Matrix3.zero_init() + self.assertEqual(b[0], Vector3(0)) + self.assertEqual(b[1], Vector3(0)) + self.assertEqual(b[2], Vector3(0)) + + c1 = Matrix3.identity_init() + self.assertEqual(c1[0], Vector3.x_axis()) + self.assertEqual(c1[1], Vector3.y_axis()) + self.assertEqual(c1[2], Vector3.z_axis()) + + c3 = Matrix3.identity_init(3.0) + self.assertEqual(c3[0], Vector3.x_axis(3.0)) + self.assertEqual(c3[1], Vector3.y_axis(3.0)) + self.assertEqual(c3[2], Vector3.z_axis(3.0)) + + d = Matrix3(((1.0, 2.0, 3.0), + (4.0, 5.0, 6.0), + (7.0, 8.0, 9.0))) + self.assertEqual(d[0], Vector3(1.0, 2.0, 3.0)) + self.assertEqual(d[1], Vector3(4.0, 5.0, 6.0)) + self.assertEqual(d[2], Vector3(7.0, 8.0, 9.0)) + + def test_static_methods(self): + a = Matrix3.translation((0.0, -1.0)) + self.assertEqual(a[2].xy, Vector2(0.0, -1.0)) + #self.assertEqual(a.translation, Vector2(0.0, -1.0)) # TODO + + b = Matrix3.rotation(Deg(45.0)) + self.assertEqual(b.right, Vector2(0.707107, 0.707107)) + self.assertEqual(b.up, Vector2(-0.707107, 0.707107)) + self.assertEqual(b.rotation(), Matrix2x2( + (0.707107, 0.707107), + (-0.707107, 0.707107))) + + c = Matrix3.scaling((1.0, 2.0)) + self.assertEqual(c.scaling(), Vector2(1.0, 2.0)) + + def test_methods(self): + self.assertEqual(Matrix3.rotation(Deg(45.0)).inverted(), + Matrix3.rotation(Deg(-45.0))) + +class Matrix4_(unittest.TestCase): + def test_init(self): + a = Matrix4() + self.assertEqual(a[0], Vector4(1.0, 0.0, 0.0, 0.0)) + self.assertEqual(a[1], Vector4(0.0, 1.0, 0.0, 0.0)) + self.assertEqual(a[2], Vector4(0.0, 0.0, 1.0, 0.0)) + self.assertEqual(a[3], Vector4(0.0, 0.0, 0.0, 1.0)) + + b = Matrix4.zero_init() + self.assertEqual(b[0], Vector4(0)) + self.assertEqual(b[1], Vector4(0)) + self.assertEqual(b[2], Vector4(0)) + + c1 = Matrix4.identity_init() + self.assertEqual(c1[0], Vector4(1.0, 0.0, 0.0, 0.0)) + self.assertEqual(c1[1], Vector4(0.0, 1.0, 0.0, 0.0)) + self.assertEqual(c1[2], Vector4(0.0, 0.0, 1.0, 0.0)) + self.assertEqual(c1[3], Vector4(0.0, 0.0, 0.0, 1.0)) + + c3 = Matrix4.identity_init(3.0) + self.assertEqual(c3[0], Vector4(3.0, 0.0, 0.0, 0.0)) + self.assertEqual(c3[1], Vector4(0.0, 3.0, 0.0, 0.0)) + self.assertEqual(c3[2], Vector4(0.0, 0.0, 3.0, 0.0)) + self.assertEqual(c3[3], Vector4(0.0, 0.0, 0.0, 3.0)) + + d = Matrix4(((1.0, 2.0, 3.0, 4.0), + (5.0, 6.0, 7.0, 8.0), + (9.0, 10.0, 11.0, 12.0), + (13.0, 14.0, 15.0, 16.0))) + self.assertEqual(d[0], Vector4(1.0, 2.0, 3.0, 4.0)) + self.assertEqual(d[1], Vector4(5.0, 6.0, 7.0, 8.0)) + self.assertEqual(d[2], Vector4(9.0, 10.0, 11.0, 12.0)) + self.assertEqual(d[3], Vector4(13.0, 14.0, 15.0, 16.0)) + + def test_static_methods(self): + a = Matrix4.translation((0.0, -1.0, 2.0)) + self.assertEqual(a[3].xyz, Vector3(0.0, -1.0, 2.0)) + #self.assertEqual(a.translation, Vector3(0.0, -1.0, 2.0)) # TODO + + b = Matrix4.rotation(Deg(45.0), Vector3.x_axis()) + self.assertEqual(b.right, Vector3(1.0, 0.0, 0.0)) + self.assertEqual(b.up, Vector3(0.0, 0.707107, 0.707107)) + self.assertEqual(b.backward, Vector3(0.0, -0.707107, 0.707107)) + self.assertEqual(b.rotation(), Matrix3x3( + (1.0, 0.0, 0.0), + (0.0, 0.707107, 0.707107), + (0.0, -0.707107, 0.707107))) + + c = Matrix4.scaling((1.0, 2.0, 3.5)) + self.assertEqual(c.scaling(), Vector3(1.0, 2.0, 3.5)) + + def test_methods(self): + self.assertEqual(Matrix4.rotation_x(Deg(45.0)).inverted(), + Matrix4.rotation_x(Deg(-45.0)))