/**
 * @file llviewerregion.cpp
 * @brief Implementation of the LLViewerRegion class.
 *
 * $LicenseInfo:firstyear=2000&license=viewerlgpl$
 * Second Life Viewer Source Code
 * Copyright (C) 2010-2013, Linden Research, Inc.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation;
 * version 2.1 of the License only.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
 * $/LicenseInfo$
 */

#include "llviewerprecompiledheaders.h"

#include "llviewerregion.h"

// linden libraries
#include "indra_constants.h"
#include "llaisapi.h"
#include "llavatarnamecache.h"      // name lookup cap url
#include "llfloaterreg.h"
#include "llmath.h"
#include "llregionflags.h"
#include "llregionhandle.h"
#include "llsurface.h"
#include "message.h"
//#include "vmath.h"
#include "v3math.h"
#include "v4math.h"

#include "llagent.h"
#include "llagentcamera.h"
#include "llappviewer.h"
#include "llavatarrenderinfoaccountant.h"
#include "llcallingcard.h"
#include "llcommandhandler.h"
#include "lldir.h"
#include "lleventpoll.h"
#include "llfloatergodtools.h"
#include "llfloaterreporter.h"
#include "llfloaterregioninfo.h"
#include "llgltfmateriallist.h"
#include "llhttpnode.h"
#include "llregioninfomodel.h"
#include "llsdutil.h"
#include "llstartup.h"
#include "lltrans.h"
#include "llurldispatcher.h"
#include "llviewerobjectlist.h"
#include "llviewerparceloverlay.h"
#include "llviewerstatsrecorder.h"
#include "llvlmanager.h"
#include "llvlcomposition.h"
#include "llvoavatarself.h"
#include "llvocache.h"
#include "llworld.h"
#include "llspatialpartition.h"
#include "stringize.h"
#include "llviewercontrol.h"
#include "llsdserialize.h"
#include "llfloaterperms.h"
#include "llvieweroctree.h"
#include "llviewerdisplay.h"
#include "llviewerwindow.h"
#include "llprogressview.h"
#include "llcoros.h"
#include "lleventcoro.h"
#include "llcorehttputil.h"
#include "llcallstack.h"
#include "llsettingsdaycycle.h"

#include <boost/regex.hpp>

#ifdef LL_WINDOWS
    #pragma warning(disable:4355)
#endif

// When we receive a base grant of capabilities that has a different number of
// capabilities than the original base grant received for the region, print
// out the two lists of capabilities for analysis.
//#define DEBUG_CAPS_GRANTS

// The server only keeps our pending agent info for 60 seconds.
// We want to allow for seed cap retry, but its not useful after that 60 seconds.
// Even though we gave up on login, keep trying for caps after we are logged in:
const S32 MAX_CAP_REQUEST_ATTEMPTS = 30;
const U32 DEFAULT_MAX_REGION_WIDE_PRIM_COUNT = 15000;

BOOL LLViewerRegion::sVOCacheCullingEnabled = FALSE;
S32  LLViewerRegion::sLastCameraUpdated = 0;
S32  LLViewerRegion::sNewObjectCreationThrottle = -1;
LLViewerRegion::vocache_entry_map_t LLViewerRegion::sRegionCacheCleanup;

const std::string LLViewerRegion::IL_MODE_DEFAULT = "default";
const std::string LLViewerRegion::IL_MODE_360     = "360";

typedef std::map<std::string, std::string> CapabilityMap;

static void log_capabilities(const CapabilityMap &capmap);

namespace
{

void newRegionEntry(LLViewerRegion& region)
{
    LL_INFOS("LLViewerRegion") << "Entering region [" << region.getName() << "]" << LL_ENDL;
    gDebugInfo["CurrentRegion"] = region.getName();
    LLAppViewer::instance()->writeDebugInfo();
}

} // anonymous namespace

// support for secondlife:///app/region/{REGION} SLapps
// N.B. this is defined to work exactly like the classic secondlife://{REGION}
// However, the later syntax cannot support spaces in the region name because
// spaces (and %20 chars) are illegal in the hostname of an http URL. Some
// browsers let you get away with this, but some do not (such as Qt's Webkit).
// Hence we introduced the newer secondlife:///app/region alternative.
class LLRegionHandler : public LLCommandHandler
{
public:
    // requests will be throttled from a non-trusted browser
    LLRegionHandler() : LLCommandHandler("region", UNTRUSTED_THROTTLE) {}

    bool handle(const LLSD& params, const LLSD& query_map, const std::string& grid, LLMediaCtrl* web)
    {
        // make sure that we at least have a region name
        int num_params = params.size();
        if (num_params < 1)
        {
            return false;
        }

        // build a secondlife://{PLACE} SLurl from this SLapp
        std::string url = "secondlife://";
        if (!grid.empty())
        {
            url += grid + "/secondlife/";
        }
        boost::regex name_rx("[A-Za-z0-9()_%]+");
        boost::regex coord_rx("[0-9]+");
        for (int i = 0; i < num_params; i++)
        {
            if (i > 0)
            {
                url += "/";
            }
            if (!boost::regex_match(params[i].asString(), i > 0 ? coord_rx : name_rx))
            {
                return false;
            }

            url += params[i].asString();
        }

        // Process the SLapp as if it was a secondlife://{PLACE} SLurl
        LLURLDispatcher::dispatch(url, LLCommandHandler::NAV_TYPE_CLICKED, web, true);
        return true;
    }

};
LLRegionHandler gRegionHandler;


class LLViewerRegionImpl
{
public:
    LLViewerRegionImpl(LLViewerRegion * region, LLHost const & host):
        mHost(host),
        mCompositionp(NULL),
        mEventPoll(NULL),
        mSeedCapMaxAttempts(MAX_CAP_REQUEST_ATTEMPTS),
        mSeedCapAttempts(0),
        mHttpResponderID(0),
        mLastCameraUpdate(0),
        mLastCameraOrigin(),
        mVOCachePartition(NULL),
        mLandp(NULL)
    {}

    static void buildCapabilityNames(LLSD& capabilityNames);

    // The surfaces and other layers
    LLSurface*  mLandp;

    // Region geometry data
    LLVector3d  mOriginGlobal;  // Location of southwest corner of region (meters)
    LLVector3d  mCenterGlobal;  // Location of center in world space (meters)
    LLHost      mHost;

    // The unique ID for this region.
    LLUUID mRegionID;

    // region/estate owner - usually null.
    LLUUID mOwnerID;

    // Network statistics for the region's circuit...
    LLTimer mLastNetUpdate;

    // Misc
    LLVLComposition *mCompositionp;     // Composition layer for the surface

    LLVOCacheEntry::vocache_entry_map_t   mCacheMap; //all cached entries
    LLVOCacheEntry::vocache_entry_set_t   mActiveSet; //all active entries;
    LLVOCacheEntry::vocache_entry_set_t   mWaitingSet; //entries waiting for LLDrawable to be generated.
    std::set< LLPointer<LLViewerOctreeGroup> >      mVisibleGroups; //visible groupa
    LLVOCachePartition*                   mVOCachePartition;
    LLVOCacheEntry::vocache_entry_set_t   mVisibleEntries; //must-be-created visible entries wait for objects creation.
    LLVOCacheEntry::vocache_entry_priority_list_t mWaitingList; //transient list storing sorted visible entries waiting for object creation.
    std::set<U32>                          mNonCacheableCreatedList; //list of local ids of all non-cacheable objects
    LLVOCacheEntry::vocache_gltf_overrides_map_t mGLTFOverridesLLSD; // for materials

    // time?
    // LRU info?

    // Cache ID is unique per-region, across renames, moving locations,
    // etc.
    LLUUID mCacheID;

    CapabilityMap mCapabilities;
    CapabilityMap mSecondCapabilitiesTracker;

    LLEventPoll* mEventPoll;

    S32 mSeedCapMaxAttempts;
    S32 mSeedCapAttempts;

    S32 mHttpResponderID;

    //spatial partitions for objects in this region
    std::vector<LLViewerOctreePartition*> mObjectPartition;

    LLVector3   mLastCameraOrigin;
    U32         mLastCameraUpdate;

    static void        requestBaseCapabilitiesCoro(U64 regionHandle);
    static void        requestBaseCapabilitiesCompleteCoro(U64 regionHandle);
    static void        requestSimulatorFeatureCoro(std::string url, U64 regionHandle);
};

void LLViewerRegionImpl::requestBaseCapabilitiesCoro(U64 regionHandle)
{
    LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID);
    LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t
        httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("BaseCapabilitiesRequest", httpPolicy));
    LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest);

    LLSD result;
    LLViewerRegion *regionp = NULL;

    // This loop is used for retrying a capabilities request.
    do
    {
        if (STATE_WORLD_INIT > LLStartUp::getStartupState())
        {
            LL_INFOS("AppInit", "Capabilities") << "Aborting capabilities request, reason: returned to login screen" << LL_ENDL;
            return;
        }

        if (!LLWorld::instanceExists())
        {
            LL_WARNS("AppInit", "Capabilities") << "Attempting to get capabilities, but world no longer exists!" << LL_ENDL;
            return;
        }

        regionp = LLWorld::getInstance()->getRegionFromHandle(regionHandle);
        if (!regionp) //region was removed
        {
            LL_WARNS("AppInit", "Capabilities") << "Attempting to get capabilities for region that no longer exists!" << LL_ENDL;
            return; // this error condition is not recoverable.
        }
        LLViewerRegionImpl* impl = regionp->getRegionImplNC();
        LL_DEBUGS("AppInit", "Capabilities") << "requesting seed caps for handle " << regionHandle
                                             << " name " << regionp->getName() << LL_ENDL;

        std::string url = regionp->getCapability("Seed");
        if (url.empty())
        {
            LL_WARNS("AppInit", "Capabilities") << "Failed to get seed capabilities, and can not determine url!" << LL_ENDL;
            regionp->setCapabilitiesError();
            return; // this error condition is not recoverable.
        }

        // record that we just entered a new region
        newRegionEntry(*regionp);

        if (impl->mSeedCapAttempts > impl->mSeedCapMaxAttempts)
        {
            // *TODO: Give a user pop-up about this error?
            LL_WARNS("AppInit", "Capabilities") << "Failed to get seed capabilities from '" << url << "' after " << impl->mSeedCapAttempts << " attempts.  Giving up!" << LL_ENDL;
            return;  // this error condition is not recoverable.
        }

        S32 id = ++(impl->mHttpResponderID);

        LLSD capabilityNames = LLSD::emptyArray();
        impl->buildCapabilityNames(capabilityNames);

        LL_INFOS("AppInit", "Capabilities") << "Requesting seed from " << url
                                            << " region name " << regionp->getName()
                                            << " region id " << regionp->getRegionID()
                                            << " handle " << regionp->getHandle()
                                            << " (attempt #" << impl->mSeedCapAttempts + 1 << ")" << LL_ENDL;
        LL_DEBUGS("AppInit", "Capabilities") << "Capabilities requested: " << capabilityNames << LL_ENDL;

        regionp = NULL;
        impl = NULL;
        result = httpAdapter->postAndSuspend(httpRequest, url, capabilityNames);

        if (STATE_WORLD_INIT > LLStartUp::getStartupState())
        {
            LL_INFOS("AppInit", "Capabilities") << "Aborting capabilities request, reason: returned to login screen" << LL_ENDL;
            return;
        }

        if (LLApp::isExiting() || gDisconnected)
        {
            LL_DEBUGS("AppInit", "Capabilities") << "Shutting down" << LL_ENDL;
            return;
        }

        if (!LLWorld::instanceExists())
        {
            LL_WARNS("AppInit", "Capabilities") << "Received capabilities, but world no longer exists!" << LL_ENDL;
            return;
        }

        regionp = LLWorld::getInstance()->getRegionFromHandle(regionHandle);
        if (!regionp) //region was removed
        {
            LL_WARNS("AppInit", "Capabilities") << "Received capabilities for region that no longer exists!" << LL_ENDL;
            return; // this error condition is not recoverable.
        }

        impl = regionp->getRegionImplNC();

        ++(impl->mSeedCapAttempts);

        if (!result.isMap() || result.has("error"))
        {
            LL_WARNS("AppInit", "Capabilities") << "Malformed response" << LL_ENDL;
            // setup for retry.
            continue;
        }

        LLSD httpResults = result["http_result"];
        LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults);
        if (!status)
        {
            LL_WARNS("AppInit", "Capabilities") << "HttpStatus error " << LL_ENDL;
            // setup for retry.
            continue;
        }

        // remove the http_result from the llsd
        result.erase("http_result");

        if (id != impl->mHttpResponderID) // region is no longer referring to this request
        {
            LL_WARNS("AppInit", "Capabilities") << "Received results for a stale capabilities request!" << LL_ENDL;
            // setup for retry.
            continue;
        }

        LLSD::map_const_iterator iter;
        for (iter = result.beginMap(); iter != result.endMap(); ++iter)
        {
            regionp->setCapability(iter->first, iter->second);

            LL_DEBUGS("AppInit", "Capabilities")
                << "Capability '" << iter->first << "' is '" << iter->second << "'" << LL_ENDL;
        }

#if 0
        log_capabilities(mCapabilities);
#endif

        LL_DEBUGS("AppInit", "Capabilities", "Teleport") << "received caps for handle " << regionHandle
                                                         << " region name " << regionp->getName() << LL_ENDL;
        regionp->setCapabilitiesReceived(true);

        break;
    }
    while (true);

    if (regionp && regionp->isCapabilityAvailable("ServerReleaseNotes") &&
            regionp->getReleaseNotesRequested())
    {   // *HACK: we're waiting for the ServerReleaseNotes
        regionp->showReleaseNotes();
    }
}


void LLViewerRegionImpl::requestBaseCapabilitiesCompleteCoro(U64 regionHandle)
{
    LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID);
    LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t
        httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("BaseCapabilitiesRequest", httpPolicy));
    LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest);

    LLSD result;
    LLViewerRegion *regionp = NULL;

    // This loop is used for retrying a capabilities request.
    do
    {
        LLWorld *world_inst = LLWorld::getInstance(); // Not a singleton!
        if (!world_inst)
        {
            LL_WARNS("AppInit", "Capabilities") << "Attempting to get capabilities, but world no longer exists!" << LL_ENDL;
            return;
        }

        regionp = world_inst->getRegionFromHandle(regionHandle);
        if (!regionp) //region was removed
        {
            LL_WARNS("AppInit", "Capabilities") << "Attempting to get capabilities for region that no longer exists!" << LL_ENDL;
            break; // this error condition is not recoverable.
        }

        std::string url = regionp->getCapabilityDebug("Seed");
        if (url.empty())
        {
            LL_WARNS("AppInit", "Capabilities") << "Failed to get seed capabilities, and can not determine url!" << LL_ENDL;
            if (regionp->getCapability("Seed").empty())
            {
                // initial attempt failed to get this cap as well
                regionp->setCapabilitiesError();
            }
            break; // this error condition is not recoverable.
        }

        // record that we just entered a new region
        newRegionEntry(*regionp);

        LLSD capabilityNames = LLSD::emptyArray();
        buildCapabilityNames(capabilityNames);

        LL_INFOS("AppInit", "Capabilities") << "Requesting second Seed from " << url << " for region " << regionp->getRegionID() << LL_ENDL;

        regionp = NULL;
        world_inst = NULL;
        result = httpAdapter->postAndSuspend(httpRequest, url, capabilityNames);

        LLSD httpResults = result["http_result"];
        LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults);
        if (!status)
        {
            LL_WARNS("AppInit", "Capabilities") << "HttpStatus error " << LL_ENDL;
            break;  // no retry
        }

        if (LLApp::isExiting() || gDisconnected)
        {
            break;
        }

        world_inst = LLWorld::getInstance();
        if (!world_inst)
        {
            LL_WARNS("AppInit", "Capabilities") << "Received capabilities, but world no longer exists!" << LL_ENDL;
            return;
        }

        regionp = world_inst->getRegionFromHandle(regionHandle);
        if (!regionp) //region was removed
        {
            LL_WARNS("AppInit", "Capabilities") << "Received capabilities for region that no longer exists!" << LL_ENDL;
            break; // this error condition is not recoverable.
        }
        LLViewerRegionImpl* impl = regionp->getRegionImplNC();

        // remove the http_result from the llsd
        result.erase("http_result");

        LLSD::map_const_iterator iter;
        for (iter = result.beginMap(); iter != result.endMap(); ++iter)
        {
            regionp->setCapabilityDebug(iter->first, iter->second);
            //LL_INFOS()<<"BaseCapabilitiesCompleteTracker New Caps "<<iter->first<<" "<< iter->second<<LL_ENDL;
        }

#if 0
        log_capabilities(impl->mCapabilities);
#endif

        if (impl->mCapabilities.size() != impl->mSecondCapabilitiesTracker.size())
        {
            LL_WARNS("AppInit", "Capabilities")
                << "Sim sent duplicate base caps that differ in size from what we initially received - most likely content. "
                << "mCapabilities == " << impl->mCapabilities.size()
                << " mSecondCapabilitiesTracker == " << impl->mSecondCapabilitiesTracker.size()
                << LL_ENDL;
#ifdef DEBUG_CAPS_GRANTS
            LL_WARNS("AppInit", "Capabilities")
                << "Initial Base capabilities: " << LL_ENDL;

            log_capabilities(impl->mCapabilities);

            LL_WARNS("AppInit", "Capabilities")
                << "Latest base capabilities: " << LL_ENDL;

            log_capabilities(impl->mSecondCapabilitiesTracker);

#endif

            if (impl->mSecondCapabilitiesTracker.size() > impl->mCapabilities.size())
            {
                // *HACK Since we were granted more base capabilities in this grant request than the initial, replace
                // the old with the new. This shouldn't happen i.e. we should always get the same capabilities from a
                // sim. The simulator fix from SH-3895 should prevent it from happening, at least in the case of the
                // inventory api capability grants.

                // Need to clear a std::map before copying into it because old keys take precedence.
                impl->mCapabilities.clear();
                impl->mCapabilities = impl->mSecondCapabilitiesTracker;
            }
        }
        else
        {
            LL_DEBUGS("CrossingCaps") << "Sim sent multiple base cap grants with matching sizes." << LL_ENDL;
        }
        impl->mSecondCapabilitiesTracker.clear();
    }
    while (false);
}

void LLViewerRegionImpl::requestSimulatorFeatureCoro(std::string url, U64 regionHandle)
{
    LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID);
    LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t
        httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("BaseCapabilitiesRequest", httpPolicy));
    LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest);

    LLViewerRegion *regionp = NULL;
    S32 attemptNumber = 0;
    // This loop is used for retrying a capabilities request.
    do
    {
        ++attemptNumber;

        if (attemptNumber > MAX_CAP_REQUEST_ATTEMPTS)
        {
            LL_WARNS("AppInit", "SimulatorFeatures") << "Retries count exceeded attempting to get Simulator feature from "
                << url << LL_ENDL;
            break;
        }

        LLWorld *world_inst = LLWorld::getInstance(); // Not a singleton!
        if (!world_inst)
        {
            LL_WARNS("AppInit", "Capabilities") << "Attempting to request Sim Feature, but world no longer exists!" << LL_ENDL;
            return;
        }

        regionp = world_inst->getRegionFromHandle(regionHandle);
        if (!regionp) //region was removed
        {
            LL_WARNS("AppInit", "SimulatorFeatures") << "Attempting to request Sim Feature for region that no longer exists!" << LL_ENDL;
            break; // this error condition is not recoverable.
        }

        regionp = NULL;
        world_inst = NULL;
        LLSD result = httpAdapter->getAndSuspend(httpRequest, url);

        LLSD httpResults = result["http_result"];
        LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults);
        if (!status)
        {
            LL_WARNS("AppInit", "SimulatorFeatures") << "HttpStatus error retrying" << LL_ENDL;
            continue;
        }

        if (LLApp::isExiting() || gDisconnected)
        {
            break;
        }

        // remove the http_result from the llsd
        result.erase("http_result");

        world_inst = LLWorld::getInstance();
        if (!world_inst)
        {
            LL_WARNS("AppInit", "Capabilities") << "Attempting to request Sim Feature, but world no longer exists!" << LL_ENDL;
            return;
        }

        regionp = world_inst->getRegionFromHandle(regionHandle);
        if (!regionp) //region was removed
        {
            LL_WARNS("AppInit", "SimulatorFeatures") << "Attempting to set Sim Feature for region that no longer exists!" << LL_ENDL;
            break; // this error condition is not recoverable.
        }

        regionp->setSimulatorFeatures(result);

        break;
    }
    while (true);

}

LLViewerRegion::LLViewerRegion(const U64 &handle,
                               const LLHost &host,
                               const U32 grids_per_region_edge,
                               const U32 grids_per_patch_edge,
                               const F32 region_width_meters)
:   mImpl(new LLViewerRegionImpl(this, host)),
    mHandle(handle),
    mTimeDilation(1.0f),
    mName(""),
    mZoning(""),
    mIsEstateManager(FALSE),
    mRegionFlags( REGION_FLAGS_DEFAULT ),
    mRegionProtocols( 0 ),
    mSimAccess( SIM_ACCESS_MIN ),
    mBillableFactor(1.0),
    mMaxTasks(DEFAULT_MAX_REGION_WIDE_PRIM_COUNT),
    mCentralBakeVersion(1),
    mClassID(0),
    mCPURatio(0),
    mColoName("unknown"),
    mProductSKU("unknown"),
    mProductName("unknown"),
    mViewerAssetUrl(""),
    mCacheLoaded(FALSE),
    mCacheDirty(FALSE),
    mReleaseNotesRequested(FALSE),
    mCapabilitiesState(CAPABILITIES_STATE_INIT),
    mSimulatorFeaturesReceived(false),
    mBitsReceived(0.f),
    mPacketsReceived(0.f),
    mDead(FALSE),
    mLastVisitedEntry(NULL),
    mInvisibilityCheckHistory(-1),
    mPaused(FALSE),
    mRegionCacheHitCount(0),
    mRegionCacheMissCount(0),
    mInterestListMode(IL_MODE_DEFAULT)
{
    mWidth = region_width_meters;
    mImpl->mOriginGlobal = from_region_handle(handle);
    updateRenderMatrix();

    mImpl->mLandp = new LLSurface('l', NULL);

    // Create the composition layer for the surface
    mImpl->mCompositionp =
        new LLVLComposition(mImpl->mLandp,
                            grids_per_region_edge,
                            region_width_meters / grids_per_region_edge);
    mImpl->mCompositionp->setSurface(mImpl->mLandp);

    // Create the surfaces
    mImpl->mLandp->setRegion(this);
    mImpl->mLandp->create(grids_per_region_edge,
                    grids_per_patch_edge,
                    mImpl->mOriginGlobal,
                    mWidth);

    mParcelOverlay = new LLViewerParcelOverlay(this, region_width_meters);

    setOriginGlobal(from_region_handle(handle));
    calculateCenterGlobal();

    // Create the object lists
    initStats();

    //create object partitions
    //MUST MATCH declaration of eObjectPartitions
    mImpl->mObjectPartition.push_back(new LLHUDPartition(this));        //PARTITION_HUD
    mImpl->mObjectPartition.push_back(new LLTerrainPartition(this));    //PARTITION_TERRAIN
    mImpl->mObjectPartition.push_back(new LLVoidWaterPartition(this));  //PARTITION_VOIDWATER
    mImpl->mObjectPartition.push_back(new LLWaterPartition(this));      //PARTITION_WATER
    mImpl->mObjectPartition.push_back(new LLTreePartition(this));       //PARTITION_TREE
    mImpl->mObjectPartition.push_back(new LLParticlePartition(this));   //PARTITION_PARTICLE
    mImpl->mObjectPartition.push_back(new LLGrassPartition(this));      //PARTITION_GRASS
    mImpl->mObjectPartition.push_back(new LLVolumePartition(this)); //PARTITION_VOLUME
    mImpl->mObjectPartition.push_back(new LLBridgePartition(this)); //PARTITION_BRIDGE
    mImpl->mObjectPartition.push_back(new LLAvatarPartition(this)); //PARTITION_AVATAR
    mImpl->mObjectPartition.push_back(new LLControlAVPartition(this));  //PARTITION_CONTROL_AV
    mImpl->mObjectPartition.push_back(new LLHUDParticlePartition(this));//PARTITION_HUD_PARTICLE
    mImpl->mObjectPartition.push_back(new LLVOCachePartition(this)); //PARTITION_VO_CACHE
    mImpl->mObjectPartition.push_back(NULL);                    //PARTITION_NONE
    mImpl->mVOCachePartition = getVOCachePartition();

    setCapabilitiesReceivedCallback(boost::bind(&LLAvatarRenderInfoAccountant::scanNewRegion, _1));
}


void LLViewerRegion::initStats()
{
    mImpl->mLastNetUpdate.reset();
    mPacketsIn = 0;
    mBitsIn = (U32Bits)0;
    mLastBitsIn = (U32Bits)0;
    mLastPacketsIn = 0;
    mPacketsOut = 0;
    mLastPacketsOut = 0;
    mPacketsLost = 0;
    mLastPacketsLost = 0;
    mPingDelay = (U32Seconds)0;
    mAlive = false;                 // can become false if circuit disconnects
}

static LLTrace::BlockTimerStatHandle FTM_CLEANUP_REGION_OBJECTS("Cleanup Region Objects");
static LLTrace::BlockTimerStatHandle FTM_SAVE_REGION_CACHE("Save Region Cache");

LLViewerRegion::~LLViewerRegion()
{
    LL_PROFILE_ZONE_SCOPED;
    mDead = TRUE;
    mImpl->mActiveSet.clear();
    mImpl->mVisibleEntries.clear();
    mImpl->mVisibleGroups.clear();
    mImpl->mWaitingSet.clear();

    gVLManager.cleanupData(this);
    // Can't do this on destruction, because the neighbor pointers might be invalid.
    // This should be reference counted...
    disconnectAllNeighbors();
    LLViewerPartSim::getInstance()->cleanupRegion(this);

    {
        LL_RECORD_BLOCK_TIME(FTM_CLEANUP_REGION_OBJECTS);
        gObjectList.killObjects(this);
    }

    delete mImpl->mCompositionp;
    delete mParcelOverlay;
    delete mImpl->mLandp;
    delete mImpl->mEventPoll;
#if 0
    LLHTTPSender::clearSender(mImpl->mHost);
#endif
    std::for_each(mImpl->mObjectPartition.begin(), mImpl->mObjectPartition.end(), DeletePointer());

    {
        LL_RECORD_BLOCK_TIME(FTM_SAVE_REGION_CACHE);
        saveObjectCache();
    }

    delete mImpl;
    mImpl = NULL;
}

/*virtual*/
const LLHost&   LLViewerRegion::getHost() const
{
    return mImpl->mHost;
}

LLSurface & LLViewerRegion::getLand() const
{
    return *mImpl->mLandp;
}

const LLUUID& LLViewerRegion::getRegionID() const
{
    return mImpl->mRegionID;
}

void LLViewerRegion::setRegionID(const LLUUID& region_id)
{
    mImpl->mRegionID = region_id;
}

void LLViewerRegion::loadObjectCache()
{
    if (mCacheLoaded)
    {
        return;
    }

    // Presume success.  If it fails, we don't want to try again.
    mCacheLoaded = TRUE;

    if(LLVOCache::instanceExists())
    {
        LLVOCache & vocache = LLVOCache::instance();
        vocache.readFromCache(mHandle, mImpl->mCacheID, mImpl->mCacheMap);
        vocache.readGenericExtrasFromCache(mHandle, mImpl->mCacheID, mImpl->mGLTFOverridesLLSD);

        if (mImpl->mCacheMap.empty())
        {
            mCacheDirty = TRUE;
        }
    }
}


void LLViewerRegion::saveObjectCache()
{
    if (!mCacheLoaded)
    {
        return;
    }

    if (mImpl->mCacheMap.empty())
    {
        return;
    }

    if(LLVOCache::instanceExists())
    {
        const F32 start_time_threshold = 600.0f; //seconds
        bool removal_enabled = sVOCacheCullingEnabled && (mRegionTimer.getElapsedTimeF32() > start_time_threshold); //allow to remove invalid objects from object cache file.

        LLVOCache & instance = LLVOCache::instance();

        instance.writeToCache(mHandle, mImpl->mCacheID, mImpl->mCacheMap, mCacheDirty, removal_enabled);
        instance.writeGenericExtrasToCache(mHandle, mImpl->mCacheID, mImpl->mGLTFOverridesLLSD, mCacheDirty, removal_enabled);
        mCacheDirty = FALSE;
    }

    if (LLAppViewer::instance()->isQuitting())
    {
        mImpl->mCacheMap.clear();
    }
    else
    {
        // Map of LLVOCacheEntry takes time to release, store map for cleanup on idle
        sRegionCacheCleanup.insert(mImpl->mCacheMap.begin(), mImpl->mCacheMap.end());
        mImpl->mCacheMap.clear();
        // TODO - probably need to do the same for overrides cache
    }
}

void LLViewerRegion::sendMessage()
{
    gMessageSystem->sendMessage(mImpl->mHost);
}

void LLViewerRegion::sendReliableMessage()
{
    gMessageSystem->sendReliable(mImpl->mHost);
}

void LLViewerRegion::setWaterHeight(F32 water_level)
{
    mImpl->mLandp->setWaterHeight(water_level);
}

F32 LLViewerRegion::getWaterHeight() const
{
    return mImpl->mLandp->getWaterHeight();
}

BOOL LLViewerRegion::isVoiceEnabled() const
{
    return getRegionFlag(REGION_FLAGS_ALLOW_VOICE);
}

void LLViewerRegion::setRegionFlags(U64 flags)
{
    mRegionFlags = flags;
}


void LLViewerRegion::setOriginGlobal(const LLVector3d &origin_global)
{
    mImpl->mOriginGlobal = origin_global;
    updateRenderMatrix();
    mImpl->mLandp->setOriginGlobal(origin_global);
    mWind.setOriginGlobal(origin_global);
    calculateCenterGlobal();
}

void LLViewerRegion::updateRenderMatrix()
{
    mRenderMatrix.setTranslation(getOriginAgent());
}

void LLViewerRegion::setTimeDilation(F32 time_dilation)
{
    mTimeDilation = time_dilation;
}

const LLVector3d & LLViewerRegion::getOriginGlobal() const
{
    return mImpl->mOriginGlobal;
}

LLVector3 LLViewerRegion::getOriginAgent() const
{
    return gAgent.getPosAgentFromGlobal(mImpl->mOriginGlobal);
}

const LLVector3d & LLViewerRegion::getCenterGlobal() const
{
    return mImpl->mCenterGlobal;
}

LLVector3 LLViewerRegion::getCenterAgent() const
{
    return gAgent.getPosAgentFromGlobal(mImpl->mCenterGlobal);
}

void LLViewerRegion::setOwner(const LLUUID& owner_id)
{
    mImpl->mOwnerID = owner_id;
}

const LLUUID& LLViewerRegion::getOwner() const
{
    return mImpl->mOwnerID;
}

void LLViewerRegion::setRegionNameAndZone   (const std::string& name_zone)
{
    std::string::size_type pipe_pos = name_zone.find('|');
    S32 length   = name_zone.size();
    if (pipe_pos != std::string::npos)
    {
        mName   = name_zone.substr(0, pipe_pos);
        mZoning = name_zone.substr(pipe_pos+1, length-(pipe_pos+1));
    }
    else
    {
        mName   = name_zone;
        mZoning = "";
    }

    LLStringUtil::stripNonprintable(mName);
    LLStringUtil::stripNonprintable(mZoning);
}

BOOL LLViewerRegion::canManageEstate() const
{
    return gAgent.isGodlike()
        || isEstateManager()
        || gAgent.getID() == getOwner();
}

const std::string LLViewerRegion::getSimAccessString() const
{
    return accessToString(mSimAccess);
}

std::string LLViewerRegion::getLocalizedSimProductName() const
{
    std::string localized_spn;
    return LLTrans::findString(localized_spn, mProductName) ? localized_spn : mProductName;
}

// static
std::string LLViewerRegion::regionFlagsToString(U64 flags)
{
    std::string result;

    if (flags & REGION_FLAGS_SANDBOX)
    {
        result += "Sandbox";
    }

    if (flags & REGION_FLAGS_ALLOW_DAMAGE)
    {
        result += " Not Safe";
    }

    return result;
}

// static
std::string LLViewerRegion::accessToString(U8 sim_access)
{
    switch(sim_access)
    {
    case SIM_ACCESS_PG:
        return LLTrans::getString("SIM_ACCESS_PG");

    case SIM_ACCESS_MATURE:
        return LLTrans::getString("SIM_ACCESS_MATURE");

    case SIM_ACCESS_ADULT:
        return LLTrans::getString("SIM_ACCESS_ADULT");

    case SIM_ACCESS_DOWN:
        return LLTrans::getString("SIM_ACCESS_DOWN");

    case SIM_ACCESS_MIN:
    default:
        return LLTrans::getString("SIM_ACCESS_MIN");
    }
}

// static
std::string LLViewerRegion::getAccessIcon(U8 sim_access)
{
    switch(sim_access)
    {
    case SIM_ACCESS_MATURE:
        return "Parcel_M_Dark";

    case SIM_ACCESS_ADULT:
        return "Parcel_R_Light";

    case SIM_ACCESS_PG:
        return "Parcel_PG_Light";

    case SIM_ACCESS_MIN:
    default:
        return "";
    }
}

// static
std::string LLViewerRegion::accessToShortString(U8 sim_access)
{
    switch(sim_access)      /* Flawfinder: ignore */
    {
    case SIM_ACCESS_PG:
        return "PG";

    case SIM_ACCESS_MATURE:
        return "M";

    case SIM_ACCESS_ADULT:
        return "A";

    case SIM_ACCESS_MIN:
    default:
        return "U";
    }
}

// static
U8 LLViewerRegion::shortStringToAccess(const std::string &sim_access)
{
    U8 accessValue;

    if (LLStringUtil::compareStrings(sim_access, "PG") == 0)
    {
        accessValue = SIM_ACCESS_PG;
    }
    else if (LLStringUtil::compareStrings(sim_access, "M") == 0)
    {
        accessValue = SIM_ACCESS_MATURE;
    }
    else if (LLStringUtil::compareStrings(sim_access, "A") == 0)
    {
        accessValue = SIM_ACCESS_ADULT;
    }
    else
    {
        accessValue = SIM_ACCESS_MIN;
    }

    return accessValue;
}

// static
void LLViewerRegion::processRegionInfo(LLMessageSystem* msg, void**)
{
    // send it to 'observers'
    // *TODO: switch the floaters to using LLRegionInfoModel
    LL_INFOS() << "Processing region info" << LL_ENDL;
    LLRegionInfoModel::instance().update(msg);
    LLFloaterGodTools::processRegionInfo(msg);
    LLFloaterRegionInfo::processRegionInfo(msg);
}

void LLViewerRegion::setCacheID(const LLUUID& id)
{
    mImpl->mCacheID = id;
}

void LLViewerRegion::renderPropertyLines()
{
    if (mParcelOverlay)
    {
        mParcelOverlay->renderPropertyLines();
    }
}

void LLViewerRegion::renderPropertyLinesOnMinimap(F32 scale_pixels_per_meter, const F32 *parcel_outline_color)
{
    if (mParcelOverlay)
    {
        mParcelOverlay->renderPropertyLinesOnMinimap(scale_pixels_per_meter, parcel_outline_color);
    }
}


// This gets called when the height field changes.
void LLViewerRegion::dirtyHeights()
{
    // Property lines need to be reconstructed when the land changes.
    if (mParcelOverlay)
    {
        mParcelOverlay->setDirty();
    }
}

//physically delete the cache entry
void LLViewerRegion::killCacheEntry(LLVOCacheEntry* entry, bool for_rendering)
{
    if(!entry || !entry->isValid())
    {
        return;
    }

    if(for_rendering && !entry->isState(LLVOCacheEntry::ACTIVE))
    {
        addNewObject(entry); //force to add to rendering pipeline
    }

    //remove from active list and waiting list
    if(entry->isState(LLVOCacheEntry::ACTIVE))
    {
        mImpl->mActiveSet.erase(entry);
    }
    else
    {
        if(entry->isState(LLVOCacheEntry::WAITING))
        {
            mImpl->mWaitingSet.erase(entry);
        }

        //remove from mVOCachePartition
        removeFromVOCacheTree(entry);
    }

    //remove from the forced visible list
    mImpl->mVisibleEntries.erase(entry);

    //disconnect from parent if it is a child
    if(entry->getParentID() > 0)
    {
        LLVOCacheEntry* parent = getCacheEntry(entry->getParentID());
        if(parent)
        {
            parent->removeChild(entry);
        }
    }
    else if(entry->getNumOfChildren() > 0)//remove children from cache if has any
    {
        LLVOCacheEntry* child = entry->getChild();
        while(child != NULL)
        {
            killCacheEntry(child, for_rendering);
            child = entry->getChild();
        }
    }

    //will remove it from the object cache, real deletion
    entry->setState(LLVOCacheEntry::INACTIVE);
    entry->removeOctreeEntry();
    entry->setValid(FALSE);

    // TODO kill extras/material overrides cache too
}

//physically delete the cache entry
void LLViewerRegion::killCacheEntry(U32 local_id)
{
    killCacheEntry(getCacheEntry(local_id));
}

U32 LLViewerRegion::getNumOfActiveCachedObjects() const
{
    return  mImpl->mActiveSet.size();
}

void LLViewerRegion::addActiveCacheEntry(LLVOCacheEntry* entry)
{
    if(!entry || mDead)
    {
        return;
    }
    if(entry->isState(LLVOCacheEntry::ACTIVE))
    {
        return; //already inserted.
    }

    if(entry->isState(LLVOCacheEntry::WAITING))
    {
        mImpl->mWaitingSet.erase(entry);
    }

    entry->setState(LLVOCacheEntry::ACTIVE);
    entry->setVisible();

    llassert(entry->getEntry()->hasDrawable());
    mImpl->mActiveSet.insert(entry);
}

void LLViewerRegion::removeActiveCacheEntry(LLVOCacheEntry* entry, LLDrawable* drawablep)
{
    if(mDead || !entry || !entry->isValid())
    {
        return;
    }
    if(!entry->isState(LLVOCacheEntry::ACTIVE))
    {
        return; //not an active entry.
    }

    //shift to the local regional space from agent space
    if(drawablep != NULL && drawablep->getVObj().notNull())
    {
        const LLVector3& pos = drawablep->getVObj()->getPositionRegion();
        LLVector4a shift;
        shift.load3(pos.mV);
        shift.sub(entry->getPositionGroup());
        entry->shift(shift);
    }

    if(entry->getParentID() > 0) //is a child
    {
        LLVOCacheEntry* parent = getCacheEntry(entry->getParentID());
        if(parent)
        {
            parent->addChild(entry);
        }
        else //parent not in cache.
        {
            //this happens only when parent is not cacheable.
            mOrphanMap[entry->getParentID()].push_back(entry->getLocalID());
        }
    }
    else //insert to vo cache tree.
    {
        entry->updateParentBoundingInfo();
        entry->saveBoundingSphere();
        addToVOCacheTree(entry);
    }

    mImpl->mVisibleEntries.erase(entry);
    mImpl->mActiveSet.erase(entry);
    mImpl->mWaitingSet.erase(entry);
    entry->setState(LLVOCacheEntry::INACTIVE);
}

bool LLViewerRegion::addVisibleGroup(LLViewerOctreeGroup* group)
{
    if(mDead || group->isEmpty())
    {
        return false;
    }

    mImpl->mVisibleGroups.insert(group);

    return true;
}

U32 LLViewerRegion::getNumOfVisibleGroups() const
{
    return mImpl ? mImpl->mVisibleGroups.size() : 0;
}

void LLViewerRegion::updateReflectionProbes()
{
    LL_PROFILE_ZONE_SCOPED_CATEGORY_DISPLAY;
    const F32 probe_spacing = 32.f;
    const F32 probe_radius = sqrtf((probe_spacing * 0.5f) * (probe_spacing * 0.5f) * 3.f);
    const F32 hover_height = 2.f;

    F32 start = probe_spacing * 0.5f;

    U32 grid_width = REGION_WIDTH_METERS / probe_spacing;

    mReflectionMaps.resize(grid_width * grid_width);

    F32 water_height = getWaterHeight();
    LLVector3 origin = getOriginAgent();

    for (U32 i = 0; i < grid_width; ++i)
    {
        F32 x = i * probe_spacing + start;
        for (U32 j = 0; j < grid_width; ++j)
        {
            F32 y = j * probe_spacing + start;

            U32 idx = i * grid_width + j;

            if (mReflectionMaps[idx].isNull())
            {
                mReflectionMaps[idx] = gPipeline.mReflectionMapManager.addProbe();
            }

            LLVector3 probe_origin = LLVector3(x, y, llmax(water_height, mImpl->mLandp->resolveHeightRegion(x, y)));
            probe_origin.mV[2] += hover_height;
            probe_origin += origin;

            mReflectionMaps[idx]->mOrigin.load3(probe_origin.mV);
            mReflectionMaps[idx]->mRadius = probe_radius;
        }
    }
}

void LLViewerRegion::addToVOCacheTree(LLVOCacheEntry* entry)
{
    if(!sVOCacheCullingEnabled)
    {
        return;
    }

    if(mDead || !entry || !entry->getEntry() || !entry->isValid())
    {
        return;
    }
    if(entry->getParentID() > 0)
    {
        return; //no child prim in cache octree.
    }

    if(entry->hasState(LLVOCacheEntry::IN_VO_TREE))
    {
        return; //already in the tree.
    }

    llassert_always(!entry->getGroup()); //not in octree.
    llassert(!entry->getEntry()->hasDrawable()); //not have drawables

    if(mImpl->mVOCachePartition->addEntry(entry->getEntry()))
    {
        entry->setState(LLVOCacheEntry::IN_VO_TREE);
    }
}

void LLViewerRegion::removeFromVOCacheTree(LLVOCacheEntry* entry)
{
    if(mDead || !entry || !entry->getEntry())
    {
        return;
    }

    if(!entry->hasState(LLVOCacheEntry::IN_VO_TREE))
    {
        return; //not in the tree.
    }
    entry->clearState(LLVOCacheEntry::IN_VO_TREE);

    mImpl->mVOCachePartition->removeEntry(entry->getEntry());
}

//add child objects as visible entries
void LLViewerRegion::addVisibleChildCacheEntry(LLVOCacheEntry* parent, LLVOCacheEntry* child)
{
    if(mDead)
    {
        return;
    }

    if(parent && (!parent->isValid() || !parent->isState(LLVOCacheEntry::ACTIVE)))
    {
        return; //parent must be valid and in rendering pipeline
    }

    if(child && (!child->getEntry() || !child->isValid() || !child->isState(LLVOCacheEntry::INACTIVE)))
    {
        return; //child must be valid and not in the rendering pipeline
    }

    if(child)
    {
        child->setState(LLVOCacheEntry::IN_QUEUE);
        mImpl->mVisibleEntries.insert(child);
    }
    else if(parent && parent->getNumOfChildren() > 0) //add all children
    {
        child = parent->getChild();
        while(child != NULL)
        {
            addVisibleChildCacheEntry(NULL, child);
            child = parent->getChild();
        }
    }
}

void LLViewerRegion::updateVisibleEntries(F32 max_time)
{
    if(mDead)
    {
        return;
    }

    if(mImpl->mVisibleGroups.empty() && mImpl->mVisibleEntries.empty())
    {
        return;
    }

    if(!sNewObjectCreationThrottle)
    {
        return;
    }

    const F32 LARGE_SCENE_CONTRIBUTION = 1000.f; //a large number to force to load the object.
    const LLVector3 camera_origin = LLViewerCamera::getInstance()->getOrigin();
    const U32 cur_frame = LLViewerOctreeEntryData::getCurrentFrame();
    bool needs_update = ((cur_frame - mImpl->mLastCameraUpdate) > 5) && ((camera_origin - mImpl->mLastCameraOrigin).lengthSquared() > 10.f);
    U32 last_update = mImpl->mLastCameraUpdate;
    LLVector4a local_origin;
    local_origin.load3((camera_origin - getOriginAgent()).mV);

    //process visible entries
    for(LLVOCacheEntry::vocache_entry_set_t::iterator iter = mImpl->mVisibleEntries.begin(); iter != mImpl->mVisibleEntries.end();)
    {
        LLVOCacheEntry* vo_entry = *iter;

        if(vo_entry->isValid() && vo_entry->getState() < LLVOCacheEntry::WAITING)
        {
            //set a large number to force to load this object.
            vo_entry->setSceneContribution(LARGE_SCENE_CONTRIBUTION);

            mImpl->mWaitingList.insert(vo_entry);
            ++iter;
        }
        else
        {
            LLVOCacheEntry::vocache_entry_set_t::iterator next_iter = iter;
            ++next_iter;
            mImpl->mVisibleEntries.erase(iter);
            iter = next_iter;
        }
    }

    //
    //process visible groups
    //
    //object projected area threshold
    F32 projection_threshold = LLVOCacheEntry::getSquaredPixelThreshold(mImpl->mVOCachePartition->isFrontCull());
    F32 dist_threshold = mImpl->mVOCachePartition->isFrontCull() ? gAgentCamera.mDrawDistance : LLVOCacheEntry::sRearFarRadius;

    std::set< LLPointer<LLViewerOctreeGroup> >::iterator group_iter = mImpl->mVisibleGroups.begin();
    for(; group_iter != mImpl->mVisibleGroups.end(); ++group_iter)
    {
        LLPointer<LLViewerOctreeGroup> group = *group_iter;
        if(group->getNumRefs() < 3 || //group to be deleted
            !group->getOctreeNode() || group->isEmpty()) //group empty
        {
            continue;
        }

        for (LLViewerOctreeGroup::element_iter i = group->getDataBegin(); i != group->getDataEnd(); ++i)
        {
            if((*i)->hasVOCacheEntry())
            {
                LLVOCacheEntry* vo_entry = (LLVOCacheEntry*)(*i)->getVOCacheEntry();

                if(vo_entry->getParentID() > 0) //is a child
                {
                    //child visibility depends on its parent.
                    continue;
                }
                if(!vo_entry->isValid())
                {
                    continue; //skip invalid entry.
                }

                vo_entry->calcSceneContribution(local_origin, needs_update, last_update, dist_threshold);
                if(vo_entry->getSceneContribution() > projection_threshold)
                {
                    mImpl->mWaitingList.insert(vo_entry);
                }
            }
        }
    }

    if(needs_update)
    {
        mImpl->mLastCameraOrigin = camera_origin;
        mImpl->mLastCameraUpdate = cur_frame;
    }

    return;
}

void LLViewerRegion::createVisibleObjects(F32 max_time)
{
    if(mDead)
    {
        return;
    }
    if(mImpl->mWaitingList.empty())
    {
        mImpl->mVOCachePartition->setCullHistory(FALSE);
        return;
    }

    S32 throttle = sNewObjectCreationThrottle;
    BOOL has_new_obj = FALSE;
    LLTimer update_timer;
    for(LLVOCacheEntry::vocache_entry_priority_list_t::iterator iter = mImpl->mWaitingList.begin();
        iter != mImpl->mWaitingList.end(); ++iter)
    {
        LLVOCacheEntry* vo_entry = *iter;

        if(vo_entry->getState() < LLVOCacheEntry::WAITING)
        {
            addNewObject(vo_entry);
            has_new_obj = TRUE;
            if(throttle > 0 && !(--throttle) && update_timer.getElapsedTimeF32() > max_time)
            {
                break;
            }
        }
    }

    mImpl->mVOCachePartition->setCullHistory(has_new_obj);

    return;
}

void LLViewerRegion::clearCachedVisibleObjects()
{
    mImpl->mWaitingList.clear();
    mImpl->mVisibleGroups.clear();

    //reset all occluders
    mImpl->mVOCachePartition->resetOccluders();
    mPaused = TRUE;

    //clean visible entries
    for(LLVOCacheEntry::vocache_entry_set_t::iterator iter = mImpl->mVisibleEntries.begin(); iter != mImpl->mVisibleEntries.end();)
    {
        LLVOCacheEntry* entry = *iter;
        LLVOCacheEntry* parent = getCacheEntry(entry->getParentID());

        if(!entry->getParentID() || parent) //no child or parent is cache-able
        {
            if(parent) //has a cache-able parent
            {
                parent->addChild(entry);
            }

            LLVOCacheEntry::vocache_entry_set_t::iterator next_iter = iter;
            ++next_iter;
            mImpl->mVisibleEntries.erase(iter);
            iter = next_iter;
        }
        else //parent is not cache-able, leave it.
        {
            ++iter;
        }
    }

    //remove all visible entries.
    mLastVisitedEntry = NULL;
    std::vector<LLDrawable*> delete_list;
    for(LLVOCacheEntry::vocache_entry_set_t::iterator iter = mImpl->mActiveSet.begin();
        iter != mImpl->mActiveSet.end(); ++iter)
    {
        LLVOCacheEntry* vo_entry = *iter;
        if (!vo_entry || !vo_entry->getEntry())
        {
            continue;
        }
        LLDrawable* drawablep = (LLDrawable*)vo_entry->getEntry()->getDrawable();

        if(drawablep && !drawablep->getParent())
        {
            delete_list.push_back(drawablep);
        }
    }

    if(!delete_list.empty())
    {
        for(S32 i = 0; i < delete_list.size(); i++)
        {
            gObjectList.killObject(delete_list[i]->getVObj());
        }
        delete_list.clear();
    }

    return;
}

//perform some necessary but very light updates.
//to replace the function idleUpdate(...) in case there is no enough time.
void LLViewerRegion::lightIdleUpdate()
{
    if(!sVOCacheCullingEnabled)
    {
        return;
    }
    if(mImpl->mCacheMap.empty())
    {
        return;
    }

    //reset all occluders
    mImpl->mVOCachePartition->resetOccluders();
}

void LLViewerRegion::idleUpdate(F32 max_update_time)
{
    LL_PROFILE_ZONE_SCOPED;
    LLTimer update_timer;
    F32 max_time;

    mLastUpdate = LLViewerOctreeEntryData::getCurrentFrame();

    mImpl->mLandp->idleUpdate(max_update_time);

    if (mParcelOverlay)
    {
        // Hopefully not a significant time sink...
        mParcelOverlay->idleUpdate();
    }

    if(!sVOCacheCullingEnabled)
    {
        return;
    }
    if(mImpl->mCacheMap.empty())
    {
        return;
    }
    if(mPaused)
    {
        mPaused = FALSE; //unpause.
    }

    LLViewerCamera::eCameraID old_camera_id = LLViewerCamera::sCurCameraID;
    LLViewerCamera::sCurCameraID = LLViewerCamera::CAMERA_WORLD;

    //reset all occluders
    mImpl->mVOCachePartition->resetOccluders();

    max_time = max_update_time - update_timer.getElapsedTimeF32();

    //kill invisible objects
    killInvisibleObjects(max_time * 0.4f);
    max_time = max_update_time - update_timer.getElapsedTimeF32();

    updateVisibleEntries(max_time);
    max_time = max_update_time - update_timer.getElapsedTimeF32();

    createVisibleObjects(max_time);

    mImpl->mWaitingList.clear();
    mImpl->mVisibleGroups.clear();

    LLViewerCamera::sCurCameraID = old_camera_id;
    return;
}

// static
void LLViewerRegion::idleCleanup(F32 max_update_time)
{
    LLTimer update_timer;
    while (!sRegionCacheCleanup.empty() && (max_update_time - update_timer.getElapsedTimeF32() > 0))
    {
        sRegionCacheCleanup.erase(sRegionCacheCleanup.begin());
    }
}

//update the throttling number for new object creation
void LLViewerRegion::calcNewObjectCreationThrottle()
{
    static LLCachedControl<S32> new_object_creation_throttle(gSavedSettings,"NewObjectCreationThrottle");
    static LLCachedControl<F32> throttle_delay_time(gSavedSettings,"NewObjectCreationThrottleDelayTime");
    static LLFrameTimer timer;

    //
    //sNewObjectCreationThrottle =
    //-2: throttle is disabled because either the screen is showing progress view, or immediate after the screen is not black
    //-1: throttle is disabled by the debug setting
    //0:  no new object creation is allowed
    //>0: valid throttling number
    //

    if(gViewerWindow->getProgressView()->getVisible() && throttle_delay_time > 0.f)
    {
        sNewObjectCreationThrottle = -2; //cancel the throttling
        timer.reset();
    }
    else if(sNewObjectCreationThrottle < -1) //just recoved from the login/teleport screen
    {
        if(timer.getElapsedTimeF32() > throttle_delay_time) //wait for throttle_delay_time to reset the throttle
        {
            sNewObjectCreationThrottle = new_object_creation_throttle; //reset
            if(sNewObjectCreationThrottle < -1)
            {
                sNewObjectCreationThrottle = -1;
            }
        }
    }

    //update some LLVOCacheEntry debug setting factors.
    LLVOCacheEntry::updateDebugSettings();
}

BOOL LLViewerRegion::isViewerCameraStatic()
{
    return sLastCameraUpdated < LLViewerOctreeEntryData::getCurrentFrame();
}

void LLViewerRegion::killInvisibleObjects(F32 max_time)
{
#if 1 // TODO: kill this.  This is ill-conceived, objects that aren't in the camera frustum should not be deleted from memory.
        // because of this, every time you turn around the simulator sends a swarm of full object update messages from cache
    // probe misses and objects have to be reloaded from scratch.  From some reason, disabling this causes holes to
    // appear in the scene when flying back and forth between regions
    if(!sVOCacheCullingEnabled)
    {
        return;
    }
    if(mImpl->mActiveSet.empty())
    {
        return;
    }
    if(sNewObjectCreationThrottle < 0)
    {
        return;
    }

    LLTimer update_timer;
    LLVector4a camera_origin;
    camera_origin.load3(LLViewerCamera::getInstance()->getOrigin().mV);
    LLVector4a local_origin;
    local_origin.load3((LLViewerCamera::getInstance()->getOrigin() - getOriginAgent()).mV);
    F32 back_threshold = LLVOCacheEntry::sRearFarRadius;

    size_t max_update = 64;
    if(!mInvisibilityCheckHistory && isViewerCameraStatic())
    {
        //history is clean, reduce number of checking
        max_update /= 2;
    }

    std::vector<LLDrawable*> delete_list;
    S32 update_counter = llmin(max_update, mImpl->mActiveSet.size());
    LLVOCacheEntry::vocache_entry_set_t::iterator iter = mImpl->mActiveSet.upper_bound(mLastVisitedEntry);

    for(; update_counter > 0; --update_counter, ++iter)
    {
        if(iter == mImpl->mActiveSet.end())
        {
            iter = mImpl->mActiveSet.begin();
        }
        if((*iter)->getParentID() > 0)
        {
            continue; //skip child objects, they are removed with their parent.
        }

        LLVOCacheEntry* vo_entry = *iter;
        if(!vo_entry->isAnyVisible(camera_origin, local_origin, back_threshold) && vo_entry->mLastCameraUpdated < sLastCameraUpdated)
        {
            killObject(vo_entry, delete_list);
        }

        if(max_time < update_timer.getElapsedTimeF32()) //time out
        {
            break;
        }
    }

    if(iter == mImpl->mActiveSet.end())
    {
        mLastVisitedEntry = NULL;
    }
    else
    {
        mLastVisitedEntry = *iter;
    }

    mInvisibilityCheckHistory <<= 1;
    if(!delete_list.empty())
    {
        mInvisibilityCheckHistory |= 1;
        S32 count = delete_list.size();
        for(S32 i = 0; i < count; i++)
        {
            gObjectList.killObject(delete_list[i]->getVObj());
        }
        delete_list.clear();
    }

    return;
#endif
}

void LLViewerRegion::killObject(LLVOCacheEntry* entry, std::vector<LLDrawable*>& delete_list)
{
    //kill the object.
    LLDrawable* drawablep = (LLDrawable*)entry->getEntry()->getDrawable();
    llassert(drawablep);
    llassert(drawablep->getRegion() == this);

    if(drawablep && !drawablep->getParent())
    {
        LLViewerObject* v_obj = drawablep->getVObj();
        if (v_obj->isSelected()
            || (v_obj->flagAnimSource() && isAgentAvatarValid() && gAgentAvatarp->hasMotionFromSource(v_obj->getID())))
        {
            // do not remove objects user is interacting with
            ((LLViewerOctreeEntryData*)drawablep)->setVisible();
            return;
        }
        LLViewerObject::const_child_list_t& child_list = v_obj->getChildren();
        for (LLViewerObject::child_list_t::const_iterator iter = child_list.begin();
            iter != child_list.end(); iter++)
        {
            LLViewerObject* child = *iter;
            if(child->mDrawable)
            {
                if( !child->mDrawable->getEntry()
                    || !child->mDrawable->getEntry()->hasVOCacheEntry()
                    || child->isSelected()
                    || (child->flagAnimSource() && isAgentAvatarValid() && gAgentAvatarp->hasMotionFromSource(child->getID())))
                {
                    //do not remove parent if any of its children non-cacheable, animating or selected
                    //especially for the case that an avatar sits on a cache-able object
                    ((LLViewerOctreeEntryData*)drawablep)->setVisible();
                    return;
                }

                LLOcclusionCullingGroup* group = (LLOcclusionCullingGroup*)child->mDrawable->getGroup();
                if(group && group->isAnyRecentlyVisible())
                {
                    //set the parent visible if any of its children visible.
                    ((LLViewerOctreeEntryData*)drawablep)->setVisible();
                    return;
                }
            }
        }
        delete_list.push_back(drawablep);
    }
}

LLViewerObject* LLViewerRegion::addNewObject(LLVOCacheEntry* entry)
{
    if(!entry || !entry->getEntry())
    {
        if(entry)
        {
            mImpl->mVisibleEntries.erase(entry);
            entry->setState(LLVOCacheEntry::INACTIVE);
        }
        return NULL;
    }

    LLViewerObject* obj = NULL;
    if(!entry->getEntry()->hasDrawable()) //not added to the rendering pipeline yet
    {
        //add the object
        obj = gObjectList.processObjectUpdateFromCache(entry, this);
        if(obj)
        {
            if(!entry->isState(LLVOCacheEntry::ACTIVE))
            {
                mImpl->mWaitingSet.insert(entry);
                entry->setState(LLVOCacheEntry::WAITING);
            }
        }
    }
    else
    {
        LLViewerRegion* old_regionp = ((LLDrawable*)entry->getEntry()->getDrawable())->getRegion();
        if(old_regionp != this)
        {
            //this object exists in two regions at the same time;
            //this case can be safely ignored here because
            //server should soon send update message to remove one region for this object.

            LL_WARNS() << "Entry: " << entry->getLocalID() << " exists in two regions at the same time." << LL_ENDL;
            return NULL;
        }

        LL_WARNS() << "Entry: " << entry->getLocalID() << " in rendering pipeline but not set to be active." << LL_ENDL;

        //should not hit here any more, but does not hurt either, just put it back to active list
        addActiveCacheEntry(entry);
    }

    return obj;
}

//update object cache if the object receives a full-update or terse update
//update_type == EObjectUpdateType::OUT_TERSE_IMPROVED or EObjectUpdateType::OUT_FULL
LLViewerObject* LLViewerRegion::updateCacheEntry(U32 local_id, LLViewerObject* objectp)
{
    LLVOCacheEntry* entry = getCacheEntry(local_id);
    if (!entry)
    {
        return objectp; //not in the cache, do nothing.
    }
    if(!objectp) //object not created
    {
        //create a new object from cache.
        objectp = addNewObject(entry);
    }

    //remove from cache.
    killCacheEntry(entry, true);

    return objectp;
}

// As above, but forcibly do the update.
void LLViewerRegion::forceUpdate()
{
    mImpl->mLandp->idleUpdate(0.f);

    if (mParcelOverlay)
    {
        mParcelOverlay->idleUpdate(true);
    }
}

void LLViewerRegion::connectNeighbor(LLViewerRegion *neighborp, U32 direction)
{
    mImpl->mLandp->connectNeighbor(neighborp->mImpl->mLandp, direction);
}


void LLViewerRegion::disconnectAllNeighbors()
{
    mImpl->mLandp->disconnectAllNeighbors();
}

LLVLComposition * LLViewerRegion::getComposition() const
{
    return mImpl->mCompositionp;
}

F32 LLViewerRegion::getCompositionXY(const S32 x, const S32 y) const
{
    if (x >= 256)
    {
        if (y >= 256)
        {
            LLVector3d center = getCenterGlobal() + LLVector3d(256.f, 256.f, 0.f);
            LLViewerRegion *regionp = LLWorld::getInstance()->getRegionFromPosGlobal(center);
            if (regionp)
            {
                // OK, we need to do some hackery here - different simulators no longer use
                // the same composition values, necessarily.
                // If we're attempting to blend, then we want to make the fractional part of
                // this region match the fractional of the adjacent.  For now, just minimize
                // the delta.
                F32 our_comp = getComposition()->getValueScaled(255, 255);
                F32 adj_comp = regionp->getComposition()->getValueScaled(x - 256.f, y - 256.f);
                while (llabs(our_comp - adj_comp) >= 1.f)
                {
                    if (our_comp > adj_comp)
                    {
                        adj_comp += 1.f;
                    }
                    else
                    {
                        adj_comp -= 1.f;
                    }
                }
                return adj_comp;
            }
        }
        else
        {
            LLVector3d center = getCenterGlobal() + LLVector3d(256.f, 0, 0.f);
            LLViewerRegion *regionp = LLWorld::getInstance()->getRegionFromPosGlobal(center);
            if (regionp)
            {
                // OK, we need to do some hackery here - different simulators no longer use
                // the same composition values, necessarily.
                // If we're attempting to blend, then we want to make the fractional part of
                // this region match the fractional of the adjacent.  For now, just minimize
                // the delta.
                F32 our_comp = getComposition()->getValueScaled(255.f, (F32)y);
                F32 adj_comp = regionp->getComposition()->getValueScaled(x - 256.f, (F32)y);
                while (llabs(our_comp - adj_comp) >= 1.f)
                {
                    if (our_comp > adj_comp)
                    {
                        adj_comp += 1.f;
                    }
                    else
                    {
                        adj_comp -= 1.f;
                    }
                }
                return adj_comp;
            }
        }
    }
    else if (y >= 256)
    {
        LLVector3d center = getCenterGlobal() + LLVector3d(0.f, 256.f, 0.f);
        LLViewerRegion *regionp = LLWorld::getInstance()->getRegionFromPosGlobal(center);
        if (regionp)
        {
            // OK, we need to do some hackery here - different simulators no longer use
            // the same composition values, necessarily.
            // If we're attempting to blend, then we want to make the fractional part of
            // this region match the fractional of the adjacent.  For now, just minimize
            // the delta.
            F32 our_comp = getComposition()->getValueScaled((F32)x, 255.f);
            F32 adj_comp = regionp->getComposition()->getValueScaled((F32)x, y - 256.f);
            while (llabs(our_comp - adj_comp) >= 1.f)
            {
                if (our_comp > adj_comp)
                {
                    adj_comp += 1.f;
                }
                else
                {
                    adj_comp -= 1.f;
                }
            }
            return adj_comp;
        }
    }

    return getComposition()->getValueScaled((F32)x, (F32)y);
}

void LLViewerRegion::calculateCenterGlobal()
{
    mImpl->mCenterGlobal = mImpl->mOriginGlobal;
    mImpl->mCenterGlobal.mdV[VX] += 0.5 * mWidth;
    mImpl->mCenterGlobal.mdV[VY] += 0.5 * mWidth;
    mImpl->mCenterGlobal.mdV[VZ] = 0.5 * mImpl->mLandp->getMinZ() + mImpl->mLandp->getMaxZ();
}

void LLViewerRegion::calculateCameraDistance()
{
    mCameraDistanceSquared = (F32)(gAgentCamera.getCameraPositionGlobal() - getCenterGlobal()).magVecSquared();
}

std::ostream& operator<<(std::ostream &s, const LLViewerRegion &region)
{
    s << "{ ";
    s << region.mImpl->mHost;
    s << " mOriginGlobal = " << region.getOriginGlobal()<< "\n";
    std::string name(region.getName()), zone(region.getZoning());
    if (! name.empty())
    {
        s << " mName         = " << name << '\n';
    }
    if (! zone.empty())
    {
        s << " mZoning       = " << zone << '\n';
    }
    s << "}";
    return s;
}


// ---------------- Protected Member Functions ----------------

void LLViewerRegion::updateNetStats()
{
    F32 dt = mImpl->mLastNetUpdate.getElapsedTimeAndResetF32();

    LLCircuitData *cdp = gMessageSystem->mCircuitInfo.findCircuit(mImpl->mHost);
    if (!cdp)
    {
        mAlive = false;
        return;
    }

    mAlive = true;
    mDeltaTime = dt;

    mLastPacketsIn =    mPacketsIn;
    mLastBitsIn =       mBitsIn;
    mLastPacketsOut =   mPacketsOut;
    mLastPacketsLost =  mPacketsLost;

    mPacketsIn =                cdp->getPacketsIn();
    mBitsIn =                   8 * cdp->getBytesIn();
    mPacketsOut =               cdp->getPacketsOut();
    mPacketsLost =              cdp->getPacketsLost();
    mPingDelay =                cdp->getPingDelay();

    mBitsReceived += mBitsIn - mLastBitsIn;
    mPacketsReceived += mPacketsIn - mLastPacketsIn;
}


U32 LLViewerRegion::getPacketsLost() const
{
    LLCircuitData *cdp = gMessageSystem->mCircuitInfo.findCircuit(mImpl->mHost);
    if (!cdp)
    {
        LL_INFOS() << "LLViewerRegion::getPacketsLost couldn't find circuit for " << mImpl->mHost << LL_ENDL;
        return 0;
    }
    else
    {
        return cdp->getPacketsLost();
    }
}

S32 LLViewerRegion::getHttpResponderID() const
{
    return mImpl->mHttpResponderID;
}

BOOL LLViewerRegion::pointInRegionGlobal(const LLVector3d &point_global) const
{
    LLVector3 pos_region = getPosRegionFromGlobal(point_global);

    if (pos_region.mV[VX] < 0)
    {
        return FALSE;
    }
    if (pos_region.mV[VX] >= mWidth)
    {
        return FALSE;
    }
    if (pos_region.mV[VY] < 0)
    {
        return FALSE;
    }
    if (pos_region.mV[VY] >= mWidth)
    {
        return FALSE;
    }
    return TRUE;
}

LLVector3 LLViewerRegion::getPosRegionFromGlobal(const LLVector3d &point_global) const
{
    LLVector3 pos_region;
    pos_region.setVec(point_global - mImpl->mOriginGlobal);
    return pos_region;
}

LLVector3d LLViewerRegion::getPosGlobalFromRegion(const LLVector3 &pos_region) const
{
    LLVector3d pos_region_d;
    pos_region_d.setVec(pos_region);
    return pos_region_d + mImpl->mOriginGlobal;
}

LLVector3 LLViewerRegion::getPosAgentFromRegion(const LLVector3 &pos_region) const
{
    LLVector3d pos_global = getPosGlobalFromRegion(pos_region);

    return gAgent.getPosAgentFromGlobal(pos_global);
}

LLVector3 LLViewerRegion::getPosRegionFromAgent(const LLVector3 &pos_agent) const
{
    return pos_agent - getOriginAgent();
}

F32 LLViewerRegion::getLandHeightRegion(const LLVector3& region_pos)
{
    return mImpl->mLandp->resolveHeightRegion( region_pos );
}

bool LLViewerRegion::isAlive()
{
    return mAlive;
}

BOOL LLViewerRegion::isOwnedSelf(const LLVector3& pos)
{
    if (mParcelOverlay)
    {
        return mParcelOverlay->isOwnedSelf(pos);
    } else {
        return FALSE;
    }
}

// Owned by a group you belong to?  (officer or member)
BOOL LLViewerRegion::isOwnedGroup(const LLVector3& pos)
{
    if (mParcelOverlay)
    {
        return mParcelOverlay->isOwnedGroup(pos);
    } else {
        return FALSE;
    }
}

// the new TCP coarse location handler node
class CoarseLocationUpdate : public LLHTTPNode
{
public:
    virtual void post(
        ResponsePtr responder,
        const LLSD& context,
        const LLSD& input) const
    {
        LLHost host(input["sender"].asString());

        LLWorld *world_inst = LLWorld::getInstance(); // Not a singleton!
        if (!world_inst)
        {
            return;
        }

        LLViewerRegion* region = world_inst->getRegion(host);
        if( !region )
        {
            return;
        }

        S32 target_index = input["body"]["Index"][0]["Prey"].asInteger();
        S32 you_index    = input["body"]["Index"][0]["You" ].asInteger();

        std::vector<U32>* avatar_locs = &region->mMapAvatars;
        std::vector<LLUUID>* avatar_ids = &region->mMapAvatarIDs;
        avatar_locs->clear();
        avatar_ids->clear();

        //LL_INFOS() << "coarse locations agent[0] " << input["body"]["AgentData"][0]["AgentID"].asUUID() << LL_ENDL;
        //LL_INFOS() << "my agent id = " << gAgent.getID() << LL_ENDL;
        //LL_INFOS() << ll_pretty_print_sd(input) << LL_ENDL;

        LLSD
            locs   = input["body"]["Location"],
            agents = input["body"]["AgentData"];
        LLSD::array_iterator
            locs_it = locs.beginArray(),
            agents_it = agents.beginArray();
        BOOL has_agent_data = input["body"].has("AgentData");

        for(int i=0;
            locs_it != locs.endArray();
            i++, locs_it++)
        {
            U8
                x = locs_it->get("X").asInteger(),
                y = locs_it->get("Y").asInteger(),
                z = locs_it->get("Z").asInteger();
            // treat the target specially for the map, and don't add you or the target
            if(i == target_index)
            {
                LLVector3d global_pos(region->getOriginGlobal());
                global_pos.mdV[VX] += (F64)x;
                global_pos.mdV[VY] += (F64)y;
                global_pos.mdV[VZ] += (F64)z * 4.0;
                LLAvatarTracker::instance().setTrackedCoarseLocation(global_pos);
            }
            else if( i != you_index)
            {
                U32 pos = 0x0;
                pos |= x;
                pos <<= 8;
                pos |= y;
                pos <<= 8;
                pos |= z;
                avatar_locs->push_back(pos);
                //LL_INFOS() << "next pos: " << x << "," << y << "," << z << ": " << pos << LL_ENDL;
                if(has_agent_data) // for backwards compatibility with old message format
                {
                    LLUUID agent_id(agents_it->get("AgentID").asUUID());
                    //LL_INFOS() << "next agent: " << agent_id.asString() << LL_ENDL;
                    avatar_ids->push_back(agent_id);
                }
            }
            if (has_agent_data)
            {
                agents_it++;
            }
        }
    }
};

// build the coarse location HTTP node under the "/message" URL
LLHTTPRegistration<CoarseLocationUpdate>
   gHTTPRegistrationCoarseLocationUpdate(
       "/message/CoarseLocationUpdate");


// the deprecated coarse location handler
void LLViewerRegion::updateCoarseLocations(LLMessageSystem* msg)
{
    //LL_INFOS() << "CoarseLocationUpdate" << LL_ENDL;
    mMapAvatars.clear();
    mMapAvatarIDs.clear(); // only matters in a rare case but it's good to be safe.

    U8 x_pos = 0;
    U8 y_pos = 0;
    U8 z_pos = 0;

    U32 pos = 0x0;

    S16 agent_index;
    S16 target_index;
    msg->getS16Fast(_PREHASH_Index, _PREHASH_You, agent_index);
    msg->getS16Fast(_PREHASH_Index, _PREHASH_Prey, target_index);

    BOOL has_agent_data = msg->has(_PREHASH_AgentData);
    S32 count = msg->getNumberOfBlocksFast(_PREHASH_Location);
    for(S32 i = 0; i < count; i++)
    {
        msg->getU8Fast(_PREHASH_Location, _PREHASH_X, x_pos, i);
        msg->getU8Fast(_PREHASH_Location, _PREHASH_Y, y_pos, i);
        msg->getU8Fast(_PREHASH_Location, _PREHASH_Z, z_pos, i);
        LLUUID agent_id = LLUUID::null;
        if(has_agent_data)
        {
            msg->getUUIDFast(_PREHASH_AgentData, _PREHASH_AgentID, agent_id, i);
        }

        //LL_INFOS() << "  object X: " << (S32)x_pos << " Y: " << (S32)y_pos
        //      << " Z: " << (S32)(z_pos * 4)
        //      << LL_ENDL;

        // treat the target specially for the map
        if(i == target_index)
        {
            LLVector3d global_pos(mImpl->mOriginGlobal);
            global_pos.mdV[VX] += (F64)(x_pos);
            global_pos.mdV[VY] += (F64)(y_pos);
            global_pos.mdV[VZ] += (F64)(z_pos) * 4.0;
            LLAvatarTracker::instance().setTrackedCoarseLocation(global_pos);
        }

        //don't add you
        if( i != agent_index)
        {
            pos = 0x0;
            pos |= x_pos;
            pos <<= 8;
            pos |= y_pos;
            pos <<= 8;
            pos |= z_pos;
            mMapAvatars.push_back(pos);
            if(has_agent_data)
            {
                mMapAvatarIDs.push_back(agent_id);
            }
        }
    }
}

void LLViewerRegion::getInfo(LLSD& info)
{
    info["Region"]["Host"] = getHost().getIPandPort();
    info["Region"]["Name"] = getName();
    U32 x, y;
    from_region_handle(getHandle(), &x, &y);
    info["Region"]["Handle"]["x"] = (LLSD::Integer)x;
    info["Region"]["Handle"]["y"] = (LLSD::Integer)y;
}

void LLViewerRegion::requestSimulatorFeatures()
{
    LL_DEBUGS("SimulatorFeatures") << "region " << getName() << " ptr " << this
                                   << " trying to request SimulatorFeatures" << LL_ENDL;
    // kick off a request for simulator features
    std::string url = getCapability("SimulatorFeatures");
    if (!url.empty())
    {
        std::string coroname =
            LLCoros::instance().launch("LLViewerRegionImpl::requestSimulatorFeatureCoro",
                                       boost::bind(&LLViewerRegionImpl::requestSimulatorFeatureCoro, url, getHandle()));

        // requestSimulatorFeatures can be called from other coros,
        // launch() acts like a suspend()
        // Make sure we are still good to do
        LLCoros::checkStop();

        LL_INFOS("AppInit", "SimulatorFeatures") << "Launching " << coroname << " requesting simulator features from " << url << " for region " << getRegionID() << LL_ENDL;
    }
    else
    {
        LL_WARNS("AppInit", "SimulatorFeatures") << "SimulatorFeatures cap not set" << LL_ENDL;
    }
}

boost::signals2::connection LLViewerRegion::setSimulatorFeaturesReceivedCallback(const caps_received_signal_t::slot_type& cb)
{
    return mSimulatorFeaturesReceivedSignal.connect(cb);
}

void LLViewerRegion::setSimulatorFeaturesReceived(bool received)
{
    mSimulatorFeaturesReceived = received;
    if (received)
    {
        mSimulatorFeaturesReceivedSignal(getRegionID(), this);
        mSimulatorFeaturesReceivedSignal.disconnect_all_slots();
    }
}

bool LLViewerRegion::simulatorFeaturesReceived() const
{
    return mSimulatorFeaturesReceived;
}

void LLViewerRegion::getSimulatorFeatures(LLSD& sim_features) const
{
    sim_features = mSimulatorFeatures;

}

void LLViewerRegion::setSimulatorFeatures(const LLSD& sim_features)
{
    std::stringstream str;

    LLSDSerialize::toPrettyXML(sim_features, str);
    LL_INFOS() << "region " << getName() << " "  << str.str() << LL_ENDL;
    mSimulatorFeatures = sim_features;

    setSimulatorFeaturesReceived(true);

}

//this is called when the parent is not cacheable.
//move all orphan children out of cache and insert to rendering octree.
void LLViewerRegion::findOrphans(U32 parent_id)
{
    orphan_list_t::iterator iter = mOrphanMap.find(parent_id);
    if(iter != mOrphanMap.end())
    {
        std::vector<U32>* children = &mOrphanMap[parent_id];
        for(S32 i = 0; i < children->size(); i++)
        {
            //parent is visible, so is the child.
            addVisibleChildCacheEntry(NULL, getCacheEntry((*children)[i]));
        }
        children->clear();
        mOrphanMap.erase(parent_id);
    }
}

void LLViewerRegion::decodeBoundingInfo(LLVOCacheEntry* entry)
{
    if(!sVOCacheCullingEnabled)
    {
        gObjectList.processObjectUpdateFromCache(entry, this);
        return;
    }
    if(!entry || !entry->isValid())
    {
        return;
    }

    if(!entry->getEntry())
    {
        entry->setOctreeEntry(NULL);
    }

    if(entry->getEntry()->hasDrawable()) //already in the rendering pipeline
    {
        LLViewerRegion* old_regionp = ((LLDrawable*)entry->getEntry()->getDrawable())->getRegion();
        if(old_regionp != this && old_regionp)
        {
            LLViewerObject* obj = ((LLDrawable*)entry->getEntry()->getDrawable())->getVObj();
            if(obj)
            {
                //remove from old region
                old_regionp->killCacheEntry(obj->getLocalID());

                //change region
                obj->setRegion(this);
            }
        }

        addActiveCacheEntry(entry);

        //set parent id
        U32 parent_id = 0;
        if (entry->getDP()) // NULL if nothing cached
        {
            LLViewerObject::unpackParentID(entry->getDP(), parent_id);
        }
        if(parent_id != entry->getParentID())
        {
            entry->setParentID(parent_id);
        }

        //update the object
        gObjectList.processObjectUpdateFromCache(entry, this);
        return; //done
    }

    //must not be active.
    llassert_always(!entry->isState(LLVOCacheEntry::ACTIVE));
    removeFromVOCacheTree(entry); //remove from cache octree if it is in.

    LLVector3 pos;
    LLVector3 scale;
    LLQuaternion rot;

    //decode spatial info and parent info
    U32 parent_id = entry->getDP() ? LLViewerObject::extractSpatialExtents(entry->getDP(), pos, scale, rot) : entry->getParentID();

    U32 old_parent_id = entry->getParentID();
    bool same_old_parent = false;
    if(parent_id != old_parent_id) //parent changed.
    {
        if(old_parent_id > 0) //has an old parent, disconnect it
        {
            LLVOCacheEntry* old_parent = getCacheEntry(old_parent_id);
            if(old_parent)
            {
                old_parent->removeChild(entry);
                if(!old_parent->isState(LLVOCacheEntry::INACTIVE))
                {
                    mImpl->mVisibleEntries.erase(entry);
                    entry->setState(LLVOCacheEntry::INACTIVE);
                }
            }
        }
        entry->setParentID(parent_id);
    }
    else
    {
        same_old_parent = true;
    }

    if(parent_id > 0) //has a new parent
    {
        //1, find the parent in cache
        LLVOCacheEntry* parent = getCacheEntry(parent_id);

        //2, parent is not in the cache, put into the orphan list.
        if(!parent)
        {
            if(!same_old_parent)
            {
                //check if parent is non-cacheable and already created
                if(isNonCacheableObjectCreated(parent_id))
                {
                    //parent is visible, so is the child.
                    addVisibleChildCacheEntry(NULL, entry);
                }
                else
                {
                    entry->setBoundingInfo(pos, scale);
                    mOrphanMap[parent_id].push_back(entry->getLocalID());
                }
            }
            else
            {
                entry->setBoundingInfo(pos, scale);
            }
        }
        else //parent in cache.
        {
            if(!parent->isState(LLVOCacheEntry::INACTIVE))
            {
                //parent is visible, so is the child.
                addVisibleChildCacheEntry(parent, entry);
            }
            else
            {
                entry->setBoundingInfo(pos, scale);
                parent->addChild(entry);

                if(parent->getGroup()) //re-insert parent to vo-cache tree because its bounding info changed.
                {
                    removeFromVOCacheTree(parent);
                    addToVOCacheTree(parent);
                }
            }
        }

        return;
    }

    //
    //no parent
    //
    entry->setBoundingInfo(pos, scale);

    if(!parent_id) //a potential parent
    {
        //find all children and update their bounding info
        orphan_list_t::iterator iter = mOrphanMap.find(entry->getLocalID());
        if(iter != mOrphanMap.end())
        {
            std::vector<U32>* orphans = &mOrphanMap[entry->getLocalID()];
            S32 size = orphans->size();
            for(S32 i = 0; i < size; i++)
            {
                LLVOCacheEntry* child = getCacheEntry((*orphans)[i]);
                if(child)
                {
                    entry->addChild(child);
                }
            }
            orphans->clear();
            mOrphanMap.erase(entry->getLocalID());
        }
    }

    if(!entry->getGroup() && entry->isState(LLVOCacheEntry::INACTIVE))
    {
        addToVOCacheTree(entry);
    }
    return ;
}

LLViewerRegion::eCacheUpdateResult LLViewerRegion::cacheFullUpdate(LLDataPackerBinaryBuffer &dp, U32 flags)
{
    eCacheUpdateResult result;
    U32 crc;
    U32 local_id;

    LLViewerObject::unpackU32(&dp, local_id, "LocalID");
    LLViewerObject::unpackU32(&dp, crc, "CRC");

    LLVOCacheEntry* entry = getCacheEntry(local_id, false);

    if (entry)
    {
        entry->setValid();

        // we've seen this object before
        if (entry->getCRC() == crc)
        {
            LL_DEBUGS("AnimatedObjects") << " got dupe for local_id " << local_id << LL_ENDL;
            dumpStack("AnimatedObjectsStack");

            // Record a hit
            entry->recordDupe();
            result = CACHE_UPDATE_DUPE;
        }
        else //CRC changed
        {
            LL_DEBUGS("AnimatedObjects") << " got update for local_id " << local_id << LL_ENDL;
            dumpStack("AnimatedObjectsStack");

            // Update the cache entry
            entry->updateEntry(crc, dp);

            decodeBoundingInfo(entry);

            result = CACHE_UPDATE_CHANGED;
        }
    }
    else
    {
        LL_DEBUGS("AnimatedObjects") << " got first notification for local_id " << local_id << LL_ENDL;
        dumpStack("AnimatedObjectsStack");

        // we haven't seen this object before
        // Create new entry and add to map
        result = CACHE_UPDATE_ADDED;
        entry = new LLVOCacheEntry(local_id, crc, dp);
        record(LLStatViewer::OBJECT_CACHE_HIT_RATE, LLUnits::Ratio::fromValue(0));

        mImpl->mCacheMap[local_id] = entry;

        decodeBoundingInfo(entry);
    }
    entry->setUpdateFlags(flags);

    return result;
    }

LLViewerRegion::eCacheUpdateResult LLViewerRegion::cacheFullUpdate(LLViewerObject* objectp, LLDataPackerBinaryBuffer &dp, U32 flags)
{
    eCacheUpdateResult result = cacheFullUpdate(dp, flags);

    return result;
}

void LLViewerRegion::cacheFullUpdateGLTFOverride(const LLGLTFOverrideCacheEntry &override_data)
{
    U32 local_id = override_data.mLocalId;
    mImpl->mGLTFOverridesLLSD[local_id] = override_data;
}

LLVOCacheEntry* LLViewerRegion::getCacheEntryForOctree(U32 local_id)
{
    if(!sVOCacheCullingEnabled)
    {
        return NULL;
    }

    LLVOCacheEntry* entry = getCacheEntry(local_id);
    removeFromVOCacheTree(entry);

    return entry;
}

LLVOCacheEntry* LLViewerRegion::getCacheEntry(U32 local_id, bool valid)
{
    LLVOCacheEntry::vocache_entry_map_t::iterator iter = mImpl->mCacheMap.find(local_id);
    if(iter != mImpl->mCacheMap.end())
    {
        if(!valid || iter->second->isValid())
        {
            return iter->second;
        }
    }
    return NULL;
}

void LLViewerRegion::addCacheMiss(U32 id, LLViewerRegion::eCacheMissType cache_miss_type)
{
    mRegionCacheMissCount++;
    mCacheMissList.push_back(CacheMissItem(id, cache_miss_type));
}

//check if a non-cacheable object is already created.
bool LLViewerRegion::isNonCacheableObjectCreated(U32 local_id)
{
    if(mImpl && local_id > 0 && mImpl->mNonCacheableCreatedList.find(local_id) != mImpl->mNonCacheableCreatedList.end())
    {
        return true;
    }
    return false;
}

void LLViewerRegion::removeFromCreatedList(U32 local_id)
{
    if(mImpl && local_id > 0)
    {
        std::set<U32>::iterator iter = mImpl->mNonCacheableCreatedList.find(local_id);
        if(iter != mImpl->mNonCacheableCreatedList.end())
        {
            mImpl->mNonCacheableCreatedList.erase(iter);
        }
    }
    }

void LLViewerRegion::addToCreatedList(U32 local_id)
{
    if(mImpl && local_id > 0)
    {
        mImpl->mNonCacheableCreatedList.insert(local_id);
    }
}

// Get data packer for this object, if we have cached data
// AND the CRC matches. JC
bool LLViewerRegion::probeCache(U32 local_id, U32 crc, U32 flags, U8 &cache_miss_type)
{
    //llassert(mCacheLoaded);  This assert failes often, changing to early-out -- davep, 2010/10/18

    LLVOCacheEntry* entry = getCacheEntry(local_id, false);

    if (entry)
    {
        // we've seen this object before
        if (entry->getCRC() == crc)
        {
            // Record a hit
            mRegionCacheHitCount++;
            entry->recordHit();
            cache_miss_type = CACHE_MISS_TYPE_NONE;
            entry->setUpdateFlags(flags);

            if(entry->isState(LLVOCacheEntry::ACTIVE))
            {
                ((LLDrawable*)entry->getEntry()->getDrawable())->getVObj()->loadFlags(flags);
                return true;
            }

            if(entry->isValid())
            {
                return true; //already probed
            }

            entry->setValid();
            decodeBoundingInfo(entry);

            //loadCacheMiscExtras(local_id, entry, crc);

            return true;
        }
        else
        {
            // LL_INFOS() << "CRC miss for " << local_id << LL_ENDL;

            addCacheMiss(local_id, CACHE_MISS_TYPE_CRC);
            cache_miss_type = CACHE_MISS_TYPE_CRC;
        }
    }
    else
    {   // Total miss, don't have the object in cache
        // LL_INFOS() << "Cache miss for " << local_id << LL_ENDL;
        addCacheMiss(local_id, CACHE_MISS_TYPE_TOTAL);
        cache_miss_type = CACHE_MISS_TYPE_TOTAL;
    }

    return false;
}

void LLViewerRegion::addCacheMissFull(const U32 local_id)
{
    addCacheMiss(local_id, CACHE_MISS_TYPE_TOTAL);
}

void LLViewerRegion::requestCacheMisses()
{
    if (!mCacheMissList.size())
    {
        return;
    }

    LLMessageSystem* msg = gMessageSystem;
    BOOL start_new_message = TRUE;
    S32 blocks = 0;

    //send requests for all cache-missed objects
    for (CacheMissItem::cache_miss_list_t::iterator iter = mCacheMissList.begin(); iter != mCacheMissList.end(); ++iter)
    {
        if (start_new_message)
        {
            msg->newMessageFast(_PREHASH_RequestMultipleObjects);
            msg->nextBlockFast(_PREHASH_AgentData);
            msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID());
            msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID());
            start_new_message = FALSE;
        }

        msg->nextBlockFast(_PREHASH_ObjectData);
        msg->addU8Fast(_PREHASH_CacheMissType, (*iter).mType);
        msg->addU32Fast(_PREHASH_ID, (*iter).mID);

        LL_DEBUGS("AnimatedObjects") << "Requesting cache missed object " << (*iter).mID << LL_ENDL;

        blocks++;

        if (blocks >= 255)
        {
            sendReliableMessage();
            start_new_message = TRUE;
            blocks = 0;
        }
    }

    // finish any pending message
    if (!start_new_message)
    {
        sendReliableMessage();
    }

    mCacheDirty = TRUE ;
    // LL_INFOS() << "KILLDEBUG Sent cache miss full " << full_count << " crc " << crc_count << LL_ENDL;
    LLViewerStatsRecorder::instance().requestCacheMissesEvent(mCacheMissList.size());

    mCacheMissList.clear();
}

void LLViewerRegion::dumpCache()
{
    const S32 BINS = 4;
    S32 hit_bin[BINS];
    S32 change_bin[BINS];

    S32 i;
    for (i = 0; i < BINS; ++i)
    {
        hit_bin[i] = 0;
        change_bin[i] = 0;
    }

    LLVOCacheEntry *entry;
    for(LLVOCacheEntry::vocache_entry_map_t::iterator iter = mImpl->mCacheMap.begin(); iter != mImpl->mCacheMap.end(); ++iter)
    {
        entry = iter->second ;

        S32 hits = entry->getHitCount();
        S32 changes = entry->getCRCChangeCount();

        hits = llclamp(hits, 0, BINS-1);
        changes = llclamp(changes, 0, BINS-1);

        hit_bin[hits]++;
        change_bin[changes]++;
    }

    LL_INFOS() << "Count " << mImpl->mCacheMap.size() << LL_ENDL;
    for (i = 0; i < BINS; i++)
    {
        LL_INFOS() << "Hits " << i << " " << hit_bin[i] << LL_ENDL;
    }
    for (i = 0; i < BINS; i++)
    {
        LL_INFOS() << "Changes " << i << " " << change_bin[i] << LL_ENDL;
    }
    // TODO - add overrides cache too
}

void LLViewerRegion::unpackRegionHandshake()
{
    LLMessageSystem *msg = gMessageSystem;

    U64 region_flags = 0;
    U64 region_protocols = 0;
    U8 sim_access;
    std::string sim_name;
    LLUUID sim_owner;
    BOOL is_estate_manager;
    F32 water_height;
    F32 billable_factor;
    LLUUID cache_id;

    msg->getU8      ("RegionInfo", "SimAccess", sim_access);
    msg->getString  ("RegionInfo", "SimName", sim_name);
    msg->getUUID    ("RegionInfo", "SimOwner", sim_owner);
    msg->getBOOL    ("RegionInfo", "IsEstateManager", is_estate_manager);
    msg->getF32     ("RegionInfo", "WaterHeight", water_height);
    msg->getF32     ("RegionInfo", "BillableFactor", billable_factor);
    msg->getUUID    ("RegionInfo", "CacheID", cache_id );

    if (msg->has(_PREHASH_RegionInfo4))
    {
        msg->getU64Fast(_PREHASH_RegionInfo4, _PREHASH_RegionFlagsExtended, region_flags);
        msg->getU64Fast(_PREHASH_RegionInfo4, _PREHASH_RegionProtocols, region_protocols);
    }
    else
    {
        U32 flags = 0;
        msg->getU32Fast(_PREHASH_RegionInfo, _PREHASH_RegionFlags, flags);
        region_flags = flags;
    }

    setRegionFlags(region_flags);
    setRegionProtocols(region_protocols);
    setSimAccess(sim_access);
    setRegionNameAndZone(sim_name);
    setOwner(sim_owner);
    setIsEstateManager(is_estate_manager);
    setWaterHeight(water_height);
    setBillableFactor(billable_factor);
    setCacheID(cache_id);

    LLUUID region_id;
    msg->getUUID("RegionInfo2", "RegionID", region_id);
    setRegionID(region_id);

    // Retrieve the CR-53 (Homestead/Land SKU) information
    S32 classID = 0;
    S32 cpuRatio = 0;
    std::string coloName;
    std::string productSKU;
    std::string productName;

    // the only reasonable way to decide if we actually have any data is to
    // check to see if any of these fields have positive sizes
    if (msg->getSize("RegionInfo3", "ColoName") > 0 ||
        msg->getSize("RegionInfo3", "ProductSKU") > 0 ||
        msg->getSize("RegionInfo3", "ProductName") > 0)
    {
        msg->getS32     ("RegionInfo3", "CPUClassID",  classID);
        msg->getS32     ("RegionInfo3", "CPURatio",    cpuRatio);
        msg->getString  ("RegionInfo3", "ColoName",    coloName);
        msg->getString  ("RegionInfo3", "ProductSKU",  productSKU);
        msg->getString  ("RegionInfo3", "ProductName", productName);

        mClassID = classID;
        mCPURatio = cpuRatio;
        mColoName = coloName;
        mProductSKU = productSKU;
        mProductName = productName;
    }

    mCentralBakeVersion = region_protocols & 1; // was (S32)gSavedSettings.getBOOL("UseServerTextureBaking");
    LLVLComposition *compp = getComposition();
    if (compp)
    {
        LLUUID tmp_id;

        bool changed = false;

        // Get the 4 textures for land
        msg->getUUID("RegionInfo", "TerrainDetail0", tmp_id);
        changed |= (tmp_id != compp->getDetailTextureID(0));
        compp->setDetailTextureID(0, tmp_id);

        msg->getUUID("RegionInfo", "TerrainDetail1", tmp_id);
        changed |= (tmp_id != compp->getDetailTextureID(1));
        compp->setDetailTextureID(1, tmp_id);

        msg->getUUID("RegionInfo", "TerrainDetail2", tmp_id);
        changed |= (tmp_id != compp->getDetailTextureID(2));
        compp->setDetailTextureID(2, tmp_id);

        msg->getUUID("RegionInfo", "TerrainDetail3", tmp_id);
        changed |= (tmp_id != compp->getDetailTextureID(3));
        compp->setDetailTextureID(3, tmp_id);

        // Get the start altitude and range values for land textures
        F32 tmp_f32;
        msg->getF32("RegionInfo", "TerrainStartHeight00", tmp_f32);
        changed |= (tmp_f32 != compp->getStartHeight(0));
        compp->setStartHeight(0, tmp_f32);

        msg->getF32("RegionInfo", "TerrainStartHeight01", tmp_f32);
        changed |= (tmp_f32 != compp->getStartHeight(1));
        compp->setStartHeight(1, tmp_f32);

        msg->getF32("RegionInfo", "TerrainStartHeight10", tmp_f32);
        changed |= (tmp_f32 != compp->getStartHeight(2));
        compp->setStartHeight(2, tmp_f32);

        msg->getF32("RegionInfo", "TerrainStartHeight11", tmp_f32);
        changed |= (tmp_f32 != compp->getStartHeight(3));
        compp->setStartHeight(3, tmp_f32);


        msg->getF32("RegionInfo", "TerrainHeightRange00", tmp_f32);
        changed |= (tmp_f32 != compp->getHeightRange(0));
        compp->setHeightRange(0, tmp_f32);

        msg->getF32("RegionInfo", "TerrainHeightRange01", tmp_f32);
        changed |= (tmp_f32 != compp->getHeightRange(1));
        compp->setHeightRange(1, tmp_f32);

        msg->getF32("RegionInfo", "TerrainHeightRange10", tmp_f32);
        changed |= (tmp_f32 != compp->getHeightRange(2));
        compp->setHeightRange(2, tmp_f32);

        msg->getF32("RegionInfo", "TerrainHeightRange11", tmp_f32);
        changed |= (tmp_f32 != compp->getHeightRange(3));
        compp->setHeightRange(3, tmp_f32);

        // If this is an UPDATE (params already ready, we need to regenerate
        // all of our terrain stuff, by
        if (compp->getParamsReady())
        {
            // Update if the land changed
            if (changed)
            {
                getLand().dirtyAllPatches();
            }
        }
        else
        {
            compp->setParamsReady();
        }
    }


    // Now that we have the name, we can load the cache file
    // off disk.
    loadObjectCache();

    // After loading cache, signal that simulator can start
    // sending data.
    // TODO: Send all upstream viewer->sim handshake info here.
    LLHost host = msg->getSender();
    msg->newMessage("RegionHandshakeReply");
    msg->nextBlock("AgentData");
    msg->addUUID("AgentID", gAgent.getID());
    msg->addUUID("SessionID", gAgent.getSessionID());
    msg->nextBlock("RegionInfo");

    U32 flags = 0;
    flags |= REGION_HANDSHAKE_SUPPORTS_SELF_APPEARANCE;

    if(sVOCacheCullingEnabled)
    {
        flags |= 0x00000001; //set the bit 0 to be 1 to ask sim to send all cacheable objects.
    }
    if(mImpl->mCacheMap.empty())
    {
        flags |= 0x00000002; //set the bit 1 to be 1 to tell sim the cache file is empty, no need to send cache probes.
    }
    msg->addU32("Flags", flags );
    msg->sendReliable(host);

    mRegionTimer.reset(); //reset region timer.
}

// static
void LLViewerRegionImpl::buildCapabilityNames(LLSD& capabilityNames)
{
    capabilityNames.append("AbuseCategories");
    capabilityNames.append("AcceptFriendship");
    capabilityNames.append("AcceptGroupInvite"); // ReadOfflineMsgs recieved messages only!!!
    capabilityNames.append("AgentPreferences");
    capabilityNames.append("AgentProfile");
    capabilityNames.append("AgentState");
    capabilityNames.append("AttachmentResources");
    capabilityNames.append("AvatarPickerSearch");
    capabilityNames.append("AvatarRenderInfo");
    capabilityNames.append("CharacterProperties");
    capabilityNames.append("ChatSessionRequest");
    capabilityNames.append("CopyInventoryFromNotecard");
    capabilityNames.append("CreateInventoryCategory");
    capabilityNames.append("DeclineFriendship");
    capabilityNames.append("DeclineGroupInvite"); // ReadOfflineMsgs recieved messages only!!!
    capabilityNames.append("DispatchRegionInfo");
    capabilityNames.append("DirectDelivery");
    capabilityNames.append("EnvironmentSettings");
    capabilityNames.append("EstateAccess");
    capabilityNames.append("EstateChangeInfo");
    capabilityNames.append("EventQueueGet");
    capabilityNames.append("ExtEnvironment");

    capabilityNames.append("FetchLib2");
    capabilityNames.append("FetchLibDescendents2");
    capabilityNames.append("FetchInventory2");
    capabilityNames.append("FetchInventoryDescendents2");
    capabilityNames.append("IncrementCOFVersion");
    AISAPI::getCapNames(capabilityNames);

    capabilityNames.append("InterestList");

    capabilityNames.append("InventoryThumbnailUpload");
    capabilityNames.append("GetDisplayNames");
    capabilityNames.append("GetExperiences");
    capabilityNames.append("AgentExperiences");
    capabilityNames.append("FindExperienceByName");
    capabilityNames.append("GetExperienceInfo");
    capabilityNames.append("GetAdminExperiences");
    capabilityNames.append("GetCreatorExperiences");
    capabilityNames.append("ExperiencePreferences");
    capabilityNames.append("GroupExperiences");
    capabilityNames.append("UpdateExperience");
    capabilityNames.append("IsExperienceAdmin");
    capabilityNames.append("IsExperienceContributor");
    capabilityNames.append("RegionExperiences");
    capabilityNames.append("ExperienceQuery");
    capabilityNames.append("GetMetadata");
    capabilityNames.append("GetObjectCost");
    capabilityNames.append("GetObjectPhysicsData");
    capabilityNames.append("GroupAPIv1");
    capabilityNames.append("GroupMemberData");
    capabilityNames.append("GroupProposalBallot");
    capabilityNames.append("HomeLocation");
    capabilityNames.append("LandResources");
    capabilityNames.append("LSLSyntax");
    capabilityNames.append("MapLayer");
    capabilityNames.append("MapLayerGod");
    capabilityNames.append("MeshUploadFlag");
    capabilityNames.append("ModifyMaterialParams");
    capabilityNames.append("NavMeshGenerationStatus");
    capabilityNames.append("NewFileAgentInventory");
    capabilityNames.append("ObjectAnimation");
    capabilityNames.append("ObjectMedia");
    capabilityNames.append("ObjectMediaNavigate");
    capabilityNames.append("ObjectNavMeshProperties");
    capabilityNames.append("ParcelPropertiesUpdate");
    capabilityNames.append("ParcelVoiceInfoRequest");
    capabilityNames.append("ProductInfoRequest");
    capabilityNames.append("ProvisionVoiceAccountRequest");
    capabilityNames.append("ReadOfflineMsgs"); // Requires to respond reliably: AcceptFriendship, AcceptGroupInvite, DeclineFriendship, DeclineGroupInvite
    capabilityNames.append("RegionObjects");
    capabilityNames.append("RemoteParcelRequest");
    capabilityNames.append("RenderMaterials");
    capabilityNames.append("RequestTextureDownload");
    capabilityNames.append("ResourceCostSelected");
    capabilityNames.append("RetrieveNavMeshSrc");
    capabilityNames.append("SearchStatRequest");
    capabilityNames.append("SearchStatTracking");
    capabilityNames.append("SendPostcard");
    capabilityNames.append("SendUserReport");
    capabilityNames.append("SendUserReportWithScreenshot");
    capabilityNames.append("ServerReleaseNotes");
    capabilityNames.append("SetDisplayName");
    capabilityNames.append("SimConsoleAsync");
    capabilityNames.append("SimulatorFeatures");
    capabilityNames.append("StartGroupProposal");
    capabilityNames.append("TerrainNavMeshProperties");
    capabilityNames.append("TextureStats");
    capabilityNames.append("UntrustedSimulatorMessage");
    capabilityNames.append("UpdateAgentInformation");
    capabilityNames.append("UpdateAgentLanguage");
    capabilityNames.append("UpdateAvatarAppearance");
    capabilityNames.append("UpdateGestureAgentInventory");
    capabilityNames.append("UpdateGestureTaskInventory");
    capabilityNames.append("UpdateNotecardAgentInventory");
    capabilityNames.append("UpdateNotecardTaskInventory");
    capabilityNames.append("UpdateScriptAgent");
    capabilityNames.append("UpdateScriptTask");
    capabilityNames.append("UpdateSettingsAgentInventory");
    capabilityNames.append("UpdateSettingsTaskInventory");
    capabilityNames.append("UploadAgentProfileImage");
    capabilityNames.append("UpdateMaterialAgentInventory");
    capabilityNames.append("UpdateMaterialTaskInventory");
    capabilityNames.append("UploadBakedTexture");
    capabilityNames.append("UserInfo");
    capabilityNames.append("ViewerAsset");
    capabilityNames.append("ViewerBenefits");
    capabilityNames.append("ViewerMetrics");
    capabilityNames.append("ViewerStartAuction");
    capabilityNames.append("ViewerStats");

    // Please add new capabilities alphabetically to reduce
    // merge conflicts.
}

void LLViewerRegion::setSeedCapability(const std::string& url)
{
    if (getCapability("Seed") == url)
    {
        setCapabilityDebug("Seed", url);
        LL_WARNS("CrossingCaps") <<  "Received duplicate seed capability for " << getRegionID() << ", posting to seed " <<
                url << LL_ENDL;

        //Instead of just returning we build up a second set of seed caps and compare them
        //to the "original" seed cap received and determine why there is problem!
        std::string coroname =
            LLCoros::instance().launch("LLEnvironmentRequest::requestBaseCapabilitiesCompleteCoro",
            boost::bind(&LLViewerRegionImpl::requestBaseCapabilitiesCompleteCoro, getHandle()));

        // setSeedCapability can be called from other coros,
        // launch() acts like a suspend()
        // Make sure we are still good to do
        LLCoros::checkStop();

        return;
    }

    delete mImpl->mEventPoll;
    mImpl->mEventPoll = NULL;

    mImpl->mCapabilities.clear();
    setCapability("Seed", url);

    std::string coroname =
        LLCoros::instance().launch("LLViewerRegionImpl::requestBaseCapabilitiesCoro",
        boost::bind(&LLViewerRegionImpl::requestBaseCapabilitiesCoro, getHandle()));

    // setSeedCapability can be called from other coros,
    // launch() acts like a suspend()
    // Make sure we are still good to do
    LLCoros::checkStop();

    LL_INFOS("AppInit", "Capabilities") << "Launching " << coroname << " requesting seed capabilities from " << url << " for region " << getRegionID() << LL_ENDL;
}

S32 LLViewerRegion::getNumSeedCapRetries()
{
    return mImpl->mSeedCapAttempts;
}

void LLViewerRegion::setCapability(const std::string& name, const std::string& url)
{
    if(name == "EventQueueGet")
    {
        delete mImpl->mEventPoll;
        mImpl->mEventPoll = NULL;
        mImpl->mEventPoll = new LLEventPoll(url, getHost());
    }
    else if(name == "UntrustedSimulatorMessage")
    {
        mImpl->mHost.setUntrustedSimulatorCap(url);
    }
    else if (name == "SimulatorFeatures")
    {
        mImpl->mCapabilities["SimulatorFeatures"] = url;
        requestSimulatorFeatures();
    }
    else
    {
        mImpl->mCapabilities[name] = url;
        if(name == "ViewerAsset")
        {
            /*==============================================================*/
            // The following inserted lines are a hack for testing MAINT-7081,
            // which is why the indentation and formatting are left ugly.
            const char* VIEWERASSET = getenv("VIEWERASSET");
            if (VIEWERASSET)
            {
                mImpl->mCapabilities[name] = VIEWERASSET;
                mViewerAssetUrl = VIEWERASSET;
            }
            else
            /*==============================================================*/
            mViewerAssetUrl = url;
        }
    }
}

void LLViewerRegion::setCapabilityDebug(const std::string& name, const std::string& url)
{
    // Continue to not add certain caps, as we do in setCapability. This is so they match up when we check them later.
    if ( ! ( name == "EventQueueGet" || name == "UntrustedSimulatorMessage" || name == "SimulatorFeatures" ) )
    {
        mImpl->mSecondCapabilitiesTracker[name] = url;
        if(name == "ViewerAsset")
        {
            /*==============================================================*/
            // The following inserted lines are a hack for testing MAINT-7081,
            // which is why the indentation and formatting are left ugly.
            const char* VIEWERASSET = getenv("VIEWERASSET");
            if (VIEWERASSET)
            {
                mImpl->mSecondCapabilitiesTracker[name] = VIEWERASSET;
                mViewerAssetUrl = VIEWERASSET;
            }
            else
            /*==============================================================*/
            mViewerAssetUrl = url;
        }
    }
}

std::string LLViewerRegion::getCapabilityDebug(const std::string& name) const
{
    CapabilityMap::const_iterator iter = mImpl->mSecondCapabilitiesTracker.find(name);
    if (iter == mImpl->mSecondCapabilitiesTracker.end())
    {
        return "";
    }

    return iter->second;
}


bool LLViewerRegion::isSpecialCapabilityName(const std::string &name)
{
    return name == "EventQueueGet" || name == "UntrustedSimulatorMessage";
}

std::string LLViewerRegion::getCapability(const std::string& name) const
{
    if (!capabilitiesReceived() && (name!=std::string("Seed")) && (name!=std::string("ObjectMedia")))
    {
        LL_WARNS() << "getCapability called before caps received for " << name << LL_ENDL;
    }

    CapabilityMap::const_iterator iter = mImpl->mCapabilities.find(name);
    if(iter == mImpl->mCapabilities.end())
    {
        return "";
    }

    return iter->second;
}

bool LLViewerRegion::isCapabilityAvailable(const std::string& name) const
{
    if (!capabilitiesReceived() && (name!=std::string("Seed")) && (name!=std::string("ObjectMedia")))
    {
        LL_WARNS() << "isCapabilityAvailable called before caps received for " << name << LL_ENDL;
    }

    CapabilityMap::const_iterator iter = mImpl->mCapabilities.find(name);
    if(iter == mImpl->mCapabilities.end())
    {
        return false;
    }

    return true;
}

bool LLViewerRegion::capabilitiesReceived() const
{
    return mCapabilitiesState == CAPABILITIES_STATE_RECEIVED;
}

bool LLViewerRegion::capabilitiesError() const
{
    return mCapabilitiesState == CAPABILITIES_STATE_ERROR;
}

void LLViewerRegion::setCapabilitiesReceived(bool received)
{
    mCapabilitiesState = received ? CAPABILITIES_STATE_RECEIVED : CAPABILITIES_STATE_INIT;

    // Tell interested parties that we've received capabilities,
    // so that they can safely use getCapability().
    if (received)
    {
        mCapabilitiesReceivedSignal(getRegionID(), this);

        LLFloaterPermsDefault::sendInitialPerms();

        // This is a single-shot signal. Forget callbacks to save resources.
        mCapabilitiesReceivedSignal.disconnect_all_slots();

        // Set the region to the desired interest list mode
        setInterestListMode(gAgent.getInterestListMode());
    }
}

void LLViewerRegion::setCapabilitiesError()
{
    mCapabilitiesState = CAPABILITIES_STATE_ERROR;
}

boost::signals2::connection LLViewerRegion::setCapabilitiesReceivedCallback(const caps_received_signal_t::slot_type& cb)
{
    return mCapabilitiesReceivedSignal.connect(cb);
}

void LLViewerRegion::logActiveCapabilities() const
{
    log_capabilities(mImpl->mCapabilities);
}


bool LLViewerRegion::requestPostCapability(const std::string &capName, LLSD &postData, httpCallback_t cbSuccess, httpCallback_t cbFailure)
{
    std::string url = getCapability(capName);

    if (url.empty())
    {
        LL_WARNS("Region") << "Could not retrieve region " << getRegionID()
            << " POST capability \"" << capName << "\"" << LL_ENDL;
        return false;
    }

    LLCoreHttpUtil::HttpCoroutineAdapter::callbackHttpPost(url, gAgent.getAgentPolicy(), postData, cbSuccess, cbFailure);
    return true;
}

bool LLViewerRegion::requestGetCapability(const std::string &capName, httpCallback_t cbSuccess, httpCallback_t cbFailure)
{
    std::string url;

    url = getCapability(capName);

    if (url.empty())
    {
        LL_WARNS("Region") << "Could not retrieve region " << getRegionID()
                           << " GET capability \"" << capName << "\"" << LL_ENDL;
        return false;
    }

    LLCoreHttpUtil::HttpCoroutineAdapter::callbackHttpGet(url, gAgent.getAgentPolicy(), cbSuccess, cbFailure);
    return true;
}

bool LLViewerRegion::requestDelCapability(const std::string &capName, httpCallback_t cbSuccess, httpCallback_t cbFailure)
{
    std::string url;

    url = getCapability(capName);

    if (url.empty())
    {
        LL_WARNS("Region") << "Could not retrieve region " << getRegionID() << " DEL capability \"" << capName << "\"" << LL_ENDL;
        return false;
    }

    LLCoreHttpUtil::HttpCoroutineAdapter::callbackHttpDel(url, gAgent.getAgentPolicy(), cbSuccess, cbFailure);
    return true;
}

void LLViewerRegion::setInterestListMode(const std::string &new_mode)
{
    if (new_mode != mInterestListMode)
    {
        mInterestListMode = new_mode;

        if (mInterestListMode != std::string(IL_MODE_DEFAULT) && mInterestListMode != std::string(IL_MODE_360))
        {
            LL_WARNS("360Capture") << "Region " << getRegionID() << " setInterestListMode() invalid interest list mode: "
                << mInterestListMode << ", setting to default" << LL_ENDL;
            mInterestListMode = IL_MODE_DEFAULT;
        }

        LLSD body;
        body["mode"] = mInterestListMode;
        if (requestPostCapability("InterestList", body,
                                  [](const LLSD &response) {
                                      LL_DEBUGS("360Capture") << "InterestList capability responded: \n"
                                          << ll_pretty_print_sd(response) << LL_ENDL;
                                  }))
        {
            LL_DEBUGS("360Capture") << "Region " << getRegionID()
                                    << " Successfully posted an InterestList capability request with payload: \n"
                                    << ll_pretty_print_sd(body) << LL_ENDL;
        }
        else
        {
            LL_WARNS("360Capture") << "Region " << getRegionID()
                                   << " Unable to post an InterestList capability request with payload: \n"
                                   << ll_pretty_print_sd(body) << LL_ENDL;
        }
    }
    else
    {
        LL_DEBUGS("360Capture") << "Region " << getRegionID() << "No change, skipping Interest List mode POST to "
                                << new_mode << " mode" << LL_ENDL;
    }
}


void LLViewerRegion::resetInterestList()
{
    if (requestDelCapability("InterestList", [](const LLSD &response) {
                            LL_DEBUGS("360Capture") << "InterestList capability DEL responded: \n" << ll_pretty_print_sd(response) << LL_ENDL;
                        }))
    {
        LL_DEBUGS("360Capture") << "Region " << getRegionID() << " Successfully reset InterestList capability" << LL_ENDL;
    }
    else
    {
        LL_WARNS("360Capture") << "Region " << getRegionID() << " Unable to DEL InterestList capability request" << LL_ENDL;
    }
}


LLSpatialPartition *LLViewerRegion::getSpatialPartition(U32 type)
{
    if (type < mImpl->mObjectPartition.size() && type < PARTITION_VO_CACHE)
    {
        return (LLSpatialPartition*)mImpl->mObjectPartition[type];
    }
    return NULL;
}

LLVOCachePartition* LLViewerRegion::getVOCachePartition()
{
    if(PARTITION_VO_CACHE < mImpl->mObjectPartition.size())
    {
        return (LLVOCachePartition*)mImpl->mObjectPartition[PARTITION_VO_CACHE];
    }
    return NULL;
}

// the viewer can not yet distinquish between normal- and estate-owned objects
// so we collapse these two bits and enable the UI if either are set
const U64 ALLOW_RETURN_ENCROACHING_OBJECT = REGION_FLAGS_ALLOW_RETURN_ENCROACHING_OBJECT
                                            | REGION_FLAGS_ALLOW_RETURN_ENCROACHING_ESTATE_OBJECT;

bool LLViewerRegion::objectIsReturnable(const LLVector3& pos, const std::vector<LLBBox>& boxes) const
{
    return (mParcelOverlay != NULL)
        && (mParcelOverlay->isOwnedSelf(pos)
            || mParcelOverlay->isOwnedGroup(pos)
            || (getRegionFlag(ALLOW_RETURN_ENCROACHING_OBJECT)
                && mParcelOverlay->encroachesOwned(boxes)) );
}

bool LLViewerRegion::childrenObjectReturnable( const std::vector<LLBBox>& boxes ) const
{
    bool result = false;
    result = ( mParcelOverlay && mParcelOverlay->encroachesOnUnowned( boxes ) ) ? 1 : 0;
    return result;
}

bool LLViewerRegion::objectsCrossParcel(const std::vector<LLBBox>& boxes) const
{
    return mParcelOverlay && mParcelOverlay->encroachesOnNearbyParcel(boxes);
}

void LLViewerRegion::getNeighboringRegions( std::vector<LLViewerRegion*>& uniqueRegions )
{
    mImpl->mLandp->getNeighboringRegions( uniqueRegions );
}
void LLViewerRegion::getNeighboringRegionsStatus( std::vector<S32>& regions )
{
    mImpl->mLandp->getNeighboringRegionsStatus( regions );
}
void LLViewerRegion::showReleaseNotes()
{
    std::string url = this->getCapability("ServerReleaseNotes");

    if (url.empty()) {
        // HACK haven't received the capability yet, we'll wait until
        // it arives.
        mReleaseNotesRequested = TRUE;
        return;
    }

    LLWeb::loadURL(url);
    mReleaseNotesRequested = FALSE;
}

std::string LLViewerRegion::getDescription() const
{
    return stringize(*this);
}

bool LLViewerRegion::meshUploadEnabled() const
{
    return (mSimulatorFeatures.has("MeshUploadEnabled") &&
        mSimulatorFeatures["MeshUploadEnabled"].asBoolean());
}

bool LLViewerRegion::bakesOnMeshEnabled() const
{
    return (mSimulatorFeatures.has("BakesOnMeshEnabled") &&
        mSimulatorFeatures["BakesOnMeshEnabled"].asBoolean());
}

bool LLViewerRegion::meshRezEnabled() const
{
    return (mSimulatorFeatures.has("MeshRezEnabled") &&
                mSimulatorFeatures["MeshRezEnabled"].asBoolean());
}

bool LLViewerRegion::dynamicPathfindingEnabled() const
{
    return ( mSimulatorFeatures.has("DynamicPathfindingEnabled") &&
             mSimulatorFeatures["DynamicPathfindingEnabled"].asBoolean());
}

bool LLViewerRegion::avatarHoverHeightEnabled() const
{
    return ( mSimulatorFeatures.has("AvatarHoverHeightEnabled") &&
             mSimulatorFeatures["AvatarHoverHeightEnabled"].asBoolean());
}
/* Static Functions */

void log_capabilities(const CapabilityMap &capmap)
{
    S32 count = 0;
    CapabilityMap::const_iterator iter;
    for (iter = capmap.begin(); iter != capmap.end(); ++iter, ++count)
    {
        if (!iter->second.empty())
        {
            LL_INFOS() << "log_capabilities: " << iter->first << " URL is " << iter->second << LL_ENDL;
        }
    }
    LL_INFOS() << "log_capabilities: Dumped " << count << " entries." << LL_ENDL;
}
void LLViewerRegion::resetMaterialsCapThrottle()
{
    F32 requests_per_sec =  1.0f; // original default;
    if (   mSimulatorFeatures.has("RenderMaterialsCapability")
        && mSimulatorFeatures["RenderMaterialsCapability"].isReal() )
    {
        requests_per_sec = mSimulatorFeatures["RenderMaterialsCapability"].asReal();
        if ( requests_per_sec == 0.0f )
        {
            requests_per_sec = 1.0f;
            LL_WARNS("Materials")
                << "region '" << getName()
                << "' returned zero for RenderMaterialsCapability; using default "
                << requests_per_sec << " per second"
                << LL_ENDL;
        }
        LL_DEBUGS("Materials") << "region '" << getName()
                               << "' RenderMaterialsCapability " << requests_per_sec
                               << LL_ENDL;
    }
    else
    {
        LL_DEBUGS("Materials")
            << "region '" << getName()
            << "' did not return RenderMaterialsCapability, using default "
            << requests_per_sec << " per second"
            << LL_ENDL;
    }

    mMaterialsCapThrottleTimer.resetWithExpiry( 1.0f / requests_per_sec );
}

U32 LLViewerRegion::getMaxMaterialsPerTransaction() const
{
    U32 max_entries = 50; // original hard coded default
    if (   mSimulatorFeatures.has( "MaxMaterialsPerTransaction" )
        && mSimulatorFeatures[ "MaxMaterialsPerTransaction" ].isInteger())
    {
        max_entries = mSimulatorFeatures[ "MaxMaterialsPerTransaction" ].asInteger();
    }
    return max_entries;
}

std::string LLViewerRegion::getSimHostName()
{
    if (mSimulatorFeaturesReceived)
    {
        return mSimulatorFeatures.has("HostName") ? mSimulatorFeatures["HostName"].asString() : getHost().getHostName();
    }
    return std::string("...");
}

void LLViewerRegion::applyCacheMiscExtras(LLViewerObject* obj)
{
    LL_PROFILE_ZONE_SCOPED_CATEGORY_DISPLAY;
    llassert(obj);

    U32 local_id = obj->getLocalID();
    auto iter = mImpl->mGLTFOverridesLLSD.find(local_id);
    if (iter != mImpl->mGLTFOverridesLLSD.end())
    {
        llassert(iter->second.mGLTFMaterial.size() == iter->second.mSides.size());

        for (auto& side : iter->second.mGLTFMaterial)
        {
            obj->setTEGLTFMaterialOverride(side.first, side.second);
        }
    }
}