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, 1375 insertions, 0 deletions
diff --git a/src/mesh/assimp-master/code/Common/SceneCombiner.cpp b/src/mesh/assimp-master/code/Common/SceneCombiner.cpp new file mode 100644 index 0000000..2c2539e --- /dev/null +++ b/src/mesh/assimp-master/code/Common/SceneCombiner.cpp @@ -0,0 +1,1375 @@ +/* +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 |