You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
411 lines
16 KiB
C++
411 lines
16 KiB
C++
#include "world.hpp"
|
|
|
|
#include <OgreAxisAlignedBox.h>
|
|
#include <OgreCamera.h>
|
|
#include <OgreHardwareBufferManager.h>
|
|
#include <OgreHardwarePixelBuffer.h>
|
|
#include <OgreRoot.h>
|
|
|
|
#include <components/esm/loadland.hpp>
|
|
#include <components/loadinglistener/loadinglistener.hpp>
|
|
|
|
#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
|
|
{
|
|
|
|
World::World(Loading::Listener* loadingListener, Ogre::SceneManager* sceneMgr,
|
|
Storage* storage, int visibilityFlags, bool distantLand, bool shaders)
|
|
: mStorage(storage)
|
|
, mMinBatchSize(1)
|
|
, mMaxBatchSize(64)
|
|
, mSceneMgr(sceneMgr)
|
|
, mVisibilityFlags(visibilityFlags)
|
|
, mDistantLand(distantLand)
|
|
, mShaders(shaders)
|
|
, mVisible(true)
|
|
, mLoadingListener(loadingListener)
|
|
{
|
|
loadingListener->setLabel("Creating terrain");
|
|
loadingListener->indicateProgress();
|
|
|
|
mCompositeMapSceneMgr = Ogre::Root::getSingleton().createSceneManager(Ogre::ST_GENERIC);
|
|
|
|
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);
|
|
|
|
mBounds = storage->getBounds();
|
|
|
|
int origSizeX = mBounds.getSize().x;
|
|
int origSizeY = mBounds.getSize().y;
|
|
|
|
// 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
|
|
Ogre::Vector3 center = mBounds.getCenter() + Ogre::Vector3((size-origSizeX)/2.f, (size-origSizeY)/2.f, 0);
|
|
|
|
mRootSceneNode = mSceneMgr->getRootSceneNode()->createChildSceneNode();
|
|
|
|
mRootNode = new QuadTreeNode(this, Root, size, Ogre::Vector2(center.x, center.y), NULL);
|
|
buildQuadTree(mRootNode);
|
|
loadingListener->indicateProgress();
|
|
mRootNode->initAabb();
|
|
loadingListener->indicateProgress();
|
|
mRootNode->initNeighbours();
|
|
loadingListener->indicateProgress();
|
|
}
|
|
|
|
World::~World()
|
|
{
|
|
delete mRootNode;
|
|
delete mStorage;
|
|
}
|
|
|
|
void World::buildQuadTree(QuadTreeNode *node)
|
|
{
|
|
float halfSize = node->getSize()/2.f;
|
|
|
|
if (node->getSize() <= mMinBatchSize)
|
|
{
|
|
// We arrived at a leaf
|
|
float minZ,maxZ;
|
|
Ogre::Vector2 center = node->getCenter();
|
|
if (mStorage->getMinMaxHeights(node->getSize(), center, minZ, maxZ))
|
|
node->setBoundingBox(Ogre::AxisAlignedBox(Ogre::Vector3(-halfSize*8192, -halfSize*8192, minZ),
|
|
Ogre::Vector3(halfSize*8192, halfSize*8192, maxZ)));
|
|
else
|
|
node->markAsDummy(); // no data available for this node, skip it
|
|
return;
|
|
}
|
|
|
|
if (node->getCenter().x - halfSize > mBounds.getMaximum().x
|
|
|| node->getCenter().x + halfSize < mBounds.getMinimum().x
|
|
|| node->getCenter().y - halfSize > mBounds.getMaximum().y
|
|
|| node->getCenter().y + halfSize < mBounds.getMinimum().y )
|
|
// 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));
|
|
buildQuadTree(node->getChild(SE));
|
|
buildQuadTree(node->getChild(NW));
|
|
buildQuadTree(node->getChild(NE));
|
|
|
|
// if all children are dummy, we are also dummy
|
|
for (int i=0; i<4; ++i)
|
|
{
|
|
if (!node->getChild((ChildDirection)i)->isDummy())
|
|
return;
|
|
}
|
|
node->markAsDummy();
|
|
}
|
|
|
|
void World::update(const Ogre::Vector3& cameraPos)
|
|
{
|
|
if (!mVisible)
|
|
return;
|
|
mRootNode->update(cameraPos, mLoadingListener);
|
|
mRootNode->updateIndexBuffers();
|
|
}
|
|
|
|
Ogre::AxisAlignedBox World::getWorldBoundingBox (const Ogre::Vector2& center)
|
|
{
|
|
if (center.x > mBounds.getMaximum().x
|
|
|| center.x < mBounds.getMinimum().x
|
|
|| center.y > mBounds.getMaximum().y
|
|
|| center.y < mBounds.getMinimum().y)
|
|
return Ogre::AxisAlignedBox::BOX_NULL;
|
|
QuadTreeNode* node = findNode(center, mRootNode);
|
|
Ogre::AxisAlignedBox box = node->getBoundingBox();
|
|
box.setExtents(box.getMinimum() + Ogre::Vector3(center.x, center.y, 0) * 8192,
|
|
box.getMaximum() + Ogre::Vector3(center.x, center.y, 0) * 8192);
|
|
return box;
|
|
}
|
|
|
|
Ogre::HardwareVertexBufferSharedPtr World::getVertexBuffer(int numVertsOneSide)
|
|
{
|
|
if (mUvBufferMap.find(numVertsOneSide) != mUvBufferMap.end())
|
|
{
|
|
return mUvBufferMap[numVertsOneSide];
|
|
}
|
|
|
|
int vertexCount = numVertsOneSide * numVertsOneSide;
|
|
|
|
std::vector<float> uvs;
|
|
uvs.reserve(vertexCount*2);
|
|
|
|
for (int col = 0; col < numVertsOneSide; ++col)
|
|
{
|
|
for (int row = 0; row < numVertsOneSide; ++row)
|
|
{
|
|
uvs.push_back(col / static_cast<float>(numVertsOneSide-1)); // U
|
|
uvs.push_back(row / static_cast<float>(numVertsOneSide-1)); // V
|
|
}
|
|
}
|
|
|
|
Ogre::HardwareBufferManager* mgr = Ogre::HardwareBufferManager::getSingletonPtr();
|
|
Ogre::HardwareVertexBufferSharedPtr buffer = mgr->createVertexBuffer(
|
|
Ogre::VertexElement::getTypeSize(Ogre::VET_FLOAT2),
|
|
vertexCount, Ogre::HardwareBuffer::HBU_STATIC);
|
|
|
|
buffer->writeData(0, buffer->getSizeInBytes(), &uvs[0], true);
|
|
|
|
mUvBufferMap[numVertsOneSide] = buffer;
|
|
return buffer;
|
|
}
|
|
|
|
Ogre::HardwareIndexBufferSharedPtr World::getIndexBuffer(int flags, size_t& numIndices)
|
|
{
|
|
if (mIndexBufferMap.find(flags) != mIndexBufferMap.end())
|
|
{
|
|
numIndices = mIndexBufferMap[flags]->getNumIndexes();
|
|
return mIndexBufferMap[flags];
|
|
}
|
|
|
|
// LOD level n means every 2^n-th vertex is kept
|
|
size_t lodLevel = (flags >> (4*4));
|
|
|
|
size_t lodDeltas[4];
|
|
for (int i=0; i<4; ++i)
|
|
lodDeltas[i] = (flags >> (4*i)) & (0xf);
|
|
|
|
bool anyDeltas = (lodDeltas[North] || lodDeltas[South] || lodDeltas[West] || lodDeltas[East]);
|
|
|
|
size_t increment = 1 << lodLevel;
|
|
assert((int)increment < ESM::Land::LAND_SIZE);
|
|
std::vector<short> indices;
|
|
indices.reserve((ESM::Land::LAND_SIZE-1)*(ESM::Land::LAND_SIZE-1)*2*3 / increment);
|
|
|
|
size_t rowStart = 0, colStart = 0, rowEnd = ESM::Land::LAND_SIZE-1, colEnd = ESM::Land::LAND_SIZE-1;
|
|
// If any edge needs stitching we'll skip all edges at this point,
|
|
// mainly because stitching one edge would have an effect on corners and on the adjacent edges
|
|
if (anyDeltas)
|
|
{
|
|
colStart += increment;
|
|
colEnd -= increment;
|
|
rowEnd -= increment;
|
|
rowStart += increment;
|
|
}
|
|
for (size_t row = rowStart; row < rowEnd; row += increment)
|
|
{
|
|
for (size_t col = colStart; col < colEnd; col += increment)
|
|
{
|
|
indices.push_back(ESM::Land::LAND_SIZE*col+row);
|
|
indices.push_back(ESM::Land::LAND_SIZE*(col+increment)+row+increment);
|
|
indices.push_back(ESM::Land::LAND_SIZE*col+row+increment);
|
|
|
|
indices.push_back(ESM::Land::LAND_SIZE*col+row);
|
|
indices.push_back(ESM::Land::LAND_SIZE*(col+increment)+row);
|
|
indices.push_back(ESM::Land::LAND_SIZE*(col+increment)+row+increment);
|
|
}
|
|
}
|
|
|
|
size_t innerStep = increment;
|
|
if (anyDeltas)
|
|
{
|
|
// Now configure LOD transitions at the edges - this is pretty tedious,
|
|
// and some very long and boring code, but it works great
|
|
|
|
// South
|
|
size_t row = 0;
|
|
size_t outerStep = 1 << (lodDeltas[South] + lodLevel);
|
|
for (size_t col = 0; col < ESM::Land::LAND_SIZE-1; col += outerStep)
|
|
{
|
|
indices.push_back(ESM::Land::LAND_SIZE*col+row);
|
|
indices.push_back(ESM::Land::LAND_SIZE*(col+outerStep)+row);
|
|
// Make sure not to touch the right edge
|
|
if (col+outerStep == ESM::Land::LAND_SIZE-1)
|
|
indices.push_back(ESM::Land::LAND_SIZE*(col+outerStep-innerStep)+row+innerStep);
|
|
else
|
|
indices.push_back(ESM::Land::LAND_SIZE*(col+outerStep)+row+innerStep);
|
|
|
|
for (size_t i = 0; i < outerStep; i += innerStep)
|
|
{
|
|
// Make sure not to touch the left or right edges
|
|
if (col+i == 0 || col+i == ESM::Land::LAND_SIZE-1-innerStep)
|
|
continue;
|
|
indices.push_back(ESM::Land::LAND_SIZE*(col)+row);
|
|
indices.push_back(ESM::Land::LAND_SIZE*(col+i+innerStep)+row+innerStep);
|
|
indices.push_back(ESM::Land::LAND_SIZE*(col+i)+row+innerStep);
|
|
}
|
|
}
|
|
|
|
// North
|
|
row = ESM::Land::LAND_SIZE-1;
|
|
outerStep = 1 << (lodDeltas[North] + lodLevel);
|
|
for (size_t col = 0; col < ESM::Land::LAND_SIZE-1; col += outerStep)
|
|
{
|
|
indices.push_back(ESM::Land::LAND_SIZE*(col+outerStep)+row);
|
|
indices.push_back(ESM::Land::LAND_SIZE*col+row);
|
|
// Make sure not to touch the left edge
|
|
if (col == 0)
|
|
indices.push_back(ESM::Land::LAND_SIZE*(col+innerStep)+row-innerStep);
|
|
else
|
|
indices.push_back(ESM::Land::LAND_SIZE*col+row-innerStep);
|
|
|
|
for (size_t i = 0; i < outerStep; i += innerStep)
|
|
{
|
|
// Make sure not to touch the left or right edges
|
|
if (col+i == 0 || col+i == ESM::Land::LAND_SIZE-1-innerStep)
|
|
continue;
|
|
indices.push_back(ESM::Land::LAND_SIZE*(col+i)+row-innerStep);
|
|
indices.push_back(ESM::Land::LAND_SIZE*(col+i+innerStep)+row-innerStep);
|
|
indices.push_back(ESM::Land::LAND_SIZE*(col+outerStep)+row);
|
|
}
|
|
}
|
|
|
|
// West
|
|
size_t col = 0;
|
|
outerStep = 1 << (lodDeltas[West] + lodLevel);
|
|
for (size_t row = 0; row < ESM::Land::LAND_SIZE-1; row += outerStep)
|
|
{
|
|
indices.push_back(ESM::Land::LAND_SIZE*col+row+outerStep);
|
|
indices.push_back(ESM::Land::LAND_SIZE*col+row);
|
|
// Make sure not to touch the top edge
|
|
if (row+outerStep == ESM::Land::LAND_SIZE-1)
|
|
indices.push_back(ESM::Land::LAND_SIZE*(col+innerStep)+row+outerStep-innerStep);
|
|
else
|
|
indices.push_back(ESM::Land::LAND_SIZE*(col+innerStep)+row+outerStep);
|
|
|
|
for (size_t i = 0; i < outerStep; i += innerStep)
|
|
{
|
|
// Make sure not to touch the top or bottom edges
|
|
if (row+i == 0 || row+i == ESM::Land::LAND_SIZE-1-innerStep)
|
|
continue;
|
|
indices.push_back(ESM::Land::LAND_SIZE*col+row);
|
|
indices.push_back(ESM::Land::LAND_SIZE*(col+innerStep)+row+i);
|
|
indices.push_back(ESM::Land::LAND_SIZE*(col+innerStep)+row+i+innerStep);
|
|
}
|
|
}
|
|
|
|
// East
|
|
col = ESM::Land::LAND_SIZE-1;
|
|
outerStep = 1 << (lodDeltas[East] + lodLevel);
|
|
for (size_t row = 0; row < ESM::Land::LAND_SIZE-1; row += outerStep)
|
|
{
|
|
indices.push_back(ESM::Land::LAND_SIZE*col+row);
|
|
indices.push_back(ESM::Land::LAND_SIZE*col+row+outerStep);
|
|
// Make sure not to touch the bottom edge
|
|
if (row == 0)
|
|
indices.push_back(ESM::Land::LAND_SIZE*(col-innerStep)+row+innerStep);
|
|
else
|
|
indices.push_back(ESM::Land::LAND_SIZE*(col-innerStep)+row);
|
|
|
|
for (size_t i = 0; i < outerStep; i += innerStep)
|
|
{
|
|
// Make sure not to touch the top or bottom edges
|
|
if (row+i == 0 || row+i == ESM::Land::LAND_SIZE-1-innerStep)
|
|
continue;
|
|
indices.push_back(ESM::Land::LAND_SIZE*col+row+outerStep);
|
|
indices.push_back(ESM::Land::LAND_SIZE*(col-innerStep)+row+i+innerStep);
|
|
indices.push_back(ESM::Land::LAND_SIZE*(col-innerStep)+row+i);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
numIndices = indices.size();
|
|
|
|
Ogre::HardwareBufferManager* mgr = Ogre::HardwareBufferManager::getSingletonPtr();
|
|
Ogre::HardwareIndexBufferSharedPtr buffer = mgr->createIndexBuffer(Ogre::HardwareIndexBuffer::IT_16BIT,
|
|
numIndices, Ogre::HardwareBuffer::HBU_STATIC);
|
|
buffer->writeData(0, buffer->getSizeInBytes(), &indices[0], true);
|
|
mIndexBufferMap[flags] = buffer;
|
|
return buffer;
|
|
}
|
|
|
|
void World::renderCompositeMap(Ogre::TexturePtr target)
|
|
{
|
|
mCompositeMapRenderTarget->update();
|
|
target->getBuffer()->blit(mCompositeMapRenderTexture->getBuffer());
|
|
}
|
|
|
|
void World::clearCompositeMapSceneManager()
|
|
{
|
|
mCompositeMapSceneMgr->destroyAllManualObjects();
|
|
mCompositeMapSceneMgr->clearScene();
|
|
}
|
|
|
|
float World::getHeightAt(const Ogre::Vector3 &worldPos)
|
|
{
|
|
return mStorage->getHeightAt(worldPos);
|
|
}
|
|
|
|
void World::applyMaterials(bool shadows, bool splitShadows)
|
|
{
|
|
mShadows = shadows;
|
|
mSplitShadows = splitShadows;
|
|
mRootNode->applyMaterials();
|
|
}
|
|
|
|
void World::setVisible(bool visible)
|
|
{
|
|
if (visible && !mVisible)
|
|
mSceneMgr->getRootSceneNode()->addChild(mRootSceneNode);
|
|
else if (!visible && mVisible)
|
|
mSceneMgr->getRootSceneNode()->removeChild(mRootSceneNode);
|
|
|
|
mVisible = visible;
|
|
}
|
|
|
|
bool World::getVisible()
|
|
{
|
|
return mVisible;
|
|
}
|
|
|
|
|
|
}
|