Refactor non-distant land terrain path to a grid based implementation (Fixes #1562)

This commit is contained in:
scrawl 2014-06-29 02:42:36 +02:00
parent 4949aa1fbb
commit e25fa6c157
17 changed files with 825 additions and 495 deletions

View file

@ -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

View file

@ -22,7 +22,7 @@
#include <openengine/bullet/physic.hpp>
#include <components/settings/settings.hpp>
#include <components/terrain/world.hpp>
#include <components/terrain/defaultworld.hpp>
#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());

View file

@ -0,0 +1,165 @@
#include "terraingrid.hpp"
#include <OgreSceneManager.h>
#include <OgreSceneNode.h>
#include <OgreAxisAlignedBox.h>
#include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp"
#include <components/terrain/chunk.hpp>
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<float> positions;
std::vector<float> normals;
std::vector<Ogre::uint8> 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<Ogre::PixelBox> blendmaps;
std::vector<Terrain::LayerInfo> layerList;
mStorage->getBlendmaps(1, center, mShaders, blendmaps, layerList);
element.mMaterialGenerator.setLayerList(layerList);
// upload blendmaps to GPU
std::vector<Ogre::TexturePtr> blendTextures;
for (std::vector<Ogre::PixelBox>::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<Ogre::TexturePtr>& blendmaps = element.mMaterialGenerator.getBlendmapList();
for (std::vector<Ogre::TexturePtr>::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()
{
}
}

View file

@ -0,0 +1,75 @@
#ifndef OPENMW_MWRENDER_TERRAINGRID_H
#define OPENMW_MWRENDER_TERRAINGRID_H
#include <components/terrain/world.hpp>
#include <components/terrain/material.hpp>
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<std::pair<int, int>, GridElement> Grid;
Grid mGrid;
Ogre::SceneNode* mRootNode;
bool mVisible;
};
}
#endif

View file

@ -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) -

View file

@ -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

View file

View file

@ -8,19 +8,17 @@
#include <extern/shiny/Main/Factory.hpp>
#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<float>& positions, const std::vector<float>& normals, const std::vector<Ogre::uint8>& 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);

View file

@ -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<float>& positions,
const std::vector<float>& normals,
const std::vector<Ogre::uint8>& colours);
virtual ~Chunk();

View file

@ -0,0 +1,315 @@
#include "defaultworld.hpp"
#include <OgreAxisAlignedBox.h>
#include <OgreCamera.h>
#include <OgreHardwarePixelBuffer.h>
#include <OgreTextureManager.h>
#include <OgreRenderTexture.h>
#include <OgreSceneNode.h>
#include <OgreRoot.h>
#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<QuadTreeNode*>& 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<LoadRequestData>(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<LayersRequestData>(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<LoadResponseData*>(res->getData());
const LoadRequestData requestData = Ogre::any_cast<LoadRequestData>(res->getRequest()->getData());
requestData.mNode->load(*data);
delete data;
--mChunksLoading;
}
else // REQ_ID_LAYERS
{
LayersResponseData* data = Ogre::any_cast<LayersResponseData*>(res->getData());
for (std::vector<LayerCollection>::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;
}
}

View file

@ -0,0 +1,156 @@
#ifndef COMPONENTS_TERRAIN_H
#define COMPONENTS_TERRAIN_H
#include <OgreAxisAlignedBox.h>
#include <OgreTexture.h>
#include <OgreWorkQueue.h>
#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<QuadTreeNode*>& 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<float> mPositions;
std::vector<float> mNormals;
std::vector<Ogre::uint8> mColours;
friend std::ostream& operator<<(std::ostream& o, const LoadResponseData& r)
{ return o; }
};
struct LayersRequestData
{
std::vector<QuadTreeNode*> mNodes;
bool mPack;
friend std::ostream& operator<<(std::ostream& o, const LayersRequestData& r)
{ return o; }
};
struct LayersResponseData
{
std::vector<LayerCollection> mLayerCollections;
friend std::ostream& operator<<(std::ostream& o, const LayersResponseData& r)
{ return o; }
};
}
#endif

View file

@ -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)

View file

@ -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<LayerInfo>& layerList) { mLayerList = layerList; }
bool hasLayers() { return mLayerList.size(); }
@ -23,6 +19,7 @@ namespace Terrain
const std::vector<Ogre::TexturePtr>& 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; }

View file

@ -6,7 +6,7 @@
#include <OgreMaterialManager.h>
#include <OgreTextureManager.h>
#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 &center, QuadTreeNode* parent)
QuadTreeNode::QuadTreeNode(DefaultWorld* terrain, ChildDirection dir, float size, const Ogre::Vector2 &center, 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 &center)
@ -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<float> 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<LayerInfo> layer;
layer.push_back(mTerrain->getStorage()->getDefaultLayer());
matGen.setLayerList(layer);

View file

@ -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;

View file

@ -1,356 +1,60 @@
#include "world.hpp"
#include <OgreAxisAlignedBox.h>
#include <OgreCamera.h>
#include <OgreHardwarePixelBuffer.h>
#include <OgreTextureManager.h>
#include <OgreRenderTexture.h>
#include <OgreSceneNode.h>
#include <OgreRoot.h>
#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<QuadTreeNode*>& 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<LoadRequestData>(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<LayersRequestData>(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<LoadResponseData*>(res->getData());
const LoadRequestData requestData = Ogre::any_cast<LoadRequestData>(res->getRequest()->getData());
requestData.mNode->load(*data);
delete data;
--mChunksLoading;
}
else // REQ_ID_LAYERS
{
LayersResponseData* data = Ogre::any_cast<LayersResponseData*>(res->getData());
for (std::vector<LayerCollection>::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;
}
}
}

View file

@ -1,50 +1,38 @@
#ifndef COMPONENTS_TERRAIN_H
#define COMPONENTS_TERRAIN_H
#ifndef COMPONENTS_TERRAIN_WORLD_H
#define COMPONENTS_TERRAIN_WORLD_H
#include <OgreAxisAlignedBox.h>
#include <OgreTexture.h>
#include <OgreWorkQueue.h>
#include <OgreVector3.h>
#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<QuadTreeNode*>& 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<float> mPositions;
std::vector<float> mNormals;
std::vector<Ogre::uint8> mColours;
friend std::ostream& operator<<(std::ostream& o, const LoadResponseData& r)
{ return o; }
};
struct LayersRequestData
{
std::vector<QuadTreeNode*> mNodes;
bool mPack;
friend std::ostream& operator<<(std::ostream& o, const LayersRequestData& r)
{ return o; }
};
struct LayersResponseData
{
std::vector<LayerCollection> mLayerCollections;
friend std::ostream& operator<<(std::ostream& o, const LayersResponseData& r)
{ return o; }
};
}