forked from mirror/openmw-tes3mp
Terrain rendering
parent
10f938ff87
commit
cdd0623009
@ -1,169 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2015 scrawl <scrawl@baseoftrash.de>
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
|
||||||
* in the Software without restriction, including without limitation the rights
|
|
||||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
* copies of the Software, and to permit persons to whom the Software is
|
|
||||||
* furnished to do so, subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be included in
|
|
||||||
* all copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
* THE SOFTWARE.
|
|
||||||
*/
|
|
||||||
#include "chunk.hpp"
|
|
||||||
|
|
||||||
#include <OgreSceneNode.h>
|
|
||||||
#include <OgreHardwareBufferManager.h>
|
|
||||||
#include <OgreRenderQueue.h>
|
|
||||||
#include <OgreMaterialManager.h>
|
|
||||||
#include <OgreStringConverter.h>
|
|
||||||
|
|
||||||
namespace Terrain
|
|
||||||
{
|
|
||||||
|
|
||||||
Chunk::Chunk(Ogre::HardwareVertexBufferSharedPtr uvBuffer, const Ogre::AxisAlignedBox& bounds,
|
|
||||||
const std::vector<float>& positions, const std::vector<float>& normals, const std::vector<Ogre::uint8>& colours)
|
|
||||||
: mBounds(bounds)
|
|
||||||
, mOwnMaterial(false)
|
|
||||||
{
|
|
||||||
mVertexData = OGRE_NEW Ogre::VertexData;
|
|
||||||
mVertexData->vertexStart = 0;
|
|
||||||
mVertexData->vertexCount = positions.size()/3;
|
|
||||||
|
|
||||||
// Set up the vertex declaration, which specifies the info for each vertex (normals, colors, UVs, etc)
|
|
||||||
Ogre::VertexDeclaration* vertexDecl = mVertexData->vertexDeclaration;
|
|
||||||
|
|
||||||
Ogre::HardwareBufferManager* mgr = Ogre::HardwareBufferManager::getSingletonPtr();
|
|
||||||
size_t nextBuffer = 0;
|
|
||||||
|
|
||||||
// Positions
|
|
||||||
vertexDecl->addElement(nextBuffer++, 0, Ogre::VET_FLOAT3, Ogre::VES_POSITION);
|
|
||||||
Ogre::HardwareVertexBufferSharedPtr vertexBuffer = mgr->createVertexBuffer(Ogre::VertexElement::getTypeSize(Ogre::VET_FLOAT3),
|
|
||||||
mVertexData->vertexCount, Ogre::HardwareBuffer::HBU_STATIC);
|
|
||||||
|
|
||||||
// Normals
|
|
||||||
vertexDecl->addElement(nextBuffer++, 0, Ogre::VET_FLOAT3, Ogre::VES_NORMAL);
|
|
||||||
Ogre::HardwareVertexBufferSharedPtr normalBuffer = mgr->createVertexBuffer(Ogre::VertexElement::getTypeSize(Ogre::VET_FLOAT3),
|
|
||||||
mVertexData->vertexCount, Ogre::HardwareBuffer::HBU_STATIC);
|
|
||||||
|
|
||||||
|
|
||||||
// UV texture coordinates
|
|
||||||
vertexDecl->addElement(nextBuffer++, 0, Ogre::VET_FLOAT2,
|
|
||||||
Ogre::VES_TEXTURE_COORDINATES, 0);
|
|
||||||
|
|
||||||
// Colours
|
|
||||||
vertexDecl->addElement(nextBuffer++, 0, Ogre::VET_COLOUR, Ogre::VES_DIFFUSE);
|
|
||||||
Ogre::HardwareVertexBufferSharedPtr colourBuffer = mgr->createVertexBuffer(Ogre::VertexElement::getTypeSize(Ogre::VET_COLOUR),
|
|
||||||
mVertexData->vertexCount, Ogre::HardwareBuffer::HBU_STATIC);
|
|
||||||
|
|
||||||
vertexBuffer->writeData(0, vertexBuffer->getSizeInBytes(), &positions[0], true);
|
|
||||||
normalBuffer->writeData(0, normalBuffer->getSizeInBytes(), &normals[0], true);
|
|
||||||
colourBuffer->writeData(0, colourBuffer->getSizeInBytes(), &colours[0], true);
|
|
||||||
|
|
||||||
mVertexData->vertexBufferBinding->setBinding(0, vertexBuffer);
|
|
||||||
mVertexData->vertexBufferBinding->setBinding(1, normalBuffer);
|
|
||||||
mVertexData->vertexBufferBinding->setBinding(2, uvBuffer);
|
|
||||||
mVertexData->vertexBufferBinding->setBinding(3, colourBuffer);
|
|
||||||
|
|
||||||
// Assign a default material in case terrain material fails to be created
|
|
||||||
mMaterial = Ogre::MaterialManager::getSingleton().getByName("BaseWhite");
|
|
||||||
|
|
||||||
mIndexData = OGRE_NEW Ogre::IndexData();
|
|
||||||
mIndexData->indexStart = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Chunk::setIndexBuffer(Ogre::HardwareIndexBufferSharedPtr buffer)
|
|
||||||
{
|
|
||||||
mIndexData->indexBuffer = buffer;
|
|
||||||
mIndexData->indexCount = buffer->getNumIndexes();
|
|
||||||
}
|
|
||||||
|
|
||||||
Chunk::~Chunk()
|
|
||||||
{
|
|
||||||
if (!mMaterial.isNull() && mOwnMaterial)
|
|
||||||
{
|
|
||||||
#if TERRAIN_USE_SHADER
|
|
||||||
sh::Factory::getInstance().destroyMaterialInstance(mMaterial->getName());
|
|
||||||
#endif
|
|
||||||
Ogre::MaterialManager::getSingleton().remove(mMaterial->getName());
|
|
||||||
}
|
|
||||||
OGRE_DELETE mVertexData;
|
|
||||||
OGRE_DELETE mIndexData;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Chunk::setMaterial(const Ogre::MaterialPtr &material, bool own)
|
|
||||||
{
|
|
||||||
// Clean up the previous material, if we own it
|
|
||||||
if (!mMaterial.isNull() && mOwnMaterial)
|
|
||||||
{
|
|
||||||
#if TERRAIN_USE_SHADER
|
|
||||||
sh::Factory::getInstance().destroyMaterialInstance(mMaterial->getName());
|
|
||||||
#endif
|
|
||||||
Ogre::MaterialManager::getSingleton().remove(mMaterial->getName());
|
|
||||||
}
|
|
||||||
|
|
||||||
mMaterial = material;
|
|
||||||
mOwnMaterial = own;
|
|
||||||
}
|
|
||||||
|
|
||||||
const Ogre::AxisAlignedBox& Chunk::getBoundingBox(void) const
|
|
||||||
{
|
|
||||||
return mBounds;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ogre::Real Chunk::getBoundingRadius(void) const
|
|
||||||
{
|
|
||||||
return mBounds.getHalfSize().length();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Chunk::_updateRenderQueue(Ogre::RenderQueue* queue)
|
|
||||||
{
|
|
||||||
queue->addRenderable(this, mRenderQueueID);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Chunk::visitRenderables(Ogre::Renderable::Visitor* visitor,
|
|
||||||
bool debugRenderables)
|
|
||||||
{
|
|
||||||
visitor->visit(this, 0, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
const Ogre::MaterialPtr& Chunk::getMaterial(void) const
|
|
||||||
{
|
|
||||||
return mMaterial;
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
op.indexData = mIndexData;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Chunk::getWorldTransforms(Ogre::Matrix4* xform) const
|
|
||||||
{
|
|
||||||
*xform = getParentSceneNode()->_getFullTransform();
|
|
||||||
}
|
|
||||||
|
|
||||||
Ogre::Real Chunk::getSquaredViewDepth(const Ogre::Camera* cam) const
|
|
||||||
{
|
|
||||||
return getParentSceneNode()->getSquaredViewDepth(cam);
|
|
||||||
}
|
|
||||||
|
|
||||||
const Ogre::LightList& Chunk::getLights(void) const
|
|
||||||
{
|
|
||||||
return queryLights();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,75 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2015 scrawl <scrawl@baseoftrash.de>
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
|
||||||
* in the Software without restriction, including without limitation the rights
|
|
||||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
* copies of the Software, and to permit persons to whom the Software is
|
|
||||||
* furnished to do so, subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be included in
|
|
||||||
* all copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
* THE SOFTWARE.
|
|
||||||
*/
|
|
||||||
#ifndef COMPONENTS_TERRAIN_TERRAINBATCH_H
|
|
||||||
#define COMPONENTS_TERRAIN_TERRAINBATCH_H
|
|
||||||
|
|
||||||
#include <OgreRenderable.h>
|
|
||||||
#include <OgreMovableObject.h>
|
|
||||||
|
|
||||||
namespace Terrain
|
|
||||||
{
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief A movable object representing a chunk of terrain.
|
|
||||||
*/
|
|
||||||
class Chunk : public Ogre::Renderable, public Ogre::MovableObject
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
Chunk (Ogre::HardwareVertexBufferSharedPtr uvBuffer, const Ogre::AxisAlignedBox& bounds,
|
|
||||||
const std::vector<float>& positions,
|
|
||||||
const std::vector<float>& normals,
|
|
||||||
const std::vector<Ogre::uint8>& colours);
|
|
||||||
|
|
||||||
virtual ~Chunk();
|
|
||||||
|
|
||||||
/// @param own Should we take ownership of the material?
|
|
||||||
void setMaterial (const Ogre::MaterialPtr& material, bool own=true);
|
|
||||||
|
|
||||||
void setIndexBuffer(Ogre::HardwareIndexBufferSharedPtr buffer);
|
|
||||||
|
|
||||||
// Inherited from MovableObject
|
|
||||||
virtual const Ogre::String& getMovableType(void) const { static Ogre::String t = "MW_TERRAIN"; return t; }
|
|
||||||
virtual const Ogre::AxisAlignedBox& getBoundingBox(void) const;
|
|
||||||
virtual Ogre::Real getBoundingRadius(void) const;
|
|
||||||
virtual void _updateRenderQueue(Ogre::RenderQueue* queue);
|
|
||||||
virtual void visitRenderables(Renderable::Visitor* visitor,
|
|
||||||
bool debugRenderables = false);
|
|
||||||
|
|
||||||
// Inherited from Renderable
|
|
||||||
virtual const Ogre::MaterialPtr& getMaterial(void) const;
|
|
||||||
virtual void getRenderOperation(Ogre::RenderOperation& op);
|
|
||||||
virtual void getWorldTransforms(Ogre::Matrix4* xform) const;
|
|
||||||
virtual Ogre::Real getSquaredViewDepth(const Ogre::Camera* cam) const;
|
|
||||||
virtual const Ogre::LightList& getLights(void) const;
|
|
||||||
|
|
||||||
private:
|
|
||||||
Ogre::AxisAlignedBox mBounds;
|
|
||||||
Ogre::MaterialPtr mMaterial;
|
|
||||||
bool mOwnMaterial; // Should we remove mMaterial on destruction?
|
|
||||||
|
|
||||||
Ogre::VertexData* mVertexData;
|
|
||||||
Ogre::IndexData* mIndexData;
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
@ -1,336 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2015 scrawl <scrawl@baseoftrash.de>
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
|
||||||
* in the Software without restriction, including without limitation the rights
|
|
||||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
* copies of the Software, and to permit persons to whom the Software is
|
|
||||||
* furnished to do so, subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be included in
|
|
||||||
* all copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
* THE SOFTWARE.
|
|
||||||
*/
|
|
||||||
#include "defaultworld.hpp"
|
|
||||||
|
|
||||||
#include <OgreAxisAlignedBox.h>
|
|
||||||
#include <OgreCamera.h>
|
|
||||||
#include <OgreHardwarePixelBuffer.h>
|
|
||||||
#include <OgreTextureManager.h>
|
|
||||||
#include <OgreRenderTexture.h>
|
|
||||||
#include <OgreSceneNode.h>
|
|
||||||
#include <OgreRoot.h>
|
|
||||||
|
|
||||||
#include "storage.hpp"
|
|
||||||
#include "quadtreenode.hpp"
|
|
||||||
|
|
||||||
namespace
|
|
||||||
{
|
|
||||||
|
|
||||||
bool isPowerOfTwo(int x)
|
|
||||||
{
|
|
||||||
return ( (x > 0) && ((x & (x - 1)) == 0) );
|
|
||||||
}
|
|
||||||
|
|
||||||
int nextPowerOfTwo (int v)
|
|
||||||
{
|
|
||||||
if (isPowerOfTwo(v)) return v;
|
|
||||||
int depth=0;
|
|
||||||
while(v)
|
|
||||||
{
|
|
||||||
v >>= 1;
|
|
||||||
depth++;
|
|
||||||
}
|
|
||||||
return 1 << depth;
|
|
||||||
}
|
|
||||||
|
|
||||||
Terrain::QuadTreeNode* findNode (const Ogre::Vector2& center, Terrain::QuadTreeNode* node)
|
|
||||||
{
|
|
||||||
if (center == node->getCenter())
|
|
||||||
return node;
|
|
||||||
|
|
||||||
if (center.x > node->getCenter().x && center.y > node->getCenter().y)
|
|
||||||
return findNode(center, node->getChild(Terrain::NE));
|
|
||||||
else if (center.x > node->getCenter().x && center.y < node->getCenter().y)
|
|
||||||
return findNode(center, node->getChild(Terrain::SE));
|
|
||||||
else if (center.x < node->getCenter().x && center.y > node->getCenter().y)
|
|
||||||
return findNode(center, node->getChild(Terrain::NW));
|
|
||||||
else //if (center.x < node->getCenter().x && center.y < node->getCenter().y)
|
|
||||||
return findNode(center, node->getChild(Terrain::SW));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace Terrain
|
|
||||||
{
|
|
||||||
|
|
||||||
const Ogre::uint REQ_ID_CHUNK = 1;
|
|
||||||
const Ogre::uint REQ_ID_LAYERS = 2;
|
|
||||||
|
|
||||||
DefaultWorld::DefaultWorld(Ogre::SceneManager* sceneMgr,
|
|
||||||
Storage* storage, int visibilityFlags, bool shaders, Alignment align, float minBatchSize, float maxBatchSize)
|
|
||||||
: World(sceneMgr, storage, visibilityFlags, shaders, align)
|
|
||||||
, mWorkQueueChannel(0)
|
|
||||||
, mVisible(true)
|
|
||||||
, mChunksLoading(0)
|
|
||||||
, mMinX(0)
|
|
||||||
, mMaxX(0)
|
|
||||||
, mMinY(0)
|
|
||||||
, mMaxY(0)
|
|
||||||
, mMinBatchSize(minBatchSize)
|
|
||||||
, mMaxBatchSize(maxBatchSize)
|
|
||||||
, mLayerLoadPending(true)
|
|
||||||
{
|
|
||||||
#if TERRAIN_USE_SHADER == 0
|
|
||||||
if (mShaders)
|
|
||||||
std::cerr << "Compiled Terrain without shader support, disabling..." << std::endl;
|
|
||||||
mShaders = false;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
mCompositeMapSceneMgr = Ogre::Root::getSingleton().createSceneManager(Ogre::ST_GENERIC);
|
|
||||||
|
|
||||||
/// \todo make composite map size configurable
|
|
||||||
Ogre::Camera* compositeMapCam = mCompositeMapSceneMgr->createCamera("a");
|
|
||||||
mCompositeMapRenderTexture = Ogre::TextureManager::getSingleton().createManual(
|
|
||||||
"terrain/comp/rt", Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME,
|
|
||||||
Ogre::TEX_TYPE_2D, 128, 128, 0, Ogre::PF_A8B8G8R8, Ogre::TU_RENDERTARGET);
|
|
||||||
mCompositeMapRenderTarget = mCompositeMapRenderTexture->getBuffer()->getRenderTarget();
|
|
||||||
mCompositeMapRenderTarget->setAutoUpdated(false);
|
|
||||||
mCompositeMapRenderTarget->addViewport(compositeMapCam);
|
|
||||||
|
|
||||||
storage->getBounds(mMinX, mMaxX, mMinY, mMaxY);
|
|
||||||
|
|
||||||
int origSizeX = static_cast<int>(mMaxX - mMinX);
|
|
||||||
int origSizeY = static_cast<int>(mMaxY - mMinY);
|
|
||||||
|
|
||||||
// Dividing a quad tree only works well for powers of two, so round up to the nearest one
|
|
||||||
int size = nextPowerOfTwo(std::max(origSizeX, origSizeY));
|
|
||||||
|
|
||||||
// Adjust the center according to the new size
|
|
||||||
float centerX = (mMinX+mMaxX)/2.f + (size-origSizeX)/2.f;
|
|
||||||
float centerY = (mMinY+mMaxY)/2.f + (size-origSizeY)/2.f;
|
|
||||||
|
|
||||||
mRootSceneNode = mSceneMgr->getRootSceneNode()->createChildSceneNode();
|
|
||||||
|
|
||||||
// While building the quadtree, remember leaf nodes since we need to load their layers
|
|
||||||
LayersRequestData data;
|
|
||||||
data.mPack = getShadersEnabled();
|
|
||||||
|
|
||||||
mRootNode = new QuadTreeNode(this, Root, static_cast<float>(size), Ogre::Vector2(centerX, centerY), NULL);
|
|
||||||
buildQuadTree(mRootNode, data.mNodes);
|
|
||||||
//loadingListener->indicateProgress();
|
|
||||||
mRootNode->initAabb();
|
|
||||||
//loadingListener->indicateProgress();
|
|
||||||
mRootNode->initNeighbours();
|
|
||||||
//loadingListener->indicateProgress();
|
|
||||||
|
|
||||||
Ogre::WorkQueue* wq = Ogre::Root::getSingleton().getWorkQueue();
|
|
||||||
mWorkQueueChannel = wq->getChannel("LargeTerrain");
|
|
||||||
wq->addRequestHandler(mWorkQueueChannel, this);
|
|
||||||
wq->addResponseHandler(mWorkQueueChannel, this);
|
|
||||||
|
|
||||||
// Start loading layers in the background (for leaf nodes)
|
|
||||||
wq->addRequest(mWorkQueueChannel, REQ_ID_LAYERS, Ogre::Any(data));
|
|
||||||
}
|
|
||||||
|
|
||||||
DefaultWorld::~DefaultWorld()
|
|
||||||
{
|
|
||||||
Ogre::WorkQueue* wq = Ogre::Root::getSingleton().getWorkQueue();
|
|
||||||
wq->removeRequestHandler(mWorkQueueChannel, this);
|
|
||||||
wq->removeResponseHandler(mWorkQueueChannel, this);
|
|
||||||
|
|
||||||
delete mRootNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
void DefaultWorld::buildQuadTree(QuadTreeNode *node, std::vector<QuadTreeNode*>& leafs)
|
|
||||||
{
|
|
||||||
float halfSize = node->getSize()/2.f;
|
|
||||||
|
|
||||||
if (node->getSize() <= mMinBatchSize)
|
|
||||||
{
|
|
||||||
// We arrived at a leaf
|
|
||||||
float minZ,maxZ;
|
|
||||||
Ogre::Vector2 center = node->getCenter();
|
|
||||||
float cellWorldSize = getStorage()->getCellWorldSize();
|
|
||||||
if (mStorage->getMinMaxHeights(static_cast<float>(node->getSize()), center, minZ, maxZ))
|
|
||||||
{
|
|
||||||
Ogre::AxisAlignedBox bounds(Ogre::Vector3(-halfSize*cellWorldSize, -halfSize*cellWorldSize, minZ),
|
|
||||||
Ogre::Vector3(halfSize*cellWorldSize, halfSize*cellWorldSize, maxZ));
|
|
||||||
convertBounds(bounds);
|
|
||||||
node->setBoundingBox(bounds);
|
|
||||||
leafs.push_back(node);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
node->markAsDummy(); // no data available for this node, skip it
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (node->getCenter().x - halfSize > mMaxX
|
|
||||||
|| node->getCenter().x + halfSize < mMinX
|
|
||||||
|| node->getCenter().y - halfSize > mMaxY
|
|
||||||
|| node->getCenter().y + halfSize < mMinY )
|
|
||||||
// Out of bounds of the actual terrain - this will happen because
|
|
||||||
// we rounded the size up to the next power of two
|
|
||||||
{
|
|
||||||
node->markAsDummy();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Not a leaf, create its children
|
|
||||||
node->createChild(SW, halfSize, node->getCenter() - halfSize/2.f);
|
|
||||||
node->createChild(SE, halfSize, node->getCenter() + Ogre::Vector2(halfSize/2.f, -halfSize/2.f));
|
|
||||||
node->createChild(NW, halfSize, node->getCenter() + Ogre::Vector2(-halfSize/2.f, halfSize/2.f));
|
|
||||||
node->createChild(NE, halfSize, node->getCenter() + halfSize/2.f);
|
|
||||||
buildQuadTree(node->getChild(SW), leafs);
|
|
||||||
buildQuadTree(node->getChild(SE), leafs);
|
|
||||||
buildQuadTree(node->getChild(NW), leafs);
|
|
||||||
buildQuadTree(node->getChild(NE), leafs);
|
|
||||||
|
|
||||||
// if all children are dummy, we are also dummy
|
|
||||||
for (int i=0; i<4; ++i)
|
|
||||||
{
|
|
||||||
if (!node->getChild((ChildDirection)i)->isDummy())
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
node->markAsDummy();
|
|
||||||
}
|
|
||||||
|
|
||||||
void DefaultWorld::update(const Ogre::Vector3& cameraPos)
|
|
||||||
{
|
|
||||||
if (!mVisible)
|
|
||||||
return;
|
|
||||||
mRootNode->update(cameraPos);
|
|
||||||
mRootNode->updateIndexBuffers();
|
|
||||||
}
|
|
||||||
|
|
||||||
Ogre::AxisAlignedBox DefaultWorld::getWorldBoundingBox (const Ogre::Vector2& center)
|
|
||||||
{
|
|
||||||
if (center.x > mMaxX
|
|
||||||
|| center.x < mMinX
|
|
||||||
|| center.y > mMaxY
|
|
||||||
|| center.y < mMinY)
|
|
||||||
return Ogre::AxisAlignedBox::BOX_NULL;
|
|
||||||
QuadTreeNode* node = findNode(center, mRootNode);
|
|
||||||
return node->getWorldBoundingBox();
|
|
||||||
}
|
|
||||||
|
|
||||||
void DefaultWorld::renderCompositeMap(Ogre::TexturePtr target)
|
|
||||||
{
|
|
||||||
mCompositeMapRenderTarget->update();
|
|
||||||
target->getBuffer()->blit(mCompositeMapRenderTexture->getBuffer());
|
|
||||||
}
|
|
||||||
|
|
||||||
void DefaultWorld::clearCompositeMapSceneManager()
|
|
||||||
{
|
|
||||||
mCompositeMapSceneMgr->destroyAllManualObjects();
|
|
||||||
mCompositeMapSceneMgr->clearScene();
|
|
||||||
}
|
|
||||||
|
|
||||||
void DefaultWorld::applyMaterials(bool shadows, bool splitShadows)
|
|
||||||
{
|
|
||||||
mShadows = shadows;
|
|
||||||
mSplitShadows = splitShadows;
|
|
||||||
mRootNode->applyMaterials();
|
|
||||||
}
|
|
||||||
|
|
||||||
void DefaultWorld::setVisible(bool visible)
|
|
||||||
{
|
|
||||||
if (visible && !mVisible)
|
|
||||||
mSceneMgr->getRootSceneNode()->addChild(mRootSceneNode);
|
|
||||||
else if (!visible && mVisible)
|
|
||||||
mSceneMgr->getRootSceneNode()->removeChild(mRootSceneNode);
|
|
||||||
|
|
||||||
mVisible = visible;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DefaultWorld::getVisible()
|
|
||||||
{
|
|
||||||
return mVisible;
|
|
||||||
}
|
|
||||||
|
|
||||||
void DefaultWorld::syncLoad()
|
|
||||||
{
|
|
||||||
while (mChunksLoading || mLayerLoadPending)
|
|
||||||
{
|
|
||||||
OGRE_THREAD_SLEEP(0);
|
|
||||||
Ogre::Root::getSingleton().getWorkQueue()->processResponses();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ogre::WorkQueue::Response* DefaultWorld::handleRequest(const Ogre::WorkQueue::Request *req, const Ogre::WorkQueue *srcQ)
|
|
||||||
{
|
|
||||||
if (req->getType() == REQ_ID_CHUNK)
|
|
||||||
{
|
|
||||||
const LoadRequestData data = Ogre::any_cast<LoadRequestData>(req->getData());
|
|
||||||
|
|
||||||
QuadTreeNode* node = data.mNode;
|
|
||||||
|
|
||||||
LoadResponseData* responseData = new LoadResponseData();
|
|
||||||
|
|
||||||
getStorage()->fillVertexBuffers(node->getNativeLodLevel(), static_cast<float>(node->getSize()), node->getCenter(), getAlign(),
|
|
||||||
responseData->mPositions, responseData->mNormals, responseData->mColours);
|
|
||||||
|
|
||||||
return OGRE_NEW Ogre::WorkQueue::Response(req, true, Ogre::Any(responseData));
|
|
||||||
}
|
|
||||||
else // REQ_ID_LAYERS
|
|
||||||
{
|
|
||||||
const LayersRequestData data = Ogre::any_cast<LayersRequestData>(req->getData());
|
|
||||||
|
|
||||||
LayersResponseData* responseData = new LayersResponseData();
|
|
||||||
|
|
||||||
getStorage()->getBlendmaps(data.mNodes, responseData->mLayerCollections, data.mPack);
|
|
||||||
|
|
||||||
return OGRE_NEW Ogre::WorkQueue::Response(req, true, Ogre::Any(responseData));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void DefaultWorld::handleResponse(const Ogre::WorkQueue::Response *res, const Ogre::WorkQueue *srcQ)
|
|
||||||
{
|
|
||||||
assert(res->succeeded() && "Response failure not handled");
|
|
||||||
|
|
||||||
if (res->getRequest()->getType() == REQ_ID_CHUNK)
|
|
||||||
{
|
|
||||||
LoadResponseData* data = Ogre::any_cast<LoadResponseData*>(res->getData());
|
|
||||||
|
|
||||||
const LoadRequestData requestData = Ogre::any_cast<LoadRequestData>(res->getRequest()->getData());
|
|
||||||
|
|
||||||
requestData.mNode->load(*data);
|
|
||||||
|
|
||||||
delete data;
|
|
||||||
|
|
||||||
--mChunksLoading;
|
|
||||||
}
|
|
||||||
else // REQ_ID_LAYERS
|
|
||||||
{
|
|
||||||
LayersResponseData* data = Ogre::any_cast<LayersResponseData*>(res->getData());
|
|
||||||
|
|
||||||
for (std::vector<LayerCollection>::iterator it = data->mLayerCollections.begin(); it != data->mLayerCollections.end(); ++it)
|
|
||||||
{
|
|
||||||
it->mTarget->loadLayers(*it);
|
|
||||||
}
|
|
||||||
|
|
||||||
delete data;
|
|
||||||
|
|
||||||
mRootNode->loadMaterials();
|
|
||||||
|
|
||||||
mLayerLoadPending = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void DefaultWorld::queueLoad(QuadTreeNode *node)
|
|
||||||
{
|
|
||||||
LoadRequestData data;
|
|
||||||
data.mNode = node;
|
|
||||||
|
|
||||||
Ogre::Root::getSingleton().getWorkQueue()->addRequest(mWorkQueueChannel, REQ_ID_CHUNK, Ogre::Any(data));
|
|
||||||
++mChunksLoading;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,177 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2015 scrawl <scrawl@baseoftrash.de>
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
|
||||||
* in the Software without restriction, including without limitation the rights
|
|
||||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
* copies of the Software, and to permit persons to whom the Software is
|
|
||||||
* furnished to do so, subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be included in
|
|
||||||
* all copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
* THE SOFTWARE.
|
|
||||||
*/
|
|
||||||
#ifndef COMPONENTS_TERRAIN_H
|
|
||||||
#define COMPONENTS_TERRAIN_H
|
|
||||||
|
|
||||||
#include <OgreAxisAlignedBox.h>
|
|
||||||
#include <OgreTexture.h>
|
|
||||||
#include <OgreWorkQueue.h>
|
|
||||||
|
|
||||||
#include "world.hpp"
|
|
||||||
|
|
||||||
namespace Ogre
|
|
||||||
{
|
|
||||||
class Camera;
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace Terrain
|
|
||||||
{
|
|
||||||
|
|
||||||
class QuadTreeNode;
|
|
||||||
class Storage;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief A quadtree-based terrain implementation suitable for large data sets. \n
|
|
||||||
* Near cells are rendered with alpha splatting, distant cells are merged
|
|
||||||
* together in batches and have their layers pre-rendered onto a composite map. \n
|
|
||||||
* Cracks at LOD transitions are avoided using stitching.
|
|
||||||
* @note Multiple cameras are not supported yet
|
|
||||||
*/
|
|
||||||
class DefaultWorld : public World, public Ogre::WorkQueue::RequestHandler, public Ogre::WorkQueue::ResponseHandler
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
/// @note takes ownership of \a storage
|
|
||||||
/// @param sceneMgr scene manager to use
|
|
||||||
/// @param storage Storage instance to get terrain data from (heights, normals, colors, textures..)
|
|
||||||
/// @param visbilityFlags visibility flags for the created meshes
|
|
||||||
/// @param shaders Whether to use splatting shader, or multi-pass fixed function splatting. Shader is usually
|
|
||||||
/// faster so this is just here for compatibility.
|
|
||||||
/// @param align The align of the terrain, see Alignment enum
|
|
||||||
/// @param minBatchSize Minimum size of a terrain batch along one side (in cell units). Used for building the quad tree.
|
|
||||||
/// @param maxBatchSize Maximum size of a terrain batch along one side (in cell units). Used when traversing the quad tree.
|
|
||||||
DefaultWorld(Ogre::SceneManager* sceneMgr,
|
|
||||||
Storage* storage, int visibilityFlags, bool shaders, Alignment align, float minBatchSize, float maxBatchSize);
|
|
||||||
~DefaultWorld();
|
|
||||||
|
|
||||||
/// Update chunk LODs according to this camera position
|
|
||||||
/// @note Calling this method might lead to composite textures being rendered, so it is best
|
|
||||||
/// not to call it when render commands are still queued, since that would cause a flush.
|
|
||||||
virtual void update (const Ogre::Vector3& cameraPos);
|
|
||||||
|
|
||||||
/// Get the world bounding box of a chunk of terrain centered at \a center
|
|
||||||
virtual Ogre::AxisAlignedBox getWorldBoundingBox (const Ogre::Vector2& center);
|
|
||||||
|
|
||||||
Ogre::SceneNode* getRootSceneNode() { return mRootSceneNode; }
|
|
||||||
|
|
||||||
/// Show or hide the whole terrain
|
|
||||||
/// @note this setting will be invalidated once you call Terrain::update, so do not call it while the terrain should be hidden
|
|
||||||
virtual void setVisible(bool visible);
|
|
||||||
virtual bool getVisible();
|
|
||||||
|
|
||||||
/// Recreate materials used by terrain chunks. This should be called whenever settings of
|
|
||||||
/// the material factory are changed. (Relying on the factory to update those materials is not
|
|
||||||
/// enough, since turning a feature on/off can change the number of texture units available for layer/blend
|
|
||||||
/// textures, and to properly respond to this we may need to change the structure of the material, such as
|
|
||||||
/// adding or removing passes. This can only be achieved by a full rebuild.)
|
|
||||||
virtual void applyMaterials(bool shadows, bool splitShadows);
|
|
||||||
|
|
||||||
int getMaxBatchSize() { return static_cast<int>(mMaxBatchSize); }
|
|
||||||
|
|
||||||
/// Wait until all background loading is complete.
|
|
||||||
void syncLoad();
|
|
||||||
|
|
||||||
private:
|
|
||||||
// Called from a background worker thread
|
|
||||||
virtual Ogre::WorkQueue::Response* handleRequest(const Ogre::WorkQueue::Request* req, const Ogre::WorkQueue* srcQ);
|
|
||||||
// Called from the main thread
|
|
||||||
virtual void handleResponse(const Ogre::WorkQueue::Response* res, const Ogre::WorkQueue* srcQ);
|
|
||||||
Ogre::uint16 mWorkQueueChannel;
|
|
||||||
|
|
||||||
bool mVisible;
|
|
||||||
|
|
||||||
QuadTreeNode* mRootNode;
|
|
||||||
Ogre::SceneNode* mRootSceneNode;
|
|
||||||
|
|
||||||
/// The number of chunks currently loading in a background thread. If 0, we have finished loading!
|
|
||||||
int mChunksLoading;
|
|
||||||
|
|
||||||
Ogre::SceneManager* mCompositeMapSceneMgr;
|
|
||||||
|
|
||||||
/// Bounds in cell units
|
|
||||||
float mMinX, mMaxX, mMinY, mMaxY;
|
|
||||||
|
|
||||||
/// Minimum size of a terrain batch along one side (in cell units)
|
|
||||||
float mMinBatchSize;
|
|
||||||
/// Maximum size of a terrain batch along one side (in cell units)
|
|
||||||
float mMaxBatchSize;
|
|
||||||
|
|
||||||
void buildQuadTree(QuadTreeNode* node, std::vector<QuadTreeNode*>& leafs);
|
|
||||||
|
|
||||||
// Are layers for leaf nodes loaded? This is done once at startup (but in a background thread)
|
|
||||||
bool mLayerLoadPending;
|
|
||||||
|
|
||||||
public:
|
|
||||||
// ----INTERNAL----
|
|
||||||
Ogre::SceneManager* getCompositeMapSceneManager() { return mCompositeMapSceneMgr; }
|
|
||||||
|
|
||||||
bool areLayersLoaded() { return !mLayerLoadPending; }
|
|
||||||
|
|
||||||
// Delete all quads
|
|
||||||
void clearCompositeMapSceneManager();
|
|
||||||
void renderCompositeMap (Ogre::TexturePtr target);
|
|
||||||
|
|
||||||
// Adds a WorkQueue request to load a chunk for this node in the background.
|
|
||||||
void queueLoad (QuadTreeNode* node);
|
|
||||||
|
|
||||||
private:
|
|
||||||
Ogre::RenderTarget* mCompositeMapRenderTarget;
|
|
||||||
Ogre::TexturePtr mCompositeMapRenderTexture;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct LoadRequestData
|
|
||||||
{
|
|
||||||
QuadTreeNode* mNode;
|
|
||||||
|
|
||||||
friend std::ostream& operator<<(std::ostream& o, const LoadRequestData& r)
|
|
||||||
{ return o; }
|
|
||||||
};
|
|
||||||
|
|
||||||
struct LoadResponseData
|
|
||||||
{
|
|
||||||
std::vector<float> mPositions;
|
|
||||||
std::vector<float> mNormals;
|
|
||||||
std::vector<Ogre::uint8> mColours;
|
|
||||||
|
|
||||||
friend std::ostream& operator<<(std::ostream& o, const LoadResponseData& r)
|
|
||||||
{ return o; }
|
|
||||||
};
|
|
||||||
|
|
||||||
struct LayersRequestData
|
|
||||||
{
|
|
||||||
std::vector<QuadTreeNode*> mNodes;
|
|
||||||
bool mPack;
|
|
||||||
|
|
||||||
friend std::ostream& operator<<(std::ostream& o, const LayersRequestData& r)
|
|
||||||
{ return o; }
|
|
||||||
};
|
|
||||||
|
|
||||||
struct LayersResponseData
|
|
||||||
{
|
|
||||||
std::vector<LayerCollection> mLayerCollections;
|
|
||||||
|
|
||||||
friend std::ostream& operator<<(std::ostream& o, const LayersResponseData& r)
|
|
||||||
{ return o; }
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
@ -1,611 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2015 scrawl <scrawl@baseoftrash.de>
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
|
||||||
* in the Software without restriction, including without limitation the rights
|
|
||||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
* copies of the Software, and to permit persons to whom the Software is
|
|
||||||
* furnished to do so, subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be included in
|
|
||||||
* all copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
* THE SOFTWARE.
|
|
||||||
*/
|
|
||||||
#include "quadtreenode.hpp"
|
|
||||||
|
|
||||||
#include <OgreSceneManager.h>
|
|
||||||
#include <OgreManualObject.h>
|
|
||||||
#include <OgreSceneNode.h>
|
|
||||||
#include <OgreMaterialManager.h>
|
|
||||||
#include <OgreTextureManager.h>
|
|
||||||
|
|
||||||
#include "defaultworld.hpp"
|
|
||||||
#include "chunk.hpp"
|
|
||||||
#include "storage.hpp"
|
|
||||||
#include "buffercache.hpp"
|
|
||||||
#include "material.hpp"
|
|
||||||
|
|
||||||
using namespace Terrain;
|
|
||||||
|
|
||||||
namespace
|
|
||||||
{
|
|
||||||
int Log2( int n )
|
|
||||||
{
|
|
||||||
assert(n > 0);
|
|
||||||
int targetlevel = 0;
|
|
||||||
while (n >>= 1) ++targetlevel;
|
|
||||||
return targetlevel;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Utility functions for neighbour finding algorithm
|
|
||||||
ChildDirection reflect(ChildDirection dir, Direction dir2)
|
|
||||||
{
|
|
||||||
assert(dir != Root);
|
|
||||||
|
|
||||||
const int lookupTable[4][4] =
|
|
||||||
{
|
|
||||||
// NW NE SW SE
|
|
||||||
{ SW, SE, NW, NE }, // N
|
|
||||||
{ NE, NW, SE, SW }, // E
|
|
||||||
{ SW, SE, NW, NE }, // S
|
|
||||||
{ NE, NW, SE, SW } // W
|
|
||||||
};
|
|
||||||
return (ChildDirection)lookupTable[dir2][dir];
|
|
||||||
}
|
|
||||||
|
|
||||||
bool adjacent(ChildDirection dir, Direction dir2)
|
|
||||||
{
|
|
||||||
assert(dir != Root);
|
|
||||||
const bool lookupTable[4][4] =
|
|
||||||
{
|
|
||||||
// NW NE SW SE
|
|
||||||
{ true, true, false, false }, // N
|
|
||||||
{ false, true, false, true }, // E
|
|
||||||
{ false, false, true, true }, // S
|
|
||||||
{ true, false, true, false } // W
|
|
||||||
};
|
|
||||||
return lookupTable[dir2][dir];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Algorithm described by Hanan Samet - 'Neighbour Finding in Quadtrees'
|
|
||||||
// http://www.cs.umd.edu/~hjs/pubs/SametPRIP81.pdf
|
|
||||||
QuadTreeNode* searchNeighbourRecursive (QuadTreeNode* currentNode, Direction dir)
|
|
||||||
{
|
|
||||||
if (!currentNode->getParent())
|
|
||||||
return NULL; // Arrived at root node, the root node does not have neighbours
|
|
||||||
|
|
||||||
QuadTreeNode* nextNode;
|
|
||||||
if (adjacent(currentNode->getDirection(), dir))
|
|
||||||
nextNode = searchNeighbourRecursive(currentNode->getParent(), dir);
|
|
||||||
else
|
|
||||||
nextNode = currentNode->getParent();
|
|
||||||
|
|
||||||
if (nextNode && nextNode->hasChildren())
|
|
||||||
return nextNode->getChild(reflect(currentNode->getDirection(), dir));
|
|
||||||
else
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a 2D quad
|
|
||||||
void makeQuad(Ogre::SceneManager* sceneMgr, float left, float top, float right, float bottom, Ogre::MaterialPtr material)
|
|
||||||
{
|
|
||||||
Ogre::ManualObject* manual = sceneMgr->createManualObject();
|
|
||||||
|
|
||||||
// Use identity view/projection matrices to get a 2d quad
|
|
||||||
manual->setUseIdentityProjection(true);
|
|
||||||
manual->setUseIdentityView(true);
|
|
||||||
|
|
||||||
manual->begin(material->getName());
|
|
||||||
|
|
||||||
float normLeft = left*2-1;
|
|
||||||
float normTop = top*2-1;
|
|
||||||
float normRight = right*2-1;
|
|
||||||
float normBottom = bottom*2-1;
|
|
||||||
|
|
||||||
manual->position(normLeft, normTop, 0.0);
|
|
||||||
manual->textureCoord(0, 1);
|
|
||||||
manual->position(normRight, normTop, 0.0);
|
|
||||||
manual->textureCoord(1, 1);
|
|
||||||
manual->position(normRight, normBottom, 0.0);
|
|
||||||
manual->textureCoord(1, 0);
|
|
||||||
manual->position(normLeft, normBottom, 0.0);
|
|
||||||
manual->textureCoord(0, 0);
|
|
||||||
|
|
||||||
manual->quad(0,1,2,3);
|
|
||||||
|
|
||||||
manual->end();
|
|
||||||
|
|
||||||
Ogre::AxisAlignedBox aabInf;
|
|
||||||
aabInf.setInfinite();
|
|
||||||
manual->setBoundingBox(aabInf);
|
|
||||||
|
|
||||||
sceneMgr->getRootSceneNode()->createChildSceneNode()->attachObject(manual);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
QuadTreeNode::QuadTreeNode(DefaultWorld* terrain, ChildDirection dir, float size, const Ogre::Vector2 ¢er, QuadTreeNode* parent)
|
|
||||||
: mMaterialGenerator(NULL)
|
|
||||||
, mLoadState(LS_Unloaded)
|
|
||||||
, mIsDummy(false)
|
|
||||||
, mSize(size)
|
|
||||||
, mLodLevel(Log2(static_cast<int>(mSize)))
|
|
||||||
, mBounds(Ogre::AxisAlignedBox::BOX_NULL)
|
|
||||||
, mWorldBounds(Ogre::AxisAlignedBox::BOX_NULL)
|
|
||||||
, mDirection(dir)
|
|
||||||
, mCenter(center)
|
|
||||||
, mSceneNode(NULL)
|
|
||||||
, mParent(parent)
|
|
||||||
, mChunk(NULL)
|
|
||||||
, mTerrain(terrain)
|
|
||||||
{
|
|
||||||
mBounds.setNull();
|
|
||||||
for (int i=0; i<4; ++i)
|
|
||||||
mChildren[i] = NULL;
|
|
||||||
for (int i=0; i<4; ++i)
|
|
||||||
mNeighbours[i] = NULL;
|
|
||||||
|
|
||||||
if (mDirection == Root)
|
|
||||||
mSceneNode = mTerrain->getRootSceneNode();
|
|
||||||
else
|
|
||||||
mSceneNode = mTerrain->getSceneManager()->createSceneNode();
|
|
||||||
Ogre::Vector2 pos (0,0);
|
|
||||||
if (mParent)
|
|
||||||
pos = mParent->getCenter();
|
|
||||||
pos = mCenter - pos;
|
|
||||||
float cellWorldSize = mTerrain->getStorage()->getCellWorldSize();
|
|
||||||
|
|
||||||
Ogre::Vector3 sceneNodePos (pos.x*cellWorldSize, pos.y*cellWorldSize, 0);
|
|
||||||
mTerrain->convertPosition(sceneNodePos);
|
|
||||||
|
|
||||||
mSceneNode->setPosition(sceneNodePos);
|
|
||||||
|
|
||||||
mMaterialGenerator = new MaterialGenerator();
|
|
||||||
mMaterialGenerator->enableShaders(mTerrain->getShadersEnabled());
|
|
||||||
}
|
|
||||||
|
|
||||||
void QuadTreeNode::createChild(ChildDirection id, float size, const Ogre::Vector2 ¢er)
|
|
||||||
{
|
|
||||||
mChildren[id] = new QuadTreeNode(mTerrain, id, size, center, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
QuadTreeNode::~QuadTreeNode()
|
|
||||||
{
|
|
||||||
for (int i=0; i<4; ++i)
|
|
||||||
delete mChildren[i];
|
|
||||||
delete mChunk;
|
|
||||||
delete mMaterialGenerator;
|
|
||||||
}
|
|
||||||
|
|
||||||
QuadTreeNode* QuadTreeNode::getNeighbour(Direction dir)
|
|
||||||
{
|
|
||||||
return mNeighbours[static_cast<int>(dir)];
|
|
||||||
}
|
|
||||||
|
|
||||||
void QuadTreeNode::initNeighbours()
|
|
||||||
{
|
|
||||||
for (int i=0; i<4; ++i)
|
|
||||||
mNeighbours[i] = searchNeighbourRecursive(this, (Direction)i);
|
|
||||||
|
|
||||||
if (hasChildren())
|
|
||||||
for (int i=0; i<4; ++i)
|
|
||||||
mChildren[i]->initNeighbours();
|
|
||||||
}
|
|
||||||
|
|
||||||
void QuadTreeNode::initAabb()
|
|
||||||
{
|
|
||||||
float cellWorldSize = mTerrain->getStorage()->getCellWorldSize();
|
|
||||||
if (hasChildren())
|
|
||||||
{
|
|
||||||
for (int i=0; i<4; ++i)
|
|
||||||
{
|
|
||||||
mChildren[i]->initAabb();
|
|
||||||
mBounds.merge(mChildren[i]->getBoundingBox());
|
|
||||||
}
|
|
||||||
float minH, maxH;
|
|
||||||
switch (mTerrain->getAlign())
|
|
||||||
{
|
|
||||||
case Terrain::Align_XY:
|
|
||||||
minH = mBounds.getMinimum().z;
|
|
||||||
maxH = mBounds.getMaximum().z;
|
|
||||||
break;
|
|
||||||
case Terrain::Align_XZ:
|
|
||||||
minH = mBounds.getMinimum().y;
|
|
||||||
maxH = mBounds.getMaximum().y;
|
|
||||||
break;
|
|
||||||
case Terrain::Align_YZ:
|
|
||||||
minH = mBounds.getMinimum().x;
|
|
||||||
maxH = mBounds.getMaximum().x;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
Ogre::Vector3 min(-mSize/2*cellWorldSize, -mSize/2*cellWorldSize, minH);
|
|
||||||
Ogre::Vector3 max(Ogre::Vector3(mSize/2*cellWorldSize, mSize/2*cellWorldSize, maxH));
|
|
||||||
mBounds = Ogre::AxisAlignedBox (min, max);
|
|
||||||
mTerrain->convertBounds(mBounds);
|
|
||||||
}
|
|
||||||
Ogre::Vector3 offset(mCenter.x*cellWorldSize, mCenter.y*cellWorldSize, 0);
|
|
||||||
mTerrain->convertPosition(offset);
|
|
||||||
mWorldBounds = Ogre::AxisAlignedBox(mBounds.getMinimum() + offset,
|
|
||||||
mBounds.getMaximum() + offset);
|
|
||||||
}
|
|
||||||
|
|
||||||
void QuadTreeNode::setBoundingBox(const Ogre::AxisAlignedBox &box)
|
|
||||||
{
|
|
||||||
mBounds = box;
|
|
||||||
}
|
|
||||||
|
|
||||||
const Ogre::AxisAlignedBox& QuadTreeNode::getBoundingBox()
|
|
||||||
{
|
|
||||||
return mBounds;
|
|
||||||
}
|
|
||||||
|
|
||||||
const Ogre::AxisAlignedBox& QuadTreeNode::getWorldBoundingBox()
|
|
||||||
{
|
|
||||||
return mWorldBounds;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool QuadTreeNode::update(const Ogre::Vector3 &cameraPos)
|
|
||||||
{
|
|
||||||
if (isDummy())
|
|
||||||
return true;
|
|
||||||
|
|
||||||
if (mBounds.isNull())
|
|
||||||
return true;
|
|
||||||
|
|
||||||
float dist = mWorldBounds.distance(cameraPos);
|
|
||||||
|
|
||||||
// Make sure our scene node is attached
|
|
||||||
if (!mSceneNode->isInSceneGraph())
|
|
||||||
{
|
|
||||||
mParent->getSceneNode()->addChild(mSceneNode);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Simple LOD selection
|
|
||||||
/// \todo use error metrics?
|
|
||||||
size_t wantedLod = 0;
|
|
||||||
float cellWorldSize = mTerrain->getStorage()->getCellWorldSize();
|
|
||||||
|
|
||||||
if (dist > cellWorldSize*64)
|
|
||||||
wantedLod = 6;
|
|
||||||
else if (dist > cellWorldSize*32)
|
|
||||||
wantedLod = 5;
|
|
||||||
else if (dist > cellWorldSize*12)
|
|
||||||
wantedLod = 4;
|
|
||||||
else if (dist > cellWorldSize*5)
|
|
||||||
wantedLod = 3;
|
|
||||||
else if (dist > cellWorldSize*2)
|
|
||||||
wantedLod = 2;
|
|
||||||
else if (dist > cellWorldSize * 1.42) // < sqrt2 so the 3x3 grid around player is always highest lod
|
|
||||||
wantedLod = 1;
|
|
||||||
|
|
||||||
bool wantToDisplay = mSize <= mTerrain->getMaxBatchSize() && mLodLevel <= wantedLod;
|
|
||||||
|
|
||||||
if (wantToDisplay)
|
|
||||||
{
|
|
||||||
// Wanted LOD is small enough to render this node in one chunk
|
|
||||||
if (mLoadState == LS_Unloaded)
|
|
||||||
{
|
|
||||||
mLoadState = LS_Loading;
|
|
||||||
mTerrain->queueLoad(this);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mLoadState == LS_Loaded)
|
|
||||||
{
|
|
||||||
// Additional (index buffer) LOD is currently disabled.
|
|
||||||
// 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 (!mChunk->getVisible() && hasChildren())
|
|
||||||
{
|
|
||||||
for (int i=0; i<4; ++i)
|
|
||||||
mChildren[i]->unload(true);
|
|
||||||
}
|
|
||||||
mChunk->setVisible(true);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false; // LS_Loading
|
|
||||||
}
|
|
||||||
|
|
||||||
// We do not want to display this node - delegate to children if they are already loaded
|
|
||||||
if (!wantToDisplay && hasChildren())
|
|
||||||
{
|
|
||||||
if (mChunk)
|
|
||||||
{
|
|
||||||
// Are children already loaded?
|
|
||||||
bool childrenLoaded = true;
|
|
||||||
for (int i=0; i<4; ++i)
|
|
||||||
if (!mChildren[i]->update(cameraPos))
|
|
||||||
childrenLoaded = false;
|
|
||||||
|
|
||||||
if (!childrenLoaded)
|
|
||||||
{
|
|
||||||
mChunk->setVisible(true);
|
|
||||||
// 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);
|
|
||||||
|
|
||||||
mChunk = new Chunk(mTerrain->getBufferCache().getUVBuffer(), mBounds, data.mPositions, data.mNormals, data.mColours);
|
|
||||||
mChunk->setVisibilityFlags(mTerrain->getVisibilityFlags());
|
|
||||||
mChunk->setCastShadows(true);
|
|
||||||
mSceneNode->attachObject(mChunk);
|
|
||||||
|
|
||||||
mMaterialGenerator->enableShadows(mTerrain->getShadowsEnabled());
|
|
||||||
mMaterialGenerator->enableSplitShadows(mTerrain->getSplitShadowsEnabled());
|
|
||||||
|
|
||||||
if (mTerrain->areLayersLoaded())
|
|
||||||
{
|
|
||||||
if (mSize == 1)
|
|
||||||
{
|
|
||||||
mChunk->setMaterial(mMaterialGenerator->generate());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ensureCompositeMap();
|
|
||||||
mMaterialGenerator->setCompositeMap(mCompositeMap->getName());
|
|
||||||
mChunk->setMaterial(mMaterialGenerator->generateForCompositeMap());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// else: will be loaded in loadMaterials() after background thread has finished loading layers
|
|
||||||
mChunk->setVisible(false);
|
|
||||||
|
|
||||||
mLoadState = LS_Loaded;
|
|
||||||
}
|
|
||||||
|
|
||||||
void QuadTreeNode::unload(bool recursive)
|
|
||||||
{
|
|
||||||
if (mChunk)
|
|
||||||
{
|
|
||||||
mSceneNode->detachObject(mChunk);
|
|
||||||
|
|
||||||
delete mChunk;
|
|
||||||
mChunk = NULL;
|
|
||||||
|
|
||||||
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(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void QuadTreeNode::updateIndexBuffers()
|
|
||||||
{
|
|
||||||
if (hasChunk())
|
|
||||||
{
|
|
||||||
// Fetch a suitable index buffer (which may be shared)
|
|
||||||
size_t ourLod = getActualLodLevel();
|
|
||||||
|
|
||||||
unsigned int flags = 0;
|
|
||||||
|
|
||||||
for (int i=0; i<4; ++i)
|
|
||||||
{
|
|
||||||
QuadTreeNode* neighbour = getNeighbour((Direction)i);
|
|
||||||
|
|
||||||
// If the neighbour isn't currently rendering itself,
|
|
||||||
// go up until we find one. NOTE: We don't need to go down,
|
|
||||||
// because in that case neighbour's detail would be higher than
|
|
||||||
// our detail and the neighbour would handle stitching by itself.
|
|
||||||
while (neighbour && !neighbour->hasChunk())
|
|
||||||
neighbour = neighbour->getParent();
|
|
||||||
size_t lod = 0;
|
|
||||||
if (neighbour)
|
|
||||||
lod = neighbour->getActualLodLevel();
|
|
||||||
if (lod <= ourLod) // We only need to worry about neighbours less detailed than we are -
|
|
||||||
lod = 0; // neighbours with more detail will do the stitching themselves
|
|
||||||
// Use 4 bits for each LOD delta
|
|
||||||
if (lod > 0)
|
|
||||||
{
|
|
||||||
assert (lod - ourLod < (1 << 4));
|
|
||||||
flags |= static_cast<unsigned int>(lod - ourLod) << (4*i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
flags |= 0 /*((int)mAdditionalLod)*/ << (4*4);
|
|
||||||
|
|
||||||
mChunk->setIndexBuffer(mTerrain->getBufferCache().getIndexBuffer(flags));
|
|
||||||
}
|
|
||||||
else if (hasChildren())
|
|
||||||
{
|
|
||||||
for (int i=0; i<4; ++i)
|
|
||||||
mChildren[i]->updateIndexBuffers();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool QuadTreeNode::hasChunk()
|
|
||||||
{
|
|
||||||
return mSceneNode->isInSceneGraph() && mChunk && mChunk->getVisible();
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t QuadTreeNode::getActualLodLevel()
|
|
||||||
{
|
|
||||||
assert(hasChunk() && "Can't get actual LOD level if this node has no render chunk");
|
|
||||||
return mLodLevel /* + mChunk->getAdditionalLod() */;
|
|
||||||
}
|
|
||||||
|
|
||||||
void QuadTreeNode::loadLayers(const LayerCollection& collection)
|
|
||||||
{
|
|
||||||
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;
|
|
||||||
|
|
||||||
// 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();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mChunk)
|
|
||||||
{
|
|
||||||
if (mSize == 1)
|
|
||||||
{
|
|
||||||
mChunk->setMaterial(mMaterialGenerator->generate());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ensureCompositeMap();
|
|
||||||
mMaterialGenerator->setCompositeMap(mCompositeMap->getName());
|
|
||||||
mChunk->setMaterial(mMaterialGenerator->generateForCompositeMap());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void QuadTreeNode::prepareForCompositeMap(Ogre::TRect<float> area)
|
|
||||||
{
|
|
||||||
Ogre::SceneManager* sceneMgr = mTerrain->getCompositeMapSceneManager();
|
|
||||||
|
|
||||||
if (mIsDummy)
|
|
||||||
{
|
|
||||||
// TODO - store this default material somewhere instead of creating one for each empty cell
|
|
||||||
MaterialGenerator matGen;
|
|
||||||
matGen.enableShaders(mTerrain->getShadersEnabled());
|
|
||||||
std::vector<LayerInfo> layer;
|
|
||||||
layer.push_back(mTerrain->getStorage()->getDefaultLayer());
|
|
||||||
matGen.setLayerList(layer);
|
|
||||||
makeQuad(sceneMgr, area.left, area.top, area.right, area.bottom, matGen.generateForCompositeMapRTT());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (mSize > 1)
|
|
||||||
{
|
|
||||||
assert(hasChildren());
|
|
||||||
|
|
||||||
// 0,0 -------- 1,0
|
|
||||||
// | | |
|
|
||||||
// |-----|------|
|
|
||||||
// | | |
|
|
||||||
// 0,1 -------- 1,1
|
|
||||||
|
|
||||||
float halfW = area.width()/2.f;
|
|
||||||
float halfH = area.height()/2.f;
|
|
||||||
mChildren[NW]->prepareForCompositeMap(Ogre::TRect<float>(area.left, area.top, area.right-halfW, area.bottom-halfH));
|
|
||||||
mChildren[NE]->prepareForCompositeMap(Ogre::TRect<float>(area.left+halfW, area.top, area.right, area.bottom-halfH));
|
|
||||||
mChildren[SW]->prepareForCompositeMap(Ogre::TRect<float>(area.left, area.top+halfH, area.right-halfW, area.bottom));
|
|
||||||
mChildren[SE]->prepareForCompositeMap(Ogre::TRect<float>(area.left+halfW, area.top+halfH, area.right, area.bottom));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// TODO: when to destroy?
|
|
||||||
Ogre::MaterialPtr material = mMaterialGenerator->generateForCompositeMapRTT();
|
|
||||||
makeQuad(sceneMgr, area.left, area.top, area.right, area.bottom, material);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void QuadTreeNode::ensureCompositeMap()
|
|
||||||
{
|
|
||||||
if (!mCompositeMap.isNull())
|
|
||||||
return;
|
|
||||||
|
|
||||||
static int i=0;
|
|
||||||
std::stringstream name;
|
|
||||||
name << "terrain/comp" << i++;
|
|
||||||
|
|
||||||
const int size = 128;
|
|
||||||
mCompositeMap = Ogre::TextureManager::getSingleton().createManual(
|
|
||||||
name.str(), Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME,
|
|
||||||
Ogre::TEX_TYPE_2D, size, size, Ogre::MIP_DEFAULT, Ogre::PF_A8B8G8R8);
|
|
||||||
|
|
||||||
// Create quads for each cell
|
|
||||||
prepareForCompositeMap(Ogre::TRect<float>(0,0,1,1));
|
|
||||||
|
|
||||||
mTerrain->renderCompositeMap(mCompositeMap);
|
|
||||||
|
|
||||||
mTerrain->clearCompositeMapSceneManager();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void QuadTreeNode::applyMaterials()
|
|
||||||
{
|
|
||||||
if (mChunk)
|
|
||||||
{
|
|
||||||
mMaterialGenerator->enableShadows(mTerrain->getShadowsEnabled());
|
|
||||||
mMaterialGenerator->enableSplitShadows(mTerrain->getSplitShadowsEnabled());
|
|
||||||
if (mSize <= 1)
|
|
||||||
mChunk->setMaterial(mMaterialGenerator->generate());
|
|
||||||
else
|
|
||||||
mChunk->setMaterial(mMaterialGenerator->generateForCompositeMap());
|
|
||||||
}
|
|
||||||
if (hasChildren())
|
|
||||||
for (int i=0; i<4; ++i)
|
|
||||||
mChildren[i]->applyMaterials();
|
|
||||||
}
|
|
@ -1,189 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2015 scrawl <scrawl@baseoftrash.de>
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
|
||||||
* in the Software without restriction, including without limitation the rights
|
|
||||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
* copies of the Software, and to permit persons to whom the Software is
|
|
||||||
* furnished to do so, subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be included in
|
|
||||||
* all copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
* THE SOFTWARE.
|
|
||||||
*/
|
|
||||||
#ifndef COMPONENTS_TERRAIN_QUADTREENODE_H
|
|
||||||
#define COMPONENTS_TERRAIN_QUADTREENODE_H
|
|
||||||
|
|
||||||
#include <OgreAxisAlignedBox.h>
|
|
||||||
#include <OgreVector2.h>
|
|
||||||
#include <OgreTexture.h>
|
|
||||||
|
|
||||||
#include "defs.hpp"
|
|
||||||
|
|
||||||
namespace Ogre
|
|
||||||
{
|
|
||||||
class Rectangle2D;
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace Terrain
|
|
||||||
{
|
|
||||||
class DefaultWorld;
|
|
||||||
class Chunk;
|
|
||||||
class MaterialGenerator;
|
|
||||||
struct LoadResponseData;
|
|
||||||
|
|
||||||
enum ChildDirection
|
|
||||||
{
|
|
||||||
NW = 0,
|
|
||||||
NE = 1,
|
|
||||||
SW = 2,
|
|
||||||
SE = 3,
|
|
||||||
Root
|
|
||||||
};
|
|
||||||
|
|
||||||
enum LoadState
|
|
||||||
{
|
|
||||||
LS_Unloaded,
|
|
||||||
LS_Loading,
|
|
||||||
LS_Loaded
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @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),
|
|
||||||
* or delegate the render process to its children, rendering each child in at least one batch.
|
|
||||||
*/
|
|
||||||
class QuadTreeNode
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
/// @param terrain
|
|
||||||
/// @param dir relative to parent, or Root if we are the root node
|
|
||||||
/// @param size size (in *cell* units!)
|
|
||||||
/// @param center center (in *cell* units!)
|
|
||||||
/// @param parent parent node
|
|
||||||
QuadTreeNode (DefaultWorld* terrain, ChildDirection dir, float size, const Ogre::Vector2& center, QuadTreeNode* parent);
|
|
||||||
~QuadTreeNode();
|
|
||||||
|
|
||||||
/// Rebuild all materials
|
|
||||||
void applyMaterials();
|
|
||||||
|
|
||||||
/// Initialize neighbours - do this after the quadtree is built
|
|
||||||
void initNeighbours();
|
|
||||||
/// Initialize bounding boxes of non-leafs by merging children bounding boxes.
|
|
||||||
/// Do this after the quadtree is built - note that leaf bounding boxes
|
|
||||||
/// need to be set first via setBoundingBox!
|
|
||||||
void initAabb();
|
|
||||||
|
|
||||||
/// @note takes ownership of \a child
|
|
||||||
void createChild (ChildDirection id, float size, const Ogre::Vector2& center);
|
|
||||||
|
|
||||||
/// Mark this node as a dummy node. This can happen if the terrain size isn't a power of two.
|
|
||||||
/// For the QuadTree to work, we need to round the size up to a power of two, which means we'll
|
|
||||||
/// end up with empty nodes that don't actually render anything.
|
|
||||||
void markAsDummy() { mIsDummy = true; }
|
|
||||||
bool isDummy() { return mIsDummy; }
|
|
||||||
|
|
||||||
QuadTreeNode* getParent() { return mParent; }
|
|
||||||
|
|
||||||
Ogre::SceneNode* getSceneNode() { return mSceneNode; }
|
|
||||||
|
|
||||||
int getSize() { return static_cast<int>(mSize); }
|
|
||||||
Ogre::Vector2 getCenter() { return mCenter; }
|
|
||||||
|
|
||||||
bool hasChildren() { return mChildren[0] != 0; }
|
|
||||||
QuadTreeNode* getChild(ChildDirection dir) { return mChildren[dir]; }
|
|
||||||
|
|
||||||
/// Get neighbour node in this direction
|
|
||||||
QuadTreeNode* getNeighbour (Direction dir);
|
|
||||||
|
|
||||||
/// Returns our direction relative to the parent node, or Root if we are the root node.
|
|
||||||
ChildDirection getDirection() { return mDirection; }
|
|
||||||
|
|
||||||
/// Set bounding box in local coordinates. Should be done at load time for leaf nodes.
|
|
||||||
/// Other nodes can merge AABB of child nodes.
|
|
||||||
void setBoundingBox (const Ogre::AxisAlignedBox& box);
|
|
||||||
|
|
||||||
/// Get bounding box in local coordinates
|
|
||||||
const Ogre::AxisAlignedBox& getBoundingBox();
|
|
||||||
|
|
||||||
const Ogre::AxisAlignedBox& getWorldBoundingBox();
|
|
||||||
|
|
||||||
DefaultWorld* getTerrain() { return mTerrain; }
|
|
||||||
|
|
||||||
/// Adjust LODs for the given camera position, possibly splitting up chunks or merging them.
|
|
||||||
/// @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!
|
|
||||||
void updateIndexBuffers();
|
|
||||||
|
|
||||||
/// Destroy chunks rendered by this node *and* its children (if param is true)
|
|
||||||
void destroyChunks(bool children);
|
|
||||||
|
|
||||||
/// Get the effective LOD level if this node was rendered in one chunk
|
|
||||||
/// with Storage::getCellVertices^2 vertices
|
|
||||||
size_t getNativeLodLevel() { return mLodLevel; }
|
|
||||||
|
|
||||||
/// Get the effective current LOD level used by the chunk rendering this node
|
|
||||||
size_t getActualLodLevel();
|
|
||||||
|
|
||||||
/// Is this node currently configured to render itself?
|
|
||||||
bool hasChunk();
|
|
||||||
|
|
||||||
/// 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
|
|
||||||
void prepareForCompositeMap(Ogre::TRect<float> area);
|
|
||||||
|
|
||||||
/// Create a chunk for this node from the given data.
|
|
||||||
void load (const LoadResponseData& data);
|
|
||||||
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; }
|
|
||||||
|
|
||||||
private:
|
|
||||||
// Stored here for convenience in case we need layer list again
|
|
||||||
MaterialGenerator* mMaterialGenerator;
|
|
||||||
|
|
||||||
LoadState mLoadState;
|
|
||||||
|
|
||||||
bool mIsDummy;
|
|
||||||
float mSize;
|
|
||||||
size_t mLodLevel; // LOD if we were to render this node in one chunk
|
|
||||||
Ogre::AxisAlignedBox mBounds;
|
|
||||||
Ogre::AxisAlignedBox mWorldBounds;
|
|
||||||
ChildDirection mDirection;
|
|
||||||
Ogre::Vector2 mCenter;
|
|
||||||
|
|
||||||
Ogre::SceneNode* mSceneNode;
|
|
||||||
|
|
||||||
QuadTreeNode* mParent;
|
|
||||||
QuadTreeNode* mChildren[4];
|
|
||||||
QuadTreeNode* mNeighbours[4];
|
|
||||||
|
|
||||||
Chunk* mChunk;
|
|
||||||
|
|
||||||
DefaultWorld* mTerrain;
|
|
||||||
|
|
||||||
Ogre::TexturePtr mCompositeMap;
|
|
||||||
|
|
||||||
void ensureCompositeMap();
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
Loading…
Reference in New Issue