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 |
75 | BVHLoader::BVHLoader() |
76 | : mLine(), |
77 | mAnimTickDuration(), |
78 | mAnimNumFrames(), |
79 | noSkeletonMesh() |
80 | {} |
81 | |
82 | // ------------------------------------------------------------------------------------------------ |
83 | // Destructor, private as well |
84 | BVHLoader::~BVHLoader() |
85 | {} |
86 | |
87 | // ------------------------------------------------------------------------------------------------ |
88 | // Returns whether the class can handle the format of the given file. |
89 | 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 | // ------------------------------------------------------------------------------------------------ |
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 |
112 | const aiImporterDesc* BVHLoader::GetInfo () const |
113 | { |
114 | return &desc; |
115 | } |
116 | |
117 | // ------------------------------------------------------------------------------------------------ |
118 | // Imports the given file into the given scene structure. |
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 |
151 | void BVHLoader::ReadStructure( aiScene* pScene) |
152 | { |
153 | // first comes hierarchy |
154 | std::string = 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 |
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; |
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. |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 | |