/*
* 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.

// Description : Loading trees, buildings, register/unregister entities for rendering


#include "StdAfx.h"

#include "StatObj.h"
#include "ObjMan.h"
#include "VisAreas.h"
#include "CullBuffer.h"
#include "3dEngine.h"
#include "IndexedMesh.h"
#include "Brush.h"
#include "Vegetation.h"
#include "ObjectsTree.h"
#include <IResourceManager.h>
#include "DecalRenderNode.h"
#include <cctype>
#include <Terrain/ITerrainNode.h>
#include <Terrain/Bus/LegacyTerrainBus.h>

#include <StatObjBus.h>
#include <Vegetation/StaticVegetationBus.h>

#include <LoadScreenBus.h>

#define BRUSH_LIST_FILE "brushlist.txt"
#define CGF_LEVEL_CACHE_PAK "cgf.pak"

namespace BoneNames
{
    const char* Cloth = "cloth";
}

//////////////////////////////////////////////////////////////////////////
IStatObj* CObjManager::GetStaticObjectByTypeID(int nTypeID, int nSID)
{
    assert(nSID >= 0 && nSID < m_lstStaticTypes.Count());

    if (nTypeID >= 0 && nTypeID < m_lstStaticTypes[nSID].Count())
    {
        return m_lstStaticTypes[nSID][nTypeID].pStatObj;
    }

    return 0;
}

IStatObj* CObjManager::FindStaticObjectByFilename(const char* filename)
{
    string lowerFileName(filename);
    return stl::find_in_map(m_nameToObjectMap, CONST_TEMP_STRING(lowerFileName.MakeLower()), NULL);
}

void CObjManager::UnloadVegetationModels(bool bDeleteAll)
{
    for (uint32 nSID = 0; nSID < m_lstStaticTypes.size(); nSID++)
    {
        PodArray<StatInstGroup>& rGroupTable = m_lstStaticTypes[nSID];
        for (uint32 nGroupId = 0; nGroupId < rGroupTable.size(); nGroupId++)
        {
            StatInstGroup& rGroup = rGroupTable[nGroupId];

            rGroup.pStatObj = NULL;
            rGroup.pMaterial = NULL;
            ReleaseStatInstGroupId(nGroupId);
        }

        if (bDeleteAll)
        {
            rGroupTable.Free();
        }
    }
    Vegetation::StaticVegetationNotificationBus::Broadcast(&Vegetation::StaticVegetationNotificationBus::Events::VegetationCleared);
}

//////////////////////////////////////////////////////////////////////////
void CObjManager::UnloadObjects(bool bDeleteAll)
{
    UnloadVegetationModels(bDeleteAll);

    CleanStreamingData();

    m_pRMBox = 0;

    m_decalsToPrecreate.resize(0);

    // Clear all objects that are in the garbage collector.
    ClearStatObjGarbage();

    stl::free_container(m_checkForGarbage);
    m_bGarbageCollectionEnabled = false;

    if (bDeleteAll)
    {
        {
            AZStd::unique_lock<AZStd::recursive_mutex> loadLock(m_loadMutex);

            m_lockedObjects.clear(); // Lock/Unlock resources will not work with this.

            // Release default stat obj.
            m_pDefaultCGF = 0;

            m_nameToObjectMap.clear();
            m_lstLoadedObjects.clear();
        }

        int nNumLeaks = 0;
        std::vector<CStatObj*> garbage;
        for (CStatObj* pStatObj = CStatObj::get_intrusive_list_root(); pStatObj; pStatObj = pStatObj->m_next_intrusive)
        {
            garbage.push_back(pStatObj);

#if !defined(_RELEASE)
            if (!pStatObj->IsDefaultObject())
            {
                nNumLeaks++;
                Warning("StatObj not deleted: %s (%s)  RefCount: %d", pStatObj->m_szFileName.c_str(), pStatObj->m_szGeomName.c_str(), pStatObj->m_nUsers);
            }
#endif //_RELEASE
        }

#ifndef _RELEASE
        // deleting leaked objects
        if (nNumLeaks > 0)
        {
            Warning("CObjManager::CheckObjectLeaks: %d object(s) found in memory", nNumLeaks);
        }
#endif //_RELEASE

        for (int i = 0, num = (int)garbage.size(); i < num; i++)
        {
            CStatObj* pStatObj = garbage[i];
            pStatObj->ShutDown();
        }
        for (int i = 0, num = (int)garbage.size(); i < num; i++)
        {
            CStatObj* pStatObj = garbage[i];
            delete pStatObj;
        }

#ifdef POOL_STATOBJ_ALLOCS
        assert(m_statObjPool->GetTotalMemory().nUsed == 0);
#endif
    }
    m_bGarbageCollectionEnabled = true;

#ifdef POOL_STATOBJ_ALLOCS
    m_statObjPool->FreeMemoryIfEmpty();
#endif

    //If this collection is not cleared on unload then character materials will
    //leak and most likely crash the engine across level loads.
    stl::free_container(m_collectedMaterials);
    stl::free_container(m_lstTmpCastingNodes);
    stl::free_container(m_decalsToPrecreate);
    stl::free_container(m_tmpAreas0);
    stl::free_container(m_tmpAreas1);
    for (int nSID = 0; nSID < m_lstStaticTypes.Count(); nSID++)
    {
        m_lstStaticTypes[nSID].Free();
    }
}

//////////////////////////////////////////////////////////////////////////
void CObjManager::CleanStreamingData()
{
    stl::free_container(m_arrStreamingNodeStack);

    stl::free_container(m_arrStreamableToRelease);
    stl::free_container(m_arrStreamableToLoad);
    stl::free_container(m_arrStreamableToDelete);
}

//////////////////////////////////////////////////////////////////////////
// class for asyncronous preloading of level CGF's
//////////////////////////////////////////////////////////////////////////
struct CLevelStatObjLoader
    : public IStreamCallback
    , public Cry3DEngineBase
{
    int m_nTasksNum;

    CLevelStatObjLoader()
    {
        m_nTasksNum = 0;
    }

    void StartStreaming(const char* pFileName)
    {
        m_nTasksNum++;

        // request the file
        StreamReadParams params;
        params.dwUserData = 0;
        params.nSize = 0;
        params.pBuffer = NULL;
        params.nLoadTime = 0;
        params.nMaxLoadTime = 0;
        params.ePriority = estpUrgent;
        GetSystem()->GetStreamEngine()->StartRead(eStreamTaskTypeGeometry, pFileName, this, &params);
    }

    virtual void StreamOnComplete (IReadStream* pStream, unsigned nError)
    {
        if (!nError)
        {
            string szName = pStream->GetName();
            // remove game folder from path
            const char* szInGameName = strstr(szName, "\\");
            // load CGF from memory
            GetObjManager()->LoadStatObjUnsafeManualRef(szInGameName + 1, NULL, NULL, true, 0, pStream->GetBuffer(), pStream->GetBytesRead());
        }

        m_nTasksNum--;
    }
};

//////////////////////////////////////////////////////////////////////////
// Preload in efficient way all CGF's used in level
//////////////////////////////////////////////////////////////////////////
void CObjManager::PreloadLevelObjects()
{
    LOADING_TIME_PROFILE_SECTION;

    // Starting a new level, so make sure the round ids are ahead of what they were in the last level
    m_nUpdateStreamingPrioriryRoundId += 8;
    m_nUpdateStreamingPrioriryRoundIdFast += 8;

    PrintMessage("Starting loading level CGF's ...");
    INDENT_LOG_DURING_SCOPE();

    float fStartTime = GetCurAsyncTimeSec();

    bool bCgfCacheExist = false;
    if (GetCVars()->e_StreamCgf != 0)
    {
        // Only when streaming enable use no-mesh cgf pak.
        //bCgfCacheExist = GetISystem()->GetIResourceManager()->LoadLevelCachePak( CGF_LEVEL_CACHE_PAK,"" );
    }
    IResourceList* pResList = GetISystem()->GetIResourceManager()->GetLevelResourceList();

    // Construct streamer object
    CLevelStatObjLoader cgfStreamer;

    CryPathString cgfFilename;
    int nCgfCounter = 0;
    int nInLevelCacheCount = 0;

    bool bVerboseLogging = GetCVars()->e_StatObjPreload > 1;


    //////////////////////////////////////////////////////////////////////////
    // Enumerate all .CGF inside level from the "brushlist.txt" file.
    {
        string brushListFilename = Get3DEngine()->GetLevelFilePath(BRUSH_LIST_FILE);
        CCryFile file;
        if (file.Open(brushListFilename.c_str(), "rb") && file.GetLength() > 0)
        {
            int nFileLength = file.GetLength();
            char* buf = new char[nFileLength + 1];
            buf[nFileLength] = 0; // Null terminate
            file.ReadRaw(buf, nFileLength);

            // Parse file, every line in a file represents a resource filename.
            char seps[] = "\r\n";
            char *next_token = nullptr;
            char* token = azstrtok(buf, 0, seps, &next_token);
            while (token != NULL)
            {
                int nAliasLen = sizeof("%level%") - 1;
                if (strncmp(token, "%level%", nAliasLen) == 0)
                {
                    cgfFilename = Get3DEngine()->GetLevelFilePath(token + nAliasLen);
                }
                else
                {
                    cgfFilename = token;
                }

                if (bVerboseLogging)
                {
                    CryLog("%s", cgfFilename.c_str());
                }
                // Do not use streaming for the Brushes from level.pak.
                GetObjManager()->LoadStatObjUnsafeManualRef(cgfFilename.c_str(), NULL, 0, false, 0);
                //cgfStreamer.StartStreaming(cgfFilename.c_str());
                nCgfCounter++;

                token = azstrtok(NULL, 0, seps, &next_token);

                //This loop can take a few seconds, so we should refresh the loading screen and call the loading tick functions to ensure that no big gaps in coverage occur.
                SYNCHRONOUS_LOADING_TICK();
            }
            delete []buf;
        }
    }
    //////////////////////////////////////////////////////////////////////////

    // Request objects loading from Streaming System.
    if (const char* pCgfName = pResList->GetFirst())
    {
        while (pCgfName)
        {
            if (strstr(pCgfName, ".cgf"))
            {
                const char* sLodName = strstr(pCgfName, "_lod");
                if (sLodName && (sLodName[4] >= '0' && sLodName[4] <= '9'))
                {
                    // Ignore Lod files.
                    pCgfName = pResList->GetNext();
                    continue;
                }

                cgfFilename = pCgfName;

                if (bVerboseLogging)
                {
                    CryLog("%s", cgfFilename.c_str());
                }
                IStatObj* pStatObj = GetObjManager()->LoadStatObjUnsafeManualRef(cgfFilename.c_str(), NULL, 0, true, 0);
                if (pStatObj)
                {
                    if (pStatObj->IsMeshStrippedCGF())
                    {
                        nInLevelCacheCount++;
                    }
                }
                //cgfStreamer.StartStreaming(cgfFilename.c_str());
                nCgfCounter++;

                //This loop can take a few seconds, so we should refresh the loading screen and call the loading tick functions to ensure that no big gaps in coverage occur.
                SYNCHRONOUS_LOADING_TICK();
            }

            pCgfName = pResList->GetNext();
        }
    }


    //  PrintMessage("Finished requesting level CGF's: %d objects in %.1f sec", nCgfCounter, GetCurAsyncTimeSec()-fStartTime);

    // Continue updating streaming system until all CGF's are loaded
    if (cgfStreamer.m_nTasksNum > 0)
    {
        LOADING_TIME_PROFILE_SECTION_NAMED("CObjManager::PreloadLevelObjects_StreamEngine_Update");
        GetSystem()->GetStreamEngine()->UpdateAndWait();
    }

    if (bCgfCacheExist)
    {
        //GetISystem()->GetIResourceManager()->UnloadLevelCachePak( CGF_LEVEL_CACHE_PAK );
    }

    float dt = GetCurAsyncTimeSec() - fStartTime;
    PrintMessage("Finished loading level CGF's: %d objects loaded (%d from LevelCache) in %.1f sec", nCgfCounter, nInLevelCacheCount, dt);
}


//////////////////////////////////////////////////////////////////////////////////////////////////////////
// Create / delete object
//////////////////////////////////////////////////////////////////////////////////////////////////////////

template<typename T>
T CObjManager::LoadStatObjInternal(const char* filename,
    const char* _szGeomName,
    IStatObj::SSubObject** ppSubObject,
    bool bUseStreaming,
    unsigned long nLoadingFlags,
    const void* pData,
    int nDataSize,
    const char* szBlockName)
{
    LoadDefaultCGF(filename, nLoadingFlags);

    LOADING_TIME_PROFILE_SECTION;
    
    if (ppSubObject)
    {
        *ppSubObject = NULL;
    }

    AZStd::unique_lock<AZStd::recursive_mutex> loadLock(m_loadMutex);

    if (!strcmp(filename, "NOFILE"))
    { // make empty object to be filled from outside
        CStatObj* pObject = new CStatObj();
        m_lstLoadedObjects.insert(pObject);

        return pObject;
    }

    // Normalize file name
    // Remap %level% alias if needed and unify filename
    char normalizedFilename[_MAX_PATH];
    NormalizeLevelName(filename, normalizedFilename);

    bool bForceBreakable = strstr(normalizedFilename, "break") != 0;
    if (_szGeomName && !strcmp(_szGeomName, "#ForceBreakable"))
    {
        bForceBreakable = true;
        _szGeomName = 0;
    }

    // Try to find already loaded object
    IStatObj* pObject = 0;

    int flagCloth = 0;
    if (_szGeomName && !strcmp(_szGeomName, BoneNames::Cloth))
    {
        _szGeomName = 0;
        flagCloth = STATIC_OBJECT_DYNAMIC | STATIC_OBJECT_CLONE;
    }
    else
    {
        // This branch needs to be handled carefully to avoid returning an object that is in the process of being deleted by ClearStatObjGarbage on another thread
        // It is important that ClearStatObjGarbage is not run during this time (done via m_loadMutex)

        AZStd::string lowerFileName(normalizedFilename);
        int(* toLowerFunc)(int) = std::tolower; //Needed to help the compiler figure out the right tolower to use on android
        AZStd::transform(lowerFileName.begin(), lowerFileName.end(), lowerFileName.begin(), toLowerFunc);

        pObject = stl::find_in_map(m_nameToObjectMap, CONST_TEMP_STRING(lowerFileName.c_str()), NULL);
        if (pObject)
        {
            assert(!pData);

            return (T)LoadFromCacheNoRef(pObject, bUseStreaming, nLoadingFlags, _szGeomName, ppSubObject);
        }
    }

    // Load new CGF
    return LoadNewCGF(pObject, flagCloth, bUseStreaming, bForceBreakable, nLoadingFlags, normalizedFilename, pData, nDataSize, filename, _szGeomName, ppSubObject);
}

IStatObj* CObjManager::LoadStatObjUnsafeManualRef(const char* filename,
    const char* _szGeomName,
    IStatObj::SSubObject** ppSubObject,
    bool bUseStreaming,
    unsigned long nLoadingFlags,
    const void* pData,
    int nDataSize,
    const char* szBlockName)
{
    return LoadStatObjInternal<IStatObj*>(filename, _szGeomName, ppSubObject, bUseStreaming, nLoadingFlags, pData, nDataSize, szBlockName);
}

_smart_ptr<IStatObj> CObjManager::LoadStatObjAutoRef(const char* filename,
    const char* _szGeomName,
    IStatObj::SSubObject** ppSubObject,
    bool bUseStreaming,
    unsigned long nLoadingFlags,
    const void* pData,
    int nDataSize,
    const char* szBlockName)
{
    return LoadStatObjInternal<_smart_ptr<IStatObj> >(filename, _szGeomName, ppSubObject, bUseStreaming, nLoadingFlags, pData, nDataSize, szBlockName);
}

template <size_t SIZE_IN_CHARS>
void CObjManager::NormalizeLevelName(const char* filename, char(&normalizedFilename)[SIZE_IN_CHARS])
{
    const int nAliasNameLen = sizeof("%level%") - 1;

    if (strncmp(filename, "%level%", nAliasNameLen) == 0)
    {
        cry_strcpy(normalizedFilename, Get3DEngine()->GetLevelFilePath(filename + nAliasNameLen));
    }
    else
    {
        cry_strcpy(normalizedFilename, filename);
    }

    PREFAST_SUPPRESS_WARNING(6054) // normalizedFilename is null terminated
    std::replace(normalizedFilename, normalizedFilename + strlen(normalizedFilename), '\\', '/'); // To Unix Path
}

void CObjManager::LoadDefaultCGF(const char* filename, unsigned long nLoadingFlags)
{
    string fixedFileName(filename);
    fixedFileName = PathUtil::ToUnixPath(fixedFileName);

    if (!m_pDefaultCGF && azstricmp(fixedFileName.c_str(), DEFAULT_CGF_NAME) != 0)
    {
        // Load default object if not yet loaded.
        const char* sDefaulObjFilename = DEFAULT_CGF_NAME;
        // prepare default object
        m_pDefaultCGF = LoadStatObjUnsafeManualRef(sDefaulObjFilename, NULL, NULL, false, nLoadingFlags);
        if (!m_pDefaultCGF)
        {
            Error("CObjManager::LoadStatObj: Default object not found (%s)", sDefaulObjFilename);
            m_pDefaultCGF = new CStatObj();
        }
        m_pDefaultCGF->SetDefaultObject(true);
    }
}

IStatObj* CObjManager::LoadNewCGF(IStatObj* pObject, int flagCloth, bool bUseStreaming, bool bForceBreakable, unsigned long nLoadingFlags, const char* normalizedFilename, const void* pData, int nDataSize, const char* originalFilename, const char* geomName, IStatObj::SSubObject** ppSubObject)
{
    pObject = new CStatObj();
    pObject->SetFlags(pObject->GetFlags() | flagCloth);

    bUseStreaming &= (GetCVars()->e_StreamCgf != 0);

    if (bUseStreaming)
    {
        pObject->SetCanUnload(true);
    }
    if (bForceBreakable)
    {
        nLoadingFlags |= IStatObj::ELoadingFlagsForceBreakable;
    }

    if (!pObject->LoadCGF(normalizedFilename, strstr(normalizedFilename, "_lod") != NULL, nLoadingFlags, pData, nDataSize))
    {
        Error("Failed to load cgf: %s", originalFilename);
        // object not found
        // if geom name is specified - just return 0
        if (geomName && geomName[0])
        {
            delete pObject;
            return nullptr;
        }

        // make unique default CGF for every case of missing CGF, this will make export process more reliable and help finding missing CGF's in pure game
        /*      pObject->m_bCanUnload = false;
        if (m_bEditor && pObject->LoadCGF( DEFAULT_CGF_NAME, false, nLoadingFlags, pData, nDataSize ))
        {
        pObject->m_szFileName = sFilename;
        pObject->m_bDefaultObject = true;
        }
        else*/
        {
            delete pObject;
            return m_pDefaultCGF;
        }
    }

    // now try to load lods
    if (!pObject->AreLodsLoaded())
    {
        pObject->LoadLowLODs(bUseStreaming, nLoadingFlags);
    }

    if (!pObject->IsUnloadable())
    { // even if streaming is disabled we register object for potential streaming (streaming system will never unload it)
        pObject->DisableStreaming();
    }

    // sub meshes merging
    pObject->TryMergeSubObjects(false);

    m_lstLoadedObjects.insert(pObject);
    m_nameToObjectMap[pObject->GetFileName().MakeLower()] = pObject;

    if (geomName && geomName[0])
    {
        // Return SubObject.
        CStatObj::SSubObject* pSubObject = pObject->FindSubObject(geomName);
        if (!pSubObject || !pSubObject->pStatObj)
        {
            return 0;
        }
        if (pSubObject->pStatObj)
        {
            if (ppSubObject)
            {
                *ppSubObject = pSubObject;
            }
            return (CStatObj*)pSubObject->pStatObj;
        }
    }

#if AZ_LOADSCREENCOMPONENT_ENABLED
    if (GetISystem() && GetISystem()->GetGlobalEnvironment() && GetISystem()->GetGlobalEnvironment()->mMainThreadId == CryGetCurrentThreadId())
    {
        EBUS_EVENT(LoadScreenBus, UpdateAndRender);
    }
#endif // if AZ_LOADSCREENCOMPONENT_ENABLED
    return pObject;
}

IStatObj* CObjManager::LoadFromCacheNoRef(IStatObj* pObject, bool bUseStreaming, unsigned long nLoadingFlags, const char* geomName, IStatObj::SSubObject** ppSubObject)
{
    if (!bUseStreaming && pObject->IsUnloadable())
    {
        pObject->DisableStreaming();
    }

    if (!pObject->AreLodsLoaded())
    {
        pObject->LoadLowLODs(bUseStreaming, nLoadingFlags);
    }

    if (geomName && geomName[0])
    {
        // Return SubObject.
        IStatObj::SSubObject* pSubObject = pObject->FindSubObject(geomName);
        if (!pSubObject || !pSubObject->pStatObj)
        {
            return nullptr;
        }

        if (ppSubObject)
        {
            *ppSubObject = pSubObject;
        }

        // UnregisterForGarbage needs to be called on pObject before returning
        UnregisterForGarbage(pObject);

        return (CStatObj*)pSubObject->pStatObj;
    }

    UnregisterForGarbage(pObject);

    return pObject;
}

//////////////////////////////////////////////////////////////////////////
bool CObjManager::InternalDeleteObject(IStatObj* pObject)
{
    assert(pObject);

    AZStd::lock_guard<AZStd::recursive_mutex> loadLock(m_loadMutex);

    if (!m_bLockCGFResources && !IsResourceLocked(pObject->GetFileName()))
    {
        LoadedObjects::iterator it = m_lstLoadedObjects.find(pObject);
        if (it != m_lstLoadedObjects.end())
        {
            m_lstLoadedObjects.erase(it);
            m_nameToObjectMap.erase(pObject->GetFileName().MakeLower());
        }
        else
        {
            //Warning( "CObjManager::ReleaseObject called on object not loaded in ObjectManager %s",pObject->m_szFileName.c_str() );
            //return false;
        }

        delete pObject;
        return true;
    }
    else if (m_bLockCGFResources)
    {
        // Put them to locked stat obj list.
        stl::push_back_unique(m_lockedObjects, pObject);
    }

    return false;
}

IStatObj* CObjManager::AllocateStatObj()
{
#ifdef POOL_STATOBJ_ALLOCS
    return (IStatObj*)m_statObjPool->Allocate();
#else
    return (IStatObj*)malloc(sizeof(CStatObj));
#endif
}

void CObjManager::FreeStatObj(IStatObj* pObj)
{
#ifdef POOL_STATOBJ_ALLOCS
    m_statObjPool->Deallocate(pObj);
#else
    free(pObj);
#endif
}

CObjManager::CObjManager()
    : m_pDefaultCGF (NULL)
    , m_decalsToPrecreate()
    , m_bNeedProcessObjectsStreaming_Finish(false)
    , m_CullThread()
    , m_fGSMMaxDistance(0)
    , m_bLockCGFResources(false)
    , m_sunAnimIndex(0)
    , m_sunAnimSpeed(0)
    , m_sunAnimPhase(0)
    , m_nNextPrecachePointId(0)
    , m_bCameraPrecacheOverridden(false)
    , m_fILMul(1.f)
    , m_fSSAOAmount(1.f)
    , m_fSSAOContrast(1.f)
    , m_pRMBox(nullptr)
    , m_bGarbageCollectionEnabled(true)
{
#ifdef POOL_STATOBJ_ALLOCS
    m_statObjPool = new stl::PoolAllocator<sizeof(CStatObj), stl::PSyncMultiThread, alignof(CStatObj)>(stl::FHeap().PageSize(64)); // 20Kb per page
#endif

    m_vStreamPreCachePointDefs.Add(SObjManPrecachePoint());
    m_vStreamPreCacheCameras.Add(SObjManPrecacheCamera());

    m_pObjManager = this;

    m_vSunColor.Set(0, 0, 0);
    m_rainParams.nUpdateFrameID = -1;
    m_rainParams.fAmount = 0.f;
    m_rainParams.fRadius = 1.f;
    m_rainParams.vWorldPos.Set(0, 0, 0);
    m_rainParams.vColor.Set(1, 1, 1);
    m_rainParams.fFakeGlossiness = 0.5f;
    m_rainParams.fFakeReflectionAmount = 1.5f;
    m_rainParams.fDiffuseDarkening = 0.5f;
    m_rainParams.fRainDropsAmount = 0.5f;
    m_rainParams.fRainDropsSpeed = 1.f;
    m_rainParams.fRainDropsLighting = 1.f;
    m_rainParams.fMistAmount = 3.f;
    m_rainParams.fMistHeight = 8.f;
    m_rainParams.fPuddlesAmount = 1.5f;
    m_rainParams.fPuddlesMaskAmount = 1.0f;
    m_rainParams.fPuddlesRippleAmount = 2.0f;
    m_rainParams.fSplashesAmount = 1.3f;
    m_rainParams.bIgnoreVisareas = false;
    m_rainParams.bDisableOcclusion = false;


#ifdef SUPP_HWOBJ_OCCL
    if (GetRenderer()->GetFeatures() & RFT_OCCLUSIONTEST)
    {
        m_pShaderOcclusionQuery = GetRenderer()->EF_LoadShader("OcclusionTest");
    }
    else
    {
        m_pShaderOcclusionQuery = 0;
    }
#endif
    
    m_decalsToPrecreate.reserve(128);

    // init queue for check occlusion
    m_CheckOcclusionQueue.Init(GetCVars()->e_CheckOcclusionQueueSize);
    m_CheckOcclusionOutputQueue.Init(GetCVars()->e_CheckOcclusionOutputQueueSize);

    StatInstGroupEventBus::Handler::BusConnect();
}

// make unit box for occlusion test
void CObjManager::MakeUnitCube()
{
    if (m_pRMBox)
    {
        return;
    }

    SVF_P3F_C4B_T2F arrVerts[8];
    arrVerts[0].xyz = Vec3(0, 0, 0);
    arrVerts[1].xyz = Vec3(1, 0, 0);
    arrVerts[2].xyz = Vec3(0, 0, 1);
    arrVerts[3].xyz = Vec3(1, 0, 1);
    arrVerts[4].xyz = Vec3(0, 1, 0);
    arrVerts[5].xyz = Vec3(1, 1, 0);
    arrVerts[6].xyz = Vec3(0, 1, 1);
    arrVerts[7].xyz = Vec3(1, 1, 1);

    //      6-------7
    //   /         /|
    //  2-------3   |
    //  |        |  |
    //  |   4    | 5
    //  |        |/
    //  0-------1

    static const vtx_idx arrIndices[] =
    {
        // front + back
        1, 0, 2,
        2, 3, 1,
        5, 6, 4,
        5, 7, 6,
        // left + right
        0, 6, 2,
        0, 4, 6,
        1, 3, 7,
        1, 7, 5,
        // top + bottom
        3, 2, 6,
        6, 7, 3,
        1, 4, 0,
        1, 5, 4
    };

    m_pRMBox = GetRenderer()->CreateRenderMeshInitialized(
            arrVerts,
            sizeof(arrVerts) / sizeof(arrVerts[0]),
            eVF_P3F_C4B_T2F,
            arrIndices,
            sizeof(arrIndices) / sizeof(arrIndices[0]),
            prtTriangleList,
            "OcclusionQueryCube", "OcclusionQueryCube",
            eRMT_Static);

    m_pRMBox->SetChunk(NULL, 0, sizeof(arrVerts) / sizeof(arrVerts[0]), 0, sizeof(arrIndices) / sizeof(arrIndices[0]), 1.0f, eVF_P3F_C4B_T2F, 0);

    m_bGarbageCollectionEnabled = true;
}

CObjManager::~CObjManager()
{
    StatInstGroupEventBus::Handler::BusDisconnect();

    // free default object
    m_pDefaultCGF = 0;

    // free brushes
    /*  assert(!m_lstBrushContainer.Count());
        for(int i=0; i<m_lstBrushContainer.Count(); i++)
        {
        if(m_lstBrushContainer[i]->GetEntityStatObj())
          ReleaseObject((CStatObj*)m_lstBrushContainer[i]->GetEntityStatObj());
            delete m_lstBrushContainer[i];
        }
        m_lstBrushContainer.Reset();
    */
    UnloadObjects(true);
#ifdef POOL_STATOBJ_ALLOCS
    delete m_statObjPool;
#endif
}


// mostly xy size
float CObjManager::GetXYRadius(int type, int nSID)
{
    assert(nSID >= 0 && nSID < m_lstStaticTypes.Count());

    if ((m_lstStaticTypes[nSID].Count() <= type || !m_lstStaticTypes[nSID][type].pStatObj))
    {
        return 0.0f;
    }

    Vec3 vSize = m_lstStaticTypes[nSID][type].pStatObj->GetBoxMax() - m_lstStaticTypes[nSID][type].pStatObj->GetBoxMin();
    vSize.z *= 0.5f;

    float fXYRadius = vSize.GetLength() * 0.5f;

    return fXYRadius;
}

bool CObjManager::GetStaticObjectBBox(int nType, Vec3& vBoxMin, Vec3& vBoxMax, int nSID)
{
    assert(nSID >= 0 && nSID < m_lstStaticTypes.Count());

    if ((m_lstStaticTypes[nSID].Count() <= nType || !m_lstStaticTypes[nSID][nType].pStatObj))
    {
        return false;
    }

    vBoxMin = m_lstStaticTypes[nSID][nType].pStatObj->GetBoxMin();
    vBoxMax = m_lstStaticTypes[nSID][nType].pStatObj->GetBoxMax();

    return true;
}


void CObjManager::AddDecalToRenderer(float fDistance,
    _smart_ptr<IMaterial> pMat,
    const uint8 sortPrio,
    Vec3 right,
    Vec3 up,
    const UCol& ucResCol,
    const uint8 uBlendType,
    const Vec3& vAmbientColor,
    Vec3 vPos,
    const int nAfterWater,
    const SRenderingPassInfo& passInfo,
    CVegetation* pVegetation,
    const SRendItemSorter& rendItemSorter)
{
    FUNCTION_PROFILER_3DENGINE;
    bool bBending = pVegetation && pVegetation->m_pRNTmpData && !!pVegetation->m_pRNTmpData->userData.m_Bending.m_vBending;

    if (bBending)
    { // transfer decal into object space
        Matrix34A objMat;
        IStatObj* pEntObject = pVegetation->GetEntityStatObj(0, 0, &objMat);
        assert(pEntObject);
        if (pEntObject)
        {
            objMat.Invert();
            vPos = objMat.TransformPoint(vPos);
            right = objMat.TransformVector(right);
            up = objMat.TransformVector(up);
        }
    }

    // repeated objects are free imedeately in renderer
    CRenderObject* pOb(GetIdentityCRenderObject(passInfo.ThreadID()));
    if (!pOb)
    {
        return;
    }

    if (bBending)
    {
        pVegetation->GetEntityStatObj(0, 0, &pOb->m_II.m_Matrix);
        IStatObj* pBody = pVegetation->GetStatObj();
        assert(pBody);
        if (pBody)
        {
            Get3DEngine()->SetupBending(pOb, pVegetation, pBody->GetRadiusVert(), passInfo);
        }
    }

    // prepare render object
    pOb->m_fDistance = fDistance;
    pOb->m_nTextureID = -1;
    pOb->m_fAlpha = (float)ucResCol.bcolor[3] / 255.f;
    pOb->m_II.m_AmbColor = vAmbientColor;
    pOb->m_fSort = 0;
    pOb->m_ObjFlags |= FOB_DECAL;
    pOb->m_nSort = sortPrio;

    SVF_P3F_C4B_T2F pVerts[4];
    uint16 pIndices[6];

    // TODO: determine whether this is a decal on opaque or transparent geometry
    // (put it in the respective renderlist for correct shadowing)
    // fill general vertex data
    pVerts[0].xyz = (-right - up) + vPos;
    pVerts[0].st = Vec2(0, 1);
    pVerts[0].color.dcolor = ~0;

    pVerts[1].xyz = (right - up) + vPos;
    pVerts[1].st = Vec2(1, 1);
    pVerts[1].color.dcolor = ~0;

    pVerts[2].xyz = (right + up) + vPos;
    pVerts[2].st = Vec2(1, 0);
    pVerts[2].color.dcolor = ~0;

    pVerts[3].xyz = (-right + up) + vPos;
    pVerts[3].st = Vec2(0, 0);
    pVerts[3].color.dcolor = ~0;

    // prepare tangent space (tangent, bitangent) and fill it in
    Vec3 rightUnit(right.GetNormalized());
    Vec3 upUnit(up.GetNormalized());

    SPipTangents pTangents[4];

    pTangents[0] = SPipTangents(rightUnit, -upUnit, -1);
    pTangents[1] = pTangents[0];
    pTangents[2] = pTangents[0];
    pTangents[3] = pTangents[0];

    // fill decals topology (two triangles)
    pIndices[0] = 0;
    pIndices[1] = 1;
    pIndices[2] = 2;

    pIndices[3] = 0;
    pIndices[4] = 2;
    pIndices[5] = 3;

    GetRenderer()->EF_AddPolygonToScene(pMat->GetShaderItem(), 4, pVerts, pTangents, pOb, passInfo, pIndices, 6, nAfterWater, rendItemSorter);
}


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

    {
        SIZER_COMPONENT_NAME(pSizer, "StaticTypes");
        pSizer->AddObject(m_lstStaticTypes);
    }

    {
        SIZER_COMPONENT_NAME(pSizer, "CMesh");
        CIndexedMesh* pMesh = CIndexedMesh::get_intrusive_list_root();
        while (pMesh)
        {
            pSizer->AddObject(pMesh);
            pMesh = pMesh->m_next_intrusive;
        }
    }

    {
        SIZER_COMPONENT_NAME(pSizer, "StatObj");
        for (CStatObj* pStatObj = CStatObj::get_intrusive_list_root(); pStatObj; pStatObj = pStatObj->m_next_intrusive)
        {
            pStatObj->GetMemoryUsage(pSizer);
        }
    }

    {
        SIZER_COMPONENT_NAME(pSizer, "EmptyNodes");
        pSizer->AddObject(COctreeNode::m_arrEmptyNodes);
    }
}

// retrieves the bandwidth calculations for the audio streaming
void CObjManager::GetBandwidthStats(float* fBandwidthRequested)
{
#if !defined (_RELEASE) || defined(ENABLE_STATOSCOPE_RELEASE)
    if (fBandwidthRequested && CStatObj::s_fStreamingTime != 0.0f)
    {
        *fBandwidthRequested = (CStatObj::s_nBandwidth / CStatObj::s_fStreamingTime) / 1024.0f;
    }
#endif
}

void CObjManager::ReregisterEntitiesInArea(Vec3 vBoxMin, Vec3 vBoxMax)
{
    PodArray<SRNInfo> lstEntitiesInArea;

    AABB vBoxAABB(vBoxMin, vBoxMax);

    Get3DEngine()->MoveObjectsIntoListGlobal(&lstEntitiesInArea, &vBoxAABB, true);

    if (GetVisAreaManager())
    {
        GetVisAreaManager()->MoveObjectsIntoList(&lstEntitiesInArea, vBoxAABB, true);
    }

    int nChanged = 0;
    for (int i = 0; i < lstEntitiesInArea.Count(); i++)
    {
        IVisArea* pPrevArea = lstEntitiesInArea[i].pNode->GetEntityVisArea();
        Get3DEngine()->UnRegisterEntityDirect(lstEntitiesInArea[i].pNode);

        if (lstEntitiesInArea[i].pNode->GetRenderNodeType() == eERType_Decal)
        {
            ((CDecalRenderNode*)lstEntitiesInArea[i].pNode)->RequestUpdate();
        }

        Get3DEngine()->RegisterEntity(lstEntitiesInArea[i].pNode);
        if (pPrevArea != lstEntitiesInArea[i].pNode->GetEntityVisArea())
        {
            nChanged++;
        }
    }
}


void CObjManager::FreeNotUsedCGFs()
{
    AZStd::vector<CStatObj*> garbageList;

    {
        AZStd::lock_guard<AZStd::recursive_mutex> loadLock(m_loadMutex);

        m_lockedObjects.clear();

        if (!m_bLockCGFResources)
        {
            //Timur, You MUST use next here, or with erase you invalidating
            LoadedObjects::iterator next;
            for (LoadedObjects::iterator it = m_lstLoadedObjects.begin(); it != m_lstLoadedObjects.end(); it = next)
            {
                next = it;
                ++next;
                CStatObj* p = (CStatObj*)(*it);
                if (p->m_nUsers <= 0)
                {
                    // Add to the list and run CheckForGarbage afterwards so we don't have to hold two locks at once
                    garbageList.push_back(p);
                }
            }
        }
    }

    {
        AZStd::lock_guard<AZStd::recursive_mutex> lock(m_garbageMutex);
        for (auto* object : garbageList)
        {
            CheckForGarbage(object);
        }
    }

    ClearStatObjGarbage();
}

//////////////////////////////////////////////////////////////////////////
void CObjManager::GetLoadedStatObjArray(IStatObj** pObjectsArray, int& nCount)
{
    AZStd::lock_guard<AZStd::recursive_mutex> loadLock(m_loadMutex);

    if (!pObjectsArray)
    {
        nCount = m_lstLoadedObjects.size();
        return;
    }

    CObjManager::LoadedObjects::iterator it = m_lstLoadedObjects.begin();
    for (int i = 0; i < nCount && it != m_lstLoadedObjects.end(); i++, it++)
    {
        pObjectsArray[i] = *it;
    }
}

void StatInstGroup::Update(CVars* pCVars, int nGeomDetailScreenRes)
{
    m_dwRndFlags = 0;

    static ICVar* pObjShadowCastSpec = gEnv->pConsole->GetCVar("e_ObjShadowCastSpec");
    if (nCastShadowMinSpec <= pObjShadowCastSpec->GetIVal())
    {
        m_dwRndFlags |= ERF_CASTSHADOWMAPS | ERF_HAS_CASTSHADOWMAPS;
    }

    if (bDynamicDistanceShadows)
    {
        m_dwRndFlags |= ERF_DYNAMIC_DISTANCESHADOWS;
    }
    if (bHideability)
    {
        m_dwRndFlags |= ERF_HIDABLE;
    }
    if (bHideabilitySecondary)
    {
        m_dwRndFlags |= ERF_HIDABLE_SECONDARY;
    }
    if (!bAllowIndoor)
    {
        m_dwRndFlags |= ERF_OUTDOORONLY;
    }

    uint32 nSpec = (uint32)minConfigSpec;
    if (nSpec != 0)
    {
        m_dwRndFlags &= ~ERF_SPEC_BITS_MASK;
        m_dwRndFlags |= (nSpec << ERF_SPEC_BITS_SHIFT) & ERF_SPEC_BITS_MASK;
    }

    if (GetStatObj())
    {
        fVegRadiusVert = GetStatObj()->GetRadiusVert();
        fVegRadiusHor = GetStatObj()->GetRadiusHors();
        fVegRadius = max(fVegRadiusVert, fVegRadiusHor);
    }
    else
    {
        fVegRadiusHor = fVegRadius = fVegRadiusVert = 0;
    }

#if defined(FEATURE_SVO_GI)
    _smart_ptr<IMaterial> pMat = pMaterial ? pMaterial : (pStatObj ? pStatObj->GetMaterial() : 0);
    if (pMat && (gEnv->pConsole->GetCVar("e_svoTI_Active") && 
        gEnv->pConsole->GetCVar("e_svoTI_Active")->GetIVal() && 
        gEnv->pConsole->GetCVar("e_GI")->GetIVal()))
    {
        pMat->SetKeepLowResSysCopyForDiffTex();
    }
#endif
}

float StatInstGroup::GetAlignToTerrainAmount() const
{
    return fAlignToTerrainCoefficient * Cry3DEngineBase::GetCVars()->e_VegetationAlignToTerrainAmount;
}

bool CObjManager::SphereRenderMeshIntersection(IRenderMesh* pRenderMesh, const Vec3& vInPos, const float fRadius, _smart_ptr<IMaterial> pMat)
{
    FUNCTION_PROFILER_3DENGINE;

    // get position offset and stride
    int nPosStride = 0;
    byte* pPos  = pRenderMesh->GetPosPtr(nPosStride, FSL_READ);

    // get indices
    vtx_idx* pInds = pRenderMesh->GetIndexPtr(FSL_READ);
    int nInds = pRenderMesh->GetIndicesCount();
    assert(nInds % 3 == 0);

    // test tris
    TRenderChunkArray& Chunks = pRenderMesh->GetChunks();
    for (int nChunkId = 0; nChunkId < Chunks.size(); nChunkId++)
    {
        CRenderChunk* pChunk = &Chunks[nChunkId];
        if (pChunk->m_nMatFlags & MTL_FLAG_NODRAW || !pChunk->pRE)
        {
            continue;
        }

        // skip transparent and alpha test
        if (pMat)
        {
            const SShaderItem& shaderItem = pMat->GetShaderItem(pChunk->m_nMatID);
            if (!shaderItem.m_pShader || shaderItem.m_pShader->GetFlags() & EF_NODRAW)
            {
                continue;
            }
        }

        int nLastIndexId = pChunk->nFirstIndexId + pChunk->nNumIndices;
        for (int i = pChunk->nFirstIndexId; i < nLastIndexId; i += 3)
        {
            assert((int)pInds[i + 0] < pRenderMesh->GetVerticesCount());
            assert((int)pInds[i + 1] < pRenderMesh->GetVerticesCount());
            assert((int)pInds[i + 2] < pRenderMesh->GetVerticesCount());

            // get triangle vertices
            Vec3 v0 = (*(Vec3*)&pPos[nPosStride * pInds[i + 0]]);
            Vec3 v1 = (*(Vec3*)&pPos[nPosStride * pInds[i + 1]]);
            Vec3 v2 = (*(Vec3*)&pPos[nPosStride * pInds[i + 2]]);

            AABB triBox;
            triBox.min = v0;
            triBox.max = v0;
            triBox.Add(v1);
            triBox.Add(v2);

            if (Overlap::Sphere_AABB(Sphere(vInPos, fRadius), triBox))
            {
                return true;
            }
        }
    }

    return false;
}

//////////////////////////////////////////////////////////////////////////
void CObjManager::ClearStatObjGarbage()
{
    FUNCTION_PROFILER_3DENGINE;

    // No work? Exit early before attempting to take any locks
    if (m_checkForGarbage.empty())
    {
        return;
    }

    // We have to take the load lock here because InternalDeleteObject needs this lock and loadMutex has to be locked before garbageMutex
    // Additionally, we need to hold one of these locks for the entire duration of this function to prevent the loading thread from using an object that is about to be deleted
    // To avoid stalls due to the threads loading files, try to get the lock and if it fails simply try again next frame unless there are too many garbage objects.
    AZStd::unique_lock<AZStd::recursive_mutex> loadLock(m_loadMutex, AZStd::try_to_lock_t());
    if (!loadLock.owns_lock())
    {
        if (m_checkForGarbage.size() > s_maxPendingGarbageObjects)
        {
            AZ_PROFILE_SCOPE_STALL(AZ::Debug::ProfileCategory::ThreeDEngine, "StatObjGarbage overflow");
            // There are too many objects pending garbage collection so force a clear this frame even if loading is happening.
            loadLock.lock();
        }
        else
        {
            return;
        }
    }

    AZStd::vector<IStatObj*> garbage;

    // We might need to perform the entire garbage collection logic more than once because the call to
    // pStatObj->Shutdown() has the potential to add separate LOD models back onto the m_checkForGarbage list.
    // If we only run the logic once, we might exit the ClearStatObjGarbage() function with garbage that hasn't been
    // fully cleared.
    while (!m_checkForGarbage.empty())
    {
        {
            AZStd::unique_lock<AZStd::recursive_mutex> garbageLock(m_garbageMutex);

            // Make sure all stat objects inside this array are unique.
            // Only check explicitly added objects.
            IStatObj* pStatObj;

            while (!m_checkForGarbage.empty())
            {
                pStatObj = m_checkForGarbage.back();
                m_checkForGarbage.pop_back();

                if (pStatObj->CheckGarbage())
                {
                    // Check if it must be released.
                    int nChildRefs = pStatObj->CountChildReferences();

                    if (pStatObj->GetUserCount() <= 0 && nChildRefs <= 0)
                    {
                        garbage.push_back(pStatObj);
                    }
                    else
                    {
                        pStatObj->SetCheckGarbage(false);
                    }
                }
            }
        }

        // First ShutDown object clearing all pointers.
        for (int i = 0, num = (int)garbage.size(); i < num; i++)
        {
            IStatObj* pStatObj = garbage[i];

            if (!m_bLockCGFResources && !IsResourceLocked(pStatObj->GetFileName()))
            {
                // only shutdown object if it can be deleted by InternalDeleteObject()
                pStatObj->ShutDown();
            }
        }

        // Then delete all garbage objects.
        for (int i = 0, num = (int)garbage.size(); i < num; i++)
        {
            IStatObj* pStatObj = garbage[i];
            InternalDeleteObject(pStatObj);
        }

        garbage.clear();
    }
}

//////////////////////////////////////////////////////////////////////////
IRenderMesh* CObjManager::GetRenderMeshBox()
{
    if (!m_pRMBox)
    {
        MakeUnitCube();
    }
    return m_pRMBox;
}

//////////////////////////////////////////////////////////////////////////
void CObjManager::CheckForGarbage(IStatObj* pObject)
{
    if (m_bGarbageCollectionEnabled &&
        !pObject->CheckGarbage())
    {
        AZStd::lock_guard<AZStd::recursive_mutex> lock(m_garbageMutex);

        pObject->SetCheckGarbage(true);
        m_checkForGarbage.push_back(pObject);
    }
}

//////////////////////////////////////////////////////////////////////////
void CObjManager::UnregisterForGarbage(IStatObj* pObject)
{
    CRY_ASSERT(pObject);

    if (m_bGarbageCollectionEnabled &&
        pObject->CheckGarbage())
    {
        AZStd::lock_guard<AZStd::recursive_mutex> lock(m_garbageMutex);

        if (!m_checkForGarbage.empty())
        {
            auto iterator = std::find(m_checkForGarbage.begin(), m_checkForGarbage.end(), pObject);

            if (iterator != m_checkForGarbage.end())
            {
                m_checkForGarbage.erase(iterator);
            }
        }

        pObject->SetCheckGarbage(false);
    }
}

void CObjManager::MakeDepthCubemapRenderItemList(CVisArea* pReceiverArea, const AABB& cubemapAABB, int renderNodeFlags, PodArray<struct IShadowCaster*>* objectsList, const SRenderingPassInfo& passInfo)
{
    if (pReceiverArea)
    {
        if (pReceiverArea->m_pObjectsTree)
        {
            pReceiverArea->m_pObjectsTree->FillDepthCubemapRenderList(cubemapAABB, passInfo, objectsList);
        }
    }
    else
    {
        if (Get3DEngine()->IsObjectTreeReady())
        {
            Get3DEngine()->GetObjectTree()->FillDepthCubemapRenderList(cubemapAABB, passInfo, objectsList);
        }

        if (passInfo.RenderTerrain())
        {
            PodArray<ITerrainNode*> terrainNodes;
            LegacyTerrain::LegacyTerrainDataRequestBus::Broadcast(&LegacyTerrain::LegacyTerrainDataRequests::IntersectWithBox, cubemapAABB, &terrainNodes);
            // make list of entities
            for (int s = 0; s < terrainNodes.Count(); s++)
            {
                ITerrainNode* pNode = terrainNodes[s];

                objectsList->Add(pNode);
            }
        }
    }
}

//////////////////////////////////////////////////////////////////////////
// StatInstGroupEventBus
//////////////////////////////////////////////////////////////////////////
StatInstGroupId CObjManager::GenerateStatInstGroupId()
{
    // generate new id
    StatInstGroupId id = StatInstGroupEvents::s_InvalidStatInstGroupId;
    for (StatInstGroupId i = 0; i < std::numeric_limits<StatInstGroupId>::max(); ++i)
    {
        if (m_usedIds.find(i) == m_usedIds.end())
        {
            id = i;
            break;
        }
    }

    if (id == StatInstGroupEvents::s_InvalidStatInstGroupId)
    {
        return id;
    }

    // Mark id as used
    m_usedIds.insert(id);
    return id;
}

void CObjManager::ReleaseStatInstGroupId(StatInstGroupId statInstGroupId)
{
    // Free id for this object
    m_usedIds.erase(statInstGroupId);
}

void CObjManager::ReleaseStatInstGroupIdSet(const AZStd::unordered_set<StatInstGroupId>& statInstGroupIdSet)
{
    for (auto groupId : statInstGroupIdSet)
    {
        m_usedIds.erase(groupId);
    }
}

void CObjManager::ReserveStatInstGroupIdRange(StatInstGroupId from, StatInstGroupId to)
{
    for (StatInstGroupId id = from; id < to; ++id)
    {
        m_usedIds.insert(id);
    }
}