////////////////////////////////////////////////////////////////////////////
//
//  Crytek Engine Source File.
//  Copyright (C), Crytek Studios, 2001-2004.
// -------------------------------------------------------------------------
//  File name:   StatCGFPhysicalize.cpp
//  Version:     v1.00
//  Created:     8/11/2004 by Timur.
//  Compilers:   Visual Studio.NET 2003
//  Description: 
// -------------------------------------------------------------------------
//  History:
//
////////////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include "StatCGFPhysicalize.h"
#include "MeshCompiler\MeshCompiler.h"

#define SMALL_MESH_NUM_INDEX 30	

//////////////////////////////////////////////////////////////////////////
CPhysicsInterface::CPhysicsInterface()
{
	m_pPhysicalWorld = m_physLoader.GetWorldPtr();
}

//////////////////////////////////////////////////////////////////////////
CPhysicsInterface::~CPhysicsInterface()
{
}

//////////////////////////////////////////////////////////////////////////
bool CPhysicsInterface::Physicalize( CNodeCGF *pNodeCGF,CContentCGF *pCGF )
{
	if (!pNodeCGF->pMesh)
		return false;

	PhysicalizeGeomType( PHYS_GEOM_TYPE_DEFAULT,pNodeCGF,pCGF );
	PhysicalizeGeomType( PHYS_GEOM_TYPE_NO_COLLIDE,pNodeCGF,pCGF );
	PhysicalizeGeomType( PHYS_GEOM_TYPE_OBSTRUCT,pNodeCGF,pCGF );

	return true;
}

//////////////////////////////////////////////////////////////////////////
bool CPhysicsInterface::PhysicalizeGeomType( int nGeomType,CNodeCGF *pNodeCGF,CContentCGF *pCGF )
{
	CMesh &mesh = *pNodeCGF->pMesh;

	CPhysicalizeInfoCGF &physInfo = *pCGF->GetPhysiclizeInfo();

	std::vector<uint16> indices;
	std::vector<uint16> indicesRemap;
	std::vector<char> faceMaterials;
	std::vector<int> originalFaces;
	indices.reserve( mesh.GetIndexCount() );
	indicesRemap.reserve( mesh.GetIndexCount() );
	originalFaces.reserve( mesh.GetIndexCount()/3 );

	int *pForeignIndices = 0;
	bool bPhysicalizeProxy = pNodeCGF->type == CNodeCGF::NODE_HELPER;

	for (int m = 0; m < mesh.GetSubSetCount(); m++)
	{
		SMeshSubset &subset = mesh.m_subsets[m];
		
		if ((subset.nPhysicalizeType == PHYS_GEOM_TYPE_NONE) || ((subset.nPhysicalizeType&0xF) != nGeomType) ||
			subset.nNumIndices <= 0 || subset.nNumVerts <= 0)
			continue;

		// Everything not PHYS_GEOM_TYPE_DEFAULT is a physical proxy and dont need foreign ids.
		if (subset.nPhysicalizeType != PHYS_GEOM_TYPE_DEFAULT)
		{
			bPhysicalizeProxy = true;
		}

		for (int idx = subset.nFirstIndexId; idx < subset.nFirstIndexId+subset.nNumIndices; idx += 3)
		{
			indices.push_back( mesh.m_pIndices[idx] );
			indices.push_back( mesh.m_pIndices[idx+1] );
			indices.push_back( mesh.m_pIndices[idx+2] );
			faceMaterials.push_back( subset.nMatID );
			int nOriginalFace = idx/3;
			originalFaces.push_back( nOriginalFace );
		}
	}

	if (originalFaces.size() > 0 && !bPhysicalizeProxy)
	{
		pForeignIndices = &originalFaces[0];
	}

	if (indices.empty())
		return false;

	Vec3 *pVertices = pVertices = mesh.m_pPositions;
	int nVerts = mesh.GetVertexCount();

	//////////////////////////////////////////////////////////////////////////
	if (pNodeCGF->type == CNodeCGF::NODE_HELPER && pCGF->GetExportInfo()->bMergeAllNodes)
	{
		// Transform helper nodes into the world space when everything must be merged.
		pVertices = new Vec3[nVerts];
		for (int i = 0; i < nVerts; i++)
		{
			pVertices[i] = pNodeCGF->worldTM.TransformPoint(mesh.m_pPositions[i]);
		}
	}

	if (nVerts > 2)
	{
		IGeomManager *pGeoman = GetPhysicalWorld()->GetGeomManager();
		Vec3 ptmin = mesh.m_bbox.min;
		Vec3 ptmax = mesh.m_bbox.max;

		int flags = mesh_multicontact0;
		float tol = 0.05f;
		flags |= indices.size() <= SMALL_MESH_NUM_INDEX ? mesh_SingleBB : mesh_OBB|mesh_AABB|mesh_AABB_rotated;
		flags |= mesh_approx_box | mesh_approx_sphere | mesh_approx_cylinder | mesh_approx_capsule;

		flags |= mesh_keep_vtxmap_for_saving;

		//////////////////////////////////////////////////////////////////////////
		// Get flags from properties string.
		//////////////////////////////////////////////////////////////////////////
		string props = pNodeCGF->properties;
		if (!props.empty())
		{
			int curPos = 0;
			string token = props.Tokenize("\n",curPos);
			while (token != "")
			{
				token = token.Trim();

				if (strstr(token,"wheel") || strstr(token,"cylinder"))
				{
					flags &= ~(mesh_approx_box | mesh_approx_sphere | mesh_approx_capsule);
					flags |= mesh_approx_cylinder;
					tol = 1.0f;
				} else if (strstr(token,"explosion") || strstr(token,"crack")) // temporary solution
				{
					flags = flags & ~mesh_multicontact1 | mesh_multicontact2;	// temporary solution
					flags &= ~(mesh_approx_box | mesh_approx_sphere | mesh_approx_cylinder);
				}	else if (strstr(token,"box"))
				{
					flags &= ~(mesh_approx_cylinder | mesh_approx_sphere | mesh_approx_capsule);
					flags |= mesh_approx_box;
					tol = 1.0f;	
				}	else if (strstr(token,"sphere"))
				{
					flags &= ~(mesh_approx_cylinder | mesh_approx_box | mesh_approx_capsule);
					flags |= mesh_approx_sphere;
					tol = 1.0f;	
				} else if (strstr(token,"capsule"))
				{
					flags &= ~(mesh_approx_cylinder | mesh_approx_box | mesh_approx_sphere);
					flags |= mesh_approx_capsule;
					tol = 1.0f;	
				}

				token = props.Tokenize("\n",curPos);
			};
		}

		int nMinTrisPerNode = 2;
		int nMaxTrisPerNode = 4;
		Vec3 size = ptmax - ptmin;
		if (indices.size() < 600 && max(max(size.x,size.y),size.z) > 6) // make more dense OBBs for large (wrt terrain grid) objects
			nMinTrisPerNode = nMaxTrisPerNode = 1;

		//////////////////////////////////////////////////////////////////////////
		// Create physical mesh.
		//////////////////////////////////////////////////////////////////////////
		IGeometry *pGeom = pGeoman->CreateMesh(
			pVertices,
			&indices[0],
			&faceMaterials[0],
			pForeignIndices,
			indices.size()/3,
			flags, tol, nMinTrisPerNode,nMaxTrisPerNode, 2.5f );

		if(pGeom)
		{
			if (pGeom->GetErrorCount())
				Log("%d error(s) in physical geometry", pGeom->GetErrorCount()); 

			CMemStream stm(false);
			phys_geometry *pPhysGeom = pGeoman->RegisterGeometry( pGeom,faceMaterials[0] );
			pGeoman->SavePhysGeometry( stm,pPhysGeom );

			// Add physicalized data to the node.
			pNodeCGF->physicalGeomData[nGeomType].resize(stm.GetUsedSize());
			memcpy( &pNodeCGF->physicalGeomData[nGeomType][0],stm.GetBuf(),stm.GetUsedSize() );

			pGeoman->UnregisterGeometry(pPhysGeom);
			pGeom->Release();
		}
	}

	// Free temporary verts array.
	if (pVertices != mesh.m_pPositions)
		delete [] pVertices;

	return true;
}

namespace {

	int SaveStlSurface(const char *fname, Vec3 *pt,uint16 *pidx,int nTris)
	{
		int i,j;
		Vec3 n;
		FILE *f = fopen(fname,"wt");
		if (!f)
			return 0;

		fprintf(f, "solid tmpobj\n");
		for(i=0;i<nTris;i++) {
			n = (pt[pidx[i*3+1]]-pt[pidx[i*3]] ^ pt[pidx[i*3+2]]-pt[pidx[i*3]]).normalized();
			fprintf(f, "  facet normal %.8g %.8g %.8g\n", n.x,n.y,n.z);
			fprintf(f, "    outer loop\n");
			for(j=0;j<3;j++)
				fprintf(f, "      vertex %.8g %.8g %.8g\n", pt[pidx[i*3+j]].x,pt[pidx[i*3+j]].y,pt[pidx[i*3+j]].z);
			fprintf(f, "    endloop\n");
			fprintf(f, "  endfacet\n");
		}
		fprintf(f, "endsolid tmpobj\n");
		fclose(f);

		return 1;
	}

	int LoadNetgenTetrahedrization(const char *fname, Vec3 *&pVtx,int &nVtx, int *&pTets,int &nTets)
	{
		int i,j,nj;
		FILE *f = fopen(fname,"rt");
		char buf[65536],*pbuf=buf,*str;

		pVtx=0; pTets=0;
		while ((!pVtx || !pTets) && (str=fgets(pbuf,65536, f))) {
			if (!strncmp(str,"volumeelements",14)) {
				fscanf(f, "%d",&nTets); pTets = new int[nTets*4];
				for(i=0;i<nTets;i++) {
					fscanf(f, " %d %d %d %d %d %d", &j,&nj, pTets+i*4,pTets+i*4+1,pTets+i*4+2,pTets+i*4+3);
					for(j=0;j<nj;j++) pTets[i*4+j]--;
				}
			}     else if (!strncmp(str,"points",6)) {
				fscanf(f, "%d",&nVtx); pVtx = new Vec3[nVtx];
				for(i=0;i<nVtx;i++) 
					fscanf(f, " %f %f %f", &pVtx[i].x,&pVtx[i].y,&pVtx[i].z);
			}
		}
		return pVtx && pTets;
	}
}

//////////////////////////////////////////////////////////////////////////
void CPhysicsInterface::ProcessBreakablePhysics( CContentCGF *pCompiledCGF,CContentCGF *pSrcCGF )
{
	if(!pSrcCGF)
		return;
	
	CPhysicalizeInfoCGF* pPi = pSrcCGF->GetPhysiclizeInfo();

	if(pPi->nMode==-1 || pPi->nGranularity==-1)
		return;

	char path[1024];
	GetModuleFileName(GetModuleHandle(NULL),path, 1024);
	char * ch = strrchr(path, '\\');
	if(!ch)
		return;
	strcpy(ch, "\\netgen\\");

	char filepath[4096];
	sprintf(filepath, "%sng.exe", path);

	char stlFile[1024];
	sprintf(stlFile, "%stempin.stl", path);

	char volFile[1024];
	sprintf(volFile, "%stempout.vol", path);

	{
		// Remove read only attribute of ng.ini
		char ngIni[1024];
		sprintf(ngIni, "%sng.ini", path);
		SetFileAttributes( ngIni,FILE_ATTRIBUTE_ARCHIVE );
	}

	CNodeCGF * pNode = 0;
	int i;
	for(i=0; i<pSrcCGF->GetNodeCount(); i++)
	{
		pNode = pSrcCGF->GetNode(i);
		if(!strcmp(pNode->name, "$tet"))
			break;
		pNode = 0;
	}
	if(!pNode)
		return;
	CMesh * pMesh = pNode->pMesh;
	if(!pMesh)
		return;

	Vec3 *pVtx = new Vec3[pMesh->m_numVertices];
	for(i=0; i<pMesh->m_numVertices; i++)
		pVtx[i] = pNode->worldTM*pMesh->m_pPositions[i];

	if(!pMesh->m_pIndices)
	{
		uint16 * pInds = new uint16[pMesh->m_numFaces * 3];

		for(i=0; i<pMesh->m_numFaces; i++)
		{
			pInds[i*3+0] = pMesh->m_pFaces[i].v[0];
			pInds[i*3+1] = pMesh->m_pFaces[i].v[1];
			pInds[i*3+2] = pMesh->m_pFaces[i].v[2];
		}

		SaveStlSurface(stlFile, pVtx, pInds, pMesh->m_numFaces);

		delete[] pInds;
	}
	else
		SaveStlSurface(stlFile, pVtx, pMesh->m_pIndices, pMesh->m_nIndexCount/3);
	delete[] pVtx;

	const char * pGr = "";

	switch(pPi->nGranularity)
	{
	case 0:
		pGr = "-verycoarse";
		break;
	case 1:
		pGr = "-coarse";
		break;
	case 2:
		pGr = "-moderate";
		break;
	case 3:
		pGr = "-fine";
		break;
	case 4:
		pGr = "-veryfine";
		break;
	}

	char cmd[4096];
	sprintf(cmd, "%s -geofile=tempin.stl -meshfile=tempout.vol -batchmode %s", filepath, pGr);

	SetEnvironmentVariable("TCL_LIBRARY", ".\\tcl8.3");
	SetEnvironmentVariable("TIX_LIBRARY", ".\\tix8.2");

	PROCESS_INFORMATION pi;
	STARTUPINFO si;
	memset( &si,0,sizeof(si) );
	si.cb = sizeof(si);
	if (!CreateProcess( NULL,cmd,NULL,NULL,FALSE,0,NULL,path,&si,&pi ))
	{
		MessageBox(NULL, "Error executing netgen", "Warning", MB_OK|MB_ICONERROR);
		return;
	}
	// Wait until child process exits.
	WaitForSingleObject( pi.hProcess, INFINITE );

	DWORD lpExitCode = 0;
	GetExitCodeProcess( pi.hProcess,&lpExitCode );
	// Close process and thread handles.
	CloseHandle( pi.hProcess );
	CloseHandle( pi.hThread );

	if (lpExitCode != EXIT_SUCCESS)
		return;

	CPhysicalizeInfoCGF* pConPi = pCompiledCGF->GetPhysiclizeInfo();

	LoadNetgenTetrahedrization(volFile, pConPi->pRetVtx, pConPi->nRetVtx, pConPi->pRetTets, pConPi->nRetTets);


	remove(stlFile);
	remove(volFile);
}

//////////////////////////////////////////////////////////////////////////
void CPhysicsInterface::DeletePhysicalProxySubsets( CMesh &mesh )
{
	for (int i = (int)mesh.m_subsets.size()-1; i >= 0; i--)
	{
		SMeshSubset &subset = mesh.m_subsets[i];
		if (subset.nPhysicalizeType != PHYS_GEOM_TYPE_NONE && subset.nPhysicalizeType != PHYS_GEOM_TYPE_DEFAULT)
		{
			// This is physical proxy, remove it from render mesh.

			// Collapse indices.
			mesh.RemoveRangeFromStream( CMesh::INDICES,subset.nFirstIndexId,subset.nNumIndices );
		
			// Collapse vertices.
			mesh.RemoveRangeFromStream( CMesh::POSITIONS,subset.nFirstVertId,subset.nNumVerts );
			mesh.RemoveRangeFromStream( CMesh::NORMALS,subset.nFirstVertId,subset.nNumVerts );
			mesh.RemoveRangeFromStream( CMesh::TEXCOORDS,subset.nFirstVertId,subset.nNumVerts );
			mesh.RemoveRangeFromStream( CMesh::COLORS_0,subset.nFirstVertId,subset.nNumVerts );
			mesh.RemoveRangeFromStream( CMesh::COLORS_1,subset.nFirstVertId,subset.nNumVerts );
			mesh.RemoveRangeFromStream( CMesh::TANGENTS,subset.nFirstVertId,subset.nNumVerts );
			mesh.RemoveRangeFromStream( CMesh::SHCOEFFS,subset.nFirstVertId,subset.nNumVerts );
			mesh.RemoveRangeFromStream( CMesh::SHAPEDEFORMATION,subset.nFirstVertId,subset.nNumVerts );

			// Delete this subset from the mesh.
			//mesh.m_subsets.erase(mesh.m_subsets.begin()+i);
			//i--;
			subset.nFirstIndexId = 0;
			subset.nFirstVertId = 0;
			subset.nNumIndices = 0;
			subset.nNumVerts = 0;
			subset.vCenter.Set(0,0,0);
			subset.fRadius = 0;
		}
	}
}
