nuclear@0: /** Implementation of the BVH loader */ nuclear@0: /* nuclear@0: --------------------------------------------------------------------------- nuclear@0: Open Asset Import Library (assimp) nuclear@0: --------------------------------------------------------------------------- nuclear@0: nuclear@0: Copyright (c) 2006-2012, assimp team nuclear@0: nuclear@0: All rights reserved. nuclear@0: nuclear@0: Redistribution and use of this software in source and binary forms, nuclear@0: with or without modification, are permitted provided that the following nuclear@0: conditions are met: nuclear@0: nuclear@0: * Redistributions of source code must retain the above nuclear@0: copyright notice, this list of conditions and the nuclear@0: following disclaimer. nuclear@0: nuclear@0: * Redistributions in binary form must reproduce the above nuclear@0: copyright notice, this list of conditions and the nuclear@0: following disclaimer in the documentation and/or other nuclear@0: materials provided with the distribution. nuclear@0: nuclear@0: * Neither the name of the assimp team, nor the names of its nuclear@0: contributors may be used to endorse or promote products nuclear@0: derived from this software without specific prior nuclear@0: written permission of the assimp team. nuclear@0: nuclear@0: THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS nuclear@0: "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT nuclear@0: LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR nuclear@0: A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT nuclear@0: OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, nuclear@0: SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT nuclear@0: LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, nuclear@0: DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY nuclear@0: THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT nuclear@0: (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE nuclear@0: OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. nuclear@0: --------------------------------------------------------------------------- nuclear@0: */ nuclear@0: nuclear@0: #include "AssimpPCH.h" nuclear@0: #ifndef ASSIMP_BUILD_NO_BVH_IMPORTER nuclear@0: nuclear@0: #include "BVHLoader.h" nuclear@0: #include "fast_atof.h" nuclear@0: #include "SkeletonMeshBuilder.h" nuclear@0: nuclear@0: using namespace Assimp; nuclear@0: nuclear@0: static const aiImporterDesc desc = { nuclear@0: "BVH Importer (MoCap)", nuclear@0: "", nuclear@0: "", nuclear@0: "", nuclear@0: aiImporterFlags_SupportTextFlavour, nuclear@0: 0, nuclear@0: 0, nuclear@0: 0, nuclear@0: 0, nuclear@0: "bvh" nuclear@0: }; nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: // Constructor to be privately used by Importer nuclear@0: BVHLoader::BVHLoader() nuclear@0: : noSkeletonMesh() nuclear@0: {} nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: // Destructor, private as well nuclear@0: BVHLoader::~BVHLoader() nuclear@0: {} nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: // Returns whether the class can handle the format of the given file. nuclear@0: bool BVHLoader::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool cs) const nuclear@0: { nuclear@0: // check file extension nuclear@0: const std::string extension = GetExtension(pFile); nuclear@0: nuclear@0: if( extension == "bvh") nuclear@0: return true; nuclear@0: nuclear@0: if ((!extension.length() || cs) && pIOHandler) { nuclear@0: const char* tokens[] = {"HIERARCHY"}; nuclear@0: return SearchFileHeaderForToken(pIOHandler,pFile,tokens,1); nuclear@0: } nuclear@0: return false; nuclear@0: } nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: void BVHLoader::SetupProperties(const Importer* pImp) nuclear@0: { nuclear@0: noSkeletonMesh = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_NO_SKELETON_MESHES,0) != 0; nuclear@0: } nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: // Loader meta information nuclear@0: const aiImporterDesc* BVHLoader::GetInfo () const nuclear@0: { nuclear@0: return &desc; nuclear@0: } nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: // Imports the given file into the given scene structure. nuclear@0: void BVHLoader::InternReadFile( const std::string& pFile, aiScene* pScene, IOSystem* pIOHandler) nuclear@0: { nuclear@0: mFileName = pFile; nuclear@0: nuclear@0: // read file into memory nuclear@0: boost::scoped_ptr file( pIOHandler->Open( pFile)); nuclear@0: if( file.get() == NULL) nuclear@0: throw DeadlyImportError( "Failed to open file " + pFile + "."); nuclear@0: nuclear@0: size_t fileSize = file->FileSize(); nuclear@0: if( fileSize == 0) nuclear@0: throw DeadlyImportError( "File is too small."); nuclear@0: nuclear@0: mBuffer.resize( fileSize); nuclear@0: file->Read( &mBuffer.front(), 1, fileSize); nuclear@0: nuclear@0: // start reading nuclear@0: mReader = mBuffer.begin(); nuclear@0: mLine = 1; nuclear@0: ReadStructure( pScene); nuclear@0: nuclear@0: if (!noSkeletonMesh) { nuclear@0: // build a dummy mesh for the skeleton so that we see something at least nuclear@0: SkeletonMeshBuilder meshBuilder( pScene); nuclear@0: } nuclear@0: nuclear@0: // construct an animation from all the motion data we read nuclear@0: CreateAnimation( pScene); nuclear@0: } nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: // Reads the file nuclear@0: void BVHLoader::ReadStructure( aiScene* pScene) nuclear@0: { nuclear@0: // first comes hierarchy nuclear@0: std::string header = GetNextToken(); nuclear@0: if( header != "HIERARCHY") nuclear@0: ThrowException( "Expected header string \"HIERARCHY\"."); nuclear@0: ReadHierarchy( pScene); nuclear@0: nuclear@0: // then comes the motion data nuclear@0: std::string motion = GetNextToken(); nuclear@0: if( motion != "MOTION") nuclear@0: ThrowException( "Expected beginning of motion data \"MOTION\"."); nuclear@0: ReadMotion( pScene); nuclear@0: } nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: // Reads the hierarchy nuclear@0: void BVHLoader::ReadHierarchy( aiScene* pScene) nuclear@0: { nuclear@0: std::string root = GetNextToken(); nuclear@0: if( root != "ROOT") nuclear@0: ThrowException( "Expected root node \"ROOT\"."); nuclear@0: nuclear@0: // Go read the hierarchy from here nuclear@0: pScene->mRootNode = ReadNode(); nuclear@0: } nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: // Reads a node and recursively its childs and returns the created node; nuclear@0: aiNode* BVHLoader::ReadNode() nuclear@0: { nuclear@0: // first token is name nuclear@0: std::string nodeName = GetNextToken(); nuclear@0: if( nodeName.empty() || nodeName == "{") nuclear@0: ThrowException( boost::str( boost::format( "Expected node name, but found \"%s\".") % nodeName)); nuclear@0: nuclear@0: // then an opening brace should follow nuclear@0: std::string openBrace = GetNextToken(); nuclear@0: if( openBrace != "{") nuclear@0: ThrowException( boost::str( boost::format( "Expected opening brace \"{\", but found \"%s\".") % openBrace)); nuclear@0: nuclear@0: // Create a node nuclear@0: aiNode* node = new aiNode( nodeName); nuclear@0: std::vector childNodes; nuclear@0: nuclear@0: // and create an bone entry for it nuclear@0: mNodes.push_back( Node( node)); nuclear@0: Node& internNode = mNodes.back(); nuclear@0: nuclear@0: // now read the node's contents nuclear@0: while( 1) nuclear@0: { nuclear@0: std::string token = GetNextToken(); nuclear@0: nuclear@0: // node offset to parent node nuclear@0: if( token == "OFFSET") nuclear@0: ReadNodeOffset( node); nuclear@0: else if( token == "CHANNELS") nuclear@0: ReadNodeChannels( internNode); nuclear@0: else if( token == "JOINT") nuclear@0: { nuclear@0: // child node follows nuclear@0: aiNode* child = ReadNode(); nuclear@0: child->mParent = node; nuclear@0: childNodes.push_back( child); nuclear@0: } nuclear@0: else if( token == "End") nuclear@0: { nuclear@0: // The real symbol is "End Site". Second part comes in a separate token nuclear@0: std::string siteToken = GetNextToken(); nuclear@0: if( siteToken != "Site") nuclear@0: ThrowException( boost::str( boost::format( "Expected \"End Site\" keyword, but found \"%s %s\".") % token % siteToken)); nuclear@0: nuclear@0: aiNode* child = ReadEndSite( nodeName); nuclear@0: child->mParent = node; nuclear@0: childNodes.push_back( child); nuclear@0: } nuclear@0: else if( token == "}") nuclear@0: { nuclear@0: // we're done with that part of the hierarchy nuclear@0: break; nuclear@0: } else nuclear@0: { nuclear@0: // everything else is a parse error nuclear@0: ThrowException( boost::str( boost::format( "Unknown keyword \"%s\".") % token)); nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: // add the child nodes if there are any nuclear@0: if( childNodes.size() > 0) nuclear@0: { nuclear@0: node->mNumChildren = childNodes.size(); nuclear@0: node->mChildren = new aiNode*[node->mNumChildren]; nuclear@0: std::copy( childNodes.begin(), childNodes.end(), node->mChildren); nuclear@0: } nuclear@0: nuclear@0: // and return the sub-hierarchy we built here nuclear@0: return node; nuclear@0: } nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: // Reads an end node and returns the created node. nuclear@0: aiNode* BVHLoader::ReadEndSite( const std::string& pParentName) nuclear@0: { nuclear@0: // check opening brace nuclear@0: std::string openBrace = GetNextToken(); nuclear@0: if( openBrace != "{") nuclear@0: ThrowException( boost::str( boost::format( "Expected opening brace \"{\", but found \"%s\".") % openBrace)); nuclear@0: nuclear@0: // Create a node nuclear@0: aiNode* node = new aiNode( "EndSite_" + pParentName); nuclear@0: nuclear@0: // now read the node's contents. Only possible entry is "OFFSET" nuclear@0: while( 1) nuclear@0: { nuclear@0: std::string token = GetNextToken(); nuclear@0: nuclear@0: // end node's offset nuclear@0: if( token == "OFFSET") nuclear@0: { nuclear@0: ReadNodeOffset( node); nuclear@0: } nuclear@0: else if( token == "}") nuclear@0: { nuclear@0: // we're done with the end node nuclear@0: break; nuclear@0: } else nuclear@0: { nuclear@0: // everything else is a parse error nuclear@0: ThrowException( boost::str( boost::format( "Unknown keyword \"%s\".") % token)); nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: // and return the sub-hierarchy we built here nuclear@0: return node; nuclear@0: } nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: // Reads a node offset for the given node nuclear@0: void BVHLoader::ReadNodeOffset( aiNode* pNode) nuclear@0: { nuclear@0: // Offset consists of three floats to read nuclear@0: aiVector3D offset; nuclear@0: offset.x = GetNextTokenAsFloat(); nuclear@0: offset.y = GetNextTokenAsFloat(); nuclear@0: offset.z = GetNextTokenAsFloat(); nuclear@0: nuclear@0: // build a transformation matrix from it nuclear@0: pNode->mTransformation = aiMatrix4x4( 1.0f, 0.0f, 0.0f, offset.x, 0.0f, 1.0f, 0.0f, offset.y, nuclear@0: 0.0f, 0.0f, 1.0f, offset.z, 0.0f, 0.0f, 0.0f, 1.0f); nuclear@0: } nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: // Reads the animation channels for the given node nuclear@0: void BVHLoader::ReadNodeChannels( BVHLoader::Node& pNode) nuclear@0: { nuclear@0: // number of channels. Use the float reader because we're lazy nuclear@0: float numChannelsFloat = GetNextTokenAsFloat(); nuclear@0: unsigned int numChannels = (unsigned int) numChannelsFloat; nuclear@0: nuclear@0: for( unsigned int a = 0; a < numChannels; a++) nuclear@0: { nuclear@0: std::string channelToken = GetNextToken(); nuclear@0: nuclear@0: if( channelToken == "Xposition") nuclear@0: pNode.mChannels.push_back( Channel_PositionX); nuclear@0: else if( channelToken == "Yposition") nuclear@0: pNode.mChannels.push_back( Channel_PositionY); nuclear@0: else if( channelToken == "Zposition") nuclear@0: pNode.mChannels.push_back( Channel_PositionZ); nuclear@0: else if( channelToken == "Xrotation") nuclear@0: pNode.mChannels.push_back( Channel_RotationX); nuclear@0: else if( channelToken == "Yrotation") nuclear@0: pNode.mChannels.push_back( Channel_RotationY); nuclear@0: else if( channelToken == "Zrotation") nuclear@0: pNode.mChannels.push_back( Channel_RotationZ); nuclear@0: else nuclear@0: ThrowException( boost::str( boost::format( "Invalid channel specifier \"%s\".") % channelToken)); nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: // Reads the motion data nuclear@0: void BVHLoader::ReadMotion( aiScene* /*pScene*/) nuclear@0: { nuclear@0: // Read number of frames nuclear@0: std::string tokenFrames = GetNextToken(); nuclear@0: if( tokenFrames != "Frames:") nuclear@0: ThrowException( boost::str( boost::format( "Expected frame count \"Frames:\", but found \"%s\".") % tokenFrames)); nuclear@0: nuclear@0: float numFramesFloat = GetNextTokenAsFloat(); nuclear@0: mAnimNumFrames = (unsigned int) numFramesFloat; nuclear@0: nuclear@0: // Read frame duration nuclear@0: std::string tokenDuration1 = GetNextToken(); nuclear@0: std::string tokenDuration2 = GetNextToken(); nuclear@0: if( tokenDuration1 != "Frame" || tokenDuration2 != "Time:") nuclear@0: ThrowException( boost::str( boost::format( "Expected frame duration \"Frame Time:\", but found \"%s %s\".") % tokenDuration1 % tokenDuration2)); nuclear@0: nuclear@0: mAnimTickDuration = GetNextTokenAsFloat(); nuclear@0: nuclear@0: // resize value vectors for each node nuclear@0: for( std::vector::iterator it = mNodes.begin(); it != mNodes.end(); ++it) nuclear@0: it->mChannelValues.reserve( it->mChannels.size() * mAnimNumFrames); nuclear@0: nuclear@0: // now read all the data and store it in the corresponding node's value vector nuclear@0: for( unsigned int frame = 0; frame < mAnimNumFrames; ++frame) nuclear@0: { nuclear@0: // on each line read the values for all nodes nuclear@0: for( std::vector::iterator it = mNodes.begin(); it != mNodes.end(); ++it) nuclear@0: { nuclear@0: // get as many values as the node has channels nuclear@0: for( unsigned int c = 0; c < it->mChannels.size(); ++c) nuclear@0: it->mChannelValues.push_back( GetNextTokenAsFloat()); nuclear@0: } nuclear@0: nuclear@0: // after one frame worth of values for all nodes there should be a newline, but we better don't rely on it nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: // Retrieves the next token nuclear@0: std::string BVHLoader::GetNextToken() nuclear@0: { nuclear@0: // skip any preceeding whitespace nuclear@0: while( mReader != mBuffer.end()) nuclear@0: { nuclear@0: if( !isspace( *mReader)) nuclear@0: break; nuclear@0: nuclear@0: // count lines nuclear@0: if( *mReader == '\n') nuclear@0: mLine++; nuclear@0: nuclear@0: ++mReader; nuclear@0: } nuclear@0: nuclear@0: // collect all chars till the next whitespace. BVH is easy in respect to that. nuclear@0: std::string token; nuclear@0: while( mReader != mBuffer.end()) nuclear@0: { nuclear@0: if( isspace( *mReader)) nuclear@0: break; nuclear@0: nuclear@0: token.push_back( *mReader); nuclear@0: ++mReader; nuclear@0: nuclear@0: // little extra logic to make sure braces are counted correctly nuclear@0: if( token == "{" || token == "}") nuclear@0: break; nuclear@0: } nuclear@0: nuclear@0: // empty token means end of file, which is just fine nuclear@0: return token; nuclear@0: } nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: // Reads the next token as a float nuclear@0: float BVHLoader::GetNextTokenAsFloat() nuclear@0: { nuclear@0: std::string token = GetNextToken(); nuclear@0: if( token.empty()) nuclear@0: ThrowException( "Unexpected end of file while trying to read a float"); nuclear@0: nuclear@0: // check if the float is valid by testing if the atof() function consumed every char of the token nuclear@0: const char* ctoken = token.c_str(); nuclear@0: float result = 0.0f; nuclear@0: ctoken = fast_atoreal_move( ctoken, result); nuclear@0: nuclear@0: if( ctoken != token.c_str() + token.length()) nuclear@0: ThrowException( boost::str( boost::format( "Expected a floating point number, but found \"%s\".") % token)); nuclear@0: nuclear@0: return result; nuclear@0: } nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: // Aborts the file reading with an exception nuclear@0: void BVHLoader::ThrowException( const std::string& pError) nuclear@0: { nuclear@0: throw DeadlyImportError( boost::str( boost::format( "%s:%d - %s") % mFileName % mLine % pError)); nuclear@0: } nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: // Constructs an animation for the motion data and stores it in the given scene nuclear@0: void BVHLoader::CreateAnimation( aiScene* pScene) nuclear@0: { nuclear@0: // create the animation nuclear@0: pScene->mNumAnimations = 1; nuclear@0: pScene->mAnimations = new aiAnimation*[1]; nuclear@0: aiAnimation* anim = new aiAnimation; nuclear@0: pScene->mAnimations[0] = anim; nuclear@0: nuclear@0: // put down the basic parameters nuclear@0: anim->mName.Set( "Motion"); nuclear@0: anim->mTicksPerSecond = 1.0 / double( mAnimTickDuration); nuclear@0: anim->mDuration = double( mAnimNumFrames - 1); nuclear@0: nuclear@0: // now generate the tracks for all nodes nuclear@0: anim->mNumChannels = mNodes.size(); nuclear@0: anim->mChannels = new aiNodeAnim*[anim->mNumChannels]; nuclear@0: nuclear@0: // FIX: set the array elements to NULL to ensure proper deletion if an exception is thrown nuclear@0: for (unsigned int i = 0; i < anim->mNumChannels;++i) nuclear@0: anim->mChannels[i] = NULL; nuclear@0: nuclear@0: for( unsigned int a = 0; a < anim->mNumChannels; a++) nuclear@0: { nuclear@0: const Node& node = mNodes[a]; nuclear@0: const std::string nodeName = std::string( node.mNode->mName.data ); nuclear@0: aiNodeAnim* nodeAnim = new aiNodeAnim; nuclear@0: anim->mChannels[a] = nodeAnim; nuclear@0: nodeAnim->mNodeName.Set( nodeName); nuclear@0: nuclear@0: // translational part, if given nuclear@0: if( node.mChannels.size() == 6) nuclear@0: { nuclear@0: nodeAnim->mNumPositionKeys = mAnimNumFrames; nuclear@0: nodeAnim->mPositionKeys = new aiVectorKey[mAnimNumFrames]; nuclear@0: aiVectorKey* poskey = nodeAnim->mPositionKeys; nuclear@0: for( unsigned int fr = 0; fr < mAnimNumFrames; ++fr) nuclear@0: { nuclear@0: poskey->mTime = double( fr); nuclear@0: nuclear@0: // Now compute all translations in the right order nuclear@0: for( unsigned int channel = 0; channel < 3; ++channel) nuclear@0: { nuclear@0: switch( node.mChannels[channel]) nuclear@0: { nuclear@0: case Channel_PositionX: poskey->mValue.x = node.mChannelValues[fr * node.mChannels.size() + channel]; break; nuclear@0: case Channel_PositionY: poskey->mValue.y = node.mChannelValues[fr * node.mChannels.size() + channel]; break; nuclear@0: case Channel_PositionZ: poskey->mValue.z = node.mChannelValues[fr * node.mChannels.size() + channel]; break; nuclear@0: default: throw DeadlyImportError( "Unexpected animation channel setup at node " + nodeName ); nuclear@0: } nuclear@0: } nuclear@0: ++poskey; nuclear@0: } nuclear@0: } else nuclear@0: { nuclear@0: // if no translation part is given, put a default sequence nuclear@0: aiVector3D nodePos( node.mNode->mTransformation.a4, node.mNode->mTransformation.b4, node.mNode->mTransformation.c4); nuclear@0: nodeAnim->mNumPositionKeys = 1; nuclear@0: nodeAnim->mPositionKeys = new aiVectorKey[1]; nuclear@0: nodeAnim->mPositionKeys[0].mTime = 0.0; nuclear@0: nodeAnim->mPositionKeys[0].mValue = nodePos; nuclear@0: } nuclear@0: nuclear@0: // rotation part. Always present. First find value offsets nuclear@0: { nuclear@0: unsigned int rotOffset = 0; nuclear@0: if( node.mChannels.size() == 6) nuclear@0: { nuclear@0: // Offset all further calculations nuclear@0: rotOffset = 3; nuclear@0: } nuclear@0: nuclear@0: // Then create the number of rotation keys nuclear@0: nodeAnim->mNumRotationKeys = mAnimNumFrames; nuclear@0: nodeAnim->mRotationKeys = new aiQuatKey[mAnimNumFrames]; nuclear@0: aiQuatKey* rotkey = nodeAnim->mRotationKeys; nuclear@0: for( unsigned int fr = 0; fr < mAnimNumFrames; ++fr) nuclear@0: { nuclear@0: aiMatrix4x4 temp; nuclear@0: aiMatrix3x3 rotMatrix; nuclear@0: nuclear@0: for( unsigned int channel = 0; channel < 3; ++channel) nuclear@0: { nuclear@0: // translate ZXY euler angels into a quaternion nuclear@0: const float angle = node.mChannelValues[fr * node.mChannels.size() + rotOffset + channel] * float( AI_MATH_PI) / 180.0f; nuclear@0: nuclear@0: // Compute rotation transformations in the right order nuclear@0: switch (node.mChannels[rotOffset+channel]) nuclear@0: { nuclear@0: case Channel_RotationX: aiMatrix4x4::RotationX( angle, temp); rotMatrix *= aiMatrix3x3( temp); break; nuclear@0: case Channel_RotationY: aiMatrix4x4::RotationY( angle, temp); rotMatrix *= aiMatrix3x3( temp); break; nuclear@0: case Channel_RotationZ: aiMatrix4x4::RotationZ( angle, temp); rotMatrix *= aiMatrix3x3( temp); break; nuclear@0: default: throw DeadlyImportError( "Unexpected animation channel setup at node " + nodeName ); nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: rotkey->mTime = double( fr); nuclear@0: rotkey->mValue = aiQuaternion( rotMatrix); nuclear@0: ++rotkey; nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: // scaling part. Always just a default track nuclear@0: { nuclear@0: nodeAnim->mNumScalingKeys = 1; nuclear@0: nodeAnim->mScalingKeys = new aiVectorKey[1]; nuclear@0: nodeAnim->mScalingKeys[0].mTime = 0.0; nuclear@0: nodeAnim->mScalingKeys[0].mValue.Set( 1.0f, 1.0f, 1.0f); nuclear@0: } nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: #endif // !! ASSIMP_BUILD_NO_BVH_IMPORTER