New terrain renderer - improvements:

- Consistent triangle alignment, fixes a noticable crack near the census and excise office. Note that alignment is still not the same as vanilla. Vanilla uses a weird diagonal pattern. I hope there aren't more trouble spots that will force us to replicate vanilla, but at least we can do that now.
 - Fixes several blending issues and cell border seams
 - Fix map render to use the terrain bounding box instead of an arbitrary height
 - Different LODs are now properly connected instead of using skirts
 - Support self shadowing
 - Normals and colors are stored in the vertices instead of a texture, this enables per-vertex lighting which should improve performance, fix compatibility issues due to the PS getting too large and mimic vanilla better
 - Support a fixed function fallback (though the splatting shader usually performs better)
 - Designed for distant land support - test: https://www.youtube.com/watch?v=2wnd9EuPJIY - we can't really enable this yet due to depth precision issues when using a large view distance
pull/51/head
scrawl 12 years ago
parent a41a23c90a
commit e27437f8ed

@ -15,8 +15,9 @@ source_group(game FILES ${GAME} ${GAME_HEADER})
add_openmw_dir (mwrender
renderingmanager debugging sky camera animation npcanimation creatureanimation activatoranimation
actors objects renderinginterface localmap occlusionquery terrain terrainmaterial water shadows
actors objects renderinginterface localmap occlusionquery water shadows
compositors characterpreview externalrendering globalmap videoplayer ripplesimulation refraction
terrainstorage
)
add_openmw_dir (mwinput

@ -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.
@ -597,4 +597,4 @@ void OMW::Engine::setStartupScript (const std::string& path)
void OMW::Engine::setActivationDistanceOverride (int distance)
{
mActivationDistanceOverride = distance;
}
}

@ -58,8 +58,6 @@ namespace MWRender
//if (!boost::filesystem::exists(mCacheDir + "/GlobalMap.png"))
if (1)
{
Ogre::Image image;
std::vector<Ogre::uchar> data (mWidth * mHeight * 3);
for (int x = mMinX; x <= mMaxX; ++x)
@ -144,9 +142,6 @@ namespace MWRender
b = waterDeepColour.b * 255;
}
// uncomment this line to outline cell borders
//if (cellX == 0 || cellX == cellSize-1 || cellY == 0|| cellY == cellSize-1) r = 255;
data[texelY * mWidth * 3 + texelX * 3] = r;
data[texelY * mWidth * 3 + texelX * 3+1] = g;
data[texelY * mWidth * 3 + texelX * 3+2] = b;
@ -155,13 +150,11 @@ namespace MWRender
}
}
image.loadDynamicImage (&data[0], mWidth, mHeight, Ogre::PF_B8G8R8);
//image.save (mCacheDir + "/GlobalMap.png");
Ogre::DataStreamPtr stream(new Ogre::MemoryDataStream(&data[0], data.size()));
tex = Ogre::TextureManager::getSingleton ().createManual ("GlobalMap.png", Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME,
Ogre::TEX_TYPE_2D, mWidth, mHeight, 0, Ogre::PF_B8G8R8, Ogre::TU_STATIC);
tex->loadImage(image);
tex->loadRawData(stream, mWidth, mHeight, Ogre::PF_B8G8R8);
}
else
tex = Ogre::TextureManager::getSingleton ().getByName ("GlobalMap.png");

@ -2,6 +2,10 @@
#include <OgreMaterialManager.h>
#include <OgreHardwarePixelBuffer.h>
#include <OgreSceneManager.h>
#include <OgreSceneNode.h>
#include <OgreCamera.h>
#include <OgreTextureManager.h>
#include "../mwworld/esmstore.hpp"
@ -104,7 +108,7 @@ void LocalMap::saveFogOfWar(MWWorld::Ptr::CellStore* cell)
}
}
void LocalMap::requestMap(MWWorld::Ptr::CellStore* cell)
void LocalMap::requestMap(MWWorld::Ptr::CellStore* cell, float zMin, float zMax)
{
mInterior = false;
@ -118,7 +122,7 @@ void LocalMap::requestMap(MWWorld::Ptr::CellStore* cell)
mCameraPosNode->setPosition(Vector3(0,0,0));
render((x+0.5)*sSize, (y+0.5)*sSize, -10000, 10000, sSize, sSize, name);
render((x+0.5)*sSize, (y+0.5)*sSize, zMin, zMax, sSize, sSize, name);
}
void LocalMap::requestMap(MWWorld::Ptr::CellStore* cell,

@ -28,16 +28,18 @@ namespace MWRender
* Request the local map for an exterior cell.
* @remarks It will either be loaded from a disk cache,
* or rendered if it is not already cached.
* @param exterior cell
* @param cell exterior cell
* @param zMin min height of objects or terrain in cell
* @param zMax max height of objects or terrain in cell
*/
void requestMap (MWWorld::CellStore* cell);
void requestMap (MWWorld::CellStore* cell, float zMin, float zMax);
/**
* Request the local map for an interior cell.
* @remarks It will either be loaded from a disk cache,
* or rendered if it is not already cached.
* @param interior cell
* @param bounding box of the cell
* @param cell interior cell
* @param bounds bounding box of the cell
*/
void requestMap (MWWorld::CellStore* cell,
Ogre::AxisAlignedBox bounds);

@ -25,6 +25,8 @@
#include <components/esm/loadstat.hpp>
#include <components/settings/settings.hpp>
#include <components/terrain/terrain.hpp>
#include "../mwworld/esmstore.hpp"
#include "../mwworld/class.hpp"
@ -46,6 +48,7 @@
#include "externalrendering.hpp"
#include "globalmap.hpp"
#include "videoplayer.hpp"
#include "terrainstorage.hpp"
using namespace MWRender;
using namespace Ogre;
@ -63,6 +66,7 @@ RenderingManager::RenderingManager(OEngine::Render::OgreRenderer& _rend, const b
, mAmbientMode(0)
, mSunEnabled(0)
, mPhysicsEngine(engine)
, mTerrain(NULL)
{
// select best shader mode
bool openGL = (Ogre::Root::getSingleton ().getRenderSystem ()->getName().find("OpenGL") != std::string::npos);
@ -78,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"), 5);
mRendering.createScene("PlayerCam", Settings::Manager::getFloat("field of view", "General"), 50);
mRendering.getWindow()->addListener(this);
mRendering.setWindowListener(this);
@ -166,8 +170,6 @@ RenderingManager::RenderingManager(OEngine::Render::OgreRenderer& _rend, const b
mShadows = new Shadows(&mRendering);
mTerrainManager = new TerrainManager(mRendering.getScene(), this);
mSkyManager = new SkyManager(mRootNode, mRendering.getCamera());
mOcclusionQuery = new OcclusionQuery(&mRendering, mSkyManager->getSunNode());
@ -194,7 +196,7 @@ RenderingManager::~RenderingManager ()
delete mSkyManager;
delete mDebugging;
delete mShadows;
delete mTerrainManager;
delete mTerrain;
delete mLocalMap;
delete mOcclusionQuery;
delete mCompositors;
@ -225,8 +227,6 @@ void RenderingManager::removeCell (MWWorld::Ptr::CellStore *store)
mObjects.removeCell(store);
mActors.removeCell(store);
mDebugging->cellRemoved(store);
if (store->mCell->isExterior())
mTerrainManager->cellRemoved(store);
}
void RenderingManager::removeWater ()
@ -244,8 +244,14 @@ void RenderingManager::cellAdded (MWWorld::Ptr::CellStore *store)
mObjects.buildStaticGeometry (*store);
sh::Factory::getInstance().unloadUnreferencedMaterials();
mDebugging->cellAdded(store);
if (store->mCell->isExterior())
mTerrainManager->cellAdded(store);
if (store->isExterior())
{
if (!mTerrain)
{
mTerrain = new Terrain::Terrain(mRendering.getScene(), new MWRender::TerrainStorage(), RV_Terrain);
mTerrain->update(mRendering.getCamera());
}
}
waterAdded(store);
}
@ -550,7 +556,8 @@ void RenderingManager::setAmbientMode()
float RenderingManager::getTerrainHeightAt(Ogre::Vector3 worldPos)
{
return mTerrainManager->getTerrainHeightAt(worldPos);
assert(mTerrain);
return mTerrain->getHeightAt(worldPos);
}
@ -595,7 +602,6 @@ void RenderingManager::setSunColour(const Ogre::ColourValue& colour)
if (!mSunEnabled) return;
mSun->setDiffuseColour(colour);
mSun->setSpecularColour(colour);
mTerrainManager->setDiffuse(colour);
}
void RenderingManager::setAmbientColour(const Ogre::ColourValue& colour)
@ -608,7 +614,6 @@ void RenderingManager::setAmbientColour(const Ogre::ColourValue& colour)
final += Ogre::ColourValue(0.7,0.7,0.7,0) * std::min(1.f, (nightEye/100.f));
mRendering.getScene()->setAmbientLight(final);
mTerrainManager->setAmbient(final);
}
void RenderingManager::sunEnable(bool real)
@ -652,7 +657,12 @@ void RenderingManager::setGlare(bool glare)
void RenderingManager::requestMap(MWWorld::Ptr::CellStore* cell)
{
if (cell->mCell->isExterior())
mLocalMap->requestMap(cell);
{
Ogre::AxisAlignedBox dims = mObjects.getDimensions(cell);
Ogre::Vector2 center(cell->mCell->getGridX() + 0.5, -cell->mCell->getGridY() + 1 - 0.5);
dims.merge(mTerrain->getWorldBoundingBox(center));
mLocalMap->requestMap(cell, dims.getMinimum().z, dims.getMaximum().z);
}
else
mLocalMap->requestMap(cell, mObjects.getDimensions(cell));
}
@ -984,6 +994,9 @@ void RenderingManager::updateWaterRippleEmitterPtr (const MWWorld::Ptr& old, con
void RenderingManager::frameStarted(float dt)
{
if (mTerrain)
mTerrain->update(mRendering.getCamera());
mWater->frameStarted(dt);
}

@ -2,7 +2,6 @@
#define _GAME_RENDERING_MANAGER_H
#include "sky.hpp"
#include "terrain.hpp"
#include "debugging.hpp"
#include <openengine/ogre/fader.hpp>
@ -39,6 +38,11 @@ namespace sh
class Factory;
}
namespace Terrain
{
class Terrain;
}
namespace MWRender
{
class Shadows;
@ -228,7 +232,7 @@ private:
OcclusionQuery* mOcclusionQuery;
TerrainManager* mTerrainManager;
Terrain::Terrain* mTerrain;
MWRender::Water *mWater;

@ -108,7 +108,6 @@ void Shadows::recreate()
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");
for (int i = 0; i < (split ? 3 : 1); ++i)
{
TexturePtr shadowTexture = mSceneMgr->getShadowTexture(i);

@ -1,531 +0,0 @@
#include <boost/lexical_cast.hpp>
#include <OgreTerrain.h>
#include <OgreTerrainGroup.h>
#include <OgreHardwarePixelBuffer.h>
#include <OgreRoot.h>
#include "../mwworld/esmstore.hpp"
#include <components/settings/settings.hpp>
#include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp"
#include "terrainmaterial.hpp"
#include "terrain.hpp"
#include "renderconst.hpp"
#include "shadows.hpp"
#include "renderingmanager.hpp"
using namespace Ogre;
namespace MWRender
{
//----------------------------------------------------------------------------------------------
TerrainManager::TerrainManager(Ogre::SceneManager* mgr, RenderingManager* rend) :
mTerrainGroup(TerrainGroup(mgr, Terrain::ALIGN_X_Y, mLandSize, mWorldSize)), mRendering(rend)
{
mTerrainGlobals = OGRE_NEW TerrainGlobalOptions();
TerrainMaterialGeneratorPtr matGen;
TerrainMaterial* matGenP = new TerrainMaterial();
matGen.bind(matGenP);
mTerrainGlobals->setDefaultMaterialGenerator(matGen);
TerrainMaterialGenerator::Profile* const activeProfile =
mTerrainGlobals->getDefaultMaterialGenerator()
->getActiveProfile();
mActiveProfile = static_cast<TerrainMaterial::Profile*>(activeProfile);
// We don't want any pixel error at all. Really, LOD makes no sense here - morrowind uses 65x65 verts in one cell,
// so applying LOD is most certainly slower than doing no LOD at all.
// Setting this to 0 seems to cause glitches though. :/
mTerrainGlobals->setMaxPixelError(1);
mTerrainGlobals->setLayerBlendMapSize(ESM::Land::LAND_TEXTURE_SIZE/2 + 1);
//10 (default) didn't seem to be quite enough
mTerrainGlobals->setSkirtSize(128);
//due to the sudden flick between composite and non composite textures,
//this seemed the distance where it wasn't too noticeable
mTerrainGlobals->setCompositeMapDistance(mWorldSize*2);
mTerrainGroup.setOrigin(Vector3(mWorldSize/2,
mWorldSize/2,
0));
Terrain::ImportData& importSettings = mTerrainGroup.getDefaultImportSettings();
importSettings.inputBias = 0;
importSettings.terrainSize = mLandSize;
importSettings.worldSize = mWorldSize;
importSettings.minBatchSize = 9;
importSettings.maxBatchSize = mLandSize;
importSettings.deleteInputData = true;
}
//----------------------------------------------------------------------------------------------
float TerrainManager::getTerrainHeightAt(Vector3 worldPos)
{
Ogre::Terrain* terrain = NULL;
float height = mTerrainGroup.getHeightAtWorldPosition(worldPos, &terrain);
if (terrain == NULL)
return std::numeric_limits<int>().min();
return height;
}
//----------------------------------------------------------------------------------------------
TerrainManager::~TerrainManager()
{
OGRE_DELETE mTerrainGlobals;
}
//----------------------------------------------------------------------------------------------
void TerrainManager::setDiffuse(const ColourValue& diffuse)
{
mTerrainGlobals->setCompositeMapDiffuse(diffuse);
}
//----------------------------------------------------------------------------------------------
void TerrainManager::setAmbient(const ColourValue& ambient)
{
mTerrainGlobals->setCompositeMapAmbient(ambient);
}
//----------------------------------------------------------------------------------------------
void TerrainManager::cellAdded(MWWorld::Ptr::CellStore *store)
{
const int cellX = store->mCell->getGridX();
const int cellY = store->mCell->getGridY();
ESM::Land* land =
MWBase::Environment::get().getWorld()->getStore().get<ESM::Land>().search(cellX, cellY);
if (land == NULL) // no land data means we're not going to create any terrain.
return;
int dataRequired = ESM::Land::DATA_VHGT | ESM::Land::DATA_VCLR;
if (!land->isDataLoaded(dataRequired))
{
land->loadData(dataRequired);
}
//split the cell terrain into four segments
const int numTextures = ESM::Land::LAND_TEXTURE_SIZE/2;
for ( int x = 0; x < 2; x++ )
{
for ( int y = 0; y < 2; y++ )
{
Terrain::ImportData terrainData =
mTerrainGroup.getDefaultImportSettings();
const int terrainX = cellX * 2 + x;
const int terrainY = cellY * 2 + y;
//it makes far more sense to reallocate the memory here,
//and let Ogre deal with it due to the issues with deleting
//it at the wrong time if using threads (Which Terrain does)
terrainData.inputFloat = OGRE_ALLOC_T(float,
mLandSize*mLandSize,
MEMCATEGORY_GEOMETRY);
//copy the height data row by row
for ( int terrainCopyY = 0; terrainCopyY < mLandSize; terrainCopyY++ )
{
//the offset of the current segment
const size_t yOffset = y * (mLandSize-1) * ESM::Land::LAND_SIZE +
//offset of the row
terrainCopyY * ESM::Land::LAND_SIZE;
const size_t xOffset = x * (mLandSize-1);
memcpy(&terrainData.inputFloat[terrainCopyY*mLandSize],
&land->mLandData->mHeights[yOffset + xOffset],
mLandSize*sizeof(float));
}
std::map<uint16_t, int> indexes;
initTerrainTextures(&terrainData, cellX, cellY,
x * numTextures, y * numTextures,
numTextures, indexes, land->mPlugin);
if (mTerrainGroup.getTerrain(terrainX, terrainY) == NULL)
{
mTerrainGroup.defineTerrain(terrainX, terrainY, &terrainData);
mTerrainGroup.loadTerrain(terrainX, terrainY, true);
Terrain* terrain = mTerrainGroup.getTerrain(terrainX, terrainY);
initTerrainBlendMaps(terrain,
cellX, cellY,
x * numTextures, y * numTextures,
numTextures,
indexes);
terrain->setVisibilityFlags(RV_Terrain);
terrain->setRenderQueueGroup(RQG_Main);
// disable or enable global colour map (depends on available vertex colours)
if ( land->mLandData->mUsingColours )
{
TexturePtr vertex = getVertexColours(land,
cellX, cellY,
x*(mLandSize-1),
y*(mLandSize-1),
mLandSize);
mActiveProfile->setGlobalColourMapEnabled(true);
mActiveProfile->setGlobalColourMap (terrain, vertex->getName());
}
else
mActiveProfile->setGlobalColourMapEnabled (false);
}
}
}
// when loading from a heightmap, Ogre::Terrain does not update the derived data (normal map, LOD)
// synchronously, even if we supply synchronous = true parameter to loadTerrain.
// the following to be the only way to make sure derived data is ready when rendering the next frame.
while (mTerrainGroup.isDerivedDataUpdateInProgress())
{
// we need to wait for this to finish
OGRE_THREAD_SLEEP(5);
Root::getSingleton().getWorkQueue()->processResponses();
}
mTerrainGroup.freeTemporaryResources();
}
//----------------------------------------------------------------------------------------------
void TerrainManager::cellRemoved(MWWorld::Ptr::CellStore *store)
{
for ( int x = 0; x < 2; x++ )
{
for ( int y = 0; y < 2; y++ )
{
int terrainX = store->mCell->getGridX() * 2 + x;
int terrainY = store->mCell->getGridY() * 2 + y;
if (mTerrainGroup.getTerrain(terrainX, terrainY) != NULL)
mTerrainGroup.unloadTerrain(terrainX, terrainY);
}
}
}
//----------------------------------------------------------------------------------------------
void TerrainManager::initTerrainTextures(Terrain::ImportData* terrainData,
int cellX, int cellY,
int fromX, int fromY, int size,
std::map<uint16_t, int>& indexes, size_t plugin)
{
// FIXME: In a multiple esm configuration, we have multiple palettes. Since this code
// crosses cell boundaries, we no longer have a unique terrain palette. Instead, we need
// to adopt the following code for a dynamic palette. And this is evil - the current design
// does not work well for this task...
assert(terrainData != NULL && "Must have valid terrain data");
assert(fromX >= 0 && fromY >= 0 &&
"Can't get a terrain texture on terrain outside the current cell");
assert(fromX+size <= ESM::Land::LAND_TEXTURE_SIZE &&
fromY+size <= ESM::Land::LAND_TEXTURE_SIZE &&
"Can't get a terrain texture on terrain outside the current cell");
//this ensures that the ltex indexes are sorted (or retrived as sorted
//which simplifies shading between cells).
//
//If we don't sort the ltex indexes, the splatting order may differ between
//cells which may lead to inconsistent results when shading between cells
int num = MWBase::Environment::get().getWorld()->getStore().get<ESM::LandTexture>().getSize(plugin);
std::set<uint16_t> ltexIndexes;
for ( int y = fromY; y < fromY + size + 1; y++ )
{
for ( int x = fromX - 1; x < fromX + size; x++ ) // NB we wrap X from the other side because Y is reversed
{
int idx = getLtexIndexAt(cellX, cellY, x, y);
// This is a quick hack to prevent the program from trying to fetch textures
// from a neighboring cell, which might originate from a different plugin,
// and use a separate texture palette. Right now, we simply cast it to the
// default texture (i.e. 0).
if (idx > num)
idx = 0;
ltexIndexes.insert(idx);
}
}
//there is one texture that we want to use as a base (i.e. it won't have
//a blend map). This holds the ltex index of that base texture so that
//we know not to include it in the output map
int baseTexture = -1;
for ( std::set<uint16_t>::iterator iter = ltexIndexes.begin();
iter != ltexIndexes.end();
++iter )
{
uint16_t ltexIndex = *iter;
//this is the base texture, so we can ignore this at present
if ( ltexIndex == baseTexture )
{
continue;
}
const std::map<uint16_t, int>::const_iterator it = indexes.find(ltexIndex);
if ( it == indexes.end() )
{
//NB: All vtex ids are +1 compared to the ltex ids
const MWWorld::Store<ESM::LandTexture> &ltexStore =
MWBase::Environment::get().getWorld()->getStore().get<ESM::LandTexture>();
// NOTE: using the quick hack above, we should no longer end up with textures indices
// that are out of bounds. However, I haven't updated the test to a multi-palette
// system yet. We probably need more work here, so we skip it for now.
//assert( (int)ltexStore.getSize() >= (int)ltexIndex - 1 &&
//"LAND.VTEX must be within the bounds of the LTEX array");
std::string texture;
if ( ltexIndex == 0 )
{
texture = "_land_default.dds";
}
else
{
texture = ltexStore.search(ltexIndex-1, plugin)->mTexture;
//TODO this is needed due to MWs messed up texture handling
texture = texture.substr(0, texture.rfind(".")) + ".dds";
}
const size_t position = terrainData->layerList.size();
terrainData->layerList.push_back(Terrain::LayerInstance());
terrainData->layerList[position].worldSize = 256;
terrainData->layerList[position].textureNames.push_back("textures\\" + texture);
if ( baseTexture == -1 )
{
baseTexture = ltexIndex;
}
else
{
indexes[ltexIndex] = position;
}
}
}
}
//----------------------------------------------------------------------------------------------
void TerrainManager::initTerrainBlendMaps(Terrain* terrain,
int cellX, int cellY,
int fromX, int fromY, int size,
const std::map<uint16_t, int>& indexes)
{
assert(terrain != NULL && "Must have valid terrain");
assert(fromX >= 0 && fromY >= 0 &&
"Can't get a terrain texture on terrain outside the current cell");
assert(fromX+size <= ESM::Land::LAND_TEXTURE_SIZE &&
fromY+size <= ESM::Land::LAND_TEXTURE_SIZE &&
"Can't get a terrain texture on terrain outside the current cell");
//size must be a power of 2 as we do divisions with a power of 2 number
//that need to result in an integer for correct splatting
assert( (size & (size - 1)) == 0 && "Size must be a power of 2");
const int blendMapSize = terrain->getLayerBlendMapSize();
//zero out every map
std::map<uint16_t, int>::const_iterator iter;
for ( iter = indexes.begin(); iter != indexes.end(); ++iter )
{
float* pBlend = terrain->getLayerBlendMap(iter->second)
->getBlendPointer();
memset(pBlend, 0, sizeof(float) * blendMapSize * blendMapSize);
}
//covert the ltex data into a set of blend maps
for ( int texY = fromY; texY < fromY + size + 1; texY++ )
{
for ( int texX = fromX - 1; texX < fromX + size; texX++ ) // NB we wrap X from the other side because Y is reversed
{
const uint16_t ltexIndex = getLtexIndexAt(cellX, cellY, texX, texY);
//check if it is the base texture (which isn't in the map) and
//if it is don't bother altering the blend map for it
if ( indexes.find(ltexIndex) == indexes.end() )
{
continue;
}
//while texX is the splat index relative to the entire cell,
//relX is relative to the current segment we are splatting
const int relX = texX - fromX + 1;
const int relY = texY - fromY;
const int layerIndex = indexes.find(ltexIndex)->second;
float* const pBlend = terrain->getLayerBlendMap(layerIndex)
->getBlendPointer();
//Note: Y is reversed
const int splatY = blendMapSize - relY - 1;
const int splatX = relX;
assert(splatX >= 0 && splatX < blendMapSize);
assert(splatY >= 0 && splatY < blendMapSize);
const int index = (splatY)*blendMapSize + splatX;
pBlend[index] = 1;
}
}
for ( int i = 1; i < terrain->getLayerCount(); i++ )
{
TerrainLayerBlendMap* blend = terrain->getLayerBlendMap(i);
blend->dirty();
blend->update();
}
}
//----------------------------------------------------------------------------------------------
int TerrainManager::getLtexIndexAt(int cellX, int cellY,
int x, int y)
{
//check texture index falls within the 9 cell bounds
//as this function can't cope with anything above that
assert(x >= -ESM::Land::LAND_TEXTURE_SIZE &&
y >= -ESM::Land::LAND_TEXTURE_SIZE &&
"Trying to get land textures that are out of bounds");
assert(x < 2*ESM::Land::LAND_TEXTURE_SIZE &&
y < 2*ESM::Land::LAND_TEXTURE_SIZE &&
"Trying to get land textures that are out of bounds");
if ( x < 0 )
{
cellX--;
x += ESM::Land::LAND_TEXTURE_SIZE;
}
else if ( x >= ESM::Land::LAND_TEXTURE_SIZE )
{
cellX++;
x -= ESM::Land::LAND_TEXTURE_SIZE;
}
if ( y < 0 )
{
cellY--;
y += ESM::Land::LAND_TEXTURE_SIZE;
}
else if ( y >= ESM::Land::LAND_TEXTURE_SIZE )
{
cellY++;
y -= ESM::Land::LAND_TEXTURE_SIZE;
}
ESM::Land* land =
MWBase::Environment::get().getWorld()->getStore().get<ESM::Land>().search(cellX, cellY);
if ( land != NULL )
{
if (!land->isDataLoaded(ESM::Land::DATA_VTEX))
{
land->loadData(ESM::Land::DATA_VTEX);
}
return land->mLandData
->mTextures[y * ESM::Land::LAND_TEXTURE_SIZE + x];
}
else
{
return 0;
}
}
//----------------------------------------------------------------------------------------------
TexturePtr TerrainManager::getVertexColours(ESM::Land* land,
int cellX, int cellY,
int fromX, int fromY, int size)
{
TextureManager* const texMgr = TextureManager::getSingletonPtr();
const std::string colourTextureName = "VtexColours_" +
boost::lexical_cast<std::string>(cellX) +
"_" +
boost::lexical_cast<std::string>(cellY) +
"_" +
boost::lexical_cast<std::string>(fromX) +
"_" +
boost::lexical_cast<std::string>(fromY);
TexturePtr tex = texMgr->getByName(colourTextureName);
if ( !tex.isNull() )
{
return tex;
}
tex = texMgr->createManual(colourTextureName,
ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME,
TEX_TYPE_2D, size, size, 0, PF_BYTE_BGR);
HardwarePixelBufferSharedPtr pixelBuffer = tex->getBuffer();
pixelBuffer->lock(HardwareBuffer::HBL_DISCARD);
const PixelBox& pixelBox = pixelBuffer->getCurrentLock();
uint8* pDest = static_cast<uint8*>(pixelBox.data);
if ( land != NULL )
{
const char* const colours = land->mLandData->mColours;
for ( int y = 0; y < size; y++ )
{
for ( int x = 0; x < size; x++ )
{
const size_t colourOffset = (y+fromY)*3*65 + (x+fromX)*3;
assert( colourOffset < 65*65*3 &&
"Colour offset is out of the expected bounds of record" );
const unsigned char r = colours[colourOffset + 0];
const unsigned char g = colours[colourOffset + 1];
const unsigned char b = colours[colourOffset + 2];
//as is the case elsewhere we need to flip the y
const size_t imageOffset = (size - 1 - y)*size*4 + x*4;
pDest[imageOffset + 0] = b;
pDest[imageOffset + 1] = g;
pDest[imageOffset + 2] = r;
}
}
}
else
{
for ( int y = 0; y < size; y++ )
{
for ( int x = 0; x < size; x++ )
{
for ( int k = 0; k < 3; k++ )
{
*pDest++ = 0;
}
}
}
}
pixelBuffer->unlock();
return tex;
}
}

@ -1,128 +0,0 @@
#ifndef _GAME_RENDER_TERRAIN_H
#define _GAME_RENDER_TERRAIN_H
#include <OgreTerrain.h>
#include <OgreTerrainGroup.h>
#include <components/esm/loadland.hpp>
#include "terrainmaterial.hpp"
namespace Ogre{
class SceneManager;
class TerrainGroup;
class TerrainGlobalOptions;
class Terrain;
}
namespace MWWorld
{
class CellStore;
}
namespace MWRender{
class RenderingManager;
/**
* Implements the Morrowind terrain using the Ogre Terrain Component
*
* Each terrain cell is split into four blocks as this leads to an increase
* in performance and means we don't hit splat limits quite as much
*/
class TerrainManager{
public:
TerrainManager(Ogre::SceneManager* mgr, RenderingManager* rend);
virtual ~TerrainManager();
void setDiffuse(const Ogre::ColourValue& diffuse);
void setAmbient(const Ogre::ColourValue& ambient);
void cellAdded(MWWorld::CellStore* store);
void cellRemoved(MWWorld::CellStore* store);
float getTerrainHeightAt (Ogre::Vector3 worldPos);
private:
Ogre::TerrainGlobalOptions* mTerrainGlobals;
Ogre::TerrainGroup mTerrainGroup;
RenderingManager* mRendering;
TerrainMaterial::Profile* mActiveProfile;
/**
* The length in verticies of a single terrain block.
*/
static const int mLandSize = (ESM::Land::LAND_SIZE - 1)/2 + 1;
/**
* The length in game units of a single terrain block.
*/
static const int mWorldSize = ESM::Land::REAL_SIZE/2;
/**
* Setups up the list of textures for part of a cell, using indexes as
* an output to create a mapping of MW LtexIndex to the relevant terrain
* layer
*
* @param terrainData the terrain data to setup the textures for
* @param cellX the coord of the cell
* @param cellY the coord of the cell
* @param fromX the ltex index in the current cell to start making the texture from
* @param fromY the ltex index in the current cell to start making the texture from
* @param size the size (number of splats) to get
* @param indexes a mapping of ltex index to the terrain texture layer that
* can be used by initTerrainBlendMaps
*/
void initTerrainTextures(Ogre::Terrain::ImportData* terrainData,
int cellX, int cellY,
int fromX, int fromY, int size,
std::map<uint16_t, int>& indexes, size_t plugin);
/**
* Creates the blend (splatting maps) for the given terrain from the ltex data.
*
* @param terrain the terrain object for the current cell
* @param cellX the coord of the cell
* @param cellY the coord of the cell
* @param fromX the ltex index in the current cell to start making the texture from
* @param fromY the ltex index in the current cell to start making the texture from
* @param size the size (number of splats) to get
* @param indexes the mapping of ltex to blend map produced by initTerrainTextures
*/
void initTerrainBlendMaps(Ogre::Terrain* terrain,
int cellX, int cellY,
int fromX, int fromY, int size,
const std::map<uint16_t, int>& indexes);
/**
* Gets a LTEX index at the given point, assuming the current cell
* starts at (0,0). This supports getting values from the surrounding
* cells so negative x, y is acceptable
*
* @param cellX the coord of the cell
* @param cellY the coord of the cell
* @param x, y the splat position of the ltex index to get relative to the
* first splat of the current cell
*/
int getLtexIndexAt(int cellX, int cellY, int x, int y);
/**
* Due to the fact that Ogre terrain doesn't support vertex colours
* we have to generate them manually
*
* @param cellX the coord of the cell
* @param cellY the coord of the cell
* @param fromX the *vertex* index in the current cell to start making texture from
* @param fromY the *vertex* index in the current cell to start making the texture from
* @param size the size (number of vertexes) to get
*/
Ogre::TexturePtr getVertexColours(ESM::Land* land,
int cellX, int cellY,
int fromX, int fromY, int size);
};
}
#endif // _GAME_RENDER_TERRAIN_H

@ -1,246 +0,0 @@
#include "terrainmaterial.hpp"
#include <OgreTerrain.h>
#include <stdexcept>
#include <extern/shiny/Main/Factory.hpp>
namespace
{
Ogre::String getComponent (int num)
{
if (num == 0)
return "x";
else if (num == 1)
return "y";
else if (num == 2)
return "z";
else
return "w";
}
}
namespace MWRender
{
TerrainMaterial::TerrainMaterial()
{
mLayerDecl.samplers.push_back(Ogre::TerrainLayerSampler("albedo_specular", Ogre::PF_BYTE_RGBA));
//mLayerDecl.samplers.push_back(Ogre::TerrainLayerSampler("normal_height", Ogre::PF_BYTE_RGBA));
mLayerDecl.elements.push_back(
Ogre::TerrainLayerSamplerElement(0, Ogre::TLSS_ALBEDO, 0, 3));
//mLayerDecl.elements.push_back(
// Ogre::TerrainLayerSamplerElement(0, Ogre::TLSS_SPECULAR, 3, 1));
//mLayerDecl.elements.push_back(
// Ogre::TerrainLayerSamplerElement(1, Ogre::TLSS_NORMAL, 0, 3));
//mLayerDecl.elements.push_back(
// Ogre::TerrainLayerSamplerElement(1, Ogre::TLSS_HEIGHT, 3, 1));
mProfiles.push_back(OGRE_NEW Profile(this, "SM2", "Profile for rendering on Shader Model 2 capable cards"));
setActiveProfile("SM2");
}
// -----------------------------------------------------------------------------------------------------------------------
TerrainMaterial::Profile::Profile(Ogre::TerrainMaterialGenerator* parent, const Ogre::String& name, const Ogre::String& desc)
: Ogre::TerrainMaterialGenerator::Profile(parent, name, desc)
, mGlobalColourMap(false)
, mMaterial(0)
{
}
TerrainMaterial::Profile::~Profile()
{
if (mMaterial)
sh::Factory::getInstance().destroyMaterialInstance(mMaterial->getName());
}
Ogre::MaterialPtr TerrainMaterial::Profile::generate(const Ogre::Terrain* terrain)
{
const Ogre::String& matName = terrain->getMaterialName();
sh::Factory::getInstance().destroyMaterialInstance (matName);
Ogre::MaterialPtr mat = Ogre::MaterialManager::getSingleton().getByName(matName);
if (!mat.isNull())
Ogre::MaterialManager::getSingleton().remove(matName);
mMaterial = sh::Factory::getInstance().createMaterialInstance (matName);
mMaterial->setProperty ("allow_fixed_function", sh::makeProperty<sh::BooleanValue>(new sh::BooleanValue(false)));
int numPasses = getRequiredPasses(terrain);
int maxLayersInOnePass = getMaxLayersPerPass(terrain);
for (int pass=0; pass<numPasses; ++pass)
{
int layerOffset = maxLayersInOnePass * pass;
int blendmapOffset = (pass == 0) ? 1 : 0; // the first layer of the first pass is the base layer and does not need a blend map
sh::MaterialInstancePass* p = mMaterial->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 (pass != 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 ("colour_map", sh::makeProperty(new sh::BooleanValue(mGlobalColourMap)));
p->mShaderProperties.setProperty ("is_first_pass", sh::makeProperty(new sh::BooleanValue(pass == 0)));
// global colour map
sh::MaterialInstanceTextureUnit* colourMap = p->createTextureUnit ("colourMap");
colourMap->setProperty ("texture_alias", sh::makeProperty<sh::StringValue> (new sh::StringValue(mMaterial->getName() + "_colourMap")));
colourMap->setProperty ("tex_address_mode", sh::makeProperty<sh::StringValue> (new sh::StringValue("clamp")));
// global normal map
sh::MaterialInstanceTextureUnit* normalMap = p->createTextureUnit ("normalMap");
normalMap->setProperty ("direct_texture", sh::makeProperty<sh::StringValue> (new sh::StringValue(terrain->getTerrainNormalMap ()->getName())));
normalMap->setProperty ("tex_address_mode", sh::makeProperty<sh::StringValue> (new sh::StringValue("clamp")));
Ogre::uint numLayersInThisPass = std::min(maxLayersInOnePass, terrain->getLayerCount()-layerOffset);
// HACK: Terrain::getLayerBlendTextureIndex should be const, but it is not.
// Remove this once ogre got fixed.
Ogre::Terrain* nonconstTerrain = const_cast<Ogre::Terrain*>(terrain);
// a blend map might be shared between two passes
// so we can't just use terrain->getBlendTextureCount()
Ogre::uint numBlendTextures=0;
std::vector<std::string> blendTextures;
for (unsigned int layer=blendmapOffset; layer<numLayersInThisPass; ++layer)
{
std::string blendTextureName = terrain->getBlendTextureName(nonconstTerrain->getLayerBlendTextureIndex(
static_cast<Ogre::uint8>(layerOffset+layer)).first);
if (std::find(blendTextures.begin(), blendTextures.end(), blendTextureName) == blendTextures.end())
{
blendTextures.push_back(blendTextureName);
++numBlendTextures;
}
}
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 (terrain->getLayerCount() == 1) // special case. if there's only one layer, we don't need blend maps at all
blendmapStart = 0;
else
blendmapStart = nonconstTerrain->getLayerBlendTextureIndex(static_cast<Ogre::uint8>(layerOffset+blendmapOffset)).first;
for (Ogre::uint i = 0; i < numBlendTextures; ++i)
{
sh::MaterialInstanceTextureUnit* blendTex = p->createTextureUnit ("blendMap" + Ogre::StringConverter::toString(i));
blendTex->setProperty ("direct_texture", sh::makeProperty (new sh::StringValue(terrain->getBlendTextureName(blendmapStart+i))));
blendTex->setProperty ("tex_address_mode", sh::makeProperty (new sh::StringValue("clamp")));
}
// layer maps
for (Ogre::uint i = 0; i < numLayersInThisPass; ++i)
{
sh::MaterialInstanceTextureUnit* diffuseTex = p->createTextureUnit ("diffuseMap" + Ogre::StringConverter::toString(i));
diffuseTex->setProperty ("direct_texture", sh::makeProperty (new sh::StringValue(terrain->getLayerTextureName(layerOffset+i, 0))));
if (i+layerOffset > 0)
{
int blendTextureIndex = nonconstTerrain->getLayerBlendTextureIndex(static_cast<Ogre::uint8>(layerOffset+i)).first;
int blendTextureComponent = nonconstTerrain->getLayerBlendTextureIndex(static_cast<Ogre::uint8>(layerOffset+i)).second;
p->mShaderProperties.setProperty ("blendmap_component_" + Ogre::StringConverter::toString(i),
sh::makeProperty (new sh::StringValue(Ogre::StringConverter::toString(blendTextureIndex-blendmapStart) + "." + getComponent(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
for (Ogre::uint i = 0; i < 3; ++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 + 2))));
// 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(pass)));
}
return Ogre::MaterialManager::getSingleton().getByName(matName);
}
void TerrainMaterial::Profile::setGlobalColourMapEnabled (bool enabled)
{
mGlobalColourMap = enabled;
mParent->_markChanged();
}
void TerrainMaterial::Profile::setGlobalColourMap (Ogre::Terrain* terrain, const std::string& name)
{
sh::Factory::getInstance ().setTextureAlias (terrain->getMaterialName () + "_colourMap", name);
}
Ogre::MaterialPtr TerrainMaterial::Profile::generateForCompositeMap(const Ogre::Terrain* terrain)
{
throw std::runtime_error ("composite map not supported");
}
Ogre::uint8 TerrainMaterial::Profile::getMaxLayers(const Ogre::Terrain* terrain) const
{
return 255;
}
int TerrainMaterial::Profile::getMaxLayersPerPass (const Ogre::Terrain* terrain)
{
// count the texture units free
Ogre::uint8 freeTextureUnits = 16;
// normalmap
--freeTextureUnits;
// colourmap
--freeTextureUnits;
// shadow
--freeTextureUnits;
--freeTextureUnits;
--freeTextureUnits;
// each layer needs 1.25 units (1xdiffusespec, 0.25xblend)
return static_cast<Ogre::uint8>(freeTextureUnits / (1.25f));
}
int TerrainMaterial::Profile::getRequiredPasses (const Ogre::Terrain* terrain)
{
int maxLayersPerPass = getMaxLayersPerPass(terrain);
assert(terrain->getLayerCount());
assert(maxLayersPerPass);
return std::ceil(static_cast<float>(terrain->getLayerCount()) / maxLayersPerPass);
}
void TerrainMaterial::Profile::updateParams(const Ogre::MaterialPtr& mat, const Ogre::Terrain* terrain)
{
}
void TerrainMaterial::Profile::updateParamsForCompositeMap(const Ogre::MaterialPtr& mat, const Ogre::Terrain* terrain)
{
}
void TerrainMaterial::Profile::requestOptions(Ogre::Terrain* terrain)
{
terrain->_setMorphRequired(true);
terrain->_setNormalMapRequired(true); // global normal map
terrain->_setLightMapRequired(false);
terrain->_setCompositeMapRequired(false);
}
}

@ -1,88 +0,0 @@
/*
-----------------------------------------------------------------------------
This source file is part of OGRE
(Object-oriented Graphics Rendering Engine)
For the latest info, see http://www.ogre3d.org/
Copyright (c) 2000-2011 Torus Knot Software Ltd
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
-----------------------------------------------------------------------------
*/
#ifndef MWRENDER_TERRAINMATERIAL_H
#define MWRENDER_TERRAINMATERIAL_H
#include "OgreTerrainPrerequisites.h"
#include "OgreTerrainMaterialGenerator.h"
#include "OgreGpuProgramParams.h"
namespace sh
{
class MaterialInstance;
}
namespace MWRender
{
class TerrainMaterial : public Ogre::TerrainMaterialGenerator
{
public:
class Profile : public Ogre::TerrainMaterialGenerator::Profile
{
public:
Profile(Ogre::TerrainMaterialGenerator* parent, const Ogre::String& name, const Ogre::String& desc);
virtual ~Profile();
virtual bool isVertexCompressionSupported() const { return false; }
virtual Ogre::MaterialPtr generate(const Ogre::Terrain* terrain);
virtual Ogre::MaterialPtr generateForCompositeMap(const Ogre::Terrain* terrain);
virtual Ogre::uint8 getMaxLayers(const Ogre::Terrain* terrain) const;
virtual void updateParams(const Ogre::MaterialPtr& mat, const Ogre::Terrain* terrain);
virtual void updateParamsForCompositeMap(const Ogre::MaterialPtr& mat, const Ogre::Terrain* terrain);
virtual void requestOptions(Ogre::Terrain* terrain);
void setGlobalColourMapEnabled(bool enabled);
void setGlobalColourMap (Ogre::Terrain* terrain, const std::string& name);
virtual void setLightmapEnabled(bool) {}
private:
sh::MaterialInstance* mMaterial;
int getRequiredPasses (const Ogre::Terrain* terrain);
int getMaxLayersPerPass (const Ogre::Terrain* terrain);
bool mGlobalColourMap;
};
TerrainMaterial();
};
}
#endif

@ -0,0 +1,56 @@
#include "terrainstorage.hpp"
#include "../mwbase/world.hpp"
#include "../mwbase/environment.hpp"
#include "../mwworld/esmstore.hpp"
namespace MWRender
{
Ogre::AxisAlignedBox TerrainStorage::getBounds()
{
int minX = 0, minY = 0, maxX = 0, maxY = 0;
const MWWorld::ESMStore &esmStore =
MWBase::Environment::get().getWorld()->getStore();
MWWorld::Store<ESM::Cell>::iterator it = esmStore.get<ESM::Cell>().extBegin();
for (; it != esmStore.get<ESM::Cell>().extEnd(); ++it)
{
if (it->getGridX() < minX)
minX = it->getGridX();
if (it->getGridX() > maxX)
maxX = it->getGridX();
if (it->getGridY() < minY)
minY = it->getGridY();
if (it->getGridY() > maxY)
maxY = it->getGridY();
}
// since grid coords are at cell origin, we need to add 1 cell
maxX += 1;
maxY += 1;
return Ogre::AxisAlignedBox(minX, minY, 0, maxX, maxY, 0);
}
ESM::Land* TerrainStorage::getLand(int cellX, int cellY)
{
const MWWorld::ESMStore &esmStore =
MWBase::Environment::get().getWorld()->getStore();
ESM::Land* land = esmStore.get<ESM::Land>().search(cellX, cellY);
// Load the data we are definitely going to need
int mask = ESM::Land::DATA_VHGT | ESM::Land::DATA_VNML | ESM::Land::DATA_VCLR | ESM::Land::DATA_VTEX;
if (land && !land->isDataLoaded(mask))
land->loadData(mask);
return land;
}
const ESM::LandTexture* TerrainStorage::getLandTexture(int index, short plugin)
{
const MWWorld::ESMStore &esmStore =
MWBase::Environment::get().getWorld()->getStore();
return esmStore.get<ESM::LandTexture>().find(index, plugin);
}
}

@ -0,0 +1,22 @@
#ifndef MWRENDER_TERRAINSTORAGE_H
#define MWRENDER_TERRAINSTORAGE_H
#include <components/terrain/storage.hpp>
namespace MWRender
{
class TerrainStorage : public Terrain::Storage
{
private:
virtual ESM::Land* getLand (int cellX, int cellY);
virtual const ESM::LandTexture* getLandTexture(int index, short plugin);
public:
virtual Ogre::AxisAlignedBox getBounds();
///< Get bounds in cell units
};
}
#endif

@ -202,7 +202,10 @@ Water::Water (Ogre::Camera *camera, RenderingManager* rend) :
mWaterPlane = Plane(Vector3::UNIT_Z, 0);
MeshManager::getSingleton().createPlane("water", ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, mWaterPlane, CELL_SIZE*5, CELL_SIZE * 5, 10, 10, true, 1, 3,3, Vector3::UNIT_Y);
int waterScale = 300;
MeshManager::getSingleton().createPlane("water", ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, mWaterPlane,
CELL_SIZE*5*waterScale, CELL_SIZE*5*waterScale, 10, 10, true, 1, 3*waterScale,3*waterScale, Vector3::UNIT_Y);
mWater = mSceneMgr->createEntity("water");
mWater->setVisibilityFlags(RV_Water);

@ -85,6 +85,7 @@ namespace MWWorld
std::cout << "Unloading cell\n";
ListAndResetHandles functor;
/*
(*iter)->forEach<ListAndResetHandles>(functor);
{
// silence annoying g++ warning
@ -95,6 +96,7 @@ namespace MWWorld
mPhysics->removeObject (node->getName());
}
}
*/
if ((*iter)->mCell->isExterior())
{
@ -148,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);

@ -1,5 +1,7 @@
#include "worldimp.hpp"
#include <OgreSceneNode.h>
#include <libs/openengine/bullet/physic.hpp>
#include <components/bsa/bsa_archive.hpp>
@ -833,7 +835,7 @@ namespace MWWorld
bool isPlayer = ptr == mPlayer->getPlayer();
bool haveToMove = isPlayer || mWorldScene->isCellActive(*currCell);
if (*currCell != newCell)
if (false ) //*currCell != newCell)
{
removeContainerScripts(ptr);
@ -1024,7 +1026,7 @@ namespace MWWorld
return;
}
float terrainHeight = mRendering->getTerrainHeightAt(pos);
float terrainHeight = -std::numeric_limits<float>().max();// mRendering->getTerrainHeightAt(pos);
if (pos.z < terrainHeight)
pos.z = terrainHeight;

@ -65,6 +65,10 @@ add_component_dir (interpreter
add_component_dir (translation
translation
)
add_component_dir (terrain
quadtreenode chunk terrain storage material
)
find_package(Qt4 COMPONENTS QtCore QtGui)

@ -70,7 +70,7 @@ struct Land
};
#pragma pack(pop)
typedef uint8_t VNML[LAND_NUM_VERTS * 3];
typedef signed char VNML[LAND_NUM_VERTS * 3];
struct LandData
{

@ -0,0 +1,171 @@
#include "chunk.hpp"
#include <OgreSceneNode.h>
#include <OgreHardwareBufferManager.h>
#include <OgreMaterialManager.h>
#include <OgreTechnique.h>
#include <OgrePass.h>
#include "quadtreenode.hpp"
#include "terrain.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 /= std::pow(2, 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->searchNeighbour((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 < std::pow(2,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();
}
}

@ -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

@ -0,0 +1,291 @@
#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)
{
}
int MaterialGenerator::getMaxLayersPerPass ()
{
// count the texture units free
Ogre::uint8 freeTextureUnits = 16;
// first layer doesn't need blendmap
--freeTextureUnits;
// each layer needs 1.25 units (1xdiffusespec, 0.25xblend)
return static_cast<Ogre::uint8>(freeTextureUnits / (1.25f)) + 1;
}
int MaterialGenerator::getRequiredPasses ()
{
int maxLayersPerPass = getMaxLayersPerPass();
return std::max(1.f, std::ceil(static_cast<float>(mLayerList.size()) / maxLayersPerPass));
}
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);
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));
float scroll = 1/16.f*0.5;
tus->setTextureScale(scale,scale);
tus->setTextureScroll(-scroll,-scroll);
}
// 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);
}
}
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
for (Ogre::uint i = 0; i < 3; ++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
{
int numPasses = getRequiredPasses();
assert(numPasses);
int maxLayersInOnePass = getMaxLayersPerPass();
for (int pass=0; pass<numPasses; ++pass)
{
int layerOffset = maxLayersInOnePass * pass;
int blendmapOffset = (pass == 0) ? 1 : 0; // the first layer of the first pass is the base layer and does not need a blend map
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 (pass != 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 ("is_first_pass", sh::makeProperty(new sh::BooleanValue(pass == 0)));
p->mShaderProperties.setProperty ("render_composite_map", sh::makeProperty(new sh::BooleanValue(renderCompositeMap)));
p->mShaderProperties.setProperty ("display_composite_map", sh::makeProperty(new sh::BooleanValue(displayCompositeMap)));
Ogre::uint numLayersInThisPass = std::min(maxLayersInOnePass, (int)mLayerList.size()-layerOffset);
// a blend map might be shared between two passes
Ogre::uint numBlendTextures=0;
std::vector<std::string> blendTextures;
for (unsigned int layer=blendmapOffset; layer<numLayersInThisPass; ++layer)
{
std::string blendTextureName = mBlendmapList[getBlendmapIndexForLayer(layerOffset+layer)]->getName();
if (std::find(blendTextures.begin(), blendTextures.end(), blendTextureName) == blendTextures.end())
{
blendTextures.push_back(blendTextureName);
++numBlendTextures;
}
}
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 (Ogre::uint 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 (Ogre::uint 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
for (Ogre::uint i = 0; i < 3; ++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(pass)));
}
}
}
return Ogre::MaterialManager::getSingleton().getByName(name.str());
}
}

@ -0,0 +1,54 @@
#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; }
void setCompositeMap (const std::string& name) { mCompositeMap = name; }
/// 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);
int getRequiredPasses ();
int getMaxLayersPerPass ();
int mNumLayers;
std::vector<std::string> mLayerList;
std::vector<Ogre::TexturePtr> mBlendmapList;
std::string mCompositeMap;
bool mShaders;
};
}
#endif

@ -0,0 +1,387 @@
#include "quadtreenode.hpp"
#include <OgreSceneManager.h>
#include <OgreManualObject.h>
#include "terrain.hpp"
#include "chunk.hpp"
#include "storage.hpp"
#include "material.hpp"
using namespace Terrain;
namespace
{
// 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
Terrain::QuadTreeNode* searchNeighbourRecursive (Terrain::QuadTreeNode* currentNode, Terrain::Direction dir)
{
if (!currentNode->getParent())
return NULL; // Arrived at root node, the root node does not have neighbours
Terrain::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(Terrain* 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.setNull();
for (int i=0; i<4; ++i)
mChildren[i] = NULL;
mSceneNode = mTerrain->getSceneManager()->getRootSceneNode()->createChildSceneNode(
Ogre::Vector3(mCenter.x*8192, mCenter.y*8192, 0));
mLodLevel = log2(mSize);
mMaterialGenerator = new MaterialGenerator(true);
}
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::searchNeighbour(Direction dir)
{
return searchNeighbourRecursive(this, dir);
}
const Ogre::AxisAlignedBox& QuadTreeNode::getBoundingBox()
{
if (mIsDummy)
return Ogre::AxisAlignedBox::BOX_NULL;
if (mBounds.isNull())
{
if (hasChildren())
{
// 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));
}
else
throw std::runtime_error("Leaf node should have bounds set!");
}
return mBounds;
}
void QuadTreeNode::update(const Ogre::Vector3 &cameraPos)
{
const Ogre::AxisAlignedBox& bounds = getBoundingBox();
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(worldBounds, cameraPos);
/// \todo implement error metrics or some other means of not using arbitrary values
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;
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);
if (mSize == 1)
{
ensureLayerInfo();
mChunk->setMaterial(mMaterialGenerator->generate(mChunk->getMaterial()));
}
else
{
ensureCompositeMap();
mMaterialGenerator->setCompositeMap(mCompositeMap->getName());
mChunk->setMaterial(mMaterialGenerator->generateForCompositeMap(mChunk->getMaterial()));
}
}
mChunk->setAdditionalLod(wantedLod - mLodLevel);
mChunk->setVisible(true);
if (hasChildren())
{
for (int i=0; i<4; ++i)
mChildren[i]->removeChunks();
}
}
else
{
// Wanted LOD is too detailed to be rendered in one chunk,
// so split it up by delegating to child nodes
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);
}
}
void QuadTreeNode::removeChunks()
{
if (mChunk)
mChunk->setVisible(false);
if (hasChildren())
{
for (int i=0; i<4; ++i)
mChildren[i]->removeChunks();
}
}
void QuadTreeNode::updateIndexBuffers()
{
if (hasChunk())
mChunk->updateIndexBuffer();
else if (hasChildren())
{
for (int i=0; i<4; ++i)
mChildren[i]->updateIndexBuffers();
}
}
bool QuadTreeNode::hasChunk()
{
return 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, true, blendmaps, layerList);
mMaterialGenerator->setLayerList(layerList);
mMaterialGenerator->setBlendmapList(blendmaps);
}
void QuadTreeNode::prepareForCompositeMap(Ogre::TRect<float> area)
{
Ogre::SceneManager* sceneMgr = mTerrain->getCompositeMapSceneManager();
if (mIsDummy)
{
MaterialGenerator matGen(true);
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.generate(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);
}
}
bool QuadTreeNode::hasCompositeMap()
{
return !mCompositeMap.isNull();
}
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();
}

@ -0,0 +1,141 @@
#ifndef COMPONENTS_TERRAIN_QUADTREENODE_H
#define COMPONENTS_TERRAIN_QUADTREENODE_H
#include <OgreAxisAlignedBox.h>
#include <OgreVector2.h>
#include <OgreTexture.h>
namespace Ogre
{
class Rectangle2D;
}
namespace Terrain
{
class Terrain;
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 (Terrain* terrain, ChildDirection dir, float size, const Ogre::Vector2& center, QuadTreeNode* parent);
~QuadTreeNode();
/// @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; }
int getSize() { return mSize; }
Ogre::Vector2 getCenter() { return mCenter; }
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);
/// 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; }
/// Get bounding box in local coordinates
const Ogre::AxisAlignedBox& getBoundingBox();
Terrain* getTerrain() { return mTerrain; }
/// Adjust LODs for the given camera position, possibly splitting up chunks or merging them.
void update (const Ogre::Vector3& cameraPos);
/// Adjust index buffers of chunks to stitch together chunks of different LOD, so that cracks are avoided.
/// Call after QuadTreeNode::update!
void updateIndexBuffers();
/// Remove chunks rendered by this node and all its children
void removeChunks();
/// 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();
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.
/// @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;
bool mIsDummy;
float mSize;
size_t mLodLevel; // LOD if we were to render this node in one chunk
Ogre::AxisAlignedBox mBounds;
ChildDirection mDirection;
Ogre::Vector2 mCenter;
Ogre::SceneNode* mSceneNode;
QuadTreeNode* mParent;
QuadTreeNode* mChildren[4];
Chunk* mChunk;
Terrain* mTerrain;
Ogre::TexturePtr mCompositeMap;
void ensureLayerInfo();
void ensureCompositeMap();
};
}
#endif

@ -0,0 +1,318 @@
#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)
{
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->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];
}
}
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 = std::pow(2, 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];
// 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[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;
}
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)
{
// 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)
{
cellX++;
x -= ESM::Land::LAND_TEXTURE_SIZE;
}
if (y >= ESM::Land::LAND_TEXTURE_SIZE)
{
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 really 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)
{
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));
// 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)
{
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);
}
}
}

@ -0,0 +1,76 @@
#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
{
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);
private:
void fixNormal (Ogre::Vector3& normal, int cellX, int cellY, int col, int row);
// 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

@ -0,0 +1,359 @@
#include "terrain.hpp"
#include <OgreAxisAlignedBox.h>
#include <OgreCamera.h>
#include <OgreHardwareBufferManager.h>
#include <OgreHardwarePixelBuffer.h>
#include <OgreRoot.h>
#include <components/esm/loadland.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
{
Terrain::Terrain(Ogre::SceneManager* sceneMgr, Storage* storage, int visibilityFlags)
: mStorage(storage)
, mMinBatchSize(1)
, mMaxBatchSize(64)
, mSceneMgr(sceneMgr)
, mVisibilityFlags(visibilityFlags)
{
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);
mRootNode = new QuadTreeNode(this, Root, size, Ogre::Vector2(center.x, center.y), NULL);
buildQuadTree(mRootNode);
}
Terrain::~Terrain()
{
delete mRootNode;
delete mStorage;
}
void Terrain::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 Terrain::update(Ogre::Camera *camera)
{
mRootNode->update(camera->getRealPosition());
mRootNode->updateIndexBuffers();
}
Ogre::AxisAlignedBox Terrain::getWorldBoundingBox (const Ogre::Vector2& center)
{
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 Terrain::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 Terrain::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 = std::pow(2, 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);
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+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 = std::pow(2, 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 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+outerStep)+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 = std::pow(2, 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 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+i)+row-innerStep);
indices.push_back(ESM::Land::LAND_SIZE*(col+i+innerStep)+row-innerStep);
indices.push_back(ESM::Land::LAND_SIZE*col+row);
}
}
// West
size_t col = 0;
outerStep = std::pow(2, 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 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);
indices.push_back(ESM::Land::LAND_SIZE*(col+innerStep)+row+i+innerStep);
}
}
// East
col = ESM::Land::LAND_SIZE-1;
outerStep = std::pow(2, 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 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+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 Terrain::renderCompositeMap(Ogre::TexturePtr target)
{
mCompositeMapRenderTarget->update();
target->getBuffer()->blit(mCompositeMapRenderTexture->getBuffer());
}
void Terrain::clearCompositeMapSceneManager()
{
mCompositeMapSceneMgr->destroyAllManualObjects();
mCompositeMapSceneMgr->clearScene();
}
}

@ -0,0 +1,121 @@
#ifndef COMPONENTS_TERRAIN_H
#define COMPONENTS_TERRAIN_H
#include <OgreHardwareIndexBuffer.h>
#include <OgreHardwareVertexBuffer.h>
#include <OgreAxisAlignedBox.h>
#include <OgreTexture.h>
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 Terrain
{
public:
/// @note takes ownership of \a storage
Terrain(Ogre::SceneManager* sceneMgr, Storage* storage, int visiblityFlags);
~Terrain();
/// 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; }
/// 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; }
Storage* getStorage() { return mStorage; }
/// Show or hide the whole terrain
void setVisible(bool visible);
/// 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();
int getVisiblityFlags() { return mVisibilityFlags; }
int getMaxBatchSize() { return mMaxBatchSize; }
void enableSplattingShader(bool enabled);
private:
QuadTreeNode* mRootNode;
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

@ -80,7 +80,6 @@
#endif
#if VERTEX_LIGHTING
shUniform(float, lightCount) @shAutoConstant(lightCount, light_count)
shUniform(float4, lightPosition[@shGlobalSettingString(num_lights)]) @shAutoConstant(lightPosition, light_position_view_space_array, @shGlobalSettingString(num_lights))
shUniform(float4, lightDiffuse[@shGlobalSettingString(num_lights)]) @shAutoConstant(lightDiffuse, light_diffuse_colour_array, @shGlobalSettingString(num_lights))
shUniform(float4, lightAttenuation[@shGlobalSettingString(num_lights)]) @shAutoConstant(lightAttenuation, light_attenuation_array, @shGlobalSettingString(num_lights))

@ -2,7 +2,7 @@
#define IS_FIRST_PASS (@shPropertyString(pass_index) == 0)
#define FOG @shGlobalSettingBool(fog)
#define FOG (@shGlobalSettingBool(fog) && !@shPropertyBool(render_composite_map))
#define SHADOWS_PSSM @shGlobalSettingBool(shadows_pssm)
#define SHADOWS @shGlobalSettingBool(shadows)
@ -11,8 +11,6 @@
#include "shadows.h"
#endif
#define COLOUR_MAP @shPropertyBool(colour_map)
#define NUM_LAYERS @shPropertyString(num_layers)
#if FOG || SHADOWS_PSSM
@ -23,9 +21,12 @@
#define VIEWPROJ_FIX @shGlobalSettingBool(viewproj_fix)
#if !IS_FIRST_PASS
// This is not the first pass.
#endif
#define RENDERCMP @shPropertyBool(render_composite_map)
#define LIGHTING !RENDERCMP
#define COMPOSITE_MAP @shPropertyBool(display_composite_map)
#if NEED_DEPTH
@shAllocatePassthrough(1, depth)
@ -35,6 +36,11 @@
@shAllocatePassthrough(3, worldPos)
#if LIGHTING
@shAllocatePassthrough(3, lightResult)
@shAllocatePassthrough(3, directionalResult)
#endif
#if SHADOWS
@shAllocatePassthrough(4, lightSpacePos0)
#endif
@ -55,11 +61,19 @@
#if VIEWPROJ_FIX
shUniform(float4, vpRow2Fix) @shSharedParameter(vpRow2Fix, vpRow2Fix)
#endif
shUniform(float2, lodMorph) @shAutoConstant(lodMorph, custom, 1001)
shVertexInput(float2, uv0)
shVertexInput(float2, uv1) // lodDelta, lodThreshold
#if LIGHTING
shNormalInput(float4)
shColourInput(float4)
shUniform(float, lightCount) @shAutoConstant(lightCount, light_count)
shUniform(float4, lightPosition[@shGlobalSettingString(num_lights)]) @shAutoConstant(lightPosition, light_position_object_space_array, @shGlobalSettingString(num_lights))
shUniform(float4, lightDiffuse[@shGlobalSettingString(num_lights)]) @shAutoConstant(lightDiffuse, light_diffuse_colour_array, @shGlobalSettingString(num_lights))
shUniform(float4, lightAttenuation[@shGlobalSettingString(num_lights)]) @shAutoConstant(lightAttenuation, light_attenuation_array, @shGlobalSettingString(num_lights))
shUniform(float4, lightAmbient) @shAutoConstant(lightAmbient, ambient_light_colour)
#if SHADOWS
shUniform(float4x4, texViewProjMatrix0) @shAutoConstant(texViewProjMatrix0, texture_viewproj_matrix)
@ -71,31 +85,15 @@
@shEndForeach
#endif
#endif
@shPassthroughVertexOutputs
SH_START_PROGRAM
{
float4 worldPos = shMatrixMult(worldMatrix, shInputPosition);
// determine whether to apply the LOD morph to this vertex
// we store the deltas against all vertices so we only want to apply
// the morph to the ones which would disappear. The target LOD which is
// being morphed to is stored in lodMorph.y, and the LOD at which
// the vertex should be morphed is stored in uv.w. If we subtract
// the former from the latter, and arrange to only morph if the
// result is negative (it will only be -1 in fact, since after that
// the vertex will never be indexed), we will achieve our aim.
// sign(vertexLOD - targetLOD) == -1 is to morph
float toMorph = -min(0, sign(uv1.y - lodMorph.y));
// morph
// this assumes XY terrain alignment
worldPos.z += uv1.x * toMorph * lodMorph.x;
shOutputPosition = shMatrixMult(viewProjMatrix, worldPos);
#if NEED_DEPTH
@ -124,6 +122,8 @@
@shPassthroughAssign(worldPos, worldPos.xyz);
#if LIGHTING
#if SHADOWS
float4 lightSpacePos = shMatrixMult(texViewProjMatrix0, shMatrixMult(worldMatrix, shInputPosition));
@shPassthroughAssign(lightSpacePos0, lightSpacePos);
@ -138,6 +138,33 @@
@shEndForeach
#endif
// Lighting
float3 lightDir;
float d;
float3 lightResult = float3(0,0,0);
float3 directionalResult = float3(0,0,0);
@shForeach(@shGlobalSettingString(num_lights))
lightDir = lightPosition[@shIterator].xyz - (shInputPosition.xyz * lightPosition[@shIterator].w);
d = length(lightDir);
lightDir = normalize(lightDir);
lightResult.xyz += lightDiffuse[@shIterator].xyz
* shSaturate(1.0 / ((lightAttenuation[@shIterator].y) + (lightAttenuation[@shIterator].z * d) + (lightAttenuation[@shIterator].w * d * d)))
* max(dot(normal.xyz, lightDir), 0);
#if @shIterator == 0
directionalResult = lightResult.xyz;
#endif
@shEndForeach
lightResult.xyz += lightAmbient.xyz;
lightResult.xyz *= colour.xyz;
@shPassthroughAssign(lightResult, lightResult);
@shPassthroughAssign(directionalResult, directionalResult);
#endif
}
#else
@ -151,12 +178,9 @@
SH_BEGIN_PROGRAM
#if COLOUR_MAP
shSampler2D(colourMap)
#endif
shSampler2D(normalMap) // global normal map
#if COMPOSITE_MAP
shSampler2D(compositeMap)
#else
@shForeach(@shPropertyString(num_blendmaps))
shSampler2D(blendMap@shIterator)
@ -165,6 +189,8 @@
@shForeach(@shPropertyString(num_layers))
shSampler2D(diffuseMap@shIterator)
@shEndForeach
#endif
#if FOG
shUniform(float3, fogColour) @shAutoConstant(fogColour, fog_colour)
@ -215,9 +241,6 @@
float2 UV = @shPassthroughReceive(UV);
float3 worldPos = @shPassthroughReceive(worldPos);
float3 normal = shSample(normalMap, UV).rgb * 2 - 1;
normal = normalize(normal);
#if UNDERWATER
@ -230,17 +253,26 @@
float previousAlpha = 1.f;
#endif
shOutputColour(0) = float4(1,1,1,1);
#if COMPOSITE_MAP
shOutputColour(0).xyz = shSample(compositeMap, UV).xyz;
#else
// Layer calculations
// rescale UV to directly map vertices to texel centers
// rescale UV to directly map edge vertices to texel centers - this is
// important to get correct blending at cell transitions
// TODO: parameterize texel size
float2 blendUV = (UV - 0.5) * (8.0 / (8.0+1.0)) + 0.5;
float2 blendUV = (UV - 0.5) * (16.0 / (16.0+1.0)) + 0.5;
@shForeach(@shPropertyString(num_blendmaps))
float4 blendValues@shIterator = shSample(blendMap@shIterator, blendUV);
float4 blendValues@shIterator = shSaturate(shSample(blendMap@shIterator, blendUV));
@shEndForeach
float3 albedo = float3(0,0,0);
float2 layerUV = UV * 8;
float2 layerUV = UV * 16;
@shForeach(@shPropertyString(num_layers))
@ -262,25 +294,14 @@ float2 blendUV = (UV - 0.5) * (8.0 / (8.0+1.0)) + 0.5;
#endif
@shEndForeach
shOutputColour(0) = float4(1,1,1,1);
shOutputColour(0).rgb *= albedo;
#if COLOUR_MAP
// Since we're emulating vertex colors here,
// rescale UV to directly map vertices to texel centers. TODO: parameterize texel size
const float colourmapSize = 33.f;
float2 colourUV = (UV - 0.5) * (colourmapSize / (colourmapSize+1.f)) + 0.5;
shOutputColour(0).rgb *= shSample(colourMap, colourUV).rgb;
#endif
shOutputColour(0).rgb *= albedo;
#if LIGHTING
// Lighting
float3 lightResult = @shPassthroughReceive(lightResult);
float3 directionalResult = @shPassthroughReceive(directionalResult);
// shadows only for the first (directional) light
#if SHADOWS
@ -305,40 +326,9 @@ float2 blendUV = (UV - 0.5) * (8.0 / (8.0+1.0)) + 0.5;
float shadow = 1.0;
#endif
float3 lightDir;
float3 diffuse = float3(0,0,0);
float d;
@shForeach(@shGlobalSettingString(terrain_num_lights))
lightDir = lightPosObjSpace@shIterator.xyz - (worldPos.xyz * lightPosObjSpace@shIterator.w);
d = length(lightDir);
lightDir = normalize(lightDir);
#if @shIterator == 0
#if (SHADOWS || SHADOWS_PSSM)
diffuse += lightDiffuse@shIterator.xyz * (1.0 / ((lightAttenuation@shIterator.y) + (lightAttenuation@shIterator.z * d) + (lightAttenuation@shIterator.w * d * d))) * max(dot(normal, lightDir), 0) * shadow;
#else
diffuse += lightDiffuse@shIterator.xyz * (1.0 / ((lightAttenuation@shIterator.y) + (lightAttenuation@shIterator.z * d) + (lightAttenuation@shIterator.w * d * d))) * max(dot(normal, lightDir), 0);
#endif
#else
diffuse += lightDiffuse@shIterator.xyz * (1.0 / ((lightAttenuation@shIterator.y) + (lightAttenuation@shIterator.z * d) + (lightAttenuation@shIterator.w * d * d))) * max(dot(normal, lightDir), 0);
shOutputColour(0).xyz *= (lightResult - directionalResult * (1.0-shadow));
#endif
@shEndForeach
shOutputColour(0).xyz *= (lightAmbient.xyz + diffuse);
#if FOG
float fogValue = shSaturate((depth - fogParams.y) * fogParams.w);

@ -56,7 +56,7 @@ void ImageRotate::rotate(const std::string& sourceImage, const std::string& dest
TEX_TYPE_2D,
width, height,
0,
PF_FLOAT16_RGBA,
PF_A8B8G8R8,
TU_RENDERTARGET);
RenderTarget* rtt = destTextureRot->getBuffer()->getRenderTarget();
@ -75,7 +75,7 @@ void ImageRotate::rotate(const std::string& sourceImage, const std::string& dest
TEX_TYPE_2D,
width, height,
0,
PF_FLOAT16_RGBA,
PF_A8B8G8R8,
Ogre::TU_STATIC);
destTexture->getBuffer()->blit(destTextureRot->getBuffer());

Loading…
Cancel
Save