/src/assimp/code/AssetLib/ASE/ASELoader.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 | | 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 ASELoader.cpp |
43 | | * @brief Implementation of the ASE importer class |
44 | | */ |
45 | | |
46 | | #ifndef ASSIMP_BUILD_NO_ASE_IMPORTER |
47 | | #ifndef ASSIMP_BUILD_NO_3DS_IMPORTER |
48 | | |
49 | | // internal headers |
50 | | #include "ASELoader.h" |
51 | | #include "Common/TargetAnimation.h" |
52 | | #include <assimp/SkeletonMeshBuilder.h> |
53 | | #include <assimp/StringComparison.h> |
54 | | |
55 | | #include <assimp/importerdesc.h> |
56 | | #include <assimp/scene.h> |
57 | | #include <assimp/DefaultLogger.hpp> |
58 | | #include <assimp/IOSystem.hpp> |
59 | | #include <assimp/Importer.hpp> |
60 | | |
61 | | #include <memory> |
62 | | |
63 | | // utilities |
64 | | #include <assimp/fast_atof.h> |
65 | | |
66 | | namespace Assimp { |
67 | | using namespace Assimp::ASE; |
68 | | |
69 | | static constexpr aiImporterDesc desc = { |
70 | | "ASE Importer", |
71 | | "", |
72 | | "", |
73 | | "Similar to 3DS but text-encoded", |
74 | | aiImporterFlags_SupportTextFlavour, |
75 | | 0, |
76 | | 0, |
77 | | 0, |
78 | | 0, |
79 | | "ase ask" |
80 | | }; |
81 | | |
82 | | // ------------------------------------------------------------------------------------------------ |
83 | | // Constructor to be privately used by Importer |
84 | | ASEImporter::ASEImporter() : |
85 | 33 | mParser(), mBuffer(), pcScene(), configRecomputeNormals(), noSkeletonMesh() { |
86 | | // empty |
87 | 33 | } |
88 | | |
89 | | // ------------------------------------------------------------------------------------------------ |
90 | | // Returns whether the class can handle the format of the given file. |
91 | 27 | bool ASEImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool /*checkSig*/) const { |
92 | 27 | static const char *tokens[] = { "*3dsmax_asciiexport" }; |
93 | 27 | return SearchFileHeaderForToken(pIOHandler, pFile, tokens, AI_COUNT_OF(tokens)); |
94 | 27 | } |
95 | | |
96 | | // ------------------------------------------------------------------------------------------------ |
97 | | // Loader meta information |
98 | 35 | const aiImporterDesc *ASEImporter::GetInfo() const { |
99 | 35 | return &desc; |
100 | 35 | } |
101 | | |
102 | | // ------------------------------------------------------------------------------------------------ |
103 | | // Setup configuration options |
104 | 2 | void ASEImporter::SetupProperties(const Importer *pImp) { |
105 | 2 | configRecomputeNormals = (pImp->GetPropertyInteger( |
106 | 2 | AI_CONFIG_IMPORT_ASE_RECONSTRUCT_NORMALS, 1) ? |
107 | 2 | true : |
108 | 2 | false); |
109 | | |
110 | 2 | noSkeletonMesh = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_NO_SKELETON_MESHES, 0) != 0; |
111 | 2 | } |
112 | | |
113 | | // ------------------------------------------------------------------------------------------------ |
114 | | // Imports the given file into the given scene structure. |
115 | | void ASEImporter::InternReadFile(const std::string &pFile, |
116 | 2 | aiScene *pScene, IOSystem *pIOHandler) { |
117 | 2 | std::unique_ptr<IOStream> file(pIOHandler->Open(pFile, "rb")); |
118 | | |
119 | | // Check whether we can read from the file |
120 | 2 | if (file == nullptr) { |
121 | 0 | throw DeadlyImportError("Failed to open ASE file ", pFile, "."); |
122 | 0 | } |
123 | | |
124 | | // Allocate storage and copy the contents of the file to a memory buffer |
125 | 2 | std::vector<char> mBuffer2; |
126 | 2 | TextFileToBuffer(file.get(), mBuffer2); |
127 | 2 | const size_t fileSize = mBuffer2.size(); |
128 | 2 | this->mBuffer = &mBuffer2[0]; |
129 | 2 | this->pcScene = pScene; |
130 | | |
131 | | // ------------------------------------------------------------------ |
132 | | // Guess the file format by looking at the extension |
133 | | // ASC is considered to be the older format 110, |
134 | | // ASE is the actual version 200 (that is currently written by max) |
135 | | // ------------------------------------------------------------------ |
136 | 2 | unsigned int defaultFormat; |
137 | 2 | std::string::size_type s = pFile.length() - 1; |
138 | 2 | switch (pFile.c_str()[s]) { |
139 | | |
140 | 0 | case 'C': |
141 | 0 | case 'c': |
142 | 0 | defaultFormat = AI_ASE_OLD_FILE_FORMAT; |
143 | 0 | break; |
144 | 2 | default: |
145 | 2 | defaultFormat = AI_ASE_NEW_FILE_FORMAT; |
146 | 2 | }; |
147 | | |
148 | | // Construct an ASE parser and parse the file |
149 | 2 | ASE::Parser parser(mBuffer, fileSize, defaultFormat); |
150 | 2 | mParser = &parser; |
151 | 2 | mParser->Parse(); |
152 | | |
153 | | //------------------------------------------------------------------ |
154 | | // Check whether we god at least one mesh. If we did - generate |
155 | | // materials and copy meshes. |
156 | | // ------------------------------------------------------------------ |
157 | 2 | if (!mParser->m_vMeshes.empty()) { |
158 | | |
159 | | // If absolutely no material has been loaded from the file |
160 | | // we need to generate a default material |
161 | 0 | GenerateDefaultMaterial(); |
162 | | |
163 | | // process all meshes |
164 | 0 | bool tookNormals = false; |
165 | 0 | std::vector<aiMesh *> avOutMeshes; |
166 | 0 | avOutMeshes.reserve(mParser->m_vMeshes.size() * 2); |
167 | 0 | for (std::vector<ASE::Mesh>::iterator i = mParser->m_vMeshes.begin(); i != mParser->m_vMeshes.end(); ++i) { |
168 | 0 | if ((*i).bSkip) { |
169 | 0 | continue; |
170 | 0 | } |
171 | 0 | BuildUniqueRepresentation(*i); |
172 | | |
173 | | // Need to generate proper vertex normals if necessary |
174 | 0 | if (GenerateNormals(*i)) { |
175 | 0 | tookNormals = true; |
176 | 0 | } |
177 | | |
178 | | // Convert all meshes to aiMesh objects |
179 | 0 | ConvertMeshes(*i, avOutMeshes); |
180 | 0 | } |
181 | 0 | if (tookNormals) { |
182 | 0 | ASSIMP_LOG_DEBUG("ASE: Taking normals from the file. Use " |
183 | 0 | "the AI_CONFIG_IMPORT_ASE_RECONSTRUCT_NORMALS setting if you " |
184 | 0 | "experience problems"); |
185 | 0 | } |
186 | | |
187 | | // Now build the output mesh list. Remove dummies |
188 | 0 | pScene->mNumMeshes = (unsigned int)avOutMeshes.size(); |
189 | 0 | aiMesh **pp = pScene->mMeshes = new aiMesh *[pScene->mNumMeshes]; |
190 | 0 | for (std::vector<aiMesh *>::const_iterator i = avOutMeshes.begin(); i != avOutMeshes.end(); ++i) { |
191 | 0 | if (!(*i)->mNumFaces) { |
192 | 0 | continue; |
193 | 0 | } |
194 | 0 | *pp++ = *i; |
195 | 0 | } |
196 | 0 | pScene->mNumMeshes = (unsigned int)(pp - pScene->mMeshes); |
197 | | |
198 | | // Build final material indices (remove submaterials and setup |
199 | | // the final list) |
200 | 0 | BuildMaterialIndices(); |
201 | 0 | } |
202 | | |
203 | | // ------------------------------------------------------------------ |
204 | | // Copy all scene graph nodes - lights, cameras, dummies and meshes |
205 | | // into one huge list. |
206 | | //------------------------------------------------------------------ |
207 | 2 | std::vector<BaseNode *> nodes; |
208 | 2 | nodes.reserve(mParser->m_vMeshes.size() + mParser->m_vLights.size() + mParser->m_vCameras.size() + mParser->m_vDummies.size()); |
209 | | |
210 | | // Lights |
211 | 2 | for (auto &light : mParser->m_vLights) |
212 | 0 | nodes.push_back(&light); |
213 | | // Cameras |
214 | 2 | for (auto &camera : mParser->m_vCameras) |
215 | 0 | nodes.push_back(&camera); |
216 | | // Meshes |
217 | 2 | for (auto &mesh : mParser->m_vMeshes) |
218 | 0 | nodes.push_back(&mesh); |
219 | | // Dummies |
220 | 2 | for (auto &dummy : mParser->m_vDummies) |
221 | 0 | nodes.push_back(&dummy); |
222 | | |
223 | | // build the final node graph |
224 | 2 | BuildNodes(nodes); |
225 | | |
226 | | // build output animations |
227 | 2 | BuildAnimations(nodes); |
228 | | |
229 | | // build output cameras |
230 | 2 | BuildCameras(); |
231 | | |
232 | | // build output lights |
233 | 2 | BuildLights(); |
234 | | |
235 | | // ------------------------------------------------------------------ |
236 | | // If we have no meshes use the SkeletonMeshBuilder helper class |
237 | | // to build a mesh for the animation skeleton |
238 | | // FIXME: very strange results |
239 | | // ------------------------------------------------------------------ |
240 | 2 | if (!pScene->mNumMeshes) { |
241 | 0 | pScene->mFlags |= AI_SCENE_FLAGS_INCOMPLETE; |
242 | 0 | if (!noSkeletonMesh) { |
243 | 0 | SkeletonMeshBuilder skeleton(pScene); |
244 | 0 | } |
245 | 0 | } |
246 | 2 | } |
247 | | // ------------------------------------------------------------------------------------------------ |
248 | 0 | void ASEImporter::GenerateDefaultMaterial() { |
249 | 0 | ai_assert(nullptr != mParser); |
250 | |
|
251 | 0 | bool bHas = false; |
252 | 0 | for (std::vector<ASE::Mesh>::iterator i = mParser->m_vMeshes.begin(); i != mParser->m_vMeshes.end(); ++i) { |
253 | 0 | if ((*i).bSkip) continue; |
254 | 0 | if (ASE::Face::DEFAULT_MATINDEX == (*i).iMaterialIndex) { |
255 | 0 | (*i).iMaterialIndex = (unsigned int)mParser->m_vMaterials.size(); |
256 | 0 | bHas = true; |
257 | 0 | } |
258 | 0 | } |
259 | 0 | if (bHas || mParser->m_vMaterials.empty()) { |
260 | | // add a simple material without submaterials to the parser's list |
261 | 0 | mParser->m_vMaterials.emplace_back(AI_DEFAULT_MATERIAL_NAME); |
262 | 0 | ASE::Material &mat = mParser->m_vMaterials.back(); |
263 | |
|
264 | 0 | mat.mDiffuse = aiColor3D(0.6f, 0.6f, 0.6f); |
265 | 0 | mat.mSpecular = aiColor3D(1.0f, 1.0f, 1.0f); |
266 | 0 | mat.mAmbient = aiColor3D(0.05f, 0.05f, 0.05f); |
267 | 0 | mat.mShading = Discreet3DS::Gouraud; |
268 | 0 | } |
269 | 0 | } |
270 | | |
271 | | // ------------------------------------------------------------------------------------------------ |
272 | 0 | void ASEImporter::BuildAnimations(const std::vector<BaseNode *> &nodes) { |
273 | | // check whether we have at least one mesh which has animations |
274 | 0 | std::vector<ASE::BaseNode *>::const_iterator i = nodes.begin(); |
275 | 0 | unsigned int iNum = 0; |
276 | 0 | for (; i != nodes.end(); ++i) { |
277 | | |
278 | | // TODO: Implement Bezier & TCB support |
279 | 0 | if ((*i)->mAnim.mPositionType != ASE::Animation::TRACK) { |
280 | 0 | ASSIMP_LOG_WARN("ASE: Position controller uses Bezier/TCB keys. " |
281 | 0 | "This is not supported."); |
282 | 0 | } |
283 | 0 | if ((*i)->mAnim.mRotationType != ASE::Animation::TRACK) { |
284 | 0 | ASSIMP_LOG_WARN("ASE: Rotation controller uses Bezier/TCB keys. " |
285 | 0 | "This is not supported."); |
286 | 0 | } |
287 | 0 | if ((*i)->mAnim.mScalingType != ASE::Animation::TRACK) { |
288 | 0 | ASSIMP_LOG_WARN("ASE: Position controller uses Bezier/TCB keys. " |
289 | 0 | "This is not supported."); |
290 | 0 | } |
291 | | |
292 | | // We compare against 1 here - firstly one key is not |
293 | | // really an animation and secondly MAX writes dummies |
294 | | // that represent the node transformation. |
295 | 0 | if ((*i)->mAnim.akeyPositions.size() > 1 || (*i)->mAnim.akeyRotations.size() > 1 || (*i)->mAnim.akeyScaling.size() > 1) { |
296 | 0 | ++iNum; |
297 | 0 | } |
298 | 0 | if ((*i)->mTargetAnim.akeyPositions.size() > 1 && is_not_qnan((*i)->mTargetPosition.x)) { |
299 | 0 | ++iNum; |
300 | 0 | } |
301 | 0 | } |
302 | 0 | if (iNum) { |
303 | | // Generate a new animation channel and setup everything for it |
304 | 0 | pcScene->mNumAnimations = 1; |
305 | 0 | pcScene->mAnimations = new aiAnimation *[1]; |
306 | 0 | aiAnimation *pcAnim = pcScene->mAnimations[0] = new aiAnimation(); |
307 | 0 | pcAnim->mNumChannels = iNum; |
308 | 0 | pcAnim->mChannels = new aiNodeAnim *[iNum]; |
309 | 0 | pcAnim->mTicksPerSecond = mParser->iFrameSpeed * mParser->iTicksPerFrame; |
310 | |
|
311 | 0 | iNum = 0; |
312 | | |
313 | | // Now iterate through all meshes and collect all data we can find |
314 | 0 | for (i = nodes.begin(); i != nodes.end(); ++i) { |
315 | |
|
316 | 0 | ASE::BaseNode *me = *i; |
317 | 0 | if (me->mTargetAnim.akeyPositions.size() > 1 && is_not_qnan(me->mTargetPosition.x)) { |
318 | | // Generate an extra channel for the camera/light target. |
319 | | // BuildNodes() does also generate an extra node, named |
320 | | // <baseName>.Target. |
321 | 0 | aiNodeAnim *nd = pcAnim->mChannels[iNum++] = new aiNodeAnim(); |
322 | 0 | nd->mNodeName.Set(me->mName + ".Target"); |
323 | | |
324 | | // Allocate the key array and fill it |
325 | 0 | nd->mNumPositionKeys = (unsigned int)me->mTargetAnim.akeyPositions.size(); |
326 | 0 | nd->mPositionKeys = new aiVectorKey[nd->mNumPositionKeys]; |
327 | |
|
328 | 0 | ::memcpy(nd->mPositionKeys, &me->mTargetAnim.akeyPositions[0], |
329 | 0 | nd->mNumPositionKeys * sizeof(aiVectorKey)); |
330 | 0 | } |
331 | |
|
332 | 0 | if (me->mAnim.akeyPositions.size() > 1 || me->mAnim.akeyRotations.size() > 1 || me->mAnim.akeyScaling.size() > 1) { |
333 | | // Begin a new node animation channel for this node |
334 | 0 | aiNodeAnim *nd = pcAnim->mChannels[iNum++] = new aiNodeAnim(); |
335 | 0 | nd->mNodeName.Set(me->mName); |
336 | | |
337 | | // copy position keys |
338 | 0 | if (me->mAnim.akeyPositions.size() > 1) { |
339 | | // Allocate the key array and fill it |
340 | 0 | nd->mNumPositionKeys = (unsigned int)me->mAnim.akeyPositions.size(); |
341 | 0 | nd->mPositionKeys = new aiVectorKey[nd->mNumPositionKeys]; |
342 | |
|
343 | 0 | ::memcpy(nd->mPositionKeys, &me->mAnim.akeyPositions[0], |
344 | 0 | nd->mNumPositionKeys * sizeof(aiVectorKey)); |
345 | 0 | } |
346 | | // copy rotation keys |
347 | 0 | if (me->mAnim.akeyRotations.size() > 1) { |
348 | | // Allocate the key array and fill it |
349 | 0 | nd->mNumRotationKeys = (unsigned int)me->mAnim.akeyRotations.size(); |
350 | 0 | nd->mRotationKeys = new aiQuatKey[nd->mNumRotationKeys]; |
351 | | |
352 | | // -------------------------------------------------------------------- |
353 | | // Rotation keys are offsets to the previous keys. |
354 | | // We have the quaternion representations of all |
355 | | // of them, so we just need to concatenate all |
356 | | // (unit-length) quaternions to get the absolute |
357 | | // rotations. |
358 | | // Rotation keys are ABSOLUTE for older files |
359 | | // -------------------------------------------------------------------- |
360 | |
|
361 | 0 | aiQuaternion cur; |
362 | 0 | for (unsigned int a = 0; a < nd->mNumRotationKeys; ++a) { |
363 | 0 | aiQuatKey q = me->mAnim.akeyRotations[a]; |
364 | |
|
365 | 0 | if (mParser->iFileFormat > 110) { |
366 | 0 | cur = (a ? cur * q.mValue : q.mValue); |
367 | 0 | q.mValue = cur.Normalize(); |
368 | 0 | } |
369 | 0 | nd->mRotationKeys[a] = q; |
370 | | |
371 | | // need this to get to Assimp quaternion conventions |
372 | 0 | nd->mRotationKeys[a].mValue.w *= -1.f; |
373 | 0 | } |
374 | 0 | } |
375 | | // copy scaling keys |
376 | 0 | if (me->mAnim.akeyScaling.size() > 1) { |
377 | | // Allocate the key array and fill it |
378 | 0 | nd->mNumScalingKeys = (unsigned int)me->mAnim.akeyScaling.size(); |
379 | 0 | nd->mScalingKeys = new aiVectorKey[nd->mNumScalingKeys]; |
380 | |
|
381 | 0 | ::memcpy(nd->mScalingKeys, &me->mAnim.akeyScaling[0], |
382 | 0 | nd->mNumScalingKeys * sizeof(aiVectorKey)); |
383 | 0 | } |
384 | 0 | } |
385 | 0 | } |
386 | 0 | } |
387 | 0 | } |
388 | | |
389 | | // ------------------------------------------------------------------------------------------------ |
390 | | // Build output cameras |
391 | 0 | void ASEImporter::BuildCameras() { |
392 | 0 | if (!mParser->m_vCameras.empty()) { |
393 | 0 | pcScene->mNumCameras = (unsigned int)mParser->m_vCameras.size(); |
394 | 0 | pcScene->mCameras = new aiCamera *[pcScene->mNumCameras]; |
395 | |
|
396 | 0 | for (unsigned int i = 0; i < pcScene->mNumCameras; ++i) { |
397 | 0 | aiCamera *out = pcScene->mCameras[i] = new aiCamera(); |
398 | 0 | ASE::Camera &in = mParser->m_vCameras[i]; |
399 | | |
400 | | // copy members |
401 | 0 | out->mClipPlaneFar = in.mFar; |
402 | 0 | out->mClipPlaneNear = (in.mNear ? in.mNear : 0.1f); |
403 | 0 | out->mHorizontalFOV = in.mFOV; |
404 | |
|
405 | 0 | out->mName.Set(in.mName); |
406 | 0 | } |
407 | 0 | } |
408 | 0 | } |
409 | | |
410 | | // ------------------------------------------------------------------------------------------------ |
411 | | // Build output lights |
412 | 0 | void ASEImporter::BuildLights() { |
413 | 0 | if (!mParser->m_vLights.empty()) { |
414 | 0 | pcScene->mNumLights = (unsigned int)mParser->m_vLights.size(); |
415 | 0 | pcScene->mLights = new aiLight *[pcScene->mNumLights]; |
416 | |
|
417 | 0 | for (unsigned int i = 0; i < pcScene->mNumLights; ++i) { |
418 | 0 | aiLight *out = pcScene->mLights[i] = new aiLight(); |
419 | 0 | ASE::Light &in = mParser->m_vLights[i]; |
420 | | |
421 | | // The direction is encoded in the transformation matrix of the node. |
422 | | // In 3DS MAX the light source points into negative Z direction if |
423 | | // the node transformation is the identity. |
424 | 0 | out->mDirection = aiVector3D(0.f, 0.f, -1.f); |
425 | |
|
426 | 0 | out->mName.Set(in.mName); |
427 | 0 | switch (in.mLightType) { |
428 | 0 | case ASE::Light::TARGET: |
429 | 0 | out->mType = aiLightSource_SPOT; |
430 | 0 | out->mAngleInnerCone = AI_DEG_TO_RAD(in.mAngle); |
431 | 0 | out->mAngleOuterCone = (in.mFalloff ? AI_DEG_TO_RAD(in.mFalloff) : out->mAngleInnerCone); |
432 | 0 | break; |
433 | | |
434 | 0 | case ASE::Light::DIRECTIONAL: |
435 | 0 | out->mType = aiLightSource_DIRECTIONAL; |
436 | 0 | break; |
437 | | |
438 | 0 | default: |
439 | | //case ASE::Light::OMNI: |
440 | 0 | out->mType = aiLightSource_POINT; |
441 | 0 | break; |
442 | 0 | }; |
443 | 0 | out->mColorDiffuse = out->mColorSpecular = in.mColor * in.mIntensity; |
444 | 0 | } |
445 | 0 | } |
446 | 0 | } |
447 | | |
448 | | // ------------------------------------------------------------------------------------------------ |
449 | 0 | void ASEImporter::AddNodes(const std::vector<BaseNode *> &nodes, aiNode *pcParent, const std::string &name) { |
450 | 0 | aiMatrix4x4 m; |
451 | 0 | AddNodes(nodes, pcParent, name, m); |
452 | 0 | } |
453 | | |
454 | | // ------------------------------------------------------------------------------------------------ |
455 | | // Add meshes to a given node |
456 | 0 | void ASEImporter::AddMeshes(const ASE::BaseNode *snode, aiNode *node) { |
457 | 0 | for (unsigned int i = 0; i < pcScene->mNumMeshes; ++i) { |
458 | | // Get the name of the mesh (the mesh instance has been temporarily stored in the third vertex color) |
459 | 0 | const aiMesh *pcMesh = pcScene->mMeshes[i]; |
460 | 0 | const ASE::Mesh *mesh = (const ASE::Mesh *)pcMesh->mColors[2]; |
461 | |
|
462 | 0 | if (mesh == snode) { |
463 | 0 | ++node->mNumMeshes; |
464 | 0 | } |
465 | 0 | } |
466 | |
|
467 | 0 | if (node->mNumMeshes) { |
468 | 0 | node->mMeshes = new unsigned int[node->mNumMeshes]; |
469 | 0 | for (unsigned int i = 0, p = 0; i < pcScene->mNumMeshes; ++i) { |
470 | |
|
471 | 0 | const aiMesh *pcMesh = pcScene->mMeshes[i]; |
472 | 0 | const ASE::Mesh *mesh = (const ASE::Mesh *)pcMesh->mColors[2]; |
473 | 0 | if (mesh == snode) { |
474 | 0 | node->mMeshes[p++] = i; |
475 | | |
476 | | // Transform all vertices of the mesh back into their local space -> |
477 | | // at the moment they are pretransformed |
478 | 0 | aiMatrix4x4 m = mesh->mTransform; |
479 | 0 | m.Inverse(); |
480 | |
|
481 | 0 | aiVector3D *pvCurPtr = pcMesh->mVertices; |
482 | 0 | const aiVector3D *pvEndPtr = pvCurPtr + pcMesh->mNumVertices; |
483 | 0 | while (pvCurPtr != pvEndPtr) { |
484 | 0 | *pvCurPtr = m * (*pvCurPtr); |
485 | 0 | pvCurPtr++; |
486 | 0 | } |
487 | | |
488 | | // Do the same for the normal vectors, if we have them. |
489 | | // As always, inverse transpose. |
490 | 0 | if (pcMesh->mNormals) { |
491 | 0 | aiMatrix3x3 m3 = aiMatrix3x3(mesh->mTransform); |
492 | 0 | m3.Transpose(); |
493 | |
|
494 | 0 | pvCurPtr = pcMesh->mNormals; |
495 | 0 | pvEndPtr = pvCurPtr + pcMesh->mNumVertices; |
496 | 0 | while (pvCurPtr != pvEndPtr) { |
497 | 0 | *pvCurPtr = m3 * (*pvCurPtr); |
498 | 0 | pvCurPtr++; |
499 | 0 | } |
500 | 0 | } |
501 | 0 | } |
502 | 0 | } |
503 | 0 | } |
504 | 0 | } |
505 | | |
506 | | // ------------------------------------------------------------------------------------------------ |
507 | | // Add child nodes to a given parent node |
508 | | void ASEImporter::AddNodes(const std::vector<BaseNode *> &nodes, aiNode *pcParent, const std::string &name, |
509 | 0 | const aiMatrix4x4 &mat) { |
510 | 0 | const size_t len = name.size(); |
511 | 0 | ai_assert(4 <= AI_MAX_NUMBER_OF_COLOR_SETS); |
512 | | |
513 | | // Receives child nodes for the pcParent node |
514 | 0 | std::vector<aiNode *> apcNodes; |
515 | | |
516 | | // Now iterate through all nodes in the scene and search for one |
517 | | // which has *us* as parent. |
518 | 0 | for (std::vector<BaseNode *>::const_iterator it = nodes.begin(), end = nodes.end(); it != end; ++it) { |
519 | 0 | const BaseNode *snode = *it; |
520 | 0 | if (!name.empty()) { |
521 | 0 | if (len != snode->mParent.length() || name != snode->mParent.c_str()) { |
522 | 0 | continue; |
523 | 0 | } |
524 | 0 | } else if (snode->mParent.length()) { |
525 | 0 | continue; |
526 | 0 | } |
527 | | |
528 | 0 | (*it)->mProcessed = true; |
529 | | |
530 | | // Allocate a new node and add it to the output data structure |
531 | 0 | apcNodes.push_back(new aiNode); |
532 | 0 | aiNode *node = apcNodes.back(); |
533 | |
|
534 | 0 | node->mName.Set((snode->mName.length() ? snode->mName.c_str() : "Unnamed_Node")); |
535 | 0 | node->mParent = pcParent; |
536 | | |
537 | | // Setup the transformation matrix of the node |
538 | 0 | aiMatrix4x4 mParentAdjust = mat; |
539 | 0 | mParentAdjust.Inverse(); |
540 | 0 | node->mTransformation = mParentAdjust * snode->mTransform; |
541 | | |
542 | | // Add sub nodes - prevent stack overflow due to recursive parenting |
543 | 0 | if (node->mName != node->mParent->mName && node->mName != node->mParent->mParent->mName) { |
544 | 0 | AddNodes(nodes, node, node->mName.C_Str(), snode->mTransform); |
545 | 0 | } |
546 | | |
547 | | // Further processing depends on the type of the node |
548 | 0 | if (snode->mType == ASE::BaseNode::Mesh) { |
549 | | // If the type of this node is "Mesh" we need to search |
550 | | // the list of output meshes in the data structure for |
551 | | // all those that belonged to this node once. This is |
552 | | // slightly inconvinient here and a better solution should |
553 | | // be used when this code is refactored next. |
554 | 0 | AddMeshes(snode, node); |
555 | 0 | } else if (is_not_qnan(snode->mTargetPosition.x)) { |
556 | | // If this is a target camera or light we generate a small |
557 | | // child node which marks the position of the camera |
558 | | // target (the direction information is contained in *this* |
559 | | // node's animation track but the exact target position |
560 | | // would be lost otherwise) |
561 | 0 | if (!node->mNumChildren) { |
562 | 0 | node->mChildren = new aiNode *[1]; |
563 | 0 | } |
564 | |
|
565 | 0 | aiNode *nd = new aiNode(); |
566 | |
|
567 | 0 | nd->mName.Set(snode->mName + ".Target"); |
568 | |
|
569 | 0 | nd->mTransformation.a4 = snode->mTargetPosition.x - snode->mTransform.a4; |
570 | 0 | nd->mTransformation.b4 = snode->mTargetPosition.y - snode->mTransform.b4; |
571 | 0 | nd->mTransformation.c4 = snode->mTargetPosition.z - snode->mTransform.c4; |
572 | |
|
573 | 0 | nd->mParent = node; |
574 | | |
575 | | // The .Target node is always the first child node |
576 | 0 | for (unsigned int m = 0; m < node->mNumChildren; ++m) |
577 | 0 | node->mChildren[m + 1] = node->mChildren[m]; |
578 | |
|
579 | 0 | node->mChildren[0] = nd; |
580 | 0 | node->mNumChildren++; |
581 | | |
582 | | // What we did is so great, it is at least worth a debug message |
583 | 0 | ASSIMP_LOG_VERBOSE_DEBUG("ASE: Generating separate target node (", snode->mName, ")"); |
584 | 0 | } |
585 | 0 | } |
586 | | |
587 | | // Allocate enough space for the child nodes |
588 | | // We allocate one slot more in case this is a target camera/light |
589 | 0 | pcParent->mNumChildren = (unsigned int)apcNodes.size(); |
590 | 0 | if (pcParent->mNumChildren) { |
591 | 0 | pcParent->mChildren = new aiNode *[apcNodes.size() + 1 /* PLUS ONE !!! */]; |
592 | | |
593 | | // now build all nodes for our nice new children |
594 | 0 | for (unsigned int p = 0; p < apcNodes.size(); ++p) |
595 | 0 | pcParent->mChildren[p] = apcNodes[p]; |
596 | 0 | } |
597 | 0 | return; |
598 | 0 | } |
599 | | |
600 | | // ------------------------------------------------------------------------------------------------ |
601 | | // Build the output node graph |
602 | 0 | void ASEImporter::BuildNodes(std::vector<BaseNode *> &nodes) { |
603 | 0 | ai_assert(nullptr != pcScene); |
604 | | |
605 | | // allocate the one and only root node |
606 | 0 | aiNode *root = pcScene->mRootNode = new aiNode(); |
607 | 0 | root->mName.Set("<ASERoot>"); |
608 | | |
609 | | // Setup the coordinate system transformation |
610 | 0 | pcScene->mRootNode->mNumChildren = 1; |
611 | 0 | pcScene->mRootNode->mChildren = new aiNode *[1]; |
612 | 0 | aiNode *ch = pcScene->mRootNode->mChildren[0] = new aiNode(); |
613 | 0 | ch->mParent = root; |
614 | | |
615 | | // Change the transformation matrix of all nodes |
616 | 0 | for (BaseNode *node : nodes) { |
617 | 0 | aiMatrix4x4 &m = node->mTransform; |
618 | 0 | m.Transpose(); // row-order vs column-order |
619 | 0 | } |
620 | | |
621 | | // add all nodes |
622 | 0 | static const std::string none = ""; |
623 | 0 | AddNodes(nodes, ch, none); |
624 | | |
625 | | // now iterate through al nodes and find those that have not yet |
626 | | // been added to the nodegraph (= their parent could not be recognized) |
627 | 0 | std::vector<const BaseNode *> aiList; |
628 | 0 | for (std::vector<BaseNode *>::iterator it = nodes.begin(), end = nodes.end(); it != end; ++it) { |
629 | 0 | if ((*it)->mProcessed) { |
630 | 0 | continue; |
631 | 0 | } |
632 | | |
633 | | // check whether our parent is known |
634 | 0 | bool bKnowParent = false; |
635 | | |
636 | | // search the list another time, starting *here* and try to find out whether |
637 | | // there is a node that references *us* as a parent |
638 | 0 | for (std::vector<BaseNode *>::const_iterator it2 = nodes.begin(); it2 != end; ++it2) { |
639 | 0 | if (it2 == it) { |
640 | 0 | continue; |
641 | 0 | } |
642 | | |
643 | 0 | if ((*it2)->mName == (*it)->mParent) { |
644 | 0 | bKnowParent = true; |
645 | 0 | break; |
646 | 0 | } |
647 | 0 | } |
648 | 0 | if (!bKnowParent) { |
649 | 0 | aiList.push_back(*it); |
650 | 0 | } |
651 | 0 | } |
652 | | |
653 | | // Are there any orphaned nodes? |
654 | 0 | if (!aiList.empty()) { |
655 | 0 | std::vector<aiNode *> apcNodes; |
656 | 0 | apcNodes.reserve(aiList.size() + pcScene->mRootNode->mNumChildren); |
657 | |
|
658 | 0 | for (unsigned int i = 0; i < pcScene->mRootNode->mNumChildren; ++i) |
659 | 0 | apcNodes.push_back(pcScene->mRootNode->mChildren[i]); |
660 | |
|
661 | 0 | delete[] pcScene->mRootNode->mChildren; |
662 | 0 | for (std::vector<const BaseNode *>::/*const_*/ iterator i = aiList.begin(); i != aiList.end(); ++i) { |
663 | 0 | const ASE::BaseNode *src = *i; |
664 | | |
665 | | // The parent is not known, so we can assume that we must add |
666 | | // this node to the root node of the whole scene |
667 | 0 | aiNode *pcNode = new aiNode(); |
668 | 0 | pcNode->mParent = pcScene->mRootNode; |
669 | 0 | pcNode->mName.Set(src->mName); |
670 | 0 | AddMeshes(src, pcNode); |
671 | 0 | AddNodes(nodes, pcNode, pcNode->mName.data); |
672 | 0 | apcNodes.push_back(pcNode); |
673 | 0 | } |
674 | | |
675 | | // Regenerate our output array |
676 | 0 | pcScene->mRootNode->mChildren = new aiNode *[apcNodes.size()]; |
677 | 0 | for (unsigned int i = 0; i < apcNodes.size(); ++i) |
678 | 0 | pcScene->mRootNode->mChildren[i] = apcNodes[i]; |
679 | |
|
680 | 0 | pcScene->mRootNode->mNumChildren = (unsigned int)apcNodes.size(); |
681 | 0 | } |
682 | | |
683 | | // Reset the third color set to nullptr - we used this field to store a temporary pointer |
684 | 0 | for (unsigned int i = 0; i < pcScene->mNumMeshes; ++i) |
685 | 0 | pcScene->mMeshes[i]->mColors[2] = nullptr; |
686 | | |
687 | | // The root node should not have at least one child or the file is valid |
688 | 0 | if (!pcScene->mRootNode->mNumChildren) { |
689 | 0 | throw DeadlyImportError("ASE: No nodes loaded. The file is either empty or corrupt"); |
690 | 0 | } |
691 | | |
692 | | // Now rotate the whole scene 90 degrees around the x axis to convert to internal coordinate system |
693 | 0 | pcScene->mRootNode->mTransformation = aiMatrix4x4(1.f, 0.f, 0.f, 0.f, |
694 | 0 | 0.f, 0.f, 1.f, 0.f, 0.f, -1.f, 0.f, 0.f, 0.f, 0.f, 0.f, 1.f); |
695 | 0 | } |
696 | | |
697 | | // ------------------------------------------------------------------------------------------------ |
698 | | // Convert the imported data to the internal verbose representation |
699 | 0 | void ASEImporter::BuildUniqueRepresentation(ASE::Mesh &mesh) { |
700 | | // allocate output storage |
701 | 0 | std::vector<aiVector3D> mPositions; |
702 | 0 | std::vector<aiVector3D> amTexCoords[AI_MAX_NUMBER_OF_TEXTURECOORDS]; |
703 | 0 | std::vector<aiColor4D> mVertexColors; |
704 | 0 | std::vector<aiVector3D> mNormals; |
705 | 0 | std::vector<BoneVertex> mBoneVertices; |
706 | |
|
707 | 0 | unsigned int iSize = (unsigned int)mesh.mFaces.size() * 3; |
708 | 0 | mPositions.resize(iSize); |
709 | | |
710 | | // optional texture coordinates |
711 | 0 | for (unsigned int i = 0; i < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++i) { |
712 | 0 | if (!mesh.amTexCoords[i].empty()) { |
713 | 0 | amTexCoords[i].resize(iSize); |
714 | 0 | } |
715 | 0 | } |
716 | | // optional vertex colors |
717 | 0 | if (!mesh.mVertexColors.empty()) { |
718 | 0 | mVertexColors.resize(iSize); |
719 | 0 | } |
720 | | |
721 | | // optional vertex normals (vertex normals can simply be copied) |
722 | 0 | if (!mesh.mNormals.empty()) { |
723 | 0 | mNormals.resize(iSize); |
724 | 0 | } |
725 | | // bone vertices. There is no need to change the bone list |
726 | 0 | if (!mesh.mBoneVertices.empty()) { |
727 | 0 | mBoneVertices.resize(iSize); |
728 | 0 | } |
729 | | |
730 | | // iterate through all faces in the mesh |
731 | 0 | unsigned int iCurrent = 0, fi = 0; |
732 | 0 | for (std::vector<ASE::Face>::iterator i = mesh.mFaces.begin(); i != mesh.mFaces.end(); ++i, ++fi) { |
733 | 0 | for (unsigned int n = 0; n < 3; ++n, ++iCurrent) { |
734 | 0 | mPositions[iCurrent] = mesh.mPositions[(*i).mIndices[n]]; |
735 | | |
736 | | // add texture coordinates |
737 | 0 | for (unsigned int c = 0; c < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++c) { |
738 | 0 | if (mesh.amTexCoords[c].empty()) break; |
739 | 0 | amTexCoords[c][iCurrent] = mesh.amTexCoords[c][(*i).amUVIndices[c][n]]; |
740 | 0 | } |
741 | | // add vertex colors |
742 | 0 | if (!mesh.mVertexColors.empty()) { |
743 | 0 | mVertexColors[iCurrent] = mesh.mVertexColors[(*i).mColorIndices[n]]; |
744 | 0 | } |
745 | | // add normal vectors |
746 | 0 | if (!mesh.mNormals.empty()) { |
747 | 0 | mNormals[iCurrent] = mesh.mNormals[fi * 3 + n]; |
748 | 0 | mNormals[iCurrent].Normalize(); |
749 | 0 | } |
750 | | |
751 | | // handle bone vertices |
752 | 0 | if ((*i).mIndices[n] < mesh.mBoneVertices.size()) { |
753 | | // (sometimes this will cause bone verts to be duplicated |
754 | | // however, I' quite sure Schrompf' JoinVerticesStep |
755 | | // will fix that again ...) |
756 | 0 | mBoneVertices[iCurrent] = mesh.mBoneVertices[(*i).mIndices[n]]; |
757 | 0 | } |
758 | 0 | (*i).mIndices[n] = iCurrent; |
759 | 0 | } |
760 | 0 | } |
761 | | |
762 | | // replace the old arrays |
763 | 0 | mesh.mNormals = mNormals; |
764 | 0 | mesh.mPositions = mPositions; |
765 | 0 | mesh.mVertexColors = mVertexColors; |
766 | |
|
767 | 0 | for (unsigned int c = 0; c < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++c) |
768 | 0 | mesh.amTexCoords[c] = amTexCoords[c]; |
769 | 0 | } |
770 | | |
771 | | // ------------------------------------------------------------------------------------------------ |
772 | | // Copy a texture from the ASE structs to the output material |
773 | 0 | void CopyASETexture(aiMaterial &mat, ASE::Texture &texture, aiTextureType type) { |
774 | | // Setup the texture name |
775 | 0 | aiString tex; |
776 | 0 | tex.Set(texture.mMapName); |
777 | 0 | mat.AddProperty(&tex, AI_MATKEY_TEXTURE(type, 0)); |
778 | | |
779 | | // Setup the texture blend factor |
780 | 0 | if (is_not_qnan(texture.mTextureBlend)) |
781 | 0 | mat.AddProperty<ai_real>(&texture.mTextureBlend, 1, AI_MATKEY_TEXBLEND(type, 0)); |
782 | | |
783 | | // Setup texture UV transformations |
784 | 0 | mat.AddProperty<ai_real>(&texture.mOffsetU, 5, AI_MATKEY_UVTRANSFORM(type, 0)); |
785 | 0 | } |
786 | | |
787 | | // ------------------------------------------------------------------------------------------------ |
788 | | // Convert from ASE material to output material |
789 | 0 | void ASEImporter::ConvertMaterial(ASE::Material &mat) { |
790 | | // LARGE TODO: Much code her is copied from 3DS ... join them maybe? |
791 | | |
792 | | // Allocate the output material |
793 | 0 | mat.pcInstance = new aiMaterial(); |
794 | | |
795 | | // At first add the base ambient color of the |
796 | | // scene to the material |
797 | 0 | mat.mAmbient.r += mParser->m_clrAmbient.r; |
798 | 0 | mat.mAmbient.g += mParser->m_clrAmbient.g; |
799 | 0 | mat.mAmbient.b += mParser->m_clrAmbient.b; |
800 | |
|
801 | 0 | aiString name; |
802 | 0 | name.Set(mat.mName); |
803 | 0 | mat.pcInstance->AddProperty(&name, AI_MATKEY_NAME); |
804 | | |
805 | | // material colors |
806 | 0 | mat.pcInstance->AddProperty(&mat.mAmbient, 1, AI_MATKEY_COLOR_AMBIENT); |
807 | 0 | mat.pcInstance->AddProperty(&mat.mDiffuse, 1, AI_MATKEY_COLOR_DIFFUSE); |
808 | 0 | mat.pcInstance->AddProperty(&mat.mSpecular, 1, AI_MATKEY_COLOR_SPECULAR); |
809 | 0 | mat.pcInstance->AddProperty(&mat.mEmissive, 1, AI_MATKEY_COLOR_EMISSIVE); |
810 | | |
811 | | // shininess |
812 | 0 | if (0.0f != mat.mSpecularExponent && 0.0f != mat.mShininessStrength) { |
813 | 0 | mat.pcInstance->AddProperty(&mat.mSpecularExponent, 1, AI_MATKEY_SHININESS); |
814 | 0 | mat.pcInstance->AddProperty(&mat.mShininessStrength, 1, AI_MATKEY_SHININESS_STRENGTH); |
815 | 0 | } |
816 | | // If there is no shininess, we can disable phong lighting |
817 | 0 | else if (D3DS::Discreet3DS::Metal == mat.mShading || |
818 | 0 | D3DS::Discreet3DS::Phong == mat.mShading || |
819 | 0 | D3DS::Discreet3DS::Blinn == mat.mShading) { |
820 | 0 | mat.mShading = D3DS::Discreet3DS::Gouraud; |
821 | 0 | } |
822 | | |
823 | | // opacity |
824 | 0 | mat.pcInstance->AddProperty<ai_real>(&mat.mTransparency, 1, AI_MATKEY_OPACITY); |
825 | | |
826 | | // Two sided rendering? |
827 | 0 | if (mat.mTwoSided) { |
828 | 0 | int i = 1; |
829 | 0 | mat.pcInstance->AddProperty<int>(&i, 1, AI_MATKEY_TWOSIDED); |
830 | 0 | } |
831 | | |
832 | | // shading mode |
833 | 0 | aiShadingMode eShading = aiShadingMode_NoShading; |
834 | 0 | switch (mat.mShading) { |
835 | 0 | case D3DS::Discreet3DS::Flat: |
836 | 0 | eShading = aiShadingMode_Flat; |
837 | 0 | break; |
838 | 0 | case D3DS::Discreet3DS::Phong: |
839 | 0 | eShading = aiShadingMode_Phong; |
840 | 0 | break; |
841 | 0 | case D3DS::Discreet3DS::Blinn: |
842 | 0 | eShading = aiShadingMode_Blinn; |
843 | 0 | break; |
844 | | |
845 | | // I don't know what "Wire" shading should be, |
846 | | // assume it is simple lambertian diffuse (L dot N) shading |
847 | 0 | case D3DS::Discreet3DS::Wire: { |
848 | | // set the wireframe flag |
849 | 0 | unsigned int iWire = 1; |
850 | 0 | mat.pcInstance->AddProperty<int>((int *)&iWire, 1, AI_MATKEY_ENABLE_WIREFRAME); |
851 | 0 | } |
852 | | // fallthrough |
853 | 0 | case D3DS::Discreet3DS::Gouraud: |
854 | 0 | eShading = aiShadingMode_Gouraud; |
855 | 0 | break; |
856 | 0 | case D3DS::Discreet3DS::Metal: |
857 | 0 | eShading = aiShadingMode_CookTorrance; |
858 | 0 | break; |
859 | 0 | } |
860 | 0 | mat.pcInstance->AddProperty<int>((int *)&eShading, 1, AI_MATKEY_SHADING_MODEL); |
861 | | |
862 | | // DIFFUSE texture |
863 | 0 | if (mat.sTexDiffuse.mMapName.length() > 0) |
864 | 0 | CopyASETexture(*mat.pcInstance, mat.sTexDiffuse, aiTextureType_DIFFUSE); |
865 | | |
866 | | // SPECULAR texture |
867 | 0 | if (mat.sTexSpecular.mMapName.length() > 0) |
868 | 0 | CopyASETexture(*mat.pcInstance, mat.sTexSpecular, aiTextureType_SPECULAR); |
869 | | |
870 | | // AMBIENT texture |
871 | 0 | if (mat.sTexAmbient.mMapName.length() > 0) |
872 | 0 | CopyASETexture(*mat.pcInstance, mat.sTexAmbient, aiTextureType_AMBIENT); |
873 | | |
874 | | // OPACITY texture |
875 | 0 | if (mat.sTexOpacity.mMapName.length() > 0) |
876 | 0 | CopyASETexture(*mat.pcInstance, mat.sTexOpacity, aiTextureType_OPACITY); |
877 | | |
878 | | // EMISSIVE texture |
879 | 0 | if (mat.sTexEmissive.mMapName.length() > 0) |
880 | 0 | CopyASETexture(*mat.pcInstance, mat.sTexEmissive, aiTextureType_EMISSIVE); |
881 | | |
882 | | // BUMP texture |
883 | 0 | if (mat.sTexBump.mMapName.length() > 0) |
884 | 0 | CopyASETexture(*mat.pcInstance, mat.sTexBump, aiTextureType_HEIGHT); |
885 | | |
886 | | // SHININESS texture |
887 | 0 | if (mat.sTexShininess.mMapName.length() > 0) |
888 | 0 | CopyASETexture(*mat.pcInstance, mat.sTexShininess, aiTextureType_SHININESS); |
889 | | |
890 | | // store the name of the material itself, too |
891 | 0 | if (mat.mName.length() > 0) { |
892 | 0 | aiString tex; |
893 | 0 | tex.Set(mat.mName); |
894 | 0 | mat.pcInstance->AddProperty(&tex, AI_MATKEY_NAME); |
895 | 0 | } |
896 | 0 | return; |
897 | 0 | } |
898 | | |
899 | | // ------------------------------------------------------------------------------------------------ |
900 | | // Build output meshes |
901 | 0 | void ASEImporter::ConvertMeshes(ASE::Mesh &mesh, std::vector<aiMesh *> &avOutMeshes) { |
902 | | // validate the material index of the mesh |
903 | 0 | if (mesh.iMaterialIndex >= mParser->m_vMaterials.size()) { |
904 | 0 | mesh.iMaterialIndex = (unsigned int)mParser->m_vMaterials.size() - 1; |
905 | 0 | ASSIMP_LOG_WARN("Material index is out of range"); |
906 | 0 | } |
907 | | |
908 | | // If the material the mesh is assigned to consists of submeshes, split it |
909 | 0 | if (!mParser->m_vMaterials[mesh.iMaterialIndex].avSubMaterials.empty()) { |
910 | 0 | std::vector<ASE::Material> vSubMaterials = mParser->m_vMaterials[mesh.iMaterialIndex].avSubMaterials; |
911 | |
|
912 | 0 | std::vector<unsigned int> *aiSplit = new std::vector<unsigned int>[vSubMaterials.size()]; |
913 | | |
914 | | // build a list of all faces per sub-material |
915 | 0 | for (unsigned int i = 0; i < mesh.mFaces.size(); ++i) { |
916 | | // check range |
917 | 0 | if (mesh.mFaces[i].iMaterial >= vSubMaterials.size()) { |
918 | 0 | ASSIMP_LOG_WARN("Submaterial index is out of range"); |
919 | | |
920 | | // use the last material instead |
921 | 0 | aiSplit[vSubMaterials.size() - 1].push_back(i); |
922 | 0 | } else |
923 | 0 | aiSplit[mesh.mFaces[i].iMaterial].push_back(i); |
924 | 0 | } |
925 | | |
926 | | // now generate submeshes |
927 | 0 | for (unsigned int p = 0; p < vSubMaterials.size(); ++p) { |
928 | 0 | if (!aiSplit[p].empty()) { |
929 | |
|
930 | 0 | aiMesh *p_pcOut = new aiMesh(); |
931 | 0 | p_pcOut->mPrimitiveTypes = aiPrimitiveType_TRIANGLE; |
932 | | |
933 | | // let the sub material index |
934 | 0 | p_pcOut->mMaterialIndex = p; |
935 | | |
936 | | // we will need this material |
937 | 0 | mParser->m_vMaterials[mesh.iMaterialIndex].avSubMaterials[p].bNeed = true; |
938 | | |
939 | | // store the real index here ... color channel 3 |
940 | 0 | p_pcOut->mColors[3] = (aiColor4D *)(uintptr_t)mesh.iMaterialIndex; |
941 | | |
942 | | // store a pointer to the mesh in color channel 2 |
943 | 0 | p_pcOut->mColors[2] = (aiColor4D *)&mesh; |
944 | 0 | avOutMeshes.push_back(p_pcOut); |
945 | | |
946 | | // convert vertices |
947 | 0 | p_pcOut->mNumVertices = (unsigned int)aiSplit[p].size() * 3; |
948 | 0 | p_pcOut->mNumFaces = (unsigned int)aiSplit[p].size(); |
949 | | |
950 | | // receive output vertex weights |
951 | 0 | std::vector<std::pair<unsigned int, float>> *avOutputBones = nullptr; |
952 | 0 | if (!mesh.mBones.empty()) { |
953 | 0 | avOutputBones = new std::vector<std::pair<unsigned int, float>>[mesh.mBones.size()]; |
954 | 0 | } |
955 | | |
956 | | // allocate enough storage for faces |
957 | 0 | p_pcOut->mFaces = new aiFace[p_pcOut->mNumFaces]; |
958 | |
|
959 | 0 | unsigned int iBase = 0, iIndex; |
960 | 0 | if (p_pcOut->mNumVertices) { |
961 | 0 | p_pcOut->mVertices = new aiVector3D[p_pcOut->mNumVertices]; |
962 | 0 | p_pcOut->mNormals = new aiVector3D[p_pcOut->mNumVertices]; |
963 | 0 | for (unsigned int q = 0; q < aiSplit[p].size(); ++q) { |
964 | |
|
965 | 0 | iIndex = aiSplit[p][q]; |
966 | |
|
967 | 0 | p_pcOut->mFaces[q].mIndices = new unsigned int[3]; |
968 | 0 | p_pcOut->mFaces[q].mNumIndices = 3; |
969 | |
|
970 | 0 | for (unsigned int t = 0; t < 3; ++t, ++iBase) { |
971 | 0 | const uint32_t iIndex2 = mesh.mFaces[iIndex].mIndices[t]; |
972 | |
|
973 | 0 | p_pcOut->mVertices[iBase] = mesh.mPositions[iIndex2]; |
974 | 0 | p_pcOut->mNormals[iBase] = mesh.mNormals[iIndex2]; |
975 | | |
976 | | // convert bones, if existing |
977 | 0 | if (!mesh.mBones.empty()) { |
978 | 0 | ai_assert(avOutputBones); |
979 | | // check whether there is a vertex weight for this vertex index |
980 | 0 | if (iIndex2 < mesh.mBoneVertices.size()) { |
981 | |
|
982 | 0 | for (std::vector<std::pair<int, float>>::const_iterator |
983 | 0 | blubb = mesh.mBoneVertices[iIndex2].mBoneWeights.begin(); |
984 | 0 | blubb != mesh.mBoneVertices[iIndex2].mBoneWeights.end(); ++blubb) { |
985 | | |
986 | | // NOTE: illegal cases have already been filtered out |
987 | 0 | avOutputBones[(*blubb).first].emplace_back( |
988 | 0 | iBase, (*blubb).second); |
989 | 0 | } |
990 | 0 | } |
991 | 0 | } |
992 | 0 | p_pcOut->mFaces[q].mIndices[t] = iBase; |
993 | 0 | } |
994 | 0 | } |
995 | 0 | } |
996 | | // convert texture coordinates (up to AI_MAX_NUMBER_OF_TEXTURECOORDS sets supported) |
997 | 0 | for (unsigned int c = 0; c < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++c) { |
998 | 0 | if (!mesh.amTexCoords[c].empty()) { |
999 | 0 | p_pcOut->mTextureCoords[c] = new aiVector3D[p_pcOut->mNumVertices]; |
1000 | 0 | iBase = 0; |
1001 | 0 | for (unsigned int q = 0; q < aiSplit[p].size(); ++q) { |
1002 | 0 | iIndex = aiSplit[p][q]; |
1003 | 0 | for (unsigned int t = 0; t < 3; ++t) { |
1004 | 0 | p_pcOut->mTextureCoords[c][iBase++] = mesh.amTexCoords[c][mesh.mFaces[iIndex].mIndices[t]]; |
1005 | 0 | } |
1006 | 0 | } |
1007 | | // Setup the number of valid vertex components |
1008 | 0 | p_pcOut->mNumUVComponents[c] = mesh.mNumUVComponents[c]; |
1009 | 0 | } |
1010 | 0 | } |
1011 | | |
1012 | | // Convert vertex colors (only one set supported) |
1013 | 0 | if (!mesh.mVertexColors.empty()) { |
1014 | 0 | p_pcOut->mColors[0] = new aiColor4D[p_pcOut->mNumVertices]; |
1015 | 0 | iBase = 0; |
1016 | 0 | for (unsigned int q = 0; q < aiSplit[p].size(); ++q) { |
1017 | 0 | iIndex = aiSplit[p][q]; |
1018 | 0 | for (unsigned int t = 0; t < 3; ++t) { |
1019 | 0 | p_pcOut->mColors[0][iBase++] = mesh.mVertexColors[mesh.mFaces[iIndex].mIndices[t]]; |
1020 | 0 | } |
1021 | 0 | } |
1022 | 0 | } |
1023 | | // Copy bones |
1024 | 0 | if (!mesh.mBones.empty()) { |
1025 | 0 | p_pcOut->mNumBones = 0; |
1026 | 0 | for (unsigned int mrspock = 0; mrspock < mesh.mBones.size(); ++mrspock) |
1027 | 0 | if (!avOutputBones[mrspock].empty()) p_pcOut->mNumBones++; |
1028 | |
|
1029 | 0 | p_pcOut->mBones = new aiBone *[p_pcOut->mNumBones]; |
1030 | 0 | aiBone **pcBone = p_pcOut->mBones; |
1031 | 0 | for (unsigned int mrspock = 0; mrspock < mesh.mBones.size(); ++mrspock) { |
1032 | 0 | if (!avOutputBones[mrspock].empty()) { |
1033 | | // we will need this bone. add it to the output mesh and |
1034 | | // add all per-vertex weights |
1035 | 0 | aiBone *pc = *pcBone = new aiBone(); |
1036 | 0 | pc->mName.Set(mesh.mBones[mrspock].mName); |
1037 | |
|
1038 | 0 | pc->mNumWeights = (unsigned int)avOutputBones[mrspock].size(); |
1039 | 0 | pc->mWeights = new aiVertexWeight[pc->mNumWeights]; |
1040 | |
|
1041 | 0 | for (unsigned int captainkirk = 0; captainkirk < pc->mNumWeights; ++captainkirk) { |
1042 | 0 | const std::pair<unsigned int, float> &ref = avOutputBones[mrspock][captainkirk]; |
1043 | 0 | pc->mWeights[captainkirk].mVertexId = ref.first; |
1044 | 0 | pc->mWeights[captainkirk].mWeight = ref.second; |
1045 | 0 | } |
1046 | 0 | ++pcBone; |
1047 | 0 | } |
1048 | 0 | } |
1049 | | // delete allocated storage |
1050 | 0 | delete[] avOutputBones; |
1051 | 0 | } |
1052 | 0 | } |
1053 | 0 | } |
1054 | | // delete storage |
1055 | 0 | delete[] aiSplit; |
1056 | 0 | } else { |
1057 | | // Otherwise we can simply copy the data to one output mesh |
1058 | | // This codepath needs less memory and uses fast memcpy()s |
1059 | | // to do the actual copying. So I think it is worth the |
1060 | | // effort here. |
1061 | |
|
1062 | 0 | aiMesh *p_pcOut = new aiMesh(); |
1063 | 0 | p_pcOut->mPrimitiveTypes = aiPrimitiveType_TRIANGLE; |
1064 | | |
1065 | | // set an empty sub material index |
1066 | 0 | p_pcOut->mMaterialIndex = ASE::Face::DEFAULT_MATINDEX; |
1067 | 0 | mParser->m_vMaterials[mesh.iMaterialIndex].bNeed = true; |
1068 | | |
1069 | | // store the real index here ... in color channel 3 |
1070 | 0 | p_pcOut->mColors[3] = (aiColor4D *)(uintptr_t)mesh.iMaterialIndex; |
1071 | | |
1072 | | // store a pointer to the mesh in color channel 2 |
1073 | 0 | p_pcOut->mColors[2] = (aiColor4D *)&mesh; |
1074 | 0 | avOutMeshes.push_back(p_pcOut); |
1075 | | |
1076 | | // If the mesh hasn't faces or vertices, there are two cases |
1077 | | // possible: 1. the model is invalid. 2. This is a dummy |
1078 | | // helper object which we are going to remove later ... |
1079 | 0 | if (mesh.mFaces.empty() || mesh.mPositions.empty()) { |
1080 | 0 | return; |
1081 | 0 | } |
1082 | | |
1083 | | // convert vertices |
1084 | 0 | p_pcOut->mNumVertices = (unsigned int)mesh.mPositions.size(); |
1085 | 0 | p_pcOut->mNumFaces = (unsigned int)mesh.mFaces.size(); |
1086 | | |
1087 | | // allocate enough storage for faces |
1088 | 0 | p_pcOut->mFaces = new aiFace[p_pcOut->mNumFaces]; |
1089 | | |
1090 | | // copy vertices |
1091 | 0 | p_pcOut->mVertices = new aiVector3D[mesh.mPositions.size()]; |
1092 | 0 | memcpy(p_pcOut->mVertices, &mesh.mPositions[0], |
1093 | 0 | mesh.mPositions.size() * sizeof(aiVector3D)); |
1094 | | |
1095 | | // copy normals |
1096 | 0 | p_pcOut->mNormals = new aiVector3D[mesh.mNormals.size()]; |
1097 | 0 | memcpy(p_pcOut->mNormals, &mesh.mNormals[0], |
1098 | 0 | mesh.mNormals.size() * sizeof(aiVector3D)); |
1099 | | |
1100 | | // copy texture coordinates |
1101 | 0 | for (unsigned int c = 0; c < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++c) { |
1102 | 0 | if (!mesh.amTexCoords[c].empty()) { |
1103 | 0 | p_pcOut->mTextureCoords[c] = new aiVector3D[mesh.amTexCoords[c].size()]; |
1104 | 0 | memcpy(p_pcOut->mTextureCoords[c], &mesh.amTexCoords[c][0], |
1105 | 0 | mesh.amTexCoords[c].size() * sizeof(aiVector3D)); |
1106 | | |
1107 | | // setup the number of valid vertex components |
1108 | 0 | p_pcOut->mNumUVComponents[c] = mesh.mNumUVComponents[c]; |
1109 | 0 | } |
1110 | 0 | } |
1111 | | |
1112 | | // copy vertex colors |
1113 | 0 | if (!mesh.mVertexColors.empty()) { |
1114 | 0 | p_pcOut->mColors[0] = new aiColor4D[mesh.mVertexColors.size()]; |
1115 | 0 | memcpy(p_pcOut->mColors[0], &mesh.mVertexColors[0], |
1116 | 0 | mesh.mVertexColors.size() * sizeof(aiColor4D)); |
1117 | 0 | } |
1118 | | |
1119 | | // copy faces |
1120 | 0 | for (unsigned int iFace = 0; iFace < p_pcOut->mNumFaces; ++iFace) { |
1121 | 0 | p_pcOut->mFaces[iFace].mNumIndices = 3; |
1122 | 0 | p_pcOut->mFaces[iFace].mIndices = new unsigned int[3]; |
1123 | | |
1124 | | // copy indices |
1125 | 0 | p_pcOut->mFaces[iFace].mIndices[0] = mesh.mFaces[iFace].mIndices[0]; |
1126 | 0 | p_pcOut->mFaces[iFace].mIndices[1] = mesh.mFaces[iFace].mIndices[1]; |
1127 | 0 | p_pcOut->mFaces[iFace].mIndices[2] = mesh.mFaces[iFace].mIndices[2]; |
1128 | 0 | } |
1129 | | |
1130 | | // copy vertex bones |
1131 | 0 | if (!mesh.mBones.empty() && !mesh.mBoneVertices.empty()) { |
1132 | 0 | std::vector<std::vector<aiVertexWeight>> avBonesOut(mesh.mBones.size()); |
1133 | | |
1134 | | // find all vertex weights for this bone |
1135 | 0 | unsigned int quak = 0; |
1136 | 0 | for (std::vector<BoneVertex>::const_iterator harrypotter = mesh.mBoneVertices.begin(); |
1137 | 0 | harrypotter != mesh.mBoneVertices.end(); ++harrypotter, ++quak) { |
1138 | |
|
1139 | 0 | for (std::vector<std::pair<int, float>>::const_iterator |
1140 | 0 | ronaldweasley = (*harrypotter).mBoneWeights.begin(); |
1141 | 0 | ronaldweasley != (*harrypotter).mBoneWeights.end(); ++ronaldweasley) { |
1142 | 0 | aiVertexWeight weight; |
1143 | 0 | weight.mVertexId = quak; |
1144 | 0 | weight.mWeight = (*ronaldweasley).second; |
1145 | 0 | avBonesOut[(*ronaldweasley).first].push_back(weight); |
1146 | 0 | } |
1147 | 0 | } |
1148 | | |
1149 | | // now build a final bone list |
1150 | 0 | p_pcOut->mNumBones = 0; |
1151 | 0 | for (unsigned int jfkennedy = 0; jfkennedy < mesh.mBones.size(); ++jfkennedy) |
1152 | 0 | if (!avBonesOut[jfkennedy].empty()) p_pcOut->mNumBones++; |
1153 | |
|
1154 | 0 | p_pcOut->mBones = new aiBone *[p_pcOut->mNumBones]; |
1155 | 0 | aiBone **pcBone = p_pcOut->mBones; |
1156 | 0 | for (unsigned int jfkennedy = 0; jfkennedy < mesh.mBones.size(); ++jfkennedy) { |
1157 | 0 | if (!avBonesOut[jfkennedy].empty()) { |
1158 | 0 | aiBone *pc = *pcBone = new aiBone(); |
1159 | 0 | pc->mName.Set(mesh.mBones[jfkennedy].mName); |
1160 | 0 | pc->mNumWeights = (unsigned int)avBonesOut[jfkennedy].size(); |
1161 | 0 | pc->mWeights = new aiVertexWeight[pc->mNumWeights]; |
1162 | 0 | ::memcpy(pc->mWeights, &avBonesOut[jfkennedy][0], |
1163 | 0 | sizeof(aiVertexWeight) * pc->mNumWeights); |
1164 | 0 | ++pcBone; |
1165 | 0 | } |
1166 | 0 | } |
1167 | 0 | } |
1168 | 0 | } |
1169 | 0 | } |
1170 | | |
1171 | | // ------------------------------------------------------------------------------------------------ |
1172 | | // Setup proper material indices and build output materials |
1173 | 0 | void ASEImporter::BuildMaterialIndices() { |
1174 | 0 | ai_assert(nullptr != pcScene); |
1175 | | |
1176 | | // iterate through all materials and check whether we need them |
1177 | 0 | for (unsigned int iMat = 0; iMat < mParser->m_vMaterials.size(); ++iMat) { |
1178 | 0 | ASE::Material &mat = mParser->m_vMaterials[iMat]; |
1179 | 0 | if (mat.bNeed) { |
1180 | | // Convert it to the aiMaterial layout |
1181 | 0 | ConvertMaterial(mat); |
1182 | 0 | ++pcScene->mNumMaterials; |
1183 | 0 | } |
1184 | 0 | for (unsigned int iSubMat = 0; iSubMat < mat.avSubMaterials.size(); ++iSubMat) { |
1185 | 0 | ASE::Material &submat = mat.avSubMaterials[iSubMat]; |
1186 | 0 | if (submat.bNeed) { |
1187 | | // Convert it to the aiMaterial layout |
1188 | 0 | ConvertMaterial(submat); |
1189 | 0 | ++pcScene->mNumMaterials; |
1190 | 0 | } |
1191 | 0 | } |
1192 | 0 | } |
1193 | | |
1194 | | // allocate the output material array |
1195 | 0 | pcScene->mMaterials = new aiMaterial *[pcScene->mNumMaterials]; |
1196 | 0 | D3DS::Material **pcIntMaterials = new D3DS::Material *[pcScene->mNumMaterials]; |
1197 | |
|
1198 | 0 | unsigned int iNum = 0; |
1199 | 0 | for (unsigned int iMat = 0; iMat < mParser->m_vMaterials.size(); ++iMat) { |
1200 | 0 | ASE::Material &mat = mParser->m_vMaterials[iMat]; |
1201 | 0 | if (mat.bNeed) { |
1202 | 0 | ai_assert(nullptr != mat.pcInstance); |
1203 | 0 | pcScene->mMaterials[iNum] = mat.pcInstance; |
1204 | | |
1205 | | // Store the internal material, too |
1206 | 0 | pcIntMaterials[iNum] = &mat; |
1207 | | |
1208 | | // Iterate through all meshes and search for one which is using |
1209 | | // this top-level material index |
1210 | 0 | for (unsigned int iMesh = 0; iMesh < pcScene->mNumMeshes; ++iMesh) { |
1211 | 0 | aiMesh *mesh = pcScene->mMeshes[iMesh]; |
1212 | 0 | if (ASE::Face::DEFAULT_MATINDEX == mesh->mMaterialIndex && |
1213 | 0 | iMat == (uintptr_t)mesh->mColors[3]) { |
1214 | 0 | mesh->mMaterialIndex = iNum; |
1215 | 0 | mesh->mColors[3] = nullptr; |
1216 | 0 | } |
1217 | 0 | } |
1218 | 0 | iNum++; |
1219 | 0 | } |
1220 | 0 | for (unsigned int iSubMat = 0; iSubMat < mat.avSubMaterials.size(); ++iSubMat) { |
1221 | 0 | ASE::Material &submat = mat.avSubMaterials[iSubMat]; |
1222 | 0 | if (submat.bNeed) { |
1223 | 0 | ai_assert(nullptr != submat.pcInstance); |
1224 | 0 | pcScene->mMaterials[iNum] = submat.pcInstance; |
1225 | | |
1226 | | // Store the internal material, too |
1227 | 0 | pcIntMaterials[iNum] = &submat; |
1228 | | |
1229 | | // Iterate through all meshes and search for one which is using |
1230 | | // this sub-level material index |
1231 | 0 | for (unsigned int iMesh = 0; iMesh < pcScene->mNumMeshes; ++iMesh) { |
1232 | 0 | aiMesh *mesh = pcScene->mMeshes[iMesh]; |
1233 | |
|
1234 | 0 | if (iSubMat == mesh->mMaterialIndex && iMat == (uintptr_t)mesh->mColors[3]) { |
1235 | 0 | mesh->mMaterialIndex = iNum; |
1236 | 0 | mesh->mColors[3] = nullptr; |
1237 | 0 | } |
1238 | 0 | } |
1239 | 0 | iNum++; |
1240 | 0 | } |
1241 | 0 | } |
1242 | 0 | } |
1243 | | |
1244 | | // Delete our temporary array |
1245 | 0 | delete[] pcIntMaterials; |
1246 | 0 | } |
1247 | | |
1248 | | // ------------------------------------------------------------------------------------------------ |
1249 | | // Generate normal vectors basing on smoothing groups |
1250 | 0 | bool ASEImporter::GenerateNormals(ASE::Mesh &mesh) { |
1251 | |
|
1252 | 0 | if (!mesh.mNormals.empty() && !configRecomputeNormals) { |
1253 | | // Check whether there are only uninitialized normals. If there are |
1254 | | // some, skip all normals from the file and compute them on our own |
1255 | 0 | for (std::vector<aiVector3D>::const_iterator qq = mesh.mNormals.begin(); qq != mesh.mNormals.end(); ++qq) { |
1256 | 0 | if ((*qq).x || (*qq).y || (*qq).z) { |
1257 | 0 | return true; |
1258 | 0 | } |
1259 | 0 | } |
1260 | 0 | } |
1261 | | // The array is reused. |
1262 | 0 | ComputeNormalsWithSmoothingsGroups<ASE::Face>(mesh); |
1263 | 0 | return false; |
1264 | 0 | } |
1265 | | |
1266 | | } |
1267 | | |
1268 | | #endif // ASSIMP_BUILD_NO_3DS_IMPORTER |
1269 | | |
1270 | | #endif // !! ASSIMP_BUILD_NO_BASE_IMPORTER |