From e25fa6c157e97baa6e49de90163da629ea29c0ed Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 29 Jun 2014 02:42:36 +0200 Subject: [PATCH] Refactor non-distant land terrain path to a grid based implementation (Fixes #1562) --- apps/openmw/CMakeLists.txt | 2 +- apps/openmw/mwrender/renderingmanager.cpp | 18 +- apps/openmw/mwrender/terraingrid.cpp | 165 +++++++++ apps/openmw/mwrender/terraingrid.hpp | 75 +++++ apps/openmw/mwrender/terrainstorage.hpp | 4 +- components/CMakeLists.txt | 2 +- components/terrain/backgroundloader.cpp | 0 components/terrain/chunk.cpp | 14 +- components/terrain/chunk.hpp | 10 +- components/terrain/defaultworld.cpp | 315 +++++++++++++++++ components/terrain/defaultworld.hpp | 156 +++++++++ components/terrain/material.cpp | 4 +- components/terrain/material.hpp | 7 +- components/terrain/quadtreenode.cpp | 16 +- components/terrain/quadtreenode.hpp | 8 +- components/terrain/world.cpp | 390 +++------------------- components/terrain/world.hpp | 134 ++------ 17 files changed, 825 insertions(+), 495 deletions(-) create mode 100644 apps/openmw/mwrender/terraingrid.cpp create mode 100644 apps/openmw/mwrender/terraingrid.hpp create mode 100644 components/terrain/backgroundloader.cpp create mode 100644 components/terrain/defaultworld.cpp create mode 100644 components/terrain/defaultworld.hpp diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 7c047adef..5c5b0d16c 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -15,7 +15,7 @@ add_openmw_dir (mwrender renderingmanager debugging sky camera animation npcanimation creatureanimation activatoranimation actors objects renderinginterface localmap occlusionquery water shadows characterpreview globalmap videoplayer ripplesimulation refraction - terrainstorage renderconst effectmanager weaponanimation + terrainstorage renderconst effectmanager weaponanimation terraingrid ) add_openmw_dir (mwinput diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index ee1cbfe5d..23edb3a7f 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -22,7 +22,7 @@ #include #include -#include +#include #include "../mwworld/esmstore.hpp" #include "../mwworld/class.hpp" @@ -45,6 +45,7 @@ #include "globalmap.hpp" #include "terrainstorage.hpp" #include "effectmanager.hpp" +#include "terraingrid.hpp" using namespace MWRender; using namespace Ogre; @@ -223,6 +224,9 @@ MWRender::Camera* RenderingManager::getCamera() const void RenderingManager::removeCell (MWWorld::CellStore *store) { + if (store->isExterior()) + mTerrain->unloadCell(store->getCell()->getGridX(), store->getCell()->getGridY()); + mLocalMap->saveFogOfWar(store); mObjects->removeCell(store); mActors->removeCell(store); @@ -241,6 +245,9 @@ bool RenderingManager::toggleWater() void RenderingManager::cellAdded (MWWorld::CellStore *store) { + if (store->isExterior()) + mTerrain->loadCell(store->getCell()->getGridX(), store->getCell()->getGridY()); + mObjects->buildStaticGeometry (*store); sh::Factory::getInstance().unloadUnreferencedMaterials(); mDebugging->cellAdded(store); @@ -1039,9 +1046,12 @@ void RenderingManager::enableTerrain(bool enable) { if (!mTerrain) { - mTerrain = new Terrain::World(mRendering.getScene(), new MWRender::TerrainStorage(), RV_Terrain, - Settings::Manager::getBool("distant land", "Terrain"), - Settings::Manager::getBool("shader", "Terrain"), Terrain::Align_XY, 1, 64); + if (Settings::Manager::getBool("distant land", "Terrain")) + mTerrain = new Terrain::DefaultWorld(mRendering.getScene(), new MWRender::TerrainStorage(), RV_Terrain, + Settings::Manager::getBool("shader", "Terrain"), Terrain::Align_XY, 1, 64); + else + mTerrain = new MWRender::TerrainGrid(mRendering.getScene(), new MWRender::TerrainStorage(), RV_Terrain, + Settings::Manager::getBool("shader", "Terrain"), Terrain::Align_XY); mTerrain->applyMaterials(Settings::Manager::getBool("enabled", "Shadows"), Settings::Manager::getBool("split", "Shadows")); mTerrain->update(mRendering.getCamera()->getRealPosition()); diff --git a/apps/openmw/mwrender/terraingrid.cpp b/apps/openmw/mwrender/terraingrid.cpp new file mode 100644 index 000000000..4688fbfd9 --- /dev/null +++ b/apps/openmw/mwrender/terraingrid.cpp @@ -0,0 +1,165 @@ +#include "terraingrid.hpp" + +#include +#include +#include + +#include "../mwbase/environment.hpp" +#include "../mwbase/world.hpp" + +#include + +namespace MWRender +{ + +TerrainGrid::TerrainGrid(Ogre::SceneManager *sceneMgr, Terrain::Storage *storage, int visibilityFlags, bool shaders, Terrain::Alignment align) + : Terrain::World(sceneMgr, storage, visibilityFlags, shaders, align) + , mVisible(true) +{ + mRootNode = mSceneMgr->getRootSceneNode()->createChildSceneNode(); +} + +TerrainGrid::~TerrainGrid() +{ + while (!mGrid.empty()) + { + unloadCell(mGrid.begin()->first.first, mGrid.begin()->first.second); + } + + mSceneMgr->destroySceneNode(mRootNode); +} + +void TerrainGrid::update(const Ogre::Vector3 &cameraPos) +{ +} + +void TerrainGrid::loadCell(int x, int y) +{ + if (mGrid.find(std::make_pair(x, y)) != mGrid.end()) + return; // already loaded + + Ogre::Vector2 center(x+0.5, y+0.5); + float minH, maxH; + if (!mStorage->getMinMaxHeights(1, center, minH, maxH)) + return; // no terrain defined + + Ogre::Vector3 min (-0.5*mStorage->getCellWorldSize(), + -0.5*mStorage->getCellWorldSize(), + minH); + Ogre::Vector3 max (0.5*mStorage->getCellWorldSize(), + 0.5*mStorage->getCellWorldSize(), + maxH); + + Ogre::AxisAlignedBox bounds(min, max); + + GridElement element; + + Ogre::Vector2 worldCenter = center*mStorage->getCellWorldSize(); + element.mSceneNode = mRootNode->createChildSceneNode(Ogre::Vector3(worldCenter.x, worldCenter.y, 0)); + + std::vector positions; + std::vector normals; + std::vector colours; + mStorage->fillVertexBuffers(0, 1, center, mAlign, positions, normals, colours); + + element.mChunk = new Terrain::Chunk(mCache.getUVBuffer(), bounds, positions, normals, colours); + element.mChunk->setIndexBuffer(mCache.getIndexBuffer(0)); + + std::vector blendmaps; + std::vector layerList; + mStorage->getBlendmaps(1, center, mShaders, blendmaps, layerList); + + element.mMaterialGenerator.setLayerList(layerList); + + // upload blendmaps to GPU + std::vector blendTextures; + for (std::vector::const_iterator it = blendmaps.begin(); it != blendmaps.end(); ++it) + { + static int count=0; + Ogre::TexturePtr map = Ogre::TextureManager::getSingleton().createManual("terrain/blend/" + + Ogre::StringConverter::toString(count++), Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, + Ogre::TEX_TYPE_2D, it->getWidth(), it->getHeight(), 0, it->format); + + Ogre::DataStreamPtr stream(new Ogre::MemoryDataStream(it->data, it->getWidth()*it->getHeight()*Ogre::PixelUtil::getNumElemBytes(it->format), true)); + map->loadRawData(stream, it->getWidth(), it->getHeight(), it->format); + blendTextures.push_back(map); + } + + element.mMaterialGenerator.setBlendmapList(blendTextures); + + element.mSceneNode->attachObject(element.mChunk); + updateMaterial(element); + + mGrid[std::make_pair(x,y)] = element; +} + +void TerrainGrid::updateMaterial(GridElement &element) +{ + element.mMaterialGenerator.enableShadows(getShadowsEnabled()); + element.mMaterialGenerator.enableSplitShadows(getSplitShadowsEnabled()); + element.mChunk->setMaterial(element.mMaterialGenerator.generate()); +} + +void TerrainGrid::unloadCell(int x, int y) +{ + Grid::iterator it = mGrid.find(std::make_pair(x,y)); + if (it == mGrid.end()) + return; + + GridElement& element = it->second; + delete element.mChunk; + element.mChunk = NULL; + + const std::vector& blendmaps = element.mMaterialGenerator.getBlendmapList(); + for (std::vector::const_iterator it = blendmaps.begin(); it != blendmaps.end(); ++it) + Ogre::TextureManager::getSingleton().remove((*it)->getName()); + + mSceneMgr->destroySceneNode(element.mSceneNode); + element.mSceneNode = NULL; + + mGrid.erase(it); +} + +void TerrainGrid::applyMaterials(bool shadows, bool splitShadows) +{ + mShadows = shadows; + mSplitShadows = splitShadows; + for (Grid::iterator it = mGrid.begin(); it != mGrid.end(); ++it) + { + updateMaterial(it->second); + } +} + +bool TerrainGrid::getVisible() +{ + return mVisible; +} + +void TerrainGrid::setVisible(bool visible) +{ + mVisible = visible; + mRootNode->setVisible(visible); +} + +Ogre::AxisAlignedBox TerrainGrid::getWorldBoundingBox (const Ogre::Vector2& center) +{ + int cellX, cellY; + MWBase::Environment::get().getWorld()->positionToIndex(center.x, center.y, cellX, cellY); + + Grid::iterator it = mGrid.find(std::make_pair(cellX, cellY)); + if (it == mGrid.end()) + return Ogre::AxisAlignedBox::BOX_NULL; + + Terrain::Chunk* chunk = it->second.mChunk; + Ogre::SceneNode* node = it->second.mSceneNode; + Ogre::AxisAlignedBox box = chunk->getBoundingBox(); + box = Ogre::AxisAlignedBox(box.getMinimum() + node->getPosition(), box.getMaximum() + node->getPosition()); + return box; +} + +void TerrainGrid::syncLoad() +{ + +} + +} diff --git a/apps/openmw/mwrender/terraingrid.hpp b/apps/openmw/mwrender/terraingrid.hpp new file mode 100644 index 000000000..1b5250dcf --- /dev/null +++ b/apps/openmw/mwrender/terraingrid.hpp @@ -0,0 +1,75 @@ +#ifndef OPENMW_MWRENDER_TERRAINGRID_H +#define OPENMW_MWRENDER_TERRAINGRID_H + +#include +#include + +namespace Terrain +{ + class Chunk; +} + +namespace MWRender +{ + + struct GridElement + { + Ogre::SceneNode* mSceneNode; + + Terrain::MaterialGenerator mMaterialGenerator; + + Terrain::Chunk* mChunk; + }; + + /// @brief Simple terrain implementation that loads cells in a grid, with no LOD + class TerrainGrid : public Terrain::World + { + public: + /// @note takes ownership of \a storage + /// @param sceneMgr scene manager to use + /// @param storage Storage instance to get terrain data from (heights, normals, colors, textures..) + /// @param visbilityFlags visibility flags for the created meshes + /// @param shaders Whether to use splatting shader, or multi-pass fixed function splatting. Shader is usually + /// faster so this is just here for compatibility. + /// @param align The align of the terrain, see Alignment enum + TerrainGrid(Ogre::SceneManager* sceneMgr, + Terrain::Storage* storage, int visibilityFlags, bool shaders, Terrain::Alignment align); + ~TerrainGrid(); + + /// Update chunk LODs according to this camera position + virtual void update (const Ogre::Vector3& cameraPos); + + virtual void loadCell(int x, int y); + virtual void unloadCell(int x, int y); + + /// Get the world bounding box of a chunk of terrain centered at \a center + virtual Ogre::AxisAlignedBox getWorldBoundingBox (const Ogre::Vector2& center); + + /// Show or hide the whole terrain + /// @note this setting may be invalidated once you call Terrain::update, so do not call it while the terrain should be hidden + virtual void setVisible(bool visible); + virtual bool getVisible(); + + /// Recreate materials used by terrain chunks. This should be called whenever settings of + /// the material factory are changed. (Relying on the factory to update those materials is not + /// enough, since turning a feature on/off can change the number of texture units available for layer/blend + /// textures, and to properly respond to this we may need to change the structure of the material, such as + /// adding or removing passes. This can only be achieved by a full rebuild.) + virtual void applyMaterials(bool shadows, bool splitShadows); + + /// Wait until all background loading is complete. + virtual void syncLoad(); + + private: + void updateMaterial (GridElement& element); + + typedef std::map, GridElement> Grid; + Grid mGrid; + + Ogre::SceneNode* mRootNode; + bool mVisible; + }; + +} + +#endif diff --git a/apps/openmw/mwrender/terrainstorage.hpp b/apps/openmw/mwrender/terrainstorage.hpp index b5e6012f3..c1fb74445 100644 --- a/apps/openmw/mwrender/terrainstorage.hpp +++ b/apps/openmw/mwrender/terrainstorage.hpp @@ -46,7 +46,7 @@ namespace MWRender /// Create textures holding layer blend values for a terrain chunk. /// @note The terrain chunk shouldn't be larger than one cell since otherwise we might /// have to do a ridiculous amount of different layers. For larger chunks, composite maps should be used. - /// @note May be called from *one* background thread. + /// @note May be called from background threads. /// @param chunkSize size of the terrain chunk in cell units /// @param chunkCenter center of the chunk in cell units /// @param pack Whether to pack blend values for up to 4 layers into one texture (one in each channel) - @@ -62,7 +62,7 @@ namespace MWRender /// This variant is provided to eliminate the overhead of virtual function calls when retrieving a large number of blendmaps at once. /// @note The terrain chunks shouldn't be larger than one cell since otherwise we might /// have to do a ridiculous amount of different layers. For larger chunks, composite maps should be used. - /// @note May be called from *one* background thread. + /// @note May be called from background threads. /// @param nodes A collection of nodes for which to retrieve the aforementioned data /// @param out Output vector /// @param pack Whether to pack blend values for up to 4 layers into one texture (one in each channel) - diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 060e3472d..b8ebb84b1 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -72,7 +72,7 @@ add_component_dir (translation add_definitions(-DTERRAIN_USE_SHADER=1) add_component_dir (terrain - quadtreenode chunk world storage material buffercache defs + quadtreenode chunk world defaultworld storage material buffercache defs backgroundloader ) add_component_dir (loadinglistener diff --git a/components/terrain/backgroundloader.cpp b/components/terrain/backgroundloader.cpp new file mode 100644 index 000000000..e69de29bb diff --git a/components/terrain/chunk.cpp b/components/terrain/chunk.cpp index bb8710b87..9c60ee017 100644 --- a/components/terrain/chunk.cpp +++ b/components/terrain/chunk.cpp @@ -8,19 +8,17 @@ #include - -#include "world.hpp" // FIXME: for LoadResponseData, move to backgroundloader.hpp - namespace Terrain { - Chunk::Chunk(Ogre::HardwareVertexBufferSharedPtr uvBuffer, const Ogre::AxisAlignedBox& bounds, const LoadResponseData& data) + Chunk::Chunk(Ogre::HardwareVertexBufferSharedPtr uvBuffer, const Ogre::AxisAlignedBox& bounds, + const std::vector& positions, const std::vector& normals, const std::vector& colours) : mBounds(bounds) , mOwnMaterial(false) { mVertexData = OGRE_NEW Ogre::VertexData; mVertexData->vertexStart = 0; - mVertexData->vertexCount = data.mPositions.size()/3; + mVertexData->vertexCount = positions.size()/3; // Set up the vertex declaration, which specifies the info for each vertex (normals, colors, UVs, etc) Ogre::VertexDeclaration* vertexDecl = mVertexData->vertexDeclaration; @@ -48,9 +46,9 @@ namespace Terrain Ogre::HardwareVertexBufferSharedPtr colourBuffer = mgr->createVertexBuffer(Ogre::VertexElement::getTypeSize(Ogre::VET_COLOUR), mVertexData->vertexCount, Ogre::HardwareBuffer::HBU_STATIC); - vertexBuffer->writeData(0, vertexBuffer->getSizeInBytes(), &data.mPositions[0], true); - normalBuffer->writeData(0, normalBuffer->getSizeInBytes(), &data.mNormals[0], true); - colourBuffer->writeData(0, colourBuffer->getSizeInBytes(), &data.mColours[0], true); + vertexBuffer->writeData(0, vertexBuffer->getSizeInBytes(), &positions[0], true); + normalBuffer->writeData(0, normalBuffer->getSizeInBytes(), &normals[0], true); + colourBuffer->writeData(0, colourBuffer->getSizeInBytes(), &colours[0], true); mVertexData->vertexBufferBinding->setBinding(0, vertexBuffer); mVertexData->vertexBufferBinding->setBinding(1, normalBuffer); diff --git a/components/terrain/chunk.hpp b/components/terrain/chunk.hpp index 9550b3046..9b2ed76ac 100644 --- a/components/terrain/chunk.hpp +++ b/components/terrain/chunk.hpp @@ -7,16 +7,16 @@ namespace Terrain { - class BufferCache; - struct LoadResponseData; - /** - * @brief Renders a chunk of terrain, either using alpha splatting or a composite map. + * @brief A movable object representing a chunk of terrain. */ class Chunk : public Ogre::Renderable, public Ogre::MovableObject { public: - Chunk (Ogre::HardwareVertexBufferSharedPtr uvBuffer, const Ogre::AxisAlignedBox& bounds, const LoadResponseData& data); + Chunk (Ogre::HardwareVertexBufferSharedPtr uvBuffer, const Ogre::AxisAlignedBox& bounds, + const std::vector& positions, + const std::vector& normals, + const std::vector& colours); virtual ~Chunk(); diff --git a/components/terrain/defaultworld.cpp b/components/terrain/defaultworld.cpp new file mode 100644 index 000000000..943658235 --- /dev/null +++ b/components/terrain/defaultworld.cpp @@ -0,0 +1,315 @@ +#include "defaultworld.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#include "storage.hpp" +#include "quadtreenode.hpp" + +namespace +{ + + bool isPowerOfTwo(int x) + { + return ( (x > 0) && ((x & (x - 1)) == 0) ); + } + + int nextPowerOfTwo (int v) + { + if (isPowerOfTwo(v)) return v; + int depth=0; + while(v) + { + v >>= 1; + depth++; + } + return 1 << depth; + } + + Terrain::QuadTreeNode* findNode (const Ogre::Vector2& center, Terrain::QuadTreeNode* node) + { + if (center == node->getCenter()) + return node; + + if (center.x > node->getCenter().x && center.y > node->getCenter().y) + return findNode(center, node->getChild(Terrain::NE)); + else if (center.x > node->getCenter().x && center.y < node->getCenter().y) + return findNode(center, node->getChild(Terrain::SE)); + else if (center.x < node->getCenter().x && center.y > node->getCenter().y) + return findNode(center, node->getChild(Terrain::NW)); + else //if (center.x < node->getCenter().x && center.y < node->getCenter().y) + return findNode(center, node->getChild(Terrain::SW)); + } + +} + +namespace Terrain +{ + + const Ogre::uint REQ_ID_CHUNK = 1; + const Ogre::uint REQ_ID_LAYERS = 2; + + DefaultWorld::DefaultWorld(Ogre::SceneManager* sceneMgr, + Storage* storage, int visibilityFlags, bool shaders, Alignment align, float minBatchSize, float maxBatchSize) + : World(sceneMgr, storage, visibilityFlags, shaders, align) + , mMinBatchSize(minBatchSize) + , mMaxBatchSize(maxBatchSize) + , mVisible(true) + , mMaxX(0) + , mMinX(0) + , mMaxY(0) + , mMinY(0) + , mChunksLoading(0) + , mWorkQueueChannel(0) + , mLayerLoadPending(true) + { +#if TERRAIN_USE_SHADER == 0 + if (mShaders) + std::cerr << "Compiled Terrain without shader support, disabling..." << std::endl; + mShaders = false; +#endif + + mCompositeMapSceneMgr = Ogre::Root::getSingleton().createSceneManager(Ogre::ST_GENERIC); + + /// \todo make composite map size configurable + Ogre::Camera* compositeMapCam = mCompositeMapSceneMgr->createCamera("a"); + mCompositeMapRenderTexture = Ogre::TextureManager::getSingleton().createManual( + "terrain/comp/rt", Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, + Ogre::TEX_TYPE_2D, 128, 128, 0, Ogre::PF_A8B8G8R8, Ogre::TU_RENDERTARGET); + mCompositeMapRenderTarget = mCompositeMapRenderTexture->getBuffer()->getRenderTarget(); + mCompositeMapRenderTarget->setAutoUpdated(false); + mCompositeMapRenderTarget->addViewport(compositeMapCam); + + storage->getBounds(mMinX, mMaxX, mMinY, mMaxY); + + int origSizeX = mMaxX-mMinX; + int origSizeY = mMaxY-mMinY; + + // Dividing a quad tree only works well for powers of two, so round up to the nearest one + int size = nextPowerOfTwo(std::max(origSizeX, origSizeY)); + + // Adjust the center according to the new size + float centerX = (mMinX+mMaxX)/2.f + (size-origSizeX)/2.f; + float centerY = (mMinY+mMaxY)/2.f + (size-origSizeY)/2.f; + + mRootSceneNode = mSceneMgr->getRootSceneNode()->createChildSceneNode(); + + // While building the quadtree, remember leaf nodes since we need to load their layers + LayersRequestData data; + data.mPack = getShadersEnabled(); + + mRootNode = new QuadTreeNode(this, Root, size, Ogre::Vector2(centerX, centerY), NULL); + buildQuadTree(mRootNode, data.mNodes); + //loadingListener->indicateProgress(); + mRootNode->initAabb(); + //loadingListener->indicateProgress(); + mRootNode->initNeighbours(); + //loadingListener->indicateProgress(); + + Ogre::WorkQueue* wq = Ogre::Root::getSingleton().getWorkQueue(); + mWorkQueueChannel = wq->getChannel("LargeTerrain"); + wq->addRequestHandler(mWorkQueueChannel, this); + wq->addResponseHandler(mWorkQueueChannel, this); + + // Start loading layers in the background (for leaf nodes) + wq->addRequest(mWorkQueueChannel, REQ_ID_LAYERS, Ogre::Any(data)); + } + + DefaultWorld::~DefaultWorld() + { + Ogre::WorkQueue* wq = Ogre::Root::getSingleton().getWorkQueue(); + wq->removeRequestHandler(mWorkQueueChannel, this); + wq->removeResponseHandler(mWorkQueueChannel, this); + + delete mRootNode; + } + + void DefaultWorld::buildQuadTree(QuadTreeNode *node, std::vector& leafs) + { + float halfSize = node->getSize()/2.f; + + if (node->getSize() <= mMinBatchSize) + { + // We arrived at a leaf + float minZ,maxZ; + Ogre::Vector2 center = node->getCenter(); + float cellWorldSize = getStorage()->getCellWorldSize(); + if (mStorage->getMinMaxHeights(node->getSize(), center, minZ, maxZ)) + { + Ogre::AxisAlignedBox bounds(Ogre::Vector3(-halfSize*cellWorldSize, -halfSize*cellWorldSize, minZ), + Ogre::Vector3(halfSize*cellWorldSize, halfSize*cellWorldSize, maxZ)); + convertBounds(bounds); + node->setBoundingBox(bounds); + leafs.push_back(node); + } + else + node->markAsDummy(); // no data available for this node, skip it + return; + } + + if (node->getCenter().x - halfSize > mMaxX + || node->getCenter().x + halfSize < mMinX + || node->getCenter().y - halfSize > mMaxY + || node->getCenter().y + halfSize < mMinY ) + // Out of bounds of the actual terrain - this will happen because + // we rounded the size up to the next power of two + { + node->markAsDummy(); + return; + } + + // Not a leaf, create its children + node->createChild(SW, halfSize, node->getCenter() - halfSize/2.f); + node->createChild(SE, halfSize, node->getCenter() + Ogre::Vector2(halfSize/2.f, -halfSize/2.f)); + node->createChild(NW, halfSize, node->getCenter() + Ogre::Vector2(-halfSize/2.f, halfSize/2.f)); + node->createChild(NE, halfSize, node->getCenter() + halfSize/2.f); + buildQuadTree(node->getChild(SW), leafs); + buildQuadTree(node->getChild(SE), leafs); + buildQuadTree(node->getChild(NW), leafs); + buildQuadTree(node->getChild(NE), leafs); + + // if all children are dummy, we are also dummy + for (int i=0; i<4; ++i) + { + if (!node->getChild((ChildDirection)i)->isDummy()) + return; + } + node->markAsDummy(); + } + + void DefaultWorld::update(const Ogre::Vector3& cameraPos) + { + if (!mVisible) + return; + mRootNode->update(cameraPos); + mRootNode->updateIndexBuffers(); + } + + Ogre::AxisAlignedBox DefaultWorld::getWorldBoundingBox (const Ogre::Vector2& center) + { + if (center.x > mMaxX + || center.x < mMinX + || center.y > mMaxY + || center.y < mMinY) + return Ogre::AxisAlignedBox::BOX_NULL; + QuadTreeNode* node = findNode(center, mRootNode); + return node->getWorldBoundingBox(); + } + + void DefaultWorld::renderCompositeMap(Ogre::TexturePtr target) + { + mCompositeMapRenderTarget->update(); + target->getBuffer()->blit(mCompositeMapRenderTexture->getBuffer()); + } + + void DefaultWorld::clearCompositeMapSceneManager() + { + mCompositeMapSceneMgr->destroyAllManualObjects(); + mCompositeMapSceneMgr->clearScene(); + } + + void DefaultWorld::applyMaterials(bool shadows, bool splitShadows) + { + mShadows = shadows; + mSplitShadows = splitShadows; + mRootNode->applyMaterials(); + } + + void DefaultWorld::setVisible(bool visible) + { + if (visible && !mVisible) + mSceneMgr->getRootSceneNode()->addChild(mRootSceneNode); + else if (!visible && mVisible) + mSceneMgr->getRootSceneNode()->removeChild(mRootSceneNode); + + mVisible = visible; + } + + bool DefaultWorld::getVisible() + { + return mVisible; + } + + void DefaultWorld::syncLoad() + { + while (mChunksLoading || mLayerLoadPending) + { + OGRE_THREAD_SLEEP(0); + Ogre::Root::getSingleton().getWorkQueue()->processResponses(); + } + } + + Ogre::WorkQueue::Response* DefaultWorld::handleRequest(const Ogre::WorkQueue::Request *req, const Ogre::WorkQueue *srcQ) + { + if (req->getType() == REQ_ID_CHUNK) + { + const LoadRequestData data = Ogre::any_cast(req->getData()); + + QuadTreeNode* node = data.mNode; + + LoadResponseData* responseData = new LoadResponseData(); + + getStorage()->fillVertexBuffers(node->getNativeLodLevel(), node->getSize(), node->getCenter(), getAlign(), + responseData->mPositions, responseData->mNormals, responseData->mColours); + + return OGRE_NEW Ogre::WorkQueue::Response(req, true, Ogre::Any(responseData)); + } + else // REQ_ID_LAYERS + { + const LayersRequestData data = Ogre::any_cast(req->getData()); + + LayersResponseData* responseData = new LayersResponseData(); + + getStorage()->getBlendmaps(data.mNodes, responseData->mLayerCollections, data.mPack); + + return OGRE_NEW Ogre::WorkQueue::Response(req, true, Ogre::Any(responseData)); + } + } + + void DefaultWorld::handleResponse(const Ogre::WorkQueue::Response *res, const Ogre::WorkQueue *srcQ) + { + assert(res->succeeded() && "Response failure not handled"); + + if (res->getRequest()->getType() == REQ_ID_CHUNK) + { + LoadResponseData* data = Ogre::any_cast(res->getData()); + + const LoadRequestData requestData = Ogre::any_cast(res->getRequest()->getData()); + + requestData.mNode->load(*data); + + delete data; + + --mChunksLoading; + } + else // REQ_ID_LAYERS + { + LayersResponseData* data = Ogre::any_cast(res->getData()); + + for (std::vector::iterator it = data->mLayerCollections.begin(); it != data->mLayerCollections.end(); ++it) + { + it->mTarget->loadLayers(*it); + } + + delete data; + + mRootNode->loadMaterials(); + + mLayerLoadPending = false; + } + } + + void DefaultWorld::queueLoad(QuadTreeNode *node) + { + LoadRequestData data; + data.mNode = node; + + Ogre::Root::getSingleton().getWorkQueue()->addRequest(mWorkQueueChannel, REQ_ID_CHUNK, Ogre::Any(data)); + ++mChunksLoading; + } +} diff --git a/components/terrain/defaultworld.hpp b/components/terrain/defaultworld.hpp new file mode 100644 index 000000000..8769a0d88 --- /dev/null +++ b/components/terrain/defaultworld.hpp @@ -0,0 +1,156 @@ +#ifndef COMPONENTS_TERRAIN_H +#define COMPONENTS_TERRAIN_H + +#include +#include +#include + +#include "world.hpp" + +namespace Ogre +{ + class Camera; +} + +namespace Terrain +{ + + class QuadTreeNode; + class Storage; + + /** + * @brief A quadtree-based terrain implementation suitable for large data sets. \n + * Near cells are rendered with alpha splatting, distant cells are merged + * together in batches and have their layers pre-rendered onto a composite map. \n + * Cracks at LOD transitions are avoided using stitching. + * @note Multiple cameras are not supported yet + */ + class DefaultWorld : public World, public Ogre::WorkQueue::RequestHandler, public Ogre::WorkQueue::ResponseHandler + { + public: + /// @note takes ownership of \a storage + /// @param sceneMgr scene manager to use + /// @param storage Storage instance to get terrain data from (heights, normals, colors, textures..) + /// @param visbilityFlags visibility flags for the created meshes + /// @param shaders Whether to use splatting shader, or multi-pass fixed function splatting. Shader is usually + /// faster so this is just here for compatibility. + /// @param align The align of the terrain, see Alignment enum + /// @param minBatchSize Minimum size of a terrain batch along one side (in cell units). Used for building the quad tree. + /// @param maxBatchSize Maximum size of a terrain batch along one side (in cell units). Used when traversing the quad tree. + DefaultWorld(Ogre::SceneManager* sceneMgr, + Storage* storage, int visibilityFlags, bool shaders, Alignment align, float minBatchSize, float maxBatchSize); + ~DefaultWorld(); + + /// Update chunk LODs according to this camera position + /// @note Calling this method might lead to composite textures being rendered, so it is best + /// not to call it when render commands are still queued, since that would cause a flush. + virtual void update (const Ogre::Vector3& cameraPos); + + /// Get the world bounding box of a chunk of terrain centered at \a center + virtual Ogre::AxisAlignedBox getWorldBoundingBox (const Ogre::Vector2& center); + + Ogre::SceneNode* getRootSceneNode() { return mRootSceneNode; } + + /// Show or hide the whole terrain + /// @note this setting will be invalidated once you call Terrain::update, so do not call it while the terrain should be hidden + virtual void setVisible(bool visible); + virtual bool getVisible(); + + /// Recreate materials used by terrain chunks. This should be called whenever settings of + /// the material factory are changed. (Relying on the factory to update those materials is not + /// enough, since turning a feature on/off can change the number of texture units available for layer/blend + /// textures, and to properly respond to this we may need to change the structure of the material, such as + /// adding or removing passes. This can only be achieved by a full rebuild.) + virtual void applyMaterials(bool shadows, bool splitShadows); + + int getMaxBatchSize() { return mMaxBatchSize; } + + /// Wait until all background loading is complete. + void syncLoad(); + + private: + // Called from a background worker thread + Ogre::WorkQueue::Response* handleRequest(const Ogre::WorkQueue::Request* req, const Ogre::WorkQueue* srcQ); + // Called from the main thread + void handleResponse(const Ogre::WorkQueue::Response* res, const Ogre::WorkQueue* srcQ); + Ogre::uint16 mWorkQueueChannel; + + bool mVisible; + + QuadTreeNode* mRootNode; + Ogre::SceneNode* mRootSceneNode; + + /// The number of chunks currently loading in a background thread. If 0, we have finished loading! + int mChunksLoading; + + Ogre::SceneManager* mCompositeMapSceneMgr; + + /// Bounds in cell units + float mMinX, mMaxX, mMinY, mMaxY; + + /// Minimum size of a terrain batch along one side (in cell units) + float mMinBatchSize; + /// Maximum size of a terrain batch along one side (in cell units) + float mMaxBatchSize; + + void buildQuadTree(QuadTreeNode* node, std::vector& leafs); + + // Are layers for leaf nodes loaded? This is done once at startup (but in a background thread) + bool mLayerLoadPending; + + public: + // ----INTERNAL---- + Ogre::SceneManager* getCompositeMapSceneManager() { return mCompositeMapSceneMgr; } + + bool areLayersLoaded() { return !mLayerLoadPending; } + + // Delete all quads + void clearCompositeMapSceneManager(); + void renderCompositeMap (Ogre::TexturePtr target); + + // Adds a WorkQueue request to load a chunk for this node in the background. + void queueLoad (QuadTreeNode* node); + + private: + Ogre::RenderTarget* mCompositeMapRenderTarget; + Ogre::TexturePtr mCompositeMapRenderTexture; + }; + + struct LoadRequestData + { + QuadTreeNode* mNode; + + friend std::ostream& operator<<(std::ostream& o, const LoadRequestData& r) + { return o; } + }; + + struct LoadResponseData + { + std::vector mPositions; + std::vector mNormals; + std::vector mColours; + + friend std::ostream& operator<<(std::ostream& o, const LoadResponseData& r) + { return o; } + }; + + struct LayersRequestData + { + std::vector mNodes; + bool mPack; + + friend std::ostream& operator<<(std::ostream& o, const LayersRequestData& r) + { return o; } + }; + + struct LayersResponseData + { + std::vector mLayerCollections; + + friend std::ostream& operator<<(std::ostream& o, const LayersResponseData& r) + { return o; } + }; + +} + +#endif diff --git a/components/terrain/material.cpp b/components/terrain/material.cpp index 771dcdf91..a40e576ed 100644 --- a/components/terrain/material.cpp +++ b/components/terrain/material.cpp @@ -36,8 +36,8 @@ std::string getBlendmapComponentForLayer (int layerIndex) namespace Terrain { - MaterialGenerator::MaterialGenerator(bool shaders) - : mShaders(shaders) + MaterialGenerator::MaterialGenerator() + : mShaders(true) , mShadows(false) , mSplitShadows(false) , mNormalMapping(true) diff --git a/components/terrain/material.hpp b/components/terrain/material.hpp index 7f607a7af..b9000cb1b 100644 --- a/components/terrain/material.hpp +++ b/components/terrain/material.hpp @@ -11,11 +11,7 @@ namespace Terrain class MaterialGenerator { public: - /// @param layerList layer textures - /// @param blendmapList blend textures - /// @param shaders Whether to use shaders. With a shader, blendmap packing can be used (4 channels instead of one), - /// so if this parameter is true, then the supplied blend maps are expected to be packed. - MaterialGenerator (bool shaders); + MaterialGenerator (); void setLayerList (const std::vector& layerList) { mLayerList = layerList; } bool hasLayers() { return mLayerList.size(); } @@ -23,6 +19,7 @@ namespace Terrain const std::vector& getBlendmapList() { return mBlendmapList; } void setCompositeMap (const std::string& name) { mCompositeMap = name; } + void enableShaders(bool shaders) { mShaders = shaders; } void enableShadows(bool shadows) { mShadows = shadows; } void enableNormalMapping(bool normalMapping) { mNormalMapping = normalMapping; } void enableParallaxMapping(bool parallaxMapping) { mParallaxMapping = parallaxMapping; } diff --git a/components/terrain/quadtreenode.cpp b/components/terrain/quadtreenode.cpp index 37c638da0..44974eeb1 100644 --- a/components/terrain/quadtreenode.cpp +++ b/components/terrain/quadtreenode.cpp @@ -6,7 +6,7 @@ #include #include -#include "world.hpp" +#include "defaultworld.hpp" #include "chunk.hpp" #include "storage.hpp" #include "buffercache.hpp" @@ -142,7 +142,7 @@ namespace } } -QuadTreeNode::QuadTreeNode(World* terrain, ChildDirection dir, float size, const Ogre::Vector2 ¢er, QuadTreeNode* parent) +QuadTreeNode::QuadTreeNode(DefaultWorld* terrain, ChildDirection dir, float size, const Ogre::Vector2 ¢er, QuadTreeNode* parent) : mMaterialGenerator(NULL) , mIsDummy(false) , mSize(size) @@ -178,7 +178,8 @@ QuadTreeNode::QuadTreeNode(World* terrain, ChildDirection dir, float size, const mSceneNode->setPosition(sceneNodePos); - mMaterialGenerator = new MaterialGenerator(mTerrain->getShadersEnabled()); + mMaterialGenerator = new MaterialGenerator(); + mMaterialGenerator->enableShaders(mTerrain->getShadersEnabled()); } void QuadTreeNode::createChild(ChildDirection id, float size, const Ogre::Vector2 ¢er) @@ -386,11 +387,9 @@ void QuadTreeNode::load(const LoadResponseData &data) { assert (!mChunk); - mChunk = new Chunk(mTerrain->getBufferCache().getUVBuffer(), mBounds, data); - mChunk->setVisibilityFlags(mTerrain->getVisiblityFlags()); + mChunk = new Chunk(mTerrain->getBufferCache().getUVBuffer(), mBounds, data.mPositions, data.mNormals, data.mColours); + mChunk->setVisibilityFlags(mTerrain->getVisibilityFlags()); mChunk->setCastShadows(true); - if (!mTerrain->getDistantLandEnabled()) - mChunk->setRenderingDistance(8192); mSceneNode->attachObject(mChunk); mMaterialGenerator->enableShadows(mTerrain->getShadowsEnabled()); @@ -550,7 +549,8 @@ void QuadTreeNode::prepareForCompositeMap(Ogre::TRect area) if (mIsDummy) { // TODO - store this default material somewhere instead of creating one for each empty cell - MaterialGenerator matGen(mTerrain->getShadersEnabled()); + MaterialGenerator matGen; + matGen.enableShaders(mTerrain->getShadersEnabled()); std::vector layer; layer.push_back(mTerrain->getStorage()->getDefaultLayer()); matGen.setLayerList(layer); diff --git a/components/terrain/quadtreenode.hpp b/components/terrain/quadtreenode.hpp index c57589487..626572701 100644 --- a/components/terrain/quadtreenode.hpp +++ b/components/terrain/quadtreenode.hpp @@ -14,7 +14,7 @@ namespace Ogre namespace Terrain { - class World; + class DefaultWorld; class Chunk; class MaterialGenerator; struct LoadResponseData; @@ -48,7 +48,7 @@ namespace Terrain /// @param size size (in *cell* units!) /// @param center center (in *cell* units!) /// @param parent parent node - QuadTreeNode (World* terrain, ChildDirection dir, float size, const Ogre::Vector2& center, QuadTreeNode* parent); + QuadTreeNode (DefaultWorld* terrain, ChildDirection dir, float size, const Ogre::Vector2& center, QuadTreeNode* parent); ~QuadTreeNode(); /// Rebuild all materials @@ -95,7 +95,7 @@ namespace Terrain const Ogre::AxisAlignedBox& getWorldBoundingBox(); - World* getTerrain() { return mTerrain; } + DefaultWorld* getTerrain() { return mTerrain; } /// Adjust LODs for the given camera position, possibly splitting up chunks or merging them. /// @param force Always choose to render this node, even if not the perfect LOD. @@ -158,7 +158,7 @@ namespace Terrain Chunk* mChunk; - World* mTerrain; + DefaultWorld* mTerrain; Ogre::TexturePtr mCompositeMap; diff --git a/components/terrain/world.cpp b/components/terrain/world.cpp index 3d968470f..49fb9b5c9 100644 --- a/components/terrain/world.cpp +++ b/components/terrain/world.cpp @@ -1,356 +1,60 @@ #include "world.hpp" #include -#include -#include -#include -#include -#include -#include #include "storage.hpp" -#include "quadtreenode.hpp" - -namespace -{ - - bool isPowerOfTwo(int x) - { - return ( (x > 0) && ((x & (x - 1)) == 0) ); - } - - int nextPowerOfTwo (int v) - { - if (isPowerOfTwo(v)) return v; - int depth=0; - while(v) - { - v >>= 1; - depth++; - } - return 1 << depth; - } - - Terrain::QuadTreeNode* findNode (const Ogre::Vector2& center, Terrain::QuadTreeNode* node) - { - if (center == node->getCenter()) - return node; - - if (center.x > node->getCenter().x && center.y > node->getCenter().y) - return findNode(center, node->getChild(Terrain::NE)); - else if (center.x > node->getCenter().x && center.y < node->getCenter().y) - return findNode(center, node->getChild(Terrain::SE)); - else if (center.x < node->getCenter().x && center.y > node->getCenter().y) - return findNode(center, node->getChild(Terrain::NW)); - else //if (center.x < node->getCenter().x && center.y < node->getCenter().y) - return findNode(center, node->getChild(Terrain::SW)); - } - -} namespace Terrain { - const Ogre::uint REQ_ID_CHUNK = 1; - const Ogre::uint REQ_ID_LAYERS = 2; +World::World(Ogre::SceneManager* sceneMgr, + Storage* storage, int visibilityFlags, bool shaders, Alignment align) + : mStorage(storage) + , mSceneMgr(sceneMgr) + , mVisibilityFlags(visibilityFlags) + , mShaders(shaders) + , mAlign(align) + , mCache(storage->getCellVertices()) +{ +} - World::World(Ogre::SceneManager* sceneMgr, - Storage* storage, int visibilityFlags, bool distantLand, bool shaders, Alignment align, float minBatchSize, float maxBatchSize) - : mStorage(storage) - , mMinBatchSize(minBatchSize) - , mMaxBatchSize(maxBatchSize) - , mSceneMgr(sceneMgr) - , mVisibilityFlags(visibilityFlags) - , mDistantLand(distantLand) - , mShaders(shaders) - , mVisible(true) - , mAlign(align) - , mMaxX(0) - , mMinX(0) - , mMaxY(0) - , mMinY(0) - , mChunksLoading(0) - , mWorkQueueChannel(0) - , mCache(storage->getCellVertices()) - , mLayerLoadPending(true) +World::~World() +{ + delete mStorage; +} + +float World::getHeightAt(const Ogre::Vector3 &worldPos) +{ + return mStorage->getHeightAt(worldPos); +} + +void World::convertPosition(float &x, float &y, float &z) +{ + Terrain::convertPosition(mAlign, x, y, z); +} + +void World::convertPosition(Ogre::Vector3 &pos) +{ + convertPosition(pos.x, pos.y, pos.z); +} + +void World::convertBounds(Ogre::AxisAlignedBox& bounds) +{ + switch (mAlign) { -#if TERRAIN_USE_SHADER == 0 - if (mShaders) - std::cerr << "Compiled Terrain without shader support, disabling..." << std::endl; - mShaders = false; -#endif - - mCompositeMapSceneMgr = Ogre::Root::getSingleton().createSceneManager(Ogre::ST_GENERIC); - - /// \todo make composite map size configurable - Ogre::Camera* compositeMapCam = mCompositeMapSceneMgr->createCamera("a"); - mCompositeMapRenderTexture = Ogre::TextureManager::getSingleton().createManual( - "terrain/comp/rt", Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, - Ogre::TEX_TYPE_2D, 128, 128, 0, Ogre::PF_A8B8G8R8, Ogre::TU_RENDERTARGET); - mCompositeMapRenderTarget = mCompositeMapRenderTexture->getBuffer()->getRenderTarget(); - mCompositeMapRenderTarget->setAutoUpdated(false); - mCompositeMapRenderTarget->addViewport(compositeMapCam); - - storage->getBounds(mMinX, mMaxX, mMinY, mMaxY); - - int origSizeX = mMaxX-mMinX; - int origSizeY = mMaxY-mMinY; - - // Dividing a quad tree only works well for powers of two, so round up to the nearest one - int size = nextPowerOfTwo(std::max(origSizeX, origSizeY)); - - // Adjust the center according to the new size - float centerX = (mMinX+mMaxX)/2.f + (size-origSizeX)/2.f; - float centerY = (mMinY+mMaxY)/2.f + (size-origSizeY)/2.f; - - mRootSceneNode = mSceneMgr->getRootSceneNode()->createChildSceneNode(); - - // While building the quadtree, remember leaf nodes since we need to load their layers - LayersRequestData data; - data.mPack = getShadersEnabled(); - - mRootNode = new QuadTreeNode(this, Root, size, Ogre::Vector2(centerX, centerY), NULL); - buildQuadTree(mRootNode, data.mNodes); - //loadingListener->indicateProgress(); - mRootNode->initAabb(); - //loadingListener->indicateProgress(); - mRootNode->initNeighbours(); - //loadingListener->indicateProgress(); - - Ogre::WorkQueue* wq = Ogre::Root::getSingleton().getWorkQueue(); - mWorkQueueChannel = wq->getChannel("LargeTerrain"); - wq->addRequestHandler(mWorkQueueChannel, this); - wq->addResponseHandler(mWorkQueueChannel, this); - - // Start loading layers in the background (for leaf nodes) - wq->addRequest(mWorkQueueChannel, REQ_ID_LAYERS, Ogre::Any(data)); - } - - World::~World() - { - Ogre::WorkQueue* wq = Ogre::Root::getSingleton().getWorkQueue(); - wq->removeRequestHandler(mWorkQueueChannel, this); - wq->removeResponseHandler(mWorkQueueChannel, this); - - delete mRootNode; - delete mStorage; - } - - void World::buildQuadTree(QuadTreeNode *node, std::vector& leafs) - { - float halfSize = node->getSize()/2.f; - - if (node->getSize() <= mMinBatchSize) - { - // We arrived at a leaf - float minZ,maxZ; - Ogre::Vector2 center = node->getCenter(); - float cellWorldSize = getStorage()->getCellWorldSize(); - if (mStorage->getMinMaxHeights(node->getSize(), center, minZ, maxZ)) - { - Ogre::AxisAlignedBox bounds(Ogre::Vector3(-halfSize*cellWorldSize, -halfSize*cellWorldSize, minZ), - Ogre::Vector3(halfSize*cellWorldSize, halfSize*cellWorldSize, maxZ)); - convertBounds(bounds); - node->setBoundingBox(bounds); - leafs.push_back(node); - } - else - node->markAsDummy(); // no data available for this node, skip it - return; - } - - if (node->getCenter().x - halfSize > mMaxX - || node->getCenter().x + halfSize < mMinX - || node->getCenter().y - halfSize > mMaxY - || node->getCenter().y + halfSize < mMinY ) - // Out of bounds of the actual terrain - this will happen because - // we rounded the size up to the next power of two - { - node->markAsDummy(); - return; - } - - // Not a leaf, create its children - node->createChild(SW, halfSize, node->getCenter() - halfSize/2.f); - node->createChild(SE, halfSize, node->getCenter() + Ogre::Vector2(halfSize/2.f, -halfSize/2.f)); - node->createChild(NW, halfSize, node->getCenter() + Ogre::Vector2(-halfSize/2.f, halfSize/2.f)); - node->createChild(NE, halfSize, node->getCenter() + halfSize/2.f); - buildQuadTree(node->getChild(SW), leafs); - buildQuadTree(node->getChild(SE), leafs); - buildQuadTree(node->getChild(NW), leafs); - buildQuadTree(node->getChild(NE), leafs); - - // if all children are dummy, we are also dummy - for (int i=0; i<4; ++i) - { - if (!node->getChild((ChildDirection)i)->isDummy()) - return; - } - node->markAsDummy(); - } - - void World::update(const Ogre::Vector3& cameraPos) - { - if (!mVisible) - return; - mRootNode->update(cameraPos); - mRootNode->updateIndexBuffers(); - } - - Ogre::AxisAlignedBox World::getWorldBoundingBox (const Ogre::Vector2& center) - { - if (center.x > mMaxX - || center.x < mMinX - || center.y > mMaxY - || center.y < mMinY) - return Ogre::AxisAlignedBox::BOX_NULL; - QuadTreeNode* node = findNode(center, mRootNode); - return node->getWorldBoundingBox(); - } - - void World::renderCompositeMap(Ogre::TexturePtr target) - { - mCompositeMapRenderTarget->update(); - target->getBuffer()->blit(mCompositeMapRenderTexture->getBuffer()); - } - - void World::clearCompositeMapSceneManager() - { - mCompositeMapSceneMgr->destroyAllManualObjects(); - mCompositeMapSceneMgr->clearScene(); - } - - float World::getHeightAt(const Ogre::Vector3 &worldPos) - { - return mStorage->getHeightAt(worldPos); - } - - void World::applyMaterials(bool shadows, bool splitShadows) - { - mShadows = shadows; - mSplitShadows = splitShadows; - mRootNode->applyMaterials(); - } - - void World::setVisible(bool visible) - { - if (visible && !mVisible) - mSceneMgr->getRootSceneNode()->addChild(mRootSceneNode); - else if (!visible && mVisible) - mSceneMgr->getRootSceneNode()->removeChild(mRootSceneNode); - - mVisible = visible; - } - - bool World::getVisible() - { - return mVisible; - } - - void World::convertPosition(float &x, float &y, float &z) - { - Terrain::convertPosition(mAlign, x, y, z); - } - - void World::convertPosition(Ogre::Vector3 &pos) - { - convertPosition(pos.x, pos.y, pos.z); - } - - void World::convertBounds(Ogre::AxisAlignedBox& bounds) - { - switch (mAlign) - { - case Align_XY: - return; - case Align_XZ: - convertPosition(bounds.getMinimum()); - convertPosition(bounds.getMaximum()); - // Because we changed sign of Z - std::swap(bounds.getMinimum().z, bounds.getMaximum().z); - return; - case Align_YZ: - convertPosition(bounds.getMinimum()); - convertPosition(bounds.getMaximum()); - return; - } - } - - void World::syncLoad() - { - while (mChunksLoading || mLayerLoadPending) - { - OGRE_THREAD_SLEEP(0); - Ogre::Root::getSingleton().getWorkQueue()->processResponses(); - } - } - - Ogre::WorkQueue::Response* World::handleRequest(const Ogre::WorkQueue::Request *req, const Ogre::WorkQueue *srcQ) - { - if (req->getType() == REQ_ID_CHUNK) - { - const LoadRequestData data = Ogre::any_cast(req->getData()); - - QuadTreeNode* node = data.mNode; - - LoadResponseData* responseData = new LoadResponseData(); - - getStorage()->fillVertexBuffers(node->getNativeLodLevel(), node->getSize(), node->getCenter(), getAlign(), - responseData->mPositions, responseData->mNormals, responseData->mColours); - - return OGRE_NEW Ogre::WorkQueue::Response(req, true, Ogre::Any(responseData)); - } - else // REQ_ID_LAYERS - { - const LayersRequestData data = Ogre::any_cast(req->getData()); - - LayersResponseData* responseData = new LayersResponseData(); - - getStorage()->getBlendmaps(data.mNodes, responseData->mLayerCollections, data.mPack); - - return OGRE_NEW Ogre::WorkQueue::Response(req, true, Ogre::Any(responseData)); - } - } - - void World::handleResponse(const Ogre::WorkQueue::Response *res, const Ogre::WorkQueue *srcQ) - { - assert(res->succeeded() && "Response failure not handled"); - - if (res->getRequest()->getType() == REQ_ID_CHUNK) - { - LoadResponseData* data = Ogre::any_cast(res->getData()); - - const LoadRequestData requestData = Ogre::any_cast(res->getRequest()->getData()); - - requestData.mNode->load(*data); - - delete data; - - --mChunksLoading; - } - else // REQ_ID_LAYERS - { - LayersResponseData* data = Ogre::any_cast(res->getData()); - - for (std::vector::iterator it = data->mLayerCollections.begin(); it != data->mLayerCollections.end(); ++it) - { - it->mTarget->loadLayers(*it); - } - - delete data; - - mRootNode->loadMaterials(); - - mLayerLoadPending = false; - } - } - - void World::queueLoad(QuadTreeNode *node) - { - LoadRequestData data; - data.mNode = node; - - Ogre::Root::getSingleton().getWorkQueue()->addRequest(mWorkQueueChannel, REQ_ID_CHUNK, Ogre::Any(data)); - ++mChunksLoading; + case Align_XY: + return; + case Align_XZ: + convertPosition(bounds.getMinimum()); + convertPosition(bounds.getMaximum()); + // Because we changed sign of Z + std::swap(bounds.getMinimum().z, bounds.getMaximum().z); + return; + case Align_YZ: + convertPosition(bounds.getMinimum()); + convertPosition(bounds.getMaximum()); + return; } } + +} diff --git a/components/terrain/world.hpp b/components/terrain/world.hpp index 26a6d034d..beca7903a 100644 --- a/components/terrain/world.hpp +++ b/components/terrain/world.hpp @@ -1,50 +1,38 @@ -#ifndef COMPONENTS_TERRAIN_H -#define COMPONENTS_TERRAIN_H +#ifndef COMPONENTS_TERRAIN_WORLD_H +#define COMPONENTS_TERRAIN_WORLD_H -#include -#include -#include +#include #include "defs.hpp" #include "buffercache.hpp" namespace Ogre { - class Camera; + class SceneManager; } namespace Terrain { - - class QuadTreeNode; class Storage; /** - * @brief A quadtree-based terrain implementation suitable for large data sets. \n - * Near cells are rendered with alpha splatting, distant cells are merged - * together in batches and have their layers pre-rendered onto a composite map. \n - * Cracks at LOD transitions are avoided using stitching. - * @note Multiple cameras are not supported yet + * @brief The basic interface for a terrain world. How the terrain chunks are paged and displayed + * is up to the implementation. */ - class World : public Ogre::WorkQueue::RequestHandler, public Ogre::WorkQueue::ResponseHandler + class World { public: /// @note takes ownership of \a storage /// @param sceneMgr scene manager to use /// @param storage Storage instance to get terrain data from (heights, normals, colors, textures..) /// @param visbilityFlags visibility flags for the created meshes - /// @param distantLand Whether to draw all of the terrain, or only a 3x3 grid around the camera. - /// This is a temporary option until it can be streamlined. /// @param shaders Whether to use splatting shader, or multi-pass fixed function splatting. Shader is usually /// faster so this is just here for compatibility. /// @param align The align of the terrain, see Alignment enum - /// @param minBatchSize Minimum size of a terrain batch along one side (in cell units). Used for building the quad tree. - /// @param maxBatchSize Maximum size of a terrain batch along one side (in cell units). Used when traversing the quad tree. World(Ogre::SceneManager* sceneMgr, - Storage* storage, int visiblityFlags, bool distantLand, bool shaders, Alignment align, float minBatchSize, float maxBatchSize); - ~World(); + Storage* storage, int visiblityFlags, bool shaders, Alignment align); + virtual ~World(); - bool getDistantLandEnabled() { return mDistantLand; } bool getShadersEnabled() { return mShaders; } bool getShadowsEnabled() { return mShadows; } bool getSplitShadowsEnabled() { return mSplitShadows; } @@ -54,138 +42,60 @@ namespace Terrain /// Update chunk LODs according to this camera position /// @note Calling this method might lead to composite textures being rendered, so it is best /// not to call it when render commands are still queued, since that would cause a flush. - void update (const Ogre::Vector3& cameraPos); + virtual void update (const Ogre::Vector3& cameraPos) = 0; + + // This is only a hint and may be ignored by the implementation. + virtual void loadCell(int x, int y) {} + virtual void unloadCell(int x, int y) {} /// Get the world bounding box of a chunk of terrain centered at \a center - Ogre::AxisAlignedBox getWorldBoundingBox (const Ogre::Vector2& center); + virtual Ogre::AxisAlignedBox getWorldBoundingBox (const Ogre::Vector2& center) = 0; Ogre::SceneManager* getSceneManager() { return mSceneMgr; } - Ogre::SceneNode* getRootSceneNode() { return mRootSceneNode; } - Storage* getStorage() { return mStorage; } /// Show or hide the whole terrain - /// @note this setting will be invalidated once you call Terrain::update, so do not call it while the terrain should be hidden - void setVisible(bool visible); - bool getVisible(); + /// @note this setting may be invalidated once you call Terrain::update, so do not call it while the terrain should be hidden + virtual void setVisible(bool visible) = 0; + virtual bool getVisible() = 0; /// Recreate materials used by terrain chunks. This should be called whenever settings of /// the material factory are changed. (Relying on the factory to update those materials is not /// enough, since turning a feature on/off can change the number of texture units available for layer/blend /// textures, and to properly respond to this we may need to change the structure of the material, such as /// adding or removing passes. This can only be achieved by a full rebuild.) - void applyMaterials(bool shadows, bool splitShadows); + virtual void applyMaterials(bool shadows, bool splitShadows) = 0; - int getVisiblityFlags() { return mVisibilityFlags; } - - int getMaxBatchSize() { return mMaxBatchSize; } - - void enableSplattingShader(bool enabled); + int getVisibilityFlags() { return mVisibilityFlags; } Alignment getAlign() { return mAlign; } /// Wait until all background loading is complete. - void syncLoad(); + virtual void syncLoad() {} - private: - // Called from a background worker thread - Ogre::WorkQueue::Response* handleRequest(const Ogre::WorkQueue::Request* req, const Ogre::WorkQueue* srcQ); - // Called from the main thread - void handleResponse(const Ogre::WorkQueue::Response* res, const Ogre::WorkQueue* srcQ); - Ogre::uint16 mWorkQueueChannel; - - bool mDistantLand; + protected: bool mShaders; bool mShadows; bool mSplitShadows; - bool mVisible; Alignment mAlign; - QuadTreeNode* mRootNode; - Ogre::SceneNode* mRootSceneNode; Storage* mStorage; int mVisibilityFlags; - /// The number of chunks currently loading in a background thread. If 0, we have finished loading! - int mChunksLoading; - Ogre::SceneManager* mSceneMgr; - Ogre::SceneManager* mCompositeMapSceneMgr; - - /// Bounds in cell units - float mMinX, mMaxX, mMinY, mMaxY; - - /// Minimum size of a terrain batch along one side (in cell units) - float mMinBatchSize; - /// Maximum size of a terrain batch along one side (in cell units) - float mMaxBatchSize; - - void buildQuadTree(QuadTreeNode* node, std::vector& leafs); BufferCache mCache; - // Are layers for leaf nodes loaded? This is done once at startup (but in a background thread) - bool mLayerLoadPending; - public: // ----INTERNAL---- - Ogre::SceneManager* getCompositeMapSceneManager() { return mCompositeMapSceneMgr; } BufferCache& getBufferCache() { return mCache; } - bool areLayersLoaded() { return !mLayerLoadPending; } - - // Delete all quads - void clearCompositeMapSceneManager(); - void renderCompositeMap (Ogre::TexturePtr target); - // Convert the given position from Z-up align, i.e. Align_XY to the wanted align set in mAlign void convertPosition (float& x, float& y, float& z); void convertPosition (Ogre::Vector3& pos); void convertBounds (Ogre::AxisAlignedBox& bounds); - - // Adds a WorkQueue request to load a chunk for this node in the background. - void queueLoad (QuadTreeNode* node); - - private: - Ogre::RenderTarget* mCompositeMapRenderTarget; - Ogre::TexturePtr mCompositeMapRenderTexture; - }; - - struct LoadRequestData - { - QuadTreeNode* mNode; - - friend std::ostream& operator<<(std::ostream& o, const LoadRequestData& r) - { return o; } - }; - - struct LoadResponseData - { - std::vector mPositions; - std::vector mNormals; - std::vector mColours; - - friend std::ostream& operator<<(std::ostream& o, const LoadResponseData& r) - { return o; } - }; - - struct LayersRequestData - { - std::vector mNodes; - bool mPack; - - friend std::ostream& operator<<(std::ostream& o, const LayersRequestData& r) - { return o; } - }; - - struct LayersResponseData - { - std::vector mLayerCollections; - - friend std::ostream& operator<<(std::ostream& o, const LayersResponseData& r) - { return o; } }; }