You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

1140 lines
52 KiB

#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š <mosra@centrum.cz>
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 <cctype> /* std::isupper() */
#include <sstream>
#include <unordered_map> /* sceneFieldNames */
#include <Corrade/Containers/Optional.h>
#include <Corrade/Containers/Pair.h>
#include <Corrade/Utility/Arguments.h>
#include "Magnum/Math/FunctionsBatch.h"
#include "Magnum/SceneTools/Hierarchy.h"
#include "Magnum/Trade/AbstractSceneConverter.h"
#include "Magnum/Trade/AnimationData.h"
#include "Magnum/Trade/CameraData.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 {
void printSceneConverterInfo(const Debug::Flags useColor, const Trade::AbstractSceneConverter& converter) {
Trade::Implementation::printPluginInfo(useColor, converter);
Trade::Implementation::printPluginConfigurationInfo(useColor, converter);
}
/** @todo const Array& doesn't work, minmax() would fail to match */
template<class T> Containers::String calculateBounds(Containers::Array<T>&& attribute) {
/** @todo clean up when Debug::toString() exists */
std::ostringstream out;
Debug{&out, Debug::Flag::NoNewlineAtTheEnd} << Debug::packed << Math::minmax(attribute);
return out.str();
}
bool printInfo(const Debug::Flags useColor, const bool useColor24, const Utility::Arguments& args, Trade::AbstractImporter& importer, std::chrono::high_resolution_clock::duration& importTime) {
struct AnimationTrackInfo {
Trade::AnimationTrackTarget targetName;
Containers::String customTargetName;
Trade::AnimationTrackType type, resultType;
Animation::Interpolation interpolation;
Animation::Extrapolation before, after;
Range1D duration;
std::size_t size;
};
struct AnimationInfo {
UnsignedInt animation;
Range1D duration;
Containers::Array<AnimationTrackInfo> tracks;
std::size_t dataSize;
Trade::DataFlags dataFlags;
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 CameraInfo {
UnsignedInt camera;
Trade::CameraData 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<MeshAttributeInfo> 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<SceneFieldInfo> fields;
std::size_t dataSize;
Trade::DataFlags dataFlags;
Containers::String name;
/* Populated only if --object-hierarchy is set */
Containers::Array<Containers::Pair<UnsignedInt, UnsignedInt>> childrenDepthFirst;
};
struct ObjectInfo {
UnsignedLong object;
/* A bitfield, assuming no more than 32 scenes */
/** @todo might be too little? */
UnsignedInt scenes;
Containers::Array<Containers::Pair<Trade::SceneField, UnsignedInt>> fields;
Containers::String name;
};
/* Parse everything first to avoid errors interleaved with output */
bool error = false;
/* Object properties */
Containers::Array<ObjectInfo> objectInfos;
if(args.isSet("info") || args.isSet("info-objects")) {
objectInfos = Containers::Array<ObjectInfo>{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<SceneInfo> 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<UnsignedInt, Containers::String> sceneFieldNames;
Containers::Array<UnsignedInt> materialReferenceCount;
Containers::Array<UnsignedInt> lightReferenceCount;
Containers::Array<UnsignedInt> cameraReferenceCount;
Containers::Array<UnsignedInt> meshReferenceCount;
Containers::Array<UnsignedInt> skin2DReferenceCount;
Containers::Array<UnsignedInt> skin3DReferenceCount;
if((args.isSet("info") || args.isSet("info-scenes") || args.isSet("object-hierarchy")) && importer.sceneCount()) {
if(args.isSet("info") || args.isSet("info-scenes")) {
materialReferenceCount = Containers::Array<UnsignedInt>{importer.materialCount()};
lightReferenceCount = Containers::Array<UnsignedInt>{importer.lightCount()};
cameraReferenceCount = Containers::Array<UnsignedInt>{importer.cameraCount()};
meshReferenceCount = Containers::Array<UnsignedInt>{importer.meshCount()};
skin2DReferenceCount = Containers::Array<UnsignedInt>{importer.skin2DCount()};
skin3DReferenceCount = Containers::Array<UnsignedInt>{importer.skin3DCount()};
}
for(UnsignedInt i = 0; i != importer.sceneCount(); ++i) {
Containers::Optional<Trade::SceneData> scene = importer.scene(i);
if(!scene) {
Error{} << "Can't import scene" << i;
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);
if(args.isSet("info") || args.isSet("info-scenes")) for(UnsignedInt j = 0; j != scene->fieldCount(); ++j) {
const Trade::SceneField name = scene->fieldName(j);
if(name == Trade::SceneField::Mesh) for(const Containers::Pair<UnsignedInt, Containers::Pair<UnsignedInt, Int>>& 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<UnsignedInt, UnsignedInt> 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<UnsignedInt, UnsignedInt>& light: scene->lightsAsArray()) {
if(light.second() < lightReferenceCount.size())
++lightReferenceCount[light.second()];
}
if(name == Trade::SceneField::Camera) for(const Containers::Pair<UnsignedInt, UnsignedInt>& camera: scene->camerasAsArray()) {
if(camera.second() < cameraReferenceCount.size())
++cameraReferenceCount[camera.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);
}
}
if(args.isSet("object-hierarchy") && scene->hasField(Trade::SceneField::Parent))
info.childrenDepthFirst = SceneTools::childrenDepthFirst(*scene);
arrayAppend(sceneInfos, std::move(info));
}
}
/* Animation properties */
Containers::Array<AnimationInfo> animationInfos;
if(args.isSet("info") || args.isSet("info-animations")) for(UnsignedInt i = 0; i != importer.animationCount(); ++i) {
Containers::Optional<Trade::AnimationData> animation;
{
Trade::Implementation::Duration d{importTime};
if(!(animation = importer.animation(i))) {
Error{} << "Can't import animation" << i;
error = true;
continue;
}
}
AnimationInfo info{};
info.animation = i;
info.name = importer.animationName(i);
info.dataSize = animation->data().size();
info.dataFlags = animation->dataFlags();
info.duration = animation->duration();
for(UnsignedInt j = 0; j != animation->trackCount(); ++j) {
const Trade::AnimationTrackTarget name = animation->trackTargetName(j);
arrayAppend(info.tracks, InPlaceInit,
name,
Trade::isAnimationTrackTargetCustom(name) ?
importer.animationTrackTargetName(name) : "",
animation->trackType(j),
animation->trackResultType(j),
animation->track(j).interpolation(),
animation->track(j).before(),
animation->track(j).after(),
animation->track(j).duration(),
animation->track(j).size());
}
arrayAppend(animationInfos, std::move(info));
}
/* Skin properties */
Containers::Array<SkinInfo> skinInfos;
if(args.isSet("info") || args.isSet("info-skins")) {
for(UnsignedInt i = 0; i != importer.skin2DCount(); ++i) {
Containers::Optional<Trade::SkinData2D> skin;
{
Trade::Implementation::Duration d{importTime};
if(!(skin = importer.skin2D(i))) {
Error{} << "Can't import 2D skin" << 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<Trade::SkinData3D> skin;
{
Trade::Implementation::Duration d{importTime};
if(!(skin = importer.skin3D(i))) {
Error{} << "Can't import 3D skin" << 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<LightInfo> lightInfos;
if(args.isSet("info") || args.isSet("info-lights")) for(UnsignedInt i = 0; i != importer.lightCount(); ++i) {
Containers::Optional<Trade::LightData> light;
{
Trade::Implementation::Duration d{importTime};
if(!(light = importer.light(i))) {
Error{} << "Can't import 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));
}
/* Camera properties */
Containers::Array<CameraInfo> cameraInfos;
if(args.isSet("info") || args.isSet("info-cameras")) for(UnsignedInt i = 0; i != importer.cameraCount(); ++i) {
Containers::Optional<Trade::CameraData> camera;
{
Trade::Implementation::Duration d{importTime};
if(!(camera = importer.camera(i))) {
Error{} << "Can't import camera" << i;
error = true;
continue;
}
}
CameraInfo info{};
info.camera = i;
info.name = importer.cameraName(i);
info.data = *std::move(camera);
arrayAppend(cameraInfos, 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<MaterialInfo> materialInfos;
Containers::Array<UnsignedInt> textureReferenceCount;
if((args.isSet("info") || args.isSet("info-materials")) && importer.materialCount()) {
textureReferenceCount = Containers::Array<UnsignedInt>{importer.textureCount()};
for(UnsignedInt i = 0; i != importer.materialCount(); ++i) {
Containers::Optional<Trade::MaterialData> material;
{
Trade::Implementation::Duration d{importTime};
if(!(material = importer.material(i))) {
Error{} << "Can't import 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<UnsignedInt>(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 */
const bool showBounds = args.isSet("bounds");
Containers::Array<MeshInfo> 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<Trade::MeshData> mesh;
{
Trade::Implementation::Duration d{importTime};
if(!(mesh = importer.mesh(i, j))) {
Error{} << "Can't import mesh" << i << "level" << 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(showBounds)
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(showBounds && !isVertexFormatImplementationSpecific(mesh->attributeFormat(k))) switch(name) {
case Trade::MeshAttribute::Position:
bounds = calculateBounds(mesh->positions3DAsArray(mesh->attributeId(k)));
break;
case Trade::MeshAttribute::Tangent:
bounds = calculateBounds(mesh->tangentsAsArray(mesh->attributeId(k)));
break;
case Trade::MeshAttribute::Bitangent:
bounds = calculateBounds(mesh->bitangentsAsArray(mesh->attributeId(k)));
break;
case Trade::MeshAttribute::Normal:
bounds = calculateBounds(mesh->normalsAsArray(mesh->attributeId(k)));
break;
case Trade::MeshAttribute::TextureCoordinates:
bounds = calculateBounds(mesh->textureCoordinates2DAsArray(mesh->attributeId(k)));
break;
case Trade::MeshAttribute::Color:
bounds = calculateBounds(mesh->colorsAsArray(mesh->attributeId(k)));
break;
/* These two are arrays, and thus the bounds should be
calculated across the array elements as well. But as
the data are returned in a flattened array, it's done
implicitly without having to additionally do some
`vec.minmax()` like in case of vectors. Yay for
accidental timesavers! */
case Trade::MeshAttribute::JointIds:
bounds = calculateBounds(mesh->jointIdsAsArray(mesh->attributeId(k)));
break;
case Trade::MeshAttribute::Weights:
bounds = calculateBounds(mesh->weightsAsArray(mesh->attributeId(k)));
break;
case Trade::MeshAttribute::ObjectId:
bounds = calculateBounds(mesh->objectIdsAsArray(mesh->attributeId(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<TextureInfo> textureInfos;
Containers::Array<UnsignedInt> image1DReferenceCount;
Containers::Array<UnsignedInt> image2DReferenceCount;
Containers::Array<UnsignedInt> image3DReferenceCount;
if((args.isSet("info") || args.isSet("info-textures")) && importer.textureCount()) {
image1DReferenceCount = Containers::Array<UnsignedInt>{importer.image1DCount()};
image2DReferenceCount = Containers::Array<UnsignedInt>{importer.image2DCount()};
image3DReferenceCount = Containers::Array<UnsignedInt>{importer.image3DCount()};
for(UnsignedInt i = 0; i != importer.textureCount(); ++i) {
Containers::Optional<Trade::TextureData> texture;
{
Trade::Implementation::Duration d{importTime};
if(!(texture = importer.texture(i))) {
Error{} << "Can't import 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<Trade::Implementation::ImageInfo> imageInfos;
if(args.isSet("info") || args.isSet("info-images")) {
imageInfos = Trade::Implementation::imageInfo(importer, error, importTime);
}
/* Print default scene also if sceneInfos is empty (for example due to an
import failure) */
if((args.isSet("info") || args.isSet("info-scenes")) && importer.defaultScene() != -1)
Debug{useColor} << Debug::boldColor(Debug::Color::Default) << "Default scene:" << Debug::resetColor << importer.defaultScene();
const auto printObjectFieldInfo = [&sceneFieldNames](Debug& d, const ObjectInfo& info) {
for(std::size_t i = 0; i != info.fields.size(); ++i) {
if(i) d << Debug::nospace << ",";
const Containers::Pair<Trade::SceneField, UnsignedInt> 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 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 << ")";
if(info.fields) {
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";
}
}
if(args.isSet("object-hierarchy") && objectInfos) {
d << Debug::newline << " Object hierarchy:";
Containers::Array<std::size_t> childRangeEnds;
arrayAppend(childRangeEnds, info.childrenDepthFirst.size());
for(std::size_t i = 0; i != info.childrenDepthFirst.size(); ++i) {
while(childRangeEnds.back() == i)
arrayRemoveSuffix(childRangeEnds, 1);
const UnsignedInt object = info.childrenDepthFirst[i].first();
const UnsignedInt childCount = info.childrenDepthFirst[i].second();
const ObjectInfo& objectInfo = objectInfos[object];
const Containers::String indent = " "_s*(childRangeEnds.size());
d << Debug::newline << indent << Debug::nospace << Debug::boldColor(Debug::Color::Default) << " Object"
<< object << Debug::nospace << ":" << Debug::resetColor;
if(objectInfo.name) d << Debug::boldColor(Debug::Color::Yellow)
<< objectInfo.name << Debug::resetColor;
if(objectInfo.fields) {
d << Debug::newline << indent << Debug::nospace << " Fields:";
printObjectFieldInfo(d, objectInfo);
}
if(childCount) {
CORRADE_INTERNAL_ASSERT(childRangeEnds.back() > i + 1);
arrayAppend(childRangeEnds, i + childCount + 1);
}
}
}
totalSceneDataSize += info.dataSize;
}
if(!sceneInfos.isEmpty())
Debug{} << "Total scene data size:" << Utility::format("{:.1f}", totalSceneDataSize/1024.0f) << "kB";
/* If --object-hierarchy was specified, the object list was printed as part
of the scene already */
if(!args.isSet("object-hierarchy")) 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:";
printObjectFieldInfo(d, info);
}
}
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.duration.min() << Debug::nospace << ","
<< info.duration.max() << Debug::nospace << "} ("
<< 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 << ")";
for(UnsignedInt i = 0; i != info.tracks.size(); ++i) {
const AnimationTrackInfo& track = info.tracks[i];
d << Debug::newline << " Track" << i << Debug::nospace << ":"
<< Debug::boldColor(Debug::Color::Default);
if(Trade::isAnimationTrackTargetCustom(track.targetName)) {
d << "Custom(" << Debug::nospace
<< Trade::animationTrackTargetCustom(track.targetName)
<< Debug::nospace << ":" << Debug::nospace
<< Debug::color(Debug::Color::Yellow)
<< track.customTargetName << Debug::nospace
<< Debug::boldColor(Debug::Color::Default) << ")";
} else d << Debug::packed << track.targetName;
d << Debug::color(Debug::Color::Blue) << "@" << Debug::packed
<< Debug::color(Debug::Color::Cyan) << track.type
<< Debug::resetColor;
if(track.type != track.resultType)
d << Debug::color(Debug::Color::Blue) << "->"
<< Debug::packed << Debug::color(Debug::Color::Cyan)
<< track.resultType << Debug::resetColor;
d << Debug::nospace << "," << track.size << "keyframes";
if(track.duration != info.duration)
d << Debug::newline << " Duration: {" << Debug::nospace
/** @todo have a nice packed printing for Range instead */
<< track.duration.min() << Debug::nospace << ","
<< track.duration.max() << Debug::nospace << "}";
d << Debug::newline
<< " Interpolation:"
<< Debug::packed << Debug::color(track.interpolation == Animation::Interpolation::Custom ? Debug::Color::Yellow : Debug::Color::Cyan)
<< track.interpolation << Debug::resetColor << Debug::nospace
<< "," << Debug::packed << Debug::color(Debug::Color::Cyan)
<< track.before << Debug::resetColor << Debug::nospace
<< "," << Debug::packed << Debug::color(Debug::Color::Cyan)
<< track.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.dataSize;
}
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::LightType::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<Color3ub>(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::LightType::Ambient &&
info.data.type() != Trade::LightType::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 CameraInfo& info: cameraInfos) {
Debug d{useColor};
d << Debug::boldColor(Debug::Color::Default) << "Camera" << info.camera << Debug::resetColor;
/* Print reference count only if there actually are scenes and they
were parsed, otherwise this information is useless */
if(cameraReferenceCount) {
const UnsignedInt count = cameraReferenceCount[info.camera];
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 << Debug::newline;
/* Print orthographic cameras with size, perspective with FoV */
if(info.data.type() == Trade::CameraType::Orthographic2D ||
info.data.type() == Trade::CameraType::Orthographic3D) {
d << " Size:" << Debug::packed << info.data.size();
} else if(info.data.type() == Trade::CameraType::Perspective3D) {
d << " FoV:" << Debug::packed << Deg(info.data.fov())
<< Debug::nospace << "°";
}
/* Near/far is implicitly 0 for 2D */
if(info.data.type() != Trade::CameraType::Orthographic2D)
d << Debug::nospace << "," << info.data.near() << "-" << info.data.far();
d << Debug::newline << " Aspect ratio:" << info.data.aspectRatio();
}
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<type>(i, j); \
break;
#define _ct(name, type) case Trade::MaterialAttributeType::name: \
d << Debug::packed << info.data.attribute<type>(i, j); \
break;
_ct(Bool, bool)
/* 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 */
_ct(Pointer, const void*)
_ct(MutablePointer, void*)
_ct(String, Containers::StringView)
_ct(TextureSwizzle, Trade::MaterialTextureSwizzle)
#undef _c
#undef _ct
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<Color3ub>(info.data.attribute<Vector3>(i, j));
d << Debug::packed << info.data.attribute<Vector3>(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<Color3ub>(info.data.attribute<Vector4>(i, j).rgb());
d << Debug::packed << info.data.attribute<Vector4>(i, j);
break;
case Trade::MaterialAttributeType::Buffer:
d << info.data.attribute<Containers::ArrayView<const void>>(i, j).size() << "bytes";
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