From d62b44e3cd195b0780f2273918766b5ecdf638fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Thu, 9 Feb 2023 17:26:50 +0100 Subject: [PATCH] python: support also normalized VertexFormat values. They're unpacked to full floats on element access and packed back from full floats on mutable access, which makes this all very nice and transparent. Yay Python! That's it for now, I'll postpone half-float and matrix types for later when these are actually needed, as it needs extra testing for the aligned variants too. --- doc/python/magnum.trade.rst | 7 ++++++ src/python/magnum/test/test_trade.py | 17 +++++++++++++++ src/python/magnum/trade.cpp | 32 ++++++++++++++++++++++++++-- 3 files changed, 54 insertions(+), 2 deletions(-) diff --git a/doc/python/magnum.trade.rst b/doc/python/magnum.trade.rst index 5be9476..3f593ec 100644 --- a/doc/python/magnum.trade.rst +++ b/doc/python/magnum.trade.rst @@ -119,6 +119,13 @@ :ref:`mutable_indices` and :ref:`mutable_attribute()`, for example to perform a static transformation of the mesh before passing it to OpenGL. + Normalized formats (such as :ref:`VertexFormat.VECTOR3UB_NORMALIZED`) are + unpacked to a corresponding floating-point representation in element access + and packed from a floating-point representation in mutable acess. The type + annotation is however still matching the original type (such as :py:`'3B'` + in this case), so code consuming these via the buffer protocol needs to + handle the normalization explicitly if needed. + .. py:property:: magnum.trade.MeshData.mutable_index_data :raise AttributeError: If :ref:`index_data_flags` doesn't contain :ref:`DataFlag.MUTABLE` diff --git a/src/python/magnum/test/test_trade.py b/src/python/magnum/test/test_trade.py index 21450eb..e8f3331 100644 --- a/src/python/magnum/test/test_trade.py +++ b/src/python/magnum/test/test_trade.py @@ -461,6 +461,23 @@ class MeshData(unittest.TestCase): mutable_packed[1] -= Vector3i(12, 56, 200) self.assertEqual(packed[1], Vector3(39, 46, 55)) + def test_normalized_attribute_access(self): + importer = trade.ImporterManager().load_and_instantiate('GltfImporter') + importer.open_file(os.path.join(os.path.dirname(__file__), 'mesh.gltf')) + + mesh = importer.mesh(0) + self.assertEqual(mesh.index_data_flags, trade.DataFlag.OWNED|trade.DataFlag.MUTABLE) + self.assertEqual(mesh.attribute_format(trade.MeshAttribute.COLOR), VertexFormat.VECTOR3UB_NORMALIZED) + + normalized = mesh.attribute(trade.MeshAttribute.COLOR) + mutable_normalized = mesh.mutable_attribute(trade.MeshAttribute.COLOR) + self.assertEqual(normalized[1], Vector3(0.2, 0.4, 1)) + self.assertEqual(mutable_normalized[1], Vector3(0.2, 0.4, 1)) + + mutable_normalized[1] *= 0.5 + # Rounding errors are expected + self.assertEqual(normalized[1], Vector3(0.101961, 0.2, 0.501961)) + def test_data_access_not_mutable(self): mesh = primitives.cube_solid() # TODO split this once there's a mesh where only one or the other would diff --git a/src/python/magnum/trade.cpp b/src/python/magnum/trade.cpp index 6bbffad..7d2be8d 100644 --- a/src/python/magnum/trade.cpp +++ b/src/python/magnum/trade.cpp @@ -29,8 +29,8 @@ #include /** @todo drop once we have our string casters */ #include #include -#include #include +#include #include #include #include @@ -489,47 +489,75 @@ Containers::Triple(item) = format(py::cast(object)); \ }}; + /* Normalized types that need to be packed/unpacked before passed + from/to pybind */ + #define _cNormalized(format, unpackType) \ + case VertexFormat::format ## Normalized: return { \ + Containers::Implementation::pythonFormatString(), \ + [](const char* item) { \ + return py::cast(Math::unpack(*reinterpret_cast(item))); \ + }, \ + [](char* item, py::handle object) { \ + *reinterpret_cast(item) = Math::pack(py::cast(object)); \ + }}; /* LCOV_EXCL_START */ _c(Float) _c(Double) _c(UnsignedByte) + _cNormalized(UnsignedByte, Float) _c(Byte) + _cNormalized(Byte, Float) _c(UnsignedShort) + _cNormalized(UnsignedShort, Float) _c(Short) + _cNormalized(Short, Float) _c(UnsignedInt) _c(Int) _c(Vector2) _c(Vector2d) _cc(Vector2ub, Vector2ui) + _cNormalized(Vector2ub, Vector2) _cc(Vector2b, Vector2i) + _cNormalized(Vector2b, Vector2) _cc(Vector2us, Vector2ui) + _cNormalized(Vector2us, Vector2) _cc(Vector2s, Vector2i) + _cNormalized(Vector2s, Vector2) _c(Vector2ui) _c(Vector2i) _c(Vector3) _c(Vector3d) _cc(Vector3ub, Vector3ui) + _cNormalized(Vector3ub, Vector3) _cc(Vector3b, Vector3i) + _cNormalized(Vector3b, Vector3) _cc(Vector3us, Vector3ui) + _cNormalized(Vector3us, Vector3) _cc(Vector3s, Vector3i) + _cNormalized(Vector3s, Vector3) _c(Vector3ui) _c(Vector3i) _c(Vector4) _c(Vector4d) _cc(Vector4ub, Vector4ui) + _cNormalized(Vector4ub, Vector4) _cc(Vector4b, Vector4i) + _cNormalized(Vector4b, Vector4) _cc(Vector4us, Vector4ui) + _cNormalized(Vector4us, Vector4) _cc(Vector4s, Vector4i) + _cNormalized(Vector4s, Vector4) _c(Vector4ui) _c(Vector4i) /* LCOV_EXCL_STOP */ #undef _c #undef _cc + #undef _cNormalized - /** @todo handle half, normalized and matrix types */ + /** @todo handle half and matrix types */ default: return {}; }