Browse Source

Refactoring exporters & fixing light exporters (light exporting almost working)

Panagiotis Christopoulos Charitos 11 years ago
parent
commit
1196b8340c

+ 1 - 1
CMakeLists.txt

@@ -248,7 +248,7 @@ endif()
 #
 
 # AnKi compiler flags (Mainly warnings)
-set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pedantic -Wall -W -Wextra -Wwrite-strings -Wunused -Wunused-variable -Wno-unused-parameter -Wundef -std=c++11 ")
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pedantic -Wall -W -Wextra -Wstrict-aliasing -Wwrite-strings -Wunused -Wunused-variable -Wno-unused-parameter -Wundef -std=c++11 ")
 
 #
 # Add anki related dirs

+ 2 - 2
testapp/Main.cpp

@@ -527,7 +527,7 @@ Error initSubsystems(int argc, char* argv[])
 	config.set("is.sm.enabled", true);
 	config.set("is.sm.poissonEnabled", true);
 	config.set("is.sm.resolution", 1024);
-	config.set("pps.enabled", false);
+	config.set("pps.enabled", true);
 	config.set("pps.hdr.enabled", true);
 	config.set("pps.hdr.renderingQuality", 0.5);
 	config.set("pps.hdr.blurringDist", 1.0);
@@ -554,7 +554,7 @@ Error initSubsystems(int argc, char* argv[])
 	config.set("tilesXCount", 16);
 	config.set("tilesYCount", 16);
 
-	config.set("fullscreenDesktopResolution", false);
+	config.set("fullscreenDesktopResolution", true);
 	config.set("debugContext", false);
 
 	app = new App;

+ 0 - 111
tools/scene/Animation.cpp

@@ -1,111 +0,0 @@
-// Copyright (C) 2014, Panagiotis Christopoulos Charitos.
-// All rights reserved.
-// Code licensed under the BSD License.
-// http://www.anki3d.org/LICENSE
-
-#include "Common.h"
-
-//==============================================================================
-void exportAnimation(
-	const Exporter& exporter,
-	const aiAnimation& anim,
-	uint32_t index)
-{
-	// Get name
-	std::string name = anim.mName.C_Str();
-	if(name.size() == 0)
-	{
-		name = std::string("unnamed_") + std::to_string(index);
-	}
-
-	// Find if it's skeleton animation
-	/*bool isSkeletalAnimation = false;
-	for(uint32_t i = 0; i < scene.mNumMeshes; i++)
-	{
-		const aiMesh& mesh = *scene.mMeshes[i];
-		if(mesh.HasBones())
-		{
-
-		}
-	}*/
-
-	std::fstream file;
-	LOGI("Exporting animation %s\n", name.c_str());
-
-	file.open(exporter.outDir + name + ".ankianim", std::ios::out);
-
-	file << XML_HEADER << "\n";
-	file << "<animation>\n";
-
-	file << "\t<channels>\n";
-
-	for(uint32_t i = 0; i < anim.mNumChannels; i++)
-	{
-		const aiNodeAnim& nAnim = *anim.mChannels[i];
-
-		file << "\t\t<channel>\n";
-		
-		// Name
-		file << "\t\t\t<name>" << nAnim.mNodeName.C_Str() << "</name>\n";
-
-		// Positions
-		file << "\t\t\t<positionKeys>\n";
-		for(uint32_t j = 0; j < nAnim.mNumPositionKeys; j++)
-		{
-			const aiVectorKey& key = nAnim.mPositionKeys[j];
-
-			if(exporter.flipyz)
-			{
-				file << "\t\t\t\t<key><time>" << key.mTime << "</time>"
-					<< "<value>" << key.mValue[0] << " " 
-					<< key.mValue[2] << " " << -key.mValue[1] 
-					<< "</value></key>\n";
-			}
-			else
-			{
-				file << "\t\t\t\t<key><time>" << key.mTime << "</time>"
-					<< "<value>" << key.mValue[0] << " " 
-					<< key.mValue[1] << " " << key.mValue[2] 
-					<< "</value></key>\n";
-			}
-		}
-		file << "\t\t\t</positionKeys>\n";
-
-		// Rotations
-		file << "\t\t\t<rotationKeys>\n";
-		for(uint32_t j = 0; j < nAnim.mNumRotationKeys; j++)
-		{
-			const aiQuatKey& key = nAnim.mRotationKeys[j];
-
-			aiMatrix3x3 mat = 
-				toAnkiMatrix(key.mValue.GetMatrix(), exporter.flipyz);
-			aiQuaternion quat(mat);
-			//aiQuaternion quat(key.mValue);
-
-			file << "\t\t\t\t<key><time>" << key.mTime << "</time>"
-				<< "<value>" << quat.x << " " << quat.y 
-				<< " " << quat.z << " "
-				<< quat.w << "</value></key>\n";
-		}
-		file << "\t\t\t</rotationKeys>\n";
-
-		// Scale
-		file << "\t\t\t<scalingKeys>\n";
-		for(uint32_t j = 0; j < nAnim.mNumScalingKeys; j++)
-		{
-			const aiVectorKey& key = nAnim.mScalingKeys[j];
-
-			// Note: only uniform scale
-			file << "\t\t\t\t<key><time>" << key.mTime << "</time>"
-				<< "<value>" 
-				<< ((key.mValue[0] + key.mValue[1] + key.mValue[2]) / 3.0)
-				<< "</value></key>\n";
-		}
-		file << "\t\t\t</scalingKeys>\n";
-
-		file << "\t\t</channel>\n";
-	}
-
-	file << "\t</channels>\n";
-	file << "</animation>\n";
-}

+ 1 - 2
tools/scene/CMakeLists.txt

@@ -2,6 +2,5 @@ include_directories("../../thirdparty/assimp/include")
 
 add_definitions("-fexceptions")
 
-add_executable(ankisceneimp Main.cpp Model.cpp Animation.cpp Light.cpp 
-	Common.cpp Exporter.cpp)
+add_executable(ankisceneimp Main.cpp Common.cpp Exporter.cpp)
 target_link_libraries(ankisceneimp ankiassimp) 

+ 31 - 92
tools/scene/Common.cpp

@@ -4,9 +4,39 @@
 // http://www.anki3d.org/LICENSE
 
 #include "Common.h"
+#include <cstdarg>
+
+#define TERMINAL_COL_INFO "\033[0;32m"
+#define TERMINAL_COL_ERROR "\033[1;31m"
+#define TERMINAL_COL_WARNING "\033[0;33m"
+#define TERMINAL_COL_RESET "\033[0m"
 
 //==============================================================================
-const char* XML_HEADER = R"(<?xml version="1.0" encoding="UTF-8" ?>)";
+void log(const char* file, int line, unsigned type, const char* fmt, ...)
+{
+	char buffer[1024];
+	va_list args;
+	
+	va_start(args, fmt);
+	vsnprintf(buffer, sizeof(buffer), fmt, args);
+	va_end(args);
+
+	switch(type)
+	{
+	case 1:
+		fprintf(stdout, TERMINAL_COL_INFO "(%s:%4d) Info: %s\n" 
+			TERMINAL_COL_RESET, file, line, buffer);
+		break;
+	case 2:
+		fprintf(stderr, TERMINAL_COL_ERROR "(%s:%4d) Error: %s\n" 
+			TERMINAL_COL_RESET, file, line, buffer);
+		break;
+	case 3:
+		fprintf(stderr, TERMINAL_COL_WARNING "(%s:%4d) Warning: %s\n" 
+			TERMINAL_COL_RESET, file, line, buffer);
+		break;
+	};
+}
 
 //==============================================================================
 std::string replaceAllString(
@@ -48,94 +78,3 @@ std::string getFilename(const std::string& path)
 	return out;
 }
 
-//==============================================================================
-aiMatrix4x4 toAnkiMatrix(const aiMatrix4x4& in, bool flipyz)
-{
-	static const aiMatrix4x4 toLeftHanded(
-		1, 0, 0, 0, 
-		0, 0, 1, 0, 
-		0, -1, 0, 0, 
-		0, 0, 0, 1);
-
-	static const aiMatrix4x4 toLeftHandedInv(
-		1, 0, 0, 0, 
-		0, 0, -1, 0, 
-		0, 1, 0, 0, 
-		0, 0, 0, 1);
-
-	if(flipyz)
-	{
-		return toLeftHanded * in * toLeftHandedInv;
-	}
-	else
-	{
-		return in;
-	}
-}
-
-//==============================================================================
-aiMatrix3x3 toAnkiMatrix(const aiMatrix3x3& in, bool flipyz)
-{
-	static const aiMatrix3x3 toLeftHanded(
-		1, 0, 0,
-		0, 0, 1, 
-		0, -1, 0);
-
-	static const aiMatrix3x3 toLeftHandedInv(
-		1, 0, 0, 
-		0, 0, -1, 
-		0, 1, 0);
-
-	if(flipyz)
-	{
-		return toLeftHanded * in;
-	}
-	else
-	{
-		return in;
-	}
-}
-
-//==============================================================================
-void writeNodeTransform(const Exporter& exporter, std::ofstream& file, 
-	const std::string& node, const aiMatrix4x4& mat)
-{
-	aiMatrix4x4 m = toAnkiMatrix(mat, exporter.flipyz);
-
-	float pos[3];
-	pos[0] = m[0][3];
-	pos[1] = m[1][3];
-	pos[2] = m[2][3];
-
-	file << "pos = Vec4.new()\n";
-	file << "pos:setAll(" << pos[0] << ", " << pos[1] << ", " << pos[2] 
-		<< ", 0.0)\n";
-	file << node 
-		<< ":getSceneNodeBase():getMoveComponent():setLocalOrigin(pos)\n";
-
-	file << "rot = Mat3x4.new()\n";
-	file << "rot:setAll(";
-	for(unsigned j = 0; j < 3; j++)
-	{
-		for(unsigned i = 0; i < 4; i++)
-		{
-			if(i == 3)
-			{
-				file << "0";
-			}
-			else
-			{
-				file << m[j][i];
-			}
-
-			if(!(i == 3 && j == 2))
-			{
-				file << ", ";
-			}
-		}
-	}
-	file << ")\n";
-
-	file << node 
-		<< ":getSceneNodeBase():getMoveComponent():setLocalRotation(rot)\n";
-}

+ 13 - 115
tools/scene/Common.h

@@ -6,136 +6,34 @@
 #ifndef ANKI_TOOLS_SCENE_MISC_H
 #define ANKI_TOOLS_SCENE_MISC_H
 
-#include <assimp/Importer.hpp>
-#include <assimp/scene.h>
-#include <assimp/postprocess.h>
-#include <cstdint>
-#include <cstdlib>
 #include <string>
-#include <vector>
-#include <fstream>
-#include <unordered_map>
 
-//==============================================================================
-// Log and errors
+/// Write to log. Don't use it directly.
+void log(
+	const char* file, 
+	int line, 
+	unsigned type,
+	const char* fmt, 
+	...);
 
-#define STR(s) #s
-#define XSTR(s) STR(s)
-#define LOGI(...) \
-	printf("[I] (" __FILE__ ":" XSTR(__LINE__) ") " __VA_ARGS__)
+// Log and errors
+#define LOGI(...) log(__FILE__, __LINE__, 1, __VA_ARGS__)
 
 #define ERROR(...) \
 	do { \
-		fprintf(stderr, "[E] (" __FILE__ ":" XSTR(__LINE__) ") " __VA_ARGS__); \
+		log(__FILE__, __LINE__, 2, __VA_ARGS__); \
 		exit(1); \
 	} while(0)
 
-#define LOGW(...) \
-	fprintf(stderr, "[W] (" __FILE__ ":" XSTR(__LINE__) ") " __VA_ARGS__)
-
-/// A string
-extern const char* XML_HEADER;
-
-const uint32_t INVALID_INDEX = 0xFFFFFFFF;
-
-/// Thin mesh wrapper
-struct Model
-{
-	uint32_t meshIndex = INVALID_INDEX; ///< Mesh index in the scene
-	uint32_t mtlIndex = INVALID_INDEX;
-	bool instanced = false;
-};
-
-/// Scene node
-struct Node
-{
-	uint32_t modelIndex; ///< Index inside Exporter::models
-	std::vector<aiMatrix4x4> transforms;
-};
-
-const uint32_t MAX_BONES_PER_VERTEX = 4;
-
-/// Bone/weight info for a single vertex
-struct VertexWeight
-{
-	uint32_t boneIds[MAX_BONES_PER_VERTEX];
-	float weigths[MAX_BONES_PER_VERTEX];
-	uint32_t bonesCount;
-};
-
-/// Exporter class. This holds all the globals
-struct Exporter
-{
-	std::string inputFname;
-	std::string outDir;
-	std::string rpath;
-	std::string texrpath;
-	bool flipyz = false; ///< Handle blender's annoying coordinate system
-
-	const aiScene* scene = nullptr;
-	Assimp::Importer importer;
-
-	std::vector<Model> models;
-	std::vector<Node> nodes;
-};
+#define LOGW(...) log(__FILE__, __LINE__, 3, __VA_ARGS__)
 
 /// Replace all @a from substrings in @a str to @a to
-extern std::string replaceAllString(
+std::string replaceAllString(
 	const std::string& str, 
 	const std::string& from, 
 	const std::string& to);
 
 /// From a path return only the filename
-extern std::string getFilename(const std::string& path);
-
-/// Convert one 4x4 matrix to AnKi friendly matrix
-extern aiMatrix4x4 toAnkiMatrix(const aiMatrix4x4& in, bool flipyz);
-
-/// Convert one 3x3 matrix to AnKi friendly matrix
-extern aiMatrix3x3 toAnkiMatrix(const aiMatrix3x3& in, bool flipyz);
-
-/// Write transformation of a node
-void writeNodeTransform(
-	const Exporter& exporter, 
-	std::ofstream& file, 
-	const std::string& node, 
-	const aiMatrix4x4& mat);
-
-/// Export animation
-extern void exportAnimation(
-	const Exporter& exporter,
-	const aiAnimation& anim,
-	uint32_t index);
-
-/// Export a mesh
-extern void exportMesh(
-	const Exporter& exporter,
-	const aiMesh& mesh, 
-	const aiMatrix4x4* transform);
-
-/// Export a skeleton
-extern void exportSkeleton(
-	const Exporter& exporter, 
-	const aiMesh& mesh);
-
-/// Export material
-extern void exportMaterial(
-	const Exporter& exporter, 
-	const aiMaterial& mtl, 
-	bool instanced);
-
-/// Export model
-void exportModel(
-	const Exporter& exporter, 
-	const Model& model);
-
-/// Get model name
-std::string getModelName(const Exporter& exporter, const Model& model);
-
-/// Write light to the scene file
-extern void exportLight(
-	const Exporter& exporter,
-	const aiLight& light, 
-	std::ofstream& file);
+std::string getFilename(const std::string& path);
 
 #endif

+ 1095 - 0
tools/scene/Exporter.cpp

@@ -5,4 +5,1099 @@
 
 #include "Exporter.h"
 
+//==============================================================================
+// Statics                                                                     =
+//==============================================================================
 
+static const char* XML_HEADER = R"(<?xml version="1.0" encoding="UTF-8" ?>)";
+
+//==============================================================================
+static std::string getMaterialName(const aiMaterial& mtl, bool instanced)
+{
+	aiString ainame;
+	std::string name;
+	if(mtl.Get(AI_MATKEY_NAME, ainame) == AI_SUCCESS)
+	{
+		name = ainame.C_Str();
+
+		if(instanced)
+		{
+			name += "_inst";
+		}
+	}
+	else
+	{
+		ERROR("Material's name is missing");
+	}
+
+	return name;
+}
+
+//==============================================================================
+static std::string getMeshName(const aiMesh& mesh)
+{
+	return std::string(mesh.mName.C_Str());
+}
+
+//==============================================================================
+/// Walk the node hierarchy and find the node.
+static const aiNode* findNodeWithName(
+	const std::string& name, 
+	const aiNode* node)
+{
+	if(node == nullptr || node->mName.C_Str() == name)
+	{
+		return node;
+	}
+
+	const aiNode* out = nullptr;
+
+	// Go to children
+	for(uint32_t i = 0; i < node->mNumChildren; i++)
+	{
+		out = findNodeWithName(name, node->mChildren[i]);
+		if(out)
+		{
+			break;
+		}
+	}
+
+	return out;
+}
+
+//==============================================================================
+// Exporter                                                                    =
+//==============================================================================
+
+//==============================================================================
+aiMatrix4x4 Exporter::toAnkiMatrix(const aiMatrix4x4& in) const
+{
+	static const aiMatrix4x4 toLeftHanded(
+		1, 0, 0, 0, 
+		0, 0, 1, 0, 
+		0, -1, 0, 0, 
+		0, 0, 0, 1);
+
+	static const aiMatrix4x4 toLeftHandedInv(
+		1, 0, 0, 0, 
+		0, 0, -1, 0, 
+		0, 1, 0, 0, 
+		0, 0, 0, 1);
+
+	if(m_flipyz)
+	{
+		return toLeftHanded * in * toLeftHandedInv;
+	}
+	else
+	{
+		return in;
+	}
+}
+
+//==============================================================================
+aiMatrix3x3 Exporter::toAnkiMatrix(const aiMatrix3x3& in) const
+{
+	static const aiMatrix3x3 toLeftHanded(
+		1, 0, 0,
+		0, 0, 1, 
+		0, -1, 0);
+
+	static const aiMatrix3x3 toLeftHandedInv(
+		1, 0, 0, 
+		0, 0, -1, 
+		0, 1, 0);
+
+	if(m_flipyz)
+	{
+		return toLeftHanded * in;
+	}
+	else
+	{
+		return in;
+	}
+}
+
+//==============================================================================
+void Exporter::writeNodeTransform(
+	const std::string& node, 
+	const aiMatrix4x4& mat)
+{
+	std::ofstream& file = m_sceneFile;
+
+	aiMatrix4x4 m = toAnkiMatrix(mat);
+
+	float pos[3];
+	pos[0] = m[0][3];
+	pos[1] = m[1][3];
+	pos[2] = m[2][3];
+
+	file << "pos = Vec4.new()\n";
+	file << "pos:setAll(" << pos[0] << ", " << pos[1] << ", " << pos[2] 
+		<< ", 0)\n";
+	file << node 
+		<< ":getSceneNodeBase():getMoveComponent():setLocalOrigin(pos)\n";
+
+	file << "rot = Mat3x4.new()\n";
+	file << "rot:setAll(";
+	for(unsigned j = 0; j < 3; j++)
+	{
+		for(unsigned i = 0; i < 4; i++)
+		{
+			if(i == 3)
+			{
+				file << "0";
+			}
+			else
+			{
+				file << m[j][i];
+			}
+
+			if(!(i == 3 && j == 2))
+			{
+				file << ", ";
+			}
+		}
+	}
+	file << ")\n";
+
+	file << node 
+		<< ":getSceneNodeBase():getMoveComponent():setLocalRotation(rot)\n";
+}
+
+//==============================================================================
+const aiMesh& Exporter::getMeshAt(unsigned index) const
+{
+	assert(index < m_scene->mNumMeshes);
+	return *m_scene->mMeshes[index];
+}
+
+//==============================================================================
+const aiMaterial& Exporter::getMaterialAt(unsigned index) const
+{
+	assert(index < m_scene->mNumMaterials);
+	return *m_scene->mMaterials[index];
+}
+
+//==============================================================================
+std::string Exporter::getModelName(const Model& model) const
+{
+	std::string mtlName = getMaterialName(
+		getMaterialAt(model.m_materialIndex), model.m_instanced);
+
+	std::string name = 
+		getMeshName(getMeshAt(model.m_meshIndex)) + "_" + mtlName;
+
+	return name;
+}
+
+//==============================================================================
+void Exporter::exportMesh(
+	const aiMesh& mesh, 
+	const aiMatrix4x4* transform) const
+{
+	std::string name = getMeshName(mesh);
+	std::fstream file;
+	LOGI("Exporting mesh %s", name.c_str());
+
+	uint32_t vertsCount = mesh.mNumVertices;
+
+	// Open file
+	file.open(m_outputDirectory + name + ".ankimesh",
+		std::ios::out | std::ios::binary);
+
+	// Write magic word
+	file.write("ANKIMESH", 8);
+
+	// Write the name
+	uint32_t size = name.size();
+	file.write((char*)&size, sizeof(uint32_t));
+	file.write(&name[0], size);
+
+	// Write positions
+	file.write((char*)&vertsCount, sizeof(uint32_t));
+	for(uint32_t i = 0; i < mesh.mNumVertices; i++)
+	{
+		aiVector3D pos = mesh.mVertices[i];
+
+		// Transform
+		if(transform)
+		{
+			pos = (*transform) * pos;
+		}
+
+		// flip 
+		if(m_flipyz)
+		{
+			static const aiMatrix4x4 toLefthanded(
+				1, 0, 0, 0, 
+				0, 0, 1, 0, 
+				0, -1, 0, 0, 
+				0, 0, 0, 1);
+
+			pos = toLefthanded * pos;
+		}
+
+		for(uint32_t j = 0; j < 3; j++)
+		{
+			file.write((char*)&pos[j], sizeof(float));
+		}
+	}
+
+	// Write the indices
+	file.write((char*)&mesh.mNumFaces, sizeof(uint32_t));
+	for(uint32_t i = 0; i < mesh.mNumFaces; i++)
+	{
+		const aiFace& face = mesh.mFaces[i];
+		
+		if(face.mNumIndices != 3)
+		{
+			ERROR("For some reason the assimp didn't triangulate");
+		}
+
+		for(uint32_t j = 0; j < 3; j++)
+		{
+			uint32_t index = face.mIndices[j];
+			file.write((char*)&index, sizeof(uint32_t));
+		}
+	}
+
+	// Write the tex coords
+	file.write((char*)&vertsCount, sizeof(uint32_t));
+
+	// For all channels
+	for(uint32_t ch = 0; ch < mesh.GetNumUVChannels(); ch++)
+	{
+		if(mesh.mNumUVComponents[ch] != 2)
+		{
+			ERROR("Incorrect number of UV components");
+		}
+
+		// For all tex coords of this channel
+		for(uint32_t i = 0; i < vertsCount; i++)
+		{
+			aiVector3D texCoord = mesh.mTextureCoords[ch][i];
+
+			for(uint32_t j = 0; j < 2; j++)
+			{
+				file.write((char*)&texCoord[j], sizeof(float));
+			}
+		}
+	}
+
+	// Write bone weigths count
+	if(mesh.HasBones())
+	{
+#if 0
+		// Write file
+		file.write((char*)&vertsCount, sizeof(uint32_t));
+
+		// Gather info for each vertex
+		std::vector<Vw> vw;
+		vw.resize(vertsCount);
+		memset(&vw[0], 0, sizeof(Vw) * vertsCount);
+
+		// For all bones
+		for(uint32_t i = 0; i < mesh.mNumBones; i++)
+		{
+			const aiBone& bone = *mesh.mBones[i];
+
+			// for every weights of the bone
+			for(uint32_t j = 0; j < bone.mWeightsCount; j++)
+			{
+				const aiVertexWeight& weigth = bone.mWeights[j];
+
+				// Sanity check
+				if(weight.mVertexId >= vertCount)
+				{
+					ERROR("Out of bounds vert ID");
+				}
+
+				Vm& a = vm[weight.mVertexId];
+
+				// Check out of bounds
+				if(a.bonesCount >= MAX_BONES_PER_VERTEX)
+				{
+					LOGW("Too many bones for vertex %d", weigth.mVertexId);
+					continue;
+				}
+
+				// Write to vertex
+				a.boneIds[a.bonesCount] = i;
+				a.weigths[a.bonesCount] = weigth.mWeigth;
+				++a.bonesCount;
+			}
+
+			// Now write the file
+		}
+#endif
+	}
+	else
+	{
+		uint32_t num = 0;
+		file.write((char*)&num, sizeof(uint32_t));
+	}
+}
+
+//==============================================================================
+void Exporter::exportSkeleton(const aiMesh& mesh) const
+{
+	assert(mesh.HasBones());
+	std::string name = mesh.mName.C_Str();
+	std::fstream file;
+	LOGI("Exporting skeleton %s", name.c_str());
+
+	// Open file
+	file.open(m_outputDirectory + name + ".skel", std::ios::out);
+	
+	file << XML_HEADER << "\n";
+	file << "<skeleton>\n";
+	file << "\t<bones>\n";
+
+	bool rootBoneFound = false;
+
+	for(uint32_t i = 0; i < mesh.mNumBones; i++)
+	{
+		const aiBone& bone = *mesh.mBones[i];
+
+		file << "\t\t<bone>\n";
+
+		// <name>
+		file << "\t\t\t<name>" << bone.mName.C_Str() << "</name>\n";
+
+		if(strcmp(bone.mName.C_Str(), "root") == 0)
+		{
+			rootBoneFound = true;
+		}
+
+		// <transform>
+		file << "\t\t\t<transform>";
+		for(uint32_t j = 0; j < 16; j++)
+		{
+			file << bone.mOffsetMatrix[j] << " ";
+		}
+		file << "</transform>\n";
+
+		file << "\t\t</bone>\n";
+	}
+
+	if(!rootBoneFound)
+	{
+		ERROR("There should be one bone named \"root\"");
+	}
+
+	file << "\t</bones>\n";
+	file << "</skeleton>\n";
+}
+
+//==============================================================================
+void Exporter::exportMaterial(
+	const aiMaterial& mtl, 
+	bool instanced) const
+{
+	std::string diffTex;
+	std::string normTex;
+	std::string specColTex;
+	std::string shininessTex;
+	std::string dispTex;
+
+	aiString path;
+
+	std::string name = getMaterialName(mtl, instanced);
+	LOGI("Exporting material %s", name.c_str());
+
+	// Diffuse texture
+	if(mtl.GetTextureCount(aiTextureType_DIFFUSE) > 0)
+	{
+		if(mtl.GetTexture(aiTextureType_DIFFUSE, 0, &path) == AI_SUCCESS)
+		{
+			diffTex = getFilename(path.C_Str());
+		}
+		else
+		{
+			ERROR("Failed to retrieve texture");
+		}
+	}
+
+	// Normal texture
+	if(mtl.GetTextureCount(aiTextureType_NORMALS) > 0)
+	{	
+		if(mtl.GetTexture(aiTextureType_NORMALS, 0, &path) == AI_SUCCESS)
+		{
+			normTex = getFilename(path.C_Str());
+		}
+		else
+		{
+			ERROR("Failed to retrieve texture");
+		}
+	}
+
+	// Specular color
+	if(mtl.GetTextureCount(aiTextureType_SPECULAR) > 0)
+	{
+		if(mtl.GetTexture(aiTextureType_SPECULAR, 0, &path) == AI_SUCCESS)
+		{
+			specColTex = getFilename(path.C_Str());
+		}
+		else
+		{
+			ERROR("Failed to retrieve texture");
+		}
+	}
+
+	// Shininess color
+	if(mtl.GetTextureCount(aiTextureType_SHININESS) > 0)
+	{
+		if(mtl.GetTexture(aiTextureType_SHININESS, 0, &path) == AI_SUCCESS)
+		{
+			shininessTex = getFilename(path.C_Str());
+		}
+		else
+		{
+			ERROR("Failed to retrieve texture");
+		}
+	}
+
+	// Height texture
+	if(mtl.GetTextureCount(aiTextureType_DISPLACEMENT) > 0)
+	{	
+		if(mtl.GetTexture(aiTextureType_DISPLACEMENT, 0, &path) == AI_SUCCESS)
+		{
+			dispTex = getFilename(path.C_Str());
+		}
+		else
+		{
+			ERROR("Failed to retrieve texture");
+		}
+	}
+
+	// Write file
+	static const char* diffNormSpecFragTemplate = 
+#include "templates/diffNormSpecFrag.h"
+		;
+	static const char* simpleVertTemplate = 
+#include "templates/simpleVert.h"
+		;
+	static const char* tessVertTemplate = 
+#include "templates/tessVert.h"
+		;
+
+	static const char* readRgbFromTextureTemplate = R"(
+				<operation>
+					<id>%id%</id>
+					<returnType>vec3</returnType>
+					<function>readRgbFromTexture</function>
+					<arguments>
+						<argument>%map%</argument>
+						<argument>out2</argument>
+					</arguments>
+				</operation>)";
+
+	static const char* readRFromTextureTemplate = R"(
+				<operation>
+					<id>%id%</id>
+					<returnType>float</returnType>
+					<function>readRFromTexture</function>
+					<arguments>
+						<argument>%map%</argument>
+						<argument>out2</argument>
+					</arguments>
+				</operation>)";
+
+	// Compose full template
+	// First geometry part
+	std::string materialStr;
+	materialStr = R"(<?xml version="1.0" encoding="UTF-8" ?>)";
+	materialStr += "\n<material>\n\t<programs>\n";
+	if(dispTex.empty())
+	{
+		materialStr += simpleVertTemplate;
+	}
+	else
+	{
+		materialStr += tessVertTemplate;
+	}
+
+	materialStr += "\n";
+
+	// Then fragment part
+	materialStr += diffNormSpecFragTemplate;
+	materialStr += "\n\t</programs>\t</material>";
+
+	// Replace strings
+	if(!dispTex.empty())
+	{
+		materialStr = replaceAllString(materialStr, "%dispMap%", 
+			m_texrpath + dispTex);
+	}
+
+	// Diffuse
+	if(!diffTex.empty())
+	{
+		materialStr = replaceAllString(materialStr, "%diffuseColorInput%", 
+			R"(<input><type>sampler2D</type><name>uDiffuseColor</name><value>)"
+			+ m_texrpath + diffTex
+			+ R"(</value></input>)");
+
+		materialStr = replaceAllString(materialStr, "%diffuseColorFunc%", 
+			readRgbFromTextureTemplate);
+
+		materialStr = replaceAllString(materialStr, "%id%", 
+			"10");
+
+		materialStr = replaceAllString(materialStr, "%map%", 
+			"uDiffuseColor");
+
+		materialStr = replaceAllString(materialStr, "%diffuseColorArg%", 
+			"out10");
+	}
+	else
+	{
+		aiColor3D diffCol = {0.0, 0.0, 0.0};
+		mtl.Get(AI_MATKEY_COLOR_DIFFUSE, diffCol);
+
+		materialStr = replaceAllString(materialStr, "%diffuseColorInput%", 
+			R"(<input><type>vec3</type><name>uDiffuseColor</name><value>)"
+			+ std::to_string(diffCol[0]) + " "
+			+ std::to_string(diffCol[1]) + " "
+			+ std::to_string(diffCol[2])
+			+ R"(</value></input>)");
+
+		materialStr = replaceAllString(materialStr, "%diffuseColorFunc%", 
+			"");
+
+		materialStr = replaceAllString(materialStr, "%diffuseColorArg%", 
+			"uDiffuseColor");
+	}
+
+	// Normal
+	if(!normTex.empty())
+	{
+		materialStr = replaceAllString(materialStr, "%normalInput%", 
+			R"(<input><type>sampler2D</type><name>uNormal</name><value>)"
+			+ m_texrpath + normTex
+			+ R"(</value></input>)");
+
+		materialStr = replaceAllString(materialStr, "%normalFunc%", 
+				R"(
+				<operation>
+					<id>20</id>
+					<returnType>vec3</returnType>
+					<function>readNormalFromTexture</function>
+					<arguments>
+						<argument>out0</argument>
+						<argument>out1</argument>
+						<argument>uNormal</argument>
+						<argument>out2</argument>
+					</arguments>
+				</operation>)");
+
+		materialStr = replaceAllString(materialStr, "%normalArg%", 
+			"out20");
+	}
+	else
+	{
+		materialStr = replaceAllString(materialStr, "%normalInput%", " ");
+
+		materialStr = replaceAllString(materialStr, "%normalFunc%", " ");
+
+		materialStr = replaceAllString(materialStr, "%normalArg%", "out0");
+	}
+
+	// Specular
+	if(!specColTex.empty())
+	{
+		materialStr = replaceAllString(materialStr, "%specularColorInput%", 
+			R"(<input><type>sampler2D</type><name>uSpecularColor</name><value>)"
+			+ m_texrpath + specColTex
+			+ R"(</value></input>)");
+
+		materialStr = replaceAllString(materialStr, "%specularColorFunc%", 
+			readRFromTextureTemplate);
+
+		materialStr = replaceAllString(materialStr, "%id%", 
+			"50");
+
+		materialStr = replaceAllString(materialStr, "%map%", 
+			"uSpecularColor");
+
+		materialStr = replaceAllString(materialStr, "%specularColorArg%", 
+			"out50");
+	}
+	else
+	{
+		aiColor3D specCol = {0.0, 0.0, 0.0};
+		mtl.Get(AI_MATKEY_COLOR_SPECULAR, specCol);
+
+		materialStr = replaceAllString(materialStr, "%specularColorInput%", 
+			R"(<input><type>float</type><name>uSpecularColor</name><value>)"
+			+ std::to_string((specCol[0] + specCol[1] + specCol[2]) / 3.0)
+			+ R"(</value></input>)");
+
+		materialStr = replaceAllString(materialStr, "%specularColorFunc%", 
+			"");
+
+		materialStr = replaceAllString(materialStr, "%specularColorArg%", 
+			"uSpecularColor");
+	}
+
+	if(!shininessTex.empty())
+	{
+		materialStr = replaceAllString(materialStr, "%specularPowerInput%", 
+			R"(<input><type>sampler2D</type><name>uSpecularPower</name><value>)"
+			+ m_texrpath + shininessTex
+			+ R"(</value></input>)");
+
+		materialStr = replaceAllString(materialStr, "%specularPowerValue%", 
+			m_texrpath + shininessTex);
+
+		materialStr = replaceAllString(materialStr, "%specularPowerFunc%", 
+			readRFromTextureTemplate);
+
+		materialStr = replaceAllString(materialStr, "%id%", 
+			"60");
+
+		materialStr = replaceAllString(materialStr, "%map%", 
+			"uSpecularPower");
+
+		materialStr = replaceAllString(materialStr, "%specularPowerArg%", 
+			"out60");
+	}
+	else
+	{
+		float shininess = 0.0;
+		mtl.Get(AI_MATKEY_SHININESS, shininess);
+		//shininess = std::min(128.0f, shininess)  128.0;
+		const int MAX_SHININESS = 511.0;
+		if(shininess > MAX_SHININESS)
+		{
+			LOGW("Shininness exceeds %d", MAX_SHININESS);
+		}
+
+		shininess = shininess / MAX_SHININESS;
+
+		materialStr = replaceAllString(materialStr, "%specularPowerInput%", 
+			R"(<input><type>float</type><name>uSpecularPower</name><value>)"
+			+ std::to_string(shininess)
+			+ R"(</value></input>)");
+
+		materialStr = replaceAllString(materialStr, "%specularPowerFunc%", 
+			"");
+
+		materialStr = replaceAllString(materialStr, "%specularPowerArg%", 
+			"uSpecularPower");
+	}
+
+	materialStr = replaceAllString(materialStr, "%maxSpecularPower%", " ");
+
+	materialStr = replaceAllString(materialStr, "%instanced%", 
+		(instanced) ? "1" : "0");
+	materialStr = replaceAllString(materialStr, "%diffuseMap%", 
+		m_texrpath + diffTex);
+
+	// Replace texture extensions with .anki
+	materialStr = replaceAllString(materialStr, ".tga", ".ankitex");
+	materialStr = replaceAllString(materialStr, ".png", ".ankitex");
+	materialStr = replaceAllString(materialStr, ".jpg", ".ankitex");
+	materialStr = replaceAllString(materialStr, ".jpeg", ".ankitex");
+
+	// Open and write file
+	std::fstream file;
+	file.open(m_outputDirectory + name + ".ankimtl", std::ios::out);
+	file << materialStr;
+}
+
+//==============================================================================
+void Exporter::exportModel(const Model& model) const
+{
+	std::string name = getModelName(model);
+	LOGI("Exporting model %s", name.c_str());
+
+	std::fstream file;
+	file.open(m_outputDirectory + name + ".ankimdl", std::ios::out);
+
+	file << XML_HEADER << '\n';
+	file << "<model>\n";
+	file << "\t<modelPatches>\n";
+	
+	// start
+	file << "\t\t<modelPatch>\n";
+
+	// Write mesh
+	file << "\t\t\t<mesh>" << m_rpath 
+		<< getMeshName(getMeshAt(model.m_meshIndex)) 
+		<< ".ankimesh</mesh>\n";
+
+	// Write material
+	file << "\t\t\t<material>" << m_rpath 
+		<< getMaterialName(getMaterialAt(model.m_materialIndex), 
+			model.m_instanced) 
+		<< ".ankimtl</material>\n";
+
+	// end
+	file << "\t\t</modelPatch>\n";
+
+	file << "\t</modelPatches>\n";
+	file << "</model>\n";
+}
+
+//==============================================================================
+void Exporter::exportLight(const aiLight& light)
+{
+	std::ofstream& file = m_sceneFile;
+
+	LOGI("Exporting light %s", light.mName.C_Str());
+
+	if(light.mType != aiLightSource_POINT && light.mType != aiLightSource_SPOT)
+	{
+		LOGW("Skipping light %s. Unsupported type (0x%x)", 
+			light.mName.C_Str(), light.mType);
+		return;
+	}
+
+	if(light.mAttenuationLinear != 0.0)
+	{
+		LOGW("Skipping light %s. Linear attenuation is not 0.0", 
+			light.mName.C_Str());
+		return;
+	}
+
+	file << "\nnode = scene:new" 
+		<< ((light.mType == aiLightSource_POINT) ? "Point" : "Spot") 
+		<< "Light(\"" << light.mName.C_Str() << "\")\n";
+	
+	file << "lcomp = node:getSceneNodeBase():getLightComponent()\n";
+
+	// Colors
+	file << "col = Vec4.new()\n"
+		<< "col:setAll("
+		<< light.mColorDiffuse[0] << ", " 
+		<< light.mColorDiffuse[1] << ", " 
+		<< light.mColorDiffuse[2] << ", " 
+		<< "1)\n"
+		<< "lcomp:setDiffuseColor(col)\n" ;
+
+	file << "col = Vec4.new()\n"
+		<< "col:setAll("
+		<< light.mColorSpecular[0] << ", " 
+		<< light.mColorSpecular[1] << ", " 
+		<< light.mColorSpecular[2] << ", " 
+		<< "1)\n"
+		<< "lcomp:setSpecularColor(col)\n" ;
+
+	// Geometry
+	aiVector3D direction(0.0, 0.0, 1.0);
+
+	switch(light.mType)
+	{
+	case aiLightSource_POINT:
+		{
+			// At this point I want the radius and have the attenuation factors
+			// att = Ac + Al*d + Aq*d^2. When d = r then att = 0.0. Also if we 
+			// assume that Al is 0 then:
+			// 0 = Ac + Aq*r^2. Solving by r is easy
+			float r = 
+				sqrt(light.mAttenuationConstant / light.mAttenuationQuadratic);
+			file << "lcomp:setRadius(" << r << ")\n";
+		}
+		break;
+	case aiLightSource_SPOT:
+		{
+			float dist = 
+				sqrt(light.mAttenuationConstant / light.mAttenuationQuadratic);
+
+			float outer = light.mAngleOuterCone;
+			float inner = light.mAngleInnerCone;
+			if(outer == inner)
+			{
+				inner = outer / 2.0;
+			}
+
+			file << "lcomp:setInnerAngle(" << inner << ")\n"
+				<< "lcomp:setOuterAngle(" << outer << ")\n"
+				<< "lcomp:setDistance(" << dist << ")\n";
+
+			direction = light.mDirection;
+			break;
+		}
+	default:
+		assert(0);
+		break;
+	}
+
+	// Transform
+	const aiNode* node = 
+		findNodeWithName(light.mName.C_Str(), m_scene->mRootNode);
+	
+	if(node == nullptr)
+	{
+		ERROR("Couldn't find node for light %s", light.mName.C_Str());
+	}
+
+	writeNodeTransform("node", node->mTransformation);
+}
+
+//==============================================================================
+void Exporter::exportAnimation(
+	const aiAnimation& anim,
+	unsigned index)
+{
+	// Get name
+	std::string name = anim.mName.C_Str();
+	if(name.size() == 0)
+	{
+		name = std::string("unnamed_") + std::to_string(index);
+	}
+
+	// Find if it's skeleton animation
+	/*bool isSkeletalAnimation = false;
+	for(uint32_t i = 0; i < scene.mNumMeshes; i++)
+	{
+		const aiMesh& mesh = *scene.mMeshes[i];
+		if(mesh.HasBones())
+		{
+
+		}
+	}*/
+
+	std::fstream file;
+	LOGI("Exporting animation %s", name.c_str());
+
+	file.open(m_outputDirectory + name + ".ankianim", std::ios::out);
+
+	file << XML_HEADER << "\n";
+	file << "<animation>\n";
+
+	file << "\t<channels>\n";
+
+	for(uint32_t i = 0; i < anim.mNumChannels; i++)
+	{
+		const aiNodeAnim& nAnim = *anim.mChannels[i];
+
+		file << "\t\t<channel>\n";
+		
+		// Name
+		file << "\t\t\t<name>" << nAnim.mNodeName.C_Str() << "</name>\n";
+
+		// Positions
+		file << "\t\t\t<positionKeys>\n";
+		for(uint32_t j = 0; j < nAnim.mNumPositionKeys; j++)
+		{
+			const aiVectorKey& key = nAnim.mPositionKeys[j];
+
+			if(m_flipyz)
+			{
+				file << "\t\t\t\t<key><time>" << key.mTime << "</time>"
+					<< "<value>" << key.mValue[0] << " " 
+					<< key.mValue[2] << " " << -key.mValue[1] 
+					<< "</value></key>\n";
+			}
+			else
+			{
+				file << "\t\t\t\t<key><time>" << key.mTime << "</time>"
+					<< "<value>" << key.mValue[0] << " " 
+					<< key.mValue[1] << " " << key.mValue[2] 
+					<< "</value></key>\n";
+			}
+		}
+		file << "\t\t\t</positionKeys>\n";
+
+		// Rotations
+		file << "\t\t\t<rotationKeys>\n";
+		for(uint32_t j = 0; j < nAnim.mNumRotationKeys; j++)
+		{
+			const aiQuatKey& key = nAnim.mRotationKeys[j];
+
+			aiMatrix3x3 mat = toAnkiMatrix(key.mValue.GetMatrix());
+			aiQuaternion quat(mat);
+			//aiQuaternion quat(key.mValue);
+
+			file << "\t\t\t\t<key><time>" << key.mTime << "</time>"
+				<< "<value>" << quat.x << " " << quat.y 
+				<< " " << quat.z << " "
+				<< quat.w << "</value></key>\n";
+		}
+		file << "\t\t\t</rotationKeys>\n";
+
+		// Scale
+		file << "\t\t\t<scalingKeys>\n";
+		for(uint32_t j = 0; j < nAnim.mNumScalingKeys; j++)
+		{
+			const aiVectorKey& key = nAnim.mScalingKeys[j];
+
+			// Note: only uniform scale
+			file << "\t\t\t\t<key><time>" << key.mTime << "</time>"
+				<< "<value>" 
+				<< ((key.mValue[0] + key.mValue[1] + key.mValue[2]) / 3.0)
+				<< "</value></key>\n";
+		}
+		file << "\t\t\t</scalingKeys>\n";
+
+		file << "\t\t</channel>\n";
+	}
+
+	file << "\t</channels>\n";
+	file << "</animation>\n";
+}
+
+//==============================================================================
+void Exporter::load()
+{
+	LOGI("Loading file %s", &m_inputFilename[0]);
+
+	const aiScene* scene = m_importer.ReadFile(m_inputFilename, 0
+		//| aiProcess_FindInstances
+		| aiProcess_Triangulate
+		| aiProcess_JoinIdenticalVertices
+		//| aiProcess_SortByPType
+		| aiProcess_ImproveCacheLocality
+		| aiProcess_OptimizeMeshes
+		| aiProcess_RemoveRedundantMaterials
+		);
+
+	if(!scene)
+	{
+		ERROR("%s", m_importer.GetErrorString());
+	}
+
+	m_scene = scene;
+}
+
+//==============================================================================
+void Exporter::visitNode(const aiNode* ainode)
+{
+	if(ainode == nullptr)
+	{
+		return;
+	}
+
+	// For every mesh of this node
+	for(unsigned i = 0; i < ainode->mNumMeshes; i++)
+	{
+		unsigned meshIndex = ainode->mMeshes[i];
+		unsigned mtlIndex =  m_scene->mMeshes[meshIndex]->mMaterialIndex;
+
+		// Find if there is another node with the same mesh-material pair
+		std::vector<Node>::iterator it;
+		for(it = m_nodes.begin(); it != m_nodes.end(); ++it)
+		{
+			const Node& node = *it;
+			const Model& model = m_models[node.m_modelIndex];
+
+			if(model.m_meshIndex == meshIndex 
+				&& model.m_materialIndex == mtlIndex)
+			{
+				break;
+			}
+		}
+
+		if(it != m_nodes.end())
+		{
+			// A node with the same model exists. It's instanced
+
+			Node& node = *it;
+			Model& model = m_models[node.m_modelIndex];
+
+			assert(node.m_transforms.size() > 0);
+			node.m_transforms.push_back(ainode->mTransformation);
+			
+			model.m_instanced = true;
+			break;
+		}
+
+		// Create new model
+		Model mdl;
+		mdl.m_meshIndex = meshIndex;
+		mdl.m_materialIndex = mtlIndex;
+		m_models.push_back(mdl);
+
+		// Create new node
+		Node node;
+		node.m_modelIndex = m_models.size() - 1;
+		node.m_transforms.push_back(ainode->mTransformation);
+		m_nodes.push_back(node);
+	}
+
+	// Go to children
+	for(uint32_t i = 0; i < ainode->mNumChildren; i++)
+	{
+		visitNode(ainode->mChildren[i]);
+	}
+}
+
+//==============================================================================
+void Exporter::exportAll()
+{
+	LOGI("Exporting scene to %s", &m_outputDirectory[0]);
+
+	//
+	// Open scene file
+	//
+	m_sceneFile.open(m_outputDirectory + "scene.lua");
+	std::ofstream& file = m_sceneFile;
+
+	file << "local scene = getSceneGraph()\n"
+		<< "local pos\n"
+		<< "local rot\n"
+		<< "local node\n"
+		<< "local inst\n"
+		<< "local lcomp\n"
+		<< "local col\n";
+
+	//
+	// Get all node/model data
+	//
+	visitNode(m_scene->mRootNode);
+
+	//
+	// Export nodes and models.
+	//
+	for(uint32_t i = 0; i < m_nodes.size(); i++)
+	{
+		Node& node = m_nodes[i];
+		Model& model = m_models[node.m_modelIndex];
+
+		// TODO If not instanced bake transform
+		exportMesh(*m_scene->mMeshes[model.m_meshIndex], nullptr);
+
+		exportMaterial(*m_scene->mMaterials[model.m_materialIndex], 
+			model.m_instanced);
+
+		exportModel(model);
+		std::string name = getModelName(model);
+
+		// Write the main node
+		file << "\nnode = scene:newModelNode(\"" 
+			<< name << "\", \"" 
+			<< m_rpath << name << ".ankimdl" << "\")\n"; 
+		writeNodeTransform("node", node.m_transforms[0]);
+
+		// Write instance nodes
+		for(unsigned j = 1; j < node.m_transforms.size(); j++)
+		{
+			file << "inst = scene:newInstanceNode(\"" 
+				<< name << "_inst" << (j - 1) << "\")\n"
+				<< "node:getSceneNodeBase():addChild("
+				<< "inst:getSceneNodeBase())\n";
+
+			writeNodeTransform("inst", node.m_transforms[j]);
+		}
+	}
+
+	//
+	// Lights
+	//
+	for(unsigned i = 0; i < m_scene->mNumLights; i++)
+	{
+		exportLight(*m_scene->mLights[i]);
+	}
+
+	//
+	// Animations
+	//
+	for(unsigned i = 0; i < m_scene->mNumAnimations; i++)
+	{
+		exportAnimation(*m_scene->mAnimations[i], i);
+	}
+
+	LOGI("Done exporting scene!");
+}

+ 39 - 1
tools/scene/Exporter.h

@@ -16,6 +16,8 @@
 #include <assimp/scene.h>
 #include <assimp/postprocess.h>
 
+#include "Common.h"
+
 const uint32_t INVALID_INDEX = 0xFFFFFFFF;
 
 /// Thin mesh wrapper
@@ -69,6 +71,9 @@ public:
 	void exportAll();
 
 private:
+	/// @name Helpers
+	/// @{
+
 	/// Convert one 4x4 matrix to AnKi friendly matrix.
 	aiMatrix4x4 toAnkiMatrix(const aiMatrix4x4& in) const;
 
@@ -78,7 +83,40 @@ private:
 	/// Write transformation of a node
 	void writeNodeTransform(
 		const std::string& node, 
-		const aiMatrix4x4& mat) const;
+		const aiMatrix4x4& mat);
+
+	const aiMesh& getMeshAt(unsigned index) const;
+	const aiMaterial& getMaterialAt(unsigned index) const;
+	std::string getModelName(const Model& model) const;
+
+	/// Visits the node hierarchy and gathers models and nodes.
+	void visitNode(const aiNode* ainode);
+	/// @}
+
+	/// Export a mesh.
+	/// @param transform If not nullptr then transform the vertices using that.
+	void exportMesh(
+		const aiMesh& mesh, 
+		const aiMatrix4x4* transform) const;
+
+	/// Export a skeleton.
+	void exportSkeleton(const aiMesh& mesh) const;
+
+	/// Export a material.
+	void exportMaterial(
+		const aiMaterial& mtl, 
+		bool instanced) const;
+
+	/// Export a model.
+	void exportModel(const Model& model) const;
+
+	/// Export a light.
+	void exportLight(const aiLight& light);
+
+	/// Export an animation.
+	void exportAnimation(
+		const aiAnimation& anim,
+		unsigned index);
 };
 
 #endif

+ 0 - 116
tools/scene/Light.cpp

@@ -1,116 +0,0 @@
-// Copyright (C) 2014, Panagiotis Christopoulos Charitos.
-// All rights reserved.
-// Code licensed under the BSD License.
-// http://www.anki3d.org/LICENSE
-
-#include "Common.h"
-#include <cassert>
-
-//==============================================================================
-static const aiNode* findNodeWithName(
-	const std::string& name, 
-	const aiNode* node)
-{
-	if(node == nullptr || node->mName.C_Str() == name)
-	{
-		return node;
-	}
-
-	const aiNode* out = nullptr;
-
-	// Go to children
-	for(uint32_t i = 0; i < node->mNumChildren; i++)
-	{
-		out = findNodeWithName(name, node->mChildren[i]);
-		if(out)
-		{
-			break;
-		}
-	}
-
-	return out;
-}
-
-//==============================================================================
-void exportLight(
-	const Exporter& exporter,
-	const aiLight& light, 
-	std::ofstream& file)
-{
-	if(light.mType != aiLightSource_POINT && light.mType != aiLightSource_SPOT)
-	{
-		LOGW("Skipping light %s. Unsupported type (0x%x)\n", 
-			light.mName.C_Str(), light.mType);
-		return;
-	}
-
-	if(light.mAttenuationLinear != 0.0)
-	{
-		LOGW("Skipping light %s. Linear attenuation is not 0.0\n", 
-			light.mName.C_Str());
-		return;
-	}
-
-	file << "node = scene:new" 
-		<< ((light.mType == aiLightSource_POINT) ? "Point" : "Spot") 
-		<< "Light(\"" << light.mName.C_Str() << "\")\n";
-	
-	file << "lcomp = node:getLightComponent()\n";
-
-	// Colors
-	file << "lcomp:setDiffuseColor(" 
-		<< light.mColorDiffuse[0] << ", " 
-		<< light.mColorDiffuse[1] << ", " 
-		<< light.mColorDiffuse[2] << ", " 
-		<< "1.0)\n";
-
-	file << "lcomp:setSpecularColor(" 
-		<< light.mColorSpecular[0] << ", " 
-		<< light.mColorSpecular[1] << ", " 
-		<< light.mColorSpecular[2] << ", " 
-		<< "1.0)\n";
-
-	// Geometry
-	aiVector3D direction(0.0, 0.0, 1.0);
-
-	switch(light.mType)
-	{
-	case aiLightSource_POINT:
-		{
-			// At this point I want the radius and have the attenuation factors
-			// att = Ac + Al*d + Aq*d^2. When d = r then att = 0.0. Also if we 
-			// assume that Al is 0 then:
-			// 0 = Ac + Aq*r^2. Solving by r is easy
-			float r = 
-				sqrt(light.mAttenuationConstant / light.mAttenuationQuadratic);
-			file << "lcomp:setRadius(" << r << ")\n";
-		}
-		break;
-	case aiLightSource_SPOT:
-		{
-			float dist = 
-				sqrt(light.mAttenuationConstant / light.mAttenuationQuadratic);
-
-			file << "lcomp:setInnerAngle(" << light.mAngleInnerCone << ")\n"
-				<< "lcomp:setOuterAngle(" << light.mAngleOuterCone << ")\n"
-				<< "lcomp:setDistance(" << dist << ")\n";
-
-			direction = light.mDirection;
-			break;
-		}
-	default:
-		assert(0);
-		break;
-	}
-
-	// Transform
-	const aiNode* node = 
-		findNodeWithName(light.mName.C_Str(), exporter.scene->mRootNode);
-	
-	if(node == nullptr)
-	{
-		ERROR("Couldn't find node for light %s", light.mName.C_Str());
-	}
-
-	writeNodeTransform(exporter, file, "node", node->mTransformation);
-}

+ 13 - 193
tools/scene/Main.cpp

@@ -3,45 +3,7 @@
 // Code licensed under the BSD License.
 // http://www.anki3d.org/LICENSE
 
-#include <assimp/Importer.hpp>
-#include <assimp/scene.h>
-#include <assimp/postprocess.h>
-#include <iostream>
-#include <stdexcept>
-#include <cstdarg>
-#include <fstream>
-#include <cstdint>
-#include <sstream>
-#include <cassert>
-#include "Common.h"
-
-//==============================================================================
-/// Load the scene
-static void load(
-	Exporter& exporter,
-	const std::string& filename)
-{
-	LOGI("Loading file %s\n", filename.c_str());
-
-	const aiScene* scene = exporter.importer.ReadFile(filename, 0
-		//| aiProcess_FindInstances
-		| aiProcess_Triangulate
-		| aiProcess_JoinIdenticalVertices
-		//| aiProcess_SortByPType
-		| aiProcess_ImproveCacheLocality
-		| aiProcess_OptimizeMeshes
-		| aiProcess_RemoveRedundantMaterials
-		);
-
-	if(!scene)
-	{
-		ERROR("%s\n", exporter.importer.GetErrorString());
-	}
-
-	exporter.scene = scene;
-
-	LOGI("File loaded successfully!\n");
-}
+#include "Exporter.h"
 
 //==============================================================================
 static void parseCommandLineArgs(int argc, char** argv, Exporter& exporter)
@@ -59,8 +21,8 @@ Options:
 		goto error;
 	}
 
-	exporter.inputFname = argv[1];
-	exporter.outDir = argv[2] + std::string("/");
+	exporter.m_inputFilename = argv[1];
+	exporter.m_outputDirectory = argv[2] + std::string("/");
 
 	for(int i = 3; i < argc; i++)
 	{
@@ -70,7 +32,7 @@ Options:
 
 			if(i < argc)
 			{
-				exporter.texrpath = argv[i] + std::string("/");
+				exporter.m_texrpath = argv[i] + std::string("/");
 			}
 			else
 			{
@@ -83,7 +45,7 @@ Options:
 
 			if(i < argc)
 			{
-				exporter.rpath = argv[i] + std::string("/");
+				exporter.m_rpath = argv[i] + std::string("/");
 			}
 			else
 			{
@@ -92,7 +54,7 @@ Options:
 		}
 		else if(strcmp(argv[i], "-flipyz") == 0)
 		{
-			exporter.flipyz = true;
+			exporter.m_flipyz = true;
 		}
 		else
 		{
@@ -100,14 +62,14 @@ Options:
 		}
 	}
 
-	if(exporter.rpath.empty())
+	if(exporter.m_rpath.empty())
 	{
-		exporter.rpath = exporter.outDir;
+		exporter.m_rpath = exporter.m_outputDirectory;
 	}
 
-	if(exporter.texrpath.empty())
+	if(exporter.m_texrpath.empty())
 	{
-		exporter.texrpath = exporter.outDir;
+		exporter.m_texrpath = exporter.m_outputDirectory;
 	}
 
 	return;
@@ -117,148 +79,6 @@ error:
 	exit(1);
 }
 
-//==============================================================================
-static void visitNode(Exporter& exporter, const aiNode* ainode)
-{
-	if(ainode == nullptr)
-	{
-		return;
-	}
-
-	// For every mesh of this node
-	for(unsigned i = 0; i < ainode->mNumMeshes; i++)
-	{
-		unsigned meshIndex = ainode->mMeshes[i];
-		unsigned mtlIndex =  exporter.scene->mMeshes[meshIndex]->mMaterialIndex;
-
-		// Find if there is another node with the same model
-		std::vector<Node>::iterator it;
-		for(it = exporter.nodes.begin(); it != exporter.nodes.end(); it++)
-		{
-			const Node& node = *it;
-			const Model& model = exporter.models[node.modelIndex];
-
-			if(model.meshIndex == meshIndex && model.mtlIndex == mtlIndex)
-			{
-				break;
-			}
-		}
-
-		if(it != exporter.nodes.end())
-		{
-			// A node with the same model exists. It's instanced
-
-			Node& node = *it;
-			Model& model = exporter.models[node.modelIndex];
-
-			assert(node.transforms.size() > 0);
-			node.transforms.push_back(ainode->mTransformation);
-			
-			model.instanced = true;
-			break;
-		}
-
-		// Create new model
-		Model mdl;
-		mdl.meshIndex = meshIndex;
-		mdl.mtlIndex = mtlIndex;
-		exporter.models.push_back(mdl);
-
-		// Create new node
-		Node node;
-		node.modelIndex = exporter.models.size() - 1;
-		node.transforms.push_back(ainode->mTransformation);
-		exporter.nodes.push_back(node);
-	}
-
-	// Go to children
-	for(uint32_t i = 0; i < ainode->mNumChildren; i++)
-	{
-		visitNode(exporter, ainode->mChildren[i]);
-	}
-}
-
-//==============================================================================
-static void exportScene(Exporter& exporter)
-{
-	LOGI("Exporting scene to %s\n", exporter.outDir.c_str());
-
-	//
-	// Open scene file
-	//
-	std::ofstream file;
-	file.open(exporter.outDir + "scene.lua");
-
-	file << "local scene = getSceneGraph()\n"
-		<< "local pos\n"
-		<< "local rot\n"
-		<< "local node\n"
-		<< "local inst\n"
-		<< "local lcomp\n";
-
-	//
-	// Get all the data
-	//
-
-	const aiNode* node = exporter.scene->mRootNode;
-	visitNode(exporter, node);
-
-	//
-	// Export nodes
-	//
-	for(uint32_t i = 0; i < exporter.nodes.size(); i++)
-	{
-		Node& node = exporter.nodes[i];
-
-		Model& model = exporter.models[node.modelIndex];
-
-		exportMesh(exporter, 
-			*exporter.scene->mMeshes[model.meshIndex], nullptr);
-
-		exportMaterial(exporter, 
-			*exporter.scene->mMaterials[model.mtlIndex], 
-			model.instanced);
-
-		exportModel(exporter, model);
-		std::string name = getModelName(exporter, model);
-
-		// Write the main node
-		file << "\nnode = scene:newModelNode(\"" 
-			<< name << "\", \"" 
-			<< exporter.rpath << name << ".ankimdl" << "\")\n"; 
-		writeNodeTransform(exporter, file, "node", node.transforms[0]);
-
-		// Write instance nodes
-		for(unsigned j = 1; j < node.transforms.size(); j++)
-		{
-			file << "inst = scene:newInstanceNode(\"" 
-				<< name << "_inst" << (j - 1) << "\")\n"
-				<< "node:getSceneNodeBase():addChild("
-				<< "inst:getSceneNodeBase())\n";
-
-			writeNodeTransform(exporter, file, "inst", node.transforms[j]);
-		}
-	}
-
-	//
-	// Lights
-	//
-	for(unsigned i = 0; i < exporter.scene->mNumLights; i++)
-	{
-		exportLight(exporter, *exporter.scene->mLights[i], file);
-	}
-
-	//
-	// Animations
-	//
-	for(unsigned i = 0; i < exporter.scene->mNumAnimations; i++)
-	{
-		exportAnimation(exporter, *exporter.scene->mAnimations[i], i);
-	}
-
-	LOGI("Done exporting scene!\n");
-}
-
 //==============================================================================
 int main(int argc, char** argv)
 {
@@ -269,13 +89,13 @@ int main(int argc, char** argv)
 		parseCommandLineArgs(argc, argv, exporter);
 
 		// Load file
-		load(exporter, exporter.inputFname);
+		exporter.load();
 
 		// Export
-		exportScene(exporter);
+		exporter.exportAll();
 	}
 	catch(std::exception& e)
 	{
-		std::cerr << "Exception: " << e.what() << std::endl;
+		ERROR("Exception: %s", &e.what()[0]);
 	}
 }

+ 0 - 613
tools/scene/Model.cpp

@@ -1,613 +0,0 @@
-// Copyright (C) 2014, Panagiotis Christopoulos Charitos.
-// All rights reserved.
-// Code licensed under the BSD License.
-// http://www.anki3d.org/LICENSE
-
-#include "Common.h"
-#include <cassert>
-#include <algorithm>
-
-//==============================================================================
-static const aiMesh& getMesh(const Exporter& exporter, unsigned index)
-{
-	assert(index < exporter.scene->mNumMeshes);
-	return *exporter.scene->mMeshes[index];
-}
-
-//==============================================================================
-static const aiMaterial& getMaterial(const Exporter& exporter, unsigned index)
-{
-	assert(index < exporter.scene->mNumMaterials);
-	return *exporter.scene->mMaterials[index];
-}
-
-//==============================================================================
-static std::string getMeshName(const aiMesh& mesh)
-{
-	return std::string(mesh.mName.C_Str());
-}
-
-//==============================================================================
-static std::string getMaterialName(const aiMaterial& mtl, bool instanced)
-{
-	aiString ainame;
-	std::string name;
-	if(mtl.Get(AI_MATKEY_NAME, ainame) == AI_SUCCESS)
-	{
-		name = ainame.C_Str();
-
-		if(instanced)
-		{
-			name += "_inst";
-		}
-	}
-	else
-	{
-		ERROR("Material's name is missing\n");
-	}
-
-	return name;
-}
-
-//==============================================================================
-std::string getModelName(const Exporter& exporter, const Model& model)
-{
-	std::string name = 
-		getMeshName(getMesh(exporter, model.meshIndex)) + "_"
-		+ getMaterialName(getMaterial(exporter, model.mtlIndex), 
-			model.instanced);
-
-	return name;
-}
-
-//==============================================================================
-void exportMesh(
-	const Exporter& exporter,
-	const aiMesh& mesh, 
-	const aiMatrix4x4* transform)
-{
-	std::string name = getMeshName(mesh);
-	std::fstream file;
-	LOGI("Exporting mesh %s\n", name.c_str());
-
-	uint32_t vertsCount = mesh.mNumVertices;
-
-	// Open file
-	file.open(exporter.outDir + name + ".ankimesh",
-		std::ios::out | std::ios::binary);
-
-	// Write magic word
-	file.write("ANKIMESH", 8);
-
-	// Write the name
-	uint32_t size = name.size();
-	file.write((char*)&size, sizeof(uint32_t));
-	file.write(&name[0], size);
-
-	// Write positions
-	file.write((char*)&vertsCount, sizeof(uint32_t));
-	for(uint32_t i = 0; i < mesh.mNumVertices; i++)
-	{
-		aiVector3D pos = mesh.mVertices[i];
-
-		// Transform
-		if(transform)
-		{
-			pos = (*transform) * pos;
-		}
-
-		// flip 
-		if(exporter.flipyz)
-		{
-			static const aiMatrix4x4 toLefthanded(
-				1, 0, 0, 0, 
-				0, 0, 1, 0, 
-				0, -1, 0, 0, 
-				0, 0, 0, 1);
-
-			pos = toLefthanded * pos;
-		}
-
-		for(uint32_t j = 0; j < 3; j++)
-		{
-			file.write((char*)&pos[j], sizeof(float));
-		}
-	}
-
-	// Write the indices
-	file.write((char*)&mesh.mNumFaces, sizeof(uint32_t));
-	for(uint32_t i = 0; i < mesh.mNumFaces; i++)
-	{
-		const aiFace& face = mesh.mFaces[i];
-		
-		if(face.mNumIndices != 3)
-		{
-			ERROR("For some reason the assimp didn't triangulate\n");
-		}
-
-		for(uint32_t j = 0; j < 3; j++)
-		{
-			uint32_t index = face.mIndices[j];
-			file.write((char*)&index, sizeof(uint32_t));
-		}
-	}
-
-	// Write the tex coords
-	file.write((char*)&vertsCount, sizeof(uint32_t));
-
-	// For all channels
-	for(uint32_t ch = 0; ch < mesh.GetNumUVChannels(); ch++)
-	{
-		if(mesh.mNumUVComponents[ch] != 2)
-		{
-			ERROR("Incorrect number of UV components\n");
-		}
-
-		// For all tex coords of this channel
-		for(uint32_t i = 0; i < vertsCount; i++)
-		{
-			aiVector3D texCoord = mesh.mTextureCoords[ch][i];
-
-			for(uint32_t j = 0; j < 2; j++)
-			{
-				file.write((char*)&texCoord[j], sizeof(float));
-			}
-		}
-	}
-
-	// Write bone weigths count
-	if(mesh.HasBones())
-	{
-#if 0
-		// Write file
-		file.write((char*)&vertsCount, sizeof(uint32_t));
-
-		// Gather info for each vertex
-		std::vector<Vw> vw;
-		vw.resize(vertsCount);
-		memset(&vw[0], 0, sizeof(Vw) * vertsCount);
-
-		// For all bones
-		for(uint32_t i = 0; i < mesh.mNumBones; i++)
-		{
-			const aiBone& bone = *mesh.mBones[i];
-
-			// for every weights of the bone
-			for(uint32_t j = 0; j < bone.mWeightsCount; j++)
-			{
-				const aiVertexWeight& weigth = bone.mWeights[j];
-
-				// Sanity check
-				if(weight.mVertexId >= vertCount)
-				{
-					ERROR("Out of bounds vert ID");
-				}
-
-				Vm& a = vm[weight.mVertexId];
-
-				// Check out of bounds
-				if(a.bonesCount >= MAX_BONES_PER_VERTEX)
-				{
-					LOGW("Too many bones for vertex %d\n", weigth.mVertexId);
-					continue;
-				}
-
-				// Write to vertex
-				a.boneIds[a.bonesCount] = i;
-				a.weigths[a.bonesCount] = weigth.mWeigth;
-				++a.bonesCount;
-			}
-
-			// Now write the file
-		}
-#endif
-	}
-	else
-	{
-		uint32_t num = 0;
-		file.write((char*)&num, sizeof(uint32_t));
-	}
-}
-
-//==============================================================================
-void exportSkeleton(const Exporter& exporter, const aiMesh& mesh)
-{
-	assert(mesh.HasBones());
-	std::string name = mesh.mName.C_Str();
-	std::fstream file;
-	LOGI("Exporting skeleton %s\n", name.c_str());
-
-	// Open file
-	file.open(exporter.outDir + name + ".skel", std::ios::out);
-	
-	file << XML_HEADER << "\n";
-	file << "<skeleton>\n";
-	file << "\t<bones>\n";
-
-	bool rootBoneFound = false;
-
-	for(uint32_t i = 0; i < mesh.mNumBones; i++)
-	{
-		const aiBone& bone = *mesh.mBones[i];
-
-		file << "\t\t<bone>\n";
-
-		// <name>
-		file << "\t\t\t<name>" << bone.mName.C_Str() << "</name>\n";
-
-		if(strcmp(bone.mName.C_Str(), "root") == 0)
-		{
-			rootBoneFound = true;
-		}
-
-		// <transform>
-		file << "\t\t\t<transform>";
-		for(uint32_t j = 0; j < 16; j++)
-		{
-			file << bone.mOffsetMatrix[j] << " ";
-		}
-		file << "</transform>\n";
-
-		file << "\t\t</bone>\n";
-	}
-
-	if(!rootBoneFound)
-	{
-		ERROR("There should be one bone named \"root\"\n");
-	}
-
-	file << "\t</bones>\n";
-	file << "</skeleton>\n";
-}
-
-//==============================================================================
-void exportMaterial(
-	const Exporter& exporter, 
-	const aiMaterial& mtl, 
-	bool instanced)
-{
-	std::string diffTex;
-	std::string normTex;
-	std::string specColTex;
-	std::string shininessTex;
-	std::string dispTex;
-
-	aiString path;
-
-	std::string name = getMaterialName(mtl, instanced);
-	LOGI("Exporting material %s\n", name.c_str());
-
-	// Diffuse texture
-	if(mtl.GetTextureCount(aiTextureType_DIFFUSE) > 0)
-	{
-		if(mtl.GetTexture(aiTextureType_DIFFUSE, 0, &path) == AI_SUCCESS)
-		{
-			diffTex = getFilename(path.C_Str());
-		}
-		else
-		{
-			ERROR("Failed to retrieve texture\n");
-		}
-	}
-
-	// Normal texture
-	if(mtl.GetTextureCount(aiTextureType_NORMALS) > 0)
-	{	
-		if(mtl.GetTexture(aiTextureType_NORMALS, 0, &path) == AI_SUCCESS)
-		{
-			normTex = getFilename(path.C_Str());
-		}
-		else
-		{
-			ERROR("Failed to retrieve texture\n");
-		}
-	}
-
-	// Specular color
-	if(mtl.GetTextureCount(aiTextureType_SPECULAR) > 0)
-	{
-		if(mtl.GetTexture(aiTextureType_SPECULAR, 0, &path) == AI_SUCCESS)
-		{
-			specColTex = getFilename(path.C_Str());
-		}
-		else
-		{
-			ERROR("Failed to retrieve texture\n");
-		}
-	}
-
-	// Shininess color
-	if(mtl.GetTextureCount(aiTextureType_SHININESS) > 0)
-	{
-		if(mtl.GetTexture(aiTextureType_SHININESS, 0, &path) == AI_SUCCESS)
-		{
-			shininessTex = getFilename(path.C_Str());
-		}
-		else
-		{
-			ERROR("Failed to retrieve texture\n");
-		}
-	}
-
-	// Height texture
-	if(mtl.GetTextureCount(aiTextureType_DISPLACEMENT) > 0)
-	{	
-		if(mtl.GetTexture(aiTextureType_DISPLACEMENT, 0, &path) == AI_SUCCESS)
-		{
-			dispTex = getFilename(path.C_Str());
-		}
-		else
-		{
-			ERROR("Failed to retrieve texture\n");
-		}
-	}
-
-	// Write file
-	static const char* diffNormSpecFragTemplate = 
-#include "templates/diffNormSpecFrag.h"
-		;
-	static const char* simpleVertTemplate = 
-#include "templates/simpleVert.h"
-		;
-	static const char* tessVertTemplate = 
-#include "templates/tessVert.h"
-		;
-
-	static const char* readRgbFromTextureTemplate = R"(
-				<operation>
-					<id>%id%</id>
-					<returnType>vec3</returnType>
-					<function>readRgbFromTexture</function>
-					<arguments>
-						<argument>%map%</argument>
-						<argument>out2</argument>
-					</arguments>
-				</operation>)";
-
-	static const char* readRFromTextureTemplate = R"(
-				<operation>
-					<id>%id%</id>
-					<returnType>float</returnType>
-					<function>readRFromTexture</function>
-					<arguments>
-						<argument>%map%</argument>
-						<argument>out2</argument>
-					</arguments>
-				</operation>)";
-
-	// Compose full template
-	// First geometry part
-	std::string materialStr;
-	materialStr = R"(<?xml version="1.0" encoding="UTF-8" ?>)";
-	materialStr += "\n<material>\n\t<programs>\n";
-	if(dispTex.empty())
-	{
-		materialStr += simpleVertTemplate;
-	}
-	else
-	{
-		materialStr += tessVertTemplate;
-	}
-
-	materialStr += "\n";
-
-	// Then fragment part
-	materialStr += diffNormSpecFragTemplate;
-	materialStr += "\n\t</programs>\t</material>";
-
-	// Replace strings
-	if(!dispTex.empty())
-	{
-		materialStr = replaceAllString(materialStr, "%dispMap%", 
-			exporter.texrpath + dispTex);
-	}
-
-	// Diffuse
-	if(!diffTex.empty())
-	{
-		materialStr = replaceAllString(materialStr, "%diffuseColorInput%", 
-			R"(<input><type>sampler2D</type><name>uDiffuseColor</name><value>)"
-			+ exporter.texrpath + diffTex
-			+ R"(</value></input>)");
-
-		materialStr = replaceAllString(materialStr, "%diffuseColorFunc%", 
-			readRgbFromTextureTemplate);
-
-		materialStr = replaceAllString(materialStr, "%id%", 
-			"10");
-
-		materialStr = replaceAllString(materialStr, "%map%", 
-			"uDiffuseColor");
-
-		materialStr = replaceAllString(materialStr, "%diffuseColorArg%", 
-			"out10");
-	}
-	else
-	{
-		aiColor3D diffCol = {0.0, 0.0, 0.0};
-		mtl.Get(AI_MATKEY_COLOR_DIFFUSE, diffCol);
-
-		materialStr = replaceAllString(materialStr, "%diffuseColorInput%", 
-			R"(<input><type>vec3</type><name>uDiffuseColor</name><value>)"
-			+ std::to_string(diffCol[0]) + " "
-			+ std::to_string(diffCol[1]) + " "
-			+ std::to_string(diffCol[2])
-			+ R"(</value></input>)");
-
-		materialStr = replaceAllString(materialStr, "%diffuseColorFunc%", 
-			"");
-
-		materialStr = replaceAllString(materialStr, "%diffuseColorArg%", 
-			"uDiffuseColor");
-	}
-
-	// Normal
-	if(!normTex.empty())
-	{
-		materialStr = replaceAllString(materialStr, "%normalInput%", 
-			R"(<input><type>sampler2D</type><name>uNormal</name><value>)"
-			+ exporter.texrpath + normTex
-			+ R"(</value></input>)");
-
-		materialStr = replaceAllString(materialStr, "%normalFunc%", 
-				R"(
-				<operation>
-					<id>20</id>
-					<returnType>vec3</returnType>
-					<function>readNormalFromTexture</function>
-					<arguments>
-						<argument>out0</argument>
-						<argument>out1</argument>
-						<argument>uNormal</argument>
-						<argument>out2</argument>
-					</arguments>
-				</operation>)");
-
-		materialStr = replaceAllString(materialStr, "%normalArg%", 
-			"out20");
-	}
-	else
-	{
-		materialStr = replaceAllString(materialStr, "%normalInput%", " ");
-
-		materialStr = replaceAllString(materialStr, "%normalFunc%", " ");
-
-		materialStr = replaceAllString(materialStr, "%normalArg%", "out0");
-	}
-
-	// Specular
-	if(!specColTex.empty())
-	{
-		materialStr = replaceAllString(materialStr, "%specularColorInput%", 
-			R"(<input><type>sampler2D</type><name>uSpecularColor</name><value>)"
-			+ exporter.texrpath + specColTex
-			+ R"(</value></input>)");
-
-		materialStr = replaceAllString(materialStr, "%specularColorFunc%", 
-			readRFromTextureTemplate);
-
-		materialStr = replaceAllString(materialStr, "%id%", 
-			"50");
-
-		materialStr = replaceAllString(materialStr, "%map%", 
-			"uSpecularColor");
-
-		materialStr = replaceAllString(materialStr, "%specularColorArg%", 
-			"out50");
-	}
-	else
-	{
-		aiColor3D specCol = {0.0, 0.0, 0.0};
-		mtl.Get(AI_MATKEY_COLOR_SPECULAR, specCol);
-
-		materialStr = replaceAllString(materialStr, "%specularColorInput%", 
-			R"(<input><type>float</type><name>uSpecularColor</name><value>)"
-			+ std::to_string((specCol[0] + specCol[1] + specCol[2]) / 3.0)
-			+ R"(</value></input>)");
-
-		materialStr = replaceAllString(materialStr, "%specularColorFunc%", 
-			"");
-
-		materialStr = replaceAllString(materialStr, "%specularColorArg%", 
-			"uSpecularColor");
-	}
-
-	if(!shininessTex.empty())
-	{
-		materialStr = replaceAllString(materialStr, "%specularPowerInput%", 
-			R"(<input><type>sampler2D</type><name>uSpecularPower</name><value>)"
-			+ exporter.texrpath + shininessTex
-			+ R"(</value></input>)");
-
-		materialStr = replaceAllString(materialStr, "%specularPowerValue%", 
-			exporter.texrpath + shininessTex);
-
-		materialStr = replaceAllString(materialStr, "%specularPowerFunc%", 
-			readRFromTextureTemplate);
-
-		materialStr = replaceAllString(materialStr, "%id%", 
-			"60");
-
-		materialStr = replaceAllString(materialStr, "%map%", 
-			"uSpecularPower");
-
-		materialStr = replaceAllString(materialStr, "%specularPowerArg%", 
-			"out60");
-	}
-	else
-	{
-		float shininess = 0.0;
-		mtl.Get(AI_MATKEY_SHININESS, shininess);
-		//shininess = std::min(128.0f, shininess)  128.0;
-		const int MAX_SHININESS = 511.0;
-		if(shininess > MAX_SHININESS)
-		{
-			LOGW("Shininness exceeds %d\n", MAX_SHININESS);
-		}
-
-		shininess = shininess / MAX_SHININESS;
-
-		materialStr = replaceAllString(materialStr, "%specularPowerInput%", 
-			R"(<input><type>float</type><name>uSpecularPower</name><value>)"
-			+ std::to_string(shininess)
-			+ R"(</value></input>)");
-
-		materialStr = replaceAllString(materialStr, "%specularPowerFunc%", 
-			"");
-
-		materialStr = replaceAllString(materialStr, "%specularPowerArg%", 
-			"uSpecularPower");
-	}
-
-	materialStr = replaceAllString(materialStr, "%maxSpecularPower%", " ");
-
-	materialStr = replaceAllString(materialStr, "%instanced%", 
-		(instanced) ? "1" : "0");
-	materialStr = replaceAllString(materialStr, "%diffuseMap%", 
-		exporter.texrpath + diffTex);
-
-	// Replace texture extensions with .anki
-	materialStr = replaceAllString(materialStr, ".tga", ".ankitex");
-	materialStr = replaceAllString(materialStr, ".png", ".ankitex");
-	materialStr = replaceAllString(materialStr, ".jpg", ".ankitex");
-	materialStr = replaceAllString(materialStr, ".jpeg", ".ankitex");
-
-	// Open and write file
-	std::fstream file;
-	file.open(exporter.outDir + name + ".ankimtl", std::ios::out);
-	file << materialStr;
-}
-
-//==============================================================================
-void exportModel(const Exporter& exporter, const Model& model)
-{
-	std::string name = getModelName(exporter, model);
-	LOGI("Exporting model %s\n", name.c_str());
-
-	std::fstream file;
-	file.open(exporter.outDir + name + ".ankimdl", std::ios::out);
-
-	file << XML_HEADER << '\n';
-	file << "<model>\n";
-	file << "\t<modelPatches>\n";
-	
-	// start
-	file << "\t\t<modelPatch>\n";
-
-	// Write mesh
-	file << "\t\t\t<mesh>" << exporter.rpath 
-		<< getMeshName(getMesh(exporter, model.meshIndex)) 
-		<< ".ankimesh</mesh>\n";
-
-	// Write material
-	file << "\t\t\t<material>" << exporter.rpath 
-		<< getMaterialName(getMaterial(exporter, model.mtlIndex),
-			model.instanced) 
-		<< ".ankimtl</material>\n";
-
-	// end
-	file << "\t\t</modelPatch>\n";
-
-	file << "\t</modelPatches>\n";
-	file << "</model>\n";
-}