//========= Copyright © 1996-2003, Valve LLC, All rights reserved. ============
//
// Purpose: Implements a decal helper. The decal attaches itself to nearby
//			solids, dynamically creating decal faces as necessary.
//
//=============================================================================

#include "stdafx.h"
#include "ClipCode.h"
#include "MapDoc.h"
#include "MapDecal.h"
#include "MapFace.h"
#include "MapSolid.h"
#include "MapWorld.h"
#include "Render3D.h"
#include "TextureSystem.h"


IMPLEMENT_MAPCLASS(CMapDecal)


//-----------------------------------------------------------------------------
// Purpose: Factory function. Used for creating a CMapDecal from a set
//			of string parameters from the FGD file.
// Input  : pInfo - Pointer to helper info class which gives us information
//				about how to create the class.
// Output : Returns a pointer to the class, NULL if an error occurs.
//-----------------------------------------------------------------------------
CMapClass *CMapDecal::CreateMapDecal(CHelperInfo *pHelperInfo, CMapEntity *pParent)
{
	CMapDecal *pDecal = new CMapDecal;
	return(pDecal);
}


//-----------------------------------------------------------------------------
// Purpose: Constructor. Initializes data members.
//-----------------------------------------------------------------------------
CMapDecal::CMapDecal(void)
{
	m_pTexture = NULL;
}


//-----------------------------------------------------------------------------
// Purpose: Destructor. Frees allocated memory.
//-----------------------------------------------------------------------------
CMapDecal::~CMapDecal(void)
{
	//
	// Delete our list of faces and each face in the list.
	//
	POSITION pos = m_Faces.GetHeadPosition();
	while (pos != NULL)
	{
		DecalFace_t *pDecalFace = m_Faces.GetNext(pos);
		delete pDecalFace->pFace;
		delete pDecalFace;
	}
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : pSolid - 
//-----------------------------------------------------------------------------
void CMapDecal::AddSolid(CMapSolid *pSolid)
{
	if (m_Solids.Find(pSolid) == NULL)
	{
		UpdateDependency(NULL, pSolid);
		m_Solids.AddTail(pSolid);
	}
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : bFullUpdate - 
//-----------------------------------------------------------------------------
void CMapDecal::CalcBounds(BOOL bFullUpdate)
{
	CMapClass::CalcBounds(bFullUpdate);

	//
	// Calculate the 2D render box.
	//
	Vector Mins = m_Origin - Vector(2, 2, 2);
	Vector Maxs = m_Origin + Vector(2, 2, 2);

	m_Render2DBox.UpdateBounds(Mins, Maxs);

	//
	// Calculate the 3D culling bounds.
	//
	m_CullBox.ResetBounds();

	if (!m_Faces.IsEmpty())
	{
		Vector Mins;
		Vector Maxs;

		POSITION pos = m_Faces.GetHeadPosition();
		while (pos != NULL)
		{
			DecalFace_t *pDecalFace = m_Faces.GetNext(pos);

			if ((pDecalFace != NULL) && (pDecalFace->pFace != NULL))
			{
				pDecalFace->pFace->GetFaceBounds(Mins, Maxs);

				m_CullBox.UpdateBounds(Mins, Maxs);
			}
		}

		//
		// Insure that the 3D bounds are at least 1 unit in all dimensions.
		//
		for (int nDim = 0; nDim < 3; nDim++)
		{
			if ((m_CullBox.bmaxs[nDim] - m_CullBox.bmins[nDim]) == 0)
			{
				m_CullBox.bmins[nDim] -= 0.5;
				m_CullBox.bmaxs[nDim] += 0.5;
			}
		}
	}
	else
	{
		m_CullBox.UpdateBounds(Mins, Maxs);
	}
}


//-----------------------------------------------------------------------------
// Purpose: Determines whether we can attach to this solid by looking at the
//			normal distance to the solid face. We still may not lie within the
//			face; that is determined by the clipping code.
// Input  : pSolid - Solid to check.
//			ppFaces - Returns with pointers to the faces that are eligible.
// Output : Returns the number of faces that were eligible.
//-----------------------------------------------------------------------------
int CMapDecal::CanDecalSolid(CMapSolid *pSolid, CMapFace **ppFaces)
{
	//
	// Check distance from our origin to each face along the face's normal.
	// If the distance is very very small, add the face.
	//
	int nDecalFaces = 0;

	int nFaces = pSolid->GetFaceCount();
	ASSERT(nFaces <= MAPSOLID_MAX_FACES);
	for (int i = 0; i < nFaces; i++)
	{
		CMapFace *pFace = pSolid->GetFace(i);
		float fDistance = pFace->GetNormalDistance(m_Origin);

		if ((fDistance <= 16.0f) && (fDistance >= -0.0001))
		{
			if (ppFaces != NULL)
			{
				ppFaces[nDecalFaces] = pFace;
			}

			nDecalFaces++;
		}
	}

	return(nDecalFaces);
}


//-----------------------------------------------------------------------------
// Purpose: 
// Output : CMapClass
//-----------------------------------------------------------------------------
CMapClass *CMapDecal::Copy(bool bUpdateDependencies)
{
	CMapDecal *pCopy = new CMapDecal;

	if (pCopy != NULL)
	{
		pCopy->CopyFrom(this, bUpdateDependencies);
	}

	return(pCopy);
}


//-----------------------------------------------------------------------------
// Purpose: Makes this object identical to the given object.
// Input  : pObject - Object to copy.
//			bUpdateDependencies - 
//-----------------------------------------------------------------------------
CMapClass *CMapDecal::CopyFrom(CMapClass *pObject, bool bUpdateDependencies)
{
	ASSERT(pObject->IsMapClass(MAPCLASS_TYPE(CMapDecal)));
	CMapDecal *pFrom = (CMapDecal *)pObject;

	CMapClass::CopyFrom(pObject, bUpdateDependencies);

	m_pTexture = pFrom->m_pTexture;

	//
	// Copy our list of solids to which we are attached.
	//
	m_Solids.RemoveAll();
	m_Solids.AddTail(&pFrom->m_Solids);

	//
	// Copy our decal faces. We don't copy the pointers because we don't do
	// reference counting yet.
	//
	m_Faces.RemoveAll();
	POSITION pos = pFrom->m_Faces.GetHeadPosition();
	while (pos != NULL)
	{
		DecalFace_t *pDecalFace = new DecalFace_t;
		if (pDecalFace != NULL)
		{
			pDecalFace->pFace = new CMapFace;
			if (pDecalFace->pFace != NULL)
			{
				DecalFace_t *pFromDecalFace = pFrom->m_Faces.GetNext(pos);

				pDecalFace->pFace->CopyFrom(pFromDecalFace->pFace);
				pDecalFace->pFace->SetParent(this);

				pDecalFace->pSolid = pFromDecalFace->pSolid;

				m_Faces.AddTail(pDecalFace);
			}
		}
	}

	return(this);
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : pSolid - 
//			org - 
//			piFacesRvl - 
// Output : int
//-----------------------------------------------------------------------------
int CMapDecal::DecalSolid(CMapSolid *pSolid)
{
	if (m_pTexture == NULL)
	{
		return(0);
	}

	//
	// Determine how many, if any, faces will accept the decal.
	//
	CMapFace *ppFaces[MAPSOLID_MAX_FACES];
	int nDecalFaces = 0;
	int nTestFaces = CanDecalSolid(pSolid, ppFaces);
	if (nTestFaces != 0)
	{
		//
		// Apply the decal to each face that will accept it.
		//
		for (int nFace = 0; nFace < nTestFaces; nFace++)
		{
			CMapFace *pFace = ppFaces[nFace];

			//
			// Create the polygon, clipping it to this face.
			//
			vec5_t ClipPoints[MAX_CLIPVERT];
			int nPointCount = CreateClippedPoly(pFace, m_pTexture, m_Origin, ClipPoints, sizeof(ClipPoints) / sizeof(ClipPoints[0]));

			if (nPointCount != 0)
			{
				nDecalFaces++;

				Vector CreatePoints[64];
				for (int nPoint = 0; nPoint < nPointCount; nPoint++)
				{
					CreatePoints[nPoint][0] = ClipPoints[nPoint][0];
					CreatePoints[nPoint][1] = ClipPoints[nPoint][1];
					CreatePoints[nPoint][2] = ClipPoints[nPoint][2];
				}

				//
				// Create the decal face from the polygon.
				//
				DecalFace_t *pDecalFace = new DecalFace_t;
				pDecalFace->pFace = new CMapFace;

				pDecalFace->pFace->CreateFace(CreatePoints, nPointCount);

				pDecalFace->pFace->SetRenderColor(255, 255, 255);
				pDecalFace->pFace->SetParent(this);

				//
				// Associate this decal face with the solid.
				//
				pDecalFace->pSolid = pSolid;

				//
				// Set the texture in the decal face.
				//
				pDecalFace->pFace->SetTexture(m_pTexture);
				pDecalFace->pFace->CalcTextureCoords();

				for (nPoint = 0; nPoint < nPointCount; nPoint++)
				{
					pDecalFace->pFace->SetTextureCoords(nPoint, ClipPoints[nPoint][3], ClipPoints[nPoint][4]);
				}

				m_Faces.AddTail(pDecalFace);
				break;
			}
		}
	}

	return(nDecalFaces);
}


//-----------------------------------------------------------------------------
// Purpose: Notifies that this object's parent entity has had a key value change.
// Input  : szKey - The key that changed.
//			szValue - The new value of the key.
//-----------------------------------------------------------------------------
void CMapDecal::OnParentKeyChanged(LPCSTR szKey, LPCSTR szValue)
{
	//
	// The decal texture has changed.
	//
	if (!stricmp(szKey, "texture"))
	{
		IEditorTexture *pTexNew = g_Textures.FindActiveTexture(szValue);

		if (pTexNew != NULL)
		{
			m_pTexture = pTexNew;

			//
			// Rebuild all the decal faces with the new texture by pretending
			// that all the solids we are attached to have changed.
			//
			POSITION pos = m_Solids.GetHeadPosition();
			while (pos != NULL)
			{
				CMapSolid *pSolid = (CMapSolid *)m_Solids.GetNext(pos);
				if (pSolid != NULL)
				{
					OnNotifyDependent(pSolid, Notify_Changed);
				}
			}
		}
	}
}


//-----------------------------------------------------------------------------
// Purpose: This function enumerates all children of the world and tries to
//			apply the decal to them.
//-----------------------------------------------------------------------------
void CMapDecal::DecalAllSolids(CMapWorld *pWorld)
{
	ASSERT(pWorld != NULL);

	if (pWorld != NULL)
	{
		//
		// Try to apply the decal to every solid in the world.
		//
		EnumChildrenPos_t pos;
		CMapClass *pChild = pWorld->GetFirstDescendent(pos);
		while (pChild != NULL)
		{
			CMapSolid *pSolid = dynamic_cast <CMapSolid *> (pChild);
			if ((pSolid != NULL) && (DecalSolid(pSolid) != 0))
			{
				AddSolid(pSolid);
			}

			pChild = pWorld->GetNextDescendent(pos);
		}

		PostUpdate(Notify_Changed);
	}
}		


//-----------------------------------------------------------------------------
// Purpose: Notifys this decal of a change to a solid that it is attached to.
// Input  : pSolid - The solid that is changing.
//			bSolidDeleted - whether the solid is being deleted.
// Output : Returns true if the decal is still attached to the solid, false if not.
//-----------------------------------------------------------------------------
void CMapDecal::OnNotifyDependent(CMapClass *pObject, Notify_Dependent_t eNotifyType)
{
	CMapSolid *pSolid = dynamic_cast <CMapSolid *> (pObject);

	if (pSolid != NULL)
	{
		//
		// Delete any decal faces that are attached to this solid. They will be
		// rebuilt if we can still decal the solid.
		//
		POSITION pos = m_Faces.GetHeadPosition();
		while (pos != NULL)
		{
			POSITION PrevPos = pos;

			DecalFace_t *pDecalFace = m_Faces.GetNext(pos);
			if ((pDecalFace != NULL) && (pDecalFace->pSolid == pSolid))
			{
				delete pDecalFace->pFace;
				delete pDecalFace;

				m_Faces.RemoveAt(PrevPos);
			}
		}

		//
		// Attempt to re-attach to the solid.
		//
		if (eNotifyType != Notify_Removed)
		{
			if (DecalSolid(pSolid) != 0)
			{
				CalcBounds();
				return;
			}
		}

		//
		// We could not re-attach to the solid because it was moved out of range or deleted. If we are
		// no longer attached to any solids, remove our entity from the world.
		//
		pos = m_Solids.Find(pSolid);
		if (pos != NULL)
		{
			m_Solids.RemoveAt(pos);
			UpdateDependency(pSolid, NULL);
		}
	}

	CalcBounds();
}


//-----------------------------------------------------------------------------
// Purpose: Called after the entire map has been loaded. This allows the object
//			to perform any linking with other map objects or to do other operations
//			that require all world objects to be present.
// Input  : pWorld - The world that we are in.
//-----------------------------------------------------------------------------
void CMapDecal::PostloadWorld(CMapWorld *pWorld)
{
	CMapClass::PostloadWorld(pWorld);

	// Apply ourselves to all solids now that the map is loaded.
	DecalAllSolids(pWorld);
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CMapDecal::RebuildDecalFaces(void)
{
	//
	// Delete all current decal faces. They will be rebuilt below.
	//
	POSITION pos = m_Faces.GetHeadPosition();
	while (pos != NULL)
	{
		DecalFace_t *pDecalFace = m_Faces.GetNext(pos);
		if (pDecalFace != NULL)
		{
			delete pDecalFace->pFace;
			delete pDecalFace;
		}
	}

	m_Faces.RemoveAll();

	//
	// Attach to all eligible solids in the world.
	//
	CMapWorld *pWorld = (CMapWorld *)GetWorldObject(this);
	DecalAllSolids(pWorld);	
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : pRender - 
//-----------------------------------------------------------------------------
void CMapDecal::Render3D(CRender3D *pRender)
{
	pRender->SetRenderMode(RENDER_MODE_DEFAULT);

	//
	// Determine whether we need to render in one or two passes. If we are selected,
	// and rendering in flat or textured mode, we need to render using two passes.
	//
	int nPasses;
	SelectionState_t eSelectionState = GetSelectionState();
	RenderMode_t eDefaultRenderMode = pRender->GetDefaultRenderMode();
	if ((eSelectionState != SELECT_NONE) && (eDefaultRenderMode != RENDER_MODE_WIREFRAME))
	{
		nPasses = 2;
	}
	else
	{
		nPasses = 1;
	}

	// use the texture instead of the lightmap coord for decals
	if (eDefaultRenderMode == RENDER_MODE_LIGHTMAP_GRID)
		pRender->SetRenderMode( RENDER_MODE_TEXTURED );

	pRender->RenderEnable( RENDER_POLYGON_OFFSET_FILL, true );

	for (int nPass = 1; nPass <= nPasses; nPass++)
	{
		//
		// Render the second pass in wireframe.
		//
		if (nPass == 2)
		{
			pRender->SetRenderMode(RENDER_MODE_WIREFRAME);
		}

		POSITION pos = m_Faces.GetHeadPosition();
		while (pos != NULL)
		{
			DecalFace_t *pDecalFace = m_Faces.GetNext(pos);

			if ((pDecalFace != NULL) && (pDecalFace->pFace != NULL))
			{
				pDecalFace->pFace->Render3D(pRender);
			}
		}
	}

	pRender->RenderEnable( RENDER_POLYGON_OFFSET_FILL, false );

	pRender->SetRenderMode(RENDER_MODE_DEFAULT);
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : File - 
//			bRMF - 
// Output : int
//-----------------------------------------------------------------------------
int CMapDecal::SerializeRMF(fstream &File, BOOL bRMF)
{
	return(0);
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : File - 
//			bRMF - 
// Output : int
//-----------------------------------------------------------------------------
int CMapDecal::SerializeMAP(fstream &File, BOOL bRMF)
{
	return(0);
}


//-----------------------------------------------------------------------------
// Purpose: Notifies us that a copy of ourselves was pasted.
//-----------------------------------------------------------------------------
void CMapDecal::OnPaste(CMapClass *pCopy, CMapWorld *pSourceWorld, CMapWorld *pDestWorld, CMapObjectList &OriginalList, CMapObjectList &NewList)
{
	CMapClass::OnPaste(pCopy, pSourceWorld, pDestWorld, OriginalList, NewList);

	//
	// Apply the copy to all solids in the destination world.
	//
	((CMapDecal *)pCopy)->DecalAllSolids(pDestWorld);
}


//-----------------------------------------------------------------------------
// Purpose: Called just after this object has been removed from the world so
//			that it can unlink itself from other objects in the world.
// Input  : pWorld - The world that we were just removed from.
//			bNotifyChildren - Whether we should forward notification to our children.
//-----------------------------------------------------------------------------
void CMapDecal::OnRemoveFromWorld(CMapWorld *pWorld, bool bNotifyChildren)
{
	CMapClass::OnRemoveFromWorld(pWorld, bNotifyChildren);

	//
	// We're going away. Unlink ourselves from any solids that we are attached to.
	//
	POSITION pos = m_Solids.GetHeadPosition();
	while (pos)
	{
		CMapSolid *pSolid = (CMapSolid *)m_Solids.GetNext(pos);
		UpdateDependency(pSolid, NULL);
	}

	m_Solids.RemoveAll();
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : pTransBox - 
//-----------------------------------------------------------------------------
void CMapDecal::DoTransform(Box3D *pTransBox)
{
	CMapClass::DoTransform(pTransBox);
	RebuildDecalFaces();
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : RefPoint - 
//-----------------------------------------------------------------------------
void CMapDecal::DoTransFlip(const Vector &RefPoint)
{
	CMapClass::DoTransFlip(RefPoint);
	RebuildDecalFaces();
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : Delta - 
//-----------------------------------------------------------------------------
void CMapDecal::DoTransMove(const Vector &Delta)
{
	CMapClass::DoTransMove(Delta);
	RebuildDecalFaces();
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : pRefPoint - 
//			Angles - 
//-----------------------------------------------------------------------------
void CMapDecal::DoTransRotate(const Vector *pRefPoint, const QAngle &Angles)
{
	CMapClass::DoTransRotate(pRefPoint, Angles);
	RebuildDecalFaces();
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : RefPoint - 
//			Scale - 
//-----------------------------------------------------------------------------
void CMapDecal::DoTransScale(const Vector &RefPoint, const Vector &Scale)
{
	CMapClass::DoTransScale(RefPoint, Scale);
	RebuildDecalFaces();
}


