/src/assimp/code/AssetLib/X/XFileImporter.cpp
Line | Count | Source |
1 | | /* |
2 | | --------------------------------------------------------------------------- |
3 | | Open Asset Import Library (assimp) |
4 | | --------------------------------------------------------------------------- |
5 | | |
6 | | Copyright (c) 2006-2026, 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 | | /** @file XFileImporter.cpp |
42 | | * @brief Implementation of the XFile importer class |
43 | | */ |
44 | | |
45 | | #ifndef ASSIMP_BUILD_NO_X_IMPORTER |
46 | | |
47 | | #include "XFileImporter.h" |
48 | | #include "XFileParser.h" |
49 | | #include "PostProcessing/ConvertToLHProcess.h" |
50 | | |
51 | | #include <assimp/TinyFormatter.h> |
52 | | #include <assimp/IOSystem.hpp> |
53 | | #include <assimp/scene.h> |
54 | | #include <assimp/DefaultLogger.hpp> |
55 | | #include <assimp/importerdesc.h> |
56 | | |
57 | | #include <cctype> |
58 | | #include <memory> |
59 | | |
60 | | namespace Assimp { |
61 | | |
62 | | using namespace Assimp::Formatter; |
63 | | |
64 | | static constexpr aiImporterDesc desc = { |
65 | | "Direct3D XFile Importer", |
66 | | "", |
67 | | "", |
68 | | "", |
69 | | aiImporterFlags_SupportTextFlavour | aiImporterFlags_SupportBinaryFlavour | aiImporterFlags_SupportCompressedFlavour, |
70 | | 1, |
71 | | 3, |
72 | | 1, |
73 | | 5, |
74 | | "x" |
75 | | }; |
76 | | |
77 | | // ------------------------------------------------------------------------------------------------ |
78 | | // Returns whether the class can handle the format of the given file. |
79 | 0 | bool XFileImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool /*checkSig*/) const { |
80 | 0 | static constexpr uint32_t token[] = { AI_MAKE_MAGIC("xof ") }; |
81 | 0 | return CheckMagicToken(pIOHandler, pFile, token, AI_COUNT_OF(token)); |
82 | 0 | } |
83 | | |
84 | | // ------------------------------------------------------------------------------------------------ |
85 | | // Get file extension list |
86 | 38.0k | const aiImporterDesc *XFileImporter::GetInfo() const { |
87 | 38.0k | return &desc; |
88 | 38.0k | } |
89 | | |
90 | | // ------------------------------------------------------------------------------------------------ |
91 | | // Imports the given file into the given scene structure. |
92 | 0 | void XFileImporter::InternReadFile(const std::string &pFile, aiScene *pScene, IOSystem *pIOHandler) { |
93 | | // read file into memory |
94 | 0 | std::unique_ptr<IOStream> file(pIOHandler->Open(pFile)); |
95 | 0 | if (file == nullptr) { |
96 | 0 | throw DeadlyImportError("Failed to open file ", pFile, "."); |
97 | 0 | } |
98 | | |
99 | 0 | static const size_t MinSize = 16; |
100 | 0 | size_t fileSize = file->FileSize(); |
101 | 0 | if (fileSize < MinSize) { |
102 | 0 | throw DeadlyImportError("XFile is too small."); |
103 | 0 | } |
104 | | |
105 | | // in the hope that binary files will never start with a BOM ... |
106 | 0 | mBuffer.resize(fileSize + 1); |
107 | 0 | file->Read(&mBuffer.front(), 1, fileSize); |
108 | 0 | ConvertToUTF8(mBuffer); |
109 | | |
110 | | // parse the file into a temporary representation |
111 | 0 | XFileParser parser(mBuffer); |
112 | | |
113 | | // and create the proper return structures out of it |
114 | 0 | CreateDataRepresentationFromImport(pScene, parser.GetImportedData()); |
115 | | |
116 | | // if nothing came from it, report it as error |
117 | 0 | if (!pScene->mRootNode) { |
118 | 0 | throw DeadlyImportError("XFile is ill-formatted - no content imported."); |
119 | 0 | } |
120 | 0 | } |
121 | | |
122 | | // ------------------------------------------------------------------------------------------------ |
123 | | // Constructs the return data structure out of the imported data. |
124 | 0 | void XFileImporter::CreateDataRepresentationFromImport(aiScene *pScene, XFile::Scene *pData) { |
125 | | // Read the global materials first so that meshes referring to them can find them later |
126 | 0 | ConvertMaterials(pScene, pData->mGlobalMaterials); |
127 | | |
128 | | // copy nodes, extracting meshes and materials on the way |
129 | 0 | pScene->mRootNode = CreateNodes(pScene, nullptr, pData->mRootNode); |
130 | | |
131 | | // extract animations |
132 | 0 | CreateAnimations(pScene, pData); |
133 | | |
134 | | // read the global meshes that were stored outside of any node |
135 | 0 | if (!pData->mGlobalMeshes.empty()) { |
136 | | // create a root node to hold them if there isn't any, yet |
137 | 0 | if (pScene->mRootNode == nullptr) { |
138 | 0 | pScene->mRootNode = new aiNode; |
139 | 0 | pScene->mRootNode->mName.Set("$dummy_node"); |
140 | 0 | } |
141 | | |
142 | | // convert all global meshes and store them in the root node. |
143 | | // If there was one before, the global meshes now suddenly have its transformation matrix... |
144 | | // Don't know what to do there, I don't want to insert another node under the present root node |
145 | | // just to avoid this. |
146 | 0 | CreateMeshes(pScene, pScene->mRootNode, pData->mGlobalMeshes); |
147 | 0 | } |
148 | |
|
149 | 0 | if (!pScene->mRootNode) { |
150 | 0 | throw DeadlyImportError("No root node"); |
151 | 0 | } |
152 | | |
153 | | // Convert everything to OpenGL space... it's the same operation as the conversion back, so we can reuse the step directly |
154 | 0 | MakeLeftHandedProcess convertProcess; |
155 | 0 | convertProcess.Execute(pScene); |
156 | |
|
157 | 0 | FlipWindingOrderProcess flipper; |
158 | 0 | flipper.Execute(pScene); |
159 | | |
160 | | // finally: create a dummy material if not material was imported |
161 | 0 | if (pScene->mNumMaterials == 0) { |
162 | 0 | pScene->mNumMaterials = 1; |
163 | | // create the Material |
164 | 0 | aiMaterial *mat = new aiMaterial; |
165 | 0 | int shadeMode = (int)aiShadingMode_Gouraud; |
166 | 0 | mat->AddProperty<int>(&shadeMode, 1, AI_MATKEY_SHADING_MODEL); |
167 | | // material colours |
168 | 0 | int specExp = 1; |
169 | |
|
170 | 0 | aiColor3D clr = aiColor3D(0, 0, 0); |
171 | 0 | mat->AddProperty(&clr, 1, AI_MATKEY_COLOR_EMISSIVE); |
172 | 0 | mat->AddProperty(&clr, 1, AI_MATKEY_COLOR_SPECULAR); |
173 | |
|
174 | 0 | clr = aiColor3D(0.5f, 0.5f, 0.5f); |
175 | 0 | mat->AddProperty(&clr, 1, AI_MATKEY_COLOR_DIFFUSE); |
176 | 0 | mat->AddProperty(&specExp, 1, AI_MATKEY_SHININESS); |
177 | |
|
178 | 0 | pScene->mMaterials = new aiMaterial *[1]; |
179 | 0 | pScene->mMaterials[0] = mat; |
180 | 0 | } |
181 | 0 | } |
182 | | |
183 | | // ------------------------------------------------------------------------------------------------ |
184 | | // Recursively creates scene nodes from the imported hierarchy. |
185 | 0 | aiNode *XFileImporter::CreateNodes(aiScene *pScene, aiNode *pParent, const XFile::Node *pNode) { |
186 | 0 | if (!pNode) { |
187 | 0 | return nullptr; |
188 | 0 | } |
189 | | |
190 | | // create node |
191 | 0 | aiNode *node = new aiNode; |
192 | 0 | node->mName.length = (ai_uint32)pNode->mName.length(); |
193 | 0 | node->mParent = pParent; |
194 | 0 | memcpy(node->mName.data, pNode->mName.c_str(), pNode->mName.length()); |
195 | 0 | node->mName.data[node->mName.length] = 0; |
196 | 0 | node->mTransformation = pNode->mTrafoMatrix; |
197 | | |
198 | | // convert meshes from the source node |
199 | 0 | CreateMeshes(pScene, node, pNode->mMeshes); |
200 | | |
201 | | // handle children |
202 | 0 | if (!pNode->mChildren.empty()) { |
203 | 0 | node->mNumChildren = (unsigned int)pNode->mChildren.size(); |
204 | 0 | node->mChildren = new aiNode *[node->mNumChildren]; |
205 | |
|
206 | 0 | for (unsigned int a = 0; a < pNode->mChildren.size(); ++a) { |
207 | 0 | node->mChildren[a] = CreateNodes(pScene, node, pNode->mChildren[a]); |
208 | 0 | } |
209 | 0 | } |
210 | |
|
211 | 0 | return node; |
212 | 0 | } |
213 | | |
214 | | // ------------------------------------------------------------------------------------------------ |
215 | | // Creates the meshes for the given node. |
216 | 0 | void XFileImporter::CreateMeshes(aiScene *pScene, aiNode *pNode, const std::vector<XFile::Mesh *> &pMeshes) { |
217 | 0 | if (pMeshes.empty()) { |
218 | 0 | return; |
219 | 0 | } |
220 | | |
221 | | // create a mesh for each mesh-material combination in the source node |
222 | 0 | std::vector<aiMesh *> meshes; |
223 | 0 | for (unsigned int a = 0; a < pMeshes.size(); ++a) { |
224 | 0 | XFile::Mesh *sourceMesh = pMeshes[a]; |
225 | 0 | if (nullptr == sourceMesh) { |
226 | 0 | continue; |
227 | 0 | } |
228 | | |
229 | | // first convert its materials so that we can find them with their index afterwards |
230 | 0 | ConvertMaterials(pScene, sourceMesh->mMaterials); |
231 | |
|
232 | 0 | unsigned int numMaterials = std::max((unsigned int)sourceMesh->mMaterials.size(), 1u); |
233 | 0 | for (unsigned int b = 0; b < numMaterials; ++b) { |
234 | | // collect the faces belonging to this material |
235 | 0 | std::vector<unsigned int> faces; |
236 | 0 | unsigned int numVertices = 0; |
237 | 0 | if (!sourceMesh->mFaceMaterials.empty()) { |
238 | | // if there is a per-face material defined, select the faces with the corresponding material |
239 | 0 | for (unsigned int c = 0; c < sourceMesh->mFaceMaterials.size(); ++c) { |
240 | 0 | if (sourceMesh->mFaceMaterials[c] == b) { |
241 | 0 | faces.push_back(c); |
242 | 0 | numVertices += (unsigned int)sourceMesh->mPosFaces[c].mIndices.size(); |
243 | 0 | } |
244 | 0 | } |
245 | 0 | } else { |
246 | | // if there is no per-face material, place everything into one mesh |
247 | 0 | for (unsigned int c = 0; c < sourceMesh->mPosFaces.size(); ++c) { |
248 | 0 | faces.push_back(c); |
249 | 0 | numVertices += (unsigned int)sourceMesh->mPosFaces[c].mIndices.size(); |
250 | 0 | } |
251 | 0 | } |
252 | | |
253 | | // no faces/vertices using this material? strange... |
254 | 0 | if (numVertices == 0) { |
255 | 0 | continue; |
256 | 0 | } |
257 | | |
258 | | // create a submesh using this material |
259 | 0 | aiMesh *mesh = new aiMesh; |
260 | 0 | meshes.push_back(mesh); |
261 | | |
262 | | // find the material in the scene's material list. Either own material |
263 | | // or referenced material, it should already have a valid index |
264 | 0 | if (!sourceMesh->mFaceMaterials.empty()) { |
265 | 0 | mesh->mMaterialIndex = static_cast<unsigned int>(sourceMesh->mMaterials[b].sceneIndex); |
266 | 0 | } else { |
267 | 0 | mesh->mMaterialIndex = 0; |
268 | 0 | } |
269 | | |
270 | | // Create properly sized data arrays in the mesh. We store unique vertices per face, |
271 | | // as specified |
272 | 0 | mesh->mNumVertices = numVertices; |
273 | 0 | mesh->mVertices = new aiVector3D[numVertices]; |
274 | 0 | mesh->mNumFaces = (unsigned int)faces.size(); |
275 | 0 | mesh->mFaces = new aiFace[mesh->mNumFaces]; |
276 | | |
277 | | // name |
278 | 0 | mesh->mName.Set(sourceMesh->mName); |
279 | | |
280 | | // normals? |
281 | 0 | if (sourceMesh->mNormals.size() > 0) { |
282 | 0 | mesh->mNormals = new aiVector3D[numVertices]; |
283 | 0 | } |
284 | | // texture coords |
285 | 0 | for (unsigned int c = 0; c < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++c) { |
286 | 0 | if (!sourceMesh->mTexCoords[c].empty()) { |
287 | 0 | mesh->mTextureCoords[c] = new aiVector3D[numVertices]; |
288 | 0 | } |
289 | 0 | } |
290 | | // vertex colors |
291 | 0 | for (unsigned int c = 0; c < AI_MAX_NUMBER_OF_COLOR_SETS; ++c) { |
292 | 0 | if (!sourceMesh->mColors[c].empty()) { |
293 | 0 | mesh->mColors[c] = new aiColor4D[numVertices]; |
294 | 0 | } |
295 | 0 | } |
296 | | |
297 | | // now collect the vertex data of all data streams present in the imported mesh |
298 | 0 | unsigned int newIndex(0); |
299 | 0 | std::vector<unsigned int> orgPoints; // from which original point each new vertex stems |
300 | 0 | orgPoints.resize(numVertices, 0); |
301 | |
|
302 | 0 | for (unsigned int c = 0; c < faces.size(); ++c) { |
303 | 0 | unsigned int f = faces[c]; // index of the source face |
304 | 0 | const XFile::Face &pf = sourceMesh->mPosFaces[f]; // position source face |
305 | | |
306 | | // create face. either triangle or triangle fan depending on the index count |
307 | 0 | aiFace &df = mesh->mFaces[c]; // destination face |
308 | 0 | df.mNumIndices = (unsigned int)pf.mIndices.size(); |
309 | 0 | df.mIndices = new unsigned int[df.mNumIndices]; |
310 | | |
311 | | // collect vertex data for indices of this face |
312 | 0 | for (unsigned int d = 0; d < df.mNumIndices; ++d) { |
313 | 0 | df.mIndices[d] = newIndex; |
314 | 0 | const unsigned int newIdx = pf.mIndices[d]; |
315 | 0 | if (newIdx >= sourceMesh->mPositions.size()) { |
316 | 0 | continue; |
317 | 0 | } |
318 | | |
319 | 0 | orgPoints[newIndex] = pf.mIndices[d]; |
320 | | |
321 | | // Position |
322 | 0 | mesh->mVertices[newIndex] = sourceMesh->mPositions[pf.mIndices[d]]; |
323 | | // Normal, if present |
324 | 0 | if (mesh->HasNormals()) { |
325 | 0 | if (sourceMesh->mNormFaces[f].mIndices.size() > d) { |
326 | 0 | const size_t idx(sourceMesh->mNormFaces[f].mIndices[d]); |
327 | 0 | if (idx < sourceMesh->mNormals.size()) { |
328 | 0 | mesh->mNormals[newIndex] = sourceMesh->mNormals[idx]; |
329 | 0 | } |
330 | 0 | } |
331 | 0 | } |
332 | | |
333 | | // texture coord sets |
334 | 0 | for (unsigned int e = 0; e < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++e) { |
335 | 0 | if (mesh->HasTextureCoords(e)) { |
336 | 0 | aiVector2D tex = sourceMesh->mTexCoords[e][pf.mIndices[d]]; |
337 | 0 | mesh->mTextureCoords[e][newIndex] = aiVector3D(tex.x, 1.0f - tex.y, 0.0f); |
338 | 0 | } |
339 | 0 | } |
340 | | // vertex color sets |
341 | 0 | for (unsigned int e = 0; e < AI_MAX_NUMBER_OF_COLOR_SETS; ++e) { |
342 | 0 | if (mesh->HasVertexColors(e)) { |
343 | 0 | mesh->mColors[e][newIndex] = sourceMesh->mColors[e][pf.mIndices[d]]; |
344 | 0 | } |
345 | 0 | } |
346 | |
|
347 | 0 | newIndex++; |
348 | 0 | } |
349 | 0 | } |
350 | | |
351 | | // there should be as much new vertices as we calculated before |
352 | 0 | ai_assert(newIndex == numVertices); |
353 | | |
354 | | // convert all bones of the source mesh which influence vertices in this newly created mesh |
355 | 0 | const std::vector<XFile::Bone> &bones = sourceMesh->mBones; |
356 | 0 | std::vector<aiBone *> newBones; |
357 | 0 | for (unsigned int c = 0; c < bones.size(); ++c) { |
358 | 0 | const XFile::Bone &obone = bones[c]; |
359 | | // set up a vertex-linear array of the weights for quick searching if a bone influences a vertex |
360 | 0 | std::vector<ai_real> oldWeights(sourceMesh->mPositions.size(), 0.0); |
361 | 0 | for (unsigned int d = 0; d < obone.mWeights.size(); ++d) { |
362 | | // TODO The conditional against boneIdx which was added in commit f844c33 |
363 | | // TODO (https://github.com/assimp/assimp/commit/f844c3397d7726477ab0fdca8efd3df56c18366b) |
364 | | // TODO causes massive breakage as detailed in: |
365 | | // TODO https://github.com/assimp/assimp/issues/5332 |
366 | | // TODO In cases like this unit tests are less useful, since the model still has |
367 | | // TODO meshes, textures, animations etc. and asserts against these values may pass; |
368 | | // TODO when touching importer code, it is crucial that developers also run manual, visual |
369 | | // TODO checks to ensure there's no obvious breakage _before_ commiting to main branch |
370 | | //const unsigned int boneIdx = obone.mWeights[d].mVertex; |
371 | | //if (boneIdx < obone.mWeights.size()) { |
372 | 0 | oldWeights[obone.mWeights[d].mVertex] = obone.mWeights[d].mWeight; |
373 | | //} |
374 | 0 | } |
375 | | |
376 | | // collect all vertex weights that influence a vertex in the new mesh |
377 | 0 | std::vector<aiVertexWeight> newWeights; |
378 | 0 | newWeights.reserve(numVertices); |
379 | 0 | for (unsigned int d = 0; d < orgPoints.size(); ++d) { |
380 | | // does the new vertex stem from an old vertex which was influenced by this bone? |
381 | 0 | ai_real w = oldWeights[orgPoints[d]]; |
382 | 0 | if (w > 0.0) { |
383 | 0 | newWeights.emplace_back(d, w); |
384 | 0 | } |
385 | 0 | } |
386 | | |
387 | | // if the bone has no weights in the newly created mesh, ignore it |
388 | 0 | if (newWeights.empty()) { |
389 | 0 | continue; |
390 | 0 | } |
391 | | |
392 | | // create |
393 | 0 | aiBone *nbone = new aiBone; |
394 | 0 | newBones.push_back(nbone); |
395 | | // copy name and matrix |
396 | 0 | nbone->mName.Set(obone.mName); |
397 | 0 | nbone->mOffsetMatrix = obone.mOffsetMatrix; |
398 | 0 | nbone->mNumWeights = (unsigned int)newWeights.size(); |
399 | 0 | nbone->mWeights = new aiVertexWeight[nbone->mNumWeights]; |
400 | 0 | for (unsigned int d = 0; d < newWeights.size(); ++d) { |
401 | 0 | nbone->mWeights[d] = newWeights[d]; |
402 | 0 | } |
403 | 0 | } |
404 | | |
405 | | // store the bones in the mesh |
406 | 0 | mesh->mNumBones = (unsigned int)newBones.size(); |
407 | 0 | if (!newBones.empty()) { |
408 | 0 | mesh->mBones = new aiBone *[mesh->mNumBones]; |
409 | 0 | std::copy(newBones.begin(), newBones.end(), mesh->mBones); |
410 | 0 | } |
411 | 0 | } |
412 | 0 | } |
413 | | |
414 | | // reallocate scene mesh array to be large enough |
415 | 0 | aiMesh **prevArray = pScene->mMeshes; |
416 | 0 | pScene->mMeshes = new aiMesh *[pScene->mNumMeshes + meshes.size()]; |
417 | 0 | if (prevArray) { |
418 | 0 | memcpy(pScene->mMeshes, prevArray, pScene->mNumMeshes * sizeof(aiMesh *)); |
419 | 0 | delete[] prevArray; |
420 | 0 | } |
421 | | |
422 | | // allocate mesh index array in the node |
423 | 0 | pNode->mNumMeshes = (unsigned int)meshes.size(); |
424 | 0 | pNode->mMeshes = new unsigned int[pNode->mNumMeshes]; |
425 | | |
426 | | // store all meshes in the mesh library of the scene and store their indices in the node |
427 | 0 | for (unsigned int a = 0; a < meshes.size(); a++) { |
428 | 0 | pScene->mMeshes[pScene->mNumMeshes] = meshes[a]; |
429 | 0 | pNode->mMeshes[a] = pScene->mNumMeshes; |
430 | 0 | pScene->mNumMeshes++; |
431 | 0 | } |
432 | 0 | } |
433 | | |
434 | | // ------------------------------------------------------------------------------------------------ |
435 | | // Converts the animations from the given imported data and creates them in the scene. |
436 | 0 | void XFileImporter::CreateAnimations(aiScene *pScene, const XFile::Scene *pData) { |
437 | 0 | std::vector<aiAnimation *> newAnims; |
438 | |
|
439 | 0 | for (unsigned int a = 0; a < pData->mAnims.size(); ++a) { |
440 | 0 | const XFile::Animation *anim = pData->mAnims[a]; |
441 | | // some exporters mock me with empty animation tags. |
442 | 0 | if (anim->mAnims.empty()) { |
443 | 0 | continue; |
444 | 0 | } |
445 | | |
446 | | // create a new animation to hold the data |
447 | 0 | aiAnimation *nanim = new aiAnimation; |
448 | 0 | newAnims.push_back(nanim); |
449 | 0 | nanim->mName.Set(anim->mName); |
450 | | // duration will be determined by the maximum length |
451 | 0 | nanim->mDuration = 0; |
452 | 0 | nanim->mTicksPerSecond = pData->mAnimTicksPerSecond; |
453 | 0 | nanim->mNumChannels = (unsigned int)anim->mAnims.size(); |
454 | 0 | nanim->mChannels = new aiNodeAnim *[nanim->mNumChannels]; |
455 | |
|
456 | 0 | for (unsigned int b = 0; b < anim->mAnims.size(); ++b) { |
457 | 0 | const XFile::AnimBone *bone = anim->mAnims[b]; |
458 | 0 | aiNodeAnim *nbone = new aiNodeAnim; |
459 | 0 | nbone->mNodeName.Set(bone->mBoneName); |
460 | 0 | nanim->mChannels[b] = nbone; |
461 | | |
462 | | // key-frames are given as combined transformation matrix keys |
463 | 0 | if (!bone->mTrafoKeys.empty()) { |
464 | 0 | nbone->mNumPositionKeys = (unsigned int)bone->mTrafoKeys.size(); |
465 | 0 | nbone->mPositionKeys = new aiVectorKey[nbone->mNumPositionKeys]; |
466 | 0 | nbone->mNumRotationKeys = (unsigned int)bone->mTrafoKeys.size(); |
467 | 0 | nbone->mRotationKeys = new aiQuatKey[nbone->mNumRotationKeys]; |
468 | 0 | nbone->mNumScalingKeys = (unsigned int)bone->mTrafoKeys.size(); |
469 | 0 | nbone->mScalingKeys = new aiVectorKey[nbone->mNumScalingKeys]; |
470 | |
|
471 | 0 | for (unsigned int c = 0; c < bone->mTrafoKeys.size(); ++c) { |
472 | | // deconstruct each matrix into separate position, rotation and scaling |
473 | 0 | double time = bone->mTrafoKeys[c].mTime; |
474 | 0 | aiMatrix4x4 trafo = bone->mTrafoKeys[c].mMatrix; |
475 | | |
476 | | // extract position |
477 | 0 | aiVector3D pos(trafo.a4, trafo.b4, trafo.c4); |
478 | |
|
479 | 0 | nbone->mPositionKeys[c].mTime = time; |
480 | 0 | nbone->mPositionKeys[c].mValue = pos; |
481 | | |
482 | | // extract scaling |
483 | 0 | aiVector3D scale; |
484 | 0 | scale.x = aiVector3D(trafo.a1, trafo.b1, trafo.c1).Length(); |
485 | 0 | scale.y = aiVector3D(trafo.a2, trafo.b2, trafo.c2).Length(); |
486 | 0 | scale.z = aiVector3D(trafo.a3, trafo.b3, trafo.c3).Length(); |
487 | 0 | nbone->mScalingKeys[c].mTime = time; |
488 | 0 | nbone->mScalingKeys[c].mValue = scale; |
489 | | |
490 | | // reconstruct rotation matrix without scaling |
491 | 0 | aiMatrix3x3 rotmat( |
492 | 0 | trafo.a1 / scale.x, trafo.a2 / scale.y, trafo.a3 / scale.z, |
493 | 0 | trafo.b1 / scale.x, trafo.b2 / scale.y, trafo.b3 / scale.z, |
494 | 0 | trafo.c1 / scale.x, trafo.c2 / scale.y, trafo.c3 / scale.z); |
495 | | |
496 | | // and convert it into a quaternion |
497 | 0 | nbone->mRotationKeys[c].mTime = time; |
498 | 0 | nbone->mRotationKeys[c].mValue = aiQuaternion(rotmat); |
499 | 0 | } |
500 | | |
501 | | // longest lasting key sequence determines duration |
502 | 0 | nanim->mDuration = std::max(nanim->mDuration, bone->mTrafoKeys.back().mTime); |
503 | 0 | } else { |
504 | | // separate key sequences for position, rotation, scaling |
505 | 0 | nbone->mNumPositionKeys = (unsigned int)bone->mPosKeys.size(); |
506 | 0 | if (nbone->mNumPositionKeys != 0) { |
507 | 0 | nbone->mPositionKeys = new aiVectorKey[nbone->mNumPositionKeys]; |
508 | 0 | for (unsigned int c = 0; c < nbone->mNumPositionKeys; ++c) { |
509 | 0 | aiVector3D pos = bone->mPosKeys[c].mValue; |
510 | |
|
511 | 0 | nbone->mPositionKeys[c].mTime = bone->mPosKeys[c].mTime; |
512 | 0 | nbone->mPositionKeys[c].mValue = pos; |
513 | 0 | } |
514 | 0 | } |
515 | | |
516 | | // rotation |
517 | 0 | nbone->mNumRotationKeys = (unsigned int)bone->mRotKeys.size(); |
518 | 0 | if (nbone->mNumRotationKeys != 0) { |
519 | 0 | nbone->mRotationKeys = new aiQuatKey[nbone->mNumRotationKeys]; |
520 | 0 | for (unsigned int c = 0; c < nbone->mNumRotationKeys; ++c) { |
521 | 0 | aiMatrix3x3 rotmat = bone->mRotKeys[c].mValue.GetMatrix(); |
522 | |
|
523 | 0 | nbone->mRotationKeys[c].mTime = bone->mRotKeys[c].mTime; |
524 | 0 | nbone->mRotationKeys[c].mValue = aiQuaternion(rotmat); |
525 | 0 | nbone->mRotationKeys[c].mValue.w *= -1.0f; // needs quat inversion |
526 | 0 | } |
527 | 0 | } |
528 | | |
529 | | // scaling |
530 | 0 | nbone->mNumScalingKeys = (unsigned int)bone->mScaleKeys.size(); |
531 | 0 | if (nbone->mNumScalingKeys != 0) { |
532 | 0 | nbone->mScalingKeys = new aiVectorKey[nbone->mNumScalingKeys]; |
533 | 0 | for (unsigned int c = 0; c < nbone->mNumScalingKeys; c++) |
534 | 0 | nbone->mScalingKeys[c] = bone->mScaleKeys[c]; |
535 | 0 | } |
536 | | |
537 | | // longest lasting key sequence determines duration |
538 | 0 | if (bone->mPosKeys.size() > 0) |
539 | 0 | nanim->mDuration = std::max(nanim->mDuration, bone->mPosKeys.back().mTime); |
540 | 0 | if (bone->mRotKeys.size() > 0) |
541 | 0 | nanim->mDuration = std::max(nanim->mDuration, bone->mRotKeys.back().mTime); |
542 | 0 | if (bone->mScaleKeys.size() > 0) |
543 | 0 | nanim->mDuration = std::max(nanim->mDuration, bone->mScaleKeys.back().mTime); |
544 | 0 | } |
545 | 0 | } |
546 | 0 | } |
547 | | |
548 | | // store all converted animations in the scene |
549 | 0 | if (newAnims.size() > 0) { |
550 | 0 | pScene->mNumAnimations = (unsigned int)newAnims.size(); |
551 | 0 | pScene->mAnimations = new aiAnimation *[pScene->mNumAnimations]; |
552 | 0 | for (unsigned int a = 0; a < newAnims.size(); a++) |
553 | 0 | pScene->mAnimations[a] = newAnims[a]; |
554 | 0 | } |
555 | 0 | } |
556 | | |
557 | | // ------------------------------------------------------------------------------------------------ |
558 | | // Converts all materials in the given array and stores them in the scene's material list. |
559 | 0 | void XFileImporter::ConvertMaterials(aiScene *pScene, std::vector<XFile::Material> &pMaterials) { |
560 | | // count the non-referrer materials in the array |
561 | 0 | unsigned int numNewMaterials(0); |
562 | 0 | for (unsigned int a = 0; a < pMaterials.size(); ++a) { |
563 | 0 | if (!pMaterials[a].mIsReference) { |
564 | 0 | ++numNewMaterials; |
565 | 0 | } |
566 | 0 | } |
567 | | |
568 | | // resize the scene's material list to offer enough space for the new materials |
569 | 0 | if (numNewMaterials > 0) { |
570 | 0 | aiMaterial **prevMats = pScene->mMaterials; |
571 | 0 | pScene->mMaterials = new aiMaterial *[pScene->mNumMaterials + numNewMaterials]; |
572 | 0 | if (nullptr != prevMats) { |
573 | 0 | ::memcpy(pScene->mMaterials, prevMats, pScene->mNumMaterials * sizeof(aiMaterial *)); |
574 | 0 | delete[] prevMats; |
575 | 0 | } |
576 | 0 | } |
577 | | |
578 | | // convert all the materials given in the array |
579 | 0 | for (unsigned int a = 0; a < pMaterials.size(); ++a) { |
580 | 0 | XFile::Material &oldMat = pMaterials[a]; |
581 | 0 | if (oldMat.mIsReference) { |
582 | | // find the material it refers to by name, and store its index |
583 | 0 | for (size_t b = 0; b < pScene->mNumMaterials; ++b) { |
584 | 0 | aiString name; |
585 | 0 | pScene->mMaterials[b]->Get(AI_MATKEY_NAME, name); |
586 | 0 | if (strcmp(name.C_Str(), oldMat.mName.data()) == 0) { |
587 | 0 | oldMat.sceneIndex = b; |
588 | 0 | break; |
589 | 0 | } |
590 | 0 | } |
591 | |
|
592 | 0 | if (oldMat.sceneIndex == SIZE_MAX) { |
593 | 0 | ASSIMP_LOG_WARN("Could not resolve global material reference \"", oldMat.mName, "\""); |
594 | 0 | oldMat.sceneIndex = 0; |
595 | 0 | } |
596 | |
|
597 | 0 | continue; |
598 | 0 | } |
599 | | |
600 | 0 | aiMaterial *mat = new aiMaterial; |
601 | 0 | aiString name; |
602 | 0 | name.Set(oldMat.mName); |
603 | 0 | mat->AddProperty(&name, AI_MATKEY_NAME); |
604 | | |
605 | | // Shading model: hard-coded to PHONG, there is no such information in an XFile |
606 | | // FIX (aramis): If the specular exponent is 0, use gouraud shading. This is a bugfix |
607 | | // for some models in the SDK (e.g. good old tiny.x) |
608 | 0 | int shadeMode = (int)oldMat.mSpecularExponent == 0.0f ? aiShadingMode_Gouraud : aiShadingMode_Phong; |
609 | |
|
610 | 0 | mat->AddProperty<int>(&shadeMode, 1, AI_MATKEY_SHADING_MODEL); |
611 | | // material colours |
612 | | // Unclear: there's no ambient colour, but emissive. What to put for ambient? |
613 | | // Probably nothing at all, let the user select a suitable default. |
614 | 0 | mat->AddProperty(&oldMat.mEmissive, 1, AI_MATKEY_COLOR_EMISSIVE); |
615 | 0 | mat->AddProperty(&oldMat.mDiffuse, 1, AI_MATKEY_COLOR_DIFFUSE); |
616 | 0 | mat->AddProperty(&oldMat.mSpecular, 1, AI_MATKEY_COLOR_SPECULAR); |
617 | 0 | mat->AddProperty(&oldMat.mSpecularExponent, 1, AI_MATKEY_SHININESS); |
618 | | |
619 | | // texture, if there is one |
620 | 0 | if (1 == oldMat.mTextures.size()) { |
621 | 0 | const XFile::TexEntry &otex = oldMat.mTextures.back(); |
622 | 0 | if (otex.mName.length()) { |
623 | | // if there is only one texture assume it contains the diffuse color |
624 | 0 | aiString tex(otex.mName); |
625 | 0 | if (otex.mIsNormalMap) { |
626 | 0 | mat->AddProperty(&tex, AI_MATKEY_TEXTURE_NORMALS(0)); |
627 | 0 | } else { |
628 | 0 | mat->AddProperty(&tex, AI_MATKEY_TEXTURE_DIFFUSE(0)); |
629 | 0 | } |
630 | 0 | } |
631 | 0 | } else { |
632 | | // Otherwise ... try to search for typical strings in the |
633 | | // texture's file name like 'bump' or 'diffuse' |
634 | 0 | unsigned int iHM = 0, iNM = 0, iDM = 0, iSM = 0, iAM = 0, iEM = 0; |
635 | 0 | for (unsigned int b = 0; b < oldMat.mTextures.size(); ++b) { |
636 | 0 | const XFile::TexEntry &otex = oldMat.mTextures[b]; |
637 | 0 | std::string sz = otex.mName; |
638 | 0 | if (!sz.length()) { |
639 | 0 | continue; |
640 | 0 | } |
641 | | |
642 | | // find the file name |
643 | 0 | std::string::size_type s = sz.find_last_of("\\/"); |
644 | 0 | if (std::string::npos == s) { |
645 | 0 | s = 0; |
646 | 0 | } |
647 | | |
648 | | // cut off the file extension |
649 | 0 | std::string::size_type sExt = sz.find_last_of('.'); |
650 | 0 | if (std::string::npos != sExt) { |
651 | 0 | sz[sExt] = '\0'; |
652 | 0 | } |
653 | | |
654 | | // convert to lower case for easier comparison |
655 | 0 | for (unsigned int c = 0; c < sz.length(); ++c) { |
656 | 0 | sz[c] = (char)tolower((unsigned char)sz[c]); |
657 | 0 | } |
658 | | |
659 | | // Place texture filename property under the corresponding name |
660 | 0 | aiString tex(oldMat.mTextures[b].mName); |
661 | | |
662 | | // bump map |
663 | 0 | if (std::string::npos != sz.find("bump", s) || std::string::npos != sz.find("height", s)) { |
664 | 0 | mat->AddProperty(&tex, AI_MATKEY_TEXTURE_HEIGHT(iHM++)); |
665 | 0 | } else if (otex.mIsNormalMap || std::string::npos != sz.find("normal", s) || std::string::npos != sz.find("nm", s)) { |
666 | 0 | mat->AddProperty(&tex, AI_MATKEY_TEXTURE_NORMALS(iNM++)); |
667 | 0 | } else if (std::string::npos != sz.find("spec", s) || std::string::npos != sz.find("glanz", s)) { |
668 | 0 | mat->AddProperty(&tex, AI_MATKEY_TEXTURE_SPECULAR(iSM++)); |
669 | 0 | } else if (std::string::npos != sz.find("ambi", s) || std::string::npos != sz.find("env", s)) { |
670 | 0 | mat->AddProperty(&tex, AI_MATKEY_TEXTURE_AMBIENT(iAM++)); |
671 | 0 | } else if (std::string::npos != sz.find("emissive", s) || std::string::npos != sz.find("self", s)) { |
672 | 0 | mat->AddProperty(&tex, AI_MATKEY_TEXTURE_EMISSIVE(iEM++)); |
673 | 0 | } else { |
674 | | // Assume it is a diffuse texture |
675 | 0 | mat->AddProperty(&tex, AI_MATKEY_TEXTURE_DIFFUSE(iDM++)); |
676 | 0 | } |
677 | 0 | } |
678 | 0 | } |
679 | |
|
680 | 0 | pScene->mMaterials[pScene->mNumMaterials] = mat; |
681 | 0 | oldMat.sceneIndex = pScene->mNumMaterials; |
682 | 0 | pScene->mNumMaterials++; |
683 | 0 | } |
684 | 0 | } |
685 | | |
686 | | } // namespace Assimp |
687 | | |
688 | | #endif // !! ASSIMP_BUILD_NO_X_IMPORTER |