mirror of
https://github.com/TES3MP/openmw-tes3mp.git
synced 2025-01-29 21:45:32 +00:00
Terrain: background load blendmaps & layer textures. Refactor QuadTree update.
This commit is contained in:
parent
4328e08162
commit
1d926816b5
13 changed files with 296 additions and 143 deletions
|
@ -651,13 +651,20 @@ void RenderingManager::setGlare(bool glare)
|
|||
mSkyManager->setGlare(glare);
|
||||
}
|
||||
|
||||
void RenderingManager::updateTerrain()
|
||||
{
|
||||
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)
|
||||
{
|
||||
// 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)
|
||||
mTerrain->syncLoad();
|
||||
|
||||
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 ¢er, 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())
|
||||
{
|
||||
for (int i=0; i<4; ++i)
|
||||
if (mChildren[i]->hasChunk())
|
||||
mChildren[i]->update(cameraPos);
|
||||
}
|
||||
}
|
||||
if (!wantToDisplay)
|
||||
{
|
||||
// We do not want to display this node - delegate to children
|
||||
if (mChunk)
|
||||
mChunk->setVisible(false);
|
||||
if (hasChildren())
|
||||
{
|
||||
// Are children already loaded?
|
||||
bool childrenLoaded = true;
|
||||
for (int i=0; i<4; ++i)
|
||||
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;
|
||||
}
|
||||
else
|
||||
{
|
||||
bool success = true;
|
||||
for (int i=0; i<4; ++i)
|
||||
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)
|
||||
if (mTerrain->areLayersLoaded())
|
||||
{
|
||||
ensureLayerInfo();
|
||||
mChunk->setMaterial(mMaterialGenerator->generate(mChunk->getMaterial()));
|
||||
if (mSize == 1)
|
||||
{
|
||||
mChunk->setMaterial(mMaterialGenerator->generate(mChunk->getMaterial()));
|
||||
}
|
||||
else
|
||||
{
|
||||
ensureCompositeMap();
|
||||
mMaterialGenerator->setCompositeMap(mCompositeMap->getName());
|
||||
mChunk->setMaterial(mMaterialGenerator->generateForCompositeMap(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());
|
||||
|
||||
responseData->time = timer.getMicroseconds();
|
||||
LayersResponseData* responseData = new LayersResponseData();
|
||||
|
||||
return OGRE_NEW Ogre::WorkQueue::Response(req, true, Ogre::Any(responseData));
|
||||
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)
|
||||
{
|
||||
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…
Reference in a new issue