Imported Upstream version 0.26.0

This commit is contained in:
Bret Curtis 2013-10-17 16:37:22 +02:00
commit 9a2b6c69b6
1398 changed files with 212217 additions and 0 deletions

View file

@ -0,0 +1,169 @@
#include "chunk.hpp"
#include <OgreSceneNode.h>
#include <OgreHardwareBufferManager.h>
#include "quadtreenode.hpp"
#include "world.hpp"
#include "storage.hpp"
namespace Terrain
{
Chunk::Chunk(QuadTreeNode* node, short lodLevel)
: mNode(node)
, mVertexLod(lodLevel)
, mAdditionalLod(0)
{
mVertexData = OGRE_NEW Ogre::VertexData;
mVertexData->vertexStart = 0;
// Set the total number of vertices
size_t numVertsOneSide = mNode->getSize() * (ESM::Land::LAND_SIZE-1);
numVertsOneSide /= 1 << lodLevel;
numVertsOneSide += 1;
assert((int)numVertsOneSide == ESM::Land::LAND_SIZE);
mVertexData->vertexCount = numVertsOneSide * numVertsOneSide;
// 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);
mVertexBuffer = 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);
mNormalBuffer = 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);
Ogre::HardwareVertexBufferSharedPtr uvBuf = mNode->getTerrain()->getVertexBuffer(numVertsOneSide);
// Colours
vertexDecl->addElement(nextBuffer++, 0, Ogre::VET_COLOUR, Ogre::VES_DIFFUSE);
mColourBuffer = mgr->createVertexBuffer(Ogre::VertexElement::getTypeSize(Ogre::VET_COLOUR),
mVertexData->vertexCount, Ogre::HardwareBuffer::HBU_STATIC);
mNode->getTerrain()->getStorage()->fillVertexBuffers(lodLevel, mNode->getSize(), mNode->getCenter(),
mVertexBuffer, mNormalBuffer, mColourBuffer);
mVertexData->vertexBufferBinding->setBinding(0, mVertexBuffer);
mVertexData->vertexBufferBinding->setBinding(1, mNormalBuffer);
mVertexData->vertexBufferBinding->setBinding(2, uvBuf);
mVertexData->vertexBufferBinding->setBinding(3, mColourBuffer);
mIndexData = OGRE_NEW Ogre::IndexData();
mIndexData->indexStart = 0;
}
void Chunk::updateIndexBuffer()
{
// Fetch a suitable index buffer (which may be shared)
size_t ourLod = mVertexLod + mAdditionalLod;
int flags = 0;
for (int i=0; i<4; ++i)
{
QuadTreeNode* neighbour = mNode->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 |= int(lod - ourLod) << (4*i);
}
}
flags |= ((int)mAdditionalLod) << (4*4);
size_t numIndices;
mIndexBuffer = mNode->getTerrain()->getIndexBuffer(flags, numIndices);
mIndexData->indexCount = numIndices;
mIndexData->indexBuffer = mIndexBuffer;
}
Chunk::~Chunk()
{
OGRE_DELETE mVertexData;
OGRE_DELETE mIndexData;
}
void Chunk::setMaterial(const Ogre::MaterialPtr &material)
{
mMaterial = material;
}
const Ogre::AxisAlignedBox& Chunk::getBoundingBox(void) const
{
return mNode->getBoundingBox();
}
Ogre::Real Chunk::getBoundingRadius(void) const
{
return mNode->getBoundingBox().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 (!mIndexBuffer.isNull() && "Trying to render, but no index buffer 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();
}
}

View file

@ -0,0 +1,63 @@
#ifndef COMPONENTS_TERRAIN_TERRAINBATCH_H
#define COMPONENTS_TERRAIN_TERRAINBATCH_H
#include <OgreRenderable.h>
#include <OgreMovableObject.h>
namespace Terrain
{
class QuadTreeNode;
/**
* @brief Renders a chunk of terrain, either using alpha splatting or a composite map.
*/
class Chunk : public Ogre::Renderable, public Ogre::MovableObject
{
public:
/// @param lodLevel LOD level for the vertex buffer.
Chunk (QuadTreeNode* node, short lodLevel);
virtual ~Chunk();
void setMaterial (const Ogre::MaterialPtr& material);
/// Set additional LOD applied on top of vertex LOD. \n
/// This is achieved by changing the index buffer to omit vertices.
void setAdditionalLod (size_t lod) { mAdditionalLod = lod; }
size_t getAdditionalLod() { return mAdditionalLod; }
void updateIndexBuffer();
// 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:
QuadTreeNode* mNode;
Ogre::MaterialPtr mMaterial;
size_t mVertexLod;
size_t mAdditionalLod;
Ogre::VertexData* mVertexData;
Ogre::IndexData* mIndexData;
Ogre::HardwareVertexBufferSharedPtr mVertexBuffer;
Ogre::HardwareVertexBufferSharedPtr mNormalBuffer;
Ogre::HardwareVertexBufferSharedPtr mColourBuffer;
Ogre::HardwareIndexBufferSharedPtr mIndexBuffer;
};
}
#endif

View file

@ -0,0 +1,300 @@
#include "material.hpp"
#include <OgreMaterialManager.h>
#include <OgreTechnique.h>
#include <OgrePass.h>
#include <extern/shiny/Main/Factory.hpp>
namespace
{
int getBlendmapIndexForLayer (int layerIndex)
{
return std::floor((layerIndex-1)/4.f);
}
std::string getBlendmapComponentForLayer (int layerIndex)
{
int n = (layerIndex-1)%4;
if (n == 0)
return "x";
if (n == 1)
return "y";
if (n == 2)
return "z";
else
return "w";
}
}
namespace Terrain
{
MaterialGenerator::MaterialGenerator(bool shaders)
: mShaders(shaders)
, mShadows(false)
, mSplitShadows(false)
{
}
Ogre::MaterialPtr MaterialGenerator::generate(Ogre::MaterialPtr mat)
{
return create(mat, false, false);
}
Ogre::MaterialPtr MaterialGenerator::generateForCompositeMapRTT(Ogre::MaterialPtr mat)
{
return create(mat, true, false);
}
Ogre::MaterialPtr MaterialGenerator::generateForCompositeMap(Ogre::MaterialPtr mat)
{
return create(mat, false, true);
}
Ogre::MaterialPtr MaterialGenerator::create(Ogre::MaterialPtr mat, bool renderCompositeMap, bool displayCompositeMap)
{
assert(!renderCompositeMap || !displayCompositeMap);
if (!mat.isNull())
{
sh::Factory::getInstance().destroyMaterialInstance(mat->getName());
Ogre::MaterialManager::getSingleton().remove(mat->getName());
}
static int count = 0;
std::stringstream name;
name << "terrain/mat" << count++;
if (!mShaders)
{
mat = Ogre::MaterialManager::getSingleton().create(name.str(),
Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
Ogre::Technique* technique = mat->getTechnique(0);
technique->removeAllPasses();
if (displayCompositeMap)
{
Ogre::Pass* pass = technique->createPass();
pass->setVertexColourTracking(Ogre::TVC_AMBIENT|Ogre::TVC_DIFFUSE);
pass->createTextureUnitState(mCompositeMap)->setTextureAddressingMode(Ogre::TextureUnitState::TAM_CLAMP);
}
else
{
assert(mLayerList.size() == mBlendmapList.size()+1);
std::vector<Ogre::TexturePtr>::iterator blend = mBlendmapList.begin();
for (std::vector<std::string>::iterator layer = mLayerList.begin(); layer != mLayerList.end(); ++layer)
{
Ogre::Pass* pass = technique->createPass();
pass->setLightingEnabled(false);
pass->setVertexColourTracking(Ogre::TVC_NONE);
// TODO: How to handle fog?
pass->setFog(true, Ogre::FOG_NONE);
bool first = (layer == mLayerList.begin());
Ogre::TextureUnitState* tus;
if (!first)
{
pass->setSceneBlending(Ogre::SBT_TRANSPARENT_ALPHA);
pass->setDepthFunction(Ogre::CMPF_EQUAL);
tus = pass->createTextureUnitState((*blend)->getName());
tus->setAlphaOperation(Ogre::LBX_BLEND_TEXTURE_ALPHA,
Ogre::LBS_TEXTURE,
Ogre::LBS_TEXTURE);
tus->setColourOperationEx(Ogre::LBX_BLEND_DIFFUSE_ALPHA,
Ogre::LBS_TEXTURE,
Ogre::LBS_TEXTURE);
tus->setIsAlpha(true);
tus->setTextureAddressingMode(Ogre::TextureUnitState::TAM_CLAMP);
float scale = (16/(16.f+1.f));
tus->setTextureScale(1.f/scale,1.f/scale);
}
// Add the actual layer texture on top of the alpha map.
tus = pass->createTextureUnitState("textures\\" + *layer);
if (!first)
tus->setColourOperationEx(Ogre::LBX_BLEND_DIFFUSE_ALPHA,
Ogre::LBS_TEXTURE,
Ogre::LBS_CURRENT);
tus->setTextureScale(1/16.f,1/16.f);
if (!first)
++blend;
}
if (!renderCompositeMap)
{
Ogre::Pass* lightingPass = technique->createPass();
lightingPass->setSceneBlending(Ogre::SBT_MODULATE);
lightingPass->setVertexColourTracking(Ogre::TVC_AMBIENT|Ogre::TVC_DIFFUSE);
lightingPass->setFog(true, Ogre::FOG_NONE);
}
}
return mat;
}
else
{
sh::MaterialInstance* material = sh::Factory::getInstance().createMaterialInstance (name.str());
material->setProperty ("allow_fixed_function", sh::makeProperty<sh::BooleanValue>(new sh::BooleanValue(false)));
if (displayCompositeMap)
{
sh::MaterialInstancePass* p = material->createPass ();
p->setProperty ("vertex_program", sh::makeProperty<sh::StringValue>(new sh::StringValue("terrain_vertex")));
p->setProperty ("fragment_program", sh::makeProperty<sh::StringValue>(new sh::StringValue("terrain_fragment")));
p->mShaderProperties.setProperty ("is_first_pass", sh::makeProperty(new sh::BooleanValue(true)));
p->mShaderProperties.setProperty ("render_composite_map", sh::makeProperty(new sh::BooleanValue(false)));
p->mShaderProperties.setProperty ("display_composite_map", sh::makeProperty(new sh::BooleanValue(true)));
p->mShaderProperties.setProperty ("num_layers", sh::makeProperty (new sh::StringValue("0")));
p->mShaderProperties.setProperty ("num_blendmaps", sh::makeProperty (new sh::StringValue("0")));
sh::MaterialInstanceTextureUnit* tex = p->createTextureUnit ("compositeMap");
tex->setProperty ("direct_texture", sh::makeProperty (new sh::StringValue(mCompositeMap)));
tex->setProperty ("tex_address_mode", sh::makeProperty (new sh::StringValue("clamp")));
// shadow. TODO: repeated, put in function
if (mShadows)
{
for (Ogre::uint i = 0; i < (mSplitShadows ? 3 : 1); ++i)
{
sh::MaterialInstanceTextureUnit* shadowTex = p->createTextureUnit ("shadowMap" + Ogre::StringConverter::toString(i));
shadowTex->setProperty ("content_type", sh::makeProperty<sh::StringValue> (new sh::StringValue("shadow")));
}
}
p->mShaderProperties.setProperty ("shadowtexture_offset", sh::makeProperty (new sh::StringValue(
Ogre::StringConverter::toString(1))));
p->mShaderProperties.setProperty ("pass_index", sh::makeProperty(new sh::IntValue(0)));
}
else
{
bool shadows = mShadows && !renderCompositeMap;
int layerOffset = 0;
while (layerOffset < (int)mLayerList.size())
{
int blendmapOffset = (layerOffset == 0) ? 1 : 0; // the first layer of the first pass is the base layer and does not need a blend map
// Check how many layers we can fit in this pass
int numLayersInThisPass = 0;
int numBlendTextures = 0;
std::vector<std::string> blendTextures;
int remainingTextureUnits = OGRE_MAX_TEXTURE_LAYERS;
if (shadows)
remainingTextureUnits -= (mSplitShadows ? 3 : 1);
while (remainingTextureUnits && layerOffset + numLayersInThisPass < (int)mLayerList.size())
{
int layerIndex = numLayersInThisPass + layerOffset;
int neededTextureUnits=0;
int neededBlendTextures=0;
if (layerIndex != 0)
{
std::string blendTextureName = mBlendmapList[getBlendmapIndexForLayer(layerIndex)]->getName();
if (std::find(blendTextures.begin(), blendTextures.end(), blendTextureName) == blendTextures.end())
{
blendTextures.push_back(blendTextureName);
++neededBlendTextures;
++neededTextureUnits; // blend texture
}
}
++neededTextureUnits; // layer texture
if (neededTextureUnits <= remainingTextureUnits)
{
// We can fit another!
remainingTextureUnits -= neededTextureUnits;
numBlendTextures += neededBlendTextures;
++numLayersInThisPass;
}
else
break; // We're full
}
sh::MaterialInstancePass* p = material->createPass ();
p->setProperty ("vertex_program", sh::makeProperty<sh::StringValue>(new sh::StringValue("terrain_vertex")));
p->setProperty ("fragment_program", sh::makeProperty<sh::StringValue>(new sh::StringValue("terrain_fragment")));
if (layerOffset != 0)
{
p->setProperty ("scene_blend", sh::makeProperty(new sh::StringValue("alpha_blend")));
// Only write if depth is equal to the depth value written by the previous pass.
p->setProperty ("depth_func", sh::makeProperty(new sh::StringValue("equal")));
}
p->mShaderProperties.setProperty ("render_composite_map", sh::makeProperty(new sh::BooleanValue(renderCompositeMap)));
p->mShaderProperties.setProperty ("display_composite_map", sh::makeProperty(new sh::BooleanValue(displayCompositeMap)));
p->mShaderProperties.setProperty ("num_layers", sh::makeProperty (new sh::StringValue(Ogre::StringConverter::toString(numLayersInThisPass))));
p->mShaderProperties.setProperty ("num_blendmaps", sh::makeProperty (new sh::StringValue(Ogre::StringConverter::toString(numBlendTextures))));
// blend maps
// the index of the first blend map used in this pass
int blendmapStart;
if (mLayerList.size() == 1) // special case. if there's only one layer, we don't need blend maps at all
blendmapStart = 0;
else
blendmapStart = getBlendmapIndexForLayer(layerOffset+blendmapOffset);
for (int i = 0; i < numBlendTextures; ++i)
{
sh::MaterialInstanceTextureUnit* blendTex = p->createTextureUnit ("blendMap" + Ogre::StringConverter::toString(i));
blendTex->setProperty ("direct_texture", sh::makeProperty (new sh::StringValue(mBlendmapList[blendmapStart+i]->getName())));
blendTex->setProperty ("tex_address_mode", sh::makeProperty (new sh::StringValue("clamp")));
}
// layer maps
for (int i = 0; i < numLayersInThisPass; ++i)
{
sh::MaterialInstanceTextureUnit* diffuseTex = p->createTextureUnit ("diffuseMap" + Ogre::StringConverter::toString(i));
diffuseTex->setProperty ("direct_texture", sh::makeProperty (new sh::StringValue("textures\\"+mLayerList[layerOffset+i])));
if (i+layerOffset > 0)
{
int blendTextureIndex = getBlendmapIndexForLayer(layerOffset+i);
std::string blendTextureComponent = getBlendmapComponentForLayer(layerOffset+i);
p->mShaderProperties.setProperty ("blendmap_component_" + Ogre::StringConverter::toString(i),
sh::makeProperty (new sh::StringValue(Ogre::StringConverter::toString(blendTextureIndex-blendmapStart) + "." + blendTextureComponent)));
}
else
{
// just to make it shut up about blendmap_component_0 not existing in the first pass.
// it might be retrieved, but will never survive the preprocessing step.
p->mShaderProperties.setProperty ("blendmap_component_" + Ogre::StringConverter::toString(i),
sh::makeProperty (new sh::StringValue("")));
}
}
// shadow
if (shadows)
{
for (Ogre::uint i = 0; i < (mSplitShadows ? 3 : 1); ++i)
{
sh::MaterialInstanceTextureUnit* shadowTex = p->createTextureUnit ("shadowMap" + Ogre::StringConverter::toString(i));
shadowTex->setProperty ("content_type", sh::makeProperty<sh::StringValue> (new sh::StringValue("shadow")));
}
}
p->mShaderProperties.setProperty ("shadowtexture_offset", sh::makeProperty (new sh::StringValue(
Ogre::StringConverter::toString(numBlendTextures + numLayersInThisPass))));
// Make sure the pass index is fed to the permutation handler, because blendmap components may be different
p->mShaderProperties.setProperty ("pass_index", sh::makeProperty(new sh::IntValue(layerOffset)));
layerOffset += numLayersInThisPass;
}
}
}
return Ogre::MaterialManager::getSingleton().getByName(name.str());
}
}

View file

@ -0,0 +1,56 @@
#ifndef COMPONENTS_TERRAIN_MATERIAL_H
#define COMPONENTS_TERRAIN_MATERIAL_H
#include <OgreMaterial.h>
namespace Terrain
{
class MaterialGenerator
{
public:
/// @param layerList layer textures
/// @param blendmapList blend textures
/// @param shaders Whether to use shaders. With a shader, blendmap packing can be used (4 channels instead of one),
/// so if this parameter is true, then the supplied blend maps are expected to be packed.
MaterialGenerator (bool shaders);
void setLayerList (const std::vector<std::string>& layerList) { mLayerList = layerList; }
bool hasLayers() { return mLayerList.size(); }
void setBlendmapList (const std::vector<Ogre::TexturePtr>& blendmapList) { mBlendmapList = blendmapList; }
const std::vector<Ogre::TexturePtr>& getBlendmapList() { return mBlendmapList; }
void setCompositeMap (const std::string& name) { mCompositeMap = name; }
void enableShadows(bool shadows) { mShadows = shadows; }
void enableSplitShadows(bool splitShadows) { mSplitShadows = splitShadows; }
/// Creates a material suitable for displaying a chunk of terrain using alpha-blending.
/// @param mat Material that will be replaced by the generated material. May be empty as well, in which case
/// a new material is created.
Ogre::MaterialPtr generate (Ogre::MaterialPtr mat);
/// Creates a material suitable for displaying a chunk of terrain using a ready-made composite map.
/// @param mat Material that will be replaced by the generated material. May be empty as well, in which case
/// a new material is created.
Ogre::MaterialPtr generateForCompositeMap (Ogre::MaterialPtr mat);
/// Creates a material suitable for rendering composite maps, i.e. for "baking" several layer textures
/// into one. The main difference compared to a normal material is that no shading is applied at this point.
/// @param mat Material that will be replaced by the generated material. May be empty as well, in which case
/// a new material is created.
Ogre::MaterialPtr generateForCompositeMapRTT (Ogre::MaterialPtr mat);
private:
Ogre::MaterialPtr create (Ogre::MaterialPtr mat, bool renderCompositeMap, bool displayCompositeMap);
std::vector<std::string> mLayerList;
std::vector<Ogre::TexturePtr> mBlendmapList;
std::string mCompositeMap;
bool mShaders;
bool mShadows;
bool mSplitShadows;
};
}
#endif

View file

@ -0,0 +1,512 @@
#include "quadtreenode.hpp"
#include <OgreSceneManager.h>
#include <OgreManualObject.h>
#include "world.hpp"
#include "chunk.hpp"
#include "storage.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;
}
// Ogre::AxisAlignedBox::distance is broken in 1.8.
Ogre::Real distance(const Ogre::AxisAlignedBox& box, const Ogre::Vector3& v)
{
if (box.contains(v))
return 0;
else
{
Ogre::Vector3 maxDist(0,0,0);
const Ogre::Vector3& minimum = box.getMinimum();
const Ogre::Vector3& maximum = box.getMaximum();
if (v.x < minimum.x)
maxDist.x = minimum.x - v.x;
else if (v.x > maximum.x)
maxDist.x = v.x - maximum.x;
if (v.y < minimum.y)
maxDist.y = minimum.y - v.y;
else if (v.y > maximum.y)
maxDist.y = v.y - maximum.y;
if (v.z < minimum.z)
maxDist.z = minimum.z - v.z;
else if (v.z > maximum.z)
maxDist.z = v.z - maximum.z;
return maxDist.length();
}
}
// 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(World* terrain, ChildDirection dir, float size, const Ogre::Vector2 &center, QuadTreeNode* parent)
: mSize(size)
, mCenter(center)
, mParent(parent)
, mDirection(dir)
, mIsDummy(false)
, mSceneNode(NULL)
, mTerrain(terrain)
, mChunk(NULL)
, mMaterialGenerator(NULL)
, mBounds(Ogre::AxisAlignedBox::BOX_NULL)
, mWorldBounds(Ogre::AxisAlignedBox::BOX_NULL)
{
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;
mSceneNode->setPosition(Ogre::Vector3(pos.x*8192, pos.y*8192, 0));
mLodLevel = Log2(mSize);
mMaterialGenerator = new MaterialGenerator(mTerrain->getShadersEnabled());
}
void QuadTreeNode::createChild(ChildDirection id, float size, const Ogre::Vector2 &center)
{
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()
{
if (hasChildren())
{
for (int i=0; i<4; ++i)
{
mChildren[i]->initAabb();
mBounds.merge(mChildren[i]->getBoundingBox());
}
mBounds = Ogre::AxisAlignedBox (Ogre::Vector3(-mSize/2*8192, -mSize/2*8192, mBounds.getMinimum().z),
Ogre::Vector3(mSize/2*8192, mSize/2*8192, mBounds.getMaximum().z));
}
mWorldBounds = Ogre::AxisAlignedBox(mBounds.getMinimum() + Ogre::Vector3(mCenter.x*8192, mCenter.y*8192, 0),
mBounds.getMaximum() + Ogre::Vector3(mCenter.x*8192, mCenter.y*8192, 0));
}
void QuadTreeNode::setBoundingBox(const Ogre::AxisAlignedBox &box)
{
mBounds = box;
}
const Ogre::AxisAlignedBox& QuadTreeNode::getBoundingBox()
{
return mBounds;
}
void QuadTreeNode::update(const Ogre::Vector3 &cameraPos, Loading::Listener* loadingListener)
{
const Ogre::AxisAlignedBox& bounds = getBoundingBox();
if (bounds.isNull())
return;
float dist = distance(mWorldBounds, cameraPos);
bool distantLand = mTerrain->getDistantLandEnabled();
// Make sure our scene node is attached
if (!mSceneNode->isInSceneGraph())
{
mParent->getSceneNode()->addChild(mSceneNode);
}
/// \todo implement error metrics or some other means of not using arbitrary values
/// (general quality needs to be user configurable as well)
size_t wantedLod = 0;
if (dist > 8192*1)
wantedLod = 1;
if (dist > 8192*2)
wantedLod = 2;
if (dist > 8192*5)
wantedLod = 3;
if (dist > 8192*12)
wantedLod = 4;
if (dist > 8192*32)
wantedLod = 5;
if (dist > 8192*64)
wantedLod = 6;
bool hadChunk = hasChunk();
if (loadingListener)
loadingListener->indicateProgress();
if (!distantLand && dist > 8192*2)
{
if (mIsActive)
{
destroyChunks(true);
mIsActive = false;
}
return;
}
mIsActive = true;
if (mSize <= mTerrain->getMaxBatchSize() && mLodLevel <= wantedLod)
{
// Wanted LOD is small enough to render this node in one chunk
if (!mChunk)
{
mChunk = new Chunk(this, mLodLevel);
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()));
}
}
// 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);
mChunk->setVisible(true);
if (!hadChunk && hasChildren())
{
// Make sure child scene nodes are detached
mSceneNode->removeAllChildren();
// If distant land is enabled, keep the chunks around in case we need them again,
// otherwise, prefer low memory usage
if (!distantLand)
for (int i=0; i<4; ++i)
mChildren[i]->destroyChunks(true);
}
}
else
{
// Wanted LOD is too detailed to be rendered in one chunk,
// so split it up by delegating to child nodes
if (hadChunk)
{
// If distant land is enabled, keep the chunks around in case we need them again,
// otherwise, prefer low memory usage
if (!distantLand)
destroyChunks(false);
else if (mChunk)
mChunk->setVisible(false);
}
assert(hasChildren() && "Leaf node's LOD needs to be 0");
for (int i=0; i<4; ++i)
mChildren[i]->update(cameraPos, loadingListener);
}
}
void QuadTreeNode::destroyChunks(bool children)
{
if (mChunk)
{
Ogre::MaterialManager::getSingleton().remove(mChunk->getMaterial()->getName());
mSceneNode->detachObject(mChunk);
delete mChunk;
mChunk = NULL;
// destroy blendmaps
if (mMaterialGenerator)
{
const std::vector<Ogre::TexturePtr>& list = mMaterialGenerator->getBlendmapList();
for (std::vector<Ogre::TexturePtr>::const_iterator it = list.begin(); it != list.end(); ++it)
Ogre::TextureManager::getSingleton().remove((*it)->getName());
mMaterialGenerator->setBlendmapList(std::vector<Ogre::TexturePtr>());
mMaterialGenerator->setLayerList(std::vector<std::string>());
mMaterialGenerator->setCompositeMap("");
}
if (!mCompositeMap.isNull())
{
Ogre::TextureManager::getSingleton().remove(mCompositeMap->getName());
mCompositeMap.setNull();
}
}
else if (children && hasChildren())
for (int i=0; i<4; ++i)
mChildren[i]->destroyChunks(true);
}
void QuadTreeNode::updateIndexBuffers()
{
if (hasChunk())
mChunk->updateIndexBuffer();
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::ensureLayerInfo()
{
if (mMaterialGenerator->hasLayers())
return;
std::vector<Ogre::TexturePtr> blendmaps;
std::vector<std::string> layerList;
mTerrain->getStorage()->getBlendmaps(mSize, mCenter, mTerrain->getShadersEnabled(), blendmaps, layerList);
mMaterialGenerator->setLayerList(layerList);
mMaterialGenerator->setBlendmapList(blendmaps);
}
void QuadTreeNode::prepareForCompositeMap(Ogre::TRect<float> area)
{
Ogre::SceneManager* sceneMgr = mTerrain->getCompositeMapSceneManager();
if (mIsDummy)
{
// TODO - why is this completely black?
// TODO - store this default material somewhere instead of creating one for each empty cell
MaterialGenerator matGen(mTerrain->getShadersEnabled());
std::vector<std::string> layer;
layer.push_back("_land_default.dds");
matGen.setLayerList(layer);
makeQuad(sceneMgr, area.left, area.top, area.right, area.bottom, matGen.generateForCompositeMapRTT(Ogre::MaterialPtr()));
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
{
ensureLayerInfo();
Ogre::MaterialPtr material = mMaterialGenerator->generateForCompositeMapRTT(Ogre::MaterialPtr());
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(Ogre::MaterialPtr()));
else
mChunk->setMaterial(mMaterialGenerator->generateForCompositeMap(Ogre::MaterialPtr()));
}
if (hasChildren())
for (int i=0; i<4; ++i)
mChildren[i]->applyMaterials();
}
void QuadTreeNode::setVisible(bool visible)
{
if (!visible && mChunk)
mChunk->setVisible(false);
if (hasChildren())
for (int i=0; i<4; ++i)
mChildren[i]->setVisible(visible);
}

View file

@ -0,0 +1,161 @@
#ifndef COMPONENTS_TERRAIN_QUADTREENODE_H
#define COMPONENTS_TERRAIN_QUADTREENODE_H
#include <OgreAxisAlignedBox.h>
#include <OgreVector2.h>
#include <OgreTexture.h>
#include <components/loadinglistener/loadinglistener.hpp>
namespace Ogre
{
class Rectangle2D;
}
namespace Terrain
{
class World;
class Chunk;
class MaterialGenerator;
enum Direction
{
North = 0,
East = 1,
South = 2,
West = 3
};
enum ChildDirection
{
NW = 0,
NE = 1,
SW = 2,
SE = 3,
Root
};
/**
* @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 (World* terrain, ChildDirection dir, float size, const Ogre::Vector2& center, QuadTreeNode* parent);
~QuadTreeNode();
void setVisible(bool visible);
/// 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 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();
World* getTerrain() { return mTerrain; }
/// Adjust LODs for the given camera position, possibly splitting up chunks or merging them.
void update (const Ogre::Vector3& cameraPos, Loading::Listener* loadingListener);
/// 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 ESM::Land::LAND_SIZE^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.
/// @param area area in image space to put the quad
/// @param quads collect quads here so they can be deleted later
void prepareForCompositeMap(Ogre::TRect<float> area);
private:
// Stored here for convenience in case we need layer list again
MaterialGenerator* mMaterialGenerator;
/// Is this node (or any of its child nodes) currently configured to render itself?
/// (only relevant when distant land is disabled, otherwise whole terrain is always rendered)
bool mIsActive;
bool mIsDummy;
float mSize;
size_t mLodLevel; // LOD if we were to render this node in one chunk
Ogre::AxisAlignedBox mBounds;
Ogre::AxisAlignedBox mWorldBounds;
ChildDirection mDirection;
Ogre::Vector2 mCenter;
Ogre::SceneNode* mSceneNode;
QuadTreeNode* mParent;
QuadTreeNode* mChildren[4];
QuadTreeNode* mNeighbours[4];
Chunk* mChunk;
World* mTerrain;
Ogre::TexturePtr mCompositeMap;
void ensureLayerInfo();
void ensureCompositeMap();
};
}
#endif

View file

@ -0,0 +1,470 @@
#include "storage.hpp"
#include <OgreVector2.h>
#include <OgreTextureManager.h>
#include <OgreStringConverter.h>
#include <OgreRenderSystem.h>
#include <OgreRoot.h>
#include <boost/multi_array.hpp>
namespace Terrain
{
struct VertexElement
{
Ogre::Vector3 pos;
Ogre::Vector3 normal;
Ogre::ColourValue colour;
};
bool Storage::getMinMaxHeights(float size, const Ogre::Vector2 &center, float &min, float &max)
{
assert (size <= 1 && "Storage::getMinMaxHeights, chunk size should be <= 1 cell");
/// \todo investigate if min/max heights should be stored at load time in ESM::Land instead
Ogre::Vector2 origin = center - Ogre::Vector2(size/2.f, size/2.f);
assert(origin.x == (int) origin.x);
assert(origin.y == (int) origin.y);
int cellX = origin.x;
int cellY = origin.y;
const ESM::Land* land = getLand(cellX, cellY);
if (!land)
return false;
min = std::numeric_limits<float>().max();
max = -std::numeric_limits<float>().max();
for (int row=0; row<ESM::Land::LAND_SIZE; ++row)
{
for (int col=0; col<ESM::Land::LAND_SIZE; ++col)
{
float h = land->mLandData->mHeights[col*ESM::Land::LAND_SIZE+row];
if (h > max)
max = h;
if (h < min)
min = h;
}
}
return true;
}
void Storage::fixNormal (Ogre::Vector3& normal, int cellX, int cellY, int col, int row)
{
while (col >= ESM::Land::LAND_SIZE-1)
{
++cellY;
col -= ESM::Land::LAND_SIZE-1;
}
while (row >= ESM::Land::LAND_SIZE-1)
{
++cellX;
row -= ESM::Land::LAND_SIZE-1;
}
while (col < 0)
{
--cellY;
col += ESM::Land::LAND_SIZE-1;
}
while (row < 0)
{
--cellX;
row += ESM::Land::LAND_SIZE-1;
}
ESM::Land* land = getLand(cellX, cellY);
if (land && land->mHasData)
{
normal.x = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3];
normal.y = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+1];
normal.z = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+2];
normal.normalise();
}
else
normal = Ogre::Vector3(0,0,1);
}
void Storage::averageNormal(Ogre::Vector3 &normal, int cellX, int cellY, int col, int row)
{
Ogre::Vector3 n1,n2,n3,n4;
fixNormal(n1, cellX, cellY, col+1, row);
fixNormal(n2, cellX, cellY, col-1, row);
fixNormal(n3, cellX, cellY, col, row+1);
fixNormal(n4, cellX, cellY, col, row-1);
normal = (n1+n2+n3+n4);
normal.normalise();
}
void Storage::fixColour (Ogre::ColourValue& color, int cellX, int cellY, int col, int row)
{
if (col == ESM::Land::LAND_SIZE-1)
{
++cellY;
col = 0;
}
if (row == ESM::Land::LAND_SIZE-1)
{
++cellX;
row = 0;
}
ESM::Land* land = getLand(cellX, cellY);
if (land && land->mLandData->mUsingColours)
{
color.r = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3] / 255.f;
color.g = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3+1] / 255.f;
color.b = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3+2] / 255.f;
}
else
{
color.r = 1;
color.g = 1;
color.b = 1;
}
}
void Storage::fillVertexBuffers (int lodLevel, float size, const Ogre::Vector2& center,
Ogre::HardwareVertexBufferSharedPtr vertexBuffer,
Ogre::HardwareVertexBufferSharedPtr normalBuffer,
Ogre::HardwareVertexBufferSharedPtr colourBuffer)
{
// LOD level n means every 2^n-th vertex is kept
size_t increment = 1 << lodLevel;
Ogre::Vector2 origin = center - Ogre::Vector2(size/2.f, size/2.f);
assert(origin.x == (int) origin.x);
assert(origin.y == (int) origin.y);
int startX = origin.x;
int startY = origin.y;
size_t numVerts = size*(ESM::Land::LAND_SIZE-1)/increment + 1;
std::vector<uint8_t> colors;
colors.resize(numVerts*numVerts*4);
std::vector<float> positions;
positions.resize(numVerts*numVerts*3);
std::vector<float> normals;
normals.resize(numVerts*numVerts*3);
Ogre::Vector3 normal;
Ogre::ColourValue color;
float vertY;
float vertX;
float vertY_ = 0; // of current cell corner
for (int cellY = startY; cellY < startY + std::ceil(size); ++cellY)
{
float vertX_ = 0; // of current cell corner
for (int cellX = startX; cellX < startX + std::ceil(size); ++cellX)
{
ESM::Land* land = getLand(cellX, cellY);
if (land && !land->mHasData)
land = NULL;
bool hasColors = land && land->mLandData->mUsingColours;
int rowStart = 0;
int colStart = 0;
// Skip the first row / column unless we're at a chunk edge,
// since this row / column is already contained in a previous cell
if (colStart == 0 && vertY_ != 0)
colStart += increment;
if (rowStart == 0 && vertX_ != 0)
rowStart += increment;
vertY = vertY_;
for (int col=colStart; col<ESM::Land::LAND_SIZE; col += increment)
{
vertX = vertX_;
for (int row=rowStart; row<ESM::Land::LAND_SIZE; row += increment)
{
positions[vertX*numVerts*3 + vertY*3] = ((vertX/float(numVerts-1)-0.5) * size * 8192);
positions[vertX*numVerts*3 + vertY*3 + 1] = ((vertY/float(numVerts-1)-0.5) * size * 8192);
if (land)
positions[vertX*numVerts*3 + vertY*3 + 2] = land->mLandData->mHeights[col*ESM::Land::LAND_SIZE+row];
else
positions[vertX*numVerts*3 + vertY*3 + 2] = -2048;
if (land)
{
normal.x = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3];
normal.y = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+1];
normal.z = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+2];
normal.normalise();
}
else
normal = Ogre::Vector3(0,0,1);
// Normals apparently don't connect seamlessly between cells
if (col == ESM::Land::LAND_SIZE-1 || row == ESM::Land::LAND_SIZE-1)
fixNormal(normal, cellX, cellY, col, row);
// some corner normals appear to be complete garbage (z < 0)
if ((row == 0 || row == ESM::Land::LAND_SIZE-1) && (col == 0 || col == ESM::Land::LAND_SIZE-1))
averageNormal(normal, cellX, cellY, col, row);
assert(normal.z > 0);
normals[vertX*numVerts*3 + vertY*3] = normal.x;
normals[vertX*numVerts*3 + vertY*3 + 1] = normal.y;
normals[vertX*numVerts*3 + vertY*3 + 2] = normal.z;
if (hasColors)
{
color.r = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3] / 255.f;
color.g = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3+1] / 255.f;
color.b = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3+2] / 255.f;
}
else
{
color.r = 1;
color.g = 1;
color.b = 1;
}
// Unlike normals, colors mostly connect seamlessly between cells, but not always...
if (col == ESM::Land::LAND_SIZE-1 || row == ESM::Land::LAND_SIZE-1)
fixColour(color, cellX, cellY, col, row);
color.a = 1;
Ogre::uint32 rsColor;
Ogre::Root::getSingleton().getRenderSystem()->convertColourValue(color, &rsColor);
memcpy(&colors[vertX*numVerts*4 + vertY*4], &rsColor, sizeof(Ogre::uint32));
++vertX;
}
++vertY;
}
vertX_ = vertX;
}
vertY_ = vertY;
assert(vertX_ == 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);
}
Storage::UniqueTextureId Storage::getVtexIndexAt(int cellX, int cellY,
int x, int y)
{
// For the first/last row/column, we need to get the texture from the neighbour cell
// to get consistent blending at the borders
--x;
if (x < 0)
{
--cellX;
x += ESM::Land::LAND_TEXTURE_SIZE;
}
if (y >= ESM::Land::LAND_TEXTURE_SIZE) // Y appears to be wrapped from the other side because why the hell not?
{
++cellY;
y -= ESM::Land::LAND_TEXTURE_SIZE;
}
assert(x<ESM::Land::LAND_TEXTURE_SIZE);
assert(y<ESM::Land::LAND_TEXTURE_SIZE);
ESM::Land* land = getLand(cellX, cellY);
if (land)
{
if (!land->isDataLoaded(ESM::Land::DATA_VTEX))
land->loadData(ESM::Land::DATA_VTEX);
int tex = land->mLandData->mTextures[y * ESM::Land::LAND_TEXTURE_SIZE + x];
if (tex == 0)
return std::make_pair(0,0); // vtex 0 is always the base texture, regardless of plugin
return std::make_pair(tex, land->mPlugin);
}
else
return std::make_pair(0,0);
}
std::string Storage::getTextureName(UniqueTextureId id)
{
if (id.first == 0)
return "_land_default.dds"; // Not sure if the default texture floatly is hardcoded?
// NB: All vtex ids are +1 compared to the ltex ids
const ESM::LandTexture* ltex = getLandTexture(id.first-1, id.second);
std::string texture = ltex->mTexture;
//TODO this is needed due to MWs messed up texture handling
texture = texture.substr(0, texture.rfind(".")) + ".dds";
return texture;
}
void Storage::getBlendmaps(float chunkSize, const Ogre::Vector2 &chunkCenter,
bool pack, std::vector<Ogre::TexturePtr> &blendmaps, std::vector<std::string> &layerList)
{
// TODO - blending isn't completely right yet; the blending radius appears to be
// different at a cell transition (2 vertices, not 4), so we may need to create a larger blendmap
// and interpolate the rest of the cell by hand? :/
Ogre::Vector2 origin = chunkCenter - Ogre::Vector2(chunkSize/2.f, chunkSize/2.f);
int cellX = origin.x;
int cellY = origin.y;
// Save the used texture indices so we know the total number of textures
// and number of required blend maps
std::set<UniqueTextureId> textureIndices;
// Due to the way the blending works, the base layer will always shine through in between
// blend transitions (eg halfway between two texels, both blend values will be 0.5, so 25% of base layer visible).
// To get a consistent look, we need to make sure to use the same base layer in all cells.
// So we're always adding _land_default.dds as the base layer here, even if it's not referenced in this cell.
textureIndices.insert(std::make_pair(0,0));
for (int y=0; y<ESM::Land::LAND_TEXTURE_SIZE+1; ++y)
for (int x=0; x<ESM::Land::LAND_TEXTURE_SIZE+1; ++x)
{
UniqueTextureId id = getVtexIndexAt(cellX, cellY, x, y);
textureIndices.insert(id);
}
// Makes sure the indices are sorted, or rather,
// retrieved as sorted. This is important to keep the splatting order
// consistent across cells.
std::map<UniqueTextureId, int> textureIndicesMap;
for (std::set<UniqueTextureId>::iterator it = textureIndices.begin(); it != textureIndices.end(); ++it)
{
int size = textureIndicesMap.size();
textureIndicesMap[*it] = size;
layerList.push_back(getTextureName(*it));
}
int numTextures = textureIndices.size();
// numTextures-1 since the base layer doesn't need blending
int numBlendmaps = pack ? std::ceil((numTextures-1) / 4.f) : (numTextures-1);
int channels = pack ? 4 : 1;
// Second iteration - create and fill in the blend maps
const int blendmapSize = ESM::Land::LAND_TEXTURE_SIZE+1;
std::vector<Ogre::uchar> data;
data.resize(blendmapSize * blendmapSize * channels, 0);
for (int i=0; i<numBlendmaps; ++i)
{
Ogre::PixelFormat format = pack ? Ogre::PF_A8B8G8R8 : Ogre::PF_A8;
static int count=0;
Ogre::TexturePtr map = Ogre::TextureManager::getSingleton().createManual("terrain/blend/"
+ Ogre::StringConverter::toString(count++), Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME,
Ogre::TEX_TYPE_2D, blendmapSize, blendmapSize, 0, format);
for (int y=0; y<blendmapSize; ++y)
{
for (int x=0; x<blendmapSize; ++x)
{
UniqueTextureId id = getVtexIndexAt(cellX, cellY, x, y);
int layerIndex = textureIndicesMap.find(id)->second;
int blendIndex = (pack ? std::floor((layerIndex-1)/4.f) : layerIndex-1);
int channel = pack ? std::max(0, (layerIndex-1) % 4) : 0;
if (blendIndex == i)
data[y*blendmapSize*channels + x*channels + channel] = 255;
else
data[y*blendmapSize*channels + x*channels + channel] = 0;
}
}
// All done, upload to GPU
Ogre::DataStreamPtr stream(new Ogre::MemoryDataStream(&data[0], data.size()));
map->loadRawData(stream, blendmapSize, blendmapSize, format);
blendmaps.push_back(map);
}
}
float Storage::getHeightAt(const Ogre::Vector3 &worldPos)
{
int cellX = std::floor(worldPos.x / 8192.f);
int cellY = std::floor(worldPos.y / 8192.f);
ESM::Land* land = getLand(cellX, cellY);
if (!land)
return -2048;
// Mostly lifted from Ogre::Terrain::getHeightAtTerrainPosition
// Normalized position in the cell
float nX = (worldPos.x - (cellX * 8192))/8192.f;
float nY = (worldPos.y - (cellY * 8192))/8192.f;
// get left / bottom points (rounded down)
float factor = ESM::Land::LAND_SIZE - 1.0f;
float invFactor = 1.0f / factor;
int startX = static_cast<int>(nX * factor);
int startY = static_cast<int>(nY * factor);
int endX = startX + 1;
int endY = startY + 1;
assert(endX < ESM::Land::LAND_SIZE);
assert(endY < ESM::Land::LAND_SIZE);
// now get points in terrain space (effectively rounding them to boundaries)
float startXTS = startX * invFactor;
float startYTS = startY * invFactor;
float endXTS = endX * invFactor;
float endYTS = endY * invFactor;
// get parametric from start coord to next point
float xParam = (nX - startXTS) * factor;
float yParam = (nY - startYTS) * factor;
/* For even / odd tri strip rows, triangles are this shape:
even odd
3---2 3---2
| / | | \ |
0---1 0---1
*/
// Build all 4 positions in normalized cell space, using point-sampled height
Ogre::Vector3 v0 (startXTS, startYTS, getVertexHeight(land, startX, startY) / 8192.f);
Ogre::Vector3 v1 (endXTS, startYTS, getVertexHeight(land, endX, startY) / 8192.f);
Ogre::Vector3 v2 (endXTS, endYTS, getVertexHeight(land, endX, endY) / 8192.f);
Ogre::Vector3 v3 (startXTS, endYTS, getVertexHeight(land, startX, endY) / 8192.f);
// define this plane in terrain space
Ogre::Plane plane;
// (At the moment, all rows have the same triangle alignment)
if (true)
{
// odd row
bool secondTri = ((1.0 - yParam) > xParam);
if (secondTri)
plane.redefine(v0, v1, v3);
else
plane.redefine(v1, v2, v3);
}
else
{
// even row
bool secondTri = (yParam > xParam);
if (secondTri)
plane.redefine(v0, v2, v3);
else
plane.redefine(v0, v1, v2);
}
// Solve plane equation for z
return (-plane.normal.x * nX
-plane.normal.y * nY
- plane.d) / plane.normal.z * 8192;
}
float Storage::getVertexHeight(const ESM::Land *land, int x, int y)
{
assert(x < ESM::Land::LAND_SIZE);
assert(y < ESM::Land::LAND_SIZE);
return land->mLandData->mHeights[y * ESM::Land::LAND_SIZE + x];
}
}

View file

@ -0,0 +1,84 @@
#ifndef COMPONENTS_TERRAIN_STORAGE_H
#define COMPONENTS_TERRAIN_STORAGE_H
#include <components/esm/loadland.hpp>
#include <components/esm/loadltex.hpp>
#include <OgreAxisAlignedBox.h>
#include <OgreHardwareVertexBuffer.h>
namespace Terrain
{
/// We keep storage of terrain data abstract here since we need different implementations for game and editor
class Storage
{
public:
virtual ~Storage() {}
private:
virtual ESM::Land* getLand (int cellX, int cellY) = 0;
virtual const ESM::LandTexture* getLandTexture(int index, short plugin) = 0;
public:
/// Get bounds of the whole terrain in cell units
virtual Ogre::AxisAlignedBox getBounds() = 0;
/// Get the minimum and maximum heights of a terrain chunk.
/// @note Should only be called for chunks <= 1 cell, i.e. leafs of the quad tree.
/// Larger chunks can simply merge AABB of children.
/// @param size size of the chunk in cell units
/// @param center center of the chunk in cell units
/// @param min min height will be stored here
/// @param max max height will be stored here
/// @return true if there was data available for this terrain chunk
bool getMinMaxHeights (float size, const Ogre::Vector2& center, float& min, float& max);
/// Fill vertex buffers for a terrain chunk.
/// @param lodLevel LOD level, 0 = most detailed
/// @param size size of the terrain chunk in cell units
/// @param center center of the chunk in cell units
/// @param vertexBuffer buffer to write vertices
/// @param normalBuffer buffer to write vertex normals
/// @param colourBuffer buffer to write vertex colours
void fillVertexBuffers (int lodLevel, float size, const Ogre::Vector2& center,
Ogre::HardwareVertexBufferSharedPtr vertexBuffer,
Ogre::HardwareVertexBufferSharedPtr normalBuffer,
Ogre::HardwareVertexBufferSharedPtr colourBuffer);
/// Create textures holding layer blend values for a terrain chunk.
/// @note The terrain chunk shouldn't be larger than one cell since otherwise we might
/// have to do a ridiculous amount of different layers. For larger chunks, composite maps should be used.
/// @param chunkSize size of the terrain chunk in cell units
/// @param chunkCenter center of the chunk in cell units
/// @param pack Whether to pack blend values for up to 4 layers into one texture (one in each channel) -
/// otherwise, each texture contains blend values for one layer only. Shader-based rendering
/// can utilize packing, FFP can't.
/// @param blendmaps created blendmaps will be written here
/// @param layerList names of the layer textures used will be written here
void getBlendmaps (float chunkSize, const Ogre::Vector2& chunkCenter, bool pack,
std::vector<Ogre::TexturePtr>& blendmaps,
std::vector<std::string>& layerList);
float getHeightAt (const Ogre::Vector3& worldPos);
private:
void fixNormal (Ogre::Vector3& normal, int cellX, int cellY, int col, int row);
void fixColour (Ogre::ColourValue& colour, int cellX, int cellY, int col, int row);
void averageNormal (Ogre::Vector3& normal, int cellX, int cellY, int col, int row);
float getVertexHeight (const ESM::Land* land, int x, int y);
// Since plugins can define new texture palettes, we need to know the plugin index too
// in order to retrieve the correct texture name.
// pair <texture id, plugin id>
typedef std::pair<short, short> UniqueTextureId;
UniqueTextureId getVtexIndexAt(int cellX, int cellY,
int x, int y);
std::string getTextureName (UniqueTextureId id);
};
}
#endif

View file

@ -0,0 +1,410 @@
#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;
}
}

View file

@ -0,0 +1,154 @@
#ifndef COMPONENTS_TERRAIN_H
#define COMPONENTS_TERRAIN_H
#include <OgreHardwareIndexBuffer.h>
#include <OgreHardwareVertexBuffer.h>
#include <OgreAxisAlignedBox.h>
#include <OgreTexture.h>
namespace Loading
{
class Listener;
}
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 World
{
public:
/// @note takes ownership of \a storage
/// @param loadingListener Listener to update with progress
/// @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 distantLand Whether to draw all of the terrain, or only a 3x3 grid around the camera.
/// This is a temporary option until it can be streamlined.
/// @param shaders Whether to use splatting shader, or multi-pass fixed function splatting. Shader is usually
/// faster so this is just here for compatibility.
World(Loading::Listener* loadingListener, Ogre::SceneManager* sceneMgr,
Storage* storage, int visiblityFlags, bool distantLand, bool shaders);
~World();
void setLoadingListener(Loading::Listener* loadingListener) { mLoadingListener = loadingListener; }
bool getDistantLandEnabled() { return mDistantLand; }
bool getShadersEnabled() { return mShaders; }
bool getShadowsEnabled() { return mShadows; }
bool getSplitShadowsEnabled() { return mSplitShadows; }
float getHeightAt (const Ogre::Vector3& worldPos);
/// 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.
void update (const Ogre::Vector3& cameraPos);
/// Get the world bounding box of a chunk of terrain centered at \a center
Ogre::AxisAlignedBox getWorldBoundingBox (const Ogre::Vector2& center);
Ogre::SceneManager* getSceneManager() { return mSceneMgr; }
Ogre::SceneNode* getRootSceneNode() { return mRootSceneNode; }
Storage* getStorage() { return mStorage; }
/// 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
void setVisible(bool visible);
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.)
void applyMaterials(bool shadows, bool splitShadows);
int getVisiblityFlags() { return mVisibilityFlags; }
int getMaxBatchSize() { return mMaxBatchSize; }
void enableSplattingShader(bool enabled);
private:
bool mDistantLand;
bool mShaders;
bool mShadows;
bool mSplitShadows;
bool mVisible;
Loading::Listener* mLoadingListener;
QuadTreeNode* mRootNode;
Ogre::SceneNode* mRootSceneNode;
Storage* mStorage;
int mVisibilityFlags;
Ogre::SceneManager* mSceneMgr;
Ogre::SceneManager* mCompositeMapSceneMgr;
/// Bounds in cell units
Ogre::AxisAlignedBox mBounds;
/// 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);
public:
// ----INTERNAL----
enum IndexBufferFlags
{
IBF_North = 1 << 0,
IBF_East = 1 << 1,
IBF_South = 1 << 2,
IBF_West = 1 << 3
};
/// @param flags first 4*4 bits are LOD deltas on each edge, respectively (4 bits each)
/// next 4 bits are LOD level of the index buffer (LOD 0 = don't omit any vertices)
/// @param numIndices number of indices that were used will be written here
Ogre::HardwareIndexBufferSharedPtr getIndexBuffer (int flags, size_t& numIndices);
Ogre::HardwareVertexBufferSharedPtr getVertexBuffer (int numVertsOneSide);
Ogre::SceneManager* getCompositeMapSceneManager() { return mCompositeMapSceneMgr; }
// Delete all quads
void clearCompositeMapSceneManager();
void renderCompositeMap (Ogre::TexturePtr target);
private:
// 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.
std::map<int, Ogre::HardwareIndexBufferSharedPtr> mIndexBufferMap;
std::map<int, Ogre::HardwareVertexBufferSharedPtr> mUvBufferMap;
Ogre::RenderTarget* mCompositeMapRenderTarget;
Ogre::TexturePtr mCompositeMapRenderTexture;
};
}
#endif