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