1/*
2Open Asset Import Library (assimp)
3----------------------------------------------------------------------
4
5Copyright (c) 2006-2017, assimp team
6
7All rights reserved.
8
9Redistribution and use of this software in source and binary forms,
10with or without modification, are permitted provided that the
11following conditions are met:
12
13* Redistributions of source code must retain the above
14 copyright notice, this list of conditions and the
15 following disclaimer.
16
17* Redistributions in binary form must reproduce the above
18 copyright notice, this list of conditions and the
19 following disclaimer in the documentation and/or other
20 materials provided with the distribution.
21
22* Neither the name of the assimp team, nor the names of its
23 contributors may be used to endorse or promote products
24 derived from this software without specific prior
25 written permission of the assimp team.
26
27THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
28"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
29LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
30A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
31OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
32SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
33LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
34DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
35THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
36(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
37OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
38
39----------------------------------------------------------------------
40*/
41
42#ifndef ASSIMP_BUILD_NO_EXPORT
43#ifndef ASSIMP_BUILD_NO_3DS_EXPORTER
44
45#include "3DSExporter.h"
46#include "3DSLoader.h"
47#include "3DSHelper.h"
48#include <assimp/SceneCombiner.h>
49#include "SplitLargeMeshes.h"
50#include "StringComparison.h"
51#include <assimp/IOSystem.hpp>
52#include <assimp/DefaultLogger.hpp>
53#include <assimp/Exporter.hpp>
54#include <memory>
55
56using namespace Assimp;
57namespace Assimp {
58using namespace D3DS;
59
60namespace {
61
62 //////////////////////////////////////////////////////////////////////////////////////
63 // Scope utility to write a 3DS file chunk.
64 //
65 // Upon construction, the chunk header is written with the chunk type (flags)
66 // filled out, but the chunk size left empty. Upon destruction, the correct chunk
67 // size based on the then-position of the output stream cursor is filled in.
68 class ChunkWriter {
69 enum {
70 CHUNK_SIZE_NOT_SET = 0xdeadbeef
71 , SIZE_OFFSET = 2
72 };
73 public:
74
75 ChunkWriter(StreamWriterLE& writer, uint16_t chunk_type)
76 : writer(writer)
77 {
78 chunk_start_pos = writer.GetCurrentPos();
79 writer.PutU2(chunk_type);
80 writer.PutU4(CHUNK_SIZE_NOT_SET);
81 }
82
83 ~ChunkWriter() {
84 std::size_t head_pos = writer.GetCurrentPos();
85
86 ai_assert(head_pos > chunk_start_pos);
87 const std::size_t chunk_size = head_pos - chunk_start_pos;
88
89 writer.SetCurrentPos(chunk_start_pos + SIZE_OFFSET);
90 writer.PutU4(static_cast<uint32_t>(chunk_size));
91 writer.SetCurrentPos(head_pos);
92 }
93
94 private:
95 StreamWriterLE& writer;
96 std::size_t chunk_start_pos;
97 };
98
99
100 // Return an unique name for a given |mesh| attached to |node| that
101 // preserves the mesh's given name if it has one. |index| is the index
102 // of the mesh in |aiScene::mMeshes|.
103 std::string GetMeshName(const aiMesh& mesh, unsigned int index, const aiNode& node) {
104 static const std::string underscore = "_";
105 char postfix[10] = {0};
106 ASSIMP_itoa10(postfix, index);
107
108 std::string result = node.mName.C_Str();
109 if (mesh.mName.length > 0) {
110 result += underscore + mesh.mName.C_Str();
111 }
112 return result + underscore + postfix;
113 }
114
115 // Return an unique name for a given |mat| with original position |index|
116 // in |aiScene::mMaterials|. The name preserves the original material
117 // name if possible.
118 std::string GetMaterialName(const aiMaterial& mat, unsigned int index) {
119 static const std::string underscore = "_";
120 char postfix[10] = {0};
121 ASSIMP_itoa10(postfix, index);
122
123 aiString mat_name;
124 if (AI_SUCCESS == mat.Get(AI_MATKEY_NAME, mat_name)) {
125 return mat_name.C_Str() + underscore + postfix;
126 }
127
128 return "Material" + underscore + postfix;
129 }
130
131 // Collect world transformations for each node
132 void CollectTrafos(const aiNode* node, std::map<const aiNode*, aiMatrix4x4>& trafos) {
133 const aiMatrix4x4& parent = node->mParent ? trafos[node->mParent] : aiMatrix4x4();
134 trafos[node] = parent * node->mTransformation;
135 for (unsigned int i = 0; i < node->mNumChildren; ++i) {
136 CollectTrafos(node->mChildren[i], trafos);
137 }
138 }
139
140 // Generate a flat list of the meshes (by index) assigned to each node
141 void CollectMeshes(const aiNode* node, std::multimap<const aiNode*, unsigned int>& meshes) {
142 for (unsigned int i = 0; i < node->mNumMeshes; ++i) {
143 meshes.insert(std::make_pair(node, node->mMeshes[i]));
144 }
145 for (unsigned int i = 0; i < node->mNumChildren; ++i) {
146 CollectMeshes(node->mChildren[i], meshes);
147 }
148 }
149}
150
151// ------------------------------------------------------------------------------------------------
152// Worker function for exporting a scene to 3DS. Prototyped and registered in Exporter.cpp
153void ExportScene3DS(const char* pFile, IOSystem* pIOSystem, const aiScene* pScene, const ExportProperties* /*pProperties*/)
154{
155 std::shared_ptr<IOStream> outfile (pIOSystem->Open(pFile, "wb"));
156 if(!outfile) {
157 throw DeadlyExportError("Could not open output .3ds file: " + std::string(pFile));
158 }
159
160 // TODO: This extra copy should be avoided and all of this made a preprocess
161 // requirement of the 3DS exporter.
162 //
163 // 3DS meshes can be max 0xffff (16 Bit) vertices and faces, respectively.
164 // SplitLargeMeshes can do this, but it requires the correct limit to be set
165 // which is not possible with the current way of specifying preprocess steps
166 // in |Exporter::ExportFormatEntry|.
167 aiScene* scenecopy_tmp;
168 SceneCombiner::CopyScene(&scenecopy_tmp,pScene);
169 std::unique_ptr<aiScene> scenecopy(scenecopy_tmp);
170
171 SplitLargeMeshesProcess_Triangle tri_splitter;
172 tri_splitter.SetLimit(0xffff);
173 tri_splitter.Execute(scenecopy.get());
174
175 SplitLargeMeshesProcess_Vertex vert_splitter;
176 vert_splitter.SetLimit(0xffff);
177 vert_splitter.Execute(scenecopy.get());
178
179 // Invoke the actual exporter
180 Discreet3DSExporter exporter(outfile, scenecopy.get());
181}
182
183} // end of namespace Assimp
184
185// ------------------------------------------------------------------------------------------------
186Discreet3DSExporter:: Discreet3DSExporter(std::shared_ptr<IOStream> outfile, const aiScene* scene)
187: scene(scene)
188, writer(outfile)
189{
190 CollectTrafos(scene->mRootNode, trafos);
191 CollectMeshes(scene->mRootNode, meshes);
192
193 ChunkWriter chunk(writer, Discreet3DS::CHUNK_MAIN);
194
195 {
196 ChunkWriter chunk(writer, Discreet3DS::CHUNK_OBJMESH);
197 WriteMaterials();
198 WriteMeshes();
199
200 {
201 ChunkWriter chunk(writer, Discreet3DS::CHUNK_MASTER_SCALE);
202 writer.PutF4(1.0f);
203 }
204 }
205
206 {
207 ChunkWriter chunk(writer, Discreet3DS::CHUNK_KEYFRAMER);
208 WriteHierarchy(*scene->mRootNode, -1, -1);
209 }
210}
211
212// ------------------------------------------------------------------------------------------------
213Discreet3DSExporter::~Discreet3DSExporter() {
214 // empty
215}
216
217
218// ------------------------------------------------------------------------------------------------
219int Discreet3DSExporter::WriteHierarchy(const aiNode& node, int seq, int sibling_level)
220{
221 // 3DS scene hierarchy is serialized as in http://www.martinreddy.net/gfx/3d/3DS.spec
222 {
223 ChunkWriter chunk(writer, Discreet3DS::CHUNK_TRACKINFO);
224 {
225 ChunkWriter chunk(writer, Discreet3DS::CHUNK_TRACKOBJNAME);
226
227 // Assimp node names are unique and distinct from all mesh-node
228 // names we generate; thus we can use them as-is
229 WriteString(node.mName);
230
231 // Two unknown int16 values - it is even unclear if 0 is a safe value
232 // but luckily importers do not know better either.
233 writer.PutI4(0);
234
235 int16_t hierarchy_pos = static_cast<int16_t>(seq);
236 if (sibling_level != -1) {
237 hierarchy_pos = sibling_level;
238 }
239
240 // Write the hierarchy position
241 writer.PutI2(hierarchy_pos);
242 }
243 }
244
245 // TODO: write transformation chunks
246
247 ++seq;
248 sibling_level = seq;
249
250 // Write all children
251 for (unsigned int i = 0; i < node.mNumChildren; ++i) {
252 seq = WriteHierarchy(*node.mChildren[i], seq, i == 0 ? -1 : sibling_level);
253 }
254
255 // Write all meshes as separate nodes to be able to reference the meshes by name
256 for (unsigned int i = 0; i < node.mNumMeshes; ++i) {
257 const bool first_child = node.mNumChildren == 0 && i == 0;
258
259 const unsigned int mesh_idx = node.mMeshes[i];
260 const aiMesh& mesh = *scene->mMeshes[mesh_idx];
261
262 ChunkWriter chunk(writer, Discreet3DS::CHUNK_TRACKINFO);
263 {
264 ChunkWriter chunk(writer, Discreet3DS::CHUNK_TRACKOBJNAME);
265 WriteString(GetMeshName(mesh, mesh_idx, node));
266
267 writer.PutI4(0);
268 writer.PutI2(static_cast<int16_t>(first_child ? seq : sibling_level));
269 ++seq;
270 }
271 }
272 return seq;
273}
274
275// ------------------------------------------------------------------------------------------------
276void Discreet3DSExporter::WriteMaterials()
277{
278 for (unsigned int i = 0; i < scene->mNumMaterials; ++i) {
279 ChunkWriter chunk(writer, Discreet3DS::CHUNK_MAT_MATERIAL);
280 const aiMaterial& mat = *scene->mMaterials[i];
281
282 {
283 ChunkWriter chunk(writer, Discreet3DS::CHUNK_MAT_MATNAME);
284 const std::string& name = GetMaterialName(mat, i);
285 WriteString(name);
286 }
287
288 aiColor3D color;
289 if (mat.Get(AI_MATKEY_COLOR_DIFFUSE, color) == AI_SUCCESS) {
290 ChunkWriter chunk(writer, Discreet3DS::CHUNK_MAT_DIFFUSE);
291 WriteColor(color);
292 }
293
294 if (mat.Get(AI_MATKEY_COLOR_SPECULAR, color) == AI_SUCCESS) {
295 ChunkWriter chunk(writer, Discreet3DS::CHUNK_MAT_SPECULAR);
296 WriteColor(color);
297 }
298
299 if (mat.Get(AI_MATKEY_COLOR_AMBIENT, color) == AI_SUCCESS) {
300 ChunkWriter chunk(writer, Discreet3DS::CHUNK_MAT_AMBIENT);
301 WriteColor(color);
302 }
303
304 if (mat.Get(AI_MATKEY_COLOR_EMISSIVE, color) == AI_SUCCESS) {
305 ChunkWriter chunk(writer, Discreet3DS::CHUNK_MAT_SELF_ILLUM);
306 WriteColor(color);
307 }
308
309 aiShadingMode shading_mode = aiShadingMode_Flat;
310 if (mat.Get(AI_MATKEY_SHADING_MODEL, shading_mode) == AI_SUCCESS) {
311 ChunkWriter chunk(writer, Discreet3DS::CHUNK_MAT_SHADING);
312
313 Discreet3DS::shadetype3ds shading_mode_out;
314 switch(shading_mode) {
315 case aiShadingMode_Flat:
316 case aiShadingMode_NoShading:
317 shading_mode_out = Discreet3DS::Flat;
318 break;
319
320 case aiShadingMode_Gouraud:
321 case aiShadingMode_Toon:
322 case aiShadingMode_OrenNayar:
323 case aiShadingMode_Minnaert:
324 shading_mode_out = Discreet3DS::Gouraud;
325 break;
326
327 case aiShadingMode_Phong:
328 case aiShadingMode_Blinn:
329 case aiShadingMode_CookTorrance:
330 case aiShadingMode_Fresnel:
331 shading_mode_out = Discreet3DS::Phong;
332 break;
333
334 default:
335 shading_mode_out = Discreet3DS::Flat;
336 ai_assert(false);
337 };
338 writer.PutU2(static_cast<uint16_t>(shading_mode_out));
339 }
340
341
342 float f;
343 if (mat.Get(AI_MATKEY_SHININESS, f) == AI_SUCCESS) {
344 ChunkWriter chunk(writer, Discreet3DS::CHUNK_MAT_SHININESS);
345 WritePercentChunk(f);
346 }
347
348 if (mat.Get(AI_MATKEY_SHININESS_STRENGTH, f) == AI_SUCCESS) {
349 ChunkWriter chunk(writer, Discreet3DS::CHUNK_MAT_SHININESS_PERCENT);
350 WritePercentChunk(f);
351 }
352
353 int twosided;
354 if (mat.Get(AI_MATKEY_TWOSIDED, twosided) == AI_SUCCESS && twosided != 0) {
355 ChunkWriter chunk(writer, Discreet3DS::CHUNK_MAT_TWO_SIDE);
356 writer.PutI2(1);
357 }
358
359 WriteTexture(mat, aiTextureType_DIFFUSE, Discreet3DS::CHUNK_MAT_TEXTURE);
360 WriteTexture(mat, aiTextureType_HEIGHT, Discreet3DS::CHUNK_MAT_BUMPMAP);
361 WriteTexture(mat, aiTextureType_OPACITY, Discreet3DS::CHUNK_MAT_OPACMAP);
362 WriteTexture(mat, aiTextureType_SHININESS, Discreet3DS::CHUNK_MAT_MAT_SHINMAP);
363 WriteTexture(mat, aiTextureType_SPECULAR, Discreet3DS::CHUNK_MAT_SPECMAP);
364 WriteTexture(mat, aiTextureType_EMISSIVE, Discreet3DS::CHUNK_MAT_SELFIMAP);
365 WriteTexture(mat, aiTextureType_REFLECTION, Discreet3DS::CHUNK_MAT_REFLMAP);
366 }
367}
368
369// ------------------------------------------------------------------------------------------------
370void Discreet3DSExporter::WriteTexture(const aiMaterial& mat, aiTextureType type, uint16_t chunk_flags)
371{
372 aiString path;
373 aiTextureMapMode map_mode[2] = {
374 aiTextureMapMode_Wrap, aiTextureMapMode_Wrap
375 };
376 ai_real blend = 1.0;
377 if (mat.GetTexture(type, 0, &path, NULL, NULL, &blend, NULL, map_mode) != AI_SUCCESS || !path.length) {
378 return;
379 }
380
381 // TODO: handle embedded textures properly
382 if (path.data[0] == '*') {
383 DefaultLogger::get()->error("Ignoring embedded texture for export: " + std::string(path.C_Str()));
384 return;
385 }
386
387 ChunkWriter chunk(writer, chunk_flags);
388 {
389 ChunkWriter chunk(writer, Discreet3DS::CHUNK_MAPFILE);
390 WriteString(path);
391 }
392
393 WritePercentChunk(blend);
394
395 {
396 ChunkWriter chunk(writer, Discreet3DS::CHUNK_MAT_MAP_TILING);
397 uint16_t val = 0; // WRAP
398 if (map_mode[0] == aiTextureMapMode_Mirror) {
399 val = 0x2;
400 }
401 else if (map_mode[0] == aiTextureMapMode_Decal) {
402 val = 0x10;
403 }
404 writer.PutU2(val);
405 }
406 // TODO: export texture transformation (i.e. UV offset, scale, rotation)
407}
408
409// ------------------------------------------------------------------------------------------------
410void Discreet3DSExporter::WriteMeshes()
411{
412 // NOTE: 3DS allows for instances. However:
413 // i) not all importers support reading them
414 // ii) instances are not as flexible as they are in assimp, in particular,
415 // nodes can carry (and instance) only one mesh.
416 //
417 // This exporter currently deep clones all instanced meshes, i.e. for each mesh
418 // attached to a node a full TRIMESH chunk is written to the file.
419 //
420 // Furthermore, the TRIMESH is transformed into world space so that it will
421 // appear correctly if importers don't read the scene hierarchy at all.
422 for (MeshesByNodeMap::const_iterator it = meshes.begin(); it != meshes.end(); ++it) {
423 const aiNode& node = *(*it).first;
424 const unsigned int mesh_idx = (*it).second;
425
426 const aiMesh& mesh = *scene->mMeshes[mesh_idx];
427
428 // This should not happen if the SLM step is correctly executed
429 // before the scene is handed to the exporter
430 ai_assert(mesh.mNumVertices <= 0xffff);
431 ai_assert(mesh.mNumFaces <= 0xffff);
432
433 const aiMatrix4x4& trafo = trafos[&node];
434
435 ChunkWriter chunk(writer, Discreet3DS::CHUNK_OBJBLOCK);
436
437 // Mesh name is tied to the node it is attached to so it can later be referenced
438 const std::string& name = GetMeshName(mesh, mesh_idx, node);
439 WriteString(name);
440
441
442 // TRIMESH chunk
443 ChunkWriter chunk2(writer, Discreet3DS::CHUNK_TRIMESH);
444
445 // Vertices in world space
446 {
447 ChunkWriter chunk(writer, Discreet3DS::CHUNK_VERTLIST);
448
449 const uint16_t count = static_cast<uint16_t>(mesh.mNumVertices);
450 writer.PutU2(count);
451 for (unsigned int i = 0; i < mesh.mNumVertices; ++i) {
452 const aiVector3D& v = trafo * mesh.mVertices[i];
453 writer.PutF4(v.x);
454 writer.PutF4(v.y);
455 writer.PutF4(v.z);
456 }
457 }
458
459 // UV coordinates
460 if (mesh.HasTextureCoords(0)) {
461 ChunkWriter chunk(writer, Discreet3DS::CHUNK_MAPLIST);
462 const uint16_t count = static_cast<uint16_t>(mesh.mNumVertices);
463 writer.PutU2(count);
464
465 for (unsigned int i = 0; i < mesh.mNumVertices; ++i) {
466 const aiVector3D& v = mesh.mTextureCoords[0][i];
467 writer.PutF4(v.x);
468 writer.PutF4(v.y);
469 }
470 }
471
472 // Faces (indices)
473 {
474 ChunkWriter chunk(writer, Discreet3DS::CHUNK_FACELIST);
475
476 ai_assert(mesh.mNumFaces <= 0xffff);
477
478 // Count triangles, discard lines and points
479 uint16_t count = 0;
480 for (unsigned int i = 0; i < mesh.mNumFaces; ++i) {
481 const aiFace& f = mesh.mFaces[i];
482 if (f.mNumIndices < 3) {
483 continue;
484 }
485 // TRIANGULATE step is a pre-requisite so we should not see polys here
486 ai_assert(f.mNumIndices == 3);
487 ++count;
488 }
489
490 writer.PutU2(count);
491 for (unsigned int i = 0; i < mesh.mNumFaces; ++i) {
492 const aiFace& f = mesh.mFaces[i];
493 if (f.mNumIndices < 3) {
494 continue;
495 }
496
497 for (unsigned int j = 0; j < 3; ++j) {
498 ai_assert(f.mIndices[j] <= 0xffff);
499 writer.PutI2(static_cast<uint16_t>(f.mIndices[j]));
500 }
501
502 // Edge visibility flag
503 writer.PutI2(0x0);
504 }
505
506 // TODO: write smoothing groups (CHUNK_SMOOLIST)
507
508 WriteFaceMaterialChunk(mesh);
509 }
510
511 // Transformation matrix by which the mesh vertices have been pre-transformed with.
512 {
513 ChunkWriter chunk(writer, Discreet3DS::CHUNK_TRMATRIX);
514 for (unsigned int r = 0; r < 4; ++r) {
515 for (unsigned int c = 0; c < 3; ++c) {
516 writer.PutF4(trafo[r][c]);
517 }
518 }
519 }
520 }
521}
522
523// ------------------------------------------------------------------------------------------------
524void Discreet3DSExporter::WriteFaceMaterialChunk(const aiMesh& mesh)
525{
526 ChunkWriter chunk(writer, Discreet3DS::CHUNK_FACEMAT);
527 const std::string& name = GetMaterialName(*scene->mMaterials[mesh.mMaterialIndex], mesh.mMaterialIndex);
528 WriteString(name);
529
530 // Because assimp splits meshes by material, only a single
531 // FACEMAT chunk needs to be written
532 ai_assert(mesh.mNumFaces <= 0xffff);
533 const uint16_t count = static_cast<uint16_t>(mesh.mNumFaces);
534 writer.PutU2(count);
535
536 for (unsigned int i = 0; i < mesh.mNumFaces; ++i) {
537 writer.PutU2(static_cast<uint16_t>(i));
538 }
539}
540
541// ------------------------------------------------------------------------------------------------
542void Discreet3DSExporter::WriteString(const std::string& s) {
543 for (std::string::const_iterator it = s.begin(); it != s.end(); ++it) {
544 writer.PutI1(*it);
545 }
546 writer.PutI1('\0');
547}
548
549// ------------------------------------------------------------------------------------------------
550void Discreet3DSExporter::WriteString(const aiString& s) {
551 for (std::size_t i = 0; i < s.length; ++i) {
552 writer.PutI1(s.data[i]);
553 }
554 writer.PutI1('\0');
555}
556
557// ------------------------------------------------------------------------------------------------
558void Discreet3DSExporter::WriteColor(const aiColor3D& color) {
559 ChunkWriter chunk(writer, Discreet3DS::CHUNK_RGBF);
560 writer.PutF4(color.r);
561 writer.PutF4(color.g);
562 writer.PutF4(color.b);
563}
564
565// ------------------------------------------------------------------------------------------------
566void Discreet3DSExporter::WritePercentChunk(float f) {
567 ChunkWriter chunk(writer, Discreet3DS::CHUNK_PERCENTF);
568 writer.PutF4(f);
569}
570
571// ------------------------------------------------------------------------------------------------
572void Discreet3DSExporter::WritePercentChunk(double f) {
573 ChunkWriter chunk(writer, Discreet3DS::CHUNK_PERCENTD);
574 writer.PutF8(f);
575}
576
577
578#endif // ASSIMP_BUILD_NO_3DS_EXPORTER
579#endif // ASSIMP_BUILD_NO_EXPORT
580