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