diff --git a/doc/changelog.dox b/doc/changelog.dox index e48b9e990..fd39bece2 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -162,7 +162,8 @@ See also: - A new, redesigned @ref Trade::MeshData class that allows much more flexible access to vertex/index data without unnecessary allocations and data - conversions or copies + conversions or copies. Importers expose it through the new + @ref Trade::AbstractImporter::mesh() family of APIs. - Ability to import image mip levels via an additional parameter in @ref Trade::AbstractImporter::image2D(), @ref Trade::AbstractImporter::image2DLevelCount() and similar APIs for 1D diff --git a/src/Magnum/Trade/AbstractImporter.cpp b/src/Magnum/Trade/AbstractImporter.cpp index 3138c40db..d9abe242e 100644 --- a/src/Magnum/Trade/AbstractImporter.cpp +++ b/src/Magnum/Trade/AbstractImporter.cpp @@ -38,6 +38,7 @@ #include "Magnum/Trade/CameraData.h" #include "Magnum/Trade/ImageData.h" #include "Magnum/Trade/LightData.h" +#include "Magnum/Trade/MeshData.h" #include "Magnum/Trade/MeshData2D.h" #include "Magnum/Trade/MeshData3D.h" #include "Magnum/Trade/ObjectData2D.h" @@ -403,6 +404,59 @@ Containers::Pointer AbstractImporter::doObject3D(UnsignedInt) { CORRADE_ASSERT(false, "Trade::AbstractImporter::object3D(): not implemented", {}); } +UnsignedInt AbstractImporter::meshCount() const { + CORRADE_ASSERT(isOpened(), "Trade::AbstractImporter::meshCount(): no file opened", {}); + return doMeshCount(); +} + +UnsignedInt AbstractImporter::doMeshCount() const { return 0; } + +Int AbstractImporter::meshForName(const std::string& name) { + CORRADE_ASSERT(isOpened(), "Trade::AbstractImporter::meshForName(): no file opened", {}); + return doMeshForName(name); +} + +Int AbstractImporter::doMeshForName(const std::string&) { return -1; } + +std::string AbstractImporter::meshName(const UnsignedInt id) { + CORRADE_ASSERT(isOpened(), "Trade::AbstractImporter::meshName(): no file opened", {}); + CORRADE_ASSERT(id < doMeshCount(), "Trade::AbstractImporter::meshName(): index" << id << "out of range for" << doMeshCount() << "entries", {}); + return doMeshName(id); +} + +std::string AbstractImporter::doMeshName(UnsignedInt) { return {}; } + +Containers::Optional AbstractImporter::mesh(const UnsignedInt id) { + CORRADE_ASSERT(isOpened(), "Trade::AbstractImporter::mesh(): no file opened", {}); + CORRADE_ASSERT(id < doMeshCount(), "Trade::AbstractImporter::mesh(): index" << id << "out of range for" << doMeshCount() << "entries", {}); + Containers::Optional mesh = doMesh(id); + CORRADE_ASSERT(!mesh || (!mesh->_indexData.deleter() && !mesh->_vertexData.deleter() && !mesh->_attributes.deleter()), "Trade::AbstractImporter::mesh(): implementation is not allowed to use a custom Array deleter", {}); + return mesh; +} + +Containers::Optional AbstractImporter::doMesh(UnsignedInt) { + CORRADE_ASSERT(false, "Trade::AbstractImporter::mesh(): not implemented", {}); +} + +MeshAttribute AbstractImporter::meshAttributeForName(const std::string& name) { + const MeshAttribute out = doMeshAttributeForName(name); + CORRADE_ASSERT(out == MeshAttribute{} || isMeshAttributeCustom(out), + "Trade::AbstractImporter::meshAttributeForName(): implementation-returned" << out << "is neither custom nor invalid", {}); + return out; +} + +MeshAttribute AbstractImporter::doMeshAttributeForName(const std::string&) { + return {}; +} + +std::string AbstractImporter::meshAttributeName(MeshAttribute name) { + CORRADE_ASSERT(isMeshAttributeCustom(name), + "Trade::AbstractImporter::meshAttributeName():" << name << "is not custom", {}); + return doMeshAttributeName(meshAttributeCustom(name)); +} + +std::string AbstractImporter::doMeshAttributeName(UnsignedShort) { return {}; } + UnsignedInt AbstractImporter::mesh2DCount() const { CORRADE_ASSERT(isOpened(), "Trade::AbstractImporter::mesh2DCount(): no file opened", {}); return doMesh2DCount(); diff --git a/src/Magnum/Trade/AbstractImporter.h b/src/Magnum/Trade/AbstractImporter.h index 5900873c6..024c7ffaa 100644 --- a/src/Magnum/Trade/AbstractImporter.h +++ b/src/Magnum/Trade/AbstractImporter.h @@ -159,8 +159,8 @@ expose internal state through various accessors: imported by @ref image1D(), @ref image2D() or @ref image3D() - @ref LightData::importerState() can expose importer state for a light imported by @ref light() -- @ref MeshData3D::importerState() can expose importer state for a mesh - imported by @ref mesh2D() or @ref mesh3D() +- @ref MeshData::importerState() can expose importer state for a mesh + imported by @ref mesh() - @ref ObjectData3D::importerState() can expose importer state for an object imported by @ref object2D() or @ref object3D() - @ref SceneData::importerState() can expose importer state for a scene @@ -680,6 +680,72 @@ class MAGNUM_TRADE_EXPORT AbstractImporter: public PluginManager::AbstractManagi */ Containers::Pointer object3D(UnsignedInt id); + /** + * @brief Mesh count + * @m_since_latest + * + * Expects that a file is opened. + */ + UnsignedInt meshCount() const; + + /** + * @brief Mesh ID for given name + * @m_since_latest + * + * If no mesh for given name exists, returns @cpp -1 @ce. Expects that + * a file is opened. + * @see @ref meshName() + */ + Int meshForName(const std::string& name); + + /** + * @brief Mesh name + * @param id Mesh ID, from range [0, @ref meshCount()). + * @m_since_latest + * + * Expects that a file is opened. + * @see @ref meshForName() + */ + std::string meshName(UnsignedInt id); + + /** + * @brief Mesh + * @param id Mesh ID, from range [0, @ref meshCount()). + * @m_since_latest + * + * Returns given mesh or @ref Containers::NullOpt if importing failed. + * Expects that a file is opened. + */ + Containers::Optional mesh(UnsignedInt id); + + /** + * @brief Mesh attribute for given name + * @m_since_latest + * + * If the name is not recognized, returns a zero (invalid) + * @ref MeshAttribute, otherwise returns a custom mesh attribute. Note + * that the value returned by this function may depend on whether a + * file is opened or not and also be different for different files --- + * see documentation of a particular importer for more information. + * @see @ref isMeshAttributeCustom() + */ + MeshAttribute meshAttributeForName(const std::string& name); + + /** + * @brief String name for given custom mesh attribute + * @m_since_latest + * + * Given a custom @p name returned by @ref mesh() in a @ref MeshData, + * returns a string identifier. If a string representation is not + * available or @p name is not recognized, returns an empty string. + * Expects that @p name is custom. Note that the value returned by + * this function may depend on whether a file is opened or not and also + * be different for different files --- see documentation of a + * particular importer for more information. + * @see @ref isMeshAttributeCustom() + */ + std::string meshAttributeName(MeshAttribute name); + /** * @brief Two-dimensional mesh count * @@ -1164,6 +1230,53 @@ class MAGNUM_TRADE_EXPORT AbstractImporter: public PluginManager::AbstractManagi /** @brief Implementation for @ref object3D() */ virtual Containers::Pointer doObject3D(UnsignedInt id); + /** + * @brief Implementation for @ref meshCount() + * @m_since_latest + * + * Default implementation returns @cpp 0 @ce. + */ + virtual UnsignedInt doMeshCount() const; + + /** + * @brief Implementation for @ref meshForName() + * @m_since_latest + * + * Default implementation returns @cpp -1 @ce. + */ + virtual Int doMeshForName(const std::string& name); + + /** + * @brief Implementation for @ref meshName() + * @m_since_latest + * + * Default implementation returns an empty string. + */ + virtual std::string doMeshName(UnsignedInt id); + + /** + * @brief Implementation for @ref mesh() + * @m_since_latest + */ + virtual Containers::Optional doMesh(UnsignedInt id); + + /** + * @brief Implementation for @ref meshAttributeForName() + * @m_since_latest + * + * Default implementation returns an invalid (zero) value. + */ + virtual MeshAttribute doMeshAttributeForName(const std::string& name); + + /** + * @brief Implementation for @ref meshAttributeName() + * @m_since_latest + * + * Receives the custom ID extracted via @ref meshAttributeCustom(MeshAttribute). + * Default implementation returns an empty string. + */ + virtual std::string doMeshAttributeName(UnsignedShort name); + /** * @brief Implementation for @ref mesh2DCount() * diff --git a/src/Magnum/Trade/MeshData.h b/src/Magnum/Trade/MeshData.h index 6df4f05fa..de41b4d3b 100644 --- a/src/Magnum/Trade/MeshData.h +++ b/src/Magnum/Trade/MeshData.h @@ -44,11 +44,16 @@ namespace Magnum { namespace Trade { @brief Mesh attribute name @m_since_latest -@see @ref MeshData, @ref MeshAttributeData, @ref VertexFormat +@see @ref MeshData, @ref MeshAttributeData, @ref VertexFormat, + @ref AbstractImporter::meshAttributeForName(), + @ref AbstractImporter::meshAttributeName() */ /* 16 bits because 8 bits is not enough to cover all potential per-edge, per-face, per-instance and per-meshlet attributes */ enum class MeshAttribute: UnsignedShort { + /* 0 reserved for an invalid value (returned from + AbstractImporter::meshAttributeForName()) */ + /** * Position. Type is usually @ref Magnum::Vector2 "Vector2" for 2D and * @ref Magnum::Vector3 "Vector3" for 3D. Corresponds to @@ -57,7 +62,7 @@ enum class MeshAttribute: UnsignedShort { * @ref MeshData::positions2DAsArray(), * @ref MeshData::positions3DAsArray() */ - Position, + Position = 1, /** * Normal. Type is usually @ref Magnum::Vector3 "Vector3". Corresponds to @@ -243,7 +248,8 @@ class MAGNUM_TRADE_EXPORT MeshAttributeData { @m_since_latest Provides access to mesh vertex and index data, together with additional -information such as primitive type. +information such as primitive type. Populated instances of this class are +returned from @ref AbstractImporter::mesh(). @section Trade-MeshData-usage Basic usage @@ -268,6 +274,8 @@ of data, which you can directly upload, and then use provided metadata to let the GPU know of the format and layout: @snippet MagnumTrade.cpp MeshData-usage-advanced + +@see @ref AbstractImporter::mesh() */ class MAGNUM_TRADE_EXPORT MeshData { public: @@ -441,7 +449,9 @@ class MAGNUM_TRADE_EXPORT MeshData { * @brief Attribute name * * The @p id is expected to be smaller than @ref attributeCount() const. - * @see @ref attributeFormat(), @ref isMeshAttributeCustom() + * @see @ref attributeFormat(), @ref isMeshAttributeCustom(), + * @ref AbstractImporter::meshAttributeForName(), + * @ref AbstractImporter::meshAttributeName() */ MeshAttribute attributeName(UnsignedInt id) const; @@ -714,6 +724,11 @@ class MAGNUM_TRADE_EXPORT MeshData { const void* importerState() const { return _importerState; } private: + /* For custom deleter checks. Not done in the constructors here because + the restriction is pointless when used outside of plugin + implementations. */ + friend AbstractImporter; + UnsignedInt attributeFor(MeshAttribute name, UnsignedInt id) const; UnsignedInt _vertexCount; diff --git a/src/Magnum/Trade/Test/AbstractImporterTest.cpp b/src/Magnum/Trade/Test/AbstractImporterTest.cpp index 60f4d699d..9f44d98d7 100644 --- a/src/Magnum/Trade/Test/AbstractImporterTest.cpp +++ b/src/Magnum/Trade/Test/AbstractImporterTest.cpp @@ -37,6 +37,7 @@ #include "Magnum/Trade/CameraData.h" #include "Magnum/Trade/ImageData.h" #include "Magnum/Trade/LightData.h" +#include "Magnum/Trade/MeshData.h" #include "Magnum/Trade/MeshData2D.h" #include "Magnum/Trade/MeshData3D.h" #include "Magnum/Trade/MeshObjectData2D.h" @@ -156,6 +157,25 @@ struct AbstractImporterTest: TestSuite::Tester { void object3DNoFile(); void object3DOutOfRange(); + void mesh(); + void meshCountNotImplemented(); + void meshCountNoFile(); + void meshForNameNotImplemented(); + void meshForNameNoFile(); + void meshNameNotImplemented(); + void meshNameNoFile(); + void meshNameOutOfRange(); + void meshNotImplemented(); + void meshNoFile(); + void meshOutOfRange(); + void meshCustomIndexDataDeleter(); + void meshCustomVertexDataDeleter(); + void meshCustomAttributesDeleter(); + + void meshAttributeName(); + void meshAttributeNameNotImplemented(); + void meshAttributeNameNotCustom(); + void mesh2D(); void mesh2DCountNotImplemented(); void mesh2DCountNoFile(); @@ -371,6 +391,25 @@ AbstractImporterTest::AbstractImporterTest() { &AbstractImporterTest::object3DNoFile, &AbstractImporterTest::object3DOutOfRange, + &AbstractImporterTest::mesh, + &AbstractImporterTest::meshCountNotImplemented, + &AbstractImporterTest::meshCountNoFile, + &AbstractImporterTest::meshForNameNotImplemented, + &AbstractImporterTest::meshForNameNoFile, + &AbstractImporterTest::meshNameNotImplemented, + &AbstractImporterTest::meshNameNoFile, + &AbstractImporterTest::meshNameOutOfRange, + &AbstractImporterTest::meshNotImplemented, + &AbstractImporterTest::meshNoFile, + &AbstractImporterTest::meshOutOfRange, + &AbstractImporterTest::meshCustomIndexDataDeleter, + &AbstractImporterTest::meshCustomVertexDataDeleter, + &AbstractImporterTest::meshCustomAttributesDeleter, + + &AbstractImporterTest::meshAttributeName, + &AbstractImporterTest::meshAttributeNameNotImplemented, + &AbstractImporterTest::meshAttributeNameNotCustom, + &AbstractImporterTest::mesh2D, &AbstractImporterTest::mesh2DCountNotImplemented, &AbstractImporterTest::mesh2DCountNoFile, @@ -2031,6 +2070,287 @@ void AbstractImporterTest::object3DOutOfRange() { CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::object3D(): index 8 out of range for 8 entries\n"); } +void AbstractImporterTest::mesh() { + struct: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + UnsignedInt doMeshCount() const override { return 8; } + Int doMeshForName(const std::string& name) override { + if(name == "eighth") return 7; + else return -1; + } + std::string doMeshName(UnsignedInt id) override { + if(id == 7) return "eighth"; + else return {}; + } + Containers::Optional doMesh(UnsignedInt id) override { + /* Verify that initializer list is converted to an array with + the default deleter and not something disallowed */ + if(id == 7) return MeshData{MeshPrimitive::Points, nullptr, {MeshAttributeData{MeshAttribute::Position, VertexFormat::Vector3, nullptr}}, &state}; + else return {}; + } + } importer; + + CORRADE_COMPARE(importer.meshCount(), 8); + CORRADE_COMPARE(importer.meshForName("eighth"), 7); + CORRADE_COMPARE(importer.meshName(7), "eighth"); + + auto data = importer.mesh(7); + CORRADE_VERIFY(data); + CORRADE_COMPARE(data->importerState(), &state); +} + +void AbstractImporterTest::meshCountNotImplemented() { + struct: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + } importer; + + CORRADE_COMPARE(importer.meshCount(), 0); +} + +void AbstractImporterTest::meshCountNoFile() { + struct: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return false; } + void doClose() override {} + } importer; + + std::ostringstream out; + Error redirectError{&out}; + + importer.meshCount(); + CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::meshCount(): no file opened\n"); +} + +void AbstractImporterTest::meshForNameNotImplemented() { + struct: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + } importer; + + CORRADE_COMPARE(importer.meshForName(""), -1); +} + +void AbstractImporterTest::meshForNameNoFile() { + struct: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return false; } + void doClose() override {} + } importer; + + std::ostringstream out; + Error redirectError{&out}; + + importer.meshForName(""); + CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::meshForName(): no file opened\n"); +} + +void AbstractImporterTest::meshNameNotImplemented() { + struct: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + UnsignedInt doMeshCount() const override { return 8; } + } importer; + + CORRADE_COMPARE(importer.meshName(7), ""); +} + +void AbstractImporterTest::meshNameNoFile() { + struct: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return false; } + void doClose() override {} + } importer; + + std::ostringstream out; + Error redirectError{&out}; + + importer.meshName(42); + CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::meshName(): no file opened\n"); +} + +void AbstractImporterTest::meshNameOutOfRange() { + struct: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + UnsignedInt doMeshCount() const override { return 8; } + } importer; + + std::ostringstream out; + Error redirectError{&out}; + + importer.meshName(8); + CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::meshName(): index 8 out of range for 8 entries\n"); +} + +void AbstractImporterTest::meshNotImplemented() { + struct: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + UnsignedInt doMeshCount() const override { return 8; } + } importer; + + std::ostringstream out; + Error redirectError{&out}; + + importer.mesh(7); + CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::mesh(): not implemented\n"); +} + +void AbstractImporterTest::meshNoFile() { + struct: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return false; } + void doClose() override {} + } importer; + + std::ostringstream out; + Error redirectError{&out}; + + importer.mesh(42); + CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::mesh(): no file opened\n"); +} + +void AbstractImporterTest::meshOutOfRange() { + struct: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + UnsignedInt doMeshCount() const override { return 8; } + } importer; + + std::ostringstream out; + Error redirectError{&out}; + + importer.mesh(8); + CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::mesh(): index 8 out of range for 8 entries\n"); +} + +void AbstractImporterTest::meshCustomIndexDataDeleter() { + struct: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + UnsignedInt doMeshCount() const override { return 1; } + Containers::Optional doMesh(UnsignedInt) override { + return MeshData{MeshPrimitive::Triangles, Containers::Array{data, 1, [](char*, std::size_t) {}}, MeshIndexData{MeshIndexType::UnsignedByte, data}}; + } + + char data[1]; + } importer; + + std::ostringstream out; + Error redirectError{&out}; + + importer.mesh(0); + CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::mesh(): implementation is not allowed to use a custom Array deleter\n"); +} + +void AbstractImporterTest::meshCustomVertexDataDeleter() { + struct: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + UnsignedInt doMeshCount() const override { return 1; } + Containers::Optional doMesh(UnsignedInt) override { + return MeshData{MeshPrimitive::Triangles, Containers::Array{nullptr, 0, [](char*, std::size_t) {}}, {MeshAttributeData{MeshAttribute::Position, VertexFormat::Vector3, nullptr}}}; + } + } importer; + + std::ostringstream out; + Error redirectError{&out}; + + importer.mesh(0); + CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::mesh(): implementation is not allowed to use a custom Array deleter\n"); +} + +void AbstractImporterTest::meshCustomAttributesDeleter() { + struct: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + UnsignedInt doMeshCount() const override { return 1; } + Containers::Optional doMesh(UnsignedInt) override { + return MeshData{MeshPrimitive::Triangles, nullptr, Containers::Array{&positions, 1, [](MeshAttributeData*, std::size_t) {}}}; + } + + MeshAttributeData positions{MeshAttribute::Position, VertexFormat::Vector3, nullptr}; + } importer; + + std::ostringstream out; + Error redirectError{&out}; + + importer.mesh(0); + CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::mesh(): implementation is not allowed to use a custom Array deleter\n"); +} + +void AbstractImporterTest::meshAttributeName() { + struct: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return false; } + void doClose() override {} + + MeshAttribute doMeshAttributeForName(const std::string& name) override { + if(name == "SMOOTH_GROUP_ID") return meshAttributeCustom(37); + return MeshAttribute{}; + } + + std::string doMeshAttributeName(UnsignedShort id) override { + if(id == 37) return "SMOOTH_GROUP_ID"; + return ""; + } + } importer; + + CORRADE_COMPARE(importer.meshAttributeForName("SMOOTH_GROUP_ID"), meshAttributeCustom(37)); + CORRADE_COMPARE(importer.meshAttributeName(meshAttributeCustom(37)), "SMOOTH_GROUP_ID"); +} + +void AbstractImporterTest::meshAttributeNameNotImplemented() { + struct: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return false; } + void doClose() override {} + } importer; + + CORRADE_COMPARE(importer.meshAttributeForName(""), MeshAttribute{}); + CORRADE_COMPARE(importer.meshAttributeName(meshAttributeCustom(37)), ""); +} + +void AbstractImporterTest::meshAttributeNameNotCustom() { + struct: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return false; } + void doClose() override {} + + MeshAttribute doMeshAttributeForName(const std::string&) override { + return MeshAttribute::Position; + } + } importer; + + std::ostringstream out; + Error redirectError{&out}; + importer.meshAttributeForName("SMOOTH_GROUP_ID"); + importer.meshAttributeName(MeshAttribute::Position); + CORRADE_COMPARE(out.str(), + "Trade::AbstractImporter::meshAttributeForName(): implementation-returned Trade::MeshAttribute::Position is neither custom nor invalid\n" + "Trade::AbstractImporter::meshAttributeName(): Trade::MeshAttribute::Position is not custom\n"); +} + void AbstractImporterTest::mesh2D() { struct: AbstractImporter { ImporterFeatures doFeatures() const override { return {}; }