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

#include "StdAfx.h"
#include "ScriptBind_Physics.h"

#include <ISystem.h>
#include <I3DEngine.h>
#include <IPhysics.h>
#include <IEntitySystem.h>

//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////
CScriptBind_Physics::CScriptBind_Physics(IScriptSystem *pScriptSystem, ISystem *pSystem)
{
	m_pSystem = NULL;
	m_p3DEngine = NULL;

	CScriptableBase::Init( pScriptSystem,pSystem );
	SetGlobalName( "Physics" );
	m_pSystem = pSystem;
	m_p3DEngine = m_pSystem->GetI3DEngine();
	m_pPhysicalWorld = m_pSystem->GetIPhysicalWorld();

#undef SCRIPT_REG_CLASSNAME 
#define SCRIPT_REG_CLASSNAME &CScriptBind_Physics::

	SCRIPT_REG_TEMPLFUNC(SimulateExplosion,"tableExplosion" );
	SCRIPT_REG_TEMPLFUNC(RegisterExplosionShape,"sGeometryFile,fSize,nIdMaterial,fProbability,sSplintersFile,fSplintersOffset,sSplintersCloudEffect" );
	SCRIPT_REG_TEMPLFUNC(RegisterExplosionCrack,"sGeometryFile,nIdMaterial" );
	SCRIPT_REG_FUNC(RayWorldIntersection);
	SCRIPT_REG_TEMPLFUNC(RayTraceCheck,"vSrc,vTrg,hSkipEntityId1,hSkipEntityId2");
	SCRIPT_REG_TEMPLFUNC(SamplePhysEnvironment,"vPoint,fRadius");
}

//////////////////////////////////////////////////////////////////////////
CScriptBind_Physics::~CScriptBind_Physics()
{
}

//////////////////////////////////////////////////////////////////////////
int CScriptBind_Physics::SimulateExplosion(IFunctionHandler *pH,SmartScriptTable explisionTable)
{
	{
		// Must be in local scope.
		CScriptSetGetChain params(explisionTable);

		pe_explosion expl;
		params.GetValue( "pos",expl.epicenter );
		params.GetValue( "radius",expl.r );
		params.GetValue( "direction",expl.explDir );
		params.GetValue( "impulse_pos",expl.epicenterImp );
		params.GetValue( "impulse_pressure",expl.impulsivePressureAtR );
		params.GetValue( "rmin",expl.rmin );
		params.GetValue( "rmax",expl.rmax );
		params.GetValue( "hole_size",expl.holeSize );

		m_pPhysicalWorld->SimulateExplosion( &expl );
	}

	return pH->EndFunction();
}


//////////////////////////////////////////////////////////////////////////
int CScriptBind_Physics::RegisterExplosionShape(IFunctionHandler *pH,const char *sGeometryFile,float fSize,int nIdMaterial,float fProbability,
																								const char *sSplintersFile, float fSplintersOffset, const char *sSplintersCloudEffect)
{
	//////////////////////////////////////////////////////////////////////////
	// Remove all this.
	//////////////////////////////////////////////////////////////////////////
	IStatObj *pObj = gEnv->p3DEngine->LoadStatObj( sGeometryFile,"#ForceBreakable" );
	if (!pObj || pObj->IsDefaultObject())
	{
		ScriptWarning( "<RegisterExplosionShape> Object file %s not found",sGeometryFile );
		return pH->EndFunction();
	}
	pObj->AddRef();

	if(sSplintersFile && *sSplintersFile!=0)			// if sSplintersFile was specified
	{
		IStatObj *pSplinters = gEnv->p3DEngine->LoadStatObj(sSplintersFile);
		if (pSplinters)
		{
			pObj->SetSubObjectCount(pObj->GetSubObjectCount()+1);
			IStatObj::SSubObject *pSubObj = pObj->GetSubObject(pObj->GetSubObjectCount()-1);
			pSubObj->nType = STATIC_SUB_OBJECT_MESH;
			pSubObj->bHidden = true;
			pSubObj->name = "splinters";
			(pSubObj->pStatObj = pSplinters)->AddRef();
			pSubObj->helperSize.x = fSplintersOffset;
			nIdMaterial |= 1<<16;

			if (*sSplintersCloudEffect)
			{
				pSplinters->SetSubObjectCount(pSplinters->GetSubObjectCount()+1);
				pSplinters->SetFlags(pSplinters->GetFlags() & ~STATIC_OBJECT_COMPOUND);
				pSubObj = pSplinters->GetSubObject(pSplinters->GetSubObjectCount()-1);
				pSubObj->nType = STATIC_SUB_OBJECT_DUMMY;
				pSubObj->bHidden = true;
				pSubObj->name = "splinters_cloud";
				pSubObj->properties = sSplintersCloudEffect;
			}
		}
	}

	phys_geometry *pPhysGeom = pObj->GetPhysGeom(0);
	if (pPhysGeom)
	{
		m_pPhysicalWorld->AddExplosionShape( pPhysGeom->pGeom,fSize,nIdMaterial,fProbability );
	}
	return pH->EndFunction();
}


//////////////////////////////////////////////////////////////////////////
int CScriptBind_Physics::RegisterExplosionCrack(IFunctionHandler *pH,const char *sGeometryFile,int nIdMaterial )
{
	IStatObj *pObj = gEnv->p3DEngine->LoadStatObj( sGeometryFile,"#ForceBreakable" );
	if (!pObj || pObj->IsDefaultObject())
	{
		ScriptWarning( "<RegisterExplosionCrack> Object file %s not found",sGeometryFile );
		return pH->EndFunction();
	}
	pObj->AddRef();
	Vec3 vtx[3] = { pObj->GetHelperPos("1"),pObj->GetHelperPos("2"),pObj->GetHelperPos("3") };
	//@TODO: restore it.
	m_pPhysicalWorld->GetGeomManager()->RegisterCrack( pObj->GetPhysGeom(0)->pGeom,vtx,0 );
	return pH->EndFunction();
}

//////////////////////////////////////////////////////////////////////////
int CScriptBind_Physics::RayWorldIntersection(IFunctionHandler *pH)
{
	assert(pH->GetParamCount()>=3 && pH->GetParamCount()<=7);

	Vec3 vPos(0,0,0);
	Vec3 vDir(0,0,0);
	ScriptHandle skipId1;
	ScriptHandle skipId2;
	IEntity	*skipEnt1 = 0;
	IEntity	*skipEnt2 = 0;

	int nMaxHits,iEntTypes=ent_all;

	SmartScriptTable hitTable(m_pSS);

	if (!pH->GetParams(vPos,vDir,nMaxHits,iEntTypes))
		return pH->EndFunction();

	int nParams = pH->GetParamCount();
	if (nParams >= 5 && pH->GetParamType(5) != svtNull && pH->GetParam(5, skipId1))
		skipEnt1 = m_pSystem->GetIEntitySystem()->GetEntity(skipId1.n);
	if (nParams >= 6 && pH->GetParamType(6) != svtNull && pH->GetParam(6, skipId2))
		skipEnt2 = m_pSystem->GetIEntitySystem()->GetEntity(skipId2.n);
	
	bool bHitTablePassed = (nParams >= 7 );

	if(bHitTablePassed)
		pH->GetParam(7, hitTable);

	if (nMaxHits>10)
		nMaxHits=10;

	ray_hit RayHit[10];

	//filippo: added support for skip certain entities.
	int skipIdCount = 0;

	IPhysicalEntity	*skipPhys[2] = {0,0};

	if (skipEnt1)
	{
		++skipIdCount;
		skipPhys[0] = skipEnt1->GetPhysics();
	}
	if (skipEnt2)
	{
		++skipIdCount;
		skipPhys[1] = skipEnt2->GetPhysics();
	}

	Vec3 src = vPos;
	Vec3 dir = vDir;

	int nHits= m_pSystem->GetIPhysicalWorld()->RayWorldIntersection(src, dir, iEntTypes,
		geom_colltype0<<rwi_colltype_bit | rwi_stop_at_pierceable, RayHit, nMaxHits,skipPhys,skipIdCount);


	for (int i=0;i<nHits;i++)
	{
		SmartScriptTable pHitObj(m_pSS);
		ray_hit &Hit=RayHit[i];
		pHitObj->SetValue("pos",Hit.pt);
		pHitObj->SetValue("normal",Hit.n);
		pHitObj->SetValue("dist", Hit.dist);
		pHitObj->SetValue("surface", Hit.surface_idx);
		IEntity * pEntity = (IEntity*) Hit.pCollider->GetForeignData(PHYS_FOREIGN_ID_ENTITY);
		if (pEntity)
			pHitObj->SetValue("entity", pEntity->GetScriptTable());
		else
		{
			if(Hit.pCollider->GetiForeignData()==PHYS_FOREIGN_ID_STATIC)
			{
				IRenderNode * pRN = (IRenderNode*)Hit.pCollider->GetForeignData(PHYS_FOREIGN_ID_STATIC);
				if (pRN)
					pHitObj->SetValue("renderNode", ScriptHandle(pRN));
			}
			else if(Hit.pCollider->GetiForeignData()==PHYS_FOREIGN_ID_FOLIAGE)
			{
				IRenderNode * pRN = ((IFoliage*)Hit.pCollider->GetForeignData(PHYS_FOREIGN_ID_FOLIAGE))->GetIRenderNode();
				if (pRN)
					pHitObj->SetValue("renderNode", ScriptHandle(pRN));
			}
		}
		hitTable->SetAt(i+1, pHitObj);
	}

	if (bHitTablePassed )
		return pH->EndFunction(nHits);

	return pH->EndFunction(*hitTable);
}

//////////////////////////////////////////////////////////////////////////
int CScriptBind_Physics::RayTraceCheck(IFunctionHandler *pH,Vec3 src,Vec3 dst,ScriptHandle skipEntityId1,ScriptHandle skipEntityId2 )
{
	SCRIPT_CHECK_PARAMETERS(4);

	IEntity	*skipEnt1 = m_pSystem->GetIEntitySystem()->GetEntity(skipEntityId1.n);
	IEntity	*skipEnt2 = m_pSystem->GetIEntitySystem()->GetEntity(skipEntityId2.n);
	IPhysicalEntity	*skipPhys[2] = {0,0};

	if(skipEnt1) skipPhys[0] = skipEnt1->GetPhysics();
	if(skipEnt2) skipPhys[1] = skipEnt2->GetPhysics();

	ray_hit RayHit;
	//TODO? add an extraparam to specify what kind of objects to check? now its world and static
	int nHits = m_pSystem->GetIPhysicalWorld()->RayWorldIntersection(src, dst-src, ent_static|ent_terrain,	rwi_ignore_noncolliding |  rwi_stop_at_pierceable, &RayHit, 1, skipPhys,2 );

	return pH->EndFunction((bool)(nHits==0));
}
//////////////////////////////////////////////////////////////////////////

int CScriptBind_Physics::SamplePhysEnvironment(IFunctionHandler *pH)
{
	int nEnts,i,nHits=0,objtypes = ent_static|ent_rigid|ent_sleeping_rigid|ent_sort_by_mass;
	pe_status_nparts snp; 
	pe_status_pos sp;
	IPhysicalEntity **pEnts;
	geom_world_data gwd;
	IGeometry *pSphere;
	primitives::sphere sph;
	intersection_params ip;
	geom_contact *pcontacts;
	IPhysicalWorld *pWorld = m_pSystem->GetIPhysicalWorld();
	IEntity *pEntity;
	SmartScriptTable pObj(m_pSS);
	ip.bStopAtFirstTri=ip.bNoBorder=ip.bNoAreaContacts = true;

	if (!pH->GetParams(sph.center,sph.r))
		return pH->EndFunction();
	if (pH->GetParamCount()>2)
		pH->GetParam(3,objtypes);
	pSphere = pWorld->GetGeomManager()->CreatePrimitive(primitives::sphere::type,&sph);

	nEnts = pWorld->GetEntitiesInBox(sph.center-Vec3(sph.r),sph.center+Vec3(sph.r),pEnts,objtypes);
	for(i=0;i<nEnts;i++) for(sp.ipart=pEnts[i]->GetStatus(&snp)-1;sp.ipart>=0;sp.ipart--)
	{
		sp.partid=-1; pEnts[i]->GetStatus(&sp);
		gwd.offset=sp.pos; gwd.R=Matrix33(sp.q); gwd.scale=sp.scale;
		if (sp.pGeomProxy->Intersect(pSphere,&gwd,0,&ip,pcontacts))
		{
			WriteLockCond lock(*ip.plock,0); lock.SetActive();
			++nHits;
			if (pEntity = (IEntity*)pEnts[i]->GetForeignData(PHYS_FOREIGN_ID_ENTITY))
				pObj->SetAt(nHits*3-2, pEntity->GetScriptTable());
			else
				pObj->SetNullAt(nHits*3-2);
			pObj->SetAt(nHits*3-1, sp.partid);
			pObj->SetAt(nHits*3, pWorld->GetPhysicalEntityId(pEnts[i]));
		}
	}
	pSphere->Release();

	return pH->EndFunction(*pObj);	
}