/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
// Original file Copyright Crytek GMBH or its affiliates, used under license.

#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 = gEnv->p3DEngine;
    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->LoadStatObjUnsafeManualRef(sGeometryFile, "#ForceBreakable", NULL, false);
    if (!pObj || pObj->IsDefaultObject())
    {
        ScriptWarning("<RegisterExplosionShape> Object file %s not found", sGeometryFile);
        return pH->EndFunction();
    }
    pObj->AddRef();
    pObj->GetIndexedMesh(true); // prepare idxMesh now

    if (sSplintersFile && *sSplintersFile != 0)            // if sSplintersFile was specified
    {
        IStatObj* pSplinters = gEnv->p3DEngine->LoadStatObjUnsafeManualRef(sSplintersFile, NULL, NULL, false);
        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();
    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->LoadStatObjUnsafeManualRef(sGeometryFile, "#ForceBreakable", NULL, false);
    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()->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 = gEnv->pEntitySystem->GetEntity((EntityId)skipId1.n);
    }
    if (nParams >= 6 && pH->GetParamType(6) != svtNull && pH->GetParam(6, skipId2))
    {
        skipEnt2 = gEnv->pEntitySystem->GetEntity((EntityId)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 = gEnv->pEntitySystem->GetEntity((EntityId)skipEntityId1.n);
    IEntity* skipEnt2 = gEnv->pEntitySystem->GetEntity((EntityId)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;
    ip.bThreadSafe = 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;
            WriteLockCond lock;
            if ((sp.flagsOR & (geom_colltype0 | geom_no_coll_response)) == geom_colltype0 && sp.pGeomProxy->IntersectLocked(pSphere, &gwd, 0, &ip, pcontacts, lock))
            {
                ++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]));
                if (pEnts[i]->GetType() != PE_ARTICULATED)
                {
                    break;
                }
            }
        }
    }
    pSphere->Release();

    return pH->EndFunction(*pObj);
}