From 081d32938a1ec2057c39f32cbbd9837e30c70186 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Tue, 23 Apr 2019 17:23:37 +0200 Subject: [PATCH] python: expose all Vector variants. --- src/python/magnum/CMakeLists.txt | 4 +- src/python/magnum/__init__.py | 4 + src/python/magnum/bootstrap.h | 2 + src/python/magnum/magnum.cpp | 2 + src/python/magnum/math.cpp | 7 +- src/python/magnum/math.h | 44 ++++ src/python/magnum/math.vector.h | 247 ++++++++++++++++++++++ src/python/magnum/math.vectorfloat.cpp | 90 ++++++++ src/python/magnum/math.vectorintegral.cpp | 83 ++++++++ src/python/magnum/test/test_math.py | 133 ++++++++++++ 10 files changed, 609 insertions(+), 7 deletions(-) create mode 100644 src/python/magnum/math.h create mode 100644 src/python/magnum/math.vector.h create mode 100644 src/python/magnum/math.vectorfloat.cpp create mode 100644 src/python/magnum/math.vectorintegral.cpp diff --git a/src/python/magnum/CMakeLists.txt b/src/python/magnum/CMakeLists.txt index 8899874..c3a5ef6 100644 --- a/src/python/magnum/CMakeLists.txt +++ b/src/python/magnum/CMakeLists.txt @@ -25,7 +25,9 @@ set(magnum_SRCS magnum.cpp - math.cpp) + math.cpp + math.vectorfloat.cpp + math.vectorintegral.cpp) pybind11_add_module(magnum ${magnum_SRCS}) target_include_directories(magnum PRIVATE ${PROJECT_SOURCE_DIR}/src/python) diff --git a/src/python/magnum/__init__.py b/src/python/magnum/__init__.py index b227129..aaedb30 100644 --- a/src/python/magnum/__init__.py +++ b/src/python/magnum/__init__.py @@ -31,4 +31,8 @@ __all__ = [ 'Deg', 'Rad', 'BoolVector2', 'BoolVector3', 'BoolVector4', + 'Vector2', 'Vector3', 'Vector4', + 'Vector2d', 'Vector3d', 'Vector4d', + 'Vector2i', 'Vector3i', 'Vector4i', + 'Vector2ui', 'Vector3ui', 'Vector4ui', ] diff --git a/src/python/magnum/bootstrap.h b/src/python/magnum/bootstrap.h index 7a7087a..ad0fe99 100644 --- a/src/python/magnum/bootstrap.h +++ b/src/python/magnum/bootstrap.h @@ -34,6 +34,8 @@ using namespace Magnum; namespace py = pybind11; void math(py::module& root, py::module& m); +void mathVectorFloat(py::module& root, py::module& m); +void mathVectorIntegral(py::module& root, py::module& m); } diff --git a/src/python/magnum/magnum.cpp b/src/python/magnum/magnum.cpp index ba1c434..4345c64 100644 --- a/src/python/magnum/magnum.cpp +++ b/src/python/magnum/magnum.cpp @@ -34,4 +34,6 @@ PYBIND11_MODULE(_magnum, m) { py::module math = m.def_submodule("math"); magnum::math(m, math); + magnum::mathVectorFloat(m, math); + magnum::mathVectorIntegral(m, math); } diff --git a/src/python/magnum/math.cpp b/src/python/magnum/math.cpp index 51320aa..7770751 100644 --- a/src/python/magnum/math.cpp +++ b/src/python/magnum/math.cpp @@ -31,17 +31,12 @@ #include #include "magnum/bootstrap.h" +#include "magnum/math.h" namespace magnum { namespace { -template std::string repr(const T& value) { - std::ostringstream out; - Debug{&out, Debug::Flag::NoNewlineAtTheEnd} << value; - return out.str(); -} - template void angle(py::class_& c) { /* Missing APIs: diff --git a/src/python/magnum/math.h b/src/python/magnum/math.h new file mode 100644 index 0000000..3ee74f8 --- /dev/null +++ b/src/python/magnum/math.h @@ -0,0 +1,44 @@ +#ifndef magnum_math_h +#define magnum_math_h +/* + 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 "magnum/bootstrap.h" + +namespace magnum { + +template std::string repr(const T& value) { + std::ostringstream out; + Debug{&out, Debug::Flag::NoNewlineAtTheEnd} << value; + return out.str(); +} + +} + +#endif diff --git a/src/python/magnum/math.vector.h b/src/python/magnum/math.vector.h new file mode 100644 index 0000000..a638378 --- /dev/null +++ b/src/python/magnum/math.vector.h @@ -0,0 +1,247 @@ +#ifndef magnum_math_vector_h +#define magnum_math_vector_h +/* + 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 "magnum/math.h" + +namespace magnum { + +/* Things common for vectors of all sizes and types */ +template void vector(py::module& m, py::class_& c) { + /* + Missing APIs: + + from(T*) + Type + construction from different types + VectorNi * VectorN and variants (5) + */ + + m + .def("dot", [](const T& a, const T& b) { return Math::dot(a, b); }, + "Dot product of two vectors"); + + c + /* Constructors */ + .def_static("zero_init", []() { + return T{Math::ZeroInit}; + }, "Construct a zero vector") + .def(py::init(), "Default constructor") + .def(py::init(), "Construct a vector with one value for all components") + + /* Comparison */ + .def(py::self == py::self, "Equality comparison") + .def(py::self != py::self, "Non-equality comparison") + .def(py::self < py::self, "Component-wise less than comparison") + .def(py::self > py::self, "Component-wise greater than comparison") + .def(py::self <= py::self, "Component-wise less than or equal comparison") + .def(py::self >= py::self, "Component-wise greater than or equal comparison") + + /* Set / get */ + .def("__setitem__", [](T& self, std::size_t i, typename T::Type value) { + self[i] = value; + }, "Set a value at given position") + .def("__getitem__", [](const T& self, std::size_t i) { + return self[i]; + }, "Value at given position") + + /* Operators */ + .def(-py::self, "Negated vector") + .def(py::self += py::self, "Add and assign a vector") + .def(py::self + py::self, "Add a vector") + .def(py::self -= py::self, "Subtract and assign a vector") + .def(py::self - py::self, "Subtract a vector") + .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 a vector component-wise and assign") + .def(py::self * py::self, "Multiply a vector component-wise") + .def(py::self /= py::self, "Divide a vector component-wise and assign") + .def(py::self / py::self, "Divide a vector component-wise") + .def(typename T::Type{} * py::self, "Multiply a scalar with a vector") + .def(typename T::Type{} / py::self, "Divide a vector with a scalar and invert") + + /* Member functions common for floating-point and integer types */ + .def("is_zero", &T::isZero, "Whether the vector is zero") + .def("dot", static_cast(&T::dot), "Dot product of the vector") + .def("flipped", &T::flipped, "Flipped vector") + .def("sum", &T::sum, "Sum of values in the vector") + .def("product", &T::product, "Product of values in the vector") + .def("min", &T::min, "Minimal value in the vector") + .def("max", &T::max, "Maximal value in the vector") + .def("minmax", &T::minmax, "Minimal and maximal value in the vector") + + .def("__repr__", repr, "Object representation"); + + /* Vector length */ + char lenDocstring[] = "Vector size. Returns _."; + lenDocstring[sizeof(lenDocstring) - 3] = '0' + T::Size; + c.def_static("__len__", []() { return int(T::Size); }, lenDocstring); +} + +template void vector2(py::class_>& c) { + c + /* Constructors */ + .def(py::init(), "Constructor") + + /* Static constructors */ + .def_static("x_axis", &Math::Vector2::xAxis, + "Vector in a direction of X axis (right)", py::arg("length") = T(1)) + .def_static("y_axis", &Math::Vector2::yAxis, + "Vector in a direction of Y axis (up)", py::arg("length") = T(1)) + .def_static("x_scale", &Math::Vector2::xScale, + "Scaling vector in a direction of X axis (width)", py::arg("scale")) + .def_static("y_scale", &Math::Vector2::yScale, + "Scaling vector in a direction of Y axis (height)", py::arg("scale")) + + /* Methods */ + .def("perpendicular", &Math::Vector2::perpendicular, + "Perpendicular vector") + + /* Properties */ + .def_property("x", + static_cast::*)() const>(&Math::Vector2::x), + [](Math::Vector2& self, T value) { self.x() = value; }, + "X component") + .def_property("y", + static_cast::*)() const>(&Math::Vector2::y), + [](Math::Vector2& self, T value) { self.y() = value; }, + "Y component"); +} + +template void vector3(py::class_>& c) { + c + /* Constructors */ + .def(py::init(), "Constructor") + + /* Static constructors */ + .def_static("x_axis", &Math::Vector3::xAxis, + "Vector in a direction of X axis (right)", py::arg("length") = T(1)) + .def_static("y_axis", &Math::Vector3::yAxis, + "Vector in a direction of Y axis (up)", py::arg("length") = T(1)) + .def_static("z_axis", &Math::Vector3::zAxis, + "Vector in a direction of Z axis (backward)", py::arg("length") = T(1)) + .def_static("x_scale", &Math::Vector3::xScale, + "Scaling vector in a direction of X axis (width)", py::arg("scale")) + .def_static("y_scale", &Math::Vector3::yScale, + "Scaling vector in a direction of Y axis (height)", py::arg("scale")) + .def_static("z_scale", &Math::Vector3::zScale, + "Scaling vector in a direction of Z axis (depth)", py::arg("scale")) + + /* Properties */ + .def_property("x", + static_cast::*)() const>(&Math::Vector3::x), + [](Math::Vector3& self, T value) { self.x() = value; }, + "X component") + .def_property("y", + static_cast::*)() const>(&Math::Vector3::y), + [](Math::Vector3& self, T value) { self.y() = value; }, + "Y component") + .def_property("z", + static_cast::*)() const>(&Math::Vector3::z), + [](Math::Vector3& self, T value) { self.z() = value; }, + "Z component") + + .def_property("r", + static_cast::*)() const>(&Math::Vector3::r), + [](Math::Vector3& self, T value) { self.r() = value; }, + "R component") + .def_property("g", + static_cast::*)() const>(&Math::Vector3::g), + [](Math::Vector3& self, T value) { self.g() = value; }, + "G component") + .def_property("b", + static_cast::*)() const>(&Math::Vector3::b), + [](Math::Vector3& self, T value) { self.b() = value; }, + "B component") + + .def_property("xy", + static_cast(Math::Vector3::*)() const>(&Math::Vector3::xy), + [](Math::Vector3& self, const Math::Vector2& value) { self.xy() = value; }, + "XY part of the vector"); +} + +template void vector4(py::class_>& c) { + c + /* Constructors */ + .def(py::init(), "Constructor") + + /* Properties */ + .def_property("x", + static_cast::*)() const>(&Math::Vector4::x), + [](Math::Vector4& self, T value) { self.x() = value; }, + "X component") + .def_property("y", + static_cast::*)() const>(&Math::Vector4::y), + [](Math::Vector4& self, T value) { self.y() = value; }, + "Y component") + .def_property("z", + static_cast::*)() const>(&Math::Vector4::z), + [](Math::Vector4& self, T value) { self.z() = value; }, + "Z component") + .def_property("w", + static_cast::*)() const>(&Math::Vector4::w), + [](Math::Vector4& self, T value) { self.w() = value; }, + "W component") + + .def_property("r", + static_cast::*)() const>(&Math::Vector4::r), + [](Math::Vector4& self, T value) { self.r() = value; }, + "R component") + .def_property("g", + static_cast::*)() const>(&Math::Vector4::g), + [](Math::Vector4& self, T value) { self.g() = value; }, + "G component") + .def_property("b", + static_cast::*)() const>(&Math::Vector4::b), + [](Math::Vector4& self, T value) { self.b() = value; }, + "B component") + .def_property("a", + static_cast::*)() const>(&Math::Vector4::a), + [](Math::Vector4& self, T value) { self.a() = value; }, + "A component") + + .def_property("xyz", + static_cast(Math::Vector4::*)() const>(&Math::Vector4::xyz), + [](Math::Vector4& self, const Math::Vector3& value) { self.xyz() = value; }, + "XYZ part of the vector") + .def_property("rgb", + static_cast(Math::Vector4::*)() const>(&Math::Vector4::rgb), + [](Math::Vector4& self, const Math::Vector3& value) { self.rgb() = value; }, + "RGB part of the vector") + .def_property("xy", + static_cast(Math::Vector4::*)() const>(&Math::Vector4::xy), + [](Math::Vector4& self, const Math::Vector2& value) { self.xy() = value; }, + "XY part of the vector"); +} + +} + +#endif diff --git a/src/python/magnum/math.vectorfloat.cpp b/src/python/magnum/math.vectorfloat.cpp new file mode 100644 index 0000000..f0ef2f2 --- /dev/null +++ b/src/python/magnum/math.vectorfloat.cpp @@ -0,0 +1,90 @@ +/* + 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 "magnum/math.vector.h" + +namespace magnum { + +namespace { + +template void vectorFloat(py::module& m, py::class_& c) { + m + .def("angle", [](const T& a, const T& b) { return Radd(Math::angle(a, b)); }, + "Angle between normalized vectors", py::arg("normalized_a"), py::arg("normalized_b")); + + c + .def("is_normalized", &T::isNormalized, "Whether the vector is normalized") + .def("length", &T::length, "Vector length") + + /* Cast needed because these are enabled only for floats */ + .def("length_inverted", static_cast(&T::lengthInverted), "Inverse vector length") + .def("normalized", static_cast(&T::normalized), + "Normalized vector (of unit length)") + .def("resized", static_cast(&T::resized), + "Resized vector") + .def("projected", [](const T& self, const T& line) { + return self.projected(line); + }, "Vector projected onto a line") + .def("projected_onto_normalized", [](const T& self, const T& line) { + return self.projectedOntoNormalized(line); + }, "Vector projected onto a normalized line"); +} + +template void vectorsFloat(py::module& m, py::class_>& vector2_, py::class_>& vector3_, py::class_>& vector4_) { + vector2_ + .def("aspect_ratio", static_cast::*)() const>(&Math::Vector2::aspectRatio), + "Aspect ratio") + .def("cross", static_cast&, const Math::Vector2&)>(Math::cross), + "2D cross product"); + vector>(m, vector2_); + vectorFloat>(m, vector2_); + vector2(vector2_); + + vector3_ + .def("cross", static_cast(*)(const Math::Vector3&, const Math::Vector3&)>(Math::cross), + "Cross product"); + vector>(m, vector3_); + vectorFloat>(m, vector3_); + vector3(vector3_); + + vector>(m, vector4_); + vectorFloat>(m, vector4_); + vector4(vector4_); +} + +} + +void mathVectorFloat(py::module& root, py::module& m) { + py::class_ vector2{root, "Vector2", "Two-component float vector"}; + py::class_ vector3{root, "Vector3", "Threee-component float vector"}; + py::class_ vector4{root, "Vector4", "Four-component float vector"}; + py::class_ vector2d{root, "Vector2d", "Two-component double vector"}; + py::class_ vector3d{root, "Vector3d", "Threee-component double vector"}; + py::class_ vector4d{root, "Vector4d", "Four-component double vector"}; + vectorsFloat(m, vector2, vector3, vector4); + vectorsFloat(m, vector2d, vector3d, vector4d); +} + +} diff --git a/src/python/magnum/math.vectorintegral.cpp b/src/python/magnum/math.vectorintegral.cpp new file mode 100644 index 0000000..e55f6da --- /dev/null +++ b/src/python/magnum/math.vectorintegral.cpp @@ -0,0 +1,83 @@ +/* + 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 "magnum/math.vector.h" + +namespace magnum { + +namespace { + +template void vectorIntegral(py::class_& c) { + c + .def(py::self %= typename T::Type{}, "Do modulo of an integral vector and assign") + .def(py::self % typename T::Type{}, "Modulo of an integral vector") + .def(py::self %= py::self, "Do module of two integral vectors and assign") + .def(py::self % py::self, "Modulo of two integral vectors") + .def(~py::self, "Bitwise NOT of an integral vector") + .def(py::self &= py::self, "Do bitwise AND of two integral vectors and assign") + .def(py::self & py::self, "Bitwise AND of two integral vectors") + .def(py::self |= py::self, "Do bitwise OR of two integral vectors and assign") + .def(py::self | py::self, "Bitwise OR of two integral vectors") + .def(py::self ^= py::self, "Do bitwise XOR of two integral vectors and assign") + .def(py::self ^ py::self, "Bitwise XOR of two integral vectors") + .def(py::self <<= typename T::Type{}, "Do bitwise left shift of an integral vector and assign") + .def(py::self << typename T::Type{}, "Bitwise left shift of an integral vector") + .def(py::self >>= typename T::Type{}, "Do bitwise right shift of an integral vector and assign") + .def(py::self >> typename T::Type{}, "Bitwise right shift of an integral vector") + .def(py::self *= Float{}, "Multiply an integral vector with a floating-point number and assign") + .def(py::self * Float{}, "Multiply an integral vector with a floating-point number") + .def(Float{} * py::self, "Multiply a floating-point number with an integral vector") + .def(py::self /= Float{}, "Divide an integral vector with a floating-point number and assign") + .def(py::self / Float{}, "Divide an integral vector with a floating-point number"); +} + +template void vectorsIntegral(py::module& m, py::class_>& vector2_, py::class_>& vector3_, py::class_>& vector4_) { + vector>(m, vector2_); + vectorIntegral>(vector2_); + vector2(vector2_); + + vector>(m, vector3_); + vectorIntegral>(vector3_); + vector3(vector3_); + + vector>(m, vector4_); + vectorIntegral>(vector4_); + vector4(vector4_); +} + +} + +void mathVectorIntegral(py::module& root, py::module& m) { + py::class_ vector2i{root, "Vector2i", "Two-component signed integer vector"}; + py::class_ vector3i{root, "Vector3i", "Threee-component signed integral vector"}; + py::class_ vector4i{root, "Vector4i", "Four-component signed integral vector"}; + py::class_ vector2ui{root, "Vector2ui", "Two-component unsigned integral vector"}; + py::class_ vector3ui{root, "Vector3ui", "Threee-component unsigned integral vector"}; + py::class_ vector4ui{root, "Vector4ui", "Four-component unsigned integral vector"}; + vectorsIntegral(m, vector2i, vector3i, vector4i); + vectorsIntegral(m, vector2ui, vector3ui, vector4ui); +} + +} diff --git a/src/python/magnum/test/test_math.py b/src/python/magnum/test/test_math.py index 90bac8d..ecc8685 100644 --- a/src/python/magnum/test/test_math.py +++ b/src/python/magnum/test/test_math.py @@ -102,3 +102,136 @@ class Constants(unittest.TestCase): def test(self): self.assertAlmostEqual(math.sqrt2**2, 2.0, 6) self.assertAlmostEqual(math.sqrt3**2, 3.0) + +class Vector(unittest.TestCase): + def test_init(self): + a = Vector4i() + b = Vector3d.zero_init() + c = Vector2i(44, -3) + self.assertEqual(a, Vector4i(0, 0, 0, 0)) + self.assertEqual(b, Vector3d(0.0, 0.0, 0.0)) + self.assertEqual(c, Vector2i(44, -3)) + + def test_static_methods(self): + self.assertEqual(Vector2.y_scale(5), Vector2(1, 5)) + self.assertEqual(Vector3d.z_axis(-3), Vector3d(0, 0, -3)) + self.assertEqual(Vector3i.x_axis(), Vector3i(1, 0, 0)) + + def test_length(self): + self.assertEqual(Vector3.__len__(), 3) + #self.assertEqual(len(Vector3), 3) TODO: Y not? + self.assertEqual(len(Vector4i()), 4) + + def test_properties(self): + a = Vector2i() + a.x = 1 + a.y = 2 + self.assertEqual(a.x, 1) + self.assertEqual(a.y, 2) + self.assertEqual(a, Vector2i(1, 2)) + + a = Vector3() + a.x = 1.0 + a.y = 2.0 + a.z = 3.0 + self.assertEqual(a.x, 1.0) + self.assertEqual(a.y, 2.0) + self.assertEqual(a.z, 3.0) + self.assertEqual(a, Vector3(1.0, 2.0, 3.0)) + + a.xy = (-1.0, -2.0) + self.assertEqual(a.xy, Vector2(-1.0, -2.0)) + self.assertEqual(a, Vector3(-1.0, -2.0, 3.0)) + + a = Vector3() + a.r = 1.0 + a.g = 2.0 + a.b = 3.0 + self.assertEqual(a.r, 1.0) + self.assertEqual(a.g, 2.0) + self.assertEqual(a.b, 3.0) + self.assertEqual(a, Vector3(1.0, 2.0, 3.0)) + + a = Vector4d() + a.x = 1.0 + a.y = 2.0 + a.z = 3.0 + a.w = 4.0 + self.assertEqual(a.x, 1.0) + self.assertEqual(a.y, 2.0) + self.assertEqual(a.z, 3.0) + self.assertEqual(a.w, 4.0) + self.assertEqual(a, Vector4d(1.0, 2.0, 3.0, 4.0)) + + a = Vector4d() + a.r = 1.0 + a.g = 2.0 + a.b = 3.0 + a.a = 4.0 + self.assertEqual(a.r, 1.0) + self.assertEqual(a.g, 2.0) + self.assertEqual(a.b, 3.0) + self.assertEqual(a.a, 4.0) + self.assertEqual(a, Vector4d(1.0, 2.0, 3.0, 4.0)) + + a.xy = (-1.0, -2.0) + self.assertEqual(a.xy, Vector2d(-1.0, -2.0)) + self.assertEqual(a, Vector4d(-1.0, -2.0, 3.0, 4.0)) + + a.xyz = (0.5, 0.25, 0.125) + self.assertEqual(a.xyz, Vector3d(0.5, 0.25, 0.125)) + self.assertEqual(a, Vector4d(0.5, 0.25, 0.125, 4.0)) + + def test_properties_rgb(self): + a = Vector3() + a.r = 1.0 + a.g = 2.0 + a.b = 3.0 + self.assertEqual(a.r, 1.0) + self.assertEqual(a.g, 2.0) + self.assertEqual(a.b, 3.0) + self.assertEqual(a, Vector3(1.0, 2.0, 3.0)) + + a = Vector4d() + a.r = 1.0 + a.g = 2.0 + a.b = 3.0 + a.a = 4.0 + self.assertEqual(a.r, 1.0) + self.assertEqual(a.g, 2.0) + self.assertEqual(a.b, 3.0) + self.assertEqual(a.a, 4.0) + self.assertEqual(a, Vector4d(1.0, 2.0, 3.0, 4.0)) + + a.rgb = (0.5, 0.25, 0.125) + self.assertEqual(a.rgb, Vector3d(0.5, 0.25, 0.125)) + self.assertEqual(a, Vector4d(0.5, 0.25, 0.125, 4.0)) + + def test_set_get(self): + a = Vector3(1.0, 3.14, -13.37) + self.assertAlmostEqual(a[1], 3.14, 6) + + a[2] = 0.13 + self.assertEqual(a, Vector3(1.0, 3.14, 0.13)) + + b = Vector4i(3, 4, 5, 6) + b.b *= 3 + b.xy += Vector2i(1, -1) + self.assertEqual(b, Vector4i(4, 3, 15, 6)) + + def test_ops(self): + self.assertEqual(math.dot(Vector2(0.5, 3.0), Vector2(2.0, 0.5)), 2.5) + self.assertEqual(Deg(math.angle( + Vector2(0.5, 3.0).normalized(), + Vector2(2.0, 0.5).normalized())), Deg(66.5014333443446)) + self.assertEqual(Vector3(1.0, 2.0, 0.3).projected(Vector3.y_axis()), + Vector3.y_axis(2.0)) + self.assertEqual(Vector3(1.0, 2.0, 0.3).projected_onto_normalized(Vector3.y_axis()), + Vector3.y_axis(2.0)) + + def test_ops_number_on_the_left(self): + self.assertEqual(2.0*Vector2(1.0, -3.0), Vector2(2.0, -6.0)) + self.assertEqual(6.0/Vector2(2.0, -3.0), Vector2(3.0, -2.0)) + + def test_repr(self): + self.assertEqual(repr(Vector3(1.0, 3.14, -13.37)), 'Vector(1, 3.14, -13.37)')