1/** Implementation of the BVH loader */
2/*
3---------------------------------------------------------------------------
4Open Asset Import Library (assimp)
5---------------------------------------------------------------------------
6
7Copyright (c) 2006-2017, assimp team
8
9
10All rights reserved.
11
12Redistribution and use of this software in source and binary forms,
13with or without modification, are permitted provided that the following
14conditions are met:
15
16* Redistributions of source code must retain the above
17copyright notice, this list of conditions and the
18following disclaimer.
19
20* Redistributions in binary form must reproduce the above
21copyright notice, this list of conditions and the
22following disclaimer in the documentation and/or other
23materials provided with the distribution.
24
25* Neither the name of the assimp team, nor the names of its
26contributors may be used to endorse or promote products
27derived from this software without specific prior
28written permission of the assimp team.
29
30THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
31"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
32LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
33A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
34OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
35SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
36LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
37DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
38THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
39(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
40OF 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
57using namespace Assimp;
58using namespace Assimp::Formatter;
59
60static 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
75BVHLoader::BVHLoader()
76 : mLine(),
77 mAnimTickDuration(),
78 mAnimNumFrames(),
79 noSkeletonMesh()
80{}
81
82// ------------------------------------------------------------------------------------------------
83// Destructor, private as well
84BVHLoader::~BVHLoader()
85{}
86
87// ------------------------------------------------------------------------------------------------
88// Returns whether the class can handle the format of the given file.
89bool 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// ------------------------------------------------------------------------------------------------
105void 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
112const aiImporterDesc* BVHLoader::GetInfo () const
113{
114 return &desc;
115}
116
117// ------------------------------------------------------------------------------------------------
118// Imports the given file into the given scene structure.
119void 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
151void 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
168void 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;
180aiNode* 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.
253aiNode* 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
289void 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
304void 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
333void 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
372std::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
408float 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
427AI_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
434void 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