diff options
Diffstat (limited to 'src/mesh/assimp-master/code/Common/SceneCombiner.cpp')
-rw-r--r-- | src/mesh/assimp-master/code/Common/SceneCombiner.cpp | 1375 |
1 files changed, 0 insertions, 1375 deletions
diff --git a/src/mesh/assimp-master/code/Common/SceneCombiner.cpp b/src/mesh/assimp-master/code/Common/SceneCombiner.cpp deleted file mode 100644 index 2c2539e..0000000 --- a/src/mesh/assimp-master/code/Common/SceneCombiner.cpp +++ /dev/null @@ -1,1375 +0,0 @@ -/* -Open Asset Import Library (assimp) ----------------------------------------------------------------------- - -Copyright (c) 2006-2022, assimp team - - -All rights reserved. - -Redistribution and use of this software in source and binary forms, -with or without modification, are permitted provided that the -following conditions are met: - -* Redistributions of source code must retain the above - copyright notice, this list of conditions and the - following disclaimer. - -* Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the - following disclaimer in the documentation and/or other - materials provided with the distribution. - -* Neither the name of the assimp team, nor the names of its - contributors may be used to endorse or promote products - derived from this software without specific prior - written permission of the assimp team. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ----------------------------------------------------------------------- -*/ - -// TODO: refactor entire file to get rid of the "flat-copy" first approach -// to copying structures. This easily breaks in the most unintuitive way -// possible as new fields are added to assimp structures. - -// ---------------------------------------------------------------------------- -/** - * @file Implements Assimp::SceneCombiner. This is a smart utility - * class that combines multiple scenes, meshes, ... into one. Currently - * these utilities are used by the IRR and LWS loaders and the - * OptimizeGraph step. - */ -// ---------------------------------------------------------------------------- -#include "ScenePrivate.h" -#include "time.h" -#include <assimp/Hash.h> -#include <assimp/SceneCombiner.h> -#include <assimp/StringUtils.h> -#include <assimp/fast_atof.h> -#include <assimp/mesh.h> -#include <assimp/metadata.h> -#include <assimp/scene.h> -#include <stdio.h> -#include <assimp/DefaultLogger.hpp> - -namespace Assimp { - -#if (__GNUC__ >= 8 && __GNUC_MINOR__ >= 0) -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wclass-memaccess" -#endif - -// ------------------------------------------------------------------------------------------------ -// Add a prefix to a string -inline void PrefixString(aiString &string, const char *prefix, unsigned int len) { - // If the string is already prefixed, we won't prefix it a second time - if (string.length >= 1 && string.data[0] == '$') - return; - - if (len + string.length >= MAXLEN - 1) { - ASSIMP_LOG_VERBOSE_DEBUG("Can't add an unique prefix because the string is too long"); - ai_assert(false); - return; - } - - // Add the prefix - ::memmove(string.data + len, string.data, string.length + 1); - ::memcpy(string.data, prefix, len); - - // And update the string's length - string.length += len; -} - -// ------------------------------------------------------------------------------------------------ -// Add node identifiers to a hashing set -void SceneCombiner::AddNodeHashes(aiNode *node, std::set<unsigned int> &hashes) { - // Add node name to hashing set if it is non-empty - empty nodes are allowed - // and they can't have any anims assigned so its absolutely safe to duplicate them. - if (node->mName.length) { - hashes.insert(SuperFastHash(node->mName.data, static_cast<uint32_t>(node->mName.length))); - } - - // Process all children recursively - for (unsigned int i = 0; i < node->mNumChildren; ++i) { - AddNodeHashes(node->mChildren[i], hashes); - } -} - -// ------------------------------------------------------------------------------------------------ -// Add a name prefix to all nodes in a hierarchy -void SceneCombiner::AddNodePrefixes(aiNode *node, const char *prefix, unsigned int len) { - ai_assert(nullptr != prefix); - - PrefixString(node->mName, prefix, len); - - // Process all children recursively - for (unsigned int i = 0; i < node->mNumChildren; ++i) { - AddNodePrefixes(node->mChildren[i], prefix, len); - } -} - -// ------------------------------------------------------------------------------------------------ -// Search for matching names -bool SceneCombiner::FindNameMatch(const aiString &name, std::vector<SceneHelper> &input, unsigned int cur) { - const unsigned int hash = SuperFastHash(name.data, static_cast<uint32_t>(name.length)); - - // Check whether we find a positive match in one of the given sets - for (unsigned int i = 0; i < input.size(); ++i) { - if (cur != i && input[i].hashes.find(hash) != input[i].hashes.end()) { - return true; - } - } - return false; -} - -// ------------------------------------------------------------------------------------------------ -// Add a name prefix to all nodes in a hierarchy if a hash match is found -void SceneCombiner::AddNodePrefixesChecked(aiNode *node, const char *prefix, unsigned int len, - std::vector<SceneHelper> &input, unsigned int cur) { - ai_assert(nullptr != prefix); - - const unsigned int hash = SuperFastHash(node->mName.data, static_cast<uint32_t>(node->mName.length)); - - // Check whether we find a positive match in one of the given sets - for (unsigned int i = 0; i < input.size(); ++i) { - if (cur != i && input[i].hashes.find(hash) != input[i].hashes.end()) { - PrefixString(node->mName, prefix, len); - break; - } - } - - // Process all children recursively - for (unsigned int i = 0; i < node->mNumChildren; ++i) { - AddNodePrefixesChecked(node->mChildren[i], prefix, len, input, cur); - } -} - -// ------------------------------------------------------------------------------------------------ -// Add an offset to all mesh indices in a node graph -void SceneCombiner::OffsetNodeMeshIndices(aiNode *node, unsigned int offset) { - for (unsigned int i = 0; i < node->mNumMeshes; ++i) - node->mMeshes[i] += offset; - - for (unsigned int i = 0; i < node->mNumChildren; ++i) { - OffsetNodeMeshIndices(node->mChildren[i], offset); - } -} - -// ------------------------------------------------------------------------------------------------ -// Merges two scenes. Currently only used by the LWS loader. -void SceneCombiner::MergeScenes(aiScene **_dest, std::vector<aiScene *> &src, unsigned int flags) { - if (nullptr == _dest) { - return; - } - - // if _dest points to nullptr allocate a new scene. Otherwise clear the old and reuse it - if (src.empty()) { - if (*_dest) { - (*_dest)->~aiScene(); - SceneCombiner::CopySceneFlat(_dest, src[0]); - } else - *_dest = src[0]; - return; - } - if (*_dest) { - (*_dest)->~aiScene(); - new (*_dest) aiScene(); - } else - *_dest = new aiScene(); - - // Create a dummy scene to serve as master for the others - aiScene *master = new aiScene(); - master->mRootNode = new aiNode(); - master->mRootNode->mName.Set("<MergeRoot>"); - - std::vector<AttachmentInfo> srcList(src.size()); - for (unsigned int i = 0; i < srcList.size(); ++i) { - srcList[i] = AttachmentInfo(src[i], master->mRootNode); - } - - // 'master' will be deleted afterwards - MergeScenes(_dest, master, srcList, flags); -} - -// ------------------------------------------------------------------------------------------------ -void SceneCombiner::AttachToGraph(aiNode *attach, std::vector<NodeAttachmentInfo> &srcList) { - unsigned int cnt; - for (cnt = 0; cnt < attach->mNumChildren; ++cnt) { - AttachToGraph(attach->mChildren[cnt], srcList); - } - - cnt = 0; - for (std::vector<NodeAttachmentInfo>::iterator it = srcList.begin(); - it != srcList.end(); ++it) { - if ((*it).attachToNode == attach && !(*it).resolved) - ++cnt; - } - - if (cnt) { - aiNode **n = new aiNode *[cnt + attach->mNumChildren]; - if (attach->mNumChildren) { - ::memcpy(n, attach->mChildren, sizeof(void *) * attach->mNumChildren); - delete[] attach->mChildren; - } - attach->mChildren = n; - - n += attach->mNumChildren; - attach->mNumChildren += cnt; - - for (unsigned int i = 0; i < srcList.size(); ++i) { - NodeAttachmentInfo &att = srcList[i]; - if (att.attachToNode == attach && !att.resolved) { - *n = att.node; - (**n).mParent = attach; - ++n; - - // mark this attachment as resolved - att.resolved = true; - } - } - } -} - -// ------------------------------------------------------------------------------------------------ -void SceneCombiner::AttachToGraph(aiScene *master, std::vector<NodeAttachmentInfo> &src) { - ai_assert(nullptr != master); - - AttachToGraph(master->mRootNode, src); -} - -// ------------------------------------------------------------------------------------------------ -void SceneCombiner::MergeScenes(aiScene **_dest, aiScene *master, std::vector<AttachmentInfo> &srcList, unsigned int flags) { - if (nullptr == _dest) { - return; - } - - // if _dest points to nullptr allocate a new scene. Otherwise clear the old and reuse it - if (srcList.empty()) { - if (*_dest) { - SceneCombiner::CopySceneFlat(_dest, master); - } else - *_dest = master; - return; - } - if (*_dest) { - (*_dest)->~aiScene(); - new (*_dest) aiScene(); - } else - *_dest = new aiScene(); - - aiScene *dest = *_dest; - - std::vector<SceneHelper> src(srcList.size() + 1); - src[0].scene = master; - for (unsigned int i = 0; i < srcList.size(); ++i) { - src[i + 1] = SceneHelper(srcList[i].scene); - } - - // this helper array specifies which scenes are duplicates of others - std::vector<unsigned int> duplicates(src.size(), UINT_MAX); - - // this helper array is used as lookup table several times - std::vector<unsigned int> offset(src.size()); - - // Find duplicate scenes - for (unsigned int i = 0; i < src.size(); ++i) { - if (duplicates[i] != i && duplicates[i] != UINT_MAX) { - continue; - } - - duplicates[i] = i; - for (unsigned int a = i + 1; a < src.size(); ++a) { - if (src[i].scene == src[a].scene) { - duplicates[a] = i; - } - } - } - - // Generate unique names for all named stuff? - if (flags & AI_INT_MERGE_SCENE_GEN_UNIQUE_NAMES) { -#if 0 - // Construct a proper random number generator - boost::mt19937 rng( ); - boost::uniform_int<> dist(1u,1 << 24u); - boost::variate_generator<boost::mt19937&, boost::uniform_int<> > rndGen(rng, dist); -#endif - for (unsigned int i = 1; i < src.size(); ++i) { - //if (i != duplicates[i]) - //{ - // // duplicate scenes share the same UID - // ::strcpy( src[i].id, src[duplicates[i]].id ); - // src[i].idlen = src[duplicates[i]].idlen; - - // continue; - //} - - src[i].idlen = ai_snprintf(src[i].id, 32, "$%.6X$_", i); - - if (flags & AI_INT_MERGE_SCENE_GEN_UNIQUE_NAMES_IF_NECESSARY) { - - // Compute hashes for all identifiers in this scene and store them - // in a sorted table (for convenience I'm using std::set). We hash - // just the node and animation channel names, all identifiers except - // the material names should be caught by doing this. - AddNodeHashes(src[i]->mRootNode, src[i].hashes); - - for (unsigned int a = 0; a < src[i]->mNumAnimations; ++a) { - aiAnimation *anim = src[i]->mAnimations[a]; - src[i].hashes.insert(SuperFastHash(anim->mName.data, static_cast<uint32_t>(anim->mName.length))); - } - } - } - } - - unsigned int cnt; - - // First find out how large the respective output arrays must be - for (unsigned int n = 0; n < src.size(); ++n) { - SceneHelper *cur = &src[n]; - - if (n == duplicates[n] || flags & AI_INT_MERGE_SCENE_DUPLICATES_DEEP_CPY) { - dest->mNumTextures += (*cur)->mNumTextures; - dest->mNumMaterials += (*cur)->mNumMaterials; - dest->mNumMeshes += (*cur)->mNumMeshes; - } - - dest->mNumLights += (*cur)->mNumLights; - dest->mNumCameras += (*cur)->mNumCameras; - dest->mNumAnimations += (*cur)->mNumAnimations; - - // Combine the flags of all scenes - // We need to process them flag-by-flag here to get correct results - // dest->mFlags ; //|= (*cur)->mFlags; - if ((*cur)->mFlags & AI_SCENE_FLAGS_NON_VERBOSE_FORMAT) { - dest->mFlags |= AI_SCENE_FLAGS_NON_VERBOSE_FORMAT; - } - } - - // generate the output texture list + an offset table for all texture indices - if (dest->mNumTextures) { - aiTexture **pip = dest->mTextures = new aiTexture *[dest->mNumTextures]; - cnt = 0; - for (unsigned int n = 0; n < src.size(); ++n) { - SceneHelper *cur = &src[n]; - for (unsigned int i = 0; i < (*cur)->mNumTextures; ++i) { - if (n != duplicates[n]) { - if (flags & AI_INT_MERGE_SCENE_DUPLICATES_DEEP_CPY) - Copy(pip, (*cur)->mTextures[i]); - - else - continue; - } else - *pip = (*cur)->mTextures[i]; - ++pip; - } - - offset[n] = cnt; - cnt = (unsigned int)(pip - dest->mTextures); - } - } - - // generate the output material list + an offset table for all material indices - if (dest->mNumMaterials) { - aiMaterial **pip = dest->mMaterials = new aiMaterial *[dest->mNumMaterials]; - cnt = 0; - for (unsigned int n = 0; n < src.size(); ++n) { - SceneHelper *cur = &src[n]; - for (unsigned int i = 0; i < (*cur)->mNumMaterials; ++i) { - if (n != duplicates[n]) { - if (flags & AI_INT_MERGE_SCENE_DUPLICATES_DEEP_CPY) - Copy(pip, (*cur)->mMaterials[i]); - - else - continue; - } else - *pip = (*cur)->mMaterials[i]; - - if ((*cur)->mNumTextures != dest->mNumTextures) { - // We need to update all texture indices of the mesh. So we need to search for - // a material property called '$tex.file' - - for (unsigned int a = 0; a < (*pip)->mNumProperties; ++a) { - aiMaterialProperty *prop = (*pip)->mProperties[a]; - if (!strncmp(prop->mKey.data, "$tex.file", 9)) { - // Check whether this texture is an embedded texture. - // In this case the property looks like this: *<n>, - // where n is the index of the texture. - // Copy here because we overwrite the string data in-place and the buffer inside of aiString - // will be a lie if we just reinterpret from prop->mData. The size of mData is not guaranteed to be - // MAXLEN in size. - aiString s(*(aiString *)prop->mData); - if ('*' == s.data[0]) { - // Offset the index and write it back .. - const unsigned int idx = strtoul10(&s.data[1]) + offset[n]; - const unsigned int oldLen = s.length; - - s.length = 1 + ASSIMP_itoa10(&s.data[1], sizeof(s.data) - 1, idx); - - // The string changed in size so we need to reallocate the buffer for the property. - if (oldLen < s.length) { - prop->mDataLength += s.length - oldLen; - delete[] prop->mData; - prop->mData = new char[prop->mDataLength]; - } - - memcpy(prop->mData, static_cast<void*>(&s), prop->mDataLength); - } - } - - // Need to generate new, unique material names? - else if (!::strcmp(prop->mKey.data, "$mat.name") && flags & AI_INT_MERGE_SCENE_GEN_UNIQUE_MATNAMES) { - aiString *pcSrc = (aiString *)prop->mData; - PrefixString(*pcSrc, (*cur).id, (*cur).idlen); - } - } - } - ++pip; - } - - offset[n] = cnt; - cnt = (unsigned int)(pip - dest->mMaterials); - } - } - - // generate the output mesh list + again an offset table for all mesh indices - if (dest->mNumMeshes) { - aiMesh **pip = dest->mMeshes = new aiMesh *[dest->mNumMeshes]; - cnt = 0; - for (unsigned int n = 0; n < src.size(); ++n) { - SceneHelper *cur = &src[n]; - for (unsigned int i = 0; i < (*cur)->mNumMeshes; ++i) { - if (n != duplicates[n]) { - if (flags & AI_INT_MERGE_SCENE_DUPLICATES_DEEP_CPY) - Copy(pip, (*cur)->mMeshes[i]); - - else - continue; - } else - *pip = (*cur)->mMeshes[i]; - - // update the material index of the mesh - (*pip)->mMaterialIndex += offset[n]; - ++pip; - } - - // reuse the offset array - store now the mesh offset in it - offset[n] = cnt; - cnt = (unsigned int)(pip - dest->mMeshes); - } - } - - std::vector<NodeAttachmentInfo> nodes; - nodes.reserve(srcList.size()); - - // ---------------------------------------------------------------------------- - // Now generate the output node graph. We need to make those - // names in the graph that are referenced by anims or lights - // or cameras unique. So we add a prefix to them ... $<rand>_ - // We could also use a counter, but using a random value allows us to - // use just one prefix if we are joining multiple scene hierarchies recursively. - // Chances are quite good we don't collide, so we try that ... - // ---------------------------------------------------------------------------- - - // Allocate space for light sources, cameras and animations - aiLight **ppLights = dest->mLights = (dest->mNumLights ? new aiLight *[dest->mNumLights] : nullptr); - - aiCamera **ppCameras = dest->mCameras = (dest->mNumCameras ? new aiCamera *[dest->mNumCameras] : nullptr); - - aiAnimation **ppAnims = dest->mAnimations = (dest->mNumAnimations ? new aiAnimation *[dest->mNumAnimations] : nullptr); - - for (int n = static_cast<int>(src.size() - 1); n >= 0; --n) /* !!! important !!! */ - { - SceneHelper *cur = &src[n]; - aiNode *node; - - // To offset or not to offset, this is the question - if (n != (int)duplicates[n]) { - // Get full scene-graph copy - Copy(&node, (*cur)->mRootNode); - OffsetNodeMeshIndices(node, offset[duplicates[n]]); - - if (flags & AI_INT_MERGE_SCENE_DUPLICATES_DEEP_CPY) { - // (note:) they are already 'offseted' by offset[duplicates[n]] - OffsetNodeMeshIndices(node, offset[n] - offset[duplicates[n]]); - } - } else // if (n == duplicates[n]) - { - node = (*cur)->mRootNode; - OffsetNodeMeshIndices(node, offset[n]); - } - if (n) // src[0] is the master node - nodes.push_back(NodeAttachmentInfo(node, srcList[n - 1].attachToNode, n)); - - // add name prefixes? - if (flags & AI_INT_MERGE_SCENE_GEN_UNIQUE_NAMES) { - - // or the whole scenegraph - if (flags & AI_INT_MERGE_SCENE_GEN_UNIQUE_NAMES_IF_NECESSARY) { - AddNodePrefixesChecked(node, (*cur).id, (*cur).idlen, src, n); - } else - AddNodePrefixes(node, (*cur).id, (*cur).idlen); - - // meshes - for (unsigned int i = 0; i < (*cur)->mNumMeshes; ++i) { - aiMesh *mesh = (*cur)->mMeshes[i]; - - // rename all bones - for (unsigned int a = 0; a < mesh->mNumBones; ++a) { - if (flags & AI_INT_MERGE_SCENE_GEN_UNIQUE_NAMES_IF_NECESSARY) { - if (!FindNameMatch(mesh->mBones[a]->mName, src, n)) - continue; - } - PrefixString(mesh->mBones[a]->mName, (*cur).id, (*cur).idlen); - } - } - } - - // -------------------------------------------------------------------- - // Copy light sources - for (unsigned int i = 0; i < (*cur)->mNumLights; ++i, ++ppLights) { - if (n != (int)duplicates[n]) // duplicate scene? - { - Copy(ppLights, (*cur)->mLights[i]); - } else - *ppLights = (*cur)->mLights[i]; - - // Add name prefixes? - if (flags & AI_INT_MERGE_SCENE_GEN_UNIQUE_NAMES) { - if (flags & AI_INT_MERGE_SCENE_GEN_UNIQUE_NAMES_IF_NECESSARY) { - if (!FindNameMatch((*ppLights)->mName, src, n)) - continue; - } - - PrefixString((*ppLights)->mName, (*cur).id, (*cur).idlen); - } - } - - // -------------------------------------------------------------------- - // Copy cameras - for (unsigned int i = 0; i < (*cur)->mNumCameras; ++i, ++ppCameras) { - if (n != (int)duplicates[n]) // duplicate scene? - { - Copy(ppCameras, (*cur)->mCameras[i]); - } else - *ppCameras = (*cur)->mCameras[i]; - - // Add name prefixes? - if (flags & AI_INT_MERGE_SCENE_GEN_UNIQUE_NAMES) { - if (flags & AI_INT_MERGE_SCENE_GEN_UNIQUE_NAMES_IF_NECESSARY) { - if (!FindNameMatch((*ppCameras)->mName, src, n)) - continue; - } - - PrefixString((*ppCameras)->mName, (*cur).id, (*cur).idlen); - } - } - - // -------------------------------------------------------------------- - // Copy animations - for (unsigned int i = 0; i < (*cur)->mNumAnimations; ++i, ++ppAnims) { - if (n != (int)duplicates[n]) // duplicate scene? - { - Copy(ppAnims, (*cur)->mAnimations[i]); - } else - *ppAnims = (*cur)->mAnimations[i]; - - // Add name prefixes? - if (flags & AI_INT_MERGE_SCENE_GEN_UNIQUE_NAMES) { - if (flags & AI_INT_MERGE_SCENE_GEN_UNIQUE_NAMES_IF_NECESSARY) { - if (!FindNameMatch((*ppAnims)->mName, src, n)) - continue; - } - - PrefixString((*ppAnims)->mName, (*cur).id, (*cur).idlen); - - // don't forget to update all node animation channels - for (unsigned int a = 0; a < (*ppAnims)->mNumChannels; ++a) { - if (flags & AI_INT_MERGE_SCENE_GEN_UNIQUE_NAMES_IF_NECESSARY) { - if (!FindNameMatch((*ppAnims)->mChannels[a]->mNodeName, src, n)) - continue; - } - - PrefixString((*ppAnims)->mChannels[a]->mNodeName, (*cur).id, (*cur).idlen); - } - } - } - } - - // Now build the output graph - AttachToGraph(master, nodes); - dest->mRootNode = master->mRootNode; - - // Check whether we succeeded at building the output graph - for (std::vector<NodeAttachmentInfo>::iterator it = nodes.begin(); - it != nodes.end(); ++it) { - if (!(*it).resolved) { - if (flags & AI_INT_MERGE_SCENE_RESOLVE_CROSS_ATTACHMENTS) { - // search for this attachment point in all other imported scenes, too. - for (unsigned int n = 0; n < src.size(); ++n) { - if (n != (*it).src_idx) { - AttachToGraph(src[n].scene, nodes); - if ((*it).resolved) - break; - } - } - } - if (!(*it).resolved) { - ASSIMP_LOG_ERROR("SceneCombiner: Failed to resolve attachment ", (*it).node->mName.data, - " ", (*it).attachToNode->mName.data); - } - } - } - - // now delete all input scenes. Make sure duplicate scenes aren't - // deleted more than one time - for (unsigned int n = 0; n < src.size(); ++n) { - if (n != duplicates[n]) // duplicate scene? - continue; - - aiScene *deleteMe = src[n].scene; - - // We need to delete the arrays before the destructor is called - - // we are reusing the array members - delete[] deleteMe->mMeshes; - deleteMe->mMeshes = nullptr; - delete[] deleteMe->mCameras; - deleteMe->mCameras = nullptr; - delete[] deleteMe->mLights; - deleteMe->mLights = nullptr; - delete[] deleteMe->mMaterials; - deleteMe->mMaterials = nullptr; - delete[] deleteMe->mAnimations; - deleteMe->mAnimations = nullptr; - delete[] deleteMe->mTextures; - deleteMe->mTextures = nullptr; - - deleteMe->mRootNode = nullptr; - - // Now we can safely delete the scene - delete deleteMe; - } - - // Check flags - if (!dest->mNumMeshes || !dest->mNumMaterials) { - dest->mFlags |= AI_SCENE_FLAGS_INCOMPLETE; - } - - // We're finished -} - -// ------------------------------------------------------------------------------------------------ -// Build a list of unique bones -void SceneCombiner::BuildUniqueBoneList(std::list<BoneWithHash> &asBones, - std::vector<aiMesh *>::const_iterator it, - std::vector<aiMesh *>::const_iterator end) { - unsigned int iOffset = 0; - for (; it != end; ++it) { - for (unsigned int l = 0; l < (*it)->mNumBones; ++l) { - aiBone *p = (*it)->mBones[l]; - uint32_t itml = SuperFastHash(p->mName.data, (unsigned int)p->mName.length); - - std::list<BoneWithHash>::iterator it2 = asBones.begin(); - std::list<BoneWithHash>::iterator end2 = asBones.end(); - - for (; it2 != end2; ++it2) { - if ((*it2).first == itml) { - (*it2).pSrcBones.push_back(BoneSrcIndex(p, iOffset)); - break; - } - } - if (end2 == it2) { - // need to begin a new bone entry - asBones.push_back(BoneWithHash()); - BoneWithHash &btz = asBones.back(); - - // setup members - btz.first = itml; - btz.second = &p->mName; - btz.pSrcBones.push_back(BoneSrcIndex(p, iOffset)); - } - } - iOffset += (*it)->mNumVertices; - } -} - -// ------------------------------------------------------------------------------------------------ -// Merge a list of bones -void SceneCombiner::MergeBones(aiMesh *out, std::vector<aiMesh *>::const_iterator it, - std::vector<aiMesh *>::const_iterator end) { - if (nullptr == out || out->mNumBones == 0) { - return; - } - - // find we need to build an unique list of all bones. - // we work with hashes to make the comparisons MUCH faster, - // at least if we have many bones. - std::list<BoneWithHash> asBones; - BuildUniqueBoneList(asBones, it, end); - - // now create the output bones - out->mNumBones = 0; - out->mBones = new aiBone *[asBones.size()]; - - for (std::list<BoneWithHash>::const_iterator boneIt = asBones.begin(), boneEnd = asBones.end(); boneIt != boneEnd; ++boneIt) { - // Allocate a bone and setup it's name - aiBone *pc = out->mBones[out->mNumBones++] = new aiBone(); - pc->mName = aiString(*(boneIt->second)); - - std::vector<BoneSrcIndex>::const_iterator wend = boneIt->pSrcBones.end(); - - // Loop through all bones to be joined for this bone - for (std::vector<BoneSrcIndex>::const_iterator wmit = boneIt->pSrcBones.begin(); wmit != wend; ++wmit) { - pc->mNumWeights += (*wmit).first->mNumWeights; - - // NOTE: different offset matrices for bones with equal names - // are - at the moment - not handled correctly. - if (wmit != boneIt->pSrcBones.begin() && pc->mOffsetMatrix != wmit->first->mOffsetMatrix) { - ASSIMP_LOG_WARN("Bones with equal names but different offset matrices can't be joined at the moment"); - continue; - } - pc->mOffsetMatrix = wmit->first->mOffsetMatrix; - } - - // Allocate the vertex weight array - aiVertexWeight *avw = pc->mWeights = new aiVertexWeight[pc->mNumWeights]; - - // And copy the final weights - adjust the vertex IDs by the - // face index offset of the corresponding mesh. - for (std::vector<BoneSrcIndex>::const_iterator wmit = (*boneIt).pSrcBones.begin(); wmit != (*boneIt).pSrcBones.end(); ++wmit) { - if (wmit == wend) { - break; - } - - aiBone *pip = (*wmit).first; - for (unsigned int mp = 0; mp < pip->mNumWeights; ++mp, ++avw) { - const aiVertexWeight &vfi = pip->mWeights[mp]; - avw->mWeight = vfi.mWeight; - avw->mVertexId = vfi.mVertexId + (*wmit).second; - } - } - } -} - -// ------------------------------------------------------------------------------------------------ -// Merge a list of meshes -void SceneCombiner::MergeMeshes(aiMesh **_out, unsigned int /*flags*/, - std::vector<aiMesh *>::const_iterator begin, - std::vector<aiMesh *>::const_iterator end) { - if (nullptr == _out) { - return; - } - - if (begin == end) { - *_out = nullptr; // no meshes ... - return; - } - - // Allocate the output mesh - aiMesh *out = *_out = new aiMesh(); - out->mMaterialIndex = (*begin)->mMaterialIndex; - - std::string name; - // Find out how much output storage we'll need - for (std::vector<aiMesh *>::const_iterator it = begin; it != end; ++it) { - const char *meshName((*it)->mName.C_Str()); - name += std::string(meshName); - if (it != end - 1) { - name += "."; - } - out->mNumVertices += (*it)->mNumVertices; - out->mNumFaces += (*it)->mNumFaces; - out->mNumBones += (*it)->mNumBones; - - // combine primitive type flags - out->mPrimitiveTypes |= (*it)->mPrimitiveTypes; - } - out->mName.Set(name.c_str()); - - if (out->mNumVertices) { - aiVector3D *pv2; - - // copy vertex positions - if ((**begin).HasPositions()) { - - pv2 = out->mVertices = new aiVector3D[out->mNumVertices]; - for (std::vector<aiMesh *>::const_iterator it = begin; it != end; ++it) { - if ((*it)->mVertices) { - ::memcpy(pv2, (*it)->mVertices, (*it)->mNumVertices * sizeof(aiVector3D)); - } else - ASSIMP_LOG_WARN("JoinMeshes: Positions expected but input mesh contains no positions"); - pv2 += (*it)->mNumVertices; - } - } - // copy normals - if ((**begin).HasNormals()) { - - pv2 = out->mNormals = new aiVector3D[out->mNumVertices]; - for (std::vector<aiMesh *>::const_iterator it = begin; it != end; ++it) { - if ((*it)->mNormals) { - ::memcpy(pv2, (*it)->mNormals, (*it)->mNumVertices * sizeof(aiVector3D)); - } else { - ASSIMP_LOG_WARN("JoinMeshes: Normals expected but input mesh contains no normals"); - } - pv2 += (*it)->mNumVertices; - } - } - // copy tangents and bi-tangents - if ((**begin).HasTangentsAndBitangents()) { - - pv2 = out->mTangents = new aiVector3D[out->mNumVertices]; - aiVector3D *pv2b = out->mBitangents = new aiVector3D[out->mNumVertices]; - - for (std::vector<aiMesh *>::const_iterator it = begin; it != end; ++it) { - if ((*it)->mTangents) { - ::memcpy(pv2, (*it)->mTangents, (*it)->mNumVertices * sizeof(aiVector3D)); - ::memcpy(pv2b, (*it)->mBitangents, (*it)->mNumVertices * sizeof(aiVector3D)); - } else { - ASSIMP_LOG_WARN("JoinMeshes: Tangents expected but input mesh contains no tangents"); - } - pv2 += (*it)->mNumVertices; - pv2b += (*it)->mNumVertices; - } - } - // copy texture coordinates - unsigned int n = 0; - while ((**begin).HasTextureCoords(n)) { - out->mNumUVComponents[n] = (*begin)->mNumUVComponents[n]; - - pv2 = out->mTextureCoords[n] = new aiVector3D[out->mNumVertices]; - for (std::vector<aiMesh *>::const_iterator it = begin; it != end; ++it) { - if ((*it)->mTextureCoords[n]) { - ::memcpy(pv2, (*it)->mTextureCoords[n], (*it)->mNumVertices * sizeof(aiVector3D)); - } else { - ASSIMP_LOG_WARN("JoinMeshes: UVs expected but input mesh contains no UVs"); - } - pv2 += (*it)->mNumVertices; - } - ++n; - } - // copy vertex colors - n = 0; - while ((**begin).HasVertexColors(n)) { - aiColor4D *pVec2 = out->mColors[n] = new aiColor4D[out->mNumVertices]; - for (std::vector<aiMesh *>::const_iterator it = begin; it != end; ++it) { - if ((*it)->mColors[n]) { - ::memcpy(pVec2, (*it)->mColors[n], (*it)->mNumVertices * sizeof(aiColor4D)); - } else { - ASSIMP_LOG_WARN("JoinMeshes: VCs expected but input mesh contains no VCs"); - } - pVec2 += (*it)->mNumVertices; - } - ++n; - } - } - - if (out->mNumFaces) // just for safety - { - // copy faces - out->mFaces = new aiFace[out->mNumFaces]; - aiFace *pf2 = out->mFaces; - - unsigned int ofs = 0; - for (std::vector<aiMesh *>::const_iterator it = begin; it != end; ++it) { - for (unsigned int m = 0; m < (*it)->mNumFaces; ++m, ++pf2) { - aiFace &face = (*it)->mFaces[m]; - pf2->mNumIndices = face.mNumIndices; - pf2->mIndices = face.mIndices; - - if (ofs) { - // add the offset to the vertex - for (unsigned int q = 0; q < face.mNumIndices; ++q) { - face.mIndices[q] += ofs; - } - } - face.mIndices = nullptr; - } - ofs += (*it)->mNumVertices; - } - } - - // bones - as this is quite lengthy, I moved the code to a separate function - if (out->mNumBones) - MergeBones(out, begin, end); - - // delete all source meshes - for (std::vector<aiMesh *>::const_iterator it = begin; it != end; ++it) - delete *it; -} - -// ------------------------------------------------------------------------------------------------ -void SceneCombiner::MergeMaterials(aiMaterial **dest, - std::vector<aiMaterial *>::const_iterator begin, - std::vector<aiMaterial *>::const_iterator end) { - if (nullptr == dest) { - return; - } - - if (begin == end) { - *dest = nullptr; // no materials ... - return; - } - - // Allocate the output material - aiMaterial *out = *dest = new aiMaterial(); - - // Get the maximal number of properties - unsigned int size = 0; - for (std::vector<aiMaterial *>::const_iterator it = begin; it != end; ++it) { - size += (*it)->mNumProperties; - } - - out->Clear(); - delete[] out->mProperties; - - out->mNumAllocated = size; - out->mNumProperties = 0; - out->mProperties = new aiMaterialProperty *[out->mNumAllocated]; - - for (std::vector<aiMaterial *>::const_iterator it = begin; it != end; ++it) { - for (unsigned int i = 0; i < (*it)->mNumProperties; ++i) { - aiMaterialProperty *sprop = (*it)->mProperties[i]; - - // Test if we already have a matching property - const aiMaterialProperty *prop_exist; - if (aiGetMaterialProperty(out, sprop->mKey.C_Str(), sprop->mSemantic, sprop->mIndex, &prop_exist) != AI_SUCCESS) { - // If not, we add it to the new material - aiMaterialProperty *prop = out->mProperties[out->mNumProperties] = new aiMaterialProperty(); - - prop->mDataLength = sprop->mDataLength; - prop->mData = new char[prop->mDataLength]; - ::memcpy(prop->mData, sprop->mData, prop->mDataLength); - - prop->mIndex = sprop->mIndex; - prop->mSemantic = sprop->mSemantic; - prop->mKey = sprop->mKey; - prop->mType = sprop->mType; - - out->mNumProperties++; - } - } - } -} - -// ------------------------------------------------------------------------------------------------ -template <typename Type> -inline void CopyPtrArray(Type **&dest, const Type *const *src, ai_uint num) { - if (!num) { - dest = nullptr; - return; - } - dest = new Type *[num]; - for (ai_uint i = 0; i < num; ++i) { - SceneCombiner::Copy(&dest[i], src[i]); - } -} - -// ------------------------------------------------------------------------------------------------ -template <typename Type> -inline void GetArrayCopy(Type *&dest, ai_uint num) { - if (!dest) { - return; - } - Type *old = dest; - - dest = new Type[num]; - ::memcpy(dest, old, sizeof(Type) * num); -} - -// ------------------------------------------------------------------------------------------------ -void SceneCombiner::CopySceneFlat(aiScene **_dest, const aiScene *src) { - if (nullptr == _dest || nullptr == src) { - return; - } - - // reuse the old scene or allocate a new? - if (*_dest) { - (*_dest)->~aiScene(); - new (*_dest) aiScene(); - } else { - *_dest = new aiScene(); - } - CopyScene(_dest, src, false); -} - -// ------------------------------------------------------------------------------------------------ -void SceneCombiner::CopyScene(aiScene **_dest, const aiScene *src, bool allocate) { - if (nullptr == _dest || nullptr == src) { - return; - } - - if (allocate) { - *_dest = new aiScene(); - } - aiScene *dest = *_dest; - ai_assert(nullptr != dest); - - // copy metadata - if (nullptr != src->mMetaData) { - dest->mMetaData = new aiMetadata(*src->mMetaData); - } - - // copy animations - dest->mNumAnimations = src->mNumAnimations; - CopyPtrArray(dest->mAnimations, src->mAnimations, - dest->mNumAnimations); - - // copy textures - dest->mNumTextures = src->mNumTextures; - CopyPtrArray(dest->mTextures, src->mTextures, - dest->mNumTextures); - - // copy materials - dest->mNumMaterials = src->mNumMaterials; - CopyPtrArray(dest->mMaterials, src->mMaterials, - dest->mNumMaterials); - - // copy lights - dest->mNumLights = src->mNumLights; - CopyPtrArray(dest->mLights, src->mLights, - dest->mNumLights); - - // copy cameras - dest->mNumCameras = src->mNumCameras; - CopyPtrArray(dest->mCameras, src->mCameras, - dest->mNumCameras); - - // copy meshes - dest->mNumMeshes = src->mNumMeshes; - CopyPtrArray(dest->mMeshes, src->mMeshes, - dest->mNumMeshes); - - // now - copy the root node of the scene (deep copy, too) - Copy(&dest->mRootNode, src->mRootNode); - - // and keep the flags ... - dest->mFlags = src->mFlags; - - // source private data might be nullptr if the scene is user-allocated (i.e. for use with the export API) - if (dest->mPrivate != nullptr) { - ScenePriv(dest)->mPPStepsApplied = ScenePriv(src) ? ScenePriv(src)->mPPStepsApplied : 0; - } -} - -// ------------------------------------------------------------------------------------------------ -void SceneCombiner::Copy(aiMesh **_dest, const aiMesh *src) { - if (nullptr == _dest || nullptr == src) { - return; - } - - aiMesh *dest = *_dest = new aiMesh(); - - // get a flat copy - *dest = *src; - - // and reallocate all arrays - GetArrayCopy(dest->mVertices, dest->mNumVertices); - GetArrayCopy(dest->mNormals, dest->mNumVertices); - GetArrayCopy(dest->mTangents, dest->mNumVertices); - GetArrayCopy(dest->mBitangents, dest->mNumVertices); - - unsigned int n = 0; - while (dest->HasTextureCoords(n)) { - GetArrayCopy(dest->mTextureCoords[n++], dest->mNumVertices); - } - - n = 0; - while (dest->HasVertexColors(n)) { - GetArrayCopy(dest->mColors[n++], dest->mNumVertices); - } - - // make a deep copy of all bones - CopyPtrArray(dest->mBones, dest->mBones, dest->mNumBones); - - // make a deep copy of all faces - GetArrayCopy(dest->mFaces, dest->mNumFaces); - for (unsigned int i = 0; i < dest->mNumFaces; ++i) { - aiFace &f = dest->mFaces[i]; - GetArrayCopy(f.mIndices, f.mNumIndices); - } - - // make a deep copy of all blend shapes - CopyPtrArray(dest->mAnimMeshes, dest->mAnimMeshes, dest->mNumAnimMeshes); - - // make a deep copy of all texture coordinate names - if (src->mTextureCoordsNames != nullptr) { - dest->mTextureCoordsNames = new aiString *[AI_MAX_NUMBER_OF_TEXTURECOORDS] {}; - for (unsigned int i = 0; i < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++i) { - Copy(&dest->mTextureCoordsNames[i], src->mTextureCoordsNames[i]); - } - } -} - -// ------------------------------------------------------------------------------------------------ -void SceneCombiner::Copy(aiAnimMesh **_dest, const aiAnimMesh *src) { - if (nullptr == _dest || nullptr == src) { - return; - } - - aiAnimMesh *dest = *_dest = new aiAnimMesh(); - - // get a flat copy - *dest = *src; - - // and reallocate all arrays - GetArrayCopy(dest->mVertices, dest->mNumVertices); - GetArrayCopy(dest->mNormals, dest->mNumVertices); - GetArrayCopy(dest->mTangents, dest->mNumVertices); - GetArrayCopy(dest->mBitangents, dest->mNumVertices); - - unsigned int n = 0; - while (dest->HasTextureCoords(n)) - GetArrayCopy(dest->mTextureCoords[n++], dest->mNumVertices); - - n = 0; - while (dest->HasVertexColors(n)) - GetArrayCopy(dest->mColors[n++], dest->mNumVertices); -} - -// ------------------------------------------------------------------------------------------------ -void SceneCombiner::Copy(aiMaterial **_dest, const aiMaterial *src) { - if (nullptr == _dest || nullptr == src) { - return; - } - - aiMaterial *dest = (aiMaterial *)(*_dest = new aiMaterial()); - - dest->Clear(); - delete[] dest->mProperties; - - dest->mNumAllocated = src->mNumAllocated; - dest->mNumProperties = src->mNumProperties; - dest->mProperties = new aiMaterialProperty *[dest->mNumAllocated]; - - for (unsigned int i = 0; i < dest->mNumProperties; ++i) { - aiMaterialProperty *prop = dest->mProperties[i] = new aiMaterialProperty(); - aiMaterialProperty *sprop = src->mProperties[i]; - - prop->mDataLength = sprop->mDataLength; - prop->mData = new char[prop->mDataLength]; - ::memcpy(prop->mData, sprop->mData, prop->mDataLength); - - prop->mIndex = sprop->mIndex; - prop->mSemantic = sprop->mSemantic; - prop->mKey = sprop->mKey; - prop->mType = sprop->mType; - } -} - -// ------------------------------------------------------------------------------------------------ -void SceneCombiner::Copy(aiTexture **_dest, const aiTexture *src) { - if (nullptr == _dest || nullptr == src) { - return; - } - - aiTexture *dest = *_dest = new aiTexture(); - - // get a flat copy - *dest = *src; - - // and reallocate all arrays. We must do it manually here - const char *old = (const char *)dest->pcData; - if (old) { - unsigned int cpy; - if (!dest->mHeight) - cpy = dest->mWidth; - else - cpy = dest->mHeight * dest->mWidth * sizeof(aiTexel); - - if (!cpy) { - dest->pcData = nullptr; - return; - } - // the cast is legal, the aiTexel c'tor does nothing important - dest->pcData = (aiTexel *)new char[cpy]; - ::memcpy(dest->pcData, old, cpy); - } -} - -// ------------------------------------------------------------------------------------------------ -void SceneCombiner::Copy(aiAnimation **_dest, const aiAnimation *src) { - if (nullptr == _dest || nullptr == src) { - return; - } - - aiAnimation *dest = *_dest = new aiAnimation(); - - // get a flat copy - *dest = *src; - - // and reallocate all arrays - CopyPtrArray(dest->mChannels, src->mChannels, dest->mNumChannels); - CopyPtrArray(dest->mMorphMeshChannels, src->mMorphMeshChannels, dest->mNumMorphMeshChannels); -} - -// ------------------------------------------------------------------------------------------------ -void SceneCombiner::Copy(aiNodeAnim **_dest, const aiNodeAnim *src) { - if (nullptr == _dest || nullptr == src) { - return; - } - - aiNodeAnim *dest = *_dest = new aiNodeAnim(); - - // get a flat copy - *dest = *src; - - // and reallocate all arrays - GetArrayCopy(dest->mPositionKeys, dest->mNumPositionKeys); - GetArrayCopy(dest->mScalingKeys, dest->mNumScalingKeys); - GetArrayCopy(dest->mRotationKeys, dest->mNumRotationKeys); -} - -void SceneCombiner::Copy(aiMeshMorphAnim **_dest, const aiMeshMorphAnim *src) { - if (nullptr == _dest || nullptr == src) { - return; - } - - aiMeshMorphAnim *dest = *_dest = new aiMeshMorphAnim(); - - // get a flat copy - *dest = *src; - - // and reallocate all arrays - GetArrayCopy(dest->mKeys, dest->mNumKeys); - for (ai_uint i = 0; i < dest->mNumKeys; ++i) { - dest->mKeys[i].mValues = new unsigned int[dest->mKeys[i].mNumValuesAndWeights]; - dest->mKeys[i].mWeights = new double[dest->mKeys[i].mNumValuesAndWeights]; - ::memcpy(dest->mKeys[i].mValues, src->mKeys[i].mValues, dest->mKeys[i].mNumValuesAndWeights * sizeof(unsigned int)); - ::memcpy(dest->mKeys[i].mWeights, src->mKeys[i].mWeights, dest->mKeys[i].mNumValuesAndWeights * sizeof(double)); - } -} - -// ------------------------------------------------------------------------------------------------ -void SceneCombiner::Copy(aiCamera **_dest, const aiCamera *src) { - if (nullptr == _dest || nullptr == src) { - return; - } - - aiCamera *dest = *_dest = new aiCamera(); - - // get a flat copy, that's already OK - *dest = *src; -} - -// ------------------------------------------------------------------------------------------------ -void SceneCombiner::Copy(aiLight **_dest, const aiLight *src) { - if (nullptr == _dest || nullptr == src) { - return; - } - - aiLight *dest = *_dest = new aiLight(); - - // get a flat copy, that's already OK - *dest = *src; -} - -// ------------------------------------------------------------------------------------------------ -void SceneCombiner::Copy(aiBone **_dest, const aiBone *src) { - if (nullptr == _dest || nullptr == src) { - return; - } - - aiBone *dest = *_dest = new aiBone(); - - // get a flat copy - *dest = *src; -} - -// ------------------------------------------------------------------------------------------------ -void SceneCombiner::Copy(aiNode **_dest, const aiNode *src) { - ai_assert(nullptr != _dest); - ai_assert(nullptr != src); - - aiNode *dest = *_dest = new aiNode(); - - // get a flat copy - *dest = *src; - - if (src->mMetaData) { - Copy(&dest->mMetaData, src->mMetaData); - } - - // and reallocate all arrays - GetArrayCopy(dest->mMeshes, dest->mNumMeshes); - CopyPtrArray(dest->mChildren, src->mChildren, dest->mNumChildren); - - // need to set the mParent fields to the created aiNode. - for (unsigned int i = 0; i < dest->mNumChildren; i++) { - dest->mChildren[i]->mParent = dest; - } -} - -// ------------------------------------------------------------------------------------------------ -void SceneCombiner::Copy(aiMetadata **_dest, const aiMetadata *src) { - if (nullptr == _dest || nullptr == src) { - return; - } - - if (0 == src->mNumProperties) { - return; - } - - aiMetadata *dest = *_dest = aiMetadata::Alloc(src->mNumProperties); - std::copy(src->mKeys, src->mKeys + src->mNumProperties, dest->mKeys); - - for (unsigned int i = 0; i < src->mNumProperties; ++i) { - aiMetadataEntry &in = src->mValues[i]; - aiMetadataEntry &out = dest->mValues[i]; - out.mType = in.mType; - switch (dest->mValues[i].mType) { - case AI_BOOL: - out.mData = new bool(*static_cast<bool *>(in.mData)); - break; - case AI_INT32: - out.mData = new int32_t(*static_cast<int32_t *>(in.mData)); - break; - case AI_UINT64: - out.mData = new uint64_t(*static_cast<uint64_t *>(in.mData)); - break; - case AI_FLOAT: - out.mData = new float(*static_cast<float *>(in.mData)); - break; - case AI_DOUBLE: - out.mData = new double(*static_cast<double *>(in.mData)); - break; - case AI_AISTRING: - out.mData = new aiString(*static_cast<aiString *>(in.mData)); - break; - case AI_AIVECTOR3D: - out.mData = new aiVector3D(*static_cast<aiVector3D *>(in.mData)); - break; - default: - ai_assert(false); - break; - } - } -} - -// ------------------------------------------------------------------------------------------------ -void SceneCombiner::Copy(aiString **_dest, const aiString *src) { - if (nullptr == _dest || nullptr == src) { - return; - } - - aiString *dest = *_dest = new aiString(); - - // get a flat copy - *dest = *src; -} - -#if (__GNUC__ >= 8 && __GNUC_MINOR__ >= 0) -#pragma GCC diagnostic pop -#endif - -} // Namespace Assimp |