Coverage Report

Created: 2025-08-26 06:41

/src/assimp/code/AssetLib/CSM/CSMLoader.cpp
Line
Count
Source (jump to first uncovered line)
1
/*
2
---------------------------------------------------------------------------
3
Open Asset Import Library (assimp)
4
---------------------------------------------------------------------------
5
6
Copyright (c) 2006-2025, assimp team
7
8
All rights reserved.
9
10
Redistribution and use of this software in source and binary forms,
11
with or without modification, are permitted provided that the following
12
conditions are met:
13
14
* Redistributions of source code must retain the above
15
  copyright notice, this list of conditions and the
16
  following disclaimer.
17
18
* Redistributions in binary form must reproduce the above
19
  copyright notice, this list of conditions and the
20
  following disclaimer in the documentation and/or other
21
  materials provided with the distribution.
22
23
* Neither the name of the assimp team, nor the names of its
24
  contributors may be used to endorse or promote products
25
  derived from this software without specific prior
26
  written permission of the assimp team.
27
28
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
29
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
30
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
31
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
32
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
33
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
34
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
35
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
36
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
37
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
38
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
39
---------------------------------------------------------------------------
40
*/
41
42
/** @file  CSMLoader.cpp
43
 *  Implementation of the CSM importer class.
44
 */
45
#ifndef ASSIMP_BUILD_NO_CSM_IMPORTER
46
47
#include "CSMLoader.h"
48
#include <assimp/SkeletonMeshBuilder.h>
49
#include <assimp/ParsingUtils.h>
50
#include <assimp/fast_atof.h>
51
#include <assimp/Importer.hpp>
52
#include <memory>
53
#include <assimp/IOSystem.hpp>
54
#include <assimp/anim.h>
55
#include <assimp/DefaultLogger.hpp>
56
#include <assimp/scene.h>
57
#include <assimp/importerdesc.h>
58
59
using namespace Assimp;
60
61
static constexpr aiImporterDesc desc = {
62
    "CharacterStudio Motion Importer (MoCap)",
63
    "",
64
    "",
65
    "",
66
    aiImporterFlags_SupportTextFlavour,
67
    0,
68
    0,
69
    0,
70
    0,
71
    "csm"
72
};
73
74
// ------------------------------------------------------------------------------------------------
75
// Constructor to be privately used by Importer
76
220
CSMImporter::CSMImporter() : noSkeletonMesh() {
77
    // empty
78
220
}
79
80
// ------------------------------------------------------------------------------------------------
81
// Returns whether the class can handle the format of the given file.
82
112
bool CSMImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool /*checkSig*/) const {
83
112
    static const char* tokens[] = {"$Filename"};
84
112
    return SearchFileHeaderForToken(pIOHandler,pFile,tokens,AI_COUNT_OF(tokens));
85
112
}
86
87
// ------------------------------------------------------------------------------------------------
88
// Build a string of all file extensions supported
89
210
const aiImporterDesc* CSMImporter::GetInfo () const {
90
210
    return &desc;
91
210
}
92
93
// ------------------------------------------------------------------------------------------------
94
// Setup configuration properties for the loader
95
0
void CSMImporter::SetupProperties(const Importer* pImp) {
96
0
    noSkeletonMesh = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_NO_SKELETON_MESHES,0) != 0;
97
0
}
98
99
// ------------------------------------------------------------------------------------------------
100
// Imports the given file into the given scene structure.
101
void CSMImporter::InternReadFile( const std::string& pFile,
102
0
        aiScene* pScene, IOSystem* pIOHandler) {
103
0
    std::unique_ptr<IOStream> file( pIOHandler->Open( pFile, "rb"));
104
105
    // Check whether we can read from the file
106
0
    if (file == nullptr) {
107
0
        throw DeadlyImportError( "Failed to open CSM file ", pFile, ".");
108
0
    }
109
110
    // allocate storage and copy the contents of the file to a memory buffer
111
0
    std::vector<char> mBuffer2;
112
0
    TextFileToBuffer(file.get(),mBuffer2);
113
0
    const char* buffer = &mBuffer2[0];
114
0
    const char *end = &mBuffer2[mBuffer2.size() - 1] + 1;
115
0
    std::unique_ptr<aiAnimation> anim(new aiAnimation());
116
0
    int first = 0, last = 0x00ffffff;
117
118
    // now process the file and look out for '$' sections
119
0
    while (true) {
120
0
        SkipSpaces(&buffer, end);
121
0
        if ('\0' == *buffer) {
122
0
            break;
123
0
        }
124
125
0
        if ('$'  == *buffer) {
126
0
            ++buffer;
127
0
            if (TokenMatchI(buffer,"firstframe",10)) {
128
0
                SkipSpaces(&buffer, end);
129
0
                first = strtol10(buffer,&buffer);
130
0
            }
131
0
            else if (TokenMatchI(buffer,"lastframe",9)) {
132
0
                SkipSpaces(&buffer, end);
133
0
                last = strtol10(buffer,&buffer);
134
0
            }
135
0
            else if (TokenMatchI(buffer,"rate",4))  {
136
0
                SkipSpaces(&buffer, end);
137
0
                float d = { 0.0f };
138
0
                buffer = fast_atoreal_move(buffer,d);
139
0
                anim->mTicksPerSecond = d;
140
0
            }
141
0
            else if (TokenMatchI(buffer,"order",5)) {
142
0
                std::vector< aiNodeAnim* > anims_temp;
143
0
                anims_temp.reserve(30);
144
0
                while (true) {
145
0
                    SkipSpaces(&buffer, end);
146
0
                    if (IsLineEnd(*buffer) && SkipSpacesAndLineEnd(&buffer, end) && *buffer == '$')
147
0
                        break; // next section
148
149
                    // Construct a new node animation channel and setup its name
150
0
                    anims_temp.push_back(new aiNodeAnim());
151
0
                    aiNodeAnim* nda = anims_temp.back();
152
153
0
                    char *ot = nda->mNodeName.data;
154
0
                    const char *ot_end = nda->mNodeName.data + AI_MAXLEN;
155
0
                    while (!IsSpaceOrNewLine(*buffer) && buffer != end && ot != ot_end) {
156
0
                        *ot++ = *buffer++;
157
0
                    }
158
159
0
                    *ot = '\0';
160
0
                    nda->mNodeName.length = static_cast<ai_uint32>(ot-nda->mNodeName.data);
161
0
                }
162
163
0
                anim->mNumChannels = static_cast<unsigned int>(anims_temp.size());
164
0
                if (!anim->mNumChannels) {
165
0
                    throw DeadlyImportError("CSM: Empty $order section");
166
0
                }
167
168
                // copy over to the output animation
169
0
                anim->mChannels = new aiNodeAnim*[anim->mNumChannels];
170
0
                ::memcpy(anim->mChannels,&anims_temp[0],sizeof(aiNodeAnim*)*anim->mNumChannels);
171
0
            } else if (TokenMatchI(buffer,"points",6)) {
172
0
                if (!anim->mNumChannels) {
173
0
                    throw DeadlyImportError("CSM: \'$order\' section is required to appear prior to \'$points\'");
174
0
                }
175
176
                // If we know how many frames we'll read, we can preallocate some storage
177
0
                unsigned int alloc = 100;
178
0
                if (last != 0x00ffffff) {
179
                    // re-init if the file has last frame data
180
0
                    alloc = last-first;
181
0
                    alloc += alloc>>2u; // + 25%
182
0
                    for (unsigned int i = 0; i < anim->mNumChannels; ++i) {
183
0
                        if (anim->mChannels[i]->mPositionKeys != nullptr) delete[] anim->mChannels[i]->mPositionKeys;
184
0
                        anim->mChannels[i]->mPositionKeys = new aiVectorKey[alloc];
185
0
                    }
186
0
                } else {
187
                    // default init
188
0
                    for (unsigned int i = 0; i < anim->mNumChannels; ++i) {
189
0
                        if (anim->mChannels[i]->mPositionKeys != nullptr) continue;
190
0
                        anim->mChannels[i]->mPositionKeys = new aiVectorKey[alloc];
191
0
                    }
192
0
                }
193
194
0
                unsigned int filled = 0;
195
196
                // Now read all point data.
197
0
                while (true) {
198
0
                    SkipSpaces(&buffer, end);
199
0
                    if (IsLineEnd(*buffer) && (!SkipSpacesAndLineEnd(&buffer, end) || *buffer == '$'))   {
200
0
                        break; // next section
201
0
                    }
202
203
                    // read frame
204
0
                    const int frame = ::strtoul10(buffer,&buffer);
205
0
                    last  = std::max(frame,last);
206
0
                    first = std::min(frame,last);
207
0
                    for (unsigned int i = 0; i < anim->mNumChannels;++i)    {
208
209
0
                        aiNodeAnim* s = anim->mChannels[i];
210
0
                        if (s->mNumPositionKeys == alloc)   {
211
                            // need to reallocate?
212
0
                            aiVectorKey* old = s->mPositionKeys;
213
0
                            s->mPositionKeys = new aiVectorKey[alloc*2];
214
0
                            ::memcpy(s->mPositionKeys,old,sizeof(aiVectorKey)*alloc);
215
0
                            delete[] old;
216
0
                        }
217
218
                        // read x,y,z
219
0
                        if (!SkipSpacesAndLineEnd(&buffer, end)) {
220
0
                            throw DeadlyImportError("CSM: Unexpected EOF occurred reading sample x coord");
221
0
                        }
222
223
0
                        if (TokenMatchI(buffer, "DROPOUT", 7))  {
224
                            // seems this is invalid marker data; at least the doc says it's possible
225
0
                            ASSIMP_LOG_WARN("CSM: Encountered invalid marker data (DROPOUT)");
226
0
                        } else {
227
0
                            aiVectorKey* sub = s->mPositionKeys + s->mNumPositionKeys;
228
0
                            sub->mTime = (double)frame;
229
0
                            buffer = fast_atoreal_move(buffer, sub->mValue.x);
230
231
0
                            if (!SkipSpacesAndLineEnd(&buffer, end)) {
232
0
                                throw DeadlyImportError("CSM: Unexpected EOF occurred reading sample y coord");
233
0
                            }
234
0
                            buffer = fast_atoreal_move(buffer, sub->mValue.y);
235
236
0
                            if (!SkipSpacesAndLineEnd(&buffer, end)) {
237
0
                                throw DeadlyImportError("CSM: Unexpected EOF occurred reading sample z coord");
238
0
                            }
239
0
                            buffer = fast_atoreal_move(buffer, sub->mValue.z);
240
241
0
                            ++s->mNumPositionKeys;
242
0
                        }
243
0
                    }
244
245
                    // update allocation granularity
246
0
                    if (filled == alloc) {
247
0
                        alloc *= 2;
248
0
                    }
249
250
0
                    ++filled;
251
0
                }
252
                // all channels must be complete in order to continue safely.
253
0
                for (unsigned int i = 0; i < anim->mNumChannels;++i)    {
254
0
                    if (!anim->mChannels[i]->mNumPositionKeys) {
255
0
                        throw DeadlyImportError("CSM: Invalid marker track");
256
0
                    }
257
0
                }
258
0
            }
259
0
        } else {
260
            // advance to the next line
261
0
            SkipLine(&buffer, end);
262
0
        }
263
0
    }
264
265
    // Setup a proper animation duration
266
0
    anim->mDuration = last - std::min( first, 0 );
267
268
    // build a dummy root node with the tiny markers as children
269
0
    pScene->mRootNode = new aiNode();
270
0
    pScene->mRootNode->mName.Set("$CSM_DummyRoot");
271
272
0
    pScene->mRootNode->mNumChildren = anim->mNumChannels;
273
0
    pScene->mRootNode->mChildren = new aiNode* [anim->mNumChannels];
274
275
0
    for (unsigned int i = 0; i < anim->mNumChannels;++i) {
276
0
        aiNodeAnim* na = anim->mChannels[i];
277
278
0
        aiNode* nd  = pScene->mRootNode->mChildren[i] = new aiNode();
279
0
        nd->mName   = anim->mChannels[i]->mNodeName;
280
0
        nd->mParent = pScene->mRootNode;
281
282
0
        if (na->mPositionKeys != nullptr && na->mNumPositionKeys > 0) {
283
0
            aiMatrix4x4::Translation(na->mPositionKeys[0].mValue, nd->mTransformation);
284
0
        } else {
285
            // Use identity matrix if no valid position data is available
286
0
            nd->mTransformation = aiMatrix4x4();
287
0
            DefaultLogger::get()->warn("CSM: No position keys available for node - using identity transformation");
288
0
        }
289
0
    }
290
291
    // Store the one and only animation in the scene
292
0
    pScene->mAnimations    = new aiAnimation*[pScene->mNumAnimations=1];
293
0
    anim->mName.Set("$CSM_MasterAnim");
294
0
    pScene->mAnimations[0] = anim.release();
295
296
    // mark the scene as incomplete and run SkeletonMeshBuilder on it
297
0
    pScene->mFlags |= AI_SCENE_FLAGS_INCOMPLETE;
298
299
0
    if (!noSkeletonMesh) {
300
0
        SkeletonMeshBuilder maker(pScene,pScene->mRootNode,true);
301
0
    }
302
0
}
303
304
#endif // !! ASSIMP_BUILD_NO_CSM_IMPORTER