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