diff --git a/src/Magnum/Trade/AbstractImporter.cpp b/src/Magnum/Trade/AbstractImporter.cpp index 5606fe084..3486743e6 100644 --- a/src/Magnum/Trade/AbstractImporter.cpp +++ b/src/Magnum/Trade/AbstractImporter.cpp @@ -462,6 +462,16 @@ UnsignedInt AbstractImporter::meshCount() const { UnsignedInt AbstractImporter::doMeshCount() const { return 0; } +UnsignedInt AbstractImporter::meshLevelCount(const UnsignedInt id) { + CORRADE_ASSERT(isOpened(), "Trade::AbstractImporter::meshLevelCount(): no file opened", {}); + CORRADE_ASSERT(id < doMeshCount(), "Trade::AbstractImporter::meshLevelCount(): index" << id << "out of range for" << doMeshCount() << "entries", {}); + const UnsignedInt out = doMeshLevelCount(id); + CORRADE_ASSERT(out, "Trade::AbstractImporter::meshLevelCount(): implementation reported zero levels", {}); + return out; +} + +UnsignedInt AbstractImporter::doMeshLevelCount(UnsignedInt) { return 1; } + Int AbstractImporter::meshForName(const std::string& name) { CORRADE_ASSERT(isOpened(), "Trade::AbstractImporter::meshForName(): no file opened", {}); return doMeshForName(name); @@ -477,10 +487,21 @@ std::string AbstractImporter::meshName(const UnsignedInt id) { std::string AbstractImporter::doMeshName(UnsignedInt) { return {}; } -Containers::Optional AbstractImporter::mesh(const UnsignedInt id) { +Containers::Optional AbstractImporter::mesh(const UnsignedInt id, const UnsignedInt level) { 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); + #ifndef CORRADE_NO_ASSERT + /* Check for the range only if requested level is nonzero, as + meshLevelCount() is expected to return >= 1. This is done to prevent + random assertions and messages from a doMeshLevelCount() to be printed + (which are unlikely, but let's be consistent with what image*D() does). */ + if(level) { + const UnsignedInt levelCount = doMeshLevelCount(id); + CORRADE_ASSERT(levelCount, "Trade::AbstractImporter::mesh(): implementation reported zero levels", {}); + CORRADE_ASSERT(level < levelCount, "Trade::AbstractImporter::mesh(): level" << level << "out of range for" << levelCount << "entries", {}); + } + #endif + Containers::Optional mesh = doMesh(id, level); CORRADE_ASSERT(!mesh || ( (!mesh->_indexData.deleter() || mesh->_indexData.deleter() == Implementation::nonOwnedArrayDeleter || mesh->_indexData.deleter() == ArrayAllocator::deleter) && (!mesh->_vertexData.deleter() || mesh->_vertexData.deleter() == Implementation::nonOwnedArrayDeleter || mesh->_vertexData.deleter() == ArrayAllocator::deleter) && @@ -489,15 +510,15 @@ Containers::Optional AbstractImporter::mesh(const UnsignedInt id) { return mesh; } -Containers::Optional AbstractImporter::doMesh(UnsignedInt) { +Containers::Optional AbstractImporter::doMesh(UnsignedInt, UnsignedInt) { CORRADE_ASSERT(false, "Trade::AbstractImporter::mesh(): not implemented", {}); } -Containers::Optional AbstractImporter::mesh(const std::string& name) { +Containers::Optional AbstractImporter::mesh(const std::string& name, const UnsignedInt level) { CORRADE_ASSERT(isOpened(), "Trade::AbstractImporter::mesh(): no file opened", {}); const Int id = doMeshForName(name); if(id == -1) return {}; - return mesh(id); /* not doMesh(), so we get the checks also */ + return mesh(id, level); /* not doMesh(), so we get the checks also */ } MeshAttribute AbstractImporter::meshAttributeForName(const std::string& name) { @@ -602,7 +623,7 @@ Containers::Optional AbstractImporter::mesh3D(const UnsignedInt id) } Containers::Optional AbstractImporter::doMesh3D(const UnsignedInt id) { - Containers::Optional out = doMesh(id); + Containers::Optional out = doMesh(id, 0); if(out) return MeshData3D{*out}; return Containers::NullOpt; } diff --git a/src/Magnum/Trade/AbstractImporter.h b/src/Magnum/Trade/AbstractImporter.h index 58ccdb742..8e0fe8881 100644 --- a/src/Magnum/Trade/AbstractImporter.h +++ b/src/Magnum/Trade/AbstractImporter.h @@ -245,9 +245,9 @@ checked by the implementation: there is any file opened. - All `do*()` implementations taking data ID as parameter are called only if the ID is from valid range. -- For `doImage*()` and @p level parameter being nonzero, implementations are - called only if it is from valid range. Level zero is always expected to be - present and thus no check is done in that case. +- For @ref doMesh() and `doImage*()` and @p level parameter being nonzero, + implementations are called only if it is from valid range. Level zero is + always expected to be present and thus no check is done in that case. @m_class{m-block m-warning} @@ -760,16 +760,27 @@ class MAGNUM_TRADE_EXPORT AbstractImporter: public PluginManager::AbstractManagi * @m_since_latest * * Expects that a file is opened. + * @see @ref meshLevelCount() */ UnsignedInt meshCount() const; + /** + * @brief Mesh level count + * @param id Mesh ID, from range [0, @ref meshCount()). + * @m_since_latest + * + * Always returns at least one level, import failures are deferred to + * @ref mesh(). Expects that a file is opened. + */ + UnsignedInt meshLevelCount(UnsignedInt id); + /** * @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(), @ref mesh(const std::string&) + * @see @ref meshName(), @ref mesh(const std::string&, UnsignedInt) */ Int meshForName(const std::string& name); @@ -786,24 +797,30 @@ class MAGNUM_TRADE_EXPORT AbstractImporter: public PluginManager::AbstractManagi /** * @brief Mesh * @param id Mesh ID, from range [0, @ref meshCount()). + * @param level Mesh level, from range [0, @ref meshLevelCount()) * @m_since_latest * * Returns given mesh or @ref Containers::NullOpt if importing failed. - * Expects that a file is opened. - * @see @ref mesh(const std::string&) + * The @p level parameter allows access to additional data and is + * largely left as importer-specific --- for example allowing access to + * per-instance, per-face or per-edge data. Expects that a file is + * opened. + * @see @ref mesh(const std::string&, UnsignedInt), + * @ref MeshPrimitive::Instances, @ref MeshPrimitive::Faces, + * @ref MeshPrimitive::Edges */ - Containers::Optional mesh(UnsignedInt id); + Containers::Optional mesh(UnsignedInt id, UnsignedInt level = 0); /** * @brief Mesh for given name * @m_since_latest * * A convenience API combining @ref meshForName() and - * @ref mesh(UnsignedInt). Returns @ref Containers::NullOpt either if - * @ref meshForName() returns @cpp -1 @ce or if importing fails. - * Expects that a file is opened. + * @ref mesh(UnsignedInt, UnsignedInt). Returns + * @ref Containers::NullOpt either if @ref meshForName() returns + * @cpp -1 @ce or if importing fails. Expects that a file is opened. */ - Containers::Optional mesh(const std::string& name); + Containers::Optional mesh(const std::string& name, UnsignedInt level = 0); /** * @brief Mesh attribute for given name @@ -1403,6 +1420,20 @@ class MAGNUM_TRADE_EXPORT AbstractImporter: public PluginManager::AbstractManagi */ virtual UnsignedInt doMeshCount() const; + /** + * @brief Implementation for @ref meshLevelCount() + * @m_since_latest + * + * Default implementation returns @cpp 1 @ce. Similarly to all other + * `*Count()` functions, this function isn't expected to fail --- if an + * import error occus, this function should return @cpp 1 @ce and the + * error state should be returned from @ref mesh() instead. + * + * Deliberately not @cpp const @ce to allow plugins cache decoded + * data. + */ + virtual UnsignedInt doMeshLevelCount(UnsignedInt id); + /** * @brief Implementation for @ref meshForName() * @m_since_latest @@ -1423,7 +1454,7 @@ class MAGNUM_TRADE_EXPORT AbstractImporter: public PluginManager::AbstractManagi * @brief Implementation for @ref mesh() * @m_since_latest */ - virtual Containers::Optional doMesh(UnsignedInt id); + virtual Containers::Optional doMesh(UnsignedInt id, UnsignedInt level); /** * @brief Implementation for @ref meshAttributeForName() diff --git a/src/Magnum/Trade/Test/AbstractImporterTest.cpp b/src/Magnum/Trade/Test/AbstractImporterTest.cpp index ed02d1a9e..7c948f2bf 100644 --- a/src/Magnum/Trade/Test/AbstractImporterTest.cpp +++ b/src/Magnum/Trade/Test/AbstractImporterTest.cpp @@ -171,6 +171,10 @@ struct AbstractImporterTest: TestSuite::Tester { #endif void meshCountNotImplemented(); void meshCountNoFile(); + void meshLevelCountNotImplemented(); + void meshLevelCountNoFile(); + void meshLevelCountOutOfRange(); + void meshLevelCountZero(); void meshForNameNotImplemented(); void meshForNameNoFile(); void meshNameNotImplemented(); @@ -179,6 +183,7 @@ struct AbstractImporterTest: TestSuite::Tester { void meshNotImplemented(); void meshNoFile(); void meshOutOfRange(); + void meshLevelOutOfRange(); void meshNonOwningDeleters(); void meshGrowableDeleters(); void meshCustomIndexDataDeleter(); @@ -420,6 +425,10 @@ AbstractImporterTest::AbstractImporterTest() { #endif &AbstractImporterTest::meshCountNotImplemented, &AbstractImporterTest::meshCountNoFile, + &AbstractImporterTest::meshLevelCountNotImplemented, + &AbstractImporterTest::meshLevelCountNoFile, + &AbstractImporterTest::meshLevelCountOutOfRange, + &AbstractImporterTest::meshLevelCountZero, &AbstractImporterTest::meshForNameNotImplemented, &AbstractImporterTest::meshForNameNoFile, &AbstractImporterTest::meshNameNotImplemented, @@ -428,6 +437,7 @@ AbstractImporterTest::AbstractImporterTest() { &AbstractImporterTest::meshNotImplemented, &AbstractImporterTest::meshNoFile, &AbstractImporterTest::meshOutOfRange, + &AbstractImporterTest::meshLevelOutOfRange, &AbstractImporterTest::meshNonOwningDeleters, &AbstractImporterTest::meshGrowableDeleters, &AbstractImporterTest::meshCustomIndexDataDeleter, @@ -2239,6 +2249,10 @@ void AbstractImporterTest::mesh() { void doClose() override {} UnsignedInt doMeshCount() const override { return 8; } + UnsignedInt doMeshLevelCount(UnsignedInt id) override { + if(id == 7) return 3; + else return {}; + } Int doMeshForName(const std::string& name) override { if(name == "eighth") return 7; else return -1; @@ -2247,10 +2261,10 @@ void AbstractImporterTest::mesh() { if(id == 7) return "eighth"; else return {}; } - Containers::Optional doMesh(UnsignedInt id) override { + Containers::Optional doMesh(UnsignedInt id, UnsignedInt level) 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}; + if(id == 7 && level == 2) return MeshData{MeshPrimitive::Points, nullptr, {MeshAttributeData{MeshAttribute::Position, VertexFormat::Vector3, nullptr}}, &state}; else return {}; } } importer; @@ -2260,11 +2274,11 @@ void AbstractImporterTest::mesh() { CORRADE_COMPARE(importer.meshName(7), "eighth"); { - auto data = importer.mesh(7); + auto data = importer.mesh(7, 2); CORRADE_VERIFY(data); CORRADE_COMPARE(data->importerState(), &state); } { - auto data = importer.mesh("eighth"); + auto data = importer.mesh("eighth", 2); CORRADE_VERIFY(data); CORRADE_COMPARE(data->importerState(), &state); } { @@ -2289,8 +2303,8 @@ void AbstractImporterTest::meshDeprecatedFallback() { if(id == 7) return "eighth"; else return {}; } - Containers::Optional doMesh(UnsignedInt id) override { - if(id == 7) return MeshData{MeshPrimitive::Points, nullptr, {MeshAttributeData{MeshAttribute::Position, VertexFormat::Vector3, nullptr}}, &state}; + Containers::Optional doMesh(UnsignedInt id, UnsignedInt level) override { + if(id == 7 && level == 0) return MeshData{MeshPrimitive::Points, nullptr, {MeshAttributeData{MeshAttribute::Position, VertexFormat::Vector3, nullptr}}, &state}; else return {}; } } importer; @@ -2336,6 +2350,70 @@ void AbstractImporterTest::meshCountNoFile() { CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::meshCount(): no file opened\n"); } +void AbstractImporterTest::meshLevelCountNotImplemented() { + 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.meshLevelCount(7), 1); +} + +void AbstractImporterTest::meshLevelCountNoFile() { + struct: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return false; } + void doClose() override {} + } importer; + + std::ostringstream out; + Error redirectError{&out}; + importer.meshLevelCount(7); + CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::meshLevelCount(): no file opened\n"); +} + +void AbstractImporterTest::meshLevelCountOutOfRange() { + 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.meshLevelCount(8); + CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::meshLevelCount(): index 8 out of range for 8 entries\n"); +} + +void AbstractImporterTest::meshLevelCountZero() { + 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&) override { return 0; } + UnsignedInt doMeshLevelCount(UnsignedInt) override { return 0; } + } importer; + + std::ostringstream out; + Error redirectError{&out}; + importer.meshLevelCount(7); + /* This should print a similar message instead of a confusing + "level 1 out of range for 0 entries" */ + importer.mesh(7, 1); + importer.mesh("", 1); + CORRADE_COMPARE(out.str(), + "Trade::AbstractImporter::meshLevelCount(): implementation reported zero levels\n" + "Trade::AbstractImporter::mesh(): implementation reported zero levels\n" + "Trade::AbstractImporter::mesh(): implementation reported zero levels\n"); +} + void AbstractImporterTest::meshForNameNotImplemented() { struct: AbstractImporter { ImporterFeatures doFeatures() const override { return {}; } @@ -2451,6 +2529,26 @@ void AbstractImporterTest::meshOutOfRange() { CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::mesh(): index 8 out of range for 8 entries\n"); } +void AbstractImporterTest::meshLevelOutOfRange() { + 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&) override { return 0; } + UnsignedInt doMeshLevelCount(UnsignedInt) override { return 3; } + } importer; + + std::ostringstream out; + Error redirectError{&out}; + importer.mesh(7, 3); + importer.mesh("", 3); + CORRADE_COMPARE(out.str(), + "Trade::AbstractImporter::mesh(): level 3 out of range for 3 entries\n" + "Trade::AbstractImporter::mesh(): level 3 out of range for 3 entries\n"); +} + void AbstractImporterTest::meshNonOwningDeleters() { struct: AbstractImporter { ImporterFeatures doFeatures() const override { return {}; } @@ -2458,7 +2556,7 @@ void AbstractImporterTest::meshNonOwningDeleters() { void doClose() override {} UnsignedInt doMeshCount() const override { return 1; } - Containers::Optional doMesh(UnsignedInt) override { + Containers::Optional doMesh(UnsignedInt, UnsignedInt) override { return MeshData{MeshPrimitive::Triangles, Containers::Array{indexData, 1, Implementation::nonOwnedArrayDeleter}, MeshIndexData{MeshIndexType::UnsignedByte, indexData}, Containers::Array{nullptr, 0, Implementation::nonOwnedArrayDeleter}, @@ -2483,7 +2581,7 @@ void AbstractImporterTest::meshGrowableDeleters() { void doClose() override {} UnsignedInt doMeshCount() const override { return 1; } - Containers::Optional doMesh(UnsignedInt) override { + Containers::Optional doMesh(UnsignedInt, UnsignedInt) override { Containers::Array indexData; Containers::arrayAppend(indexData, '\xab'); Containers::Array vertexData; @@ -2511,7 +2609,7 @@ void AbstractImporterTest::meshCustomIndexDataDeleter() { UnsignedInt doMeshCount() const override { return 1; } Int doMeshForName(const std::string&) override { return 0; } - Containers::Optional doMesh(UnsignedInt) override { + Containers::Optional doMesh(UnsignedInt, UnsignedInt) override { return MeshData{MeshPrimitive::Triangles, Containers::Array{data, 1, [](char*, std::size_t) {}}, MeshIndexData{MeshIndexType::UnsignedByte, data}}; } @@ -2536,7 +2634,7 @@ void AbstractImporterTest::meshCustomVertexDataDeleter() { UnsignedInt doMeshCount() const override { return 1; } Int doMeshForName(const std::string&) override { return 0; } - Containers::Optional doMesh(UnsignedInt) override { + Containers::Optional doMesh(UnsignedInt, UnsignedInt) override { return MeshData{MeshPrimitive::Triangles, Containers::Array{nullptr, 0, [](char*, std::size_t) {}}, {MeshAttributeData{MeshAttribute::Position, VertexFormat::Vector3, nullptr}}}; } } importer; @@ -2559,7 +2657,7 @@ void AbstractImporterTest::meshCustomAttributesDeleter() { UnsignedInt doMeshCount() const override { return 1; } Int doMeshForName(const std::string&) override { return 0; } - Containers::Optional doMesh(UnsignedInt) override { + Containers::Optional doMesh(UnsignedInt, UnsignedInt) override { return MeshData{MeshPrimitive::Triangles, nullptr, Containers::Array{&positions, 1, [](MeshAttributeData*, std::size_t) {}}}; }