/*
* 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"
#if ENABLE_CRY_PHYSICS
#include "CREBreakableGlass.h"

#include "Utils/PolygonMath2D.h"
#include "Utils/SpatialHashGrid.h"

#include "I3DEngine.h"
#include "IEntityRenderState.h"
#include <IParticles.h>
#include <IRenderAuxGeom.h>

// Statics
const SBreakableGlassCVars* CREBreakableGlass::s_pCVars = NULL;
float CREBreakableGlass::s_loosenTimer = 0.0f;
float CREBreakableGlass::s_impactTimer = 0.0f;

// Forward declare constant "CBreakableGlassBuffer::NoBuffer"
#define NO_BUFFER                                   0

// Profiling
#ifndef GLASS_FUNC_PROFILER
// #ifndef RELEASE
//  #include "FrameProfiler.h"
//  #define GLASS_FUNC_PROFILER         FUNCTION_PROFILER_SYS(GLASS)
//  #define GLASS_PROFILE_ENABLED
//  #define GLASS_PROFILE_AUTO_BREAK
// #else
    #define GLASS_FUNC_PROFILER
//#endif
#endif

// Debug drawing
#ifdef GLASS_DEBUG_MODE
#define IMPACT_SIZE                             0.01f
#define IMPACT_HALF_SIZE                    (IMPACT_SIZE * 0.5f)
#define IMPACT_NUM_IMPULSE_SIDES    10
#endif

// Error logging
#ifndef RELEASE
#define LOG_GLASS_ERROR(...)            CryLogAlways("[BreakGlassSystem Error]: " __VA_ARGS__)
#else
#define LOG_GLASS_ERROR(...)
#endif

// RGBA vertex layout
enum EVertRGBAIndex
{
    EColIdx_R = 2,
    EColIdx_G = 1,
    EColIdx_B = 0,
    EColIdx_A = 3,
};


//--------------------------------------------------------------------------------------------------
// Name: CREBreakableGlass
// Desc: Constructor
//--------------------------------------------------------------------------------------------------
CREBreakableGlass::CREBreakableGlass()
    : m_invMaxGlassSize(0.0f)
    , m_impactEnergy(0.0f)
    , m_geomBufferId(NO_BUFFER)
    , m_seed(0)
    , m_fragsActive(0)
    , m_fragsLoose(0)
    , m_fragsFree(0)
    , m_lastFragBit(0)
    , m_numDecalImpacts(0)
    , m_numDecalPSConsts(0)
    , m_pointArrayCount(0)
    , m_lastPhysFrag(0)
    , m_totalNumFrags(0)
    , m_lastFragIndex(0)
    , m_numLooseFrags(0)
    , m_shattered(false)
    , m_geomDirty(false)
    , m_geomRebuilt(false)
    , m_geomBufferLost(false)
    , m_dirtyGeomBufferCount(0)
    , m_geomUpdateFrame(0)
#if GLASSCFG_USE_HASH_GRID
    , m_pHashGrid(NULL)
#endif
{
#ifndef RELEASE
    // Data size assumption/optimization validations
    TGlassDefFragmentArray sizeTestArray1;
    TGlassFragmentIndexArray sizeTestArray2;

    COMPILE_TIME_ASSERT(GLASSCFG_FRAGMENT_ARRAY_SIZE <= 32);
    CRY_ASSERT(POLY_ARRAY_SIZE == sizeTestArray1.max_size());
    CRY_ASSERT(POLY_ARRAY_SIZE == sizeTestArray2.max_size());
#endif

    // Default data
    memset(m_pointArray, 0, sizeof(Vec2) * GLASSCFG_MAX_NUM_CRACK_POINTS);

    for (uint i = 0; i < GLASSCFG_MAX_NUM_PHYS_FRAGMENTS; ++i)
    {
        m_fragGeomRebuilt[i] = false;
    }

    SetHashGridSize(Vec2_One);

    // Initially all frag indices are free
    uint32 fragBit = 1;
    for (uint i = 0; i < GLASSCFG_FRAGMENT_ARRAY_SIZE; ++i, fragBit <<= 1)
    {
        m_frags.push_back(SGlassFragment());
        m_freeFragIndices.push_back(i);
        m_fragsFree |= fragBit;
    }

    // Mark fragment geom buffers with invalid Ids
    for (uint i = 0; i < GLASSCFG_MAX_NUM_PHYS_FRAGMENTS; ++i)
    {
        m_fragGeomBufferIds[i].m_fragId = GLASSCFG_FRAGMENT_ARRAY_SIZE;
        m_fragGeomBufferIds[i].m_geomBufferId = NO_BUFFER;
    }

    // Set invisible/invalid data for decal shader constants
    const float invalidUVPosition = -100.0f;
    const float invalidUVScale = 0.01f;

    for (uint i = 0; i < GLASSCFG_MAX_NUM_IMPACT_DECALS; ++i)
    {
        m_decalPSConsts[i].decal.x = m_decalPSConsts[i].decal.y = invalidUVPosition;
        m_decalPSConsts[i].decal.z = m_decalPSConsts[i].decal.w = invalidUVScale;
    }

}//-------------------------------------------------------------------------------------------------

//--------------------------------------------------------------------------------------------------
// Name: ~CREBreakableGlass
// Desc: Destructor (Called from RT)
//--------------------------------------------------------------------------------------------------
CREBreakableGlass::~CREBreakableGlass()
{
#if GLASSCFG_USE_HASH_GRID
    SAFE_DELETE(m_pHashGrid);
#endif
}//-------------------------------------------------------------------------------------------------

//--------------------------------------------------------------------------------------------------
// Name: InitialiseRenderElement
// Desc: Initializes render element
//--------------------------------------------------------------------------------------------------
bool CREBreakableGlass::InitialiseRenderElement(const SBreakableGlassInitParams& params)
{
#if GLASSCFG_USE_HASH_GRID
    if (m_pHashGrid)
    {
        CRY_ASSERT_MESSAGE(0, "Glass render element initialised twice.");
        LOG_GLASS_ERROR("Element initialised twice.");
    }
#endif

    // Copy data
    m_glassParams = params;

    // Assert minimum size data
    const float minGlassSize = 0.05f;
    m_glassParams.size.x = max(m_glassParams.size.x, minGlassSize);
    m_glassParams.size.y = max(m_glassParams.size.y, minGlassSize);

    m_invMaxGlassSize = 1.0f / max(m_glassParams.size.x, m_glassParams.size.y);

    // Calculate uv coord range
    Vec2 uvPt0 = m_glassParams.uvOrigin;
    Vec2 uvPt1 = m_glassParams.uvOrigin + m_glassParams.uvXAxis * m_glassParams.size.x;
    Vec2 uvPt2 = m_glassParams.uvOrigin + m_glassParams.uvYAxis * m_glassParams.size.y;
    Vec2 uvPt3 = m_glassParams.uvOrigin + m_glassParams.uvXAxis * m_glassParams.size.x + m_glassParams.uvYAxis * m_glassParams.size.y;

    Vec2 uvMin, uvMax;
    uvMin.x = min(min(uvPt0.x, uvPt1.x), min(uvPt2.x, uvPt3.x));
    uvMin.y = min(min(uvPt0.y, uvPt1.y), min(uvPt2.y, uvPt3.y));
    uvMax.x = max(max(uvPt0.x, uvPt1.x), max(uvPt2.x, uvPt3.x));
    uvMax.y = max(max(uvPt0.y, uvPt1.y), max(uvPt2.y, uvPt3.y));

    m_invUVRange = uvMax - uvMin;
    m_invUVRange.x = 1.0f / max(m_invUVRange.x, 0.01f);
    m_invUVRange.y = 1.0f / max(m_invUVRange.y, 0.01f);

    // Pre-factor (uv-space) glass size into range
    Vec2 uvXAxis = m_glassParams.uvXAxis.GetNormalized();
    Vec2 uvYAxis = m_glassParams.uvYAxis.GetNormalized();

    Vec2 uvGlassSize;
    uvGlassSize.x = m_glassParams.size.x * uvXAxis.x + m_glassParams.size.y * uvYAxis.x;
    uvGlassSize.y = m_glassParams.size.x * uvXAxis.y + m_glassParams.size.y * uvYAxis.y;

    m_invUVRange.x *= uvGlassSize.x;
    m_invUVRange.y *= uvGlassSize.y;

    // Create hash grid
#if GLASSCFG_USE_HASH_GRID
    if (!m_pHashGrid)
    {
        m_pHashGrid = new TGlassHashGrid(params.size.x, params.size.y);
        m_pHashGrid->ClearGrid();
    }
#endif

    // Re-calculate hash cell sizes
    SetHashGridSize(m_glassParams.size);

    // Reset glass state
    m_shattered = false;
    m_geomDirty = false;
    m_geomRebuilt = false;
    m_dirtyGeomBufferCount = 0;
    m_fragsActive = 0;
    m_fragsLoose = 0;

    for (uint i = 0; i < GLASSCFG_MAX_NUM_PHYS_FRAGMENTS; ++i)
    {
        m_fragGeomRebuilt[i] = false;
    }

    // Create initial mesh
    if (!GenerateGeomFromFragment(params.pInitialFrag, params.numInitialFragPts))
    {
        GenerateDefaultPlaneGeom();
    }

    return true;
}//-------------------------------------------------------------------------------------------------

//--------------------------------------------------------------------------------------------------
// Name: ReleaseRenderElement
// Desc: Initializes render element
//--------------------------------------------------------------------------------------------------
void CREBreakableGlass::ReleaseRenderElement()
{
#if GLASSCFG_USE_HASH_GRID
    // Hash grid and element data
    SAFE_DELETE(m_pHashGrid);
#endif

    // "False" here allows a delayed delete, avoiding any issues of it being mid draw-call etc.
    Release(false);
}//-------------------------------------------------------------------------------------------------

//--------------------------------------------------------------------------------------------------
// Name: Update
// Desc: Propels loose pieces away from plane to the ground
//--------------------------------------------------------------------------------------------------
void CREBreakableGlass::Update(SBreakableGlassUpdateParams& params)
{
    // Periodically loosen weakly-linked fragments
    if (s_loosenTimer > 1.0f + GetRandF() && s_impactTimer > 1.0f && s_impactTimer < 2.0f)
    {
        const uint32 activeState = m_fragsActive & ~m_fragsFree;
        uint32 fragBit = 1;

        for (uint i = 0; i < GLASSCFG_FRAGMENT_ARRAY_SIZE; ++i, fragBit <<= 1)
        {
            const SGlassFragment& frag = m_frags[i];

            if (activeState & fragBit && IsFragmentWeaklyLinked(frag))
            {
                // Force update as we will loosen this fragment
                m_geomDirty = true;
                break;
            }
        }
    }

    s_loosenTimer += params.m_frametime;
    s_impactTimer += params.m_frametime;

    // Fill return flags before they are cleared
    params.m_geomChanged = m_geomDirty;

#ifdef GLASS_PROFILE_AUTO_BREAK
    static int autoBreak = 0;
    uint32 fragBit = 1;
    SGlassImpactParams impact;

    switch (autoBreak)
    {
    case 3:
        ResetTempCrackData();

        impact = m_impactParams.back();
        impact.seed = 0;

        m_impactParams.clear();
        m_frags.clear();
        m_freeFragIndices.clear();
        m_fragsFree = 0;

        for (uint i = 0; i < GLASSCFG_FRAGMENT_ARRAY_SIZE; ++i, fragBit <<= 1)
        {
            m_frags.push_back(SGlassFragment());
            m_freeFragIndices.push_back(i);
            m_fragsFree |= fragBit;
        }

        m_fragsActive = 0;
        m_fragsLoose = 0;
        m_lastFragBit = 0;
        m_lastPhysFrag = 0;
        m_totalNumFrags = 0;
        m_lastFragIndex = 0;
        m_numLooseFrags = 0;
        m_numDecalImpacts = 0;
        m_numDecalPSConsts = 0;
        m_shattered = false;
        m_geomDirty = false;
        m_geomRebuilt = false;
        m_geomBufferLost = false;
        m_dirtyGeomBufferCount = 0;

        m_geomBufferId = NO_BUFFER;

        for (uint i = 0; i < GLASSCFG_MAX_NUM_PHYS_FRAGMENTS; ++i)
        {
            m_fragGeomBufferIds[i].m_fragId = GLASSCFG_FRAGMENT_ARRAY_SIZE;
            m_fragGeomBufferIds[i].m_geomBufferId = NO_BUFFER;
            m_fragGeomRebuilt[i] = false;
        }

        for (uint i = 0; i < GLASSCFG_MAX_NUM_IMPACT_DECALS; ++i)
        {
            m_decalPSConsts[i].decal.x = m_decalPSConsts[i].decal.y = -100.0f;
            m_decalPSConsts[i].decal.z = m_decalPSConsts[i].decal.w = 0.01f;
        }

#ifdef GLASS_DEBUG_MODE
        m_impactLineList.clear();
#endif

        GenerateDefaultPlaneGeom();
        break;

    case 6:
        impact.velocity = Vec3(-930.58624, -442.86945, 384.53485);
        impact.x = 0.29331410;
        impact.y = 1.3911150;
        impact.impulse = 308.00000;
        impact.speed = 1099.9960;
        impact.radius = 0.0099999998;
        impact.seed = 3527700152;
        ApplyImpactToGlass(impact);
        break;

    case 9:
        impact.velocity = Vec3(-1085.3186, 78.216599, 161.12334);
        impact.x = 2.3538542;
        impact.y = 0.37980682;
        impact.impulse = 308.00000;
        impact.speed = 1099.9978;
        impact.radius = 0.0099999998;
        impact.seed = 2126611280;
        ApplyImpactToGlass(impact);
        break;

    case 12:
        impact.velocity = Vec3(-1047.5664, -277.38022, 188.83971);
        impact.x = 0.96540481;
        impact.y = 0.51500261;
        impact.impulse = 308.00000;
        impact.speed = 1099.9979;
        impact.radius = 0.0099999998;
        impact.seed = 1035809870;
        ApplyImpactToGlass(impact);
        break;

    case 15:
        impact.velocity = Vec3(-986.25885, 144.72720, 465.11740);
        impact.x = 2.5825634;
        impact.y = 1.5812253;
        impact.impulse = 308.00000;
        impact.speed = 1099.9939;
        impact.radius = 0.0099999998;
        impact.seed = 381451321;
        ApplyImpactToGlass(impact);
        break;

    case 18:
        impact.velocity = Vec3(-910.84277, -478.89627, 388.60526);
        impact.x = 0.11862427;
        impact.y = 1.4369123;
        impact.impulse = 308.00000;
        impact.speed = 1099.9956;
        impact.radius = 0.0099999998;
        impact.seed = 3410067940;
        ApplyImpactToGlass(impact);
        break;

    case 21:
        impact.velocity = Vec3(-1001.4235, -436.36160, 129.36899);
        impact.x = 0.21810441;
        impact.y = 0.29683763;
        impact.impulse = 308.00000;
        impact.speed = 1099.9985;
        impact.radius = 0.0099999998;
        impact.seed = 2357971410;
        ApplyImpactToGlass(impact);
        break;

    case 24:
        impact.velocity = Vec3(-1079.0485, -41.873333, 209.51102);
        impact.x = 1.8947206;
        impact.y = 0.57215804;
        impact.impulse = 308.00000;
        impact.speed = 1099.9972;
        impact.radius = 0.0099999998;
        impact.seed = 2609387782;
        ApplyImpactToGlass(impact);
        break;

    case 27:
        impact.velocity = Vec3(-1072.4220, 144.37529, 197.63870);
        impact.x = 2.6052594;
        impact.y = 0.53135908;
        impact.impulse = 308.00000;
        impact.speed = 1099.9974;
        impact.radius = 0.0099999998;
        impact.seed = 3082654461;
        ApplyImpactToGlass(impact);
        break;
    }

    static int updateAuto = 0;
    autoBreak += (updateAuto == 0 || (autoBreak % 3) == 0) ? 1 : 0;
    updateAuto = (updateAuto + 1) % 7;
    if (autoBreak > 35)
    {
        autoBreak = 0;
    }
#endif // GLASS_PROFILE_AUTO_BREAK

    // Shatter glass if/when we lose our geom buffer
    if (m_geomBufferLost)
    {
        ShatterGlass();
    }

    // Process any physicalized fragments that were destroyed
    if (params.m_pDeadPhysFrags && !params.m_pDeadPhysFrags->empty())
    {
        const uint numDeadFrags = params.m_pDeadPhysFrags->size();
        for (uint i = 0; i < numDeadFrags; ++i)
        {
            const uint16 fragData = params.m_pDeadPhysFrags->at(i);
            const uint8 buffId = fragData & 0xFF;
            const uint8 fragId = (fragData >> 8) & 0xFF;

            if (m_fragGeomBufferIds[buffId].m_fragId == fragId)
            {
                m_fragGeomBufferIds[buffId].m_fragId = GLASSCFG_FRAGMENT_ARRAY_SIZE;
                m_fragGeomBufferIds[buffId].m_geomBufferId = NO_BUFFER;

                // Not happy here as threading scariness, but we should really deal with
                // the case where we get stuck waiting for an update that's never coming
                //              if (!m_geomRebuilt && m_fragGeomRebuilt[fragId])
                //              {
                //                  m_fragGeomRebuilt[fragId] = false;
                //                  --m_dirtyGeomBufferCount;
                //              }
            }
        }
    }

    // Regenerate geometry if dirty
    if (m_geomDirty)
    {
        FindLooseFragments(params.m_pPhysFrags, params.m_pPhysFragsInitData);
        RebuildAllFragmentGeom();
    }
}//-------------------------------------------------------------------------------------------------


//--------------------------------------------------------------------------------------------------
// Name: GetGlassState
// Desc: Returns the current state of the glass
//--------------------------------------------------------------------------------------------------
void CREBreakableGlass::GetGlassState(SBreakableGlassState& state)
{
    state.m_numImpacts = m_impactParams.size();
    state.m_numLooseFrags = m_numLooseFrags;
    state.m_hasShattered = m_shattered;
}//-------------------------------------------------------------------------------------------------

//--------------------------------------------------------------------------------------------------
// Name: SetCVars
// Desc: Updates the cvar pointer
//--------------------------------------------------------------------------------------------------
void CREBreakableGlass::SetCVars(const SBreakableGlassCVars* pCVars)
{
    s_pCVars = pCVars;
}//-------------------------------------------------------------------------------------------------

//--------------------------------------------------------------------------------------------------
// Name: ResetTempCrackData
// Desc: Clears all temporary geom and point data used during crack generation
//--------------------------------------------------------------------------------------------------
void CREBreakableGlass::ResetTempCrackData()
{
    memset(m_pointArray, 0, sizeof(Vec2) * GLASSCFG_MAX_NUM_CRACK_POINTS);
    m_pointArrayCount = 0;

    for (uint i = 0; i < GLASSCFG_NUM_RADIAL_CRACKS; ++i)
    {
        m_radialCrackTrees[i].Clear();
    }
}//-------------------------------------------------------------------------------------------------

//--------------------------------------------------------------------------------------------------
// Name: SeedRand
// Desc: Regenerates the internal random values
//--------------------------------------------------------------------------------------------------
static const uint s_gNumRandValues = 32;
static struct
{
    uint32  currValue;
    float   values[s_gNumRandValues];
} s_gRandData;

void CREBreakableGlass::SeedRand()
{
    CRndGen randGen;
    randGen.Seed(m_seed);

    for (uint i = 0; i < s_gNumRandValues; ++i)
    {
        s_gRandData.values[i] = randGen.GenerateFloat();
    }

    s_gRandData.currValue = 0;
}//-------------------------------------------------------------------------------------------------

//--------------------------------------------------------------------------------------------------
// Name: GetRandF
// Desc: Retrieves the next random float value
//--------------------------------------------------------------------------------------------------
float CREBreakableGlass::GetRandF()
{
    ++s_gRandData.currValue;
    return s_gRandData.values[s_gRandData.currValue & 31]; // Optimized "value % 32"
}//-------------------------------------------------------------------------------------------------

//--------------------------------------------------------------------------------------------------
// Name: GetRandF
// Desc: Retrieves a specific random float value
//--------------------------------------------------------------------------------------------------
float CREBreakableGlass::GetRandF(const uint index)
{
    return s_gRandData.values[index & 31]; // Optimized "index % 32"
}//-------------------------------------------------------------------------------------------------

//--------------------------------------------------------------------------------------------------
// Name: GetClosestImpactDistance
// Desc: Returns the distance to the closest impact point
//--------------------------------------------------------------------------------------------------
float CREBreakableGlass::GetClosestImpactDistance(const Vec2& pt)
{
    GLASS_FUNC_PROFILER

    const uint numImpacts = m_impactParams.size();
    const float radialScale = 0.5f;
    float centerDist = FLT_MAX;

    for (uint i = 0; i < numImpacts; ++i)
    {
        float radius = GetDecalRadius(i) * radialScale;
        Vec2 center = Vec2(m_impactParams[i].x, m_impactParams[i].y);

        float dist = max((center - pt).GetLength() - radius, 0.0f);
        centerDist = min(centerDist, dist);
    }

    return centerDist;
}//-------------------------------------------------------------------------------------------------

//--------------------------------------------------------------------------------------------------
// Name: HandleAdditionalImpact
// Desc: Allows the glass to fully shatter after too many impacts
//--------------------------------------------------------------------------------------------------
void CREBreakableGlass::HandleAdditionalImpact(const SGlassImpactParams& params)
{
    // Only impact if there is still stable glass
    const uint numImpacts = m_impactParams.size();

    if (!m_shattered && numImpacts == GLASSCFG_MAX_NUM_IMPACTS)
    {
        ShatterGlass();
    }
}//-------------------------------------------------------------------------------------------------

//--------------------------------------------------------------------------------------------------
// Name: ShatterGlass
// Desc: Instantly knocks all pieces from the glass
//--------------------------------------------------------------------------------------------------
void CREBreakableGlass::ShatterGlass()
{
    if (!m_shattered)
    {
        // On shatter, all fragments still active become loose
        m_fragsLoose |= m_fragsActive;

#if GLASSCFG_USE_HASH_GRID
        // No stable fragments means no hashed data
        SAFE_DELETE(m_pHashGrid);
#endif

        // Update state
        m_shattered = true;
        m_geomDirty = true;
    }
}//-------------------------------------------------------------------------------------------------

//--------------------------------------------------------------------------------------------------
// Name: PlayShatterEffect
// Desc: Spawns/plays glass shatter effects
//--------------------------------------------------------------------------------------------------
void CREBreakableGlass::PlayShatterEffect(const uint8 fragIndex)
{
    if (!s_pCVars || s_pCVars->m_particleFXEnable > 0)
    {
        if (m_glassParams.pShatterEffect && !m_impactParams.empty())
        {
            // Get fragment data
            const SGlassFragment& frag = m_frags[fragIndex];
            const Vec2* pFragPts = frag.m_outlinePts.begin();
            const int numFragPts = frag.m_outlinePts.Count();

            if (numFragPts >= 3)
            {
                // Calculate particle spawn parameters
                const Vec2& center = frag.m_center;
                Vec3 worldPos = m_params.matrix.TransformPoint(center);

                Vec3 dir;

                // Velocity from impact or just "up" relative to surface
                if (!m_impactParams.empty())
                {
                    dir = m_impactParams.back().velocity.GetNormalizedFast();
                }
                else
                {
                    dir = m_params.matrix.GetColumn2();
                    LOG_GLASS_ERROR("Creating particle effect but no valid impacts found");
                }

                Vec2 bounds = CalculatePolygonBounds2D(pFragPts, numFragPts);
                float size = (bounds.x + bounds.y) * 0.5f;

                float effectScale = s_pCVars ? s_pCVars->m_particleFXScale : 0.25f;

                // Spawn effect emitter
                const uint emitterFlags = 0;
                const float spawnScale = 1.0f;

                SpawnParams spawnParams;
                spawnParams.fCountScale = effectScale * size;
                spawnParams.fSizeScale = size;

                QuatTS spawnLoc;
                spawnLoc.q = Quat::CreateRotationVDir(dir);
                spawnLoc.t = worldPos;
                spawnLoc.s = spawnScale;

                m_glassParams.pShatterEffect->Spawn(spawnLoc, emitterFlags, &spawnParams);
            }
        }
    }
}//-------------------------------------------------------------------------------------------------

//--------------------------------------------------------------------------------------------------
// Name: CalculateBreakThreshold
// Desc: Randomly adjusts the break threshold (biased towards center)
//--------------------------------------------------------------------------------------------------
float CREBreakableGlass::CalculateBreakThreshold()
{
    const float thresholdCenterStr = 0.5f;
    const SGlassImpactParams& currImpact = m_impactParams.back();

    // Adjust threshold to give weaker center
    float planeMaxX = m_glassParams.size.x;
    float planeMaxY = m_glassParams.size.y;

    float xDistFromEdge = planeMaxX - fabs_tpl(currImpact.x - (planeMaxX * 0.5f)) * 2.0f;
    float yDistFromEdge = planeMaxY - fabs_tpl(currImpact.y - (planeMaxY * 0.5f)) * 2.0f;
    float distFromEdge = min(xDistFromEdge, yDistFromEdge);

    float threshold = LERP(1.0f, thresholdCenterStr, distFromEdge * m_invMaxGlassSize);

    // Severely weaken glass if its already been broken
    if (m_impactParams.size() > 1)
    {
        threshold *= 0.0f;
    }

    return threshold;
}//-------------------------------------------------------------------------------------------------

//--------------------------------------------------------------------------------------------------
// Name: CheckForGlassBreak
// Desc: Determines if the impact speed is sufficient to break the glass,
//           and calculates the excess impact energy
//--------------------------------------------------------------------------------------------------
bool CREBreakableGlass::CheckForGlassBreak(const float breakThreshold, float& excessEnergy)
{
    const float breakSpeedMult = 1.0f / GLASSCFG_PLANE_AVG_SPEED_TO_BREAK;

    const SGlassImpactParams& currImpact = m_impactParams.back();
    float thresholdSpeed = currImpact.speed * breakSpeedMult;

    // Excess energy is speed left over past break threshold
    excessEnergy = max(thresholdSpeed - breakThreshold, 0.0f);

    return (breakThreshold < thresholdSpeed);
}//-------------------------------------------------------------------------------------------------

//--------------------------------------------------------------------------------------------------
// Name: CalculateImpactEnergies
// Desc: Partitions input energy towards forming radial or circular cracks
//--------------------------------------------------------------------------------------------------
void CREBreakableGlass::CalculateImpactEnergies(const float totalEnergy, float& radialEnergy, float& circularEnergy)
{
    // General idea is lower impact collisions cause large radial cracks. As total energy increases,
    // radial cracks become shorter and more concentrated circular cracks begin to appear. Happens
    // as faster object spends less time in contact with plane (impulse=f*t), causing glass to dissipate
    // less energy bending (causing radial cracks), instead breaking at a concentrated point
    const float circularFadeIn = 1.0f;
    const float radialFadeOut = 3.5f;
    const float minRadialEnergy = 0.2f;

    // Energy can be fully radial, fully circular or a blend
    if (totalEnergy < circularFadeIn)
    {
        radialEnergy = totalEnergy;
        circularEnergy = 0.0f;
    }
    else if (totalEnergy > radialFadeOut)
    {
        radialEnergy = minRadialEnergy;
        circularEnergy = totalEnergy;
    }
    else
    {
        float blend = (totalEnergy - circularFadeIn) / (radialFadeOut - circularFadeIn);
        circularEnergy = totalEnergy * blend;
        radialEnergy = max(minRadialEnergy, totalEnergy - circularEnergy);
    }
}//-------------------------------------------------------------------------------------------------

//--------------------------------------------------------------------------------------------------
// Name: ApplyImpactToGlass
// Desc: Splits and updates the impacted glass fragments
//--------------------------------------------------------------------------------------------------
void CREBreakableGlass::ApplyImpactToGlass(const SGlassImpactParams& params)
{
    GLASS_FUNC_PROFILER

    // Store param data even if this impact is about to cause a full shatter
    if (!m_shattered)
    {
        m_impactParams.push_back(params);
    }

    // Potentially shatter on new impact
    HandleAdditionalImpact(params);

    if (!m_shattered)
    {
        // Pre-calculate randomised params
        SGlassImpactParams& currImpact = m_impactParams.back();

        if (m_impactParams.size() == 1)
        {
            m_seed = currImpact.seed;
        }

        SeedRand();

        // Check if impact actually breaks the glass
        float breakThreshold = CalculateBreakThreshold();
        float excessEnergy = 0.0f;
        bool impactHit = false;

        if (CheckForGlassBreak(breakThreshold, excessEnergy))
        {
            m_impactEnergy = excessEnergy;

            // Force all very fast objects to make a hole
            if (currImpact.speed > GLASSCFG_MIN_BULLET_SPEED)
            {
                currImpact.impulse = max(currImpact.impulse, GLASSCFG_PLANE_SPLIT_IMPULSE * GLASSCFG_MIN_BULLET_HOLE_SCALE);
            }

            // Apply impact
            if (ApplyImpactToFragments(currImpact))
            {
#ifdef GLASS_DEBUG_MODE
                GenerateImpactGeom(currImpact);
#endif
                impactHit = true;
                s_impactTimer = 0.0f;
                s_loosenTimer = 0.0f;

                // Check if we still have any stable geometry left
                const uint32 stableFrags = m_fragsActive & ~(m_fragsLoose | m_fragsFree);

                if (stableFrags == 0)
                {
                    m_shattered = true;
                }

                // Rebuild geometry if changed
                m_geomDirty = true;
            }
        }

        // Undo impact if it didn't actually collide
        if (!impactHit)
        {
            m_impactParams.pop_back();
        }
    }
}//-------------------------------------------------------------------------------------------------

//--------------------------------------------------------------------------------------------------
// Name: ApplyExplosionToGlass
// Desc: Knocks out all fragments in the explosion, and splits those clipping
//--------------------------------------------------------------------------------------------------
void CREBreakableGlass::ApplyExplosionToGlass(const SGlassImpactParams& params)
{
    GLASS_FUNC_PROFILER

    // Store param data even if this impact is about to cause a full shatter
    if (!m_shattered)
    {
        m_impactParams.push_back(params);
    }

    // Potentially shatter on new impact
    HandleAdditionalImpact(params);

    if (!m_shattered)
    {
#if GLASSCFG_SHATTER_ON_EXPLOSION
        // Auto-shatter
        ShatterGlass();
        bool impactHit = true;

#else
        // Calculate explosion details
        bool impactHit = false;

        const Vec2 center(params.x, params.y);

        const float radiusVariation = 0.3f;
        const float radius = GetImpactRadius(params) * (1.0f + GetRandF() * radiusVariation);

#if GLASSCFG_SPLIT_ON_EXPLOSION
        // Pre-calculate randomised params
        SGlassImpactParams& currImpact = m_impactParams.back();
        SeedRand();

        // Firstly split as with regular impact, as this leads to nicer fragment patterns
        if (ApplyImpactToFragments(currImpact))
        {
            impactHit = true;
        }
#endif

        // Pre-evaluate fragment state bits
        const uint32 validState = m_fragsActive & ~(m_fragsLoose | m_fragsFree);
        uint32 fragBit = 1;

        // Check all active fragments against explosion
        // Note: Would be nicer to simply walk connections from impact fragment,
        // which should avoid lots of redundant checks. May find with explosion
        // being so large however, that almost all fragments get clipped anyway!
        TPolygonArray tempFragShape;
        TPolygonArray splitFragShape;

        for (uint i = 0; i < GLASSCFG_FRAGMENT_ARRAY_SIZE; ++i, fragBit <<= 1)
        {
            if (validState & fragBit)
            {
                // Clip fragment against explosion radius
                EPolygonInCircle2D state = PolygonInCircle2D(center, radius, m_frags[i].m_outlinePts.begin(), m_frags[i].m_outlinePts.Count());

                if (state == EPolygonInCircle2D_Inside)
                {
                    // Instantly remove
                    impactHit = true;
                    m_fragsLoose |= fragBit;
                }
                else if (state == EPolygonInCircle2D_Overlap)
                {
                    impactHit = true;

                    // Remove hash grid references now, as shape will change
                    const bool remove = true;
                    HashFragment(i, remove);

                    // Apply split and create new polygon if necessary
                    splitFragShape.clear();
                    SplitPolygonAroundPoint(m_frags[i].m_outlinePts, center, radius, splitFragShape);

                    const int noParent = 0;
                    const bool forceLoose = true;
                    CreateFragmentFromOutline(splitFragShape.begin(), splitFragShape.size(), noParent, forceLoose);

                    // Rebuild remaining fragment section
                    RebuildChangedFragment(i);
                }
            }
        }
#endif // GLASSCFG_SHATTER_ON_EXPLOSION

        // Need to rebuild geometry if hit
        if (impactHit)
        {
#ifdef GLASS_DEBUG_MODE
            GenerateImpactGeom(params);
#endif
            m_geomDirty = true;
        }

        // Undo impact if it didn't actually collide
        else
        {
            m_impactParams.pop_back();
        }
    }
}//-------------------------------------------------------------------------------------------------

//--------------------------------------------------------------------------------------------------
// Name: GenerateCracksFromImpact
// Desc: Generates temporary crack data from the current impact points
//--------------------------------------------------------------------------------------------------
uint CREBreakableGlass::GenerateCracksFromImpact(const uint8 parentFragIndex, const SGlassImpactParams& impact)
{
    GLASS_FUNC_PROFILER

    // Clear old data
        ResetTempCrackData();

    // Determine crack generation depth
    const uint impactDepth = m_frags[parentFragIndex].m_depth;
    const float fragArea = m_frags[parentFragIndex].m_area;

    // Generate crack data from impact point
    m_pointArray[0].x = impact.x;
    m_pointArray[0].y = impact.y;
    m_pointArrayCount = 1;

    const uint numCracks = GenerateRadialCracks(m_impactEnergy, impactDepth, fragArea);
    return numCracks;
}//-------------------------------------------------------------------------------------------------

//--------------------------------------------------------------------------------------------------
// Name: PropagateRadialCrack
// Desc: Adds a new radial crack to the end of the list. Returns energy consumed
//--------------------------------------------------------------------------------------------------
float   CREBreakableGlass::PropagateRadialCrack(const uint startIndex, const uint currIndex, const uint localIndex, const float angle, const float energyPerStep)
{
    GLASS_FUNC_PROFILER

    const float energyStepVariance = 0.015f;
    const float globalEnergyMultipler = 4.0f;

    // Randomise energy used for this step slightly
    float energyStep = 1.0f + (GetRandF() * energyStepVariance) * 0.5f + 0.5f;
    energyStep *= energyPerStep;

    float energyUsed = energyStep * (globalEnergyMultipler + localIndex * localIndex) * GLASSCFG_SIMPLIFY_CRACKS;

    // Get points
    Vec2& startPt = m_pointArray[startIndex];
    Vec2& currPt = m_pointArray[currIndex];

    // Calculate position data
    float sinA, cosA;
    sincos_tpl(angle, &sinA, &cosA);

    currPt.x = startPt.x + cosA * energyUsed;
    currPt.y = startPt.y + sinA * energyUsed;

    // For now, we always want cracks to continue until they clip
    energyStep = 0.0f;

    // Get plane bounds
    const float planeMin = 0.0f;
    const float planeMaxX = m_glassParams.size.x;
    const float planeMaxY = m_glassParams.size.y;

    // Check plane bounds, and end crack if hit
    //  -Note: Points are always different, so will not get divby0 error
    const float useAllEnergy = 1000.0f;

    if (currPt.x < planeMin || currPt.x > planeMaxX)
    {
        float bound = currPt.x < planeMin ? planeMin : planeMaxX;
        float excessDelta = (currPt.x - bound) / (currPt.x - startPt.x);
        currPt.y -= (currPt.y - startPt.y) * excessDelta;

        currPt.x = clamp_tpl<float>(currPt.x, planeMin, planeMaxX);
        energyStep = useAllEnergy;
    }

    if (currPt.y < planeMin || currPt.y > planeMaxY)
    {
        float bound = currPt.y < planeMin ? planeMin : planeMaxY;
        float excessDelta = (currPt.y - bound) / (currPt.y - startPt.y);
        currPt.x -= (currPt.x - startPt.x) * excessDelta;

        currPt.y = clamp_tpl<float>(currPt.y, planeMin, planeMaxY);
        energyStep = useAllEnergy;
    }

    // Additional point generated
    ++m_pointArrayCount;

    // Return energy consumed
    return energyStep;
}

//--------------------------------------------------------------------------------------------------
// Name: GenerateRadialCracks
// Desc: Generates radial point data formed by cracks perpendicular to the impact point
//--------------------------------------------------------------------------------------------------
uint CREBreakableGlass::GenerateRadialCracks(const float totalEnergy, const uint impactDepth, const float fragArea)
{
    GLASS_FUNC_PROFILER

    const uint minCracksToBreak = 3;
    const float minEnergyPerStep = 0.005f;
    const float maxEnergyPerStep = 0.1f;
    const float energyStepRelaxRate = 2.5f;
    const float randAngle = gf_PI * 0.8f;
    const float angleStepAccel = 0.15f;

    // The higher the energy, the easier it is for cracks to appear as they become smaller
    float energyStepRelaxation = clamp_tpl<float>(totalEnergy * energyStepRelaxRate, 0.0f, 1.0f);
    float energyPerStep = LERP(maxEnergyPerStep, minEnergyPerStep, energyStepRelaxation);

    // Already decide to break, so clamp to bounds regardless of energy
    const uint numCracks = GLASSCFG_NUM_RADIAL_CRACKS;

    //if (numCracks >= minCracksToBreak)
    {
        // Divide energy amongst cracks
        float fNumCracks = (float)numCracks;
        float currentEnergyPerCrack = totalEnergy / fNumCracks;

        // Calculate per-crack angle increment
        const float angleInc = gf_PI2 / fNumCracks;
        const float halfCosAngleInc = 2.0f / fNumCracks;
        const float randAnglePerInc = randAngle / fNumCracks;
        const float halfRandAnglePerInc = randAnglePerInc * 0.5f;

        float crackAngle = GetRandF() * gf_PI2;

        // All radial cracks begin at central impact point
        Vec2* pCenterPt = &m_pointArray[0];
        uint ptIndex = m_pointArrayCount;

        // Generate initial cracks
        for (uint i = 0; i < numCracks; ++i)
        {
            // Add center point to tree lists
            m_radialCrackTrees[i].AddNew() = pCenterPt;

            // Increment angle, then randomize local offset
            crackAngle += angleInc;
            float angle = crackAngle + (GetRandF() * randAnglePerInc - halfRandAnglePerInc);

            // Reset for new crack
            float crackEnergy = max(currentEnergyPerCrack, energyPerStep);
            float localAngle = angle;

            Vec2* pStartPt = pCenterPt;
            Vec2 initialDir;
            uint startPtIndex = 0;
            uint crackStepIndex = 0;

            while (crackEnergy >= energyPerStep)
            {
                float totalEnergyStep = 0.0f;

                // Randomise angle for step, further steps getting more aggressive
                float localAngleStep = GetRandF() * randAnglePerInc - halfRandAnglePerInc;
                localAngleStep *= 1.0f + crackStepIndex * angleStepAccel;
                localAngle += localAngleStep;

                // If getting too far away from local sector, need to return to avoid intersections
                if (crackStepIndex > 1)
                {
                    float sinA, cosA;
                    sincos_tpl(angle, &sinA, &cosA);

                    const Vec2 nextPt(pStartPt->x + cosA, pStartPt->y + sinA);
                    const Vec2 nextDir = (nextPt - *pCenterPt).Normalize();
                    const float cosAngle = nextDir.Dot(initialDir);

                    if (cosAngle < halfCosAngleInc)
                    {
                        localAngle -= localAngleStep * 2.0f;
                    }
                }

                // Create new point
                totalEnergyStep = PropagateRadialCrack(startPtIndex, ptIndex, crackStepIndex, localAngle, energyPerStep);

                // Dissipate energy
                crackEnergy -= totalEnergyStep;

                // Push new point into radial tree
                m_radialCrackTrees[i].AddNew() = &m_pointArray[ptIndex];

                // Prepare for next step
                pStartPt = &m_pointArray[ptIndex];
                startPtIndex = ptIndex;
                ++ptIndex;
                ++crackStepIndex;

                // Record initial direction
                if (crackStepIndex == 1)
                {
                    initialDir = (*pStartPt - *pCenterPt).Normalize();
                }

                // Assert boundaries
                if (ptIndex >= GLASSCFG_MAX_NUM_CRACK_POINTS)
                {
                    LOG_GLASS_ERROR("Allocated too many crack points, need to expand array!");
                    return 0;
                }
            }
        }
    }

    // Circular cracks need to be aware of shape
    return numCracks;
}//-------------------------------------------------------------------------------------------------

//--------------------------------------------------------------------------------------------------
// Name: GetImpactRadius
// Desc: Calculates the radius of an impact
//--------------------------------------------------------------------------------------------------
float   CREBreakableGlass::GetImpactRadius(const SGlassImpactParams& impact)
{
    // Now using fixed minimum impulse, rather than phys event approximation
    const float minImpulse = GLASSCFG_PLANE_SPLIT_IMPULSE * GLASSCFG_MIN_BULLET_HOLE_SCALE;

    // Calculate minimum radius
    float minSplitRadius = (minImpulse /*impact.impulse*/ - GLASSCFG_PLANE_SPLIT_IMPULSE) * GLASSCFG_PLANE_SPLIT_IMPULSE_STRENGTH;
    minSplitRadius = (minSplitRadius + minSplitRadius * minSplitRadius) * 0.5f; // Square here to change behaviour, not to get sq radius

    return max(minSplitRadius, impact.radius);
}//-------------------------------------------------------------------------------------------------

//--------------------------------------------------------------------------------------------------
// Name: GetDecalRadius
// Desc: Calculates the radius of an impact decal
//--------------------------------------------------------------------------------------------------
float CREBreakableGlass::GetDecalRadius(const uint8 impactIndex)
{
    // Get impact size multipliers
    float minImpactSpeed = s_pCVars ? s_pCVars->m_minImpactSpeed : GLASSCFG_MIN_BULLET_SPEED;
    float minImpactRadius = s_pCVars ? s_pCVars->m_decalMinRadius : 0.25f;
    float maxImpactRadius = s_pCVars ? s_pCVars->m_decalMaxRadius : 1.25f;
    float randDecalChance = s_pCVars ? s_pCVars->m_decalRandChance : 0.67f;
    float randDecalScale = s_pCVars ? s_pCVars->m_decalRandScale : 1.6f;
    float impactScale = s_pCVars ? max(s_pCVars->m_decalScale, 0.01f) : 2.5f;

    // Calculate scale
    const SGlassImpactParams& impact = m_impactParams[impactIndex];
    float decalRadius = GetImpactRadius(impact) * impactScale;
    decalRadius = clamp_tpl<float>(decalRadius, minImpactRadius, maxImpactRadius);
    decalRadius *= impactScale * 0.5f;

    bool isBullet = (impact.speed >= minImpactSpeed);
    bool isLargeDecal = (isBullet && GetRandF(impactIndex) >= randDecalChance);
    decalRadius *= isLargeDecal ? randDecalScale : 1.0f;

    return decalRadius;
}//-------------------------------------------------------------------------------------------------

//--------------------------------------------------------------------------------------------------
// Name: ApplyImpactToFragments
// Desc: Allows multiple impacts to remove triangles from the mesh
//--------------------------------------------------------------------------------------------------
bool CREBreakableGlass::ApplyImpactToFragments(SGlassImpactParams& impact)
{
    GLASS_FUNC_PROFILER

    // Find the impact fragment (if any)
    uint8 impactFrag = 0;
    bool foundFrag = FindImpactFragment(impact, impactFrag);

    if (foundFrag)
    {
        uint32 fragBit = (uint32)1 << impactFrag;
        const uint impactIndex = m_impactParams.size() - 1;

        // Shift impact slightly towards center of fragment to avoid
        // incredibly rare case where point is *exactly* on fragment edge
        SGlassFragment& frag = m_frags[impactFrag];
        const Vec2& impactFragCenter = frag.m_center;
        const Vec2 impactCenter = Vec2(impact.x, impact.y);

        Vec2 impactToFragDir = impactFragCenter - impactCenter;
        impactToFragDir.Normalize();

        const float impactPtShift = 0.0001f;
        impact.x += impactToFragDir.x * impactPtShift;
        impact.y += impactToFragDir.y * impactPtShift;

        // With larger impacts, randomly knock out a few surrounding fragments
        const float extraImpactRadius = 0.2f;
        if (impact.radius >= extraImpactRadius)
        {
            // Randomly rotated impact points
#ifdef GLASSCFG_HIGH_QUALITY_MODE
            const uint numExtraImpacts = 5;
            const float angleInc = 1.0f / 5.0f;
#else
            const uint numExtraImpacts = 3;
            const float angleInc = 1.0f / 3.0f;
#endif
            const float offset = GetRandF();

            // Fixed maximum range for extra impacts
            const float maxExtraImpactRadius = 0.4f;
            SGlassImpactParams extraImpact = impact;
            extraImpact.radius = min(impact.radius, maxExtraImpactRadius);

            for (uint i = 0; i < numExtraImpacts; ++i)
            {
                // Jitter impact position
                float sinA, cosA;
                sincos_tpl((angleInc * i + offset) * gf_PI2, &sinA, &cosA);

                extraImpact.x = impact.x + sinA * extraImpact.radius;
                extraImpact.y = impact.y + cosA * extraImpact.radius;

                // Knock out hit fragment, if it's not the one we're splitting
                uint8 extraImpactFrag = GLASSCFG_MAX_NUM_FRAGMENTS;
                if (FindImpactFragment(extraImpact, extraImpactFrag) && extraImpactFrag != impactFrag)
                {
                    fragBit = (uint32)1 << extraImpactFrag;
                    m_fragsLoose |= fragBit;

                    RemoveFragmentConnections(extraImpactFrag, m_frags[extraImpactFrag].m_outConnections);
                    RemoveFragmentConnections(extraImpactFrag, m_frags[extraImpactFrag].m_inConnections);

#ifdef GLASS_DEBUG_MODE
                    // Add debug impact geom
                    extraImpact.radius = 0.025f;
                    GenerateImpactGeom(extraImpact);
#endif
                }
            }
        }

        // If possible, split the fragment
        if (impactFrag < GLASSCFG_MAX_NUM_FRAGMENTS)
        {
            if (m_totalNumFrags + GLASSCFG_NUM_RADIAL_CRACKS < GLASSCFG_MAX_NUM_FRAGMENTS)
            {
                // Split the fragment into it's new sub-fragments
                GenerateSubFragments(impactFrag, impact);

                // Need to safely dispose of this fragment
                RemoveFragment(impactFrag);
            }
            else
            {
                // Simply knock out this fragment
                m_fragsLoose |= fragBit;
                RemoveFragmentConnections(impactFrag, m_frags[impactFrag].m_outConnections);
                RemoveFragmentConnections(impactFrag, m_frags[impactFrag].m_inConnections);
            }
        }
    }

    // Report success
    return foundFrag;
}//-------------------------------------------------------------------------------------------------

//--------------------------------------------------------------------------------------------------
// Name: FindImpactFragment
// Desc: Tests the impact point against hashed fragment triangles
//--------------------------------------------------------------------------------------------------
bool CREBreakableGlass::FindImpactFragment(const SGlassImpactParams& impact, uint8& fragIndex)
{
    GLASS_FUNC_PROFILER

    bool foundFrag = false;
    const Vec2 impactPt = Vec2(impact.x, impact.y);

#if GLASSCFG_USE_HASH_GRID
    if (m_pHashGrid)
    {
        // Hash impact to a grid cell of fragment triangles
        const uint32 cellIndex = m_pHashGrid->HashPosition(impact.x, impact.y);
        const TGlassHashBucket* const impactFrags = m_pHashGrid->GetBucket(cellIndex);

        // Test impact point against fragment polygons
        if (impactFrags && !impactFrags->empty())
        {
            const uint numImpactFrags = impactFrags->size();
            const uint8* pImpactFrags = impactFrags->begin();

            for (uint i = 0; i < numImpactFrags; ++i)
            {
                // Get hashed data
                const uint8 ownerFrag = pImpactFrags[i];
                CRY_ASSERT_MESSAGE(ownerFrag < GLASSCFG_FRAGMENT_ARRAY_SIZE, "Invalid frag index");

                // Check if this is the impact fragment
                const PodArray<Vec2>& fragOutline = m_frags[ownerFrag].m_outlinePts;

                if (PointInPolygon2D(impactPt, fragOutline.begin(), fragOutline.Count()))
                {
                    fragIndex = ownerFrag;
                    foundFrag = true;
                    break;
                }
            }
        }
    }
#else
    // Perform a brute force loop through all active fragments
    const uint32 validState = m_fragsActive & ~(m_fragsLoose | m_fragsFree);
    const SGlassFragment* pFrags = m_frags.begin();

    uint32 fragBit = 1;
    for (uint i = 0; i < GLASSCFG_FRAGMENT_ARRAY_SIZE; ++i, fragBit <<= 1)
    {
        if (validState & fragBit)
        {
            // Check if this is the impact fragment
            const PodArray<Vec2>& fragOutline = m_frags[i].m_outlinePts;

            if (PointInPolygon2D(impactPt, fragOutline.begin(), fragOutline.Count()))
            {
                fragIndex = i;
                foundFrag = true;
                break;
            }
        }
    }
#endif // GLASSCFG_USE_HASH_GRID

    // If we didn't find a fragment, the impact passed through a hole
    return foundFrag;
}//-------------------------------------------------------------------------------------------------

//--------------------------------------------------------------------------------------------------
// Name: AddFragment
// Desc: Claims and re-initialises a fragment container
//--------------------------------------------------------------------------------------------------
SGlassFragment* CREBreakableGlass::AddFragment()
{
    CRY_ASSERT_MESSAGE(!m_freeFragIndices.empty(), "Tried to add glass fragment, but none free.");
    SGlassFragment* pFrag = NULL;

    if (!m_freeFragIndices.empty())
    {
        // Claim next free fragment
        m_lastFragIndex = m_freeFragIndices.back();
        m_freeFragIndices.pop_back();

        // Re-initialise it
        m_lastFragBit = (uint32)1 << m_lastFragIndex;

        m_frags[m_lastFragIndex].Reset();
        ++m_totalNumFrags;

        m_fragsActive |= m_lastFragBit;
        m_fragsLoose &= ~m_lastFragBit;
        m_fragsFree &= ~m_lastFragBit;

        pFrag = &m_frags[m_lastFragIndex];
    }
    //  else
    //  {
    //      // Want to log this, but shotguns spam the log
    //      //LOG_GLASS_ERROR("Tried to add fragment, but none free.");
    //  }

    return pFrag;
}

//--------------------------------------------------------------------------------------------------
// Name: RemoveFragment
// Desc: Removes a fragment from the hash grid, and clears all data
//--------------------------------------------------------------------------------------------------
void CREBreakableGlass::RemoveFragment(const uint8 fragIndex)
{
    GLASS_FUNC_PROFILER

    const uint32 fragBit = (uint32)1 << fragIndex;
    const uint32 validState = m_fragsActive & ~m_fragsFree;

    if (fragIndex < GLASSCFG_FRAGMENT_ARRAY_SIZE)
    {
        if (validState & fragBit)
        {
            SGlassFragment& frag = m_frags[fragIndex];

            // Remove inter-fragment connections
            RemoveFragmentConnections(fragIndex, frag.m_outConnections);
            RemoveFragmentConnections(fragIndex, frag.m_inConnections);

            // Remove all hash grid references to this fragment
            const bool remove = true;
            HashFragment(fragIndex, remove);

            // Free all fragment data as now unused
            frag.m_outConnections.Free();
            frag.m_inConnections.Free();

            frag.m_outlinePts.Free();
            frag.m_triInds.Free();

            // Flag fragment as inactive
            DeactivateFragment(fragIndex, fragBit);
        }
    }
}//-------------------------------------------------------------------------------------------------

//--------------------------------------------------------------------------------------------------
// Name: RebuildChangedFragment
// Desc: Re-connects, triangulates and hashes a changed fragment
//--------------------------------------------------------------------------------------------------
void CREBreakableGlass::RebuildChangedFragment(const uint8 fragIndex)
{
    SGlassFragment& frag = m_frags[fragIndex];
    const uint32 fragBit = (uint32)1 << fragIndex;

    // Rebuild if valid data available
    if (frag.m_outlinePts.Count() >= 3)
    {
        // Re-connect fragment
        const int fragCount = 1;
        ReplaceFragmentConnections(fragIndex, &fragIndex, fragCount);

        // Re-triangulate fragment
        frag.m_triInds.Clear();
        TriangulatePolygon2D(frag.m_outlinePts.begin(), frag.m_outlinePts.Count(), NULL, &frag.m_triInds);

        // Hash new fragment triangles
        HashFragment(fragIndex);
    }

    // Just discard
    else
    {
        m_fragsLoose |= fragBit;
    }
}//-------------------------------------------------------------------------------------------------

//--------------------------------------------------------------------------------------------------
// Name: GenerateSubFragments
// Desc: Generates stable piece triangles within a specified polygon
//--------------------------------------------------------------------------------------------------
void CREBreakableGlass::GenerateSubFragments(const uint8 parentFragIndex, const SGlassImpactParams& impact)
{
    GLASS_FUNC_PROFILER

    // Begin by generating internal crack data (clipped within parent)
    TRadialCrackArray fragIntersectPts;
    GenerateSubFragmentCracks(parentFragIndex, impact, fragIntersectPts);

    // Find how many trees were made
    int numUsedCrackTrees = fragIntersectPts.size();
    CRY_ASSERT_MESSAGE(numUsedCrackTrees > 0, "Should always generate something, as it's forced");

    // When we hit our limit, all new fragments are loose
    // - Leads to fragments still being split when knocked out
    const bool hitStableLimit = (m_totalNumFrags + numUsedCrackTrees >= GLASSCFG_MAX_NUM_STABLE_FRAGMENTS);

    // Impact split variables
    const float impactSplitMinRadius = s_pCVars ? s_pCVars->m_impactSplitMinRadius : 0.05f;
    const float impactSplitRandMin = s_pCVars ? s_pCVars->m_impactSplitRandMin : 0.5f;
    const float impactSplitRandMax = s_pCVars ? s_pCVars->m_impactSplitRandMax : 1.5f;

    // Walk trees and parent to generate sub-fragment outlines
    TPolygonArray subFragShape;
    TPolygonArray splitFragShape;
    TSubFragArray stableSubFrags;

    for (int i = 0; i < numUsedCrackTrees; ++i)
    {
        // Resizing the vector, so need to re-grab parent fragment pointer
        SGlassFragment& parentFrag = m_frags[parentFragIndex];
        int parentOutlineSize = parentFrag.m_outlinePts.Count();

        // Calculate number of parent outline sections to add
        int nextTreeIndex = i + 1 < numUsedCrackTrees ? i + 1 : 0;
        int outlineCount = fragIntersectPts[nextTreeIndex] - fragIntersectPts[i];

        if (fragIntersectPts[nextTreeIndex] < fragIntersectPts[i])
        {
            outlineCount += parentOutlineSize;
        }

        // Pre-size the container
        const int firstTreeSize = m_radialCrackTrees[i].Count();
        const int secondTreeSize = m_radialCrackTrees[nextTreeIndex].Count() - 1;

        const int fullFragSize = firstTreeSize + outlineCount + secondTreeSize;

        if (fullFragSize >= 3 && fullFragSize <= POLY_ARRAY_SIZE)
        {
            subFragShape.resize(fullFragSize);

            // Add entire first tree
            Vec2* pSubFragPts = subFragShape.begin();

            for (int j = 0; j < firstTreeSize; ++j)
            {
                memcpy(&pSubFragPts[j], m_radialCrackTrees[i][j], sizeof(Vec2));
            }

            // Add parent outline between tree intersections
            if (outlineCount > 0)
            {
                for (int j = 0, k = fragIntersectPts[i] + 1; j < outlineCount; ++j, ++k)
                {
                    // Cyclic outlines, so loop index when necessary
                    if (k >= parentOutlineSize)
                    {
                        k -= parentOutlineSize;
                    }

                    memcpy(&pSubFragPts[firstTreeSize + j], &parentFrag.m_outlinePts[k], sizeof(Vec2));
                }
            }

            // Add opposite tree, excluding center impact pt
            const int secondTreeOffset = firstTreeSize + outlineCount;

            for (int j = 0, k = secondTreeSize; j < secondTreeSize; ++j, --k)
            {
                memcpy(&pSubFragPts[secondTreeOffset + j], m_radialCrackTrees[nextTreeIndex][k], sizeof(Vec2));
            }

            // Create new sub-fragment if there's enough valid data
            if (subFragShape.size() >= 3)
            {
                // In case of high-impulse collision, radially split the new fragment
                if ((impact.radius > 0.02f || impact.impulse > GLASSCFG_PLANE_SPLIT_IMPULSE) && numUsedCrackTrees > 2)
                {
                    const float impactRadius = max(GetImpactRadius(impact), impactSplitMinRadius);
                    const float splitVariation = clamp_tpl<float>(impactRadius, impactSplitRandMin, impactSplitRandMax);
                    const Vec2 splitPt(impact.x, impact.y);
                    float splitRadius = impactRadius * (1.0f + GetRandF() * splitVariation);

                    SplitPolygonAroundPoint(subFragShape, splitPt, splitRadius, splitFragShape);

                    const bool forceLoose = true;
                    CreateFragmentFromOutline(splitFragShape.begin(), splitFragShape.size(), parentFragIndex, forceLoose);
                }

                // Create main sub-fragment (if still valid)
                if (CreateFragmentFromOutline(subFragShape.begin(), subFragShape.size(), parentFragIndex, hitStableLimit))
                {
                    // Store fragment index if stable
                    if (!(m_fragsLoose & m_lastFragBit))
                    {
                        stableSubFrags.push_back(m_lastFragIndex);
                    }
                }
            }
        }
        else
        {
            LOG_GLASS_ERROR("Intersections form invalid sized fragment, had to discard.");
        }

        // Done with this sub-fragment
        subFragShape.clear();
        splitFragShape.clear();
    }

    // Need to connect new sub-fragments to each other,
    // and then to parent's old neighbours
    const uint8* pStableSubFrags = stableSubFrags.begin();
    const int numStableSubFrags = stableSubFrags.size();

    if (numStableSubFrags > 0)
    {
        ConnectSubFragments(pStableSubFrags, numStableSubFrags);
        ReplaceFragmentConnections(parentFragIndex, pStableSubFrags, numStableSubFrags);
    }
}//-------------------------------------------------------------------------------------------------

//--------------------------------------------------------------------------------------------------
// Name: GenerateSubFragmentCracks
// Desc: Generates radial crack triangles within a specified fragment
//--------------------------------------------------------------------------------------------------
void CREBreakableGlass::GenerateSubFragmentCracks(const uint8 parentFragIndex, const SGlassImpactParams& impact, TRadialCrackArray& fragIntersectPts)
{
    GLASS_FUNC_PROFILER

    // Get parent fragment data
    SGlassFragment& parentFrag = m_frags[parentFragIndex];
    const PodArray<Vec2>& fragOutline = parentFrag.m_outlinePts;

    const Vec2* pFragPts = fragOutline.begin();
    const int numFragPts = fragOutline.Count();

    // Generate cracks from current impact pt
    fragIntersectPts.clear();
    const int numRadialCracks = GenerateCracksFromImpact(parentFragIndex, impact);

    const float planeMin = 0.0f;
    const float planeMaxX = m_glassParams.size.x;
    const float planeMaxY = m_glassParams.size.y;

    // Clip crack trees to fragment bounds
    for (int i = 0; i < numRadialCracks; ++i)
    {
        if (m_radialCrackTrees[i].IsEmpty())
        {
            break;
        }

        const int treeSize = m_radialCrackTrees[i].Count();
        Vec2** pRadialPts = &m_radialCrackTrees[i][0];

        for (int j = 0; j < treeSize - 1; ++j)
        {
            const Vec2& nextPt = (*pRadialPts[j + 1]);

            bool ptOnBounds = nextPt.x == planeMin
                || nextPt.x == planeMaxX
                || nextPt.y == planeMin
                || nextPt.y == planeMaxY;

            // Test to see if tree has left fragment
            if (!ptOnBounds && PointInPolygon2D(nextPt, pFragPts, numFragPts))
            {
                continue;
            }

            // Create last tree line
            Lineseg crackLine;
            crackLine.start = (*pRadialPts[j]);
            crackLine.end = nextPt;

            // Find intersection point with fragment outline
            Lineseg outline;
            float outA = 0.0f, outB = 0.0f;
            bool found = false;

            for (int k = 0; k < numFragPts; ++k)
            {
                const int nextIndex = k + 1 < numFragPts ? k + 1 : 0;

                outline.start = fragOutline[k];
                outline.end = fragOutline[nextIndex];

                if (Intersect::Lineseg_Lineseg2D(crackLine, outline, outA, outB))
                {
                    found = true;
                    fragIntersectPts.push_back(k);
                    break;
                }
            }

            CRY_ASSERT_MESSAGE(found, "Crack line leaves fragment but did not find intersection point.");

            // Move end point to intersection
            pRadialPts[j + 1]->x = crackLine.start.x * (1.0f - outA) + crackLine.end.x * outA;
            pRadialPts[j + 1]->y = crackLine.start.y * (1.0f - outA) + crackLine.end.y * outA;

            // Discard remaining sections
            const int numLeft = j + 2;
            m_radialCrackTrees[i].resize(numLeft);

            // Break because the const loop reference value has changed
            break;
        }
    }
}//-------------------------------------------------------------------------------------------------

//--------------------------------------------------------------------------------------------------
// Name: CreateFragmentFromOutline
// Desc: Creates and hashes a new sub fragment with the input data
//--------------------------------------------------------------------------------------------------
bool CREBreakableGlass::CreateFragmentFromOutline(const Vec2* pOutline, const int outlineSize, const uint8 parentFragIndex, const bool forceLoose)
{
    GLASS_FUNC_PROFILER
    bool success = false;

    if (pOutline && outlineSize >= 3)
    {
        // Check against fixed working array size
        if (outlineSize <= POLY_ARRAY_SIZE)
        {
            // Create new fragment
            SGlassFragment* pSubFrag = AddFragment();

            if (pSubFrag)
            {
                const uint8 fragIndex = m_lastFragIndex;
                const uint32 fragBitIndex = m_lastFragBit;

                // Store the outline points
                pSubFrag->m_outlinePts.resize(outlineSize);
                memcpy(pSubFrag->m_outlinePts.begin(), pOutline, sizeof(Vec2) * outlineSize);

                Vec2* pPts = pSubFrag->m_outlinePts.begin();
                const int numPts = pSubFrag->m_outlinePts.Count();

                // Calculate modified fragment area - Smaller fragments give a simpler sim overall
                pSubFrag->m_area = CalculatePolygonArea2D(pPts, numPts);
                pSubFrag->m_area *= (1.0f / GLASSCFG_SIMPLIFY_AREA);

                // Minimum size for actual creation
                if (pSubFrag->m_area > GLASSCFG_MIN_VALID_FRAG_AREA)
                {
                    // Triangulate fragment outline
                    TriangulatePolygon2D(pPts, numPts, NULL, &pSubFrag->m_triInds);

                    // Calculate fragment center position
                    pSubFrag->m_center = CalculatePolygonCenter2D(pPts, numPts);

                    // Determine if fragment is loose
                    const uint8 numFrags = (uint8)m_totalNumFrags;
                    pSubFrag->m_depth = parentFragIndex < numFrags ? m_frags[parentFragIndex].m_depth + 1 : 0;

                    if (forceLoose || IsFragmentLoose(*pSubFrag))
                    {
                        m_fragsLoose |= fragBitIndex;
                    }
                    else
                    {
                        // Hash the new triangles if stable
                        HashFragment(fragIndex);
                    }

                    // Done with fragment
                    success = true;
                }
                else
                {
                    RemoveFragment(fragIndex);
                }
            }
        }
        else
        {
            LOG_GLASS_ERROR("Attempted to create invalid sized fragment.");
        }
    }

    //  if (!success)
    //  {
    //      // It could be worth throwing a particle effect here
    //      // to cover the pop when the new fragment disappears
    //  }

    // Invalid data, so failed to create
    return success;
}//-------------------------------------------------------------------------------------------------

//--------------------------------------------------------------------------------------------------
// Name: IsFragmentLoose
// Desc: Determines if a new fragment is loose
//--------------------------------------------------------------------------------------------------
bool CREBreakableGlass::IsFragmentLoose(const SGlassFragment& frag)
{
    const int MinLooseFragDepth = 3;
    const int LooseFragDepthRange = 3; // Range is 3 -> 6
    const float EdgeStableFragSizeMul = 1.0f;

    bool isLoose = false;
    const int fragDepth = frag.m_depth;

    // Test depth test against randomised range
    if (fragDepth >= MinLooseFragDepth)
    {
        const int looseFragDepth = cry_random(0, LooseFragDepthRange - 1) + MinLooseFragDepth;
        isLoose = (fragDepth >= looseFragDepth);
    }

    // Test area against fixed value, weighted to allow more stable pieces
    // nearer to edges. Means we get lots of nice little shards sticking around
    if (!isLoose)
    {
        const float planeMaxX = m_glassParams.size.x;
        const float planeMaxY = m_glassParams.size.y;

        const float xDistFromEdge = planeMaxX - fabs_tpl(frag.m_center.x - (planeMaxX * 0.5f)) * 2.0f;
        const float yDistFromEdge = planeMaxY - fabs_tpl(frag.m_center.y - (planeMaxY * 0.5f)) * 2.0f;
        const float minDistFromEdge = min(xDistFromEdge, yDistFromEdge);

        float edgeSizeMul = 1.0f - (minDistFromEdge * m_invMaxGlassSize);
        edgeSizeMul = 1.0f + EdgeStableFragSizeMul * edgeSizeMul * edgeSizeMul;

        isLoose = (frag.m_area < GLASSCFG_MIN_STABLE_FRAG_AREA * edgeSizeMul);
    }

    return isLoose;
}//-------------------------------------------------------------------------------------------------

//--------------------------------------------------------------------------------------------------
// Name: IsFragmentWeaklyLinked
// Desc: Determines if an existing fragment is "weakly linked"
//--------------------------------------------------------------------------------------------------
bool CREBreakableGlass::IsFragmentWeaklyLinked(const SGlassFragment& frag)
{
    // Weakly linked means large fragment with little inward support, but giving outward support
    return (frag.m_area > GLASSCFG_MIN_STABLE_FRAG_AREA * 2.0f) &&
           (frag.m_inConnections.size() == 1) &&
           (frag.m_outConnections.size() >= 1);
}//-------------------------------------------------------------------------------------------------

//--------------------------------------------------------------------------------------------------
// Name: RemoveFragmentConnections
// Desc: Removes all registered connections with the fragment
//--------------------------------------------------------------------------------------------------
void CREBreakableGlass::RemoveFragmentConnections(const uint8 fragIndex, PodArray<uint8>& connections)
{
    GLASS_FUNC_PROFILER

    while (!connections.IsEmpty())
    {
        // Remove last connection
        const uint8 neighbour = connections.back();
        connections.DeleteLast();

        // Delete from neighbour's outgoing connection list
        //  - Manually doing delete search code here as uint8
        //      template gives ambiguous function signatures
        PodArray<uint8>& neighbourOutConns = m_frags[neighbour].m_outConnections;
        const uint8* pNeighbourOutConns = neighbourOutConns.begin();
        const int numOutConns = neighbourOutConns.Count();

        for (int i = 0; i < numOutConns; ++i)
        {
            if (pNeighbourOutConns[i] == fragIndex)
            {
                const int numElems = 1;
                neighbourOutConns.Delete(i, numElems);

                break;
            }
        }

        // Delete from neighbour's incoming connection list
        PodArray<uint8>& neighbourInConns = m_frags[neighbour].m_inConnections;
        const uint8* pNeighbourInConns = neighbourInConns.begin();
        const int numInConns = neighbourInConns.Count();

        for (int i = 0; i < numInConns; ++i)
        {
            if (pNeighbourInConns[i] == fragIndex)
            {
                const int numElems = 1;
                neighbourInConns.Delete(i, numElems);

                break;
            }
        }
    }
}//-------------------------------------------------------------------------------------------------

//--------------------------------------------------------------------------------------------------
// Name: ConnectSubFragments
// Desc: Forms connections for a circular list of new sub-fragments
//--------------------------------------------------------------------------------------------------
void CREBreakableGlass::ConnectSubFragments(const uint8* pSubFrags, const uint8 numSubFrags)
{
    if (pSubFrags && numSubFrags > 1)
    {
        // All solid fragments just created may be connected, always in a circular fashion
        for (int i = 0, j = 1; i < numSubFrags; ++i, ++j)
        {
            j = (j == numSubFrags) ? 0 : j;
            ConnectFragmentPair(pSubFrags[i], pSubFrags[j]);
        }
    }
}//-------------------------------------------------------------------------------------------------

//--------------------------------------------------------------------------------------------------
// Name: ReplaceFragmentConnections
// Desc: When a fragment is sub-divided, tells neighbours to reconnect to the child fragments
//--------------------------------------------------------------------------------------------------
void CREBreakableGlass::ReplaceFragmentConnections(const uint8 parentFragIndex, const uint8* pSubFrags, const uint8 numSubFrags)
{
    // Check for potential new connections between parent neighbours and new sub-fragments
    if (pSubFrags && numSubFrags > 0)
    {
        PodArray<uint8>& neighbours = m_frags[parentFragIndex].m_inConnections;
        const uint8* pNeighbours = neighbours.begin();
        const int numNeighbours = neighbours.Count();

        for (int i = 0; i < numNeighbours; ++i)
        {
            for (int j = 0; j < numSubFrags; ++j)
            {
                ConnectFragmentPair(pNeighbours[i], pSubFrags[j]);
            }
        }
    }

    // Remove all old connections
    RemoveFragmentConnections(parentFragIndex, m_frags[parentFragIndex].m_outConnections);
    RemoveFragmentConnections(parentFragIndex, m_frags[parentFragIndex].m_inConnections);
}//-------------------------------------------------------------------------------------------------

//--------------------------------------------------------------------------------------------------
// Name: ConnectFragmentPair
// Desc: Forms directional connections between a pair of fragments if strong enough
//--------------------------------------------------------------------------------------------------
void CREBreakableGlass::ConnectFragmentPair(const uint8 fragAIndex, const uint8 fragBIndex)
{
    GLASS_FUNC_PROFILER

    const float minFragConnection = 0.2f;

    // Validate fragment state
    if (fragAIndex != fragBIndex)
    {
        SGlassFragment& fragA = m_frags[fragAIndex];
        SGlassFragment& fragB = m_frags[fragBIndex];

        float connectionA, connectionB;
        if (CalculatePolygonSharedPerimeter2D(fragA.m_outlinePts.begin(), fragA.m_outlinePts.Count(),
                fragB.m_outlinePts.begin(), fragB.m_outlinePts.Count(), connectionA, connectionB))
        {
            /*
            --------            A is ~10% connected
            |      |__      B is ~30% connected
            |  A   |B |
            |      |--      In this case, only one connection formed from A to B. This
            --------            is because we can then traverse from A to B, but not other
            way around. Can be thought of as "A is supporting B".
            */

            // Connect the fragments where stable connection, to control
            // which fragments are strong enough to support others
            if (connectionB > minFragConnection)
            {
                fragA.m_outConnections.push_back(fragBIndex);
                fragB.m_inConnections.push_back(fragAIndex);
            }

            if (connectionA > minFragConnection)
            {
                fragB.m_outConnections.push_back(fragAIndex);
                fragA.m_inConnections.push_back(fragBIndex);
            }
        }
    }
    else
    {
        LOG_GLASS_ERROR("Tried to connect invalid fragments, ignoring.");
    }
}//-------------------------------------------------------------------------------------------------

//--------------------------------------------------------------------------------------------------
// Name: FindLooseFragments
// Desc: Creates a tree of stable fragments, flagging any loose ones not caught in the traversal
//--------------------------------------------------------------------------------------------------
void CREBreakableGlass::FindLooseFragments(TGlassPhysFragmentArray* pPhysFrags, TGlassPhysFragmentInitArray* pPhysFragsInitData)
{
    GLASS_FUNC_PROFILER

    TFragIndexPodArray stableFrags;
    bool stableFragsFound = false;

    // Pre-evaluate fragments bit states
    const uint32 stableState = m_fragsActive & ~(m_fragsLoose | m_fragsFree);

    // Find all anchored fragments
    if (!m_shattered)
    {
        const uint numAnchors = m_glassParams.numAnchors;
        const Vec2* pAnchors = &m_glassParams.anchors[0];
        TFragIndexPodArray anchorFrags;

        for (uint i = 0; i < numAnchors; ++i)
        {
            SGlassImpactParams anchorParams;
            anchorParams.x = pAnchors[i].x;
            anchorParams.y = pAnchors[i].y;

            uint8 anchorFrag = 0;
            if (FindImpactFragment(anchorParams, anchorFrag))
            {
                // Only store result if found an active fragment
                const uint32 anchorFragBit = (uint32)1 << anchorFrag;

                if (stableState & anchorFragBit)
                {
                    anchorFrags.push_back(anchorFrag);
                }
            }
        }

        // Traverse all valid connections, and log all stable fragments
        const uint numAnchorFrags = anchorFrags.size();
        stableFragsFound = (numAnchorFrags > 0);

        for (uint i = 0; i < numAnchorFrags; ++i)
        {
            TraverseStableConnection(anchorFrags[i], stableFrags);
        }
    }

    // Mark all active fragments not in list as loose
    const uint32 activeState = m_fragsActive & ~m_fragsFree;
    uint32 fragBit = 1;

    for (uint i = 0; i < GLASSCFG_FRAGMENT_ARRAY_SIZE; ++i, fragBit <<= 1)
    {
        if (activeState & fragBit)
        {
            bool dampenVelocity = false;
            bool noVelocity = false;

            // Unconnected fragments should just fall
            if (!(m_fragsLoose & fragBit) && (!stableFragsFound || stableFrags.find(i) < 0))
            {
                m_fragsLoose |= fragBit;
                dampenVelocity = true;
            }

            // Periodically loosen weakly-connected fragments
            else if (s_loosenTimer > 1.0f + GetRandF() && s_impactTimer > 1.0f && s_impactTimer < 2.0f)
            {
                const SGlassFragment& frag = m_frags[i];
                if (IsFragmentWeaklyLinked(frag))
                {
                    m_fragsLoose |= fragBit;
                    noVelocity = true;
                    s_loosenTimer = 0.0f - GetRandF();
                }
            }

            // Resolve any loose fragments
            if (m_fragsLoose & fragBit)
            {
                PrePhysicalizeLooseFragment(pPhysFrags, pPhysFragsInitData, i, dampenVelocity, noVelocity);
            }
        }
    }

    // Reset loosen timer
    s_loosenTimer = 0.0f;

    // No stable fragments means we should shatter
    if (!stableFragsFound)
    {
        ShatterGlass();
    }
}//-------------------------------------------------------------------------------------------------

//--------------------------------------------------------------------------------------------------
// Name: TraverseStableConnection
// Desc: Recursively adds this fragment and all connected fragments to the input list
//--------------------------------------------------------------------------------------------------
void CREBreakableGlass::TraverseStableConnection(const uint8 fragIndex, TFragIndexPodArray& stableFrags)
{
    GLASS_FUNC_PROFILER

    // NOTE: This method should actually take into account a ratio of fragment
    // area and connected edge, as we can currently have a small piece holding
    // together two large pieces. If mass was taken into account, we could
    // instead have the hanging large fragment pull out the smaller one

    // Getting here means this is a stable fragment. Can be a cyclic
    // graph though, so only continue if not already visited
    if (stableFrags.find(fragIndex) < 0)
    {
        stableFrags.push_back(fragIndex);

        // Traverse through all valid out-going connections
        PodArray<uint8>& connections = m_frags[fragIndex].m_outConnections;
        uint8* pConnections = connections.begin();
        const int numConnections = connections.Count();

        for (int i = 0; i < numConnections; ++i)
        {
            TraverseStableConnection(pConnections[i], stableFrags);
        }
    }
}//-------------------------------------------------------------------------------------------------

//--------------------------------------------------------------------------------------------------
// Name: PrePhysicalizeLooseFragment
// Desc: Creates data for a fragment to become physicalized and removes it from the plane
//--------------------------------------------------------------------------------------------------
void CREBreakableGlass::PrePhysicalizeLooseFragment(TGlassPhysFragmentArray* pPhysFrags, TGlassPhysFragmentInitArray* pPhysFragsInitData, const uint8 fragIndex, const bool dampenVel, const bool noVel)
{
    GLASS_FUNC_PROFILER

        assert(pPhysFragsInitData);
    PREFAST_ASSUME(pPhysFragsInitData);

    SGlassFragment& frag = m_frags[fragIndex];
    const uint32 fragBit = (uint32)1 << fragIndex;

    Vec2* pPts = frag.m_outlinePts.begin();
    const uint numPts = frag.m_outlinePts.size();

    // Remove inter-fragment connections
    RemoveFragmentConnections(fragIndex, frag.m_outConnections);
    RemoveFragmentConnections(fragIndex, frag.m_inConnections);

    frag.m_outConnections.Free();
    frag.m_inConnections.Free();

    // Remove all hash grid references to this fragment (if it was ever stable)
    if (frag.m_area >= GLASSCFG_MIN_STABLE_FRAG_AREA)
    {
        const bool remove = true;
        HashFragment(fragIndex, remove);
    }

    // Only physicalize if fragment is large enough
    const float physSizeScale = 0.7f;
    const float minPhysFragSize = 0.065f;

    const Vec2 bounds = CalculatePolygonBounds2D(pPts, numPts);
    const float physFragSize = max(bounds.x, bounds.y) * physSizeScale;

    if (physFragSize >= minPhysFragSize)
    {
        // Can run out of space, in which case just shatter to particles
        const bool outOfSpace = (!pPhysFrags || pPhysFrags->size() == GLASSCFG_MAX_NUM_PHYS_FRAGMENTS);

#ifndef RELEASE
        // Arrays should always be in sync
        const int numPhysFrags = pPhysFrags ? pPhysFrags->size() : 0;
        const int numPhysFragsInitData = pPhysFragsInitData ? pPhysFragsInitData->size() : 0;
        CRY_ASSERT(numPhysFrags == numPhysFragsInitData);
#endif

        if (outOfSpace)
        {
            PlayShatterEffect(fragIndex);
        }
        else
        {
            SGlassPhysFragment physFrag;
            SGlassPhysFragmentInitData physFragsInitData;

            // Prepare to be physicalized
            physFrag.m_fragIndex = fragIndex;
            physFrag.m_bufferIndex = m_lastPhysFrag;
            physFrag.m_matrix = m_params.matrix;
            physFrag.m_size = physFragSize;

            physFragsInitData.m_center = frag.m_center;

            // Re-center fragment mesh at origin
            for (uint i = 0; i < numPts; ++i)
            {
                pPts[i] -= physFragsInitData.m_center;
            }

            // Get cvar constants
            float fragImpulseScale = 5.0f;
            float fragAngImpulseScale = 0.1f;
            float fragImpulseDampen = 0.3f;
            float fragAngImpulseDampen = 0.4f;
            float fragSpread = 1.5f;
            float fragMaxSpeed = 4.0f;
            float fragRandSpread = 0.5f;

            if (s_pCVars)
            {
                fragImpulseScale = s_pCVars->m_fragImpulseScale;
                fragAngImpulseScale = s_pCVars->m_fragAngImpulseScale;
                fragImpulseDampen = s_pCVars->m_fragImpulseDampen;
                fragAngImpulseDampen = s_pCVars->m_fragAngImpulseDampen;
                fragSpread = s_pCVars->m_fragSpread;
                fragMaxSpeed = s_pCVars->m_fragMaxSpeed;
            }

            // Initial impulse based on generation impact
            float angularImpulseScale = fragAngImpulseScale * physFrag.m_size;

            if (dampenVel)
            {
                fragMaxSpeed *= fragImpulseDampen;
                angularImpulseScale *= fragAngImpulseDampen;
            }

            if (noVel)
            {
                fragMaxSpeed = 0.0f;
                angularImpulseScale *= 0.5f;
            }

            if (!m_impactParams.empty())
            {
                // Impact driven physicalization
                SGlassImpactParams& impact = m_impactParams.back();
                const Vec2 impactPt = Vec2(impact.x, impact.y);
                const Vec2 toImpactPt = (impactPt - physFragsInitData.m_center).GetNormalized();

                // Max speed clamp
                Vec3 velocity = impact.velocity;
                float speedSq = impact.velocity.GetLengthSquared();

                if (speedSq > fragMaxSpeed * fragMaxSpeed)
                {
                    velocity = velocity.GetNormalizedFast() * fragMaxSpeed;
                }

                // Adjust velocity to apply artificial spread
                float randSpread = (1.0f - fragRandSpread) + fragRandSpread * GetRandF();
                Vec3 worldFromImpact = physFrag.m_matrix.TransformVector(-toImpactPt);
                velocity += worldFromImpact * GetImpactRadius(impact) * fragSpread * randSpread;

                // Calculate impulse (w/ point between center and edge)
                physFragsInitData.m_impulse = velocity * fragImpulseScale;
                physFragsInitData.m_impulsePt = physFragsInitData.m_center + toImpactPt * angularImpulseScale;
            }
            else
            {
                // Simple piece drop
                physFragsInitData.m_impulse = Vec3_Zero;
                physFragsInitData.m_impulsePt = physFragsInitData.m_center;
            }

            physFragsInitData.m_impulsePt = physFrag.m_matrix.TransformPoint(physFragsInitData.m_impulsePt);

            // Scale impulse to match fragment size
            const float impulseSizeScale = 1.0f + physFrag.m_size;
            physFragsInitData.m_impulse *= impulseSizeScale * impulseSizeScale;

            // Push into lists
            pPhysFrags->push_back(physFrag);
            pPhysFragsInitData->push_back(physFragsInitData);

            // Reset fragment geometry buffer id
            m_fragGeomBufferIds[m_lastPhysFrag].m_geomBufferId = NO_BUFFER;
            m_fragGeomBufferIds[m_lastPhysFrag].m_fragId = fragIndex;
            m_lastPhysFrag = m_lastPhysFrag + 1 < GLASSCFG_MAX_NUM_PHYS_FRAGMENTS ? m_lastPhysFrag + 1 : 0;
        }
    }

    // Flag fragment as inactive
    DeactivateFragment(fragIndex, fragBit);
    ++m_numLooseFrags;
}//-------------------------------------------------------------------------------------------------

//--------------------------------------------------------------------------------------------------
// Name: SetHashGridSize
// Desc: Re-calculates the hash grid sizes
//--------------------------------------------------------------------------------------------------
void CREBreakableGlass::SetHashGridSize(const Vec2& size)
{
#if GLASSCFG_USE_HASH_GRID
    // Resize hash grid to fit new dimensions
    if (m_pHashGrid)
    {
        m_pHashGrid->Resize(size.x, size.y);
        m_pHashGrid->ClearGrid();
    }
#endif

    // Pre-calculate a few constants
    m_hashCellSize = size * (1.0f / (float)GLASSCFG_HASH_GRID_SIZE);
    m_hashCellInvSize.set(1.0f / m_hashCellSize.x, 1.0f / m_hashCellSize.y);
    m_hashCellHalfSize = m_hashCellSize * 0.5f;
}//-------------------------------------------------------------------------------------------------

//--------------------------------------------------------------------------------------------------
// Name: HashFragment
// Desc: Hashes a fragment into the grid
//--------------------------------------------------------------------------------------------------
void CREBreakableGlass::HashFragment(const uint8 fragIndex, const bool remove)
{
#if GLASSCFG_USE_HASH_GRID
    // Get fragment
    const SGlassFragment& frag = m_frags[fragIndex];

    // Hash polygon
    const int numTriIndices = frag.m_triInds.Count();
    const uint8* pIndices = frag.m_triInds.begin();

    for (int j = 0; j < numTriIndices; j += 3)
    {
        const uint index0 = pIndices[j];
        const uint index1 = pIndices[j + 1];
        const uint index2 = pIndices[j + 2];

        HashFragmentTriangle(frag.m_outlinePts[index0], frag.m_outlinePts[index1], frag.m_outlinePts[index2], fragIndex, remove);
    }
#endif
}//-------------------------------------------------------------------------------------------------

//--------------------------------------------------------------------------------------------------
// Name: STriEdge/STriSpan
// Desc: Triangle hashing helper classes
//--------------------------------------------------------------------------------------------------
struct STriEdge
{
    STriEdge(const Vec2& a, const Vec2& b)
    {
        // Lowest point first
        const float abMin = b.y - a.y;
        const float abMax = a.y - b.y;
        pt[0].x = (float)fsel(abMin, a.x, b.x);
        pt[0].y = (float)fsel(abMin, a.y, b.y);
        pt[1].x = (float)fsel(abMax, a.x, b.x);
        pt[1].y = (float)fsel(abMax, a.y, b.y);

        // Edge height
        height = pt[1].y - pt[0].y;
    }

    Vec2 pt[2];
    float height;
};

struct STriSpan
{
    STriSpan(const float x1, const float x2)
    {
        // Left-most point first
        pt[0] = min(x1, x2);
        pt[1] = max(x1, x2);

        // Span length (with direction)
        width = x2 - x1;
    }

    float pt[2];
    float width;
};

//--------------------------------------------------------------------------------------------------
// Name: HashFragmentTriangle
// Desc: Hashes a fragment triangle into the grid
//--------------------------------------------------------------------------------------------------
void CREBreakableGlass::HashFragmentTriangle(const Vec2& a, const Vec2& b, const Vec2& c,
    const uint8& triFrag, const bool removeTri)
{
#if GLASSCFG_USE_HASH_GRID
    GLASS_FUNC_PROFILER

    if (m_pHashGrid)
    {
        // Create triangle edges
        STriEdge edges[3] =
        {
            STriEdge(a, b),
            STriEdge(a, c),
            STriEdge(b, c)
        };

        // Find tallest edge
        uint tallEdge = edges[0].height > edges[1].height ? 0 : 1;
        tallEdge = edges[2].height > edges[tallEdge].height ? 2 : tallEdge;

        uint shortEdge1 = inc_mod3[tallEdge];
        uint shortEdge2 = dec_mod3[tallEdge];

        // Need to essentially rasterise triangle to hash all contained cells into grid
        const float epsilon = 0.05f;

        if (fabs_tpl(edges[shortEdge1].height) >= m_hashCellSize.y * epsilon)
        {
            HashFragmentTriangleSpan(edges[tallEdge], edges[shortEdge1], triFrag, removeTri);
        }

        if (fabs_tpl(edges[shortEdge2].height) >= m_hashCellSize.y * epsilon)
        {
            HashFragmentTriangleSpan(edges[tallEdge], edges[shortEdge2], triFrag, removeTri);
        }
    }
#endif // GLASSCFG_USE_HASH_GRID
}//-------------------------------------------------------------------------------------------------

//--------------------------------------------------------------------------------------------------
// Name: HashFragmentTriangleSpan
// Desc: Hashes common spans of triangle edges into the grid
//--------------------------------------------------------------------------------------------------
void CREBreakableGlass::HashFragmentTriangleSpan(const STriEdge& a, const STriEdge& b,
    const uint8& triFrag, const bool removeTri)
{
#if GLASSCFG_USE_HASH_GRID
    GLASS_FUNC_PROFILER

    // Hash spans along height of smaller edge
    STriSpan spanA(a.pt[0].x, a.pt[1].x);
    STriSpan spanB(b.pt[0].x, b.pt[1].x);

    float spanALerp = (b.pt[0].y - a.pt[0].y) / a.height;
    float spanBLerp = 0.0f;

    float spanAStep = m_hashCellSize.y / a.height;
    float spanBStep = m_hashCellSize.y / b.height;

    uint numYSteps = static_cast<int>(fabs_tpl(b.height - m_hashCellHalfSize.y) * m_hashCellInvSize.y) + 1;
    float yOffset = b.pt[0].y;

    for (uint y = 0; y <= numYSteps; ++y, yOffset += m_hashCellSize.y)
    {
        // Generate span info
        STriSpan span(a.pt[0].x + spanA.width * spanALerp, b.pt[0].x + spanB.width * spanBLerp);
        uint numXSteps = static_cast<uint>(fabs_tpl(span.width - m_hashCellHalfSize.x) * m_hashCellInvSize.x) + 1;

        // Pre-hash first cell
        uint32 cellOffset = m_pHashGrid->HashPosition(span.pt[0], yOffset);

        // Check valid cell
        if (cellOffset < GLASSCFG_HASH_GRID_SIZE * GLASSCFG_HASH_GRID_SIZE)
        {
            // Simple addition step through span as we will touch
            // contiguous set of tiles when moving horizontally
            if (removeTri)
            {
                for (uint x = 0; x < numXSteps; ++x, ++cellOffset)
                {
                    m_pHashGrid->RemoveElementFromGrid(cellOffset, triFrag);
                }
            }
            else
            {
                for (uint x = 0; x < numXSteps; ++x, ++cellOffset)
                {
                    m_pHashGrid->AddElementToGrid(cellOffset, triFrag);
                }
            }
        }

        // Step along edges
        spanALerp += spanAStep;
        spanBLerp += spanBStep;
    }
#endif // GLASSCFG_USE_HASH_GRID
}//-------------------------------------------------------------------------------------------------

//--------------------------------------------------------------------------------------------------
// Name: DeactivateFragment
// Desc: Deactivates and recycles a fragment index
//--------------------------------------------------------------------------------------------------
void CREBreakableGlass::DeactivateFragment(const uint8 index, const uint32 bit)
{
    // Mark as inactive
    m_fragsActive &= ~bit;

    // Recycle index
    m_fragsFree |= bit;
    m_freeFragIndices.push_back(index);
}//-------------------------------------------------------------------------------------------------

//--------------------------------------------------------------------------------------------------
// Name: RebuildAllFragmentGeom
// Desc: Creates simple plane geometry
//--------------------------------------------------------------------------------------------------
void CREBreakableGlass::RebuildAllFragmentGeom()
{
    GLASS_FUNC_PROFILER

    if (m_geomDirty && !m_geomRebuilt && m_dirtyGeomBufferCount == 0)
    {
        // Clear old data
        m_planeGeom.Clear();
        m_crackGeom.Clear();

        for (uint i = 0; i < GLASSCFG_MAX_NUM_PHYS_FRAGMENTS; ++i)
        {
            m_fragFullGeom[i].Clear();
        }

        // Pool fragments into single lists for vertex creation
        ProcessFragmentGeomData();

        // Update the impact count so the shader constants can update too
        m_numDecalImpacts = (uint8)m_impactParams.size();

        // Rebuilt, so ready for buffer copy/update
        MemoryBarrier();
        m_geomDirty = false;
        uint8 numDirtyBuffers = 1;

        // Toggle flag for all active loose fragments too
        for (uint i = 0; i < GLASSCFG_MAX_NUM_PHYS_FRAGMENTS; ++i)
        {
            if (m_fragGeomBufferIds[i].m_fragId < GLASSCFG_FRAGMENT_ARRAY_SIZE)
            {
                m_fragGeomRebuilt[i] = true;
                ++numDirtyBuffers;
            }
        }

        m_dirtyGeomBufferCount = numDirtyBuffers;
        m_geomUpdateFrame = 0; // PC doesn't suffer same delay issues
        m_geomRebuilt = true;
        MemoryBarrier();
    }
}//-------------------------------------------------------------------------------------------------

//--------------------------------------------------------------------------------------------------
// Name: RehashAllFragmentData
// Desc: Clears the grid and re-hashes all fragments
//--------------------------------------------------------------------------------------------------
void CREBreakableGlass::RehashAllFragmentData()
{
#if GLASSCFG_USE_HASH_GRID
    GLASS_FUNC_PROFILER

    if (m_pHashGrid)
    {
        // Clear old data
        m_pHashGrid->ClearGrid();

        // Pre-evaluate fragment state bits
        const uint32 validState = m_fragsActive & ~(m_fragsLoose | m_fragsFree);
        uint32 fragBit = 1;

        // Hash all active fragments
        for (uint i = 0; i < GLASSCFG_FRAGMENT_ARRAY_SIZE; ++i, fragBit <<= 1)
        {
            if (validState & fragBit)
            {
                HashFragment(i);
            }
        }
    }
#endif // GLASSCFG_USE_HASH_GRID
}//-------------------------------------------------------------------------------------------------

//--------------------------------------------------------------------------------------------------
// Name: ProcessFragmentGeomData
// Desc: Generates the geometry data for all fragments
//--------------------------------------------------------------------------------------------------
void CREBreakableGlass::ProcessFragmentGeomData()
{
    GLASS_FUNC_PROFILER

    // Pre-evaluate fragment state bits
    const uint32 validState = m_fragsActive & ~(m_fragsLoose | m_fragsFree);
    uint32 fragBit = 1;

    // Pre-size geom buffers for active fragments
    const SGlassFragment* pFrags = m_frags.begin();

    const int crackVertStep = 2;
    const int crackIndStep = 6;

    uint vertCount = 0;
    uint indCount = 0;

    for (uint i = 0; i < GLASSCFG_FRAGMENT_ARRAY_SIZE; ++i, fragBit <<= 1)
    {
        if (validState & fragBit)
        {
            vertCount += pFrags[i].m_outlinePts.Count();
            indCount += pFrags[i].m_triInds.Count();
        }
    }

    m_planeGeom.m_verts.resize(vertCount);
    m_planeGeom.m_inds.resize(indCount);
    m_planeGeom.m_tans.resize(vertCount);

    m_crackGeom.m_verts.resize(vertCount * crackVertStep);
    m_crackGeom.m_inds.resize(indCount * crackIndStep);
    m_crackGeom.m_tans.resize(vertCount * crackVertStep);

    // Create geom in arrays
    uint planeVOffs = 0, planeIOffs = 0;
    uint crackVOffs = 0, crackIOffs = 0;
    fragBit = 1;

    for (uint i = 0; i < GLASSCFG_FRAGMENT_ARRAY_SIZE; ++i, fragBit <<= 1)
    {
        if (validState & fragBit)
        {
            BuildFragmentTriangleData(i, planeVOffs, planeIOffs);
            BuildFragmentOutlineData(i, crackVOffs, crackIOffs);
        }
    }

    // Create geom for all loose fragments
    for (uint i = 0; i < GLASSCFG_MAX_NUM_PHYS_FRAGMENTS; ++i)
    {
        const uint8 fragId = m_fragGeomBufferIds[i].m_fragId;

        // Validate fragment
        if (fragId < GLASSCFG_FRAGMENT_ARRAY_SIZE)
        {
            // Pre-size buffer to hold entire geom
            const uint numVerts = pFrags[fragId].m_outlinePts.Count();
            const uint numInds = pFrags[fragId].m_triInds.Count();

            vertCount = numVerts + numVerts * crackVertStep;
            indCount = numInds + numVerts * crackIndStep;

            m_fragFullGeom[i].m_verts.resize(vertCount);
            m_fragFullGeom[i].m_inds.resize(indCount);
            m_fragFullGeom[i].m_tans.resize(vertCount);

            // Reset offsets
            planeVOffs = planeIOffs = 0;

            // Build phys fragments
            BuildFragmentOutlineData(fragId, planeVOffs, planeIOffs, i);
            BuildFragmentTriangleData(fragId, planeVOffs, planeIOffs, EGlassSurfaceSide_Front, i);
        }
    }
}//-------------------------------------------------------------------------------------------------

//--------------------------------------------------------------------------------------------------
// Name: BuildFragmentTriangleData
// Desc: Generates the stable triangle data for all fragments
//--------------------------------------------------------------------------------------------------
void CREBreakableGlass::BuildFragmentTriangleData(const uint8 fragIndex, uint& vertOffs, uint& indOffs, const EGlassSurfaceSide side, const int subFragID)
{
    GLASS_FUNC_PROFILER

    // Get fragment data
    const PodArray<Vec2>& vertices = m_frags[fragIndex].m_outlinePts;
    const PodArray<uint8>& indices = m_frags[fragIndex].m_triInds;

    const int vertCount = vertices.Count();
    const int indCount = indices.Count();

    // Get offset into output arrays
    const bool isPhysFrag = (subFragID >= 0);
    SGlassGeom& buffer = isPhysFrag ? m_fragFullGeom[subFragID] : m_planeGeom;

    const int vertOffset = vertOffs;
    const int indOffset = indOffs;

    // Increment external counters
    vertOffs += vertCount;
    indOffs += indCount;

    // Slightly non-flat surface tangent (Fixed per fragment for consistency)
    const float randTangentScale = 0.015f;
    const float randTangents[8] = {0.0f, 0.7f, 0.5f, 0.1f, 1.0f, 0.6f, 0.2f, 0.8f};
    const uint8 randIndex = fragIndex & 7; // Optimized "index % 8"

    const Vec3 tanOffset(randTangents[randIndex] * randTangentScale);

    Vec3 tangent = (side == EGlassSurfaceSide_Back) ? Vec3(0.0f, 1.0f, 0.0f) : Vec3(0.0f, -1.0f, 0.0f);
    Vec3 bitangent = (side == EGlassSurfaceSide_Back) ? Vec3(-1.0f, 0.0f, 0.0f) : Vec3(1.0f, 0.0f, 0.0f);

    tangent = tangent + tanOffset;
    bitangent = bitangent - tanOffset;

    SPipTangents planeTangent;
    PackTriangleTangent(tangent, bitangent, planeTangent);

    // Physicalized fragments use offset positions, so need UV coords adjusting
    const Vec2& uvOffset = isPhysFrag ? m_frags[fragIndex].m_center : Vec2_Zero;

    // Surface side determines Z offset
    float surfaceZOffset = 0.0f;

    switch (side)
    {
    case EGlassSurfaceSide_Front:
        surfaceZOffset = m_glassParams.thickness;
        break;

    case EGlassSurfaceSide_Back:
        surfaceZOffset = 0.0f;
        break;

    default: // EGlassSurfaceSide_Center
        surfaceZOffset = m_glassParams.thickness * 0.5f;
    }
    ;

    // Create vertex data
    for (int i = 0; i < vertCount; ++i)
    {
        const int offset = vertOffset + i;

        GenerateVertFromPoint(vertices[i], uvOffset, buffer.m_verts[offset]);
        buffer.m_verts[offset].xyz.z += surfaceZOffset;
        buffer.m_tans[offset] = planeTangent;
    }

    // Copy indices with correct offset into vertices
    for (int i = 0; i < indCount; ++i)
    {
        buffer.m_inds[indOffset + i] = indices[i] + vertOffset;
    }
}//-------------------------------------------------------------------------------------------------

//--------------------------------------------------------------------------------------------------
// Name: BuildFragmentOutlineData
// Desc: Generates the outline triangle data for all fragments
//--------------------------------------------------------------------------------------------------
void CREBreakableGlass::BuildFragmentOutlineData(const uint8 fragIndex, uint& vertOffs, uint& indOffs, const int subFragID)
{
    GLASS_FUNC_PROFILER

    Vec3 pt0, pt1, pt2;
    const float thickness = m_glassParams.thickness;
    const float edgeUVOffset = 0.003f;
    const bool impactDistInAlpha = true;

    // Get fragment data
    const PodArray<Vec2>& pts = m_frags[fragIndex].m_outlinePts;
    const Vec2* pPts = pts.begin();
    const int numPts = pts.Count();

    const int vertStep = 2;
    const int indStep = 6;

    const int vertCount = numPts * vertStep;
    const int indCount = numPts * indStep;

    // Get offset into output arrays
    const bool isPhysFrag = (subFragID >= 0);
    SGlassGeom& buffer = isPhysFrag ? m_fragFullGeom[subFragID] : m_crackGeom;

    const int vertOffset = vertOffs;
    const int indOffset = indOffs;

    // Increment external counters
    vertOffs += vertCount;
    indOffs += indCount;

    // Log if we've somehow managed to get out of sync
    if (vertOffs > (uint)buffer.m_verts.Count() || indOffs > (uint)buffer.m_inds.Count() || vertOffs > (uint)buffer.m_tans.Count())
    {
        LOG_GLASS_ERROR("Fragment geometry buffers out of sync. "
            "vCnt(%i), vOffs(%i), iCnt(%i), iOffs(%i), "
            "vbCnt(%i), ibCnt(%i), tbCnt(%i), "
            "frIdx(%i), sbFrId(%i)",
            vertCount, vertOffs, indCount, indOffs,
            buffer.m_verts.Count(), buffer.m_inds.Count(), buffer.m_tans.Count(),
            fragIndex, subFragID);
    }

    // Create a pair of vertices at each point to show plane thickness
    for (int i = 0, j = 0; i < vertCount; i += vertStep, ++j)
    {
        // Expand (and jitter) back point
        pt0 = pt1 = pPts[j];
        pt1.z += thickness;

        // Next point along, used for normal generation
        pt2 = pPts[(j + 1) % numPts];

        // Physicalized fragments use offset positions, so need UV coords adjusting
        const Vec2& uvOffset = isPhysFrag ? m_frags[fragIndex].m_center : Vec2_Zero;

        // Create vertex data
        const int offset = vertOffset + i;
        GenerateVertFromPoint(pt0, uvOffset, buffer.m_verts[offset], impactDistInAlpha);
        GenerateVertFromPoint(pt1, uvOffset, buffer.m_verts[offset + 1]);

        buffer.m_verts[offset + 1].color = buffer.m_verts[offset].color;

        // Apply a slight uv offset to help avoid stretching
        //  - Would be nice to map/mirror away from fragment center
        buffer.m_verts[offset].st.y -= edgeUVOffset;
        buffer.m_verts[offset + 1].st.y += edgeUVOffset;

        // Calculate triangle tangent
        GenerateTriangleTangent(pt0, pt1, pt2, buffer.m_tans[offset]);
        buffer.m_tans[offset + 1] = buffer.m_tans[offset];
    }

    // Create quad indices with correct offset into vertices
    for (int i = 0, j = 0; i < indCount; i += indStep, j += vertStep)
    {
        // Get cyclic vert indices
        const int currIndex = vertOffset + j;
        const int nextIndex = vertOffset + (j + vertStep >= vertCount ? 0 : j + vertStep);

        // Create index data
        const int offset = indOffset + i;

        buffer.m_inds[offset]    = currIndex;
        buffer.m_inds[offset + 1] = currIndex + 1;
        buffer.m_inds[offset + 2] = nextIndex + 1;

        buffer.m_inds[offset + 3] = currIndex;
        buffer.m_inds[offset + 4] = nextIndex + 1;
        buffer.m_inds[offset + 5] = nextIndex;
    }
}//-------------------------------------------------------------------------------------------------

//--------------------------------------------------------------------------------------------------
// Name: GenerateDefaultPlaneGeom
// Desc: Creates simple plane geometry
//--------------------------------------------------------------------------------------------------
void CREBreakableGlass::GenerateDefaultPlaneGeom()
{
    // Create plane fragment shape
    TPolygonArray outline;
    outline.resize(4);

    const float planeMin = 0.0f;
    const float planeMaxX = m_glassParams.size.x;
    const float planeMaxY = m_glassParams.size.y;

    outline[0].set(planeMaxX, planeMin);
    outline[1].set(planeMaxX, planeMaxY);

    outline[2].set(planeMin, planeMaxY);
    outline[3].set(planeMin, planeMin);

    // Initialise fragment
    const uint8 noParent = (uint8) - 1; // Forcing a value we *know* is invalid
    if (CreateFragmentFromOutline(outline.begin(), outline.size(), noParent))
    {
        // Force geometry rebuild
        m_geomDirty = true;
    }
    else
    {
        LOG_GLASS_ERROR("Failed to create default plane, possible corrupt glass dimensions.");
    }
}//-------------------------------------------------------------------------------------------------

//--------------------------------------------------------------------------------------------------
// Name: GenerateGeomFromFragment
// Desc: Creates initial fragment from input
//--------------------------------------------------------------------------------------------------
bool CREBreakableGlass::GenerateGeomFromFragment(const Vec2* pFragPts, const uint numPts)
{
    bool success = false;
    if (pFragPts && numPts >= 3)
    {
        // Initialise fragment
        const int noParent = -1;
        if (CreateFragmentFromOutline(pFragPts, numPts, noParent))
        {
            // Force geometry rebuild
            m_geomDirty = true;
            success = true;
        }
        else
        {
            LOG_GLASS_ERROR("Failed to create initial fragment, possible corrupt geometry extraction.");
        }
    }

    return success;
}//-------------------------------------------------------------------------------------------------

//--------------------------------------------------------------------------------------------------
// Name: GenerateVertFromPoint
// Desc: Generates a single vertex from input point
//--------------------------------------------------------------------------------------------------
void CREBreakableGlass::GenerateVertFromPoint(const Vec3& pt, const Vec2& uvOffset, SVF_P3F_C4B_T2F& vert, const bool impactDistInAlpha)
{
    GLASS_FUNC_PROFILER

    // Position
    vert.xyz = pt;

    // UV coordinates
    const Vec2 uvPt(pt.x + uvOffset.x, pt.y + uvOffset.y);

    vert.st = m_glassParams.uvOrigin;
    vert.st += m_glassParams.uvXAxis * uvPt.x;
    vert.st += m_glassParams.uvYAxis * uvPt.y;

    // White colour
    vert.color.dcolor = 0xFFFFFFFF;

    // Distance to closest impact point
    if (impactDistInAlpha)
    {
        float alphaDistScale = 255.0f;
        alphaDistScale *= s_pCVars ? s_pCVars->m_impactEdgeFadeScale : 2.0f;

        float centerDist = GetClosestImpactDistance(Vec2(pt.x, pt.y));
        centerDist = clamp_tpl<float>(centerDist * alphaDistScale, 0.0f, 255.0f);

        vert.color.bcolor[EColIdx_A] = 0xFF - (byte)centerDist;

        // Add a subtle dark blue-green tint
        vert.color.bcolor[EColIdx_B] = 0xDF;
        vert.color.bcolor[EColIdx_G] = 0xDF;
        vert.color.bcolor[EColIdx_R] = 0xBF;
    }
}//-------------------------------------------------------------------------------------------------

//--------------------------------------------------------------------------------------------------
// Name: GenerateTriangleTangent
// Desc: Generates triangle tangent/bitangent data
//--------------------------------------------------------------------------------------------------
void CREBreakableGlass::GenerateTriangleTangent(const Vec3& triPt0, const Vec3& triPt1, const Vec3& triPt2, SPipTangents& tangent)
{
    const Vec3 forward = (triPt1 - triPt0).GetNormalizedSafe();
    const Vec3 right = (triPt2 - triPt0).GetNormalizedSafe();

    PackTriangleTangent(forward, right, tangent);
}//-------------------------------------------------------------------------------------------------

//--------------------------------------------------------------------------------------------------
// Name: PackTriangleTangent
// Desc: Packs triangle tangent/bitangent data into SPipTangent structure
//--------------------------------------------------------------------------------------------------
void CREBreakableGlass::PackTriangleTangent(const Vec3& tangent, const Vec3& bitangent, SPipTangents& tanBitan)
{
    tanBitan = SPipTangents(tangent, bitangent, 1);
}//-------------------------------------------------------------------------------------------------

#ifdef GLASS_DEBUG_MODE
//--------------------------------------------------------------------------------------------------
// Name: GenerateImpactGeom
// Desc: Creates simple impact quad line geometry
//--------------------------------------------------------------------------------------------------
void CREBreakableGlass::GenerateImpactGeom(const SGlassImpactParams& impact)
{
#ifndef GLASS_PROFILER_ENABLED
    // Revert existing transformation
    if (!m_impactLineList.IsEmpty())
    {
        const bool inverse = true;
        TransformPointList(m_impactLineList, inverse);
    }

    // Add new quad
    int offset = m_impactLineList.Count();
    m_impactLineList.resize(offset + 8);

    m_impactLineList[offset + 0].Set(impact.x - IMPACT_HALF_SIZE, impact.y + IMPACT_HALF_SIZE, 0.0f);
    m_impactLineList[offset + 1].Set(impact.x + IMPACT_HALF_SIZE, impact.y + IMPACT_HALF_SIZE, 0.0f);

    m_impactLineList[offset + 2].Set(impact.x - IMPACT_HALF_SIZE, impact.y - IMPACT_HALF_SIZE, 0.0f);
    m_impactLineList[offset + 3].Set(impact.x + IMPACT_HALF_SIZE, impact.y - IMPACT_HALF_SIZE, 0.0f);

    m_impactLineList[offset + 4].Set(impact.x + IMPACT_HALF_SIZE, impact.y - IMPACT_HALF_SIZE, 0.0f);
    m_impactLineList[offset + 5].Set(impact.x + IMPACT_HALF_SIZE, impact.y + IMPACT_HALF_SIZE, 0.0f);

    m_impactLineList[offset + 6].Set(impact.x - IMPACT_HALF_SIZE, impact.y - IMPACT_HALF_SIZE, 0.0f);
    m_impactLineList[offset + 7].Set(impact.x - IMPACT_HALF_SIZE, impact.y + IMPACT_HALF_SIZE, 0.0f);

    // Add circle showing impact radius
    const Vec3 center(impact.x, impact.y, 0.0f);
    const float radius = GetImpactRadius(impact);

    if (radius > 0.0f)
    {
        float angle = 0.0f;
        const float angleInc = gf_PI2 / (float)IMPACT_NUM_IMPULSE_SIDES;

        offset = m_impactLineList.Count();

        const int doubleNumSides = IMPACT_NUM_IMPULSE_SIDES * 2;
        m_impactLineList.resize(offset + doubleNumSides);

        // Pre-calculate first sin/cos
        float sinA, cosA, sinB, cosB;
        sincos_tpl(angle, &sinA, &cosA);
        angle += angleInc;

        // Generate circle shape
        for (int i = 0; i < doubleNumSides; i += 2, angle += angleInc)
        {
            sincos_tpl(angle, &sinB, &cosB);

            const Vec3 a(sinA, cosA, 0.0f);
            const Vec3 b(sinB, cosB, 0.0f);

            m_impactLineList[offset + i] = a * radius + center;
            m_impactLineList[offset + i + 1] = b * radius + center;

            sinA = sinB;
            cosA = cosB;
        }
    }

    // Transform for rendering
    TransformPointList(m_impactLineList);
#endif // !GLASS_PROFILER_ENABLED
}//-------------------------------------------------------------------------------------------------

//--------------------------------------------------------------------------------------------------
// Name: TransformPointList
// Desc: Transforms a list of points to/from world-space
//--------------------------------------------------------------------------------------------------
void CREBreakableGlass::TransformPointList(PodArray<Vec3>& ptList, const bool inverse)
{
#ifndef GLASS_PROFILER_ENABLED
    // Get transformation
    Matrix34 transMat = m_params.matrix;

    if (inverse)
    {
        transMat.Invert();
    }

    // Transform points
    const int numPts = ptList.Count();
    Vec3* pPts = ptList.begin();

    for (int i = 0; i < numPts; ++i)
    {
        pPts[i] = transMat.TransformPoint(pPts[i]);
    }
#endif // !GLASS_PROFILER_ENABLED
}//-------------------------------------------------------------------------------------------------
#endif // GLASS_DEBUG_MODE

#endif // ENABLE_CRY_PHYSICS