Browse Source

Update the mesh exporter and mesh loader and some other bits. Still incomplete

Panagiotis Christopoulos Charitos 7 years ago
parent
commit
36fc2676fa

+ 6 - 4
shaders/Common.glsl

@@ -44,10 +44,12 @@ const uint UBO_MAX_SIZE = 16384u;
 // Common locations
 #define POSITION_LOCATION 0
 #define TEXTURE_COORDINATE_LOCATION 1
-#define NORMAL_LOCATION 2
-#define TANGENT_LOCATION 3
-#define BONE_WEIGHTS_LOCATION 4
-#define BONE_INDICES_LOCATION 5
+#define TEXTURE_COORDINATE_LOCATION_2 2
+#define NORMAL_LOCATION 3
+#define TANGENT_LOCATION 4
+#define BONE_WEIGHTS_LOCATION 5
+#define BONE_INDICES_LOCATION 6
+
 #define SCALE_LOCATION 1
 #define ALPHA_LOCATION 2
 

+ 27 - 3
src/anki/math/Functions.h

@@ -146,7 +146,7 @@ inline T toDegrees(const T rad)
 /// @param[in] to Ending value
 /// @param[in] u The percentage from the from "from" value. Values from [0.0, 1.0]
 template<typename Type>
-static Type linearInterpolate(const Type& from, const Type& to, F32 u)
+inline Type linearInterpolate(const Type& from, const Type& to, F32 u)
 {
 	return from * (1.0f - u) + to * u;
 }
@@ -156,7 +156,7 @@ static Type linearInterpolate(const Type& from, const Type& to, F32 u)
 /// @param[in] to Ending value
 /// @param[in] u The percentage from the from "from" value. Values from [0.0, 1.0]
 template<typename Type>
-static Type cosInterpolate(const Type& from, const Type& to, F32 u)
+inline Type cosInterpolate(const Type& from, const Type& to, F32 u)
 {
 	F32 u2 = (1.0f - cos<Type>(u * PI)) / 2.0f;
 	return from * (1.0f - u2) + to * u2;
@@ -169,7 +169,7 @@ static Type cosInterpolate(const Type& from, const Type& to, F32 u)
 /// @param[in] d Point d
 /// @param[in] u The percentage from the from b point to d point. Value from [0.0, 1.0]
 template<typename Type>
-static Type cubicInterpolate(const Type& a, const Type& b, const Type& c, const Type& d, F32 u)
+inline Type cubicInterpolate(const Type& a, const Type& b, const Type& c, const Type& d, F32 u)
 {
 	F32 u2 = u * u;
 	Type a0 = d - c - a + b;
@@ -179,6 +179,30 @@ static Type cubicInterpolate(const Type& a, const Type& b, const Type& c, const
 
 	return (a0 * u * u2 + a1 * u2 + a2 * u + a3);
 }
+
+/// Pack 4 color components to R10G10B10A2 SNORM format.
+inline U32 packColorToR10G10B10A2SNorm(F32 r, F32 g, F32 b, F32 a)
+{
+	union SignedR10G10B10A10
+	{
+		struct
+		{
+			I m_x : 10;
+			I m_y : 10;
+			I m_z : 10;
+			I m_w : 2;
+		} m_unpacked;
+		U32 m_packed;
+	};
+
+	SignedR10G10B10A10 out;
+	out.m_unpacked.m_x = I(round(r * 511.0f));
+	out.m_unpacked.m_y = I(round(g * 511.0f));
+	out.m_unpacked.m_z = I(round(b * 511.0f));
+	out.m_unpacked.m_w = I(round(a * 1.0f));
+
+	return out.m_packed;
+}
 /// @}
 
 } // end namespace anki

+ 16 - 0
src/anki/resource/Common.h

@@ -36,6 +36,22 @@ const U MAX_LOD_COUNT = 3;
 const U MAX_INSTANCES = 64;
 const U MAX_SUB_DRAWCALLS = 64; ///< @warning If changed don't forget to change MAX_INSTANCE_GROUPS
 const U MAX_INSTANCE_GROUPS = 7; ///< It's log2(MAX_INSTANCES) + 1
+
+/// Standard attribute locations. Should be the same as in Common.glsl.
+enum class VertexAttributeLocation : U8
+{
+	POSITION,
+	UV,
+	UV2,
+	NORMAL,
+	TANGENT,
+	COLOR,
+	BONE_WEIGHTS,
+	BONE_INDICES,
+
+	COUNT
+};
+ANKI_ENUM_ALLOW_NUMERIC_OPERATIONS(VertexAttributeLocation, inline)
 /// @}
 
 /// Deleter for ResourcePtr.

+ 141 - 151
src/anki/resource/MeshLoader.cpp

@@ -17,9 +17,6 @@ MeshLoader::MeshLoader(ResourceManager* manager)
 
 MeshLoader::~MeshLoader()
 {
-	// WARNING: Watch the order of deallocation. Reverse of the deallocation to have successful cleanups
-	m_verts.destroy(m_alloc);
-	m_indices.destroy(m_alloc);
 	m_subMeshes.destroy(m_alloc);
 }
 
@@ -28,220 +25,213 @@ Error MeshLoader::load(const ResourceFilename& filename)
 	auto& alloc = m_alloc;
 
 	// Load header
-	ResourceFilePtr file;
-	ANKI_CHECK(m_manager->getFilesystem().openFile(filename, file));
-	ANKI_CHECK(file->read(&m_header, sizeof(m_header)));
+	ANKI_CHECK(m_manager->getFilesystem().openFile(filename, m_file));
+	ANKI_CHECK(m_file->read(&m_header, sizeof(m_header)));
+	ANKI_CHECK(checkHeader());
 
-	//
-	// Check header
-	//
-	if(memcmp(&m_header.m_magic[0], "ANKIMES3", 8) != 0)
+	// Read submesh info
 	{
-		ANKI_RESOURCE_LOGE("Wrong magic word");
-		return Error::USER_DATA;
-	}
+		m_subMeshes.create(alloc, m_header.m_subMeshCount);
+		ANKI_CHECK(m_file->read(&m_subMeshes[0], m_subMeshes.getSizeInBytes()));
 
-	if(checkFormat(m_header.m_positionsFormat, "positions", true)
-		|| checkFormat(m_header.m_normalsFormat, "normals", true)
-		|| checkFormat(m_header.m_tangentsFormat, "tangents", true)
-		|| checkFormat(m_header.m_colorsFormat, "colors", false) || checkFormat(m_header.m_uvsFormat, "UVs", true)
-		|| checkFormat(m_header.m_boneWeightsFormat, "bone weights", false)
-		|| checkFormat(m_header.m_boneIndicesFormat, "bone ids", false)
-		|| checkFormat(m_header.m_indicesFormat, "indices format", true))
-	{
-		return Error::USER_DATA;
-	}
+		// Checks
+		const U32 indicesPerFace = !!(m_header.m_flags & MeshBinaryFile::Flag::QUAD) ? 4 : 3;
+		U idxSum = 0;
+		for(U i = 0; i < m_subMeshes.getSize(); i++)
+		{
+			const MeshBinaryFile::SubMesh& sm = m_subMeshes[0];
+			if(sm.m_firstIndex != idxSum || (sm.m_indexCount % indicesPerFace) != 0)
+			{
+				ANKI_RESOURCE_LOGE("Incorrect sub mesh info");
+				return Error::USER_DATA;
+			}
+
+			idxSum += sm.m_indexCount;
+		}
 
-	// Check positions
-	if(m_header.m_positionsFormat.m_components != ComponentFormat::R32G32B32
-		|| m_header.m_positionsFormat.m_transform != FormatTransform::FLOAT)
-	{
-		ANKI_RESOURCE_LOGE("Incorrect/unsupported positions format");
-		return Error::USER_DATA;
+		if(idxSum != m_header.m_totalIndexCount)
+		{
+			ANKI_RESOURCE_LOGE("Incorrect sub mesh info");
+			return Error::USER_DATA;
+		}
 	}
 
-	// Check normals
-	if(m_header.m_normalsFormat.m_components != ComponentFormat::R10G10B10A2
-		|| m_header.m_normalsFormat.m_transform != FormatTransform::SNORM)
+	// Read vert buffer info
 	{
-		ANKI_RESOURCE_LOGE("Incorrect/unsupported normals format");
-		return Error::USER_DATA;
-	}
+		U32 vertBufferMask = 0;
+		U32 vertBufferCount = 0;
+		for(const MeshBinaryFile::VertexAttribute& attrib : m_header.m_vertexAttributes)
+		{
+			if(attrib.m_format == Format::NONE)
+			{
+				continue;
+			}
 
-	// Check tangents
-	if(m_header.m_tangentsFormat.m_components != ComponentFormat::R10G10B10A2
-		|| m_header.m_tangentsFormat.m_transform != FormatTransform::SNORM)
-	{
-		ANKI_RESOURCE_LOGE("Incorrect/unsupported tangents format");
-		return Error::USER_DATA;
-	}
+			vertBufferCount = max(attrib.m_bufferBinding + 1, vertBufferCount);
+			vertBufferMask |= 1 << attrib.m_bufferBinding;
+		}
 
-	// Check colors
-	if(m_header.m_colorsFormat.m_components != ComponentFormat::NONE
-		|| m_header.m_colorsFormat.m_transform != FormatTransform::NONE)
-	{
-		ANKI_RESOURCE_LOGE("Incorrect/unsupported color format");
-		return Error::USER_DATA;
-	}
+		if(U(__builtin_popcount(vertBufferMask)) != vertBufferCount)
+		{
+			ANKI_RESOURCE_LOGE("Problem in vertex buffers");
+			return Error::USER_DATA;
+		}
 
-	// Check UVs
-	if(m_header.m_uvsFormat.m_components != ComponentFormat::R16G16
-		|| m_header.m_uvsFormat.m_transform != FormatTransform::FLOAT)
-	{
-		ANKI_RESOURCE_LOGE("Incorrect/unsupported UVs format");
-		return Error::USER_DATA;
+		m_vertBufferCount = vertBufferCount;
 	}
 
-	Bool hasBoneInfo = false;
-	if(m_header.m_boneWeightsFormat.m_components != ComponentFormat::NONE)
+	// Count and check the file size
 	{
-		// Has bone info
+		U32 totalSize = sizeof(m_header);
 
-		hasBoneInfo = true;
+		totalSize += sizeof(MeshBinaryFile::SubMesh) * m_header.m_subMeshCount;
+		totalSize += getIndexBufferSize();
 
-		// Bone weights
-		if(m_header.m_boneWeightsFormat.m_components != ComponentFormat::R8G8B8A8
-			|| m_header.m_boneWeightsFormat.m_transform != FormatTransform::UNORM)
+		for(U i = 0; i < m_vertBufferCount; ++i)
 		{
-			ANKI_RESOURCE_LOGE("Incorrect/unsupported UVs format");
-			return Error::USER_DATA;
+			totalSize += m_header.m_vertexBuffers[i].m_vertexStride * m_header.m_totalVertexCount;
 		}
 
-		// Bone indices
-		if(m_header.m_boneIndicesFormat.m_components != ComponentFormat::R16G16B16A16
-			|| m_header.m_boneIndicesFormat.m_transform != FormatTransform::UINT)
+		if(totalSize != m_file->getSize())
 		{
-			ANKI_RESOURCE_LOGE("Incorrect/unsupported UVs format");
+			ANKI_RESOURCE_LOGE("Unexpected file size");
 			return Error::USER_DATA;
 		}
 	}
-	else
-	{
-		// No bone info
 
-		// Bone weights
-		if(m_header.m_boneWeightsFormat.m_components != ComponentFormat::NONE
-			|| m_header.m_boneWeightsFormat.m_transform != FormatTransform::NONE)
-		{
-			ANKI_RESOURCE_LOGE("Incorrect/unsupported UVs format");
-			return Error::USER_DATA;
-		}
+	return Error::NONE;
+}
 
-		// Bone indices
-		if(m_header.m_boneIndicesFormat.m_components != ComponentFormat::NONE
-			|| m_header.m_boneIndicesFormat.m_transform != FormatTransform::NONE)
+Error MeshLoader::checkFormat(VertexAttributeLocation type, ConstWeakArray<Format> supportedFormats) const
+{
+	const MeshBinaryFile::VertexAttribute& attrib = m_header.m_vertexAttributes[type];
+
+	// Check format
+	Bool found = false;
+	for(Format fmt : supportedFormats)
+	{
+		if(fmt == attrib.m_format)
 		{
-			ANKI_RESOURCE_LOGE("Incorrect/unsupported UVs format");
-			return Error::USER_DATA;
+			found = true;
+			break;
 		}
 	}
 
-	// Check indices
-	U indicesPerFace = ((m_header.m_flags & Flag::QUADS) == Flag::QUADS) ? 4 : 3;
-	if(m_header.m_totalIndicesCount < indicesPerFace || (m_header.m_totalIndicesCount % indicesPerFace) != 0
-		|| m_header.m_totalIndicesCount > MAX_U16 || m_header.m_indicesFormat.m_components != ComponentFormat::R16
-		|| m_header.m_indicesFormat.m_transform != FormatTransform::UINT)
+	if(!found)
 	{
-		// Only 16bit indices are supported for now
-		ANKI_RESOURCE_LOGE("Incorrect/unsuported index info");
+		ANKI_RESOURCE_LOGE(
+			"Vertex attribute %u has unsupported format %u", U(type), U(m_header.m_vertexAttributes[type].m_format));
 		return Error::USER_DATA;
 	}
 
-	// Check other
-	if(m_header.m_totalVerticesCount == 0)
+	// Scale should be 1.0 for now
+	if(attrib.m_scale != 0.0)
 	{
-		ANKI_RESOURCE_LOGE("Incorrect/unsuported vertex count");
+		ANKI_RESOURCE_LOGE("Vertex attribute %u should have 1.0 scale", U(type));
 		return Error::USER_DATA;
 	}
 
-	if(m_header.m_uvsChannelCount != 1)
+	return Error::NONE;
+}
+
+Error MeshLoader::checkHeader() const
+{
+	const MeshBinaryFile::Header& h = m_header;
+
+	// Header
+	if(memcmp(&h.m_magic[0], MeshBinaryFile::MAGIC, 8) != 0)
 	{
-		ANKI_RESOURCE_LOGE("Incorrect/unsuported UVs channel count");
+		ANKI_RESOURCE_LOGE("Wrong magic word");
 		return Error::USER_DATA;
 	}
 
-	if(m_header.m_subMeshCount == 0)
+	// Flags
+	if((h.m_flags & MeshBinaryFile::Flag::ALL) != MeshBinaryFile::Flag::ALL)
 	{
-		ANKI_RESOURCE_LOGE("Incorrect/unsuported submesh count");
+		ANKI_RESOURCE_LOGE("Wrong header flags");
 		return Error::USER_DATA;
 	}
 
-	//
-	// Read submesh info
-	//
-	m_subMeshes.create(alloc, m_header.m_subMeshCount);
-	ANKI_CHECK(file->read(&m_subMeshes[0], m_subMeshes.getSizeInBytes()));
+	// Attributes
+	ANKI_CHECK(checkFormat(
+		VertexAttributeLocation::POSITION, Array<Format, 2>{{Format::R16G16B16_SFLOAT, Format::R32G32B32_SFLOAT}}));
+	ANKI_CHECK(checkFormat(VertexAttributeLocation::NORMAL, Array<Format, 1>{{Format::A2B10G10R10_SNORM_PACK32}}));
+	ANKI_CHECK(checkFormat(VertexAttributeLocation::TANGENT, Array<Format, 1>{{Format::A2B10G10R10_SNORM_PACK32}}));
+	ANKI_CHECK(
+		checkFormat(VertexAttributeLocation::UV, Array<Format, 2>{{Format::R16G16_UNORM, Format::R16G16_SFLOAT}}));
+	ANKI_CHECK(checkFormat(
+		VertexAttributeLocation::BONE_INDICES, Array<Format, 2>{{Format::NONE, Format::R16G16B16A16_UINT}}));
+	ANKI_CHECK(
+		checkFormat(VertexAttributeLocation::BONE_WEIGHTS, Array<Format, 2>{{Format::NONE, Format::R8G8B8A8_UNORM}}));
 
-	// Checks
-	U idxSum = 0;
-	for(U i = 0; i < m_subMeshes.getSize(); i++)
+	// Indices format
+	if(h.m_indicesFormat != Format::R16_UINT || h.m_indicesFormat != Format::R32_UINT)
 	{
-		const SubMesh& sm = m_subMeshes[0];
-		if(sm.m_firstIndex != idxSum || sm.m_indicesCount < 3)
-		{
-			ANKI_RESOURCE_LOGE("Incorrect sub mesh info");
-			return Error::USER_DATA;
-		}
-
-		idxSum += sm.m_indicesCount;
+		ANKI_RESOURCE_LOGE("Wrong format for indices");
+		return Error::USER_DATA;
 	}
 
-	if(idxSum != m_header.m_totalIndicesCount)
+	// m_totalIndexCount
+	const U indicesPerFace = !!(h.m_flags & MeshBinaryFile::Flag::QUAD) ? 4 : 3;
+	if(h.m_totalIndexCount == 0 || (h.m_totalIndexCount % indicesPerFace) != 0)
 	{
-		ANKI_RESOURCE_LOGE("Incorrect sub mesh info");
+		ANKI_RESOURCE_LOGE("Wrong index count");
 		return Error::USER_DATA;
 	}
 
-	//
-	// Read indices
-	//
-	m_indices.create(alloc, m_header.m_totalIndicesCount * sizeof(U16));
-	ANKI_CHECK(file->read(&m_indices[0], m_indices.getSizeInBytes()));
-
-	//
-	// Read vertices
-	//
-	m_vertSize = 3 * sizeof(F32) // pos
-				 + 1 * sizeof(U32) // norm
-				 + 1 * sizeof(U32) // tang
-				 + 2 * sizeof(U16) // uvs
-				 + ((hasBoneInfo) ? (4 * sizeof(U8) + 4 * sizeof(U16)) : 0);
+	// m_totalVertexCount
+	if(h.m_totalVertexCount == 0)
+	{
+		ANKI_RESOURCE_LOGE("Wrong vertex count");
+		return Error::USER_DATA;
+	}
 
-	m_verts.create(alloc, m_header.m_totalVerticesCount * m_vertSize);
-	ANKI_CHECK(file->read(&m_verts[0], m_verts.getSizeInBytes()));
+	// m_subMeshCount
+	if(h.m_subMeshCount == 0)
+	{
+		ANKI_RESOURCE_LOGE("Wrong submesh count");
+		return Error::USER_DATA;
+	}
 
 	return Error::NONE;
 }
 
-Error MeshLoader::checkFormat(const Format& fmt, const CString& attrib, Bool cannotBeEmpty)
+Error MeshLoader::storeIndexBuffer(void* ptr, PtrSize size)
 {
-	if(fmt.m_components >= ComponentFormat::COUNT)
+	ANKI_ASSERT(isLoaded());
+	ANKI_ASSERT(size == getIndexBufferSize());
+	ANKI_ASSERT(m_loadedChunk == 0);
+
+	if(ptr)
 	{
-		ANKI_RESOURCE_LOGE("Incorrect component format for %s", &attrib[0]);
-		return Error::USER_DATA;
+		ANKI_CHECK(m_file->read(ptr, size));
 	}
-
-	if(fmt.m_transform >= FormatTransform::COUNT)
+	else
 	{
-		ANKI_RESOURCE_LOGE("Incorrect format transform for %s", &attrib[0]);
-		return Error::USER_DATA;
+		ANKI_CHECK(m_file->seek(size, ResourceFile::SeekOrigin::CURRENT));
 	}
 
-	if(cannotBeEmpty)
-	{
-		if(fmt.m_components == ComponentFormat::NONE)
-		{
-			ANKI_RESOURCE_LOGE("Format cannot be zero for %s", &attrib[0]);
-			return Error::USER_DATA;
-		}
+	++m_loadedChunk;
+	return Error::NONE;
+}
 
-		if(fmt.m_transform == FormatTransform::NONE)
-		{
-			ANKI_RESOURCE_LOGE("Transform cannot be zero for %s", &attrib[0]);
-			return Error::USER_DATA;
-		}
+Error MeshLoader::storeVertexBuffer(U32 bufferIdx, void* ptr, PtrSize size)
+{
+	ANKI_ASSERT(isLoaded());
+	ANKI_ASSERT(bufferIdx < m_vertBufferCount);
+	ANKI_ASSERT(size == m_header.m_vertexBuffers[bufferIdx].m_vertexStride * m_header.m_totalVertexCount);
+	ANKI_ASSERT(m_loadedChunk == bufferIdx + 1);
+
+	if(ptr)
+	{
+		ANKI_CHECK(m_file->read(ptr, size));
+	}
+	else
+	{
+		ANKI_CHECK(m_file->seek(size, ResourceFile::SeekOrigin::CURRENT));
 	}
 
+	++m_loadedChunk;
 	return Error::NONE;
 }
 

+ 27 - 141
src/anki/resource/MeshLoader.h

@@ -6,8 +6,10 @@
 #pragma once
 
 #include <anki/resource/Common.h>
+#include <anki/resource/ResourceFilesystem.h>
 #include <anki/Math.h>
 #include <anki/util/Enum.h>
+#include <anki/util/WeakArray.h>
 
 namespace anki
 {
@@ -21,25 +23,12 @@ class MeshBinaryFile
 public:
 	static constexpr const char* MAGIC = "ANKIMES4";
 
-	enum class VertexAttrubuteType : U32
-	{
-		POSITION,
-		NORMAL,
-		TANGENT,
-		COLOR,
-		UV,
-		UV2,
-		BONE_WEIGHTS,
-		BONE_INDICES,
-
-		COUNT
-	};
-	ANKI_ENUM_ALLOW_NUMERIC_OPERATIONS(VertexAttrubuteType, friend)
-
 	enum class Flag : U32
 	{
 		NONE = 0,
 		QUAD = 1 << 0,
+
+		ALL = QUAD,
 	};
 	ANKI_ENUM_ALLOW_NUMERIC_OPERATIONS(Flag, friend)
 
@@ -67,9 +56,9 @@ public:
 		char m_magic[8]; ///< Magic word.
 		Flag m_flags;
 
-		Array<VertexBuffer, U32(VertexAttrubuteType::COUNT)> m_vertexBuffers;
+		Array<VertexBuffer, U32(VertexAttributeLocation::COUNT)> m_vertexBuffers;
 
-		Array<VertexAttribute, U32(VertexAttrubuteType::COUNT)> m_vertexAttributes;
+		Array<VertexAttribute, U32(VertexAttributeLocation::COUNT)> m_vertexAttributes;
 
 		Format m_indicesFormat; ///< Can be R32_UI or R16_UI.
 
@@ -83,89 +72,6 @@ public:
 class MeshLoader
 {
 public:
-	/// Type of the components.
-	enum class ComponentFormat : U32
-	{
-		NONE,
-
-		R8,
-		R8G8,
-		R8G8B8,
-		R8G8B8A8,
-
-		R16,
-		R16G16,
-		R16G16B16,
-		R16G16B16A16,
-
-		R32,
-		R32G32,
-		R32G32B32,
-		R32G32B32A32,
-
-		R10G10B10A2,
-
-		COUNT
-	};
-
-	enum class FormatTransform : U32
-	{
-		NONE,
-
-		UNORM,
-		SNORM,
-		UINT,
-		SINT,
-		FLOAT,
-
-		COUNT
-	};
-
-	struct Format
-	{
-		ComponentFormat m_components = ComponentFormat::NONE;
-		FormatTransform m_transform = FormatTransform::NONE;
-	};
-
-	enum class Flag : U32
-	{
-		NONE = 0,
-		QUADS = 1 << 0
-	};
-	ANKI_ENUM_ALLOW_NUMERIC_OPERATIONS(Flag, friend);
-
-	struct Header
-	{
-		Array<U8, 8> m_magic; ///< Magic word.
-		U32 m_flags;
-		U32 m_flags2;
-		Format m_positionsFormat;
-		Format m_normalsFormat;
-		Format m_tangentsFormat;
-		Format m_colorsFormat; ///< Vertex color.
-		Format m_uvsFormat;
-		Format m_boneWeightsFormat;
-		Format m_boneIndicesFormat;
-		Format m_indicesFormat; ///< Vertex indices.
-
-		U32 m_totalIndicesCount;
-		U32 m_totalVerticesCount;
-		/// Number of UV sets. Eg one for normal diffuse and another for lightmaps.
-		U32 m_uvsChannelCount;
-		U32 m_subMeshCount;
-
-		U8 m_padding[32];
-	};
-
-	static_assert(sizeof(Header) == 128, "Check size of struct");
-
-	class SubMesh
-	{
-	public:
-		U32 m_firstIndex = 0;
-		U32 m_indicesCount = 0;
-	};
-
 	MeshLoader(ResourceManager* manager);
 
 	MeshLoader(ResourceManager* manager, GenericMemoryPoolAllocator<U8> alloc)
@@ -178,67 +84,47 @@ public:
 
 	ANKI_USE_RESULT Error load(const ResourceFilename& filename);
 
-	const Header& getHeader() const
-	{
-		ANKI_ASSERT(isLoaded());
-		return m_header;
-	}
-
-	const U8* getVertexData() const
-	{
-		ANKI_ASSERT(isLoaded());
-		return &m_verts[0];
-	}
+	ANKI_USE_RESULT Error storeIndexBuffer(void* ptr, PtrSize size);
 
-	PtrSize getVertexDataSize() const
-	{
-		ANKI_ASSERT(isLoaded());
-		return m_verts.getSizeInBytes();
-	}
+	ANKI_USE_RESULT Error storeVertexBuffer(U32 bufferIdx, void* ptr, PtrSize size);
 
-	PtrSize getVertexSize() const
+	const MeshBinaryFile::Header& getHeader() const
 	{
 		ANKI_ASSERT(isLoaded());
-		return m_vertSize;
-	}
-
-	const U8* getIndexData() const
-	{
-		ANKI_ASSERT(isLoaded());
-		return &m_indices[0];
-	}
-
-	PtrSize getIndexDataSize() const
-	{
-		ANKI_ASSERT(isLoaded());
-		return m_indices.getSizeInBytes();
+		return m_header;
 	}
 
 	Bool hasBoneInfo() const
 	{
 		ANKI_ASSERT(isLoaded());
-		return m_header.m_boneWeightsFormat.m_components != ComponentFormat::NONE;
+		return m_header.m_vertexAttributes[VertexAttributeLocation::BONE_INDICES].m_format != Format::NONE;
 	}
 
 private:
-	template<typename T>
-	using MDynamicArray = DynamicArray<T>;
-
 	ResourceManager* m_manager;
 	GenericMemoryPoolAllocator<U8> m_alloc;
-	Header m_header;
 
-	MDynamicArray<U8> m_verts;
-	MDynamicArray<U8> m_indices;
-	MDynamicArray<SubMesh> m_subMeshes;
-	U8 m_vertSize = 0;
+	ResourceFilePtr m_file;
+
+	MeshBinaryFile::Header m_header;
+
+	DynamicArray<MeshBinaryFile::SubMesh> m_subMeshes;
+	U32 m_vertBufferCount = 0;
+
+	U32 m_loadedChunk = 0; ///< Because the store methods need to be called in sequence.
 
 	Bool isLoaded() const
 	{
-		return m_verts.getSize() > 0;
+		return m_file.get() != nullptr;
+	}
+
+	U32 getIndexBufferSize() const
+	{
+		return m_header.m_totalIndexCount * ((m_header.m_indicesFormat == Format::R16_UINT) ? 2 : 4);
 	}
 
-	static ANKI_USE_RESULT Error checkFormat(const Format& fmt, const CString& attrib, Bool cannotBeEmpty);
+	ANKI_USE_RESULT Error checkHeader() const;
+	ANKI_USE_RESULT Error checkFormat(VertexAttributeLocation type, ConstWeakArray<Format> supportedFormats) const;
 };
 /// @}
 

+ 2 - 2
src/anki/resource/MeshResource.cpp

@@ -81,8 +81,8 @@ Error MeshResource::load(const ResourceFilename& filename, Bool async)
 	MeshLoader& loader = ctx->m_loader;
 	ANKI_CHECK(loader.load(filename));
 
-	const MeshLoader::Header& header = loader.getHeader();
-	m_indicesCount = header.m_totalIndicesCount;
+	const MeshBinaryFile::Header& header = loader.getHeader();
+	m_indicesCount = header.m_totalIndexCount;
 
 	m_vertSize = loader.getVertexSize();
 

+ 47 - 45
src/anki/resource/MeshResource.h

@@ -28,72 +28,69 @@ public:
 
 	~MeshResource();
 
-	U32 getTextureChannelsCount() const
-	{
-		return m_texChannelsCount;
-	}
+	/// Helper function for correct loading
+	Bool isCompatible(const MeshResource& other) const;
 
-	Bool hasBoneWeights() const
-	{
-		return m_weights;
-	}
+	/// Load from a mesh file
+	ANKI_USE_RESULT Error load(const ResourceFilename& filename, Bool async);
 
-	/// Used only to clone the VBO
-	U32 getVerticesCount() const
+	const Obb& getBoundingShape() const
 	{
-		return m_vertsCount;
+		return m_obb;
 	}
 
-	U32 getIndicesCount() const
+	/// Get submesh info.
+	void getSubMeshInfo(U subMeshId, U32& indexOffset, U32& indexCount, Obb* obb) const
 	{
-		return m_indicesCount;
+		const SubMesh& sm = m_subMeshes[subMeshId];
+		indexOffset = sm.m_indicesOffset;
+		indexCount = sm.m_indicesCount;
+		if(obb)
+		{
+			*obb = sm.m_obb;
+		}
 	}
 
-	const Obb& getBoundingShape() const
+	/// If returns zero then the mesh is a single uniform mesh
+	U32 getSubMeshCount() const
 	{
-		return m_obb;
+		return m_subMeshes.getSize();
 	}
 
-	/// Get indices count and offset of submesh
-	U32 getIndicesCountSub(U subMeshId, U32& offset) const
+	void getIndexBufferInfo(BufferPtr& buff, U32& buffOffset, U32& indexCount, Format& indexFormat) const
 	{
-		const SubMesh& sm = m_subMeshes[subMeshId];
-		offset = sm.m_indicesOffset;
-		return sm.m_indicesCount;
+		buff = m_indexBuff;
+		buffOffset = 0;
+		indexCount = m_indexCount;
+		indexFormat = m_indexFormat;
 	}
 
-	const Obb& getBoundingShapeSub(U subMeshId) const
+	U32 getVertexBufferCount() const
 	{
-		return m_subMeshes[subMeshId].m_obb;
+		return m_vertBuffCount;
 	}
 
-	/// If returns zero then the mesh is a single uniform mesh
-	U32 getSubMeshesCount() const
+	void getVertexBufferInfo(U32 buffIdx, BufferPtr& buff, U32& offset, U32& stride) const
 	{
-		return m_subMeshes.getSize();
+		buff = m_vertBuff;
+		// TODO
 	}
 
-	BufferPtr getVertexBuffer() const
+	void getVerteAttributeInfo(VertexAttributeLocation attrib, U32& bufferIdx, Format& format, U32& relativeOffset)
 	{
-		return m_vertBuff;
+		// TODO
 	}
 
-	BufferPtr getIndexBuffer() const
+	U32 getTextureChannelsCount() const
 	{
-		return m_indicesBuff;
+		return m_texChannelsCount;
 	}
 
-	U32 getVertexSize() const
+	Bool hasBoneWeights() const
 	{
-		return m_vertSize;
+		return m_weights;
 	}
 
-	/// Helper function for correct loading
-	Bool isCompatible(const MeshResource& other) const;
-
-	/// Load from a mesh file
-	ANKI_USE_RESULT Error load(const ResourceFilename& filename, Bool async);
-
 protected:
 	class LoadTask;
 	class LoadContext;
@@ -105,17 +102,22 @@ protected:
 		U32 m_indicesOffset;
 		Obb m_obb;
 	};
-
 	DynamicArray<SubMesh> m_subMeshes;
-	U32 m_indicesCount;
-	U32 m_vertsCount;
-	U32 m_vertSize;
-	Obb m_obb;
-	U8 m_texChannelsCount;
-	Bool8 m_weights;
 
+	// Index stuff
+	U32 m_indexCount = 0;
+	BufferPtr m_indexBuff;
+	Format m_indexFormat = Format::NONE;
+
+	// Vertex stuff
+	U32 m_vertCount = 0;
 	BufferPtr m_vertBuff;
-	BufferPtr m_indicesBuff;
+	U8 m_vertBuffCount = 0;
+	U8 m_texChannelsCount = 0;
+
+	// Other
+	Obb m_obb;
+	Bool8 m_weights = false;
 
 	static ANKI_USE_RESULT Error load(LoadContext& ctx);
 };

+ 1 - 1
src/anki/resource/ResourceFilesystem.h

@@ -86,7 +86,7 @@ public:
 
 	ANKI_USE_RESULT Error init(const ConfigSet& config, const CString& cacheDir);
 
-	/// Search the path list to find the file. Then open the file for reading.
+	/// Search the path list to find the file. Then open the file for reading. It's thread-safe.
 	ANKI_USE_RESULT Error openFile(const ResourceFilename& filename, ResourceFilePtr& file);
 
 #if !ANKI_TESTS

+ 4 - 2
src/anki/scene/ReflectionProxyNode.cpp

@@ -40,6 +40,7 @@ public:
 
 Error ReflectionProxyNode::init(const CString& proxyMesh)
 {
+#if 0
 	// Move component first
 	newComponent<MoveComponent>(this);
 
@@ -50,13 +51,13 @@ Error ReflectionProxyNode::init(const CString& proxyMesh)
 	MeshLoader loader(&getResourceManager());
 	ANKI_CHECK(loader.load(proxyMesh));
 
-	if((loader.getHeader().m_flags & MeshLoader::Flag::QUADS) == MeshLoader::Flag::NONE)
+	if(!(loader.getHeader().m_flags & MeshBinaryFile::Flag::QUAD))
 	{
 		ANKI_SCENE_LOGE("Expecting quad mesh");
 		return Error::USER_DATA;
 	}
 
-	const U indexCount = loader.getHeader().m_totalIndicesCount;
+	const U indexCount = loader.getHeader().m_totalIndexCount;
 	const U quadCount = indexCount / 4;
 	m_quadsLSpace.create(getSceneAllocator(), quadCount);
 
@@ -91,6 +92,7 @@ Error ReflectionProxyNode::init(const CString& proxyMesh)
 	m_boxWSpace = m_boxLSpace;
 
 	newComponent<SpatialComponent>(this, &m_boxWSpace);
+#endif
 
 	return Error::NONE;
 }

+ 2 - 1
tools/scene/CMakeLists.txt

@@ -1,7 +1,8 @@
 include_directories("../../thirdparty/assimp/include")
+include_directories("../../src")
 
 add_definitions("-fexceptions")
 
 add_executable(sceneimp Main.cpp Common.cpp Exporter.cpp ExporterMesh.cpp ExporterMaterial.cpp)
-target_link_libraries(sceneimp ankiassimp)
+target_link_libraries(sceneimp ankiassimp anki)
 installExecutable(sceneimp)

+ 261 - 308
tools/scene/ExporterMesh.cpp

@@ -4,205 +4,19 @@
 // http://www.anki3d.org/LICENSE
 
 #include "Exporter.h"
-#include "../../src/anki/resource/MeshLoader.h"
+#include <anki/resource/MeshLoader.h>
+#include <anki/Math.h>
 #include <cmath>
-
-enum class ComponentFormat : uint32_t
-{
-	NONE,
-
-	R8,
-	R8G8,
-	R8G8B8,
-	R8G8B8A8,
-
-	R16,
-	R16G16,
-	R16G16B16,
-	R16G16B16A16,
-
-	R32,
-	R32G32,
-	R32G32B32,
-	R32G32B32A32,
-
-	R10G10B10A2,
-
-	COUNT
-};
-
-enum class FormatTransform : uint32_t
-{
-	NONE,
-
-	UNORM,
-	SNORM,
-	UINT,
-	SINT,
-	FLOAT,
-
-	COUNT
-};
-
-struct Format
-{
-	ComponentFormat m_components = ComponentFormat::NONE;
-	FormatTransform m_transform = FormatTransform::NONE;
-};
-
-const uint32_t FLAG_QUADS = 1;
-
-struct Header
-{
-	char m_magic[8]; ///< Magic word.
-	uint32_t m_flags;
-	uint32_t m_flags2;
-	Format m_positionsFormat;
-	Format m_normalsFormat;
-	Format m_tangentsFormat;
-	Format m_colorsFormat; ///< Vertex color.
-	Format m_uvsFormat;
-	Format m_boneWeightsFormat;
-	Format m_boneIndicesFormat;
-	Format m_indicesFormat; ///< Vertex indices.
-
-	uint32_t m_totalIndicesCount;
-	uint32_t m_totalVerticesCount;
-	uint32_t m_uvsChannelCount;
-	uint32_t m_subMeshCount;
-
-	uint8_t m_padding[32];
-};
-
-struct SubMesh
-{
-	uint32_t m_firstIndex = 0;
-	uint32_t m_indicesCount = 0;
-};
-
-struct Vertex
-{
-	float m_position[3];
-	uint16_t m_uv[2];
-	uint32_t m_normal;
-	uint32_t m_tangent;
-};
-
-struct BoneVertex : Vertex
-{
-	uint8_t m_weights[4] = {0, 0, 0, 0};
-	uint16_t m_boneIndices[4] = {0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF};
-};
-
-static_assert(sizeof(BoneVertex) == 9 * 4, "Wrong size");
-
-static uint16_t toF16(float f)
-{
-	union Val32
-	{
-		int32_t i;
-		float f;
-		uint32_t u;
-	};
-
-	uint16_t out;
-	Val32 v32;
-	v32.f = f;
-	int32_t i = v32.i;
-	int32_t s = (i >> 16) & 0x00008000;
-	int32_t e = ((i >> 23) & 0x000000ff) - (127 - 15);
-	int32_t m = i & 0x007fffff;
-
-	if(e <= 0)
-	{
-		if(e < -10)
-		{
-			out = 0;
-		}
-		else
-		{
-			m = (m | 0x00800000) >> (1 - e);
-
-			if(m & 0x00001000)
-			{
-				m += 0x00002000;
-			}
-
-			out = s | (m >> 13);
-		}
-	}
-	else if(e == 0xff - (127 - 15))
-	{
-		if(m == 0)
-		{
-			out = s | 0x7c00;
-		}
-		else
-		{
-			m >>= 13;
-			out = s | 0x7c00 | m | (m == 0);
-		}
-	}
-	else
-	{
-		if(m & 0x00001000)
-		{
-			m += 0x00002000;
-
-			if(m & 0x00800000)
-			{
-				m = 0;
-				e += 1;
-			}
-		}
-
-		if(e > 30)
-		{
-			assert(0 && "Overflow");
-			out = s | 0x7c00;
-		}
-		else
-		{
-			out = s | (e << 10) | (m >> 13);
-		}
-	}
-
-	return out;
-}
-
-union SignedR10G10B10A10
-{
-	struct
-	{
-		int m_x : 10;
-		int m_y : 10;
-		int m_z : 10;
-		int m_w : 2;
-	} m_unpacked;
-	uint32_t m_packed;
-};
-
-uint32_t toR10G10B10A2Sint(float r, float g, float b, float a)
-{
-	SignedR10G10B10A10 out;
-	out.m_unpacked.m_x = int(round(r * 511.0));
-	out.m_unpacked.m_y = int(round(g * 511.0));
-	out.m_unpacked.m_z = int(round(b * 511.0));
-	out.m_unpacked.m_w = int(round(a * 1.0));
-
-	return out.m_packed;
-}
+#include <cfloat>
 
 void Exporter::exportMesh(const aiMesh& mesh, const aiMatrix4x4* transform, unsigned vertCountPerFace) const
 {
 	std::string name = mesh.mName.C_Str();
-	std::fstream file;
 	LOGI("Exporting mesh %s", name.c_str());
 
-	// Open file
-	file.open(m_outputDirectory + name + ".ankimesh", std::ios::out | std::ios::binary);
+	const bool hasBoneWeights = mesh.mNumBones > 0;
 
-	Header header;
+	anki::MeshBinaryFile::Header header;
 	memset(&header, 0, sizeof(header));
 
 	// Checks
@@ -236,87 +50,231 @@ void Exporter::exportMesh(const aiMesh& mesh, const aiMatrix4x4* transform, unsi
 		ERROR("Missing UVs");
 	}
 
-	// Write header
-	static const char* magic = "ANKIMES3";
-	memcpy(&header.m_magic, magic, 8);
-
-	if(vertCountPerFace == 4)
+	//
+	// Gather the attributes
+	//
+	struct WeightVertex
 	{
-		header.m_flags = FLAG_QUADS;
-	}
+		uint16_t m_boneIndices[4] = {0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF};
+		uint8_t m_weights[4] = {0, 0, 0, 0};
+	};
+	std::vector<WeightVertex> bweights;
 
-	header.m_positionsFormat.m_components = ComponentFormat::R32G32B32;
-	header.m_positionsFormat.m_transform = FormatTransform::FLOAT;
+	std::vector<float> positions;
 
-	header.m_normalsFormat.m_components = ComponentFormat::R10G10B10A2;
-	header.m_normalsFormat.m_transform = FormatTransform::SNORM;
+	struct NTVertex
+	{
+		float m_n[3];
+		float m_t[4];
+		float m_uv[2];
+	};
 
-	header.m_tangentsFormat.m_components = ComponentFormat::R10G10B10A2;
-	header.m_tangentsFormat.m_transform = FormatTransform::SNORM;
+	std::vector<NTVertex> ntVerts;
 
-	header.m_uvsFormat.m_components = ComponentFormat::R16G16;
-	header.m_uvsFormat.m_transform = FormatTransform::FLOAT;
+	float maxPositionDistance = 0.0; // Distance of positions from zero
+	float maxUvDistance = -FLT_MAX, minUvDistance = FLT_MAX;
 
-	header.m_indicesFormat.m_components = ComponentFormat::R16;
-	header.m_indicesFormat.m_transform = FormatTransform::UINT;
+	{
+		const aiMatrix3x3 normalMat = (transform) ? aiMatrix3x3(*transform) : aiMatrix3x3();
 
-	header.m_totalIndicesCount = mesh.mNumFaces * vertCountPerFace;
-	header.m_totalVerticesCount = mesh.mNumVertices;
-	header.m_uvsChannelCount = 1;
-	header.m_subMeshCount = 1;
+		const unsigned vertCount = mesh.mNumVertices;
 
-	const bool hasBoneWeights = mesh.mNumBones > 0;
-	if(hasBoneWeights)
-	{
-		header.m_boneIndicesFormat.m_components = ComponentFormat::R16G16B16A16;
-		header.m_boneIndicesFormat.m_transform = FormatTransform::UINT;
+		positions.resize(vertCount * 3);
+		ntVerts.resize(vertCount);
 
-		header.m_boneWeightsFormat.m_components = ComponentFormat::R8G8B8A8;
-		header.m_boneWeightsFormat.m_transform = FormatTransform::UNORM;
-	}
+		for(unsigned i = 0; i < vertCount; i++)
+		{
+			aiVector3D pos = mesh.mVertices[i];
+			aiVector3D n = mesh.mNormals[i];
+			aiVector3D t = mesh.mTangents[i];
+			aiVector3D b = mesh.mBitangents[i];
+			const aiVector3D& uv = mesh.mTextureCoords[0][i];
 
-	file.write(reinterpret_cast<char*>(&header), sizeof(header));
+			if(transform)
+			{
+				pos = (*transform) * pos;
+				n = normalMat * n;
+				t = normalMat * t;
+				b = normalMat * b;
+			}
 
-	// Gather the bone weights
-	struct VertWeights
-	{
-		float m_weights[4];
-		uint32_t m_boneIndices[4];
-		uint32_t m_boneCount = 0;
-	};
+			if(m_flipyz)
+			{
+				static const aiMatrix4x4 toLefthanded(1, 0, 0, 0, 0, 0, 1, 0, 0, -1, 0, 0, 0, 0, 0, 1);
 
-	std::vector<VertWeights> weights;
+				pos = toLefthanded * pos;
+				n = toLefthanded * n;
+				t = toLefthanded * t;
+				b = toLefthanded * b;
+			}
 
-	if(hasBoneWeights)
-	{
-		weights.resize(mesh.mNumVertices);
+			positions[i * 3 + 0] = pos.x;
+			positions[i * 3 + 1] = pos.y;
+			positions[i * 3 + 2] = pos.z;
+			maxPositionDistance = std::max<float>(maxPositionDistance, fabs(pos.x));
+			maxPositionDistance = std::max<float>(maxPositionDistance, fabs(pos.y));
+			maxPositionDistance = std::max<float>(maxPositionDistance, fabs(pos.z));
+
+			ntVerts[i].m_n[0] = n.x;
+			ntVerts[i].m_n[1] = n.y;
+			ntVerts[i].m_n[2] = n.z;
+
+			ntVerts[i].m_t[0] = t.x;
+			ntVerts[i].m_t[1] = t.y;
+			ntVerts[i].m_t[2] = t.z;
+			ntVerts[i].m_t[3] = ((n ^ t) * b < 0.0) ? 1.0 : -1.0;
+
+			ntVerts[i].m_uv[0] = uv.x;
+			ntVerts[i].m_uv[1] = uv.y;
+			maxUvDistance = std::max(maxUvDistance, std::max(uv.x, uv.y));
+			minUvDistance = std::min(minUvDistance, std::min(uv.x, uv.y));
+		}
 
-		for(unsigned i = 0; i < mesh.mNumBones; ++i)
+		if(hasBoneWeights)
 		{
-			const aiBone& bone = *mesh.mBones[i];
-			for(unsigned j = 0; j < bone.mNumWeights; ++j)
-			{
-				const aiVertexWeight& aiWeight = bone.mWeights[j];
-				assert(aiWeight.mVertexId < weights.size());
-				VertWeights& vert = weights[aiWeight.mVertexId];
+			bweights.resize(vertCount);
 
-				if(vert.m_boneCount == 4)
+			for(unsigned i = 0; i < mesh.mNumBones; ++i)
+			{
+				const aiBone& bone = *mesh.mBones[i];
+				for(unsigned j = 0; j < bone.mNumWeights; ++j)
 				{
-					ERROR("Vertex has more than 4 bone weights");
+					const aiVertexWeight& aiWeight = bone.mWeights[j];
+					assert(aiWeight.mVertexId < bweights.size());
+
+					WeightVertex& vert = bweights[aiWeight.mVertexId];
+
+					unsigned idx;
+					if(vert.m_boneIndices[0] == 0xFFFF)
+					{
+						idx = 0;
+					}
+					else if(vert.m_boneIndices[1] == 0xFFFF)
+					{
+						idx = 1;
+					}
+					else if(vert.m_boneIndices[2] == 0xFFFF)
+					{
+						idx = 2;
+					}
+					else if(vert.m_boneIndices[3] == 0xFFFF)
+					{
+						idx = 3;
+					}
+					else
+					{
+						ERROR("Vertex has more than 4 bone weights");
+					}
+
+					vert.m_boneIndices[idx] = i;
+					vert.m_weights[idx] = aiWeight.mWeight * 0xFF;
 				}
-
-				vert.m_boneIndices[vert.m_boneCount] = i;
-				vert.m_weights[vert.m_boneCount] = aiWeight.mWeight;
-				++vert.m_boneCount;
 			}
 		}
 	}
 
+	// Chose the formats of the attributes
+	{
+		// Positions
+		auto& posa = header.m_vertexAttributes[anki::MeshBinaryFile::VertexAttributeType::POSITION];
+		posa.m_bufferBinding = 0;
+		posa.m_format = (maxPositionDistance < 2.0) ? anki::Format::R16G16B16_SFLOAT : anki::Format::R32G32B32_SFLOAT;
+		posa.m_relativeOffset = 0;
+		posa.m_scale = 1.0;
+
+		// Normals
+		auto& na = header.m_vertexAttributes[anki::MeshBinaryFile::VertexAttributeType::NORMAL];
+		na.m_bufferBinding = 1;
+		na.m_format = anki::Format::A2B10G10R10_SNORM_PACK32;
+		na.m_relativeOffset = 0;
+		na.m_scale = 1.0;
+
+		// Tangents
+		auto& ta = header.m_vertexAttributes[anki::MeshBinaryFile::VertexAttributeType::TANGENT];
+		ta.m_bufferBinding = 1;
+		ta.m_format = anki::Format::A2B10G10R10_SNORM_PACK32;
+		ta.m_relativeOffset = sizeof(uint32_t);
+		ta.m_scale = 1.0;
+
+		// UVs
+		auto& uva = header.m_vertexAttributes[anki::MeshBinaryFile::VertexAttributeType::UV];
+		uva.m_bufferBinding = 1;
+		if(minUvDistance >= 0.0 && maxUvDistance <= 1.0)
+		{
+			uva.m_format = anki::Format::R16G16_UNORM;
+		}
+		else
+		{
+			uva.m_format = anki::Format::R16G16_SFLOAT;
+		}
+		uva.m_relativeOffset = sizeof(uint32_t) * 2;
+		uva.m_scale = 1.0;
+
+		// Bone weight
+		if(hasBoneWeights)
+		{
+			auto& bidxa = header.m_vertexAttributes[anki::MeshBinaryFile::VertexAttributeType::BONE_INDICES];
+			bidxa.m_bufferBinding = 2;
+			bidxa.m_format = anki::Format::R16G16B16A16_UINT;
+			bidxa.m_relativeOffset = 0;
+			bidxa.m_scale = 1.0;
+
+			auto& wa = header.m_vertexAttributes[anki::MeshBinaryFile::VertexAttributeType::BONE_WEIGHTS];
+			wa.m_bufferBinding = 2;
+			wa.m_format = anki::Format::R8G8B8A8_UNORM;
+			wa.m_relativeOffset = sizeof(uint16_t) * 4;
+			wa.m_scale = 1.0;
+		}
+	}
+
+	// Arange the attributes into vert buffers
+	{
+		// First buff has positions
+		const auto& posa = header.m_vertexAttributes[anki::MeshBinaryFile::VertexAttributeType::POSITION];
+		if(posa.m_format == anki::Format::R32G32B32_SFLOAT)
+		{
+			header.m_vertexBuffers[0].m_vertexStride = sizeof(float) * 3;
+		}
+		else
+		{
+			header.m_vertexBuffers[0].m_vertexStride = sizeof(uint16_t) * 3;
+		}
+
+		// 2nd buff has normal + tangent + texcoords
+		header.m_vertexBuffers[1].m_vertexStride = sizeof(uint32_t) * 2 + sizeof(uint16_t) * 2;
+
+		// 3rd has bone weights
+		if(hasBoneWeights)
+		{
+			header.m_vertexBuffers[2].m_vertexStride = sizeof(WeightVertex);
+		}
+	}
+
+	// Write some other header stuff
+	{
+		memcpy(&header.m_magic[0], anki::MeshBinaryFile::MAGIC, 8);
+		header.m_flags = (vertCountPerFace == 4) ? anki::MeshBinaryFile::Flag::QUAD : anki::MeshBinaryFile::Flag::NONE;
+		header.m_indicesFormat = anki::Format::R16_UINT;
+		header.m_totalIndexCount = mesh.mNumFaces * vertCountPerFace;
+		header.m_totalVertexCount = mesh.mNumVertices;
+		header.m_subMeshCount = 1;
+	}
+
+	// Open file
+	std::fstream file;
+	file.open(m_outputDirectory + name + ".ankimesh", std::ios::out | std::ios::binary);
+
+	// Write header
+	file.write(reinterpret_cast<char*>(&header), sizeof(header));
+
 	// Write sub meshes
-	SubMesh smesh;
-	smesh.m_firstIndex = 0;
-	smesh.m_indicesCount = header.m_totalIndicesCount;
-	file.write(reinterpret_cast<char*>(&smesh), sizeof(smesh));
+	{
+		anki::MeshBinaryFile::SubMesh smesh;
+		smesh.m_firstIndex = 0;
+		smesh.m_indexCount = header.m_totalIndexCount;
+
+		file.write(reinterpret_cast<char*>(&smesh), sizeof(smesh));
+	}
 
 	// Write indices
 	for(unsigned i = 0; i < mesh.mNumFaces; i++)
@@ -343,82 +301,77 @@ void Exporter::exportMesh(const aiMesh& mesh, const aiMatrix4x4* transform, unsi
 		}
 	}
 
-	// Write vertices
-	aiMatrix3x3 normalMat(*transform);
-
-	for(unsigned i = 0; i < mesh.mNumVertices; i++)
+	// Write first vert buffer
 	{
-		aiVector3D pos = mesh.mVertices[i];
-		aiVector3D n = mesh.mNormals[i];
-		aiVector3D t = mesh.mTangents[i];
-		aiVector3D b = mesh.mBitangents[i];
-		const aiVector3D& uv = mesh.mTextureCoords[0][i];
-
-		if(transform)
+		const auto& posa = header.m_vertexAttributes[anki::MeshBinaryFile::VertexAttributeType::POSITION];
+		if(posa.m_format == anki::Format::R32G32B32_SFLOAT)
 		{
-			pos = (*transform) * pos;
-			n = normalMat * n;
-			t = normalMat * t;
-			b = normalMat * b;
+			file.write(reinterpret_cast<char*>(&positions[0]), positions.size() * sizeof(positions[0]));
 		}
-
-		if(m_flipyz)
+		else if(posa.m_format == anki::Format::R16G16B16_SFLOAT)
 		{
-			static const aiMatrix4x4 toLefthanded(1, 0, 0, 0, 0, 0, 1, 0, 0, -1, 0, 0, 0, 0, 0, 1);
+			std::vector<uint16_t> pos16;
+			pos16.resize(positions.size());
 
-			pos = toLefthanded * pos;
-			n = toLefthanded * n;
-			t = toLefthanded * t;
-			b = toLefthanded * b;
-		}
+			for(unsigned i = 0; i < positions.size(); ++i)
+			{
+				pos16[i] = anki::F16(positions[i]).toU16();
+			}
 
-		BoneVertex vert;
+			file.write(reinterpret_cast<char*>(&pos16[0]), pos16.size() * sizeof(pos16[0]));
+		}
+		else
+		{
+			assert(0);
+		}
+	}
 
-		// Position
-		vert.m_position[0] = pos[0];
-		vert.m_position[1] = pos[1];
-		vert.m_position[2] = pos[2];
+	// Write 2nd vert buffer
+	{
+		struct Vert
+		{
+			uint32_t m_n;
+			uint32_t m_t;
+			uint16_t m_uv[2];
+		};
 
-		// Tex coords
-		vert.m_uv[0] = toF16(uv[0]);
-		vert.m_uv[1] = toF16(uv[1]);
+		std::vector<Vert> verts;
+		verts.resize(mesh.mNumVertices);
 
-		// Normal
-		vert.m_normal = toR10G10B10A2Sint(n[0], n[1], n[2], 0.0);
+		for(unsigned i = 0; i < mesh.mNumVertices; ++i)
+		{
+			const auto& inVert = ntVerts[i];
 
-		// Tangent
-		float w = ((n ^ t) * b < 0.0) ? 1.0 : -1.0;
-		vert.m_tangent = toR10G10B10A2Sint(t[0], t[1], t[2], w);
+			verts[i].m_n = anki::packColorToR10G10B10A2SNorm(inVert.m_n[0], inVert.m_n[1], inVert.m_n[2], 0.0);
+			verts[i].m_t =
+				anki::packColorToR10G10B10A2SNorm(inVert.m_t[0], inVert.m_t[1], inVert.m_t[2], inVert.m_t[3]);
 
-		// Write
-		if(hasBoneWeights)
-		{
-			// Normalize weights
-			float totalWeight = 0;
-			for(unsigned j = 0; j < weights[i].m_boneCount; ++j)
+			const float uv[2] = {inVert.m_uv[0], inVert.m_uv[1]};
+			const anki::Format uvfmt =
+				header.m_vertexAttributes[anki::MeshBinaryFile::VertexAttributeType::UV].m_format;
+			if(uvfmt == anki::Format::R16G16_UNORM)
 			{
-				totalWeight += weights[i].m_weights[j];
+				assert(uv[0] <= 1.0 && uv[0] >= 0.0 && uv[1] <= 1.0 && uv[1] >= 0.0);
+				verts[i].m_uv[0] = uv[0] * 0xFFFF;
+				verts[i].m_uv[1] = uv[1] * 0xFFFF;
 			}
-
-			if(totalWeight > 0.0)
+			else if(uvfmt == anki::Format::R16G16_SFLOAT)
 			{
-				for(unsigned j = 0; j < weights[i].m_boneCount; ++j)
-				{
-					weights[i].m_weights[j] /= totalWeight;
-				}
+				verts[i].m_uv[0] = anki::F16(uv[0]).toU16();
+				verts[i].m_uv[1] = anki::F16(uv[1]).toU16();
 			}
-
-			for(unsigned j = 0; j < weights[i].m_boneCount; ++j)
+			else
 			{
-				vert.m_boneIndices[j] = weights[i].m_boneIndices[j];
-				vert.m_weights[j] = weights[i].m_weights[j] * 0xFF;
+				assert(0);
 			}
-
-			file.write(reinterpret_cast<char*>(&vert), sizeof(BoneVertex));
-		}
-		else
-		{
-			file.write(reinterpret_cast<char*>(&vert), sizeof(Vertex));
 		}
+
+		file.write(reinterpret_cast<char*>(&verts[0]), verts.size() * sizeof(verts[0]));
+	}
+
+	// Write 3rd vert buffer
+	if(hasBoneWeights)
+	{
+		file.write(reinterpret_cast<char*>(&bweights[0]), bweights.size() * sizeof(bweights[0]));
 	}
 }