//
// Asura_Mesh.h
//
// Created by Kevin Lea - 12/06/2001 11:15:51
// 
// Copyright (C) 2001 Rebellion, All Rights Reserved.
//

#ifdef ASURA_USE_PRAGMA_ONCE
    #pragma once
#endif

#ifndef __ASURA_MESH_H__
#define __ASURA_MESH_H__

#include "Mesh/Asura_Mesh_PolygonFlags.h"

#ifdef ASURA_TOOLS

#include "Core/Asura_Maths.h"
#include "Core/Asura_String.h"
#include "Core/Asura_Hash.h"
#include "Core/Asura_Material_System.h"//for ASURA_MATERIAL_MAXTEXTURES
#include "Core/Memory/Asura_Mem.h"
#include "Mesh/Asura_CollisionMesh.h"



class Asura_Mesh_Polygon
{
	public:
	
		// KJL - 27-Feb-01 11:05:02 - if the poly is a triangle, the fourth vertex is set to 0xffff.
		uint16 VertexID[4];
		
		Asura_Mesh_Polygon(){}
		Asura_Mesh_Polygon(uint16 v0, uint16 v1, uint16 v2)
		{
			VertexID[0] = v0;
			VertexID[1] = v1;
			VertexID[2] = v2;
			VertexID[3] = 0xffff;
		}

		Asura_Mesh_Polygon(uint16 v0, uint16 v1, uint16 v2, uint16 v3)
		{
			VertexID[0] = v0;
			VertexID[1] = v1;
			VertexID[2] = v2;
			VertexID[3] = v3;
		}

		inline bool IsQuad(void) const
		{
			return (VertexID[3]!=0xffff);
		}

		inline int NumberOfVertices(void) const
		{
			return ((VertexID[3]!=0xffff) ? 4 : 3);
			
		}
};

class Asura_Mesh_Vertex
{
	public:
		
		Asura_Vector_3 Position;
};


//Number of material response IDs per polygon in the asura mesh
#define TOOLS_NUM_MATERIALRESPONSE_PER_POLY (2)


class Asura_Mesh_TextureInfo
{
	public:

		Asura_Mesh_TextureInfo( void )
			: m_iMaterialID( ASURA_MATERIAL_NONE )
		{
			Asura_Mem::Set( U, 0, sizeof(U) );
			Asura_Mem::Set( V, 0, sizeof(V) );

			for( u_int u = 0; u < 4; ++u )
			{
				m_afAlpha[u] = 1.0f;
			}
			for(int i=0;i<TOOLS_NUM_MATERIALRESPONSE_PER_POLY;i++)
			{
				m_auMaterialResponseHashID[i] = 0;
			}
		}

		Asura_Mesh_TextureInfo(const Asura_Mesh_TextureInfo& xTex)
		{
			m_iMaterialID = xTex.m_iMaterialID;

			for(int i=0;i<ASURA_MATERIAL_MAXTEXTURES;i++)
			{
				for(int j=0;j<4;j++)
				{
					U[i][j] = xTex.U[i][j];
					V[i][j] = xTex.V[i][j];
				}
			}
			
			
			for(int i=0;i<4;i++)
			{
				m_afAlpha[i] = xTex.m_afAlpha[i];
			}
			
			for(int i=0;i<TOOLS_NUM_MATERIALRESPONSE_PER_POLY;i++)
			{
				m_auMaterialResponseHashID[i] = xTex.m_auMaterialResponseHashID[i];
			}
			
		}
        
		~Asura_Mesh_TextureInfo( void ) 
		{ 
		}

		const Asura_Mesh_TextureInfo& operator = (const Asura_Mesh_TextureInfo& xTex)
		{
			if(this == &xTex) return *this;

			m_iMaterialID = xTex.m_iMaterialID;

			for(int i=0;i<ASURA_MATERIAL_MAXTEXTURES;i++)
			{
				for(int j=0;j<4;j++)
				{
					U[i][j] = xTex.U[i][j];
					V[i][j] = xTex.V[i][j];
				}
			}
			
			for(int i=0;i<4;i++)
			{
				m_afAlpha[i] = xTex.m_afAlpha[i];
			}
			for(int i=0;i<TOOLS_NUM_MATERIALRESPONSE_PER_POLY;i++)
			{
				m_auMaterialResponseHashID[i] = xTex.m_auMaterialResponseHashID[i];
			}
			return *this;
		}

	

		int m_iMaterialID;
		Asura_Hash_ID m_auMaterialResponseHashID[TOOLS_NUM_MATERIALRESPONSE_PER_POLY];

		float U[ASURA_MATERIAL_MAXTEXTURES][4];
		float V[ASURA_MATERIAL_MAXTEXTURES][4];

		//alpha value for first texture in material when using materials with 2 textures
		float m_afAlpha[4];

};

class Asura_Mesh_Normals
{

	public:
		        

		Asura_Vector_3  FaceNormal;
		Asura_Vector_3 VertexNormal[4];

};

struct Asura_Colour_Components
{
	unsigned char R;
	unsigned char G;
	unsigned char B;
	unsigned char A;
};

class Asura_Mesh_Prelighting
{
	public:
		
		union
		{
			Asura_Colour_Components ColourElements[4];
			unsigned int Colour[4];
		};
			
};

class Asura_Mesh_PaintedColours
{
public:
	unsigned int Colour[4];
};

class Asura_Mesh_PolygonFlags
{
	public :
		int Flags;
};

class Asura_Mesh_ProjectPolygonData
{
public :
	u_int m_uData;
};


class Asura_Mesh_VertexFlags
{
public :
	u_int m_uFlags;
};


struct Asura_Mesh_MergedEntity
{
	Asura_Guid m_uGuid;
	u_int m_uChecksum;
};


struct Asura_Mesh_PolygonLight
{
	u_int m_uLightID;
	Asura_Vector_3 m_xColour;
	float m_fPower;
	float m_fAngle;
	u_int m_uAccuracy;
	bool m_bBleached;
	bool m_bShortRange;
	bool m_bUseRange;
	float m_fInnerRange;
	float m_fOuterRange;
	float m_fReflectionChance;
	u_int m_uLightSets;
};

//Used for temporary storage by the max import/export plugins
#ifdef ASURAIMPORT_EXPORTS
class Asura_Mesh_PluginData
{
public :
	Asura_Mesh_PluginData()
	:m_pxNode(0)
	,m_uRegion(0)
	,m_uReqdRegions(0)
	,m_uModuleFlags(0)
	,m_uLightSets(0xffffffff)
	,m_bMainMesh(false)
	,m_bDontExport(false)
	{
	}

	//Max node associated with this mesh
	class INode* m_pxNode;

	//Main mesh to attach this to. For platform specific meshes	
	Asura_String m_xDefaultPlatformMeshName;

	u_int m_uRegion;
	u_int m_uReqdRegions;
	u_int m_uModuleFlags;
	u_int m_uLightSets;

	bool m_bMainMesh;//Used to flag the primary mesh. ie not LOD/collision/platform mesh
	bool m_bDontExport;//For meshes that we decide shouldn't be exported after all.
};
#endif //ASURAIMPORT_EXPORTS


class Asura_Mesh
{
	public:
		Asura_Mesh( const Asura_Char* name )
		{
			VertexDataValid = false;
			PolygonDataValid = false;
			m_bFaceNormalsValid = false;
			m_bVertexNormalsValid = false;
			m_bSmoothedAcrossModules = false;
			TextureDataValid = false;
			SmoothingGroupDataValid = false;
			BoundingBoxDataValid = false;
			PrelightingDataValid = false;
			PolygonFlagDataValid = false;
			m_bPaintedColourDataValid = false;
			Name = name;

			m_pxNextLowerMesh = 0;
			m_fMinDistance = 0;

			VertexDataPtr=0;
			PolygonDataPtr=0;
			NormalsDataPtr=0;
			TextureDataPtr=0;
			m_pxProcessedTextureInfo=0;
			SmoothingGroupDataPtr=0;
			PrelightingDataPtr=0;
			PolygonFlagDataPtr=0;
			m_pxCollisionMesh = 0;
			m_pxMergedEntityCollisionMesh = 0;
			m_pxCollMeshGenData = 0;
			m_pxPolygonGuids = 0;
			m_pxCollPolygonGuids = 0;
			m_iNumberOfMergedEntities = 0;
			m_pxMergedEntities = 0;
			m_pxPaintedColourDataPtr = NULL;
			m_pxProjectPolygonData = 0;
			m_pxVertexFlags = 0;

			m_iNumberOfPolygonLights = 0;
			m_pxPolygonLights = 0;
			m_puPolygonLightIDs = 0;

			m_bAddToShapeTable = false;

			m_uFragmentHash = ASURA_HASH_ID_UNSET;

			m_xMeshPosition.Zero();
			m_xMeshOrient.Identity();

			m_uMaxWireframeColour = 0;

			m_uChecksum = 0;

			// JGP - New data
			m_xLayer = "";
			m_xExternalData = "";

			// JGP - Polygon index
			m_piPolygonIndexDataPtr = NULL;
			m_bPolygonIndexDataValid = false;
			
			m_iSourceFileType = 0;

			m_uPlatforms = 0;
			m_pxNextPlatformMesh = 0;
			m_pxPrevPlatformMesh = 0;

			#if !defined(ASURA_MAIN)
			m_bMaterialResponse_NotYetConvertedToDualMaterial = false;
			#endif

			m_uNumSelectionSets = 0;
			m_pxSelectionSets = 0;

		}

		~Asura_Mesh();
	
		void CopyMiscDataFrom( const Asura_Mesh* const pxFrom );

		bool LOSTest(const Asura_Vector_3& xLOSOrigin, const Asura_Vector_3& xLOSDirection, const Asura_Vector_3& xPosition, const Asura_Matrix_3x3& xOrientation, float& fNearest, int& iPolygonIndex, const Asura_Vector_3* pxScale = NULL) const;

		// mesh data
		Asura_String Name;
		int Index;
		
		// JGP - New data
		Asura_String m_xLayer;
		Asura_String m_xExternalData;

		bool VertexDataValid;
		int NumberOfVertices;
		class Asura_Mesh_Vertex* VertexDataPtr;

		bool PolygonDataValid;
		int NumberOfPolygons;
		int NumberOfTriangles;
		class Asura_Mesh_Polygon* PolygonDataPtr;

		bool TextureDataValid;
		class Asura_Mesh_TextureInfo* TextureDataPtr;

		//If this exists then it will contain some processed UV data for export.
		//This is used for platforms that need the UVs shifted to be in a certain range
		Asura_Mesh_TextureInfo* m_pxProcessedTextureInfo;

		bool SmoothingGroupDataValid;
		int* SmoothingGroupDataPtr;

		bool m_bFaceNormalsValid;
		bool m_bVertexNormalsValid;
		bool m_bSmoothedAcrossModules;//Indicates whether vertex normals were calculated taking into account adjacent modules
		class Asura_Mesh_Normals* NormalsDataPtr;
		
		bool PrelightingDataValid;
		class Asura_Mesh_Prelighting* PrelightingDataPtr;
		
		bool m_bPaintedColourDataValid;
		class Asura_Mesh_PaintedColours* m_pxPaintedColourDataPtr;
		

		mutable class Asura_Bounding_Box BoundingBox;
		mutable float BoundingRadius;
		mutable bool BoundingBoxDataValid;

		bool PolygonFlagDataValid;
		class Asura_Mesh_PolygonFlags* PolygonFlagDataPtr;

		Asura_Mesh_VertexFlags* m_pxVertexFlags;
		Asura_Mesh_ProjectPolygonData* m_pxProjectPolygonData;


		//Array of guids of original owners for each polygon (for objects that have been merged
		//int the environment) RAR 07/05/04
		Asura_Guid* m_pxPolygonGuids;
		//Similar array indicating ownership of polygons in m_pxMergedEntityCollisionMesh
		Asura_Guid* m_pxCollPolygonGuids;

		//Array of guids of entities merged to this shape
		int m_iNumberOfMergedEntities;
		Asura_Mesh_MergedEntity* m_pxMergedEntities;


		Asura_Mesh* m_pxNextLowerMesh;//for level of detail meshes
		float m_fMinDistance;//Minimum distance at which this detail level should be visible

		bool m_bAddToShapeTable;//Should be added as a shape to Asura_Shape_System on load

		Asura_Hash_ID m_uFragmentHash; //Id of fragment type produced by this mesh

		//m_pxCollisionMesh is only set if the mesh has had a special collision mesh associated with it
		//(ie. it isn't just using the one that gets automatically generated from the mesh data).
		//It won't contain any polygons from entities that have been merged into the mesh.
		Asura_CollisionMesh* m_pxCollisionMesh;
		
		//Collision consisting of polygons from entities that have been merged into the main mesh.
		//(but doesn't contain the main mesh's polygons). RAR 09/08/2004
		Asura_CollisionMesh* m_pxMergedEntityCollisionMesh;

		//Extra information about how the collision mesh should be generated
		struct Asura_Mesh_CollMeshGenData* m_pxCollMeshGenData;

		//Temporary storage for position and orientation in max plugins
		//Also used for storage of position/orientaion for Max XRef objects
		Asura_Vector_3 m_xMeshPosition;
		Asura_Matrix_3x3 m_xMeshOrient;

		//Filename and object name for Max XRef objects.
		//Stored here so it can be set up when reimporting into Max
		Asura_String m_xXRefFilename;
		Asura_String m_xXRefObjectName;


		//list of polygon lights that belong to this mesh
		int m_iNumberOfPolygonLights;
		Asura_Mesh_PolygonLight* m_pxPolygonLights;

		//array of IDs of lights that each polygon belongs to (if any)
		u_int* m_puPolygonLightIDs;


		int* m_piPolygonIndexDataPtr;
		bool m_bPolygonIndexDataValid;

		//The wireframe colour from max (alpha component not set)
		u_int m_uMaxWireframeColour;

		//Checksum based on modified time of file , for determining whether the mesh has changed.
		//Note that this won't get changed if the mesh is modified in tools , but for my purposes
		//this isn't an issue. RAR 20/03/2006
		u_int m_uChecksum;
		
		// DLH - This value si the same as the AsuraFileType. It is a int as I did not want to include the chunk system into this file
		int m_iSourceFileType;


		//Bitfield of platforms that this mesh will be used for. At least initially , only one platform will be set per mesh.
		//This won't be set for the first mesh in the chain , since that mesh will just be used as the default in cases where
		//there is no platform specific mesh.
		//RAR 12/02/2007
		u_int m_uPlatforms;

		//Linked list of platform specific meshes.
		Asura_Mesh* m_pxNextPlatformMesh;
		Asura_Mesh* m_pxPrevPlatformMesh;

		//The name of the file that this mesh was loaded from.
		Asura_String m_xStrippedFilename;

		//The selection sets that this belonged to in 3dsmax
		u_int m_uNumSelectionSets;
		Asura_String* m_pxSelectionSets;

		
		#ifdef ASURAIMPORT_EXPORTS
		//Temporary storage during imports/exports
		Asura_Mesh_PluginData m_xPluginData;
		#endif

		#if !defined(ASURA_MAIN)
		bool m_bMaterialResponse_NotYetConvertedToDualMaterial;
		#endif

		void Asura_Mesh::CalculateBoundingBox(void) const
		{
				
			BoundingBox.Reset();
			
			int j = NumberOfVertices;
			while(j--)
			{
				BoundingBox.Set(VertexDataPtr[j].Position);
			}
			
			Asura_Vector_3 centre =	BoundingBox.CalculateCentre();

			Asura_Vector_3 corner(BoundingBox.MaxX,BoundingBox.MaxY,BoundingBox.MaxZ);
			corner -= centre;
			BoundingRadius = corner.Magnitude();

			BoundingBoxDataValid = true;
		}

		const Asura_Bounding_Box& GetBoundingBox() const
		{
			if(!BoundingBoxDataValid) CalculateBoundingBox();
			return BoundingBox;
		}

		bool AreNormalsValid() const
		{
			return m_bFaceNormalsValid && m_bVertexNormalsValid;
		}

		bool AreFaceNormalsValid() const
		{
			return m_bFaceNormalsValid;
		}

		void InvalidateNormals()
		{
			delete [] NormalsDataPtr;
			NormalsDataPtr = 0;
			m_bFaceNormalsValid = false;
			m_bVertexNormalsValid = false;
		}


		//Gets the vertex normal contribution from a given vert in a polygon.
		//Typically this is the face normal , unless the splay normals flag is used
		Asura_Vector_3 GetVertexNormalContribution(int iPolygonIndex,int iVert)
		{
			return GetVertexNormalContribution(iPolygonIndex,VertexDataPtr[PolygonDataPtr[iPolygonIndex].VertexID[iVert]].Position);
		}
		Asura_Vector_3 GetVertexNormalContribution(int iPolygonIndex,const Asura_Vector_3& xVertPos);
        
        void CreateNormals();
		void CreateFaceNormals();
		void CreateVertexNormals();

		void InvalidatePolygonIndices()
		{
			m_bPolygonIndexDataValid = false;
		}             

		void Asura_Mesh::AllocatePolygonFlags()
		{
			if(PolygonFlagDataValid) return;
			PolygonFlagDataValid = true;
			PolygonFlagDataPtr = new Asura_Mesh_PolygonFlags[NumberOfPolygons];

			//initialise
			for(int i=0;i<NumberOfPolygons;i++)
			{
				PolygonFlagDataPtr[i].Flags = 0;	
			}

		}

		int GetPolygonFlags(int iPoly) const
		{
			if(!PolygonFlagDataValid) return 0;
			return PolygonFlagDataPtr[iPoly].Flags;

		}

		//allocates and initialises vertex flags if not already allcoated
		void AllocateVertexFlags();

		u_int GetVertexFlags(int iVert) const
		{
			if(!m_pxVertexFlags) return 0;
			return m_pxVertexFlags[iVert].m_uFlags;
		}

		void SetVertexFlags(int iVert,u_int uFlags)
		{
			if(uFlags == 0 && !m_pxVertexFlags) return;
			if(!m_pxVertexFlags) AllocateVertexFlags();
			m_pxVertexFlags[iVert].m_uFlags = uFlags;
		}

		//allocates and initialises project polygon data if not already allcoated
		void AllocateProjectPolygonData();

		u_int GetProjectPolygonData(int iPoly) const
		{
			if(!m_pxProjectPolygonData) return 0;
			return m_pxProjectPolygonData[iPoly].m_uData;
		}

		void SetProjectPolygonData(int iPoly,u_int uData)
		{
			if(uData == 0 && !m_pxProjectPolygonData) return;
			if(!m_pxProjectPolygonData) AllocateProjectPolygonData();
			m_pxProjectPolygonData[iPoly].m_uData = uData;
		}


		int GetSmoothingGroup(int iPolyIndex) const
		{
			return SmoothingGroupDataValid ? (SmoothingGroupDataPtr[iPolyIndex] & 0x7fffffff) : 0;
		}

		bool Asura_Mesh::IsDetailMesh() const
		{
			return m_fMinDistance>0;
		}

		bool Asura_Mesh::HasDetailMeshName() const
		{
			const char* pxName = Name.GetString();
			return pxName[0] == 'L' && pxName[1]>='1' && pxName[1]<='9' && pxName[2]=='#';
		}

		void Asura_Mesh::SetDefaultDetailDistance()
		{
			if(!HasDetailMeshName()) return;
			m_fMinDistance = (1+Name.GetString()[1]-'0')*10.0f;
		}

		Asura_Mesh* GetDetailMeshFromDistance(const float fDistance)
		{
			Asura_Mesh* pxMesh = this;
			while(pxMesh->m_pxNextLowerMesh)
			{
				if(pxMesh->m_pxNextLowerMesh->m_fMinDistance > fDistance) break;
				pxMesh = pxMesh->m_pxNextLowerMesh;
			}
			return pxMesh;
		}

		Asura_Mesh* GetDetailMeshFromIndex(int iIndex)
		{
			Asura_Mesh* pxMesh = this;
			while(iIndex && pxMesh->m_pxNextLowerMesh)
			{
				pxMesh = pxMesh->m_pxNextLowerMesh;
				iIndex--;
			}
			return pxMesh;
		}
		
		int GetNumberOfDetailLevels() const
		{
			int iCount = 0;
			const Asura_Mesh* pxMesh = this;
			while(pxMesh)
			{
				iCount++;
				pxMesh = pxMesh->m_pxNextLowerMesh;
			}
			return iCount;
		}

		//xOffset allows poly area to be calculated using the vertex world positions.
		//Used by the max plugin exporters to detect polygons that collapse down into too small polygons
		//as a result of being too far from the origin. RAR 05/09/2006
        float CalculatePolyArea( int iPolyIdx, const Asura_Vector_3& xOffset = Asura_Maths::ZeroVector3) const
        {
            float fArea = 0.0f;

            if( PolygonDataValid && VertexDataValid )
            {
                const Asura_Mesh_Polygon& xPoly = PolygonDataPtr[iPolyIdx];

                const Asura_Vector_3& xV0 = VertexDataPtr[xPoly.VertexID[0]].Position + xOffset;
                const Asura_Vector_3& xV1 = VertexDataPtr[xPoly.VertexID[1]].Position + xOffset;
                const Asura_Vector_3& xV2 = VertexDataPtr[xPoly.VertexID[2]].Position + xOffset;

                fArea = Asura_Maths::CalculateTriArea( xV0, xV1, xV2 );
                if( xPoly.IsQuad() )
                {
                    fArea += Asura_Maths::CalculateTriArea( xV0, xV2, VertexDataPtr[xPoly.VertexID[3]].Position + xOffset);
                }
            }

            return fArea;
        }

		//Indicates whether a polygon is one that has been merged in from a placed object
		//with the 'Merge to Environment' flag
		bool IsMergedObjectPolygon(int iPolygonIndex) const
		{
			if(m_pxPolygonGuids && m_pxPolygonGuids[iPolygonIndex] != ASURA_GUID_UNREGISTERED)
			{
				return true;
			}
			return false;
		}

		//Convert polygon flags from old values to current values
		void ConvertPolygonFlags();

		//handle adding ignore by los flag to polygons with dont render
		void ConvertPolygonFlags_AddIgnoreLOS();
		static int ConvertSinglePolygonFlag_AddIgnoreLOS(int iFlags);

		//and for adding walkable surface to polygons with floor/step
		void ConvertPolygonFlags_AddWalkableSurface();
		static int ConvertSinglePolygonFlag_AddWalkableSurface(int iFlags);

	
		bool IsMeshWellFormed( void) const;

		//Returns true if not the default mesh that gets used for all other platforms
		bool IsPlatformSpecificMesh() const {return m_pxPrevPlatformMesh!=0;}

		//Gets the mesh to use for any platforms not specified. This is also the one referred to the module system.
		Asura_Mesh* GetDefaultPlatformMesh() {return m_pxPrevPlatformMesh ? m_pxPrevPlatformMesh->GetDefaultPlatformMesh() : this;}
		const Asura_Mesh* GetDefaultPlatformMesh() const {return m_pxPrevPlatformMesh ? m_pxPrevPlatformMesh->GetDefaultPlatformMesh() : this;}

		//Adds mesh to linked list
		void AddPlatformMesh(Asura_Mesh* pxMesh);

		//Allocates the processed texture data and copies over the values from TextureDataPtr
		void GenerateProcessedTextureInfo();

		void DeleteProcessedTextureInfo()
		{
			if(m_pxProcessedTextureInfo)
			{
				delete [] m_pxProcessedTextureInfo;
				m_pxProcessedTextureInfo = 0;
			}
		}

		//Gets the texture info to look at during the export process.
		const Asura_Mesh_TextureInfo* GetTextureInfoForExport(int iPoly) const
		{
			Asura_Assert(TextureDataPtr,"");
			return m_pxProcessedTextureInfo ? &m_pxProcessedTextureInfo[iPoly] : &TextureDataPtr[iPoly];
		}


		static const int MAX_TRIANGLES = (65536/3);
		static const int MAX_VERTICES = (65536);
};


#endif // #ifdef ASURA_TOOLS

#endif//__ASURA_MESH_H__
