/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 |