Coverage Report

Created: 2025-12-05 06:25

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