1 /** Implementation of the BVH loader */
2 /*
3 ---------------------------------------------------------------------------
4 Open Asset Import Library (assimp)
5 ---------------------------------------------------------------------------
6 
7 Copyright (c) 2006-2017, assimp team
8 
9 
10 All rights reserved.
11 
12 Redistribution and use of this software in source and binary forms,
13 with or without modification, are permitted provided that the following
14 conditions are met:
15 
16 * Redistributions of source code must retain the above
17 copyright notice, this list of conditions and the
18 following disclaimer.
19 
20 * Redistributions in binary form must reproduce the above
21 copyright notice, this list of conditions and the
22 following disclaimer in the documentation and/or other
23 materials provided with the distribution.
24 
25 * Neither the name of the assimp team, nor the names of its
26 contributors may be used to endorse or promote products
27 derived from this software without specific prior
28 written permission of the assimp team.
29 
30 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
31 "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
32 LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
33 A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
34 OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
35 SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
36 LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
37 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
38 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
39 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
40 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
41 ---------------------------------------------------------------------------
42 */
43 
44 
45 #ifndef ASSIMP_BUILD_NO_BVH_IMPORTER
46 
47 #include "BVHLoader.h"
48 #include "fast_atof.h"
49 #include "SkeletonMeshBuilder.h"
50 #include <assimp/Importer.hpp>
51 #include <memory>
52 #include "TinyFormatter.h"
53 #include <assimp/IOSystem.hpp>
54 #include <assimp/scene.h>
55 #include <assimp/importerdesc.h>
56 
57 using namespace Assimp;
58 using namespace Assimp::Formatter;
59 
60 static const aiImporterDesc desc = {
61     "BVH Importer (MoCap)",
62     "",
63     "",
64     "",
65     aiImporterFlags_SupportTextFlavour,
66     0,
67     0,
68     0,
69     0,
70     "bvh"
71 };
72 
73 // ------------------------------------------------------------------------------------------------
74 // Constructor to be privately used by Importer
BVHLoader()75 BVHLoader::BVHLoader()
76     : mLine(),
77     mAnimTickDuration(),
78     mAnimNumFrames(),
79     noSkeletonMesh()
80 {}
81 
82 // ------------------------------------------------------------------------------------------------
83 // Destructor, private as well
~BVHLoader()84 BVHLoader::~BVHLoader()
85 {}
86 
87 // ------------------------------------------------------------------------------------------------
88 // Returns whether the class can handle the format of the given file.
CanRead(const std::string & pFile,IOSystem * pIOHandler,bool cs) const89 bool BVHLoader::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool cs) const
90 {
91     // check file extension
92     const std::string extension = GetExtension(pFile);
93 
94     if( extension == "bvh")
95         return true;
96 
97     if ((!extension.length() || cs) && pIOHandler) {
98         const char* tokens[] = {"HIERARCHY"};
99         return SearchFileHeaderForToken(pIOHandler,pFile,tokens,1);
100     }
101     return false;
102 }
103 
104 // ------------------------------------------------------------------------------------------------
SetupProperties(const Importer * pImp)105 void BVHLoader::SetupProperties(const Importer* pImp)
106 {
107     noSkeletonMesh = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_NO_SKELETON_MESHES,0) != 0;
108 }
109 
110 // ------------------------------------------------------------------------------------------------
111 // Loader meta information
GetInfo() const112 const aiImporterDesc* BVHLoader::GetInfo () const
113 {
114     return &desc;
115 }
116 
117 // ------------------------------------------------------------------------------------------------
118 // Imports the given file into the given scene structure.
InternReadFile(const std::string & pFile,aiScene * pScene,IOSystem * pIOHandler)119 void BVHLoader::InternReadFile( const std::string& pFile, aiScene* pScene, IOSystem* pIOHandler)
120 {
121     mFileName = pFile;
122 
123     // read file into memory
124     std::unique_ptr<IOStream> file( pIOHandler->Open( pFile));
125     if( file.get() == NULL)
126         throw DeadlyImportError( "Failed to open file " + pFile + ".");
127 
128     size_t fileSize = file->FileSize();
129     if( fileSize == 0)
130         throw DeadlyImportError( "File is too small.");
131 
132     mBuffer.resize( fileSize);
133     file->Read( &mBuffer.front(), 1, fileSize);
134 
135     // start reading
136     mReader = mBuffer.begin();
137     mLine = 1;
138     ReadStructure( pScene);
139 
140     if (!noSkeletonMesh) {
141         // build a dummy mesh for the skeleton so that we see something at least
142         SkeletonMeshBuilder meshBuilder( pScene);
143     }
144 
145     // construct an animation from all the motion data we read
146     CreateAnimation( pScene);
147 }
148 
149 // ------------------------------------------------------------------------------------------------
150 // Reads the file
ReadStructure(aiScene * pScene)151 void BVHLoader::ReadStructure( aiScene* pScene)
152 {
153     // first comes hierarchy
154     std::string header = GetNextToken();
155     if( header != "HIERARCHY")
156         ThrowException( "Expected header string \"HIERARCHY\".");
157     ReadHierarchy( pScene);
158 
159     // then comes the motion data
160     std::string motion = GetNextToken();
161     if( motion != "MOTION")
162         ThrowException( "Expected beginning of motion data \"MOTION\".");
163     ReadMotion( pScene);
164 }
165 
166 // ------------------------------------------------------------------------------------------------
167 // Reads the hierarchy
ReadHierarchy(aiScene * pScene)168 void BVHLoader::ReadHierarchy( aiScene* pScene)
169 {
170     std::string root = GetNextToken();
171     if( root != "ROOT")
172         ThrowException( "Expected root node \"ROOT\".");
173 
174     // Go read the hierarchy from here
175     pScene->mRootNode = ReadNode();
176 }
177 
178 // ------------------------------------------------------------------------------------------------
179 // Reads a node and recursively its childs and returns the created node;
ReadNode()180 aiNode* BVHLoader::ReadNode()
181 {
182     // first token is name
183     std::string nodeName = GetNextToken();
184     if( nodeName.empty() || nodeName == "{")
185         ThrowException( format() << "Expected node name, but found \"" << nodeName << "\"." );
186 
187     // then an opening brace should follow
188     std::string openBrace = GetNextToken();
189     if( openBrace != "{")
190         ThrowException( format() << "Expected opening brace \"{\", but found \"" << openBrace << "\"." );
191 
192     // Create a node
193     aiNode* node = new aiNode( nodeName);
194     std::vector<aiNode*> childNodes;
195 
196     // and create an bone entry for it
197     mNodes.push_back( Node( node));
198     Node& internNode = mNodes.back();
199 
200     // now read the node's contents
201     while( 1)
202     {
203         std::string token = GetNextToken();
204 
205         // node offset to parent node
206         if( token == "OFFSET")
207             ReadNodeOffset( node);
208         else if( token == "CHANNELS")
209             ReadNodeChannels( internNode);
210         else if( token == "JOINT")
211         {
212             // child node follows
213             aiNode* child = ReadNode();
214             child->mParent = node;
215             childNodes.push_back( child);
216         }
217         else if( token == "End")
218         {
219             // The real symbol is "End Site". Second part comes in a separate token
220             std::string siteToken = GetNextToken();
221             if( siteToken != "Site")
222                 ThrowException( format() << "Expected \"End Site\" keyword, but found \"" << token << " " << siteToken << "\"." );
223 
224             aiNode* child = ReadEndSite( nodeName);
225             child->mParent = node;
226             childNodes.push_back( child);
227         }
228         else if( token == "}")
229         {
230             // we're done with that part of the hierarchy
231             break;
232         } else
233         {
234             // everything else is a parse error
235             ThrowException( format() << "Unknown keyword \"" << token << "\"." );
236         }
237     }
238 
239     // add the child nodes if there are any
240     if( childNodes.size() > 0)
241     {
242         node->mNumChildren = static_cast<unsigned int>(childNodes.size());
243         node->mChildren = new aiNode*[node->mNumChildren];
244         std::copy( childNodes.begin(), childNodes.end(), node->mChildren);
245     }
246 
247     // and return the sub-hierarchy we built here
248     return node;
249 }
250 
251 // ------------------------------------------------------------------------------------------------
252 // Reads an end node and returns the created node.
ReadEndSite(const std::string & pParentName)253 aiNode* BVHLoader::ReadEndSite( const std::string& pParentName)
254 {
255     // check opening brace
256     std::string openBrace = GetNextToken();
257     if( openBrace != "{")
258         ThrowException( format() << "Expected opening brace \"{\", but found \"" << openBrace << "\".");
259 
260     // Create a node
261     aiNode* node = new aiNode( "EndSite_" + pParentName);
262 
263     // now read the node's contents. Only possible entry is "OFFSET"
264     while( 1)
265     {
266         std::string token = GetNextToken();
267 
268         // end node's offset
269         if( token == "OFFSET")
270         {
271             ReadNodeOffset( node);
272         }
273         else if( token == "}")
274         {
275             // we're done with the end node
276             break;
277         } else
278         {
279             // everything else is a parse error
280             ThrowException( format() << "Unknown keyword \"" << token << "\"." );
281         }
282     }
283 
284     // and return the sub-hierarchy we built here
285     return node;
286 }
287 // ------------------------------------------------------------------------------------------------
288 // Reads a node offset for the given node
ReadNodeOffset(aiNode * pNode)289 void BVHLoader::ReadNodeOffset( aiNode* pNode)
290 {
291     // Offset consists of three floats to read
292     aiVector3D offset;
293     offset.x = GetNextTokenAsFloat();
294     offset.y = GetNextTokenAsFloat();
295     offset.z = GetNextTokenAsFloat();
296 
297     // build a transformation matrix from it
298     pNode->mTransformation = aiMatrix4x4( 1.0f, 0.0f, 0.0f, offset.x, 0.0f, 1.0f, 0.0f, offset.y,
299         0.0f, 0.0f, 1.0f, offset.z, 0.0f, 0.0f, 0.0f, 1.0f);
300 }
301 
302 // ------------------------------------------------------------------------------------------------
303 // Reads the animation channels for the given node
ReadNodeChannels(BVHLoader::Node & pNode)304 void BVHLoader::ReadNodeChannels( BVHLoader::Node& pNode)
305 {
306     // number of channels. Use the float reader because we're lazy
307     float numChannelsFloat = GetNextTokenAsFloat();
308     unsigned int numChannels = (unsigned int) numChannelsFloat;
309 
310     for( unsigned int a = 0; a < numChannels; a++)
311     {
312         std::string channelToken = GetNextToken();
313 
314         if( channelToken == "Xposition")
315             pNode.mChannels.push_back( Channel_PositionX);
316         else if( channelToken == "Yposition")
317             pNode.mChannels.push_back( Channel_PositionY);
318         else if( channelToken == "Zposition")
319             pNode.mChannels.push_back( Channel_PositionZ);
320         else if( channelToken == "Xrotation")
321             pNode.mChannels.push_back( Channel_RotationX);
322         else if( channelToken == "Yrotation")
323             pNode.mChannels.push_back( Channel_RotationY);
324         else if( channelToken == "Zrotation")
325             pNode.mChannels.push_back( Channel_RotationZ);
326         else
327             ThrowException( format() << "Invalid channel specifier \"" << channelToken << "\"." );
328     }
329 }
330 
331 // ------------------------------------------------------------------------------------------------
332 // Reads the motion data
ReadMotion(aiScene *)333 void BVHLoader::ReadMotion( aiScene* /*pScene*/)
334 {
335     // Read number of frames
336     std::string tokenFrames = GetNextToken();
337     if( tokenFrames != "Frames:")
338         ThrowException( format() << "Expected frame count \"Frames:\", but found \"" << tokenFrames << "\".");
339 
340     float numFramesFloat = GetNextTokenAsFloat();
341     mAnimNumFrames = (unsigned int) numFramesFloat;
342 
343     // Read frame duration
344     std::string tokenDuration1 = GetNextToken();
345     std::string tokenDuration2 = GetNextToken();
346     if( tokenDuration1 != "Frame" || tokenDuration2 != "Time:")
347         ThrowException( format() << "Expected frame duration \"Frame Time:\", but found \"" << tokenDuration1 << " " << tokenDuration2 << "\"." );
348 
349     mAnimTickDuration = GetNextTokenAsFloat();
350 
351     // resize value vectors for each node
352     for( std::vector<Node>::iterator it = mNodes.begin(); it != mNodes.end(); ++it)
353         it->mChannelValues.reserve( it->mChannels.size() * mAnimNumFrames);
354 
355     // now read all the data and store it in the corresponding node's value vector
356     for( unsigned int frame = 0; frame < mAnimNumFrames; ++frame)
357     {
358         // on each line read the values for all nodes
359         for( std::vector<Node>::iterator it = mNodes.begin(); it != mNodes.end(); ++it)
360         {
361             // get as many values as the node has channels
362             for( unsigned int c = 0; c < it->mChannels.size(); ++c)
363                 it->mChannelValues.push_back( GetNextTokenAsFloat());
364         }
365 
366         // after one frame worth of values for all nodes there should be a newline, but we better don't rely on it
367     }
368 }
369 
370 // ------------------------------------------------------------------------------------------------
371 // Retrieves the next token
GetNextToken()372 std::string BVHLoader::GetNextToken()
373 {
374     // skip any preceding whitespace
375     while( mReader != mBuffer.end())
376     {
377         if( !isspace( *mReader))
378             break;
379 
380         // count lines
381         if( *mReader == '\n')
382             mLine++;
383 
384         ++mReader;
385     }
386 
387     // collect all chars till the next whitespace. BVH is easy in respect to that.
388     std::string token;
389     while( mReader != mBuffer.end())
390     {
391         if( isspace( *mReader))
392             break;
393 
394         token.push_back( *mReader);
395         ++mReader;
396 
397         // little extra logic to make sure braces are counted correctly
398         if( token == "{" || token == "}")
399             break;
400     }
401 
402     // empty token means end of file, which is just fine
403     return token;
404 }
405 
406 // ------------------------------------------------------------------------------------------------
407 // Reads the next token as a float
GetNextTokenAsFloat()408 float BVHLoader::GetNextTokenAsFloat()
409 {
410     std::string token = GetNextToken();
411     if( token.empty())
412         ThrowException( "Unexpected end of file while trying to read a float");
413 
414     // check if the float is valid by testing if the atof() function consumed every char of the token
415     const char* ctoken = token.c_str();
416     float result = 0.0f;
417     ctoken = fast_atoreal_move<float>( ctoken, result);
418 
419     if( ctoken != token.c_str() + token.length())
420         ThrowException( format() << "Expected a floating point number, but found \"" << token << "\"." );
421 
422     return result;
423 }
424 
425 // ------------------------------------------------------------------------------------------------
426 // Aborts the file reading with an exception
ThrowException(const std::string & pError)427 AI_WONT_RETURN void BVHLoader::ThrowException( const std::string& pError)
428 {
429     throw DeadlyImportError( format() << mFileName << ":" << mLine << " - " << pError);
430 }
431 
432 // ------------------------------------------------------------------------------------------------
433 // Constructs an animation for the motion data and stores it in the given scene
CreateAnimation(aiScene * pScene)434 void BVHLoader::CreateAnimation( aiScene* pScene)
435 {
436     // create the animation
437     pScene->mNumAnimations = 1;
438     pScene->mAnimations = new aiAnimation*[1];
439     aiAnimation* anim = new aiAnimation;
440     pScene->mAnimations[0] = anim;
441 
442     // put down the basic parameters
443     anim->mName.Set( "Motion");
444     anim->mTicksPerSecond = 1.0 / double( mAnimTickDuration);
445     anim->mDuration = double( mAnimNumFrames - 1);
446 
447     // now generate the tracks for all nodes
448     anim->mNumChannels = static_cast<unsigned int>(mNodes.size());
449     anim->mChannels = new aiNodeAnim*[anim->mNumChannels];
450 
451     // FIX: set the array elements to NULL to ensure proper deletion if an exception is thrown
452     for (unsigned int i = 0; i < anim->mNumChannels;++i)
453         anim->mChannels[i] = NULL;
454 
455     for( unsigned int a = 0; a < anim->mNumChannels; a++)
456     {
457         const Node& node = mNodes[a];
458         const std::string nodeName = std::string( node.mNode->mName.data );
459         aiNodeAnim* nodeAnim = new aiNodeAnim;
460         anim->mChannels[a] = nodeAnim;
461         nodeAnim->mNodeName.Set( nodeName);
462 
463         // translational part, if given
464         if( node.mChannels.size() == 6)
465         {
466             nodeAnim->mNumPositionKeys = mAnimNumFrames;
467             nodeAnim->mPositionKeys = new aiVectorKey[mAnimNumFrames];
468             aiVectorKey* poskey = nodeAnim->mPositionKeys;
469             for( unsigned int fr = 0; fr < mAnimNumFrames; ++fr)
470             {
471                 poskey->mTime = double( fr);
472 
473                 // Now compute all translations in the right order
474                 for( unsigned int channel = 0; channel < 3; ++channel)
475                 {
476                     switch( node.mChannels[channel])
477                     {
478                     case Channel_PositionX: poskey->mValue.x = node.mChannelValues[fr * node.mChannels.size() + channel]; break;
479                     case Channel_PositionY: poskey->mValue.y = node.mChannelValues[fr * node.mChannels.size() + channel]; break;
480                     case Channel_PositionZ: poskey->mValue.z = node.mChannelValues[fr * node.mChannels.size() + channel]; break;
481                     default: throw DeadlyImportError( "Unexpected animation channel setup at node " + nodeName );
482                     }
483                 }
484                 ++poskey;
485             }
486         } else
487         {
488             // if no translation part is given, put a default sequence
489             aiVector3D nodePos( node.mNode->mTransformation.a4, node.mNode->mTransformation.b4, node.mNode->mTransformation.c4);
490             nodeAnim->mNumPositionKeys = 1;
491             nodeAnim->mPositionKeys = new aiVectorKey[1];
492             nodeAnim->mPositionKeys[0].mTime = 0.0;
493             nodeAnim->mPositionKeys[0].mValue = nodePos;
494         }
495 
496         // rotation part. Always present. First find value offsets
497         {
498             unsigned int rotOffset  = 0;
499             if( node.mChannels.size() == 6)
500             {
501                 // Offset all further calculations
502                 rotOffset = 3;
503             }
504 
505             // Then create the number of rotation keys
506             nodeAnim->mNumRotationKeys = mAnimNumFrames;
507             nodeAnim->mRotationKeys = new aiQuatKey[mAnimNumFrames];
508             aiQuatKey* rotkey = nodeAnim->mRotationKeys;
509             for( unsigned int fr = 0; fr < mAnimNumFrames; ++fr)
510             {
511                 aiMatrix4x4 temp;
512                 aiMatrix3x3 rotMatrix;
513 
514                 for( unsigned int channel = 0; channel < 3; ++channel)
515                 {
516                     // translate ZXY euler angels into a quaternion
517                     const float angle = node.mChannelValues[fr * node.mChannels.size() + rotOffset + channel] * float( AI_MATH_PI) / 180.0f;
518 
519                     // Compute rotation transformations in the right order
520                     switch (node.mChannels[rotOffset+channel])
521                     {
522                     case Channel_RotationX: aiMatrix4x4::RotationX( angle, temp); rotMatrix *= aiMatrix3x3( temp); break;
523                     case Channel_RotationY: aiMatrix4x4::RotationY( angle, temp); rotMatrix *= aiMatrix3x3( temp);  break;
524                     case Channel_RotationZ: aiMatrix4x4::RotationZ( angle, temp); rotMatrix *= aiMatrix3x3( temp); break;
525                     default: throw DeadlyImportError( "Unexpected animation channel setup at node " + nodeName );
526                     }
527                 }
528 
529                 rotkey->mTime = double( fr);
530                 rotkey->mValue = aiQuaternion( rotMatrix);
531                 ++rotkey;
532             }
533         }
534 
535         // scaling part. Always just a default track
536         {
537             nodeAnim->mNumScalingKeys = 1;
538             nodeAnim->mScalingKeys = new aiVectorKey[1];
539             nodeAnim->mScalingKeys[0].mTime = 0.0;
540             nodeAnim->mScalingKeys[0].mValue.Set( 1.0f, 1.0f, 1.0f);
541         }
542     }
543 }
544 
545 #endif // !! ASSIMP_BUILD_NO_BVH_IMPORTER
546