Browse Source

Refactoring & exporter additions

Panagiotis Christopoulos Charitos 12 years ago
parent
commit
81e39e083d

+ 3 - 0
include/anki/event/EventManager.h

@@ -69,6 +69,9 @@ public:
 	/// Update
 	void updateAllEvents(F32 prevUpdateTime, F32 crntTime);
 
+	/// Delete events that pending deletion
+	void deleteEventsMarkedForDeletion();
+
 private:
 	SceneGraph* scene = nullptr;
 	EventsContainer events;

+ 9 - 2
include/anki/scene/SceneGraph.h

@@ -158,7 +158,14 @@ public:
 	void deleteSceneNode(SceneNode* node)
 	{
 		node->markForDeletion();
-		++nodesMarkedForDeletionCount;
+	}
+	void increaseObjectsMarkedForDeletion()
+	{
+		++objectsMarkedForDeletionCount;
+	}
+	void decreaseObjectsMarkedForDeletion()
+	{
+		++objectsMarkedForDeletionCount;
 	}
 
 private:
@@ -179,7 +186,7 @@ private:
 
 	EventManager events;
 
-	U32 nodesMarkedForDeletionCount = 0;
+	std::atomic<U32> objectsMarkedForDeletionCount;
 
 	/// Put a node in the appropriate containers
 	void registerNode(SceneNode* node);

+ 13 - 13
include/anki/util/Allocator.h

@@ -25,7 +25,7 @@ namespace anki {
 namespace detail {
 
 /// Internal methods for the Allocator class
-class AllocatorInternal
+class HeapAllocatorInternal
 {
 public:
 	/// Print a few debugging messages
@@ -41,7 +41,7 @@ protected:
 /// The default allocator. It uses malloc and free for 
 /// allocations/deallocations. It's STL compatible
 template<typename T>
-class Allocator: public detail::AllocatorInternal
+class HeapAllocator: public detail::HeapAllocatorInternal
 {
 public:
 	// STL Typedefs
@@ -54,28 +54,28 @@ public:
 	typedef T value_type;
 
 	/// Default constructor
-	Allocator() throw()
+	HeapAllocator() throw()
 	{}
 	/// Copy constructor
-	Allocator(const Allocator&) throw()
+	HeapAllocator(const HeapAllocator&) throw()
 	{}
 	/// Copy constructor with another type
 	template<typename U>
-	Allocator(const Allocator<U>&) throw()
+	HeapAllocator(const HeapAllocator<U>&) throw()
 	{}
 
 	/// Destructor
-	~Allocator()
+	~HeapAllocator()
 	{}
 
 	/// Copy
-	Allocator<T>& operator=(const Allocator&)
+	HeapAllocator<T>& operator=(const HeapAllocator&)
 	{
 		return *this;
 	}
 	/// Copy with another type
 	template<typename U>
-	Allocator& operator=(const Allocator<U>&) 
+	HeapAllocator& operator=(const HeapAllocator<U>&) 
 	{
 		return *this;
 	}
@@ -144,7 +144,7 @@ public:
 	template<typename U>
 	struct rebind
 	{ 
-		typedef Allocator<U> other; 
+		typedef HeapAllocator<U> other; 
 	};
 
 	/// Allocate a new object and call it's constructor
@@ -204,28 +204,28 @@ public:
 
 /// Another allocator of the same type can deallocate from this one
 template<typename T1, typename T2>
-inline bool operator==(const Allocator<T1>&, const Allocator<T2>&)
+inline bool operator==(const HeapAllocator<T1>&, const HeapAllocator<T2>&)
 {
 	return true;
 }
 
 /// Another allocator of the another type cannot deallocate from this one
 template<typename T1, typename AnotherAllocator>
-inline bool operator==(const Allocator<T1>&, const AnotherAllocator&)
+inline bool operator==(const HeapAllocator<T1>&, const AnotherAllocator&)
 {
 	return false;
 }
 
 /// Another allocator of the same type can deallocate from this one
 template<typename T1, typename T2>
-inline bool operator!=(const Allocator<T1>&, const Allocator<T2>&)
+inline bool operator!=(const HeapAllocator<T1>&, const HeapAllocator<T2>&)
 {
 	return false;
 }
 
 /// Another allocator of the another type cannot deallocate from this one
 template<typename T1, typename AnotherAllocator>
-inline bool operator!=(const Allocator<T1>&, const AnotherAllocator&)
+inline bool operator!=(const HeapAllocator<T1>&, const AnotherAllocator&)
 {
 	return true;
 }

+ 1 - 1
include/anki/util/Dictionary.h

@@ -38,7 +38,7 @@ struct DictionaryEqual
 /// The hash map that has as key an old school C string. When inserting the
 /// char MUST NOT point to a temporary or the evaluation function will fail.
 /// Its template struct because C++ does not offer template typedefs
-template<typename T, typename Alloc = Allocator<std::pair<const char*, T>>>
+template<typename T, typename Alloc = HeapAllocator<std::pair<const char*, T>>>
 using Dictionary = 
 	std::unordered_map<
 		const char*,

+ 1 - 1
include/anki/util/Object.h

@@ -35,7 +35,7 @@ struct ObjectCallbackCollection
 };
 
 /// A hierarchical object
-template<typename T, typename Alloc = Allocator<T>,
+template<typename T, typename Alloc = HeapAllocator<T>,
 	typename TCallbackCollection = ObjectCallbackCollection<T>>
 class Object: public NonCopyable
 {

+ 1 - 1
include/anki/util/Vector.h

@@ -12,7 +12,7 @@ namespace anki {
 /// @addtogroup containers
 /// @{
 
-template<typename T, typename Alloc = Allocator<T>>
+template<typename T, typename Alloc = HeapAllocator<T>>
 using Vector = std::vector<T, Alloc>;
 
 /// @}

+ 32 - 10
src/event/EventManager.cpp

@@ -56,10 +56,6 @@ void EventManager::updateAllEvents(F32 prevUpdateTime_, F32 crntTime_)
 	prevUpdateTime = prevUpdateTime_;
 	crntTime = crntTime_;
 
-	// Container to gather dead events
-	SceneFrameVector<EventsContainer::iterator> 
-		forDeletion(getSceneFrameAllocator());
-
 	EventsContainer::iterator it = events.begin();
 	for(; it != events.end(); it++)
 	{
@@ -71,7 +67,7 @@ void EventManager::updateAllEvents(F32 prevUpdateTime_, F32 crntTime_)
 			|| (pevent->getSceneNode() != nullptr 
 				&& pevent->getSceneNode()->isMarkedForDeletion()))
 		{
-			forDeletion.push_back(it);
+			pevent->markForDeletion();
 			continue;
 		}
 
@@ -81,9 +77,11 @@ void EventManager::updateAllEvents(F32 prevUpdateTime_, F32 crntTime_)
 			pevent->startTime = crntTime;
 		}
 
-		// If not dead update it
+		// Check if dead
 		if(!pevent->isDead(crntTime))
 		{
+			// If not dead update it
+
 			if(pevent->getStartTime() <= crntTime)
 			{
 				pevent->update(prevUpdateTime, crntTime);
@@ -91,6 +89,8 @@ void EventManager::updateAllEvents(F32 prevUpdateTime_, F32 crntTime_)
 		}
 		else
 		{
+			// Dead
+
 			if(pevent->bitsEnabled(Event::EF_REANIMATE))
 			{
 				pevent->startTime = prevUpdateTime;
@@ -100,16 +100,38 @@ void EventManager::updateAllEvents(F32 prevUpdateTime_, F32 crntTime_)
 			{
 				if(pevent->onKilled(prevUpdateTime, crntTime))
 				{
-					forDeletion.push_back(it);
+					pevent->markForDeletion();
 				}
 			}
 		}
 	}
+}
+
+//==============================================================================
+void EventManager::deleteEventsMarkedForDeletion()
+{
+	SceneAllocator<Event> al = getSceneAllocator();
+
+	// Gather events for deletion
+	SceneFrameVector<EventsContainer::iterator> 
+		forDeletion(getSceneFrameAllocator());
+
+	for(EventsContainer::iterator it = events.begin(); it != events.end(); ++it)
+	{
+		Event* event = *it;
+		if(event->isMarkedForDeletion())
+		{
+			forDeletion.push_back(it);
+		}
+	}
 
-	// Kick the dead events out
-	for(EventsContainer::iterator& it : forDeletion)
+	// Delete events
+	for(auto it : forDeletion)
 	{
-		events.erase(it);
+		Event* event = *it;
+
+		unregisterEvent(event);
+		al.deleteInstance(event);
 	}
 }
 

+ 11 - 17
src/scene/SceneGraph.cpp

@@ -81,6 +81,8 @@ SceneGraph::SceneGraph()
 {
 	nodes.reserve(ANKI_SCENE_OPTIMAL_SCENE_NODES_COUNT);
 
+	objectsMarkedForDeletionCount.store(0);
+
 	ambientCol = Vec3(0.0);
 }
 
@@ -152,11 +154,13 @@ SceneNode* SceneGraph::tryFindSceneNode(const char* name)
 //==============================================================================
 void SceneGraph::deleteNodesMarkedForDeletion()
 {
+	SceneAllocator<SceneNode> al = alloc;
+
 	/// Delete all nodes pending deletion. At this point all scene threads 
 	/// should have finished their tasks
-	while(nodesMarkedForDeletionCount > 0)
+	while(objectsMarkedForDeletionCount > 0)
 	{
-		// First gather the nodes that will be de
+		// First gather the nodes that will be deleted
 		SceneFrameVector<decltype(nodes)::iterator> forDeletion;
 		for(auto it = nodes.begin(); it != nodes.end(); it++)
 		{
@@ -166,27 +170,17 @@ void SceneGraph::deleteNodesMarkedForDeletion()
 			}
 		}
 
-		// Now delete
+		// Now delete nodes
 		for(auto& it : forDeletion)
 		{
-			// Disable events for that node
-			events.iterateEvents([&](Event& e)
-			{
-				if(e.getSceneNode() == *it)
-				{
-					e.markForDeletion();
-				}
-			});
-
 			// Remove it
 			unregisterNode(*it);
 
-			SceneAllocator<SceneNode> al = alloc;
-			alloc.destroy(*it);
-			alloc.deallocate(*it, 1);
-
-			++nodesMarkedForDeletionCount;
+			alloc.deleteInstance(*it);
 		}
+
+		// Do the same for events
+		events.deleteEventsMarkedForDeletion();
 	}
 }
 

+ 10 - 2
src/scene/SceneObject.cpp

@@ -14,7 +14,9 @@ SceneObject::SceneObject(Type type, SceneObject* parent, SceneGraph* scene_)
 
 //==============================================================================
 SceneObject::~SceneObject()
-{}
+{
+	scene->decreaseObjectsMarkedForDeletion();
+}
 
 //==============================================================================
 SceneAllocator<U8> SceneObject::getSceneAllocator() const
@@ -33,7 +35,13 @@ SceneAllocator<U8> SceneObject::getSceneFrameAllocator() const
 //==============================================================================
 void SceneObject::markForDeletion()
 {
-	flags |= MARKED_FOR_DELETION;
+	// Mark for deletion only when it's not already marked because we don't 
+	// want to increase the counter again
+	if(!isMarkedForDeletion())
+	{
+		flags |= MARKED_FOR_DELETION;
+		scene->increaseObjectsMarkedForDeletion();
+	}
 
 	visitChildren([](SceneObject& obj)
 	{

+ 2 - 2
src/util/Allocator.cpp

@@ -19,10 +19,10 @@ namespace anki {
 namespace detail {
 
 //==============================================================================
-PtrSize AllocatorInternal::allocatedSize = 0;
+PtrSize HeapAllocatorInternal::allocatedSize = 0;
 
 //==============================================================================
-void AllocatorInternal::dump()
+void HeapAllocatorInternal::dump()
 {
 #if ANKI_DEBUG_ALLOCATORS
 	if(allocatedSize > 0)

+ 1 - 1
src/util/Exception.cpp

@@ -40,7 +40,7 @@ Exception::Exception(const char* file, I line, const char* func,
 		len = vsnprintf(&largeStr[0], largeStr.size(), fmt, args);
 		va_end(args);
 
-		ANKI_ASSERT(len < largeStr.size());
+		ANKI_ASSERT(len < (I)largeStr.size());
 	}
 
 	err = synthErr(out, file, line, func);

+ 1 - 1
testapp/Main.cpp

@@ -164,7 +164,7 @@ void init()
 	scene.newSceneNode(spot, "spot0");
 	spot->setOuterAngle(toRad(45.0));
 	spot->setInnerAngle(toRad(15.0));
-	spot->setLocalTransform(Transform(Vec3(-1.434199, 5.474161, -10.774253),
+	spot->setLocalTransform(Transform(Vec3(8.27936, 5.86285, 1.85526),
 		Mat3(Quat(-0.125117, 0.620465, 0.154831, 0.758544)), 1.0));
 	spot->setDiffuseColor(Vec4(2.0));
 	spot->setSpecularColor(Vec4(-1.0));

+ 106 - 0
tools/scene/Animation.cpp

@@ -0,0 +1,106 @@
+#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 - 1
tools/scene/CMakeLists.txt

@@ -1,4 +1,4 @@
 INCLUDE_DIRECTORIES("../../extern/assimp/include")
 	
-ADD_EXECUTABLE(ankisceneimp Main.cpp)
+ADD_EXECUTABLE(ankisceneimp Main.cpp Model.cpp Animation.cpp Light.cpp)
 TARGET_LINK_LIBRARIES(ankisceneimp ankiassimp) 

+ 92 - 0
tools/scene/Common.cpp

@@ -0,0 +1,92 @@
+#include "Common.h"
+
+//==============================================================================
+const char* XML_HEADER = R"(<?xml version="1.0" encoding="UTF-8" ?>)";
+
+//==============================================================================
+std::string replaceAllString(
+	const std::string& str, 
+	const std::string& from, 
+	const std::string& to)
+{
+	if(from.empty())
+	{
+		return str;
+	}
+
+	std::string out = str;
+	size_t start_pos = 0;
+	while((start_pos = out.find(from, start_pos)) != std::string::npos) 
+	{
+		out.replace(start_pos, from.length(), to);
+		start_pos += to.length();
+	}
+
+	return out;
+}
+
+//==============================================================================
+std::string getFilename(const std::string& path)
+{
+	std::string out;
+
+	const size_t last = path.find_last_of("/");
+	if(std::string::npos != last)
+	{
+		out.insert(out.end(), path.begin() + last + 1, path.end());
+	}
+	else
+	{
+		out = 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;
+	}
+}

+ 120 - 0
tools/scene/Common.h

@@ -0,0 +1,120 @@
+#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>
+
+//==============================================================================
+// Log and errors
+
+#define STR(s) #s
+#define XSTR(s) STR(s)
+#define LOGI(...) \
+	printf("[I] (" __FILE__ ":" XSTR(__LINE__) ") " __VA_ARGS__)
+
+#define ERROR(...) \
+	do { \
+		fprintf(stderr, "[E] (" __FILE__ ":" XSTR(__LINE__) ") " __VA_ARGS__); \
+		exit(1); \
+	} while(0)
+
+#define LOGW(...) \
+	fprintf(stderr, "[W] (" __FILE__ ":" XSTR(__LINE__) ") " __VA_ARGS__)
+
+/// A string
+extern const char* XML_HEADER;
+
+/// Thin mesh wrapper
+struct Mesh
+{
+	uint32_t index = 0xFFFFFFFF; ///< Mesh index in the scene
+	std::vector<aiMatrix4x4> transforms;
+	uint32_t mtlIndex = 0xFFFFFFFF;
+};
+
+/// Thin material wrapper
+struct Material
+{
+	uint32_t index = 0xFFFFFFFF;
+	std::vector<uint32_t> meshIndices;
+};
+
+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
+
+	aiScene* scene = nullptr;
+	Assimp::Importer* importer;
+
+	std::vector<Mesh> meshes;
+	std::vector<Material> materials;
+};
+
+/// Replace all @a from substrings in @a str to @a to
+extern 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);
+
+/// 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 std::string* name,
+	const aiMatrix4x4* transform);
+
+/// Export a skeleton
+extern void exportSkeleton(const Exporter& exporter, const aiMesh& mesh);
+
+/// Helper function
+extern std::string getMaterialName(const aiMaterial& mtl);
+
+/// Export material
+extern void exportMaterial(
+	const Exporter& exporter, 
+	const aiMaterial& mtl, 
+	bool instanced,
+	const std::string* name);
+
+/// Write light to the scene file
+extern void exportLight(
+	const aiLight& light, 
+	std::fstream& file);
+
+#endif

+ 67 - 0
tools/scene/Light.cpp

@@ -0,0 +1,67 @@
+#include "Common.h"
+#include <cassert>
+
+//==============================================================================
+void exportLight(
+	const aiLight& light, 
+	std::fstream& file)
+{
+	if(light.mType != aiLightSource_POINT || light.mType != aiLightSource_SPOT)
+	{
+		LOGW("Skipping light %s. Unsupported type\n", light.mName.C_Str());
+		return;
+	}
+
+	file << "\t<light>\n";
+
+	file << "\t\t<name>" << light.mName.C_Str() << "</name>\n";
+
+	file << "\t\t<diffuseColor>" 
+		<< light.mColorDiffuse[0] << " " 
+		<< light.mColorDiffuse[1] << " " 
+		<< light.mColorDiffuse[2] << " " 
+		<< light.mColorDiffuse[3]
+		<< "</diffuseColor>\n";
+
+	file << "\t\t<specularColor>" 
+		<< light.mColorSpecular[0] << " " 
+		<< light.mColorSpecular[1] << " " 
+		<< light.mColorSpecular[2] << " " 
+		<< light.mColorSpecular[3]
+		<< "</specularColor>\n";
+
+	aiMatrix4x4 trf;
+	aiMatrix4x4::Translation(light.mPosition, trf);
+
+	switch(light.mType)
+	{
+	case aiLightSource_POINT:
+		{
+			file << "\t\t<type>point</type>\n";
+
+			// 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 Ac is 0 then:
+			// 0 = Al*r + Aq*r^2. Solving by r is easy
+			float r = -light.mAttenuationLinear / light.mAttenuationQuadratic;
+			file << "\t\t<radius>" << r << "</radius>\n";
+			break;
+		}
+	case aiLightSource_SPOT:
+		file << "\t\t<type>spot</type>\n";
+		break;
+	default:
+		assert(0);
+		break;
+	}
+
+	// <transform>
+	file << "\t\t<transform>";
+	for(uint32_t i = 0; i < 16; i++)
+	{
+		file << trf[i] << " ";
+	}
+	file << "</transform>\n";
+
+	file << "\t</light>\n";
+}

+ 36 - 647
tools/scene/Main.cpp

@@ -9,142 +9,37 @@
 #include <sstream>
 #include <cassert>
 
-static const char* xmlHeader = R"(<?xml version="1.0" encoding="UTF-8" ?>)";
-
-struct Mesh
-{
-	uint32_t index = 0xFFFFFFFF; ///< Mesh index in the scene
-	std::vector<aiMatrix4x4> transforms;
-	uint32_t mtlIndex = 0xFFFFFFFF;
-};
-
-struct Material
-{
-	uint32_t index = 0xFFFFFFFF;
-	std::vector<uint32_t> meshIndices;
-};
-
-struct Config
-{
-	std::string inputFname;
-	std::string outDir;
-	std::string rpath;
-	std::string texpath;
-	bool flipyz = false;
-
-	std::vector<Mesh> meshes;
-	std::vector<Material> materials;
-};
-
-Config config;
-
-//==============================================================================
-// Log and errors
-
-#define STR(s) #s
-#define XSTR(s) STR(s)
-#define LOGI(...) \
-	printf("[I] (" __FILE__ ":" XSTR(__LINE__) ") " __VA_ARGS__)
-
-#define ERROR(...) \
-	do { \
-		fprintf(stderr, "[E] (" __FILE__ ":" XSTR(__LINE__) ") " __VA_ARGS__); \
-		exit(0); \
-	} while(0)
-
-#define LOGW(...) \
-	fprintf(stderr, "[W] (" __FILE__ ":" XSTR(__LINE__) ") " __VA_ARGS__)
-
-//==============================================================================
-static std::string replaceAllString(
-	const std::string& str, 
-	const std::string& from, 
-	const std::string& to)
-{
-	if(from.empty())
-	{
-		return str;
-	}
-
-	std::string out = str;
-	size_t start_pos = 0;
-	while((start_pos = out.find(from, start_pos)) != std::string::npos) 
-	{
-		out.replace(start_pos, from.length(), to);
-		start_pos += to.length();
-	}
-
-	return out;
-}
-
 //==============================================================================
-static std::string getFilename(const std::string& path)
+/// Load the scene
+static const void load(
+	const std::string& filename,
+	Exporter& exporter)
 {
-	std::string out;
-
-	const size_t last = path.find_last_of("/");
-	if(std::string::npos != last)
-	{
-		out.insert(out.end(), path.begin() + last + 1, path.end());
-	}
-	else
-	{
-		out = path;
-	}
+	LOGI("Loading file %s\n", filename.c_str());
 
-	return out;
-}
+	const aiScene* scene = exporter.importer.ReadFile(filename, 0
+		//| aiProcess_FindInstances
+		| aiProcess_Triangulate
+		| aiProcess_JoinIdenticalVertices
+		//| aiProcess_SortByPType
+		| aiProcess_ImproveCacheLocality
+		| aiProcess_OptimizeMeshes
+		| aiProcess_RemoveRedundantMaterials
+		);
 
-//==============================================================================
-static aiMatrix4x4 toAnkiMatrix(const aiMatrix4x4& in)
-{
-	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(config.flipyz)
-	{
-		return toLeftHanded * in * toLeftHandedInv;
-	}
-	else
+	if(!scene)
 	{
-		return in;
+		ERROR("%s\n", importer.GetErrorString());
 	}
-}
-
-//==============================================================================
-static aiMatrix3x3 toAnkiMatrix(const aiMatrix3x3& in)
-{
-	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);
+	exporter.scene = scene;
 
-	if(config.flipyz)
-	{
-		return toLeftHanded * in;
-	}
-	else
-	{
-		return in;
-	}
+	LOGI("File loaded successfully!\n");
+	return *scene;
 }
 
 //==============================================================================
-static void parseConfig(int argc, char** argv)
+static void parseCommandLineArgs(int argc, char** argv, Exporter& exporter)
 {
 	static const char* usage = R"(Usage: %s in_file out_dir [options]
 Options:
@@ -159,8 +54,8 @@ Options:
 		goto error;
 	}
 
-	config.inputFname = argv[1];
-	config.outDir = argv[2] + std::string("/");
+	exporter.inputFname = argv[1];
+	exporter.outDir = argv[2] + std::string("/");
 
 	for(int i = 3; i < argc; i++)
 	{
@@ -170,7 +65,7 @@ Options:
 
 			if(i < argc)
 			{
-				config.texpath = argv[i] + std::string("/");
+				exporter.texrpath = argv[i] + std::string("/");
 			}
 			else
 			{
@@ -183,7 +78,7 @@ Options:
 
 			if(i < argc)
 			{
-				config.rpath = argv[i] + std::string("/");
+				exporter.rpath = argv[i] + std::string("/");
 			}
 			else
 			{
@@ -192,7 +87,7 @@ Options:
 		}
 		else if(strcmp(argv[i], "-flipyz") == 0)
 		{
-			config.flipyz = true;
+			exporter.flipyz = true;
 		}
 		else
 		{
@@ -200,422 +95,21 @@ Options:
 		}
 	}
 
-	if(config.rpath.empty())
+	if(exporter.rpath.empty())
 	{
-		config.rpath = config.outDir;
+		exporter.rpath = exporter.outDir;
 	}
 
-	if(config.texpath.empty())
+	if(exporter.texrpath.empty())
 	{
-		config.texpath = config.outDir;
+		exporter.texrpath = exporter.outDir;
 	}
 
 	return;
 
 error:
 	printf(usage, argv[0]);
-	exit(0);
-}
-
-//==============================================================================
-/// Load the scene
-static const aiScene& load(
-	const std::string& filename, 
-	Assimp::Importer& importer)
-{
-	LOGI("Loading file %s\n", filename.c_str());
-
-	const aiScene* scene = importer.ReadFile(filename, 0
-		//| aiProcess_FindInstances
-		| aiProcess_Triangulate
-		| aiProcess_JoinIdenticalVertices
-		//| aiProcess_SortByPType
-		| aiProcess_ImproveCacheLocality
-		| aiProcess_OptimizeMeshes
-		| aiProcess_RemoveRedundantMaterials
-		);
-
-	if(!scene)
-	{
-		ERROR("%s\n", importer.GetErrorString());
-	}
-
-	LOGI("File loaded successfully!\n");
-	return *scene;
-}
-
-//==============================================================================
-static const uint32_t MAX_BONES_PER_VERTEX = 4;
-
-/// Bone/weight info per vertex
-struct Vw
-{
-	uint32_t boneIds[MAX_BONES_PER_VERTEX];
-	float weigths[MAX_BONES_PER_VERTEX];
-	uint32_t bonesCount;
-};
-
-//==============================================================================
-static void exportMesh(
-	const aiMesh& mesh, 
-	const std::string* name_,
-	const aiMatrix4x4* transform)
-{
-	std::string name = (name_) ? *name_ : mesh.mName.C_Str();
-	std::fstream file;
-	LOGI("Exporting mesh %s\n", name.c_str());
-
-	uint32_t vertsCount = mesh.mNumVertices;
-
-	// Open file
-	file.open(config.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(config.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));
-	}
-}
-
-//==============================================================================
-static void exportSkeleton(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(config.outDir + name + ".skel", std::ios::out);
-	
-	file << xmlHeader << "\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";
-}
-
-//==============================================================================
-static std::string getMaterialName(const aiMaterial& mtl)
-{
-	aiString ainame;
-	std::string name;
-	if(mtl.Get(AI_MATKEY_NAME, ainame) == AI_SUCCESS)
-	{
-		name = ainame.C_Str();
-	}
-	else
-	{
-		ERROR("Material's name is missing\n");
-	}
-
-	return name;
-}
-
-//==============================================================================
-static void exportMaterial(
-	const aiScene& scene, 
-	const aiMaterial& mtl, 
-	bool instanced,
-	const std::string* name_)
-{
-	std::string diffTex;
-	std::string normTex;
-
-	std::string name;
-	if(name_)
-	{
-		name = *name_;
-	}
-	else
-	{
-		name = getMaterialName(mtl);
-	}
-	
-
-	LOGI("Exporting material %s\n", name.c_str());
-
-	// Diffuse texture
-	if(mtl.GetTextureCount(aiTextureType_DIFFUSE) < 1)
-	{
-		ERROR("Material has no diffuse textures\n");
-	}
-
-	aiString path;
-	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");
-		}
-	}
-
-	// Write file
-	static const char* diffMtlStr = 
-#include "diffTemplateMtl.h"
-		;
-	static const char* diffNormMtlStr = 
-#include "diffNormTemplateMtl.h"
-		;
-
-	std::fstream file;
-	file.open(config.outDir + name + ".ankimtl", std::ios::out);
-
-	// Chose the correct template
-	std::string str;
-	if(normTex.size() == 0)
-	{
-		str = diffMtlStr;
-	}
-	else
-	{
-		str = replaceAllString(diffNormMtlStr, "%normalMap%", 
-			config.texpath + normTex);
-	}
-
-	str = replaceAllString(str, "%instanced%", (instanced) ? "1" : "0");
-	str = replaceAllString(str, "%diffuseMap%", config.texpath + diffTex);
-
-	file << str;
-}
-
-//==============================================================================
-static void exportLight(
-	const aiLight& light, 
-	std::fstream& file)
-{
-	if(light.mType != aiLightSource_POINT || light.mType != aiLightSource_SPOT)
-	{
-		LOGW("Skipping light %s. Unsupported type\n", light.mName.C_Str());
-		return;
-	}
-
-	file << "\t<light>\n";
-
-	file << "\t\t<name>" << light.mName.C_Str() << "</name>\n";
-
-	file << "\t\t<diffuseColor>" 
-		<< light.mColorDiffuse[0] << " " 
-		<< light.mColorDiffuse[1] << " " 
-		<< light.mColorDiffuse[2] << " " 
-		<< light.mColorDiffuse[3]
-		<< "</diffuseColor>\n";
-
-	file << "\t\t<specularColor>" 
-		<< light.mColorSpecular[0] << " " 
-		<< light.mColorSpecular[1] << " " 
-		<< light.mColorSpecular[2] << " " 
-		<< light.mColorSpecular[3]
-		<< "</specularColor>\n";
-
-	aiMatrix4x4 trf;
-	aiMatrix4x4::Translation(light.mPosition, trf);
-
-	switch(light.mType)
-	{
-	case aiLightSource_POINT:
-		{
-			file << "\t\t<type>point</type>\n";
-
-			// 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 Ac is 0 then:
-			// 0 = Al*r + Aq*r^2. Solving by r is easy
-			float r = -light.mAttenuationLinear / light.mAttenuationQuadratic;
-			file << "\t\t<radius>" << r << "</radius>\n";
-			break;
-		}
-	case aiLightSource_SPOT:
-		file << "\t\t<type>spot</type>\n";
-		break;
-	default:
-		assert(0);
-		break;
-	}
-
-	// <transform>
-	file << "\t\t<transform>";
-	for(uint32_t i = 0; i < 16; i++)
-	{
-		file << trf[i] << " ";
-	}
-	file << "</transform>\n";
-
-	file << "\t</light>\n";
+	exit(1);
 }
 
 //==============================================================================
@@ -667,112 +161,6 @@ static void exportModel(
 	file << "</model>\n";
 }
 
-//==============================================================================
-static void exportAnimation(
-	const aiAnimation& anim, 
-	uint32_t index, 
-	const aiScene& scene)
-{
-	// Get name
-	std::string name = anim.mName.C_Str();
-	if(name.size() == 0)
-	{
-		name = std::string("animation_") + 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(config.outDir + name + ".ankianim", std::ios::out);
-
-	file << xmlHeader << "\n";
-	file << "<animation>\n";
-
-	file << "\t<duration>" << anim.mDuration << "</duration>\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(config.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";
-}
-
 //==============================================================================
 static void visitNode(const aiNode* node, const aiScene& scene)
 {
@@ -1041,14 +429,15 @@ int main(int argc, char** argv)
 {
 	try
 	{
-		parseConfig(argc, argv);
+		Exporter exporter;
+
+		parseCommandLineArgs(argc, argv, exporter);
 
-		// Load
-		Assimp::Importer importer;
-		const aiScene& scene = load(config.inputFname, importer);
+		// Load file
+		load(exporter.inputFname, exporter);
 
 		// Export
-		exportScene(scene);
+		exportScene(exporter);
 	}
 	catch(std::exception& e)
 	{

+ 300 - 0
tools/scene/Model.cpp

@@ -0,0 +1,300 @@
+#include "Common.h"
+#include <cassert>
+
+//==============================================================================
+void exportMesh(
+	const Exporter& exporter,
+	const aiMesh& mesh, 
+	const std::string* name_,
+	const aiMatrix4x4* transform)
+{
+	std::string name = (name_) ? *name_ : mesh.mName.C_Str();
+	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";
+}
+
+//==============================================================================
+std::string getMaterialName(const aiMaterial& mtl)
+{
+	aiString ainame;
+	std::string name;
+	if(mtl.Get(AI_MATKEY_NAME, ainame) == AI_SUCCESS)
+	{
+		name = ainame.C_Str();
+	}
+	else
+	{
+		ERROR("Material's name is missing\n");
+	}
+
+	return name;
+}
+
+//==============================================================================
+void exportMaterial(
+	const Exporter& exporter, 
+	const aiMaterial& mtl, 
+	bool instanced,
+	const std::string* name_)
+{
+	std::string diffTex;
+	std::string normTex;
+
+	std::string name;
+	if(name_)
+	{
+		name = *name_;
+	}
+	else
+	{
+		name = getMaterialName(mtl);
+	}
+
+	LOGI("Exporting material %s\n", name.c_str());
+
+	// Diffuse texture
+	if(mtl.GetTextureCount(aiTextureType_DIFFUSE) < 1)
+	{
+		ERROR("Material has no diffuse textures\n");
+	}
+
+	aiString path;
+	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");
+		}
+	}
+
+	// Write file
+	static const char* diffMtlStr = 
+#include "diffTemplateMtl.h"
+		;
+	static const char* diffNormMtlStr = 
+#include "diffNormTemplateMtl.h"
+		;
+
+	std::fstream file;
+	file.open(exporter.outDir + name + ".ankimtl", std::ios::out);
+
+	// Chose the correct template
+	std::string str;
+	if(normTex.size() == 0)
+	{
+		str = diffMtlStr;
+	}
+	else
+	{
+		str = replaceAllString(diffNormMtlStr, "%normalMap%", 
+			exporter.texrpath + normTex);
+	}
+
+	str = replaceAllString(str, "%instanced%", (instanced) ? "1" : "0");
+	str = replaceAllString(str, "%diffuseMap%", exporter.texrpath + diffTex);
+
+	file << str;
+}