From f6b4370f6ac1bf2db0ef9edeb0ff1ce8c7e61aab Mon Sep 17 00:00:00 2001 From: Evangel Date: Fri, 26 Jun 2020 14:26:45 +1000 Subject: [PATCH 1/3] Added arbitrary recursive metadata to allow for glTF2's extensions to be properly represented. Primary changes are to include/assimp/metadata.h, adding in the aiMetadata GetAiType function, adding the operator= to allow an aiMetadata type to be assigned, adding a check for the AI_AIMETADATA type flag as it can't be trivially memcpy'd. operator= is implemented with a by-value argument as then the copy is made by the copy constructor and we can just swap everything out and let the destructor handle the mess. Implemented parsing of the "extensions" flag on all glTF2 Nodes. Doesn't use the ReadValue helper function on numbers as it did not seem to fill out the Nullable structure properly. --- code/AssetLib/glTF2/glTF2Asset.h | 44 +++++++++++++++++++++++++++ code/AssetLib/glTF2/glTF2Asset.inl | 39 ++++++++++++++++++++++++ code/AssetLib/glTF2/glTF2Importer.cpp | 32 +++++++++++++++++-- include/assimp/metadata.h | 30 ++++++++++++++++-- 4 files changed, 140 insertions(+), 5 deletions(-) diff --git a/code/AssetLib/glTF2/glTF2Asset.h b/code/AssetLib/glTF2/glTF2Asset.h index ffecd3dbf2..543bef95c8 100644 --- a/code/AssetLib/glTF2/glTF2Asset.h +++ b/code/AssetLib/glTF2/glTF2Asset.h @@ -784,6 +784,48 @@ struct Mesh : public Object { void Read(Value &pJSON_Object, Asset &pAsset_Root); }; +struct CustomExtension : public Object { + // + // A struct containing custom extension data added to a glTF2 file + // Has to contain Object, Array, String, Double, Uint64, and Int64 at a minimum + // String, Double, Uint64, and Int64 are stored in the Nullables + // Object and Array are stored in the std::vector + // + + Nullable mStringValue; + Nullable mDoubleValue; + Nullable mUint64Value; + Nullable mInt64Value; + + // std::vector handles both Object and Array + Nullable> mValues; + + operator bool() const { + return Size(); + } + + size_t Size() const { + if (mValues.isPresent) { + return mValues.value.size(); + } else if (mStringValue.isPresent || mDoubleValue.isPresent || mUint64Value.isPresent || mInt64Value.isPresent) { + return 1; + } + return 0; + } + + CustomExtension() = default; + + CustomExtension(const CustomExtension& other) + : Object(other) + , mStringValue(other.mStringValue) + , mDoubleValue(other.mDoubleValue) + , mUint64Value(other.mUint64Value) + , mInt64Value(other.mInt64Value) + , mValues(other.mValues) + { + } +}; + struct Node : public Object { std::vector> children; std::vector> meshes; @@ -802,6 +844,8 @@ struct Node : public Object { Ref parent; //!< This is not part of the glTF specification. Used as a helper. + CustomExtension extensions; + Node() {} void Read(Value &obj, Asset &r); }; diff --git a/code/AssetLib/glTF2/glTF2Asset.inl b/code/AssetLib/glTF2/glTF2Asset.inl index 5a99525b24..71b1641490 100644 --- a/code/AssetLib/glTF2/glTF2Asset.inl +++ b/code/AssetLib/glTF2/glTF2Asset.inl @@ -1207,6 +1207,43 @@ inline void Light::Read(Value &obj, Asset & /*r*/) { } } +inline CustomExtension ReadExtensions(const char *name, Value& obj) { + CustomExtension ret; + ret.name = name; + if (obj.IsObject()) { + ret.mValues.isPresent = true; + for (auto it = obj.MemberBegin(); it != obj.MemberEnd(); ++it) { + auto& val = it->value; + ret.mValues.value.push_back(ReadExtensions(it->name.GetString(), val)); + } + } + else if (obj.IsArray()) { + ret.mValues.value.reserve(obj.Size()); + ret.mValues.isPresent = true; + for (unsigned int i = 0; i < obj.Size(); ++i) + { + ret.mValues.value.push_back(ReadExtensions(name, obj[i])); + } + } + else if (obj.IsNumber()) { + if (obj.IsUint64()) { + ret.mUint64Value.value = obj.GetUint64(); + ret.mUint64Value.isPresent = true; + } else if (obj.IsInt64()) { + ret.mInt64Value.value = obj.GetInt64(); + ret.mInt64Value.isPresent = true; + } else if (obj.IsDouble()) { + ret.mDoubleValue.value = obj.GetDouble(); + ret.mDoubleValue.isPresent = true; + } + } + else if (obj.IsString()) { + ReadValue(obj, ret.mStringValue); + ret.mStringValue.isPresent = true; + } + return ret; +} + inline void Node::Read(Value &obj, Asset &r) { if (name.empty()) { name = id; @@ -1261,6 +1298,8 @@ inline void Node::Read(Value &obj, Asset &r) { Value *curExtensions = FindObject(obj, "extensions"); if (nullptr != curExtensions) { + this->extensions = ReadExtensions("extensions", *curExtensions); + if (r.extensionsUsed.KHR_lights_punctual) { if (Value *ext = FindObject(*curExtensions, "KHR_lights_punctual")) { Value *curLight = FindUInt(*ext, "light"); diff --git a/code/AssetLib/glTF2/glTF2Importer.cpp b/code/AssetLib/glTF2/glTF2Importer.cpp index 1c044e7ce5..3a3c66e151 100644 --- a/code/AssetLib/glTF2/glTF2Importer.cpp +++ b/code/AssetLib/glTF2/glTF2Importer.cpp @@ -847,6 +847,24 @@ static std::string GetNodeName(const Node &node) { return node.name.empty() ? node.id : node.name; } +void ParseExtensions(aiMetadata *metadata, const CustomExtension &extension, unsigned int depth = 0) { + if (extension.mStringValue.isPresent) { + metadata->Add(extension.name.c_str(), aiString(extension.mStringValue.value)); + } else if (extension.mDoubleValue.isPresent) { + metadata->Add(extension.name.c_str(), extension.mDoubleValue.value); + } else if (extension.mUint64Value.isPresent) { + metadata->Add(extension.name.c_str(), extension.mUint64Value.value); + } else if (extension.mInt64Value.isPresent) { + metadata->Add(extension.name.c_str(), static_cast(extension.mInt64Value.value)); + } else if (extension.mValues.isPresent) { + aiMetadata val; + for (size_t i = 0; i < extension.mValues.value.size(); ++i) { + ParseExtensions(&val, extension.mValues.value[i], depth + 2); + } + metadata->Add(extension.name.c_str(), val); + } +} + aiNode *ImportNode(aiScene *pScene, glTF2::Asset &r, std::vector &meshOffsets, glTF2::Ref &ptr) { Node &node = *ptr; @@ -863,6 +881,11 @@ aiNode *ImportNode(aiScene *pScene, glTF2::Asset &r, std::vector & } } + if (node.extensions) { + ainode->mMetaData = new aiMetadata; + ParseExtensions(ainode->mMetaData, node.extensions); + } + GetNodeTransform(ainode->mTransformation, node); if (!node.meshes.empty()) { @@ -957,8 +980,13 @@ aiNode *ImportNode(aiScene *pScene, glTF2::Asset &r, std::vector & //range is optional - see https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_lights_punctual //it is added to meta data of parent node, because there is no other place to put it if (node.light->range.isPresent) { - ainode->mMetaData = aiMetadata::Alloc(1); - ainode->mMetaData->Set(0, "PBR_LightRange", node.light->range.value); + if (!ainode->mMetaData) { + ainode->mMetaData = aiMetadata::Alloc(1); + ainode->mMetaData->Set(0, "PBR_LightRange", node.light->range.value); + } + else { + ainode->mMetaData->Add("PBR_LightRange", node.light->range.value); + } } } diff --git a/include/assimp/metadata.h b/include/assimp/metadata.h index 684508a71b..95d9195844 100644 --- a/include/assimp/metadata.h +++ b/include/assimp/metadata.h @@ -69,7 +69,8 @@ typedef enum aiMetadataType { AI_DOUBLE = 4, AI_AISTRING = 5, AI_AIVECTOR3D = 6, - AI_META_MAX = 7, + AI_AIMETADATA = 7, + AI_META_MAX = 8, #ifndef SWIG FORCE_32BIT = INT_MAX @@ -100,6 +101,8 @@ struct aiMetadataEntry { #include +struct aiMetadata; + // ------------------------------------------------------------------------------- /** * Helper functions to get the aiType enum entry for a type @@ -127,6 +130,9 @@ inline aiMetadataType GetAiType(const aiString &) { inline aiMetadataType GetAiType(const aiVector3D &) { return AI_AIVECTOR3D; } +inline aiMetadataType GetAiType(const aiMetadata &) { + return AI_AIMETADATA; +} #endif // __cplusplus @@ -204,6 +210,11 @@ struct aiMetadata { rhs.Get(mKeys[i], v); mValues[i].mData = new aiVector3D(v); } break; + case AI_AIMETADATA: { + aiMetadata v; + rhs.Get(mKeys[i], v); + mValues[i].mData = new aiMetadata(v); + } break; #ifndef SWIG case FORCE_32BIT: #endif @@ -213,7 +224,15 @@ struct aiMetadata { } } - /** + aiMetadata &operator=(aiMetadata rhs) { + using std::swap; + swap(mNumProperties, rhs.mNumProperties); + swap(mKeys, rhs.mKeys); + swap(mValues, rhs.mValues); + return *this; + } + + /** * @brief The destructor. */ ~aiMetadata() { @@ -245,6 +264,9 @@ struct aiMetadata { case AI_AIVECTOR3D: delete static_cast(data); break; + case AI_AIMETADATA: + delete static_cast(data); + break; #ifndef SWIG case FORCE_32BIT: #endif @@ -323,8 +345,10 @@ struct aiMetadata { mValues[index].mType = GetAiType(value); // Copy the given value to the dynamic storage - if (nullptr != mValues[index].mData) { + if (nullptr != mValues[index].mData && AI_AIMETADATA != mValues[index].mType) { ::memcpy(mValues[index].mData, &value, sizeof(T)); + } else if (nullptr != mValues[index].mData && AI_AIMETADATA == mValues[index].mType) { + *static_cast(mValues[index].mData) = value; } else { mValues[index].mData = new T(value); } From d75d59a4fde93d070364cd3e7035eebc5428abf1 Mon Sep 17 00:00:00 2001 From: Evangel Date: Fri, 26 Jun 2020 15:07:42 +1000 Subject: [PATCH 2/3] Added operator== and operator!= to aiMetadata to allow for testing. Updated utMetadata.copy_test to reflect that there's now 8 metadata types. --- include/assimp/metadata.h | 83 +++++++++++++++++++++++++++++++++++++++ test/unit/utMetadata.cpp | 11 +++++- 2 files changed, 93 insertions(+), 1 deletion(-) diff --git a/include/assimp/metadata.h b/include/assimp/metadata.h index 95d9195844..52121fbf14 100644 --- a/include/assimp/metadata.h +++ b/include/assimp/metadata.h @@ -442,6 +442,89 @@ struct aiMetadata { return false; } + friend bool CompareKeys(const aiMetadata &lhs, const aiMetadata &rhs) { + if (lhs.mNumProperties != rhs.mNumProperties) { + return false; + } + + for (unsigned int i = 0; i < lhs.mNumProperties; ++i) { + if (lhs.mKeys[i] != rhs.mKeys[i]) { + return false; + } + } + return true; + } + + friend bool CompareValues(const aiMetadata &lhs, const aiMetadata &rhs) { + if (lhs.mNumProperties != rhs.mNumProperties) { + return false; + } + + for (unsigned int i = 0; i < lhs.mNumProperties; ++i) { + if (lhs.mValues[i].mType != rhs.mValues[i].mType) { + return false; + } + + switch (lhs.mValues[i].mType) { + case AI_BOOL: { + if (*static_cast(lhs.mValues[i].mData) != *static_cast(rhs.mValues[i].mData)) { + return false; + } + } break; + case AI_INT32: { + if (*static_cast(lhs.mValues[i].mData) != *static_cast(rhs.mValues[i].mData)) { + return false; + } + } break; + case AI_UINT64: { + if (*static_cast(lhs.mValues[i].mData) != *static_cast(rhs.mValues[i].mData)) { + return false; + } + } break; + case AI_FLOAT: { + if (*static_cast(lhs.mValues[i].mData) != *static_cast(rhs.mValues[i].mData)) { + return false; + } + } break; + case AI_DOUBLE: { + if (*static_cast(lhs.mValues[i].mData) != *static_cast(rhs.mValues[i].mData)) { + return false; + } + } break; + case AI_AISTRING: { + if (*static_cast(lhs.mValues[i].mData) != *static_cast(rhs.mValues[i].mData)) { + return false; + } + } break; + case AI_AIVECTOR3D: { + if (*static_cast(lhs.mValues[i].mData) != *static_cast(rhs.mValues[i].mData)) { + return false; + } + } break; + case AI_AIMETADATA: { + if (*static_cast(lhs.mValues[i].mData) != *static_cast(rhs.mValues[i].mData)) { + return false; + } + } break; +#ifndef SWIG + case FORCE_32BIT: +#endif + default: + break; + } + } + + return true; + } + + friend bool operator==(const aiMetadata &lhs, const aiMetadata &rhs) { + return CompareKeys(lhs, rhs) && CompareValues(lhs, rhs); + } + + friend bool operator!=(const aiMetadata &lhs, const aiMetadata &rhs) { + return !(lhs == rhs); + } + #endif // __cplusplus }; diff --git a/test/unit/utMetadata.cpp b/test/unit/utMetadata.cpp index a605107db1..81ab61435b 100644 --- a/test/unit/utMetadata.cpp +++ b/test/unit/utMetadata.cpp @@ -197,9 +197,11 @@ TEST_F( utMetadata, copy_test ) { m_data->Set( 5, "aiString", strVal ); aiVector3D vecVal( 1, 2, 3 ); m_data->Set( 6, "aiVector3D", vecVal ); + aiMetadata metaVal; + m_data->Set( 7, "aiMetadata", metaVal ); aiMetadata copy( *m_data ); - EXPECT_EQ( 7u, copy.mNumProperties ); + EXPECT_EQ( 8u, copy.mNumProperties ); // bool test { @@ -251,4 +253,11 @@ TEST_F( utMetadata, copy_test ) { EXPECT_TRUE( copy.Get( "aiVector3D", v ) ); EXPECT_EQ( vecVal, v ); } + + // metadata test + { + aiMetadata v; + EXPECT_TRUE( copy.Get( "aiMetadata", v ) ); + EXPECT_EQ( metaVal, v ); + } } From 6d85280c8dcd936a19caedf07eb9d454e3e511a8 Mon Sep 17 00:00:00 2001 From: Evangel Date: Sat, 27 Jun 2020 12:53:26 +1000 Subject: [PATCH 3/3] Added bool, removed unused debug parameter --- code/AssetLib/glTF2/glTF2Asset.h | 4 +++- code/AssetLib/glTF2/glTF2Asset.inl | 4 ++++ code/AssetLib/glTF2/glTF2Importer.cpp | 6 ++++-- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/code/AssetLib/glTF2/glTF2Asset.h b/code/AssetLib/glTF2/glTF2Asset.h index 543bef95c8..f79ddee87f 100644 --- a/code/AssetLib/glTF2/glTF2Asset.h +++ b/code/AssetLib/glTF2/glTF2Asset.h @@ -796,6 +796,7 @@ struct CustomExtension : public Object { Nullable mDoubleValue; Nullable mUint64Value; Nullable mInt64Value; + Nullable mBoolValue; // std::vector handles both Object and Array Nullable> mValues; @@ -807,7 +808,7 @@ struct CustomExtension : public Object { size_t Size() const { if (mValues.isPresent) { return mValues.value.size(); - } else if (mStringValue.isPresent || mDoubleValue.isPresent || mUint64Value.isPresent || mInt64Value.isPresent) { + } else if (mStringValue.isPresent || mDoubleValue.isPresent || mUint64Value.isPresent || mInt64Value.isPresent || mBoolValue.isPresent) { return 1; } return 0; @@ -821,6 +822,7 @@ struct CustomExtension : public Object { , mDoubleValue(other.mDoubleValue) , mUint64Value(other.mUint64Value) , mInt64Value(other.mInt64Value) + , mBoolValue(other.mBoolValue) , mValues(other.mValues) { } diff --git a/code/AssetLib/glTF2/glTF2Asset.inl b/code/AssetLib/glTF2/glTF2Asset.inl index 71b1641490..94f8d9ffa4 100644 --- a/code/AssetLib/glTF2/glTF2Asset.inl +++ b/code/AssetLib/glTF2/glTF2Asset.inl @@ -1241,6 +1241,10 @@ inline CustomExtension ReadExtensions(const char *name, Value& obj) { ReadValue(obj, ret.mStringValue); ret.mStringValue.isPresent = true; } + else if (obj.IsBool()) { + ret.mBoolValue.value = obj.GetBool(); + ret.mBoolValue.isPresent = true; + } return ret; } diff --git a/code/AssetLib/glTF2/glTF2Importer.cpp b/code/AssetLib/glTF2/glTF2Importer.cpp index 3a3c66e151..4d740d8c1d 100644 --- a/code/AssetLib/glTF2/glTF2Importer.cpp +++ b/code/AssetLib/glTF2/glTF2Importer.cpp @@ -847,7 +847,7 @@ static std::string GetNodeName(const Node &node) { return node.name.empty() ? node.id : node.name; } -void ParseExtensions(aiMetadata *metadata, const CustomExtension &extension, unsigned int depth = 0) { +void ParseExtensions(aiMetadata *metadata, const CustomExtension &extension) { if (extension.mStringValue.isPresent) { metadata->Add(extension.name.c_str(), aiString(extension.mStringValue.value)); } else if (extension.mDoubleValue.isPresent) { @@ -856,10 +856,12 @@ void ParseExtensions(aiMetadata *metadata, const CustomExtension &extension, uns metadata->Add(extension.name.c_str(), extension.mUint64Value.value); } else if (extension.mInt64Value.isPresent) { metadata->Add(extension.name.c_str(), static_cast(extension.mInt64Value.value)); + } else if (extension.mBoolValue.isPresent) { + metadata->Add(extension.name.c_str(), extension.mBoolValue.value); } else if (extension.mValues.isPresent) { aiMetadata val; for (size_t i = 0; i < extension.mValues.value.size(); ++i) { - ParseExtensions(&val, extension.mValues.value[i], depth + 2); + ParseExtensions(&val, extension.mValues.value[i]); } metadata->Add(extension.name.c_str(), val); }