forked from mirror/openmw-tes3mp
Terrain: geometry is now loaded in background threads.
TODO: background load layer textures and blendmaps. "Distant land" setting has been removed for now (i.e. always enabled).
This commit is contained in:
parent
b3fed853ae
commit
195071efc7
12 changed files with 266 additions and 126 deletions
|
@ -651,6 +651,11 @@ void RenderingManager::setGlare(bool glare)
|
||||||
|
|
||||||
void RenderingManager::requestMap(MWWorld::CellStore* cell)
|
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())
|
if (cell->getCell()->isExterior())
|
||||||
{
|
{
|
||||||
assert(mTerrain);
|
assert(mTerrain);
|
||||||
|
|
|
@ -164,9 +164,9 @@ namespace MWRender
|
||||||
}
|
}
|
||||||
|
|
||||||
void TerrainStorage::fillVertexBuffers (int lodLevel, float size, const Ogre::Vector2& center, Terrain::Alignment align,
|
void TerrainStorage::fillVertexBuffers (int lodLevel, float size, const Ogre::Vector2& center, Terrain::Alignment align,
|
||||||
Ogre::HardwareVertexBufferSharedPtr vertexBuffer,
|
std::vector<float>& positions,
|
||||||
Ogre::HardwareVertexBufferSharedPtr normalBuffer,
|
std::vector<float>& normals,
|
||||||
Ogre::HardwareVertexBufferSharedPtr colourBuffer)
|
std::vector<Ogre::uint8>& colours)
|
||||||
{
|
{
|
||||||
// LOD level n means every 2^n-th vertex is kept
|
// LOD level n means every 2^n-th vertex is kept
|
||||||
size_t increment = 1 << lodLevel;
|
size_t increment = 1 << lodLevel;
|
||||||
|
@ -180,11 +180,8 @@ namespace MWRender
|
||||||
|
|
||||||
size_t numVerts = size*(ESM::Land::LAND_SIZE-1)/increment + 1;
|
size_t numVerts = size*(ESM::Land::LAND_SIZE-1)/increment + 1;
|
||||||
|
|
||||||
std::vector<uint8_t> colors;
|
colours.resize(numVerts*numVerts*4);
|
||||||
colors.resize(numVerts*numVerts*4);
|
|
||||||
std::vector<float> positions;
|
|
||||||
positions.resize(numVerts*numVerts*3);
|
positions.resize(numVerts*numVerts*3);
|
||||||
std::vector<float> normals;
|
|
||||||
normals.resize(numVerts*numVerts*3);
|
normals.resize(numVerts*numVerts*3);
|
||||||
|
|
||||||
Ogre::Vector3 normal;
|
Ogre::Vector3 normal;
|
||||||
|
@ -270,7 +267,7 @@ namespace MWRender
|
||||||
color.a = 1;
|
color.a = 1;
|
||||||
Ogre::uint32 rsColor;
|
Ogre::uint32 rsColor;
|
||||||
Ogre::Root::getSingleton().getRenderSystem()->convertColourValue(color, &rsColor);
|
Ogre::Root::getSingleton().getRenderSystem()->convertColourValue(color, &rsColor);
|
||||||
memcpy(&colors[vertX*numVerts*4 + vertY*4], &rsColor, sizeof(Ogre::uint32));
|
memcpy(&colours[vertX*numVerts*4 + vertY*4], &rsColor, sizeof(Ogre::uint32));
|
||||||
|
|
||||||
++vertX;
|
++vertX;
|
||||||
}
|
}
|
||||||
|
@ -283,10 +280,6 @@ namespace MWRender
|
||||||
assert(vertX_ == numVerts); // Ensure we covered whole area
|
assert(vertX_ == numVerts); // Ensure we covered whole area
|
||||||
}
|
}
|
||||||
assert(vertY_ == numVerts); // Ensure we covered whole area
|
assert(vertY_ == numVerts); // Ensure we covered whole area
|
||||||
|
|
||||||
vertexBuffer->writeData(0, vertexBuffer->getSizeInBytes(), &positions[0], true);
|
|
||||||
normalBuffer->writeData(0, normalBuffer->getSizeInBytes(), &normals[0], true);
|
|
||||||
colourBuffer->writeData(0, colourBuffer->getSizeInBytes(), &colors[0], true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TerrainStorage::UniqueTextureId TerrainStorage::getVtexIndexAt(int cellX, int cellY,
|
TerrainStorage::UniqueTextureId TerrainStorage::getVtexIndexAt(int cellX, int cellY,
|
||||||
|
|
|
@ -19,8 +19,8 @@ namespace MWRender
|
||||||
/// Get bounds of the whole terrain in cell units
|
/// Get bounds of the whole terrain in cell units
|
||||||
virtual void getBounds(float& minX, float& maxX, float& minY, float& maxY);
|
virtual void getBounds(float& minX, float& maxX, float& minY, float& maxY);
|
||||||
|
|
||||||
/// Get the minimum and maximum heights of a terrain chunk.
|
/// Get the minimum and maximum heights of a terrain region.
|
||||||
/// @note Should only be called for chunks <= 1 cell, i.e. leafs of the quad tree.
|
/// @note Will only be called for chunks with size = minBatchSize, i.e. leafs of the quad tree.
|
||||||
/// Larger chunks can simply merge AABB of children.
|
/// Larger chunks can simply merge AABB of children.
|
||||||
/// @param size size of the chunk in cell units
|
/// @param size size of the chunk in cell units
|
||||||
/// @param center center of the chunk in cell units
|
/// @param center center of the chunk in cell units
|
||||||
|
@ -30,16 +30,18 @@ namespace MWRender
|
||||||
virtual bool getMinMaxHeights (float size, const Ogre::Vector2& center, float& min, float& max);
|
virtual bool getMinMaxHeights (float size, const Ogre::Vector2& center, float& min, float& max);
|
||||||
|
|
||||||
/// Fill vertex buffers for a terrain chunk.
|
/// Fill vertex buffers for a terrain chunk.
|
||||||
|
/// @note May be called from background threads. Make sure to only call thread-safe functions from here!
|
||||||
|
/// @note returned colors need to be in render-system specific format! Use RenderSystem::convertColourValue.
|
||||||
/// @param lodLevel LOD level, 0 = most detailed
|
/// @param lodLevel LOD level, 0 = most detailed
|
||||||
/// @param size size of the terrain chunk in cell units
|
/// @param size size of the terrain chunk in cell units
|
||||||
/// @param center center of the chunk in cell units
|
/// @param center center of the chunk in cell units
|
||||||
/// @param vertexBuffer buffer to write vertices
|
/// @param positions buffer to write vertices
|
||||||
/// @param normalBuffer buffer to write vertex normals
|
/// @param normals buffer to write vertex normals
|
||||||
/// @param colourBuffer buffer to write vertex colours
|
/// @param colours buffer to write vertex colours
|
||||||
virtual void fillVertexBuffers (int lodLevel, float size, const Ogre::Vector2& center, Terrain::Alignment align,
|
virtual void fillVertexBuffers (int lodLevel, float size, const Ogre::Vector2& center, Terrain::Alignment align,
|
||||||
Ogre::HardwareVertexBufferSharedPtr vertexBuffer,
|
std::vector<float>& positions,
|
||||||
Ogre::HardwareVertexBufferSharedPtr normalBuffer,
|
std::vector<float>& normals,
|
||||||
Ogre::HardwareVertexBufferSharedPtr colourBuffer);
|
std::vector<Ogre::uint8>& colours);
|
||||||
|
|
||||||
/// Create textures holding layer blend values for a terrain chunk.
|
/// 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
|
/// @note The terrain chunk shouldn't be larger than one cell since otherwise we might
|
||||||
|
|
|
@ -388,6 +388,8 @@ namespace MWWorld
|
||||||
|
|
||||||
typedef std::vector<ESM::LandTexture>::const_iterator iterator;
|
typedef std::vector<ESM::LandTexture>::const_iterator iterator;
|
||||||
|
|
||||||
|
// Must be threadsafe! Called from terrain background loading threads.
|
||||||
|
// Not a big deal here, since ESM::LandTexture can never be modified or inserted/erased
|
||||||
const ESM::LandTexture *search(size_t index, size_t plugin) const {
|
const ESM::LandTexture *search(size_t index, size_t plugin) const {
|
||||||
assert(plugin < mStatic.size());
|
assert(plugin < mStatic.size());
|
||||||
const LandTextureList <exl = mStatic[plugin];
|
const LandTextureList <exl = mStatic[plugin];
|
||||||
|
@ -487,6 +489,8 @@ namespace MWWorld
|
||||||
return iterator(mStatic.end());
|
return iterator(mStatic.end());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Must be threadsafe! Called from terrain background loading threads.
|
||||||
|
// Not a big deal here, since ESM::Land can never be modified or inserted/erased
|
||||||
ESM::Land *search(int x, int y) const {
|
ESM::Land *search(int x, int y) const {
|
||||||
ESM::Land land;
|
ESM::Land land;
|
||||||
land.mX = x, land.mY = y;
|
land.mX = x, land.mY = y;
|
||||||
|
|
|
@ -10,9 +10,9 @@
|
||||||
namespace Terrain
|
namespace Terrain
|
||||||
{
|
{
|
||||||
|
|
||||||
Chunk::Chunk(QuadTreeNode* node, short lodLevel)
|
Chunk::Chunk(QuadTreeNode* node, const LoadResponseData& data)
|
||||||
: mNode(node)
|
: mNode(node)
|
||||||
, mVertexLod(lodLevel)
|
, mVertexLod(node->getNativeLodLevel())
|
||||||
, mAdditionalLod(0)
|
, mAdditionalLod(0)
|
||||||
{
|
{
|
||||||
mVertexData = OGRE_NEW Ogre::VertexData;
|
mVertexData = OGRE_NEW Ogre::VertexData;
|
||||||
|
@ -20,6 +20,8 @@ namespace Terrain
|
||||||
|
|
||||||
unsigned int verts = mNode->getTerrain()->getStorage()->getCellVertices();
|
unsigned int verts = mNode->getTerrain()->getStorage()->getCellVertices();
|
||||||
|
|
||||||
|
size_t lodLevel = mNode->getNativeLodLevel();
|
||||||
|
|
||||||
// Set the total number of vertices
|
// Set the total number of vertices
|
||||||
size_t numVertsOneSide = mNode->getSize() * (verts-1);
|
size_t numVertsOneSide = mNode->getSize() * (verts-1);
|
||||||
numVertsOneSide /= 1 << lodLevel;
|
numVertsOneSide /= 1 << lodLevel;
|
||||||
|
@ -52,8 +54,9 @@ namespace Terrain
|
||||||
mColourBuffer = mgr->createVertexBuffer(Ogre::VertexElement::getTypeSize(Ogre::VET_COLOUR),
|
mColourBuffer = mgr->createVertexBuffer(Ogre::VertexElement::getTypeSize(Ogre::VET_COLOUR),
|
||||||
mVertexData->vertexCount, Ogre::HardwareBuffer::HBU_STATIC);
|
mVertexData->vertexCount, Ogre::HardwareBuffer::HBU_STATIC);
|
||||||
|
|
||||||
mNode->getTerrain()->getStorage()->fillVertexBuffers(lodLevel, mNode->getSize(), mNode->getCenter(), mNode->getTerrain()->getAlign(),
|
mVertexBuffer->writeData(0, mVertexBuffer->getSizeInBytes(), &data.mPositions[0], true);
|
||||||
mVertexBuffer, mNormalBuffer, mColourBuffer);
|
mNormalBuffer->writeData(0, mNormalBuffer->getSizeInBytes(), &data.mNormals[0], true);
|
||||||
|
mColourBuffer->writeData(0, mColourBuffer->getSizeInBytes(), &data.mColours[0], true);
|
||||||
|
|
||||||
mVertexData->vertexBufferBinding->setBinding(0, mVertexBuffer);
|
mVertexData->vertexBufferBinding->setBinding(0, mVertexBuffer);
|
||||||
mVertexData->vertexBufferBinding->setBinding(1, mNormalBuffer);
|
mVertexData->vertexBufferBinding->setBinding(1, mNormalBuffer);
|
||||||
|
|
|
@ -8,6 +8,7 @@ namespace Terrain
|
||||||
{
|
{
|
||||||
|
|
||||||
class QuadTreeNode;
|
class QuadTreeNode;
|
||||||
|
struct LoadResponseData;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Renders a chunk of terrain, either using alpha splatting or a composite map.
|
* @brief Renders a chunk of terrain, either using alpha splatting or a composite map.
|
||||||
|
@ -15,8 +16,8 @@ namespace Terrain
|
||||||
class Chunk : public Ogre::Renderable, public Ogre::MovableObject
|
class Chunk : public Ogre::Renderable, public Ogre::MovableObject
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
/// @param lodLevel LOD level for the vertex buffer.
|
Chunk (QuadTreeNode* node, const LoadResponseData& data);
|
||||||
Chunk (QuadTreeNode* node, short lodLevel);
|
|
||||||
virtual ~Chunk();
|
virtual ~Chunk();
|
||||||
|
|
||||||
void setMaterial (const Ogre::MaterialPtr& material);
|
void setMaterial (const Ogre::MaterialPtr& material);
|
||||||
|
|
|
@ -36,6 +36,13 @@ namespace Terrain
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct LayerInfo
|
||||||
|
{
|
||||||
|
std::string mDiffuseMap;
|
||||||
|
std::string mNormalMap;
|
||||||
|
bool mParallax; // Height info in normal map alpha channel?
|
||||||
|
bool mSpecular; // Specular info in diffuse map alpha channel?
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -153,6 +153,7 @@ QuadTreeNode::QuadTreeNode(World* terrain, ChildDirection dir, float size, const
|
||||||
, mParent(parent)
|
, mParent(parent)
|
||||||
, mTerrain(terrain)
|
, mTerrain(terrain)
|
||||||
, mChunk(NULL)
|
, mChunk(NULL)
|
||||||
|
, mLoadState(LS_Unloaded)
|
||||||
{
|
{
|
||||||
mBounds.setNull();
|
mBounds.setNull();
|
||||||
for (int i=0; i<4; ++i)
|
for (int i=0; i<4; ++i)
|
||||||
|
@ -266,8 +267,6 @@ void QuadTreeNode::update(const Ogre::Vector3 &cameraPos)
|
||||||
|
|
||||||
float dist = distance(mWorldBounds, cameraPos);
|
float dist = distance(mWorldBounds, cameraPos);
|
||||||
|
|
||||||
bool distantLand = mTerrain->getDistantLandEnabled();
|
|
||||||
|
|
||||||
// Make sure our scene node is attached
|
// Make sure our scene node is attached
|
||||||
if (!mSceneNode->isInSceneGraph())
|
if (!mSceneNode->isInSceneGraph())
|
||||||
{
|
{
|
||||||
|
@ -292,100 +291,110 @@ void QuadTreeNode::update(const Ogre::Vector3 &cameraPos)
|
||||||
else if (dist > cellWorldSize)
|
else if (dist > cellWorldSize)
|
||||||
wantedLod = 1;
|
wantedLod = 1;
|
||||||
|
|
||||||
bool hadChunk = hasChunk();
|
|
||||||
|
|
||||||
if (!distantLand && dist > 8192*2)
|
|
||||||
{
|
|
||||||
if (mIsActive)
|
|
||||||
{
|
|
||||||
destroyChunks(true);
|
|
||||||
mIsActive = false;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
mIsActive = true;
|
mIsActive = true;
|
||||||
|
|
||||||
if (mSize <= mTerrain->getMaxBatchSize() && mLodLevel <= wantedLod)
|
bool wantToDisplay = mSize <= mTerrain->getMaxBatchSize() && mLodLevel <= wantedLod;
|
||||||
|
|
||||||
|
if (wantToDisplay)
|
||||||
{
|
{
|
||||||
// Wanted LOD is small enough to render this node in one chunk
|
// Wanted LOD is small enough to render this node in one chunk
|
||||||
if (!mChunk)
|
if (mLoadState == LS_Unloaded)
|
||||||
{
|
{
|
||||||
mChunk = new Chunk(this, mLodLevel);
|
mLoadState = LS_Loading;
|
||||||
mChunk->setVisibilityFlags(mTerrain->getVisiblityFlags());
|
mTerrain->queueLoad(this);
|
||||||
mChunk->setCastShadows(true);
|
|
||||||
mSceneNode->attachObject(mChunk);
|
|
||||||
|
|
||||||
mMaterialGenerator->enableShadows(mTerrain->getShadowsEnabled());
|
|
||||||
mMaterialGenerator->enableSplitShadows(mTerrain->getSplitShadowsEnabled());
|
|
||||||
|
|
||||||
if (mSize == 1)
|
|
||||||
{
|
|
||||||
ensureLayerInfo();
|
|
||||||
mChunk->setMaterial(mMaterialGenerator->generate(mChunk->getMaterial()));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ensureCompositeMap();
|
|
||||||
mMaterialGenerator->setCompositeMap(mCompositeMap->getName());
|
|
||||||
mChunk->setMaterial(mMaterialGenerator->generateForCompositeMap(mChunk->getMaterial()));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Additional (index buffer) LOD is currently disabled.
|
if (mLoadState == LS_Loaded)
|
||||||
// This is due to a problem with the LOD selection when a node splits.
|
|
||||||
// After splitting, the distance is measured from the children's bounding boxes, which are possibly
|
|
||||||
// further away than the original node's bounding box, possibly causing a child to switch to a *lower* LOD
|
|
||||||
// than the original node.
|
|
||||||
// In short, we'd sometimes get a switch to a lesser detail when actually moving closer.
|
|
||||||
// This wouldn't be so bad, but unfortunately it also breaks LOD edge connections if a neighbour
|
|
||||||
// node hasn't split yet, and has a higher LOD than our node's child:
|
|
||||||
// ----- ----- ------------
|
|
||||||
// | LOD | LOD | |
|
|
||||||
// | 1 | 1 | |
|
|
||||||
// |-----|-----| LOD 0 |
|
|
||||||
// | LOD | LOD | |
|
|
||||||
// | 0 | 0 | |
|
|
||||||
// ----- ----- ------------
|
|
||||||
// To prevent this, nodes of the same size need to always select the same LOD, which is basically what we're
|
|
||||||
// doing here.
|
|
||||||
// But this "solution" does increase triangle overhead, so eventually we need to find a more clever way.
|
|
||||||
//mChunk->setAdditionalLod(wantedLod - mLodLevel);
|
|
||||||
|
|
||||||
mChunk->setVisible(true);
|
|
||||||
|
|
||||||
if (!hadChunk && hasChildren())
|
|
||||||
{
|
{
|
||||||
// Make sure child scene nodes are detached
|
// Additional (index buffer) LOD is currently disabled.
|
||||||
mSceneNode->removeAllChildren();
|
// This is due to a problem with the LOD selection when a node splits.
|
||||||
|
// After splitting, the distance is measured from the children's bounding boxes, which are possibly
|
||||||
|
// further away than the original node's bounding box, possibly causing a child to switch to a *lower* LOD
|
||||||
|
// than the original node.
|
||||||
|
// In short, we'd sometimes get a switch to a lesser detail when actually moving closer.
|
||||||
|
// This wouldn't be so bad, but unfortunately it also breaks LOD edge connections if a neighbour
|
||||||
|
// node hasn't split yet, and has a higher LOD than our node's child:
|
||||||
|
// ----- ----- ------------
|
||||||
|
// | LOD | LOD | |
|
||||||
|
// | 1 | 1 | |
|
||||||
|
// |-----|-----| LOD 0 |
|
||||||
|
// | LOD | LOD | |
|
||||||
|
// | 0 | 0 | |
|
||||||
|
// ----- ----- ------------
|
||||||
|
// To prevent this, nodes of the same size need to always select the same LOD, which is basically what we're
|
||||||
|
// doing here.
|
||||||
|
// But this "solution" does increase triangle overhead, so eventually we need to find a more clever way.
|
||||||
|
//mChunk->setAdditionalLod(wantedLod - mLodLevel);
|
||||||
|
|
||||||
// If distant land is enabled, keep the chunks around in case we need them again,
|
if (!mChunk->getVisible() && hasChildren())
|
||||||
// otherwise, prefer low memory usage
|
{
|
||||||
if (!distantLand)
|
// Make sure child scene nodes are detached
|
||||||
for (int i=0; i<4; ++i)
|
mSceneNode->removeAllChildren();
|
||||||
mChildren[i]->destroyChunks(true);
|
|
||||||
|
// TODO: unload
|
||||||
|
//for (int i=0; i<4; ++i)
|
||||||
|
// mChildren[i]->unload();
|
||||||
|
}
|
||||||
|
|
||||||
|
mChunk->setVisible(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
|
||||||
|
if (wantToDisplay && mLoadState != LS_Loaded)
|
||||||
{
|
{
|
||||||
// Wanted LOD is too detailed to be rendered in one chunk,
|
// We want to display, but aren't ready yet. Perhaps our child nodes are ready?
|
||||||
// so split it up by delegating to child nodes
|
// TODO: this doesn't check child-child nodes...
|
||||||
if (hadChunk)
|
if (hasChildren())
|
||||||
{
|
{
|
||||||
// If distant land is enabled, keep the chunks around in case we need them again,
|
for (int i=0; i<4; ++i)
|
||||||
// otherwise, prefer low memory usage
|
if (mChildren[i]->hasChunk())
|
||||||
if (!distantLand)
|
mChildren[i]->update(cameraPos);
|
||||||
destroyChunks(false);
|
}
|
||||||
else if (mChunk)
|
}
|
||||||
mChunk->setVisible(false);
|
if (!wantToDisplay)
|
||||||
|
{
|
||||||
|
// We do not want to display this node - delegate to children
|
||||||
|
if (mChunk)
|
||||||
|
mChunk->setVisible(false);
|
||||||
|
if (hasChildren())
|
||||||
|
{
|
||||||
|
for (int i=0; i<4; ++i)
|
||||||
|
mChildren[i]->update(cameraPos);
|
||||||
}
|
}
|
||||||
assert(hasChildren() && "Leaf node's LOD needs to be 0");
|
|
||||||
for (int i=0; i<4; ++i)
|
|
||||||
mChildren[i]->update(cameraPos);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void QuadTreeNode::destroyChunks(bool children)
|
void QuadTreeNode::load(const LoadResponseData &data)
|
||||||
|
{
|
||||||
|
assert (!mChunk);
|
||||||
|
|
||||||
|
std::cout << "loading " << std::endl;
|
||||||
|
mChunk = new Chunk(this, data);
|
||||||
|
mChunk->setVisibilityFlags(mTerrain->getVisiblityFlags());
|
||||||
|
mChunk->setCastShadows(true);
|
||||||
|
mSceneNode->attachObject(mChunk);
|
||||||
|
|
||||||
|
mMaterialGenerator->enableShadows(mTerrain->getShadowsEnabled());
|
||||||
|
mMaterialGenerator->enableSplitShadows(mTerrain->getSplitShadowsEnabled());
|
||||||
|
|
||||||
|
if (mSize == 1)
|
||||||
|
{
|
||||||
|
ensureLayerInfo();
|
||||||
|
mChunk->setMaterial(mMaterialGenerator->generate(mChunk->getMaterial()));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ensureCompositeMap();
|
||||||
|
mMaterialGenerator->setCompositeMap(mCompositeMap->getName());
|
||||||
|
mChunk->setMaterial(mMaterialGenerator->generateForCompositeMap(mChunk->getMaterial()));
|
||||||
|
}
|
||||||
|
|
||||||
|
mChunk->setVisible(false);
|
||||||
|
|
||||||
|
mLoadState = LS_Loaded;
|
||||||
|
}
|
||||||
|
|
||||||
|
void QuadTreeNode::unload()
|
||||||
{
|
{
|
||||||
if (mChunk)
|
if (mChunk)
|
||||||
{
|
{
|
||||||
|
@ -411,9 +420,7 @@ void QuadTreeNode::destroyChunks(bool children)
|
||||||
mCompositeMap.setNull();
|
mCompositeMap.setNull();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (children && hasChildren())
|
mLoadState = LS_Unloaded;
|
||||||
for (int i=0; i<4; ++i)
|
|
||||||
mChildren[i]->destroyChunks(true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void QuadTreeNode::updateIndexBuffers()
|
void QuadTreeNode::updateIndexBuffers()
|
||||||
|
|
|
@ -15,6 +15,7 @@ namespace Terrain
|
||||||
class World;
|
class World;
|
||||||
class Chunk;
|
class Chunk;
|
||||||
class MaterialGenerator;
|
class MaterialGenerator;
|
||||||
|
struct LoadResponseData;
|
||||||
|
|
||||||
enum Direction
|
enum Direction
|
||||||
{
|
{
|
||||||
|
@ -33,6 +34,13 @@ namespace Terrain
|
||||||
Root
|
Root
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum LoadState
|
||||||
|
{
|
||||||
|
LS_Unloaded,
|
||||||
|
LS_Loading,
|
||||||
|
LS_Loaded
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief A node in the quad tree for our terrain. Depending on LOD,
|
* @brief A node in the quad tree for our terrain. Depending on LOD,
|
||||||
* a node can either choose to render itself in one batch (merging its children),
|
* a node can either choose to render itself in one batch (merging its children),
|
||||||
|
@ -124,10 +132,18 @@ namespace Terrain
|
||||||
/// @param quads collect quads here so they can be deleted later
|
/// @param quads collect quads here so they can be deleted later
|
||||||
void prepareForCompositeMap(Ogre::TRect<float> area);
|
void prepareForCompositeMap(Ogre::TRect<float> area);
|
||||||
|
|
||||||
|
/// Create a chunk for this node from the given data.
|
||||||
|
void load (const LoadResponseData& data);
|
||||||
|
void unload();
|
||||||
|
|
||||||
|
LoadState getLoadState() { return mLoadState; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Stored here for convenience in case we need layer list again
|
// Stored here for convenience in case we need layer list again
|
||||||
MaterialGenerator* mMaterialGenerator;
|
MaterialGenerator* mMaterialGenerator;
|
||||||
|
|
||||||
|
LoadState mLoadState;
|
||||||
|
|
||||||
/// Is this node (or any of its child nodes) currently configured to render itself?
|
/// 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)
|
/// (only relevant when distant land is disabled, otherwise whole terrain is always rendered)
|
||||||
bool mIsActive;
|
bool mIsActive;
|
||||||
|
|
|
@ -7,15 +7,6 @@
|
||||||
|
|
||||||
namespace Terrain
|
namespace Terrain
|
||||||
{
|
{
|
||||||
|
|
||||||
struct LayerInfo
|
|
||||||
{
|
|
||||||
std::string mDiffuseMap;
|
|
||||||
std::string mNormalMap;
|
|
||||||
bool mParallax; // Height info in normal map alpha channel?
|
|
||||||
bool mSpecular; // Specular info in diffuse map alpha channel?
|
|
||||||
};
|
|
||||||
|
|
||||||
/// We keep storage of terrain data abstract here since we need different implementations for game and editor
|
/// We keep storage of terrain data abstract here since we need different implementations for game and editor
|
||||||
class Storage
|
class Storage
|
||||||
{
|
{
|
||||||
|
@ -26,8 +17,8 @@ namespace Terrain
|
||||||
/// Get bounds of the whole terrain in cell units
|
/// Get bounds of the whole terrain in cell units
|
||||||
virtual void getBounds(float& minX, float& maxX, float& minY, float& maxY) = 0;
|
virtual void getBounds(float& minX, float& maxX, float& minY, float& maxY) = 0;
|
||||||
|
|
||||||
/// Get the minimum and maximum heights of a terrain chunk.
|
/// Get the minimum and maximum heights of a terrain region.
|
||||||
/// @note Should only be called for chunks <= 1 cell, i.e. leafs of the quad tree.
|
/// @note Will only be called for chunks with size = minBatchSize, i.e. leafs of the quad tree.
|
||||||
/// Larger chunks can simply merge AABB of children.
|
/// Larger chunks can simply merge AABB of children.
|
||||||
/// @param size size of the chunk in cell units
|
/// @param size size of the chunk in cell units
|
||||||
/// @param center center of the chunk in cell units
|
/// @param center center of the chunk in cell units
|
||||||
|
@ -37,16 +28,18 @@ namespace Terrain
|
||||||
virtual bool getMinMaxHeights (float size, const Ogre::Vector2& center, float& min, float& max) = 0;
|
virtual bool getMinMaxHeights (float size, const Ogre::Vector2& center, float& min, float& max) = 0;
|
||||||
|
|
||||||
/// Fill vertex buffers for a terrain chunk.
|
/// Fill vertex buffers for a terrain chunk.
|
||||||
|
/// @note May be called from background threads. Make sure to only call thread-safe functions from here!
|
||||||
|
/// @note returned colors need to be in render-system specific format! Use RenderSystem::convertColourValue.
|
||||||
/// @param lodLevel LOD level, 0 = most detailed
|
/// @param lodLevel LOD level, 0 = most detailed
|
||||||
/// @param size size of the terrain chunk in cell units
|
/// @param size size of the terrain chunk in cell units
|
||||||
/// @param center center of the chunk in cell units
|
/// @param center center of the chunk in cell units
|
||||||
/// @param vertexBuffer buffer to write vertices
|
/// @param positions buffer to write vertices
|
||||||
/// @param normalBuffer buffer to write vertex normals
|
/// @param normals buffer to write vertex normals
|
||||||
/// @param colourBuffer buffer to write vertex colours
|
/// @param colours buffer to write vertex colours
|
||||||
virtual void fillVertexBuffers (int lodLevel, float size, const Ogre::Vector2& center, Terrain::Alignment align,
|
virtual void fillVertexBuffers (int lodLevel, float size, const Ogre::Vector2& center, Terrain::Alignment align,
|
||||||
Ogre::HardwareVertexBufferSharedPtr vertexBuffer,
|
std::vector<float>& positions,
|
||||||
Ogre::HardwareVertexBufferSharedPtr normalBuffer,
|
std::vector<float>& normals,
|
||||||
Ogre::HardwareVertexBufferSharedPtr colourBuffer) = 0;
|
std::vector<Ogre::uint8>& colours) = 0;
|
||||||
|
|
||||||
/// Create textures holding layer blend values for a terrain chunk.
|
/// 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
|
/// @note The terrain chunk shouldn't be larger than one cell since otherwise we might
|
||||||
|
|
|
@ -64,6 +64,8 @@ namespace Terrain
|
||||||
, mMinX(0)
|
, mMinX(0)
|
||||||
, mMaxY(0)
|
, mMaxY(0)
|
||||||
, mMinY(0)
|
, mMinY(0)
|
||||||
|
, mChunksLoading(0)
|
||||||
|
, mWorkQueueChannel(0)
|
||||||
{
|
{
|
||||||
#if TERRAIN_USE_SHADER == 0
|
#if TERRAIN_USE_SHADER == 0
|
||||||
if (mShaders)
|
if (mShaders)
|
||||||
|
@ -103,10 +105,19 @@ namespace Terrain
|
||||||
//loadingListener->indicateProgress();
|
//loadingListener->indicateProgress();
|
||||||
mRootNode->initNeighbours();
|
mRootNode->initNeighbours();
|
||||||
//loadingListener->indicateProgress();
|
//loadingListener->indicateProgress();
|
||||||
|
|
||||||
|
Ogre::WorkQueue* wq = Ogre::Root::getSingleton().getWorkQueue();
|
||||||
|
mWorkQueueChannel = wq->getChannel("LargeTerrain");
|
||||||
|
wq->addRequestHandler(mWorkQueueChannel, this);
|
||||||
|
wq->addResponseHandler(mWorkQueueChannel, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
World::~World()
|
World::~World()
|
||||||
{
|
{
|
||||||
|
Ogre::WorkQueue* wq = Ogre::Root::getSingleton().getWorkQueue();
|
||||||
|
wq->removeRequestHandler(mWorkQueueChannel, this);
|
||||||
|
wq->removeResponseHandler(mWorkQueueChannel, this);
|
||||||
|
|
||||||
delete mRootNode;
|
delete mRootNode;
|
||||||
delete mStorage;
|
delete mStorage;
|
||||||
}
|
}
|
||||||
|
@ -445,4 +456,62 @@ namespace Terrain
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void World::syncLoad()
|
||||||
|
{
|
||||||
|
while (mChunksLoading)
|
||||||
|
{
|
||||||
|
OGRE_THREAD_SLEEP(0);
|
||||||
|
Ogre::Root::getSingleton().getWorkQueue()->processResponses();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ogre::WorkQueue::Response* World::handleRequest(const Ogre::WorkQueue::Request *req, const Ogre::WorkQueue *srcQ)
|
||||||
|
{
|
||||||
|
const LoadRequestData data = Ogre::any_cast<LoadRequestData>(req->getData());
|
||||||
|
|
||||||
|
QuadTreeNode* node = data.mNode;
|
||||||
|
|
||||||
|
LoadResponseData* responseData = new LoadResponseData();
|
||||||
|
|
||||||
|
Ogre::Timer timer;
|
||||||
|
getStorage()->fillVertexBuffers(node->getNativeLodLevel(), node->getSize(), node->getCenter(), getAlign(),
|
||||||
|
responseData->mPositions, responseData->mNormals, responseData->mColours);
|
||||||
|
|
||||||
|
std::cout << "THREAD" << std::endl;
|
||||||
|
|
||||||
|
responseData->time = timer.getMicroseconds();
|
||||||
|
|
||||||
|
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())
|
||||||
|
{
|
||||||
|
LoadResponseData* data = Ogre::any_cast<LoadResponseData*>(res->getData());
|
||||||
|
|
||||||
|
const LoadRequestData requestData = Ogre::any_cast<LoadRequestData>(res->getRequest()->getData());
|
||||||
|
|
||||||
|
requestData.mNode->load(*data);
|
||||||
|
|
||||||
|
time += data->time;
|
||||||
|
|
||||||
|
delete data;
|
||||||
|
|
||||||
|
std::cout << "RESPONSE, reqs took ms" << time/1000.f << std::endl;
|
||||||
|
}
|
||||||
|
--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));
|
||||||
|
++mChunksLoading;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
#include <OgreHardwareVertexBuffer.h>
|
#include <OgreHardwareVertexBuffer.h>
|
||||||
#include <OgreAxisAlignedBox.h>
|
#include <OgreAxisAlignedBox.h>
|
||||||
#include <OgreTexture.h>
|
#include <OgreTexture.h>
|
||||||
|
#include <OgreWorkQueue.h>
|
||||||
|
|
||||||
#include "defs.hpp"
|
#include "defs.hpp"
|
||||||
|
|
||||||
|
@ -26,7 +27,7 @@ namespace Terrain
|
||||||
* Cracks at LOD transitions are avoided using stitching.
|
* Cracks at LOD transitions are avoided using stitching.
|
||||||
* @note Multiple cameras are not supported yet
|
* @note Multiple cameras are not supported yet
|
||||||
*/
|
*/
|
||||||
class World
|
class World : public Ogre::WorkQueue::RequestHandler, public Ogre::WorkQueue::ResponseHandler
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
/// @note takes ownership of \a storage
|
/// @note takes ownership of \a storage
|
||||||
|
@ -85,7 +86,16 @@ namespace Terrain
|
||||||
|
|
||||||
Alignment getAlign() { return mAlign; }
|
Alignment getAlign() { return mAlign; }
|
||||||
|
|
||||||
|
/// Wait until all background loading is complete.
|
||||||
|
void syncLoad();
|
||||||
|
|
||||||
private:
|
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;
|
bool mDistantLand;
|
||||||
bool mShaders;
|
bool mShaders;
|
||||||
bool mShadows;
|
bool mShadows;
|
||||||
|
@ -99,6 +109,9 @@ namespace Terrain
|
||||||
|
|
||||||
int mVisibilityFlags;
|
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* mSceneMgr;
|
||||||
Ogre::SceneManager* mCompositeMapSceneMgr;
|
Ogre::SceneManager* mCompositeMapSceneMgr;
|
||||||
|
|
||||||
|
@ -141,6 +154,9 @@ namespace Terrain
|
||||||
void convertPosition (Ogre::Vector3& pos);
|
void convertPosition (Ogre::Vector3& pos);
|
||||||
void convertBounds (Ogre::AxisAlignedBox& bounds);
|
void convertBounds (Ogre::AxisAlignedBox& bounds);
|
||||||
|
|
||||||
|
// Adds a WorkQueue request to load a chunk for this node in the background.
|
||||||
|
void queueLoad (QuadTreeNode* node);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Index buffers are shared across terrain batches where possible. There is one index buffer for each
|
// Index buffers are shared across terrain batches where possible. There is one index buffer for each
|
||||||
// combination of LOD deltas and index buffer LOD we may need.
|
// combination of LOD deltas and index buffer LOD we may need.
|
||||||
|
@ -152,6 +168,30 @@ namespace Terrain
|
||||||
Ogre::TexturePtr mCompositeMapRenderTexture;
|
Ogre::TexturePtr mCompositeMapRenderTexture;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct LoadRequestData
|
||||||
|
{
|
||||||
|
QuadTreeNode* mNode;
|
||||||
|
bool mPack;
|
||||||
|
|
||||||
|
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;
|
||||||
|
// 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; }
|
||||||
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
Loading…
Reference in a new issue