diff --git a/src/MagnumPlugins/AssimpImporter/AssimpImporter.conf b/src/MagnumPlugins/AssimpImporter/AssimpImporter.conf index 7c54d9429..9e9fa3a4f 100644 --- a/src/MagnumPlugins/AssimpImporter/AssimpImporter.conf +++ b/src/MagnumPlugins/AssimpImporter/AssimpImporter.conf @@ -63,6 +63,17 @@ mergeSkins=false # AI_CONFIG_* values, can be changed only before the first file is opened ImportColladaIgnoreUpDirection=false +# By default material attributes that aren't recognized as builtin attributes +# are imported with raw Assimp names and types. Enabling this option completely +# ignores unrecognized attributes instead. +ignoreUnrecognizedMaterialData=false + +# Don't attempt to recognize builtin material attributes and always import them +# as raw material data (so e.g. instead of DiffuseColor you'll get +# $clr.diffuse). Implicitly disables ignoreUnrecognizedMaterialData. Mainly for +# testing purposes. +forceRawMaterialData=false + # aiPostProcessSteps, applied to each opened file [configuration/postprocess] JoinIdenticalVertices=true diff --git a/src/MagnumPlugins/AssimpImporter/AssimpImporter.cpp b/src/MagnumPlugins/AssimpImporter/AssimpImporter.cpp index c7bc935bf..79e244fbd 100644 --- a/src/MagnumPlugins/AssimpImporter/AssimpImporter.cpp +++ b/src/MagnumPlugins/AssimpImporter/AssimpImporter.cpp @@ -29,6 +29,7 @@ #include "AssimpImporter.h" +#include #include #include #include @@ -94,8 +95,8 @@ using namespace Containers::Literals; struct AssimpImporter::File { Containers::Optional filePath; - const char* importerName = nullptr; bool importerIsGltf = false; + bool importerIsFbx = false; const aiScene* scene = nullptr; std::vector nodes; /* (materialPointer, propertyIndexInsideMaterial, imageIndex) tuple, @@ -112,6 +113,8 @@ struct AssimpImporter::File { Containers::Optional> animationsForName, + camerasForName, + lightsForName, meshesForName, skinsForName; @@ -170,6 +173,8 @@ void fillDefaultConfiguration(Utility::ConfigurationGroup& conf) { conf.setValue("mergeSkins", false); conf.setValue("ImportColladaIgnoreUpDirection", false); + conf.setValue("ignoreUnrecognizedMaterialData", false); + conf.setValue("forceRawMaterialData", false); Utility::ConfigurationGroup& postprocess = *conf.addGroup("postprocess"); postprocess.setValue("JoinIdenticalVertices", true); @@ -406,15 +411,15 @@ void AssimpImporter::doOpenData(Containers::Array&& data, DataFlags) { /* Get name of importer. Useful for workarounds based on importer/file type. If the _importer isn't populated, we got called from doOpenState() and we can't really do much here. */ - _f->importerName = "unknown"; if(_importer) { const int importerIndex = _importer->GetPropertyInteger("importerIndex", -1); if(importerIndex != -1) { const aiImporterDesc* info = _importer->GetImporterInfo(importerIndex); - if(info) _f->importerName = info->mName; + if(info) { + _f->importerIsGltf = info->mName == "glTF2 Importer"_s; + _f->importerIsFbx = info->mName == "Autodesk FBX Importer"_s; + } } - - _f->importerIsGltf = _f->importerName == "glTF2 Importer"_s; } /* Fill hashmaps for index lookup for materials/textures/meshes/nodes */ @@ -456,7 +461,7 @@ void AssimpImporter::doOpenData(Containers::Array&& data, DataFlags) { /* For some formats (such as COLLADA) Assimp fails to open the scene if there are no nodes, so there this is always non-null. For other formats - (such as glTF) Assimp happily provides a null root node, even thought + (such as glTF) Assimp happily provides a null root node, even though that's not the documented behavior. */ aiNode* const root = _f->scene->mRootNode; if(root) { @@ -622,6 +627,23 @@ Int AssimpImporter::doDefaultScene() const { return _f->scene->mRootNode ? 0 : - UnsignedInt AssimpImporter::doSceneCount() const { return _f->scene->mRootNode ? 1 : 0; } +Int AssimpImporter::doSceneForName(const std::string& name) { + static_cast(name); + #if ASSIMP_HAS_SCENE_NAME + if(_f->scene->mRootNode && name == _f->scene->mName.C_Str()) + return 0; + #endif + return -1; +} + +std::string AssimpImporter::doSceneName(UnsignedInt) { + #if ASSIMP_HAS_SCENE_NAME + return _f->scene->mName.C_Str(); + #else + return {}; + #endif +} + Containers::Optional AssimpImporter::doScene(UnsignedInt) { const aiNode* root = _f->scene->mRootNode; @@ -643,10 +665,39 @@ UnsignedInt AssimpImporter::doCameraCount() const { return _f->scene->mNumCameras; } +Int AssimpImporter::doCameraForName(const std::string& name) { + if(!_f->camerasForName) { + _f->camerasForName.emplace(); + _f->camerasForName->reserve(_f->scene->mNumCameras); + for(std::size_t i = 0; i != _f->scene->mNumCameras; ++i) + _f->camerasForName->emplace(std::string(_f->scene->mCameras[i]->mName.C_Str()), i); + } + + const auto found = _f->camerasForName->find(name); + return found == _f->camerasForName->end() ? -1 : found->second; +} + +std::string AssimpImporter::doCameraName(const UnsignedInt id) { + return _f->scene->mCameras[id]->mName.C_Str(); +} + Containers::Optional AssimpImporter::doCamera(UnsignedInt id) { const aiCamera* cam = _f->scene->mCameras[id]; - /** @todo aspect and up vector are not used... */ - return CameraData{CameraType::Perspective3D, Rad(cam->mHorizontalFOV), 1.0f, cam->mClipPlaneNear, cam->mClipPlaneFar, cam}; + const Float aspect = cam->mAspect > 0.0f ? cam->mAspect : 1.0f; + /** @todo up vector is not used */ + + #if ASSIMP_HAS_ORTHOGRAPHIC_CAMERA + if(cam->mOrthographicWidth > 0.0f) + return CameraData{CameraType::Orthographic3D, + Vector2{cam->mOrthographicWidth, cam->mOrthographicWidth/aspect}*2.0f, + cam->mClipPlaneNear, cam->mClipPlaneFar, + cam}; + #endif + + return CameraData{CameraType::Perspective3D, + Rad(cam->mHorizontalFOV), aspect, + cam->mClipPlaneNear, cam->mClipPlaneFar, + cam}; } UnsignedInt AssimpImporter::doObject3DCount() const { @@ -731,6 +782,22 @@ UnsignedInt AssimpImporter::doLightCount() const { return _f->scene->mNumLights; } +Int AssimpImporter::doLightForName(const std::string& name) { + if(!_f->lightsForName) { + _f->lightsForName.emplace(); + _f->lightsForName->reserve(_f->scene->mNumLights); + for(std::size_t i = 0; i != _f->scene->mNumLights; ++i) + _f->lightsForName->emplace(std::string(_f->scene->mLights[i]->mName.C_Str()), i); + } + + const auto found = _f->lightsForName->find(name); + return found == _f->lightsForName->end() ? -1 : found->second; +} + +std::string AssimpImporter::doLightName(const UnsignedInt id) { + return _f->scene->mLights[id]->mName.C_Str(); +} + Containers::Optional AssimpImporter::doLight(UnsignedInt id) { const aiLight* l = _f->scene->mLights[id]; @@ -746,7 +813,7 @@ Containers::Optional AssimpImporter::doLight(UnsignedInt id) { lightType = LightData::Type::Spot; color = Color3{l->mColorDiffuse}; } else if(l->mType == aiLightSource_AMBIENT) { - lightType = LightData::Type::Point; + lightType = LightData::Type::Ambient; color = Color3{l->mColorAmbient}; } else { /** @todo area lights */ @@ -786,13 +853,20 @@ std::string AssimpImporter::doMeshName(const UnsignedInt id) { Containers::Optional AssimpImporter::doMesh(const UnsignedInt id, UnsignedInt) { const aiMesh* mesh = _f->scene->mMeshes[id]; - /* Primitive */ + /* Primitive. mPrimitiveTypes is a mask but aiProcess_SortByPType (enabled + by default) should make sure only one type is set. However, since 5.1.0 + triangles can also have aiPrimitiveType_NGONEncodingFlag set which + indicates that consecutive triangles form an ngon. That flag is always + set for triangulated meshes. Masking all known types to future-proof + this against more random flags getting added. */ MeshPrimitive primitive; - if(mesh->mPrimitiveTypes == aiPrimitiveType_POINT) { + const aiPrimitiveType primitiveType = aiPrimitiveType(mesh->mPrimitiveTypes & + (aiPrimitiveType_POINT | aiPrimitiveType_LINE | aiPrimitiveType_TRIANGLE)); + if(primitiveType == aiPrimitiveType_POINT) { primitive = MeshPrimitive::Points; - } else if(mesh->mPrimitiveTypes == aiPrimitiveType_LINE) { + } else if(primitiveType == aiPrimitiveType_LINE) { primitive = MeshPrimitive::Lines; - } else if(mesh->mPrimitiveTypes == aiPrimitiveType_TRIANGLE) { + } else if(primitiveType == aiPrimitiveType_TRIANGLE) { primitive = MeshPrimitive::Triangles; } else { Error() << "Trade::AssimpImporter::mesh(): unsupported aiPrimitiveType" << mesh->mPrimitiveTypes; @@ -1074,6 +1148,101 @@ MaterialAttributeData materialColor(MaterialAttribute attribute, const aiMateria return {attribute, Color4{Color3::from(reinterpret_cast(property.mData))}}; else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ } +/* We use aiTextureType values to generate names in customMaterialKey(), but + older Assimp versions may not have those defined. Unfortunately Assimp + doesn't keep the values stable for us to define our own enum. Instead, this + SFINAE magic lets us pass through valid values or fall back to unique, + hopefully unused values otherwise. This way we save ourselves from ifdef-ing + around specific detected versions. Make sure to double-check any added + texture type names since they will invariably compile. */ +#define ASSIMP_OPTIONAL_TEXTURE_TYPE(name) \ + template struct AssimpOptionalTextureType_ ## name { \ + template constexpr static U get(T*, decltype(T::aiTextureType_ ## name)* = nullptr) { \ + return T::aiTextureType_ ## name; \ + } \ + constexpr static U get(...) { \ + return U(1000000 + __LINE__); \ + } \ + constexpr static U Value = get(static_cast(nullptr)); \ + }; + +ASSIMP_OPTIONAL_TEXTURE_TYPE(BASE_COLOR) +ASSIMP_OPTIONAL_TEXTURE_TYPE(NORMAL_CAMERA) +ASSIMP_OPTIONAL_TEXTURE_TYPE(EMISSION_COLOR) +ASSIMP_OPTIONAL_TEXTURE_TYPE(METALNESS) +ASSIMP_OPTIONAL_TEXTURE_TYPE(DIFFUSE_ROUGHNESS) +ASSIMP_OPTIONAL_TEXTURE_TYPE(AMBIENT_OCCLUSION) +ASSIMP_OPTIONAL_TEXTURE_TYPE(SHEEN) +ASSIMP_OPTIONAL_TEXTURE_TYPE(CLEARCOAT) +ASSIMP_OPTIONAL_TEXTURE_TYPE(TRANSMISSION) + +Containers::String customMaterialKey(Containers::StringView key, const aiTextureType semantic) { + using namespace Containers::Literals; + + /* Create a non-owning key String, add texture type to it if + present */ + auto keyString = Containers::String::nullTerminatedView(key); + if(semantic != aiTextureType_NONE) { + Containers::StringView keyExtra; + switch(semantic) { + /* Texture types present in all assimp versions we can reasonably + support. These are available as far back as 3.2. */ + #define _c(type) case aiTextureType_ ## type: \ + keyExtra = #type ## _s; \ + break; + _c(UNKNOWN) + _c(AMBIENT) + _c(DIFFUSE) + _c(DISPLACEMENT) + _c(EMISSIVE) + _c(HEIGHT) + _c(LIGHTMAP) + _c(NORMALS) + _c(OPACITY) + _c(REFLECTION) + _c(SHININESS) + _c(SPECULAR) + #undef _c + + /* Texture types that may not be available in aiTextureType. Needs + SFINAE magic to produce unique, unreachable fallback values. */ + #define _f(type) case AssimpOptionalTextureType_ ## type ::Value: \ + keyExtra = #type ## _s; \ + break; + #ifdef _MSC_VER + #pragma warning(push) + #pragma warning(disable: 4063) /* not a valid value for switch of enum 'aiTextureType' */ + #endif + /* Added in 5.0.0 */ + _f(BASE_COLOR) + _f(NORMAL_CAMERA) + _f(EMISSION_COLOR) + _f(METALNESS) + _f(DIFFUSE_ROUGHNESS) + _f(AMBIENT_OCCLUSION) + /* Added in 5.1.0 */ + _f(SHEEN) + _f(CLEARCOAT) + _f(TRANSMISSION) + #ifdef _MSC_VER + #pragma warning(pop) + #endif + #undef _f + + case aiTextureType_NONE: + case _aiTextureType_Force32Bit: + CORRADE_INTERNAL_ASSERT_UNREACHABLE(); + } + + CORRADE_INTERNAL_ASSERT(!keyExtra.isEmpty()); + + /** @todo use format() and drop FormatStl include when format() is + converted to StringViews */ + keyString = Utility::formatString("{}.{}", key, keyExtra); + } + + return keyString; +} #ifndef _CORRADE_HELPER_DEFER template constexpr Containers::StringView extractMaterialKey(const char(&data)[size], int, int) { @@ -1084,6 +1253,9 @@ template constexpr Containers::StringView extractMaterialKey(c } Containers::Optional AssimpImporter::doMaterial(const UnsignedInt id) { + const bool forceRaw = configuration().value("forceRawMaterialData"); + const bool unrecognizedAsRaw = forceRaw || !configuration().value("ignoreUnrecognizedMaterialData"); + const aiMaterial* mat = _f->scene->mMaterials[id]; /* Calculate how many layers there are in the material */ @@ -1097,7 +1269,7 @@ Containers::Optional AssimpImporter::doMaterial(const UnsignedInt arrayReserve(attributes, mat->mNumProperties); Containers::Array layers{maxLayer + 1}; - /* Go through each layer add then for each add all its properties so they + /* Go through each layer and then for each add all its properties so they are consecutive in the array */ for(UnsignedInt layer = 0; layer <= maxLayer; ++layer) { /* Save offset of this layer */ @@ -1110,13 +1282,13 @@ Containers::Optional AssimpImporter::doMaterial(const UnsignedInt UnsignedInt textureIndex = _f->textureIndices.at(mat); for(std::size_t i = 0; i != mat->mNumProperties; ++i) { - aiMaterialProperty& property = *mat->mProperties[i]; + const aiMaterialProperty& property = *mat->mProperties[i]; /* Process only properties from this layer (again, to have them consecutive in the attribute array), but properly increase texture index even for the skipped properties so we have the mapping correct */ - if(mat->mProperties[i]->mIndex != layer) { + if(property.mIndex != layer) { if(Containers::StringView{property.mKey.C_Str(), property.mKey.length} == _AI_MATKEY_TEXTURE_BASE) ++textureIndex; continue; @@ -1126,35 +1298,38 @@ Containers::Optional AssimpImporter::doMaterial(const UnsignedInt const Containers::StringView key{property.mKey.C_Str(), property.mKey.length, Containers::StringViewFlag::NullTerminated}; + /* AI_MATKEY_* are in form "bla",0,0, so extract the first part + and turn it into a StringView for string comparison. The + _s literal is there to avoid useless strlen() calls in every + branch. _CORRADE_HELPER_DEFER is not implementable on MSVC + (see docs in Utility/Macros.h), so there it's a constexpr + function instead. */ + #ifdef _CORRADE_HELPER_DEFER + #define _str2(name, i, j) name ## _s + #define _str(name) _CORRADE_HELPER_DEFER(_str2, name) + #else + #define _str extractMaterialKey + #endif + + /* Material name is available through materialName() / + materialForName() already, ignore it */ + if(key == _str(AI_MATKEY_NAME) && property.mType == aiPTI_String) + continue; + /* Recognize known attributes if they have expected types and - sizes */ + sizes. If they don't, they'll be treated as custom attribs in + the code below. It's also possible to skip this by enabling the + forceRawMaterialData configuration option. */ MaterialAttributeData data; MaterialAttribute attribute{}; MaterialAttributeType type{}; - { - /* AI_MATKEY_* are in form "bla",0,0, so extract the first part - and turn it into a StringView for string comparison. The - _s literal is there to avoid useless strlen() calls in every - branch. _CORRADE_HELPER_DEFER is not implementable on MSVC - (see docs in Utility/Macros.h), so there it's a constexpr - function instead. */ - #ifdef _CORRADE_HELPER_DEFER - #define _str2(name, i, j) name ## _s - #define _str(name) _CORRADE_HELPER_DEFER(_str2, name) - #else - #define _str extractMaterialKey - #endif + if(!forceRaw) { /* Properties not tied to a particular texture */ if(property.mSemantic == aiTextureType_NONE) { - /* Material name is available through materialName() / - materialForName() already, ignore it */ - if(key == _str(AI_MATKEY_NAME) && property.mType == aiPTI_String) { - continue; - /* Colors. Some formats have them three-components (OBJ), some four-component (glTF). Documentation states it's always three-component. FFS. */ - } else if(key == _str(AI_MATKEY_COLOR_AMBIENT) && property.mType == aiPTI_Float && (property.mDataLength == 4*4 || property.mDataLength == 4*3)) { + if(key == _str(AI_MATKEY_COLOR_AMBIENT) && property.mType == aiPTI_Float && (property.mDataLength == 4*4 || property.mDataLength == 4*3)) { data = materialColor(MaterialAttribute::AmbientColor, property); /* Assimp 4.1 forces ambient color to white for STL @@ -1187,6 +1362,13 @@ Containers::Optional AssimpImporter::doMaterial(const UnsignedInt type = MaterialAttributeType::Float; } + /** @todo + - MaterialAttribute::DoubleSided - AI_MATKEY_TWOSIDED + - MaterialAttribute::AlphaBlend - AI_MATKEY_GLTF_ALPHAMODE == "BLEND", glTF only + - MaterialAttribute::AlphaMask - AI_MATKEY_GLTF_ALPHAMODE == "MASK" + AI_MATKEY_GLTF_ALPHACUTOFF, glTF only + - MaterialType::Flat - AI_MATKEY_SHADING_MODEL + aiShadingMode_NoShading. + For versions < 5.1.0 this is AI_MATKEY_GLTF_UNLIT for glTF. */ + /* Properties tied to a particular texture */ } else { /* Texture index */ @@ -1207,9 +1389,9 @@ Containers::Optional AssimpImporter::doMaterial(const UnsignedInt } /* Save only if the name is recognized (and let it - be imported as a custom attribute otherwise), - but increment the texture index counter always - to stay in sync */ + be imported as a custom attribute otherwise), + but increment the texture index counter always + to stay in sync */ if(attribute != MaterialAttribute{}) data = {attribute, textureIndex}; ++textureIndex; @@ -1233,10 +1415,15 @@ Containers::Optional AssimpImporter::doMaterial(const UnsignedInt } } } - #undef _str - #undef _str2 + + /** @todo PBR support. Assimp 5.1.0 finally has unified support + for PBR attributes, including the fancy glTF extensions: + https://github.com/assimp/assimp/blob/v5.1.0/include/assimp/material.h#L971 */ } + #undef _str + #undef _str2 + /* If the attribute data is already constructed (parsed from a string value etc), put it directly in */ if(data.type() != MaterialAttributeType{}) { @@ -1249,9 +1436,120 @@ Containers::Optional AssimpImporter::doMaterial(const UnsignedInt CORRADE_INTERNAL_ASSERT(type != MaterialAttributeType{} && type != MaterialAttributeType::String); arrayAppend(attributes, InPlaceInit, attribute, type, property.mData); - /* Otherwise ignore for now. At a later point remaining attributes - will be imported as custom, but that needs a lot of testing - which I don't have time for right now. */ + /* Otherwise figure out its type, do some additional checking and + put it there as a custom attribute */ + } else if(unrecognizedAsRaw) { + /* Attribute names starting with uppercase letters are reserved + for Magnum. All assimp material keys start with one of $/?/~ + so an assert should be fine here: + https://github.com/assimp/assimp/blob/v3.2/include/assimp/material.h#L548 + Revisit if this breaks for someone. */ + /** @todo $ conflicts with Magnum's material layer names */ + CORRADE_ASSERT(!key.isEmpty() && !std::isupper(key.front()), + "Trade::AssimpImporter::material(): attribute names starting with uppercase are reserved:" << key, {}); + + if(property.mType == aiPTI_Integer) { + if(property.mDataLength == 1) + type = MaterialAttributeType::Bool; + else if(property.mDataLength/4 == 1) + type = MaterialAttributeType::Int; + else if(property.mDataLength/4 == 2) + type = MaterialAttributeType::Vector2i; + else if(property.mDataLength/4 == 3) + type = MaterialAttributeType::Vector3i; + else if(property.mDataLength/4 == 4) + type = MaterialAttributeType::Vector4i; + else { + Warning{} << "Trade::AssimpImporter::material(): property" << key << "is an integer array of" << property.mDataLength/4 << "items, saving as a typeless buffer"; + /* Abusing Pointer to indicate this is a buffer. + Together with other similar cases it's processed + below and turned into MaterialAttributeType::String. */ + /** @todo add MaterialAttributeType::Buffer for opaque + buffer data that can't be viewed as a string */ + type = MaterialAttributeType::Pointer; + } + } else if(property.mType == aiPTI_Float) { + if(property.mDataLength/4 == 1) + type = MaterialAttributeType::Float; + else if(property.mDataLength/4 == 2) + type = MaterialAttributeType::Vector2; + else if(property.mDataLength/4 == 3) + type = MaterialAttributeType::Vector3; + else if(property.mDataLength/4 == 4) + type = MaterialAttributeType::Vector4; + else { + Warning{} << "Trade::AssimpImporter::material(): property" << key << "is a float array of" << property.mDataLength/4 << "items, saving as a typeless buffer"; + type = MaterialAttributeType::Pointer; + } + #if ASSIMP_HAS_DOUBLES + } else if(property.mType == aiPTI_Double) { + /** @todo This shouldn't happen without compiling Assimp + with double support. But then the importer would only + produce garbage because it assumes ai_real equals float + everywhere. + Always assert? Ignore if ASSIMP_DOUBLE_PRECISION is + not defined? */ + Warning{} << "Trade::AssimpImporter::material():" << key << "is a double precision property, saving as a typeless buffer"; + type = MaterialAttributeType::Pointer; + #endif + } else if(property.mType == aiPTI_Buffer) { + type = MaterialAttributeType::Pointer; + } else if(property.mType == aiPTI_String) { + type = MaterialAttributeType::String; + } else { + Warning{} << "Trade::AssimpImporter::material(): property" << key << "has unknown type" << property.mType << Debug::nospace << ", saving as a typeless buffer"; + type = MaterialAttributeType::Pointer; + } + + CORRADE_INTERNAL_ASSERT(type != MaterialAttributeType{}); + + Containers::StringView value; + std::size_t valueSize; + const void* valuePointer; + aiString tempString; + if(type == MaterialAttributeType::Pointer) { + /* Typeless buffer, turn it into an owned String */ + type = MaterialAttributeType::String; + value = {property.mData, property.mDataLength}; + /* +2 is null byte + size */ + valueSize = property.mDataLength + 2; + valuePointer = &value; + } else if(type == MaterialAttributeType::String) { + /* Prior to version 5.0.0 aiString::length is a size_t but + the material property code (and only that!) pretends + it's a uint32_t, storing the string data at offset 4: + (WARNING: Don't look if you're easily scared, this code + is nightmare material. You were warned!) + https://github.com/assimp/assimp/blob/v4.1.0/code/MaterialSystem.cpp#L526 + This incurs a copy but I don't want to commit the same + pointer crimes as assimp. */ + #if !ASSIMP_IS_VERSION_5_OR_GREATER + mat->Get(property.mKey.C_Str(), property.mSemantic, property.mIndex, tempString); + const aiString& str = tempString; + #else + const aiString& str = *reinterpret_cast(property.mData); + #endif + value = {str.C_Str(), str.length}; + /* +2 is null byte + size */ + valueSize = value.size() + 2; + valuePointer = &value; + } else { + valueSize = materialAttributeTypeSize(type); + valuePointer = property.mData; + } + + CORRADE_INTERNAL_ASSERT(type != MaterialAttributeType::Pointer && + type != MaterialAttributeType::MutablePointer); + + const Containers::String keyString = customMaterialKey(key, aiTextureType(property.mSemantic)); + + /* +1 is null byte for the key */ + if(valueSize + keyString.size() + 1 + sizeof(MaterialAttributeType) > sizeof(MaterialAttributeData)) { + Error{} << "Trade::AssimpImporter::material(): property" << keyString << "is too large with" << valueSize << "bytes, skipping"; + continue; + } + + arrayAppend(attributes, InPlaceInit, keyString, type, valuePointer); } } } @@ -1262,8 +1560,10 @@ Containers::Optional AssimpImporter::doMaterial(const UnsignedInt /* Can't use growable deleters in a plugin, convert back to the default deleter */ arrayShrink(attributes, DefaultInit); + /** @todo detect PBR properties and add relevant types accordingly */ - return MaterialData{MaterialType::Phong, std::move(attributes), std::move(layers), mat}; + const MaterialType materialType = forceRaw ? MaterialType{} : MaterialType::Phong; + return MaterialData{materialType, std::move(attributes), std::move(layers), mat}; } UnsignedInt AssimpImporter::doTextureCount() const { return _f->textures.size(); } @@ -1300,6 +1600,8 @@ Containers::Optional AssimpImporter::doTexture(const UnsignedInt id if(mat->Get(AI_MATKEY_MAPPINGMODE_V(type, 0), mapMode) == AI_SUCCESS) wrappingV = toWrapping(mapMode); + /** @todo AI_MATKEY_GLTF_MAPPINGFILTER_{MIN, MAG} for glTF */ + return TextureData{TextureType::Texture2D, SamplerFilter::Linear, SamplerFilter::Linear, SamplerMipmap::Linear, {wrappingU, wrappingV, SamplerWrapping::ClampToEdge}, std::get<2>(_f->textures[id]), &_f->textures[id]}; @@ -1602,14 +1904,21 @@ Containers::Optional AssimpImporter::doAnimation(UnsignedInt id) /* For glTF files mTicksPerSecond is completely useless before https://github.com/assimp/assimp/commit/09d80ff478d825a80bce6fb787e8b19df9f321a8 - but can be assumed to always be 1000. */ - constexpr Double GltfTicksPerSecond = 1000.0; - if(_f->importerIsGltf && !Math::equal(ticksPerSecond, GltfTicksPerSecond)) { + but can be assumed to always be 1000. + + FBX files since https://github.com/assimp/assimp/commit/b3e1ee3ca0d825d384044867fc30cd0bc8417be6 + report incorrect mTicksPerSecond but it should be 1000: + https://github.com/assimp/assimp/issues/4197 */ + constexpr Double CorrectTicksPerSecond = 1000.0; + if((_f->importerIsGltf || (_f->importerIsFbx && ASSIMP_HAS_BROKEN_FBX_TICKS_PER_SECOND)) && + !Math::equal(ticksPerSecond, CorrectTicksPerSecond)) + { if(verbose) Debug{} << "Trade::AssimpImporter::animation():" << Float(ticksPerSecond) - << "ticks per second is incorrect for glTF, patching to" - << Float(GltfTicksPerSecond); - ticksPerSecond = GltfTicksPerSecond; + << "ticks per second is incorrect for" + << (_f->importerIsGltf ? "glTF," : "FBX,") << "patching to" + << Float(CorrectTicksPerSecond); + ticksPerSecond = CorrectTicksPerSecond; } const TargetTypes targetTypes = channelTargetTypes[currentChannel++]; @@ -1659,8 +1968,8 @@ Containers::Optional AssimpImporter::doAnimation(UnsignedInt id) /* Ensure shortest path is always chosen. */ if(optimizeQuaternionShortestPath) { Float flip = 1.0f; - for(std::size_t i = 0; i != values.size() - 1; ++i) { - if(Math::dot(values[i], values[i + 1]*flip) < 0) flip = -flip; + for(std::size_t i = 0; i + 1 < values.size(); ++i) { + if(Math::dot(values[i], values[i + 1]*flip) < 0.0f) flip = -flip; values[i + 1] *= flip; } } diff --git a/src/MagnumPlugins/AssimpImporter/AssimpImporter.h b/src/MagnumPlugins/AssimpImporter/AssimpImporter.h index d23113c2e..ed81f6d1d 100644 --- a/src/MagnumPlugins/AssimpImporter/AssimpImporter.h +++ b/src/MagnumPlugins/AssimpImporter/AssimpImporter.h @@ -265,6 +265,18 @@ verbosity levels in each instance. - Only materials with shading mode `aiShadingMode_Phong` are supported - Two-sided property and alpha mode is not imported +- Unrecognized Assimp material attributes are imported with their raw names + and types. You can find the attribute names in [assimp/material.h](https://github.com/assimp/assimp/blob/master/include/assimp/material.h#L944). + Unknown or raw buffer types are imported as + @ref MaterialAttributeType::String. To ignore all unrecognized Assimp + materials instead, enable the @cb{.ini} ignoreUnrecognizedMaterialData @ce + @ref Trade-AssimpImporter-configuration "configuration option". +- To load all material attributes with the raw Assimp names and types, enable + the @cb{.ini} forceRawMaterialData @ce + @ref Trade-AssimpImporter-configuration "configuration option". You will + then get attributes like `$clr.diffuse` instead of + @ref MaterialAttribute::DiffuseColor. @ref MaterialData::types() will + always be empty with this option enabled. - Assimp seems to ignore ambient textures in COLLADA files - For some reason, Assimp 4.1 imports STL models with ambient set to @cpp 0xffffff_srgbf @ce, which causes all other color information to be @@ -278,8 +290,6 @@ verbosity levels in each instance. - @ref LightData::intensity() is always @cpp 1.0f @ce, instead Assimp premultiplies @ref LightData::color() with the intensity -- Ambient lights are imported as @ref LightData::Type::Point with attenuation - set to @cpp {1.0f, 0.0f, 0.0f} @ce - The following properties are ignored: - Specular color - Custom light orientation vectors --- the orientation is always only @@ -288,7 +298,9 @@ verbosity levels in each instance. @subsection Trade-AssimpImporter-behavior-cameras Camera import -- Aspect and up vector properties are not imported +- Up vector property is not imported +- Orthographic camera support requires Assimp 5.1.0. Earlier versions import + them as perspective cameras with arbitrary field of view and aspect ratio. @subsection Trade-AssimpImporter-behavior-meshes Mesh import @@ -444,9 +456,13 @@ class MAGNUM_ASSIMPIMPORTER_EXPORT AssimpImporter: public AbstractImporter { MAGNUM_ASSIMPIMPORTER_LOCAL Int doDefaultScene() const override; MAGNUM_ASSIMPIMPORTER_LOCAL UnsignedInt doSceneCount() const override; + MAGNUM_ASSIMPIMPORTER_LOCAL Int doSceneForName(const std::string& name) override; + MAGNUM_ASSIMPIMPORTER_LOCAL std::string doSceneName(UnsignedInt id) override; MAGNUM_ASSIMPIMPORTER_LOCAL Containers::Optional doScene(UnsignedInt id) override; MAGNUM_ASSIMPIMPORTER_LOCAL UnsignedInt doCameraCount() const override; + MAGNUM_ASSIMPIMPORTER_LOCAL Int doCameraForName(const std::string& name) override; + MAGNUM_ASSIMPIMPORTER_LOCAL std::string doCameraName(UnsignedInt id) override; MAGNUM_ASSIMPIMPORTER_LOCAL Containers::Optional doCamera(UnsignedInt id) override; MAGNUM_ASSIMPIMPORTER_LOCAL UnsignedInt doObject3DCount() const override; @@ -455,6 +471,8 @@ class MAGNUM_ASSIMPIMPORTER_EXPORT AssimpImporter: public AbstractImporter { MAGNUM_ASSIMPIMPORTER_LOCAL Containers::Pointer doObject3D(UnsignedInt id) override; MAGNUM_ASSIMPIMPORTER_LOCAL UnsignedInt doLightCount() const override; + MAGNUM_ASSIMPIMPORTER_LOCAL Int doLightForName(const std::string& name) override; + MAGNUM_ASSIMPIMPORTER_LOCAL std::string doLightName(UnsignedInt id) override; MAGNUM_ASSIMPIMPORTER_LOCAL Containers::Optional doLight(UnsignedInt id) override; MAGNUM_ASSIMPIMPORTER_LOCAL UnsignedInt doMeshCount() const override; diff --git a/src/MagnumPlugins/AssimpImporter/CMakeLists.txt b/src/MagnumPlugins/AssimpImporter/CMakeLists.txt index 6ef8ab1c1..436613bb9 100644 --- a/src/MagnumPlugins/AssimpImporter/CMakeLists.txt +++ b/src/MagnumPlugins/AssimpImporter/CMakeLists.txt @@ -59,8 +59,22 @@ else() set(_ASSIMP_TRY_COMPILE_LINK_OR_INCLUDE LINK_LIBRARIES Assimp::Assimp) endif() +# Newer versions of Assimp add C compile features that end up being propagated +# to installed CMake targets: +# https://github.com/assimp/assimp/commit/799384f2b85b8d3ee9c5b9e18bbe532b4dc7c63c +# This can fail in try_compile because it uses the file extension to determine +# the language and then doesn't look for a C compiler for the required feature. +# CMAKE_PROJECT_[PROJECT]_INCLUDE allows us to include a script after any +# project() calls in try_compile, which then injects a call to +# enable_language(C). CMAKE_TRY_COMPILE is the name of the project in the +# temporary CMakeList.txt created by try_compile: +# https://github.com/Kitware/CMake/blob/v3.4.0/Source/cmCoreTryCompile.cxx#L313 +file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/checkAssimpVersionLanguageOverride.cmake "enable_language(C)") +# For some reason, this also needs C to be enabled in this scope +enable_language(C) + # Go through all versions of interest and pick the highest one that compiles -foreach(_version 20201123 20190915 20151031) +foreach(_version 20210102 20201123 20200225 20191122 20190915 20160716 20151031) try_compile(_works ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/checkAssimpVersion.cpp ${_ASSIMP_TRY_COMPILE_LINK_OR_INCLUDE} @@ -71,6 +85,8 @@ foreach(_version 20201123 20190915 20151031) # CORRADE_CXX_STANDARD property doesn't get passed through. So I # have to use an internal variable for that instead. ${CORRADE_CXX11_STANDARD_FLAG} + CMAKE_FLAGS + -DCMAKE_PROJECT_CMAKE_TRY_COMPILE_INCLUDE=${CMAKE_CURRENT_BINARY_DIR}/checkAssimpVersionLanguageOverride.cmake OUTPUT_VARIABLE _version_output) if(_works) set(ASSIMP_VERSION ${_version}) diff --git a/src/MagnumPlugins/AssimpImporter/Test/AssimpImporterTest.cpp b/src/MagnumPlugins/AssimpImporter/Test/AssimpImporterTest.cpp index a8c8ab7b8..7b84aceed 100644 --- a/src/MagnumPlugins/AssimpImporter/Test/AssimpImporterTest.cpp +++ b/src/MagnumPlugins/AssimpImporter/Test/AssimpImporterTest.cpp @@ -27,6 +27,7 @@ DEALINGS IN THE SOFTWARE. */ +#include #include #include #include @@ -64,6 +65,7 @@ #include /* in assimp 3.0, version.h is missing this include for ASSIMP_API */ #include +#include #include #include #include @@ -89,7 +91,9 @@ struct AssimpImporterTest: TestSuite::Tester { void animationGltfNoScene(); void animationGltfBrokenSplineWarning(); void animationGltfSpline(); + void animationGltfTicksPerSecondPatching(); + void animationFbxTicksPerSecondPatching(); void animationDummyTracksRemovalEnabled(); void animationDummyTracksRemovalDisabled(); @@ -106,6 +110,7 @@ struct AssimpImporterTest: TestSuite::Tester { void skinMerge(); void camera(); + void cameraOrthographic(); void light(); void lightUnsupported(); void materialColor(); @@ -115,10 +120,15 @@ struct AssimpImporterTest: TestSuite::Tester { void materialWhiteAmbientTexture(); void materialMultipleTextures(); void materialTextureCoordinateSets(); + void materialTextureLayers(); + void materialRawUnrecognized(); + void materialRaw(); + void materialRawTextureLayers(); void mesh(); void pointMesh(); void lineMesh(); + void polygonMesh(); void meshCustomAttributes(); void meshSkinningAttributes(); void meshSkinningAttributesMultiple(); @@ -131,6 +141,7 @@ struct AssimpImporterTest: TestSuite::Tester { void emptyCollada(); void emptyGltf(); void scene(); + void sceneName(); void sceneCollapsedNode(); void upDirectionPatching(); void upDirectionPatchingPreTransformVertices(); @@ -162,6 +173,8 @@ struct AssimpImporterTest: TestSuite::Tester { /* Needs to load AnyImageImporter from system-wide location */ PluginManager::Manager _manager; + + unsigned int _assimpVersion; }; constexpr struct { @@ -215,6 +228,7 @@ AssimpImporterTest::AssimpImporterTest() { &AssimpImporterTest::animationGltfSpline}); addInstancedTests({&AssimpImporterTest::animationGltfTicksPerSecondPatching, + &AssimpImporterTest::animationFbxTicksPerSecondPatching, &AssimpImporterTest::animationDummyTracksRemovalEnabled, &AssimpImporterTest::animationDummyTracksRemovalDisabled}, Containers::arraySize(VerboseData)); @@ -234,6 +248,7 @@ AssimpImporterTest::AssimpImporterTest() { &AssimpImporterTest::skinMerge, &AssimpImporterTest::camera, + &AssimpImporterTest::cameraOrthographic, &AssimpImporterTest::light, &AssimpImporterTest::lightUnsupported, @@ -245,10 +260,15 @@ AssimpImporterTest::AssimpImporterTest() { &AssimpImporterTest::materialWhiteAmbientTexture, &AssimpImporterTest::materialMultipleTextures, &AssimpImporterTest::materialTextureCoordinateSets, + &AssimpImporterTest::materialTextureLayers, + &AssimpImporterTest::materialRawUnrecognized, + &AssimpImporterTest::materialRaw, + &AssimpImporterTest::materialRawTextureLayers, &AssimpImporterTest::mesh, &AssimpImporterTest::pointMesh, &AssimpImporterTest::lineMesh, + &AssimpImporterTest::polygonMesh, &AssimpImporterTest::meshCustomAttributes}); addInstancedTests({&AssimpImporterTest::meshSkinningAttributes}, @@ -263,6 +283,7 @@ AssimpImporterTest::AssimpImporterTest() { &AssimpImporterTest::emptyCollada, &AssimpImporterTest::emptyGltf, &AssimpImporterTest::scene, + &AssimpImporterTest::sceneName, &AssimpImporterTest::sceneCollapsedNode}); addInstancedTests({&AssimpImporterTest::upDirectionPatching, @@ -313,8 +334,21 @@ AssimpImporterTest::AssimpImporterTest() { #ifdef STBIMAGEIMPORTER_PLUGIN_FILENAME CORRADE_INTERNAL_ASSERT_OUTPUT(_manager.load(STBIMAGEIMPORTER_PLUGIN_FILENAME) & PluginManager::LoadState::Loaded); #endif + + _assimpVersion = aiGetVersionMajor()*100 + aiGetVersionMinor()*10 + #if ASSIMP_HAS_VERSION_PATCH + + aiGetVersionPatch() + #endif + ; + + /* Assimp 5.0.0 reports itself as 4.1.0 */ + #if ASSIMP_IS_VERSION_5_OR_GREATER + _assimpVersion = Math::max(_assimpVersion, 500u); + #endif } +using namespace Containers::Literals; + void AssimpImporterTest::openFile() { auto&& data = VerboseData[testCaseInstanceId()]; setTestCaseDescription(data.name); @@ -369,6 +403,12 @@ void AssimpImporterTest::openData() { } void AssimpImporterTest::openDataFailed() { + /* With Assimp 5.1.0 this fires an assert in because Assimp tries + to load *anything* with X3DImporter and it doesn't perform any checks: + https://github.com/assimp/assimp/issues/4177 */ + if(_assimpVersion >= 510 && aiGetImporterDesc("x3d")) + CORRADE_SKIP("Current version of assimp would assert on this test."); + Containers::Pointer importer = _manager.instantiate("AssimpImporter"); std::ostringstream out; @@ -381,27 +421,25 @@ void AssimpImporterTest::openDataFailed() { /* This does not indicate general assimp animation support, only used to skip tests on certain versions and test files. */ -bool supportsAnimation(const Containers::StringView fileName) { +bool supportsAnimation(const Containers::StringView fileName, unsigned int assimpVersion) { /* 5.0.0 supports all of Collada, FBX, glTF */ - #if ASSIMP_IS_VERSION_5 - static_cast(fileName); - return true; - #else - if(fileName.hasSuffix(".gltf")) return false; - - const unsigned int version = aiGetVersionMajor()*100 + aiGetVersionMinor(); - CORRADE_INTERNAL_ASSERT(fileName.hasSuffix(".dae") || fileName.hasSuffix(".fbx")); - /* That's as far back as I checked, both Collada and FBX animations - supported */ - return version >= 302; - #endif + if(assimpVersion >= 500) { + return true; + } else if(fileName.hasSuffix(".gltf"_s)) { + return false; + } else { + CORRADE_INTERNAL_ASSERT(fileName.hasSuffix(".dae"_s) || fileName.hasSuffix(".fbx"_s)); + /* That's as far back as I checked, both Collada and FBX animations + supported */ + return assimpVersion >= 320; + } } void AssimpImporterTest::animation() { auto&& data = ExportedFileData[testCaseInstanceId()]; setTestCaseDescription(data.name); - if(!supportsAnimation(data.suffix)) + if(!supportsAnimation(data.suffix, _assimpVersion)) CORRADE_SKIP("Animation for this file type is not supported with the current version of Assimp"); /* Animation created and exported with Blender. Most animation tracks got @@ -492,7 +530,7 @@ void AssimpImporterTest::animation() { constexpr UnsignedInt KeyCount = 4; constexpr Float keys[KeyCount]{0.0f, 2.5f, 5.0f, 7.5f}; - CORRADE_VERIFY(player.duration().contains({ keys[0], keys[Containers::arraySize(keys) - 1] })); + CORRADE_VERIFY(player.duration().contains({keys[0], keys[Containers::arraySize(keys) - 1]})); player.play(0.0f); /* Some Blender exporters (e.g. FBX) and our manual Collada correction @@ -526,6 +564,8 @@ void AssimpImporterTest::animation() { }; for(UnsignedInt i = 0; i < Containers::arraySize(keys); i++) { + CORRADE_ITERATION(i); + player.advance(keys[i]); for(Node& n: nodes) correctNode(n); @@ -560,7 +600,7 @@ const Containers::StaticArray<3, AnimationTarget> AnimationGltfLinearTargets{ }; void AssimpImporterTest::animationGltf() { - if(!supportsAnimation(".gltf")) + if(!supportsAnimation(".gltf"_s, _assimpVersion)) CORRADE_SKIP("glTF 2 animation is not supported with the current version of Assimp"); /* Using the same files as TinyGltfImporterTest, but modified to include a @@ -684,8 +724,10 @@ void AssimpImporterTest::animationGltf() { } void AssimpImporterTest::animationGltfNoScene() { - if(!supportsAnimation(".gltf")) + if(!supportsAnimation(".gltf"_s, _assimpVersion)) CORRADE_SKIP("glTF 2 animation is not supported with the current version of Assimp"); + if(_assimpVersion >= 510) + CORRADE_SKIP("Current version of assimp wouldn't load this file."); /* This reuses the TinyGltfImporter test files, not the corrected ones used by other tests. */ Containers::Pointer importer = _manager.instantiate("AssimpImporter"); @@ -697,11 +739,12 @@ void AssimpImporterTest::animationGltfNoScene() { } void AssimpImporterTest::animationGltfBrokenSplineWarning() { - if(!supportsAnimation(".gltf")) + if(!supportsAnimation(".gltf"_s, _assimpVersion)) CORRADE_SKIP("glTF 2 animation is not supported with the current version of Assimp"); - if(!ASSIMP_HAS_BROKEN_GLTF_SPLINES) - CORRADE_SKIP("Current version of assimp correctly imports glTF spline-interpolated animations."); + #if !ASSIMP_HAS_BROKEN_GLTF_SPLINES + CORRADE_SKIP("Current version of assimp correctly imports glTF spline-interpolated animations."); + #endif Containers::Pointer importer = _manager.instantiate("AssimpImporter"); @@ -717,7 +760,7 @@ void AssimpImporterTest::animationGltfBrokenSplineWarning() { } void AssimpImporterTest::animationGltfSpline() { - if(!supportsAnimation(".gltf")) + if(!supportsAnimation(".gltf"_s, _assimpVersion)) CORRADE_SKIP("glTF 2 animation is not supported with the current version of Assimp"); Containers::Pointer importer = _manager.instantiate("AssimpImporter"); @@ -767,10 +810,10 @@ void AssimpImporterTest::animationGltfSpline() { #endif constexpr Quaternion rotationValues[]{ - {{0.780076f, 0.0260025f, 0.598059f}, 0.182018f}, - {{-0.711568f, 0.391362f, 0.355784f}, 0.462519f}, - {{0.598059f, 0.182018f, 0.0260025f}, 0.780076f}, - {{0.711568f, -0.355784f, -0.462519f}, -0.391362f} + {{ 0.780076f, 0.0260025f, 0.598059f}, 0.182018f}, + {{ 0.711568f, -0.391362f, -0.355784f}, -0.462519f}, + {{-0.598059f, -0.182018f, -0.026003f}, -0.780076f}, + {{-0.711568f, 0.355784f, 0.462519f}, 0.391362f} }; CORRADE_COMPARE_AS(rotation.values(), Containers::stridedArrayView(rotationValues), TestSuite::Compare::Container); } @@ -830,15 +873,10 @@ void AssimpImporterTest::animationGltfTicksPerSecondPatching() { auto&& data = VerboseData[testCaseInstanceId()]; setTestCaseDescription(data.name); - if(!supportsAnimation(".gltf")) + if(!supportsAnimation(".gltf"_s, _assimpVersion)) CORRADE_SKIP("glTF 2 animation is not supported with the current version of Assimp"); - /* This was fixed right after 5.0.0, but 5.0.1 only selected compilation - fixes and didn't bump the minor version. Boldly assuming the next - minor version will have fixes from 2019. */ - const unsigned int version = aiGetVersionMajor()*100 + aiGetVersionMinor(); - const bool hasInvalidTicksPerSecond = version <= 500; - if(!hasInvalidTicksPerSecond) + if(_assimpVersion > 500) CORRADE_SKIP("Current version of assimp correctly sets glTF ticks per second."); Containers::Pointer importer = _manager.instantiate("AssimpImporter"); @@ -859,11 +897,37 @@ void AssimpImporterTest::animationGltfTicksPerSecondPatching() { CORRADE_VERIFY(out.str().empty()); } +void AssimpImporterTest::animationFbxTicksPerSecondPatching() { + auto&& data = VerboseData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + #if !ASSIMP_HAS_BROKEN_FBX_TICKS_PER_SECOND + CORRADE_SKIP("Current version of assimp correctly sets FBX ticks per second."); + #endif + + Containers::Pointer importer = _manager.instantiate("AssimpImporter"); + importer->setFlags(data.flags); + CORRADE_VERIFY(importer->openFile(Utility::Directory::join(ASSIMPIMPORTER_TEST_DIR, + "exported-animation.fbx"))); + + std::ostringstream out; + { + Debug redirectDebug{&out}; + CORRADE_VERIFY(importer->animation(0)); + } + + if(data.flags >= ImporterFlag::Verbose) { + CORRADE_VERIFY(Containers::StringView{out.str()}.contains( + " ticks per second is incorrect for FBX, patching to 1000\n")); + } else + CORRADE_VERIFY(out.str().empty()); +} + void AssimpImporterTest::animationDummyTracksRemovalEnabled() { auto&& data = VerboseData[testCaseInstanceId()]; setTestCaseDescription(data.name); - if(!supportsAnimation(".gltf")) + if(!supportsAnimation(".gltf"_s, _assimpVersion)) CORRADE_SKIP("glTF 2 animation is not supported with the current version of Assimp"); /* Correct removal is already implicitly tested in animationGltf(), @@ -923,7 +987,7 @@ void AssimpImporterTest::animationDummyTracksRemovalDisabled() { auto&& data = VerboseData[testCaseInstanceId()]; setTestCaseDescription(data.name); - if(!supportsAnimation(".gltf")) + if(!supportsAnimation(".gltf"_s, _assimpVersion)) CORRADE_SKIP("glTF 2 animation is not supported with the current version of Assimp"); Containers::Pointer importer = _manager.instantiate("AssimpImporter"); @@ -979,12 +1043,16 @@ void AssimpImporterTest::animationDummyTracksRemovalDisabled() { } void AssimpImporterTest::animationShortestPathOptimizationEnabled() { - if(!supportsAnimation(".gltf")) + if(!supportsAnimation(".gltf"_s, _assimpVersion)) CORRADE_SKIP("glTF 2 animation is not supported with the current version of Assimp"); Containers::Pointer importer = _manager.instantiate("AssimpImporter"); /* Enabled by default */ CORRADE_VERIFY(importer->configuration().value("optimizeQuaternionShortestPath")); + /* Can't reuse the TinyGltfImporter test file because it has differently + sized accessors. Assimp < 5.1.0 used to allow this but newer versions + don't. This file is taken from CgltfImporter but with a default scene + added, otherwise Assimp 5.1.0 won't load it. */ CORRADE_VERIFY(importer->openFile(Utility::Directory::join(ASSIMPIMPORTER_TEST_DIR, "animation-patching.gltf"))); @@ -1031,7 +1099,7 @@ void AssimpImporterTest::animationShortestPathOptimizationEnabled() { } void AssimpImporterTest::animationShortestPathOptimizationDisabled() { - if(!supportsAnimation(".gltf")) + if(!supportsAnimation(".gltf"_s, _assimpVersion)) CORRADE_SKIP("glTF 2 animation is not supported with the current version of Assimp"); Containers::Pointer importer = _manager.instantiate("AssimpImporter"); @@ -1104,7 +1172,7 @@ void AssimpImporterTest::animationShortestPathOptimizationDisabled() { } void AssimpImporterTest::animationQuaternionNormalizationEnabled() { - if(!supportsAnimation(".gltf")) + if(!supportsAnimation(".gltf"_s, _assimpVersion)) CORRADE_SKIP("glTF 2 animation is not supported with the current version of Assimp"); Containers::Pointer importer = _manager.instantiate("AssimpImporter"); @@ -1142,7 +1210,7 @@ void AssimpImporterTest::animationQuaternionNormalizationEnabled() { } void AssimpImporterTest::animationQuaternionNormalizationDisabled() { - if(!supportsAnimation(".gltf")) + if(!supportsAnimation(".gltf"_s, _assimpVersion)) CORRADE_SKIP("glTF 2 animation is not supported with the current version of Assimp"); Containers::Pointer importer = _manager.instantiate("AssimpImporter"); @@ -1169,13 +1237,13 @@ void AssimpImporterTest::animationQuaternionNormalizationDisabled() { } void AssimpImporterTest::animationMergeEmpty() { - if(!supportsAnimation(".gltf")) + if(!supportsAnimation(".gltf"_s, _assimpVersion)) CORRADE_SKIP("glTF 2 animation is not supported with the current version of Assimp"); Containers::Pointer importer = _manager.instantiate("AssimpImporter"); /* Enable animation merging */ importer->configuration().setValue("mergeAnimationClips", true); - CORRADE_VERIFY(importer->openFile(Utility::Directory::join(TINYGLTFIMPORTER_TEST_DIR, + CORRADE_VERIFY(importer->openFile(Utility::Directory::join(ASSIMPIMPORTER_TEST_DIR, "empty.gltf"))); CORRADE_COMPARE(importer->animationCount(), 0); @@ -1183,7 +1251,7 @@ void AssimpImporterTest::animationMergeEmpty() { } void AssimpImporterTest::animationMerge() { - if(!supportsAnimation(".gltf")) + if(!supportsAnimation(".gltf"_s, _assimpVersion)) CORRADE_SKIP("glTF 2 animation is not supported with the current version of Assimp"); Containers::Pointer importer = _manager.instantiate("AssimpImporter"); @@ -1284,23 +1352,37 @@ void AssimpImporterTest::animationMerge() { CORRADE_COMPARE(scaling2.interpolation(), Animation::Interpolation::Linear); } -/* The checks are identical to animation support so re-use that. */ -bool supportsSkinning(const Containers::StringView fileName) { - return supportsAnimation(fileName); +/* The checks are identical to animation support so re-use that */ +bool supportsSkinning(const Containers::StringView fileName, unsigned int assimpVersion) { + return supportsAnimation(fileName, assimpVersion); } void calculateTransforms(Containers::ArrayView transforms, Containers::ArrayView objects, UnsignedInt objectId, const Matrix4& parentTransform = {}) { - const Matrix4 transform = objects[objectId].transformation() * parentTransform; + const Matrix4 transform = parentTransform * objects[objectId].transformation(); transforms[objectId] = transform; for(UnsignedInt childId: objects[objectId].children()) calculateTransforms(transforms, objects, childId, transform); } +/* Since 5.1.0 the Collada importer uses the mesh ID as the name. Imitate the + Blender(?) exporter that generated the IDs from the name. A bit hacky but + still better than special-casing every test that loads meshes by name. */ +std::string fixMeshName(const Containers::StringView meshName, const Containers::StringView fileName, unsigned int assimpVersion) { + std::string fixed = meshName; + if(assimpVersion >= 510 && fileName.hasSuffix(".dae"_s)) { + for(char& c: fixed) + if(c != '-' && !std::isalnum(static_cast(c))) c = '_'; + fixed += "-mesh"; + } + + return fixed; +} + void AssimpImporterTest::skin() { auto&& data = ExportedFileData[testCaseInstanceId()]; setTestCaseDescription(data.name); - if(!supportsSkinning(data.suffix)) + if(!supportsSkinning(data.suffix, _assimpVersion)) CORRADE_SKIP("Skin data for this file type is not supported with the current version of Assimp"); /* Skinned mesh imported into Blender and then exported */ @@ -1329,35 +1411,38 @@ void AssimpImporterTest::skin() { auto scene = importer->scene(sceneId); CORRADE_VERIFY(scene); + /* Some Blender exporters don't transform skin matrices correctly. Also see + the comment for ExportedFileData. */ + const Matrix4 correction{data.correction.toMatrix()}; + for(UnsignedInt i: scene->children3D()) { calculateTransforms(globalTransforms, objects, i); } - constexpr const char* meshNames[]{"Mesh_1", "Mesh_2"}; - constexpr const char* jointNames[][2]{ + constexpr Containers::StringView objectNames[]{ + "Mesh_1"_s, "Mesh_2"_s, "Plane"_s + }; + const std::string jointNames[][2]{ {"Node_1", "Node_2"}, {"Node_3", "Node_4"} }; - /* Some Blender exporters don't transform skin matrices correctly. Also see - the comment for ExportedFileData. */ - const Matrix4 correction{data.correction.toMatrix()}; - - for(UnsignedInt i = 0; i != Containers::arraySize(meshNames); ++i) { + for(UnsignedInt i = 0; i != Containers::arraySize(objectNames) - 1; ++i) { /* Skin names are taken from mesh names, skin order is arbitrary */ - const Int index = importer->skin3DForName(meshNames[i]); - CORRADE_VERIFY(index != -1); - CORRADE_COMPARE(importer->skin3DName(index), meshNames[i]); - CORRADE_VERIFY(importer->meshForName(meshNames[i]) != -1); + const std::string meshName = fixMeshName(objectNames[i], data.suffix, _assimpVersion); + const Int skinIndex = importer->skin3DForName(meshName); + CORRADE_VERIFY(skinIndex != -1); + CORRADE_COMPARE(importer->skin3DName(skinIndex), meshName); + CORRADE_VERIFY(importer->meshForName(meshName) != -1); - auto skin = importer->skin3D(index); + auto skin = importer->skin3D(skinIndex); CORRADE_VERIFY(skin); CORRADE_VERIFY(skin->importerState()); /* Don't check joint order, only presence */ auto joints = skin->joints(); CORRADE_COMPARE(joints.size(), Containers::arraySize(jointNames[i])); - for(const char* name: jointNames[i]) { + for(const std::string& name: jointNames[i]) { auto found = std::find_if(joints.begin(), joints.end(), [&](UnsignedInt joint) { /* Blender's Collada exporter adds an Armature_ prefix to object names */ @@ -1372,22 +1457,23 @@ void AssimpImporterTest::skin() { transform. */ auto bindMatrices = skin->inverseBindMatrices(); CORRADE_COMPARE(bindMatrices.size(), joints.size()); - auto meshObject = importer->object3D(meshNames[i]); + auto meshObject = importer->object3D(objectNames[i]); CORRADE_VERIFY(meshObject); CORRADE_COMPARE(meshObject->instanceType(), ObjectInstanceType3D::Mesh); - CORRADE_COMPARE(static_cast(*meshObject).skin(), index); + CORRADE_COMPARE(static_cast(*meshObject).skin(), skinIndex); const Matrix4 meshTransform = meshObject->transformation(); for(UnsignedInt j = 0; j != joints.size(); ++j) { - const Matrix4 invertedTransform = correction * meshTransform * globalTransforms[joints[j]].inverted(); + const Matrix4 invertedTransform = globalTransforms[joints[j]].inverted() * meshTransform * correction; CORRADE_COMPARE(bindMatrices[j], invertedTransform); } } { /* Unskinned meshes and mesh nodes shouldn't have a skin */ - CORRADE_VERIFY(importer->meshForName("Plane") != -1); - CORRADE_COMPARE(importer->skin3DForName("Plane"), -1); - auto meshObject = importer->object3D("Plane"); + const std::string meshName = fixMeshName(objectNames[2], data.suffix, _assimpVersion); + CORRADE_VERIFY(importer->meshForName(meshName) != -1); + CORRADE_COMPARE(importer->skin3DForName(meshName), -1); + auto meshObject = importer->object3D(objectNames[2]); CORRADE_VERIFY(meshObject); CORRADE_COMPARE(meshObject->instanceType(), ObjectInstanceType3D::Mesh); CORRADE_COMPARE(static_cast(*meshObject).skin(), -1); @@ -1395,12 +1481,11 @@ void AssimpImporterTest::skin() { } void AssimpImporterTest::skinNoMeshes() { - if(!supportsSkinning(".gltf")) + if(!supportsSkinning(".gltf"_s, _assimpVersion)) CORRADE_SKIP("glTF 2 skinning is not supported with the current version of Assimp"); - /* Reusing the TinyGltfImporter test file without meshes */ Containers::Pointer importer = _manager.instantiate("AssimpImporter"); - CORRADE_VERIFY(importer->openFile(Utility::Directory::join(TINYGLTFIMPORTER_TEST_DIR, "skin.gltf"))); + CORRADE_VERIFY(importer->openFile(Utility::Directory::join(ASSIMPIMPORTER_TEST_DIR, "skin-no-mesh.gltf"))); /* Assimp only lets us access joints for each mesh. No mesh = no joints. */ CORRADE_COMPARE(importer->meshCount(), 0); @@ -1424,7 +1509,7 @@ void AssimpImporterTest::skinMergeEmpty() { } void AssimpImporterTest::skinMerge() { - if(!supportsAnimation(".gltf")) + if(!supportsAnimation(".gltf"_s, _assimpVersion)) CORRADE_SKIP("GLTF skinning is not supported with the current version of Assimp"); Containers::Pointer importer = _manager.instantiate("AssimpImporter"); @@ -1484,18 +1569,72 @@ void AssimpImporterTest::camera() { Containers::Pointer importer = _manager.instantiate("AssimpImporter"); CORRADE_VERIFY(importer->openFile(Utility::Directory::join(ASSIMPIMPORTER_TEST_DIR, "camera.dae"))); - CORRADE_COMPARE(importer->cameraCount(), 1); - Containers::Optional camera = importer->camera(0); + CORRADE_COMPARE(importer->cameraCount(), 2); + + CORRADE_COMPARE(importer->cameraName(0), "Camera"); + CORRADE_COMPARE(importer->cameraForName("Camera"), 0); + CORRADE_COMPARE(importer->cameraName(1), "Camera-no-aspect"); + CORRADE_COMPARE(importer->cameraForName("Camera-no-aspect"), 1); + CORRADE_COMPARE(importer->cameraForName(""), -1); + + Containers::Optional camera1 = importer->camera(0); + CORRADE_VERIFY(camera1); + CORRADE_COMPARE(camera1->type(), CameraType::Perspective3D); + CORRADE_COMPARE(camera1->fov(), 49.13434_degf); + CORRADE_COMPARE(camera1->aspectRatio(), 1.77777f); + CORRADE_COMPARE(camera1->near(), 0.123f); + CORRADE_COMPARE(camera1->far(), 123.0f); + + Containers::Optional camera2 = importer->camera(1); + CORRADE_VERIFY(camera2); + CORRADE_COMPARE(camera2->type(), CameraType::Perspective3D); + CORRADE_COMPARE(camera2->fov(), 12.347_degf); + /* No aspect ratio, defaults to 1 */ + CORRADE_COMPARE(camera2->aspectRatio(), 1.0f); + CORRADE_COMPARE(camera2->near(), 0.1f); + CORRADE_COMPARE(camera2->far(), 2.0f); + + CORRADE_COMPARE(importer->object3DCount(), 2); + + Containers::Pointer cameraObject1 = importer->object3D(0); + CORRADE_COMPARE(cameraObject1->instanceType(), ObjectInstanceType3D::Camera); + CORRADE_COMPARE(cameraObject1->instance(), 0); + + Containers::Pointer cameraObject2 = importer->object3D(1); + CORRADE_COMPARE(cameraObject2->instanceType(), ObjectInstanceType3D::Camera); + CORRADE_COMPARE(cameraObject2->instance(), 1); +} + +void AssimpImporterTest::cameraOrthographic() { + if(_assimpVersion < 410) + CORRADE_SKIP("glTF 2 is supported since Assimp 4.1."); + + Containers::Pointer importer = _manager.instantiate("AssimpImporter"); + CORRADE_VERIFY(importer->openFile(Utility::Directory::join(ASSIMPIMPORTER_TEST_DIR, "camera.gltf"))); + CORRADE_COMPARE(importer->cameraCount(), 4); + + /* For some reason glTF camera names are set to the names of the nodes that + hold them. This file has no node names, so it becomes nodes[0]. + Versions before 5.1.0, of course, named them differently... */ + const char* name = _assimpVersion >= 510 ? "nodes[0]" : "nodes_0"; + Containers::Optional camera = importer->camera(name); CORRADE_VERIFY(camera); - CORRADE_COMPARE(camera->fov(), 49.13434_degf); - CORRADE_COMPARE(camera->near(), 0.123f); - CORRADE_COMPARE(camera->far(), 123.0f); + { + #if !ASSIMP_HAS_ORTHOGRAPHIC_CAMERA + CORRADE_EXPECT_FAIL("Current version of assimp imports orthograpic cameras as perspective cameras"); + #endif + CORRADE_COMPARE(camera->type(), CameraType::Orthographic3D); + CORRADE_COMPARE(camera->size(), (Vector2{4.0f, 3.0f})); + } - CORRADE_COMPARE(importer->object3DCount(), 1); + #if !ASSIMP_HAS_ORTHOGRAPHIC_CAMERA + CORRADE_COMPARE(camera->type(), CameraType::Perspective3D); + /* FOV and aspect ratio arbitrarily differ between importers. No use + testing specific values as they'll break with the next version anyway. */ + #endif - Containers::Pointer cameraObject = importer->object3D(0); - CORRADE_COMPARE(cameraObject->instanceType(), ObjectInstanceType3D::Camera); - CORRADE_COMPARE(cameraObject->instance(), 0); + CORRADE_COMPARE(camera->near(), 0.01f); + CORRADE_COMPARE(camera->far(), 100.0f); } void AssimpImporterTest::light() { @@ -1503,33 +1642,47 @@ void AssimpImporterTest::light() { CORRADE_VERIFY(importer->openFile(Utility::Directory::join(ASSIMPIMPORTER_TEST_DIR, "light.dae"))); CORRADE_COMPARE(importer->lightCount(), 4); + CORRADE_COMPARE(importer->lightForName(""), -1); + + /* Collada light import is broken in Assimp 5.1.0: + https://github.com/assimp/assimp/issues/4179 + This was fixed a few days later in 5.1.1 but (as is customary with that + library) they didn't update the version so both report as 5.1.0. + To keep our sanity, we assume 5.1.0 means 5.1.1 and ignore this was + ever broken. */ /* Spot light */ { + CORRADE_COMPARE(importer->lightName(0), "Spot"); + CORRADE_COMPARE(importer->lightForName("Spot"), 0); auto light = importer->light(0); CORRADE_VERIFY(light); CORRADE_COMPARE(light->type(), LightData::Type::Spot); CORRADE_COMPARE(light->color(), (Color3{0.12f, 0.24f, 0.36f})); CORRADE_COMPARE(light->intensity(), 1.0f); - CORRADE_COMPARE(light->attenuation(), (Vector3{0.1f, 0.3f, 0.5f})); CORRADE_COMPARE(light->range(), Constants::inf()); + CORRADE_COMPARE(light->attenuation(), (Vector3{0.1f, 0.3f, 0.5f})); CORRADE_COMPARE(light->innerConeAngle(), 45.0_degf); - /* Not sure how it got calculated from 0.15 falloff exponent, but let's - just trust Assimp for once */ + /* Not sure how it got calculated from 0.15 falloff exponent, but + let's just trust Assimp for once */ CORRADE_COMPARE(light->outerConeAngle(), 135.0_degf); /* Point light */ } { + CORRADE_COMPARE(importer->lightName(1), "Point"); + CORRADE_COMPARE(importer->lightForName("Point"), 1); auto light = importer->light(1); CORRADE_VERIFY(light); CORRADE_COMPARE(light->type(), LightData::Type::Point); CORRADE_COMPARE(light->color(), (Color3{0.5f, 0.25f, 0.05f})); CORRADE_COMPARE(light->intensity(), 1.0f); - CORRADE_COMPARE(light->attenuation(), (Vector3{0.1f, 0.7f, 0.9f})); CORRADE_COMPARE(light->range(), Constants::inf()); + CORRADE_COMPARE(light->attenuation(), (Vector3{0.1f, 0.7f, 0.9f})); /* Directional light */ } { + CORRADE_COMPARE(importer->lightName(2), "Sun"); + CORRADE_COMPARE(importer->lightForName("Sun"), 2); auto light = importer->light(2); CORRADE_VERIFY(light); CORRADE_COMPARE(light->type(), LightData::Type::Directional); @@ -1538,11 +1691,13 @@ void AssimpImporterTest::light() { CORRADE_COMPARE(light->color(), (Color3{1.0f, 0.15f, 0.45f})*10.0f); CORRADE_COMPARE(light->intensity(), 1.0f); - /* Ambient light -- imported as Point with no attenuation */ + /* Ambient light */ } { + CORRADE_COMPARE(importer->lightName(3), "Ambient"); + CORRADE_COMPARE(importer->lightForName("Ambient"), 3); auto light = importer->light(3); CORRADE_VERIFY(light); - CORRADE_COMPARE(light->type(), LightData::Type::Point); + CORRADE_COMPARE(light->type(), LightData::Type::Ambient); CORRADE_COMPARE(light->color(), (Color3{0.01f, 0.02f, 0.05f})); CORRADE_COMPARE(light->intensity(), 1.0f); CORRADE_COMPARE(light->attenuation(), (Vector3{1.0f, 0.0f, 0.0f})); @@ -1568,6 +1723,8 @@ void AssimpImporterTest::lightUnsupported() { void AssimpImporterTest::materialColor() { Containers::Pointer importer = _manager.instantiate("AssimpImporter"); + /* Unrecognized materials are tested separately in materialRaw() */ + importer->configuration().setValue("ignoreUnrecognizedMaterialData", true); CORRADE_VERIFY(importer->openFile(Utility::Directory::join(ASSIMPIMPORTER_TEST_DIR, "material-color.dae"))); CORRADE_COMPARE(importer->materialCount(), 1); @@ -1588,9 +1745,8 @@ void AssimpImporterTest::materialColor() { CORRADE_COMPARE(phong.specularColor(), (Color4{0.15f, 0.1f, 0.05f, 0.5f})); CORRADE_COMPARE(phong.shininess(), 50.0f); - const UnsignedInt version = aiGetVersionMajor()*100 + aiGetVersionMinor(); /* Ancient assimp version add "-material" suffix */ - if(version < 302) { + if(_assimpVersion < 320) { CORRADE_COMPARE(importer->materialForName("Material-material"), 0); CORRADE_COMPARE(importer->materialName(0), "Material-material"); } else { @@ -1602,6 +1758,7 @@ void AssimpImporterTest::materialColor() { void AssimpImporterTest::materialTexture() { Containers::Pointer importer = _manager.instantiate("AssimpImporter"); + importer->configuration().setValue("ignoreUnrecognizedMaterialData", true); CORRADE_VERIFY(importer->openFile(Utility::Directory::join(ASSIMPIMPORTER_TEST_DIR, "material-texture.dae"))); CORRADE_COMPARE(importer->materialCount(), 1); @@ -1638,6 +1795,7 @@ void AssimpImporterTest::materialTexture() { void AssimpImporterTest::materialColorTexture() { Containers::Pointer importer = _manager.instantiate("AssimpImporter"); + importer->configuration().setValue("ignoreUnrecognizedMaterialData", true); CORRADE_VERIFY(importer->openFile(Utility::Directory::join(ASSIMPIMPORTER_TEST_DIR, "material-color-texture.obj"))); { @@ -1653,8 +1811,7 @@ void AssimpImporterTest::materialColorTexture() { /* Newer versions import also useless zero texcoords. Not sure if it's since 4.0 or 5.0, but definitely 3.2 returns 7. */ - const UnsignedInt version = aiGetVersionMajor()*100 + aiGetVersionMinor(); - if(version < 400) + if(_assimpVersion < 400) CORRADE_COMPARE(material->attributeCount(), 7); else CORRADE_COMPARE(material->attributeCount(), 10); @@ -1688,11 +1845,8 @@ void AssimpImporterTest::materialStlWhiteAmbientPatch() { CORRADE_VERIFY(material); CORRADE_COMPARE(material->types(), MaterialType::Phong); - const UnsignedInt version = aiGetVersionMajor()*100 + aiGetVersionMinor(); { - /* aiGetVersion*() returns 401 for assimp 5, FFS, so we have to check - differently. See CMakeLists.txt for details. */ - CORRADE_EXPECT_FAIL_IF(version < 401 || ASSIMP_IS_VERSION_5, + CORRADE_EXPECT_FAIL_IF(_assimpVersion < 410 || _assimpVersion >= 500, "Assimp < 4.1 and >= 5.0 behaves properly regarding STL material ambient"); CORRADE_COMPARE(out.str(), "Trade::AssimpImporter::material(): white ambient detected, forcing back to black\n"); } @@ -1700,13 +1854,13 @@ void AssimpImporterTest::materialStlWhiteAmbientPatch() { const auto& phong = material->as(); CORRADE_VERIFY(!phong.hasAttribute(MaterialAttribute::AmbientTexture)); /* WHY SO COMPLICATED, COME ON */ - if(version < 401 || ASSIMP_IS_VERSION_5) + if(_assimpVersion < 410 || _assimpVersion >= 500) CORRADE_COMPARE(phong.ambientColor(), Color3{0.05f}); else CORRADE_COMPARE(phong.ambientColor(), 0x000000_srgbf); /* ASS IMP WHAT?! WHY 3.2 is different from 3.0 and 4.0?! */ - if(version == 302) { + if(_assimpVersion == 320) { CORRADE_COMPARE(phong.specularColor(), Color3{0.6f}); CORRADE_COMPARE(phong.diffuseColor(), Color3{0.6f}); } else { @@ -1875,6 +2029,366 @@ void AssimpImporterTest::materialTextureCoordinateSets() { CORRADE_COMPARE(phong.normalTextureCoordinates(), 2); } +void AssimpImporterTest::materialTextureLayers() { + if(_assimpVersion < 500) + CORRADE_SKIP("This version of assimp doesn't imported layered FBX materials."); + + Containers::Pointer importer = _manager.instantiate("AssimpImporter"); + importer->configuration().setValue("ignoreUnrecognizedMaterialData", true); + + CORRADE_VERIFY(importer->openFile(Utility::Directory::join(ASSIMPIMPORTER_TEST_DIR, "material-layers.fbx"))); + CORRADE_COMPARE(importer->materialCount(), 1); + + const auto material = importer->material(0); + CORRADE_VERIFY(material); + CORRADE_COMPARE(material->layerCount(), 3); + + /* Layer 0. Material attributes + diffuse texture + ambient texture. */ + { + CORRADE_VERIFY(material->hasAttribute(0, MaterialAttribute::AmbientColor)); + CORRADE_COMPARE(material->attribute(0, MaterialAttribute::AmbientColor), + (Color4{0.1f, 0.2f, 0.3f, 1.0f})); + + CORRADE_VERIFY(material->hasAttribute(0, MaterialAttribute::DiffuseColor)); + CORRADE_COMPARE(material->attribute(0, MaterialAttribute::DiffuseColor), + (Color4{0.7f, 0.6f, 0.5f, 1.0f})); + + CORRADE_VERIFY(material->hasAttribute(0, MaterialAttribute::AmbientTexture)); + CORRADE_COMPARE(material->attribute(0, MaterialAttribute::AmbientTexture), 0); + + CORRADE_VERIFY(material->hasAttribute(0, MaterialAttribute::AmbientTextureCoordinates)); + CORRADE_COMPARE(material->attribute(0, MaterialAttribute::AmbientTextureCoordinates), 0); + + CORRADE_VERIFY(material->hasAttribute(0, MaterialAttribute::DiffuseTexture)); + CORRADE_COMPARE(material->attribute(0, MaterialAttribute::DiffuseTexture), 1); + + CORRADE_VERIFY(material->hasAttribute(0, MaterialAttribute::DiffuseTextureCoordinates)); + CORRADE_COMPARE(material->attribute(0, MaterialAttribute::DiffuseTextureCoordinates), 0); + + /* Layer 1. Diffuse texture. Can't have any material attributes. */ + } { + CORRADE_VERIFY(!material->hasAttribute(1, MaterialAttribute::AmbientColor)); + CORRADE_VERIFY(!material->hasAttribute(1, MaterialAttribute::DiffuseColor)); + + CORRADE_VERIFY(material->hasAttribute(0, MaterialAttribute::DiffuseTexture)); + CORRADE_COMPARE(material->attribute(1, MaterialAttribute::DiffuseTexture), 2); + + CORRADE_VERIFY(material->hasAttribute(1, MaterialAttribute::DiffuseTextureCoordinates)); + CORRADE_COMPARE(material->attribute(1, MaterialAttribute::DiffuseTextureCoordinates), 0); + + /* Layer 2. Diffuse texture. Can't have any material attributes. */ + } { + CORRADE_VERIFY(!material->hasAttribute(2, MaterialAttribute::AmbientColor)); + CORRADE_VERIFY(!material->hasAttribute(2, MaterialAttribute::DiffuseColor)); + + CORRADE_VERIFY(material->hasAttribute(2, MaterialAttribute::DiffuseTexture)); + CORRADE_COMPARE(material->attribute(2, MaterialAttribute::DiffuseTexture), 3); + + CORRADE_VERIFY(material->hasAttribute(2, MaterialAttribute::DiffuseTextureCoordinates)); + CORRADE_COMPARE(material->attribute(2, MaterialAttribute::DiffuseTextureCoordinates), 0); + } +} + +Containers::StringView extractMaterialKey(const char* data, int, int) { + return data; +} + +void AssimpImporterTest::materialRawUnrecognized() { + if(_assimpVersion < 500) + CORRADE_SKIP("This version of Assimp doesn't import AI_MATKEY_COLOR_TRANSPARENT from FBX files."); + + Containers::Pointer importer = _manager.instantiate("AssimpImporter"); + /* Disabled by default */ + CORRADE_VERIFY(!importer->configuration().value("ignoreUnrecognizedMaterialData")); + CORRADE_VERIFY(importer->openFile(Utility::Directory::join(ASSIMPIMPORTER_TEST_DIR, "material-raw.fbx"))); + + CORRADE_COMPARE(importer->materialCount(), 2); + + Containers::Optional material = importer->material("Standard_Types"); + CORRADE_VERIFY(material); + CORRADE_COMPARE(material->types(), MaterialType::Phong); + CORRADE_COMPARE(material->layerCount(), 1); + CORRADE_COMPARE(material->attributeCount(), 4); + + /* Recognized attributes */ + { + CORRADE_VERIFY(material->hasAttribute(MaterialAttribute::AmbientColor)); + CORRADE_COMPARE(material->attributeId(MaterialAttribute::AmbientColor), 2); + CORRADE_COMPARE(material->attribute(2), (Color4{0.1f, 0.05f, 0.1f, 1.0f})); + } { + CORRADE_VERIFY(material->hasAttribute(MaterialAttribute::DiffuseColor)); + CORRADE_COMPARE(material->attributeId(MaterialAttribute::DiffuseColor), 3); + CORRADE_COMPARE(material->attribute(3), (Color4{0.4f, 0.2f, 0.1f, 1.0f})); + + /* Unrecognized attributes */ + } { + CORRADE_COMPARE(material->attributeName(0), extractMaterialKey(AI_MATKEY_COLOR_TRANSPARENT)); + CORRADE_COMPARE(material->attributeType(0), MaterialAttributeType::Vector3); + CORRADE_COMPARE(material->attribute(0), (Vector3{0.3f, 0.2f, 0.1f})); + } { + CORRADE_COMPARE(material->attributeName(1), extractMaterialKey(AI_MATKEY_OPACITY)); + CORRADE_COMPARE(material->attributeType(1), MaterialAttributeType::Float); + CORRADE_COMPARE(material->attribute(1), 0.4f); + } +} + +void AssimpImporterTest::materialRaw() { + Containers::Pointer importer = _manager.instantiate("AssimpImporter"); + /* Disabled by default */ + CORRADE_VERIFY(!importer->configuration().value("forceRawMaterialData")); + importer->configuration().setValue("forceRawMaterialData", true); + + CORRADE_VERIFY(importer->openFile(Utility::Directory::join(ASSIMPIMPORTER_TEST_DIR, "material-raw.fbx"))); + CORRADE_COMPARE(importer->materialCount(), 2); + + Containers::Optional material = importer->material("Custom_Types"); + CORRADE_VERIFY(material); + CORRADE_COMPARE(material->types(), MaterialType{}); + CORRADE_COMPARE(material->layerCount(), 1); + { + CORRADE_EXPECT_FAIL_IF(_assimpVersion < 500, + "This version of Assimp doesn't import raw FBX material properties."); + CORRADE_COMPARE(material->attributeCount(), 5); + } + + /* Not all types and sizes are tested: + - no importers currently output int2/3/4/5+ or float2/5+ + - to get double properties, we'd need to compile assimp with + ASSIMP_DOUBLE_PRECISION, which breaks the plugin */ + + /* Attributes that would normally be recognized */ + CORRADE_VERIFY(!material->hasAttribute(MaterialAttribute::AmbientColor)); + CORRADE_VERIFY(!material->hasAttribute(MaterialAttribute::DiffuseColor)); + + /* Raw Assimp attributes */ + { + CORRADE_COMPARE(material->attributeName(0), extractMaterialKey(AI_MATKEY_COLOR_AMBIENT)); + CORRADE_COMPARE(material->attributeType(0), MaterialAttributeType::Vector3); + CORRADE_COMPARE(material->attribute(0), (Color3{0.1f, 0.05f, 0.1f})); + } { + CORRADE_COMPARE(material->attributeName(1), extractMaterialKey(AI_MATKEY_COLOR_DIFFUSE)); + CORRADE_COMPARE(material->attributeType(1), MaterialAttributeType::Vector3); + CORRADE_COMPARE(material->attribute(1), (Color3{0.4f, 0.2f, 0.1f})); + } { + CORRADE_COMPARE(material->attributeName(2), extractMaterialKey(AI_MATKEY_OPACITY)); + CORRADE_COMPARE(material->attributeType(2), MaterialAttributeType::Float); + CORRADE_COMPARE(material->attribute(2), 0.25f); + } + + if(_assimpVersion < 500) + CORRADE_WARN("This version of Assimp doesn't import raw FBX material properties."); + else { + /* Raw attributes taken directly from the FBX file, prefixed with "$raw.". + Seems to be the only importer that supports that. */ + { + CORRADE_COMPARE(material->attributeName(3), "$raw.SomeColor"_s); + CORRADE_COMPARE(material->attributeType(3), MaterialAttributeType::Vector3); + CORRADE_COMPARE(material->attribute(3), (Vector3{0.1f, 0.2f, 0.3f})); + } { + CORRADE_EXPECT_FAIL_IF(_assimpVersion < 500, + "This version of Assimp doesn't import raw FBX material properties."); + CORRADE_COMPARE(material->attributeName(4), "$raw.SomeString"_s); + CORRADE_COMPARE(material->attributeType(4), MaterialAttributeType::String); + CORRADE_COMPARE(material->attribute(4), "Ministry of Finance (Turkmenistan)"); + } + } + + if(_assimpVersion < 410) + CORRADE_SKIP("glTF 2 is supported since Assimp 4.1."); + + /* glTF covers a few types/sizes not covered by FBX */ + CORRADE_VERIFY(importer->openFile(Utility::Directory::join(ASSIMPIMPORTER_TEST_DIR, "material-raw.gltf"))); + { + /* There's an extra material with only default values */ + CORRADE_EXPECT_FAIL("glTF files are imported with a dummy material."); + CORRADE_COMPARE(importer->materialCount(), 1); + } + + material = importer->material("raw"); + CORRADE_VERIFY(material); + CORRADE_COMPARE(material->types(), MaterialType{}); + { + CORRADE_EXPECT_FAIL_IF(_assimpVersion < 510, + "glTF files with non-0 texture coordinate set add an extra diffuse-only material layer."); + CORRADE_COMPARE(material->layerCount(), 1); + } + + /* Attributes that would normally be recognized */ + CORRADE_VERIFY(!material->hasAttribute(MaterialAttribute::DiffuseColor)); + + /* Raw attributes. Only checking interesting attributes, because this would + be a nightmare to maintain across multiple Assimp versions. */ + { + const Containers::StringView name = extractMaterialKey(AI_MATKEY_COLOR_DIFFUSE); + CORRADE_VERIFY(material->hasAttribute(name)); + CORRADE_COMPARE(material->attributeType(name), MaterialAttributeType::Vector4); + CORRADE_COMPARE(material->attribute(name), (Color4{0.8f, 0.2f, 0.4f, 0.3f})); + } { + /* For some reason Assimp adds an alpha channel to the emissive color */ + const Containers::StringView name = extractMaterialKey(AI_MATKEY_COLOR_EMISSIVE); + CORRADE_VERIFY(material->hasAttribute(name)); + CORRADE_COMPARE(material->attributeType(name), MaterialAttributeType::Vector4); + CORRADE_COMPARE(material->attribute(name), (Color4{0.1f, 0.2f, 0.3f})); + } { + /* The glTF importer writes bool properties as buffers with size 1, + when all the other importers write them as ints */ + const Containers::StringView name = extractMaterialKey(AI_MATKEY_TWOSIDED); + CORRADE_VERIFY(material->hasAttribute(name)); + CORRADE_COMPARE(material->attributeType(name), MaterialAttributeType::String); + const auto value = material->attribute(name); + CORRADE_COMPARE(value.size(), 1); + CORRADE_VERIFY(value.front() != 0); + } { + constexpr Containers::StringView name = _AI_MATKEY_TEXTURE_BASE ".NORMALS"_s; + CORRADE_VERIFY(material->hasAttribute(name)); + CORRADE_COMPARE(material->attributeType(name), MaterialAttributeType::String); + CORRADE_COMPARE(material->attribute(name), "normals.png"_s); + } { + /* Testing that newer texture types are handled correctly by the SFINAE + magic that produces fallback switch values */ + CORRADE_EXPECT_FAIL_IF(_assimpVersion < 510, + "Versions before Assimp 5.1.0 don't import BASE_COLOR textures."); + constexpr Containers::StringView name = _AI_MATKEY_TEXTURE_BASE ".BASE_COLOR"_s; + const bool hasAttribute = material->hasAttribute(name); + CORRADE_VERIFY(hasAttribute); + /* Still have to skip the checks to not trigger asserts for missing + attribute names in attributeType() and attribute() */ + if(hasAttribute) { + CORRADE_COMPARE(material->attributeType(name), MaterialAttributeType::String); + CORRADE_COMPARE(material->attribute(name), "basecolor.png"_s); + } + } { + CORRADE_EXPECT_FAIL_IF(_assimpVersion < 510, + "Versions before Assimp 5.1.0 don't import AI_MATKEY_UVWSRC."); + constexpr Containers::StringView name = _AI_MATKEY_UVWSRC_BASE ".NORMALS"_s; + const bool hasAttribute = material->hasAttribute(name); + CORRADE_VERIFY(hasAttribute); + if(hasAttribute) { + CORRADE_COMPARE(material->attributeType(name), MaterialAttributeType::Int); + CORRADE_COMPARE(material->attribute(name), 1); + } + } { + CORRADE_EXPECT_FAIL_IF(_assimpVersion < 510, + "Versions before Assimp 5.1.0 don't import AI_MATKEY_UVTRANSFORM."); + constexpr Containers::StringView name = _AI_MATKEY_UVTRANSFORM_BASE ".NORMALS"_s; + const bool hasAttribute = material->hasAttribute(name); + CORRADE_VERIFY(hasAttribute); + if(hasAttribute) { + /* Opaque buffer converted to String */ + CORRADE_COMPARE(material->attributeType(name), MaterialAttributeType::String); + const auto value = material->attribute(name); + /* +1 is null byte */ + CORRADE_COMPARE(value.size(), sizeof(aiUVTransform)); + const aiUVTransform& transform = *reinterpret_cast(value.data()); + const Vector2 scaling{transform.mScaling.x, transform.mScaling.y}; + CORRADE_COMPARE(scaling, (Vector2{0.25f, 0.75f})); + } + } +} + +void AssimpImporterTest::materialRawTextureLayers() { + if(_assimpVersion < 500) + CORRADE_SKIP("This version of assimp doesn't imported layered FBX materials."); + + Containers::Pointer importer = _manager.instantiate("AssimpImporter"); + importer->configuration().setValue("forceRawMaterialData", true); + + CORRADE_VERIFY(importer->openFile(Utility::Directory::join(ASSIMPIMPORTER_TEST_DIR, "material-layers.fbx"))); + CORRADE_COMPARE(importer->materialCount(), 1); + + const auto material = importer->material(0); + CORRADE_VERIFY(material); + CORRADE_COMPARE(material->layerCount(), 3); + + /* Layer 0. Material attributes + diffuse texture + ambient texture. */ + { + { + CORRADE_VERIFY(!material->hasAttribute(0, MaterialAttribute::AmbientColor)); + const Containers::StringView name = extractMaterialKey(AI_MATKEY_COLOR_AMBIENT); + CORRADE_VERIFY(material->hasAttribute(0, name)); + CORRADE_COMPARE(material->attributeType(0, name), MaterialAttributeType::Vector3); + CORRADE_COMPARE(material->attribute(0, name), (Color3{0.1f, 0.2f, 0.3f})); + } { + CORRADE_VERIFY(!material->hasAttribute(0, MaterialAttribute::DiffuseColor)); + const Containers::StringView name = extractMaterialKey(AI_MATKEY_COLOR_DIFFUSE); + CORRADE_VERIFY(material->hasAttribute(0, name)); + CORRADE_COMPARE(material->attributeType(0, name), MaterialAttributeType::Vector3); + CORRADE_COMPARE(material->attribute(0, name), (Color3{0.7f, 0.6f, 0.5f})); + }{ + /* Texture indices are calculated from the attribute order inside + AssimpImporter, but they're not material properties. Just check + the name property. */ + CORRADE_VERIFY(!material->hasAttribute(0, MaterialAttribute::AmbientTexture)); + constexpr Containers::StringView name = _AI_MATKEY_TEXTURE_BASE ".AMBIENT"_s; + CORRADE_VERIFY(material->hasAttribute(0, name)); + CORRADE_COMPARE(material->attributeType(0, name), MaterialAttributeType::String); + CORRADE_COMPARE(material->attribute(0, name), "ambient.png"_s); + } { + CORRADE_VERIFY(!material->hasAttribute(0, MaterialAttribute::AmbientTextureCoordinates)); + constexpr Containers::StringView name = _AI_MATKEY_UVWSRC_BASE ".AMBIENT"_s; + CORRADE_VERIFY(material->hasAttribute(0, name)); + CORRADE_COMPARE(material->attributeType(0, name), MaterialAttributeType::Int); + CORRADE_COMPARE(material->attribute(0, name), 0); + } { + CORRADE_VERIFY(!material->hasAttribute(0, MaterialAttribute::DiffuseTexture)); + constexpr Containers::StringView name = _AI_MATKEY_TEXTURE_BASE ".DIFFUSE"_s; + CORRADE_VERIFY(material->hasAttribute(0, name)); + CORRADE_COMPARE(material->attributeType(0, name), MaterialAttributeType::String); + CORRADE_COMPARE(material->attribute(0, name), "one.jpg"_s); + } { + CORRADE_VERIFY(!material->hasAttribute(0, MaterialAttribute::DiffuseTextureCoordinates)); + constexpr Containers::StringView name = _AI_MATKEY_UVWSRC_BASE ".DIFFUSE"_s; + CORRADE_VERIFY(material->hasAttribute(0, name)); + CORRADE_COMPARE(material->attributeType(0, name), MaterialAttributeType::Int); + CORRADE_COMPARE(material->attribute(0, name), 0); + } + + /* Layer 1. Diffuse texture. */ + } { + /* We check that there are no non-texture attributes in this layer at + the end of the test */ + { + CORRADE_VERIFY(!material->hasAttribute(1, MaterialAttribute::DiffuseTexture)); + constexpr Containers::StringView name = _AI_MATKEY_TEXTURE_BASE ".DIFFUSE"_s; + CORRADE_VERIFY(material->hasAttribute(1, name)); + CORRADE_COMPARE(material->attributeType(1, name), MaterialAttributeType::String); + CORRADE_COMPARE(material->attribute(1, name), "two.jpg"_s); + } { + CORRADE_VERIFY(!material->hasAttribute(1, MaterialAttribute::DiffuseTextureCoordinates)); + constexpr Containers::StringView name = _AI_MATKEY_UVWSRC_BASE ".DIFFUSE"_s; + CORRADE_VERIFY(material->hasAttribute(1, name)); + CORRADE_COMPARE(material->attributeType(1, name), MaterialAttributeType::Int); + CORRADE_COMPARE(material->attribute(1, name), 0); + } + + /* Layer 2. Diffuse texture. */ + } { + /* We check that there are no non-texture attributes in this layer at + the end of the test */ + { + CORRADE_VERIFY(!material->hasAttribute(2, MaterialAttribute::DiffuseTexture)); + constexpr Containers::StringView name = _AI_MATKEY_TEXTURE_BASE ".DIFFUSE"_s; + CORRADE_VERIFY(material->hasAttribute(2, name)); + CORRADE_COMPARE(material->attributeType(2, name), MaterialAttributeType::String); + CORRADE_COMPARE(material->attribute(2, name), "three.jpg"_s); + } { + CORRADE_VERIFY(!material->hasAttribute(2, MaterialAttribute::DiffuseTextureCoordinates)); + constexpr Containers::StringView name = _AI_MATKEY_UVWSRC_BASE ".DIFFUSE"_s; + CORRADE_VERIFY(material->hasAttribute(2, name)); + CORRADE_COMPARE(material->attributeType(2, name), MaterialAttributeType::Int); + CORRADE_COMPARE(material->attribute(2, name), 0); + } + } + + /* Test that layers > 0 only contains texture attributes. Those all start + with "$tex.": + https://github.com/assimp/assimp/blob/889e55969647b9bd9e832d6208b41973156ce46b/include/assimp/material.h#L1051 + Non-texture attributes always have mIndex set to 0 so shouldn't be here. */ + for(UnsignedInt l = 1; l != material->layerCount(); ++l) + for(UnsignedInt i = 0; i != material->attributeCount(l); ++i) + CORRADE_VERIFY(material->attributeName(l, i).hasPrefix("$tex."_s)); +} + void AssimpImporterTest::mesh() { Containers::Pointer importer = _manager.instantiate("AssimpImporter"); CORRADE_VERIFY(importer->openFile(Utility::Directory::join(ASSIMPIMPORTER_TEST_DIR, "mesh.dae"))); @@ -1882,8 +2396,9 @@ void AssimpImporterTest::mesh() { CORRADE_COMPARE(importer->meshCount(), 1); CORRADE_COMPARE(importer->object3DCount(), 1); - CORRADE_COMPARE(importer->meshName(0), "Cube"); - CORRADE_COMPARE(importer->meshForName("Cube"), 0); + const std::string name = fixMeshName("Cube", ".dae", _assimpVersion); + CORRADE_COMPARE(importer->meshName(0), name); + CORRADE_COMPARE(importer->meshForName(name), 0); CORRADE_COMPARE(importer->meshForName("nonexistent"), -1); Containers::Optional mesh = importer->mesh(0); @@ -1922,9 +2437,8 @@ void AssimpImporterTest::mesh() { {0.5f, 1.0f}, {0.75f, 0.5f}, {0.5f, 0.9f} }), TestSuite::Compare::Container); - const UnsignedInt version = aiGetVersionMajor()*100 + aiGetVersionMinor(); { - CORRADE_EXPECT_FAIL_IF(version < 302, + CORRADE_EXPECT_FAIL_IF(_assimpVersion < 320, "Assimp < 3.2 loads incorrect alpha value for the last color."); CORRADE_COMPARE_AS(mesh->attribute(MeshAttribute::Color), Containers::arrayView({ @@ -1992,6 +2506,36 @@ void AssimpImporterTest::lineMesh() { CORRADE_COMPARE(meshObject->instance(), 0); } +void AssimpImporterTest::polygonMesh() { + Containers::Pointer importer = _manager.instantiate("AssimpImporter"); + CORRADE_VERIFY(importer->openFile(Utility::Directory::join(ASSIMPIMPORTER_TEST_DIR, "polygon.obj"))); + + /* Just testing that triangulation doesn't break anything */ + + CORRADE_COMPARE(importer->meshCount(), 1); + CORRADE_COMPARE(importer->object3DCount(), 1); + + Containers::Optional mesh = importer->mesh(0); + CORRADE_VERIFY(mesh); + CORRADE_COMPARE(mesh->primitive(), MeshPrimitive::Triangles); + + CORRADE_VERIFY(mesh->isIndexed()); + CORRADE_COMPARE_AS(mesh->indices(), + Containers::arrayView({0, 1, 2, 0, 2, 3}), + TestSuite::Compare::Container); + + CORRADE_COMPARE(mesh->attributeCount(), 1); + CORRADE_COMPARE_AS(mesh->attribute(MeshAttribute::Position), + Containers::arrayView({ + {-1.0f, 1.0f, 0.0f}, { 1.0f, 1.0f, 0.0f}, + { 1.0f, -1.0f, 0.0f}, {-1.0f, -1.0f, 0.0f} + }), TestSuite::Compare::Container); + + Containers::Pointer meshObject = importer->object3D(0); + CORRADE_COMPARE(meshObject->instanceType(), ObjectInstanceType3D::Mesh); + CORRADE_COMPARE(meshObject->instance(), 0); +} + void AssimpImporterTest::meshCustomAttributes() { Containers::Pointer importer = _manager.instantiate("AssimpImporter"); CORRADE_VERIFY(importer->openFile(Utility::Directory::join(ASSIMPIMPORTER_TEST_DIR, @@ -2043,7 +2587,7 @@ void AssimpImporterTest::meshSkinningAttributes() { auto&& data = ExportedFileData[testCaseInstanceId()]; setTestCaseDescription(data.name); - if(!supportsSkinning(data.suffix)) + if(!supportsSkinning(data.suffix, _assimpVersion)) CORRADE_SKIP("Skin data for this file type is not supported with the current version of Assimp"); Containers::Pointer importer = _manager.instantiate("AssimpImporter"); @@ -2053,7 +2597,12 @@ void AssimpImporterTest::meshSkinningAttributes() { const MeshAttribute jointsAttribute = importer->meshAttributeForName("JOINTS"); const MeshAttribute weightsAttribute = importer->meshAttributeForName("WEIGHTS"); - for(const char* meshName: {"Mesh_1", "Mesh_2"}) { + const std::string meshNames[]{ + fixMeshName("Mesh_1"_s, data.suffix, _assimpVersion), + fixMeshName("Mesh_2"_s, data.suffix, _assimpVersion) + }; + + for(const std::string& meshName: meshNames) { Containers::Optional mesh; std::ostringstream out; { @@ -2079,7 +2628,7 @@ void AssimpImporterTest::meshSkinningAttributes() { } /* No skin joint node using this mesh */ - auto mesh = importer->mesh("Plane"); + auto mesh = importer->mesh(fixMeshName("Plane"_s, data.suffix, _assimpVersion)); CORRADE_VERIFY(mesh); CORRADE_VERIFY(!mesh->hasAttribute(jointsAttribute)); } @@ -2096,7 +2645,7 @@ void AssimpImporterTest::meshSkinningAttributesMultiple() { constexpr UnsignedInt AttributeCount = 3; - auto mesh = importer->mesh("Mesh_1"); + auto mesh = importer->mesh(fixMeshName("Mesh_1"_s, ".dae"_s, _assimpVersion)); CORRADE_VERIFY(mesh); CORRADE_VERIFY(mesh->hasAttribute(jointsAttribute)); CORRADE_COMPARE(mesh->attributeCount(jointsAttribute), AttributeCount); @@ -2118,13 +2667,21 @@ void AssimpImporterTest::meshSkinningAttributesMultiple() { } void AssimpImporterTest::meshSkinningAttributesMultipleGltf() { - if(!supportsSkinning(".gltf")) + if(!supportsSkinning(".gltf"_s, _assimpVersion)) CORRADE_SKIP("glTF 2 skinning is not supported with the current version of Assimp"); - /* Assimp glTF 2 importer only reads the last(!) set of joint weights */ + /* Assimp glTF 2 importer only reads the last(!) set of joint weights. On + 5.1.0 it outright fails to import because of broken extra validation: + https://github.com/assimp/assimp/issues/4178 */ Containers::Pointer importer = _manager.instantiate("AssimpImporter"); importer->configuration().setValue("maxJointWeights", 0); + + if(_assimpVersion >= 510) { + CORRADE_VERIFY(!importer->openFile(Utility::Directory::join(ASSIMPIMPORTER_TEST_DIR, "skin-multiple-sets.gltf"))); + CORRADE_SKIP("Current version of assimp fails to import files with multiple sets of skinning attributes"); + } + CORRADE_VERIFY(importer->openFile(Utility::Directory::join(ASSIMPIMPORTER_TEST_DIR, "skin-multiple-sets.gltf"))); const MeshAttribute jointsAttribute = importer->meshAttributeForName("JOINTS"); @@ -2190,7 +2747,7 @@ void AssimpImporterTest::meshSkinningAttributesMaxJointWeights() { /* 6 weights = 2 sets of 4, last two weights zero */ constexpr UnsignedInt AttributeCount = 2; - auto mesh = importer->mesh("Mesh_1"); + auto mesh = importer->mesh(fixMeshName("Mesh_1"_s, ".dae"_s, _assimpVersion)); CORRADE_VERIFY(mesh); CORRADE_VERIFY(mesh->hasAttribute(jointsAttribute)); CORRADE_COMPARE(mesh->attributeCount(jointsAttribute), AttributeCount); @@ -2215,7 +2772,7 @@ void AssimpImporterTest::meshSkinningAttributesMaxJointWeights() { } void AssimpImporterTest::meshSkinningAttributesDummyWeightRemoval() { - if(!supportsSkinning(".gltf")) + if(!supportsSkinning(".gltf"_s, _assimpVersion)) CORRADE_SKIP("glTF 2 skinning is not supported with the current version of Assimp"); Containers::Pointer importer = _manager.instantiate("AssimpImporter"); @@ -2264,7 +2821,7 @@ void AssimpImporterTest::meshSkinningAttributesMerge() { } { - const Int id = importer->meshForName("Mesh_1"); + const Int id = importer->meshForName(fixMeshName("Mesh_1"_s, ".dae"_s, _assimpVersion)); CORRADE_VERIFY(id != -1); auto mesh = importer->mesh(id); CORRADE_VERIFY(mesh); @@ -2281,7 +2838,7 @@ void AssimpImporterTest::meshSkinningAttributesMerge() { Containers::arrayView(MeshSkinningAttributesWeightData), TestSuite::Compare::Container); } { - const Int id = importer->meshForName("Mesh_2"); + const Int id = importer->meshForName(fixMeshName("Mesh_2"_s, ".dae"_s, _assimpVersion)); CORRADE_VERIFY(id != -1); auto mesh = importer->mesh(id); CORRADE_VERIFY(mesh); @@ -2303,7 +2860,7 @@ void AssimpImporterTest::meshSkinningAttributesMerge() { void AssimpImporterTest::meshMultiplePrimitives() { /* Possibly broken in other versions too (4.1 and 5 works, 3.2 doesn't) */ - if(aiGetVersionMajor()*100 + aiGetVersionMinor() <= 302) + if(_assimpVersion <= 320) CORRADE_SKIP("Assimp 3.2 doesn't recognize primitives used in the test COLLADA file."); Containers::Pointer importer = _manager.instantiate("AssimpImporter"); @@ -2311,12 +2868,13 @@ void AssimpImporterTest::meshMultiplePrimitives() { CORRADE_VERIFY(importer->openFile(Utility::Directory::join(ASSIMPIMPORTER_TEST_DIR, "mesh-multiple-primitives.dae"))); - /* Four meshes, but one has three primitives and one two. */ + /* Two meshes, but one has two primitives and one three. */ CORRADE_COMPARE(importer->meshCount(), 5); { - CORRADE_COMPARE(importer->meshName(0), "Multi-primitive triangle fan, line strip"); - CORRADE_COMPARE(importer->meshName(1), "Multi-primitive triangle fan, line strip"); - CORRADE_COMPARE(importer->meshForName("Multi-primitive triangle fan, line strip"), 0); + const std::string name = fixMeshName("Multi-primitive triangle fan, line strip", ".dae", _assimpVersion); + CORRADE_COMPARE(importer->meshName(0), name); + CORRADE_COMPARE(importer->meshName(1), name); + CORRADE_COMPARE(importer->meshForName(name), 0); auto mesh0 = importer->mesh(0); CORRADE_VERIFY(mesh0); @@ -2325,10 +2883,11 @@ void AssimpImporterTest::meshMultiplePrimitives() { CORRADE_VERIFY(mesh1); CORRADE_COMPARE(mesh1->primitive(), MeshPrimitive::Lines); } { - CORRADE_COMPARE(importer->meshName(2), "Multi-primitive lines, triangles, triangle strip"); - CORRADE_COMPARE(importer->meshName(3), "Multi-primitive lines, triangles, triangle strip"); - CORRADE_COMPARE(importer->meshName(4), "Multi-primitive lines, triangles, triangle strip"); - CORRADE_COMPARE(importer->meshForName("Multi-primitive lines, triangles, triangle strip"), 2); + const std::string name = fixMeshName("Multi-primitive lines, triangles, triangle strip", ".dae", _assimpVersion); + CORRADE_COMPARE(importer->meshName(2), name); + CORRADE_COMPARE(importer->meshName(3), name); + CORRADE_COMPARE(importer->meshName(4), name); + CORRADE_COMPARE(importer->meshForName(name), 2); auto mesh2 = importer->mesh(2); CORRADE_VERIFY(mesh2); @@ -2446,16 +3005,20 @@ void AssimpImporterTest::emptyCollada() { } void AssimpImporterTest::emptyGltf() { - const UnsignedInt version = aiGetVersionMajor()*100 + aiGetVersionMinor(); - if(version < 401) + if(_assimpVersion < 410) CORRADE_SKIP("glTF 2 is supported since Assimp 4.1."); Containers::Pointer importer = _manager.instantiate("AssimpImporter"); - CORRADE_VERIFY(importer->openFile(Utility::Directory::join(TINYGLTFIMPORTER_TEST_DIR, "empty.gltf"))); - CORRADE_COMPARE(importer->defaultScene(), -1); - CORRADE_COMPARE(importer->sceneCount(), 0); - CORRADE_COMPARE(importer->object3DCount(), 0); + /* We can't reuse TinyGltfImporter's empty.gltf since Assimp 5.1 complains + about a missing scene property (which is not required by the glTF spec) */ + CORRADE_VERIFY(importer->openFile(Utility::Directory::join(ASSIMPIMPORTER_TEST_DIR, "empty.gltf"))); + { + CORRADE_EXPECT_FAIL_IF(_assimpVersion < 510, "Assimp versions before 5.1.0 ignore empty glTF scenes."); + CORRADE_COMPARE(importer->defaultScene(), 0); + CORRADE_COMPARE(importer->sceneCount(), 1); + CORRADE_COMPARE(importer->object3DCount(), 1); + } /* No crazy meshes created for an empty glTF file, unlike with COLLADA files that have no meshes */ @@ -2474,6 +3037,9 @@ void AssimpImporterTest::scene() { CORRADE_VERIFY(scene); CORRADE_COMPARE(scene->children3D(), {0}); + /* Currently only glTF supports scene names */ + CORRADE_COMPARE(importer->sceneName(0), ""); + Containers::Pointer parent = importer->object3D(0); CORRADE_COMPARE(parent->children(), {1}); CORRADE_COMPARE(parent->instanceType(), ObjectInstanceType3D::Empty); @@ -2494,6 +3060,20 @@ void AssimpImporterTest::scene() { CORRADE_COMPARE(importer->object3DForName("Ghost"), -1); } +void AssimpImporterTest::sceneName() { + #if !ASSIMP_HAS_SCENE_NAME + CORRADE_SKIP("Current version of assimp doesn't expose scene names."); + #endif + + Containers::Pointer importer = _manager.instantiate("AssimpImporter"); + CORRADE_VERIFY(importer->openFile(Utility::Directory::join(ASSIMPIMPORTER_TEST_DIR, "scene-name.gltf"))); + + CORRADE_COMPARE(importer->sceneCount(), 1); + CORRADE_COMPARE(importer->sceneName(0), "This is the scene"); + CORRADE_COMPARE(importer->sceneForName("This is the scene"), 0); + CORRADE_COMPARE(importer->sceneForName("Other scene"), -1); +} + void AssimpImporterTest::sceneCollapsedNode() { Containers::Pointer importer = _manager.instantiate("AssimpImporter"); @@ -2531,9 +3111,8 @@ void AssimpImporterTest::sceneCollapsedNode() { /* Name of the scene is used for the root object */ { - const UnsignedInt version = aiGetVersionMajor()*100 + aiGetVersionMinor(); /** @todo Possibly works with other versions (definitely not 3.0) */ - CORRADE_EXPECT_FAIL_IF(version <= 302, + CORRADE_EXPECT_FAIL_IF(_assimpVersion <= 320, "Assimp 3.2 and below doesn't use name of the root node for collapsed nodes."); CORRADE_COMPARE(importer->object3DForName("Scene"), 0); CORRADE_COMPARE(importer->object3DName(0), "Scene"); @@ -2655,8 +3234,7 @@ void AssimpImporterTest::imageEmbedded() { Containers::Pointer importer = _manager.instantiate("AssimpImporter"); - const UnsignedInt version = aiGetVersionMajor()*100 + aiGetVersionMinor(); - if(version <= 302) + if(_assimpVersion <= 320) CORRADE_SKIP("Assimp < 3.2 can't load embedded textures in blend files, Assimp 3.2 can't detect blend file format when opening a memory location."); /* Open as data, so we verify opening embedded images from data does not @@ -2672,9 +3250,8 @@ void AssimpImporterTest::imageEmbedded() { } void AssimpImporterTest::imageExternal() { - const UnsignedInt version = aiGetVersionMajor()*100 + aiGetVersionMinor(); /** @todo Possibly works with earlier versions (definitely not 3.0) */ - if(version < 302) + if(_assimpVersion < 320) CORRADE_SKIP("Current version of assimp would SEGFAULT on this test."); if(_manager.loadState("PngImporter") == PluginManager::LoadState::NotFound) @@ -2692,9 +3269,8 @@ void AssimpImporterTest::imageExternal() { } void AssimpImporterTest::imageExternalNotFound() { - const UnsignedInt version = aiGetVersionMajor()*100 + aiGetVersionMinor(); /** @todo Possibly fails on more versions (definitely w/ 3.0 and 3.2) */ - if(version <= 302) + if(_assimpVersion <= 320) CORRADE_SKIP("Assimp <= 3.2 would SEGFAULT on this test."); if(_manager.loadState("PngImporter") == PluginManager::LoadState::NotFound) @@ -2715,9 +3291,8 @@ void AssimpImporterTest::imageExternalNotFound() { } void AssimpImporterTest::imageExternalNoPathNoCallback() { - const UnsignedInt version = aiGetVersionMajor()*100 + aiGetVersionMinor(); /** @todo Possibly works with earlier versions (definitely not 3.0) */ - if(version < 302) + if(_assimpVersion < 320) CORRADE_SKIP("Current version of assimp would SEGFAULT on this test."); Containers::Pointer importer = _manager.instantiate("AssimpImporter"); @@ -2806,9 +3381,8 @@ void AssimpImporterTest::imageMipLevels() { } void AssimpImporterTest::texture() { - const UnsignedInt version = aiGetVersionMajor()*100 + aiGetVersionMinor(); /** @todo Possibly works with earlier versions (definitely not 3.0) */ - if(version < 302) + if(_assimpVersion < 320) CORRADE_SKIP("Current version of assimp would SEGFAULT on this test."); if(_manager.loadState("PngImporter") == PluginManager::LoadState::NotFound) @@ -2907,9 +3481,8 @@ void AssimpImporterTest::openState() { } void AssimpImporterTest::openStateTexture() { - const UnsignedInt version = aiGetVersionMajor()*100 + aiGetVersionMinor(); /** @todo Possibly works with earlier versions (definitely not 3.0) */ - if(version < 302) + if(_assimpVersion < 320) CORRADE_SKIP("Current version of assimp would SEGFAULT on this test."); if(_manager.loadState("PngImporter") == PluginManager::LoadState::NotFound) @@ -3021,11 +3594,10 @@ void AssimpImporterTest::fileCallbackNotFound() { /* Assimp 5.0 changed the error string. aiGetVersion*() returns 401 for assimp 5, FFS, so we have to check differently. See CMakeLists.txt for details. */ - #if ASSIMP_IS_VERSION_5 - CORRADE_COMPARE(out.str(), "Trade::AssimpImporter::openFile(): failed to open some-file.dae: Failed to open file 'some-file.dae'.\n"); - #else - CORRADE_COMPARE(out.str(), "Trade::AssimpImporter::openFile(): failed to open some-file.dae: Failed to open file some-file.dae.\n"); - #endif + if(_assimpVersion >= 500) + CORRADE_COMPARE(out.str(), "Trade::AssimpImporter::openFile(): failed to open some-file.dae: Failed to open file 'some-file.dae'.\n"); + else + CORRADE_COMPARE(out.str(), "Trade::AssimpImporter::openFile(): failed to open some-file.dae: Failed to open file some-file.dae.\n"); } void AssimpImporterTest::fileCallbackEmptyFile() { @@ -3068,9 +3640,8 @@ void AssimpImporterTest::fileCallbackReset() { } void AssimpImporterTest::fileCallbackImage() { - const UnsignedInt version = aiGetVersionMajor()*100 + aiGetVersionMinor(); /** @todo Possibly works with earlier versions (definitely not 3.0) */ - if(version < 302) + if(_assimpVersion < 320) CORRADE_SKIP("Current version of assimp would SEGFAULT on this test."); if(_manager.loadState("PngImporter") == PluginManager::LoadState::NotFound) @@ -3098,9 +3669,8 @@ void AssimpImporterTest::fileCallbackImage() { } void AssimpImporterTest::fileCallbackImageNotFound() { - const UnsignedInt version = aiGetVersionMajor()*100 + aiGetVersionMinor(); /** @todo Possibly works with earlier versions (definitely not 3.0) */ - if(version < 302) + if(_assimpVersion < 320) CORRADE_SKIP("Current version of assimp would SEGFAULT on this test."); if(_manager.loadState("PngImporter") == PluginManager::LoadState::NotFound) @@ -3135,7 +3705,7 @@ void AssimpImporterTest::openTwice() { void AssimpImporterTest::importTwice() { Containers::Pointer importer = _manager.instantiate("AssimpImporter"); CORRADE_VERIFY(importer->openFile(Utility::Directory::join(ASSIMPIMPORTER_TEST_DIR, "camera.dae"))); - CORRADE_COMPARE(importer->cameraCount(), 1); + CORRADE_COMPARE(importer->cameraCount(), 2); /* Verify that everything is working the same way on second use. It's only testing a single data type, but better than nothing at all. */ @@ -3143,12 +3713,14 @@ void AssimpImporterTest::importTwice() { Containers::Optional camera = importer->camera(0); CORRADE_VERIFY(camera); CORRADE_COMPARE(camera->fov(), 49.13434_degf); + CORRADE_COMPARE(camera->aspectRatio(), 1.77777f); CORRADE_COMPARE(camera->near(), 0.123f); CORRADE_COMPARE(camera->far(), 123.0f); } { Containers::Optional camera = importer->camera(0); CORRADE_VERIFY(camera); CORRADE_COMPARE(camera->fov(), 49.13434_degf); + CORRADE_COMPARE(camera->aspectRatio(), 1.77777f); CORRADE_COMPARE(camera->near(), 0.123f); CORRADE_COMPARE(camera->far(), 123.0f); } diff --git a/src/MagnumPlugins/AssimpImporter/Test/CMakeLists.txt b/src/MagnumPlugins/AssimpImporter/Test/CMakeLists.txt index af675c211..5eeb84743 100644 --- a/src/MagnumPlugins/AssimpImporter/Test/CMakeLists.txt +++ b/src/MagnumPlugins/AssimpImporter/Test/CMakeLists.txt @@ -55,15 +55,17 @@ file(GENERATE OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/$/configure.h corrade_add_test(AssimpImporterTest AssimpImporterTest.cpp LIBRARIES Magnum::Trade FILES - empty.dae animation.gltf # Same as in TinyGltfImporterTest, but added a scene animation.bin animation-no-scene.gltf animation-patching.bin animation-patching.gltf camera.dae + camera.gltf diffuse_texture.png embedded-texture.blend + empty.dae + empty.gltf exported-animation.dae # Converted from exported-animation.blend exported-animation.fbx exported-animation.gltf @@ -80,12 +82,17 @@ corrade_add_test(AssimpImporterTest AssimpImporterTest.cpp material-color-texture.mtl material-color-texture.obj material-coordinate-sets.dae + material-layers.fbx + material-raw.fbx + material-raw.gltf mesh.dae mips.dds multiple-textures.mtl r.png g.png b.png y.png points.obj + polygon.obj scene.dae scene+mesh.dae + scene-name.gltf skin.dae # Converted from skin.blend skin.fbx skin.gltf @@ -94,6 +101,8 @@ corrade_add_test(AssimpImporterTest AssimpImporterTest.cpp skin-multiple-sets.dae skin-multiple-sets.bin skin-multiple-sets.gltf + skin-no-mesh.bin + skin-no-mesh.gltf skin-shared.gltf skin-shared.bin texture-ambient.obj diff --git a/src/MagnumPlugins/AssimpImporter/Test/animation-patching.bin b/src/MagnumPlugins/AssimpImporter/Test/animation-patching.bin index a8a20ee74..a08b0a734 100644 Binary files a/src/MagnumPlugins/AssimpImporter/Test/animation-patching.bin and b/src/MagnumPlugins/AssimpImporter/Test/animation-patching.bin differ diff --git a/src/MagnumPlugins/AssimpImporter/Test/animation-patching.gltf b/src/MagnumPlugins/AssimpImporter/Test/animation-patching.gltf index 2e823800d..c9845f835 100644 --- a/src/MagnumPlugins/AssimpImporter/Test/animation-patching.gltf +++ b/src/MagnumPlugins/AssimpImporter/Test/animation-patching.gltf @@ -1,6 +1,7 @@ { "asset": { - "version": "2.0" + "version": "2.0", + "note": "Identical to animation-patching.gltf in TinyGltfImporter/Test, but with a .bin file without a shared time track. Assimp 5.1.0 doesn't allow differently sized input and output accessors" }, "animations": [ { @@ -35,9 +36,9 @@ ], "samplers": [ { - "input": 0, + "input": 2, "interpolation": "LINEAR", - "output": 2 + "output": 3 } ] } @@ -51,15 +52,22 @@ "type": "SCALAR" }, { - "bufferView": 1, - "byteOffset": 0, + "bufferView": 0, + "byteOffset": 36, "componentType": 5126, "count": 9, "type": "VEC4" }, { - "bufferView": 1, - "byteOffset": 144, + "bufferView": 0, + "byteOffset": 180, + "componentType": 5126, + "count": 3, + "type": "SCALAR" + }, + { + "bufferView": 0, + "byteOffset": 192, "componentType": 5126, "count": 3, "type": "VEC4" @@ -69,17 +77,12 @@ { "buffer": 0, "byteOffset": 0, - "byteLength": 36 - }, - { - "buffer": 0, - "byteOffset": 36, - "byteLength": 192 + "byteLength": 240 } ], "buffers": [ { - "byteLength": 228, + "byteLength": 240, "uri": "animation-patching.bin" } ], diff --git a/src/MagnumPlugins/AssimpImporter/Test/camera.dae b/src/MagnumPlugins/AssimpImporter/Test/camera.dae index 4dd004ed4..fa5cf563e 100644 --- a/src/MagnumPlugins/AssimpImporter/Test/camera.dae +++ b/src/MagnumPlugins/AssimpImporter/Test/camera.dae @@ -13,6 +13,17 @@ + + + + + 12.347 + 0.1 + 2.0 + + + + @@ -20,6 +31,10 @@ -0.2988363 -0.2988363 0.9063078 0.1 0.6408563 0.6408564 0.4226183 0.2 -0.7071068 0.7071068 -3.09086e-8 0.3 0 0 0 1 + + -0.2988363 -0.2988363 0.9063078 0.1 0.6408563 0.6408564 0.4226183 0.2 -0.7071068 0.7071068 -3.09086e-8 0.3 0 0 0 1 + + diff --git a/src/MagnumPlugins/AssimpImporter/Test/camera.gltf b/src/MagnumPlugins/AssimpImporter/Test/camera.gltf new file mode 100644 index 000000000..41f5b69ea --- /dev/null +++ b/src/MagnumPlugins/AssimpImporter/Test/camera.gltf @@ -0,0 +1,57 @@ +{ + "asset" : { + "version" : "2.0" + }, + "note": "Same as camera.gltf from TinyGltfImporter tests, but with nodes referencing cameras so assimp imports them", + "cameras" : [ + { + "name" : "Orthographic 4:3", + "orthographic" : { + "xmag" : 2.0, + "ymag" : 1.5, + "zfar" : 100.0, + "znear" : 0.01 + }, + "type" : "orthographic" + }, + { + "name" : "Perspective 1:1 75° hFoV", + "perspective" : { + "aspectRatio" : 1.0, + "yfov" : 1.308996938995747, + "zfar" : 150.0, + "znear" : 0.1 + }, + "type" : "perspective" + }, + { + "name" : "Perspective 4:3 75° hFoV", + "perspective" : { + "aspectRatio" : 1.33333333333333, + "yfov" : 1.044412773812111, + "zfar" : 150.0, + "znear" : 0.1 + }, + "type" : "perspective" + }, + { + "name" : "Perspective 16:9 75° hFoV infinite", + "perspective" : { + "aspectRatio" : 1.77777777777777, + "yfov" : 0.8149313284027269, + "znear" : 0.1 + }, + "type" : "perspective" + } + ], + "scene": 0, + "scenes": [ + { "nodes": [0, 1, 2, 3] } + ], + "nodes": [ + { "camera": 0 }, + { "camera": 1 }, + { "camera": 2 }, + { "camera": 3 } + ] +} diff --git a/src/MagnumPlugins/AssimpImporter/Test/empty.gltf b/src/MagnumPlugins/AssimpImporter/Test/empty.gltf new file mode 100644 index 000000000..fc17d551b --- /dev/null +++ b/src/MagnumPlugins/AssimpImporter/Test/empty.gltf @@ -0,0 +1,8 @@ +{ + "asset": { + "note": "minimal glTF that all versions of assimp load without errors", + "version": "2.0" + }, + "scene": 0, + "scenes": [{}] +} diff --git a/src/MagnumPlugins/AssimpImporter/Test/material-layers.fbx b/src/MagnumPlugins/AssimpImporter/Test/material-layers.fbx new file mode 100644 index 000000000..82af29f73 --- /dev/null +++ b/src/MagnumPlugins/AssimpImporter/Test/material-layers.fbx @@ -0,0 +1,529 @@ +; FBX 7.7.0 project file +; ---------------------------------------------------- + +FBXHeaderExtension: { + FBXHeaderVersion: 1004 + FBXVersion: 7700 + CreationTimeStamp: { + Version: 1000 + Year: 2021 + Month: 11 + Day: 26 + Hour: 11 + Minute: 46 + Second: 30 + Millisecond: 44 + } + Creator: "FBX SDK/FBX Plugins version 2020.0.1" + OtherFlags: { + TCDefinition: 127 + } + SceneInfo: "SceneInfo::GlobalInfo", "UserData" { + Type: "UserData" + Version: 100 + MetaData: { + Version: 100 + Title: "" + Subject: "" + Author: "" + Keywords: "" + Revision: "" + Comment: "" + } + Properties70: { + P: "DocumentUrl", "KString", "Url", "", "material-layers.fbx" + P: "SrcDocumentUrl", "KString", "Url", "", "material-layers.fbx" + P: "Original", "Compound", "", "" + P: "Original|ApplicationVendor", "KString", "", "", "" + P: "Original|ApplicationName", "KString", "", "", "" + P: "Original|ApplicationVersion", "KString", "", "", "" + P: "Original|DateTime_GMT", "DateTime", "", "", "" + P: "Original|FileName", "KString", "", "", "" + P: "LastSaved", "Compound", "", "" + P: "LastSaved|ApplicationVendor", "KString", "", "", "" + P: "LastSaved|ApplicationName", "KString", "", "", "" + P: "LastSaved|ApplicationVersion", "KString", "", "", "" + P: "LastSaved|DateTime_GMT", "DateTime", "", "", "" + } + } +} +GlobalSettings: { + Version: 1000 + Properties70: { + P: "UpAxis", "int", "Integer", "",1 + P: "UpAxisSign", "int", "Integer", "",1 + P: "FrontAxis", "int", "Integer", "",2 + P: "FrontAxisSign", "int", "Integer", "",1 + P: "CoordAxis", "int", "Integer", "",0 + P: "CoordAxisSign", "int", "Integer", "",1 + P: "OriginalUpAxis", "int", "Integer", "",-1 + P: "OriginalUpAxisSign", "int", "Integer", "",1 + P: "UnitScaleFactor", "double", "Number", "",1 + P: "OriginalUnitScaleFactor", "double", "Number", "",1 + P: "AmbientColor", "ColorRGB", "Color", "",0,0,0 + P: "DefaultCamera", "KString", "", "", "Producer Perspective" + P: "TimeMode", "enum", "", "",0 + P: "TimeProtocol", "enum", "", "",2 + P: "SnapOnFrameMode", "enum", "", "",0 + P: "TimeSpanStart", "KTime", "Time", "",0 + P: "TimeSpanStop", "KTime", "Time", "",46186158000 + P: "CustomFrameRate", "double", "Number", "",-1 + P: "TimeMarker", "Compound", "", "" + P: "CurrentTimeMarker", "int", "Integer", "",-1 + } +} + +; Documents Description +;------------------------------------------------------------------ + +Documents: { + Count: 1 + Document: 7377280, "", "Scene" { + Properties70: { + P: "SourceObject", "object", "", "" + P: "ActiveAnimStackName", "KString", "", "", "" + } + RootNode: 0 + } +} + +; Document References +;------------------------------------------------------------------ + +References: { +} + +; Object definitions +;------------------------------------------------------------------ + +Definitions: { + Version: 100 + Count: 13 + ObjectType: "GlobalSettings" { + Count: 1 + } + ObjectType: "Model" { + Count: 1 + PropertyTemplate: "FbxNode" { + Properties70: { + P: "QuaternionInterpolate", "enum", "", "",0 + P: "RotationOffset", "Vector3D", "Vector", "",0,0,0 + P: "RotationPivot", "Vector3D", "Vector", "",0,0,0 + P: "ScalingOffset", "Vector3D", "Vector", "",0,0,0 + P: "ScalingPivot", "Vector3D", "Vector", "",0,0,0 + P: "TranslationActive", "bool", "", "",0 + P: "TranslationMin", "Vector3D", "Vector", "",0,0,0 + P: "TranslationMax", "Vector3D", "Vector", "",0,0,0 + P: "TranslationMinX", "bool", "", "",0 + P: "TranslationMinY", "bool", "", "",0 + P: "TranslationMinZ", "bool", "", "",0 + P: "TranslationMaxX", "bool", "", "",0 + P: "TranslationMaxY", "bool", "", "",0 + P: "TranslationMaxZ", "bool", "", "",0 + P: "RotationOrder", "enum", "", "",0 + P: "RotationSpaceForLimitOnly", "bool", "", "",0 + P: "RotationStiffnessX", "double", "Number", "",0 + P: "RotationStiffnessY", "double", "Number", "",0 + P: "RotationStiffnessZ", "double", "Number", "",0 + P: "AxisLen", "double", "Number", "",10 + P: "PreRotation", "Vector3D", "Vector", "",0,0,0 + P: "PostRotation", "Vector3D", "Vector", "",0,0,0 + P: "RotationActive", "bool", "", "",0 + P: "RotationMin", "Vector3D", "Vector", "",0,0,0 + P: "RotationMax", "Vector3D", "Vector", "",0,0,0 + P: "RotationMinX", "bool", "", "",0 + P: "RotationMinY", "bool", "", "",0 + P: "RotationMinZ", "bool", "", "",0 + P: "RotationMaxX", "bool", "", "",0 + P: "RotationMaxY", "bool", "", "",0 + P: "RotationMaxZ", "bool", "", "",0 + P: "InheritType", "enum", "", "",0 + P: "ScalingActive", "bool", "", "",0 + P: "ScalingMin", "Vector3D", "Vector", "",0,0,0 + P: "ScalingMax", "Vector3D", "Vector", "",1,1,1 + P: "ScalingMinX", "bool", "", "",0 + P: "ScalingMinY", "bool", "", "",0 + P: "ScalingMinZ", "bool", "", "",0 + P: "ScalingMaxX", "bool", "", "",0 + P: "ScalingMaxY", "bool", "", "",0 + P: "ScalingMaxZ", "bool", "", "",0 + P: "GeometricTranslation", "Vector3D", "Vector", "",0,0,0 + P: "GeometricRotation", "Vector3D", "Vector", "",0,0,0 + P: "GeometricScaling", "Vector3D", "Vector", "",1,1,1 + P: "MinDampRangeX", "double", "Number", "",0 + P: "MinDampRangeY", "double", "Number", "",0 + P: "MinDampRangeZ", "double", "Number", "",0 + P: "MaxDampRangeX", "double", "Number", "",0 + P: "MaxDampRangeY", "double", "Number", "",0 + P: "MaxDampRangeZ", "double", "Number", "",0 + P: "MinDampStrengthX", "double", "Number", "",0 + P: "MinDampStrengthY", "double", "Number", "",0 + P: "MinDampStrengthZ", "double", "Number", "",0 + P: "MaxDampStrengthX", "double", "Number", "",0 + P: "MaxDampStrengthY", "double", "Number", "",0 + P: "MaxDampStrengthZ", "double", "Number", "",0 + P: "PreferedAngleX", "double", "Number", "",0 + P: "PreferedAngleY", "double", "Number", "",0 + P: "PreferedAngleZ", "double", "Number", "",0 + P: "LookAtProperty", "object", "", "" + P: "UpVectorProperty", "object", "", "" + P: "Show", "bool", "", "",1 + P: "NegativePercentShapeSupport", "bool", "", "",1 + P: "DefaultAttributeIndex", "int", "Integer", "",-1 + P: "Freeze", "bool", "", "",0 + P: "LODBox", "bool", "", "",0 + P: "Lcl Translation", "Lcl Translation", "", "A",0,0,0 + P: "Lcl Rotation", "Lcl Rotation", "", "A",0,0,0 + P: "Lcl Scaling", "Lcl Scaling", "", "A",1,1,1 + P: "Visibility", "Visibility", "", "A",1 + P: "Visibility Inheritance", "Visibility Inheritance", "", "",1 + } + } + } + ObjectType: "Geometry" { + Count: 1 + PropertyTemplate: "FbxMesh" { + Properties70: { + P: "Color", "ColorRGB", "Color", "",0.8,0.8,0.8 + P: "BBoxMin", "Vector3D", "Vector", "",0,0,0 + P: "BBoxMax", "Vector3D", "Vector", "",0,0,0 + P: "Primary Visibility", "bool", "", "",1 + P: "Casts Shadows", "bool", "", "",1 + P: "Receive Shadows", "bool", "", "",1 + } + } + } + ObjectType: "Material" { + Count: 1 + PropertyTemplate: "FbxSurfaceLambert" { + Properties70: { + P: "ShadingModel", "KString", "", "", "Lambert" + P: "MultiLayer", "bool", "", "",0 + P: "EmissiveColor", "Color", "", "A",0,0,0 + P: "EmissiveFactor", "Number", "", "A",1 + P: "AmbientColor", "Color", "", "A",0.2,0.2,0.2 + P: "AmbientFactor", "Number", "", "A",1 + P: "DiffuseColor", "Color", "", "A",0.8,0.8,0.8 + P: "DiffuseFactor", "Number", "", "A",1 + P: "Bump", "Vector3D", "Vector", "",0,0,0 + P: "NormalMap", "Vector3D", "Vector", "",0,0,0 + P: "BumpFactor", "double", "Number", "",1 + P: "TransparentColor", "Color", "", "A",0,0,0 + P: "TransparencyFactor", "Number", "", "A",0 + P: "DisplacementColor", "ColorRGB", "Color", "",0,0,0 + P: "DisplacementFactor", "double", "Number", "",1 + P: "VectorDisplacementColor", "ColorRGB", "Color", "",0,0,0 + P: "VectorDisplacementFactor", "double", "Number", "",1 + } + } + } + ObjectType: "Texture" { + Count: 4 + PropertyTemplate: "FbxFileTexture" { + Properties70: { + P: "TextureTypeUse", "enum", "", "",0 + P: "Texture alpha", "Number", "", "A",1 + P: "CurrentMappingType", "enum", "", "",0 + P: "WrapModeU", "enum", "", "",0 + P: "WrapModeV", "enum", "", "",0 + P: "UVSwap", "bool", "", "",0 + P: "PremultiplyAlpha", "bool", "", "",1 + P: "Translation", "Vector", "", "A",0,0,0 + P: "Rotation", "Vector", "", "A",0,0,0 + P: "Scaling", "Vector", "", "A",1,1,1 + P: "TextureRotationPivot", "Vector3D", "Vector", "",0,0,0 + P: "TextureScalingPivot", "Vector3D", "Vector", "",0,0,0 + P: "CurrentTextureBlendMode", "enum", "", "",1 + P: "UVSet", "KString", "", "", "default" + P: "UseMaterial", "bool", "", "",0 + P: "UseMipMap", "bool", "", "",0 + } + } + } + ObjectType: "LayeredTexture" { + Count: 1 + PropertyTemplate: "FbxLayeredTexture" { + Properties70: { + P: "TextureTypeUse", "enum", "", "",0 + P: "Texture alpha", "Number", "", "A",1 + P: "CurrentMappingType", "enum", "", "",0 + P: "WrapModeU", "enum", "", "",0 + P: "WrapModeV", "enum", "", "",0 + P: "UVSwap", "bool", "", "",0 + P: "PremultiplyAlpha", "bool", "", "",1 + P: "Translation", "Vector", "", "A",0,0,0 + P: "Rotation", "Vector", "", "A",0,0,0 + P: "Scaling", "Vector", "", "A",1,1,1 + P: "TextureRotationPivot", "Vector3D", "Vector", "",0,0,0 + P: "TextureScalingPivot", "Vector3D", "Vector", "",0,0,0 + P: "CurrentTextureBlendMode", "enum", "", "",1 + P: "UVSet", "KString", "", "", "default" + } + } + } + ObjectType: "Video" { + Count: 4 + PropertyTemplate: "FbxVideo" { + Properties70: { + P: "Path", "KString", "XRefUrl", "", "" + P: "RelPath", "KString", "XRefUrl", "", "" + P: "Color", "ColorRGB", "Color", "",0.8,0.8,0.8 + P: "ClipIn", "KTime", "Time", "",0 + P: "ClipOut", "KTime", "Time", "",0 + P: "Offset", "KTime", "Time", "",0 + P: "PlaySpeed", "double", "Number", "",0 + P: "FreeRunning", "bool", "", "",0 + P: "Loop", "bool", "", "",0 + P: "Mute", "bool", "", "",0 + P: "AccessMode", "enum", "", "",0 + P: "ImageSequence", "bool", "", "",0 + P: "ImageSequenceOffset", "int", "Integer", "",0 + P: "FrameRate", "double", "Number", "",0 + P: "LastFrame", "int", "Integer", "",0 + P: "Width", "int", "Integer", "",0 + P: "Height", "int", "Integer", "",0 + P: "StartFrame", "int", "Integer", "",0 + P: "StopFrame", "int", "Integer", "",0 + P: "InterlaceMode", "enum", "", "",0 + } + } + } +} + +; Object properties +;------------------------------------------------------------------ + +Objects: { + Geometry: 7761376, "Geometry::planeMesh", "Mesh" { + Vertices: *12 { + a: -5,-5,0,5,-5,0,5,5,0,-5,5,0 + } + PolygonVertexIndex: *4 { + a: 0,1,2,-4 + } + GeometryVersion: 124 + LayerElementNormal: 0 { + Version: 102 + Name: "normals" + MappingInformationType: "ByVertice" + ReferenceInformationType: "Direct" + Normals: *12 { + a: 0,0,1,0,0,1,0,0,1,0,0,1 + } + NormalsW: *4 { + a: 0,0,0,0 + } + } + LayerElementUV: 0 { + Version: 101 + Name: "diffuseUV" + MappingInformationType: "ByPolygonVertex" + ReferenceInformationType: "Direct" + UV: *8 { + a: 0,0,1,0,1,1,0,1 + } + } + LayerElementMaterial: 0 { + Version: 101 + Name: "" + MappingInformationType: "NoMappingInformation" + ReferenceInformationType: "IndexToDirect" + Materials: *1 { + a: 0 + } + } + Layer: 0 { + Version: 100 + LayerElement: { + Type: "LayerElementNormal" + TypedIndex: 0 + } + LayerElement: { + Type: "LayerElementMaterial" + TypedIndex: 0 + } + LayerElement: { + Type: "LayerElementUV" + TypedIndex: 0 + } + } + } + Model: 7759392, "Model::planeNode", "Mesh" { + Version: 232 + Properties70: { + P: "ScalingMax", "Vector3D", "Vector", "",0,0,0 + P: "DefaultAttributeIndex", "int", "Integer", "",0 + } + Shading: T + Culling: "CullingOff" + } + Material: 7761952, "Material::myMaterial", "" { + Version: 102 + ShadingModel: "lambert" + MultiLayer: 0 + Properties70: { + P: "AmbientColor", "Color", "", "A",0.1,0.2,0.3 + P: "DiffuseColor", "Color", "", "A",0.7,0.6,0.5 + P: "Emissive", "Vector3D", "Vector", "",0,0,0 + P: "Ambient", "Vector3D", "Vector", "",0.1,0.2,0.3 + P: "Diffuse", "Vector3D", "Vector", "",0.7,0.6,0.5 + P: "Opacity", "double", "Number", "",1 + } + } + Video: 8094864, "Video::ambient", "Clip" { + Type: "Clip" + Properties70: { + P: "Path", "KString", "XRefUrl", "", "ambient.png" + P: "RelPath", "KString", "XRefUrl", "", "ambient.png" + } + UseMipMap: 0 + Filename: "ambient.png" + RelativeFilename: "ambient.png" + } + Video: 8112160, "Video::myFileTexture_0", "Clip" { + Type: "Clip" + Properties70: { + P: "Path", "KString", "XRefUrl", "", "one.jpg" + P: "RelPath", "KString", "XRefUrl", "", "one.jpg" + } + UseMipMap: 0 + Filename: "one.jpg" + RelativeFilename: "one.jpg" + } + Video: 8112640, "Video::myFileTexture_1", "Clip" { + Type: "Clip" + Properties70: { + P: "Path", "KString", "XRefUrl", "", "two.jpg" + P: "RelPath", "KString", "XRefUrl", "", "two.jpg" + } + UseMipMap: 0 + Filename: "two.jpg" + RelativeFilename: "two.jpg" + } + Video: 8113120, "Video::myFileTexture_2", "Clip" { + Type: "Clip" + Properties70: { + P: "Path", "KString", "XRefUrl", "", "three.jpg" + P: "RelPath", "KString", "XRefUrl", "", "three.jpg" + } + UseMipMap: 0 + Filename: "three.jpg" + RelativeFilename: "three.jpg" + } + Texture: 7772624, "Texture::ambient", "" { + Type: "TextureVideoClip" + Version: 202 + TextureName: "Texture::ambient" + Properties70: { + P: "UseMaterial", "bool", "", "",1 + } + Media: "Video::ambient" + FileName: "ambient.png" + RelativeFilename: "ambient.png" + ModelUVTranslation: 0,0 + ModelUVScaling: 1,1 + Texture_Alpha_Source: "None" + Cropping: 0,0,0,0 + } + Texture: 7773264, "Texture::myFileTexture_0", "" { + Type: "TextureVideoClip" + Version: 202 + TextureName: "Texture::myFileTexture_0" + Properties70: { + P: "Texture alpha", "Number", "", "A",0.2 + P: "UseMaterial", "bool", "", "",1 + } + Media: "Video::myFileTexture_0" + FileName: "one.jpg" + RelativeFilename: "one.jpg" + ModelUVTranslation: 0,0 + ModelUVScaling: 1,1 + Texture_Alpha_Source: "None" + Cropping: 0,0,0,0 + } + Texture: 7773744, "Texture::myFileTexture_1", "" { + Type: "TextureVideoClip" + Version: 202 + TextureName: "Texture::myFileTexture_1" + Properties70: { + P: "Texture alpha", "Number", "", "A",0.5 + P: "UseMaterial", "bool", "", "",1 + } + Media: "Video::myFileTexture_1" + FileName: "two.jpg" + RelativeFilename: "two.jpg" + ModelUVTranslation: 0,0 + ModelUVScaling: 1,1 + Texture_Alpha_Source: "None" + Cropping: 0,0,0,0 + } + Texture: 7807296, "Texture::myFileTexture_2", "" { + Type: "TextureVideoClip" + Version: 202 + TextureName: "Texture::myFileTexture_2" + Properties70: { + P: "Texture alpha", "Number", "", "A",0.4 + P: "UseMaterial", "bool", "", "",1 + } + Media: "Video::myFileTexture_2" + FileName: "three.jpg" + RelativeFilename: "three.jpg" + ModelUVTranslation: 0,0 + ModelUVScaling: 1,1 + Texture_Alpha_Source: "None" + Cropping: 0,0,0,0 + } + LayeredTexture: 7807776, "LayeredTexture::myLayeredTexture", "" { + LayeredTexture: 101 + BlendModes: 1,1,1 + Alphas: 1,1,1 + } +} + +; Object connections +;------------------------------------------------------------------ + +Connections: { + + ;Model::planeNode, Model::RootNode + C: "OO",7759392,0 + + ;Geometry::planeMesh, Model::planeNode + C: "OO",7761376,7759392 + + ;Material::myMaterial, Model::planeNode + C: "OO",7761952,7759392 + + ;Texture::ambient, Material::myMaterial + C: "OP",7772624,7761952, "AmbientColor" + + ;LayeredTexture::myLayeredTexture, Material::myMaterial + C: "OP",7807776,7761952, "DiffuseColor" + + ;Video::ambient, Texture::ambient + C: "OO",8094864,7772624 + + ;Video::myFileTexture_0, Texture::myFileTexture_0 + C: "OO",8112160,7773264 + + ;Video::myFileTexture_1, Texture::myFileTexture_1 + C: "OO",8112640,7773744 + + ;Video::myFileTexture_2, Texture::myFileTexture_2 + C: "OO",8113120,7807296 + + ;Texture::myFileTexture_0, LayeredTexture::myLayeredTexture + C: "OO",7773264,7807776 + + ;Texture::myFileTexture_1, LayeredTexture::myLayeredTexture + C: "OO",7773744,7807776 + + ;Texture::myFileTexture_2, LayeredTexture::myLayeredTexture + C: "OO",7807296,7807776 +} +;Takes section +;---------------------------------------------------- + +Takes: { + Current: "" +} diff --git a/src/MagnumPlugins/AssimpImporter/Test/material-raw.bin b/src/MagnumPlugins/AssimpImporter/Test/material-raw.bin new file mode 100644 index 000000000..0716ed9b2 Binary files /dev/null and b/src/MagnumPlugins/AssimpImporter/Test/material-raw.bin differ diff --git a/src/MagnumPlugins/AssimpImporter/Test/material-raw.bin.in b/src/MagnumPlugins/AssimpImporter/Test/material-raw.bin.in new file mode 100644 index 000000000..2b47670f0 --- /dev/null +++ b/src/MagnumPlugins/AssimpImporter/Test/material-raw.bin.in @@ -0,0 +1,14 @@ +type = '<3f3f3f 2f2f2f' +input = [ + # positions + 1.5, -1.0, -0.5, + -0.5, 2.5, 0.75, + -2.0, 1.0, 0.3, + + # texture coordinates + 0.3, 0.0, + 0.0, 0.5, + 0.3, 0.3 +] + +# kate: hl python diff --git a/src/MagnumPlugins/AssimpImporter/Test/material-raw.fbx b/src/MagnumPlugins/AssimpImporter/Test/material-raw.fbx new file mode 100644 index 000000000..43fd1fa4a --- /dev/null +++ b/src/MagnumPlugins/AssimpImporter/Test/material-raw.fbx @@ -0,0 +1,391 @@ +; FBX 7.7.0 project file +; ---------------------------------------------------- + +FBXHeaderExtension: { + FBXHeaderVersion: 1004 + FBXVersion: 7700 + CreationTimeStamp: { + Version: 1000 + Year: 2021 + Month: 11 + Day: 26 + Hour: 12 + Minute: 0 + Second: 47 + Millisecond: 358 + } + Creator: "FBX SDK/FBX Plugins version 2020.0.1" + OtherFlags: { + TCDefinition: 127 + } + SceneInfo: "SceneInfo::GlobalInfo", "UserData" { + Type: "UserData" + Version: 100 + MetaData: { + Version: 100 + Title: "" + Subject: "" + Author: "" + Keywords: "" + Revision: "" + Comment: "" + } + Properties70: { + P: "DocumentUrl", "KString", "Url", "", "material-raw.fbx" + P: "SrcDocumentUrl", "KString", "Url", "", "material-raw.fbx" + P: "Original", "Compound", "", "" + P: "Original|ApplicationVendor", "KString", "", "", "" + P: "Original|ApplicationName", "KString", "", "", "" + P: "Original|ApplicationVersion", "KString", "", "", "" + P: "Original|DateTime_GMT", "DateTime", "", "", "" + P: "Original|FileName", "KString", "", "", "" + P: "LastSaved", "Compound", "", "" + P: "LastSaved|ApplicationVendor", "KString", "", "", "" + P: "LastSaved|ApplicationName", "KString", "", "", "" + P: "LastSaved|ApplicationVersion", "KString", "", "", "" + P: "LastSaved|DateTime_GMT", "DateTime", "", "", "" + } + } +} +GlobalSettings: { + Version: 1000 + Properties70: { + P: "UpAxis", "int", "Integer", "",1 + P: "UpAxisSign", "int", "Integer", "",1 + P: "FrontAxis", "int", "Integer", "",2 + P: "FrontAxisSign", "int", "Integer", "",1 + P: "CoordAxis", "int", "Integer", "",0 + P: "CoordAxisSign", "int", "Integer", "",1 + P: "OriginalUpAxis", "int", "Integer", "",-1 + P: "OriginalUpAxisSign", "int", "Integer", "",1 + P: "UnitScaleFactor", "double", "Number", "",1 + P: "OriginalUnitScaleFactor", "double", "Number", "",1 + P: "AmbientColor", "ColorRGB", "Color", "",0,0,0 + P: "DefaultCamera", "KString", "", "", "Producer Perspective" + P: "TimeMode", "enum", "", "",0 + P: "TimeProtocol", "enum", "", "",2 + P: "SnapOnFrameMode", "enum", "", "",0 + P: "TimeSpanStart", "KTime", "Time", "",0 + P: "TimeSpanStop", "KTime", "Time", "",46186158000 + P: "CustomFrameRate", "double", "Number", "",-1 + P: "TimeMarker", "Compound", "", "" + P: "CurrentTimeMarker", "int", "Integer", "",-1 + } +} + +; Documents Description +;------------------------------------------------------------------ + +Documents: { + Count: 1 + Document: 7151232, "", "Scene" { + Properties70: { + P: "SourceObject", "object", "", "" + P: "ActiveAnimStackName", "KString", "", "", "" + } + RootNode: 0 + } +} + +; Document References +;------------------------------------------------------------------ + +References: { +} + +; Object definitions +;------------------------------------------------------------------ + +Definitions: { + Version: 100 + Count: 7 + ObjectType: "GlobalSettings" { + Count: 1 + } + ObjectType: "Model" { + Count: 2 + PropertyTemplate: "FbxNode" { + Properties70: { + P: "QuaternionInterpolate", "enum", "", "",0 + P: "RotationOffset", "Vector3D", "Vector", "",0,0,0 + P: "RotationPivot", "Vector3D", "Vector", "",0,0,0 + P: "ScalingOffset", "Vector3D", "Vector", "",0,0,0 + P: "ScalingPivot", "Vector3D", "Vector", "",0,0,0 + P: "TranslationActive", "bool", "", "",0 + P: "TranslationMin", "Vector3D", "Vector", "",0,0,0 + P: "TranslationMax", "Vector3D", "Vector", "",0,0,0 + P: "TranslationMinX", "bool", "", "",0 + P: "TranslationMinY", "bool", "", "",0 + P: "TranslationMinZ", "bool", "", "",0 + P: "TranslationMaxX", "bool", "", "",0 + P: "TranslationMaxY", "bool", "", "",0 + P: "TranslationMaxZ", "bool", "", "",0 + P: "RotationOrder", "enum", "", "",0 + P: "RotationSpaceForLimitOnly", "bool", "", "",0 + P: "RotationStiffnessX", "double", "Number", "",0 + P: "RotationStiffnessY", "double", "Number", "",0 + P: "RotationStiffnessZ", "double", "Number", "",0 + P: "AxisLen", "double", "Number", "",10 + P: "PreRotation", "Vector3D", "Vector", "",0,0,0 + P: "PostRotation", "Vector3D", "Vector", "",0,0,0 + P: "RotationActive", "bool", "", "",0 + P: "RotationMin", "Vector3D", "Vector", "",0,0,0 + P: "RotationMax", "Vector3D", "Vector", "",0,0,0 + P: "RotationMinX", "bool", "", "",0 + P: "RotationMinY", "bool", "", "",0 + P: "RotationMinZ", "bool", "", "",0 + P: "RotationMaxX", "bool", "", "",0 + P: "RotationMaxY", "bool", "", "",0 + P: "RotationMaxZ", "bool", "", "",0 + P: "InheritType", "enum", "", "",0 + P: "ScalingActive", "bool", "", "",0 + P: "ScalingMin", "Vector3D", "Vector", "",0,0,0 + P: "ScalingMax", "Vector3D", "Vector", "",1,1,1 + P: "ScalingMinX", "bool", "", "",0 + P: "ScalingMinY", "bool", "", "",0 + P: "ScalingMinZ", "bool", "", "",0 + P: "ScalingMaxX", "bool", "", "",0 + P: "ScalingMaxY", "bool", "", "",0 + P: "ScalingMaxZ", "bool", "", "",0 + P: "GeometricTranslation", "Vector3D", "Vector", "",0,0,0 + P: "GeometricRotation", "Vector3D", "Vector", "",0,0,0 + P: "GeometricScaling", "Vector3D", "Vector", "",1,1,1 + P: "MinDampRangeX", "double", "Number", "",0 + P: "MinDampRangeY", "double", "Number", "",0 + P: "MinDampRangeZ", "double", "Number", "",0 + P: "MaxDampRangeX", "double", "Number", "",0 + P: "MaxDampRangeY", "double", "Number", "",0 + P: "MaxDampRangeZ", "double", "Number", "",0 + P: "MinDampStrengthX", "double", "Number", "",0 + P: "MinDampStrengthY", "double", "Number", "",0 + P: "MinDampStrengthZ", "double", "Number", "",0 + P: "MaxDampStrengthX", "double", "Number", "",0 + P: "MaxDampStrengthY", "double", "Number", "",0 + P: "MaxDampStrengthZ", "double", "Number", "",0 + P: "PreferedAngleX", "double", "Number", "",0 + P: "PreferedAngleY", "double", "Number", "",0 + P: "PreferedAngleZ", "double", "Number", "",0 + P: "LookAtProperty", "object", "", "" + P: "UpVectorProperty", "object", "", "" + P: "Show", "bool", "", "",1 + P: "NegativePercentShapeSupport", "bool", "", "",1 + P: "DefaultAttributeIndex", "int", "Integer", "",-1 + P: "Freeze", "bool", "", "",0 + P: "LODBox", "bool", "", "",0 + P: "Lcl Translation", "Lcl Translation", "", "A",0,0,0 + P: "Lcl Rotation", "Lcl Rotation", "", "A",0,0,0 + P: "Lcl Scaling", "Lcl Scaling", "", "A",1,1,1 + P: "Visibility", "Visibility", "", "A",1 + P: "Visibility Inheritance", "Visibility Inheritance", "", "",1 + } + } + } + ObjectType: "Geometry" { + Count: 2 + PropertyTemplate: "FbxMesh" { + Properties70: { + P: "Color", "ColorRGB", "Color", "",0.8,0.8,0.8 + P: "BBoxMin", "Vector3D", "Vector", "",0,0,0 + P: "BBoxMax", "Vector3D", "Vector", "",0,0,0 + P: "Primary Visibility", "bool", "", "",1 + P: "Casts Shadows", "bool", "", "",1 + P: "Receive Shadows", "bool", "", "",1 + } + } + } + ObjectType: "Material" { + Count: 2 + PropertyTemplate: "FbxSurfaceLambert" { + Properties70: { + } + } + } +} + +; Object properties +;------------------------------------------------------------------ + +Objects: { + Geometry: 7639056, "Geometry::planeMesh_0", "Mesh" { + Vertices: *12 { + a: -5,-5,0,5,-5,0,5,5,0,-5,5,0 + } + PolygonVertexIndex: *4 { + a: 0,1,2,-4 + } + GeometryVersion: 124 + LayerElementNormal: 0 { + Version: 102 + Name: "normals" + MappingInformationType: "ByVertice" + ReferenceInformationType: "Direct" + Normals: *12 { + a: 0,0,1,0,0,1,0,0,1,0,0,1 + } + NormalsW: *4 { + a: 0,0,0,0 + } + } + LayerElementUV: 0 { + Version: 101 + Name: "diffuseUV" + MappingInformationType: "ByPolygonVertex" + ReferenceInformationType: "Direct" + UV: *8 { + a: 0,0,1,0,1,1,0,1 + } + } + LayerElementMaterial: 0 { + Version: 101 + Name: "" + MappingInformationType: "NoMappingInformation" + ReferenceInformationType: "IndexToDirect" + Materials: *1 { + a: 0 + } + } + Layer: 0 { + Version: 100 + LayerElement: { + Type: "LayerElementNormal" + TypedIndex: 0 + } + LayerElement: { + Type: "LayerElementMaterial" + TypedIndex: 0 + } + LayerElement: { + Type: "LayerElementUV" + TypedIndex: 0 + } + } + } + Geometry: 7642320, "Geometry::planeMesh_1", "Mesh" { + Vertices: *12 { + a: -5,-5,0,5,-5,0,5,5,0,-5,5,0 + } + PolygonVertexIndex: *4 { + a: 0,1,2,-4 + } + GeometryVersion: 124 + LayerElementNormal: 0 { + Version: 102 + Name: "normals" + MappingInformationType: "ByVertice" + ReferenceInformationType: "Direct" + Normals: *12 { + a: 0,0,1,0,0,1,0,0,1,0,0,1 + } + NormalsW: *4 { + a: 0,0,0,0 + } + } + LayerElementUV: 0 { + Version: 101 + Name: "diffuseUV" + MappingInformationType: "ByPolygonVertex" + ReferenceInformationType: "Direct" + UV: *8 { + a: 0,0,1,0,1,1,0,1 + } + } + LayerElementMaterial: 0 { + Version: 101 + Name: "" + MappingInformationType: "NoMappingInformation" + ReferenceInformationType: "IndexToDirect" + Materials: *1 { + a: 0 + } + } + Layer: 0 { + Version: 100 + LayerElement: { + Type: "LayerElementNormal" + TypedIndex: 0 + } + LayerElement: { + Type: "LayerElementMaterial" + TypedIndex: 0 + } + LayerElement: { + Type: "LayerElementUV" + TypedIndex: 0 + } + } + } + Model: 7637072, "Model::planeNode_0", "Mesh" { + Version: 232 + Properties70: { + P: "ScalingMax", "Vector3D", "Vector", "",0,0,0 + P: "DefaultAttributeIndex", "int", "Integer", "",0 + } + Shading: T + Culling: "CullingOff" + } + Model: 7640336, "Model::planeNode_1", "Mesh" { + Version: 232 + Properties70: { + P: "ScalingMax", "Vector3D", "Vector", "",0,0,0 + P: "DefaultAttributeIndex", "int", "Integer", "",0 + } + Shading: T + Culling: "CullingOff" + } + Material: 7647552, "Material::Standard_Types", "" { + Version: 102 + ShadingModel: "lambert" + MultiLayer: 0 + Properties70: { + P: "AmbientColor", "Color", "", "A",0.4,0.2,0.4 + P: "AmbientFactor", "Number", "", "A",0.25 + P: "DiffuseColor", "Color", "", "A",0.8,0.4,0.2 + P: "DiffuseFactor", "Number", "", "A",0.5 + P: "TransparentColor", "Color", "", "A",0.3,0.2,0.1 + P: "Opacity", "double", "Number", "",0.4 + } + } + Material: 7658224, "Material::Custom_Types", "" { + Version: 102 + ShadingModel: "lambert" + MultiLayer: 0 + Properties70: { + P: "AmbientColor", "Color", "", "A",0.1,0.05,0.1 + P: "DiffuseColor", "Color", "", "A",0.8,0.4,0.2 + P: "DiffuseFactor", "Number", "", "A",0.5 + P: "Opacity", "double", "Number", "",0.25 + P: "SomeColor", "Vector3D", "", "",0.1,0.2,0.3 + P: "SomeString", "KString", "", "","Ministry of Finance (Turkmenistan)" + } + } +} + +; Object connections +;------------------------------------------------------------------ + +Connections: { + + ;Model::planeNode_0, Model::RootNode + C: "OO",7637072,0 + + ;Model::planeNode_1, Model::RootNode + C: "OO",7640336,0 + + ;Geometry::planeMesh_0, Model::planeNode_0 + C: "OO",7639056,7637072 + + ;Material::Standard_Types, Model::planeNode_0 + C: "OO",7647552,7637072 + + ;Geometry::planeMesh_1, Model::planeNode_1 + C: "OO",7642320,7640336 + + ;Material::Custom_Types, Model::planeNode_1 + C: "OO",7658224,7640336 +} +;Takes section +;---------------------------------------------------- + +Takes: { + Current: "" +} diff --git a/src/MagnumPlugins/AssimpImporter/Test/material-raw.gltf b/src/MagnumPlugins/AssimpImporter/Test/material-raw.gltf new file mode 100644 index 000000000..97510b1c5 --- /dev/null +++ b/src/MagnumPlugins/AssimpImporter/Test/material-raw.gltf @@ -0,0 +1,105 @@ +{ + "asset": { + "version": "2.0" + }, + "images": [ + { + "uri": "basecolor.png" + }, + { + "uri": "normals.png" + } + ], + "nodes": [ + { "mesh": 0 } + ], + "scene": 0, + "scenes": [{ + "nodes": [0] + }], + "meshes": [ + { + "primitives": [{ + "attributes": { + "POSITION": 0, + "TEXCOORD_0": 1 + }, + "material": 0 + }] + } + ], + "accessors": [ + { + "name": "2", + "bufferView": 0, + "byteOffset": 0, + "componentType": 5126, + "count": 3, + "type": "VEC3" + }, + { + "name": "3", + "bufferView": 1, + "componentType": 5126, + "count": 3, + "type": "VEC2" + } + ], + "bufferViews": [ + { + "buffer": 0, + "byteOffset": 0, + "byteLength": 36 + }, + { + "buffer": 0, + "byteOffset": 36, + "byteLength": 24 + } + ], + "buffers": [ + { + "byteLength": 60, + "uri": "material-raw.bin" + } + ], + "materials": [ + { + "name": "raw", + "pbrMetallicRoughness": { + "baseColorFactor": [ 0.8, 0.2, 0.4, 0.3 ], + "baseColorTexture": { + "index": 0 + } + }, + "emissiveFactor": [ 0.1, 0.2, 0.3 ], + "doubleSided": true, + "normalTexture": { + "index": 1, + "texCoord": 1, + "extensions": { + "KHR_texture_transform": { + "offset": [0.0, 1.0], + "scale": [0.25, 0.75] + } + } + } + } + ], + "samplers": [ + {} + ], + "textures": [ + { + "sampler": 0, + "source": 0 + }, + { + "sampler": 0, + "source": 1 + } + ], + "extensionsUsed": [ + "KHR_texture_transform" + ] +} diff --git a/src/MagnumPlugins/AssimpImporter/Test/polygon.obj b/src/MagnumPlugins/AssimpImporter/Test/polygon.obj new file mode 100644 index 000000000..cb5c9ef97 --- /dev/null +++ b/src/MagnumPlugins/AssimpImporter/Test/polygon.obj @@ -0,0 +1,6 @@ +v -1.000000 -1.000000 0.000000 +v -1.000000 1.000000 0.000000 +v 1.000000 1.000000 0.000000 +v 0.999999 -1.000001 0.000000 + +f 2 3 4 1 diff --git a/src/MagnumPlugins/AssimpImporter/Test/scene-name.gltf b/src/MagnumPlugins/AssimpImporter/Test/scene-name.gltf new file mode 100644 index 000000000..a488cbd62 --- /dev/null +++ b/src/MagnumPlugins/AssimpImporter/Test/scene-name.gltf @@ -0,0 +1,19 @@ +{ + "asset" : { + "version" : "2.0" + }, + "scene" : 0, + "scenes" : [ + { + "name" : "This is the scene", + "nodes" : [ 0 ] + }, + { + "name" : "Other scene", + "nodes" : [ 1 ] + } + ], + "nodes" : [ + {}, {} + ] +} diff --git a/src/MagnumPlugins/AssimpImporter/Test/skin-no-mesh.bin b/src/MagnumPlugins/AssimpImporter/Test/skin-no-mesh.bin new file mode 100644 index 000000000..2553f6670 Binary files /dev/null and b/src/MagnumPlugins/AssimpImporter/Test/skin-no-mesh.bin differ diff --git a/src/MagnumPlugins/AssimpImporter/Test/skin-no-mesh.gltf b/src/MagnumPlugins/AssimpImporter/Test/skin-no-mesh.gltf new file mode 100644 index 000000000..2612ebae4 --- /dev/null +++ b/src/MagnumPlugins/AssimpImporter/Test/skin-no-mesh.gltf @@ -0,0 +1,58 @@ +{ + "asset": { + "version": "2.0" + }, + "note": "Same as skin.gltf from TinyGltfImporter tests, but with a default scene so assimp 5.1.0 loads it", + "nodes": [ + {}, + {}, + {} + ], + "skins": [ + { + "name": "implicit inverse bind matrices", + "joints": [1, 2] + }, + { + "name": "explicit inverse bind matrices", + "inverseBindMatrices": 1, + "joints": [0, 2, 1] + } + ], + "buffers": [ + { + "byteLength": 192, + "uri": "skin-no-mesh.bin" + } + ], + "bufferViews": [ + { + "buffer": 0, + "byteOffset": 0, + "byteLength": 72 + }, + { + "buffer": 0, + "byteOffset": 0, + "byteLength": 192 + } + ], + "accessors": [ + { + "bufferView": 0, + "byteOffset": 0, + "componentType": 5123, + "count": 2, + "type": "MAT3" + }, + { + "bufferView": 1, + "byteOffset": 0, + "componentType": 5126, + "count": 3, + "type": "MAT4" + } + ], + "scene": 0, + "scenes": [{}] +} diff --git a/src/MagnumPlugins/AssimpImporter/checkAssimpVersion.cpp b/src/MagnumPlugins/AssimpImporter/checkAssimpVersion.cpp index f8394aadd..e63039717 100644 --- a/src/MagnumPlugins/AssimpImporter/checkAssimpVersion.cpp +++ b/src/MagnumPlugins/AssimpImporter/checkAssimpVersion.cpp @@ -28,7 +28,9 @@ #include #endif +#include #include +#include #include #ifndef CHECK_VERSION @@ -39,6 +41,15 @@ int main() { int ret = 0; + /* Version that breaks aiAnimation::mTicksPerSecond for FBX: + https://github.com/assimp/assimp/commit/b3e1ee3ca0d825d384044867fc30cd0bc8417be6 + Check for aiQuaternion::operation *= added in + https://github.com/assimp/assimp/commit/89d4d6b68f720aaf545dba9d6a701426b948df15 */ + #if CHECK_VERSION >= 20210102 + aiQuaternion quat; + quat *= aiMatrix4x4(); + #endif + /* First version that correctly parses glTF2 spline-interpolated animation data: https://github.com/assimp/assimp/commit/e3083c21f0a7beae6c37a2265b7919a02cbf83c4. Check for Scene::mName added in @@ -48,6 +59,19 @@ int main() { scene.mName = ""; #endif + /* Support for orthographic camera width. + https://github.com/assimp/assimp/commit/ae50c4ebdf23c7f6f61300dede5bf32e0d306eb2 */ + #if CHECK_VERSION >= 20200225 + aiCamera camera; + camera.mOrthographicWidth = 1.0f; + #endif + + /* Support for patch version information. + https://github.com/assimp/assimp/commit/5cfb0fd633372bbbec87f08015139d71d330d4a6 */ + #if CHECK_VERSION >= 20191122 + ret = static_cast(aiGetVersionPatch()); + #endif + /* Assimp 5. Of all the things that could break, this version reports itself as 4.1. Since some of the insane awful bugs got fixed in version 5, the test has to check against the version in order to adjust expectations. The only way I @@ -58,6 +82,12 @@ int main() { ret = static_cast(Assimp::Math::getEpsilon()); #endif + /* Support for double types (ai_real, aiPTI_Double): + https://github.com/assimp/assimp/commit/fa1d6d8c55484a1ab97b2773585ae76f71ef6fbc */ + #if CHECK_VERSION >= 20160716 + ai_real real{}; + #endif + unsigned int version = aiGetVersionMajor()*100 + aiGetVersionMinor(); ret = static_cast(version); diff --git a/src/MagnumPlugins/AssimpImporter/configureInternal.h.cmake b/src/MagnumPlugins/AssimpImporter/configureInternal.h.cmake index 46ed01a81..398e82d4e 100644 --- a/src/MagnumPlugins/AssimpImporter/configureInternal.h.cmake +++ b/src/MagnumPlugins/AssimpImporter/configureInternal.h.cmake @@ -25,5 +25,10 @@ */ #define ASSIMP_VERSION ${ASSIMP_VERSION} -#define ASSIMP_IS_VERSION_5 (ASSIMP_VERSION >= 20190915) +#define ASSIMP_HAS_DOUBLES (ASSIMP_VERSION>= 20160716) +#define ASSIMP_IS_VERSION_5_OR_GREATER (ASSIMP_VERSION >= 20190915) +#define ASSIMP_HAS_VERSION_PATCH (ASSIMP_VERSION >= 20191122) +#define ASSIMP_HAS_ORTHOGRAPHIC_CAMERA (ASSIMP_VERSION >= 20200225) +#define ASSIMP_HAS_SCENE_NAME (ASSIMP_VERSION >= 20201123) #define ASSIMP_HAS_BROKEN_GLTF_SPLINES (ASSIMP_VERSION < 20201123) +#define ASSIMP_HAS_BROKEN_FBX_TICKS_PER_SECOND (ASSIMP_VERSION >= 20210102)