diff --git a/code/BVHLoader.cpp b/code/BVHLoader.cpp index 461a5ae11..02d52c3e2 100644 --- a/code/BVHLoader.cpp +++ b/code/BVHLoader.cpp @@ -13,18 +13,18 @@ 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. +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. +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 Development Team. +contributors may be used to endorse or promote products +derived from this software without specific prior +written permission of the ASSIMP Development Team. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT @@ -41,8 +41,10 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "AssimpPCH.h" +#include "../include/aiAnim.h" #include "BVHLoader.h" #include "fast_atof.h" +#include "SkeletonMeshBuilder.h" using namespace Assimp; @@ -99,6 +101,12 @@ void BVHLoader::InternReadFile( const std::string& pFile, aiScene* pScene, IOSys mReader = mBuffer.begin(); mLine = 1; ReadStructure( pScene); + + // build a dummy mesh for the skeleton so that we see something at least + SkeletonMeshBuilder meshBuilder( pScene); + + // construct an animation from all the motion data we read + CreateAnimation( pScene); } // ------------------------------------------------------------------------------------------------ @@ -139,10 +147,6 @@ aiNode* BVHLoader::ReadNode() if( nodeName.empty() || nodeName == "{") ThrowException( boost::str( boost::format( "Expected node name, but found \"%s\".") % nodeName)); - // HACK: (thom) end nodes are called "End Site". If the name of the node is "Site", we know it's going to be an end node - if( nodeName == "Site") - nodeName = "End Site"; - // then an opening brace should follow std::string openBrace = GetNextToken(); if( openBrace != "{") @@ -152,30 +156,39 @@ aiNode* BVHLoader::ReadNode() aiNode* node = new aiNode( nodeName); std::vector childNodes; + // and create an bone entry for it + mNodes.push_back( Node( node)); + Node& internNode = mNodes.back(); + // now read the node's contents while( 1) { std::string token = GetNextToken(); - + // node offset to parent node if( token == "OFFSET") ReadNodeOffset( node); else if( token == "CHANNELS") - ReadNodeChannels( node); + ReadNodeChannels( internNode); else if( token == "JOINT") { // child node follows aiNode* child = ReadNode(); + child->mParent = node; childNodes.push_back( child); - } else - if( token == "End") + } + else if( token == "End") { - // HACK: (thom) end child node follows. Full token is "End Site", then no name, then a node. - // But I don't want to write another function for this, so I simply leave the "Site" for ReadNode() as a node name - aiNode* child = ReadNode(); + // The real symbol is "End Site". Second part comes in a separate token + std::string siteToken = GetNextToken(); + if( siteToken != "Site") + ThrowException( boost::str( boost::format( "Expected \"End Site\" keyword, but found \"%s %s\".") % token % siteToken)); + + aiNode* child = ReadEndSite( nodeName); + child->mParent = node; childNodes.push_back( child); - } else - if( token == "}") + } + else if( token == "}") { // we're done with that part of the hierarchy break; @@ -198,6 +211,42 @@ aiNode* BVHLoader::ReadNode() return node; } +// ------------------------------------------------------------------------------------------------ +// Reads an end node and returns the created node. +aiNode* BVHLoader::ReadEndSite( const std::string& pParentName) +{ + // check opening brace + std::string openBrace = GetNextToken(); + if( openBrace != "{") + ThrowException( boost::str( boost::format( "Expected opening brace \"{\", but found \"%s\".") % openBrace)); + + // Create a node + aiNode* node = new aiNode( "EndSite_" + pParentName); + + // now read the node's contents. Only possible entry is "OFFSET" + while( 1) + { + std::string token = GetNextToken(); + + // end node's offset + if( token == "OFFSET") + { + ReadNodeOffset( node); + } + else if( token == "}") + { + // we're done with the end node + break; + } else + { + // everything else is a parse error + ThrowException( boost::str( boost::format( "Unknown keyword \"%s\".") % token)); + } + } + + // and return the sub-hierarchy we built here + return node; +} // ------------------------------------------------------------------------------------------------ // Reads a node offset for the given node void BVHLoader::ReadNodeOffset( aiNode* pNode) @@ -215,15 +264,31 @@ void BVHLoader::ReadNodeOffset( aiNode* pNode) // ------------------------------------------------------------------------------------------------ // Reads the animation channels for the given node -void BVHLoader::ReadNodeChannels( aiNode* pNode) +void BVHLoader::ReadNodeChannels( BVHLoader::Node& pNode) { // number of channels. Use the float reader because we're lazy float numChannelsFloat = GetNextTokenAsFloat(); unsigned int numChannels = (unsigned int) numChannelsFloat; - // TODO: (thom) proper channel parsing. For the moment I just skip the number of tokens for( unsigned int a = 0; a < numChannels; a++) - GetNextToken(); + { + std::string channelToken = GetNextToken(); + + if( channelToken == "Xposition") + pNode.mChannels.push_back( Channel_PositionX); + else if( channelToken == "Yposition") + pNode.mChannels.push_back( Channel_PositionY); + else if( channelToken == "Zposition") + pNode.mChannels.push_back( Channel_PositionZ); + else if( channelToken == "Xrotation") + pNode.mChannels.push_back( Channel_RotationX); + else if( channelToken == "Yrotation") + pNode.mChannels.push_back( Channel_RotationY); + else if( channelToken == "Zrotation") + pNode.mChannels.push_back( Channel_RotationZ); + else + ThrowException( boost::str( boost::format( "Invalid channel specifier \"%s\".") % channelToken)); + } } // ------------------------------------------------------------------------------------------------ @@ -236,7 +301,7 @@ void BVHLoader::ReadMotion( aiScene* pScene) ThrowException( boost::str( boost::format( "Expected frame count \"Frames:\", but found \"%s\".") % tokenFrames)); float numFramesFloat = GetNextTokenAsFloat(); - unsigned int numFrames = (unsigned int) numFramesFloat; + mAnimNumFrames = (unsigned int) numFramesFloat; // Read frame duration std::string tokenDuration1 = GetNextToken(); @@ -244,11 +309,25 @@ void BVHLoader::ReadMotion( aiScene* pScene) if( tokenDuration1 != "Frame" || tokenDuration2 != "Time:") ThrowException( boost::str( boost::format( "Expected frame duration \"Frame Time:\", but found \"%s %s\".") % tokenDuration1 % tokenDuration2)); - float frameDuration = GetNextTokenAsFloat(); + mAnimTickDuration = GetNextTokenAsFloat(); - // resize value array accordingly - // ************* Continue here ******** - //mMotionValues.resize( boost::extents[numFrames][numChannels]); + // resize value vectors for each node + for( std::vector::iterator it = mNodes.begin(); it != mNodes.end(); ++it) + it->mChannelValues.reserve( it->mChannels.size() * mAnimNumFrames); + + // now read all the data and store it in the corresponding node's value vector + for( unsigned int frame = 0; frame < mAnimNumFrames; ++frame) + { + // on each line read the values for all nodes + for( std::vector::iterator it = mNodes.begin(); it != mNodes.end(); ++it) + { + // get as many values as the node has channels + for( unsigned int c = 0; c < it->mChannels.size(); ++c) + it->mChannelValues.push_back( GetNextTokenAsFloat()); + } + + // after one frame worth of values for all nodes there should be a newline, but we better don't rely on it + } } // ------------------------------------------------------------------------------------------------ @@ -312,3 +391,118 @@ void BVHLoader::ThrowException( const std::string& pError) { throw new ImportErrorException( boost::str( boost::format( "%s:%d - %s") % mFileName % mLine % pError)); } + +// ------------------------------------------------------------------------------------------------ +// Constructs an animation for the motion data and stores it in the given scene +void BVHLoader::CreateAnimation( aiScene* pScene) +{ + // create the animation + pScene->mNumAnimations = 1; + pScene->mAnimations = new aiAnimation*[1]; + aiAnimation* anim = new aiAnimation; + pScene->mAnimations[0] = anim; + + // put down the basic parameters + anim->mName.Set( "Motion"); + anim->mTicksPerSecond = 1.0 / double( mAnimTickDuration); + anim->mDuration = double( mAnimNumFrames - 1); + + // now generate the tracks for all nodes + anim->mNumChannels = mNodes.size(); + anim->mChannels = new aiNodeAnim*[anim->mNumChannels]; + for( unsigned int a = 0; a < anim->mNumChannels; a++) + { + const Node& node = mNodes[a]; + const char* nodeName = node.mNode->mName.data; + aiNodeAnim* nodeAnim = new aiNodeAnim; + anim->mChannels[a] = nodeAnim; + nodeAnim->mNodeName.Set( std::string( nodeName)); + + // translational part, if given + if( node.mChannels.size() == 6) + { + if( node.mChannels[0] != Channel_PositionX || node.mChannels[1] != Channel_PositionY + || node.mChannels[2] != Channel_PositionZ) + { + throw new ImportErrorException( boost::str( boost::format( "Unexpected animation " + "channel setup at node \"%s\".") % nodeName)); + } + + nodeAnim->mNumPositionKeys = mAnimNumFrames; + nodeAnim->mPositionKeys = new aiVectorKey[mAnimNumFrames]; + aiVectorKey* poskey = nodeAnim->mPositionKeys; + for( unsigned int fr = 0; fr < mAnimNumFrames; ++fr) + { + poskey->mTime = double( fr); + poskey->mValue.x = node.mChannelValues[fr * node.mChannels.size() + 0]; + poskey->mValue.y = node.mChannelValues[fr * node.mChannels.size() + 1]; + poskey->mValue.z = node.mChannelValues[fr * node.mChannels.size() + 2]; + ++poskey; + } + } else + { + // if no translation part is given, put a default sequence + aiVector3D nodePos( node.mNode->mTransformation.a4, node.mNode->mTransformation.b4, node.mNode->mTransformation.c4); + nodeAnim->mNumPositionKeys = 2; + nodeAnim->mPositionKeys = new aiVectorKey[2]; + nodeAnim->mPositionKeys[0].mTime = 0.0; + nodeAnim->mPositionKeys[0].mValue = nodePos; + nodeAnim->mPositionKeys[1].mTime = anim->mDuration; + nodeAnim->mPositionKeys[1].mValue = nodePos; + } + + // rotation part. Always present. First find value offsets + { + unsigned int rotOffset = 0; + if( node.mChannels.size() == 6) + { + if( node.mChannels[3] != Channel_RotationZ || node.mChannels[4] != Channel_RotationX + || node.mChannels[5] != Channel_RotationY) + { + throw new ImportErrorException( boost::str( boost::format( "Unexpected animation " + "channel setup at node \"%s\".") % nodeName)); + } + rotOffset = 3; + } else + { + if( node.mChannels[0] != Channel_RotationZ || node.mChannels[1] != Channel_RotationX + || node.mChannels[2] != Channel_RotationY || node.mChannels.size() != 3) + { + throw new ImportErrorException( boost::str( boost::format( "Unexpected animation " + "channel setup at node \"%s\".") % nodeName)); + } + } + + // Then create the number of rotation keys + nodeAnim->mNumRotationKeys = mAnimNumFrames; + nodeAnim->mRotationKeys = new aiQuatKey[mAnimNumFrames]; + aiQuatKey* rotkey = nodeAnim->mRotationKeys; + for( unsigned int fr = 0; fr < mAnimNumFrames; ++fr) + { + // translate ZXY euler angels into a quaternion + float angleZ = node.mChannelValues[fr * node.mChannels.size() + rotOffset + 0] * float( AI_MATH_PI) / 180.0f; + float angleX = node.mChannelValues[fr * node.mChannels.size() + rotOffset + 1] * float( AI_MATH_PI) / 180.0f; + float angleY = node.mChannelValues[fr * node.mChannels.size() + rotOffset + 2] * float( AI_MATH_PI) / 180.0f; + aiMatrix4x4 temp; + aiMatrix3x3 rotMatrix; + aiMatrix4x4::RotationX( angleX, temp); rotMatrix *= aiMatrix3x3( temp); + aiMatrix4x4::RotationY( angleY, temp); rotMatrix *= aiMatrix3x3( temp); + aiMatrix4x4::RotationZ( angleZ, temp); rotMatrix *= aiMatrix3x3( temp); + + rotkey->mTime = double( fr); + rotkey->mValue = aiQuaternion( rotMatrix); + ++rotkey; + } + } + + // scaling part. Always just a default track + { + nodeAnim->mNumScalingKeys = 2; + nodeAnim->mScalingKeys = new aiVectorKey[2]; + nodeAnim->mScalingKeys[0].mTime = 0.0; + nodeAnim->mScalingKeys[0].mValue.Set( 1.0f, 1.0f, 1.0f); + nodeAnim->mScalingKeys[1].mTime = anim->mDuration; + nodeAnim->mScalingKeys[1].mValue.Set( 1.0f, 1.0f, 1.0f); + } + } +} diff --git a/code/BVHLoader.h b/code/BVHLoader.h index 9c098e882..f7cb6f8fa 100644 --- a/code/BVHLoader.h +++ b/code/BVHLoader.h @@ -44,7 +44,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #define AI_BVHLOADER_H_INC #include "BaseImporter.h" -#include namespace Assimp { @@ -57,6 +56,28 @@ class BVHLoader : public BaseImporter { friend class Importer; + /** Possible animation channels for which the motion data holds the values */ + enum ChannelType + { + Channel_PositionX, + Channel_PositionY, + Channel_PositionZ, + Channel_RotationX, + Channel_RotationY, + Channel_RotationZ + }; + + /** Collected list of node. Will be bones of the dummy mesh some day, addressed by their array index */ + struct Node + { + const aiNode* mNode; + std::vector mChannels; + std::vector mChannelValues; // motion data values for that node. Of size NumChannels * NumFrames + + Node() { } + Node( const aiNode* pNode) : mNode( pNode) { } + }; + protected: /** Constructor to be privately used by Importer */ BVHLoader(); @@ -93,11 +114,14 @@ protected: /** Reads a node and recursively its childs and returns the created node. */ aiNode* ReadNode(); + /** Reads an end node and returns the created node. */ + aiNode* ReadEndSite( const std::string& pParentName); + /** Reads a node offset for the given node */ void ReadNodeOffset( aiNode* pNode); - /** Reads the animation channels for the given node */ - void ReadNodeChannels( aiNode* pNode); + /** Reads the animation channels into the given node */ + void ReadNodeChannels( BVHLoader::Node& pNode); /** Reads the motion data */ void ReadMotion( aiScene* pScene); @@ -111,6 +135,9 @@ protected: /** Aborts the file reading with an exception */ void ThrowException( const std::string& pError); + /** Constructs an animation for the motion data and stores it in the given scene */ + void CreateAnimation( aiScene* pScene); + protected: /** Filename, for a verbose error message */ std::string mFileName; @@ -124,8 +151,14 @@ protected: /** Current line, for error messages */ unsigned int mLine; - /** motion values per frame */ - boost::multi_array mMotionValues; + /** Collected list of nodes. Will be bones of the dummy mesh some day, addressed by their array index. + * Also contain the motion data for the node's channels + */ + std::vector mNodes; + + /** basic Animation parameters */ + float mAnimTickDuration; + unsigned int mAnimNumFrames; }; } // end of namespace Assimp diff --git a/code/SkeletonMeshBuilder.cpp b/code/SkeletonMeshBuilder.cpp new file mode 100644 index 000000000..5d4dbce2f --- /dev/null +++ b/code/SkeletonMeshBuilder.cpp @@ -0,0 +1,238 @@ +/** Implementation of a little class to construct a dummy mesh for a skeleton */ + +/* +Open Asset Import Library (ASSIMP) +---------------------------------------------------------------------- + +Copyright (c) 2006-2008, ASSIMP Development 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 Development 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 "AssimpPCH.h" +#include "../include/aiScene.h" +#include "SkeletonMeshBuilder.h" + +using namespace Assimp; + +// ------------------------------------------------------------------------------------------------ +// The constructor processes the given scene and adds a mesh there. +SkeletonMeshBuilder::SkeletonMeshBuilder( aiScene* pScene) +{ + // nothing to do if there's mesh data already present at the scene + if( pScene->mNumMeshes > 0 || pScene->mRootNode == NULL) + return; + + // build some faces around each node + CreateGeometry( pScene->mRootNode); + + // create a mesh to hold all the generated faces + pScene->mNumMeshes = 1; + pScene->mMeshes = new aiMesh*[1]; + pScene->mMeshes[0] = CreateMesh(); + // and install it at the root node + pScene->mRootNode->mNumMeshes = 1; + pScene->mRootNode->mMeshes = new unsigned int[1]; + pScene->mRootNode->mMeshes[0] = 0; + + // create a dummy material for the mesh + pScene->mNumMaterials = 1; + pScene->mMaterials = new aiMaterial*[1]; + pScene->mMaterials[0] = CreateMaterial(); +} + +// ------------------------------------------------------------------------------------------------ +// Recursively builds a simple mesh representation for the given node +void SkeletonMeshBuilder::CreateGeometry( const aiNode* pNode) +{ + // add a joint entry for the node. + const unsigned int boneIndex = mBones.size(); + const unsigned int vertexStartIndex = mVertices.size(); + + // now build the geometry. + if( pNode->mNumChildren > 0) + { + // If the node has childs, we build little pointers to each of them + for( unsigned int a = 0; a < pNode->mNumChildren; a++) + { + // find a suitable coordinate system + const aiMatrix4x4& childTransform = pNode->mChildren[a]->mTransformation; + aiVector3D childpos( childTransform.a4, childTransform.b4, childTransform.c4); + float distanceToChild = childpos.Length(); + aiVector3D up = aiVector3D( childpos).Normalize(); + + aiVector3D orth( 1.0f, 0.0f, 0.0f); + if( abs( orth * up) > 0.99f) + orth.Set( 0.0f, 1.0f, 0.0f); + + aiVector3D front = (up ^ orth).Normalize(); + aiVector3D side = (front ^ up).Normalize(); + + mVertices.push_back( -front * distanceToChild * 0.1f); + mVertices.push_back( childpos); + mVertices.push_back( -side * distanceToChild * 0.1f); + mVertices.push_back( -side * distanceToChild * 0.1f); + mVertices.push_back( childpos); + mVertices.push_back( front * distanceToChild * 0.1f); + mVertices.push_back( front * distanceToChild * 0.1f); + mVertices.push_back( childpos); + mVertices.push_back( side * distanceToChild * 0.1f); + mVertices.push_back( side * distanceToChild * 0.1f); + mVertices.push_back( childpos); + mVertices.push_back( -front * distanceToChild * 0.1f); + + unsigned localVertexStart = vertexStartIndex + a * 12; + mFaces.push_back( Face( localVertexStart + 0, localVertexStart + 1, localVertexStart + 2)); + mFaces.push_back( Face( localVertexStart + 3, localVertexStart + 4, localVertexStart + 5)); + mFaces.push_back( Face( localVertexStart + 6, localVertexStart + 7, localVertexStart + 8)); + mFaces.push_back( Face( localVertexStart + 9, localVertexStart + 10, localVertexStart + 11)); + } + } else + { + // if the node has no children, it's an end node. Put a little knob there instead + aiVector3D ownpos( pNode->mTransformation.a4, pNode->mTransformation.b4, pNode->mTransformation.c4); + float sizeEstimate = ownpos.Length() * 0.2f; + + mVertices.push_back( aiVector3D( -sizeEstimate, 0.0f, 0.0f)); + mVertices.push_back( aiVector3D( 0.0f, sizeEstimate, 0.0f)); + mVertices.push_back( aiVector3D( 0.0f, 0.0f, -sizeEstimate)); + mVertices.push_back( aiVector3D( 0.0f, sizeEstimate, 0.0f)); + mVertices.push_back( aiVector3D( sizeEstimate, 0.0f, 0.0f)); + mVertices.push_back( aiVector3D( 0.0f, 0.0f, -sizeEstimate)); + mVertices.push_back( aiVector3D( sizeEstimate, 0.0f, 0.0f)); + mVertices.push_back( aiVector3D( 0.0f, -sizeEstimate, 0.0f)); + mVertices.push_back( aiVector3D( 0.0f, 0.0f, -sizeEstimate)); + mVertices.push_back( aiVector3D( 0.0f, -sizeEstimate, 0.0f)); + mVertices.push_back( aiVector3D( -sizeEstimate, 0.0f, 0.0f)); + mVertices.push_back( aiVector3D( 0.0f, 0.0f, -sizeEstimate)); + + mVertices.push_back( aiVector3D( -sizeEstimate, 0.0f, 0.0f)); + mVertices.push_back( aiVector3D( 0.0f, 0.0f, sizeEstimate)); + mVertices.push_back( aiVector3D( 0.0f, sizeEstimate, 0.0f)); + mVertices.push_back( aiVector3D( 0.0f, sizeEstimate, 0.0f)); + mVertices.push_back( aiVector3D( 0.0f, 0.0f, sizeEstimate)); + mVertices.push_back( aiVector3D( sizeEstimate, 0.0f, 0.0f)); + mVertices.push_back( aiVector3D( sizeEstimate, 0.0f, 0.0f)); + mVertices.push_back( aiVector3D( 0.0f, 0.0f, sizeEstimate)); + mVertices.push_back( aiVector3D( 0.0f, -sizeEstimate, 0.0f)); + mVertices.push_back( aiVector3D( 0.0f, -sizeEstimate, 0.0f)); + mVertices.push_back( aiVector3D( 0.0f, 0.0f, sizeEstimate)); + mVertices.push_back( aiVector3D( -sizeEstimate, 0.0f, 0.0f)); + + mFaces.push_back( Face( vertexStartIndex + 0, vertexStartIndex + 1, vertexStartIndex + 2)); + mFaces.push_back( Face( vertexStartIndex + 3, vertexStartIndex + 4, vertexStartIndex + 5)); + mFaces.push_back( Face( vertexStartIndex + 6, vertexStartIndex + 7, vertexStartIndex + 8)); + mFaces.push_back( Face( vertexStartIndex + 9, vertexStartIndex + 10, vertexStartIndex + 11)); + mFaces.push_back( Face( vertexStartIndex + 12, vertexStartIndex + 13, vertexStartIndex + 14)); + mFaces.push_back( Face( vertexStartIndex + 15, vertexStartIndex + 16, vertexStartIndex + 17)); + mFaces.push_back( Face( vertexStartIndex + 18, vertexStartIndex + 19, vertexStartIndex + 20)); + mFaces.push_back( Face( vertexStartIndex + 21, vertexStartIndex + 22, vertexStartIndex + 23)); + } + + // create a bone affecting all the newly created vertices + aiBone* bone = new aiBone; + mBones.push_back( bone); + bone->mName = pNode->mName; + + // calculate the bone offset matrix by concatenating the inverse transformations of all parents + bone->mOffsetMatrix = aiMatrix4x4( pNode->mTransformation).Inverse(); + for( aiNode* parent = pNode->mParent; parent != NULL; parent = parent->mParent) + bone->mOffsetMatrix = aiMatrix4x4( parent->mTransformation).Inverse() * bone->mOffsetMatrix; + + // add all the vertices to the bone's influences + unsigned int numVertices = mVertices.size() - vertexStartIndex; + bone->mNumWeights = numVertices; + bone->mWeights = new aiVertexWeight[numVertices]; + for( unsigned int a = 0; a < numVertices; a++) + bone->mWeights[a] = aiVertexWeight( vertexStartIndex + a, 1.0f); + + // HACK: (thom) transform all vertices to the bone's local space. Should be done before adding + // them to the array, but I'm tired now and I'm annoyed. + aiMatrix4x4 boneToMeshTransform = aiMatrix4x4( bone->mOffsetMatrix).Inverse(); + for( unsigned int a = vertexStartIndex; a < mVertices.size(); a++) + mVertices[a] = boneToMeshTransform * mVertices[a]; + + // and finally recurse into the children list + for( unsigned int a = 0; a < pNode->mNumChildren; a++) + CreateGeometry( pNode->mChildren[a]); +} + +// ------------------------------------------------------------------------------------------------ +// Creates the mesh from the internally accumulated stuff and returns it. +aiMesh* SkeletonMeshBuilder::CreateMesh() +{ + aiMesh* mesh = new aiMesh(); + + // add points + mesh->mNumVertices = mVertices.size(); + mesh->mVertices = new aiVector3D[mesh->mNumVertices]; + std::copy( mVertices.begin(), mVertices.end(), mesh->mVertices); + + // add faces + mesh->mNumFaces = mFaces.size(); + mesh->mFaces = new aiFace[mesh->mNumFaces]; + for( unsigned int a = 0; a < mesh->mNumFaces; a++) + { + const Face& inface = mFaces[a]; + aiFace& outface = mesh->mFaces[a]; + outface.mNumIndices = 3; + outface.mIndices = new unsigned int[3]; + outface.mIndices[0] = inface.mIndices[0]; + outface.mIndices[1] = inface.mIndices[1]; + outface.mIndices[2] = inface.mIndices[2]; + } + + // add the bones + mesh->mNumBones = mBones.size(); + mesh->mBones = new aiBone*[mesh->mNumBones]; + std::copy( mBones.begin(), mBones.end(), mesh->mBones); + + // default + mesh->mMaterialIndex = 0; + + return mesh; +} + +// ------------------------------------------------------------------------------------------------ +// Creates a dummy material and returns it. +aiMaterial* SkeletonMeshBuilder::CreateMaterial() +{ + Assimp::MaterialHelper* matHelper = new Assimp::MaterialHelper; + + // Name + aiString matName( std::string( "Material")); + matHelper->AddProperty( &matName, AI_MATKEY_NAME); + + return matHelper; +} diff --git a/code/SkeletonMeshBuilder.h b/code/SkeletonMeshBuilder.h new file mode 100644 index 000000000..956ca0d7e --- /dev/null +++ b/code/SkeletonMeshBuilder.h @@ -0,0 +1,102 @@ +/** Helper class to construct a dummy mesh for file formats containing only motion data */ + +/* +Open Asset Import Library (ASSIMP) +---------------------------------------------------------------------- + +Copyright (c) 2006-2008, ASSIMP Development 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 Development 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. + +---------------------------------------------------------------------- +*/ + +#ifndef AI_SKELETONMESHBUILDER_H_INC +#define AI_SKELETONMESHBUILDER_H_INC + +#include +#include "../include/aiMesh.h" + +struct aiScene; +struct aiNode; + +namespace Assimp +{ + +/** + * This little helper class constructs a dummy mesh for a given scene + * the resembles the node hierarchy. This is useful for file formats + * that don't carry any mesh data but only animation data. + */ +class SkeletonMeshBuilder +{ +public: + /** The constructor processes the given scene and adds a mesh there. Does nothing + * if the scene already has mesh data. + * @param pScene The scene for which a skeleton mesh should be constructed. + */ + SkeletonMeshBuilder( aiScene* pScene); + +protected: + /** Recursively builds a simple mesh representation for the given node and also creates + * a joint for the node that affects this part of the mesh. + * @param pNode The node to build geometry for. + */ + void CreateGeometry( const aiNode* pNode); + + /** Creates the mesh from the internally accumulated stuff and returns it. */ + aiMesh* CreateMesh(); + + /** Creates a dummy material and returns it. */ + aiMaterial* CreateMaterial(); + +protected: + /** space to assemble the mesh data: points */ + std::vector mVertices; + + /** faces */ + struct Face + { + unsigned int mIndices[3]; + Face(); + Face( unsigned int p0, unsigned int p1, unsigned int p2) + { mIndices[0] = p0; mIndices[1] = p1; mIndices[2] = p2; } + }; + std::vector mFaces; + + /** bones */ + std::vector mBones; +}; + +} // end of namespace Assimp + +#endif // AI_SKELETONMESHBUILDER_H_INC diff --git a/include/aiMatrix4x4.h b/include/aiMatrix4x4.h index 5696532f1..aef13d77a 100644 --- a/include/aiMatrix4x4.h +++ b/include/aiMatrix4x4.h @@ -128,7 +128,6 @@ struct aiMatrix4x4 */ static aiMatrix4x4& RotationX(float a, aiMatrix4x4& out); - /** \brief Returns a rotation matrix for a rotation around the y axis * \param a Rotation angle, in radians * \param out Receives the output matrix @@ -136,7 +135,6 @@ struct aiMatrix4x4 */ static aiMatrix4x4& RotationY(float a, aiMatrix4x4& out); - /** \brief Returns a rotation matrix for a rotation around the z axis * \param a Rotation angle, in radians * \param out Receives the output matrix @@ -144,13 +142,12 @@ struct aiMatrix4x4 */ static aiMatrix4x4& RotationZ(float a, aiMatrix4x4& out); - /** \brief Returns a translation matrix * \param v Translation vector * \param out Receives the output matrix * \return Reference to the output matrix */ - static aiMatrix4x4& Translation(aiVector3D v, aiMatrix4x4& out); + static aiMatrix4x4& Translation( const aiVector3D& v, aiMatrix4x4& out); #endif // __cplusplus diff --git a/include/aiMatrix4x4.inl b/include/aiMatrix4x4.inl index 4eca01d6f..571166e21 100644 --- a/include/aiMatrix4x4.inl +++ b/include/aiMatrix4x4.inl @@ -252,6 +252,7 @@ inline aiMatrix4x4& aiMatrix4x4::RotationX(float a, aiMatrix4x4& out) out.b3 = -(out.c2 = sin(a)); return out; } + // --------------------------------------------------------------------------- inline aiMatrix4x4& aiMatrix4x4::RotationY(float a, aiMatrix4x4& out) { @@ -281,7 +282,7 @@ inline aiMatrix4x4& aiMatrix4x4::RotationZ(float a, aiMatrix4x4& out) return out; } // --------------------------------------------------------------------------- -inline aiMatrix4x4& aiMatrix4x4::Translation(aiVector3D v, aiMatrix4x4& out) +inline aiMatrix4x4& aiMatrix4x4::Translation( const aiVector3D& v, aiMatrix4x4& out) { out = aiMatrix4x4(); out.a4 = v.x; @@ -290,6 +291,5 @@ inline aiMatrix4x4& aiMatrix4x4::Translation(aiVector3D v, aiMatrix4x4& out) return out; } - #endif // __cplusplus #endif // AI_MATRIX4x4_INL_INC diff --git a/workspaces/vc8/assimp.vcproj b/workspaces/vc8/assimp.vcproj index 22e92bc04..f2de1f6c6 100644 --- a/workspaces/vc8/assimp.vcproj +++ b/workspaces/vc8/assimp.vcproj @@ -1511,6 +1511,7 @@