From 9621dff027734678eb7e4d4712d46b08b564222e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antti=20M=C3=A4=C3=A4tt=C3=A4?= Date: Thu, 12 Jan 2017 13:41:32 +0200 Subject: [PATCH] Morph animation support for collada --- code/CMakeLists.txt | 2 + code/ColladaHelper.h | 31 ++++ code/ColladaLoader.cpp | 308 +++++++++++++++++++++++++++++++++++----- code/ColladaLoader.h | 5 + code/ColladaParser.cpp | 48 ++++++- code/CreateAnimMesh.cpp | 92 ++++++++++++ code/CreateAnimMesh.h | 56 ++++++++ include/assimp/anim.h | 81 ++++++++++- include/assimp/mesh.h | 36 ++++- 9 files changed, 615 insertions(+), 44 deletions(-) create mode 100644 code/CreateAnimMesh.cpp create mode 100644 code/CreateAnimMesh.h diff --git a/code/CMakeLists.txt b/code/CMakeLists.txt index 3eafbdc62..c3ae86aa4 100644 --- a/code/CMakeLists.txt +++ b/code/CMakeLists.txt @@ -173,6 +173,8 @@ SET( Common_SRCS XMLTools.h Version.cpp IOStreamBuffer.h + CreateAnimMesh.h + CreateAnimMesh.cpp ) SOURCE_GROUP(Common FILES ${Common_SRCS}) diff --git a/code/ColladaHelper.h b/code/ColladaHelper.h index a3cba264b..0fe3259ee 100644 --- a/code/ColladaHelper.h +++ b/code/ColladaHelper.h @@ -89,6 +89,21 @@ enum InputType IT_Bitangent }; +/** Supported controller types */ +enum ControllerType +{ + Skin, + Morph +}; + +/** Supported morph methods */ +enum MorphMethod +{ + Normalized, + Relative +}; + + /** Contains all data for one of the different transformation types */ struct Transform { @@ -380,6 +395,12 @@ enum PrimitiveType /** A skeleton controller to deform a mesh with the use of joints */ struct Controller { + // controller type + ControllerType mType; + + // Morphing method if type is Morph + MorphMethod mMethod; + // the URL of the mesh deformed by the controller. std::string mMeshId; @@ -402,6 +423,9 @@ struct Controller // JointIndex-WeightIndex pairs for all vertices std::vector< std::pair > mWeights; + + std::string mMorphTarget; + std::string mMorphWeight; }; /** A collada material. Pretty much the only member is a reference to an effect. */ @@ -577,6 +601,12 @@ struct AnimationChannel std::string mSourceTimes; /** Source URL of the value values. Collada calls them "output". */ std::string mSourceValues; + /** Source URL of the IN_TANGENT semantic values. */ + std::string mInTanValues; + /** Source URL of the OUT_TANGENT semantic values. */ + std::string mOutTanValues; + /** Source URL of the INTERPOLATION semantic values. */ + std::string mInterpolationValues; }; /** An animation. Container for 0-x animation channels or 0-x animations */ @@ -645,6 +675,7 @@ struct Animation struct ChannelEntry { const Collada::AnimationChannel* mChannel; ///> the source channel + std::string mTargetId; std::string mTransformId; // the ID of the transformation step of the node which is influenced size_t mTransformIndex; // Index into the node's transform chain to apply the channel to size_t mSubElement; // starting index inside the transform data diff --git a/code/ColladaLoader.cpp b/code/ColladaLoader.cpp index 5de48120c..7c6aa74bb 100644 --- a/code/ColladaLoader.cpp +++ b/code/ColladaLoader.cpp @@ -55,6 +55,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "ParsingUtils.h" #include "SkeletonMeshBuilder.h" #include "Defines.h" +#include "CreateAnimMesh.h" #include "time.h" #include "math.h" @@ -150,6 +151,7 @@ void ColladaLoader::InternReadFile( const std::string& pFile, aiScene* pScene, I mMeshIndexByID.clear(); mMaterialIndexByName.clear(); mMeshes.clear(); + mTargetMeshes.clear(); newMats.clear(); mLights.clear(); mCameras.clear(); @@ -571,6 +573,21 @@ void ColladaLoader::BuildMeshesForNode( const ColladaParser& pParser, const Coll } } +// ------------------------------------------------------------------------------------------------ +// Find mesh from either meshes or morph target meshes +aiMesh *ColladaLoader::findMesh(std::string meshid) +{ + for (unsigned int i = 0; i < mMeshes.size(); i++) + if (std::string(mMeshes[i]->mName.data) == meshid) + return mMeshes[i]; + + for (unsigned int i = 0; i < mTargetMeshes.size(); i++) + if (std::string(mTargetMeshes[i]->mName.data) == meshid) + return mTargetMeshes[i]; + + return NULL; +} + // ------------------------------------------------------------------------------------------------ // Creates a mesh for the given ColladaMesh face subset and returns the newly created mesh aiMesh* ColladaLoader::CreateMesh( const ColladaParser& pParser, const Collada::Mesh* pSrcMesh, const Collada::SubMesh& pSubMesh, @@ -656,8 +673,70 @@ aiMesh* ColladaLoader::CreateMesh( const ColladaParser& pParser, const Collada:: face.mIndices[b] = static_cast(vertex++); } + // create morph target meshes if any + std::vector targetMeshes; + std::vector targetWeights; + Collada::MorphMethod method; + + for(std::map::const_iterator it = pParser.mControllerLibrary.begin(); + it != pParser.mControllerLibrary.end(); it++) + { + const Collada::Controller &c = it->second; + const Collada::Mesh* baseMesh = pParser.ResolveLibraryReference( pParser.mMeshLibrary, c.mMeshId); + + if (c.mType == Collada::Morph && baseMesh->mName == pSrcMesh->mName) + { + const Collada::Accessor& targetAccessor = pParser.ResolveLibraryReference( pParser.mAccessorLibrary, c.mMorphTarget); + const Collada::Accessor& weightAccessor = pParser.ResolveLibraryReference( pParser.mAccessorLibrary, c.mMorphWeight); + const Collada::Data& targetData = pParser.ResolveLibraryReference( pParser.mDataLibrary, targetAccessor.mSource); + const Collada::Data& weightData = pParser.ResolveLibraryReference( pParser.mDataLibrary, weightAccessor.mSource); + + // take method + method = c.mMethod; + + if (!targetData.mIsStringArray) + throw DeadlyImportError( "target data must contain id. "); + if (weightData.mIsStringArray) + throw DeadlyImportError( "target weight data must not be textual "); + + for (unsigned int i = 0; i < targetData.mStrings.size(); ++i) + { + const Collada::Mesh* targetMesh = pParser.ResolveLibraryReference(pParser.mMeshLibrary, targetData.mStrings.at(i)); + + aiMesh *aimesh = findMesh(targetMesh->mName); + if (!aimesh) + { + if (targetMesh->mSubMeshes.size() > 1) + throw DeadlyImportError( "Morhing target mesh must be a single"); + aimesh = CreateMesh(pParser, targetMesh, targetMesh->mSubMeshes.at(0), NULL, 0, 0); + mTargetMeshes.push_back(aimesh); + } + targetMeshes.push_back(aimesh); + } + for (unsigned int i = 0; i < weightData.mValues.size(); ++i) + targetWeights.push_back(weightData.mValues.at(i)); + } + } + if (targetMeshes.size() > 0 && targetWeights.size() == targetMeshes.size()) + { + std::vector animMeshes; + for (unsigned int i = 0; i < targetMeshes.size(); i++) + { + aiAnimMesh *animMesh = aiCreateAnimMesh(targetMeshes.at(i)); + animMesh->mWeight = targetWeights[i]; + animMeshes.push_back(animMesh); + } + dstMesh->mMethod = (method == Collada::Relative) + ? aiMorphingMethod_MORPH_RELATIVE + : aiMorphingMethod_MORPH_NORMALIZED; + dstMesh->mAnimMeshes = new aiAnimMesh*[animMeshes.size()]; + dstMesh->mNumAnimMeshes = animMeshes.size(); + for (unsigned int i = 0; i < animMeshes.size(); i++) + dstMesh->mAnimMeshes[i] = animMeshes.at(i); + } + // create bones if given - if( pSrcController) + if( pSrcController && pSrcController->mType == Collada::Skin) { // refuse if the vertex count does not match // if( pSrcController->mWeightCounts.size() != dstMesh->mNumVertices) @@ -956,6 +1035,68 @@ void ColladaLoader::StoreAnimations( aiScene* pScene, const ColladaParser& pPars CreateAnimation( pScene, pParser, pSrcAnim, animName); } +struct MorphTimeValues +{ + float mTime; + struct key + { + float mWeight; + unsigned int mValue; + }; + std::vector mKeys; +}; + +void insertMorphTimeValue(std::vector &values, float time, float weight, unsigned int value) +{ + MorphTimeValues::key k; + k.mValue = value; + k.mWeight = weight; + if (values.size() == 0 || time < values[0].mTime) + { + MorphTimeValues val; + val.mTime = time; + val.mKeys.push_back(k); + values.insert(values.begin(), val); + return; + } + if (time > values.back().mTime) + { + MorphTimeValues val; + val.mTime = time; + val.mKeys.push_back(k); + values.insert(values.end(), val); + return; + } + for (unsigned int i = 0; i < values.size(); i++) + { + if (std::abs(time - values[i].mTime) < 1e-6f) + { + values[i].mKeys.push_back(k); + return; + } else if (time > values[i].mTime && time < values[i+1].mTime) + { + MorphTimeValues val; + val.mTime = time; + val.mKeys.push_back(k); + values.insert(values.begin() + i, val); + return; + } + } + // should not get here +} + +float getWeightAtKey(const std::vector &values, int key, unsigned int value) +{ + for (unsigned int i = 0; i < values[key].mKeys.size(); i++) + { + if (values[key].mKeys[i].mValue == value) + return values[key].mKeys[i].mWeight; + } + // no value at key found, try to interpolate if present at other keys. if not, return zero + // TODO: interpolation + return 0.0f; +} + // ------------------------------------------------------------------------------------------------ // Constructs the animation for the given source anim void ColladaLoader::CreateAnimation( aiScene* pScene, const ColladaParser& pParser, const Collada::Animation* pSrcAnim, const std::string& pName) @@ -965,6 +1106,8 @@ void ColladaLoader::CreateAnimation( aiScene* pScene, const ColladaParser& pPars CollectNodes( pScene->mRootNode, nodes); std::vector anims; + std::vector morphAnims; + for( std::vector::const_iterator nit = nodes.begin(); nit != nodes.end(); ++nit) { // find all the collada anim channels which refer to the current node @@ -988,7 +1131,20 @@ void ColladaLoader::CreateAnimation( aiScene* pScene, const ColladaParser& pPars // find the slash that separates the node name - there should be only one std::string::size_type slashPos = srcChannel.mTarget.find( '/'); if( slashPos == std::string::npos) + { + std::string::size_type targetPos = srcChannel.mTarget.find(srcNode->mID); + if (targetPos == std::string::npos) + continue; + + // not node transform, but something else. store as unknown animation channel for now + entry.mChannel = &(*cit); + entry.mTargetId = srcChannel.mTarget.substr(targetPos + pSrcAnim->mName.length(), + srcChannel.mTarget.length() - targetPos - pSrcAnim->mName.length()); + if (entry.mTargetId.front() == '-') + entry.mTargetId = entry.mTargetId.substr(1); + entries.push_back(entry); continue; + } if( srcChannel.mTarget.find( '/', slashPos+1) != std::string::npos) continue; std::string targetID = srcChannel.mTarget.substr( 0, slashPos); @@ -1068,8 +1224,14 @@ void ColladaLoader::CreateAnimation( aiScene* pScene, const ColladaParser& pPars if( srcNode->mTransforms[a].mID == entry.mTransformId) entry.mTransformIndex = a; - if( entry.mTransformIndex == SIZE_MAX) { - continue; + if( entry.mTransformIndex == SIZE_MAX) + { + if (entry.mTransformId.find("morph-weights") != std::string::npos) + { + entry.mTargetId = entry.mTransformId; + entry.mTransformId = ""; + } else + continue; } entry.mChannel = &(*cit); @@ -1213,49 +1375,125 @@ void ColladaLoader::CreateAnimation( aiScene* pScene, const ColladaParser& pPars // ai_assert( resultTrafos.size() > 0); // build an animation channel for the given node out of these trafo keys - if( !resultTrafos.empty() ) - { - aiNodeAnim* dstAnim = new aiNodeAnim; - dstAnim->mNodeName = nodeName; - dstAnim->mNumPositionKeys = static_cast(resultTrafos.size()); - dstAnim->mNumRotationKeys= static_cast(resultTrafos.size()); - dstAnim->mNumScalingKeys = static_cast(resultTrafos.size()); - dstAnim->mPositionKeys = new aiVectorKey[resultTrafos.size()]; - dstAnim->mRotationKeys = new aiQuatKey[resultTrafos.size()]; - dstAnim->mScalingKeys = new aiVectorKey[resultTrafos.size()]; + if( !resultTrafos.empty() ) + { + aiNodeAnim* dstAnim = new aiNodeAnim; + dstAnim->mNodeName = nodeName; + dstAnim->mNumPositionKeys = resultTrafos.size(); + dstAnim->mNumRotationKeys= resultTrafos.size(); + dstAnim->mNumScalingKeys = resultTrafos.size(); + dstAnim->mPositionKeys = new aiVectorKey[resultTrafos.size()]; + dstAnim->mRotationKeys = new aiQuatKey[resultTrafos.size()]; + dstAnim->mScalingKeys = new aiVectorKey[resultTrafos.size()]; - for( size_t a = 0; a < resultTrafos.size(); ++a) - { - aiMatrix4x4 mat = resultTrafos[a]; - double time = double( mat.d4); // remember? time is stored in mat.d4 - mat.d4 = 1.0f; + for( size_t a = 0; a < resultTrafos.size(); ++a) + { + aiMatrix4x4 mat = resultTrafos[a]; + double time = double( mat.d4); // remember? time is stored in mat.d4 + mat.d4 = 1.0f; - dstAnim->mPositionKeys[a].mTime = time; - dstAnim->mRotationKeys[a].mTime = time; - dstAnim->mScalingKeys[a].mTime = time; - mat.Decompose( dstAnim->mScalingKeys[a].mValue, dstAnim->mRotationKeys[a].mValue, dstAnim->mPositionKeys[a].mValue); - } + dstAnim->mPositionKeys[a].mTime = time; + dstAnim->mRotationKeys[a].mTime = time; + dstAnim->mScalingKeys[a].mTime = time; + mat.Decompose( dstAnim->mScalingKeys[a].mValue, dstAnim->mRotationKeys[a].mValue, dstAnim->mPositionKeys[a].mValue); + } - anims.push_back( dstAnim); - } else - { - DefaultLogger::get()->warn( "Collada loader: found empty animation channel, ignored. Please check your exporter."); - } + anims.push_back( dstAnim); + } else + { + DefaultLogger::get()->warn( "Collada loader: found empty animation channel, ignored. Please check your exporter."); + } + + if( !entries.empty() && entries.front().mTimeAccessor->mCount > 0 ) + { + std::vector morphChannels; + for( std::vector::iterator it = entries.begin(); it != entries.end(); ++it) + { + Collada::ChannelEntry& e = *it; + + // skip non-transform types + if (e.mTargetId.empty()) + continue; + + if (e.mTargetId.find("morph-weights") != std::string::npos) + morphChannels.push_back(e); + } + if (morphChannels.size() > 0) + { + // either 1) morph weight animation count should contain morph target count channels + // or 2) one channel with morph target count arrays + // assume first + + aiMeshMorphAnim *morphAnim = new aiMeshMorphAnim; + morphAnim->mName.Set(nodeName); + + std::vector morphTimeValues; + + int morphAnimChannelIndex = 0; + for( std::vector::iterator it = morphChannels.begin(); it != morphChannels.end(); ++it) + { + Collada::ChannelEntry& e = *it; + std::string::size_type apos = e.mTargetId.find('('); + std::string::size_type bpos = e.mTargetId.find(')'); + if (apos == std::string::npos || bpos == std::string::npos) + // unknown way to specify weight -> ignore this animation + continue; + + // weight target can be in format Weight_M_N, Weight_N, WeightN, or some other way + // we ignore the name and just assume the channels are in the right order + for (unsigned int i = 0; i < e.mTimeData->mValues.size(); i++) + insertMorphTimeValue(morphTimeValues, e.mTimeData->mValues.at(i), e.mValueData->mValues.at(i), morphAnimChannelIndex); + + ++morphAnimChannelIndex; + } + + morphAnim->mNumKeys = morphTimeValues.size(); + morphAnim->mKeys = new aiMeshMorphKey[morphAnim->mNumKeys]; + for (unsigned int key = 0; key < morphAnim->mNumKeys; key++) + { + morphAnim->mKeys[key].mNumValuesAndWeights = morphChannels.size(); + morphAnim->mKeys[key].mValues = new unsigned int [morphChannels.size()]; + morphAnim->mKeys[key].mWeights = new double [morphChannels.size()]; + + morphAnim->mKeys[key].mTime = morphTimeValues[key].mTime; + for (unsigned int valueIndex = 0; valueIndex < morphChannels.size(); valueIndex++) + { + morphAnim->mKeys[key].mValues[valueIndex] = valueIndex; + morphAnim->mKeys[key].mWeights[valueIndex] = getWeightAtKey(morphTimeValues, key, valueIndex); + } + } + + morphAnims.push_back(morphAnim); + } + } } - if( !anims.empty()) + if( !anims.empty() || !morphAnims.empty()) { aiAnimation* anim = new aiAnimation; anim->mName.Set( pName); - anim->mNumChannels = static_cast(anims.size()); - anim->mChannels = new aiNodeAnim*[anims.size()]; - std::copy( anims.begin(), anims.end(), anim->mChannels); + anim->mNumChannels = anims.size(); + if (anim->mNumChannels > 0) + { + anim->mChannels = new aiNodeAnim*[anims.size()]; + std::copy( anims.begin(), anims.end(), anim->mChannels); + } + anim->mNumMorphMeshChannels = morphAnims.size(); + if (anim->mNumMorphMeshChannels > 0) + { + anim->mMorphMeshChannels = new aiMeshMorphAnim*[anim->mNumMorphMeshChannels]; + std::copy( morphAnims.begin(), morphAnims.end(), anim->mMorphMeshChannels); + } anim->mDuration = 0.0f; for( size_t a = 0; a < anims.size(); ++a) { - anim->mDuration = std::max( anim->mDuration, anims[a]->mPositionKeys[anims[a]->mNumPositionKeys-1].mTime); - anim->mDuration = std::max( anim->mDuration, anims[a]->mRotationKeys[anims[a]->mNumRotationKeys-1].mTime); - anim->mDuration = std::max( anim->mDuration, anims[a]->mScalingKeys[anims[a]->mNumScalingKeys-1].mTime); + anim->mDuration = std::max( anim->mDuration, anims[a]->mPositionKeys[anims[a]->mNumPositionKeys-1].mTime); + anim->mDuration = std::max( anim->mDuration, anims[a]->mRotationKeys[anims[a]->mNumRotationKeys-1].mTime); + anim->mDuration = std::max( anim->mDuration, anims[a]->mScalingKeys[anims[a]->mNumScalingKeys-1].mTime); + } + for (size_t a = 0; a < morphAnims.size(); ++a) + { + anim->mDuration = std::max(anim->mDuration, morphAnims[a]->mKeys[morphAnims[a]->mNumKeys-1].mTime); } anim->mTicksPerSecond = 1; mAnims.push_back( anim); diff --git a/code/ColladaLoader.h b/code/ColladaLoader.h index 716b437d8..2b31531d2 100644 --- a/code/ColladaLoader.h +++ b/code/ColladaLoader.h @@ -117,6 +117,8 @@ protected: /** Builds meshes for the given node and references them */ void BuildMeshesForNode( const ColladaParser& pParser, const Collada::Node* pNode, aiNode* pTarget); + + aiMesh *findMesh(std::string meshid); /** Creates a mesh for the given ColladaMesh face subset and returns the newly created mesh */ aiMesh* CreateMesh( const ColladaParser& pParser, const Collada::Mesh* pSrcMesh, const Collada::SubMesh& pSubMesh, @@ -223,6 +225,9 @@ protected: /** Accumulated meshes for the target scene */ std::vector mMeshes; + + /** Accumulated morph target meshes */ + std::vector mTargetMeshes; /** Temporary material list */ std::vector > newMats; diff --git a/code/ColladaParser.cpp b/code/ColladaParser.cpp index 70c5e95b4..2fa3aa7d5 100644 --- a/code/ColladaParser.cpp +++ b/code/ColladaParser.cpp @@ -585,6 +585,12 @@ void ColladaParser::ReadAnimationSampler( Collada::AnimationChannel& pChannel) pChannel.mSourceTimes = source; else if( strcmp( semantic, "OUTPUT") == 0) pChannel.mSourceValues = source; + else if( strcmp( semantic, "IN_TANGENT") == 0) + pChannel.mInTanValues = source; + else if( strcmp( semantic, "OUT_TANGENT") == 0) + pChannel.mOutTanValues = source; + else if( strcmp( semantic, "INTERPOLATION") == 0) + pChannel.mInterpolationValues = source; if( !mReader->isEmptyElement()) SkipElement(); @@ -647,6 +653,9 @@ void ColladaParser::ReadControllerLibrary() // Reads a controller into the given mesh structure void ColladaParser::ReadController( Collada::Controller& pController) { + // initial values + pController.mType = Skin; + pController.mMethod = Normalized; while( mReader->read()) { if( mReader->getNodeType() == irr::io::EXN_ELEMENT) @@ -654,8 +663,15 @@ void ColladaParser::ReadController( Collada::Controller& pController) // two types of controllers: "skin" and "morph". Only the first one is relevant, we skip the other if( IsElement( "morph")) { - // should skip everything inside, so there's no danger of catching elements in between - SkipElement(); + pController.mType = Morph; + int baseIndex = GetAttribute("source"); + pController.mMeshId = mReader->getAttributeValue(baseIndex) + 1; + int methodIndex = GetAttribute("method"); + if (methodIndex > 0) { + const char *method = mReader->getAttributeValue(methodIndex); + if (strcmp(method, "RELATIVE") == 0) + pController.mMethod = Relative; + } } else if( IsElement( "skin")) { @@ -693,6 +709,32 @@ void ColladaParser::ReadController( Collada::Controller& pController) { ReadControllerWeights( pController); } + else if ( IsElement( "targets" )) + { + while (mReader->read()) { + if( mReader->getNodeType() == irr::io::EXN_ELEMENT) { + if ( IsElement( "input")) { + int semanticsIndex = GetAttribute("semantic"); + int sourceIndex = GetAttribute("source"); + + const char *semantics = mReader->getAttributeValue(semanticsIndex); + const char *source = mReader->getAttributeValue(sourceIndex); + if (strcmp(semantics, "MORPH_TARGET") == 0) { + pController.mMorphTarget = source + 1; + } + else if (strcmp(semantics, "MORPH_WEIGHT") == 0) + { + pController.mMorphWeight = source + 1; + } + } + } else if( mReader->getNodeType() == irr::io::EXN_ELEMENT_END) { + if( strcmp( mReader->getNodeName(), "targets") == 0) + break; + else + ThrowException( "Expected end of element."); + } + } + } else { // ignore the rest @@ -703,7 +745,7 @@ void ColladaParser::ReadController( Collada::Controller& pController) { if( strcmp( mReader->getNodeName(), "controller") == 0) break; - else if( strcmp( mReader->getNodeName(), "skin") != 0) + else if( strcmp( mReader->getNodeName(), "skin") != 0 && strcmp( mReader->getNodeName(), "morph") != 0) ThrowException( "Expected end of element."); } } diff --git a/code/CreateAnimMesh.cpp b/code/CreateAnimMesh.cpp new file mode 100644 index 000000000..094a414bf --- /dev/null +++ b/code/CreateAnimMesh.cpp @@ -0,0 +1,92 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (C) 2016 The Qt Company Ltd. +Copyright (c) 2006-2012, 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. +--------------------------------------------------------------------------- +*/ + +#include "CreateAnimMesh.h" + +namespace Assimp { + +aiAnimMesh *aiCreateAnimMesh(const aiMesh *mesh) +{ + aiAnimMesh *animesh = new aiAnimMesh; + animesh->mVertices = NULL; + animesh->mNormals = NULL; + animesh->mTangents = NULL; + animesh->mBitangents = NULL; + animesh->mNumVertices = mesh->mNumVertices; + if (mesh->mVertices) { + animesh->mVertices = new aiVector3D[animesh->mNumVertices]; + std::memcpy(animesh->mVertices, mesh->mVertices, mesh->mNumVertices * sizeof(aiVector3D)); + } + if (mesh->mNormals) { + animesh->mNormals = new aiVector3D[animesh->mNumVertices]; + std::memcpy(animesh->mNormals, mesh->mNormals, mesh->mNumVertices * sizeof(aiVector3D)); + } + if (mesh->mTangents) { + animesh->mTangents = new aiVector3D[animesh->mNumVertices]; + std::memcpy(animesh->mTangents, mesh->mTangents, mesh->mNumVertices * sizeof(aiVector3D)); + } + if (mesh->mBitangents) { + animesh->mBitangents = new aiVector3D[animesh->mNumVertices]; + std::memcpy(animesh->mBitangents, mesh->mBitangents, mesh->mNumVertices * sizeof(aiVector3D)); + } + + for (int i = 0; i < AI_MAX_NUMBER_OF_COLOR_SETS; ++i) { + if (mesh->mColors[i]) { + animesh->mColors[i] = new aiColor4D[animesh->mNumVertices]; + std::memcpy(animesh->mColors[i], mesh->mColors[i], mesh->mNumVertices * sizeof(aiColor4D)); + } else { + animesh->mColors[i] = NULL; + } + } + + for (int i = 0; i < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++i) { + if (mesh->mTextureCoords[i]) { + animesh->mTextureCoords[i] = new aiVector3D[animesh->mNumVertices]; + std::memcpy(animesh->mTextureCoords[i], mesh->mTextureCoords[i], mesh->mNumVertices * sizeof(aiVector3D)); + } else { + animesh->mTextureCoords[i] = NULL; + } + } + return animesh; +} + +} // end of namespace Assimp diff --git a/code/CreateAnimMesh.h b/code/CreateAnimMesh.h new file mode 100644 index 000000000..c5ceb4028 --- /dev/null +++ b/code/CreateAnimMesh.h @@ -0,0 +1,56 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2016, 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. + +---------------------------------------------------------------------- +*/ + +/** @file CreateAnimMesh.h + * Create AnimMesh from Mesh + */ +#ifndef INCLUDED_AI_CREATE_ANIM_MESH_H +#define INCLUDED_AI_CREATE_ANIM_MESH_H + +#include + +namespace Assimp { + +/** Create aiAnimMesh from aiMesh. */ +aiAnimMesh *aiCreateAnimMesh(const aiMesh *mesh); + +} // end of namespace Assimp +#endif // INCLUDED_AI_CREATE_ANIM_MESH_H + diff --git a/include/assimp/anim.h b/include/assimp/anim.h index f88faa22e..970be9989 100644 --- a/include/assimp/anim.h +++ b/include/assimp/anim.h @@ -190,6 +190,39 @@ struct aiMeshKey #endif }; +// --------------------------------------------------------------------------- +/** Binds a morph anim mesh to a specific point in time. */ +struct aiMeshMorphKey +{ + /** The time of this key */ + double mTime; + + /** The values and weights at the time of this key */ + unsigned int *mValues; + double *mWeights; + + /** The number of values and weights */ + unsigned int mNumValuesAndWeights; +#ifdef __cplusplus + aiMeshMorphKey() + : mTime(0.0) + , mValues(NULL) + , mWeights(NULL) + , mNumValuesAndWeights(0) + { + + } + + ~aiMeshMorphKey() + { + if (mNumValuesAndWeights && mValues && mWeights) { + delete [] mValues; + delete [] mWeights; + } + } +#endif +}; + // --------------------------------------------------------------------------- /** Defines how an animation channel behaves outside the defined time * range. This corresponds to aiNodeAnim::mPreState and @@ -340,6 +373,37 @@ struct aiMeshAnim #endif }; +// --------------------------------------------------------------------------- +/** Describes a morphing animation of a given mesh. */ +struct aiMeshMorphAnim +{ + /** Name of the mesh to be animated. An empty string is not allowed, + * animated meshes need to be named (not necessarily uniquely, + * the name can basically serve as wildcard to select a group + * of meshes with similar animation setup)*/ + C_STRUCT aiString mName; + + /** Size of the #mKeys array. Must be 1, at least. */ + unsigned int mNumKeys; + + /** Key frames of the animation. May not be NULL. */ + C_STRUCT aiMeshMorphKey* mKeys; + +#ifdef __cplusplus + + aiMeshMorphAnim() + : mNumKeys() + , mKeys() + {} + + ~aiMeshMorphAnim() + { + delete[] mKeys; + } + +#endif +}; + // --------------------------------------------------------------------------- /** An animation consists of key-frame data for a number of nodes. For * each node affected by the animation a separate series of data is given.*/ @@ -372,6 +436,14 @@ struct aiAnimation { * The array is mNumMeshChannels in size. */ C_STRUCT aiMeshAnim** mMeshChannels; + /** The number of mesh animation channels. Each channel affects + * a single mesh and defines morphing animation. */ + unsigned int mNumMorphMeshChannels; + + /** The morph mesh animation channels. Each channel affects a single mesh. + * The array is mNumMorphMeshChannels in size. */ + C_STRUCT aiMeshMorphAnim **mMorphMeshChannels; + #ifdef __cplusplus aiAnimation() : mDuration(-1.) @@ -379,7 +451,9 @@ struct aiAnimation { , mNumChannels(0) , mChannels(NULL) , mNumMeshChannels(0) - , mMeshChannels(NULL) { + , mMeshChannels(NULL) + , mNumMorphMeshChannels(0) + , mMorphMeshChannels(NULL) { // empty } @@ -399,6 +473,11 @@ struct aiAnimation { delete [] mMeshChannels; } + if (mNumMorphMeshChannels && mMorphMeshChannels) { + for( unsigned int a = 0; a < mNumMorphMeshChannels; a++) { + delete mMorphMeshChannels[a]; + } + } } #endif // __cplusplus }; diff --git a/include/assimp/mesh.h b/include/assimp/mesh.h index fd7560b83..8ba8d1221 100644 --- a/include/assimp/mesh.h +++ b/include/assimp/mesh.h @@ -378,6 +378,9 @@ struct aiAnimMesh */ unsigned int mNumVertices; +/** Weight of the AnimMesh. */ + float mWeight; + #ifdef __cplusplus aiAnimMesh() @@ -446,6 +449,27 @@ struct aiAnimMesh #endif }; +// --------------------------------------------------------------------------- +/** @brief Enumerates the methods of mesh morphing supported by Assimp. + */ +enum aiMorphingMethod +{ + /** Interpolation between morph targets */ + aiMorphingMethod_VERTEX_BLEND = 0x1, + + /** Normalized morphing between morph targets */ + aiMorphingMethod_MORPH_NORMALIZED = 0x2, + + /** Relative morphing between morph targets */ + aiMorphingMethod_MORPH_RELATIVE = 0x3, + + /** This value is not used. It is just here to force the + * compiler to map this enum to a 32 Bit integer. + */ +#ifndef SWIG + _aiMorphingMethod_Force32Bit = INT_MAX +#endif +}; //! enum aiMorphingMethod // --------------------------------------------------------------------------- /** @brief A mesh represents a geometry or model with a single material. @@ -600,15 +624,18 @@ struct aiMesh C_STRUCT aiString mName; - /** NOT CURRENTLY IN USE. The number of attachment meshes */ + /** The number of attachment meshes. Note! Currently only works with Collada loader. */ unsigned int mNumAnimMeshes; - /** NOT CURRENTLY IN USE. Attachment meshes for this mesh, for vertex-based animation. + /** Attachment meshes for this mesh, for vertex-based animation. * Attachment meshes carry replacement data for some of the - * mesh'es vertex components (usually positions, normals). */ + * mesh'es vertex components (usually positions, normals). + * Note! Currently only works with Collada loader.*/ C_STRUCT aiAnimMesh** mAnimMeshes; - + /** Method of morphing when animeshes are specified. */ + unsigned int mMethod; + #ifdef __cplusplus //! Default constructor. Initializes all members to 0 @@ -733,7 +760,6 @@ struct aiMesh #endif // __cplusplus }; - #ifdef __cplusplus } #endif //! extern "C"