/*
* 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 <ILMSerializationManager.h>
#include "StatObj.h"
#include "ObjMan.h"
#include "VisAreas.h"
#include "CullBuffer.h"
#include "3dEngine.h"
#include "IndexedMesh.h"
#include "Brush.h"

const char* CBrush::GetEntityClassName() const
{
    return "Brush";
}

const char* CBrush::GetName() const
{
    if (m_pStatObj)
    {
        return m_pStatObj->GetFilePath();
    }
    return "StatObjNotSet";
}

bool CBrush::HasChanged()
{
    return false;
}

#ifdef WIN64
#pragma warning( push )                                 //AMD Port
#pragma warning( disable : 4311 )
#endif

CLodValue CBrush::ComputeLod(int wantedLod, const SRenderingPassInfo& passInfo)
{
    CVars* pCVars = GetCVars();

    uint8 nDissolveRefA = 0;
    int nLodA = -1;
    int nLodB = -1;

    if (CStatObj* pStatObj = (CStatObj*)CBrush::GetEntityStatObj())
    {
        const Vec3 vCamPos = passInfo.GetCamera().GetPosition();
        const float fEntDistance = sqrt_tpl(Distance::Point_AABBSq(vCamPos, CBrush::GetBBox())) * passInfo.GetZoomFactor();
        
        if (pCVars->e_Dissolve && passInfo.IsGeneralPass() && !(pStatObj->m_nFlags & STATIC_OBJECT_COMPOUND))
        {
            int nLod = CLAMP(wantedLod, pStatObj->GetMinUsableLod(), (int)pStatObj->m_nMaxUsableLod);
            nLod = pStatObj->FindNearesLoadedLOD(nLod, true);

            SLodDistDissolveTransitionState& rState = m_pRNTmpData->userData.lodDistDissolveTransitionState;

            // if we're more than one LOD away we've either zoomed in quickly
            // or we streamed in some LODs really late. In either case we want
            // to pop rather than get stuck in a dissolve that started way too late.
            if (rState.nOldLod >= 0 && nLod >= 0 &&
                (rState.nOldLod > nLod + 1 || rState.nOldLod < nLod - 1)
                )
            {
                rState.nOldLod = nLod;
            }

            // when we first load before streaming we get a lod of -1. When a lod streams in
            // we kick off a transition to N, but without moving there's nothing to continue the transition.
            // Catch this case when we claim to be in lod -1 but are too close, and snap.
            if (rState.nOldLod == -1 && fEntDistance < 0.5f * m_fWSMaxViewDist)
            {
                rState.nOldLod = nLod;
            }

            uint32 prevState = (((uint32)rState.nOldLod) << 8) | rState.nNewLod;

            float fDissolve = GetObjManager()->GetLodDistDissolveRef(&rState, fEntDistance, nLod, passInfo);

            uint32 newState = (((uint32)rState.nOldLod) << 8) | rState.nNewLod;

            // ensure old lod is still around. If not find closest lod
            if (rState.nOldLod != rState.nNewLod && rState.nOldLod >= 0)
            {
                rState.nOldLod = pStatObj->FindNearesLoadedLOD(rState.nOldLod, true);
            }
            else if (rState.nOldLod >= 0)
            {
                // we can actually fall back into this case (even though we know nLod is valid).
                rState.nOldLod = rState.nNewLod = pStatObj->FindNearesLoadedLOD(rState.nOldLod, true);
            }

            // only bother to check if we are dissolving and we've just kicked off a new dissolve transition
            if (rState.nOldLod != rState.nNewLod && prevState != newState)
            {
                // LOD cutoff point, this is about where the transition should be triggered.
                const float fEntityLodRatio = std::max(GetLodRatioNormalized(), FLT_MIN);
                const float fDistMultiplier = 1.0f / (fEntityLodRatio * Get3DEngine()->GetFrameLodInfo().fTargetSize);
                float dist = pStatObj->GetLodDistance() * max(rState.nOldLod, rState.nNewLod) * fDistMultiplier;

                // we started way too late, most likely object LOD streamed in very late, just snap.
                if (fabsf(rState.fStartDist - dist) > GetFloatCVar(e_DissolveDistband))
                {
                    rState.nOldLod = rState.nNewLod;
                }
            }

            nDissolveRefA = (uint8)(255.f * SATURATE(fDissolve));
            nLodA = rState.nOldLod;
            nLodB = rState.nNewLod;
        }
        else
        {
            nDissolveRefA = 0;

            nLodA = CLAMP(wantedLod, pStatObj->GetMinUsableLod(), (int)pStatObj->m_nMaxUsableLod);
            if (!(pStatObj->m_nFlags & STATIC_OBJECT_COMPOUND))
            {
                nLodA = pStatObj->FindNearesLoadedLOD(nLodA, true);
            }
            nLodB = -1;
        }

        if (pCVars->e_Dissolve && !passInfo.IsCachedShadowPass())
        {
            float fDissolveDist = CLAMP(0.1f * m_fWSMaxViewDist, GetFloatCVar(e_DissolveDistMin), GetFloatCVar(e_DissolveDistMax));

            const float fDissolveStartDist = m_fWSMaxViewDist - fDissolveDist;

            if (fEntDistance > fDissolveStartDist)
            {
                float fDissolve = (fEntDistance - fDissolveStartDist)
                    / fDissolveDist;
                nDissolveRefA = (uint8)(255.f * SATURATE(fDissolve));
                nLodB = -1;
            }
        }
    }

    return CLodValue(nLodA, nDissolveRefA, nLodB);
}

void CBrush::Render(const struct SRendParams& _EntDrawParams, const SRenderingPassInfo& passInfo)
{
    FUNCTION_PROFILER_3DENGINE;

    if (!m_pStatObj || m_dwRndFlags & ERF_HIDDEN)
    {
        return; //false;
    }
    if (m_dwRndFlags & ERF_COLLISION_PROXY || m_dwRndFlags & ERF_RAYCAST_PROXY)
    {
        // Collision proxy is visible in Editor while in editing mode.
        if (!gEnv->IsEditor() || !gEnv->IsEditing())
        {
            if (GetCVars()->e_DebugDraw == 0)
            {
                return; //true;
            }
        }
    }

    // some parameters will be modified
    SRendParams rParms = _EntDrawParams;

    if (m_nMaterialLayers)
    {
        rParms.nMaterialLayers = m_nMaterialLayers;
    }

    rParms.pMatrix = &m_Matrix;
    rParms.nClipVolumeStencilRef = 0;
    rParms.pMaterial = m_pMaterial;
    rParms.ppRNTmpData = &m_pRNTmpData;

    // get statobj for rendering
    IStatObj* pStatObj = m_pStatObj;

    // render
    if (pStatObj)
    {
        pStatObj->Render(rParms, passInfo);
    }
}

#ifdef WIN64
#pragma warning( pop )                                  //AMD Port
#endif

void CBrush::SetMatrix(const Matrix34& mat)
{
    Get3DEngine()->UnRegisterEntityAsJob(this);

    bool replacePhys = false;

    if (!IsMatrixValid(mat))
    {
        Warning("Error: IRenderNode::SetMatrix: Invalid matrix passed from the editor - ignored, reset to identity: %s", GetName());
        replacePhys = true;
        m_Matrix.SetIdentity();
    }
    else
    {
        replacePhys = fabs(mat.GetColumn(0).len() - m_Matrix.GetColumn(0).len())
            + fabs(mat.GetColumn(1).len() - m_Matrix.GetColumn(1).len())
            + fabs(mat.GetColumn(2).len() - m_Matrix.GetColumn(2).len()) > FLT_EPSILON;
        m_Matrix = mat;
    }
    pe_params_foreign_data  foreignData;
    foreignData.iForeignFlags = 0;
    if (!replacePhys && m_pPhysEnt)
    {
        m_pPhysEnt->GetParams(&foreignData);
        replacePhys = !(foreignData.iForeignFlags & PFF_OUTDOOR_AREA) != !(m_dwRndFlags & ERF_NODYNWATER);
    }

    CalcBBox();

    Get3DEngine()->RegisterEntity(this);
    if (replacePhys)
    {
        Dephysicalize();
    }
    if (!m_pPhysEnt)
    {
        Physicalize();
    }
    else
    {
        // Just move physics.
        pe_status_placeholder spc;
        if (m_pPhysEnt->GetStatus(&spc) && !spc.pFullEntity)
        {
            pe_params_bbox pbb;
            pbb.BBox[0] = m_WSBBox.min;
            pbb.BBox[1] = m_WSBBox.max;
            m_pPhysEnt->SetParams(&pbb);
        }
        else
        {
            pe_params_pos par_pos;
            par_pos.pos = m_Matrix.GetTranslation();
            par_pos.q = Quat(Matrix33(m_Matrix) * Diag33(m_Matrix.GetColumn(0).len(), m_Matrix.GetColumn(1).len(), m_Matrix.GetColumn(2).len()).invert());
            m_pPhysEnt->SetParams(&par_pos);
        }

        //////////////////////////////////////////////////////////////////////////
        // Update physical flags.
        //////////////////////////////////////////////////////////////////////////
        if (m_dwRndFlags & ERF_HIDABLE)
        {
            foreignData.iForeignFlags |= PFF_HIDABLE;
        }
        else
        {
            foreignData.iForeignFlags &= ~PFF_HIDABLE;
        }
        if (m_dwRndFlags & ERF_HIDABLE_SECONDARY)
        {
            foreignData.iForeignFlags |= PFF_HIDABLE_SECONDARY;
        }
        else
        {
            foreignData.iForeignFlags &= ~PFF_HIDABLE_SECONDARY;
        }
        // flag to exclude from AI triangulation
        if (m_dwRndFlags & ERF_EXCLUDE_FROM_TRIANGULATION)
        {
            foreignData.iForeignFlags |= PFF_EXCLUDE_FROM_STATIC;
        }
        else
        {
            foreignData.iForeignFlags &= ~PFF_EXCLUDE_FROM_STATIC;
        }
        m_pPhysEnt->SetParams(&foreignData);
    }

    if (m_pDeform)
    {
        m_pDeform->BakeDeform(m_Matrix);
    }
}

void CBrush::CalcBBox()
{
    m_WSBBox.min = SetMaxBB();
    m_WSBBox.max = SetMinBB();

    if (!m_pStatObj)
    {
        return;
    }

    m_WSBBox.min = m_pStatObj->GetBoxMin();
    m_WSBBox.max = m_pStatObj->GetBoxMax();
    m_WSBBox.SetTransformedAABB(m_Matrix, m_WSBBox);
    m_fMatrixScale = m_Matrix.GetColumn0().GetLength();
}

CBrush::CBrush()
{
    m_WSBBox.min = m_WSBBox.max = Vec3(ZERO);
    m_dwRndFlags = 0;
    m_Matrix.SetIdentity();
    m_pPhysEnt = 0;
    m_Matrix.SetIdentity();
    m_pMaterial = 0;
    m_nLayerId = 0;
    m_pStatObj = NULL;
    m_pMaterial = NULL;
    m_bVehicleOnlyPhysics = false;
    m_bMerged = 0;
    m_bDrawLast = false;
    m_fMatrixScale = 1.f;
    m_collisionClassIdx = 0;
    m_pDeform = NULL;

    GetInstCount(GetRenderNodeType())++;
}

CBrush::~CBrush()
{
    INDENT_LOG_DURING_SCOPE(true, "Destroying brush \"%s\"", this->GetName());

    I3DEngine* p3DEngine = GetISystem()->GetI3DEngine();

    Dephysicalize();
    p3DEngine->FreeRenderNodeState(this);

    m_pStatObj = NULL;
    if (m_pDeform)
    {
        delete m_pDeform;
    }

    if (m_pRNTmpData)
    {
        p3DEngine->FreeRNTmpData(&m_pRNTmpData);
    }
    assert(!m_pRNTmpData);

    GetInstCount(GetRenderNodeType())--;
}

void CBrush::Physicalize(bool bInstant)
{
    PhysicalizeOnHeap(NULL, bInstant);
}

void CBrush::PhysicalizeOnHeap(IGeneralMemoryHeap* pHeap, bool bInstant)
{
    if (m_pStatObj && (m_pStatObj->GetBreakableByGame() || m_pStatObj->GetIDMatBreakable() != -1))
    {
        pHeap = m_p3DEngine->GetBreakableBrushHeap();
    }

    float fScaleX = m_Matrix.GetColumn(0).len();
    float fScaleY = m_Matrix.GetColumn(1).len();
    float fScaleZ = m_Matrix.GetColumn(2).len();

    if (!m_pStatObj || !m_pStatObj->IsPhysicsExist())
    { // skip non uniform scaled object or objects without physics
      // Check if we are acompound object.
        if (m_pStatObj && !m_pStatObj->IsPhysicsExist() && (m_pStatObj->GetFlags() & STATIC_OBJECT_COMPOUND))
        {
            // Try to physicalize compound object.
        }
        else
        {
            Dephysicalize();
            return;
        }
    }

    AABB WSBBox = GetBBox();
    bool notPodable = max(WSBBox.max.x - WSBBox.min.x, WSBBox.max.y - WSBBox.min.y) > Get3DEngine()->GetCVars()->e_OnDemandMaxSize;
    if (!(GetCVars()->e_OnDemandPhysics & 0x2)
        || notPodable
        || (m_pStatObj->GetFlags() & STATIC_OBJECT_COMPOUND))
    {
        bInstant = true;
    }

    if (m_pDeform)
    {
        m_pDeform->CreateDeformableSubObject(true, GetMatrix(), pHeap);
    }

    if (!bInstant)
    {
#if ENABLE_CRY_PHYSICS
        gEnv->pPhysicalWorld->RegisterBBoxInPODGrid(&WSBBox.min);
#endif
        return;
    }

    // create new
    if (!m_pPhysEnt)
    {
#if ENABLE_CRY_PHYSICS
        m_pPhysEnt = GetSystem()->GetIPhysicalWorld()->CreatePhysicalEntity(PE_STATIC, NULL, (IRenderNode*)this, PHYS_FOREIGN_ID_STATIC, -1, pHeap);
#endif
        if (!m_pPhysEnt)
        {
            return;
        }
    }

    pe_action_remove_all_parts remove_all;
    m_pPhysEnt->Action(&remove_all);

    pe_geomparams params;
    if (m_pStatObj->GetPhysGeom(PHYS_GEOM_TYPE_DEFAULT))
    {
        if (GetRndFlags() & ERF_COLLISION_PROXY)
        {
            // Collision proxy only collides with players and vehicles.
            params.flags = geom_colltype_player | geom_colltype_vehicle;
        }
        if (GetRndFlags() & ERF_RAYCAST_PROXY)
        {
            // Collision proxy only collides with players and vehicles.
            params.flags = geom_colltype_ray;
        }
        /*if (m_pStatObj->m_arrPhysGeomInfo[PHYS_GEOM_TYPE_NO_COLLIDE])
        params.flags &= ~geom_colltype_ray;*/
        if (m_bVehicleOnlyPhysics || (m_pStatObj->GetVehicleOnlyPhysics() != 0))
        {
            params.flags = geom_colltype_vehicle;
        }
        if (GetCVars()->e_ObjQuality != CONFIG_LOW_SPEC)
        {
            params.idmatBreakable = m_pStatObj->GetIDMatBreakable();
            if (m_pStatObj->GetBreakableByGame())
            {
                params.flags |= geom_manually_breakable;
            }
        }
        else
        {
            params.idmatBreakable = -1;
        }
    }

    Matrix34 mtxScale;
    mtxScale.SetScale(Vec3(fScaleX, fScaleY, fScaleZ));
    params.pMtx3x4 = &mtxScale;
    m_pStatObj->Physicalize(m_pPhysEnt, &params);

    if (m_dwRndFlags & (ERF_HIDABLE | ERF_HIDABLE_SECONDARY | ERF_EXCLUDE_FROM_TRIANGULATION | ERF_NODYNWATER))
    {
        pe_params_foreign_data  foreignData;
        m_pPhysEnt->GetParams(&foreignData);
        if (m_dwRndFlags & ERF_HIDABLE)
        {
            foreignData.iForeignFlags |= PFF_HIDABLE;
        }
        if (m_dwRndFlags & ERF_HIDABLE_SECONDARY)
        {
            foreignData.iForeignFlags |= PFF_HIDABLE_SECONDARY;
        }
        //[PETAR] new flag to exclude from triangulation
        if (m_dwRndFlags & ERF_EXCLUDE_FROM_TRIANGULATION)
        {
            foreignData.iForeignFlags |= PFF_EXCLUDE_FROM_STATIC;
        }
        if (m_dwRndFlags & ERF_NODYNWATER)
        {
            foreignData.iForeignFlags |= PFF_OUTDOOR_AREA;
        }
        m_pPhysEnt->SetParams(&foreignData);
    }

    pe_params_flags par_flags;
    par_flags.flagsOR = pef_never_affect_triggers | pef_log_state_changes;
    m_pPhysEnt->SetParams(&par_flags);

    pe_params_pos par_pos;
    par_pos.pos = m_Matrix.GetTranslation();
    par_pos.q = Quat(Matrix33(m_Matrix) * Diag33(fScaleX, fScaleY, fScaleZ).invert());
    par_pos.bEntGridUseOBB = 1;
    m_pPhysEnt->SetParams(&par_pos);

    pe_params_collision_class pcc;
    Get3DEngine()->GetCollisionClass(pcc.collisionClassOR, m_collisionClassIdx);
    m_pPhysEnt->SetParams(&pcc);

    if (m_pMaterial)
    {
        UpdatePhysicalMaterials();
    }

    if (GetRndFlags() & ERF_NODYNWATER)
    {
        pe_params_part ppart;
        ppart.flagsAND = ~geom_floats;
        m_pPhysEnt->SetParams(&ppart);
    }
}

bool CBrush::PhysicalizeFoliage(bool bPhysicalize, int iSource, int nSlot)
{
    if (nSlot < 0)
    {
        bool res = false;
        for (int i = 0; i < m_pStatObj->GetSubObjectCount(); i++)
        {
            res = res || PhysicalizeFoliage(bPhysicalize, iSource, i);
        }
        return res;
    }

    if (IStatObj::SSubObject* pSubObj = m_pStatObj->GetSubObject(nSlot))
    {
        if (bPhysicalize)
        {
            if (!pSubObj->pStatObj || !((CStatObj*)pSubObj->pStatObj)->m_nSpines)
            {
                return false;
            }
            if (!(m_pStatObj->GetFlags() & STATIC_OBJECT_CLONE))
            {
                m_pStatObj = m_pStatObj->Clone(false, false, false);
                pSubObj = m_pStatObj->GetSubObject(nSlot);
            }
            Matrix34 mtx = m_Matrix * pSubObj->localTM;
            pSubObj->pStatObj->PhysicalizeFoliage(m_pPhysEnt, mtx, pSubObj->pFoliage, GetCVars()->e_FoliageBranchesTimeout, iSource);
            return pSubObj->pFoliage != 0;
        }
        else if (pSubObj->pFoliage)
        {
            pSubObj->pFoliage->Release();
            return true;
        }
    }
    return false;
}

IFoliage* CBrush::GetFoliage(int nSlot)
{
    if (IStatObj::SSubObject* pSubObj = m_pStatObj->GetSubObject(nSlot))
    {
        return pSubObj->pFoliage;
    }
    return 0;
}

//////////////////////////////////////////////////////////////////////////
void CBrush::UpdatePhysicalMaterials(int bThreadSafe)
{
    if (!m_pPhysEnt)
    {
        return;
    }

    if ((GetRndFlags() & ERF_COLLISION_PROXY) && m_pPhysEnt)
    {
        pe_params_part ppart;
        ppart.flagsAND = 0;
        ppart.flagsOR = geom_colltype_player | geom_colltype_vehicle;
        m_pPhysEnt->SetParams(&ppart);
    }

    if ((GetRndFlags() & ERF_RAYCAST_PROXY) && m_pPhysEnt)
    {
        pe_params_part ppart;
        ppart.flagsAND = 0;
        ppart.flagsOR = geom_colltype_ray;
        m_pPhysEnt->SetParams(&ppart);
    }

    if (m_pMaterial)
    {
        // Assign custom material to physics.
        int surfaceTypesId[MAX_SUB_MATERIALS];
        memset(surfaceTypesId, 0, sizeof(surfaceTypesId));
        int i, numIds = m_pMaterial->FillSurfaceTypeIds(surfaceTypesId);
        ISurfaceTypeManager* pSurfaceTypeManager = Get3DEngine()->GetMaterialManager()->GetSurfaceTypeManager();
        ISurfaceType* pMat;
        bool bBreakable = false;

        for (i = 0, m_bVehicleOnlyPhysics = false; i < numIds; i++)
        {
            if (pMat = pSurfaceTypeManager->GetSurfaceType(surfaceTypesId[i]))
            {
                if (pMat->GetFlags() & SURFACE_TYPE_VEHICLE_ONLY_COLLISION)
                {
                    m_bVehicleOnlyPhysics = true;
                }
                if (pMat->GetBreakability())
                {
                    bBreakable = true;
                }
            }
        }

        if (bBreakable && m_pStatObj)
        {
            // mark the rendermesh as KepSysMesh so that it is kept in system memory
            if (m_pStatObj->GetRenderMesh())
            {
                m_pStatObj->GetRenderMesh()->KeepSysMesh(true);
            }

            m_pStatObj->SetFlags(m_pStatObj->GetFlags() | STATIC_OBJECT_DYNAMIC);
        }
        pe_params_part ppart;
        ppart.nMats = numIds;
        ppart.pMatMapping = surfaceTypesId;
        if (m_bVehicleOnlyPhysics)
        {
            ppart.flagsAND = geom_colltype_vehicle;
        }
        if ((pMat = m_pMaterial->GetSurfaceType()) && pMat->GetPhyscalParams().collType >= 0)
        {
            ppart.flagsAND = ~(geom_collides | geom_floats), ppart.flagsOR = pMat->GetPhyscalParams().collType;
        }
        m_pPhysEnt->SetParams(&ppart, bThreadSafe);
    }
    else if (m_bVehicleOnlyPhysics)
    {
        m_bVehicleOnlyPhysics = false;
        if (!m_pStatObj->GetVehicleOnlyPhysics())
        {
            pe_params_part ppart;
            ppart.flagsOR = geom_colltype_solid | geom_colltype_ray | geom_floats | geom_colltype_explosion;
            m_pPhysEnt->SetParams(&ppart, bThreadSafe);
        }
    }
}

void CBrush::Dephysicalize(bool bKeepIfReferenced)
{
    AABB WSBBox = GetBBox();

    // delete old physics
#if ENABLE_CRY_PHYSICS
    if (m_pPhysEnt && 0 != GetSystem()->GetIPhysicalWorld()->DestroyPhysicalEntity(m_pPhysEnt, ((int)bKeepIfReferenced) * 4))
#endif
    {
        m_pPhysEnt = 0;
    }

    if (!bKeepIfReferenced)
    {
        if (m_pDeform)
        {
            m_pDeform->CreateDeformableSubObject(false, GetMatrix(), NULL);
        }
    }
}

void CBrush::Dematerialize()
{
    if (m_pMaterial)
    {
        m_pMaterial = 0;
    }

    UpdateExecuteAsPreProcessJobFlag();
}

#if ENABLE_CRY_PHYSICS
IPhysicalEntity* CBrush::GetPhysics() const
{
    return m_pPhysEnt;
}

//////////////////////////////////////////////////////////////////////////
void CBrush::SetPhysics(IPhysicalEntity* pPhys)
{
    m_pPhysEnt = pPhys;
}
#endif // ENABLE_CRY_PHYSICS

//////////////////////////////////////////////////////////////////////////
bool CBrush::IsMatrixValid(const Matrix34& mat)
{
    Vec3 vScaleTest = mat.TransformVector(Vec3(0, 0, 1));
    float fDist = mat.GetTranslation().GetDistance(Vec3(0, 0, 0));

    if (vScaleTest.GetLength() > 1000.f || vScaleTest.GetLength() < 0.01f || fDist > 256000 ||
        !_finite(vScaleTest.x) || !_finite(vScaleTest.y) || !_finite(vScaleTest.z))
    {
        return false;
    }

    return true;
}

//////////////////////////////////////////////////////////////////////////
void CBrush::SetMaterial(_smart_ptr<IMaterial> pMat)
{
    m_pMaterial = pMat;

    bool bCollisionProxy = false;
    bool bRaycastProxy = false;

    if (pMat)
    {
        if (pMat->GetFlags() & MTL_FLAG_COLLISION_PROXY)
        {
            bCollisionProxy = true;
        }

        if (pMat->GetFlags() & MTL_FLAG_RAYCAST_PROXY)
        {
            bRaycastProxy = true;
        }
    }
    SetRndFlags(ERF_COLLISION_PROXY, bCollisionProxy);
    SetRndFlags(ERF_RAYCAST_PROXY, bRaycastProxy);

    UpdatePhysicalMaterials();

    // register and get brush material id
    m_pMaterial = pMat;

    UpdateExecuteAsPreProcessJobFlag();
}

void CBrush::UpdateExecuteAsPreProcessJobFlag()
{
    _smart_ptr<IMaterial> pMat = GetMaterial();
    m_bExecuteAsPreprocessJob = false;

    // check if this Brush needs to be executed as a preprocess job
    if (pMat)
    {
        SShaderItem& shaderItem = pMat->GetShaderItem();
        if (shaderItem.m_pShader)
        {
            uint32 nFlags = shaderItem.m_pShader->GetFlags2();
            m_bExecuteAsPreprocessJob = m_bExecuteAsPreprocessJob || ((nFlags & EF2_FORCE_WATERPASS) != 0);
        }

        // also check submaterials
        for (int i = 0, nNum = pMat->GetSubMtlCount(); i < nNum; ++i)
        {
            SShaderItem& subShaderItem = pMat->GetShaderItem(i);
            if (subShaderItem.m_pShader)
            {
                uint32 nFlags = subShaderItem.m_pShader->GetFlags2();
                m_bExecuteAsPreprocessJob = m_bExecuteAsPreprocessJob || ((nFlags & EF2_FORCE_WATERPASS) != 0);
            }
        }

#if defined(FEATURE_SVO_GI)
        if (pMat && (gEnv->pConsole->GetCVar("e_svoTI_Active") && 
            gEnv->pConsole->GetCVar("e_svoTI_Active")->GetIVal() && 
            gEnv->pConsole->GetCVar("e_GI")->GetIVal()))
        {
            pMat->SetKeepLowResSysCopyForDiffTex();
        }
#endif
    }
}

void CBrush::CheckPhysicalized()
{
    if (!m_pPhysEnt)
    {
        Physicalize();
    }
}


void CBrush::GetMemoryUsage(ICrySizer* pSizer) const
{
    SIZER_COMPONENT_NAME(pSizer, "Brush");
    pSizer->AddObject(this, sizeof(*this));
}

void CBrush::SetEntityStatObj(unsigned int nSlot, IStatObj* pStatObj, const Matrix34A* pMatrix)
{
    //assert(pStatObj);

    IStatObj* pPrevStatObj = m_pStatObj;

    m_pStatObj = (CStatObj*)pStatObj;

    // If object differ we must re-physicalize.
    if (pStatObj != pPrevStatObj)
    {
        if (!pPrevStatObj || !pStatObj || (pStatObj->GetCloneSourceObject() != pPrevStatObj))
        {      
            Physicalize();
        }
    }

    if (pMatrix)
    {
        SetMatrix(*pMatrix);
    }

    if (m_pRNTmpData)
    {
        Get3DEngine()->FreeRNTmpData(&m_pRNTmpData);
    }
    assert(!m_pRNTmpData);

    m_nInternalFlags |= UPDATE_DECALS;

    if (m_pStatObj && m_pStatObj->IsDeformable())
    {
        if (!m_pDeform)
        {
            m_pDeform = new CDeformableNode();
        }
        m_pDeform->SetStatObj(static_cast<CStatObj*>(m_pStatObj.get()));
        m_pDeform->BakeDeform(GetMatrix());
    }
    else
    {
        SAFE_DELETE(m_pDeform);
    }

    UpdateExecuteAsPreProcessJobFlag();
}

void CBrush::SetStatObj(IStatObj* pStatObj)
{
    m_pStatObj = pStatObj;
    if (m_pStatObj && m_pStatObj->IsDeformable())
    {
        if (!m_pDeform)
        {
            m_pDeform = new CDeformableNode();
        }
        m_pDeform->SetStatObj(static_cast<CStatObj*>(m_pStatObj.get()));
        m_pDeform->BakeDeform(GetMatrix());
    }
    else
    {
        SAFE_DELETE(m_pDeform);
    }

    UpdateExecuteAsPreProcessJobFlag();
}

IRenderNode* CBrush::Clone() const
{
    CBrush* pDestBrush     = new CBrush();

    //CBrush member vars
    //  potential issues with Smart Pointers
    pDestBrush->m_Matrix                = m_Matrix;
    pDestBrush->m_fMatrixScale  = m_fMatrixScale;
    //pDestBrush->m_pPhysEnt        //Don't want to copy the phys ent pointer
    pDestBrush->m_pMaterial         = m_pMaterial;
    pDestBrush->m_pStatObj          = m_pStatObj;

    pDestBrush->m_bVehicleOnlyPhysics   = m_bVehicleOnlyPhysics;
    pDestBrush->m_bMerged                           = m_bMerged;
    pDestBrush->m_bDrawLast                     = m_bDrawLast;
    pDestBrush->m_WSBBox                            = m_WSBBox;

    //IRenderNode member vars
    //  We cannot just copy over due to issues with the linked list of IRenderNode objects
    CopyIRenderNodeData(pDestBrush);

    pDestBrush->UpdateExecuteAsPreProcessJobFlag();

    return pDestBrush;
}

IRenderMesh* CBrush::GetRenderMesh(int nLod)
{
    IStatObj* pStatObj = m_pStatObj ? m_pStatObj->GetLodObject(nLod) : NULL;
    return pStatObj ? pStatObj->GetRenderMesh() : NULL;
}

void CBrush::OffsetPosition(const Vec3& delta)
{
    if (m_pRNTmpData)
    {
        m_pRNTmpData->OffsetPosition(delta);
    }
    m_Matrix.SetTranslation(m_Matrix.GetTranslation() + delta);
    m_WSBBox.Move(delta);

    if (m_pPhysEnt)
    {
        pe_params_pos par_pos;
        par_pos.pos = m_Matrix.GetTranslation();
        m_pPhysEnt->SetParams(&par_pos);
    }
}

bool CBrush::GetLodDistances(const SFrameLodInfo& frameLodInfo, float* distances) const
{
    const float fEntityLodRatio = GetLodRatioNormalized();
    if (fEntityLodRatio >  0.0f)
    {
        const float fDistMultiplier = 1.0f / (fEntityLodRatio * frameLodInfo.fTargetSize);
        const float lodDistance = m_pStatObj ? m_pStatObj->GetLodDistance() : FLT_MAX;

        for (uint i = 0; i < SMeshLodInfo::s_nMaxLodCount; ++i)
        {
            distances[i] = lodDistance * (i + 1) * fDistMultiplier;
        }
    }
    else
    {
        for (uint i = 0; i < SMeshLodInfo::s_nMaxLodCount; ++i)
        {
            distances[i] = FLT_MAX;
        }
    }

    return true;
}