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";