From cc29cbcc7393cdc08945449a63f7421f02930171 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Fri, 9 Sep 2022 00:32:56 +0200 Subject: [PATCH] {image,scene}converter: extract and test --info printing code. It was quite a pile, and all of it was written just once, relying only on hopefully-available model files that would hopefully touch most code paths. Which means, extremely annoying to make changes in. I extracted the code to a header that can be tested with a mocked-up importer and without having to execute the utility itself, deduplicated the image info printing code, fixed various inconsistencies (such as data/field flags sometimes denoted with superfluous "flags:" and sometimes not) and TODOs (such as 2D/3D skins, where there was no format whatsoever that would have 2D skin support, so the code couldn't get written). Now it's finally possible to easily add the remaining missing features, such as printing camera info. --- src/Magnum/SceneTools/CMakeLists.txt | 3 +- .../Implementation/sceneConverterUtilities.h | 966 ++++++++++++++ src/Magnum/SceneTools/Test/CMakeLists.txt | 26 + .../SceneTools/Test/SceneConverterTest.cpp | 1131 +++++++++++++++++ .../info-animations.txt | 12 + .../SceneConverterTestFiles/info-images.txt | 3 + .../SceneConverterTestFiles/info-lights.txt | 8 + .../info-materials.txt | 28 + .../info-meshes-bounds.txt | 21 + .../SceneConverterTestFiles/info-meshes.txt | 16 + .../SceneConverterTestFiles/info-objects.txt | 5 + .../info-references.txt | 98 ++ .../info-scenes-objects.txt | 25 + .../SceneConverterTestFiles/info-scenes.txt | 11 + .../SceneConverterTestFiles/info-skins.txt | 10 + .../SceneConverterTestFiles/info-textures.txt | 8 + src/Magnum/SceneTools/Test/configure.h.cmake | 26 + src/Magnum/SceneTools/sceneconverter.cpp | 941 +------------- .../Trade/Implementation/converterUtilities.h | 81 +- src/Magnum/Trade/Test/CMakeLists.txt | 7 + src/Magnum/Trade/Test/ImageConverterTest.cpp | 192 +++ .../Test/ImageConverterTestFiles/info.txt | 23 + src/Magnum/Trade/imageconverter.cpp | 42 +- 23 files changed, 2699 insertions(+), 984 deletions(-) create mode 100644 src/Magnum/SceneTools/Implementation/sceneConverterUtilities.h create mode 100644 src/Magnum/SceneTools/Test/SceneConverterTest.cpp create mode 100644 src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-animations.txt create mode 100644 src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-images.txt create mode 100644 src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-lights.txt create mode 100644 src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-materials.txt create mode 100644 src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-meshes-bounds.txt create mode 100644 src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-meshes.txt create mode 100644 src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-objects.txt create mode 100644 src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-references.txt create mode 100644 src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-scenes-objects.txt create mode 100644 src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-scenes.txt create mode 100644 src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-skins.txt create mode 100644 src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-textures.txt create mode 100644 src/Magnum/SceneTools/Test/configure.h.cmake create mode 100644 src/Magnum/Trade/Test/ImageConverterTest.cpp create mode 100644 src/Magnum/Trade/Test/ImageConverterTestFiles/info.txt diff --git a/src/Magnum/SceneTools/CMakeLists.txt b/src/Magnum/SceneTools/CMakeLists.txt index 9cce7c8bd..ab7e8fcc2 100644 --- a/src/Magnum/SceneTools/CMakeLists.txt +++ b/src/Magnum/SceneTools/CMakeLists.txt @@ -44,7 +44,8 @@ set(MagnumSceneTools_HEADERS set(MagnumSceneTools_PRIVATE_HEADERS Implementation/combine.h - Implementation/convertToSingleFunctionObjects.h) + Implementation/convertToSingleFunctionObjects.h + Implementation/sceneConverterUtilities.h) ## Objects shared between main and test library #add_library(MagnumSceneToolsObjects OBJECT diff --git a/src/Magnum/SceneTools/Implementation/sceneConverterUtilities.h b/src/Magnum/SceneTools/Implementation/sceneConverterUtilities.h new file mode 100644 index 000000000..6c41538d2 --- /dev/null +++ b/src/Magnum/SceneTools/Implementation/sceneConverterUtilities.h @@ -0,0 +1,966 @@ +#ifndef Magnum_SceneTools_Implementation_sceneConverterUtilities_h +#define Magnum_SceneTools_Implementation_sceneConverterUtilities_h +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, + 2020, 2021, 2022 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 /* std::isupper() */ +#include /* sceneFieldNames */ +#include +#include +#include + +#include "Magnum/Math/FunctionsBatch.h" +#include "Magnum/Trade/AnimationData.h" +#include "Magnum/Trade/LightData.h" +#include "Magnum/Trade/MaterialData.h" +#include "Magnum/Trade/MeshData.h" +#include "Magnum/Trade/SceneData.h" +#include "Magnum/Trade/SkinData.h" +#include "Magnum/Trade/TextureData.h" + +#include "Magnum/Trade/Implementation/converterUtilities.h" + +namespace Magnum { namespace SceneTools { namespace Implementation { + +using namespace Containers::Literals; + +/* Used only in executables where we don't want it to be exported -- in + particular magnum-sceneconverter and its tests */ +namespace { + +/** @todo const Array& doesn't work, minmax() would fail to match */ +template Containers::String calculateBounds(Containers::Array&& attribute) { + /** @todo clean up when Debug::toString() exists */ + std::ostringstream out; + Debug{&out, Debug::Flag::NoNewlineAtTheEnd} << Debug::packed << Math::minmax(attribute); + return out.str(); +} + +/* Named attribute index from a global index */ +/** @todo some helper for this directly on the MeshData class? */ +UnsignedInt namedAttributeId(const Trade::MeshData& mesh, UnsignedInt id) { + const Trade::MeshAttribute name = mesh.attributeName(id); + for(UnsignedInt i = 0; i != mesh.attributeCount(name); ++i) + if(mesh.attributeId(name, i) == id) return i; + CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ +} + +bool printInfo(const Debug::Flags useColor, const bool useColor24, const Utility::Arguments& args, Trade::AbstractImporter& importer, std::chrono::high_resolution_clock::duration& importTime) { + struct AnimationInfo { + UnsignedInt animation; + Trade::AnimationData data{{}, {}}; + Containers::String name; + }; + + struct SkinInfo { + bool twoDimensions; + UnsignedInt skin; + UnsignedInt jointCount; + Containers::String name; + }; + + struct LightInfo { + UnsignedInt light; + Trade::LightData data{{}, {}, {}}; + Containers::String name; + }; + + struct MaterialInfo { + UnsignedInt material; + Trade::MaterialData data{{}, {}}; + Containers::String name; + }; + + struct TextureInfo { + UnsignedInt texture; + Trade::TextureData data{{}, {}, {}, {}, {}, {}}; + Containers::String name; + }; + + struct MeshAttributeInfo { + std::size_t offset; + Int stride; + UnsignedInt arraySize; + Trade::MeshAttribute name; + Containers::String customName; + VertexFormat format; + Containers::String bounds; + }; + + struct MeshInfo { + UnsignedInt mesh, level; + MeshPrimitive primitive; + UnsignedInt indexCount, vertexCount; + std::size_t indexOffset; + Int indexStride; + Containers::String indexBounds; + MeshIndexType indexType; + Containers::Array attributes; + std::size_t indexDataSize, vertexDataSize; + Trade::DataFlags indexDataFlags, vertexDataFlags; + Containers::String name; + }; + + struct SceneFieldInfo { + Trade::SceneField name; + Trade::SceneFieldFlags flags; + Trade::SceneFieldType type; + UnsignedInt arraySize; + std::size_t size; + }; + + struct SceneInfo { + UnsignedInt scene; + Trade::SceneMappingType mappingType; + UnsignedLong mappingBound; + Containers::Array fields; + std::size_t dataSize; + Trade::DataFlags dataFlags; + Containers::String name; + }; + + struct ObjectInfo { + UnsignedLong object; + /* A bitfield, assuming no more than 32 scenes */ + /** @todo might be too little? */ + UnsignedInt scenes; + Containers::Array> fields; + Containers::String name; + }; + + /* Parse everything first to avoid errors interleaved with output */ + bool error = false; + + /* Object properties */ + Containers::Array objectInfos; + if(args.isSet("info") || args.isSet("info-objects")) { + objectInfos = Containers::Array{std::size_t(importer.objectCount())}; + + for(UnsignedLong i = 0; i != importer.objectCount(); ++i) { + objectInfos[i].object = i; + objectInfos[i].name = importer.objectName(i); + } + } + + /* Scene properties, together with counting how much is each mesh / light / + material / skin / object referenced (which gets used only if both + --info-scenes and --info-{lights,materials,skins,objects} is passed and + the file has at least one scene). Texture reference count is calculated + when parsing materials. */ + Containers::Array sceneInfos; + /* Only the very latest GCC seems to support enum classes as keys and I + can't be bothered to write a std::hash specialization, so just making + the key typeless */ + std::unordered_map sceneFieldNames; + Containers::Array materialReferenceCount; + Containers::Array lightReferenceCount; + Containers::Array meshReferenceCount; + Containers::Array skin2DReferenceCount; + Containers::Array skin3DReferenceCount; + if((args.isSet("info") || args.isSet("info-scenes")) && importer.sceneCount()) { + materialReferenceCount = Containers::Array{importer.materialCount()}; + lightReferenceCount = Containers::Array{importer.lightCount()}; + meshReferenceCount = Containers::Array{importer.meshCount()}; + skin2DReferenceCount = Containers::Array{importer.skin2DCount()}; + skin3DReferenceCount = Containers::Array{importer.skin3DCount()}; + + for(UnsignedInt i = 0; i != importer.sceneCount(); ++i) { + Containers::Optional scene = importer.scene(i); + if(!scene) { + error = true; + continue; + } + + SceneInfo info{}; + info.scene = i; + info.mappingType = scene->mappingType(); + info.mappingBound = scene->mappingBound(); + info.dataSize = scene->data().size(); + info.dataFlags = scene->dataFlags(); + info.name = importer.sceneName(i); + for(UnsignedInt j = 0; j != scene->fieldCount(); ++j) { + const Trade::SceneField name = scene->fieldName(j); + + if(name == Trade::SceneField::Mesh) for(const Containers::Pair>& meshMaterial: scene->meshesMaterialsAsArray()) { + if(meshMaterial.second().first() < meshReferenceCount.size()) + ++meshReferenceCount[meshMaterial.second().first()]; + if(UnsignedInt(meshMaterial.second().second()) < materialReferenceCount.size()) + ++materialReferenceCount[meshMaterial.second().second()]; + } + + if(name == Trade::SceneField::Skin) for(const Containers::Pair skin: scene->skinsAsArray()) { + if(scene->is2D() && skin.second() < skin2DReferenceCount.size()) + ++skin2DReferenceCount[skin.second()]; + if(scene->is3D() && skin.second() < skin3DReferenceCount.size()) + ++skin3DReferenceCount[skin.second()]; + } + + if(name == Trade::SceneField::Light) for(const Containers::Pair& light: scene->lightsAsArray()) { + if(light.second() < lightReferenceCount.size()) + ++lightReferenceCount[light.second()]; + } + + arrayAppend(info.fields, InPlaceInit, + name, + scene->fieldFlags(j), + scene->fieldType(j), + scene->fieldArraySize(j), + scene->fieldSize(j)); + + /* If the field has a custom name, save it into the map. Not + putting it into the fields array as the map is reused by + object info as well. */ + if(Trade::isSceneFieldCustom(name)) { + /* Fetch the name only if it's not already there */ + const auto inserted = sceneFieldNames.emplace(sceneFieldCustom(name), Containers::String{}); + if(inserted.second) + inserted.first->second = importer.sceneFieldName(name); + } + + if(objectInfos) for(const UnsignedInt object: scene->mappingAsArray(j)) { + if(object >= objectInfos.size()) continue; + + objectInfos[object].object = object; + objectInfos[object].scenes |= 1 << i; + + /* If the field is repeated, increase the count instead */ + if(!objectInfos[object].fields.isEmpty() && objectInfos[object].fields.back().first() == name) + ++objectInfos[object].fields.back().second(); + else + arrayAppend(objectInfos[object].fields, InPlaceInit, name, 1u); + } + } + + arrayAppend(sceneInfos, std::move(info)); + } + } + + /* Animation properties */ + Containers::Array animationInfos; + if(args.isSet("info") || args.isSet("info-animations")) for(UnsignedInt i = 0; i != importer.animationCount(); ++i) { + Containers::Optional animation; + { + Trade::Implementation::Duration d{importTime}; + if(!(animation = importer.animation(i))) { + error = true; + continue; + } + } + + AnimationInfo info{}; + info.animation = i; + info.name = importer.animationName(i); + info.data = *std::move(animation); + + arrayAppend(animationInfos, std::move(info)); + } + + /* Skin properties */ + Containers::Array skinInfos; + if(args.isSet("info") || args.isSet("info-skins")) { + for(UnsignedInt i = 0; i != importer.skin2DCount(); ++i) { + Containers::Optional skin; + { + Trade::Implementation::Duration d{importTime}; + if(!(skin = importer.skin2D(i))) { + error = true; + continue; + } + } + + SkinInfo info{}; + info.twoDimensions = true; + info.skin = i; + info.name = importer.skin2DName(i); + info.jointCount = skin->joints().size(); + + arrayAppend(skinInfos, std::move(info)); + } + + for(UnsignedInt i = 0; i != importer.skin3DCount(); ++i) { + Containers::Optional skin; + { + Trade::Implementation::Duration d{importTime}; + if(!(skin = importer.skin3D(i))) { + error = true; + continue; + } + } + + SkinInfo info{}; + info.twoDimensions = false; + info.skin = i; + info.name = importer.skin3DName(i); + info.jointCount = skin->joints().size(); + + arrayAppend(skinInfos, std::move(info)); + } + } + + /* Light properties */ + Containers::Array lightInfos; + if(args.isSet("info") || args.isSet("info-lights")) for(UnsignedInt i = 0; i != importer.lightCount(); ++i) { + Containers::Optional light; + { + Trade::Implementation::Duration d{importTime}; + if(!(light = importer.light(i))) { + error = true; + continue; + } + } + + LightInfo info{}; + info.light = i; + info.name = importer.lightName(i); + info.data = *std::move(light); + + arrayAppend(lightInfos, std::move(info)); + } + + /* Material properties, together with how much is each texture shared + (which gets used only if both --info-materials and --info-textures is + passed and the file has at least one material). */ + Containers::Array materialInfos; + Containers::Array textureReferenceCount; + if((args.isSet("info") || args.isSet("info-materials")) && importer.materialCount()) { + textureReferenceCount = Containers::Array{importer.textureCount()}; + + for(UnsignedInt i = 0; i != importer.materialCount(); ++i) { + Containers::Optional material; + { + Trade::Implementation::Duration d{importTime}; + if(!(material = importer.material(i))) { + error = true; + continue; + } + } + + /* Calculate texture reference count for all properties that look + like a texture */ + for(UnsignedInt j = 0; j != material->layerCount(); ++j) { + for(UnsignedInt k = 0; k != material->attributeCount(j); ++k) { + if(material->attributeType(j, k) != Trade::MaterialAttributeType::UnsignedInt || !material->attributeName(j, k).hasSuffix("Texture"_s)) + continue; + + const UnsignedInt texture = material->attribute(j, k); + /** @todo once StridedBitArrayView2D exists, fix this to + count each material only once by having one bit for + every material and texture */ + if(texture < textureReferenceCount.size()) + ++textureReferenceCount[texture]; + } + } + + MaterialInfo info{}; + info.material = i; + info.name = importer.materialName(i); + info.data = *std::move(material); + + arrayAppend(materialInfos, std::move(info)); + } + } + + /* Mesh properties */ + Containers::Array meshInfos; + if(args.isSet("info") || args.isSet("info-meshes")) for(UnsignedInt i = 0; i != importer.meshCount(); ++i) { + for(UnsignedInt j = 0; j != importer.meshLevelCount(i); ++j) { + Containers::Optional mesh; + { + Trade::Implementation::Duration d{importTime}; + if(!(mesh = importer.mesh(i, j))) { + error = true; + continue; + } + } + + MeshInfo info{}; + info.mesh = i; + info.level = j; + info.primitive = mesh->primitive(); + info.vertexCount = mesh->vertexCount(); + info.vertexDataSize = mesh->vertexData().size(); + info.vertexDataFlags = mesh->vertexDataFlags(); + if(!j) { + info.name = importer.meshName(i); + } + if(mesh->isIndexed()) { + info.indexCount = mesh->indexCount(); + info.indexType = mesh->indexType(); + info.indexOffset = mesh->indexOffset(); + info.indexStride = mesh->indexStride(); + info.indexDataSize = mesh->indexData().size(); + info.indexDataFlags = mesh->indexDataFlags(); + if(args.isSet("bounds")) + info.indexBounds = calculateBounds(mesh->indicesAsArray()); + } + for(UnsignedInt k = 0; k != mesh->attributeCount(); ++k) { + const Trade::MeshAttribute name = mesh->attributeName(k); + + /* Calculate bounds, if requested, if this is not an + implementation-specific format and if it's not a custom + attribute */ + Containers::String bounds; + if(args.isSet("bounds") && !isVertexFormatImplementationSpecific(mesh->attributeFormat(k))) switch(name) { + case Trade::MeshAttribute::Position: + bounds = calculateBounds(mesh->positions3DAsArray(namedAttributeId(*mesh, k))); + break; + case Trade::MeshAttribute::Tangent: + bounds = calculateBounds(mesh->tangentsAsArray(namedAttributeId(*mesh, k))); + break; + case Trade::MeshAttribute::Bitangent: + bounds = calculateBounds(mesh->bitangentsAsArray(namedAttributeId(*mesh, k))); + break; + case Trade::MeshAttribute::Normal: + bounds = calculateBounds(mesh->normalsAsArray(namedAttributeId(*mesh, k))); + break; + case Trade::MeshAttribute::TextureCoordinates: + bounds = calculateBounds(mesh->textureCoordinates2DAsArray(namedAttributeId(*mesh, k))); + break; + case Trade::MeshAttribute::Color: + bounds = calculateBounds(mesh->colorsAsArray(namedAttributeId(*mesh, k))); + break; + case Trade::MeshAttribute::ObjectId: + bounds = calculateBounds(mesh->objectIdsAsArray(namedAttributeId(*mesh, k))); + break; + } + + arrayAppend(info.attributes, InPlaceInit, + mesh->attributeOffset(k), + mesh->attributeStride(k), + mesh->attributeArraySize(k), + name, Trade::isMeshAttributeCustom(name) ? + importer.meshAttributeName(name) : "", + mesh->attributeFormat(k), + bounds); + } + + arrayAppend(meshInfos, std::move(info)); + } + } + + /* Texture properties, together with how much is each image shared (which + gets used only if both --info-textures and --info-images is passed and + the file has at least one texture). */ + Containers::Array textureInfos; + Containers::Array image1DReferenceCount; + Containers::Array image2DReferenceCount; + Containers::Array image3DReferenceCount; + if((args.isSet("info") || args.isSet("info-textures")) && importer.textureCount()) { + image1DReferenceCount = Containers::Array{importer.image1DCount()}; + image2DReferenceCount = Containers::Array{importer.image2DCount()}; + image3DReferenceCount = Containers::Array{importer.image3DCount()}; + for(UnsignedInt i = 0; i != importer.textureCount(); ++i) { + Containers::Optional texture; + { + Trade::Implementation::Duration d{importTime}; + if(!(texture = importer.texture(i))) { + error = true; + continue; + } + } + + switch(texture->type()) { + case Trade::TextureType::Texture1D: + if(texture->image() < image1DReferenceCount.size()) + ++image1DReferenceCount[texture->image()]; + break; + case Trade::TextureType::Texture1DArray: + case Trade::TextureType::Texture2D: + if(texture->image() < image2DReferenceCount.size()) + ++image2DReferenceCount[texture->image()]; + break; + case Trade::TextureType::CubeMap: + case Trade::TextureType::CubeMapArray: + case Trade::TextureType::Texture2DArray: + case Trade::TextureType::Texture3D: + if(texture->image() < image3DReferenceCount.size()) + ++image3DReferenceCount[texture->image()]; + break; + } + + TextureInfo info{}; + info.texture = i; + info.name = importer.textureName(i); + info.data = *std::move(texture); + + arrayAppend(textureInfos, std::move(info)); + } + } + + Containers::Array imageInfos; + if(args.isSet("info") || args.isSet("info-images")) { + imageInfos = Trade::Implementation::imageInfo(importer, error, importTime); + } + + std::size_t totalSceneDataSize = 0; + for(const SceneInfo& info: sceneInfos) { + Debug d{useColor}; + d << Debug::boldColor(Debug::Color::Default) << "Scene" << info.scene << Debug::nospace << ":" << Debug::resetColor; + if(info.name) d << Debug::boldColor(Debug::Color::Yellow) << info.name << Debug::resetColor; + d << Debug::newline; + d << " Bound:" << info.mappingBound << "objects" + << Debug::color(Debug::Color::Blue) << "@" << Debug::packed + << Debug::color(Debug::Color::Cyan) << info.mappingType + << Debug::resetColor << "(" << Debug::nospace + << Utility::format("{:.1f}", info.dataSize/1024.0f) << "kB"; + if(info.dataFlags != (Trade::DataFlag::Owned|Trade::DataFlag::Mutable)) + d << Debug::nospace << "," << Debug::packed + << Debug::color(Debug::Color::Green) << info.dataFlags + << Debug::resetColor; + d << Debug::nospace << ")"; + + d << Debug::newline << " Fields:"; + for(const SceneFieldInfo& field: info.fields) { + d << Debug::newline << " " + << Debug::boldColor(Debug::Color::Default); + if(Trade::isSceneFieldCustom(field.name)) { + d << "Custom(" << Debug::nospace + << Trade::sceneFieldCustom(field.name) + << Debug::nospace << ":" << Debug::nospace + << Debug::color(Debug::Color::Yellow) + << sceneFieldNames[sceneFieldCustom(field.name)] + << Debug::nospace + << Debug::boldColor(Debug::Color::Default) << ")"; + } else d << Debug::packed << field.name; + + d << Debug::color(Debug::Color::Blue) << "@" << Debug::packed << Debug::color(Debug::Color::Cyan) << field.type; + if(field.arraySize) + d << Debug::nospace << Utility::format("[{}]", field.arraySize); + d << Debug::resetColor; + if(field.flags) d << Debug::nospace << "," + << Debug::packed << Debug::color(Debug::Color::Green) + << field.flags << Debug::resetColor; + d << Debug::nospace << "," << field.size << "entries"; + } + + totalSceneDataSize += info.dataSize; + } + if(!sceneInfos.isEmpty()) + Debug{} << "Total scene data size:" << Utility::format("{:.1f}", totalSceneDataSize/1024.0f) << "kB"; + + for(const ObjectInfo& info: objectInfos) { + /* Objects without a name and not referenced by any scenes are useless, + ignore */ + if(!info.name && !info.scenes) continue; + + Debug d{useColor}; + d << Debug::boldColor(Debug::Color::Default) << "Object" << info.object << Debug::resetColor; + + if(sceneInfos) { + const UnsignedInt count = Math::popcount(info.scenes); + if(!count) d << Debug::color(Debug::Color::Red); + d << "(referenced by" << count << "scenes)"; + if(!count) d << Debug::resetColor; + } + + d << Debug::boldColor(Debug::Color::Default) << Debug::nospace << ":" + << Debug::resetColor; + if(info.name) d << Debug::boldColor(Debug::Color::Yellow) + << info.name << Debug::resetColor; + if(info.scenes) { + d << Debug::newline << " Fields:"; + + for(std::size_t i = 0; i != info.fields.size(); ++i) { + if(i) d << Debug::nospace << ","; + const Containers::Pair nameCount = info.fields[i]; + d << Debug::color(Debug::Color::Cyan); + if(Trade::isSceneFieldCustom(nameCount.first())) { + d << "Custom(" << Debug::nospace + << Trade::sceneFieldCustom(nameCount.first()) + << Debug::nospace << ":" << Debug::nospace + << Debug::color(Debug::Color::Yellow) + << sceneFieldNames[sceneFieldCustom(nameCount.first())] + << Debug::nospace + << Debug::color(Debug::Color::Cyan) << ")"; + } else d << Debug::packed << nameCount.first(); + if(nameCount.second() != 1) + d << Debug::nospace << Utility::format("[{}]", nameCount.second()); + d << Debug::resetColor; + } + } + } + + std::size_t totalAnimationDataSize = 0; + for(const AnimationInfo& info: animationInfos) { + Debug d{useColor}; + d << Debug::boldColor(Debug::Color::Default) << "Animation" << info.animation << Debug::nospace << ":" << Debug::resetColor; + if(info.name) d << Debug::boldColor(Debug::Color::Yellow) << info.name << Debug::resetColor; + + d << Debug::newline << " Duration: {" << Debug::nospace + /** @todo have a nice packed printing for Range instead */ + << info.data.duration().min() << Debug::nospace << "," + << info.data.duration().max() << Debug::nospace << "} (" + << Debug::nospace << Utility::format("{:.1f}", info.data.data().size()/1024.0f) << "kB"; + if(info.data.dataFlags() != (Trade::DataFlag::Owned|Trade::DataFlag::Mutable)) + d << Debug::nospace << "," << Debug::packed + << Debug::color(Debug::Color::Green) + << info.data.dataFlags() << Debug::resetColor; + d << Debug::nospace << ")"; + + for(UnsignedInt i = 0; i != info.data.trackCount(); ++i) { + d << Debug::newline << " Track" << i << Debug::nospace << ":" + << Debug::packed << Debug::boldColor(Debug::Color::Default) + << info.data.trackTargetType(i) + << Debug::color(Debug::Color::Blue) << "@" + << Debug::packed << Debug::color(Debug::Color::Cyan) + << info.data.trackType(i) << Debug::resetColor; + if(info.data.trackType(i) != info.data.trackResultType(i)) + d << Debug::color(Debug::Color::Blue) << "->" + << Debug::packed << Debug::color(Debug::Color::Cyan) + << info.data.trackResultType(i) << Debug::resetColor; + d << Debug::nospace << "," << info.data.track(i).size() + << "keyframes"; + if(info.data.track(i).duration() != info.data.duration()) + d << Debug::newline << " Duration: {" << Debug::nospace + /** @todo have a nice packed printing for Range instead */ + << info.data.track(i).duration().min() << Debug::nospace + << "," << info.data.track(i).duration().max() + << Debug::nospace << "}"; + d << Debug::newline + << " Interpolation:" + << Debug::packed << Debug::color(info.data.track(i).interpolation() == Animation::Interpolation::Custom ? Debug::Color::Yellow : Debug::Color::Cyan) + << info.data.track(i).interpolation() << Debug::resetColor + << Debug::nospace << "," << Debug::packed + << Debug::color(Debug::Color::Cyan) + << info.data.track(i).before() << Debug::resetColor + << Debug::nospace << "," << Debug::packed + << Debug::color(Debug::Color::Cyan) + << info.data.track(i).after() << Debug::resetColor; + /** @todo might be useful to show bounds here as well, though not + so much for things like complex numbers or quats */ + } + + totalAnimationDataSize += info.data.data().size(); + } + if(!animationInfos.isEmpty()) + Debug{} << "Total animation data size:" << Utility::format("{:.1f}", totalAnimationDataSize/1024.0f) << "kB"; + + for(const SkinInfo& info: skinInfos) { + Debug d{useColor}; + d << Debug::boldColor(Debug::Color::Default) << (info.twoDimensions ? "2D skin" : "3D skin") << info.skin + << Debug::resetColor; + + /* Print reference count only if there actually are scenes and they + were parsed, otherwise this information is useless */ + if((info.twoDimensions && skin2DReferenceCount) || + (!info.twoDimensions && skin3DReferenceCount)) + { + const UnsignedInt count = info.twoDimensions ? skin2DReferenceCount[info.skin] : skin3DReferenceCount[info.skin]; + if(!count) d << Debug::color(Debug::Color::Red); + d << "(referenced by" << count << "objects)"; + if(!count) d << Debug::resetColor; + } + + d << Debug::boldColor(Debug::Color::Default) << Debug::nospace << ":" + << Debug::resetColor; + if(info.name) d << Debug::boldColor(Debug::Color::Yellow) + << info.name << Debug::resetColor; + + d << Debug::newline << " " << info.jointCount << "joints"; + } + + for(const LightInfo& info: lightInfos) { + Debug d{useColor}; + d << Debug::boldColor(Debug::Color::Default) << "Light" << info.light << Debug::resetColor; + + /* Print reference count only if there actually are scenes and they + were parsed, otherwise this information is useless */ + if(lightReferenceCount) { + const UnsignedInt count = lightReferenceCount[info.light]; + if(!count) d << Debug::color(Debug::Color::Red); + d << "(referenced by" << count << "objects)"; + if(!count) d << Debug::resetColor; + } + + d << Debug::boldColor(Debug::Color::Default) << Debug::nospace << ":" + << Debug::resetColor; + if(info.name) d << Debug::boldColor(Debug::Color::Yellow) + << info.name << Debug::resetColor; + + d << Debug::newline << " Type:" << Debug::packed + << Debug::color(Debug::Color::Cyan) + << info.data.type() << Debug::resetColor; + if(info.data.type() == Trade::LightData::Type::Spot) + d << Debug::nospace << "," << Debug::packed + << Deg(info.data.innerConeAngle()) << Debug::nospace + << "° -" << Debug::packed << Deg(info.data.outerConeAngle()) + << Debug::nospace << "°"; + d << Debug::newline << " Color:"; + if(useColor24) d << Debug::color + << Math::pack(info.data.color()); + d << Debug::packed << info.data.color(); + if(!Math::equal(info.data.intensity(), 1.0f)) + d << "*" << info.data.intensity(); + if(info.data.type() != Trade::LightData::Type::Ambient && + info.data.type() != Trade::LightData::Type::Directional) + d << Debug::newline << " Attenuation:" << Debug::packed + << info.data.attenuation(); + if(info.data.range() != Constants::inf()) + d << Debug::newline << " Range:" << Debug::packed + << info.data.range(); + } + + for(const MaterialInfo& info: materialInfos) { + Debug d{useColor}; + d << Debug::boldColor(Debug::Color::Default) << "Material" << info.material << Debug::resetColor; + + /* Print reference count only if there actually are scenes and they + were parsed, otherwise this information is useless */ + if(materialReferenceCount) { + const UnsignedInt count = materialReferenceCount[info.material]; + if(!count) d << Debug::color(Debug::Color::Red); + d << "(referenced by" << count << "objects)"; + if(!count) d << Debug::resetColor; + } + + d << Debug::boldColor(Debug::Color::Default) << Debug::nospace << ":" + << Debug::resetColor; + if(info.name) d << Debug::boldColor(Debug::Color::Yellow) << info.name << Debug::resetColor; + + d << Debug::newline << " Type:" << Debug::packed << Debug::color(Debug::Color::Cyan) << info.data.types() << Debug::resetColor; + + for(UnsignedInt i = 0; i != info.data.layerCount(); ++i) { + /* Print extra layers with extra indent */ + const char* indent; + if(info.data.layerCount() != 1 && i != 0) { + d << Debug::newline << " Layer" << i << Debug::nospace << ":"; + if(!info.data.layerName(i).isEmpty()) { + if(std::isupper(info.data.layerName(i)[0])) + d << Debug::boldColor(Debug::Color::Default); + else + d << Debug::color(Debug::Color::Yellow); + d << info.data.layerName(i) << Debug::resetColor; + } + indent = " "; + } else { + d << Debug::newline << " Base layer:"; + indent = " "; + } + + for(UnsignedInt j = 0; j != info.data.attributeCount(i); ++j) { + /* Ignore layer name (which is always first) unless it's in the + base material, in which case we print it as it wouldn't + otherwise be shown anywhere */ + if(i && !j && info.data.attributeName(i, j) == " LayerName") + continue; + + d << Debug::newline << indent; + if(std::isupper(info.data.attributeName(i, j)[0])) + d << Debug::boldColor(Debug::Color::Default); + else + d << Debug::color(Debug::Color::Yellow); + d << info.data.attributeName(i, j) << Debug::color(Debug::Color::Blue) << "@" << Debug::packed << Debug::color(Debug::Color::Cyan) + << info.data.attributeType(i, j) << Debug::resetColor << Debug::nospace + << ":"; + switch(info.data.attributeType(i, j)) { + #define _c(type) case Trade::MaterialAttributeType::type: \ + d << Debug::packed << info.data.attribute(i, j); \ + break; + /* LCOV_EXCL_START */ + _c(Float) + _c(Deg) + _c(Rad) + _c(UnsignedInt) + _c(Int) + _c(UnsignedLong) + _c(Long) + _c(Vector2) + _c(Vector2ui) + _c(Vector2i) + /* Vector3 handled below */ + _c(Vector3ui) + _c(Vector3i) + /* Vector4 handled below */ + _c(Vector4ui) + _c(Vector4i) + _c(Matrix2x2) + _c(Matrix2x3) + _c(Matrix2x4) + _c(Matrix3x2) + _c(Matrix3x3) + _c(Matrix3x4) + _c(Matrix4x2) + _c(Matrix4x3) + /* LCOV_EXCL_STOP */ + #undef _c + case Trade::MaterialAttributeType::Bool: + d << info.data.attribute(i, j); + break; + case Trade::MaterialAttributeType::Vector3: + /** @todo hasSuffix() might be more robust against + false positives, but KHR_materials_specular in glTF + uses ColorFactor :/ */ + if(useColor24 && info.data.attributeName(i, j).contains("Color"_s)) + d << Debug::color << Math::pack(info.data.attribute(i, j)); + d << Debug::packed << info.data.attribute(i, j); + break; + case Trade::MaterialAttributeType::Vector4: + /** @todo hasSuffix() might be more robust against + false positives, but KHR_materials_specular in glTF + uses ColorFactor :/ */ + if(useColor24 && info.data.attributeName(i, j).contains("Color"_s)) + d << Debug::color << Math::pack(info.data.attribute(i, j).rgb()); + d << Debug::packed << info.data.attribute(i, j); + break; + case Trade::MaterialAttributeType::Pointer: + d << info.data.attribute(i, j); + break; + case Trade::MaterialAttributeType::MutablePointer: + d << info.data.attribute(i, j); + break; + case Trade::MaterialAttributeType::String: + d << info.data.attribute(i, j); + break; + case Trade::MaterialAttributeType::TextureSwizzle: + d << Debug::packed << info.data.attribute(i, j); + break; + } + } + } + } + + std::size_t totalMeshDataSize = 0; + for(const MeshInfo& info: meshInfos) { + Debug d{useColor}; + if(info.level == 0) { + d << Debug::boldColor(Debug::Color::Default) << "Mesh" << info.mesh << Debug::resetColor; + + /* Print reference count only if there actually are scenes and they + were parsed, otherwise this information is useless */ + if(meshReferenceCount) { + const UnsignedInt count = meshReferenceCount[info.mesh]; + if(!count) d << Debug::color(Debug::Color::Red); + d << "(referenced by" << count << "objects)"; + if(!count) d << Debug::resetColor; + } + + d << Debug::boldColor(Debug::Color::Default) << Debug::nospace << ":" + << Debug::resetColor; + if(info.name) d << Debug::boldColor(Debug::Color::Yellow) << info.name << Debug::resetColor; + d << Debug::newline; + } + d << " Level" << info.level << Debug::nospace << ":" + << info.vertexCount << "vertices" << Debug::color(Debug::Color::Blue) << "@" << Debug::packed << Debug::color(Debug::Color::Cyan) << info.primitive << Debug::resetColor << "(" << Debug::nospace + << Utility::format("{:.1f}", info.vertexDataSize/1024.0f) + << "kB"; + if(info.vertexDataFlags != (Trade::DataFlag::Owned|Trade::DataFlag::Mutable)) + d << Debug::nospace << "," << Debug::packed + << Debug::color(Debug::Color::Green) + << info.vertexDataFlags << Debug::resetColor; + d << Debug::nospace << ")"; + + for(const MeshAttributeInfo& attribute: info.attributes) { + d << Debug::newline << " " + << Debug::boldColor(Debug::Color::Default); + if(Trade::isMeshAttributeCustom(attribute.name)) { + d << "Custom(" << Debug::nospace + << Trade::meshAttributeCustom(attribute.name) + << Debug::nospace << ":" << Debug::nospace + << Debug::color(Debug::Color::Yellow) + << attribute.customName << Debug::nospace + << Debug::boldColor(Debug::Color::Default) << ")"; + } else d << Debug::packed << attribute.name; + + d << Debug::color(Debug::Color::Blue) << "@" << Debug::packed << Debug::color(Debug::Color::Cyan) << attribute.format; + if(attribute.arraySize) + d << Debug::nospace << Utility::format("[{}]", attribute.arraySize); + d << Debug::resetColor; + d << Debug::nospace << ", offset" << attribute.offset; + d << Debug::nospace << ", stride" + << attribute.stride; + if(attribute.bounds) + d << Debug::newline << " Bounds:" << attribute.bounds; + } + + if(info.indexType != MeshIndexType{}) { + d << Debug::newline << " " << info.indexCount << "indices" << Debug::color(Debug::Color::Blue) << "@" + << Debug::packed << Debug::color(Debug::Color::Cyan) << info.indexType << Debug::resetColor << Debug::nospace << ", offset" << info.indexOffset << Debug::nospace << ", stride" << info.indexStride << "(" << Debug::nospace + << Utility::format("{:.1f}", info.indexDataSize/1024.0f) + << "kB"; + if(info.indexDataFlags != (Trade::DataFlag::Owned|Trade::DataFlag::Mutable)) + d << Debug::nospace << "," << Debug::packed + << Debug::color(Debug::Color::Green) << info.indexDataFlags << Debug::resetColor; + d << Debug::nospace << ")"; + if(info.indexBounds) + d << Debug::newline << " Bounds:" << info.indexBounds; + } + + totalMeshDataSize += info.vertexDataSize + info.indexDataSize; + } + if(!meshInfos.isEmpty()) + Debug{} << "Total mesh data size:" << Utility::format("{:.1f}", totalMeshDataSize/1024.0f) << "kB"; + + for(const TextureInfo& info: textureInfos) { + Debug d{useColor}; + d << Debug::boldColor(Debug::Color::Default) << "Texture" << info.texture << Debug::resetColor; + + /* Print reference count only if there actually are materials and they + were parsed, otherwise this information is useless */ + if(textureReferenceCount) { + const UnsignedInt count = textureReferenceCount[info.texture]; + if(!count) d << Debug::color(Debug::Color::Red); + d << "(referenced by" << count << "material attributes)"; + if(!count) d << Debug::resetColor; + } + + d << Debug::boldColor(Debug::Color::Default) << Debug::nospace << ":" + << Debug::resetColor; + if(info.name) d << Debug::boldColor(Debug::Color::Yellow) + << info.name << Debug::resetColor; + d << Debug::newline; + d << " Type:" + << Debug::packed + << Debug::color(Debug::Color::Cyan) << info.data.type() + << Debug::resetColor << Debug::nospace << ", image" + << info.data.image(); + d << Debug::newline << " Minification, mipmap and magnification:" + << Debug::packed << Debug::color(Debug::Color::Cyan) + << info.data.minificationFilter() << Debug::nospace << "," + << Debug::packed << Debug::color(Debug::Color::Cyan) + << info.data.mipmapFilter() << Debug::nospace << "," + << Debug::packed << Debug::color(Debug::Color::Cyan) + << info.data.magnificationFilter() << Debug::resetColor; + /** @todo show only the dimensions that matter for a particular texture + type */ + d << Debug::newline << " Wrapping:" << Debug::resetColor << "{" << Debug::nospace + << Debug::packed << Debug::color(Debug::Color::Cyan) + << info.data.wrapping()[0] << Debug::resetColor + << Debug::nospace << "," << Debug::packed + << Debug::color(Debug::Color::Cyan) << info.data.wrapping()[1] + << Debug::resetColor << Debug::nospace << "," << Debug::packed + << Debug::color(Debug::Color::Cyan) << info.data.wrapping()[1] + << Debug::resetColor << Debug::nospace << "}"; + } + + Trade::Implementation::printImageInfo(useColor, imageInfos, image1DReferenceCount, image2DReferenceCount, image3DReferenceCount); + + return error; +} + +} + +}}} + +#endif diff --git a/src/Magnum/SceneTools/Test/CMakeLists.txt b/src/Magnum/SceneTools/Test/CMakeLists.txt index ac1e1e4e8..e021cff00 100644 --- a/src/Magnum/SceneTools/Test/CMakeLists.txt +++ b/src/Magnum/SceneTools/Test/CMakeLists.txt @@ -27,7 +27,33 @@ # property that would have to be set on each target separately. set(CMAKE_FOLDER "Magnum/SceneTools/Test") +if(CORRADE_TARGET_EMSCRIPTEN OR CORRADE_TARGET_ANDROID) + set(SCENETOOLS_TEST_DIR ".") +else() + set(SCENETOOLS_TEST_DIR ${CMAKE_CURRENT_SOURCE_DIR}) +endif() + +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/configure.h.cmake + ${CMAKE_CURRENT_BINARY_DIR}/configure.h) + corrade_add_test(SceneToolsCombineTest CombineTest.cpp LIBRARIES MagnumTrade) corrade_add_test(SceneToolsConvertToSingleFun___Test ConvertToSingleFunctionObjectsTest.cpp LIBRARIES MagnumTrade) corrade_add_test(SceneToolsFlattenMeshHierarchyTest FlattenMeshHierarchyTest.cpp LIBRARIES MagnumSceneToolsTestLib) corrade_add_test(SceneToolsOrderClusterParentsTest OrderClusterParentsTest.cpp LIBRARIES MagnumSceneToolsTestLib) + +corrade_add_test(SceneToolsSceneConverterTest SceneConverterTest.cpp + LIBRARIES MagnumSceneTools + FILES + SceneConverterTestFiles/info-animations.txt + SceneConverterTestFiles/info-images.txt + SceneConverterTestFiles/info-lights.txt + SceneConverterTestFiles/info-materials.txt + SceneConverterTestFiles/info-meshes-bounds.txt + SceneConverterTestFiles/info-meshes.txt + SceneConverterTestFiles/info-objects.txt + SceneConverterTestFiles/info-references.txt + SceneConverterTestFiles/info-scenes-objects.txt + SceneConverterTestFiles/info-scenes.txt + SceneConverterTestFiles/info-skins.txt + SceneConverterTestFiles/info-textures.txt) +target_include_directories(SceneToolsSceneConverterTest PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) diff --git a/src/Magnum/SceneTools/Test/SceneConverterTest.cpp b/src/Magnum/SceneTools/Test/SceneConverterTest.cpp new file mode 100644 index 000000000..f6bc6c649 --- /dev/null +++ b/src/Magnum/SceneTools/Test/SceneConverterTest.cpp @@ -0,0 +1,1131 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, + 2020, 2021, 2022 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 +#include +#include + +#include "Magnum/Math/CubicHermite.h" +#include "Magnum/Math/Matrix3.h" +#include "Magnum/Math/Matrix4.h" + +#include "Magnum/SceneTools/Implementation/sceneConverterUtilities.h" + +#include "configure.h" + +namespace Magnum { namespace SceneTools { namespace Test { namespace { + +struct SceneConverterTest: TestSuite::Tester { + explicit SceneConverterTest(); + + void infoImplementationEmpty(); + void infoImplementationScenesObjects(); + void infoImplementationAnimations(); + void infoImplementationSkins(); + void infoImplementationLights(); + void infoImplementationMaterials(); + void infoImplementationMeshes(); + void infoImplementationMeshesBounds(); + void infoImplementationTextures(); + void infoImplementationImages(); + /* Image info further tested in ImageConverterTest */ + void infoImplementationReferenceCount(); + void infoImplementationError(); + + Utility::Arguments _infoArgs; +}; + +using namespace Math::Literals; + +const struct { + const char* name; + const char* arg; + const char* expected; + bool printVisualCheck; +} InfoImplementationScenesObjectsData[]{ + {"", "--info", "info-scenes-objects.txt", true}, + {"only scenes", "--info-scenes", "info-scenes.txt", false}, + {"only objects", "--info-objects", "info-objects.txt", false}, +}; + +const struct { + const char* name; + bool oneOrAll; + bool printVisualCheck; +} InfoImplementationOneOrAllData[]{ + {"", true, true}, + {"--info", false, false}, +}; + +SceneConverterTest::SceneConverterTest() { + addTests({&SceneConverterTest::infoImplementationEmpty}); + + addInstancedTests({&SceneConverterTest::infoImplementationScenesObjects}, + Containers::arraySize(InfoImplementationScenesObjectsData)); + + addInstancedTests({&SceneConverterTest::infoImplementationAnimations, + &SceneConverterTest::infoImplementationSkins, + &SceneConverterTest::infoImplementationLights, + &SceneConverterTest::infoImplementationMaterials, + &SceneConverterTest::infoImplementationMeshes}, + Containers::arraySize(InfoImplementationOneOrAllData)); + + addTests({&SceneConverterTest::infoImplementationMeshesBounds}); + + addInstancedTests({&SceneConverterTest::infoImplementationTextures, + &SceneConverterTest::infoImplementationImages}, + Containers::arraySize(InfoImplementationOneOrAllData)); + + addTests({&SceneConverterTest::infoImplementationReferenceCount, + &SceneConverterTest::infoImplementationError}); + + /* A subset of arguments needed by the info printing code */ + _infoArgs.addBooleanOption("info") + .addBooleanOption("info-scenes") + .addBooleanOption("info-objects") + .addBooleanOption("info-animations") + .addBooleanOption("info-skins") + .addBooleanOption("info-lights") + .addBooleanOption("info-materials") + .addBooleanOption("info-meshes") + .addBooleanOption("info-textures") + .addBooleanOption("info-images") + .addBooleanOption("bounds"); +} + +void SceneConverterTest::infoImplementationEmpty() { + struct Importer: Trade::AbstractImporter { + Trade::ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + } importer; + + const char* argv[]{"", "--info"}; + CORRADE_VERIFY(_infoArgs.tryParse(Containers::arraySize(argv), argv)); + + std::chrono::high_resolution_clock::duration time; + + std::ostringstream out; + Debug redirectOutput{&out}; + CORRADE_VERIFY(Implementation::printInfo(Debug::Flag::DisableColors, {}, _infoArgs, importer, time) == false); + CORRADE_COMPARE(out.str(), ""); +} + +void SceneConverterTest::infoImplementationScenesObjects() { + auto&& data = InfoImplementationScenesObjectsData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + struct Importer: Trade::AbstractImporter { + Trade::ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + /* First scene has 4, second 7, the last three are not in any scene and + thus not listed. Object 5 has no fields and thus not listed either. */ + UnsignedLong doObjectCount() const override { return 10; } + UnsignedInt doSceneCount() const override { return 2; } + Containers::String doSceneName(UnsignedInt id) override { + return id == 0 ? "A simple scene" : ""; + } + Containers::String doObjectName(UnsignedLong id) override { + if(id == 0) return "Parent-less mesh"; + if(id == 2) return "Two meshes, shared among two scenes"; + if(id == 4) return "Two custom arrays"; + if(id == 6) return "Only in the second scene, but no fields, thus same as unreferenced"; + if(id == 8) return "Not in any scene"; + return ""; + } + Containers::String doSceneFieldName(UnsignedInt name) override { + if(name == 1337) return "DirectionVector"; + return ""; + } + Containers::Optional doScene(UnsignedInt id) override { + /* Builtin fields, some duplicated, one marked as ordered */ + if(id == 0) { + Containers::ArrayView parentMapping; + Containers::ArrayView parents; + Containers::ArrayView meshMapping; + Containers::ArrayView meshes; + Containers::ArrayTuple data{ + {NoInit, 3, parentMapping}, + {ValueInit, 3, parents}, + {NoInit, 4, meshMapping}, + {ValueInit, 4, meshes}, + }; + Utility::copy({1, 3, 2}, parentMapping); + Utility::copy({2, 0, 2, 1}, meshMapping); + /* No need to fill the data, zero-init is fine */ + return Trade::SceneData{Trade::SceneMappingType::UnsignedInt, 4, std::move(data), { + Trade::SceneFieldData{Trade::SceneField::Parent, parentMapping, parents}, + Trade::SceneFieldData{Trade::SceneField::Mesh, meshMapping, meshes, Trade::SceneFieldFlag::OrderedMapping}, + }}; + } + + /* Two custom fields, one array. Stored as an external memory. */ + if(id == 1) { + return Trade::SceneData{Trade::SceneMappingType::UnsignedByte, 8, Trade::DataFlag::ExternallyOwned|Trade::DataFlag::Mutable, scene2Data, { + Trade::SceneFieldData{Trade::sceneFieldCustom(42), Containers::arrayView(scene2Data->customMapping), Containers::arrayView(scene2Data->custom)}, + Trade::SceneFieldData{Trade::sceneFieldCustom(1337), Trade::SceneMappingType::UnsignedByte, scene2Data->customArrayMapping, Trade::SceneFieldType::Short, scene2Data->customArray, 3}, + }}; + } + + CORRADE_INTERNAL_ASSERT_UNREACHABLE(); + } + + struct { + UnsignedByte customMapping[2]; + Double custom[2]; + UnsignedByte customArrayMapping[3]; + Vector3s customArray[3]; + } scene2Data[1]{{ + /* No need to fill the data, zero-init is fine */ + {7, 3}, {}, {2, 4, 4}, {} + }}; + } importer; + + const char* argv[]{"", data.arg}; + CORRADE_VERIFY(_infoArgs.tryParse(Containers::arraySize(argv), argv)); + + std::chrono::high_resolution_clock::duration time; + + /* Print to visually verify coloring */ + if(data.printVisualCheck) { + Debug{} << "======================== visual color verification start ======================="; + Implementation::printInfo(Debug::isTty() ? Debug::Flags{} : Debug::Flag::DisableColors, Debug::isTty(), _infoArgs, importer, time); + Debug{} << "======================== visual color verification end ========================="; + } + + std::ostringstream out; + Debug redirectOutput{&out}; + CORRADE_VERIFY(Implementation::printInfo(Debug::Flag::DisableColors, {}, _infoArgs, importer, time) == false); + CORRADE_COMPARE_AS(out.str(), + Utility::Path::join({SCENETOOLS_TEST_DIR, "SceneConverterTestFiles", data.expected}), + TestSuite::Compare::StringToFile); +} + +void SceneConverterTest::infoImplementationAnimations() { + auto&& data = InfoImplementationOneOrAllData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + struct Importer: Trade::AbstractImporter { + Trade::ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + UnsignedInt doAnimationCount() const override { return 2; } + Containers::String doAnimationName(UnsignedInt id) override { + return id == 1 ? "Custom track duration and interpolator function" : ""; + } + Containers::Optional doAnimation(UnsignedInt id) override { + /* First has two tracks with a shared time and implicit duration, + one with a different result type. */ + if(id == 0) { + Containers::ArrayView time; + Containers::ArrayView translation; + Containers::ArrayView rotation; + Containers::ArrayTuple data{ + {ValueInit, 3, time}, + {ValueInit, 3, translation}, + {ValueInit, 3, rotation} + }; + Utility::copy({0.5f, 1.0f, 1.25f}, time); + return Trade::AnimationData{std::move(data), { + /** @todo cleanup once AnimationTrackData has sane + constructors */ + Trade::AnimationTrackData{Trade::AnimationTrackTargetType::Translation2D, 17, Animation::TrackView{time, translation, Animation::Interpolation::Linear, Animation::Extrapolation::DefaultConstructed, Animation::Extrapolation::Constant}}, + Trade::AnimationTrackData{Trade::AnimationTrackTargetType::Rotation2D, 17, Animation::TrackView{time, rotation, Animation::Interpolation::Constant, Animation::Extrapolation::Extrapolated}}, + }}; + } + + /* Second has track duration different from animation duration and + a custom interpolator. Stored as an external memory. */ + if(id == 1) { + return Trade::AnimationData{Trade::DataFlag::ExternallyOwned, animation2Data, { + /** @todo cleanup once AnimationTrackData has sane + constructors */ + Trade::AnimationTrackData{Trade::AnimationTrackTargetType::Scaling3D, 666, Animation::TrackView{animation2Data->time, animation2Data->scaling, Math::lerp, Animation::Extrapolation::DefaultConstructed, Animation::Extrapolation::Constant}}, + }, {0.1f, 1.3f}}; + } + + CORRADE_INTERNAL_ASSERT_UNREACHABLE(); + } + + struct { + Float time[5]; + Vector3 scaling[5]; + } animation2Data[1]{{ + {0.75f, 0.75f, 1.0f, 1.0f, 1.25f}, + {} + }}; + } importer; + + const char* argv[]{"", data.oneOrAll ? "--info-animations" : "--info"}; + CORRADE_VERIFY(_infoArgs.tryParse(Containers::arraySize(argv), argv)); + + std::chrono::high_resolution_clock::duration time; + + /* Print to visually verify coloring */ + if(data.printVisualCheck) { + Debug{} << "======================== visual color verification start ======================="; + Implementation::printInfo(Debug::isTty() ? Debug::Flags{} : Debug::Flag::DisableColors, Debug::isTty(), _infoArgs, importer, time); + Debug{} << "======================== visual color verification end ========================="; + } + + std::ostringstream out; + Debug redirectOutput{&out}; + CORRADE_VERIFY(Implementation::printInfo(Debug::Flag::DisableColors, {}, _infoArgs, importer, time) == false); + CORRADE_COMPARE_AS(out.str(), + Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/info-animations.txt"), + TestSuite::Compare::StringToFile); +} + +void SceneConverterTest::infoImplementationSkins() { + auto&& data = InfoImplementationOneOrAllData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + struct Importer: Trade::AbstractImporter { + Trade::ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + UnsignedInt doSkin2DCount() const override { return 2; } + Containers::String doSkin2DName(UnsignedInt id) override { + return id == 1 ? "Second 2D skin, external data" : ""; + } + Containers::Optional doSkin2D(UnsignedInt id) override { + /* First a regular skin, second externally owned */ + if(id == 0) return Trade::SkinData2D{ + {3, 6, 7, 12, 22}, + {{}, {}, {}, {}, {}} + }; + + if(id == 1) return Trade::SkinData2D{Trade::DataFlag::ExternallyOwned, skin2JointData, Trade::DataFlag::ExternallyOwned, skin2MatrixData}; + + CORRADE_INTERNAL_ASSERT_UNREACHABLE(); + } + + UnsignedInt doSkin3DCount() const override { return 3; } + Containers::String doSkin3DName(UnsignedInt id) override { + return id == 0 ? "First 3D skin, external data" : ""; + } + Containers::Optional doSkin3D(UnsignedInt id) override { + /* Reverse order in 3D, plus one more to ensure the count isn't + mismatched between 2D and 3D */ + if(id == 0) return Trade::SkinData3D{Trade::DataFlag::ExternallyOwned, skin3JointData, Trade::DataFlag::ExternallyOwned, skin3MatrixData}; + + if(id == 1) return Trade::SkinData3D{ + {3, 22}, + {{}, {}} + }; + + if(id == 2) return Trade::SkinData3D{ + {3}, + {{}} + }; + + CORRADE_INTERNAL_ASSERT_UNREACHABLE(); + } + + UnsignedInt skin2JointData[15]; + Matrix3 skin2MatrixData[15]; + UnsignedInt skin3JointData[12]; + Matrix4 skin3MatrixData[12]; + } importer; + + const char* argv[]{"", data.oneOrAll ? "--info-skins" : "--info"}; + CORRADE_VERIFY(_infoArgs.tryParse(Containers::arraySize(argv), argv)); + + std::chrono::high_resolution_clock::duration time; + + /* Print to visually verify coloring */ + if(data.printVisualCheck) { + Debug{} << "======================== visual color verification start ======================="; + Implementation::printInfo(Debug::isTty() ? Debug::Flags{} : Debug::Flag::DisableColors, Debug::isTty(), _infoArgs, importer, time); + Debug{} << "======================== visual color verification end ========================="; + } + + std::ostringstream out; + Debug redirectOutput{&out}; + CORRADE_VERIFY(Implementation::printInfo(Debug::Flag::DisableColors, {}, _infoArgs, importer, time) == false); + CORRADE_COMPARE_AS(out.str(), + Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/info-skins.txt"), + TestSuite::Compare::StringToFile); +} + +void SceneConverterTest::infoImplementationLights() { + auto&& data = InfoImplementationOneOrAllData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + struct Importer: Trade::AbstractImporter { + Trade::ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + UnsignedInt doLightCount() const override { return 2; } + Containers::String doLightName(UnsignedInt id) override { + return id == 1 ? "Directional light with always-implicit attenuation and range" : ""; + } + Containers::Optional doLight(UnsignedInt id) override { + /* First a blue spot light */ + if(id == 0) return Trade::LightData{ + Trade::LightData::Type::Spot, + 0x3457ff_rgbf, + 15.0f, + {1.2f, 0.3f, 0.04f}, + 100.0f, + 55.0_degf, + 85.0_degf + }; + + /* Second a yellow directional light with infinite range */ + if(id == 1) return Trade::LightData{ + Trade::LightData::Type::Directional, + 0xff5734_rgbf, + 5.0f + }; + + CORRADE_INTERNAL_ASSERT_UNREACHABLE(); + } + } importer; + + const char* argv[]{"", data.oneOrAll ? "--info-lights" : "--info"}; + CORRADE_VERIFY(_infoArgs.tryParse(Containers::arraySize(argv), argv)); + + std::chrono::high_resolution_clock::duration time; + + /* Print to visually verify coloring */ + if(data.printVisualCheck) { + Debug{} << "======================== visual color verification start ======================="; + Implementation::printInfo(Debug::isTty() ? Debug::Flags{} : Debug::Flag::DisableColors, Debug::isTty(), _infoArgs, importer, time); + Debug{} << "======================== visual color verification end ========================="; + } + + std::ostringstream out; + Debug redirectOutput{&out}; + CORRADE_VERIFY(Implementation::printInfo(Debug::Flag::DisableColors, false, _infoArgs, importer, time) == false); + CORRADE_COMPARE_AS(out.str(), + Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/info-lights.txt"), + TestSuite::Compare::StringToFile); +} + +void SceneConverterTest::infoImplementationMaterials() { + auto&& data = InfoImplementationOneOrAllData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + struct Importer: Trade::AbstractImporter { + Trade::ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + UnsignedInt doMaterialCount() const override { return 2; } + Containers::String doMaterialName(UnsignedInt id) override { + return id == 1 ? "Lots o' laierz" : ""; + } + Containers::Optional doMaterial(UnsignedInt id) override { + /* First has custom attributes */ + if(id == 0) return Trade::MaterialData{Trade::MaterialType::PbrMetallicRoughness, { + {Trade::MaterialAttribute::BaseColor, 0x3bd26799_rgbaf}, + {Trade::MaterialAttribute::DoubleSided, true}, + {Trade::MaterialAttribute::EmissiveColor, 0xe9eca_rgbf}, + {Trade::MaterialAttribute::RoughnessTexture, 67u}, + {Trade::MaterialAttribute::RoughnessTextureMatrix, Matrix3::translation({0.25f, 0.75f})}, + {Trade::MaterialAttribute::RoughnessTextureSwizzle, Trade::MaterialTextureSwizzle::B}, + {"reflectionAngle", 35.0_degf}, + /* These shouldn't have a color swatch rendered */ + {"notAColour4", Vector4{0.1f, 0.2f, 0.3f, 0.4f}}, + {"notAColour3", Vector3{0.2f, 0.3f, 0.4f}}, + {"deadBeef", reinterpret_cast(0xdeadbeef)}, + {"undeadBeef", reinterpret_cast(0xbeefbeef)}, + }}; + + /* Second has layers, custom layers, unnamed layers and a name */ + if(id == 1) return Trade::MaterialData{Trade::MaterialType::PbrClearCoat|Trade::MaterialType::Phong, { + {Trade::MaterialAttribute::DiffuseColor, 0xc7cf2f99_rgbaf}, + {Trade::MaterialLayer::ClearCoat}, + {Trade::MaterialAttribute::LayerFactor, 0.5f}, + {Trade::MaterialAttribute::LayerFactorTexture, 3u}, + {Trade::MaterialAttribute::LayerName, "anEmptyLayer"}, + {Trade::MaterialAttribute::LayerFactor, 0.25f}, + {Trade::MaterialAttribute::LayerFactorTexture, 2u}, + {"yes", "a string"}, + }, {1, 4, 5, 8}}; + + CORRADE_INTERNAL_ASSERT_UNREACHABLE(); + } + } importer; + + const char* argv[]{"", data.oneOrAll ? "--info-materials" : "--info"}; + CORRADE_VERIFY(_infoArgs.tryParse(Containers::arraySize(argv), argv)); + + std::chrono::high_resolution_clock::duration time; + + /* Print to visually verify coloring */ + if(data.printVisualCheck) { + Debug{} << "======================== visual color verification start ======================="; + Implementation::printInfo(Debug::isTty() ? Debug::Flags{} : Debug::Flag::DisableColors, Debug::isTty(), _infoArgs, importer, time); + Debug{} << "======================== visual color verification end ========================="; + } + + std::ostringstream out; + Debug redirectOutput{&out}; + CORRADE_VERIFY(Implementation::printInfo(Debug::Flag::DisableColors, false, _infoArgs, importer, time) == false); + CORRADE_COMPARE_AS(out.str(), + Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/info-materials.txt"), + TestSuite::Compare::StringToFile); +} + +void SceneConverterTest::infoImplementationMeshes() { + auto&& data = InfoImplementationOneOrAllData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + struct Importer: Trade::AbstractImporter { + Trade::ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + UnsignedInt doMeshCount() const override { return 3; } + UnsignedInt doMeshLevelCount(UnsignedInt id) override { + return id == 1 ? 2 : 1; + } + Containers::String doMeshName(UnsignedInt id) override { + return id == 1 ? "LODs? No, meshets." : ""; + } + Containers::String doMeshAttributeName(UnsignedShort name) override { + if(name == 25) return "vertices"; + if(name == 26) return "triangles"; + /* 37 (triangleCount) deliberately not named */ + if(name == 116) return "vertexCount"; + + return ""; + } + Containers::Optional doMesh(UnsignedInt id, UnsignedInt level) override { + /* First is indexed & externally owned */ + if(id == 0 && level == 0) return Trade::MeshData{MeshPrimitive::Points, + Trade::DataFlag::ExternallyOwned, indices, + Trade::MeshIndexData{indices}, + Trade::DataFlag::ExternallyOwned|Trade::DataFlag::Mutable, points, { + Trade::MeshAttributeData{Trade::MeshAttribute::Position, Containers::arrayView(points)} + }}; + + /* Second is multi-level, with second level being indexed meshlets + with custom (array) attributes */ + if(id == 1 && level == 0) { + Containers::ArrayView positions; + Containers::ArrayView tangents; + Containers::ArrayTuple data{ + {NoInit, 250, positions}, + {NoInit, 250, tangents}, + }; + return Trade::MeshData{MeshPrimitive::Triangles, std::move(data), { + Trade::MeshAttributeData{Trade::MeshAttribute::Position, positions}, + Trade::MeshAttributeData{Trade::MeshAttribute::Tangent, tangents}, + }}; + } + if(id == 1 && level == 1) { + Containers::StridedArrayView2D vertices; + Containers::StridedArrayView2D indices; + Containers::ArrayView triangleCount; + Containers::ArrayView vertexCount; + Containers::ArrayTuple data{ + {NoInit, {135, 64}, vertices}, + {NoInit, {135, 126}, indices}, + {NoInit, 135, triangleCount}, + {NoInit, 135, vertexCount}, + }; + return Trade::MeshData{MeshPrimitive::Meshlets, std::move(data), { + Trade::MeshAttributeData{Trade::meshAttributeCustom(25), vertices}, + Trade::MeshAttributeData{Trade::meshAttributeCustom(26), indices}, + Trade::MeshAttributeData{Trade::meshAttributeCustom(37), triangleCount}, + Trade::MeshAttributeData{Trade::meshAttributeCustom(116), vertexCount}, + }}; + } + + /* Third is an empty instance mesh */ + if(id == 2 && level == 0) return Trade::MeshData{MeshPrimitive::Instances, 15}; + + CORRADE_INTERNAL_ASSERT_UNREACHABLE(); + } + + UnsignedShort indices[70]; + Vector3 points[50]; + } importer; + + const char* argv[]{"", data.oneOrAll ? "--info-meshes" : "--info"}; + CORRADE_VERIFY(_infoArgs.tryParse(Containers::arraySize(argv), argv)); + + std::chrono::high_resolution_clock::duration time; + + /* Print to visually verify coloring */ + if(data.printVisualCheck) { + Debug{} << "======================== visual color verification start ======================="; + Implementation::printInfo(Debug::isTty() ? Debug::Flags{} : Debug::Flag::DisableColors, Debug::isTty(), _infoArgs, importer, time); + Debug{} << "======================== visual color verification end ========================="; + } + + std::ostringstream out; + Debug redirectOutput{&out}; + CORRADE_VERIFY(Implementation::printInfo(Debug::Flag::DisableColors, false, _infoArgs, importer, time) == false); + CORRADE_COMPARE_AS(out.str(), + Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/info-meshes.txt"), + TestSuite::Compare::StringToFile); +} + +void SceneConverterTest::infoImplementationMeshesBounds() { + struct Importer: Trade::AbstractImporter { + Trade::ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + UnsignedInt doMeshCount() const override { return 1; } + Containers::Optional doMesh(UnsignedInt, UnsignedInt) override { + return Trade::MeshData{MeshPrimitive::Lines, + {}, indexData, Trade::MeshIndexData{indexData}, + {}, vertexData, { + Trade::MeshAttributeData{Trade::MeshAttribute::Position, Containers::arrayView(vertexData->positions)}, + Trade::MeshAttributeData{Trade::MeshAttribute::Tangent, Containers::arrayView(vertexData->tangent)}, + Trade::MeshAttributeData{Trade::MeshAttribute::Bitangent, Containers::arrayView(vertexData->bitangent)}, + Trade::MeshAttributeData{Trade::MeshAttribute::ObjectId, Containers::arrayView(vertexData->objectId)}, + Trade::MeshAttributeData{Trade::MeshAttribute::Normal, Containers::arrayView(vertexData->normal)}, + Trade::MeshAttributeData{Trade::MeshAttribute::TextureCoordinates, Containers::arrayView(vertexData->textureCoordinates)}, + Trade::MeshAttributeData{Trade::MeshAttribute::Color, Containers::arrayView(vertexData->color)}, + Trade::MeshAttributeData{Trade::MeshAttribute::ObjectId, Containers::arrayView(vertexData->objectIdSecondary)}, + }}; + } + + UnsignedByte indexData[3]{15, 3, 176}; + + struct { + Vector3 positions[2]; + Vector3 tangent[2]; + Vector3 bitangent[2]; + UnsignedShort objectId[2]; + Vector3 normal[2]; + Vector2 textureCoordinates[2]; + Vector4 color[2]; + UnsignedInt objectIdSecondary[2]; + } vertexData[1]{{ + {{0.1f, -0.1f, 0.2f}, {0.2f, 0.0f, -0.2f}}, + {{0.2f, -0.2f, 0.8f}, {0.3f, 0.8f, 0.2f}}, + {{0.4f, 0.2f, 1.0f}, {0.3f, 0.9f, 0.0f}}, + {155, 12}, + {{0.0f, 1.0f, 0.0f}, {1.0f, 0.0f, 1.0f}}, + {{0.5f, 0.5f}, {1.5f, 0.5f}}, + {0x99336600_rgbaf, 0xff663333_rgbaf}, + {15, 337}, + }}; + } importer; + + const char* argv[]{"", "--info-meshes", "--bounds"}; + CORRADE_VERIFY(_infoArgs.tryParse(Containers::arraySize(argv), argv)); + + std::chrono::high_resolution_clock::duration time; + + /* Print to visually verify coloring */ + { + Debug{} << "======================== visual color verification start ======================="; + Implementation::printInfo(Debug::isTty() ? Debug::Flags{} : Debug::Flag::DisableColors, Debug::isTty(), _infoArgs, importer, time); + Debug{} << "======================== visual color verification end ========================="; + } + + std::ostringstream out; + Debug redirectOutput{&out}; + CORRADE_VERIFY(Implementation::printInfo(Debug::Flag::DisableColors, false, _infoArgs, importer, time) == false); + CORRADE_COMPARE_AS(out.str(), + Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/info-meshes-bounds.txt"), + TestSuite::Compare::StringToFile); +} + +void SceneConverterTest::infoImplementationTextures() { + auto&& data = InfoImplementationOneOrAllData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + struct Importer: Trade::AbstractImporter { + Trade::ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + UnsignedInt doTextureCount() const override { return 2; } + Containers::String doTextureName(UnsignedInt id) override { + return id == 1 ? "Name!" : ""; + } + Containers::Optional doTexture(UnsignedInt id) override { + /* First a 1D texture */ + if(id == 0) return Trade::TextureData{ + Trade::TextureType::Texture1D, + SamplerFilter::Nearest, + SamplerFilter::Linear, + SamplerMipmap::Nearest, + SamplerWrapping::Repeat, + 666 + }; + + /* Second a 2D array texture */ + if(id == 1) return Trade::TextureData{ + Trade::TextureType::Texture2DArray, + SamplerFilter::Linear, + SamplerFilter::Nearest, + SamplerMipmap::Linear, + {SamplerWrapping::MirroredRepeat, SamplerWrapping::ClampToEdge, SamplerWrapping::MirrorClampToEdge}, + 3 + }; + + CORRADE_INTERNAL_ASSERT_UNREACHABLE(); + } + } importer; + + const char* argv[]{"", data.oneOrAll ? "--info-textures" : "--info"}; + CORRADE_VERIFY(_infoArgs.tryParse(Containers::arraySize(argv), argv)); + + std::chrono::high_resolution_clock::duration time; + + /* Print to visually verify coloring */ + if(data.printVisualCheck) { + Debug{} << "======================== visual color verification start ======================="; + Implementation::printInfo(Debug::isTty() ? Debug::Flags{} : Debug::Flag::DisableColors, Debug::isTty(), _infoArgs, importer, time); + Debug{} << "======================== visual color verification end ========================="; + } + + std::ostringstream out; + Debug redirectOutput{&out}; + CORRADE_VERIFY(Implementation::printInfo(Debug::Flag::DisableColors, false, _infoArgs, importer, time) == false); + CORRADE_COMPARE_AS(out.str(), + Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/info-textures.txt"), + TestSuite::Compare::StringToFile); +} + +void SceneConverterTest::infoImplementationImages() { + auto&& data = InfoImplementationOneOrAllData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + /* Just the very basics to ensure image info *is* printed. Tested in full + in ImageConverterTest. */ + struct Importer: Trade::AbstractImporter { + Trade::ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + UnsignedInt doImage1DCount() const override { return 1; } + Containers::Optional doImage1D(UnsignedInt, UnsignedInt) override { + return Trade::ImageData1D{PixelFormat::R32F, 1024, Containers::Array{NoInit, 4096}}; + } + } importer; + + const char* argv[]{"", data.oneOrAll ? "--info-images" : "--info"}; + CORRADE_VERIFY(_infoArgs.tryParse(Containers::arraySize(argv), argv)); + + std::chrono::high_resolution_clock::duration time; + + /* Print to visually verify coloring */ + if(data.printVisualCheck) { + Debug{} << "======================== visual color verification start ======================="; + Implementation::printInfo(Debug::isTty() ? Debug::Flags{} : Debug::Flag::DisableColors, Debug::isTty(), _infoArgs, importer, time); + Debug{} << "======================== visual color verification end ========================="; + } + + std::ostringstream out; + Debug redirectOutput{&out}; + CORRADE_VERIFY(Implementation::printInfo(Debug::Flag::DisableColors, false, _infoArgs, importer, time) == false); + CORRADE_COMPARE_AS(out.str(), + Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/info-images.txt"), + TestSuite::Compare::StringToFile); +} + +void SceneConverterTest::infoImplementationReferenceCount() { + struct Importer: Trade::AbstractImporter { + Trade::ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + /* One data of each kind should be always referenced twice+, one once, + one not at all, and one reference should be OOB */ + + UnsignedLong doObjectCount() const override { return 4; } + Containers::String doObjectName(UnsignedLong id) override { + return id == 2 ? "Not referenced" : ""; + } + UnsignedInt doSceneCount() const override { return 2; } + Containers::Optional doScene(UnsignedInt id) override { + if(id == 0) return Trade::SceneData{Trade::SceneMappingType::UnsignedInt, 2, {}, sceneData3D, { + /* To mark the scene as 3D */ + Trade::SceneFieldData{Trade::SceneField::Transformation, Trade::SceneMappingType::UnsignedInt, nullptr, Trade::SceneFieldType::Matrix4x4, nullptr}, + Trade::SceneFieldData{Trade::SceneField::Mesh, + Containers::arrayView(sceneData3D->mapping), + Containers::arrayView(sceneData3D->meshes)}, + Trade::SceneFieldData{Trade::SceneField::MeshMaterial, + Containers::arrayView(sceneData3D->mapping), + Containers::arrayView(sceneData3D->materials)}, + Trade::SceneFieldData{Trade::SceneField::Light, + Containers::arrayView(sceneData3D->mapping), + Containers::arrayView(sceneData3D->lights)}, + Trade::SceneFieldData{Trade::SceneField::Skin, + Containers::arrayView(sceneData3D->mapping), + Containers::arrayView(sceneData3D->skins)}, + }}; + if(id == 1) return Trade::SceneData{Trade::SceneMappingType::UnsignedInt, 4, {}, sceneData2D, { + /* To mark the scene as 2D */ + Trade::SceneFieldData{Trade::SceneField::Transformation, Trade::SceneMappingType::UnsignedInt, nullptr, Trade::SceneFieldType::Matrix3x3, nullptr}, + Trade::SceneFieldData{Trade::SceneField::Mesh, + Containers::arrayView(sceneData2D->mapping), + Containers::arrayView(sceneData2D->meshes)}, + Trade::SceneFieldData{Trade::SceneField::Skin, + Containers::arrayView(sceneData2D->mapping), + Containers::arrayView(sceneData2D->skins)}, + }}; + + CORRADE_INTERNAL_ASSERT_UNREACHABLE(); + } + + UnsignedInt doSkin2DCount() const override { return 3; } + Containers::String doSkin2DName(UnsignedInt id) override { + return id == 2 ? "Not referenced" : ""; + } + Containers::Optional doSkin2D(UnsignedInt id) override { + if(id == 0) return Trade::SkinData2D{ + {35, 22}, + {{}, {}} + }; + if(id == 1) return Trade::SkinData2D{ + {33, 10, 100}, + {{}, {}, {}} + }; + if(id == 2) return Trade::SkinData2D{ + {66}, + {{}} + }; + + CORRADE_INTERNAL_ASSERT_UNREACHABLE(); + } + + UnsignedInt doSkin3DCount() const override { return 3; } + Containers::String doSkin3DName(UnsignedInt id) override { + return id == 0 ? "Not referenced" : ""; + } + Containers::Optional doSkin3D(UnsignedInt id) override { + if(id == 0) return Trade::SkinData3D{ + {35, 22}, + {{}, {}} + }; + if(id == 1) return Trade::SkinData3D{ + {37}, + {{}} + }; + if(id == 2) return Trade::SkinData3D{ + {300, 10, 1000}, + {{}, {}, {}} + }; + + CORRADE_INTERNAL_ASSERT_UNREACHABLE(); + } + + UnsignedInt doLightCount() const override { return 3; } + Containers::String doLightName(UnsignedInt id) override { + return id == 1 ? "Not referenced" : ""; + } + Containers::Optional doLight(UnsignedInt id) override { + if(id == 0) return Trade::LightData{ + Trade::LightData::Type::Directional, + 0x57ff34_rgbf, + 5.0f + }; + if(id == 1) return Trade::LightData{ + Trade::LightData::Type::Ambient, + 0xff5734_rgbf, + 0.1f + }; + if(id == 2) return Trade::LightData{ + Trade::LightData::Type::Directional, + 0x3457ff_rgbf, + 1.0f + }; + CORRADE_INTERNAL_ASSERT_UNREACHABLE(); + } + + UnsignedInt doMaterialCount() const override { return 3; } + Containers::String doMaterialName(UnsignedInt id) override { + return id == 2 ? "Not referenced" : ""; + } + Containers::Optional doMaterial(UnsignedInt id) override { + if(id == 0) return Trade::MaterialData{{}, { + {Trade::MaterialAttribute::DiffuseTexture, 2u}, + {Trade::MaterialAttribute::BaseColorTexture, 2u}, + }}; + if(id == 1) return Trade::MaterialData{{}, { + {"lookupTexture", 0u}, + {"volumeTexture", 3u}, + {Trade::MaterialAttribute::NormalTexture, 17u}, + {Trade::MaterialAttribute::EmissiveTexture, 4u}, + }}; + if(id == 2) return Trade::MaterialData{{}, {}}; + CORRADE_INTERNAL_ASSERT_UNREACHABLE(); + } + + UnsignedInt doMeshCount() const override { return 3; } + Containers::String doMeshName(UnsignedInt id) override { + return id == 1 ? "Not referenced" : ""; + } + Containers::Optional doMesh(UnsignedInt id, UnsignedInt) override { + if(id == 0) return Trade::MeshData{MeshPrimitive::Points, 5}; + if(id == 1) return Trade::MeshData{MeshPrimitive::Lines, 4}; + if(id == 2) return Trade::MeshData{MeshPrimitive::TriangleFan, 4}; + CORRADE_INTERNAL_ASSERT_UNREACHABLE(); + } + + UnsignedInt doTextureCount() const override { return 5; } + Containers::String doTextureName(UnsignedInt id) override { + return id == 1 ? "Not referenced" : ""; + } + Containers::Optional doTexture(UnsignedInt id) override { + if(id == 0) return Trade::TextureData{ + Trade::TextureType::Texture1D, + SamplerFilter::Nearest, + SamplerFilter::Linear, + SamplerMipmap::Nearest, + SamplerWrapping::Repeat, + 1 + }; + if(id == 1) return Trade::TextureData{ + Trade::TextureType::Texture1DArray, + SamplerFilter::Nearest, + SamplerFilter::Linear, + SamplerMipmap::Nearest, + SamplerWrapping::Repeat, + 225 + }; + if(id == 2) return Trade::TextureData{ + Trade::TextureType::Texture2D, + SamplerFilter::Nearest, + SamplerFilter::Linear, + SamplerMipmap::Nearest, + SamplerWrapping::Repeat, + 0 + }; + if(id == 3) return Trade::TextureData{ + Trade::TextureType::Texture3D, + SamplerFilter::Nearest, + SamplerFilter::Linear, + SamplerMipmap::Nearest, + SamplerWrapping::Repeat, + 1 + }; + if(id == 4) return Trade::TextureData{ + Trade::TextureType::Texture2D, + SamplerFilter::Nearest, + SamplerFilter::Linear, + SamplerMipmap::Nearest, + SamplerWrapping::Repeat, + 0 + }; + CORRADE_INTERNAL_ASSERT_UNREACHABLE(); + } + + UnsignedInt doImage1DCount() const override { return 2; } + Containers::String doImage1DName(UnsignedInt id) override { + return id == 0 ? "Not referenced" : ""; + } + Containers::Optional doImage1D(UnsignedInt id, UnsignedInt) override { + if(id == 0) + return Trade::ImageData1D{PixelFormat::RGBA8I, 1, Containers::Array{NoInit, 4}}; + if(id == 1) + return Trade::ImageData1D{PixelFormat::R8I, 4, Containers::Array{NoInit, 4}}; + CORRADE_INTERNAL_ASSERT_UNREACHABLE(); + } + + UnsignedInt doImage2DCount() const override { return 2; } + Containers::String doImage2DName(UnsignedInt id) override { + return id == 1 ? "Not referenced" : ""; + } + Containers::Optional doImage2D(UnsignedInt id, UnsignedInt) override { + if(id == 0) + return Trade::ImageData2D{PixelFormat::RGBA8I, {1, 2}, Containers::Array{NoInit, 8}}; + if(id == 1) + return Trade::ImageData2D{PixelFormat::R8I, {4, 1}, Containers::Array{NoInit, 4}}; + CORRADE_INTERNAL_ASSERT_UNREACHABLE(); + } + + UnsignedInt doImage3DCount() const override { return 2; } + Containers::String doImage3DName(UnsignedInt id) override { + return id == 0 ? "Not referenced" : ""; + } + Containers::Optional doImage3D(UnsignedInt id, UnsignedInt) override { + if(id == 0) + return Trade::ImageData3D{PixelFormat::RGBA8I, {1, 2, 1}, Containers::Array{NoInit, 8}}; + if(id == 1) + return Trade::ImageData3D{PixelFormat::R8I, {4, 1, 1}, Containers::Array{NoInit, 4}}; + CORRADE_INTERNAL_ASSERT_UNREACHABLE(); + } + + struct { + UnsignedInt mapping[4]; + UnsignedInt meshes[4]; + Int materials[4]; + UnsignedInt lights[4]; + UnsignedInt skins[4]; + } sceneData3D[1]{{ + {0, 1, 1, 25}, + {2, 0, 2, 67}, + {0, 1, 23, 0}, + {0, 17, 0, 2}, + {1, 1, 22, 2} + }}; + + struct { + UnsignedInt mapping[3]; + UnsignedInt meshes[3]; + UnsignedInt skins[3]; + } sceneData2D[1]{{ + {3, 116, 1}, + {2, 0, 23}, + {177, 0, 1} + }}; + } importer; + + const char* argv[]{"", "--info"}; + CORRADE_VERIFY(_infoArgs.tryParse(Containers::arraySize(argv), argv)); + + std::chrono::high_resolution_clock::duration time; + + /* Print to visually verify coloring */ + { + Debug{} << "======================== visual color verification start ======================="; + Implementation::printInfo(Debug::isTty() ? Debug::Flags{} : Debug::Flag::DisableColors, Debug::isTty(), _infoArgs, importer, time); + Debug{} << "======================== visual color verification end ========================="; + } + + std::ostringstream out; + Debug redirectOutput{&out}; + CORRADE_VERIFY(Implementation::printInfo(Debug::Flag::DisableColors, false, _infoArgs, importer, time) == false); + CORRADE_COMPARE_AS(out.str(), + Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/info-references.txt"), + TestSuite::Compare::StringToFile); +} + +void SceneConverterTest::infoImplementationError() { + struct Importer: Trade::AbstractImporter { + Trade::ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + /* The one single object is named, and that name should be printed + after all error messages */ + UnsignedLong doObjectCount() const override { return 1; } + Containers::String doObjectName(UnsignedLong) override { return "A name"; } + + UnsignedInt doSceneCount() const override { return 2; } + Containers::Optional doScene(UnsignedInt id) override { + Error{} << "Scene" << id << "error!"; + return {}; + } + + UnsignedInt doAnimationCount() const override { return 2; } + Containers::Optional doAnimation(UnsignedInt id) override { + Error{} << "Animation" << id << "error!"; + return {}; + } + + UnsignedInt doSkin2DCount() const override { return 2; } + Containers::Optional doSkin2D(UnsignedInt id) override { + Error{} << "2D skin" << id << "error!"; + return {}; + } + + UnsignedInt doSkin3DCount() const override { return 2; } + Containers::Optional doSkin3D(UnsignedInt id) override { + Error{} << "3D skin" << id << "error!"; + return {}; + } + + UnsignedInt doLightCount() const override { return 2; } + Containers::Optional doLight(UnsignedInt id) override { + Error{} << "Light" << id << "error!"; + return {}; + } + + UnsignedInt doMaterialCount() const override { return 2; } + Containers::Optional doMaterial(UnsignedInt id) override { + Error{} << "Material" << id << "error!"; + return {}; + } + + UnsignedInt doMeshCount() const override { return 2; } + Containers::Optional doMesh(UnsignedInt id, UnsignedInt) override { + Error{} << "Mesh" << id << "error!"; + return {}; + } + + UnsignedInt doTextureCount() const override { return 2; } + Containers::Optional doTexture(UnsignedInt id) override { + Error{} << "Texture" << id << "error!"; + return {}; + } + + /* Errors for all image types tested in ImageConverterTest */ + UnsignedInt doImage2DCount() const override { return 2; } + Containers::Optional doImage2D(UnsignedInt id, UnsignedInt) override { + Error{} << "Image" << id << "error!"; + return {}; + } + } importer; + + const char* argv[]{"", "--info"}; + CORRADE_VERIFY(_infoArgs.tryParse(Containers::arraySize(argv), argv)); + + std::chrono::high_resolution_clock::duration time; + + std::ostringstream out; + Debug redirectOutput{&out}; + Error redirectError{&out}; + /* It should return a failure */ + CORRADE_VERIFY(Implementation::printInfo(Debug::Flag::DisableColors, {}, _infoArgs, importer, time) == true); + CORRADE_COMPARE(out.str(), + /* It should not exit after first error... */ + "Scene 0 error!\n" + "Scene 1 error!\n" + "Animation 0 error!\n" + "Animation 1 error!\n" + "2D skin 0 error!\n" + "2D skin 1 error!\n" + "3D skin 0 error!\n" + "3D skin 1 error!\n" + "Light 0 error!\n" + "Light 1 error!\n" + "Material 0 error!\n" + "Material 1 error!\n" + "Mesh 0 error!\n" + "Mesh 1 error!\n" + "Texture 0 error!\n" + "Texture 1 error!\n" + "Image 0 error!\n" + "Image 1 error!\n" + /* ... and it should print all info output after the errors */ + "Object 0: A name\n"); +} + +}}}} + +CORRADE_TEST_MAIN(Magnum::SceneTools::Test::SceneConverterTest) diff --git a/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-animations.txt b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-animations.txt new file mode 100644 index 000000000..df71d5da7 --- /dev/null +++ b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-animations.txt @@ -0,0 +1,12 @@ +Animation 0: + Duration: {0.5, 1.25} (0.1 kB) + Track 0: Translation2D @ Vector2, 3 keyframes + Interpolation: Linear, DefaultConstructed, Constant + Track 1: Rotation2D @ CubicHermite2D -> Vector2, 3 keyframes + Interpolation: Constant, Extrapolated, Extrapolated +Animation 1: Custom track duration and interpolator function + Duration: {0.1, 1.3} (0.1 kB, ExternallyOwned) + Track 0: Scaling3D @ Vector3, 5 keyframes + Duration: {0.75, 1.25} + Interpolation: Custom, DefaultConstructed, Constant +Total animation data size: 0.2 kB diff --git a/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-images.txt b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-images.txt new file mode 100644 index 000000000..7455a53f2 --- /dev/null +++ b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-images.txt @@ -0,0 +1,3 @@ +1D image 0: + Level 0: {1024} @ R32F (4.0 kB) +Total image data size: 4.0 kB diff --git a/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-lights.txt b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-lights.txt new file mode 100644 index 000000000..7942af9c9 --- /dev/null +++ b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-lights.txt @@ -0,0 +1,8 @@ +Light 0: + Type: Spot, 55° - 85° + Color: {0.203922, 0.341176, 1} * 15 + Attenuation: {1.2, 0.3, 0.04} + Range: 100 +Light 1: Directional light with always-implicit attenuation and range + Type: Directional + Color: {1, 0.341176, 0.203922} * 5 diff --git a/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-materials.txt b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-materials.txt new file mode 100644 index 000000000..35175acc6 --- /dev/null +++ b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-materials.txt @@ -0,0 +1,28 @@ +Material 0: + Type: PbrMetallicRoughness + Base layer: + BaseColor @ Vector4: {0.231373, 0.823529, 0.403922, 0.6} + DoubleSided @ Bool: true + EmissiveColor @ Vector3: {0.054902, 0.619608, 0.792157} + RoughnessTexture @ UnsignedInt: 67 + RoughnessTextureMatrix @ Matrix3x3: {1, 0, 0.25, + 0, 1, 0.75, + 0, 0, 1} + RoughnessTextureSwizzle @ TextureSwizzle: B + deadBeef @ Pointer: 0xdeadbeef + notAColour3 @ Vector3: {0.2, 0.3, 0.4} + notAColour4 @ Vector4: {0.1, 0.2, 0.3, 0.4} + reflectionAngle @ Deg: 35 + undeadBeef @ MutablePointer: 0xbeefbeef +Material 1: Lots o' laierz + Type: Phong|PbrClearCoat + Base layer: + DiffuseColor @ Vector4: {0.780392, 0.811765, 0.184314, 0.6} + Layer 1: ClearCoat + LayerFactor @ Float: 0.5 + LayerFactorTexture @ UnsignedInt: 3 + Layer 2: anEmptyLayer + Layer 3: + LayerFactor @ Float: 0.25 + LayerFactorTexture @ UnsignedInt: 2 + yes @ String: a string diff --git a/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-meshes-bounds.txt b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-meshes-bounds.txt new file mode 100644 index 000000000..e5195e9f7 --- /dev/null +++ b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-meshes-bounds.txt @@ -0,0 +1,21 @@ +Mesh 0: + Level 0: 2 vertices @ Lines (0.2 kB, {}) + Position @ Vector3, offset 0, stride 12 + Bounds: ({0.1, -0.1, -0.2}, {0.2, 0, 0.2}) + Tangent @ Vector3, offset 24, stride 12 + Bounds: ({0.2, -0.2, 0.2}, {0.3, 0.8, 0.8}) + Bitangent @ Vector3, offset 48, stride 12 + Bounds: ({0.3, 0.2, 0}, {0.4, 0.9, 1}) + ObjectId @ UnsignedShort, offset 72, stride 2 + Bounds: (12, 155) + Normal @ Vector3, offset 76, stride 12 + Bounds: ({0, 0, 0}, {1, 1, 1}) + TextureCoordinates @ Vector2, offset 100, stride 8 + Bounds: ({0.5, 0.5}, {1.5, 0.5}) + Color @ Vector4, offset 116, stride 16 + Bounds: ({0.6, 0.2, 0.2, 0}, {1, 0.4, 0.4, 0.2}) + ObjectId @ UnsignedInt, offset 148, stride 4 + Bounds: (15, 337) + 3 indices @ UnsignedByte, offset 0, stride 1 (0.0 kB, {}) + Bounds: (3, 176) +Total mesh data size: 0.2 kB diff --git a/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-meshes.txt b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-meshes.txt new file mode 100644 index 000000000..b96fb94c9 --- /dev/null +++ b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-meshes.txt @@ -0,0 +1,16 @@ +Mesh 0: + Level 0: 50 vertices @ Points (0.6 kB, ExternallyOwned|Mutable) + Position @ Vector3, offset 0, stride 12 + 70 indices @ UnsignedShort, offset 0, stride 2 (0.1 kB, ExternallyOwned) +Mesh 1: LODs? No, meshets. + Level 0: 250 vertices @ Triangles (6.8 kB) + Position @ Vector3, offset 0, stride 12 + Tangent @ Vector4, offset 3000, stride 16 + Level 1: 135 vertices @ Meshlets (83.8 kB) + Custom(25:vertices) @ UnsignedInt[64], offset 0, stride 256 + Custom(26:triangles) @ Vector3ub[126], offset 34560, stride 378 + Custom(37:) @ UnsignedByte, offset 85590, stride 1 + Custom(116:vertexCount) @ UnsignedByte, offset 85725, stride 1 +Mesh 2: + Level 0: 15 vertices @ Instances (0.0 kB) +Total mesh data size: 91.4 kB diff --git a/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-objects.txt b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-objects.txt new file mode 100644 index 000000000..89a64cfb8 --- /dev/null +++ b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-objects.txt @@ -0,0 +1,5 @@ +Object 0: Parent-less mesh +Object 2: Two meshes, shared among two scenes +Object 4: Two custom arrays +Object 6: Only in the second scene, but no fields, thus same as unreferenced +Object 8: Not in any scene diff --git a/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-references.txt b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-references.txt new file mode 100644 index 000000000..1cda5540b --- /dev/null +++ b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-references.txt @@ -0,0 +1,98 @@ +Scene 0: + Bound: 2 objects @ UnsignedInt (0.1 kB, {}) + Fields: + Transformation @ Matrix4x4, 0 entries + Mesh @ UnsignedInt, 4 entries + MeshMaterial @ Int, 4 entries + Light @ UnsignedInt, 4 entries + Skin @ UnsignedInt, 4 entries +Scene 1: + Bound: 4 objects @ UnsignedInt (0.0 kB, {}) + Fields: + Transformation @ Matrix3x3, 0 entries + Mesh @ UnsignedInt, 3 entries + Skin @ UnsignedInt, 3 entries +Total scene data size: 0.1 kB +Object 0 (referenced by 1 scenes): + Fields: Mesh, MeshMaterial, Light, Skin +Object 1 (referenced by 2 scenes): + Fields: Mesh[2], MeshMaterial[2], Light[2], Skin[2], Mesh, Skin +Object 2 (referenced by 0 scenes): Not referenced +Object 3 (referenced by 1 scenes): + Fields: Mesh, Skin +2D skin 0 (referenced by 1 objects): + 2 joints +2D skin 1 (referenced by 1 objects): + 3 joints +2D skin 2 (referenced by 0 objects): Not referenced + 1 joints +3D skin 0 (referenced by 0 objects): Not referenced + 2 joints +3D skin 1 (referenced by 2 objects): + 1 joints +3D skin 2 (referenced by 1 objects): + 3 joints +Light 0 (referenced by 2 objects): + Type: Directional + Color: {0.341176, 1, 0.203922} * 5 +Light 1 (referenced by 0 objects): Not referenced + Type: Ambient + Color: {1, 0.341176, 0.203922} * 0.1 +Light 2 (referenced by 1 objects): + Type: Directional + Color: {0.203922, 0.341176, 1} +Material 0 (referenced by 2 objects): + Type: {} + Base layer: + BaseColorTexture @ UnsignedInt: 2 + DiffuseTexture @ UnsignedInt: 2 +Material 1 (referenced by 1 objects): + Type: {} + Base layer: + EmissiveTexture @ UnsignedInt: 4 + NormalTexture @ UnsignedInt: 17 + lookupTexture @ UnsignedInt: 0 + volumeTexture @ UnsignedInt: 3 +Material 2 (referenced by 0 objects): Not referenced + Type: {} + Base layer: +Mesh 0 (referenced by 2 objects): + Level 0: 5 vertices @ Points (0.0 kB) +Mesh 1 (referenced by 0 objects): Not referenced + Level 0: 4 vertices @ Lines (0.0 kB) +Mesh 2 (referenced by 3 objects): + Level 0: 4 vertices @ TriangleFan (0.0 kB) +Total mesh data size: 0.0 kB +Texture 0 (referenced by 1 material attributes): + Type: Texture1D, image 1 + Minification, mipmap and magnification: Nearest, Nearest, Linear + Wrapping: {Repeat, Repeat, Repeat} +Texture 1 (referenced by 0 material attributes): Not referenced + Type: Texture1DArray, image 225 + Minification, mipmap and magnification: Nearest, Nearest, Linear + Wrapping: {Repeat, Repeat, Repeat} +Texture 2 (referenced by 2 material attributes): + Type: Texture2D, image 0 + Minification, mipmap and magnification: Nearest, Nearest, Linear + Wrapping: {Repeat, Repeat, Repeat} +Texture 3 (referenced by 1 material attributes): + Type: Texture3D, image 1 + Minification, mipmap and magnification: Nearest, Nearest, Linear + Wrapping: {Repeat, Repeat, Repeat} +Texture 4 (referenced by 1 material attributes): + Type: Texture2D, image 0 + Minification, mipmap and magnification: Nearest, Nearest, Linear + Wrapping: {Repeat, Repeat, Repeat} +1D image 0 (referenced by 0 textures): Not referenced + Level 0: {1} @ RGBA8I (0.0 kB) +1D image 1 (referenced by 1 textures): + Level 0: {4} @ R8I (0.0 kB) +2D image 0 (referenced by 2 textures): + Level 0: {1, 2} @ RGBA8I (0.0 kB) +2D image 1 (referenced by 0 textures): Not referenced + Level 0: {4, 1} @ R8I (0.0 kB) +3D image 0 (referenced by 0 textures): Not referenced + Level 0: {1, 2, 1} @ RGBA8I (0.0 kB) +3D image 1 (referenced by 1 textures): + Level 0: {4, 1, 1} @ R8I (0.0 kB) +Total image data size: 0.0 kB diff --git a/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-scenes-objects.txt b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-scenes-objects.txt new file mode 100644 index 000000000..781d20482 --- /dev/null +++ b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-scenes-objects.txt @@ -0,0 +1,25 @@ +Scene 0: A simple scene + Bound: 4 objects @ UnsignedInt (0.1 kB) + Fields: + Parent @ Int, 3 entries + Mesh @ UnsignedInt, OrderedMapping, 4 entries +Scene 1: + Bound: 8 objects @ UnsignedByte (0.0 kB, ExternallyOwned|Mutable) + Fields: + Custom(42:) @ Double, 2 entries + Custom(1337:DirectionVector) @ Short[3], 3 entries +Total scene data size: 0.1 kB +Object 0 (referenced by 1 scenes): Parent-less mesh + Fields: Mesh +Object 1 (referenced by 1 scenes): + Fields: Parent, Mesh +Object 2 (referenced by 2 scenes): Two meshes, shared among two scenes + Fields: Parent, Mesh[2], Custom(1337:DirectionVector) +Object 3 (referenced by 2 scenes): + Fields: Parent, Custom(42:) +Object 4 (referenced by 1 scenes): Two custom arrays + Fields: Custom(1337:DirectionVector)[2] +Object 6 (referenced by 0 scenes): Only in the second scene, but no fields, thus same as unreferenced +Object 7 (referenced by 1 scenes): + Fields: Custom(42:) +Object 8 (referenced by 0 scenes): Not in any scene diff --git a/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-scenes.txt b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-scenes.txt new file mode 100644 index 000000000..acd050cc3 --- /dev/null +++ b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-scenes.txt @@ -0,0 +1,11 @@ +Scene 0: A simple scene + Bound: 4 objects @ UnsignedInt (0.1 kB) + Fields: + Parent @ Int, 3 entries + Mesh @ UnsignedInt, OrderedMapping, 4 entries +Scene 1: + Bound: 8 objects @ UnsignedByte (0.0 kB, ExternallyOwned|Mutable) + Fields: + Custom(42:) @ Double, 2 entries + Custom(1337:DirectionVector) @ Short[3], 3 entries +Total scene data size: 0.1 kB diff --git a/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-skins.txt b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-skins.txt new file mode 100644 index 000000000..e8eb3a983 --- /dev/null +++ b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-skins.txt @@ -0,0 +1,10 @@ +2D skin 0: + 5 joints +2D skin 1: Second 2D skin, external data + 15 joints +3D skin 0: First 3D skin, external data + 12 joints +3D skin 1: + 2 joints +3D skin 2: + 1 joints diff --git a/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-textures.txt b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-textures.txt new file mode 100644 index 000000000..0b3afe593 --- /dev/null +++ b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-textures.txt @@ -0,0 +1,8 @@ +Texture 0: + Type: Texture1D, image 666 + Minification, mipmap and magnification: Nearest, Nearest, Linear + Wrapping: {Repeat, Repeat, Repeat} +Texture 1: Name! + Type: Texture2DArray, image 3 + Minification, mipmap and magnification: Linear, Linear, Nearest + Wrapping: {MirroredRepeat, ClampToEdge, ClampToEdge} diff --git a/src/Magnum/SceneTools/Test/configure.h.cmake b/src/Magnum/SceneTools/Test/configure.h.cmake new file mode 100644 index 000000000..dcb0c8113 --- /dev/null +++ b/src/Magnum/SceneTools/Test/configure.h.cmake @@ -0,0 +1,26 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, + 2020, 2021, 2022 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. +*/ + +#define SCENETOOLS_TEST_DIR "${SCENETOOLS_TEST_DIR}" diff --git a/src/Magnum/SceneTools/sceneconverter.cpp b/src/Magnum/SceneTools/sceneconverter.cpp index a663d1e90..4dff2f194 100644 --- a/src/Magnum/SceneTools/sceneconverter.cpp +++ b/src/Magnum/SceneTools/sceneconverter.cpp @@ -23,9 +23,7 @@ DEALINGS IN THE SOFTWARE. */ -#include /* std::isupper() */ #include -#include /* sceneFieldNames */ #include #include #include @@ -35,25 +33,16 @@ #include #include -#include "Magnum/PixelFormat.h" -#include "Magnum/Implementation/converterUtilities.h" -#include "Magnum/Math/Color.h" -#include "Magnum/Math/Matrix4.h" -#include "Magnum/Math/FunctionsBatch.h" #include "Magnum/MeshTools/Concatenate.h" #include "Magnum/MeshTools/RemoveDuplicates.h" #include "Magnum/MeshTools/Transform.h" #include "Magnum/SceneTools/FlattenMeshHierarchy.h" #include "Magnum/Trade/AbstractImporter.h" -#include "Magnum/Trade/AnimationData.h" -#include "Magnum/Trade/LightData.h" -#include "Magnum/Trade/MaterialData.h" #include "Magnum/Trade/MeshData.h" -#include "Magnum/Trade/SceneData.h" -#include "Magnum/Trade/SkinData.h" -#include "Magnum/Trade/TextureData.h" #include "Magnum/Trade/AbstractSceneConverter.h" -#include "Magnum/Trade/Implementation/converterUtilities.h" + +#include "Magnum/Implementation/converterUtilities.h" +#include "Magnum/SceneTools/Implementation/sceneConverterUtilities.h" namespace Magnum { @@ -225,23 +214,6 @@ using namespace Containers::Literals; namespace { -/** @todo const Array& doesn't work, minmax() would fail to match */ -template Containers::String calculateBounds(Containers::Array&& attribute) { - /** @todo clean up when Debug::toString() exists */ - std::ostringstream out; - Debug{&out, Debug::Flag::NoNewlineAtTheEnd} << Math::minmax(attribute); - return out.str(); -} - -/* Named attribute index from a global index */ -/** @todo some helper for this directly on the MeshData class? */ -UnsignedInt namedAttributeId(const Trade::MeshData& mesh, UnsignedInt id) { - const Trade::MeshAttribute name = mesh.attributeName(id); - for(UnsignedInt i = 0; i != mesh.attributeCount(name); ++i) - if(mesh.attributeId(name, i) == id) return i; - CORRADE_INTERNAL_ASSERT_UNREACHABLE(); -} - bool isInfoRequested(const Utility::Arguments& args) { return args.isSet("info-animations") || args.isSet("info-images") || @@ -403,912 +375,7 @@ is specified as well, the IDs reference attributes of the first mesh.)") /* Print file info, if requested */ if(isInfoRequested(args)) { - struct AnimationInfo { - UnsignedInt animation; - Trade::AnimationData data{{}, {}}; - Containers::String name; - }; - - struct SkinInfo { - UnsignedInt skin; - Trade::SkinData3D data{{}, {}}; - Containers::String name; - }; - - struct LightInfo { - UnsignedInt light; - Trade::LightData data{{}, {}, {}}; - Containers::String name; - }; - - struct MaterialInfo { - UnsignedInt material; - Trade::MaterialData data{{}, {}}; - Containers::String name; - }; - - struct TextureInfo { - UnsignedInt texture; - Trade::TextureData data{{}, {}, {}, {}, {}, {}}; - Containers::String name; - }; - - struct MeshAttributeInfo { - std::size_t offset; - Int stride; - UnsignedInt arraySize; - Trade::MeshAttribute name; - Containers::String customName; - VertexFormat format; - Containers::String bounds; - }; - - struct MeshInfo { - UnsignedInt mesh, level; - MeshPrimitive primitive; - UnsignedInt indexCount, vertexCount; - std::size_t indexOffset; - Int indexStride; - Containers::String indexBounds; - MeshIndexType indexType; - Containers::Array attributes; - std::size_t indexDataSize, vertexDataSize; - Trade::DataFlags indexDataFlags, vertexDataFlags; - Containers::String name; - }; - - struct SceneFieldInfo { - Trade::SceneField name; - Trade::SceneFieldFlags flags; - Trade::SceneFieldType type; - UnsignedInt arraySize; - std::size_t size; - }; - - struct SceneInfo { - UnsignedInt scene; - Trade::SceneMappingType mappingType; - UnsignedLong mappingBound; - Containers::Array fields; - std::size_t dataSize; - Trade::DataFlags dataFlags; - Containers::String name; - }; - - struct ObjectInfo { - UnsignedLong object; - /* A bitfield, assuming no more than 32 scenes */ - /** @todo might be too little? */ - UnsignedInt scenes; - Containers::Array> fields; - Containers::String name; - }; - - /* Parse everything first to avoid errors interleaved with output */ - bool error = false; - - /* Object properties */ - Containers::Array objectInfos; - if(args.isSet("info") || args.isSet("info-objects")) { - objectInfos = Containers::Array{std::size_t(importer->objectCount())}; - - for(UnsignedLong i = 0; i != importer->objectCount(); ++i) { - objectInfos[i].object = i; - objectInfos[i].name = importer->objectName(i); - } - } - - /* Scene properties, together with counting how much is each mesh / - light / material / skin / object referenced (which gets used only if - both --info-scenes and --info-{lights,materials,skins,objects} is - passed and the file has at least one scene). Texture reference count - is calculated when parsing materials. */ - Containers::Array sceneInfos; - /* Only the very latest GCC seems to support enum classes as keys and - I can't be bothered to write a std::hash specialization, so just - making the key typeless */ - std::unordered_map sceneFieldNames; - Containers::Array materialReferenceCount; - Containers::Array lightReferenceCount; - Containers::Array meshReferenceCount; - Containers::Array skinReferenceCount; - if((args.isSet("info") || args.isSet("info-scenes")) && importer->sceneCount()) { - materialReferenceCount = Containers::Array{importer->materialCount()}; - lightReferenceCount = Containers::Array{importer->lightCount()}; - meshReferenceCount = Containers::Array{importer->meshCount()}; - skinReferenceCount = Containers::Array{importer->skin3DCount()}; - - for(UnsignedInt i = 0; i != importer->sceneCount(); ++i) { - Containers::Optional scene = importer->scene(i); - if(!scene) { - error = true; - continue; - } - - SceneInfo info{}; - info.scene = i; - info.mappingType = scene->mappingType(); - info.mappingBound = scene->mappingBound(); - info.dataSize = scene->data().size(); - info.dataFlags = scene->dataFlags(); - info.name = importer->sceneName(i); - for(UnsignedInt j = 0; j != scene->fieldCount(); ++j) { - const Trade::SceneField name = scene->fieldName(j); - - if(name == Trade::SceneField::Mesh) for(const Containers::Pair>& meshMaterial: scene->meshesMaterialsAsArray()) { - if(meshMaterial.second().first() < meshReferenceCount.size()) - ++meshReferenceCount[meshMaterial.second().first()]; - if(UnsignedInt(meshMaterial.second().second()) < materialReferenceCount.size()) - ++materialReferenceCount[meshMaterial.second().second()]; - } - - if(name == Trade::SceneField::Skin) for(const Containers::Pair skin: scene->skinsAsArray()) { - if(skin.second() < skinReferenceCount.size()) - ++skinReferenceCount[skin.second()]; - /** @todo 2D/3D distinction */ - } - - if(name == Trade::SceneField::Light) for(const Containers::Pair& light: scene->lightsAsArray()) { - if(light.second() < lightReferenceCount.size()) - ++lightReferenceCount[light.second()]; - } - - arrayAppend(info.fields, InPlaceInit, - name, - scene->fieldFlags(j), - scene->fieldType(j), - scene->fieldArraySize(j), - scene->fieldSize(j)); - - /* If the field has a custom name, save it into the map. - Not putting it into the fields array as the map is - reused by object info as well. */ - if(Trade::isSceneFieldCustom(name)) { - /* Fetch the name only if it's not already there */ - const auto inserted = sceneFieldNames.emplace(sceneFieldCustom(name), Containers::String{}); - if(inserted.second) - inserted.first->second = importer->sceneFieldName(name); - } - - if(objectInfos) for(const UnsignedInt object: scene->mappingAsArray(j)) { - if(object >= objectInfos.size()) continue; - - objectInfos[object].object = object; - objectInfos[object].scenes |= 1 << i; - - /* If the field is repeated, increase the count - instead */ - if(!objectInfos[object].fields.isEmpty() && objectInfos[object].fields.back().first() == name) - ++objectInfos[object].fields.back().second(); - else - arrayAppend(objectInfos[object].fields, InPlaceInit, name, 1u); - } - } - - arrayAppend(sceneInfos, std::move(info)); - } - } - - /* Animation properties */ - Containers::Array animationInfos; - if(args.isSet("info") || args.isSet("info-animations")) for(UnsignedInt i = 0; i != importer->animationCount(); ++i) { - Containers::Optional animation; - { - Trade::Implementation::Duration d{importTime}; - if(!(animation = importer->animation(i))) { - error = true; - continue; - } - } - - AnimationInfo info{}; - info.animation = i; - info.name = importer->animationName(i); - info.data = *std::move(animation); - - arrayAppend(animationInfos, std::move(info)); - } - - /* Skin properties */ - Containers::Array skinInfos; - if(args.isSet("info") || args.isSet("info-skins")) for(UnsignedInt i = 0; i != importer->skin3DCount(); ++i) { - Containers::Optional skin; - { - Trade::Implementation::Duration d{importTime}; - if(!(skin = importer->skin3D(i))) { - error = true; - continue; - } - } - - SkinInfo info{}; - info.skin = i; - info.name = importer->skin3DName(i); - info.data = *std::move(skin); - - arrayAppend(skinInfos, std::move(info)); - } - - /* Light properties */ - Containers::Array lightInfos; - if(args.isSet("info") || args.isSet("info-lights")) for(UnsignedInt i = 0; i != importer->lightCount(); ++i) { - Containers::Optional light; - { - Trade::Implementation::Duration d{importTime}; - if(!(light = importer->light(i))) { - error = true; - continue; - } - } - - LightInfo info{}; - info.light = i; - info.name = importer->lightName(i); - info.data = *std::move(light); - - arrayAppend(lightInfos, std::move(info)); - } - - /* Material properties, together with how much is each texture shared - (which gets used only if both --info-materials and --info-textures - is passed and the file has at least one material). */ - Containers::Array materialInfos; - Containers::Array textureReferenceCount; - if((args.isSet("info") || args.isSet("info-materials")) && importer->materialCount()) { - textureReferenceCount = Containers::Array{importer->textureCount()}; - - for(UnsignedInt i = 0; i != importer->materialCount(); ++i) { - Containers::Optional material; - { - Trade::Implementation::Duration d{importTime}; - if(!(material = importer->material(i))) { - error = true; - continue; - } - } - - /* Calculate texture reference count for all properties that - look like a texture */ - for(UnsignedInt j = 0; j != material->layerCount(); ++j) { - for(UnsignedInt k = 0; k != material->attributeCount(j); ++k) { - if(material->attributeType(j, k) != Trade::MaterialAttributeType::UnsignedInt || !Utility::String::endsWith(material->attributeName(j, k), "Texture")) - continue; - - const UnsignedInt texture = material->attribute(j, k); - /** @todo once StridedBitArrayView2D exists, fix this - to count each material only once by having one bit - for every material and texture */ - if(texture < textureReferenceCount.size()) - ++textureReferenceCount[texture]; - } - } - - MaterialInfo info{}; - info.material = i; - info.name = importer->materialName(i); - info.data = *std::move(material); - - arrayAppend(materialInfos, std::move(info)); - } - } - - /* Mesh properties */ - Containers::Array meshInfos; - if(args.isSet("info") || args.isSet("info-meshes")) for(UnsignedInt i = 0; i != importer->meshCount(); ++i) { - for(UnsignedInt j = 0; j != importer->meshLevelCount(i); ++j) { - Containers::Optional mesh; - { - Trade::Implementation::Duration d{importTime}; - if(!(mesh = importer->mesh(i, j))) { - error = true; - continue; - } - } - - MeshInfo info{}; - info.mesh = i; - info.level = j; - info.primitive = mesh->primitive(); - info.vertexCount = mesh->vertexCount(); - info.vertexDataSize = mesh->vertexData().size(); - info.vertexDataFlags = mesh->vertexDataFlags(); - if(!j) { - info.name = importer->meshName(i); - } - if(mesh->isIndexed()) { - info.indexCount = mesh->indexCount(); - info.indexType = mesh->indexType(); - info.indexOffset = mesh->indexOffset(); - info.indexStride = mesh->indexStride(); - info.indexDataSize = mesh->indexData().size(); - info.indexDataFlags = mesh->indexDataFlags(); - if(args.isSet("bounds")) - info.indexBounds = calculateBounds(mesh->indicesAsArray()); - } - for(UnsignedInt k = 0; k != mesh->attributeCount(); ++k) { - const Trade::MeshAttribute name = mesh->attributeName(k); - - /* Calculate bounds, if requested, if this is not an - implementation-specific format and if it's not a custom - attribute */ - Containers::String bounds; - if(args.isSet("bounds") && !isVertexFormatImplementationSpecific(mesh->attributeFormat(k))) switch(name) { - case Trade::MeshAttribute::Position: - bounds = calculateBounds(mesh->positions3DAsArray(namedAttributeId(*mesh, k))); - break; - case Trade::MeshAttribute::Tangent: - bounds = calculateBounds(mesh->tangentsAsArray(namedAttributeId(*mesh, k))); - break; - case Trade::MeshAttribute::Bitangent: - bounds = calculateBounds(mesh->bitangentsAsArray(namedAttributeId(*mesh, k))); - break; - case Trade::MeshAttribute::Normal: - bounds = calculateBounds(mesh->normalsAsArray(namedAttributeId(*mesh, k))); - break; - case Trade::MeshAttribute::TextureCoordinates: - bounds = calculateBounds(mesh->textureCoordinates2DAsArray(namedAttributeId(*mesh, k))); - break; - case Trade::MeshAttribute::Color: - bounds = calculateBounds(mesh->colorsAsArray(namedAttributeId(*mesh, k))); - break; - case Trade::MeshAttribute::ObjectId: - Debug{} << mesh->objectIdsAsArray(namedAttributeId(*mesh, k)); - bounds = calculateBounds(mesh->objectIdsAsArray(namedAttributeId(*mesh, k))); - break; - } - - arrayAppend(info.attributes, InPlaceInit, - mesh->attributeOffset(k), - mesh->attributeStride(k), - mesh->attributeArraySize(k), - name, Trade::isMeshAttributeCustom(name) ? - importer->meshAttributeName(name) : "", - mesh->attributeFormat(k), - bounds); - } - - arrayAppend(meshInfos, std::move(info)); - } - } - - /* Texture properties, together with how much is each image shared - (which gets used only if both --info-textures and --info-images is - passed and the file has at least one texture). */ - Containers::Array textureInfos; - Containers::Array image1DReferenceCount; - Containers::Array image2DReferenceCount; - Containers::Array image3DReferenceCount; - if((args.isSet("info") || args.isSet("info-textures")) && importer->textureCount()) { - image1DReferenceCount = Containers::Array{importer->image1DCount()}; - image2DReferenceCount = Containers::Array{importer->image2DCount()}; - image3DReferenceCount = Containers::Array{importer->image3DCount()}; - for(UnsignedInt i = 0; i != importer->textureCount(); ++i) { - Containers::Optional texture; - { - Trade::Implementation::Duration d{importTime}; - if(!(texture = importer->texture(i))) { - error = true; - continue; - } - } - - switch(texture->type()) { - case Trade::TextureType::Texture1D: - if(texture->image() < image1DReferenceCount.size()) - ++image1DReferenceCount[texture->image()]; - break; - case Trade::TextureType::Texture1DArray: - case Trade::TextureType::Texture2D: - if(texture->image() < image2DReferenceCount.size()) - ++image2DReferenceCount[texture->image()]; - break; - case Trade::TextureType::CubeMap: - case Trade::TextureType::CubeMapArray: - case Trade::TextureType::Texture2DArray: - case Trade::TextureType::Texture3D: - if(texture->image() < image3DReferenceCount.size()) - ++image3DReferenceCount[texture->image()]; - break; - } - - TextureInfo info{}; - info.texture = i; - info.name = importer->textureName(i); - info.data = *std::move(texture); - - arrayAppend(textureInfos, std::move(info)); - } - } - - - Containers::Array imageInfos; - if(args.isSet("info") || args.isSet("info-images")) { - imageInfos = Trade::Implementation::imageInfo(*importer, error, importTime); - } - - std::size_t totalSceneDataSize = 0; - for(const SceneInfo& info: sceneInfos) { - Debug d{useColor}; - d << Debug::boldColor(Debug::Color::Default) << "Scene" << info.scene << Debug::nospace << ":" << Debug::resetColor; - if(info.name) d << Debug::boldColor(Debug::Color::Yellow) << info.name << Debug::resetColor; - d << Debug::newline; - d << " Bound:" << info.mappingBound << "objects" - << Debug::color(Debug::Color::Blue) << "@" << Debug::packed - << Debug::color(Debug::Color::Cyan) << info.mappingType - << Debug::resetColor << "(" << Debug::nospace - << Utility::format("{:.1f}", info.dataSize/1024.0f) << "kB"; - if(info.dataFlags != (Trade::DataFlag::Owned|Trade::DataFlag::Mutable)) - d << Debug::nospace << "," << Debug::packed - << Debug::color(Debug::Color::Green) << info.dataFlags - << Debug::resetColor; - d << Debug::nospace << ")"; - - d << Debug::newline << " Fields:"; - for(const SceneFieldInfo& field: info.fields) { - d << Debug::newline << " " - << Debug::boldColor(Debug::Color::Default); - if(Trade::isSceneFieldCustom(field.name)) { - d << "Custom(" << Debug::nospace - << Trade::sceneFieldCustom(field.name) - << Debug::nospace << ":" << Debug::nospace - << Debug::color(Debug::Color::Yellow) - << sceneFieldNames[sceneFieldCustom(field.name)] - << Debug::nospace - << Debug::boldColor(Debug::Color::Default) << ")"; - } else d << Debug::packed << field.name; - - d << Debug::color(Debug::Color::Blue) << "@" << Debug::packed << Debug::color(Debug::Color::Cyan) << field.type; - if(field.arraySize) - d << Debug::nospace << Utility::format("[{}]", field.arraySize); - d << Debug::resetColor; - if(field.flags) d << Debug::nospace << ", flags:" - << Debug::packed << Debug::color(Debug::Color::Green) - << field.flags << Debug::resetColor; - d << Debug::nospace << "," << field.size << "entries"; - } - - totalSceneDataSize += info.dataSize; - } - if(!sceneInfos.isEmpty()) - Debug{} << "Total scene data size:" << Utility::format("{:.1f}", totalSceneDataSize/1024.0f) << "kB"; - - for(const ObjectInfo& info: objectInfos) { - /* Objects without a name and not referenced by any scenes are - useless, ignore */ - if(!info.name && !info.scenes) continue; - - Debug d{useColor}; - d << Debug::boldColor(Debug::Color::Default) << "Object" << info.object << Debug::resetColor; - - if(sceneInfos) { - const UnsignedInt count = Math::popcount(info.scenes); - if(!count) d << Debug::color(Debug::Color::Red); - d << "(referenced by" << count << "scenes)"; - if(!count) d << Debug::resetColor; - } - - d << Debug::boldColor(Debug::Color::Default) << Debug::nospace << ":" - << Debug::resetColor; - if(info.name) d << Debug::boldColor(Debug::Color::Yellow) - << info.name << Debug::resetColor; - if(info.scenes) { - d << Debug::newline << " Fields:"; - - for(std::size_t i = 0; i != info.fields.size(); ++i) { - if(i) d << Debug::nospace << ","; - const Containers::Pair nameCount = info.fields[i]; - d << Debug::color(Debug::Color::Cyan); - if(Trade::isSceneFieldCustom(nameCount.first())) { - d << "Custom(" << Debug::nospace - << Trade::sceneFieldCustom(nameCount.first()) - << Debug::nospace << ":" << Debug::nospace - << Debug::color(Debug::Color::Yellow) - << sceneFieldNames[sceneFieldCustom(nameCount.first())] - << Debug::nospace - << Debug::color(Debug::Color::Cyan) << ")"; - } else d << Debug::packed << nameCount.first(); - if(nameCount.second() != 1) - d << Debug::nospace << Utility::format("[{}]", nameCount.second()); - d << Debug::resetColor; - } - } - } - - std::size_t totalAnimationDataSize = 0; - for(const AnimationInfo& info: animationInfos) { - Debug d{useColor}; - d << Debug::boldColor(Debug::Color::Default) << "Animation" << info.animation << Debug::nospace << ":" << Debug::resetColor; - if(info.name) d << Debug::boldColor(Debug::Color::Yellow) << info.name << Debug::resetColor; - - d << Debug::newline << " Duration:" << info.data.duration() - << "(" << Debug::nospace << Utility::format("{:.1f}", info.data.data().size()/1024.0f) << "kB"; - if(info.data.dataFlags() != (Trade::DataFlag::Owned|Trade::DataFlag::Mutable)) - d << Debug::nospace << "," << Debug::packed - << Debug::color(Debug::Color::Green) - << info.data.dataFlags() << Debug::resetColor; - d << Debug::nospace << ")"; - - for(UnsignedInt i = 0; i != info.data.trackCount(); ++i) { - d << Debug::newline << " Track" << i << Debug::nospace << ":" - << Debug::packed << Debug::boldColor(Debug::Color::Default) - << info.data.trackTargetType(i) - << Debug::color(Debug::Color::Blue) << "@" - << Debug::packed << Debug::color(Debug::Color::Cyan) - << info.data.trackType(i) << Debug::resetColor; - if(info.data.trackType(i) != info.data.trackResultType(i)) - d << Debug::color(Debug::Color::Blue) << "->" - << Debug::packed << Debug::color(Debug::Color::Cyan) - << info.data.trackResultType(i) << Debug::resetColor; - d << Debug::nospace << "," << info.data.track(i).size() - << "keyframes"; - if(info.data.track(i).duration() != info.data.duration()) - d << Debug::nospace << "," << info.data.track(i).duration(); - d << Debug::newline - << " Interpolation:" - << Debug::packed << Debug::color(Debug::Color::Cyan) - << info.data.track(i).interpolation() << Debug::resetColor - << Debug::nospace << "," << Debug::packed - << Debug::color(Debug::Color::Cyan) - << info.data.track(i).before() << Debug::resetColor - << Debug::nospace << "," << Debug::packed - << Debug::color(Debug::Color::Cyan) - << info.data.track(i).after() << Debug::resetColor; - /** @todo might be useful to show bounds here as well, though - not so much for things like complex numbers or quats */ - } - - totalAnimationDataSize += info.data.data().size(); - } - if(!animationInfos.isEmpty()) - Debug{} << "Total animation data size:" << Utility::format("{:.1f}", totalAnimationDataSize/1024.0f) << "kB"; - - for(const SkinInfo& info: skinInfos) { - Debug d{useColor}; - d << Debug::boldColor(Debug::Color::Default) << "Skin" << info.skin - << Debug::resetColor; - - /* Print reference count only if there actually are scenes and they - were parsed, otherwise this information is useless */ - if(skinReferenceCount) { - const UnsignedInt count = skinReferenceCount[info.skin]; - if(!count) d << Debug::color(Debug::Color::Red); - d << "(referenced by" << count << "objects)"; - if(!count) d << Debug::resetColor; - } - - d << Debug::boldColor(Debug::Color::Default) << Debug::nospace << ":" - << Debug::resetColor; - if(info.name) d << Debug::boldColor(Debug::Color::Yellow) - << info.name << Debug::resetColor; - - d << Debug::newline << " " << info.data.joints().size() << "joints"; - } - - for(const LightInfo& info: lightInfos) { - Debug d{useColor}; - d << Debug::boldColor(Debug::Color::Default) << "Light" << info.light << Debug::resetColor; - - /* Print reference count only if there actually are scenes and they - were parsed, otherwise this information is useless */ - if(lightReferenceCount) { - const UnsignedInt count = lightReferenceCount[info.light]; - if(!count) d << Debug::color(Debug::Color::Red); - d << "(referenced by" << count << "objects)"; - if(!count) d << Debug::resetColor; - } - - d << Debug::boldColor(Debug::Color::Default) << Debug::nospace << ":" - << Debug::resetColor; - if(info.name) d << Debug::boldColor(Debug::Color::Yellow) - << info.name << Debug::resetColor; - - d << Debug::newline << " Type:" << Debug::packed - << Debug::color(Debug::Color::Cyan) - << info.data.type() << Debug::resetColor; - if(info.data.type() == Trade::LightData::Type::Spot) - d << Debug::nospace << "," << Debug::packed - << Deg(info.data.innerConeAngle()) << Debug::nospace - << "° -" << Debug::packed << Deg(info.data.outerConeAngle()) - << Debug::nospace << "°"; - d << Debug::newline << " Color:"; - if(useColor24) d << Debug::color - << Math::pack(info.data.color()); - d << Debug::packed << info.data.color(); - if(!Math::equal(info.data.intensity(), 1.0f)) - d << "*" << info.data.intensity(); - d << Debug::newline << " Attenuation:" << Debug::packed - << info.data.attenuation(); - if(info.data.range() != Constants::inf()) - d << Debug::newline << " Range:" << Debug::packed - << info.data.range(); - } - - for(const MaterialInfo& info: materialInfos) { - Debug d{useColor}; - d << Debug::boldColor(Debug::Color::Default) << "Material" << info.material << Debug::resetColor; - - /* Print reference count only if there actually are scenes and they - were parsed, otherwise this information is useless */ - if(materialReferenceCount) { - const UnsignedInt count = materialReferenceCount[info.material]; - if(!count) d << Debug::color(Debug::Color::Red); - d << "(referenced by" << count << "objects)"; - if(!count) d << Debug::resetColor; - } - - d << Debug::boldColor(Debug::Color::Default) << Debug::nospace << ":" - << Debug::resetColor; - if(info.name) d << Debug::boldColor(Debug::Color::Yellow) << info.name << Debug::resetColor; - - d << Debug::newline << " Type:" << Debug::packed << Debug::color(Debug::Color::Cyan) << info.data.types() << Debug::resetColor; - - for(UnsignedInt i = 0; i != info.data.layerCount(); ++i) { - /* Print extra layers with extra indent */ - const char* indent; - if(info.data.layerCount() != 1 && i != 0) { - d << Debug::newline << " Layer" << i << Debug::nospace << ":"; - if(!info.data.layerName(i).isEmpty()) { - if(std::isupper(info.data.layerName(i)[0])) - d << Debug::boldColor(Debug::Color::Default); - else - d << Debug::color(Debug::Color::Yellow); - d << info.data.layerName(i) << Debug::resetColor; - } - indent = " "; - } else { - d << Debug::newline << " Base layer:"; - indent = " "; - } - - for(UnsignedInt j = 0; j != info.data.attributeCount(i); ++j) { - /* Ignore layer name (which is always first) unless it's in - the base material, in which case we print it as it - wouldn't otherwise be shown anywhere */ - if(i && !j && info.data.attributeName(i, j) == " LayerName") - continue; - - d << Debug::newline << indent; - if(std::isupper(info.data.attributeName(i, j)[0])) - d << Debug::boldColor(Debug::Color::Default); - else - d << Debug::color(Debug::Color::Yellow); - d << info.data.attributeName(i, j) << Debug::color(Debug::Color::Blue) << "@" << Debug::packed << Debug::color(Debug::Color::Cyan) - << info.data.attributeType(i, j) << Debug::resetColor << Debug::nospace - << ":"; - switch(info.data.attributeType(i, j)) { - case Trade::MaterialAttributeType::Bool: - d << info.data.attribute(i, j); - break; - #define _c(type) case Trade::MaterialAttributeType::type: \ - d << Debug::packed << info.data.attribute(i, j); \ - break; - _c(Float) - _c(Deg) - _c(Rad) - _c(UnsignedInt) - _c(Int) - _c(UnsignedLong) - _c(Long) - _c(Vector2) - _c(Vector2ui) - _c(Vector2i) - case Trade::MaterialAttributeType::Vector3: - /** @todo hasSuffix() might be more robust against - false positives, but KHR_materials_specular in - glTF uses ColorFactor :/ */ - if(useColor24 && info.data.attributeName(i, j).contains("Color"_s)) - d << Debug::color << Math::pack(info.data.attribute(i, j)); - d << Debug::packed << info.data.attribute(i, j); - break; - _c(Vector3ui) - _c(Vector3i) - case Trade::MaterialAttributeType::Vector4: - /** @todo hasSuffix() might be more robust against - false positives, but KHR_materials_specular in - glTF uses ColorFactor :/ */ - if(useColor24 && info.data.attributeName(i, j).contains("Color"_s)) - d << Debug::color << Math::pack(info.data.attribute(i, j).rgb()); - d << Debug::packed << info.data.attribute(i, j); - break; - _c(Vector4ui) - _c(Vector4i) - _c(Matrix2x2) - _c(Matrix2x3) - _c(Matrix2x4) - _c(Matrix3x2) - _c(Matrix3x3) - _c(Matrix3x4) - _c(Matrix4x2) - _c(Matrix4x3) - #undef _c - case Trade::MaterialAttributeType::Pointer: - d << info.data.attribute(i, j); - break; - case Trade::MaterialAttributeType::MutablePointer: - d << info.data.attribute(i, j); - break; - case Trade::MaterialAttributeType::String: - d << info.data.attribute(i, j); - break; - case Trade::MaterialAttributeType::TextureSwizzle: - d << Debug::packed << info.data.attribute(i, j); - break; - } - } - } - } - - std::size_t totalMeshDataSize = 0; - for(const MeshInfo& info: meshInfos) { - Debug d{useColor}; - if(info.level == 0) { - d << Debug::boldColor(Debug::Color::Default) << "Mesh" << info.mesh << Debug::resetColor; - - /* Print reference count only if there actually are scenes and - they were parsed, otherwise this information is useless */ - if(meshReferenceCount) { - const UnsignedInt count = meshReferenceCount[info.mesh]; - if(!count) d << Debug::color(Debug::Color::Red); - d << "(referenced by" << count << "objects)"; - if(!count) d << Debug::resetColor; - } - - d << Debug::boldColor(Debug::Color::Default) << Debug::nospace << ":" - << Debug::resetColor; - if(info.name) d << Debug::boldColor(Debug::Color::Yellow) << info.name << Debug::resetColor; - d << Debug::newline; - } - d << " Level" << info.level << Debug::nospace << ":" - << info.vertexCount << "vertices" << Debug::color(Debug::Color::Blue) << "@" << Debug::packed << Debug::color(Debug::Color::Cyan) << info.primitive << Debug::resetColor << "(" << Debug::nospace - << Utility::format("{:.1f}", info.vertexDataSize/1024.0f) - << "kB"; - if(info.vertexDataFlags != (Trade::DataFlag::Owned|Trade::DataFlag::Mutable)) - d << Debug::nospace << ", flags:" << Debug::packed - << Debug::color(Debug::Color::Green) - << info.vertexDataFlags << Debug::resetColor; - d << Debug::nospace << ")"; - - for(const MeshAttributeInfo& attribute: info.attributes) { - d << Debug::newline << " " - << Debug::boldColor(Debug::Color::Default); - if(Trade::isMeshAttributeCustom(attribute.name)) { - d << "Custom(" << Debug::nospace - << Trade::meshAttributeCustom(attribute.name) - << Debug::nospace << ":" << Debug::nospace - << Debug::color(Debug::Color::Yellow) - << attribute.customName << Debug::nospace - << Debug::boldColor(Debug::Color::Default) << ")"; - } else d << Debug::packed << attribute.name; - - d << Debug::color(Debug::Color::Blue) << "@" << Debug::packed << Debug::color(Debug::Color::Cyan) << attribute.format; - if(attribute.arraySize) - d << Debug::nospace << Utility::format("[{}]", attribute.arraySize); - d << Debug::resetColor; - d << Debug::nospace << ", offset" << attribute.offset; - d << Debug::nospace << ", stride" - << attribute.stride; - if(attribute.bounds) - d << Debug::newline << " bounds:" << attribute.bounds; - } - - if(info.indexType != MeshIndexType{}) { - d << Debug::newline << " " << info.indexCount << "indices" << Debug::color(Debug::Color::Blue) << "@" - << Debug::packed << Debug::color(Debug::Color::Cyan) << info.indexType << Debug::resetColor << Debug::nospace << ", offset" << info.indexOffset << Debug::nospace << ", stride" << info.indexStride << "(" << Debug::nospace - << Utility::format("{:.1f}", info.indexDataSize/1024.0f) - << "kB"; - if(info.indexDataFlags != (Trade::DataFlag::Owned|Trade::DataFlag::Mutable)) - d << Debug::nospace << ", flags:" << Debug::packed - << Debug::color(Debug::Color::Green) << info.indexDataFlags << Debug::resetColor; - d << Debug::nospace << ")"; - if(info.indexBounds) - d << Debug::newline << " bounds:" << info.indexBounds; - } - - totalMeshDataSize += info.vertexDataSize + info.indexDataSize; - } - if(!meshInfos.isEmpty()) - Debug{} << "Total mesh data size:" << Utility::format("{:.1f}", totalMeshDataSize/1024.0f) << "kB"; - - for(const TextureInfo& info: textureInfos) { - Debug d{useColor}; - d << Debug::boldColor(Debug::Color::Default) << "Texture" << info.texture << Debug::resetColor; - - /* Print reference count only if there actually are materials and - they were parsed, otherwise this information is useless */ - if(textureReferenceCount) { - const UnsignedInt count = textureReferenceCount[info.texture]; - if(!count) d << Debug::color(Debug::Color::Red); - d << "(referenced by" << count << "material attributes)"; - if(!count) d << Debug::resetColor; - } - - d << Debug::boldColor(Debug::Color::Default) << Debug::nospace << ":" - << Debug::resetColor; - if(info.name) d << Debug::boldColor(Debug::Color::Yellow) - << info.name << Debug::resetColor; - d << Debug::newline; - d << " Type:" - << Debug::packed - << Debug::color(Debug::Color::Cyan) << info.data.type() - << Debug::resetColor << Debug::nospace << ", image" - << info.data.image(); - d << Debug::newline << " Minification, mipmap and magnification:" - << Debug::packed << Debug::color(Debug::Color::Cyan) - << info.data.minificationFilter() << Debug::nospace << "," - << Debug::packed << Debug::color(Debug::Color::Cyan) - << info.data.mipmapFilter() << Debug::nospace << "," - << Debug::packed << Debug::color(Debug::Color::Cyan) - << info.data.magnificationFilter() << Debug::resetColor; - d << Debug::newline << " Wrapping:" << Debug::resetColor << "{" << Debug::nospace - << Debug::packed << Debug::color(Debug::Color::Cyan) - << info.data.wrapping()[0] << Debug::resetColor - << Debug::nospace << "," << Debug::packed - << Debug::color(Debug::Color::Cyan) << info.data.wrapping()[1] - << Debug::resetColor << Debug::nospace << "," << Debug::packed - << Debug::color(Debug::Color::Cyan) << info.data.wrapping()[1] - << Debug::resetColor << Debug::nospace << "}"; - } - - std::size_t totalImageDataSize = 0; - for(const Trade::Implementation::ImageInfo& info: imageInfos) { - Debug d{useColor}; - if(info.level == 0) { - d << Debug::boldColor(Debug::Color::Default); - if(info.size.z()) d << "3D image"; - else if(info.size.y()) d << "2D image"; - else d << "1D image"; - d << info.image << Debug::resetColor; - - /* Print reference count only if there actually are textures - and they were parsed otherwise this information is - useless */ - Containers::Optional count; - if(info.size.z() && image3DReferenceCount) { - count = image3DReferenceCount[info.image]; - } else if(info.size.y() && image2DReferenceCount) { - count = image2DReferenceCount[info.image]; - } else if(image1DReferenceCount) { - count = image1DReferenceCount[info.image]; - } - if(count) { - if(!*count) d << Debug::color(Debug::Color::Red); - d << "(referenced by" << *count << "textures)"; - if(!*count) d << Debug::resetColor; - } - - d << Debug::boldColor(Debug::Color::Default) << Debug::nospace << ":" - << Debug::resetColor; - if(info.name) d << Debug::boldColor(Debug::Color::Yellow) - << info.name << Debug::resetColor; - d << Debug::newline; - } - d << " Level" << info.level << Debug::nospace << ":"; - if(info.flags.one) { - d << Debug::packed << Debug::color(Debug::Color::Cyan); - if(info.size.z()) d << info.flags.three; - else if(info.size.y()) d << info.flags.two; - else d << info.flags.one; - d << Debug::resetColor; - } - d << Debug::packed; - if(info.size.z()) d << info.size; - else if(info.size.y()) d << info.size.xy(); - else d << Math::Vector<1, Int>(info.size.x()); - d << Debug::color(Debug::Color::Blue) << "@" << Debug::resetColor; - d << Debug::packed; - if(info.compressed) d << Debug::color(Debug::Color::Yellow) << info.compressedFormat; - else d << Debug::color(Debug::Color::Cyan) << info.format; - d << Debug::resetColor << "(" << Debug::nospace << Utility::format("{:.1f}", info.dataSize/1024.0f) << "kB"; - if(info.dataFlags != (Trade::DataFlag::Owned|Trade::DataFlag::Mutable)) - d << Debug::nospace << "," << Debug::packed - << Debug::color(Debug::Color::Green) << info.dataFlags - << Debug::resetColor; - d << Debug::nospace << ")"; - - totalImageDataSize += info.dataSize; - } - if(!imageInfos.isEmpty()) - Debug{} << "Total image data size:" << Utility::format("{:.1f}", totalImageDataSize/1024.0f) << "kB"; + const bool error = SceneTools::Implementation::printInfo(useColor, useColor24, args, *importer, importTime); if(args.isSet("profile")) { Debug{} << "Import took" << UnsignedInt(std::chrono::duration_cast(importTime).count())/1.0e3f << "seconds"; diff --git a/src/Magnum/Trade/Implementation/converterUtilities.h b/src/Magnum/Trade/Implementation/converterUtilities.h index 6c1d4926f..d6dbcf57d 100644 --- a/src/Magnum/Trade/Implementation/converterUtilities.h +++ b/src/Magnum/Trade/Implementation/converterUtilities.h @@ -28,13 +28,17 @@ #include #include #include +#include +#include +#include "Magnum/PixelFormat.h" #include "Magnum/Trade/AbstractImporter.h" #include "Magnum/Trade/ImageData.h" namespace Magnum { namespace Trade { namespace Implementation { -/* Used only in executables where we don't want it to be exported */ +/* Used only in executables where we don't want it to be exported -- in + particular magnum-imageconverter, magnum-sceneconverter and their tests */ namespace { struct Duration { @@ -75,7 +79,7 @@ struct ImageInfo { Containers::Array imageInfo(AbstractImporter& importer, bool& error, std::chrono::high_resolution_clock::duration& importTime) { Containers::Array infos; for(UnsignedInt i = 0; i != importer.image1DCount(); ++i) { - const std::string name = importer.image1DName(i); + const Containers::String name = importer.image1DName(i); const UnsignedInt levelCount = importer.image1DLevelCount(i); for(UnsignedInt j = 0; j != levelCount; ++j) { @@ -97,11 +101,11 @@ Containers::Array imageInfo(AbstractImporter& importer, bool& error, image->data().size(), image->dataFlags(), ImageInfoFlags{image->flags()}, - j ? "" : importer.image1DName(i)); + j ? "" : name); } } for(UnsignedInt i = 0; i != importer.image2DCount(); ++i) { - const std::string name = importer.image2DName(i); + const Containers::String name = importer.image2DName(i); const UnsignedInt levelCount = importer.image2DLevelCount(i); for(UnsignedInt j = 0; j != levelCount; ++j) { @@ -127,7 +131,7 @@ Containers::Array imageInfo(AbstractImporter& importer, bool& error, } } for(UnsignedInt i = 0; i != importer.image3DCount(); ++i) { - const std::string name = importer.image3DName(i); + const Containers::String name = importer.image3DName(i); const UnsignedInt levelCount = importer.image3DLevelCount(i); for(UnsignedInt j = 0; j != levelCount; ++j) { @@ -156,6 +160,73 @@ Containers::Array imageInfo(AbstractImporter& importer, bool& error, return infos; } +void printImageInfo(const Debug::Flags useColor, const Containers::ArrayView imageInfos, const Containers::ArrayView image1DReferenceCount, const Containers::ArrayView image2DReferenceCount, const Containers::ArrayView image3DReferenceCount) { + std::size_t totalImageDataSize = 0; + for(const Trade::Implementation::ImageInfo& info: imageInfos) { + Debug d{useColor}; + if(info.level == 0) { + d << Debug::boldColor(Debug::Color::Default); + if(info.size.z()) d << "3D image"; + else if(info.size.y()) d << "2D image"; + else d << "1D image"; + d << info.image << Debug::resetColor; + + /* Print reference count only if there actually are any (i.e., the + arrays are non-empty) otherwise this information is useless */ + Containers::Optional count; + if(info.size.z() && image3DReferenceCount) { + count = image3DReferenceCount[info.image]; + } else if(info.size.y() && image2DReferenceCount) { + count = image2DReferenceCount[info.image]; + } else if(image1DReferenceCount) { + count = image1DReferenceCount[info.image]; + } + if(count) { + if(!*count) d << Debug::color(Debug::Color::Red); + d << "(referenced by" << *count << "textures)"; + if(!*count) d << Debug::resetColor; + } + + d << Debug::boldColor(Debug::Color::Default) << Debug::nospace << ":" + << Debug::resetColor; + if(info.name) d << Debug::boldColor(Debug::Color::Yellow) + << info.name << Debug::resetColor; + d << Debug::newline; + } + d << " Level" << info.level << Debug::nospace << ":"; + if(info.flags.one) { + d << Debug::packed << Debug::color(Debug::Color::Cyan); + if(info.size.z()) d << info.flags.three; + else if(info.size.y()) d << info.flags.two; + else d << info.flags.one; + d << Debug::resetColor; + } + d << Debug::packed; + if(info.size.z()) d << info.size; + else if(info.size.y()) d << info.size.xy(); + /* Kinda unnecessary, but makes the output more consistent if also 1D + size is in {}s */ + else d << Math::Vector<1, Int>(info.size.x()); + d << Debug::color(Debug::Color::Blue) << "@" << Debug::resetColor; + d << Debug::packed; + /* Compressed formats are printed yellow. That kinda conflicts with + custom fields / attributes elsewhere, but is significant enough to + have it highlighted. */ + if(info.compressed) d << Debug::color(Debug::Color::Yellow) << info.compressedFormat; + else d << Debug::color(Debug::Color::Cyan) << info.format; + d << Debug::resetColor << "(" << Debug::nospace << Utility::format("{:.1f}", info.dataSize/1024.0f) << "kB"; + if(info.dataFlags != (Trade::DataFlag::Owned|Trade::DataFlag::Mutable)) + d << Debug::nospace << "," << Debug::packed + << Debug::color(Debug::Color::Green) << info.dataFlags + << Debug::resetColor; + d << Debug::nospace << ")"; + + totalImageDataSize += info.dataSize; + } + if(!imageInfos.isEmpty()) + Debug{} << "Total image data size:" << Utility::format("{:.1f}", totalImageDataSize/1024.0f) << "kB"; +} + } }}} diff --git a/src/Magnum/Trade/Test/CMakeLists.txt b/src/Magnum/Trade/Test/CMakeLists.txt index 967f91aa7..3360dd59c 100644 --- a/src/Magnum/Trade/Test/CMakeLists.txt +++ b/src/Magnum/Trade/Test/CMakeLists.txt @@ -54,6 +54,13 @@ corrade_add_test(TradeAnimationDataTest AnimationDataTest.cpp LIBRARIES MagnumTr corrade_add_test(TradeCameraDataTest CameraDataTest.cpp LIBRARIES MagnumTradeTestLib) corrade_add_test(TradeDataTest DataTest.cpp LIBRARIES MagnumTrade) corrade_add_test(TradeFlatMaterialDataTest FlatMaterialDataTest.cpp LIBRARIES MagnumTradeTestLib) + +corrade_add_test(TradeImageConverterTest ImageConverterTest.cpp + LIBRARIES MagnumTrade + FILES + ImageConverterTestFiles/info.txt) +target_include_directories(TradeImageConverterTest PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) + corrade_add_test(TradeImageDataTest ImageDataTest.cpp LIBRARIES MagnumTradeTestLib) corrade_add_test(TradeLightDataTest LightDataTest.cpp LIBRARIES MagnumTradeTestLib) corrade_add_test(TradeMaterialDataTest MaterialDataTest.cpp LIBRARIES MagnumTradeTestLib) diff --git a/src/Magnum/Trade/Test/ImageConverterTest.cpp b/src/Magnum/Trade/Test/ImageConverterTest.cpp new file mode 100644 index 000000000..812caf3aa --- /dev/null +++ b/src/Magnum/Trade/Test/ImageConverterTest.cpp @@ -0,0 +1,192 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, + 2020, 2021, 2022 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 /** @todo remove once Debug is stream-free */ +#include +#include +#include /** @todo remove once Debug is stream-free */ +#include + +#include "Magnum/Trade/Implementation/converterUtilities.h" + +#include "configure.h" + +namespace Magnum { namespace Trade { namespace Test { namespace { + +struct ImageConverterTest: TestSuite::Tester { + explicit ImageConverterTest(); + + void infoImplementation(); + void infoImplementationError(); +}; + +ImageConverterTest::ImageConverterTest() { + addTests({&ImageConverterTest::infoImplementation, + &ImageConverterTest::infoImplementationError}); +} + +void ImageConverterTest::infoImplementation() { + struct Importer: Trade::AbstractImporter { + Trade::ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + /* Three 1D images, one with two levels and named, one compressed, + one just to not have two of everything */ + UnsignedInt doImage1DCount() const override { return 3; } + UnsignedInt doImage1DLevelCount(UnsignedInt id) override { + return id == 1 ? 2 : 1; + } + Containers::String doImage1DName(UnsignedInt id) override { + return id == 2 ? "Third 1D image just so there aren't two" : ""; + } + Containers::Optional doImage1D(UnsignedInt id, UnsignedInt level) override { + if(id == 0 && level == 0) + return Trade::ImageData1D{CompressedPixelFormat::Astc10x10RGBAF, 1024, Containers::Array{NoInit, 4096}}; + if(id == 1 && level == 0) + return Trade::ImageData1D{PixelFormat::RGBA8Snorm, 16, Containers::Array{NoInit, 64}}; + if(id == 1 && level == 1) + return Trade::ImageData1D{PixelFormat::RGBA8Snorm, 8, Containers::Array{NoInit, 32}}; + if(id == 2 && level == 0) + return Trade::ImageData1D{PixelFormat::Depth16Unorm, 4, Containers::Array{NoInit, 8}}; + CORRADE_INTERNAL_ASSERT_UNREACHABLE(); + } + + /* Two 2D images, one with three levels and named, the other compressed + and array */ + UnsignedInt doImage2DCount() const override { return 2; } + UnsignedInt doImage2DLevelCount(UnsignedInt id) override { + return id == 0 ? 3 : 1; + } + Containers::String doImage2DName(UnsignedInt id) override { + return id == 0 ? "A very nice mipmapped 2D image" : ""; + } + Containers::Optional doImage2D(UnsignedInt id, UnsignedInt level) override { + if(id == 0 && level == 0) + return Trade::ImageData2D{PixelFormat::RG16F, {256, 128}, Containers::Array{NoInit, 131072}}; + if(id == 0 && level == 1) + return Trade::ImageData2D{PixelFormat::RG16F, {128, 64}, Containers::Array{NoInit, 32768}}; + if(id == 0 && level == 2) + return Trade::ImageData2D{PixelFormat::RG16F, {64, 32}, Containers::Array{NoInit, 8192}}; + if(id == 1) + return Trade::ImageData2D{CompressedPixelFormat::PvrtcRGB2bppUnorm, {4, 8}, Containers::Array{NoInit, 32}, ImageFlag2D::Array}; + CORRADE_INTERNAL_ASSERT_UNREACHABLE(); + } + + /* One 2D cube map array image, one 3D mipmapped & named and two 2D + array; with one externally owned */ + UnsignedInt doImage3DCount() const override { return 4; } + UnsignedInt doImage3DLevelCount(UnsignedInt id) override { + return id == 1 ? 2 : 1; + } + Containers::String doImage3DName(UnsignedInt id) override { + return id == 1 ? "Volume kills!" : ""; + } + Containers::Optional doImage3D(UnsignedInt id, UnsignedInt level) override { + if(id == 0 && level == 0) + return Trade::ImageData3D{PixelFormat::R8Unorm, {16, 16, 12}, Containers::Array{NoInit, 3072}, ImageFlag3D::CubeMap|ImageFlag3D::Array}; + if(id == 1 && level == 0) + return Trade::ImageData3D{PixelFormat::R8Unorm, {16, 16, 16}, Containers::Array{NoInit, 4096}}; + if(id == 1 && level == 1) + return Trade::ImageData3D{PixelFormat::R8Unorm, {8, 8, 6}, Containers::Array{NoInit, 2048}}; + if(id == 2 && level == 0) + return Trade::ImageData3D{CompressedPixelFormat::Bc1RGBSrgb, {4, 1, 1}, Containers::Array{NoInit, 16}, ImageFlag3D::Array}; + if(id == 3 && level == 0) + return Trade::ImageData3D{PixelFormat::R32F, {1, 4, 1}, Trade::DataFlag::ExternallyOwned|Trade::DataFlag::Mutable, data, ImageFlag3D::Array}; + CORRADE_INTERNAL_ASSERT_UNREACHABLE(); + } + + char data[16]; + } importer; + + bool error = false; + std::chrono::high_resolution_clock::duration time; + Containers::Array infos = Implementation::imageInfo(importer, error, time); + CORRADE_VERIFY(!error); + CORRADE_COMPARE(infos.size(), 13); + + /* Print to visually verify coloring */ + { + Debug{} << "======================== visual color verification start ======================="; + Implementation::printImageInfo(Debug::isTty() ? Debug::Flags{} : Debug::Flag::DisableColors, infos, nullptr, nullptr, nullptr); + Debug{} << "======================== visual color verification end ========================="; + } + + std::ostringstream out; + Debug redirectOutput{&out}; + Implementation::printImageInfo(Debug::Flag::DisableColors, infos, nullptr, nullptr, nullptr); + CORRADE_COMPARE_AS(out.str(), + Utility::Path::join(TRADE_TEST_DIR, "ImageConverterTestFiles/info.txt"), + TestSuite::Compare::StringToFile); +} + +void ImageConverterTest::infoImplementationError() { + struct Importer: Trade::AbstractImporter { + Trade::ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + UnsignedInt doImage1DCount() const override { return 2; } + Containers::Optional doImage1D(UnsignedInt id, UnsignedInt) override { + Error{} << "1D image" << id << "error!"; + return {}; + } + + UnsignedInt doImage2DCount() const override { return 2; } + Containers::Optional doImage2D(UnsignedInt id, UnsignedInt) override { + Error{} << "2D image" << id << "error!"; + return {}; + } + + UnsignedInt doImage3DCount() const override { return 2; } + Containers::Optional doImage3D(UnsignedInt id, UnsignedInt) override { + Error{} << "3D image" << id << "error!"; + return {}; + } + } importer; + + bool error = false; + std::chrono::high_resolution_clock::duration time; + std::ostringstream out; + Debug redirectOutput{&out}; + Error redirectError{&out}; + Containers::Array infos = Implementation::imageInfo(importer, error, time); + /* It should return a failure and no output */ + CORRADE_VERIFY(error); + CORRADE_VERIFY(infos.isEmpty()); + /* But it should not exit after first error */ + CORRADE_COMPARE(out.str(), + "1D image 0 error!\n" + "1D image 1 error!\n" + "2D image 0 error!\n" + "2D image 1 error!\n" + "3D image 0 error!\n" + "3D image 1 error!\n"); +} + +}}}} + +CORRADE_TEST_MAIN(Magnum::Trade::Test::ImageConverterTest) diff --git a/src/Magnum/Trade/Test/ImageConverterTestFiles/info.txt b/src/Magnum/Trade/Test/ImageConverterTestFiles/info.txt new file mode 100644 index 000000000..bff917a68 --- /dev/null +++ b/src/Magnum/Trade/Test/ImageConverterTestFiles/info.txt @@ -0,0 +1,23 @@ +1D image 0: + Level 0: {1024} @ Astc10x10RGBAF (4.0 kB) +1D image 1: + Level 0: {16} @ RGBA8Snorm (0.1 kB) + Level 1: {8} @ RGBA8Snorm (0.0 kB) +1D image 2: Third 1D image just so there aren't two + Level 0: {4} @ Depth16Unorm (0.0 kB) +2D image 0: A very nice mipmapped 2D image + Level 0: {256, 128} @ RG16F (128.0 kB) + Level 1: {128, 64} @ RG16F (32.0 kB) + Level 2: {64, 32} @ RG16F (8.0 kB) +2D image 1: + Level 0: Array {4, 8} @ PvrtcRGB2bppUnorm (0.0 kB) +3D image 0: + Level 0: Array|CubeMap {16, 16, 12} @ R8Unorm (3.0 kB) +3D image 1: Volume kills! + Level 0: {16, 16, 16} @ R8Unorm (4.0 kB) + Level 1: {8, 8, 6} @ R8Unorm (2.0 kB) +3D image 2: + Level 0: Array {4, 1, 1} @ Bc1RGBSrgb (0.0 kB) +3D image 3: + Level 0: Array {1, 4, 1} @ R32F (0.0 kB, ExternallyOwned|Mutable) +Total image data size: 181.2 kB diff --git a/src/Magnum/Trade/imageconverter.cpp b/src/Magnum/Trade/imageconverter.cpp index a2e45731a..95657e8ee 100644 --- a/src/Magnum/Trade/imageconverter.cpp +++ b/src/Magnum/Trade/imageconverter.cpp @@ -595,47 +595,7 @@ no -C / --converter is specified, AnyImageConverter is used.)") else useColor = Debug::isTty() ? Debug::Flags{} : Debug::Flag::DisableColors; - std::size_t totalImageDataSize = 0; - for(const Trade::Implementation::ImageInfo& info: infos) { - Debug d{useColor}; - if(info.level == 0) { - d << Debug::boldColor(Debug::Color::Default); - if(info.size.z()) d << "3D image"; - else if(info.size.y()) d << "2D image"; - else d << "1D image"; - d << info.image << Debug::nospace << ":" - << Debug::resetColor; - if(info.name) d << Debug::boldColor(Debug::Color::Yellow) - << info.name << Debug::resetColor; - d << Debug::newline; - } - d << " Level" << info.level << Debug::nospace << ":"; - if(info.flags.one) { - d << Debug::packed << Debug::color(Debug::Color::Cyan); - if(info.size.z()) d << info.flags.three; - else if(info.size.y()) d << info.flags.two; - else d << info.flags.one; - d << Debug::resetColor; - } - d << Debug::packed; - if(info.size.z()) d << info.size; - else if(info.size.y()) d << info.size.xy(); - else d << Math::Vector<1, Int>(info.size.x()); - d << Debug::color(Debug::Color::Blue) << "@" << Debug::resetColor; - d << Debug::packed; - if(info.compressed) d << Debug::color(Debug::Color::Yellow) << info.compressedFormat; - else d << Debug::color(Debug::Color::Cyan) << info.format; - d << Debug::resetColor << "(" << Debug::nospace << Utility::format("{:.1f}", info.dataSize/1024.0f) << "kB"; - if(info.dataFlags != (Trade::DataFlag::Owned|Trade::DataFlag::Mutable)) - d << Debug::nospace << "," << Debug::packed - << Debug::color(Debug::Color::Green) - << info.dataFlags << Debug::resetColor; - d << Debug::nospace << ")"; - - totalImageDataSize += info.dataSize; - } - if(!infos.isEmpty()) - Debug{} << "Total image data size:" << Utility::format("{:.1f}", totalImageDataSize/1024.0f) << "kB"; + Trade::Implementation::printImageInfo(useColor, infos, nullptr, nullptr, nullptr); if(args.isSet("profile")) { Debug{} << "Import took" << UnsignedInt(std::chrono::duration_cast(importTime).count())/1.0e3f << "seconds";