/*
-----------------------------------------------------------------------------
This source file is part of OGRE
(Object-oriented Graphics Rendering Engine)
For the latest info, see http://www.ogre3d.org/

Copyright (c) 2000-2014 Torus Knot Software Ltd

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
-----------------------------------------------------------------------------
*/
#include "OgreTerrainGroup.h"
#include "OgreRoot.h"
#include "OgreWorkQueue.h"
#include "OgreStreamSerialiser.h"
#include "OgreLogManager.h"
#include "OgreTerrainAutoUpdateLod.h"
#include "OgreTerrainMaterialGeneratorA.h"
#include <cmath>
#include <iomanip>

namespace Ogre
{
    const uint16 TerrainGroup::WORKQUEUE_LOAD_REQUEST = 1;
    const uint32 TerrainGroup::CHUNK_ID = StreamSerialiser::makeIdentifier("TERG");
    const uint16 TerrainGroup::CHUNK_VERSION = 1;

    //---------------------------------------------------------------------
    TerrainGroup::TerrainGroup(SceneManager* sm, Terrain::Alignment align, 
        uint16 terrainSize, Real terrainWorldSize)
        : mSceneManager(sm)
        , mAlignment(align)
        , mTerrainSize(terrainSize)
        , mTerrainWorldSize(terrainWorldSize)
        , mOrigin(Vector3::ZERO)
        , mFilenamePrefix("terrain")
        , mFilenameExtension("dat")
        , mResourceGroup(ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME)
        , mAutoUpdateLod( TerrainAutoUpdateLodFactory::getAutoUpdateLod(NONE) )
    {
        mDefaultImportData.terrainAlign = align;
        mDefaultImportData.terrainSize = terrainSize;
        mDefaultImportData.worldSize = terrainWorldSize;
        // by default we delete input data because we copy it, unless user
        // passes us an ImportData where they explicitly don't want it copied
        mDefaultImportData.deleteInputData = true;

        WorkQueue* wq = Root::getSingleton().getWorkQueue();
        mWorkQueueChannel = wq->getChannel("Ogre/TerrainGroup");
        wq->addRequestHandler(mWorkQueueChannel, this);
        wq->addResponseHandler(mWorkQueueChannel, this);

    }
    //---------------------------------------------------------------------
    TerrainGroup::TerrainGroup(SceneManager* sm)
        : mSceneManager(sm)
        , mAlignment(Terrain::ALIGN_X_Z)
        , mTerrainSize(0)
        , mTerrainWorldSize(0)
        , mOrigin(Vector3::ZERO)
        , mFilenamePrefix("terrain")
        , mFilenameExtension("dat")
        , mResourceGroup(ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME)
        , mAutoUpdateLod(0)
    {
        mDefaultImportData.terrainAlign = mAlignment;
        mDefaultImportData.terrainSize = 0;
        mDefaultImportData.worldSize = 0;
        // by default we delete input data because we copy it, unless user
        // passes us an ImportData where they explicitly don't want it copied
        mDefaultImportData.deleteInputData = true;

        WorkQueue* wq = Root::getSingleton().getWorkQueue();
        mWorkQueueChannel = wq->getChannel("Ogre/TerrainGroup");
        wq->addRequestHandler(mWorkQueueChannel, this);
        wq->addResponseHandler(mWorkQueueChannel, this);
    }
    //---------------------------------------------------------------------
    TerrainGroup::~TerrainGroup()
    {
        if(mAutoUpdateLod)
        {
            OGRE_DELETE mAutoUpdateLod;
            mAutoUpdateLod = 0;
        }

        // waiting for terrain preparing finished
        while (getNumTerrainPrepareRequests() > 0)
        {
            OGRE_THREAD_SLEEP(50);
            Root::getSingleton().getWorkQueue()->processResponses();
        }

        removeAllTerrains();

        WorkQueue* wq = Root::getSingleton().getWorkQueue();
        wq->removeRequestHandler(mWorkQueueChannel, this);
        wq->removeResponseHandler(mWorkQueueChannel, this);

    }
    //---------------------------------------------------------------------
    void TerrainGroup::setOrigin(const Vector3& pos)
    {
        if (pos != mOrigin)
        {
            mOrigin = pos;
            for (TerrainSlotMap::iterator i = mTerrainSlots.begin(); i != mTerrainSlots.end(); ++i)
            {
                TerrainSlot* slot = i->second;
                if (slot->instance)
                {
                    slot->instance->setPosition(getTerrainSlotPosition(slot->x, slot->y));
                }
            }
        }

    }
    //---------------------------------------------------------------------
    void TerrainGroup::setFilenameConvention(const String& prefix, const String& extension)
    {
        mFilenamePrefix = prefix;
        mFilenameExtension = extension;
    }
    //---------------------------------------------------------------------
    void TerrainGroup::setFilenamePrefix(const String& prefix)
    {
        mFilenamePrefix = prefix;
    }
    //---------------------------------------------------------------------
    void TerrainGroup::setFilenameExtension(const String& extension)
    {
        mFilenameExtension = extension;
    }
    //---------------------------------------------------------------------
    void TerrainGroup::defineTerrain(long x, long y)
    {
        defineTerrain(x, y, generateFilename(x, y));
    }
    //---------------------------------------------------------------------
    void TerrainGroup::defineTerrain(long x, long y, float constantHeight)
    {
        TerrainSlot* slot = getTerrainSlot(x, y, true);

        slot->def.useImportData();

        // Copy all settings, but make sure our primary settings are immutable
        *slot->def.importData = mDefaultImportData;
        slot->def.importData->constantHeight = constantHeight;
        slot->def.importData->terrainAlign = mAlignment;
        slot->def.importData->terrainSize = mTerrainSize;
        slot->def.importData->worldSize = mTerrainWorldSize;
    }
    //---------------------------------------------------------------------
    void TerrainGroup::defineTerrain(long x, long y, const Terrain::ImportData* importData)
    {
        TerrainSlot* slot = getTerrainSlot(x, y, true);

        slot->def.useImportData();

        // Copy all settings, but make sure our primary settings are immutable
        *slot->def.importData = *importData;
        slot->def.importData->terrainAlign = mAlignment;
        slot->def.importData->terrainSize = mTerrainSize;
        slot->def.importData->worldSize = mTerrainWorldSize;

    }
    //---------------------------------------------------------------------
    void TerrainGroup::defineTerrain(long x, long y, const Image* img, 
        const Terrain::LayerInstanceList* layers /*= 0*/)
    {
        TerrainSlot* slot = getTerrainSlot(x, y, true);

        freeTerrainSlotInstance(slot);
        slot->def.useImportData();

        *slot->def.importData = mDefaultImportData;

        // Copy all settings, but make sure our primary settings are immutable
        // copy image - this will get deleted by importData
        slot->def.importData->inputImage = OGRE_NEW Image(*img);
        if (layers)
        {
            // copy (held by value)
            slot->def.importData->layerList = *layers;
        }
        slot->def.importData->terrainAlign = mAlignment;
        slot->def.importData->terrainSize = mTerrainSize;
        slot->def.importData->worldSize = mTerrainWorldSize;

    }
    //---------------------------------------------------------------------
    void TerrainGroup::defineTerrain(long x, long y, const float* pFloat /*= 0*/, 
        const Terrain::LayerInstanceList* layers /*= 0*/)
    {
        TerrainSlot* slot = getTerrainSlot(x, y, true);

        freeTerrainSlotInstance(slot);
        slot->def.useImportData();

        *slot->def.importData = mDefaultImportData;

        // Copy all settings, but make sure our primary settings are immutable
        if(pFloat)
        {
            // copy data - this will get deleted by importData
            slot->def.importData->inputFloat = OGRE_ALLOC_T(float, mTerrainSize*mTerrainSize, MEMCATEGORY_GEOMETRY);
            memcpy(slot->def.importData->inputFloat, pFloat, sizeof(float) * mTerrainSize*mTerrainSize);
        }
        if (layers)
        {
            // copy (held by value)
            slot->def.importData->layerList = *layers;
        }
        slot->def.importData->terrainAlign = mAlignment;
        slot->def.importData->terrainSize = mTerrainSize;
        slot->def.importData->worldSize = mTerrainWorldSize;

    }
    //---------------------------------------------------------------------
    void TerrainGroup::defineTerrain(long x, long y, const String& filename)
    {
        TerrainSlot* slot = getTerrainSlot(x, y, true);

        freeTerrainSlotInstance(slot);

        slot->def.useFilename();
        slot->def.filename = filename;

    }
    //---------------------------------------------------------------------
    void TerrainGroup::loadAllTerrains(bool synchronous /*= false*/)
    {
        // Just a straight iteration - for the numbers involved not worth 
        // keeping a loaded / unloaded list
        for (TerrainSlotMap::iterator i = mTerrainSlots.begin(); i != mTerrainSlots.end(); ++i)
        {
            TerrainSlot* slot = i->second;
            loadTerrainImpl(slot, synchronous);
        }

    }
    //---------------------------------------------------------------------
    void TerrainGroup::saveAllTerrains(bool onlyIfModified, bool replaceFilenames)
    {
        for (TerrainSlotMap::iterator i = mTerrainSlots.begin(); i != mTerrainSlots.end(); ++i)
        {
            TerrainSlot* slot = i->second;
            if (slot->instance)
            {
                Terrain* t = slot->instance;
                if (t->isLoaded() && 
                    (!onlyIfModified || t->isModified()))
                {
                    // Overwrite the file names?
                    if (replaceFilenames)
                        slot->def.filename = generateFilename(slot->x, slot->y);

                    String filename;
                    if (!slot->def.filename.empty())
                        filename = slot->def.filename;
                    else
                        filename = generateFilename(slot->x, slot->y);

                    t->save(filename);

                }
            }
            
        }

    }
    //---------------------------------------------------------------------
    void TerrainGroup::loadTerrain(long x, long y, bool synchronous /*= false*/)
    {
        TerrainSlot* slot = getTerrainSlot(x, y, false);
        if (slot)
        {
            loadTerrainImpl(slot, synchronous);
        }

    }

    void TerrainGroup::loadLegacyTerrain(const String& cfgFilename, long x, long y, bool synchronous)
    {
        ConfigFile cfg;
        cfg.loadFromResourceSystem(cfgFilename, mResourceGroup);
        loadLegacyTerrain(cfg, x, y, synchronous);
    }

    void TerrainGroup::loadLegacyTerrain(const ConfigFile& cfg, long x, long y, bool synchronous)
    {
        OgreAssert(cfg.getSetting("PageSource") == "Heightmap", "only heightmap PageSource supported");
        OgreAssert(cfg.getSetting("PageWorldZ") == cfg.getSetting("PageWorldX"), "terrain must be square");

        mTerrainWorldSize = StringConverter::parseReal(cfg.getSetting("PageWorldX"));
        mTerrainSize = StringConverter::parseInt(cfg.getSetting("PageSize"));

        mAlignment = Terrain::ALIGN_X_Z;
        mOrigin = Vector3(mTerrainWorldSize / 2, 0, mTerrainWorldSize / 2);

        mDefaultImportData.inputScale = StringConverter::parseReal(cfg.getSetting("MaxHeight"));
        mDefaultImportData.maxBatchSize = StringConverter::parseInt(cfg.getSetting("TileSize"));
        mDefaultImportData.minBatchSize = (mDefaultImportData.maxBatchSize - 1)/2 + 1;

        // copy data that would have normally happened on construction
        mDefaultImportData.terrainAlign = mAlignment;
        mDefaultImportData.terrainSize = mTerrainSize;
        mDefaultImportData.worldSize = mTerrainWorldSize;

        const String& worldTexName = cfg.getSetting("WorldTexture");
        if (!worldTexName.empty())
        {
            const String& detailTexName = cfg.getSetting("DetailTexture");
            OgreAssert(!detailTexName.empty(), "DetailTexture must be given");
            Real detailTile = StringConverter::parseReal(cfg.getSetting("DetailTile"), 1);
            mDefaultImportData.layerList.resize(1);
            mDefaultImportData.layerList[0].worldSize =
                (mTerrainWorldSize / mTerrainSize) * (mDefaultImportData.maxBatchSize / detailTile);
            mDefaultImportData.layerList[0].textureNames.push_back(detailTexName);
        }

        // Load terrain from heightmap
        Image img;
        img.load(cfg.getSetting("Heightmap.image"), mResourceGroup);
        defineTerrain(x, y, &img);

        auto profile = dynamic_cast<TerrainMaterialGeneratorA::SM2Profile*>(
            TerrainGlobalOptions::getSingleton().getDefaultMaterialGenerator()->getActiveProfile());

        if(profile)
        {
            profile->setLayerSpecularMappingEnabled(false);
            profile->setLayerNormalMappingEnabled(false);
            profile->setLightmapEnabled(false); // baked into diffusemap
        }

        TerrainSlot* slot = getTerrainSlot(x, y, false);
        loadTerrainImpl(slot, synchronous);

        if (!worldTexName.empty())
        {
            img.load(worldTexName, mResourceGroup);
            slot->instance->setGlobalColourMapEnabled(true, img.getWidth());
            slot->instance->getGlobalColourMap()->loadImage(img);
        }
    }

    //---------------------------------------------------------------------
    void TerrainGroup::loadTerrainImpl(TerrainSlot* slot, bool synchronous)
    {
        if (!slot->instance && 
            (!slot->def.filename.empty() || slot->def.importData))
        {
            // Allocate in main thread so no race conditions
            slot->instance = OGRE_NEW Terrain(mSceneManager);
            slot->instance->setResourceGroup(mResourceGroup);
            // Use shared pool of buffers
            slot->instance->setGpuBufferAllocator(&mBufferAllocator);

            LoadRequest req;
            req.slot = slot;
            req.origin = this;
            std::pair<TerrainPrepareRequestMap::iterator, bool> ret = mTerrainPrepareRequests.emplace(slot, 0);
            assert(ret.second == true);
            WorkQueue::RequestID id =
                Root::getSingleton().getWorkQueue()->addRequest(
                    mWorkQueueChannel, WORKQUEUE_LOAD_REQUEST,
                    req, 0, synchronous);
            if (!synchronous)
                ret.first->second = id;
        }
    }
    //---------------------------------------------------------------------
    void TerrainGroup::increaseLodLevel(long x, long y, bool synchronous /* = false */)
    {
        TerrainSlot* slot = getTerrainSlot(x, y, false);
        if (slot && slot->instance)
        {
            slot->instance->increaseLodLevel(synchronous);
        }
    }
    void TerrainGroup::decreaseLodLevel(long x, long y)
    {
        TerrainSlot* slot = getTerrainSlot(x, y, false);
        if (slot && slot->instance)
        {
            slot->instance->decreaseLodLevel();
        }
    }
    //---------------------------------------------------------------------
    void TerrainGroup::setAutoUpdateLod(TerrainAutoUpdateLod* updater)
    {
        if(mAutoUpdateLod)
            OGRE_DELETE mAutoUpdateLod;
        mAutoUpdateLod = updater;
    }
    //---------------------------------------------------------------------
    void TerrainGroup::autoUpdateLod(long x, long y, bool synchronous, const Any &data)
    {
        if(mAutoUpdateLod)
        {
            TerrainSlot* slot = getTerrainSlot(x, y, synchronous);
            if (slot)
            {
                mAutoUpdateLod->autoUpdateLod(slot->instance, synchronous, data);
            }
        }
    }
    //---------------------------------------------------------------------
    void TerrainGroup::autoUpdateLodAll(bool synchronous, const Any &data)
    {
        if(mAutoUpdateLod)
        {
            for (TerrainSlotMap::iterator i = mTerrainSlots.begin(); i != mTerrainSlots.end(); ++i)
            {
                TerrainSlot* slot = i->second;
                mAutoUpdateLod->autoUpdateLod(slot->instance, synchronous, data);
            }
        }
    }
    //---------------------------------------------------------------------
    size_t TerrainGroup::getNumTerrainPrepareRequests() const
    {
        return mTerrainPrepareRequests.size();
    }
    //---------------------------------------------------------------------
    void TerrainGroup::unloadTerrain(long x, long y)
    {
        freeTerrainSlotInstance(getTerrainSlot(x, y, false));
    }
    //---------------------------------------------------------------------
    void TerrainGroup::removeTerrain(long x, long y)
    {
        uint32 key = packIndex(x, y);
        TerrainSlotMap::iterator i = mTerrainSlots.find(key);
        if (i != mTerrainSlots.end())
        {
            OGRE_DELETE i->second;
            mTerrainSlots.erase(i);
        }

    }
    //---------------------------------------------------------------------
    void TerrainGroup::removeAllTerrains()
    {
        for (TerrainSlotMap::iterator i = mTerrainSlots.begin(); i != mTerrainSlots.end(); ++i)
        {
            OGRE_DELETE i->second;
        }
        mTerrainSlots.clear();
        // Also clear buffer pools, if we're clearing completely may not be representative
        mBufferAllocator.freeAllBuffers();
    }
    //---------------------------------------------------------------------
    TerrainGroup::TerrainSlotDefinition* TerrainGroup::getTerrainDefinition(long x, long y) const
    {
        TerrainSlot* slot = getTerrainSlot(x, y);
        if (slot)
            return &slot->def;
        else
            return 0;

    }
    //---------------------------------------------------------------------
    Terrain* TerrainGroup::getTerrain(long x, long y) const
    {
        TerrainSlot* slot = getTerrainSlot(x, y);
        if (slot)
            return slot->instance;
        else
            return 0;
    }
    //---------------------------------------------------------------------
    float TerrainGroup::getHeightAtWorldPosition(Real x, Real y, Real z, Terrain** ppTerrain /* = 0*/)
    {
        return getHeightAtWorldPosition(Vector3(x, y, z), ppTerrain);

    }
    //---------------------------------------------------------------------
    float TerrainGroup::getHeightAtWorldPosition(const Vector3& pos, Terrain** ppTerrain /*= 0*/)
    {
        long x, y;
        convertWorldPositionToTerrainSlot(pos, &x, &y);
        TerrainSlot* slot = getTerrainSlot(x, y);
        if (slot && slot->instance && slot->instance->isLoaded())
        {
            if (ppTerrain)
                *ppTerrain = slot->instance;
            return slot->instance->getHeightAtWorldPosition(pos);
        }
        else
        {
            if (ppTerrain)
                *ppTerrain = 0;
            return 0;
        }
    }
    //---------------------------------------------------------------------
    TerrainGroup::RayResult TerrainGroup::rayIntersects(const Ray& ray, Real distanceLimit /* = 0*/) const 
    {
        long curr_x, curr_z;
        convertWorldPositionToTerrainSlot(ray.getOrigin(), &curr_x, &curr_z);
        TerrainSlot* slot = getTerrainSlot(curr_x, curr_z);
        RayResult result(false, 0, Vector3::ZERO);

        Vector3 tmp, localRayDir, centreOrigin, offset;
        // get the middle of the current tile
        convertTerrainSlotToWorldPosition(curr_x, curr_z, &centreOrigin);
        offset = ray.getOrigin() - centreOrigin;
        localRayDir = ray.getDirection();
        switch (getAlignment())
        {
        case Terrain::ALIGN_X_Y:
            std::swap(localRayDir.y, localRayDir.z);
            std::swap(offset.y, offset.z);
            break;
        case Terrain::ALIGN_Y_Z:
            // x = z, z = y, y = -x
            tmp.x = localRayDir.z; 
            tmp.z = localRayDir.y; 
            tmp.y = -localRayDir.x; 
            localRayDir = tmp;
            tmp.x = offset.z; 
            tmp.z = offset.y; 
            tmp.y = -offset.x; 
            offset = tmp;
            break;
        case Terrain::ALIGN_X_Z:
            // already in X/Z but values increase in -Z
            localRayDir.z = -localRayDir.z;
            offset.z = -offset.z;
            break;
        }
        // Normalise the offset  based on the world size of a square, and rebase to the bottom left
        offset /= mTerrainWorldSize;
        offset += 0.5f;
        // this is our counter moving away from the 'current' square
        Vector3 inc(Math::Abs(localRayDir.x), Math::Abs(localRayDir.y), Math::Abs(localRayDir.z));
        long xdir = localRayDir.x > 0.0f ? 1 : -1;
        long zdir = localRayDir.z > 0.0f ? 1 : -1;

        // We're always counting from 0 to 1 regardless of what direction we're heading
        if (xdir < 0)
            offset.x = 1.0f - offset.x;
        if (zdir < 0)
            offset.z = 1.0f - offset.z;

        // find next slot
        bool keepSearching = true;
        int numGaps = 0;
        while(keepSearching)
        {
            if (Math::RealEqual(inc.x, 0.0f) && Math::RealEqual(inc.z, 0.0f))
                keepSearching = false;

            while ( (!slot || !slot->instance) && keepSearching)
            {
                ++numGaps;
                /// if we don't find any filled slot in 6 traversals, give up
                if (numGaps > 6)
                {
                    keepSearching = false;
                    break;
                }
                // find next slot
                Vector3 oldoffset = offset;
                while (offset.x < 1.0f && offset.z < 1.0f)
                    offset += inc;
                if (offset.x >= 1.0f && offset.z >= 1.0f)
                {
                    // We crossed a corner, need to figure out which we passed first
                    Real diffz = 1.0f - oldoffset.z;
                    Real diffx = 1.0f - oldoffset.x;
                    Real distz = diffz / inc.z;
                    Real distx = diffx / inc.x;
                    if (distx < distz)
                    {
                        curr_x += xdir;
                        offset.x -= 1.0f;
                    }
                    else
                    {
                        curr_z += zdir;
                        offset.z -= 1.0f;
                    }

                }
                else if (offset.x >= 1.0f)
                {
                    curr_x += xdir;
                    offset.x -= 1.0f;
                }
                else if (offset.z >= 1.0f)
                {
                    curr_z += zdir;
                    offset.z -= 1.0f;
                }
                if (distanceLimit)
                {
                    Vector3 worldPos;
                    convertTerrainSlotToWorldPosition(curr_x, curr_z, &worldPos);
                    if (ray.getOrigin().distance(worldPos) > distanceLimit)
                    {
                        keepSearching = false;
                        break;
                    }
                }
                slot = getTerrainSlot(curr_x, curr_z);
            }
            if (slot && slot->instance)
            {
                numGaps = 0;
                // don't cascade into neighbours
                std::pair<bool, Vector3> raypair = slot->instance->rayIntersects(ray, false, distanceLimit);
                if (raypair.first)
                {
                    keepSearching = false;
                    result.hit = true;
                    result.terrain = slot->instance;
                    result.position = raypair.second;
                }
                else
                {
                    // not this one, trigger search for another slot
                    slot = 0;
                }
            }

        }


        return result;

    }
    //---------------------------------------------------------------------
    void TerrainGroup::boxIntersects(const AxisAlignedBox& box, TerrainList* resultList) const
    {
        resultList->clear();
        // Much simpler test
        for (TerrainSlotMap::const_iterator i = mTerrainSlots.begin(); i != mTerrainSlots.end(); ++i)
        {
            if (i->second->instance && box.intersects(i->second->instance->getWorldAABB()))
                resultList->push_back(i->second->instance);
        }

    }
    //---------------------------------------------------------------------
    void TerrainGroup::sphereIntersects(const Sphere& sphere, TerrainList* resultList) const
    {
        resultList->clear();
        // Much simpler test
        for (TerrainSlotMap::const_iterator i = mTerrainSlots.begin(); i != mTerrainSlots.end(); ++i)
        {
            if (i->second->instance && sphere.intersects(i->second->instance->getWorldAABB()))
                resultList->push_back(i->second->instance);
        }

    }
    //---------------------------------------------------------------------
    void TerrainGroup::convertWorldPositionToTerrainSlot(const Vector3& pos, long *x, long *y) const
    {
        // 0,0 terrain is centred at the origin
        Vector3 terrainPos;
        // convert to standard xy base (z up), make relative to origin
        Terrain::convertWorldToTerrainAxes(mAlignment, pos - mOrigin, &terrainPos);

        Real offset = mTerrainWorldSize * 0.5f;
        terrainPos.x += offset;
        terrainPos.y += offset;

        *x = static_cast<long>(std::floor(terrainPos.x / mTerrainWorldSize));
        *y = static_cast<long>(std::floor(terrainPos.y / mTerrainWorldSize));


    }
    //---------------------------------------------------------------------
    void TerrainGroup::convertTerrainSlotToWorldPosition(long x, long y, Vector3* pos) const
    {
        Vector3 terrainPos(x * mTerrainWorldSize, y * mTerrainWorldSize, 0);

        Terrain::convertTerrainToWorldAxes(mAlignment, terrainPos, pos);
        *pos += mOrigin;


    }
    //---------------------------------------------------------------------
    bool TerrainGroup::isDerivedDataUpdateInProgress() const
    {
        for (TerrainSlotMap::const_iterator i = mTerrainSlots.begin(); i != mTerrainSlots.end(); ++i)
        {
            if (i->second->instance && i->second->instance->isDerivedDataUpdateInProgress())
                return true;
        }
        return false;
    }
    //---------------------------------------------------------------------
    bool TerrainGroup::canHandleRequest(const WorkQueue::Request* req, const WorkQueue* srcQ)
    {
        LoadRequest lreq = any_cast<LoadRequest>(req->getData());
        // only deal with own requests
        if (lreq.origin != this)
            return false;
        else
            return RequestHandler::canHandleRequest(req, srcQ);

    }
    //---------------------------------------------------------------------
    WorkQueue::Response* TerrainGroup::handleRequest(const WorkQueue::Request* req, const WorkQueue* srcQ)
    {
        LoadRequest lreq = any_cast<LoadRequest>(req->getData());

        TerrainSlotDefinition& def = lreq.slot->def;
        Terrain* t = lreq.slot->instance;
        assert(t && "Terrain instance should have been constructed in the main thread");
        WorkQueue::Response* response = 0;
        try
        {
            if (!def.filename.empty())
                t->prepare(def.filename);
            else
            {
                assert(def.importData && "No import data or file name");
                t->prepare(*def.importData);
                // if this worked, we can destroy the input data to save space
                def.freeImportData();
            }
            response = OGRE_NEW WorkQueue::Response(req, true, Any());
        }
        catch (Exception& e)
        {
            // oops
            response = OGRE_NEW WorkQueue::Response(req, false, Any(), 
                e.getFullDescription());
        }

        return response;


    }
    //---------------------------------------------------------------------
    bool TerrainGroup::canHandleResponse(const WorkQueue::Response* res, const WorkQueue* srcQ)
    {
        LoadRequest lreq = any_cast<LoadRequest>(res->getRequest()->getData());
        // only deal with own requests
        if (lreq.origin != this)
            return false;
        else
            return true;

    }
    //---------------------------------------------------------------------
    void TerrainGroup::handleResponse(const WorkQueue::Response* res, const WorkQueue* srcQ)
    {
        // Data was already deleted so nothing we can do anymore.
        if (res->getRequest()->getAborted())
            return;

        // No response data, just request
        LoadRequest lreq = any_cast<LoadRequest>(res->getRequest()->getData());

        TerrainPrepareRequestMap::iterator it = mTerrainPrepareRequests.find(lreq.slot);

        // This slot was scheduled to be deleted.
        if (it == mTerrainPrepareRequests.end())
        {
            freeTerrainSlotInstance(lreq.slot);
            return;
        }
        else
        {
            mTerrainPrepareRequests.erase(it);
        }

        if (res->succeeded())
        {
            TerrainSlot* slot = lreq.slot;
            Terrain* terrain = slot->instance;
            if (terrain)
            {
                // do final load now we've prepared in the background
                // we must set the position
                terrain->setPosition(getTerrainSlotPosition(slot->x, slot->y));

                // the LOD will be auto-updated, then load lowest LOD
                if(mAutoUpdateLod)
                    terrain->load(-1,false);
                else
                    terrain->load(0,true);

                // hook up with neighbours
                for (int i = -1; i <= 1; ++i)
                {
                    for (int j = -1; j <= 1; ++j)
                    {
                        if (i != 0 || j != 0)
                            connectNeighbour(slot, i, j);
                    }

                }
            }
        }
        else
        {
            // oh dear
            LogManager::getSingleton().stream(LML_CRITICAL) <<
                "We failed to prepare the terrain at (" << lreq.slot->x << ", " <<
                lreq.slot->y <<") with the error '" << res->getMessages() << "'";
            freeTerrainSlotInstance(lreq.slot);
        }

    }
    //---------------------------------------------------------------------
    void TerrainGroup::connectNeighbour(TerrainSlot* slot, long offsetx, long offsety)
    {
        TerrainSlot* neighbourSlot = getTerrainSlot(slot->x + offsetx, slot->y + offsety);
        if (neighbourSlot && neighbourSlot->instance && neighbourSlot->instance->isLoaded())
        {
            // reclaculate if imported
            slot->instance->setNeighbour(Terrain::getNeighbourIndex(offsetx, offsety), neighbourSlot->instance, 
                slot->def.importData != 0);
        }
    }
    //---------------------------------------------------------------------
    uint32 TerrainGroup::packIndex(long x, long y) const
    {
        // Convert to signed 16-bit so sign bit is in bit 15
        int16 xs16 = static_cast<int16>(x);
        int16 ys16 = static_cast<int16>(y);

        // convert to unsigned because we do not want to propagate sign bit to 32-bits
        uint16 x16 = static_cast<uint16>(xs16);
        uint16 y16 = static_cast<uint16>(ys16);

        uint32 key = 0;
        key = (x16 << 16) | y16;

        return key;


    }
    //---------------------------------------------------------------------
    void TerrainGroup::unpackIndex(uint32 key, long *x, long *y)
    {
        // inverse of packIndex
        // unsigned versions
        uint16 y16 = static_cast<uint16>(key & 0xFFFF);
        uint16 x16 = static_cast<uint16>((key >> 16) & 0xFFFF);

        *x = static_cast<int16>(x16);
        *y = static_cast<int16>(y16);

    }

    //---------------------------------------------------------------------
    String TerrainGroup::generateFilename(long x, long y) const
    {
        StringStream str;
        str << mFilenamePrefix << "_" << 
            std::setw(8) << std::setfill('0') << std::hex << packIndex(x, y) << 
            "." << mFilenameExtension;
        return str.str();
    }
    //---------------------------------------------------------------------
    Vector3 TerrainGroup::getTerrainSlotPosition(long x, long y)
    {
        Vector3 pos;
        convertTerrainSlotToWorldPosition(x, y, &pos);
        return pos;
    }
    //---------------------------------------------------------------------
    TerrainGroup::TerrainSlot* TerrainGroup::getTerrainSlot(long x, long y, bool createIfMissing)
    {
        uint32 key = packIndex(x, y);
        TerrainSlotMap::iterator i = mTerrainSlots.find(key);
        if (i != mTerrainSlots.end())
            return i->second;
        else if (createIfMissing)
        {
            TerrainSlot* slot = OGRE_NEW TerrainSlot(x, y);
            mTerrainSlots[key] = slot;
            return slot;
        }
        return 0;
    }
    //---------------------------------------------------------------------
    TerrainGroup::TerrainSlot* TerrainGroup::getTerrainSlot(long x, long y) const
    {
        uint32 key = packIndex(x, y);
        TerrainSlotMap::const_iterator i = mTerrainSlots.find(key);
        if (i != mTerrainSlots.end())
            return i->second;
        else
            return 0;

    }
    //---------------------------------------------------------------------
    void TerrainGroup::freeTerrainSlotInstance(TerrainSlot* slot)
    {
        if (!slot)
            return;

        TerrainPrepareRequestMap::iterator it = mTerrainPrepareRequests.find(slot);

        // Terrain was in load request so we need to schedule the deletion see handleResponse().
        if (it != mTerrainPrepareRequests.end())
        {
            WorkQueue::RequestID id = it->second;
            mTerrainPrepareRequests.erase(it);

            // We can free immediately since this slot was aborted before it could've been processed.
            if (Root::getSingleton().getWorkQueue()->abortPendingRequest(id))
                slot->freeInstance();
        }
        else
        {
            slot->freeInstance();
        }
    }
    //---------------------------------------------------------------------
    void TerrainGroup::freeTemporaryResources()
    {
        for (TerrainSlotMap::iterator i = mTerrainSlots.begin(); i != mTerrainSlots.end(); ++i)
        {
            if (i->second->instance)
                i->second->instance->freeTemporaryResources();
        }

    }
    //---------------------------------------------------------------------
    void TerrainGroup::update(bool synchronous)
    {
        for (TerrainSlotMap::iterator i = mTerrainSlots.begin(); i != mTerrainSlots.end(); ++i)
        {
            if (i->second->instance)
                i->second->instance->update();
        }

    }
    //---------------------------------------------------------------------
    void TerrainGroup::updateGeometry()
    {
        for (TerrainSlotMap::iterator i = mTerrainSlots.begin(); i != mTerrainSlots.end(); ++i)
        {
            if (i->second->instance)
                i->second->instance->updateGeometry();
        }

    }
    //---------------------------------------------------------------------
    void TerrainGroup::updateDerivedData(bool synchronous, uint8 typeMask)
    {
        for (TerrainSlotMap::iterator i = mTerrainSlots.begin(); i != mTerrainSlots.end(); ++i)
        {
            if (i->second->instance)
                i->second->instance->updateDerivedData();
        }

    }
    //---------------------------------------------------------------------
    TerrainGroup::TerrainIterator TerrainGroup::getTerrainIterator()
    {
        return TerrainIterator(mTerrainSlots.begin(), mTerrainSlots.end());
    }
    //---------------------------------------------------------------------
    TerrainGroup::ConstTerrainIterator TerrainGroup::getTerrainIterator() const
    {
        return ConstTerrainIterator(mTerrainSlots.begin(), mTerrainSlots.end());
    }
    //---------------------------------------------------------------------
    void TerrainGroup::saveGroupDefinition(const String& filename)
    {
        DataStreamPtr stream = Root::getSingleton().createFileStream(filename, 
            getResourceGroup(), true);
        StreamSerialiser ser(stream);
        saveGroupDefinition(ser);
    }
    //---------------------------------------------------------------------
    void TerrainGroup::saveGroupDefinition(StreamSerialiser& ser)
    {
        ser.writeChunkBegin(CHUNK_ID, CHUNK_VERSION);
        // Base details
        ser.write(&mAlignment);
        ser.write(&mTerrainSize);
        ser.write(&mTerrainWorldSize);
        ser.write(&mFilenamePrefix);
        ser.write(&mFilenameExtension);
        ser.write(&mResourceGroup);
        ser.write(&mOrigin);

        uint32 autoUpdateLodStrategyId = (mAutoUpdateLod) ? mAutoUpdateLod->getStrategyId() : 0;
        ser.write(&autoUpdateLodStrategyId);

        // Default import settings (those not duplicated by the above)
        ser.write(&mDefaultImportData.constantHeight);
        ser.write(&mDefaultImportData.inputBias);
        ser.write(&mDefaultImportData.inputScale);
        ser.write(&mDefaultImportData.maxBatchSize);
        ser.write(&mDefaultImportData.minBatchSize);
        Terrain::writeLayerDeclaration(mDefaultImportData.layerDeclaration, ser);
        Terrain::writeLayerInstanceList(mDefaultImportData.layerList, ser);


        ser.writeChunkEnd(CHUNK_ID);
    }
    //---------------------------------------------------------------------
    void TerrainGroup::loadGroupDefinition(const String& filename)
    {
        DataStreamPtr stream = Root::getSingleton().openFileStream(filename, 
            getResourceGroup());
        StreamSerialiser ser(stream);
        loadGroupDefinition(ser);
    }
    //---------------------------------------------------------------------
    void TerrainGroup::loadGroupDefinition(StreamSerialiser& ser)
    {
        if (!ser.readChunkBegin(CHUNK_ID, CHUNK_VERSION))
            OGRE_EXCEPT(Exception::ERR_ITEM_NOT_FOUND, "Stream does not contain TerrainGroup data");

        // Base details
        ser.read(&mAlignment);
        ser.read(&mTerrainSize);
        ser.read(&mTerrainWorldSize);
        ser.read(&mFilenamePrefix);
        ser.read(&mFilenameExtension);
        ser.read(&mResourceGroup);
        ser.read(&mOrigin);
        uint32 autoUpdateLodStrategyId;
        ser.read(&autoUpdateLodStrategyId);
        mAutoUpdateLod = TerrainAutoUpdateLodFactory::getAutoUpdateLod( autoUpdateLodStrategyId );

        // Default import settings (those not duplicated by the above)
        ser.read(&mDefaultImportData.constantHeight);
        ser.read(&mDefaultImportData.inputBias);
        ser.read(&mDefaultImportData.inputScale);
        ser.read(&mDefaultImportData.maxBatchSize);
        ser.read(&mDefaultImportData.minBatchSize);
        Terrain::readLayerDeclaration(ser, mDefaultImportData.layerDeclaration);
        Terrain::readLayerInstanceList(ser, mDefaultImportData.layerDeclaration.samplers.size(), 
            mDefaultImportData.layerList);

        // copy data that would have normally happened on construction
        mDefaultImportData.terrainAlign = mAlignment;
        mDefaultImportData.terrainSize = mTerrainSize;
        mDefaultImportData.worldSize = mTerrainWorldSize;
        mDefaultImportData.deleteInputData = true;

        ser.readChunkEnd(CHUNK_ID);


    }
    //---------------------------------------------------------------------
    void TerrainGroup::setTerrainWorldSize(Real newWorldSize)
    {
        if (newWorldSize != mTerrainWorldSize)
        {
            mTerrainWorldSize = newWorldSize;
            for (TerrainSlotMap::iterator i = mTerrainSlots.begin(); i != mTerrainSlots.end(); ++i)
            {
                if (i->second->instance)
                {
                    i->second->instance->setWorldSize(newWorldSize);
                    i->second->instance->setPosition(getTerrainSlotPosition(i->second->x, i->second->y));
                }
            }
        }
    }
    //---------------------------------------------------------------------
    void TerrainGroup::setTerrainSize(uint16 newTerrainSize)
    {
        if (newTerrainSize != mTerrainSize)
        {
            mTerrainSize = newTerrainSize;
            for (TerrainSlotMap::iterator i = mTerrainSlots.begin(); i != mTerrainSlots.end(); ++i)
            {
                if (i->second->instance)
                {
                    i->second->instance->setSize(newTerrainSize);
                }
            }
        }
    }
    //---------------------------------------------------------------------
    //---------------------------------------------------------------------
    TerrainGroup::TerrainSlotDefinition::~TerrainSlotDefinition()
    {
        freeImportData();
    }
    //---------------------------------------------------------------------
    void TerrainGroup::TerrainSlotDefinition::freeImportData()
    {
        OGRE_DELETE_T(importData, ImportData, MEMCATEGORY_GEOMETRY);
        importData = 0;
    }
    //---------------------------------------------------------------------
    void TerrainGroup::TerrainSlotDefinition::useImportData()
    {
        filename.clear();
        freeImportData();
        importData = OGRE_NEW_T(Terrain::ImportData, MEMCATEGORY_GEOMETRY);
        // we're going to own all the data in the def
        importData->deleteInputData = true;

    }
    //---------------------------------------------------------------------
    void TerrainGroup::TerrainSlotDefinition::useFilename()
    {
        freeImportData();
    }
    //---------------------------------------------------------------------
    //---------------------------------------------------------------------
    TerrainGroup::TerrainSlot::~TerrainSlot()
    {
        freeInstance();
    }
    //---------------------------------------------------------------------
    void TerrainGroup::TerrainSlot::freeInstance()
    {
        OGRE_DELETE instance;
        instance = 0;
    }

}

