Ver Fonte

Multimeshes & exporters

Panagiotis Christopoulos Charitos há 12 anos atrás
pai
commit
aaa031cec2

+ 5 - 0
include/anki/Config.h.cmake

@@ -140,6 +140,11 @@ inline int stoi(const string& str)
 #define ANKI_RENDERER_TILES_X_COUNT 16
 #define ANKI_RENDERER_TILES_Y_COUNT 16
 
+#define ANKI_MAX_MULTIDRAW_PRIMITIVES 32
+#define ANKI_MAX_INSTANCES 16
+
+#define ANKI_SCENE_OPTIMAL_SCENE_NODES_COUNT 1024
+
 /// @}
 
 #endif

+ 29 - 0
include/anki/gl/Drawcall.h

@@ -0,0 +1,29 @@
+#ifndef ANKI_GL_DRAWCALL_H
+#define ANKI_GL_DRAWCALL_H
+
+#include "anki/gl/Ogl.h"
+#include "anki/util/StdTypes.h"
+
+namespace anki {
+
+/// @addtogroup OpenGL
+/// @{
+
+/// A GL drawcall
+struct Drawcall
+{
+	GLenum primitiveType = 0;
+	GLenum indicesType = 0;
+	U32 instancesCount = 1;
+	GLsizei* indicesCountArray = nullptr;
+	const GLvoid** offsetsArray = nullptr;
+	U32 primCount = 1;
+
+	/// Execute the dracall
+	void enque();
+};
+/// @}
+
+} // end namespace anki
+
+#endif

+ 1 - 0
include/anki/gl/Gl.h

@@ -15,6 +15,7 @@
 #include "anki/gl/Texture.h"
 #include "anki/gl/Vao.h"
 #include "anki/gl/Vbo.h"
+#include "anki/gl/Drawcall.h"
 
 #include "anki/gl/Ogl.h"
 

+ 2 - 1
include/anki/renderer/Drawer.h

@@ -37,7 +37,8 @@ public:
 		RenderingStage stage, 
 		U32 pass, 
 		SceneNode& renderableSceneNode,
-		U64 subSpatialsMask);
+		U32* subSpatialIndices,
+		U subSpatialIndicesCount);
 
 private:
 	Renderer* r;

+ 1 - 3
include/anki/resource/Mesh.h

@@ -63,6 +63,7 @@ public:
 		return meshProtected.obb;
 	}
 
+	/// Get indices count and offset of submesh
 	U32 getIndicesCountSub(U subMeshId, U32& offset) const
 	{
 		ANKI_ASSERT(subMeshId < meshProtected.subMeshes.size());
@@ -151,9 +152,6 @@ protected:
 class BucketMesh: public Mesh
 {
 public:
-	/// The absolute limit of submeshes
-	static const U MAX_SUB_MESHES = 64;
-
 	/// Default constructor. Do nothing
 	BucketMesh()
 	{}

+ 11 - 4
include/anki/resource/Model.h

@@ -63,10 +63,17 @@ public:
 	void getRenderingData(const PassLevelKey& key, const Vao*& vao,
 		const ShaderProgram*& prog, U32& indicesCount) const;
 
-	/// Get information for multiDraw rendering
-	void getRenderingDataSub(const PassLevelKey& key, U64 subMeshesMask,
-		const Vao*& vao, const ShaderProgram*& prog,
-		U32* indicesCountArray, void** indicesOffsetArray, 
+	/// Get information for multiDraw rendering.
+	/// Given an array of submeshes that are visible return the correct indices
+	/// offsets and counts
+	void getRenderingDataSub(
+		const PassLevelKey& key, 
+		const Vao*& vao, 
+		const ShaderProgram*& prog,
+		const U32* subMeshIndicesArray,
+		U subMeshIndicesCount,
+		U32* indicesCountArray, 
+		const void** indicesOffsetArray, 
 		U32& primcount) const;
 
 protected:

+ 14 - 8
include/anki/scene/Visibility.h

@@ -36,23 +36,29 @@ enum VisibleBy
 struct VisibleNode
 {
 	SceneNode* node;
-	U64 subSpatialsMask; ///< The mask of the subspatials
+	/// An array of the visible sub spatials. Allocated but never deallocated
+	/// because it lives in the scene frame mem pool
+	U32* subSpatialIndices;
+	U32 subSpatialIndicesCount;
 
 	VisibleNode()
-		: node(nullptr), subSpatialsMask(0)
+		: node(nullptr), subSpatialIndices(nullptr), subSpatialIndicesCount(0)
 	{}
 
-	VisibleNode(SceneNode* node_, U64 subSpatialsMask_)
-		: node(node_), subSpatialsMask(subSpatialsMask_)
+	VisibleNode(SceneNode* node_, U32* subSpatialIndices_, 
+		U32 subSpatialIndicesCount_)
+		:	node(node_), 
+			subSpatialIndices(subSpatialIndices_), 
+			subSpatialIndicesCount(subSpatialIndicesCount_)
 	{
 		ANKI_ASSERT(node);
 	}
 
 	VisibleNode(const VisibleNode& other)
-		: node(other.node), subSpatialsMask(other.subSpatialsMask)
-	{
-		ANKI_ASSERT(node);
-	}
+		:	node(other.node), 
+			subSpatialIndices(other.subSpatialIndices),
+			subSpatialIndicesCount(other.subSpatialIndicesCount)
+	{}
 };
 
 /// Its actually a container for visible entities. It should be per frame

+ 73 - 0
src/gl/Drawcall.cpp

@@ -0,0 +1,73 @@
+#include "anki/gl/Drawcall.h"
+#include "anki/util/Assert.h"
+
+namespace anki {
+
+//==============================================================================
+void Drawcall::enque()
+{
+	ANKI_ASSERT(primitiveType && instancesCount > 0 && primCount > 0
+		&& offsetsArray);
+
+	if(indicesCountArray != nullptr)
+	{
+		// DrawElements
+
+		ANKI_ASSERT(indicesCountArray && indicesType);
+
+		if(instancesCount == 1)
+		{
+			// No  instancing
+
+			if(primCount == 1)
+			{
+				// No multidraw
+
+				glDrawElements(
+					primitiveType, 
+					indicesCountArray[0], 
+					indicesType, 
+					offsetsArray[0]);
+			}
+			else
+			{
+				// multidraw
+
+#if ANKI_GL == ANKI_GL_DESKTOP
+				glMultiDrawElements(
+					primitiveType, 
+					indicesCountArray, 
+					indicesType, 
+					offsetsArray, 
+					primCount);
+#else
+				for(U i = 0; i < primCount; i++)
+				{
+					glDrawElements(
+						primitiveType, 
+						indicesCountArray[i],
+						indicesType,
+						offsetsArray[i]);
+				}
+#endif
+			}
+		}
+		else
+		{
+			// Instancing
+			glDrawElementsInstanced(
+				primitiveType, 
+				indicesCountArray[0], 
+				indicesType, 
+				offsetsArray[0], 
+				instancesCount);
+		}
+	}
+	else
+	{
+		// Draw arrays
+		ANKI_ASSERT(0 && "ToDo");
+	}
+}
+
+} // end namespace anki

+ 2 - 1
src/renderer/Bs.cpp

@@ -31,7 +31,8 @@ void Bs::run()
 		++it)
 	{
 		drawer.render(scene.getActiveCamera(), RenderableDrawer::RS_BLEND,
-			0, *(*it).node, (*it).subSpatialsMask);
+			0, *(*it).node, (*it).subSpatialIndices, 
+			(*it).subSpatialIndicesCount);
 	}
 
 	GlStateSingleton::get().setDepthMaskEnabled(true);

+ 21 - 45
src/renderer/Drawer.cpp

@@ -224,7 +224,8 @@ void RenderableDrawer::setupShaderProg(const PassLevelKey& key_,
 
 //==============================================================================
 void RenderableDrawer::render(SceneNode& frsn, RenderingStage stage,
-	U32 pass, SceneNode& rsn, U64 subSpatialsMask)
+	U32 pass, SceneNode& rsn, U32* subSpatialIndices,
+	U subSpatialIndicesCount)
 {
 	ANKI_ASSERT(frsn.getFrustumable());
 	Frustumable& fr = *frsn.getFrustumable();
@@ -275,8 +276,8 @@ void RenderableDrawer::render(SceneNode& frsn, RenderingStage stage,
 	// Get rendering useful stuff
 	const ShaderProgram* prog;
 	const Vao* vao;
-	U32 indicesCountArray[64];
-	void* indicesOffsetArray[64] = {nullptr};
+	U32 indicesCountArray[ANKI_MAX_MULTIDRAW_PRIMITIVES];
+	const void* indicesOffsetArray[ANKI_MAX_MULTIDRAW_PRIMITIVES];
 #if ANKI_DEBUG
 	memset(indicesCountArray, 0, sizeof(indicesCountArray));
 	memset(indicesOffsetArray, 0, sizeof(indicesOffsetArray));
@@ -284,16 +285,19 @@ void RenderableDrawer::render(SceneNode& frsn, RenderingStage stage,
 
 	U32 primCount = 1;
 
-	if(subSpatialsMask == 0)
+	if(subSpatialIndicesCount == 0)
 	{
 		renderable->getRenderableModelPatchBase().getRenderingData(
 			key, vao, prog, indicesCountArray[0]);
+
+		indicesOffsetArray[0] = nullptr;
 	}
 	else
 	{
 		renderable->getRenderableModelPatchBase().getRenderingDataSub(
-			key, subSpatialsMask, vao, prog, indicesCountArray, 
-			indicesOffsetArray, primCount);
+			key, vao, prog, 
+			subSpatialIndices, subSpatialIndicesCount,
+			indicesCountArray, indicesOffsetArray, primCount);
 	}
 
 	// Setup shader
@@ -303,45 +307,17 @@ void RenderableDrawer::render(SceneNode& frsn, RenderingStage stage,
 	ANKI_ASSERT(vao->getAttachmentsCount() > 1);
 	vao->bind();
 
-	if(instancesCount == 1)
-	{
-		if(primCount == 1)
-		{
-			glDrawElements(
-				GL_TRIANGLES, 
-				indicesCountArray[0], 
-				GL_UNSIGNED_SHORT, 
-				indicesOffsetArray[0]);
-		}
-		else
-		{
-#if ANKI_GL == ANKI_GL_DESKTOP
-			glMultiDrawElements(GL_TRIANGLES, 
-				(GLsizei*)indicesCountArray, 
-				GL_UNSIGNED_SHORT, 
-				(const void**)indicesOffsetArray, 
-				primCount);
-#else
-			for(U i = 0; i < primCount; i++)
-			{
-				glDrawElements(
-					GL_TRIANGLES, 
-					indicesCountArray[i],
-					GL_UNSIGNED_SHORT,
-					indicesOffsetArray[i]);
-			}
-#endif
-		}
-	}
-	else
-	{
-		glDrawElementsInstanced(
-			GL_TRIANGLES, 
-			indicesCountArray[0], 
-			GL_UNSIGNED_SHORT, 
-			0, 
-			instancesCount);
-	}
+	// Draw call
+	Drawcall dc;
+
+	dc.primitiveType = GL_TRIANGLES;
+	dc.indicesType = GL_UNSIGNED_SHORT;
+	dc.instancesCount = instancesCount;
+	dc.indicesCountArray = (GLsizei*)indicesCountArray;
+	dc.offsetsArray = indicesOffsetArray;
+	dc.primCount = primCount;
+
+	dc.enque();
 }
 
 }  // end namespace anki

+ 2 - 1
src/renderer/Ez.cpp

@@ -35,7 +35,8 @@ void Ez::run()
 		it != vi.getRenderablesEnd() && count < maxObjectsToDraw; ++it)
 	{
 		r->getSceneDrawer().render(cam, RenderableDrawer::RS_MATERIAL,
-			0, *(*it).node, (*it).subSpatialsMask);
+			0, *(*it).node, (*it).subSpatialIndices, 
+			(*it).subSpatialIndicesCount);
 		++count;
 	}
 

+ 1 - 1
src/renderer/Ms.cpp

@@ -80,7 +80,7 @@ void Ms::run()
 	{
 		r->getSceneDrawer().render(r->getSceneGraph().getActiveCamera(),
 			RenderableDrawer::RS_MATERIAL, 0, *(*it).node, 
-			(*it).subSpatialsMask);
+			(*it).subSpatialIndices, (*it).subSpatialIndicesCount);
 	}
 
 	// restore depth

+ 2 - 1
src/renderer/Sm.cpp

@@ -207,7 +207,8 @@ Sm::Shadowmap* Sm::doLight(Light& light)
 	for(auto it = vi.getRenderablesBegin(); it != vi.getRenderablesEnd(); ++it)
 	{
 		r->getSceneDrawer().render(light, RenderableDrawer::RS_MATERIAL,
-			1, *(*it).node, (*it).subSpatialsMask);
+			1, *(*it).node, (*it).subSpatialIndices, 
+			(*it).subSpatialIndicesCount);
 	}
 
 	return &sm;

+ 1 - 1
src/resource/Mesh.cpp

@@ -241,7 +241,7 @@ void BucketMesh::load(const char* filename)
 			if(i != 0)
 			{
 				// Sanity check
-				if(i > MAX_SUB_MESHES)
+				if(i > ANKI_MAX_MULTIDRAW_PRIMITIVES)
 				{
 					throw ANKI_EXCEPTION("Max number of submeshes exceeded");
 				}

+ 29 - 18
src/resource/Model.cpp

@@ -106,8 +106,10 @@ void ModelPatchBase::getRenderingData(const PassLevelKey& key, const Vao*& vao,
 
 //==============================================================================
 void ModelPatchBase::getRenderingDataSub(const PassLevelKey& key,
-	U64 subMeshesMask, const Vao*& vao, const ShaderProgram*& prog,
-	U32* indicesCountArray, void** indicesOffsetArray, U32& primcount) const
+	const Vao*& vao, const ShaderProgram*& prog, 
+	const U32* subMeshIndexArray, U subMeshIndexCount,
+	U32* indicesCountArray, const void** indicesOffsetArray, 
+	U32& primcount) const
 {
 	const U meshLods = getMeshesCount();
 	ANKI_ASSERT(meshLods > 0);
@@ -117,10 +119,10 @@ void ModelPatchBase::getRenderingDataSub(const PassLevelKey& key,
 	// VAO
 	U lodsCount = std::max(meshLods, mtlLods);
 
-	U index = key.pass + std::min((U)key.level, lodsCount - 1) * lodsCount;
+	U vaoindex = key.pass + std::min((U)key.level, lodsCount - 1) * lodsCount;
 
-	ANKI_ASSERT(index < modelPatchProtected.vaos.size());
-	vao = &modelPatchProtected.vaos[index];
+	ANKI_ASSERT(vaoindex < modelPatchProtected.vaos.size());
+	vao = &modelPatchProtected.vaos[vaoindex];
 
 	// Prog
 	PassLevelKey mtlKey;
@@ -136,28 +138,37 @@ void ModelPatchBase::getRenderingDataSub(const PassLevelKey& key,
 
 	const MeshBase& meshBase = getMeshBase(meshKey);
 
-	U subMeshesCount = meshBase.getSubMeshesCount();
+	ANKI_ASSERT(subMeshIndexCount <= meshBase.getSubMeshesCount());
+
 	primcount = 0;
-	for(U i = 0; i < subMeshesCount; i++)
+	I prevIndex = -1;
+	for(U i = 0; i < subMeshIndexCount; i++)
 	{
-		if(subMeshesMask & (1 << i))
+		I index = (I)subMeshIndexArray[i];
+	
+		// Check if we can merge with the previous submesh
+		if(index > 0 && (index - 1) == prevIndex)
+		{
+			ANKI_ASSERT(primcount > 0);
+
+			// increase the indices count, leave offset alone
+			U32 offset;
+			indicesCountArray[primcount - 1] +=
+				meshBase.getIndicesCountSub((U)index, offset);
+		}
+		else
 		{
-			U32 tmp;
+			U32 offset;
 			indicesCountArray[primcount] =
-				meshBase.getIndicesCountSub(i, tmp);
+				meshBase.getIndicesCountSub((U)index, offset);
 
 			indicesOffsetArray[primcount] = 
-				reinterpret_cast<void*>((PtrSize)tmp);
+				reinterpret_cast<const void*>((PtrSize)offset);
+
 			++primcount;
 		}
-	}
 
-	// If all submeshes are covered by the mask then return the super mesh
-	if(primcount == subMeshesCount)
-	{
-		indicesCountArray[0] = meshBase.getIndicesCount();
-		indicesOffsetArray[0] = 0;
-		primcount = 1;
+		prevIndex = index;
 	}
 }
 

+ 49 - 33
src/scene/Visibility.cpp

@@ -52,40 +52,47 @@ struct VisibilityTestJob: ThreadJob
 			}
 
 			// Hierarchical spatial => check subspatials
-			U64 subSpatialsMask = 0;
-			U i = 0;
-			for(auto it = sp->getSubSpatialsBegin();
-				it != sp->getSubSpatialsEnd(); ++it)
+			U32* subSpatialIndices = nullptr;
+			U32 subSpatialIndicesCount = 0;
+			if(sp->getSubSpatialsCount())
 			{
-				Spatial* subsp = *it;
-	
-				if(frustumable->insideFrustum(*subsp))
+				subSpatialIndices = ANKI_NEW_ARRAY_0(
+					U32, frameAlloc, sp->getSubSpatialsCount());
+
+				U i = 0;
+				for(auto it = sp->getSubSpatialsBegin();
+					it != sp->getSubSpatialsEnd(); ++it)
 				{
-					subSpatialsMask |= 1 << i;
-					subsp->enableBits(Spatial::SF_VISIBLE_CAMERA);
+					Spatial* subsp = *it;
+		
+					if(frustumable->insideFrustum(*subsp))
+					{
+						subSpatialIndices[subSpatialIndicesCount++] = i;
+						subsp->enableBits(Spatial::SF_VISIBLE_CAMERA);
+					}
+					++i;
 				}
-				++i;
-			}
 
-			if(ANKI_UNLIKELY(i > 0 && subSpatialsMask == 0))
-			{
-				// The camera is looking something in between all submeshes
-				continue;
+				if(ANKI_UNLIKELY(subSpatialIndicesCount == 0))
+				{
+					// The camera is looking something in between all submeshes
+					continue;
+				}
 			}
 
 			// renderable
 			Renderable* r = node->getRenderable();
 			if(r)
 			{
-				visible->renderables.push_back(
-					VisibleNode(node, subSpatialsMask));
+				visible->renderables.push_back(VisibleNode(
+					node, subSpatialIndices, subSpatialIndicesCount));
 			}
 			else
 			{
 				Light* l = node->getLight();
 				if(l)
 				{
-					visible->lights.push_back(VisibleNode(node, 0));
+					visible->lights.push_back(VisibleNode(node, nullptr, 0));
 
 					if(l->getShadowEnabled() && fr)
 					{
@@ -137,24 +144,33 @@ struct VisibilityTestJob: ThreadJob
 			}
 
 			// Hierarchical spatial => check subspatials
-			U64 subSpatialsMask = 0;
-			U i = 0;
-			for(auto it = sp->getSubSpatialsBegin();
-				it != sp->getSubSpatialsEnd(); ++it)
+			U32* subSpatialIndices = nullptr;
+			U32 subSpatialIndicesCount = 0;
+			if(sp->getSubSpatialsCount())
 			{
-				Spatial* subsp = *it;
+				subSpatialIndices = ANKI_NEW_ARRAY_0(
+					U32, frameAlloc, sp->getSubSpatialsCount());
 
-				if(frustumableSn->getFrustumable()->insideFrustum(*subsp))
+				U i = 0;
+				for(auto it = sp->getSubSpatialsBegin();
+					it != sp->getSubSpatialsEnd(); ++it)
 				{
-					subSpatialsMask |= 1 << i;
-					subsp->enableBits(Spatial::SF_VISIBLE_LIGHT);
+					Spatial* subsp = *it;
+		
+					if(ref.insideFrustum(*subsp))
+					{
+						subSpatialIndices[subSpatialIndicesCount++] = i;
+						subsp->enableBits(Spatial::SF_VISIBLE_CAMERA);
+					}
+					++i;
 				}
-				++i;
-			}
 
-			if(i > 0 && subSpatialsMask == 0)
-			{
-				continue;
+				if(ANKI_UNLIKELY(subSpatialIndicesCount == 0))
+				{
+					// The camera is looking something in between all 
+					// subspatials
+					continue;
+				}
 			}
 
 			sp->enableBits(Spatial::SF_VISIBLE_LIGHT);
@@ -162,8 +178,8 @@ struct VisibilityTestJob: ThreadJob
 			Renderable* r = node->getRenderable();
 			if(r)
 			{
-				lvisible->renderables.push_back(
-					VisibleNode(node, subSpatialsMask));
+				lvisible->renderables.push_back(VisibleNode(
+					node, subSpatialIndices, subSpatialIndicesCount));
 			}
 		}
 	}

+ 1 - 1
testapp/Main.cpp

@@ -468,7 +468,7 @@ void mainLoop()
 
 		// Sleep
 		//
-#if 1
+#if 0
 		timer.stop();
 		if(timer.getElapsedTime() < AppSingleton::get().getTimerTick())
 		{

+ 197 - 42
tools/2anki/Main.cpp

@@ -11,6 +11,31 @@
 
 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;
+};
+
 //==============================================================================
 // Log and errors
 
@@ -62,24 +87,14 @@ std::string getFilename(const std::string& path)
 	return out;
 }
 
-//==============================================================================
-struct Config
-{
-	std::string inputFname;
-	std::string outDir;
-	std::string texturesAppend;
-	std::string rpath;
-	bool flipyz = false;
-};
-
 //==============================================================================
 void parseConfig(int argc, char** argv, Config& config)
 {
 	static const char* usage = R"(Usage: 2anki in_file out_dir [options]
 Options:
--texpath <string> : Append a string to the paths of textures
 -rpath <string>   : Append a string to the meshes and materials
 -flipyz           : Flip y with z (For blender exports)
+-texpath          : XXX
 )";
 
 	// Parse config
@@ -99,7 +114,7 @@ Options:
 
 			if(i < argc)
 			{
-				config.texturesAppend = argv[i] + std::string("/");
+				config.texpath = argv[i] + std::string("/");
 			}
 			else
 			{
@@ -142,13 +157,13 @@ const aiScene& load(const std::string& filename, Assimp::Importer& importer)
 {
 	LOGI("Loading file %s\n", filename.c_str());
 
-	const aiScene* scene = importer.ReadFile(filename,
-		aiProcess_Triangulate
+	const aiScene* scene = importer.ReadFile(filename, 0
+		| aiProcess_FindInstances
+		| aiProcess_Triangulate
 		| aiProcess_JoinIdenticalVertices
-		| aiProcess_SortByPType
+		//| aiProcess_SortByPType
 		| aiProcess_ImproveCacheLocality
 		| aiProcess_OptimizeMeshes
-		//| aiProcess_CalcTangentSpace
 		);
 
 	if(!scene)
@@ -369,13 +384,8 @@ void exportSkeleton(const aiMesh& mesh, const Config& config)
 }
 
 //==============================================================================
-void exportMaterial(const aiScene& scene, const aiMaterial& mtl, 
-	const Config& config)
+std::string getMaterialName(const aiMaterial& mtl)
 {
-	std::string diffTex;
-	std::string normTex;
-
-	// Find the name
 	aiString ainame;
 	std::string name;
 	if(mtl.Get(AI_MATKEY_NAME, ainame) == AI_SUCCESS)
@@ -386,6 +396,18 @@ void exportMaterial(const aiScene& scene, const aiMaterial& mtl,
 	{
 		ERROR("Material's name is missing\n");
 	}
+
+	return name;
+}
+
+//==============================================================================
+void exportMaterial(const aiScene& scene, const aiMaterial& mtl, 
+	const Config& config)
+{
+	std::string diffTex;
+	std::string normTex;
+
+	std::string name = getMaterialName(mtl);
 	
 
 	LOGI("Exporting material %s\n", name.c_str());
@@ -434,16 +456,16 @@ void exportMaterial(const aiScene& scene, const aiMaterial& mtl,
 	if(normTex.size() == 0)
 	{
 		file << replaceAllString(diffMtlStr, "%diffuseMap%", 
-			config.texturesAppend + diffTex);
+			config.texpath + diffTex);
 	}
 	else
 	{
 		std::string str;
 		
 		str = replaceAllString(diffNormMtlStr, "%diffuseMap%", 
-			config.texturesAppend + diffTex);
+			config.texpath + diffTex);
 		str = replaceAllString(str, "%normalMap%", 
-			config.texturesAppend + normTex);
+			config.texpath + normTex);
 
 		file << str;
 	}
@@ -688,30 +710,163 @@ void exportNode(
 }
 
 //==============================================================================
-void exportScene(const aiScene& scene, const Config& config)
+void visitNode(const aiNode* node, const aiScene& scene, Config& config)
+{
+	if(node == nullptr)
+	{
+		return;
+	}
+
+	// For every mesh of this node
+	for(uint32_t i = 0; i < node->mNumMeshes; i++)
+	{
+		uint32_t meshIndex = node->mMeshes[i];
+		const aiMesh& mesh = *scene.mMeshes[meshIndex];
+
+		// Is material set?
+		if(config.meshes[meshIndex].mtlIndex == 0xFFFFFFFF)
+		{
+			// Connect mesh with material
+			config.meshes[meshIndex].mtlIndex = mesh.mMaterialIndex;
+
+			// Connect material with mesh
+			config.materials[mesh.mMaterialIndex].meshIndices.push_back(
+				meshIndex);
+		}
+		else if(config.meshes[meshIndex].mtlIndex != mesh.mMaterialIndex)
+		{
+			ERROR("Previous material index conflict\n");
+		}
+
+		config.meshes[meshIndex].transforms.push_back(node->mTransformation);
+	}
+
+	// Go to children
+	for(uint32_t i = 0; i < node->mNumChildren; i++)
+	{
+		visitNode(node->mChildren[i], scene, config);
+	}
+}
+
+//==============================================================================
+void exportScene(const aiScene& scene, Config& config)
 {
 	LOGI("Exporting scene to %s\n", config.outDir.c_str());
 
-	// Open file
-	std::fstream file;
-	file.open(config.outDir + "scene.scene", std::ios::out);
+	// Get all the data
+	config.meshes.resize(scene.mNumMeshes);
+	config.materials.resize(scene.mNumMaterials);
 
-	// Write some stuff
-	file << xmlHeader << "\n";
-	file << "<scene>\n";
+	int i = 0;
+	for(Mesh& mesh : config.meshes)
+	{
+		mesh.index = i++;
+	}
+	i = 0;
+	for(Material& mtl : config.materials)
+	{
+		mtl.index = i++;
+	}
+
+	const aiNode* node = scene.mRootNode;
+
+	visitNode(node, scene, config);
+
+	// Export all meshes that are used
+	for(uint32_t i = 0; i < config.meshes.size(); i++)
+	{
+		// Check if used
+		if(config.meshes[i].transforms.size() < 1)
+		{
+			continue;
+		}
+
+		std::string name = "mesh_" + std::to_string(i);
 
-	// TODO The sectors/portals
+		for(uint32_t t = 0; t < config.meshes[i].transforms.size(); t++)
+		{
+			std::string nname = name + "_" + std::to_string(t);
+
+			exportMesh(*scene.mMeshes[i], &nname, 
+				&config.meshes[i].transforms[t], config);
+		}
+	}
 
-	// Geometry
-	std::fstream modelFile;
-	modelFile.open(config.outDir + "static_geometry.mdl", std::ios::out);
-	modelFile << xmlHeader << "\n";
-	modelFile << "<model>\n\t<modelPatches>\n";
-	exportNode(scene, scene.mRootNode, config, modelFile);
-	modelFile << "\t</modelPatches>\n</model>\n";
+	// Export materials
+	for(uint32_t i = 0; i < config.materials.size(); i++)
+	{
+		// Check if used
+		if(config.materials[i].meshIndices.size() < 1)
+		{
+			continue;
+		}
+
+		exportMaterial(scene, *scene.mMaterials[i], config);
+	}
+
+	// Write bmeshes
+	for(uint32_t mtlId = 0; mtlId < config.materials.size(); mtlId++)
+	{
+		const Material& mtl = config.materials[mtlId];
 
-	// End
-	file << "</scene>\n";
+		// Check if used
+		if(mtl.meshIndices.size() < 1)
+		{
+			continue;
+		}
+
+		std::string name = getMaterialName(*scene.mMaterials[mtlId]) + ".bmesh";
+
+		std::fstream file;
+		file.open(config.outDir + name, std::ios::out);
+
+		file << xmlHeader << "\n";
+		file << "<bucketMesh>\n";
+		file << "\t<meshes>\n";
+		
+		for(uint32_t j = 0; j < mtl.meshIndices.size(); j++)
+		{
+			uint32_t meshId = mtl.meshIndices[j];
+			const Mesh& mesh = config.meshes[meshId];
+
+			for(uint32_t k = 0; k < mesh.transforms.size(); k++)
+			{
+				file << "\t\t<mesh>" << config.rpath 
+					<< "mesh_" + std::to_string(meshId) << "_" 
+					<< std::to_string(k)
+					<< ".mesh</mesh>\n";
+			}
+		}
+
+		file << "\t</meshes>\n";
+		file << "</bucketMesh>\n";
+	}
+
+	// Create the master model
+	std::fstream file;
+	file.open(config.outDir + "static_geometry.mdl", std::ios::out);
+
+	file << xmlHeader << "\n";
+	file << "<model>\n";
+	file << "\t<modelPatches>\n";
+	for(uint32_t i = 0; i < config.materials.size(); i++)
+	{
+		// Check if used
+		if(config.materials[i].meshIndices.size() < 1)
+		{
+			continue;
+		}
+
+		file << "\t\t<modelPatch>\n";
+		file << "\t\t\t<bucketMesh>" << config.rpath 
+			<< getMaterialName(*scene.mMaterials[i]) << ".bmesh</bucketMesh>\n";
+		file << "\t\t\t<material>" << config.rpath 
+			<< getMaterialName(*scene.mMaterials[i]) 
+			<< ".mtl</material>\n";
+		file << "\t\t</modelPatch>\n";
+	}
+	file << "\t</modelPatches>\n";
+	file << "</model>\n";
 
 	LOGI("Done exporting scene!\n");
 }