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