/src/assimp/code/AssetLib/3DS/3DSConverter.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 | | |
42 | | /** @file Implementation of the 3ds importer class */ |
43 | | |
44 | | #ifndef ASSIMP_BUILD_NO_3DS_IMPORTER |
45 | | |
46 | | // internal headers |
47 | | #include "3DSLoader.h" |
48 | | #include "Common/TargetAnimation.h" |
49 | | #include <assimp/StringComparison.h> |
50 | | #include <assimp/scene.h> |
51 | | #include <assimp/DefaultLogger.hpp> |
52 | | #include <cctype> |
53 | | #include <memory> |
54 | | |
55 | | namespace Assimp { |
56 | | |
57 | | static constexpr unsigned int NotSet = 0xcdcdcdcd; |
58 | | |
59 | | using namespace D3DS; |
60 | | |
61 | | // ------------------------------------------------------------------------------------------------ |
62 | | // Setup final material indices, generate a default material if necessary |
63 | 2 | void Discreet3DSImporter::ReplaceDefaultMaterial() { |
64 | | // Try to find an existing material that matches the |
65 | | // typical default material setting: |
66 | | // - no textures |
67 | | // - diffuse color (in grey!) |
68 | | // NOTE: This is here to work-around the fact that some |
69 | | // exporters are writing a default material, too. |
70 | 2 | unsigned int idx(NotSet); |
71 | 7 | for (unsigned int i = 0; i < mScene->mMaterials.size(); ++i) { |
72 | 5 | auto s = mScene->mMaterials[i].mName; |
73 | 51 | for (char &it : s) { |
74 | 51 | it = static_cast<char>(::tolower(static_cast<unsigned char>(it))); |
75 | 51 | } |
76 | | |
77 | 5 | if (std::string::npos == s.find("default")) { |
78 | 4 | continue; |
79 | 4 | } |
80 | | |
81 | 1 | if (mScene->mMaterials[i].mDiffuse.r != |
82 | 1 | mScene->mMaterials[i].mDiffuse.g || |
83 | 1 | mScene->mMaterials[i].mDiffuse.r != |
84 | 1 | mScene->mMaterials[i].mDiffuse.b) continue; |
85 | | |
86 | 1 | if (ContainsTextures(i)) { |
87 | 0 | continue; |
88 | 0 | } |
89 | 1 | idx = i; |
90 | 1 | } |
91 | 2 | if (NotSet == idx) { |
92 | 1 | idx = static_cast<unsigned int>(mScene->mMaterials.size()); |
93 | 1 | } |
94 | | |
95 | | // now iterate through all meshes and through all faces and |
96 | | // find all faces that are using the default material |
97 | 2 | unsigned int cnt = 0; |
98 | 4 | for (auto i = mScene->mMeshes.begin(); i != mScene->mMeshes.end(); ++i) { |
99 | 2.13k | for (auto a = i->mFaceMaterials.begin(); a != i->mFaceMaterials.end(); ++a) { |
100 | | // NOTE: The additional check seems to be necessary, |
101 | | // some exporters seem to generate invalid data here |
102 | | |
103 | 2.13k | if (NotSet == *a) { |
104 | 0 | *a = idx; |
105 | 0 | ++cnt; |
106 | 2.13k | } else if ((*a) >= mScene->mMaterials.size()) { |
107 | 0 | *a = idx; |
108 | 0 | ASSIMP_LOG_WARN("Material index overflow in 3DS file. Using default material"); |
109 | 0 | ++cnt; |
110 | 0 | } |
111 | 2.13k | } |
112 | 2 | } |
113 | 2 | if (cnt && idx == mScene->mMaterials.size()) { |
114 | | // We need to create our own default material |
115 | 0 | Material sMat("%%%DEFAULT"); |
116 | 0 | sMat.mDiffuse = aiColor3D(0.3f, 0.3f, 0.3f); |
117 | 0 | mScene->mMaterials.push_back(sMat); |
118 | |
|
119 | 0 | ASSIMP_LOG_INFO("3DS: Generating default material"); |
120 | 0 | } |
121 | 2 | } |
122 | | |
123 | | // ------------------------------------------------------------------------------------------------ |
124 | | // Check whether all indices are valid. Otherwise we'd crash before the validation step is reached |
125 | 2 | void Discreet3DSImporter::CheckIndices(Mesh &sMesh) { |
126 | 2.13k | for (auto i = sMesh.mFaces.begin(); i != sMesh.mFaces.end(); ++i) { |
127 | | // check whether all indices are in range |
128 | 8.54k | for (unsigned int a = 0; a < 3; ++a) { |
129 | 6.40k | if ((*i).mIndices[a] >= sMesh.mPositions.size()) { |
130 | 0 | ASSIMP_LOG_WARN("3DS: Vertex index overflow)"); |
131 | 0 | (*i).mIndices[a] = static_cast<uint32_t>(sMesh.mPositions.size() - 1); |
132 | 0 | } |
133 | 6.40k | if (!sMesh.mTexCoords.empty() && (*i).mIndices[a] >= sMesh.mTexCoords.size()) { |
134 | 0 | ASSIMP_LOG_WARN("3DS: Texture coordinate index overflow)"); |
135 | 0 | (*i).mIndices[a] = static_cast<uint32_t>(sMesh.mTexCoords.size() - 1); |
136 | 0 | } |
137 | 6.40k | } |
138 | 2.13k | } |
139 | 2 | } |
140 | | |
141 | | // ------------------------------------------------------------------------------------------------ |
142 | | // Generate out unique verbose format representation |
143 | 2 | void Discreet3DSImporter::MakeUnique(Mesh &sMesh) { |
144 | | // TODO: really necessary? I don't think. Just a waste of memory and time |
145 | | // to do it now in a separate buffer. |
146 | | |
147 | | // Allocate output storage |
148 | 2 | std::vector<aiVector3D> vNew(sMesh.mFaces.size() * 3); |
149 | 2 | std::vector<aiVector3D> vNew2; |
150 | 2 | if (sMesh.mTexCoords.size()) |
151 | 0 | vNew2.resize(sMesh.mFaces.size() * 3); |
152 | | |
153 | 2.13k | for (unsigned int i = 0, base = 0; i < sMesh.mFaces.size(); ++i) { |
154 | 2.13k | Face &face = sMesh.mFaces[i]; |
155 | | |
156 | | // Positions |
157 | 8.54k | for (unsigned int a = 0; a < 3; ++a, ++base) { |
158 | 6.40k | vNew[base] = sMesh.mPositions[face.mIndices[a]]; |
159 | 6.40k | if (sMesh.mTexCoords.size()) |
160 | 0 | vNew2[base] = sMesh.mTexCoords[face.mIndices[a]]; |
161 | | |
162 | 6.40k | face.mIndices[a] = base; |
163 | 6.40k | } |
164 | 2.13k | } |
165 | 2 | sMesh.mPositions = vNew; |
166 | 2 | sMesh.mTexCoords = vNew2; |
167 | 2 | } |
168 | | |
169 | | // ------------------------------------------------------------------------------------------------ |
170 | | // Convert a 3DS texture to texture keys in an aiMaterial |
171 | 0 | void CopyTexture(aiMaterial &mat, Texture &texture, aiTextureType type) { |
172 | | // Setup the texture name |
173 | 0 | aiString tex(texture.mMapName); |
174 | 0 | mat.AddProperty(&tex, AI_MATKEY_TEXTURE(type, 0)); |
175 | | |
176 | | // Setup the texture blend factor |
177 | 0 | if (is_not_qnan(texture.mTextureBlend)) |
178 | 0 | mat.AddProperty<ai_real>(&texture.mTextureBlend, 1, AI_MATKEY_TEXBLEND(type, 0)); |
179 | | |
180 | | // Setup the texture mapping mode |
181 | 0 | auto mapMode = static_cast<int>(texture.mMapMode); |
182 | 0 | mat.AddProperty<int>(&mapMode, 1, AI_MATKEY_MAPPINGMODE_U(type, 0)); |
183 | 0 | mat.AddProperty<int>(&mapMode, 1, AI_MATKEY_MAPPINGMODE_V(type, 0)); |
184 | | |
185 | | // Mirroring - double the scaling values |
186 | | // FIXME: this is not really correct ... |
187 | 0 | if (texture.mMapMode == aiTextureMapMode_Mirror) { |
188 | 0 | texture.mScaleU *= 2.0; |
189 | 0 | texture.mScaleV *= 2.0; |
190 | 0 | texture.mOffsetU /= 2.0; |
191 | 0 | texture.mOffsetV /= 2.0; |
192 | 0 | } |
193 | | |
194 | | // Setup texture UV transformations |
195 | 0 | mat.AddProperty<ai_real>(&texture.mOffsetU, 5, AI_MATKEY_UVTRANSFORM(type, 0)); |
196 | 0 | } |
197 | | |
198 | | // ------------------------------------------------------------------------------------------------ |
199 | | // Convert a 3DS material to an aiMaterial |
200 | 5 | void Discreet3DSImporter::ConvertMaterial(Material &oldMat, aiMaterial &mat) { |
201 | | // NOTE: Pass the background image to the viewer by bypassing the |
202 | | // material system. This is an evil hack, never do it again! |
203 | 5 | if (mBackgroundImage.empty() && bHasBG) { |
204 | 0 | aiString tex(mBackgroundImage); |
205 | 0 | mat.AddProperty(&tex, AI_MATKEY_GLOBAL_BACKGROUND_IMAGE); |
206 | | |
207 | | // Be sure this is only done for the first material |
208 | 0 | mBackgroundImage = std::string(); |
209 | 0 | } |
210 | | |
211 | | // At first add the base ambient color of the scene to the material |
212 | 5 | oldMat.mAmbient.r += mClrAmbient.r; |
213 | 5 | oldMat.mAmbient.g += mClrAmbient.g; |
214 | 5 | oldMat.mAmbient.b += mClrAmbient.b; |
215 | | |
216 | 5 | aiString name(oldMat.mName); |
217 | 5 | mat.AddProperty(&name, AI_MATKEY_NAME); |
218 | | |
219 | | // Material colors |
220 | 5 | mat.AddProperty(&oldMat.mAmbient, 1, AI_MATKEY_COLOR_AMBIENT); |
221 | 5 | mat.AddProperty(&oldMat.mDiffuse, 1, AI_MATKEY_COLOR_DIFFUSE); |
222 | 5 | mat.AddProperty(&oldMat.mSpecular, 1, AI_MATKEY_COLOR_SPECULAR); |
223 | 5 | mat.AddProperty(&oldMat.mEmissive, 1, AI_MATKEY_COLOR_EMISSIVE); |
224 | | |
225 | | // Phong shininess and shininess strength |
226 | 5 | if (Discreet3DS::Phong == oldMat.mShading || Discreet3DS::Metal == oldMat.mShading) { |
227 | 5 | if (!oldMat.mSpecularExponent || !oldMat.mShininessStrength) { |
228 | 0 | oldMat.mShading = Discreet3DS::Gouraud; |
229 | 5 | } else { |
230 | 5 | mat.AddProperty(&oldMat.mSpecularExponent, 1, AI_MATKEY_SHININESS); |
231 | 5 | mat.AddProperty(&oldMat.mShininessStrength, 1, AI_MATKEY_SHININESS_STRENGTH); |
232 | 5 | } |
233 | 5 | } |
234 | | |
235 | | // Opacity |
236 | 5 | mat.AddProperty<ai_real>(&oldMat.mTransparency, 1, AI_MATKEY_OPACITY); |
237 | | |
238 | | // Bump height scaling |
239 | 5 | mat.AddProperty<ai_real>(&oldMat.mBumpHeight, 1, AI_MATKEY_BUMPSCALING); |
240 | | |
241 | | // Two sided rendering? |
242 | 5 | if (oldMat.mTwoSided) { |
243 | 0 | int i = 1; |
244 | 0 | mat.AddProperty<int>(&i, 1, AI_MATKEY_TWOSIDED); |
245 | 0 | } |
246 | | |
247 | | // Shading mode |
248 | 5 | aiShadingMode eShading = aiShadingMode_NoShading; |
249 | 5 | switch (oldMat.mShading) { |
250 | 0 | case Discreet3DS::Flat: |
251 | 0 | eShading = aiShadingMode_Flat; |
252 | 0 | break; |
253 | | |
254 | | // I don't know what "Wire" shading should be, |
255 | | // assume it is simple lambertian diffuse shading |
256 | 0 | case Discreet3DS::Wire: { |
257 | | // Set the wireframe flag |
258 | 0 | unsigned int iWire = 1; |
259 | 0 | mat.AddProperty<int>((int *)&iWire, 1, AI_MATKEY_ENABLE_WIREFRAME); |
260 | 0 | } |
261 | 0 | [[fallthrough]]; |
262 | |
|
263 | 0 | case Discreet3DS::Gouraud: |
264 | 0 | eShading = aiShadingMode_Gouraud; |
265 | 0 | break; |
266 | | |
267 | | // assume cook-torrance shading for metals. |
268 | 5 | case Discreet3DS::Phong: |
269 | 5 | eShading = aiShadingMode_Phong; |
270 | 5 | break; |
271 | | |
272 | 0 | case Discreet3DS::Metal: |
273 | 0 | eShading = aiShadingMode_CookTorrance; |
274 | 0 | break; |
275 | | |
276 | | // FIX to workaround a warning with GCC 4 who complained |
277 | | // about a missing case Blinn: here - Blinn isn't a valid |
278 | | // value in the 3DS Loader, it is just needed for ASE |
279 | 0 | case Discreet3DS::Blinn: |
280 | 0 | eShading = aiShadingMode_Blinn; |
281 | 0 | break; |
282 | 5 | } |
283 | | |
284 | 5 | const int eShading_ = eShading; |
285 | 5 | mat.AddProperty<int>(&eShading_, 1, AI_MATKEY_SHADING_MODEL); |
286 | | |
287 | | // DIFFUSE texture |
288 | 5 | if (oldMat.sTexDiffuse.mMapName.length() > 0) |
289 | 0 | CopyTexture(mat, oldMat.sTexDiffuse, aiTextureType_DIFFUSE); |
290 | | |
291 | | // SPECULAR texture |
292 | 5 | if (oldMat.sTexSpecular.mMapName.length() > 0) |
293 | 0 | CopyTexture(mat, oldMat.sTexSpecular, aiTextureType_SPECULAR); |
294 | | |
295 | | // OPACITY texture |
296 | 5 | if (oldMat.sTexOpacity.mMapName.length() > 0) |
297 | 0 | CopyTexture(mat, oldMat.sTexOpacity, aiTextureType_OPACITY); |
298 | | |
299 | | // EMISSIVE texture |
300 | 5 | if (oldMat.sTexEmissive.mMapName.length() > 0) |
301 | 0 | CopyTexture(mat, oldMat.sTexEmissive, aiTextureType_EMISSIVE); |
302 | | |
303 | | // BUMP texture |
304 | 5 | if (oldMat.sTexBump.mMapName.length() > 0) |
305 | 0 | CopyTexture(mat, oldMat.sTexBump, aiTextureType_HEIGHT); |
306 | | |
307 | | // SHININESS texture |
308 | 5 | if (oldMat.sTexShininess.mMapName.length() > 0) |
309 | 0 | CopyTexture(mat, oldMat.sTexShininess, aiTextureType_SHININESS); |
310 | | |
311 | | // REFLECTION texture |
312 | 5 | if (oldMat.sTexReflective.mMapName.length() > 0) |
313 | 0 | CopyTexture(mat, oldMat.sTexReflective, aiTextureType_REFLECTION); |
314 | | |
315 | | // Store the name of the material itself, too |
316 | 5 | if (oldMat.mName.length()) { |
317 | 5 | aiString tex; |
318 | 5 | tex.Set(oldMat.mName); |
319 | 5 | mat.AddProperty(&tex, AI_MATKEY_NAME); |
320 | 5 | } |
321 | 5 | } |
322 | | |
323 | | // ------------------------------------------------------------------------------------------------ |
324 | | // Split meshes by their materials and generate output aiMesh'es |
325 | 2 | void Discreet3DSImporter::ConvertMeshes(aiScene *pcOut) { |
326 | 2 | std::vector<aiMesh *> avOutMeshes; |
327 | 2 | avOutMeshes.reserve(mScene->mMeshes.size() * 2); |
328 | | |
329 | 2 | unsigned int iFaceCnt = 0, num = 0; |
330 | 2 | aiString name; |
331 | | |
332 | | // we need to split all meshes by their materials |
333 | 4 | for (auto i = mScene->mMeshes.begin(); i != mScene->mMeshes.end(); ++i) { |
334 | 2 | std::unique_ptr<std::vector<unsigned int>[]> aiSplit(new std::vector<unsigned int>[mScene->mMaterials.size()]); |
335 | | |
336 | 2 | name.length = ASSIMP_itoa10(name.data, num); |
337 | 2 | ++num; |
338 | | |
339 | 2 | unsigned int iNum = 0; |
340 | 2 | for (std::vector<unsigned int>::const_iterator a = (*i).mFaceMaterials.begin(); |
341 | 2.13k | a != (*i).mFaceMaterials.end(); ++a, ++iNum) { |
342 | 2.13k | aiSplit[*a].push_back(iNum); |
343 | 2.13k | } |
344 | | // now generate submeshes |
345 | 7 | for (unsigned int p = 0; p < mScene->mMaterials.size(); ++p) { |
346 | 5 | if (aiSplit[p].empty()) { |
347 | 0 | continue; |
348 | 0 | } |
349 | 5 | auto *meshOut = new aiMesh(); |
350 | 5 | meshOut->mName = name; |
351 | 5 | meshOut->mPrimitiveTypes = aiPrimitiveType_TRIANGLE; |
352 | | |
353 | | // be sure to setup the correct material index |
354 | 5 | meshOut->mMaterialIndex = p; |
355 | | |
356 | | // use the color data as temporary storage |
357 | 5 | meshOut->mColors[0] = (aiColor4D *)(&*i); |
358 | 5 | avOutMeshes.push_back(meshOut); |
359 | | |
360 | | // convert vertices |
361 | 5 | meshOut->mNumFaces = static_cast<unsigned int>(aiSplit[p].size()); |
362 | 5 | meshOut->mNumVertices = meshOut->mNumFaces * 3; |
363 | | |
364 | | // allocate enough storage for faces |
365 | 5 | meshOut->mFaces = new aiFace[meshOut->mNumFaces]; |
366 | 5 | iFaceCnt += meshOut->mNumFaces; |
367 | | |
368 | 5 | meshOut->mVertices = new aiVector3D[meshOut->mNumVertices]; |
369 | 5 | meshOut->mNormals = new aiVector3D[meshOut->mNumVertices]; |
370 | 5 | if ((*i).mTexCoords.size()) { |
371 | 0 | meshOut->mTextureCoords[0] = new aiVector3D[meshOut->mNumVertices]; |
372 | 0 | } |
373 | 2.14k | for (unsigned int q = 0, base = 0; q < aiSplit[p].size(); ++q) { |
374 | 2.13k | unsigned int index = aiSplit[p][q]; |
375 | 2.13k | aiFace &face = meshOut->mFaces[q]; |
376 | | |
377 | 2.13k | face.mIndices = new unsigned int[3]; |
378 | 2.13k | face.mNumIndices = 3; |
379 | | |
380 | 8.54k | for (unsigned int a = 0; a < 3; ++a, ++base) { |
381 | 6.40k | unsigned int idx = (*i).mFaces[index].mIndices[a]; |
382 | 6.40k | meshOut->mVertices[base] = (*i).mPositions[idx]; |
383 | 6.40k | meshOut->mNormals[base] = (*i).mNormals[idx]; |
384 | | |
385 | 6.40k | if ((*i).mTexCoords.size()) |
386 | 0 | meshOut->mTextureCoords[0][base] = (*i).mTexCoords[idx]; |
387 | | |
388 | 6.40k | face.mIndices[a] = base; |
389 | 6.40k | } |
390 | 2.13k | } |
391 | 5 | } |
392 | 2 | } |
393 | | |
394 | | // Copy them to the output array |
395 | 2 | pcOut->mNumMeshes = (unsigned int)avOutMeshes.size(); |
396 | 2 | pcOut->mMeshes = new aiMesh *[pcOut->mNumMeshes](); |
397 | 7 | for (unsigned int a = 0; a < pcOut->mNumMeshes; ++a) { |
398 | 5 | pcOut->mMeshes[a] = avOutMeshes[a]; |
399 | 5 | } |
400 | | |
401 | | // We should have at least one face here |
402 | 2 | if (!iFaceCnt) { |
403 | 0 | throw DeadlyImportError("No faces loaded. The mesh is empty"); |
404 | 0 | } |
405 | 2 | } |
406 | | |
407 | | // ------------------------------------------------------------------------------------------------ |
408 | | // Add a node to the scenegraph and setup its final transformation |
409 | 2 | void Discreet3DSImporter::AddNodeToGraph(aiScene *pcSOut, aiNode *pcOut, D3DS::Node *pcIn, aiMatrix4x4 & /*absTrafo*/) { |
410 | 2 | std::vector<unsigned int> iArray; |
411 | 2 | iArray.reserve(3); |
412 | | |
413 | 2 | aiMatrix4x4 abs; |
414 | | |
415 | | // Find all meshes with the same name as the node |
416 | 10 | for (unsigned int a = 0; a < pcSOut->mNumMeshes; ++a) { |
417 | 8 | const auto *pcMesh = (const D3DS::Mesh *)pcSOut->mMeshes[a]->mColors[0]; |
418 | 8 | ai_assert(nullptr != pcMesh); |
419 | | |
420 | 8 | if (pcIn->mName == pcMesh->mName) |
421 | 4 | iArray.push_back(a); |
422 | 8 | } |
423 | 2 | if (!iArray.empty()) { |
424 | | // The matrix should be identical for all meshes with the |
425 | | // same name. It HAS to be identical for all meshes ..... |
426 | 1 | auto *imesh = ((D3DS::Mesh *)pcSOut->mMeshes[iArray[0]]->mColors[0]); |
427 | | |
428 | | // Compute the inverse of the transformation matrix to move the |
429 | | // vertices back to their relative and local space |
430 | 1 | aiMatrix4x4 mInv = imesh->mMat, mInvTransposed = imesh->mMat; |
431 | 1 | mInv.Inverse(); |
432 | 1 | mInvTransposed.Transpose(); |
433 | 1 | aiVector3D pivot = pcIn->vPivot; |
434 | | |
435 | 1 | pcOut->mNumMeshes = static_cast<unsigned int>(iArray.size()); |
436 | 1 | pcOut->mMeshes = new unsigned int[iArray.size()]; |
437 | 5 | for (unsigned int i = 0; i < iArray.size(); ++i) { |
438 | 4 | const unsigned int iIndex = iArray[i]; |
439 | 4 | aiMesh *const mesh = pcSOut->mMeshes[iIndex]; |
440 | | |
441 | 4 | if (mesh->mColors[1] == nullptr) { |
442 | | // Transform the vertices back into their local space |
443 | | // fixme: consider computing normals after this, so we don't need to transform them |
444 | 4 | const aiVector3D *const pvEnd = mesh->mVertices + mesh->mNumVertices; |
445 | 4 | aiVector3D *pvCurrent = mesh->mVertices, *t2 = mesh->mNormals; |
446 | | |
447 | 4.10k | for (; pvCurrent != pvEnd; ++pvCurrent, ++t2) { |
448 | 4.10k | *pvCurrent = mInv * (*pvCurrent); |
449 | 4.10k | *t2 = mInvTransposed * (*t2); |
450 | 4.10k | } |
451 | | |
452 | | // Handle negative transformation matrix determinant -> invert vertex x |
453 | 4 | if (imesh->mMat.Determinant() < 0.0f) { |
454 | | // we *must* have normals |
455 | 0 | for (pvCurrent = mesh->mVertices, t2 = mesh->mNormals; pvCurrent != pvEnd; ++pvCurrent, ++t2) { |
456 | 0 | pvCurrent->x *= -1.f; |
457 | 0 | t2->x *= -1.f; |
458 | 0 | } |
459 | 0 | ASSIMP_LOG_INFO("3DS: Flipping mesh X-Axis"); |
460 | 0 | } |
461 | | |
462 | | // Handle pivot point |
463 | 4 | if (pivot.x || pivot.y || pivot.z) { |
464 | 0 | for (pvCurrent = mesh->mVertices; pvCurrent != pvEnd; ++pvCurrent) { |
465 | 0 | *pvCurrent -= pivot; |
466 | 0 | } |
467 | 0 | } |
468 | | |
469 | 4 | mesh->mColors[1] = (aiColor4D *)1; |
470 | 4 | } else |
471 | 0 | mesh->mColors[1] = (aiColor4D *)1; |
472 | | |
473 | | // Setup the mesh index |
474 | 4 | pcOut->mMeshes[i] = iIndex; |
475 | 4 | } |
476 | 1 | } |
477 | | |
478 | | // Setup the name of the node |
479 | | // First instance keeps its name otherwise something might break, all others will be postfixed with their instance number |
480 | 2 | if (pcIn->mInstanceNumber > 1) { |
481 | 0 | char tmp[12] = {'\0'}; |
482 | 0 | ASSIMP_itoa10(tmp, pcIn->mInstanceNumber); |
483 | 0 | std::string tempStr = pcIn->mName + "_inst_"; |
484 | 0 | tempStr += tmp; |
485 | 0 | pcOut->mName.Set(tempStr); |
486 | 0 | } else |
487 | 2 | pcOut->mName.Set(pcIn->mName); |
488 | | |
489 | | // Now build the transformation matrix of the node |
490 | | // ROTATION |
491 | 2 | if (pcIn->aRotationKeys.size()) { |
492 | | |
493 | | // FIX to get to Assimp's quaternion conventions |
494 | 2 | for (auto it = pcIn->aRotationKeys.begin(); it != pcIn->aRotationKeys.end(); ++it) { |
495 | 1 | (*it).mValue.w *= -1.f; |
496 | 1 | } |
497 | | |
498 | 1 | pcOut->mTransformation = aiMatrix4x4(pcIn->aRotationKeys[0].mValue.GetMatrix()); |
499 | 1 | } else if (pcIn->aCameraRollKeys.size()) { |
500 | 0 | aiMatrix4x4::RotationZ(AI_DEG_TO_RAD(-pcIn->aCameraRollKeys[0].mValue), |
501 | 0 | pcOut->mTransformation); |
502 | 0 | } |
503 | | |
504 | | // SCALING |
505 | 2 | aiMatrix4x4 &m = pcOut->mTransformation; |
506 | 2 | if (pcIn->aScalingKeys.size()) { |
507 | 1 | const aiVector3D &v = pcIn->aScalingKeys[0].mValue; |
508 | 1 | m.a1 *= v.x; |
509 | 1 | m.b1 *= v.x; |
510 | 1 | m.c1 *= v.x; |
511 | 1 | m.a2 *= v.y; |
512 | 1 | m.b2 *= v.y; |
513 | 1 | m.c2 *= v.y; |
514 | 1 | m.a3 *= v.z; |
515 | 1 | m.b3 *= v.z; |
516 | 1 | m.c3 *= v.z; |
517 | 1 | } |
518 | | |
519 | | // TRANSLATION |
520 | 2 | if (pcIn->aPositionKeys.size()) { |
521 | 1 | const aiVector3D &v = pcIn->aPositionKeys[0].mValue; |
522 | 1 | m.a4 += v.x; |
523 | 1 | m.b4 += v.y; |
524 | 1 | m.c4 += v.z; |
525 | 1 | } |
526 | | |
527 | | // Generate animation channels for the node |
528 | 2 | if (pcIn->aPositionKeys.size() > 1 || pcIn->aRotationKeys.size() > 1 || |
529 | 2 | pcIn->aScalingKeys.size() > 1 || pcIn->aCameraRollKeys.size() > 1 || |
530 | 2 | pcIn->aTargetPositionKeys.size() > 1) { |
531 | 0 | aiAnimation *anim = pcSOut->mAnimations[0]; |
532 | 0 | ai_assert(nullptr != anim); |
533 | |
|
534 | 0 | if (pcIn->aCameraRollKeys.size() > 1) { |
535 | 0 | ASSIMP_LOG_VERBOSE_DEBUG("3DS: Converting camera roll track ..."); |
536 | | |
537 | | // Camera roll keys - in fact they're just rotations |
538 | | // around the camera's z axis. The angles are given |
539 | | // in degrees (and they're clockwise). |
540 | 0 | pcIn->aRotationKeys.resize(pcIn->aCameraRollKeys.size()); |
541 | 0 | for (unsigned int i = 0; i < pcIn->aCameraRollKeys.size(); ++i) { |
542 | 0 | aiQuatKey &q = pcIn->aRotationKeys[i]; |
543 | 0 | aiFloatKey &f = pcIn->aCameraRollKeys[i]; |
544 | |
|
545 | 0 | q.mTime = f.mTime; |
546 | | |
547 | | // FIX to get to Assimp quaternion conventions |
548 | 0 | q.mValue = aiQuaternion(0.f, 0.f, AI_DEG_TO_RAD(/*-*/ f.mValue)); |
549 | 0 | } |
550 | 0 | } |
551 | | #if 0 |
552 | | if (pcIn->aTargetPositionKeys.size() > 1) |
553 | | { |
554 | | ASSIMP_LOG_VERBOSE_DEBUG("3DS: Converting target track ..."); |
555 | | |
556 | | // Camera or spot light - need to convert the separate |
557 | | // target position channel to our representation |
558 | | TargetAnimationHelper helper; |
559 | | |
560 | | if (pcIn->aPositionKeys.empty()) |
561 | | { |
562 | | // We can just pass zero here ... |
563 | | helper.SetFixedMainAnimationChannel(aiVector3D()); |
564 | | } |
565 | | else helper.SetMainAnimationChannel(&pcIn->aPositionKeys); |
566 | | helper.SetTargetAnimationChannel(&pcIn->aTargetPositionKeys); |
567 | | |
568 | | // Do the conversion |
569 | | std::vector<aiVectorKey> distanceTrack; |
570 | | helper.Process(&distanceTrack); |
571 | | |
572 | | // Now add a new node as child, name it <ourName>.Target |
573 | | // and assign the distance track to it. This is that the |
574 | | // information where the target is and how it moves is |
575 | | // not lost |
576 | | D3DS::Node* nd = new D3DS::Node(); |
577 | | pcIn->push_back(nd); |
578 | | |
579 | | nd->mName = pcIn->mName + ".Target"; |
580 | | |
581 | | aiNodeAnim* nda = anim->mChannels[anim->mNumChannels++] = new aiNodeAnim(); |
582 | | nda->mNodeName.Set(nd->mName); |
583 | | |
584 | | nda->mNumPositionKeys = (unsigned int)distanceTrack.size(); |
585 | | nda->mPositionKeys = new aiVectorKey[nda->mNumPositionKeys]; |
586 | | ::memcpy(nda->mPositionKeys,&distanceTrack[0], |
587 | | sizeof(aiVectorKey)*nda->mNumPositionKeys); |
588 | | } |
589 | | #endif |
590 | | |
591 | | // Cameras or lights define their transformation in their parent node and in the |
592 | | // corresponding light or camera chunks. However, we read and process the latter |
593 | | // to be able to return valid cameras/lights even if no scenegraph is given. |
594 | 0 | for (unsigned int n = 0; n < pcSOut->mNumCameras; ++n) { |
595 | 0 | if (pcSOut->mCameras[n]->mName == pcOut->mName) { |
596 | 0 | pcSOut->mCameras[n]->mLookAt = aiVector3D(0.f, 0.f, 1.f); |
597 | 0 | } |
598 | 0 | } |
599 | 0 | for (unsigned int n = 0; n < pcSOut->mNumLights; ++n) { |
600 | 0 | if (pcSOut->mLights[n]->mName == pcOut->mName) { |
601 | 0 | pcSOut->mLights[n]->mDirection = aiVector3D(0.f, 0.f, 1.f); |
602 | 0 | } |
603 | 0 | } |
604 | | |
605 | | // Allocate a new node anim and setup its name |
606 | 0 | auto *nda = anim->mChannels[anim->mNumChannels++] = new aiNodeAnim(); |
607 | 0 | nda->mNodeName.Set(pcIn->mName); |
608 | | |
609 | | // POSITION keys |
610 | 0 | if (!pcIn->aPositionKeys.empty()) { |
611 | 0 | nda->mNumPositionKeys = (unsigned int)pcIn->aPositionKeys.size(); |
612 | 0 | nda->mPositionKeys = new aiVectorKey[nda->mNumPositionKeys]; |
613 | 0 | ::memcpy(nda->mPositionKeys, &pcIn->aPositionKeys[0], |
614 | 0 | sizeof(aiVectorKey) * nda->mNumPositionKeys); |
615 | 0 | } |
616 | | |
617 | | // ROTATION keys |
618 | 0 | if (!pcIn->aRotationKeys.empty()) { |
619 | 0 | nda->mNumRotationKeys = (unsigned int)pcIn->aRotationKeys.size(); |
620 | 0 | nda->mRotationKeys = new aiQuatKey[nda->mNumRotationKeys]; |
621 | | |
622 | | // Rotations are quaternion offsets |
623 | 0 | aiQuaternion abs1; |
624 | 0 | for (unsigned int n = 0; n < nda->mNumRotationKeys; ++n) { |
625 | 0 | const aiQuatKey &q = pcIn->aRotationKeys[n]; |
626 | |
|
627 | 0 | abs1 = (n ? abs1 * q.mValue : q.mValue); |
628 | 0 | nda->mRotationKeys[n].mTime = q.mTime; |
629 | 0 | nda->mRotationKeys[n].mValue = abs1.Normalize(); |
630 | 0 | } |
631 | 0 | } |
632 | | |
633 | | // SCALING keys |
634 | 0 | if (!pcIn->aScalingKeys.empty()) { |
635 | 0 | nda->mNumScalingKeys = (unsigned int)pcIn->aScalingKeys.size(); |
636 | 0 | nda->mScalingKeys = new aiVectorKey[nda->mNumScalingKeys]; |
637 | 0 | ::memcpy(nda->mScalingKeys, &pcIn->aScalingKeys[0], |
638 | 0 | sizeof(aiVectorKey) * nda->mNumScalingKeys); |
639 | 0 | } |
640 | 0 | } |
641 | | |
642 | | // Allocate storage for children |
643 | 2 | const auto size = static_cast<unsigned int>(pcIn->mChildren.size()); |
644 | | |
645 | 2 | pcOut->mNumChildren = size; |
646 | 2 | if (size == 0) { |
647 | 1 | return; |
648 | 1 | } |
649 | | |
650 | 1 | pcOut->mChildren = new aiNode *[pcIn->mChildren.size()]; |
651 | | |
652 | | // Recursively process all children |
653 | | |
654 | 2 | for (unsigned int i = 0; i < size; ++i) { |
655 | 1 | pcOut->mChildren[i] = new aiNode(); |
656 | 1 | pcOut->mChildren[i]->mParent = pcOut; |
657 | 1 | AddNodeToGraph(pcSOut, pcOut->mChildren[i], pcIn->mChildren[i], abs); |
658 | 1 | } |
659 | 1 | } |
660 | | |
661 | | // ------------------------------------------------------------------------------------------------ |
662 | | // Find out how many node animation channels we'll have finally |
663 | 2 | void CountTracks(D3DS::Node *node, unsigned int &cnt) { |
664 | | ////////////////////////////////////////////////////////////////////////////// |
665 | | // We will never generate more than one channel for a node, so |
666 | | // this is rather easy here. |
667 | | |
668 | 2 | if (node->aPositionKeys.size() > 1 || node->aRotationKeys.size() > 1 || |
669 | 2 | node->aScalingKeys.size() > 1 || node->aCameraRollKeys.size() > 1 || |
670 | 2 | node->aTargetPositionKeys.size() > 1) { |
671 | 0 | ++cnt; |
672 | | |
673 | | // account for the additional channel for the camera/spotlight target position |
674 | 0 | if (node->aTargetPositionKeys.size() > 1) ++cnt; |
675 | 0 | } |
676 | | |
677 | | // Recursively process all children |
678 | 3 | for (unsigned int i = 0; i < node->mChildren.size(); ++i) |
679 | 1 | CountTracks(node->mChildren[i], cnt); |
680 | 2 | } |
681 | | |
682 | | // ------------------------------------------------------------------------------------------------ |
683 | | // Generate the output node graph |
684 | 2 | void Discreet3DSImporter::GenerateNodeGraph(aiScene *pcOut) { |
685 | 2 | pcOut->mRootNode = new aiNode(); |
686 | 2 | if (mRootNode->mChildren.empty()) { |
687 | | ////////////////////////////////////////////////////////////////////////////// |
688 | | // It seems the file is so messed up that it has not even a hierarchy. |
689 | | // generate a flat hiearachy which looks like this: |
690 | | // |
691 | | // ROOT_NODE |
692 | | // | |
693 | | // ---------------------------------------- |
694 | | // | | | | | |
695 | | // MESH_0 MESH_1 MESH_2 ... MESH_N CAMERA_0 .... |
696 | | // |
697 | 1 | ASSIMP_LOG_WARN("No hierarchy information has been found in the file. "); |
698 | | |
699 | 1 | pcOut->mRootNode->mNumChildren = pcOut->mNumMeshes + |
700 | 1 | static_cast<unsigned int>(mScene->mCameras.size() + mScene->mLights.size()); |
701 | | |
702 | 1 | pcOut->mRootNode->mChildren = new aiNode *[pcOut->mRootNode->mNumChildren]; |
703 | 1 | pcOut->mRootNode->mName.Set("<3DSDummyRoot>"); |
704 | | |
705 | | // Build dummy nodes for all meshes |
706 | 1 | unsigned int a = 0; |
707 | 2 | for (unsigned int i = 0; i < pcOut->mNumMeshes; ++i, ++a) { |
708 | 1 | pcOut->mRootNode->mChildren[a] = new aiNode(); |
709 | 1 | auto *pcNode = pcOut->mRootNode->mChildren[a]; |
710 | 1 | pcNode->mParent = pcOut->mRootNode; |
711 | 1 | pcNode->mMeshes = new unsigned int[1]; |
712 | 1 | pcNode->mMeshes[0] = i; |
713 | 1 | pcNode->mNumMeshes = 1; |
714 | | |
715 | | // Build a name for the node |
716 | 1 | pcNode->mName.length = ai_snprintf(pcNode->mName.data, AI_MAXLEN, "3DSMesh_%u", i); |
717 | 1 | } |
718 | | |
719 | | // Build dummy nodes for all cameras |
720 | 1 | for (unsigned int i = 0; i < (unsigned int)mScene->mCameras.size(); ++i, ++a) { |
721 | 0 | auto *pcNode = pcOut->mRootNode->mChildren[a] = new aiNode(); |
722 | 0 | pcNode->mParent = pcOut->mRootNode; |
723 | | |
724 | | // Build a name for the node |
725 | 0 | pcNode->mName = mScene->mCameras[i]->mName; |
726 | 0 | } |
727 | | |
728 | | // Build dummy nodes for all lights |
729 | 1 | for (unsigned int i = 0; i < (unsigned int)mScene->mLights.size(); ++i, ++a) { |
730 | 0 | auto *pcNode = pcOut->mRootNode->mChildren[a] = new aiNode(); |
731 | 0 | pcNode->mParent = pcOut->mRootNode; |
732 | | |
733 | | // Build a name for the node |
734 | 0 | pcNode->mName = mScene->mLights[i]->mName; |
735 | 0 | } |
736 | 1 | } else { |
737 | | // First of all: find out how many scaling, rotation and translation |
738 | | // animation tracks we'll have afterwards |
739 | 1 | unsigned int numChannel = 0; |
740 | 1 | CountTracks(mRootNode, numChannel); |
741 | | |
742 | 1 | if (numChannel) { |
743 | | // Allocate a primary animation channel |
744 | 0 | pcOut->mNumAnimations = 1; |
745 | 0 | pcOut->mAnimations = new aiAnimation *[1]; |
746 | 0 | auto *anim = pcOut->mAnimations[0] = new aiAnimation(); |
747 | |
|
748 | 0 | anim->mName.Set("3DSMasterAnim"); |
749 | | |
750 | | // Allocate enough storage for all node animation channels, |
751 | | // but don't set the mNumChannels member - we'll use it to |
752 | | // index into the array |
753 | 0 | anim->mChannels = new aiNodeAnim *[numChannel]; |
754 | 0 | } |
755 | | |
756 | 1 | aiMatrix4x4 m; |
757 | 1 | AddNodeToGraph(pcOut, pcOut->mRootNode, mRootNode, m); |
758 | 1 | } |
759 | | |
760 | | // We used the first and second vertex color set to store some temporary values so we need to cleanup here |
761 | 7 | for (unsigned int a = 0; a < pcOut->mNumMeshes; ++a) { |
762 | 5 | pcOut->mMeshes[a]->mColors[0] = nullptr; |
763 | 5 | pcOut->mMeshes[a]->mColors[1] = nullptr; |
764 | 5 | } |
765 | | |
766 | 2 | pcOut->mRootNode->mTransformation = aiMatrix4x4( |
767 | 2 | 1.f, 0.f, 0.f, 0.f, |
768 | 2 | 0.f, 0.f, 1.f, 0.f, |
769 | 2 | 0.f, -1.f, 0.f, 0.f, |
770 | 2 | 0.f, 0.f, 0.f, 1.f) * |
771 | 2 | pcOut->mRootNode->mTransformation; |
772 | | |
773 | | // If the root node is unnamed name it "<3DSRoot>" |
774 | 2 | if (::strstr(pcOut->mRootNode->mName.data, "UNNAMED") || |
775 | 1 | (pcOut->mRootNode->mName.data[0] == '$' && pcOut->mRootNode->mName.data[1] == '$')) { |
776 | 1 | pcOut->mRootNode->mName.Set("<3DSRoot>"); |
777 | 1 | } |
778 | 2 | } |
779 | | |
780 | | // ------------------------------------------------------------------------------------------------ |
781 | | // Convert all meshes in the scene and generate the final output scene. |
782 | 2 | void Discreet3DSImporter::ConvertScene(aiScene *pcOut) { |
783 | | // Allocate enough storage for all output materials |
784 | 2 | pcOut->mNumMaterials = static_cast<unsigned int>(mScene->mMaterials.size()); |
785 | 2 | pcOut->mMaterials = new aiMaterial *[pcOut->mNumMaterials]; |
786 | | |
787 | | // ... and convert the 3DS materials to aiMaterial's |
788 | 7 | for (unsigned int i = 0; i < pcOut->mNumMaterials; ++i) { |
789 | 5 | auto *pcNew = new aiMaterial(); |
790 | 5 | ConvertMaterial(mScene->mMaterials[i], *pcNew); |
791 | 5 | pcOut->mMaterials[i] = pcNew; |
792 | 5 | } |
793 | | |
794 | | // Generate the output mesh list |
795 | 2 | ConvertMeshes(pcOut); |
796 | | |
797 | | // Now copy all light sources to the output scene |
798 | 2 | pcOut->mNumLights = static_cast<unsigned int>(mScene->mLights.size()); |
799 | 2 | if (pcOut->mNumLights) { |
800 | 0 | pcOut->mLights = new aiLight *[pcOut->mNumLights]; |
801 | 0 | memcpy(pcOut->mLights, &mScene->mLights[0], sizeof(void *) * pcOut->mNumLights); |
802 | 0 | } |
803 | | |
804 | | // Now copy all cameras to the output scene |
805 | 2 | pcOut->mNumCameras = static_cast<unsigned int>(mScene->mCameras.size()); |
806 | 2 | if (pcOut->mNumCameras) { |
807 | 0 | pcOut->mCameras = new aiCamera *[pcOut->mNumCameras]; |
808 | 0 | memcpy(pcOut->mCameras, &mScene->mCameras[0], sizeof(void *) * pcOut->mNumCameras); |
809 | 0 | } |
810 | 2 | } |
811 | | |
812 | | } // namespace Assimp |
813 | | |
814 | | #endif // !! ASSIMP_BUILD_NO_3DS_IMPORTER |