Coverage Report

Created: 2024-08-02 07:04

/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-2024, assimp team
7
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
/** @file  CSMLoader.cpp
45
 *  Implementation of the CSM importer class.
46
 */
47
#ifndef ASSIMP_BUILD_NO_CSM_IMPORTER
48
49
#include "CSMLoader.h"
50
#include <assimp/SkeletonMeshBuilder.h>
51
#include <assimp/ParsingUtils.h>
52
#include <assimp/fast_atof.h>
53
#include <assimp/Importer.hpp>
54
#include <memory>
55
#include <assimp/IOSystem.hpp>
56
#include <assimp/anim.h>
57
#include <assimp/DefaultLogger.hpp>
58
#include <assimp/scene.h>
59
#include <assimp/importerdesc.h>
60
61
using namespace Assimp;
62
63
static constexpr aiImporterDesc desc = {
64
    "CharacterStudio Motion Importer (MoCap)",
65
    "",
66
    "",
67
    "",
68
    aiImporterFlags_SupportTextFlavour,
69
    0,
70
    0,
71
    0,
72
    0,
73
    "csm"
74
};
75
76
77
// ------------------------------------------------------------------------------------------------
78
// Constructor to be privately used by Importer
79
33
CSMImporter::CSMImporter() : noSkeletonMesh(){
80
    // empty
81
33
}
82
83
// ------------------------------------------------------------------------------------------------
84
// Returns whether the class can handle the format of the given file.
85
18
bool CSMImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool /*checkSig*/) const {
86
18
    static const char* tokens[] = {"$Filename"};
87
18
    return SearchFileHeaderForToken(pIOHandler,pFile,tokens,AI_COUNT_OF(tokens));
88
18
}
89
90
// ------------------------------------------------------------------------------------------------
91
// Build a string of all file extensions supported
92
33
const aiImporterDesc* CSMImporter::GetInfo () const {
93
33
    return &desc;
94
33
}
95
96
// ------------------------------------------------------------------------------------------------
97
// Setup configuration properties for the loader
98
0
void CSMImporter::SetupProperties(const Importer* pImp) {
99
0
    noSkeletonMesh = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_NO_SKELETON_MESHES,0) != 0;
100
0
}
101
102
// ------------------------------------------------------------------------------------------------
103
// Imports the given file into the given scene structure.
104
void CSMImporter::InternReadFile( const std::string& pFile,
105
    aiScene* pScene, IOSystem* pIOHandler)
106
0
{
107
0
    std::unique_ptr<IOStream> file( pIOHandler->Open( pFile, "rb"));
108
109
    // Check whether we can read from the file
110
0
    if (file == nullptr) {
111
0
        throw DeadlyImportError( "Failed to open CSM file ", pFile, ".");
112
0
    }
113
114
    // allocate storage and copy the contents of the file to a memory buffer
115
0
    std::vector<char> mBuffer2;
116
0
    TextFileToBuffer(file.get(),mBuffer2);
117
0
    const char* buffer = &mBuffer2[0];
118
0
    const char *end = &mBuffer2[mBuffer2.size() - 1] + 1;
119
0
    std::unique_ptr<aiAnimation> anim(new aiAnimation());
120
0
    int first = 0, last = 0x00ffffff;
121
122
    // now process the file and look out for '$' sections
123
0
    while (true) {
124
0
        SkipSpaces(&buffer, end);
125
0
        if ('\0' == *buffer)
126
0
            break;
127
128
0
        if ('$'  == *buffer)    {
129
0
            ++buffer;
130
0
            if (TokenMatchI(buffer,"firstframe",10))    {
131
0
                SkipSpaces(&buffer, end);
132
0
                first = strtol10(buffer,&buffer);
133
0
            }
134
0
            else if (TokenMatchI(buffer,"lastframe",9))     {
135
0
                SkipSpaces(&buffer, end);
136
0
                last = strtol10(buffer,&buffer);
137
0
            }
138
0
            else if (TokenMatchI(buffer,"rate",4))  {
139
0
                SkipSpaces(&buffer, end);
140
0
                float d = { 0.0f };
141
0
                buffer = fast_atoreal_move<float>(buffer,d);
142
0
                anim->mTicksPerSecond = d;
143
0
            }
144
0
            else if (TokenMatchI(buffer,"order",5)) {
145
0
                std::vector< aiNodeAnim* > anims_temp;
146
0
                anims_temp.reserve(30);
147
0
                while (true) {
148
0
                    SkipSpaces(&buffer, end);
149
0
                    if (IsLineEnd(*buffer) && SkipSpacesAndLineEnd(&buffer, end) && *buffer == '$')
150
0
                        break; // next section
151
152
                    // Construct a new node animation channel and setup its name
153
0
                    anims_temp.push_back(new aiNodeAnim());
154
0
                    aiNodeAnim* nda = anims_temp.back();
155
156
0
                    char* ot = nda->mNodeName.data;
157
0
                    while (!IsSpaceOrNewLine(*buffer)) {
158
0
                        *ot++ = *buffer++;
159
0
                    }
160
161
0
                    *ot = '\0';
162
0
                    nda->mNodeName.length = static_cast<ai_uint32>(ot-nda->mNodeName.data);
163
0
                }
164
165
0
                anim->mNumChannels = static_cast<unsigned int>(anims_temp.size());
166
0
                if (!anim->mNumChannels) {
167
0
                    throw DeadlyImportError("CSM: Empty $order section");
168
0
                }
169
170
                // copy over to the output animation
171
0
                anim->mChannels = new aiNodeAnim*[anim->mNumChannels];
172
0
                ::memcpy(anim->mChannels,&anims_temp[0],sizeof(aiNodeAnim*)*anim->mNumChannels);
173
0
            } else if (TokenMatchI(buffer,"points",6)) {
174
0
                if (!anim->mNumChannels) {
175
0
                    throw DeadlyImportError("CSM: \'$order\' section is required to appear prior to \'$points\'");
176
0
                }
177
178
                // If we know how many frames we'll read, we can preallocate some storage
179
0
                unsigned int alloc = 100;
180
0
                if (last != 0x00ffffff) {
181
0
                    alloc = last-first;
182
0
                    alloc += alloc>>2u; // + 25%
183
0
                    for (unsigned int i = 0; i < anim->mNumChannels; ++i) {
184
0
                        anim->mChannels[i]->mPositionKeys = new aiVectorKey[alloc];
185
0
                    }
186
0
                }
187
188
0
                unsigned int filled = 0;
189
190
                // Now read all point data.
191
0
                while (true) {
192
0
                    SkipSpaces(&buffer, end);
193
0
                    if (IsLineEnd(*buffer) && (!SkipSpacesAndLineEnd(&buffer, end) || *buffer == '$'))   {
194
0
                        break; // next section
195
0
                    }
196
197
                    // read frame
198
0
                    const int frame = ::strtoul10(buffer,&buffer);
199
0
                    last  = std::max(frame,last);
200
0
                    first = std::min(frame,last);
201
0
                    for (unsigned int i = 0; i < anim->mNumChannels;++i)    {
202
203
0
                        aiNodeAnim* s = anim->mChannels[i];
204
0
                        if (s->mNumPositionKeys == alloc)   {
205
                            // need to reallocate?
206
0
                            aiVectorKey* old = s->mPositionKeys;
207
0
                            s->mPositionKeys = new aiVectorKey[s->mNumPositionKeys = alloc*2];
208
0
                            ::memcpy(s->mPositionKeys,old,sizeof(aiVectorKey)*alloc);
209
0
                            delete[] old;
210
0
                        }
211
212
                        // read x,y,z
213
0
                        if (!SkipSpacesAndLineEnd(&buffer, end)) {
214
0
                            throw DeadlyImportError("CSM: Unexpected EOF occurred reading sample x coord");
215
0
                        }
216
217
0
                        if (TokenMatchI(buffer, "DROPOUT", 7))  {
218
                            // seems this is invalid marker data; at least the doc says it's possible
219
0
                            ASSIMP_LOG_WARN("CSM: Encountered invalid marker data (DROPOUT)");
220
0
                        } else {
221
0
                            aiVectorKey* sub = s->mPositionKeys + s->mNumPositionKeys;
222
0
                            sub->mTime = (double)frame;
223
0
                            buffer = fast_atoreal_move<float>(buffer, (float&)sub->mValue.x);
224
225
0
                            if (!SkipSpacesAndLineEnd(&buffer, end)) {
226
0
                                throw DeadlyImportError("CSM: Unexpected EOF occurred reading sample y coord");
227
0
                            }
228
0
                            buffer = fast_atoreal_move<float>(buffer, (float&)sub->mValue.y);
229
230
0
                            if (!SkipSpacesAndLineEnd(&buffer, end)) {
231
0
                                throw DeadlyImportError("CSM: Unexpected EOF occurred reading sample z coord");
232
0
                            }
233
0
                            buffer = fast_atoreal_move<float>(buffer, (float&)sub->mValue.z);
234
235
0
                            ++s->mNumPositionKeys;
236
0
                        }
237
0
                    }
238
239
                    // update allocation granularity
240
0
                    if (filled == alloc) {
241
0
                        alloc *= 2;
242
0
                    }
243
244
0
                    ++filled;
245
0
                }
246
                // all channels must be complete in order to continue safely.
247
0
                for (unsigned int i = 0; i < anim->mNumChannels;++i)    {
248
0
                    if (!anim->mChannels[i]->mNumPositionKeys) {
249
0
                        throw DeadlyImportError("CSM: Invalid marker track");
250
0
                    }
251
0
                }
252
0
            }
253
0
        } else {
254
            // advance to the next line
255
0
            SkipLine(&buffer, end);
256
0
        }
257
0
    }
258
259
    // Setup a proper animation duration
260
0
    anim->mDuration = last - std::min( first, 0 );
261
262
    // build a dummy root node with the tiny markers as children
263
0
    pScene->mRootNode = new aiNode();
264
0
    pScene->mRootNode->mName.Set("$CSM_DummyRoot");
265
266
0
    pScene->mRootNode->mNumChildren = anim->mNumChannels;
267
0
    pScene->mRootNode->mChildren = new aiNode* [anim->mNumChannels];
268
269
0
    for (unsigned int i = 0; i < anim->mNumChannels;++i) {
270
0
        aiNodeAnim* na = anim->mChannels[i];
271
272
0
        aiNode* nd  = pScene->mRootNode->mChildren[i] = new aiNode();
273
0
        nd->mName   = anim->mChannels[i]->mNodeName;
274
0
        nd->mParent = pScene->mRootNode;
275
276
0
        aiMatrix4x4::Translation(na->mPositionKeys[0].mValue, nd->mTransformation);
277
0
    }
278
279
    // Store the one and only animation in the scene
280
0
    pScene->mAnimations    = new aiAnimation*[pScene->mNumAnimations=1];
281
0
    anim->mName.Set("$CSM_MasterAnim");
282
0
    pScene->mAnimations[0] = anim.release();
283
284
    // mark the scene as incomplete and run SkeletonMeshBuilder on it
285
0
    pScene->mFlags |= AI_SCENE_FLAGS_INCOMPLETE;
286
287
0
    if (!noSkeletonMesh) {
288
0
        SkeletonMeshBuilder maker(pScene,pScene->mRootNode,true);
289
0
    }
290
0
}
291
292
#endif // !! ASSIMP_BUILD_NO_CSM_IMPORTER