Terrain: background load blendmaps & layer textures. Refactor QuadTree update.

pull/24/head
scrawl 11 years ago
parent 4328e08162
commit 1d926816b5

@ -651,13 +651,20 @@ void RenderingManager::setGlare(bool glare)
mSkyManager->setGlare(glare);
}
void RenderingManager::requestMap(MWWorld::CellStore* cell)
void RenderingManager::updateTerrain()
{
// FIXME: move to other method
// TODO: probably not needed when crossing a cell border. Could delay the map render until we are loaded.
if (mTerrain)
{
// Avoid updating with dims.getCenter for each cell. Player position should be good enough
mTerrain->update(mRendering.getCamera()->getRealPosition());
mTerrain->syncLoad();
// need to update again so the chunks that were just loaded can be made visible
mTerrain->update(mRendering.getCamera()->getRealPosition());
}
}
void RenderingManager::requestMap(MWWorld::CellStore* cell)
{
if (cell->getCell()->isExterior())
{
assert(mTerrain);
@ -666,9 +673,6 @@ void RenderingManager::requestMap(MWWorld::CellStore* cell)
Ogre::Vector2 center (cell->getCell()->getGridX() + 0.5, cell->getCell()->getGridY() + 0.5);
dims.merge(mTerrain->getWorldBoundingBox(center));
if (dims.isFinite())
mTerrain->update(dims.getCenter());
mLocalMap->requestMap(cell, dims.getMinimum().z, dims.getMaximum().z);
}
else
@ -1059,8 +1063,7 @@ void RenderingManager::enableTerrain(bool enable)
}
mTerrain->setVisible(true);
}
else
if (mTerrain)
else if (mTerrain)
mTerrain->setVisible(false);
}

@ -180,6 +180,10 @@ public:
void removeWaterRippleEmitter (const MWWorld::Ptr& ptr);
void updateWaterRippleEmitterPtr (const MWWorld::Ptr& old, const MWWorld::Ptr& ptr);
void updateTerrain ();
///< update the terrain according to the player position. Usually done automatically, but should be done manually
/// before calling requestMap
void requestMap (MWWorld::CellStore* cell);
///< request the local map for a cell

@ -5,6 +5,7 @@
#include <OgreStringConverter.h>
#include <OgreRenderSystem.h>
#include <OgreResourceGroupManager.h>
#include <OgreResourceBackgroundQueue.h>
#include <OgreRoot.h>
#include <boost/algorithm/string.hpp>
@ -13,6 +14,8 @@
#include "../mwbase/environment.hpp"
#include "../mwworld/esmstore.hpp"
#include <components/terrain/quadtreenode.hpp>
namespace MWRender
{
@ -329,8 +332,24 @@ namespace MWRender
return texture;
}
void TerrainStorage::getBlendmaps (const std::vector<Terrain::QuadTreeNode*>& nodes, std::vector<Terrain::LayerCollection>& out, bool pack)
{
for (std::vector<Terrain::QuadTreeNode*>::const_iterator it = nodes.begin(); it != nodes.end(); ++it)
{
out.push_back(Terrain::LayerCollection());
out.back().mTarget = *it;
getBlendmapsImpl((*it)->getSize(), (*it)->getCenter(), pack, out.back().mBlendmaps, out.back().mLayers);
}
}
void TerrainStorage::getBlendmaps(float chunkSize, const Ogre::Vector2 &chunkCenter,
bool pack, std::vector<Ogre::TexturePtr> &blendmaps, std::vector<Terrain::LayerInfo> &layerList)
bool pack, std::vector<Ogre::PixelBox> &blendmaps, std::vector<Terrain::LayerInfo> &layerList)
{
getBlendmapsImpl(chunkSize, chunkCenter, pack, blendmaps, layerList);
}
void TerrainStorage::getBlendmapsImpl(float chunkSize, const Ogre::Vector2 &chunkCenter,
bool pack, std::vector<Ogre::PixelBox> &blendmaps, std::vector<Terrain::LayerInfo> &layerList)
{
// TODO - blending isn't completely right yet; the blending radius appears to be
// different at a cell transition (2 vertices, not 4), so we may need to create a larger blendmap
@ -375,16 +394,14 @@ namespace MWRender
// Second iteration - create and fill in the blend maps
const int blendmapSize = ESM::Land::LAND_TEXTURE_SIZE+1;
std::vector<Ogre::uchar> data;
data.resize(blendmapSize * blendmapSize * channels, 0);
for (int i=0; i<numBlendmaps; ++i)
{
Ogre::PixelFormat format = pack ? Ogre::PF_A8B8G8R8 : Ogre::PF_A8;
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, blendmapSize, blendmapSize, 0, format);
Ogre::uchar* pData =
OGRE_ALLOC_T(Ogre::uchar, blendmapSize*blendmapSize*channels, Ogre::MEMCATEGORY_GENERAL);
memset(pData, 0, blendmapSize*blendmapSize*channels);
for (int y=0; y<blendmapSize; ++y)
{
@ -396,16 +413,12 @@ namespace MWRender
int channel = pack ? std::max(0, (layerIndex-1) % 4) : 0;
if (blendIndex == i)
data[y*blendmapSize*channels + x*channels + channel] = 255;
pData[y*blendmapSize*channels + x*channels + channel] = 255;
else
data[y*blendmapSize*channels + x*channels + channel] = 0;
pData[y*blendmapSize*channels + x*channels + channel] = 0;
}
}
// All done, upload to GPU
Ogre::DataStreamPtr stream(new Ogre::MemoryDataStream(&data[0], data.size()));
map->loadRawData(stream, blendmapSize, blendmapSize, format);
blendmaps.push_back(map);
blendmaps.push_back(Ogre::PixelBox(blendmapSize, blendmapSize, 1, format, pData));
}
}
@ -527,6 +540,12 @@ namespace MWRender
info.mSpecular = true;
}
// This wasn't cached, so the textures are probably not loaded either.
// Background load them so they are hopefully already loaded once we need them!
Ogre::ResourceBackgroundQueue::getSingleton().load("Texture", info.mDiffuseMap, "General");
if (!info.mNormalMap.empty())
Ogre::ResourceBackgroundQueue::getSingleton().load("Texture", info.mNormalMap, "General");
mLayerInfoMap[texture] = info;
return info;

@ -46,6 +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.
/// @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) -
@ -54,9 +55,21 @@ namespace MWRender
/// @param blendmaps created blendmaps will be written here
/// @param layerList names of the layer textures used will be written here
virtual void getBlendmaps (float chunkSize, const Ogre::Vector2& chunkCenter, bool pack,
std::vector<Ogre::TexturePtr>& blendmaps,
std::vector<Ogre::PixelBox>& blendmaps,
std::vector<Terrain::LayerInfo>& layerList);
/// Retrieve pixel data for textures holding layer blend values for terrain chunks and layer texture information.
/// 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.
/// @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) -
/// otherwise, each texture contains blend values for one layer only. Shader-based rendering
/// can utilize packing, FFP can't.
virtual void getBlendmaps (const std::vector<Terrain::QuadTreeNode*>& nodes, std::vector<Terrain::LayerCollection>& out, bool pack);
virtual float getHeightAt (const Ogre::Vector3& worldPos);
virtual Terrain::LayerInfo getDefaultLayer();
@ -86,6 +99,11 @@ namespace MWRender
std::map<std::string, Terrain::LayerInfo> mLayerInfoMap;
Terrain::LayerInfo getLayerInfo(const std::string& texture);
// Non-virtual
void getBlendmapsImpl (float chunkSize, const Ogre::Vector2& chunkCenter, bool pack,
std::vector<Ogre::PixelBox>& blendmaps,
std::vector<Terrain::LayerInfo>& layerList);
};
}

@ -195,10 +195,12 @@ namespace MWWorld
mechMgr->updateCell(old, player);
mechMgr->watchActor(player);
MWBase::Environment::get().getWindowManager()->changeCell(mCurrentCell);
mRendering.updateTerrain();
for (CellStoreCollection::iterator active = mActiveCells.begin(); active!=mActiveCells.end(); ++active)
mRendering.requestMap(*active);
MWBase::Environment::get().getWindowManager()->changeCell(mCurrentCell);
}
void Scene::changeToVoid()

@ -110,6 +110,7 @@ namespace Terrain
void Chunk::getRenderOperation(Ogre::RenderOperation& op)
{
assert (!mIndexData->indexBuffer.isNull() && "Trying to render, but no index buffer set!");
assert(!mMaterial.isNull() && "Trying to render, but no material set!");
op.useIndexes = true;
op.operationType = Ogre::RenderOperation::OT_TRIANGLE_LIST;
op.vertexData = mVertexData;

@ -3,6 +3,7 @@
namespace Terrain
{
class QuadTreeNode;
/// The alignment of the terrain
enum Alignment
@ -51,6 +52,14 @@ namespace Terrain
bool mParallax; // Height info in normal map alpha channel?
bool mSpecular; // Specular info in diffuse map alpha channel?
};
struct LayerCollection
{
QuadTreeNode* mTarget;
// Since we can't create a texture from a different thread, this only holds the raw texel data
std::vector<Ogre::PixelBox> mBlendmaps;
std::vector<LayerInfo> mLayers;
};
}
#endif

@ -48,11 +48,15 @@ namespace Terrain
Ogre::MaterialPtr MaterialGenerator::generate(Ogre::MaterialPtr mat)
{
assert(!mLayerList.empty() && "Can't create material with no layers");
return create(mat, false, false);
}
Ogre::MaterialPtr MaterialGenerator::generateForCompositeMapRTT(Ogre::MaterialPtr mat)
{
assert(!mLayerList.empty() && "Can't create material with no layers");
return create(mat, true, false);
}

@ -144,7 +144,6 @@ namespace
QuadTreeNode::QuadTreeNode(World* terrain, ChildDirection dir, float size, const Ogre::Vector2 &center, QuadTreeNode* parent)
: mMaterialGenerator(NULL)
, mIsActive(false)
, mIsDummy(false)
, mSize(size)
, mLodLevel(Log2(mSize))
@ -262,11 +261,13 @@ const Ogre::AxisAlignedBox& QuadTreeNode::getWorldBoundingBox()
return mWorldBounds;
}
void QuadTreeNode::update(const Ogre::Vector3 &cameraPos)
bool QuadTreeNode::update(const Ogre::Vector3 &cameraPos)
{
const Ogre::AxisAlignedBox& bounds = getBoundingBox();
if (bounds.isNull())
return;
if (isDummy())
return true;
if (mBounds.isNull())
return true;
float dist = distance(mWorldBounds, cameraPos);
@ -291,11 +292,9 @@ void QuadTreeNode::update(const Ogre::Vector3 &cameraPos)
wantedLod = 3;
else if (dist > cellWorldSize*2)
wantedLod = 2;
else if (dist > cellWorldSize)
else if (dist > cellWorldSize * 1.42) // < sqrt2 so the 3x3 grid around player is always highest lod
wantedLod = 1;
mIsActive = true;
bool wantToDisplay = mSize <= mTerrain->getMaxBatchSize() && mLodLevel <= wantedLod;
if (wantToDisplay)
@ -305,6 +304,7 @@ void QuadTreeNode::update(const Ogre::Vector3 &cameraPos)
{
mLoadState = LS_Loading;
mTerrain->queueLoad(this);
return false;
}
if (mLoadState == LS_Loaded)
@ -331,47 +331,60 @@ void QuadTreeNode::update(const Ogre::Vector3 &cameraPos)
if (!mChunk->getVisible() && hasChildren())
{
// Make sure child scene nodes are detached
mSceneNode->removeAllChildren();
// TODO: unload
//for (int i=0; i<4; ++i)
// mChildren[i]->unload();
for (int i=0; i<4; ++i)
mChildren[i]->unload(true);
}
mChunk->setVisible(true);
return true;
}
return false; // LS_Loading
}
if (wantToDisplay && mLoadState != LS_Loaded)
// We do not want to display this node - delegate to children if they are already loaded
if (!wantToDisplay && hasChildren())
{
// We want to display, but aren't ready yet. Perhaps our child nodes are ready?
// TODO: this doesn't check child-child nodes...
if (hasChildren())
if (mChunk)
{
// Are children already loaded?
bool childrenLoaded = true;
for (int i=0; i<4; ++i)
if (mChildren[i]->hasChunk())
mChildren[i]->update(cameraPos);
if (!mChildren[i]->update(cameraPos))
childrenLoaded = false;
if (!childrenLoaded)
{
// Make sure child scene nodes are detached until all children are loaded
mSceneNode->removeAllChildren();
}
else
{
// Delegation went well, we can unload now
unload();
for (int i=0; i<4; ++i)
{
if (!mChildren[i]->getSceneNode()->isInSceneGraph())
mSceneNode->addChild(mChildren[i]->getSceneNode());
}
}
return true;
}
}
if (!wantToDisplay)
{
// We do not want to display this node - delegate to children
if (mChunk)
mChunk->setVisible(false);
if (hasChildren())
else
{
bool success = true;
for (int i=0; i<4; ++i)
mChildren[i]->update(cameraPos);
success = mChildren[i]->update(cameraPos) & success;
return success;
}
}
return false;
}
void QuadTreeNode::load(const LoadResponseData &data)
{
assert (!mChunk);
std::cout << "loading " << std::endl;
mChunk = new Chunk(mTerrain->getBufferCache().getUVBuffer(), mBounds, data);
mChunk->setVisibilityFlags(mTerrain->getVisiblityFlags());
mChunk->setCastShadows(true);
@ -380,50 +393,49 @@ void QuadTreeNode::load(const LoadResponseData &data)
mMaterialGenerator->enableShadows(mTerrain->getShadowsEnabled());
mMaterialGenerator->enableSplitShadows(mTerrain->getSplitShadowsEnabled());
if (mSize == 1)
{
ensureLayerInfo();
mChunk->setMaterial(mMaterialGenerator->generate(mChunk->getMaterial()));
}
else
if (mTerrain->areLayersLoaded())
{
ensureCompositeMap();
mMaterialGenerator->setCompositeMap(mCompositeMap->getName());
mChunk->setMaterial(mMaterialGenerator->generateForCompositeMap(mChunk->getMaterial()));
if (mSize == 1)
{
mChunk->setMaterial(mMaterialGenerator->generate(mChunk->getMaterial()));
}
else
{
ensureCompositeMap();
mMaterialGenerator->setCompositeMap(mCompositeMap->getName());
mChunk->setMaterial(mMaterialGenerator->generateForCompositeMap(mChunk->getMaterial()));
}
}
// else: will be loaded in loadMaterials() after background thread has finished loading layers
mChunk->setVisible(false);
mLoadState = LS_Loaded;
}
void QuadTreeNode::unload()
void QuadTreeNode::unload(bool recursive)
{
if (mChunk)
{
Ogre::MaterialManager::getSingleton().remove(mChunk->getMaterial()->getName());
mSceneNode->detachObject(mChunk);
delete mChunk;
mChunk = NULL;
// destroy blendmaps
if (mMaterialGenerator)
{
const std::vector<Ogre::TexturePtr>& list = mMaterialGenerator->getBlendmapList();
for (std::vector<Ogre::TexturePtr>::const_iterator it = list.begin(); it != list.end(); ++it)
Ogre::TextureManager::getSingleton().remove((*it)->getName());
mMaterialGenerator->setBlendmapList(std::vector<Ogre::TexturePtr>());
mMaterialGenerator->setLayerList(std::vector<LayerInfo>());
mMaterialGenerator->setCompositeMap("");
}
if (!mCompositeMap.isNull())
{
Ogre::TextureManager::getSingleton().remove(mCompositeMap->getName());
mCompositeMap.setNull();
}
// Do *not* set this when we are still loading!
mLoadState = LS_Unloaded;
}
if (recursive && hasChildren())
{
for (int i=0; i<4; ++i)
mChildren[i]->unload();
}
mLoadState = LS_Unloaded;
}
void QuadTreeNode::updateIndexBuffers()
@ -479,17 +491,53 @@ size_t QuadTreeNode::getActualLodLevel()
return mLodLevel /* + mChunk->getAdditionalLod() */;
}
void QuadTreeNode::ensureLayerInfo()
void QuadTreeNode::loadLayers(const LayerCollection& collection)
{
if (mMaterialGenerator->hasLayers())
assert (!mMaterialGenerator->hasLayers());
std::vector<Ogre::TexturePtr> blendTextures;
for (std::vector<Ogre::PixelBox>::const_iterator it = collection.mBlendmaps.begin(); it != collection.mBlendmaps.end(); ++it)
{
// TODO: clean up blend textures on destruction
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);
}
mMaterialGenerator->setLayerList(collection.mLayers);
mMaterialGenerator->setBlendmapList(blendTextures);
}
void QuadTreeNode::loadMaterials()
{
if (isDummy())
return;
std::vector<Ogre::TexturePtr> blendmaps;
std::vector<LayerInfo> layerList;
mTerrain->getStorage()->getBlendmaps(mSize, mCenter, mTerrain->getShadersEnabled(), blendmaps, layerList);
// Load children first since we depend on them when creating a composite map
if (hasChildren())
{
for (int i=0; i<4; ++i)
mChildren[i]->loadMaterials();
}
mMaterialGenerator->setLayerList(layerList);
mMaterialGenerator->setBlendmapList(blendmaps);
if (mChunk)
{
if (mSize == 1)
{
mChunk->setMaterial(mMaterialGenerator->generate(mChunk->getMaterial()));
}
else
{
ensureCompositeMap();
mMaterialGenerator->setCompositeMap(mCompositeMap->getName());
mChunk->setMaterial(mMaterialGenerator->generateForCompositeMap(mChunk->getMaterial()));
}
}
}
void QuadTreeNode::prepareForCompositeMap(Ogre::TRect<float> area)
@ -525,8 +573,7 @@ void QuadTreeNode::prepareForCompositeMap(Ogre::TRect<float> area)
}
else
{
ensureLayerInfo();
// TODO: when to destroy?
Ogre::MaterialPtr material = mMaterialGenerator->generateForCompositeMapRTT(Ogre::MaterialPtr());
makeQuad(sceneMgr, area.left, area.top, area.right, area.bottom, material);
}
@ -570,13 +617,3 @@ void QuadTreeNode::applyMaterials()
for (int i=0; i<4; ++i)
mChildren[i]->applyMaterials();
}
void QuadTreeNode::setVisible(bool visible)
{
if (!visible && mChunk)
mChunk->setVisible(false);
if (hasChildren())
for (int i=0; i<4; ++i)
mChildren[i]->setVisible(visible);
}

@ -51,8 +51,6 @@ namespace Terrain
QuadTreeNode (World* terrain, ChildDirection dir, float size, const Ogre::Vector2& center, QuadTreeNode* parent);
~QuadTreeNode();
void setVisible(bool visible);
/// Rebuild all materials
void applyMaterials();
@ -100,7 +98,9 @@ namespace Terrain
World* getTerrain() { return mTerrain; }
/// Adjust LODs for the given camera position, possibly splitting up chunks or merging them.
void update (const Ogre::Vector3& cameraPos);
/// @param force Always choose to render this node, even if not the perfect LOD.
/// @return Did we (or all of our children) choose to render?
bool update (const Ogre::Vector3& cameraPos);
/// Adjust index buffers of chunks to stitch together chunks of different LOD, so that cracks are avoided.
/// Call after QuadTreeNode::update!
@ -122,13 +122,17 @@ namespace Terrain
/// Add a textured quad to a specific 2d area in the composite map scenemanager.
/// Only nodes with size <= 1 can be rendered with alpha blending, so larger nodes will simply
/// call this method on their children.
/// @note Do not call this before World::areLayersLoaded() == true
/// @param area area in image space to put the quad
/// @param quads collect quads here so they can be deleted later
void prepareForCompositeMap(Ogre::TRect<float> area);
/// Create a chunk for this node from the given data.
void load (const LoadResponseData& data);
void unload();
void unload(bool recursive=false);
void loadLayers (const LayerCollection& collection);
/// This is recursive! Call it once on the root node after all leafs have loaded layers.
void loadMaterials();
LoadState getLoadState() { return mLoadState; }
@ -138,10 +142,6 @@ namespace Terrain
LoadState mLoadState;
/// Is this node (or any of its child nodes) currently configured to render itself?
/// (only relevant when distant land is disabled, otherwise whole terrain is always rendered)
bool mIsActive;
bool mIsDummy;
float mSize;
size_t mLodLevel; // LOD if we were to render this node in one chunk
@ -162,7 +162,6 @@ namespace Terrain
Ogre::TexturePtr mCompositeMap;
void ensureLayerInfo();
void ensureCompositeMap();
};

@ -44,6 +44,7 @@ namespace Terrain
/// 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 background threads. Make sure to only call thread-safe functions from here!
/// @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) -
@ -52,9 +53,21 @@ namespace Terrain
/// @param blendmaps created blendmaps will be written here
/// @param layerList names of the layer textures used will be written here
virtual void getBlendmaps (float chunkSize, const Ogre::Vector2& chunkCenter, bool pack,
std::vector<Ogre::TexturePtr>& blendmaps,
std::vector<Ogre::PixelBox>& blendmaps,
std::vector<LayerInfo>& layerList) = 0;
/// Retrieve pixel data for textures holding layer blend values for terrain chunks and layer texture information.
/// 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 background threads. Make sure to only call thread-safe functions from here!
/// @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) -
/// otherwise, each texture contains blend values for one layer only. Shader-based rendering
/// can utilize packing, FFP can't.
virtual void getBlendmaps (const std::vector<QuadTreeNode*>& nodes, std::vector<LayerCollection>& out, bool pack) = 0;
virtual float getHeightAt (const Ogre::Vector3& worldPos) = 0;
virtual LayerInfo getDefaultLayer() = 0;

@ -7,7 +7,6 @@
#include <OgreRenderTexture.h>
#include <OgreSceneNode.h>
#include <OgreRoot.h>
#include <OgreTimer.h> // TEMP
#include "storage.hpp"
#include "quadtreenode.hpp"
@ -52,6 +51,9 @@ namespace
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 distantLand, bool shaders, Alignment align, float minBatchSize, float maxBatchSize)
: mStorage(storage)
@ -70,6 +72,7 @@ namespace Terrain
, mChunksLoading(0)
, mWorkQueueChannel(0)
, mCache(storage->getCellVertices())
, mLayerLoadPending(true)
{
#if TERRAIN_USE_SHADER == 0
if (mShaders)
@ -102,8 +105,12 @@ namespace Terrain
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);
buildQuadTree(mRootNode, data.mNodes);
//loadingListener->indicateProgress();
mRootNode->initAabb();
//loadingListener->indicateProgress();
@ -114,6 +121,9 @@ namespace Terrain
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()
@ -126,7 +136,7 @@ namespace Terrain
delete mStorage;
}
void World::buildQuadTree(QuadTreeNode *node)
void World::buildQuadTree(QuadTreeNode *node, std::vector<QuadTreeNode*>& leafs)
{
float halfSize = node->getSize()/2.f;
@ -142,6 +152,7 @@ namespace Terrain
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
@ -164,10 +175,10 @@ namespace Terrain
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));
buildQuadTree(node->getChild(SE));
buildQuadTree(node->getChild(NW));
buildQuadTree(node->getChild(NE));
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)
@ -267,7 +278,7 @@ namespace Terrain
void World::syncLoad()
{
while (mChunksLoading)
while (mChunksLoading || mLayerLoadPending)
{
OGRE_THREAD_SLEEP(0);
Ogre::Root::getSingleton().getWorkQueue()->processResponses();
@ -276,27 +287,36 @@ namespace Terrain
Ogre::WorkQueue::Response* World::handleRequest(const Ogre::WorkQueue::Request *req, const Ogre::WorkQueue *srcQ)
{
const LoadRequestData data = Ogre::any_cast<LoadRequestData>(req->getData());
if (req->getType() == REQ_ID_CHUNK)
{
const LoadRequestData data = Ogre::any_cast<LoadRequestData>(req->getData());
QuadTreeNode* node = data.mNode;
QuadTreeNode* node = data.mNode;
LoadResponseData* responseData = new LoadResponseData();
LoadResponseData* responseData = new LoadResponseData();
Ogre::Timer timer;
getStorage()->fillVertexBuffers(node->getNativeLodLevel(), node->getSize(), node->getCenter(), getAlign(),
responseData->mPositions, responseData->mNormals, responseData->mColours);
getStorage()->fillVertexBuffers(node->getNativeLodLevel(), node->getSize(), node->getCenter(), getAlign(),
responseData->mPositions, responseData->mNormals, responseData->mColours);
std::cout << "THREAD" << std::endl;
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();
responseData->time = timer.getMicroseconds();
getStorage()->getBlendmaps(data.mNodes, responseData->mLayerCollections, data.mPack);
return OGRE_NEW Ogre::WorkQueue::Response(req, true, Ogre::Any(responseData));
return OGRE_NEW Ogre::WorkQueue::Response(req, true, Ogre::Any(responseData));
}
}
void World::handleResponse(const Ogre::WorkQueue::Response *res, const Ogre::WorkQueue *srcQ)
{
static unsigned long time = 0;
if (res->succeeded())
assert(res->succeeded() && "Response failure not handled");
if (res->getRequest()->getType() == REQ_ID_CHUNK)
{
LoadResponseData* data = Ogre::any_cast<LoadResponseData*>(res->getData());
@ -304,23 +324,31 @@ namespace Terrain
requestData.mNode->load(*data);
time += data->time;
delete data;
std::cout << "RESPONSE, reqs took ms" << time/1000.f << std::endl;
--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);
}
mRootNode->loadMaterials();
mLayerLoadPending = false;
}
--mChunksLoading;
}
void World::queueLoad(QuadTreeNode *node)
{
LoadRequestData data;
data.mNode = node;
data.mPack = getShadersEnabled();
const Ogre::uint16 loadRequestId = 1;
Ogre::Root::getSingleton().getWorkQueue()->addRequest(mWorkQueueChannel, loadRequestId, Ogre::Any(data));
Ogre::Root::getSingleton().getWorkQueue()->addRequest(mWorkQueueChannel, REQ_ID_CHUNK, Ogre::Any(data));
++mChunksLoading;
}
}

@ -122,15 +122,20 @@ namespace Terrain
/// Maximum size of a terrain batch along one side (in cell units)
float mMaxBatchSize;
void buildQuadTree(QuadTreeNode* node);
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);
@ -151,7 +156,6 @@ namespace Terrain
struct LoadRequestData
{
QuadTreeNode* mNode;
bool mPack;
friend std::ostream& operator<<(std::ostream& o, const LoadRequestData& r)
{ return o; }
@ -162,16 +166,28 @@ namespace Terrain
std::vector<float> mPositions;
std::vector<float> mNormals;
std::vector<Ogre::uint8> mColours;
// Since we can't create a texture from a different thread, this only holds the raw texel data
std::vector< std::vector<Ogre::uint8> > mBlendmaps;
std::vector<Terrain::LayerInfo> mLayerList;
unsigned long time;
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

Loading…
Cancel
Save