Browse Source

python: expose Matrix3 and Matrix4.

The clash of static constructors and members / properties is ...
unfortunate. This is resolved using a minor hack, but I think that's
warranted if it preserves C++/Python API compatibility. The
translation() static constructor and property is not done yet, tho.
pull/1/head
Vladimír Vondruš 7 years ago
parent
commit
bd49350994
  1. 1
      src/python/magnum/__init__.py
  2. 217
      src/python/magnum/math.matrix.h
  3. 6
      src/python/magnum/math.matrixdouble.cpp
  4. 6
      src/python/magnum/math.matrixfloat.cpp
  5. 103
      src/python/magnum/test/test_math.py

1
src/python/magnum/__init__.py

@ -42,4 +42,5 @@ __all__ = [
'Matrix2x2d', 'Matrix2x3d', 'Matrix2x4d',
'Matrix3x2d', 'Matrix3x3d', 'Matrix3x4d',
'Matrix4x2d', 'Matrix4x3d', 'Matrix4x4d',
'Matrix3', 'Matrix4', 'Matrix3d', 'Matrix4d',
]

217
src/python/magnum/math.matrix.h

@ -27,9 +27,8 @@
#include <pybind11/pybind11.h>
#include <pybind11/operators.h>
#include "Magnum/Math/Matrix.h"
#include "Magnum/Math/Vector4.h"
#include <Magnum/Math/Matrix3.h>
#include <Magnum/Math/Matrix4.h>
#include "magnum/math.h"
@ -149,7 +148,10 @@ template<class T> void matrices(
py::class_<Math::Matrix4x2<T>>& matrix4x2,
py::class_<Math::Matrix4x3<T>>& matrix4x3,
py::class_<Math::Matrix4x4<T>>& matrix4x4
py::class_<Math::Matrix4x4<T>>& matrix4x4,
py::class_<Math::Matrix3<T>, Math::Matrix3x3<T>>& matrix3,
py::class_<Math::Matrix4<T>, Math::Matrix4x4<T>>& matrix4
) {
/* Two-column matrices */
matrix2x2
@ -330,6 +332,213 @@ template<class T> 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<Math::Matrix3<T>(*)(const Math::Vector2<T>&)>(&Math::Matrix3<T>::translation),
"2D translation matrix")
.def_static("reflection", &Math::Matrix3<T>::reflection,
"2D reflection matrix")
.def_static("shearing_x", &Math::Matrix3<T>::shearingX,
"2D shearing matrix along the X axis", py::arg("amount"))
.def_static("shearing_y", &Math::Matrix3<T>::shearingY,
"2D shearning matrix along the Y axis", py::arg("amount"))
.def_static("projection", &Math::Matrix3<T>::projection,
"2D projection matrix", py::arg("size"))
.def_static("from", static_cast<Math::Matrix3<T>(*)(const Math::Matrix2x2<T>&, const Math::Vector2<T>&)>(&Math::Matrix3<T>::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<T>{Math::ZeroInit};
}, "Construct a zero-filled matrix")
.def_static("identity_init", [](T value) {
return Math::Matrix3<T>{Math::IdentityInit, value};
}, "Construct an identity matrix", py::arg("value") = T(1))
.def(py::init(), "Default constructor")
.def(py::init<T>(), "Construct a matrix with one value for all components")
.def(py::init<const Math::Vector3<T>&, const Math::Vector3<T>&, const Math::Vector3<T>&>(),
"Construct from column vectors")
.def(py::init([](const std::tuple<Math::Vector3<T>, Math::Vector3<T>, Math::Vector3<T>>& value) {
return Math::Matrix3<T>{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<T>::isRigidTransformation,
"Check whether the matrix represents a rigid transformation")
.def("rotation_scaling", &Math::Matrix3<T>::rotationScaling,
"2D rotation and scaling part of the matrix")
.def("rotation_shear", &Math::Matrix3<T>::rotationShear,
"2D rotation and shear part of the matrix")
.def("rotation_normalized", &Math::Matrix3<T>::rotationNormalized,
"2D rotation part of the matrix assuming there is no scaling")
.def("scaling_squared", &Math::Matrix3<T>::scalingSquared,
"Non-uniform scaling part of the matrix, squared")
.def("uniform_scaling_squared", &Math::Matrix3<T>::uniformScalingSquared,
"Uniform scaling part of the matrix, squared")
.def("uniform_scaling", &Math::Matrix3<T>::uniformScaling,
"Uniform scaling part of the matrix")
.def("inverted_rigid", &Math::Matrix3<T>::invertedRigid,
"Inverted rigid transformation matrix")
.def("transform_vector", &Math::Matrix3<T>::transformVector,
"Transform a 2D vector with the matrix")
.def("transform_point", &Math::Matrix3<T>::transformPoint,
"Transform a 2D point with the matrix")
/* Properties */
.def_property("right",
static_cast<Math::Vector2<T>(Math::Matrix3<T>::*)() const>(&Math::Matrix3<T>::right),
[](Math::Matrix3<T>& self, const Math::Vector2<T>& value) { self.right() = value; },
"Right-pointing 2D vector")
.def_property("up",
static_cast<Math::Vector2<T>(Math::Matrix3<T>::*)() const>(&Math::Matrix3<T>::up),
[](Math::Matrix3<T>& self, const Math::Vector2<T>& 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<Math::Matrix3<T>(*)(const Math::Vector2<T>&)>(&Math::Matrix3<T>::scaling),
"2D scaling matrix")
.def("_iscaling", static_cast<Math::Vector2<T>(Math::Matrix3<T>::*)() const>(&Math::Matrix3<T>::scaling),
"Non-uniform scaling part of the matrix")
.def("scaling", [matrix3](py::args args, py::kwargs kwargs) {
if(py::len(args) && py::isinstance<Math::Matrix3<T>>(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<T>::rotation(Math::Rad<T>(angle));
}, "2D rotation matrix")
.def("_irotation", static_cast<Math::Matrix2x2<T>(Math::Matrix3<T>::*)() const>(&Math::Matrix3<T>::rotation),
"2D rotation part of the matrix")
.def("rotation", [matrix3](py::args args, py::kwargs kwargs) {
if(py::len(args) && py::isinstance<Math::Matrix3<T>>(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<Math::Matrix4<T>(*)(const Math::Vector3<T>&)>(&Math::Matrix4<T>::translation),
"3D translation matrix")
.def_static("rotation_x", [](Radd angle) {
return Math::Matrix4<T>::rotationX(Math::Rad<T>(angle));
}, "3D rotation matrix around the X axis")
.def_static("rotation_y", [](Radd angle) {
return Math::Matrix4<T>::rotationY(Math::Rad<T>(angle));
}, "3D rotation matrix around the Y axis")
.def_static("rotation_z", [](Radd angle) {
return Math::Matrix4<T>::rotationZ(Math::Rad<T>(angle));
}, "3D rotation matrix around the Z axis")
.def_static("reflection", &Math::Matrix4<T>::reflection,
"3D reflection matrix")
.def_static("shearing_xy", &Math::Matrix4<T>::shearingXY,
"3D shearing matrix along the XY plane", py::arg("amountx"), py::arg("amounty"))
.def_static("shearing_xz", &Math::Matrix4<T>::shearingXZ,
"3D shearning matrix along the XZ plane", py::arg("amountx"), py::arg("amountz"))
.def_static("shearing_yz", &Math::Matrix4<T>::shearingYZ,
"3D shearing matrix along the YZ plane", py::arg("amounty"), py::arg("amountz"))
.def_static("orthographic_projection", &Math::Matrix4<T>::orthographicProjection,
"3D orthographic projection matrix", py::arg("size"), py::arg("near"), py::arg("far"))
.def_static("perspective_projection",
static_cast<Math::Matrix4<T>(*)(const Math::Vector2<T>&, T, T)>(&Math::Matrix4<T>::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<T>::perspectiveProjection(Math::Rad<T>(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<T>::lookAt,
"Matrix oriented towards a specific point", py::arg("eye"), py::arg("target"), py::arg("up"))
.def_static("from", static_cast<Math::Matrix4<T>(*)(const Math::Matrix3x3<T>&, const Math::Vector3<T>&)>(&Math::Matrix4<T>::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<T>{Math::ZeroInit};
}, "Construct a zero-filled matrix")
.def_static("identity_init", [](T value) {
return Math::Matrix4<T>{Math::IdentityInit, value};
}, "Construct an identity matrix", py::arg("value") = T(1))
.def(py::init(), "Default constructor")
.def(py::init<T>(), "Construct a matrix with one value for all components")
.def(py::init<const Math::Vector4<T>&, const Math::Vector4<T>&, const Math::Vector4<T>&, const Math::Vector4<T>&>(),
"Construct from column vectors")
.def(py::init([](const std::tuple<Math::Vector4<T>, Math::Vector4<T>, Math::Vector4<T>, Math::Vector4<T>>& value) {
return Math::Matrix4<T>{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<T>::isRigidTransformation,
"Check whether the matrix represents a rigid transformation")
.def("rotation_scaling", &Math::Matrix4<T>::rotationScaling,
"3D rotation and scaling part of the matrix")
.def("rotation_shear", &Math::Matrix4<T>::rotationShear,
"3D rotation and shear part of the matrix")
.def("rotation_normalized", &Math::Matrix4<T>::rotationNormalized,
"3D rotation part of the matrix assuming there is no scaling")
.def("scaling_squared", &Math::Matrix4<T>::scalingSquared,
"Non-uniform scaling part of the matrix, squared")
.def("uniform_scaling_squared", &Math::Matrix4<T>::uniformScalingSquared,
"Uniform scaling part of the matrix, squared")
.def("uniform_scaling", &Math::Matrix4<T>::uniformScaling,
"Uniform scaling part of the matrix")
.def("inverted_rigid", &Math::Matrix4<T>::invertedRigid,
"Inverted rigid transformation matrix")
.def("transform_vector", &Math::Matrix4<T>::transformVector,
"Transform a 3D vector with the matrix")
.def("transform_point", &Math::Matrix4<T>::transformPoint,
"Transform a 3D point with the matrix")
/* Properties */
.def_property("right",
static_cast<Math::Vector3<T>(Math::Matrix4<T>::*)() const>(&Math::Matrix4<T>::right),
[](Math::Matrix4<T>& self, const Math::Vector3<T>& value) { self.right() = value; },
"Right-pointing 3D vector")
.def_property("up",
static_cast<Math::Vector3<T>(Math::Matrix4<T>::*)() const>(&Math::Matrix4<T>::up),
[](Math::Matrix4<T>& self, const Math::Vector3<T>& value) { self.up() = value; },
"Up-pointing 3D vector")
.def_property("backward",
static_cast<Math::Vector3<T>(Math::Matrix4<T>::*)() const>(&Math::Matrix4<T>::backward),
[](Math::Matrix4<T>& self, const Math::Vector3<T>& 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<Math::Matrix4<T>(*)(const Math::Vector3<T>&)>(&Math::Matrix4<T>::scaling),
"3D scaling matrix")
.def("_iscaling", static_cast<Math::Vector3<T>(Math::Matrix4<T>::*)() const>(&Math::Matrix4<T>::scaling),
"Non-uniform scaling part of the matrix")
.def("scaling", [matrix4](py::args args, py::kwargs kwargs) {
if(py::len(args) && py::isinstance<Math::Matrix4<T>>(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<T>& axis) {
return Math::Matrix4<T>::rotation(Math::Rad<T>(angle), axis);
}, "3D rotation matrix around arbitrary axis")
.def("_irotation", static_cast<Math::Matrix3x3<T>(Math::Matrix4<T>::*)() const>(&Math::Matrix4<T>::rotation),
"3D rotation part of the matrix")
.def("rotation", [matrix4](py::args args, py::kwargs kwargs) {
if(py::len(args) && py::isinstance<Math::Matrix4<T>>(args[0])) {
return matrix4.attr("_irotation")(*args, **kwargs);
} else {
return matrix4.attr("_srotation")(*args, **kwargs);
}
});
}
}

6
src/python/magnum/math.matrixdouble.cpp

@ -40,10 +40,14 @@ void mathMatrixDouble(py::module& root) {
py::class_<Matrix4x3d> matrix4x3d{root, "Matrix4x3d", "4x3 double matrix"};
py::class_<Matrix4x4d> matrix4x4d{root, "Matrix4x4d", "4x4 double matrix"};
py::class_<Matrix3d, Matrix3x3d> matrix3d{root, "Matrix3d", "2D double transformation matrix"};
py::class_<Matrix4d, Matrix4x4d> matrix4d{root, "Matrix4d", "3D double transformation matrix"};
matrices<Double>(
matrix2x2d, matrix2x3d, matrix2x4d,
matrix3x2d, matrix3x3d, matrix3x4d,
matrix4x2d, matrix4x3d, matrix4x4d);
matrix4x2d, matrix4x3d, matrix4x4d,
matrix3d, matrix4d);
}
}

6
src/python/magnum/math.matrixfloat.cpp

@ -40,10 +40,14 @@ void mathMatrixFloat(py::module& root) {
py::class_<Matrix4x3> matrix4x3{root, "Matrix4x3", "4x3 float matrix"};
py::class_<Matrix4x4> matrix4x4{root, "Matrix4x4", "4x4 float matrix"};
py::class_<Matrix3, Matrix3x3> matrix3{root, "Matrix3", "2D float transformation matrix"};
py::class_<Matrix4, Matrix4x4> matrix4{root, "Matrix4", "3D float transformation matrix"};
matrices<Float>(
matrix2x2, matrix2x3, matrix2x4,
matrix3x2, matrix3x3, matrix3x4,
matrix4x2, matrix4x3, matrix4x4);
matrix4x2, matrix4x3, matrix4x4,
matrix3, matrix4);
}
}

103
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)))

Loading…
Cancel
Save