2
0
Эх сурвалжийг харах

Merge pull request #317 from assimp/export-3ds

3DS Exporter
Alexander Gessler 11 жил өмнө
parent
commit
9eba139feb

+ 560 - 0
code/3DSExporter.cpp

@@ -0,0 +1,560 @@
+/*
+Open Asset Import Library (assimp)
+----------------------------------------------------------------------
+
+Copyright (c) 2006-2012, assimp team
+All rights reserved.
+
+Redistribution and use of this software in source and binary forms, 
+with or without modification, are permitted provided that the 
+following conditions are met:
+
+* Redistributions of source code must retain the above
+  copyright notice, this list of conditions and the
+  following disclaimer.
+
+* Redistributions in binary form must reproduce the above
+  copyright notice, this list of conditions and the
+  following disclaimer in the documentation and/or other
+  materials provided with the distribution.
+
+* Neither the name of the assimp team, nor the names of its
+  contributors may be used to endorse or promote products
+  derived from this software without specific prior
+  written permission of the assimp team.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+----------------------------------------------------------------------
+*/
+
+#include "AssimpPCH.h"
+
+#ifndef ASSIMP_BUILD_NO_EXPORT
+#ifndef ASSIMP_BUILD_NO_3DS_EXPORTER
+
+#include "3DSExporter.h"
+#include "3DSLoader.h"
+#include "SceneCombiner.h"
+#include "SplitLargeMeshes.h"
+
+using namespace Assimp;
+namespace Assimp	{
+
+namespace {
+
+	//////////////////////////////////////////////////////////////////////////////////////
+	// Scope utility to write a 3DS file chunk.
+	//
+	// Upon construction, the chunk header is written with the chunk type (flags)
+	// filled out, but the chunk size left empty. Upon destruction, the correct chunk
+	// size based on the then-position of the output stream cursor is filled in.
+	class ChunkWriter {
+		enum {
+			  CHUNK_SIZE_NOT_SET = 0xdeadbeef
+			, SIZE_OFFSET        = 2
+		};
+	public:
+
+		ChunkWriter(StreamWriterLE& writer, uint16_t chunk_type)
+			: writer(writer)
+		{
+			chunk_start_pos = writer.GetCurrentPos();
+			writer.PutU2(chunk_type);
+			writer.PutU4(CHUNK_SIZE_NOT_SET);
+		}
+
+		~ChunkWriter() {
+			std::size_t head_pos = writer.GetCurrentPos();
+
+			ai_assert(head_pos > chunk_start_pos);
+			const std::size_t chunk_size = head_pos - chunk_start_pos;
+
+			writer.SetCurrentPos(chunk_start_pos + SIZE_OFFSET);
+			writer.PutU4(chunk_size);
+			writer.SetCurrentPos(head_pos);
+		}
+		
+	private:
+		StreamWriterLE& writer;
+		std::size_t chunk_start_pos;
+	};
+
+
+	// Return an unique name for a given |mesh| attached to |node| that
+	// preserves the mesh's given name if it has one. |index| is the index
+	// of the mesh in |aiScene::mMeshes|.
+	std::string GetMeshName(const aiMesh& mesh, unsigned int index, const aiNode& node) {
+		static const std::string underscore = "_";
+		char postfix[10] = {0};
+		ASSIMP_itoa10(postfix, index);
+
+		std::string result = node.mName.C_Str();
+		if (mesh.mName.length > 0) {
+			result += underscore + mesh.mName.C_Str();
+		}
+		return result + underscore + postfix;
+	}
+
+	// Return an unique name for a given |mat| with original position |index|
+	// in |aiScene::mMaterials|. The name preserves the original material
+	// name if possible.
+	std::string GetMaterialName(const aiMaterial& mat, unsigned int index) {
+		static const std::string underscore = "_";
+		char postfix[10] = {0};
+		ASSIMP_itoa10(postfix, index);
+
+		aiString mat_name;
+		if (AI_SUCCESS == mat.Get(AI_MATKEY_NAME, mat_name)) {
+			return mat_name.C_Str() + underscore + postfix;
+		}
+
+		return "Material" + underscore + postfix;
+	}
+
+	// Collect world transformations for each node
+	void CollectTrafos(const aiNode* node, std::map<const aiNode*, aiMatrix4x4>& trafos) {
+		const aiMatrix4x4& parent = node->mParent ? trafos[node->mParent] : aiMatrix4x4();
+		trafos[node] = parent * node->mTransformation;
+		for (unsigned int i = 0; i < node->mNumChildren; ++i) {
+			CollectTrafos(node->mChildren[i], trafos);
+		}
+	}
+
+	// Generate a flat list of the meshes (by index) assigned to each node
+	void CollectMeshes(const aiNode* node, std::multimap<const aiNode*, unsigned int>& meshes) {
+		for (unsigned int i = 0; i < node->mNumMeshes; ++i) {
+			meshes.insert(std::make_pair(node, node->mMeshes[i]));
+		}
+		for (unsigned int i = 0; i < node->mNumChildren; ++i) {
+			CollectMeshes(node->mChildren[i], meshes);
+		}
+	}
+}
+
+// ------------------------------------------------------------------------------------------------
+// Worker function for exporting a scene to 3DS. Prototyped and registered in Exporter.cpp
+void ExportScene3DS(const char* pFile, IOSystem* pIOSystem, const aiScene* pScene)
+{
+	boost::shared_ptr<IOStream> outfile (pIOSystem->Open(pFile, "wb"));
+	if(outfile == NULL) {
+		throw DeadlyExportError("Could not open output .3ds file: " + std::string(pFile));
+	} 
+
+	// TODO: This extra copy should be avoided and all of this made a preprocess
+	// requirement of the 3DS exporter.
+	//
+	// 3DS meshes can be max 0xffff (16 Bit) vertices and faces, respectively.
+	// SplitLargeMeshes can do this, but it requires the correct limit to be set
+	// which is not possible with the current way of specifying preprocess steps
+	// in |Exporter::ExportFormatEntry|.
+	aiScene* scenecopy_tmp;
+	SceneCombiner::CopyScene(&scenecopy_tmp,pScene);
+	std::auto_ptr<aiScene> scenecopy(scenecopy_tmp);
+
+	SplitLargeMeshesProcess_Triangle tri_splitter;
+	tri_splitter.SetLimit(0xffff);
+	tri_splitter.Execute(scenecopy.get());
+
+	SplitLargeMeshesProcess_Vertex vert_splitter;
+	vert_splitter.SetLimit(0xffff);
+	vert_splitter.Execute(scenecopy.get());
+
+	// Invoke the actual exporter 
+	Discreet3DSExporter exporter(outfile, scenecopy.get());
+}
+
+} // end of namespace Assimp
+
+// ------------------------------------------------------------------------------------------------
+Discreet3DSExporter:: Discreet3DSExporter(boost::shared_ptr<IOStream> outfile, const aiScene* scene)
+: scene(scene)
+, writer(outfile)
+{
+	CollectTrafos(scene->mRootNode, trafos);
+	CollectMeshes(scene->mRootNode, meshes);
+
+	ChunkWriter chunk(writer, Discreet3DS::CHUNK_MAIN);
+
+	{
+		ChunkWriter chunk(writer, Discreet3DS::CHUNK_OBJMESH);
+		WriteMeshes();
+		WriteMaterials();
+
+		{
+			ChunkWriter chunk(writer, Discreet3DS::CHUNK_MASTER_SCALE);
+			writer.PutF4(1.0f);
+		}
+	}
+
+	{
+		ChunkWriter chunk(writer, Discreet3DS::CHUNK_KEYFRAMER);
+		WriteHierarchy(*scene->mRootNode, -1, -1);
+	}
+}
+
+// ------------------------------------------------------------------------------------------------
+int Discreet3DSExporter::WriteHierarchy(const aiNode& node, int seq, int sibling_level)
+{
+	// 3DS scene hierarchy is serialized as in http://www.martinreddy.net/gfx/3d/3DS.spec
+	{
+		ChunkWriter chunk(writer, Discreet3DS::CHUNK_TRACKINFO);
+		{
+			ChunkWriter chunk(writer, Discreet3DS::CHUNK_TRACKOBJNAME);
+
+			// Assimp node names are unique and distinct from all mesh-node
+			// names we generate; thus we can use them as-is
+			WriteString(node.mName);
+
+			// Two unknown int16 values - it is even unclear if 0 is a safe value
+			// but luckily importers do not know better either.
+			writer.PutI4(0);
+
+			int16_t hierarchy_pos = static_cast<int16_t>(seq);
+			if (sibling_level != -1) {
+				hierarchy_pos = sibling_level;
+			}
+
+			// Write the hierarchy position
+			writer.PutI2(hierarchy_pos);
+		}
+	}
+
+	// TODO: write transformation chunks
+
+	++seq;
+	sibling_level = seq;
+
+	// Write all children
+	for (unsigned int i = 0; i < node.mNumChildren; ++i) {
+		seq = WriteHierarchy(*node.mChildren[i], seq, i == 0 ? -1 : sibling_level);
+	}
+
+	// Write all meshes as separate nodes to be able to reference the meshes by name
+	for (unsigned int i = 0; i < node.mNumMeshes; ++i) {
+		const bool first_child = node.mNumChildren == 0 && i == 0;
+
+		const unsigned int mesh_idx = node.mMeshes[i];
+		const aiMesh& mesh = *scene->mMeshes[mesh_idx];
+
+		ChunkWriter chunk(writer, Discreet3DS::CHUNK_TRACKINFO);
+		{
+			ChunkWriter chunk(writer, Discreet3DS::CHUNK_TRACKOBJNAME);
+			WriteString(GetMeshName(mesh, mesh_idx, node));
+
+			writer.PutI4(0);
+			writer.PutI2(static_cast<int16_t>(first_child ? seq : sibling_level));
+			++seq;
+		}
+	}
+	return seq;
+}
+
+// ------------------------------------------------------------------------------------------------
+void Discreet3DSExporter::WriteMaterials()
+{
+	for (unsigned int i = 0; i < scene->mNumMaterials; ++i) {
+		ChunkWriter chunk(writer, Discreet3DS::CHUNK_MAT_MATERIAL);
+		const aiMaterial& mat = *scene->mMaterials[i];
+
+		{
+			ChunkWriter chunk(writer, Discreet3DS::CHUNK_MAT_MATNAME);
+			const std::string& name = GetMaterialName(mat, i);
+			WriteString(name);
+		}
+
+		aiColor3D color;
+		if (mat.Get(AI_MATKEY_COLOR_DIFFUSE, color) == AI_SUCCESS) {
+			ChunkWriter chunk(writer, Discreet3DS::CHUNK_MAT_DIFFUSE);
+			WriteColor(color);
+		}
+
+		if (mat.Get(AI_MATKEY_COLOR_SPECULAR, color) == AI_SUCCESS) {
+			ChunkWriter chunk(writer, Discreet3DS::CHUNK_MAT_SPECULAR);
+			WriteColor(color);
+		}
+
+		if (mat.Get(AI_MATKEY_COLOR_AMBIENT, color) == AI_SUCCESS) {
+			ChunkWriter chunk(writer, Discreet3DS::CHUNK_MAT_AMBIENT);
+			WriteColor(color);
+		}
+
+		if (mat.Get(AI_MATKEY_COLOR_EMISSIVE, color) == AI_SUCCESS) {
+			ChunkWriter chunk(writer, Discreet3DS::CHUNK_MAT_SELF_ILLUM);
+			WriteColor(color);
+		}
+
+		aiShadingMode shading_mode;
+		if (mat.Get(AI_MATKEY_SHADING_MODEL, shading_mode) == AI_SUCCESS) {
+			ChunkWriter chunk(writer, Discreet3DS::CHUNK_MAT_SHADING);
+
+			Discreet3DS::shadetype3ds shading_mode_out;
+			switch(shading_mode) {
+			case aiShadingMode_Flat:
+			case aiShadingMode_NoShading:
+				shading_mode_out = Discreet3DS::Flat;
+				break;
+
+			case aiShadingMode_Gouraud:
+			case aiShadingMode_Toon:
+			case aiShadingMode_OrenNayar:
+			case aiShadingMode_Minnaert:
+				shading_mode_out = Discreet3DS::Gouraud;
+				break;
+
+			case aiShadingMode_Phong:
+			case aiShadingMode_Blinn:
+			case aiShadingMode_CookTorrance:
+			case aiShadingMode_Fresnel:
+				shading_mode_out = Discreet3DS::Phong;
+				break;
+
+			default:
+				ai_assert(false);
+			};
+			writer.PutU2(static_cast<uint16_t>(shading_mode_out));
+		}
+
+
+		float f;
+		if (mat.Get(AI_MATKEY_SHININESS, f) == AI_SUCCESS) {
+			ChunkWriter chunk(writer, Discreet3DS::CHUNK_MAT_SHININESS);
+			WritePercentChunk(f);
+		}
+
+		if (mat.Get(AI_MATKEY_SHININESS_STRENGTH, f) == AI_SUCCESS) {
+			ChunkWriter chunk(writer, Discreet3DS::CHUNK_MAT_SHININESS_PERCENT);
+			WritePercentChunk(f);
+		}
+
+		int twosided;
+		if (mat.Get(AI_MATKEY_TWOSIDED, twosided) == AI_SUCCESS && twosided != 0) {
+			ChunkWriter chunk(writer, Discreet3DS::CHUNK_MAT_TWO_SIDE);
+			writer.PutI2(1);
+		}
+		
+		WriteTexture(mat, aiTextureType_DIFFUSE, Discreet3DS::CHUNK_MAT_TEXTURE);
+		WriteTexture(mat, aiTextureType_HEIGHT, Discreet3DS::CHUNK_MAT_BUMPMAP);
+		WriteTexture(mat, aiTextureType_OPACITY, Discreet3DS::CHUNK_MAT_OPACMAP);
+		WriteTexture(mat, aiTextureType_SHININESS, Discreet3DS::CHUNK_MAT_MAT_SHINMAP);
+		WriteTexture(mat, aiTextureType_SPECULAR, Discreet3DS::CHUNK_MAT_SPECMAP);
+		WriteTexture(mat, aiTextureType_EMISSIVE, Discreet3DS::CHUNK_MAT_SELFIMAP);
+		WriteTexture(mat, aiTextureType_REFLECTION, Discreet3DS::CHUNK_MAT_REFLMAP);
+	}
+}
+
+// ------------------------------------------------------------------------------------------------
+void Discreet3DSExporter::WriteTexture(const aiMaterial& mat, aiTextureType type, uint16_t chunk_flags) 
+{
+	aiString path;
+	aiTextureMapMode map_mode[2] = {
+		aiTextureMapMode_Wrap, aiTextureMapMode_Wrap
+	};
+	float blend = 1.0f;
+	if (mat.GetTexture(type, 0, &path, NULL, NULL, &blend, NULL, map_mode) != AI_SUCCESS || !path.length) {
+		return;
+	}
+
+	// TODO: handle embedded textures properly
+	if (path.data[0] == '*') {
+		DefaultLogger::get()->error("Ignoring embedded texture for export: " + std::string(path.C_Str()));
+		return;
+	}
+
+	ChunkWriter chunk(writer, chunk_flags);
+	{
+		ChunkWriter chunk(writer, Discreet3DS::CHUNK_MAPFILE);
+		WriteString(path);
+	}
+
+	WritePercentChunk(blend);
+
+	{
+		ChunkWriter chunk(writer, Discreet3DS::CHUNK_MAT_MAP_TILING);
+		uint16_t val = 0; // WRAP
+		if (map_mode[0] == aiTextureMapMode_Mirror) {
+			val = 0x2;
+		}
+		else if (map_mode[0] == aiTextureMapMode_Decal) {
+			val = 0x10;
+		}
+		writer.PutU2(val);
+	}
+	// TODO: export texture transformation (i.e. UV offset, scale, rotation)
+}
+
+// ------------------------------------------------------------------------------------------------
+void Discreet3DSExporter::WriteMeshes()
+{
+	// NOTE: 3DS allows for instances. However:
+	//   i)  not all importers support reading them
+	//   ii) instances are not as flexible as they are in assimp, in particular,
+	//        nodes can carry (and instance) only one mesh.
+	//
+	// This exporter currently deep clones all instanced meshes, i.e. for each mesh
+	// attached to a node a full TRIMESH chunk is written to the file.
+	//
+	// Furthermore, the TRIMESH is transformed into world space so that it will
+	// appear correctly if importers don't read the scene hierarchy at all.
+	for (MeshesByNodeMap::const_iterator it = meshes.begin(); it != meshes.end(); ++it) {
+		const aiNode& node = *(*it).first;
+		const unsigned int mesh_idx = (*it).second;
+
+		const aiMesh& mesh = *scene->mMeshes[mesh_idx];
+
+		// This should not happen if the SLM step is correctly executed
+		// before the scene is handed to the exporter
+		ai_assert(mesh.mNumVertices <= 0xffff);
+		ai_assert(mesh.mNumFaces <= 0xffff);
+
+		const aiMatrix4x4& trafo = trafos[&node];
+
+		ChunkWriter chunk(writer, Discreet3DS::CHUNK_OBJBLOCK);
+
+		// Mesh name is tied to the node it is attached to so it can later be referenced
+		const std::string& name = GetMeshName(mesh, mesh_idx, node);
+		WriteString(name);
+
+
+		// TRIMESH chunk
+		ChunkWriter chunk2(writer, Discreet3DS::CHUNK_TRIMESH);
+
+		// Vertices in world space
+		{
+			ChunkWriter chunk(writer, Discreet3DS::CHUNK_VERTLIST);
+
+			const uint16_t count = static_cast<uint16_t>(mesh.mNumVertices);
+			writer.PutU2(count);
+			for (unsigned int i = 0; i < mesh.mNumVertices; ++i) {
+				const aiVector3D& v = trafo * mesh.mVertices[i];
+				writer.PutF4(v.x);
+				writer.PutF4(v.y);
+				writer.PutF4(v.z);
+			}
+		}
+
+		// UV coordinates
+		if (mesh.HasTextureCoords(0)) {
+			ChunkWriter chunk(writer, Discreet3DS::CHUNK_MAPLIST);
+			const uint16_t count = static_cast<uint16_t>(mesh.mNumVertices);
+			writer.PutU2(count);
+
+			for (unsigned int i = 0; i < mesh.mNumVertices; ++i) {
+				const aiVector3D& v = mesh.mTextureCoords[0][i];
+				writer.PutF4(v.x);
+				writer.PutF4(v.y);
+			}
+		}
+
+		// Faces (indices)
+		{
+			ChunkWriter chunk(writer, Discreet3DS::CHUNK_FACELIST);
+
+			ai_assert(mesh.mNumFaces <= 0xffff);
+
+			// Count triangles, discard lines and points
+			uint16_t count = 0;
+			for (unsigned int i = 0; i < mesh.mNumFaces; ++i) {
+				const aiFace& f = mesh.mFaces[i];
+				if (f.mNumIndices < 3) {
+					continue;
+				}
+				// TRIANGULATE step is a pre-requisite so we should not see polys here
+				ai_assert(f.mNumIndices == 3);
+				++count;
+			}
+
+			writer.PutU2(count);
+			for (unsigned int i = 0; i < mesh.mNumFaces; ++i) {
+				const aiFace& f = mesh.mFaces[i];
+				if (f.mNumIndices < 3) {
+					continue;
+				}
+
+				for (unsigned int j = 0; j < 3; ++j) {
+					ai_assert(f.mIndices[j] <= 0xffff);
+					writer.PutI2(static_cast<uint16_t>(f.mIndices[j]));
+				}
+
+				// Edge visibility flag
+				writer.PutI2(0x0);
+			}
+
+			// TODO: write smoothing groups (CHUNK_SMOOLIST)
+
+			WriteFaceMaterialChunk(mesh);
+		}
+
+		// Transformation matrix by which the mesh vertices have been pre-transformed with.
+		{
+			ChunkWriter chunk(writer, Discreet3DS::CHUNK_TRMATRIX);
+			for (unsigned int r = 0; r < 4; ++r) {
+				for (unsigned int c = 0; c < 3; ++c) {
+					writer.PutF4(trafo[r][c]);
+				}
+			}
+		}
+	}
+}
+
+// ------------------------------------------------------------------------------------------------
+void Discreet3DSExporter::WriteFaceMaterialChunk(const aiMesh& mesh)
+{
+	ChunkWriter chunk(writer, Discreet3DS::CHUNK_FACEMAT);
+	const std::string& name = GetMaterialName(*scene->mMaterials[mesh.mMaterialIndex], mesh.mMaterialIndex);
+	WriteString(name);
+
+	// Because assimp splits meshes by material, only a single
+	// FACEMAT chunk needs to be written
+	ai_assert(mesh.mNumFaces <= 0xffff);
+	const uint16_t count = static_cast<uint16_t>(mesh.mNumFaces);
+	writer.PutU2(count);
+
+	for (unsigned int i = 0; i < mesh.mNumFaces; ++i) {
+		writer.PutU2(static_cast<uint16_t>(i));
+	}
+}
+
+// ------------------------------------------------------------------------------------------------
+void Discreet3DSExporter::WriteString(const std::string& s) {
+	for (std::string::const_iterator it = s.begin(); it != s.end(); ++it) {
+		writer.PutI1(*it);
+	}
+	writer.PutI1('\0');
+}
+
+// ------------------------------------------------------------------------------------------------
+void Discreet3DSExporter::WriteString(const aiString& s) {
+	for (std::size_t i = 0; i < s.length; ++i) {
+		writer.PutI1(s.data[i]);
+	}
+	writer.PutI1('\0');
+}
+
+// ------------------------------------------------------------------------------------------------
+void Discreet3DSExporter::WriteColor(const aiColor3D& color) {
+	ChunkWriter chunk(writer, Discreet3DS::CHUNK_RGBF);
+	writer.PutF4(color.r);
+	writer.PutF4(color.g);
+	writer.PutF4(color.b);
+}
+
+// ------------------------------------------------------------------------------------------------
+void Discreet3DSExporter::WritePercentChunk(float f) {
+	ChunkWriter chunk(writer, Discreet3DS::CHUNK_PERCENTF);
+	writer.PutF4(f);
+}
+
+
+#endif // ASSIMP_BUILD_NO_3DS_EXPORTER
+#endif // ASSIMP_BUILD_NO_EXPORT

+ 94 - 0
code/3DSExporter.h

@@ -0,0 +1,94 @@
+/*
+Open Asset Import Library (assimp)
+----------------------------------------------------------------------
+
+Copyright (c) 2006-2012, assimp team
+All rights reserved.
+
+Redistribution and use of this software in source and binary forms, 
+with or without modification, are permitted provided that the 
+following conditions are met:
+
+* Redistributions of source code must retain the above
+  copyright notice, this list of conditions and the
+  following disclaimer.
+
+* Redistributions in binary form must reproduce the above
+  copyright notice, this list of conditions and the
+  following disclaimer in the documentation and/or other
+  materials provided with the distribution.
+
+* Neither the name of the assimp team, nor the names of its
+  contributors may be used to endorse or promote products
+  derived from this software without specific prior
+  written permission of the assimp team.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+----------------------------------------------------------------------
+*/
+
+/** @file 3DSExporter.h
+ * 3DS Exporter Main Header
+ */
+#ifndef AI_3DSEXPORTER_H_INC
+#define AI_3DSEXPORTER_H_INC
+
+#include <map>
+
+#include "StreamWriter.h"
+
+struct aiScene;
+struct aiNode;
+
+namespace Assimp	
+{
+
+// ------------------------------------------------------------------------------------------------
+/** Helper class to export a given scene to a 3DS file. */
+// ------------------------------------------------------------------------------------------------
+class Discreet3DSExporter
+{
+public:
+	Discreet3DSExporter(boost::shared_ptr<IOStream> outfile, const aiScene* pScene);
+
+private:
+
+	void WriteMeshes();
+	void WriteMaterials();
+	void WriteTexture(const aiMaterial& mat, aiTextureType type, uint16_t chunk_flags);
+
+	void WriteFaceMaterialChunk(const aiMesh& mesh);
+
+	int WriteHierarchy(const aiNode& node, int level, int sibling_level);
+
+	void WriteString(const std::string& s);
+	void WriteString(const aiString& s);
+	void WriteColor(const aiColor3D& color);
+	void WritePercentChunk(float f);
+
+private:
+
+	const aiScene* const scene;
+	StreamWriterLE writer;
+
+	std::map<const aiNode*, aiMatrix4x4> trafos;
+
+	typedef std::multimap<const aiNode*, unsigned int> MeshesByNodeMap;
+	MeshesByNodeMap meshes;
+
+};
+
+}
+
+#endif

+ 3 - 0
code/CMakeLists.txt

@@ -111,6 +111,7 @@ SET( Common_SRCS
 	MemoryIOWrapper.h
 	ParsingUtils.h
 	StreamReader.h
+	StreamWriter.h
 	StringComparison.h
 	SGSpatialSort.cpp
 	SGSpatialSort.h
@@ -151,6 +152,8 @@ SET( 3DS_SRCS
 	3DSHelper.h
 	3DSLoader.cpp
 	3DSLoader.h
+	3DSExporter.h
+	3DSExporter.cpp
 )
 SOURCE_GROUP(3DS FILES ${3DS_SRCS})
 

+ 6 - 5
code/Exporter.cpp

@@ -78,7 +78,7 @@ void ExportSceneObj(const char*,IOSystem*, const aiScene*);
 void ExportSceneSTL(const char*,IOSystem*, const aiScene*);
 void ExportSceneSTLBinary(const char*,IOSystem*, const aiScene*);
 void ExportScenePly(const char*,IOSystem*, const aiScene*);
-void ExportScene3DS(const char*, IOSystem*, const aiScene*) {}
+void ExportScene3DS(const char*, IOSystem*, const aiScene*);
 
 // ------------------------------------------------------------------------------------------------
 // global array of all export formats which Assimp supports in its current build
@@ -90,7 +90,7 @@ Exporter::ExportFormatEntry gExporters[] =
 
 #ifndef ASSIMP_BUILD_NO_FXILE_EXPORTER
 	Exporter::ExportFormatEntry( "x", "X Files", "x", &ExportSceneXFile,
-	aiProcess_MakeLeftHanded | aiProcess_FlipWindingOrder | aiProcess_FlipUVs),
+		aiProcess_MakeLeftHanded | aiProcess_FlipWindingOrder | aiProcess_FlipUVs),
 #endif
 
 #ifndef ASSIMP_BUILD_NO_OBJ_EXPORTER
@@ -113,9 +113,10 @@ Exporter::ExportFormatEntry gExporters[] =
 	),
 #endif
 
-//#ifndef ASSIMP_BUILD_NO_3DS_EXPORTER
-//	ExportFormatEntry( "3ds", "Autodesk 3DS (legacy format)", "3ds" , &ExportScene3DS),
-//#endif
+#ifndef ASSIMP_BUILD_NO_3DS_EXPORTER
+	Exporter::ExportFormatEntry( "3ds", "Autodesk 3DS (legacy)", "3ds" , &ExportScene3DS,
+		aiProcess_Triangulate | aiProcess_SortByPType | aiProcess_JoinIdenticalVertices),
+#endif
 };
 
 #define ASSIMP_NUM_EXPORTERS (sizeof(gExporters)/sizeof(gExporters[0]))

+ 235 - 0
code/StreamWriter.h

@@ -0,0 +1,235 @@
+/*
+---------------------------------------------------------------------------
+Open Asset Import Library (assimp)
+---------------------------------------------------------------------------
+
+Copyright (c) 2006-2012, assimp team
+
+All rights reserved.
+
+Redistribution and use of this software in source and binary forms, 
+with or without modification, are permitted provided that the following 
+conditions are met:
+
+* Redistributions of source code must retain the above
+  copyright notice, this list of conditions and the
+  following disclaimer.
+
+* Redistributions in binary form must reproduce the above
+  copyright notice, this list of conditions and the
+  following disclaimer in the documentation and/or other
+  materials provided with the distribution.
+
+* Neither the name of the assimp team, nor the names of its
+  contributors may be used to endorse or promote products
+  derived from this software without specific prior
+  written permission of the assimp team.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+---------------------------------------------------------------------------
+*/
+
+/** @file Defines the StreamWriter class which writes data to
+ *  a binary stream with a well-defined endianess. */
+
+#ifndef AI_STREAMWRITER_H_INCLUDED
+#define AI_STREAMWRITER_H_INCLUDED
+
+#include "ByteSwap.h"
+
+namespace Assimp {
+
+// --------------------------------------------------------------------------------------------
+/** Wrapper class around IOStream to allow for consistent writing of binary data in both 
+ *  little and big endian format. Don't attempt to instance the template directly. Use 
+ *  StreamWriterLE to read from a little-endian stream and StreamWriterBE to read from a 
+ *  BE stream. Alternatively, there is StreamWriterAny if the endianess of the output
+ *  stream is to be determined at runtime.
+ */
+// --------------------------------------------------------------------------------------------
+template <bool SwapEndianess = false, bool RuntimeSwitch = false>
+class StreamWriter
+{
+	enum {
+		INITIAL_CAPACITY = 1024
+	};
+
+public:
+
+	// ---------------------------------------------------------------------
+	/** Construction from a given stream with a well-defined endianess.
+	 * 
+	 *  The StreamReader holds a permanent strong reference to the
+	 *  stream, which is released upon destruction.
+	 *  @param stream Input stream. The stream is not re-seeked and writing
+	      continues at the current position of the stream cursor.
+	 *  @param le If @c RuntimeSwitch is true: specifies whether the
+	 *    stream is in little endian byte order. Otherwise the
+	 *    endianess information is defined by the @c SwapEndianess
+	 *    template parameter and this parameter is meaningless.  */
+	StreamWriter(boost::shared_ptr<IOStream> stream, bool le = false)
+		: stream(stream)
+		, le(le)
+		, cursor()
+	{
+		ai_assert(stream); 
+		buffer.reserve(INITIAL_CAPACITY);
+	}
+
+	// ---------------------------------------------------------------------
+	StreamWriter(IOStream* stream, bool le = false)
+		: stream(boost::shared_ptr<IOStream>(stream))
+		, le(le)
+		, cursor()
+	{
+		ai_assert(stream);
+		buffer.reserve(INITIAL_CAPACITY);
+	}
+
+	// ---------------------------------------------------------------------
+	~StreamWriter() {
+		stream->Write(&buffer[0], 1, buffer.size());
+		stream->Flush();
+	}
+
+public:
+
+	// ---------------------------------------------------------------------
+	/** Write a float to the stream  */
+	void PutF4(float f)
+	{
+		Put(f);
+	}
+
+	// ---------------------------------------------------------------------
+	/** Write a double to the stream  */
+	void PutF8(double d)	{
+		Put(d);
+	}
+
+	// ---------------------------------------------------------------------
+	/** Write a signed 16 bit integer to the stream */
+	void PutI2(int16_t n)	{
+		Put(n);
+	}
+
+	// ---------------------------------------------------------------------
+	/** Write a signed 8 bit integer to the stream */
+	void PutI1(int8_t n)	{
+		Put(n);
+	}
+
+	// ---------------------------------------------------------------------
+	/** Write an signed 32 bit integer to the stream */
+	void PutI4(int32_t n)	{
+		Put(n);
+	}
+
+	// ---------------------------------------------------------------------
+	/** Write a signed 64 bit integer to the stream */
+	void PutI8(int64_t n)	{
+		Put(n);
+	}
+
+	// ---------------------------------------------------------------------
+	/** Write a unsigned 16 bit integer to the stream */
+	void PutU2(uint16_t n)	{
+		Put(n);
+	}
+
+	// ---------------------------------------------------------------------
+	/** Write a unsigned 8 bit integer to the stream */
+	void PutU1(uint8_t n)	{
+		Put(n);
+	}
+
+	// ---------------------------------------------------------------------
+	/** Write an unsigned 32 bit integer to the stream */
+	void PutU4(uint32_t n)	{
+		Put(n);
+	}
+
+	// ---------------------------------------------------------------------
+	/** Write a unsigned 64 bit integer to the stream */
+	void PutU8(uint64_t n)	{
+		Put(n);
+	}
+
+public:
+
+	// ---------------------------------------------------------------------
+	/** overload operator<< and allow chaining of MM ops. */
+	template <typename T>
+	StreamWriter& operator << (T f) {
+		Put(f); 
+		return *this;
+	}
+
+	// ---------------------------------------------------------------------
+	std::size_t GetCurrentPos() const {
+		return cursor;
+	}
+
+	// ---------------------------------------------------------------------
+	void SetCurrentPos(std::size_t new_cursor) {
+		cursor = new_cursor;
+	}
+
+private:
+
+	// ---------------------------------------------------------------------
+	/** Generic write method. ByteSwap::Swap(T*) *must* be defined */
+	template <typename T>
+	void Put(T f)	{
+		Intern :: Getter<SwapEndianess,T,RuntimeSwitch>() (&f, le);
+		
+		if (cursor + sizeof(T) >= buffer.size()) {
+			buffer.resize(cursor + sizeof(T));
+		}
+
+		void* dest = &buffer[cursor];
+
+		// reinterpret_cast + assignment breaks strict aliasing rules
+		// and generally causes trouble on platforms such as ARM that
+		// do not silently ignore alignment faults.
+		::memcpy(dest, &f, sizeof(T));
+		cursor += sizeof(T);
+	}
+
+private:
+
+	boost::shared_ptr<IOStream> stream;
+	bool le;
+
+	std::vector<uint8_t> buffer;
+	std::size_t cursor;
+};
+
+
+// --------------------------------------------------------------------------------------------
+// `static` StreamWriter. Their byte order is fixed and they might be a little bit faster.
+#ifdef AI_BUILD_BIG_ENDIAN
+	typedef StreamWriter<true>  StreamWriterLE;
+	typedef StreamWriter<false> StreamWriterBE;
+#else
+	typedef StreamWriter<true>  StreamWriterBE;
+	typedef StreamWriter<false> StreamWriterLE;
+#endif
+
+// `dynamic` StreamWriter. The byte order of the input data is specified in the
+// c'tor. This involves runtime branching and might be a little bit slower.
+typedef StreamWriter<true,true> StreamWriterAny;
+
+} // end namespace Assimp
+
+#endif // !! AI_STREAMWriter_H_INCLUDED

+ 12 - 105
test/CMakeLists.txt

@@ -15,7 +15,8 @@ SOURCE_GROUP( unit FILES
 	unit/BoostWorkaround/tupletest.cpp
 )
 
-SOURCE_GROUP( cppunit FILES 
+
+SET( CPPUNIT_SRCS
 	../contrib/cppunit-1.12.1/src/cppunit/AdditionalMessage.cpp
 	../contrib/cppunit-1.12.1/src/cppunit/Asserter.cpp
 	../contrib/cppunit-1.12.1/src/cppunit/BeOsDynamicLibraryManager.cpp
@@ -70,11 +71,10 @@ SOURCE_GROUP( cppunit FILES
 	../contrib/cppunit-1.12.1/src/cppunit/XmlOutputter.cpp
 	../contrib/cppunit-1.12.1/src/cppunit/XmlOutputterHook.cpp
 )
+SOURCE_GROUP(cppunit FILES  ${CPPUNIT_SRCS})
 
-SOURCE_GROUP( tests FILES 
-	unit/Main.cpp
-	unit/UnitTestPCH.cpp
-	unit/UnitTestPCH.h
+
+SET( TEST_SRCS
 	unit/utFindDegenerates.cpp
 	unit/utFindDegenerates.h
 	unit/utFindInvalidData.cpp
@@ -116,114 +116,21 @@ SOURCE_GROUP( tests FILES
 	unit/utVertexTriangleAdjacency.h
 	unit/utNoBoostTest.cpp
 	unit/utNoBoostTest.h
+	unit/BoostWorkaround/tupletest.cpp	
 )
 
+SOURCE_GROUP(tests FILES  ${TEST_SRCS})
+
+
 add_executable(  unit
 	unit/CCompilerTest.c
 	unit/Main.cpp
 	unit/UnitTestPCH.cpp
 	unit/UnitTestPCH.h
-	unit/utFindDegenerates.cpp
-	unit/utFindDegenerates.h
-	unit/utFindInvalidData.cpp
-	unit/utFindInvalidData.h
-	unit/utFixInfacingNormals.cpp
-	unit/utGenNormals.cpp
-	unit/utGenNormals.h
-	unit/utImporter.cpp
-	unit/utImporter.h
-	unit/utImproveCacheLocality.cpp
-	unit/utJoinVertices.cpp
-	unit/utJoinVertices.h
-	unit/utLimitBoneWeights.cpp
-	unit/utLimitBoneWeights.h
-	unit/utMaterialSystem.cpp
-	unit/utMaterialSystem.h
-	unit/utPretransformVertices.cpp
-	unit/utPretransformVertices.h
-	unit/utRemoveComments.cpp
-	unit/utRemoveComments.h
-	unit/utRemoveComponent.cpp
-	unit/utRemoveComponent.h
-	unit/utRemoveRedundantMaterials.cpp
-	unit/utRemoveRedundantMaterials.h
-	unit/utScenePreprocessor.cpp
-	unit/utScenePreprocessor.h
-	unit/utSharedPPData.cpp
-	unit/utSharedPPData.h
-	unit/utSortByPType.cpp
-	unit/utSortByPType.h
-	unit/utSplitLargeMeshes.cpp
-	unit/utSplitLargeMeshes.h
-	unit/utTargetAnimation.cpp
-	unit/utTargetAnimation.h
-	unit/utTextureTransform.cpp
-	unit/utTriangulate.cpp
-	unit/utTriangulate.h
-	unit/utVertexTriangleAdjacency.cpp
-	unit/utVertexTriangleAdjacency.h
-	unit/utNoBoostTest.cpp
-	unit/utNoBoostTest.h
-	unit/BoostWorkaround/tupletest.cpp	
-	../contrib/cppunit-1.12.1/src/cppunit/AdditionalMessage.cpp
-	../contrib/cppunit-1.12.1/src/cppunit/Asserter.cpp
-	../contrib/cppunit-1.12.1/src/cppunit/BeOsDynamicLibraryManager.cpp
-	../contrib/cppunit-1.12.1/src/cppunit/BriefTestProgressListener.cpp
-	../contrib/cppunit-1.12.1/src/cppunit/CompilerOutputter.cpp
-	../contrib/cppunit-1.12.1/src/cppunit/DefaultProtector.cpp
-	../contrib/cppunit-1.12.1/src/cppunit/DefaultProtector.h
-	../contrib/cppunit-1.12.1/src/cppunit/DllMain.cpp
-	../contrib/cppunit-1.12.1/src/cppunit/DynamicLibraryManager.cpp
-	../contrib/cppunit-1.12.1/src/cppunit/DynamicLibraryManagerException.cpp
-	../contrib/cppunit-1.12.1/src/cppunit/Exception.cpp
-	../contrib/cppunit-1.12.1/src/cppunit/Message.cpp
-	../contrib/cppunit-1.12.1/src/cppunit/PlugInManager.cpp
-	../contrib/cppunit-1.12.1/src/cppunit/PlugInParameters.cpp
-	../contrib/cppunit-1.12.1/src/cppunit/Protector.cpp
-	../contrib/cppunit-1.12.1/src/cppunit/ProtectorChain.cpp
-	../contrib/cppunit-1.12.1/src/cppunit/ProtectorChain.h
-	../contrib/cppunit-1.12.1/src/cppunit/ProtectorContext.h
-	../contrib/cppunit-1.12.1/src/cppunit/RepeatedTest.cpp
-	../contrib/cppunit-1.12.1/src/cppunit/ShlDynamicLibraryManager.cpp
-	../contrib/cppunit-1.12.1/src/cppunit/SourceLine.cpp
-	../contrib/cppunit-1.12.1/src/cppunit/StringTools.cpp
-	../contrib/cppunit-1.12.1/src/cppunit/SynchronizedObject.cpp
-	../contrib/cppunit-1.12.1/src/cppunit/Test.cpp
-	../contrib/cppunit-1.12.1/src/cppunit/TestAssert.cpp
-	../contrib/cppunit-1.12.1/src/cppunit/TestCase.cpp
-	../contrib/cppunit-1.12.1/src/cppunit/TestCaseDecorator.cpp
-	../contrib/cppunit-1.12.1/src/cppunit/TestComposite.cpp
-	../contrib/cppunit-1.12.1/src/cppunit/TestDecorator.cpp
-	../contrib/cppunit-1.12.1/src/cppunit/TestFactoryRegistry.cpp
-	../contrib/cppunit-1.12.1/src/cppunit/TestFailure.cpp
-	../contrib/cppunit-1.12.1/src/cppunit/TestLeaf.cpp
-	../contrib/cppunit-1.12.1/src/cppunit/TestNamer.cpp
-	../contrib/cppunit-1.12.1/src/cppunit/TestPath.cpp
-	../contrib/cppunit-1.12.1/src/cppunit/TestPlugInDefaultImpl.cpp
-	../contrib/cppunit-1.12.1/src/cppunit/TestResult.cpp
-	../contrib/cppunit-1.12.1/src/cppunit/TestResultCollector.cpp
-	../contrib/cppunit-1.12.1/src/cppunit/TestRunner.cpp
-	../contrib/cppunit-1.12.1/src/cppunit/TestSetUp.cpp
-	../contrib/cppunit-1.12.1/src/cppunit/TestSuccessListener.cpp
-	../contrib/cppunit-1.12.1/src/cppunit/TestSuite.cpp
-	../contrib/cppunit-1.12.1/src/cppunit/TestSuiteBuilderContext.cpp
-	../contrib/cppunit-1.12.1/src/cppunit/TextOutputter.cpp
-	../contrib/cppunit-1.12.1/src/cppunit/TextTestProgressListener.cpp
-	../contrib/cppunit-1.12.1/src/cppunit/TextTestResult.cpp
-	../contrib/cppunit-1.12.1/src/cppunit/TextTestRunner.cpp
-	../contrib/cppunit-1.12.1/src/cppunit/TypeInfoHelper.cpp
-	../contrib/cppunit-1.12.1/src/cppunit/UnixDynamicLibraryManager.cpp
-	../contrib/cppunit-1.12.1/src/cppunit/Win32DynamicLibraryManager.cpp
-	../contrib/cppunit-1.12.1/src/cppunit/XmlDocument.cpp
-	../contrib/cppunit-1.12.1/src/cppunit/XmlElement.cpp
-	../contrib/cppunit-1.12.1/src/cppunit/XmlOutputter.cpp
-	../contrib/cppunit-1.12.1/src/cppunit/XmlOutputterHook.cpp
+	${TEST_SRCS}
+	${CPPUNIT_SRCS}
 )
 
-IF( WIN32 )
-	FIND_PACKAGE(DirectX REQUIRED)
-ENDIF( WIN32 )
 SET_PROPERTY(TARGET assimp PROPERTY DEBUG_POSTFIX ${ASSIMP_DEBUG_POSTFIX})
 
-# TODO: Port to non-Windows platforms.
-target_link_libraries ( unit assimp ${DirectX_LIBRARY} ${DirectX_D3DX9_LIBRARY} comctl32.lib Winmm.lib ) 
+target_link_libraries ( unit assimp ) 

+ 4 - 5
tools/assimp_view/MessageProc.cpp

@@ -1030,18 +1030,17 @@ void PopulateExportMenu()
 //-------------------------------------------------------------------------------
 void DoExport(size_t formatId)
 {
-	if (!g_szFileName) {
+	if (!g_szFileName[0]) {
+		MessageBox(g_hDlg, "No model loaded", "Export", MB_ICONERROR);
 		return;
 	}
-
 	Exporter exp;
 	const aiExportFormatDesc* const e = exp.GetExportFormatDescription(formatId);
 	ai_assert(e);
 
 	char szFileName[MAX_PATH*2];
-	DWORD dwTemp;
-	if(ERROR_SUCCESS == RegQueryValueEx(g_hRegistry,"ModelExportDest",NULL,NULL,(BYTE*)szFileName,&dwTemp)) {
-		ai_assert(dwTemp == MAX_PATH + 1);
+	DWORD dwTemp = sizeof(szFileName);
+	if(ERROR_SUCCESS == RegQueryValueEx(g_hRegistry,"ModelExportDest",NULL,NULL,(BYTE*)szFileName, &dwTemp)) {
 		ai_assert(strlen(szFileName) <= MAX_PATH);
 
 		// invent a nice default file name