#include "lc_global.h"
#include "lc_meshloader.h"
#include "lc_file.h"
#include "lc_colors.h"
#include "lc_library.h"
#include "lc_application.h"
#include "lc_texture.h"
#include "lc_string.h"

static void lcCheckTexCoordsWrap(const lcVector4& Plane2, const lcVector3 (&Positions)[3], lcVector2 (&TexCoords)[3])
{
	lcVector2& TexCoords1 = TexCoords[0];
	lcVector2& TexCoords2 = TexCoords[1];
	lcVector2& TexCoords3 = TexCoords[2];

	const float u12 = fabsf(TexCoords1.x - TexCoords2.x);
	const float u13 = fabsf(TexCoords1.x - TexCoords3.x);
	const float u23 = fabsf(TexCoords2.x - TexCoords3.x);

	if (u12 < 0.5f && u13 < 0.5f && u23 < 0.5f)
		return;

	const float Dot1 = fabsf(lcDot(Plane2, lcVector4(Positions[0], 1.0f)));
	const float Dot2 = fabsf(lcDot(Plane2, lcVector4(Positions[1], 1.0f)));
	const float Dot3 = fabsf(lcDot(Plane2, lcVector4(Positions[2], 1.0f)));

	if (Dot1 > Dot2)
	{
		if (Dot1 > Dot3)
		{
			if (u12 > 0.5f)
				TexCoords2.x += TexCoords2.x < 0.5f ? 1.0f : -1.0f;

			if (u13 > 0.5f)
				TexCoords3.x += TexCoords3.x < 0.5f ? 1.0f : -1.0f;
		}
		else
		{
			if (u13 > 0.5f)
				TexCoords1.x += TexCoords1.x < 0.5f ? 1.0f : -1.0f;

			if (u23 > 0.5f)
				TexCoords2.x += TexCoords2.x < 0.5f ? 1.0f : -1.0f;
		}
	}
	else
	{
		if (Dot2 > Dot3)
		{
			if (u12 > 0.5f)
				TexCoords1.x += TexCoords1.x < 0.5f ? 1.0f : -1.0f;

			if (u23 > 0.5f)
				TexCoords3.x += TexCoords3.x < 0.5f ? 1.0f : -1.0f;
		}
		else
		{
			if (u13 > 0.5f)
				TexCoords1.x += TexCoords1.x < 0.5f ? 1.0f : -1.0f;

			if (u23 > 0.5f)
				TexCoords2.x += TexCoords2.x < 0.5f ? 1.0f : -1.0f;
		}
	}
}

static void lcCheckTexCoordsPole(const lcVector4& FrontPlane, const lcVector4& Plane2, const lcVector3(&Positions)[3], lcVector2(&TexCoords)[3])
{
	int PoleIndex;
	int EdgeIndex1, EdgeIndex2;

	if (fabsf(lcDot(lcVector4(Positions[0], 1.0f), FrontPlane)) < 0.01f && fabsf(lcDot(lcVector4(Positions[0], 1.0f), Plane2)) < 0.01f)
	{
		PoleIndex = 0;
		EdgeIndex1 = 1;
		EdgeIndex2 = 2;
	}
	else if (fabsf(lcDot(lcVector4(Positions[1], 1.0f), FrontPlane)) < 0.01f && fabsf(lcDot(lcVector4(Positions[1], 1.0f), Plane2)) < 0.01f)
	{
		PoleIndex = 1;
		EdgeIndex1 = 0;
		EdgeIndex2 = 2;
	}
	else if (fabsf(lcDot(lcVector4(Positions[2], 1.0f), FrontPlane)) < 0.01f && fabsf(lcDot(lcVector4(Positions[2], 1.0f), Plane2)) < 0.01f)
	{
		PoleIndex = 2;
		EdgeIndex1 = 0;
		EdgeIndex2 = 1;
	}
	else
		return;

	const lcVector3 OppositeEdge = Positions[EdgeIndex2] - Positions[EdgeIndex1];
	const lcVector3 SideEdge = Positions[PoleIndex] - Positions[EdgeIndex1];

	const float OppositeLength = lcLength(OppositeEdge);
	const float Projection = lcDot(OppositeEdge, SideEdge) / (OppositeLength * OppositeLength);

	TexCoords[PoleIndex].x = TexCoords[EdgeIndex1].x + (TexCoords[EdgeIndex2].x - TexCoords[EdgeIndex1].x) * Projection;
}

static void lcResequenceQuad(int* Indices, int a, int b, int c, int d)
{
	Indices[0] = a;
	Indices[1] = b;
	Indices[2] = c;
	Indices[3] = d;
}

static void lcTestQuad(int (&TriangleIndices)[2][3], const lcVector3 (&Vertices)[4])
{
	const lcVector3 v01 = Vertices[1] - Vertices[0];
	const lcVector3 v02 = Vertices[2] - Vertices[0];
	const lcVector3 v03 = Vertices[3] - Vertices[0];
	const lcVector3 cp1 = lcCross(v01, v02);
	const lcVector3 cp2 = lcCross(v02, v03);

	if (lcDot(cp1, cp2) > 0.0f)
		return;

	const lcVector3 v12 = Vertices[2] - Vertices[1];
	const lcVector3 v13 = Vertices[3] - Vertices[1];
	const lcVector3 v23 = Vertices[3] - Vertices[2];
	int QuadIndices[4] = { 0, 1, 2, 3 };

	if (lcDot(lcCross(v12, v01), lcCross(v01, v13)) > 0.0f)
	{
		if (-lcDot(lcCross(v02, v12), lcCross(v12, v23)) > 0.0f)
			lcResequenceQuad(QuadIndices, 1, 2, 3, 0);
		else
			lcResequenceQuad(QuadIndices, 0, 3, 1, 2);
	}
	else
	{
		if (-lcDot(lcCross(v02, v12), lcCross(v12, v23)) > 0.0f)
			lcResequenceQuad(QuadIndices, 0, 1, 3, 2);
		else
			lcResequenceQuad(QuadIndices, 1, 2, 3, 0);
	}

	TriangleIndices[0][0] = QuadIndices[0];
	TriangleIndices[0][1] = QuadIndices[1];
	TriangleIndices[0][2] = QuadIndices[2];

	TriangleIndices[1][0] = QuadIndices[2];
	TriangleIndices[1][1] = QuadIndices[3];
	TriangleIndices[1][2] = QuadIndices[0];
}

constexpr float lcDistanceEpsilon = 0.01f; // Maximum value for 50591.dat

static bool lcCompareVertices(const lcVector3& Position1, const lcVector3& Position2)
{
	return fabsf(Position1.x - Position2.x) < lcDistanceEpsilon && fabsf(Position1.y - Position2.y) < lcDistanceEpsilon && fabsf(Position1.z - Position2.z) < lcDistanceEpsilon;
}

lcMeshLoaderSection* lcMeshLoaderTypeData::AddSection(lcMeshPrimitiveType PrimitiveType, lcMeshLoaderMaterial* Material)
{
	for (const std::unique_ptr<lcMeshLoaderSection>& Section : mSections)
		if (Section->mMaterial == Material && Section->mPrimitiveType == PrimitiveType)
			return Section.get();

	mSections.emplace_back(new lcMeshLoaderSection(PrimitiveType, Material));

	return mSections.back().get();
}

quint32 lcMeshLoaderTypeData::AddVertex(const lcVector3& Position, bool Optimize)
{
	if (Optimize)
	{
		for (int VertexIdx = static_cast<int>(mVertices.size()) - 1; VertexIdx >= 0; VertexIdx--)
		{
			const lcMeshLoaderVertex& Vertex = mVertices[VertexIdx];

			if (lcCompareVertices(Position, Vertex.Position))
				return VertexIdx;
		}
	}

	lcMeshLoaderVertex& Vertex = mVertices.emplace_back();

	Vertex.Position = Position;
	Vertex.Normal = lcVector3(0.0f, 0.0f, 0.0f);
	Vertex.NormalWeight = 0.0f;

	return static_cast<quint32>(mVertices.size()) - 1;
}

quint32 lcMeshLoaderTypeData::AddVertex(const lcVector3& Position, const lcVector3& Normal, float NormalWeight, bool Optimize)
{
	if (Optimize)
	{
		for (int VertexIdx = static_cast<int>(mVertices.size()) - 1; VertexIdx >= 0; VertexIdx--)
		{
			lcMeshLoaderVertex& Vertex = mVertices[VertexIdx];

			if (lcCompareVertices(Position, Vertex.Position))
			{
				if (Vertex.NormalWeight == 0.0f)
				{
					Vertex.Normal = Normal;
					Vertex.NormalWeight = NormalWeight;
					return VertexIdx;
				}
				else if (lcDot(Normal, Vertex.Normal) > 0.71f)
				{
					Vertex.Normal = lcNormalize(Vertex.Normal * Vertex.NormalWeight + Normal * NormalWeight);
					Vertex.NormalWeight += NormalWeight;
					return VertexIdx;
				}
			}
		}
	}

	lcMeshLoaderVertex& Vertex = mVertices.emplace_back();

	Vertex.Position = Position;
	Vertex.Normal = Normal;
	Vertex.NormalWeight = 1.0f;

	return static_cast<quint32>(mVertices.size()) - 1;
}

quint32 lcMeshLoaderTypeData::AddConditionalVertex(const lcVector3(&Position)[4])
{
	lcMeshLoaderConditionalVertex& Vertex = mConditionalVertices.emplace_back();

	Vertex.Position[0] = Position[0];
	Vertex.Position[1] = Position[1];
	Vertex.Position[2] = Position[2];
	Vertex.Position[3] = Position[3];

	return static_cast<quint32>(mConditionalVertices.size()) - 1;
}

void lcMeshLoaderTypeData::ProcessLine(int LineType, lcMeshLoaderMaterial* Material, bool WindingCCW, lcVector3 (&Vertices)[4], bool Optimize)
{
	constexpr lcMeshPrimitiveType PrimitiveTypes[4] = { LC_MESH_LINES, LC_MESH_TRIANGLES, LC_MESH_TRIANGLES, LC_MESH_CONDITIONAL_LINES };
	lcMeshPrimitiveType PrimitiveType = PrimitiveTypes[LineType - 2];

	if (Material->Type != lcMeshLoaderMaterialType::Solid && PrimitiveType == LC_MESH_TRIANGLES)
		PrimitiveType = LC_MESH_TEXTURED_TRIANGLES;

	lcMeshLoaderSection* Section = AddSection(PrimitiveType, Material);

	if (LineType == 3 || LineType == 4)
	{
		int TriangleIndices[2][3] = { { 0, 1, 2 }, { 2, 3, 0 } };

		if (LineType == 4)
			lcTestQuad(TriangleIndices, Vertices);

		lcVector3 Normal = lcNormalize(lcCross(Vertices[1] - Vertices[0], Vertices[2] - Vertices[0]));

		if (!WindingCCW)
			Normal = -Normal;

		for (int TriangleIndex = 0; TriangleIndex < LineType - 2; TriangleIndex++)
		{
			const lcVector3& Vertex1 = Vertices[TriangleIndices[TriangleIndex][0]];
			const lcVector3& Vertex2 = Vertices[TriangleIndices[TriangleIndex][1]];
			const lcVector3& Vertex3 = Vertices[TriangleIndices[TriangleIndex][2]];
			const lcVector3 Edge1 = lcNormalize(Vertex2 - Vertex1);
			const lcVector3 Edge2 = lcNormalize(Vertex3 - Vertex1);
			const lcVector3 Edge3 = lcNormalize(Vertex3 - Vertex2);
			const float Angle1 = acosf(lcDot(Edge1, Edge2));
			const float Angle2 = acosf(lcDot(-Edge1, Edge3));
			const float Angle3 = LC_PI - Angle1 - Angle2;

			int Indices[3];

			Indices[0] = AddVertex(Vertex1, Normal, Angle1, Optimize);
			Indices[1] = AddVertex(Vertex2, Normal, Angle2, Optimize);
			Indices[2] = AddVertex(Vertex3, Normal, Angle3, Optimize);

			if (Indices[0] != Indices[1] && Indices[0] != Indices[2] && Indices[1] != Indices[2])
			{
				if (WindingCCW)
				{
					Section->mIndices.emplace_back(Indices[0]);
					Section->mIndices.emplace_back(Indices[1]);
					Section->mIndices.emplace_back(Indices[2]);
				}
				else
				{
					Section->mIndices.emplace_back(Indices[2]);
					Section->mIndices.emplace_back(Indices[1]);
					Section->mIndices.emplace_back(Indices[0]);
				}
			}
		}
	}
	else if (LineType == 2)
	{
		int Indices[2];

		Indices[0] = AddVertex(Vertices[0], Optimize);
		Indices[1] = AddVertex(Vertices[1], Optimize);

		if (Indices[0] != Indices[1])
		{
			Section->mIndices.emplace_back(Indices[0]);
			Section->mIndices.emplace_back(Indices[1]);
		}
	}
	else if (LineType == 5)
	{
		int Indices[2];

		Indices[0] = AddConditionalVertex(Vertices);
		Section->mIndices.emplace_back(Indices[0]);

		std::swap(Vertices[0], Vertices[1]);

		Indices[1] = AddConditionalVertex(Vertices);
		Section->mIndices.emplace_back(Indices[1]);
	}
}

void lcMeshLoaderTypeData::AddMeshData(const lcMeshLoaderTypeData& Data, const lcMatrix44& Transform, quint32 CurrentColorCode, bool InvertWinding, bool InvertNormals, lcMeshLoaderTextureMap* TextureMap)
{
	const std::vector<lcMeshLoaderVertex>& DataVertices = Data.mVertices;
	std::vector<quint32> IndexRemap;
	const lcMatrix33 NormalTransform = lcMatrix33Transpose(lcMatrix33(lcMatrix44Inverse(Transform)));

	IndexRemap.reserve(DataVertices.size());
	mVertices.reserve(mVertices.size() + DataVertices.size());

	for (const lcMeshLoaderVertex& DataVertex : DataVertices)
	{
		const lcVector3 Position = lcMul31(DataVertex.Position, Transform);
		int Index;

		if (DataVertex.NormalWeight == 0.0f)
			Index = AddVertex(Position, true);
		else
		{
			lcVector3 Normal = lcNormalize(lcMul(DataVertex.Normal, NormalTransform));
			if (InvertNormals)
				Normal = -Normal;
			Index = AddVertex(Position, Normal, DataVertex.NormalWeight, true);
		}

		IndexRemap.emplace_back(Index);
	}

	mConditionalVertices.reserve(mConditionalVertices.size() + Data.mConditionalVertices.size());
	std::vector<quint32> ConditionalRemap(Data.mConditionalVertices.size());

	for (const lcMeshLoaderConditionalVertex& DataVertex : Data.mConditionalVertices)
	{
		lcVector3 Position[4];

		Position[0] = lcMul31(DataVertex.Position[0], Transform);
		Position[1] = lcMul31(DataVertex.Position[1], Transform);
		Position[2] = lcMul31(DataVertex.Position[2], Transform);
		Position[3] = lcMul31(DataVertex.Position[3], Transform);

		const int Index = AddConditionalVertex(Position);
		ConditionalRemap.emplace_back(Index);
	}

	for (const std::unique_ptr<lcMeshLoaderSection>& SrcSection : Data.mSections)
	{
		const quint32 ColorCode = SrcSection->mMaterial->Color == 16 ? CurrentColorCode : SrcSection->mMaterial->Color;
		lcMeshPrimitiveType PrimitiveType = SrcSection->mPrimitiveType;
		lcMeshLoaderSection* DstSection;

		if (SrcSection->mMaterial->Type != lcMeshLoaderMaterialType::Solid)
		{
			lcMeshLoaderTextureParams DstTextureMap;

			if (TextureMap)
				DstTextureMap = *TextureMap;
			else
				DstTextureMap = *SrcSection->mMaterial;

			for (lcVector3& Point : DstTextureMap.Points)
				Point = lcMul31(Point, Transform);

			DstSection = AddSection(PrimitiveType, mMeshData->GetTexturedMaterial(ColorCode, DstTextureMap));
		}
		else if (TextureMap && SrcSection->mPrimitiveType == LC_MESH_TRIANGLES)
		{
			PrimitiveType = LC_MESH_TEXTURED_TRIANGLES;

			DstSection = AddSection(PrimitiveType, mMeshData->GetTexturedMaterial(ColorCode, *TextureMap));
		}
		else
		{
			DstSection = AddSection(PrimitiveType, mMeshData->GetMaterial(ColorCode));
		}

		DstSection->mIndices.reserve(DstSection->mIndices.size() + SrcSection->mIndices.size());

		if (PrimitiveType == LC_MESH_CONDITIONAL_LINES)
		{
			for (const quint32 Index : SrcSection->mIndices)
				DstSection->mIndices.emplace_back(ConditionalRemap[Index]);
		}
		else if (!InvertWinding || (PrimitiveType == LC_MESH_LINES))
		{
			for (const quint32 Index : SrcSection->mIndices)
				DstSection->mIndices.emplace_back(IndexRemap[Index]);
		}
		else
		{
			for (size_t IndexIdx = 0; IndexIdx < SrcSection->mIndices.size(); IndexIdx += 3)
			{
				DstSection->mIndices.emplace_back(IndexRemap[SrcSection->mIndices[IndexIdx + 2]]);
				DstSection->mIndices.emplace_back(IndexRemap[SrcSection->mIndices[IndexIdx + 1]]);
				DstSection->mIndices.emplace_back(IndexRemap[SrcSection->mIndices[IndexIdx + 0]]);
			}
		}
	}
}

void lcMeshLoaderTypeData::AddMeshDataNoDuplicateCheck(const lcMeshLoaderTypeData& Data, const lcMatrix44& Transform, quint32 CurrentColorCode, bool InvertWinding, bool InvertNormals, lcMeshLoaderTextureMap* TextureMap)
{
	const std::vector<lcMeshLoaderVertex>& DataVertices = Data.mVertices;
	quint32 BaseIndex;
	const lcMatrix33 NormalTransform = lcMatrix33Transpose(lcMatrix33(lcMatrix44Inverse(Transform)));

	BaseIndex = static_cast<quint32>(mVertices.size());

	mVertices.reserve(mVertices.size() + DataVertices.size());

	for (const lcMeshLoaderVertex& SrcVertex : DataVertices)
	{
		lcMeshLoaderVertex& DstVertex = mVertices.emplace_back();
		DstVertex.Position = lcMul31(SrcVertex.Position, Transform);
		DstVertex.Normal = lcNormalize(lcMul(SrcVertex.Normal, NormalTransform));
		if (InvertNormals)
			DstVertex.Normal = -DstVertex.Normal;
		DstVertex.NormalWeight = SrcVertex.NormalWeight;
	}

	mConditionalVertices.reserve(mConditionalVertices.size() + Data.mConditionalVertices.size());
	const quint32 BaseConditional = static_cast<quint32>(mConditionalVertices.size());

	for (const lcMeshLoaderConditionalVertex& DataVertex : Data.mConditionalVertices)
	{
		lcMeshLoaderConditionalVertex& Vertex = mConditionalVertices.emplace_back();

		Vertex.Position[0] = lcMul31(DataVertex.Position[0], Transform);
		Vertex.Position[1] = lcMul31(DataVertex.Position[1], Transform);
		Vertex.Position[2] = lcMul31(DataVertex.Position[2], Transform);
		Vertex.Position[3] = lcMul31(DataVertex.Position[3], Transform);
	}

	for (const std::unique_ptr<lcMeshLoaderSection>& SrcSection : Data.mSections)
	{
		const quint32 ColorCode = SrcSection->mMaterial->Color == 16 ? CurrentColorCode : SrcSection->mMaterial->Color;
		lcMeshPrimitiveType PrimitiveType = SrcSection->mPrimitiveType;
		lcMeshLoaderSection* DstSection;

		if (SrcSection->mMaterial->Type != lcMeshLoaderMaterialType::Solid)
		{
			lcMeshLoaderTextureParams DstTextureMap;

			if (TextureMap)
				DstTextureMap = *TextureMap;
			else
				DstTextureMap = *SrcSection->mMaterial;

			for (lcVector3& Point : DstTextureMap.Points)
				Point = lcMul31(Point, Transform);

			DstSection = AddSection(PrimitiveType, mMeshData->GetTexturedMaterial(ColorCode, DstTextureMap));
		}
		else if (TextureMap && SrcSection->mPrimitiveType == LC_MESH_TRIANGLES)
		{
			PrimitiveType = LC_MESH_TEXTURED_TRIANGLES;

			DstSection = AddSection(PrimitiveType, mMeshData->GetTexturedMaterial(ColorCode, *TextureMap));
		}
		else
		{
			DstSection = AddSection(PrimitiveType, mMeshData->GetMaterial(ColorCode));
		}

		DstSection->mIndices.reserve(DstSection->mIndices.size() + SrcSection->mIndices.size());

		if (PrimitiveType == LC_MESH_CONDITIONAL_LINES)
		{
			for (const quint32 Index : SrcSection->mIndices)
				DstSection->mIndices.emplace_back(BaseConditional + Index);
		}
		else if (!InvertWinding || (PrimitiveType == LC_MESH_LINES))
		{
			for (const quint32 Index : SrcSection->mIndices)
				DstSection->mIndices.emplace_back(BaseIndex + Index);
		}
		else
		{
			for (size_t IndexIdx = 0; IndexIdx < SrcSection->mIndices.size(); IndexIdx += 3)
			{
				DstSection->mIndices.emplace_back(BaseIndex + SrcSection->mIndices[IndexIdx + 2]);
				DstSection->mIndices.emplace_back(BaseIndex + SrcSection->mIndices[IndexIdx + 1]);
				DstSection->mIndices.emplace_back(BaseIndex + SrcSection->mIndices[IndexIdx + 0]);
			}
		}
	}
}

void lcLibraryMeshData::AddVertices(lcMeshDataType MeshDataType, size_t VertexCount, int* BaseVertex, lcMeshLoaderVertex** VertexBuffer)
{
	std::vector<lcMeshLoaderVertex>& Vertices = mData[MeshDataType].mVertices;
	int CurrentSize = static_cast<int>(Vertices.size());

	Vertices.resize(CurrentSize + VertexCount);

	*BaseVertex = CurrentSize;
	*VertexBuffer = &Vertices[CurrentSize];
}

void lcLibraryMeshData::AddIndices(lcMeshDataType MeshDataType, lcMeshPrimitiveType PrimitiveType, quint32 ColorCode, size_t IndexCount, quint32** IndexBuffer)
{
	lcMeshLoaderSection* Section = mData[MeshDataType].AddSection(PrimitiveType, GetMaterial(ColorCode));
	std::vector<quint32>& Indices = Section->mIndices;
	const size_t CurrentSize = Indices.size();

	Indices.resize(CurrentSize + IndexCount);

	*IndexBuffer = &Indices[CurrentSize];
}

void lcLibraryMeshData::AddMeshData(const lcLibraryMeshData& Data, const lcMatrix44& Transform, quint32 CurrentColorCode, bool InvertWinding, bool InvertNormals, lcMeshLoaderTextureMap* TextureMap, lcMeshDataType OverrideDestIndex)
{
	for (int MeshDataIdx = 0; MeshDataIdx < LC_NUM_MESHDATA_TYPES; MeshDataIdx++)
	{
		const int DestIndex = OverrideDestIndex == LC_MESHDATA_SHARED ? MeshDataIdx : OverrideDestIndex;
		mData[DestIndex].AddMeshData(Data.mData[MeshDataIdx], Transform, CurrentColorCode, InvertWinding, InvertNormals, TextureMap);
	}

	mHasTextures |= (Data.mHasTextures || TextureMap);
}

void lcLibraryMeshData::AddMeshDataNoDuplicateCheck(const lcLibraryMeshData& Data, const lcMatrix44& Transform, quint32 CurrentColorCode, bool InvertWinding, bool InvertNormals, lcMeshLoaderTextureMap* TextureMap, lcMeshDataType OverrideDestIndex)
{
	for (int MeshDataIdx = 0; MeshDataIdx < LC_NUM_MESHDATA_TYPES; MeshDataIdx++)
	{
		const int DestIndex = OverrideDestIndex == LC_MESHDATA_SHARED ? MeshDataIdx : OverrideDestIndex;
		mData[DestIndex].AddMeshDataNoDuplicateCheck(Data.mData[MeshDataIdx], Transform, CurrentColorCode, InvertWinding, InvertNormals, TextureMap);
	}

	mHasTextures |= (Data.mHasTextures || TextureMap);
}

lcMeshLoaderMaterial* lcLibraryMeshData::GetMaterial(quint32 ColorCode)
{
	for (const std::unique_ptr<lcMeshLoaderMaterial>& Material : mMaterials)
		if (Material->Type == lcMeshLoaderMaterialType::Solid && Material->Color == ColorCode)
			return Material.get();

	lcMeshLoaderMaterial* Material = new lcMeshLoaderMaterial();
	mMaterials.emplace_back(Material);

	Material->Type = lcMeshLoaderMaterialType::Solid;
	Material->Color = ColorCode;

	return Material;
}

lcMeshLoaderMaterial* lcLibraryMeshData::GetTexturedMaterial(quint32 ColorCode, const lcMeshLoaderTextureParams& TextureMap)
{
	for (const std::unique_ptr<lcMeshLoaderMaterial>& Material : mMaterials)
	{
		if (Material->Type != TextureMap.Type || Material->Color != ColorCode)
			continue;

		if (strcmp(Material->Name, TextureMap.Name))
			continue;

		if (Material->Points[0] != TextureMap.Points[0] || Material->Points[1] != TextureMap.Points[1] || Material->Points[2] != TextureMap.Points[2])
			continue;

		if (Material->Type == lcMeshLoaderMaterialType::Cylindrical)
		{
			if (Material->Angles[0] != TextureMap.Angles[0])
				continue;
		}
		else if (Material->Type == lcMeshLoaderMaterialType::Spherical)
		{
			if (Material->Angles[0] != TextureMap.Angles[0] || Material->Angles[1] != TextureMap.Angles[1])
				continue;
		}

		return Material.get();
	}

	lcMeshLoaderMaterial* Material = new lcMeshLoaderMaterial();
	mMaterials.emplace_back(Material);

	Material->Type = TextureMap.Type;
	Material->Color = ColorCode;
	Material->Points[0] = TextureMap.Points[0];
	Material->Points[1] = TextureMap.Points[1];
	Material->Points[2] = TextureMap.Points[2];
	Material->Angles[0] = TextureMap.Angles[0];
	Material->Angles[1] = TextureMap.Angles[1];
	lcstrcpy(Material->Name, TextureMap.Name);

	return Material;
}

static bool lcMeshLoaderFinalSectionCompare(const lcMeshLoaderFinalSection& a, const lcMeshLoaderFinalSection& b)
{
	if (a.PrimitiveType != b.PrimitiveType)
	{
		const lcMeshPrimitiveType PrimitiveOrder[LC_MESH_NUM_PRIMITIVE_TYPES] =
		{
			LC_MESH_TRIANGLES,
			LC_MESH_TEXTURED_TRIANGLES,
			LC_MESH_LINES,
			LC_MESH_CONDITIONAL_LINES
		};

		for (int PrimitiveType = 0; PrimitiveType < LC_MESH_NUM_PRIMITIVE_TYPES; PrimitiveType++)
		{
			const lcMeshPrimitiveType Primitive = PrimitiveOrder[PrimitiveType];

			if (a.PrimitiveType == Primitive)
				return true;

			if (b.PrimitiveType == Primitive)
				return false;
		}
	}

	const bool TranslucentA = lcIsColorTranslucent(a.Color);
	const bool TranslucentB = lcIsColorTranslucent(b.Color);

	if (TranslucentA != TranslucentB)
		return !TranslucentA;

	return a.Color > b.Color;
}

quint32 lcLibraryMeshData::AddTexturedVertex(const lcVector3& Position, const lcVector3& Normal, const lcVector2& TexCoords)
{
	for (int VertexIndex = static_cast<int>(mTexturedVertices.size()) - 1; VertexIndex >= 0; VertexIndex--)
	{
		const lcMeshLoaderTexturedVertex& Vertex = mTexturedVertices[VertexIndex];

		if (Vertex.Position == Position && Vertex.Normal == Normal && Vertex.TexCoords == TexCoords)
			return VertexIndex;
	}

	lcMeshLoaderTexturedVertex& Vertex = mTexturedVertices.emplace_back();

	Vertex.Position = Position;
	Vertex.Normal = Normal;
	Vertex.TexCoords = TexCoords;

	return static_cast<quint32>(mTexturedVertices.size()) - 1;
}

void lcLibraryMeshData::GeneratePlanarTexcoords(lcMeshLoaderSection* Section, const lcMeshLoaderTypeData& Data)
{
	const lcMeshLoaderMaterial* Material = Section->mMaterial;
	lcVector4 Planes[2];

	for (int EdgeIdx = 0; EdgeIdx < 2; EdgeIdx++)
	{
		lcVector3 Normal = Material->Points[EdgeIdx + 1] - Material->Points[0];
		const float Length = lcLength(Normal);
		Normal /= Length;

		Planes[EdgeIdx].x = Normal.x / Length;
		Planes[EdgeIdx].y = Normal.y / Length;
		Planes[EdgeIdx].z = Normal.z / Length;
		Planes[EdgeIdx].w = -lcDot(Normal, Material->Points[0]) / Length;
	}

	for (quint32& Index : Section->mIndices)
	{
		const lcMeshLoaderVertex& SrcVertex = Data.mVertices[Index];

		const lcVector2 TexCoords(lcDot3(SrcVertex.Position, Planes[0]) + Planes[0].w, lcDot3(SrcVertex.Position, Planes[1]) + Planes[1].w);

		Index = AddTexturedVertex(SrcVertex.Position, SrcVertex.Normal, TexCoords);
	}
}

void lcLibraryMeshData::GenerateCylindricalTexcoords(lcMeshLoaderSection* Section, const lcMeshLoaderTypeData& Data)
{
	const lcMeshLoaderMaterial* Material = Section->mMaterial;
	const lcVector3 Up = Material->Points[0] - Material->Points[1];
	const float UpLength = lcLength(Up);
	const lcVector3 Front = lcNormalize(Material->Points[2] - Material->Points[1]);
	const lcVector3 Plane1Normal = Up / UpLength;
	const lcVector3 Plane2Normal = lcNormalize(lcCross(Front, Up));
	const lcVector4 FrontPlane = lcVector4(Front, -lcDot(Front, Material->Points[1]));
	const lcVector4 Plane1 = lcVector4(Plane1Normal, -lcDot(Plane1Normal, Material->Points[1]));
	const lcVector4 Plane2 = lcVector4(Plane2Normal, -lcDot(Plane2Normal, Material->Points[1]));
	const float Angle = 360.0f / Material->Angles[0];

	for (size_t TriangleIndex = 0; TriangleIndex < Section->mIndices.size(); TriangleIndex += 3)
	{
		const lcVector3 Positions[3] =
		{
			Data.mVertices[Section->mIndices[TriangleIndex + 0]].Position,
			Data.mVertices[Section->mIndices[TriangleIndex + 1]].Position,
			Data.mVertices[Section->mIndices[TriangleIndex + 2]].Position
		};

		lcVector2 TexCoords[3];

		for (int CornerIndex = 0; CornerIndex < 3; CornerIndex++)
		{
			const float DotPlane1 = lcDot(lcVector4(Positions[CornerIndex], 1.0f), Plane1);
			const lcVector3 PointInPlane1 = Positions[CornerIndex] - lcVector3(Plane1) * DotPlane1;
			const float DotFrontPlane = lcDot(lcVector4(PointInPlane1, 1.0f), FrontPlane);
			const float DotPlane2 = lcDot(lcVector4(PointInPlane1, 1.0f), Plane2);
			const float Angle1 = atan2f(DotPlane2, DotFrontPlane) / LC_PI * Angle;

			TexCoords[CornerIndex].x = lcClamp(0.5f + 0.5f * Angle1, 0.0f, 1.0f);
			TexCoords[CornerIndex].y = DotPlane1 / UpLength;
		}

		lcCheckTexCoordsWrap(Plane2, Positions, TexCoords);

		for (int CornerIndex = 0; CornerIndex < 3; CornerIndex++)
			Section->mIndices[TriangleIndex + CornerIndex] = AddTexturedVertex(Positions[CornerIndex], Data.mVertices[Section->mIndices[TriangleIndex + CornerIndex]].Normal, TexCoords[CornerIndex]);
	}
}

void lcLibraryMeshData::GenerateSphericalTexcoords(lcMeshLoaderSection* Section, const lcMeshLoaderTypeData& Data)
{
	const lcMeshLoaderMaterial* Material = Section->mMaterial;
	const lcVector3 Front = lcNormalize(Material->Points[1] - Material->Points[0]);
	const lcVector3 Plane1Normal = lcNormalize(lcCross(Front, Material->Points[2] - Material->Points[0]));
	const lcVector3 Plane2Normal = lcNormalize(lcCross(Plane1Normal, Front));
	const lcVector4 FrontPlane = lcVector4(Front, -lcDot(Front, Material->Points[0]));
	const lcVector3 Center = Material->Points[0];
	const lcVector4 Plane1 = lcVector4(Plane1Normal, -lcDot(Plane1Normal, Material->Points[0]));
	const lcVector4 Plane2 = lcVector4(Plane2Normal, -lcDot(Plane2Normal, Material->Points[0]));
	const float Angle1 = 360.0f / Material->Angles[0];
	const float Angle2 = 180.0f / Material->Angles[1];

	for (size_t TriangleIndex = 0; TriangleIndex < Section->mIndices.size(); TriangleIndex += 3)
	{
		const lcVector3 Positions[3] =
		{
			Data.mVertices[Section->mIndices[TriangleIndex + 0]].Position,
			Data.mVertices[Section->mIndices[TriangleIndex + 1]].Position,
			Data.mVertices[Section->mIndices[TriangleIndex + 2]].Position
		};

		lcVector2 TexCoords[3];

		for (int CornerIndex = 0; CornerIndex < 3; CornerIndex++)
		{
			const lcVector3 VertexDir = Positions[CornerIndex] - Center;

			const float DotPlane1 = lcDot(lcVector4(Positions[CornerIndex], 1.0f), Plane1);
			const lcVector3 PointInPlane1 = Positions[CornerIndex] - lcVector3(Plane1) * DotPlane1;
			const float DotFrontPlane = lcDot(lcVector4(PointInPlane1, 1.0f), FrontPlane);
			const float DotPlane2 = lcDot(lcVector4(PointInPlane1, 1.0f), Plane2);

			const float AngleX = atan2f(DotPlane2, DotFrontPlane) / LC_PI * Angle1;
			TexCoords[CornerIndex].x = 0.5f + 0.5f * AngleX;

			const float AngleY = asinf(DotPlane1 / lcLength(VertexDir)) / LC_PI * Angle2;
			TexCoords[CornerIndex].y = 0.5f - AngleY;
		}

		lcCheckTexCoordsWrap(Plane2, Positions, TexCoords);
		lcCheckTexCoordsPole(FrontPlane, Plane2, Positions, TexCoords);

		for (int CornerIndex = 0; CornerIndex < 3; CornerIndex++)
			Section->mIndices[TriangleIndex + CornerIndex] = AddTexturedVertex(Positions[CornerIndex], Data.mVertices[Section->mIndices[TriangleIndex + CornerIndex]].Normal, TexCoords[CornerIndex]);
	}
}

void lcLibraryMeshData::GenerateTexturedVertices()
{
	for (lcMeshLoaderTypeData& Data : mData)
	{
		for (const std::unique_ptr<lcMeshLoaderSection>& Section : Data.mSections)
		{
			switch (Section->mMaterial->Type)
			{
				case lcMeshLoaderMaterialType::Solid:
					break;

				case lcMeshLoaderMaterialType::Planar:
					GeneratePlanarTexcoords(Section.get(), Data);
					break;

				case lcMeshLoaderMaterialType::Cylindrical:
					GenerateCylindricalTexcoords(Section.get(), Data);
					break;

				case lcMeshLoaderMaterialType::Spherical:
					GenerateSphericalTexcoords(Section.get(), Data);
					break;
			}
		}
	}
}

lcMesh* lcLibraryMeshData::CreateMesh()
{
	lcMesh* Mesh = new lcMesh();

	int BaseVertices[LC_NUM_MESHDATA_TYPES];
	int BaseConditionalVertices[LC_NUM_MESHDATA_TYPES];
	int NumVertices = 0;
	int ConditionalVertexCount = 0;

	for (const std::unique_ptr<lcMeshLoaderMaterial>& Material : mMaterials)
		Material->Color = lcGetColorIndex(Material->Color);

	for (int MeshDataIdx = 0; MeshDataIdx < LC_NUM_MESHDATA_TYPES; MeshDataIdx++)
	{
		BaseVertices[MeshDataIdx] = NumVertices;
		NumVertices += static_cast<int>(mData[MeshDataIdx].mVertices.size());
		BaseConditionalVertices[MeshDataIdx] = ConditionalVertexCount;
		ConditionalVertexCount += static_cast<int>(mData[MeshDataIdx].mConditionalVertices.size());
	}

	if (mHasTextures)
		GenerateTexturedVertices();

	quint16 NumSections[LC_NUM_MESH_LODS];
	int NumIndices = 0;

	std::vector<lcMeshLoaderFinalSection> FinalSections[LC_NUM_MESH_LODS];

	for (int LodIdx = 0; LodIdx < LC_NUM_MESH_LODS; LodIdx++)
	{
		auto AddFinalSection = [](lcMeshLoaderSection* Section, std::vector<lcMeshLoaderFinalSection>& FinalSections)
		{
			for (const lcMeshLoaderFinalSection& FinalSection : FinalSections)
				if (FinalSection.PrimitiveType == Section->mPrimitiveType && FinalSection.Color == Section->mMaterial->Color && !strcmp(FinalSection.Name, Section->mMaterial->Name))
					return;

			lcMeshLoaderFinalSection& FinalSection = FinalSections.emplace_back();

			FinalSection.PrimitiveType = Section->mPrimitiveType;
			FinalSection.Color = Section->mMaterial->Color;
			lcstrcpy(FinalSection.Name, Section->mMaterial->Name);
		};

		for (const std::unique_ptr<lcMeshLoaderSection>& Section : mData[LC_MESHDATA_SHARED].mSections)
		{
			NumIndices += static_cast<int>(Section->mIndices.size());

			AddFinalSection(Section.get(), FinalSections[LodIdx]);
		}

		for (const std::unique_ptr<lcMeshLoaderSection>& Section : mData[LodIdx].mSections)
		{
			NumIndices += static_cast<int>(Section->mIndices.size());

			AddFinalSection(Section.get(), FinalSections[LodIdx]);
		}

		NumSections[LodIdx] = static_cast<quint16>(FinalSections[LodIdx].size());
		std::sort(FinalSections[LodIdx].begin(), FinalSections[LodIdx].end(), lcMeshLoaderFinalSectionCompare);
	}

	Mesh->Create(NumSections, NumVertices, static_cast<int>(mTexturedVertices.size()), ConditionalVertexCount, NumIndices);

	lcVertex* DstVerts = (lcVertex*)Mesh->mVertexData;

	for (const lcMeshLoaderTypeData& Data : mData)
	{
		for (const lcMeshLoaderVertex& SrcVertex : Data.mVertices)
		{
			lcVertex& DstVertex = *DstVerts++;

			DstVertex.Position = lcVector3LDrawToLeoCAD(SrcVertex.Position);
			DstVertex.Normal = lcPackNormal(lcVector3LDrawToLeoCAD(SrcVertex.Normal));
		}
	}

	lcVertexTextured* DstTexturedVerts = (lcVertexTextured*)DstVerts;

	if (mHasTextures)
	{
		for (const lcMeshLoaderTexturedVertex& SrcVertex : mTexturedVertices)
		{
			lcVertexTextured& DstVertex = *DstTexturedVerts++;

			DstVertex.Position = lcVector3LDrawToLeoCAD(SrcVertex.Position);
			DstVertex.Normal = lcPackNormal(lcVector3LDrawToLeoCAD(SrcVertex.Normal));
			DstVertex.TexCoord = SrcVertex.TexCoords;
		}
	}

	lcVertexConditional* DstConditionalVerts = (lcVertexConditional*)DstTexturedVerts;

	for (const lcMeshLoaderTypeData& Data : mData)
	{
		for (const lcMeshLoaderConditionalVertex& SrcVertex : Data.mConditionalVertices)
		{
			lcVertexConditional& DstVertex = *DstConditionalVerts++;

			DstVertex.Position1 = lcVector3LDrawToLeoCAD(SrcVertex.Position[0]);
			DstVertex.Position2 = lcVector3LDrawToLeoCAD(SrcVertex.Position[1]);
			DstVertex.Position3 = lcVector3LDrawToLeoCAD(SrcVertex.Position[2]);
			DstVertex.Position4 = lcVector3LDrawToLeoCAD(SrcVertex.Position[3]);
		}
	}

	if (Mesh->mIndexType == GL_UNSIGNED_SHORT)
		WriteSections<quint16>(Mesh, FinalSections, BaseVertices, BaseConditionalVertices);
	else
		WriteSections<quint32>(Mesh, FinalSections, BaseVertices, BaseConditionalVertices);

	if (mHasStyleStud)
		Mesh->mFlags |= lcMeshFlag::HasStyleStud;

	UpdateMeshBoundingBox(Mesh);

	return Mesh;
}

template<typename IndexType>
void lcLibraryMeshData::WriteSections(lcMesh* Mesh, const std::vector<lcMeshLoaderFinalSection> (&FinalSections)[LC_NUM_MESH_LODS], int(&BaseVertices)[LC_NUM_MESHDATA_TYPES], int(&BaseConditionalVertices)[LC_NUM_MESHDATA_TYPES])
{
	int NumIndices = 0;

	for (int LodIdx = 0; LodIdx < LC_NUM_MESH_LODS; LodIdx++)
	{
		for (size_t SectionIndex = 0; SectionIndex < FinalSections[LodIdx].size(); SectionIndex++)
		{
			const lcMeshLoaderFinalSection& FinalSection = FinalSections[LodIdx][SectionIndex];
			lcMeshSection& DstSection = Mesh->mLods[LodIdx].Sections[SectionIndex];

			DstSection.ColorIndex = FinalSection.Color;
			DstSection.PrimitiveType = FinalSection.PrimitiveType;
			DstSection.NumIndices = 0;

			if (!FinalSection.Name[0])
				DstSection.Texture = nullptr;
			else
			{
				if (mMeshLoader)
					DstSection.Texture = lcGetPiecesLibrary()->FindTexture(FinalSection.Name, mMeshLoader->mCurrentProject, mMeshLoader->mSearchProjectFolder);
				else
					DstSection.Texture = lcGetPiecesLibrary()->FindTexture(FinalSection.Name, nullptr, false);

				if (DstSection.Texture)
					DstSection.Texture->AddRef();
			}

			DstSection.IndexOffset = NumIndices * sizeof(IndexType);

			IndexType* Index = (IndexType*)Mesh->mIndexData + NumIndices;

			const auto AddSection = [&DstSection, &Index, &BaseVertices, &BaseConditionalVertices](lcMeshLoaderSection* SrcSection, lcMeshDataType SrcDataType)
			{
				switch (DstSection.PrimitiveType)
				{
					case LC_MESH_LINES:
					case LC_MESH_TRIANGLES:
					{
						const IndexType BaseVertex = BaseVertices[SrcDataType];

						for (size_t IndexIdx = 0; IndexIdx < SrcSection->mIndices.size(); IndexIdx++)
							*Index++ = BaseVertex + SrcSection->mIndices[IndexIdx];
					}
					break;

					case LC_MESH_CONDITIONAL_LINES:
					{
						const IndexType BaseVertex = BaseConditionalVertices[SrcDataType];

						for (size_t IndexIdx = 0; IndexIdx < SrcSection->mIndices.size(); IndexIdx++)
							*Index++ = BaseVertex + SrcSection->mIndices[IndexIdx];
					}
					break;

					case LC_MESH_TEXTURED_TRIANGLES:
					{
						for (size_t IndexIdx = 0; IndexIdx < SrcSection->mIndices.size(); IndexIdx++)
							*Index++ = SrcSection->mIndices[IndexIdx];
					}
					break;

					case LC_MESH_NUM_PRIMITIVE_TYPES:
						break;
				}

				DstSection.NumIndices += static_cast<int>(SrcSection->mIndices.size());
			};

			for (const std::unique_ptr<lcMeshLoaderSection>& Section : mData[LC_MESHDATA_SHARED].mSections)
				if (FinalSection.PrimitiveType == Section->mPrimitiveType && FinalSection.Color == Section->mMaterial->Color && !strcmp(FinalSection.Name, Section->mMaterial->Name))
					AddSection(Section.get(), LC_MESHDATA_SHARED);

			const lcMeshDataType MeshDataType = (LodIdx == LC_MESH_LOD_LOW) ? LC_MESHDATA_LOW : LC_MESHDATA_HIGH;

			for (const std::unique_ptr<lcMeshLoaderSection>& Section : mData[MeshDataType].mSections)
				if (FinalSection.PrimitiveType == Section->mPrimitiveType && FinalSection.Color == Section->mMaterial->Color && !strcmp(FinalSection.Name, Section->mMaterial->Name))
					AddSection(Section.get(), MeshDataType);

			if (DstSection.PrimitiveType == LC_MESH_TRIANGLES || DstSection.PrimitiveType == LC_MESH_TEXTURED_TRIANGLES)
			{
				if (DstSection.ColorIndex == gDefaultColor)
					Mesh->mFlags |= lcMeshFlag::HasDefault;
				else
				{
					if (lcIsColorTranslucent(DstSection.ColorIndex))
						Mesh->mFlags |= lcMeshFlag::HasTranslucent;
					else
						Mesh->mFlags |= lcMeshFlag::HasSolid;
				}
			}
			else
				Mesh->mFlags |= lcMeshFlag::HasLines;

			if (DstSection.PrimitiveType == LC_MESH_TEXTURED_TRIANGLES)
				Mesh->mFlags |= lcMeshFlag::HasTexture;

			NumIndices += DstSection.NumIndices;
		}
	}
}

void lcLibraryMeshData::UpdateMeshBoundingBox(lcMesh* Mesh)
{
	lcVector3 MeshMin(FLT_MAX, FLT_MAX, FLT_MAX), MeshMax(-FLT_MAX, -FLT_MAX, -FLT_MAX);
	bool UpdatedBoundingBox = false;

	for (int LodIdx = 0; LodIdx < LC_NUM_MESH_LODS; LodIdx++)
	{
		lcMeshLod& Lod = Mesh->mLods[LodIdx];

		for (int SectionIdx = 0; SectionIdx < Lod.NumSections; SectionIdx++)
		{
			lcMeshSection& Section = Lod.Sections[SectionIdx];
			lcVector3 SectionMin(FLT_MAX, FLT_MAX, FLT_MAX), SectionMax(-FLT_MAX, -FLT_MAX, -FLT_MAX);

			if (Mesh->mIndexType == GL_UNSIGNED_SHORT)
				UpdateMeshSectionBoundingBox<quint16>(Mesh, Section, SectionMin, SectionMax);
			else
				UpdateMeshSectionBoundingBox<quint32>(Mesh, Section, SectionMin, SectionMax);

			Section.BoundingBox.Max = SectionMax;
			Section.BoundingBox.Min = SectionMin;
			Section.Radius = lcLength((SectionMax - SectionMin) / 2.0f);

			if (Section.PrimitiveType == LC_MESH_TRIANGLES || Section.PrimitiveType == LC_MESH_TEXTURED_TRIANGLES)
			{
				UpdatedBoundingBox = true;
				MeshMin = lcMin(SectionMin, MeshMin);
				MeshMax = lcMax(SectionMax, MeshMax);
			}
		}
	}

	if (!UpdatedBoundingBox)
		MeshMin = MeshMax = lcVector3(0.0f, 0.0f, 0.0f);

	Mesh->mBoundingBox.Max = MeshMax;
	Mesh->mBoundingBox.Min = MeshMin;
	Mesh->mRadius = lcLength((MeshMax - MeshMin) / 2.0f);
}

template<typename IndexType>
void lcLibraryMeshData::UpdateMeshSectionBoundingBox(const lcMesh* Mesh, const lcMeshSection& Section, lcVector3& SectionMin, lcVector3& SectionMax)
{
	const IndexType* IndexBuffer = reinterpret_cast<IndexType*>(static_cast<char*>(Mesh->mIndexData) + Section.IndexOffset);

	switch (Section.PrimitiveType)
	{
		case LC_MESH_LINES:
		case LC_MESH_TRIANGLES:
		{
			const lcVertex* VertexBuffer = Mesh->GetVertexData();

			for (int Index = 0; Index < Section.NumIndices; Index++)
			{
				const lcVector3& Position = VertexBuffer[IndexBuffer[Index]].Position;
				SectionMin = lcMin(SectionMin, Position);
				SectionMax = lcMax(SectionMax, Position);
			}
		}
		break;

		case LC_MESH_CONDITIONAL_LINES:
		{
			const lcVertexConditional* VertexBuffer = Mesh->GetConditionalVertexData();

			for (int Index = 0; Index < Section.NumIndices; Index++)
			{
				const lcVector3& Position = VertexBuffer[IndexBuffer[Index]].Position1;
				SectionMin = lcMin(SectionMin, Position);
				SectionMax = lcMax(SectionMax, Position);
			}
		}
		break;

		case LC_MESH_TEXTURED_TRIANGLES:
		{
			const lcVertexTextured* VertexBuffer = Mesh->GetTexturedVertexData();

			for (int Index = 0; Index < Section.NumIndices; Index++)
			{
				const lcVector3& Position = VertexBuffer[IndexBuffer[Index]].Position;
				SectionMin = lcMin(SectionMin, Position);
				SectionMax = lcMax(SectionMax, Position);
			}
		}
		break;

		case LC_MESH_NUM_PRIMITIVE_TYPES:
			break;
	}
}

lcMeshLoader::lcMeshLoader(lcLibraryMeshData& MeshData, bool Optimize, Project* CurrentProject, bool SearchProjectFolder)
	: mCurrentProject(CurrentProject), mSearchProjectFolder(SearchProjectFolder), mMeshData(MeshData), mOptimize(Optimize)
{
	MeshData.SetMeshLoader(this);
}

bool lcMeshLoader::LoadMesh(lcFile& File, lcMeshDataType MeshDataType)
{
	return ReadMeshData(File, lcMatrix44Identity(), 16, false, MeshDataType);
}

bool lcMeshLoader::ReadMeshData(lcFile& File, const lcMatrix44& CurrentTransform, quint32 CurrentColorCode, bool InvertWinding, lcMeshDataType MeshDataType)
{
	char Buffer[1024];
	char* Line;
	bool InvertNext = false;
	bool WindingCCW = !InvertWinding;
	lcPiecesLibrary* Library = lcGetPiecesLibrary();

	while (File.ReadLine(Buffer, sizeof(Buffer)))
	{
		if (Library->ShouldCancelLoading())
			return false;

		quint32 ColorCode, ColorCodeHex;
		bool LastToken = false;
		int LineType;

		Line = Buffer;

		if (sscanf(Line, "%d", &LineType) != 1)
			continue;

		if (LineType == 0)
		{
			char* Token = Line;

			while (*Token && *Token <= 32)
				Token++;

			Token++;

			while (*Token && *Token <= 32)
				Token++;

			char* End = Token;
			while (*End && *End > 32)
				End++;

			LastToken = (*End == 0);
			*End = 0;

			if (!strcmp(Token, "!TEXMAP"))
			{
				Token += 8;

				while (*Token && *Token <= 32)
					Token++;

				End = Token;
				while (*End && *End > 32)
					End++;
				*End = 0;

				bool Start = false;
				bool Next = false;

				if (!strcmp(Token, "START"))
				{
					Token += 6;
					Start = true;
				}
				else if (!strcmp(Token, "NEXT"))
				{
					Token += 5;
					Next = true;
				}

				if (Start || Next)
				{
					while (*Token && *Token <= 32)
						Token++;

					End = Token;
					while (*End && *End > 32)
						End++;
					*End = 0;

					auto CleanTextureName = [](char* FileName)
					{
						char* Ch;
						for (Ch = FileName; *Ch; Ch++)
						{
							if (*Ch >= 'a' && *Ch <= 'z')
								*Ch = *Ch + 'A' - 'a';
							else if (*Ch == '\\')
								*Ch = '/';
						}

						if (Ch - FileName > 4)
						{
							Ch -= 4;
							if (!memcmp(Ch, ".PNG", 4))
								*Ch = 0;
						}
					};

					if (!strcmp(Token, "PLANAR"))
					{
						Token += 7;

						mTextureStack.emplace_back();
						lcMeshLoaderTextureMap& Map = mTextureStack.back();
						Map.Type = lcMeshLoaderMaterialType::Planar;

						lcVector3 (&Points)[3] = Map.Points;

						sscanf(Token, "%f %f %f %f %f %f %f %f %f %s", &Points[0].x, &Points[0].y, &Points[0].z, &Points[1].x, &Points[1].y, &Points[1].z, &Points[2].x, &Points[2].y, &Points[2].z, Map.Name);

						Points[0] = lcMul31(Points[0], CurrentTransform);
						Points[1] = lcMul31(Points[1], CurrentTransform);
						Points[2] = lcMul31(Points[2], CurrentTransform);

						CleanTextureName(Map.Name);
					}
					else if (!strcmp(Token, "CYLINDRICAL"))
					{
						Token += 12;

						mTextureStack.emplace_back();
						lcMeshLoaderTextureMap& Map = mTextureStack.back();
						Map.Type = lcMeshLoaderMaterialType::Cylindrical;

						lcVector3 (&Points)[3] = Map.Points;
						float& Angle = Map.Angles[0];

						sscanf(Token, "%f %f %f %f %f %f %f %f %f %f %s", &Points[0].x, &Points[0].y, &Points[0].z, &Points[1].x, &Points[1].y, &Points[1].z, &Points[2].x, &Points[2].y, &Points[2].z, &Angle, Map.Name);

						Points[0] = lcMul31(Points[0], CurrentTransform);
						Points[1] = lcMul31(Points[1], CurrentTransform);
						Points[2] = lcMul31(Points[2], CurrentTransform);

						CleanTextureName(Map.Name);
					}
					else if (!strcmp(Token, "SPHERICAL"))
					{
						Token += 10;

						mTextureStack.emplace_back();
						lcMeshLoaderTextureMap& Map = mTextureStack.back();
						Map.Type = lcMeshLoaderMaterialType::Spherical;

						lcVector3(&Points)[3] = Map.Points;
						float& Angle1 = Map.Angles[0];
						float& Angle2 = Map.Angles[1];

						sscanf(Token, "%f %f %f %f %f %f %f %f %f %f %f %s", &Points[0].x, &Points[0].y, &Points[0].z, &Points[1].x, &Points[1].y, &Points[1].z, &Points[2].x, &Points[2].y, &Points[2].z, &Angle1, &Angle2, Map.Name);

						Points[0] = lcMul31(Points[0], CurrentTransform);
						Points[1] = lcMul31(Points[1], CurrentTransform);
						Points[2] = lcMul31(Points[2], CurrentTransform);

						CleanTextureName(Map.Name);
					}
				}
				else if (!strcmp(Token, "FALLBACK"))
				{
					if (!mTextureStack.empty())
						mTextureStack.back().Fallback = true;
				}
				else if (!strcmp(Token, "END"))
				{
					if (!mTextureStack.empty())
						mTextureStack.pop_back();
				}

				continue;
			}
			else if (!strcmp(Token, "BFC"))
			{
				while (!LastToken)
				{
					Token = End + 1;

					while (*Token && *Token <= 32)
						Token++;

					End = Token;
					while (*End && *End > 32)
						End++;

					LastToken = (*End == 0);
					*End = 0;

					if (!strcmp(Token, "INVERTNEXT"))
						InvertNext = true;
					else if (!strcmp(Token, "CCW"))
						WindingCCW = !InvertWinding;
					else if (!strcmp(Token, "CW"))
						WindingCCW = InvertWinding;
				}
			}
			else if (!strcmp(Token, "!:"))
			{
				Token += 3;

				Line = Token;

				if (mTextureStack.empty())
					continue;
			}
			else
				continue;
		}

		if (sscanf(Line, "%d %d", &LineType, &ColorCode) != 2)
			continue;

		if (LineType < 1 || LineType > 5)
			continue;

		if (ColorCode == 0)
		{
			sscanf(Line, "%d %i", &LineType, &ColorCodeHex);

			if (ColorCode != ColorCodeHex)
				ColorCode = ColorCodeHex | LC_COLOR_DIRECT;
		}

		if (ColorCode == 16)
			ColorCode = CurrentColorCode;

		lcMeshLoaderTextureMap* TextureMap = nullptr;

		if (!mTextureStack.empty())
		{
			TextureMap = &mTextureStack.back();

			// TODO: think about a way to handle the texture fallback
//			if (TextureMap->Texture)
			{
				if (TextureMap->Fallback)
					continue;
			}
//			else
//			{
//				if (!TextureMap->Fallback)
//					continue;
//
//				TextureMap = nullptr;
//			}
		}

		int Dummy;
		lcVector3 Points[4];

		switch (LineType)
		{
		case 1:
		{
			char OriginalFileName[LC_MAXPATH];
			float fm[12];

			sscanf(Line, "%d %i %f %f %f %f %f %f %f %f %f %f %f %f %s", &LineType, &Dummy, &fm[0], &fm[1], &fm[2], &fm[3], &fm[4], &fm[5], &fm[6], &fm[7], &fm[8], &fm[9], &fm[10], &fm[11], OriginalFileName);

			char FileName[LC_MAXPATH];
			lcstrcpy(FileName, OriginalFileName);

			char* Ch;
			for (Ch = FileName; *Ch; Ch++)
			{
				if (*Ch >= 'a' && *Ch <= 'z')
					*Ch = *Ch + 'A' - 'a';
				else if (*Ch == '\\')
					*Ch = '/';
			}

			lcLibraryPrimitive* Primitive = !TextureMap ? Library->FindPrimitive(FileName) : nullptr;
			lcMatrix44 IncludeTransform(lcVector4(fm[3], fm[6], fm[9], 0.0f), lcVector4(fm[4], fm[7], fm[10], 0.0f), lcVector4(fm[5], fm[8], fm[11], 0.0f), lcVector4(fm[0], fm[1], fm[2], 1.0f));
			IncludeTransform = lcMul(IncludeTransform, CurrentTransform);
			bool Mirror = IncludeTransform.Determinant() < 0.0f;

			const auto FileCallback = [this, &IncludeTransform, &ColorCode, &Mirror, &InvertNext, &MeshDataType](lcFile& File)
			{
				ReadMeshData(File, IncludeTransform, ColorCode, Mirror ^ InvertNext, MeshDataType);
			};

			if (Primitive)
			{
				if (Primitive->mState != lcPrimitiveState::Loaded && !Library->LoadPrimitive(Primitive))
					break;

				if (Primitive->mStud)
					mMeshData.AddMeshDataNoDuplicateCheck(Primitive->mMeshData, IncludeTransform, ColorCode, Mirror ^ InvertNext, InvertNext, TextureMap, MeshDataType);
				else if (!Primitive->mSubFile)
				{
					if (mOptimize)
						mMeshData.AddMeshData(Primitive->mMeshData, IncludeTransform, ColorCode, Mirror ^ InvertNext, InvertNext, TextureMap, MeshDataType);
					else
						mMeshData.AddMeshDataNoDuplicateCheck(Primitive->mMeshData, IncludeTransform, ColorCode, Mirror ^ InvertNext, InvertNext, TextureMap, MeshDataType);
				}
				else
					Library->GetPrimitiveFile(Primitive, FileCallback);

				mMeshData.mHasStyleStud |= Primitive->mStudStyle | Primitive->mMeshData.mHasStyleStud;
			}
			else
				Library->GetPieceFile(FileName, FileCallback);
		} break;

		case 2:
			sscanf(Line, "%d %i %f %f %f %f %f %f", &LineType, &Dummy, &Points[0].x, &Points[0].y, &Points[0].z, &Points[1].x, &Points[1].y, &Points[1].z);

			Points[0] = lcMul31(Points[0], CurrentTransform);
			Points[1] = lcMul31(Points[1], CurrentTransform);

			mMeshData.mData[MeshDataType].ProcessLine(LineType, mMeshData.GetMaterial(ColorCode), WindingCCW, Points, mOptimize);
			break;

		case 3:
			sscanf(Line, "%d %i %f %f %f %f %f %f %f %f %f", &LineType, &Dummy, &Points[0].x, &Points[0].y, &Points[0].z,
				   &Points[1].x, &Points[1].y, &Points[1].z, &Points[2].x, &Points[2].y, &Points[2].z);

			Points[0] = lcMul31(Points[0], CurrentTransform);
			Points[1] = lcMul31(Points[1], CurrentTransform);
			Points[2] = lcMul31(Points[2], CurrentTransform);

			if (!TextureMap)
				mMeshData.mData[MeshDataType].ProcessLine(LineType, mMeshData.GetMaterial(ColorCode), WindingCCW, Points, mOptimize);
			else
			{
				mMeshData.mHasTextures = true;
				mMeshData.mData[MeshDataType].ProcessLine(LineType, mMeshData.GetTexturedMaterial(ColorCode, *TextureMap), WindingCCW, Points, mOptimize);

				if (TextureMap->Next)
					mTextureStack.pop_back();
			}
			break;

		case 4:
			sscanf(Line, "%d %i %f %f %f %f %f %f %f %f %f %f %f %f", &LineType, &Dummy, &Points[0].x, &Points[0].y, &Points[0].z,
				   &Points[1].x, &Points[1].y, &Points[1].z, &Points[2].x, &Points[2].y, &Points[2].z, &Points[3].x, &Points[3].y, &Points[3].z);

			Points[0] = lcMul31(Points[0], CurrentTransform);
			Points[1] = lcMul31(Points[1], CurrentTransform);
			Points[2] = lcMul31(Points[2], CurrentTransform);
			Points[3] = lcMul31(Points[3], CurrentTransform);

			if (!TextureMap)
				mMeshData.mData[MeshDataType].ProcessLine(LineType, mMeshData.GetMaterial(ColorCode), WindingCCW, Points, mOptimize);
			else
			{
				mMeshData.mHasTextures = true;
				mMeshData.mData[MeshDataType].ProcessLine(LineType, mMeshData.GetTexturedMaterial(ColorCode, *TextureMap), WindingCCW, Points, mOptimize);

				if (TextureMap->Next)
					mTextureStack.pop_back();
			}
			break;

		case 5:
			sscanf(Line, "%d %i %f %f %f %f %f %f %f %f %f %f %f %f", &LineType, &Dummy, &Points[0].x, &Points[0].y, &Points[0].z,
				   &Points[1].x, &Points[1].y, &Points[1].z, &Points[2].x, &Points[2].y, &Points[2].z, &Points[3].x, &Points[3].y, &Points[3].z);

			Points[0] = lcMul31(Points[0], CurrentTransform);
			Points[1] = lcMul31(Points[1], CurrentTransform);
			Points[2] = lcMul31(Points[2], CurrentTransform);
			Points[3] = lcMul31(Points[3], CurrentTransform);

			mMeshData.mData[MeshDataType].ProcessLine(LineType, mMeshData.GetMaterial(ColorCode), WindingCCW, Points, mOptimize);
			break;
		}

		InvertNext = false;
	}

	return true;
}
