From 3dcb176c808c45ee7d4565d3e45b98e4cc775fe9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Thu, 25 Jul 2019 15:17:34 +0200 Subject: [PATCH] python: bind Math::Range. --- src/python/magnum/CMakeLists.txt | 1 + src/python/magnum/__init__.py | 3 + src/python/magnum/bootstrap.h | 1 + src/python/magnum/math.cpp | 3 + src/python/magnum/math.range.cpp | 363 ++++++++++++++++++++++++++++ src/python/magnum/test/test_math.py | 75 ++++++ 6 files changed, 446 insertions(+) create mode 100644 src/python/magnum/math.range.cpp diff --git a/src/python/magnum/CMakeLists.txt b/src/python/magnum/CMakeLists.txt index 877f07b..185733a 100644 --- a/src/python/magnum/CMakeLists.txt +++ b/src/python/magnum/CMakeLists.txt @@ -42,6 +42,7 @@ set(magnum_SRCS math.cpp math.matrixfloat.cpp math.matrixdouble.cpp + math.range.cpp math.vectorfloat.cpp math.vectorintegral.cpp) diff --git a/src/python/magnum/__init__.py b/src/python/magnum/__init__.py index 44102ee..559a253 100644 --- a/src/python/magnum/__init__.py +++ b/src/python/magnum/__init__.py @@ -80,6 +80,9 @@ __all__ = [ 'Matrix3', 'Matrix4', 'Matrix3d', 'Matrix4d', 'Quaternion', 'Quaterniond', + 'Range1D', 'Range1Di', 'Range1Dd', + 'Range2D', 'Range2Di', 'Range2Dd', + 'Range3D', 'Range3Di', 'Range3Dd', 'MeshPrimitive', 'MeshIndexType' ] diff --git a/src/python/magnum/bootstrap.h b/src/python/magnum/bootstrap.h index 0960ffd..530cf74 100644 --- a/src/python/magnum/bootstrap.h +++ b/src/python/magnum/bootstrap.h @@ -38,6 +38,7 @@ void mathVectorFloat(py::module& root, py::module& m); void mathVectorIntegral(py::module& root, py::module& m); void mathMatrixFloat(py::module& root); void mathMatrixDouble(py::module& root); +void mathRange(py::module& root, py::module& m); void gl(py::module& m); void meshtools(py::module& m); diff --git a/src/python/magnum/math.cpp b/src/python/magnum/math.cpp index cb6442e..b2e44e9 100644 --- a/src/python/magnum/math.cpp +++ b/src/python/magnum/math.cpp @@ -374,6 +374,9 @@ void math(py::module& root, py::module& m) { quaternion(m, quaterniond); convertible(quaternion_); convertible(quaterniond); + + /* Range */ + magnum::mathRange(root, m); } } diff --git a/src/python/magnum/math.range.cpp b/src/python/magnum/math.range.cpp new file mode 100644 index 0000000..d9ae3b4 --- /dev/null +++ b/src/python/magnum/math.range.cpp @@ -0,0 +1,363 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019 + Vladimír Vondruš + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +#include +#include +#include +#include + +#include "magnum/bootstrap.h" +#include "magnum/math.h" + +namespace magnum { + +namespace { + +template void range(py::module& m, py::class_& c) { + /* + Missing APIs: + + VectorType + */ + + py::implicitly_convertible, T>(); + + c + /* Constructors */ + .def_static("from_size", &T::fromSize, "Create a range from minimal coordinates and size") + .def_static("from_center", &T::fromCenter, "Create a range from center and half size") + .def_static("zero_init", []() { + return T{Math::ZeroInit}; + }, "Construct a zero range") + .def(py::init(), "Default constructor") + .def(py::init(), "Construct a range from minimal and maximal coordiantes") + .def(py::init>(), "Construct a range from minimal and maximal coordiantes") + + /* Comparison */ + .def(py::self == py::self, "Equality comparison") + .def(py::self != py::self, "Non-equality comparison") + + /* Properties */ + .def_property("min", + static_cast(&T::min), + [](T& self, const typename T::VectorType& value) { self.min() = value; }, + "Minimal coordinates (inclusive)") + .def_property("max", + static_cast(&T::max), + [](T& self, const typename T::VectorType& value) { self.max() = value; }, + "Maximal coordinates (exclusive)") + + /* Methods */ + .def("size", &T::size, "Range size") + .def("center", &T::center, "Range center") + .def("translated", &T::translated, "Translated range") + .def("padded", &T::padded, "Padded ange") + .def("scaled", &T::scaled, "Scaled range") + .def("scaled_from_center", &T::scaledFromCenter, "Range scaled from the center") + + .def("contains", static_cast(&T::contains), + "Whether given point is contained inside the range") + .def("contains", [](const T& self, const T& other) { + return self.contains(other); + }, "Whether another range is fully contained inside this range") + + .def("__repr__", repr, "Object representation"); + + m + /* Free functions */ + .def("join", [](const T& a, const T& b) -> T { + return Math::join(a, b); + }, "Join two ranges") + .def("intersect", [](const T& a, const T& b) -> T { + return Math::intersect(a, b); + }, "intersect two ranges") + .def("intersects", [](const T& a, const T& b) { + return Math::intersects(a, b); + }, "Whether two ranges intersect"); +} + +template void range2D(py::class_& c) { + py::implicitly_convertible, std::tuple>, T>(); + + c + /* Constructors */ + .def(py::init([](const std::pair, std::tuple>& value) { + return T{{typename T::VectorType{std::get<0>(value.first), std::get<1>(value.first)}, + typename T::VectorType{std::get<0>(value.second), std::get<1>(value.second)}}}; + }), "Construct a range from a pair of minimal and maximal coordinates") + + /* Properties */ + .def_property("bottom_left", + static_cast(&T::bottomLeft), + [](T& self, const typename T::VectorType& value) { + self.bottomLeft() = value; + }, + "Bottom left corner") + .def_property("bottom_right", + static_cast(&T::bottomRight), + [](T& self, const typename T::VectorType& value) { + self.max().x() = value.x(); + self.min().y() = value.y(); + }, + "Bottom right corner") + .def_property("top_left", + static_cast(&T::topLeft), + [](T& self, const typename T::VectorType& value) { + self.min().x() = value.x(); + self.max().y() = value.y(); + }, + "Top left corner") + .def_property("top_right", + static_cast(&T::topRight), + [](T& self, const typename T::VectorType& value) { + self.topRight() = value; + }, + "Top right corner") + + .def_property("left", + static_cast(&T::left), + [](T& self, const typename T::VectorType::Type& value) { + self.left() = value; + }, + "Left edge") + .def_property("right", + static_cast(&T::right), + [](T& self, const typename T::VectorType::Type& value) { + self.right() = value; + }, + "Right edge") + .def_property("bottom", + static_cast(&T::bottom), + [](T& self, const typename T::VectorType::Type& value) { + self.bottom() = value; + }, + "Bottom edge") + .def_property("top", + static_cast(&T::top), + [](T& self, const typename T::VectorType::Type& value) { + self.top() = value; + }, + "Top edge") + + /* Methods */ + .def("x", &T::x, "Range in the X axis") + .def("y", &T::y, "Range in the Y axis") + .def("size_x", &T::sizeX, "Range width") + .def("size_y", &T::sizeY, "Range height") + .def("center_x", &T::sizeX, "Range center on X axis") + .def("center_y", &T::sizeY, "Range center on Y axis"); +} + +template void range3D(py::class_& c) { + py::implicitly_convertible, std::tuple>, T>(); + + c + /* Constructors */ + .def(py::init([](const std::pair, std::tuple>& value) { + return T{{typename T::VectorType{std::get<0>(value.first), std::get<1>(value.first), std::get<2>(value.first)}, + typename T::VectorType{std::get<0>(value.second), std::get<1>(value.second), std::get<2>(value.second)}}}; + }), "Construct a range from a pair of minimal and maximal coordinates") + + /* Properties */ + .def_property("back_bottom_left", + static_cast(&T::backBottomLeft), + [](T& self, const typename T::VectorType& value) { + self.backBottomLeft() = value; + }, + "Back bottom left corner") + .def_property("back_bottom_right", + static_cast(&T::backBottomRight), + [](T& self, const typename T::VectorType& value) { + self.max().x() = value.x(); + self.min().y() = value.y(); + self.min().z() = value.z(); + }, + "Back bottom right corner") + .def_property("back_top_left", + static_cast(&T::backTopLeft), + [](T& self, const typename T::VectorType& value) { + self.min().x() = value.x(); + self.max().y() = value.y(); + self.min().z() = value.z(); + }, + "Back top left corner") + .def_property("back_top_right", + static_cast(&T::backTopRight), + [](T& self, const typename T::VectorType& value) { + self.max().x() = value.x(); + self.max().y() = value.y(); + self.min().z() = value.z(); + }, + "Back top right corner") + .def_property("front_bottom_left", + static_cast(&T::frontBottomLeft), + [](T& self, const typename T::VectorType& value) { + self.min().x() = value.x(); + self.min().y() = value.y(); + self.max().z() = value.z(); + }, + "Front bottom left corner") + .def_property("front_bottom_right", + static_cast(&T::frontBottomRight), + [](T& self, const typename T::VectorType& value) { + self.max().x() = value.x(); + self.min().y() = value.y(); + self.max().z() = value.z(); + }, + "Front bottom right corner") + .def_property("front_top_right", + static_cast(&T::frontTopRight), + [](T& self, const typename T::VectorType& value) { + self.frontTopRight() = value; + }, + "Front top right corner") + .def_property("front_top_left", + static_cast(&T::frontTopLeft), + [](T& self, const typename T::VectorType& value) { + self.min().x() = value.x(); + self.max().y() = value.y(); + self.max().z() = value.z(); + }, + "Front top left corner") + + .def_property("left", + static_cast(&T::left), + [](T& self, const typename T::VectorType::Type& value) { + self.left() = value; + }, + "Left edge") + .def_property("right", + static_cast(&T::right), + [](T& self, const typename T::VectorType::Type& value) { + self.right() = value; + }, + "Right edge") + .def_property("bottom", + static_cast(&T::bottom), + [](T& self, const typename T::VectorType::Type& value) { + self.bottom() = value; + }, + "Bottom edge") + .def_property("top", + static_cast(&T::top), + [](T& self, const typename T::VectorType::Type& value) { + self.top() = value; + }, + "Top edge") + .def_property("back", + static_cast(&T::back), + [](T& self, const typename T::VectorType::Type& value) { + self.back() = value; + }, + "Back edge") + .def_property("front", + static_cast(&T::front), + [](T& self, const typename T::VectorType::Type& value) { + self.front() = value; + }, + "Front edge") + + /* Methods */ + .def("x", &T::x, "Range in the X axis") + .def("y", &T::y, "Range in the Y axis") + .def("z", &T::y, "Range in the Z axis") + .def("xy", &T::xy, "Range in the XY plane") + .def("size_x", &T::sizeX, "Range width") + .def("size_y", &T::sizeY, "Range height") + .def("size_z", &T::sizeZ, "Range depth") + .def("center_x", &T::sizeX, "Range center on X axis") + .def("center_y", &T::sizeY, "Range center on Y axis") + .def("center_z", &T::sizeY, "Range center on Z axis"); +} + +template class Type, UnsignedInt dimensions, class T, class ...Args> void convertibleImplementation(py::class_, Args...>& c, std::false_type) { + c.def(py::init>(), "Construct from different underlying type"); +} + +template class Type, class T, class ...Args> void convertibleImplementation(py::class_, Args...>& c, std::false_type) { + c.def(py::init>(), "Construct from different underlying type"); +} + +template class Type, UnsignedInt dimensions, class T, class ...Args> void convertibleImplementation(py::class_, Args...>&, std::true_type) {} + +template class Type, class T, class ...Args> void convertibleImplementation(py::class_, Args...>&, std::true_type) {} + +template class Type, UnsignedInt dimensions, class T, class ...Args> void convertible(py::class_, Args...>& c) { + convertibleImplementation(c, std::is_same{}); + convertibleImplementation(c, std::is_same{}); + convertibleImplementation(c, std::is_same{}); +} + +template class Type, class T, class ...Args> void convertible(py::class_, Args...>& c) { + convertibleImplementation(c, std::is_same{}); + convertibleImplementation(c, std::is_same{}); + convertibleImplementation(c, std::is_same{}); +} + +} + +void mathRange(py::module& root, py::module& m) { + py::class_ range1D_{root, "Range1D", "One-dimensional float range"}; + py::class_ range2D_{root, "Range2D", "Two-dimensional float range"}; + py::class_ range3D_{root, "Range3D", "Three-dimensional float range"}; + + py::class_ range1Di{root, "Range1Di", "One-dimensional float range"}; + py::class_ range2Di{root, "Range2Di", "Two-dimensional float range"}; + py::class_ range3Di{root, "Range3Di", "Three-dimensional float range"}; + + py::class_ range1Dd{root, "Range1Dd", "One-dimensional double range"}; + py::class_ range2Dd{root, "Range2Dd", "Two-dimensional double range"}; + py::class_ range3Dd{root, "Range3Dd", "Three-dimensional double range"}; + + convertible(range1D_); + convertible(range2D_); + convertible(range3D_); + convertible(range1Di); + convertible(range2Di); + convertible(range3Di); + convertible(range1Dd); + convertible(range2Dd); + convertible(range3Dd); + + range(m, range1D_); + range(m, range2D_); + range(m, range3D_); + range(m, range1Di); + range(m, range2Di); + range(m, range3Di); + range(m, range1Dd); + range(m, range2Dd); + range(m, range3Dd); + + range2D(range2D_); + range2D(range2Di); + range2D(range2Dd); + + range3D(range3D_); + range3D(range3Di); + range3D(range3Dd); +} + +} diff --git a/src/python/magnum/test/test_math.py b/src/python/magnum/test/test_math.py index a972a84..1551f8e 100644 --- a/src/python/magnum/test/test_math.py +++ b/src/python/magnum/test/test_math.py @@ -903,3 +903,78 @@ class Quaternion_(unittest.TestCase): def test_repr(self): a = Quaternion.rotation(Deg(45.0), Vector3.x_axis()) self.assertEqual(repr(a), 'Quaternion({0.382683, 0, 0}, 0.92388)') + +class Range(unittest.TestCase): + def test_init(self): + a = Range1Di() + self.assertEqual(a.min, 0) + self.assertEqual(a.max, 0) + + b = Range1D(3.5, 5.0) + self.assertEqual(b.min, 3.5) + self.assertEqual(b.max, 5.0) + + c1 = Range2D(Vector2(1.0, 0.3), Vector2(2.0, 0.6)) + self.assertEqual(c1.min, Vector2(1.0, 0.3)) + self.assertEqual(c1.max, Vector2(2.0, 0.6)) + + c2 = Range2D((1.0, 0.3), (2.0, 0.6)) + self.assertEqual(c2.min, Vector2(1.0, 0.3)) + self.assertEqual(c2.max, Vector2(2.0, 0.6)) + + c3 = Range2D(((1.0, 0.3), (2.0, 0.6))) + self.assertEqual(c3.min, Vector2(1.0, 0.3)) + self.assertEqual(c3.max, Vector2(2.0, 0.6)) + + d1 = Range3Dd(Vector3d(1.0, 0.2, 0.3), Vector3d(1.0, 2.0, 3.0)) + self.assertEqual(d1.min, Vector3d(1.0, 0.2, 0.3)) + self.assertEqual(d1.max, Vector3d(1.0, 2.0, 3.0)) + + d2 = Range3Dd((1.0, 0.2, 0.3), (1.0, 2.0, 3.0)) + self.assertEqual(d2.min, Vector3d(1.0, 0.2, 0.3)) + self.assertEqual(d2.max, Vector3d(1.0, 2.0, 3.0)) + + d3 = Range3Dd(((1.0, 0.2, 0.3), (1.0, 2.0, 3.0))) + self.assertEqual(d3.min, Vector3d(1.0, 0.2, 0.3)) + self.assertEqual(d3.max, Vector3d(1.0, 2.0, 3.0)) + + def test_convert(self): + a = Range2Dd(Range2Di((3, 5), (8, 7))) + self.assertEqual(a, Range2Dd((3.0, 5.0), (8.0, 7.0))) + + def test_static_methods(self): + a = Range2D.zero_init() + self.assertEqual(a.min, Vector2()) + self.assertEqual(a.max, Vector2()) + + b = Range2D.from_size((3.0, 1.0), (2.0, 0.5)) + self.assertEqual(b.min, Vector2(3.0, 1.0)) + self.assertEqual(b.max, Vector2(5.0, 1.5)) + + c = Range2D.from_center((4.0, 1.25), (1.0, 0.25)) + self.assertEqual(c.min, Vector2(3.0, 1.0)) + self.assertEqual(c.max, Vector2(5.0, 1.5)) + + def test_properties(self): + a = Range2D((1.0, 0.2), (2.0, 0.4)) + self.assertEqual(a.bottom_right, Vector2(2.0, 0.2)) + self.assertEqual(a.top_left, Vector2(1.0, 0.4)) + + a.bottom_right = Vector2(3.0, 0.3) + a.top = 7.0 + self.assertEqual(a, Range2D((1.0, 0.3), (3.0, 7.0))) + + b = Range3D((1.0, 0.2, -1.0), (2.0, 0.4, 5.0)) + self.assertEqual(b.front_bottom_right, Vector3(2.0, 0.2, 5.0)) + self.assertEqual(b.back_top_left, Vector3(1.0, 0.4, -1.0)) + + b.back_bottom_right = Vector3(3.0, 0.3, -1.5) + b.front = 7.0 + b.left = 1.1 + self.assertEqual(b, Range3D((1.1, 0.3, -1.5), (3.0, 0.4, 7.0))) + + def test_methods(self): + a = math.join(Range2D(Vector2(), (3.0, 5.0)).translated((1.5, 0.7)), + Range2D((0.3, 2.0), (0.4, 2.1))) + self.assertEqual(a, Range2D((0.3, 0.7), (4.5, 5.7))) + self.assertEqual(a.center(), Vector2(2.4, 3.2))