Enabled terrain self shadows, implemented getHeightAt, some optimizations

actorid
scrawl 12 years ago
parent d727b15580
commit ebf9debb80

@ -69,8 +69,8 @@ void OMW::Engine::setAnimationVerbose(bool animverbose)
bool OMW::Engine::frameStarted (const Ogre::FrameEvent& evt)
{
if (!MWBase::Environment::get().getWindowManager()->isGuiMode())
MWBase::Environment::get().getWorld()->frameStarted(evt.timeSinceLastFrame);
bool paused = MWBase::Environment::get().getWindowManager()->isGuiMode();
MWBase::Environment::get().getWorld()->frameStarted(evt.timeSinceLastFrame, paused);
MWBase::Environment::get().getWindowManager ()->frameStarted(evt.timeSinceLastFrame);
return true;
}
@ -91,12 +91,12 @@ bool OMW::Engine::frameRenderingQueued (const Ogre::FrameEvent& evt)
MWBase::Environment::get().getSoundManager()->update(frametime);
// global scripts
//MWBase::Environment::get().getScriptManager()->getGlobalScripts().run();
MWBase::Environment::get().getScriptManager()->getGlobalScripts().run();
bool changed = MWBase::Environment::get().getWorld()->hasCellChanged();
// local scripts
//executeLocalScripts(); // This does not handle the case where a global script causes a cell
executeLocalScripts(); // This does not handle the case where a global script causes a cell
// change, followed by a cell change in a local script during the same
// frame.

@ -371,7 +371,7 @@ namespace MWBase
/// \todo this does not belong here
virtual void playVideo(const std::string& name, bool allowSkipping) = 0;
virtual void stopVideo() = 0;
virtual void frameStarted (float dt) = 0;
virtual void frameStarted (float dt, bool paused) = 0;
/// Find default position inside exterior cell specified by name
/// \return false if exterior with given name not exists, true otherwise

@ -125,7 +125,7 @@ namespace MWGui
getWidget(mActorShadows, "ActorShadows");
getWidget(mStaticsShadows, "StaticsShadows");
getWidget(mMiscShadows, "MiscShadows");
getWidget(mShadowsDebug, "ShadowsDebug");
getWidget(mTerrainShadows, "TerrainShadows");
getWidget(mControlsBox, "ControlsBox");
getWidget(mResetControlsButton, "ResetControlsButton");
getWidget(mInvertYButton, "InvertYButton");
@ -161,7 +161,7 @@ namespace MWGui
mActorShadows->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled);
mStaticsShadows->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled);
mMiscShadows->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled);
mShadowsDebug->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled);
mTerrainShadows->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled);
mMasterVolumeSlider->eventScrollChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onSliderChangePosition);
mVoiceVolumeSlider->eventScrollChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onSliderChangePosition);
@ -238,7 +238,7 @@ namespace MWGui
mActorShadows->setCaptionWithReplacing(Settings::Manager::getBool("actor shadows", "Shadows") ? "#{sOn}" : "#{sOff}");
mStaticsShadows->setCaptionWithReplacing(Settings::Manager::getBool("statics shadows", "Shadows") ? "#{sOn}" : "#{sOff}");
mMiscShadows->setCaptionWithReplacing(Settings::Manager::getBool("misc shadows", "Shadows") ? "#{sOn}" : "#{sOff}");
mShadowsDebug->setCaptionWithReplacing(Settings::Manager::getBool("debug", "Shadows") ? "#{sOn}" : "#{sOff}");
mTerrainShadows->setCaptionWithReplacing(Settings::Manager::getBool("terrain shadows", "Shadows") ? "#{sOn}" : "#{sOff}");
float cameraSens = (Settings::Manager::getFloat("camera sensitivity", "Input")-0.2)/(5.0-0.2);
mCameraSensitivitySlider->setScrollPosition (cameraSens * (mCameraSensitivitySlider->getScrollRange()-1));
@ -394,8 +394,8 @@ namespace MWGui
Settings::Manager::setBool("statics shadows", "Shadows", newState);
else if (_sender == mMiscShadows)
Settings::Manager::setBool("misc shadows", "Shadows", newState);
else if (_sender == mShadowsDebug)
Settings::Manager::setBool("debug", "Shadows", newState);
else if (_sender == mTerrainShadows)
Settings::Manager::setBool("terrain shadows", "Shadows", newState);
else if (_sender == mInvertYButton)
Settings::Manager::setBool("invert y axis", "Input", newState);
else if (_sender == mCrosshairButton)

@ -59,7 +59,7 @@ namespace MWGui
MyGUI::Button* mActorShadows;
MyGUI::Button* mStaticsShadows;
MyGUI::Button* mMiscShadows;
MyGUI::Button* mShadowsDebug;
MyGUI::Button* mTerrainShadows;
// audio
MyGUI::ScrollBar* mMasterVolumeSlider;

@ -82,7 +82,7 @@ RenderingManager::RenderingManager(OEngine::Render::OgreRenderer& _rend, const b
Settings::Manager::setString("shader mode", "General", openGL ? (glES ? "glsles" : "glsl") : "hlsl");
}
mRendering.createScene("PlayerCam", Settings::Manager::getFloat("field of view", "General"), 50);
mRendering.createScene("PlayerCam", Settings::Manager::getFloat("field of view", "General"), 5);
mRendering.getWindow()->addListener(this);
mRendering.setWindowListener(this);
@ -248,8 +248,10 @@ void RenderingManager::cellAdded (MWWorld::Ptr::CellStore *store)
{
if (!mTerrain)
{
mTerrain = new Terrain::Terrain(mRendering.getScene(), new MWRender::TerrainStorage(), RV_Terrain);
mTerrain->update(mRendering.getCamera());
mTerrain = new Terrain::Terrain(mRendering.getScene(), new MWRender::TerrainStorage(), RV_Terrain,
Settings::Manager::getBool("distant land", "Terrain"),
Settings::Manager::getBool("shader", "Terrain"));
mTerrain->update(mRendering.getCamera()->getRealPosition());
}
}
waterAdded(store);
@ -554,13 +556,6 @@ void RenderingManager::setAmbientMode()
}
}
float RenderingManager::getTerrainHeightAt(Ogre::Vector3 worldPos)
{
assert(mTerrain);
return mTerrain->getHeightAt(worldPos);
}
void RenderingManager::configureAmbient(MWWorld::Ptr::CellStore &mCell)
{
mAmbientColor.setAsABGR (mCell.mCell->mAmbi.mAmbient);
@ -658,9 +653,14 @@ void RenderingManager::requestMap(MWWorld::Ptr::CellStore* cell)
{
if (cell->mCell->isExterior())
{
assert(mTerrain);
Ogre::AxisAlignedBox dims = mObjects.getDimensions(cell);
Ogre::Vector2 center(cell->mCell->getGridX() + 0.5, -cell->mCell->getGridY() + 1 - 0.5);
Ogre::Vector2 center(cell->mCell->getGridX() + 0.5, cell->mCell->getGridY() + 0.5);
dims.merge(mTerrain->getWorldBoundingBox(center));
mTerrain->update(dims.getCenter());
mLocalMap->requestMap(cell, dims.getMinimum().z, dims.getMaximum().z);
}
else
@ -992,12 +992,13 @@ void RenderingManager::updateWaterRippleEmitterPtr (const MWWorld::Ptr& old, con
mWater->updateEmitterPtr(old, ptr);
}
void RenderingManager::frameStarted(float dt)
void RenderingManager::frameStarted(float dt, bool paused)
{
if (mTerrain)
mTerrain->update(mRendering.getCamera());
mTerrain->update(mRendering.getCamera()->getRealPosition());
mWater->frameStarted(dt);
if (!paused)
mWater->frameStarted(dt);
}
void RenderingManager::resetCamera()
@ -1005,4 +1006,10 @@ void RenderingManager::resetCamera()
mCamera->reset();
}
float RenderingManager::getTerrainHeightAt(Ogre::Vector3 worldPos)
{
assert(mTerrain);
return mTerrain->getHeightAt(worldPos);
}
} // namespace

@ -157,6 +157,8 @@ public:
bool occlusionQuerySupported() { return mOcclusionQuery->supported(); }
OcclusionQuery* getOcclusionQuery() { return mOcclusionQuery; }
float getTerrainHeightAt (Ogre::Vector3 worldPos);
Shadows* getShadows();
void switchToInterior();
@ -164,8 +166,6 @@ public:
void getTriangleBatchCount(unsigned int &triangles, unsigned int &batches);
float getTerrainHeightAt (Ogre::Vector3 worldPos);
void setGlare(bool glare);
void skyEnable ();
void skyDisable ();
@ -209,7 +209,7 @@ public:
void playVideo(const std::string& name, bool allowSkipping);
void stopVideo();
void frameStarted(float dt);
void frameStarted(float dt, bool paused);
protected:
virtual void windowResized(int x, int y);

@ -107,7 +107,8 @@ void Shadows::recreate()
// Set visibility mask for the shadow render textures
int visibilityMask = RV_Actors * Settings::Manager::getBool("actor shadows", "Shadows")
+ (RV_Statics + RV_StaticsSmall) * Settings::Manager::getBool("statics shadows", "Shadows")
+ RV_Misc * Settings::Manager::getBool("misc shadows", "Shadows");
+ RV_Misc * Settings::Manager::getBool("misc shadows", "Shadows")
+ RV_Terrain * (Settings::Manager::getBool("terrain shadows", "Shadows"));
for (int i = 0; i < (split ? 3 : 1); ++i)
{
TexturePtr shadowTexture = mSceneMgr->getShadowTexture(i);

@ -1,5 +1,7 @@
#include "scene.hpp"
#include <OgreSceneNode.h>
#include <components/nif/niffile.hpp>
#include <libs/openengine/ogre/fader.hpp>
@ -85,7 +87,6 @@ namespace MWWorld
std::cout << "Unloading cell\n";
ListAndResetHandles functor;
/*
(*iter)->forEach<ListAndResetHandles>(functor);
{
// silence annoying g++ warning
@ -96,7 +97,6 @@ namespace MWWorld
mPhysics->removeObject (node->getName());
}
}
*/
if ((*iter)->mCell->isExterior())
{
@ -150,7 +150,7 @@ namespace MWWorld
// ... then references. This is important for adjustPosition to work correctly.
/// \todo rescale depending on the state of a new GMST
//insertCell (*cell, true);
insertCell (*cell, true);
mRendering.cellAdded (cell);

@ -835,7 +835,7 @@ namespace MWWorld
bool isPlayer = ptr == mPlayer->getPlayer();
bool haveToMove = isPlayer || mWorldScene->isCellActive(*currCell);
if (false ) //*currCell != newCell)
if (*currCell != newCell)
{
removeContainerScripts(ptr);
@ -1026,10 +1026,13 @@ namespace MWWorld
return;
}
float terrainHeight = -std::numeric_limits<float>().max();// mRendering->getTerrainHeightAt(pos);
if (ptr.getCell()->isExterior())
{
float terrainHeight = mRendering->getTerrainHeightAt(pos);
if (pos.z < terrainHeight)
pos.z = terrainHeight;
if (pos.z < terrainHeight)
pos.z = terrainHeight;
}
ptr.getRefData().getPosition().pos[2] = pos.z + 20; // place slightly above. will snap down to ground with code below
@ -1074,15 +1077,8 @@ namespace MWWorld
{
const int cellSize = 8192;
cellX = static_cast<int> (x/cellSize);
if (x<0)
--cellX;
cellY = static_cast<int> (y/cellSize);
if (y<0)
--cellY;
cellX = std::floor(x/cellSize);
cellY = std::floor(y/cellSize);
}
void World::doPhysics(const PtrMovementList &actors, float duration)
@ -1682,9 +1678,9 @@ namespace MWWorld
mRendering->stopVideo();
}
void World::frameStarted (float dt)
void World::frameStarted (float dt, bool paused)
{
mRendering->frameStarted(dt);
mRendering->frameStarted(dt, paused);
}
void World::activateDoor(const MWWorld::Ptr& door)

@ -415,7 +415,7 @@ namespace MWWorld
/// \todo this does not belong here
virtual void playVideo(const std::string& name, bool allowSkipping);
virtual void stopVideo();
virtual void frameStarted (float dt);
virtual void frameStarted (float dt, bool paused);
/// Find center of exterior cell above land surface
/// \return false if exterior with given name not exists, true otherwise

@ -2,9 +2,6 @@
#include <OgreSceneNode.h>
#include <OgreHardwareBufferManager.h>
#include <OgreMaterialManager.h>
#include <OgreTechnique.h>
#include <OgrePass.h>
#include "quadtreenode.hpp"
#include "terrain.hpp"
@ -53,7 +50,6 @@ namespace Terrain
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);
@ -66,6 +62,8 @@ namespace Terrain
mIndexData->indexStart = 0;
}
void Chunk::updateIndexBuffer()
{
// Fetch a suitable index buffer (which may be shared)
@ -75,7 +73,7 @@ namespace Terrain
for (int i=0; i<4; ++i)
{
QuadTreeNode* neighbour = mNode->searchNeighbour((Direction)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,

@ -106,6 +106,8 @@ namespace Terrain
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());
@ -127,9 +129,7 @@ namespace Terrain
tus->setTextureAddressingMode(Ogre::TextureUnitState::TAM_CLAMP);
float scale = (16/(16.f+1.f));
float scroll = 1/16.f*0.5;
tus->setTextureScale(scale,scale);
tus->setTextureScroll(-scroll,-scroll);
tus->setTextureScale(1.f/scale,1.f/scale);
}
// Add the actual layer texture on top of the alpha map.
@ -150,6 +150,7 @@ namespace Terrain
Ogre::Pass* lightingPass = technique->createPass();
lightingPass->setSceneBlending(Ogre::SBT_MODULATE);
lightingPass->setVertexColourTracking(Ogre::TVC_AMBIENT|Ogre::TVC_DIFFUSE);
lightingPass->setFog(true, Ogre::FOG_NONE);
}
}

@ -18,6 +18,7 @@ namespace Terrain
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; }
/// Creates a material suitable for displaying a chunk of terrain using alpha-blending.

@ -142,17 +142,21 @@ QuadTreeNode::QuadTreeNode(Terrain* terrain, ChildDirection dir, float size, con
, 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;
mSceneNode = mTerrain->getSceneManager()->getRootSceneNode()->createChildSceneNode(
Ogre::Vector3(mCenter.x*8192, mCenter.y*8192, 0));
mLodLevel = log2(mSize);
mMaterialGenerator = new MaterialGenerator(true);
mMaterialGenerator = new MaterialGenerator(mTerrain->getShadersEnabled());
}
void QuadTreeNode::createChild(ChildDirection id, float size, const Ogre::Vector2 &center)
@ -168,38 +172,40 @@ QuadTreeNode::~QuadTreeNode()
delete mMaterialGenerator;
}
QuadTreeNode* QuadTreeNode::searchNeighbour(Direction dir)
QuadTreeNode* QuadTreeNode::getNeighbour(Direction dir)
{
return searchNeighbourRecursive(this, dir);
return mNeighbours[static_cast<int>(dir)];
}
const Ogre::AxisAlignedBox& QuadTreeNode::getBoundingBox()
void QuadTreeNode::initNeighbours()
{
if (mIsDummy)
return Ogre::AxisAlignedBox::BOX_NULL;
if (mBounds.isNull())
for (int i=0; i<4; ++i)
mNeighbours[i] = searchNeighbourRecursive(this, (Direction)i);
}
void QuadTreeNode::initAabb()
{
if (hasChildren())
{
if (hasChildren())
for (int i=0; i<4; ++i)
{
// X and Y are obvious, just need Z
float min = std::numeric_limits<float>().max();
float max = -std::numeric_limits<float>().max();
for (int i=0; i<4; ++i)
{
QuadTreeNode* child = getChild((ChildDirection)i);
float v = child->getBoundingBox().getMaximum().z;
if (v > max)
max = v;
v = child->getBoundingBox().getMinimum().z;
if (v < min)
min = v;
}
mBounds = Ogre::AxisAlignedBox (Ogre::Vector3(-mSize/2*8192, -mSize/2*8192, min),
Ogre::Vector3(mSize/2*8192, mSize/2*8192, max));
mChildren[i]->initAabb();
mBounds.merge(mChildren[i]->getBoundingBox());
}
else
throw std::runtime_error("Leaf node should have bounds set!");
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;
}
@ -209,11 +215,19 @@ void QuadTreeNode::update(const Ogre::Vector3 &cameraPos)
if (bounds.isNull())
return;
Ogre::AxisAlignedBox worldBounds (bounds.getMinimum() + Ogre::Vector3(mCenter.x*8192, mCenter.y*8192, 0),
bounds.getMaximum() + Ogre::Vector3(mCenter.x*8192, mCenter.y*8192, 0));
float dist = distance(mWorldBounds, cameraPos);
if (!mTerrain->getDistantLandEnabled())
{
if (dist > 8192*2)
{
destroyChunks();
return;
}
}
float dist = distance(worldBounds, cameraPos);
/// \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;
@ -230,6 +244,7 @@ void QuadTreeNode::update(const Ogre::Vector3 &cameraPos)
if (mSize <= mTerrain->getMaxBatchSize() && mLodLevel <= wantedLod)
{
bool hadChunk = hasChunk();
// Wanted LOD is small enough to render this node in one chunk
if (!mChunk)
{
@ -250,14 +265,32 @@ void QuadTreeNode::update(const Ogre::Vector3 &cameraPos)
}
}
// 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->setAdditionalLod(wantedLod - mLodLevel);
mChunk->setVisible(true);
if (hasChildren())
if (!hadChunk && hasChildren())
{
for (int i=0; i<4; ++i)
mChildren[i]->removeChunks();
mChildren[i]->hideChunks();
}
}
else
@ -272,15 +305,41 @@ void QuadTreeNode::update(const Ogre::Vector3 &cameraPos)
}
}
void QuadTreeNode::removeChunks()
void QuadTreeNode::hideChunks()
{
if (mChunk)
mChunk->setVisible(false);
if (hasChildren())
{
else if (hasChildren())
for (int i=0; i<4; ++i)
mChildren[i]->removeChunks();
mChildren[i]->hideChunks();
}
void QuadTreeNode::destroyChunks()
{
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("");
}
Ogre::TextureManager::getSingleton().remove(mCompositeMap->getName());
mCompositeMap.setNull();
}
else if (hasChildren())
for (int i=0; i<4; ++i)
mChildren[i]->destroyChunks();
}
void QuadTreeNode::updateIndexBuffers()
@ -312,7 +371,7 @@ void QuadTreeNode::ensureLayerInfo()
std::vector<Ogre::TexturePtr> blendmaps;
std::vector<std::string> layerList;
mTerrain->getStorage()->getBlendmaps(mSize, mCenter, true, blendmaps, layerList);
mTerrain->getStorage()->getBlendmaps(mSize, mCenter, mTerrain->getShadersEnabled(), blendmaps, layerList);
mMaterialGenerator->setLayerList(layerList);
mMaterialGenerator->setBlendmapList(blendmaps);
@ -324,7 +383,9 @@ void QuadTreeNode::prepareForCompositeMap(Ogre::TRect<float> area)
if (mIsDummy)
{
MaterialGenerator matGen(true);
// 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);
@ -357,12 +418,6 @@ void QuadTreeNode::prepareForCompositeMap(Ogre::TRect<float> area)
}
}
bool QuadTreeNode::hasCompositeMap()
{
return !mCompositeMap.isNull();
}
void QuadTreeNode::ensureCompositeMap()
{
if (!mCompositeMap.isNull())

@ -49,6 +49,13 @@ namespace Terrain
QuadTreeNode (Terrain* terrain, ChildDirection dir, float size, const Ogre::Vector2& center, QuadTreeNode* parent);
~QuadTreeNode();
/// 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);
@ -66,15 +73,15 @@ namespace Terrain
bool hasChildren() { return mChildren[0] != 0; }
QuadTreeNode* getChild(ChildDirection dir) { return mChildren[dir]; }
/// Search for a neighbour node in this direction
QuadTreeNode* searchNeighbour (Direction 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) { mBounds = box; }
void setBoundingBox (const Ogre::AxisAlignedBox& box);
/// Get bounding box in local coordinates
const Ogre::AxisAlignedBox& getBoundingBox();
@ -88,8 +95,11 @@ namespace Terrain
/// Call after QuadTreeNode::update!
void updateIndexBuffers();
/// Remove chunks rendered by this node and all its children
void removeChunks();
/// Hide chunks rendered by this node and all its children
void hideChunks();
/// Destroy chunks rendered by this node and all its children
void destroyChunks();
/// Get the effective LOD level if this node was rendered in one chunk
/// with ESM::Land::LAND_SIZE^2 vertices
@ -101,8 +111,6 @@ namespace Terrain
/// Is this node currently configured to render itself?
bool hasChunk();
bool hasCompositeMap();
/// 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.
@ -118,6 +126,7 @@ namespace Terrain
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;
@ -125,6 +134,7 @@ namespace Terrain
QuadTreeNode* mParent;
QuadTreeNode* mChildren[4];
QuadTreeNode* mNeighbours[4];
Chunk* mChunk;

@ -54,15 +54,25 @@ namespace Terrain
void Storage::fixNormal (Ogre::Vector3& normal, int cellX, int cellY, int col, int row)
{
if (col == ESM::Land::LAND_SIZE-1)
while (col >= ESM::Land::LAND_SIZE-1)
{
++cellY;
col = 0;
col -= ESM::Land::LAND_SIZE-1;
}
if (row == ESM::Land::LAND_SIZE-1)
while (row >= ESM::Land::LAND_SIZE-1)
{
++cellX;
row = 0;
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)
@ -70,7 +80,49 @@ namespace Terrain
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,
@ -141,17 +193,21 @@ namespace Terrain
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];
// Normals don't connect seamlessly between cells - wtf?
if (col == ESM::Land::LAND_SIZE-1 || row == ESM::Land::LAND_SIZE-1)
fixNormal(normal, cellX, cellY, col, row);
// z < 0 should never happen, but it does - I hate this data set...
if (normal.z < 0)
normal *= -1;
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;
@ -168,6 +224,11 @@ namespace Terrain
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);
@ -193,18 +254,20 @@ namespace Terrain
Storage::UniqueTextureId Storage::getVtexIndexAt(int cellX, int cellY,
int x, int y)
{
// If we're at the last row (or last column), we need to get the texture from the neighbour cell
// to get consistent blending at the border
if (x >= ESM::Land::LAND_TEXTURE_SIZE)
// 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;
--cellX;
x += ESM::Land::LAND_TEXTURE_SIZE;
}
if (y >= 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++;
++cellY;
y -= ESM::Land::LAND_TEXTURE_SIZE;
}
assert(x<ESM::Land::LAND_TEXTURE_SIZE);
assert(y<ESM::Land::LAND_TEXTURE_SIZE);
@ -226,7 +289,7 @@ namespace Terrain
std::string Storage::getTextureName(UniqueTextureId id)
{
if (id.first == 0)
return "_land_default.dds"; // Not sure if the default texture really is hardcoded?
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);
@ -241,6 +304,10 @@ namespace Terrain
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;
@ -253,7 +320,7 @@ namespace Terrain
// 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));
// NB +1 to get the last index from neighbour cell (see getVtexIndexAt)
for (int y=0; y<ESM::Land::LAND_TEXTURE_SIZE+1; ++y)
for (int x=0; x<ESM::Land::LAND_TEXTURE_SIZE+1; ++x)
{
@ -314,5 +381,90 @@ namespace Terrain
}
}
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 terrain 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];
}
}

@ -58,8 +58,14 @@ namespace Terrain
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.

@ -51,12 +51,14 @@ namespace
namespace Terrain
{
Terrain::Terrain(Ogre::SceneManager* sceneMgr, Storage* storage, int visibilityFlags)
Terrain::Terrain(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)
{
mCompositeMapSceneMgr = Ogre::Root::getSingleton().createSceneManager(Ogre::ST_GENERIC);
@ -81,6 +83,8 @@ namespace Terrain
mRootNode = new QuadTreeNode(this, Root, size, Ogre::Vector2(center.x, center.y), NULL);
buildQuadTree(mRootNode);
mRootNode->initAabb();
mRootNode->initNeighbours();
}
Terrain::~Terrain()
@ -136,14 +140,19 @@ namespace Terrain
node->markAsDummy();
}
void Terrain::update(Ogre::Camera *camera)
void Terrain::update(const Ogre::Vector3& cameraPos)
{
mRootNode->update(camera->getRealPosition());
mRootNode->update(cameraPos);
mRootNode->updateIndexBuffers();
}
Ogre::AxisAlignedBox Terrain::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,
@ -220,10 +229,10 @@ namespace Terrain
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);
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+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);
}
@ -242,17 +251,18 @@ namespace Terrain
{
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 left edge
if (col == 0)
indices.push_back(ESM::Land::LAND_SIZE*(col+innerStep)+row+innerStep);
// 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+row+innerStep);
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+outerStep)+row);
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);
}
@ -265,11 +275,11 @@ namespace Terrain
{
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 right edge
if (col+outerStep == ESM::Land::LAND_SIZE-1)
indices.push_back(ESM::Land::LAND_SIZE*(col+outerStep-innerStep)+row-innerStep);
// 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+outerStep)+row-innerStep);
indices.push_back(ESM::Land::LAND_SIZE*col+row-innerStep);
for (size_t i = 0; i < outerStep; i += innerStep)
{
@ -278,7 +288,7 @@ namespace Terrain
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+row);
indices.push_back(ESM::Land::LAND_SIZE*(col+outerStep)+row);
}
}
@ -289,18 +299,18 @@ namespace Terrain
{
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 bottom edge
if (row == 0)
indices.push_back(ESM::Land::LAND_SIZE*(col+innerStep)+row+innerStep);
// 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);
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+outerStep);
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);
}
@ -313,18 +323,18 @@ namespace Terrain
{
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 top edge
if (row+outerStep == ESM::Land::LAND_SIZE-1)
indices.push_back(ESM::Land::LAND_SIZE*(col-innerStep)+row+outerStep-innerStep);
// 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+outerStep);
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);
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);
}
@ -355,5 +365,10 @@ namespace Terrain
mCompositeMapSceneMgr->clearScene();
}
float Terrain::getHeightAt(const Ogre::Vector3 &worldPos)
{
return mStorage->getHeightAt(worldPos);
}
}

@ -28,16 +28,25 @@ namespace Terrain
{
public:
/// @note takes ownership of \a storage
Terrain(Ogre::SceneManager* sceneMgr, Storage* storage, int visiblityFlags);
/// @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.
Terrain(Ogre::SceneManager* sceneMgr, Storage* storage, int visiblityFlags, bool distantLand, bool shaders);
~Terrain();
bool getDistantLandEnabled() { return mDistantLand; }
bool getShadersEnabled() { return mShaders; }
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 (Ogre::Camera* camera);
/// \todo
float getHeightAt (const Ogre::Vector3& worldPos) { return 0; }
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);
@ -63,6 +72,9 @@ namespace Terrain
void enableSplattingShader(bool enabled);
private:
bool mDistantLand;
bool mShaders;
QuadTreeNode* mRootNode;
Storage* mStorage;

@ -160,6 +160,7 @@
@shEndForeach
lightResult.xyz += lightAmbient.xyz;
lightResult.xyz *= colour.xyz;
directionalResult.xyz *= colour.xyz;
@shPassthroughAssign(lightResult, lightResult);
@shPassthroughAssign(directionalResult, directionalResult);

@ -335,12 +335,12 @@
</Widget>
</Widget>
<Widget type="HBox" position="4 172 350 24">
<Widget type="AutoSizedButton" skin="MW_Button" align="Left Top" name="ShadowsDebug"/>
<Widget type="HBox" position="4 140 350 24">
<Widget type="AutoSizedButton" skin="MW_Button" align="Left Top" name="TerrainShadows"/>
<Widget type="AutoSizedTextBox" skin="SandText" align="Left Top">
<Property key="Caption" value="Debug overlay"/>
<Property key="Caption" value="Terrain shadows"/>
</Widget>
</Widget>
</Widget>
</Widget>

@ -80,6 +80,7 @@ texture size = 1024
actor shadows = true
misc shadows = true
statics shadows = true
terrain shadows = true
# Fraction of the total shadow distance after which the shadow starts to fade out
fade start = 0.8
@ -125,8 +126,9 @@ fog start factor = 0.5
fog end factor = 1.0
[Terrain]
# Max. number of lights that affect the terrain. Setting to 1 will only reflect sunlight
num lights = 8
distant land = false
shader = true
[Water]
shader = true

Loading…
Cancel
Save