Terrain rendering

c++11
scrawl 10 years ago
parent 10f938ff87
commit cdd0623009

@ -174,7 +174,7 @@ if (${OGRE_VERSION} VERSION_LESS "1.9")
message(FATAL_ERROR "OpenMW requires Ogre 1.9 or later, please install the latest stable version from http://ogre3d.org") message(FATAL_ERROR "OpenMW requires Ogre 1.9 or later, please install the latest stable version from http://ogre3d.org")
endif() endif()
find_package(OpenSceneGraph 3.2.0 REQUIRED osgDB osgViewer osgGA osgAnimation osgParticle osgQt osgUtil) find_package(OpenSceneGraph 3.2.0 REQUIRED osgDB osgViewer osgGA osgAnimation osgParticle osgQt osgUtil osgFX)
include_directories(${OPENSCENEGRAPH_INCLUDE_DIRS}) include_directories(${OPENSCENEGRAPH_INCLUDE_DIRS})
find_package(MyGUI REQUIRED) find_package(MyGUI REQUIRED)

@ -488,6 +488,11 @@ Resource::ResourceSystem* CSMWorld::Data::getResourceSystem()
return &mResourceSystem; return &mResourceSystem;
} }
const Resource::ResourceSystem* CSMWorld::Data::getResourceSystem() const
{
return &mResourceSystem;
}
const CSMWorld::IdCollection<ESM::Global>& CSMWorld::Data::getGlobals() const const CSMWorld::IdCollection<ESM::Global>& CSMWorld::Data::getGlobals() const
{ {
return mGlobals; return mGlobals;

@ -140,6 +140,8 @@ namespace CSMWorld
Resource::ResourceSystem* getResourceSystem(); Resource::ResourceSystem* getResourceSystem();
const Resource::ResourceSystem* getResourceSystem() const;
const IdCollection<ESM::Global>& getGlobals() const; const IdCollection<ESM::Global>& getGlobals() const;
IdCollection<ESM::Global>& getGlobals(); IdCollection<ESM::Global>& getGlobals();

@ -64,7 +64,6 @@ CSVRender::Cell::Cell (CSMWorld::Data& data, osg::Group* rootNode, const std::st
addObjects (0, rows-1); addObjects (0, rows-1);
/*
const CSMWorld::IdCollection<CSMWorld::Land>& land = mData.getLand(); const CSMWorld::IdCollection<CSMWorld::Land>& land = mData.getLand();
int landIndex = land.searchId(mId); int landIndex = land.searchId(mId);
if (landIndex != -1) if (landIndex != -1)
@ -72,27 +71,18 @@ CSVRender::Cell::Cell (CSMWorld::Data& data, osg::Group* rootNode, const std::st
const ESM::Land* esmLand = land.getRecord(mId).get().mLand.get(); const ESM::Land* esmLand = land.getRecord(mId).get().mLand.get();
if(esmLand && esmLand->mDataTypes&ESM::Land::DATA_VHGT) if(esmLand && esmLand->mDataTypes&ESM::Land::DATA_VHGT)
{ {
mTerrain.reset(new Terrain::TerrainGrid(sceneManager, new TerrainStorage(mData), Element_Terrain, true, mTerrain.reset(new Terrain::TerrainGrid(mCellNode, data.getResourceSystem(), NULL, new TerrainStorage(mData), Element_Terrain<<1));
Terrain::Align_XY));
mTerrain->loadCell(esmLand->mX, mTerrain->loadCell(esmLand->mX,
esmLand->mY); esmLand->mY);
//float verts = ESM::Land::LAND_SIZE;
//float worldsize = ESM::Land::REAL_SIZE;
mX = esmLand->mX; mX = esmLand->mX;
mY = esmLand->mY; mY = esmLand->mY;
//mPhysics->addHeightField(sceneManager,
// esmLand->mLandData->mHeights, mX, mY, 0, worldsize / (verts-1), verts);
} }
} }
*/
} }
CSVRender::Cell::~Cell() CSVRender::Cell::~Cell()
{ {
//if (mTerrain.get())
// mPhysics->removeHeightField(mSceneMgr, mX, mY);
for (std::map<std::string, Object *>::iterator iter (mObjects.begin()); for (std::map<std::string, Object *>::iterator iter (mObjects.begin());
iter!=mObjects.end(); ++iter) iter!=mObjects.end(); ++iter)
delete iter->second; delete iter->second;
@ -221,11 +211,3 @@ bool CSVRender::Cell::referenceAdded (const QModelIndex& parent, int start, int
return addObjects (start, end); return addObjects (start, end);
} }
float CSVRender::Cell::getTerrainHeightAt(const Ogre::Vector3 &pos) const
{
if(mTerrain.get() != NULL)
return mTerrain->getHeightAt(pos);
else
return -std::numeric_limits<float>::max();
}

@ -75,8 +75,6 @@ namespace CSVRender
/// \return Did this call result in a modification of the visual representation of /// \return Did this call result in a modification of the visual representation of
/// this cell? /// this cell?
bool referenceAdded (const QModelIndex& parent, int start, int end); bool referenceAdded (const QModelIndex& parent, int start, int end);
float getTerrainHeightAt(const Ogre::Vector3 &pos) const;
}; };
} }

@ -4,7 +4,8 @@ namespace CSVRender
{ {
TerrainStorage::TerrainStorage(const CSMWorld::Data &data) TerrainStorage::TerrainStorage(const CSMWorld::Data &data)
: mData(data) : ESMTerrain::Storage(data.getResourceSystem()->getVFS())
, mData(data)
{ {
} }

@ -22,8 +22,8 @@ source_group(game FILES ${GAME} ${GAME_HEADER})
add_openmw_dir (mwrender add_openmw_dir (mwrender
actors objects renderingmanager animation rotatecontroller sky npcanimation vismask actors objects renderingmanager animation rotatecontroller sky npcanimation vismask
creatureanimation effectmanager util renderinginterface pathgrid rendermode weaponanimation creatureanimation effectmanager util renderinginterface pathgrid rendermode weaponanimation
bulletdebugdraw globalmap characterpreview camera localmap water bulletdebugdraw globalmap characterpreview camera localmap water terrainstorage
# occlusionquery shadows ripplesimulation terrainstorage # occlusionquery shadows ripplesimulation
) )
add_openmw_dir (mwinput add_openmw_dir (mwinput

@ -157,7 +157,7 @@ osg::ref_ptr<osg::Camera> LocalMap::createOrthographicCamera(float x, float y, f
camera->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); camera->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
camera->setRenderOrder(osg::Camera::PRE_RENDER); camera->setRenderOrder(osg::Camera::PRE_RENDER);
camera->setCullMask(MWRender::Mask_Scene|MWRender::Mask_Water); camera->setCullMask(Mask_Scene|Mask_Water|Mask_Terrain);
camera->setNodeMask(Mask_RenderToTexture); camera->setNodeMask(Mask_RenderToTexture);
osg::ref_ptr<osg::StateSet> stateset = new osg::StateSet; osg::ref_ptr<osg::StateSet> stateset = new osg::StateSet;
@ -309,7 +309,7 @@ void LocalMap::requestExteriorMap(MWWorld::CellStore* cell)
void LocalMap::requestInteriorMap(MWWorld::CellStore* cell) void LocalMap::requestInteriorMap(MWWorld::CellStore* cell)
{ {
osg::ComputeBoundsVisitor computeBoundsVisitor; osg::ComputeBoundsVisitor computeBoundsVisitor;
computeBoundsVisitor.setTraversalMask(Mask_Scene); computeBoundsVisitor.setTraversalMask(Mask_Scene|Mask_Terrain);
mSceneRoot->accept(computeBoundsVisitor); mSceneRoot->accept(computeBoundsVisitor);
osg::BoundingBox bounds = computeBoundsVisitor.getBoundingBox(); osg::BoundingBox bounds = computeBoundsVisitor.getBoundingBox();

@ -26,6 +26,8 @@
#include <components/sceneutil/lightmanager.hpp> #include <components/sceneutil/lightmanager.hpp>
#include <components/sceneutil/statesetupdater.hpp> #include <components/sceneutil/statesetupdater.hpp>
#include <components/terrain/terraingrid.hpp>
#include <components/esm/loadcell.hpp> #include <components/esm/loadcell.hpp>
#include "sky.hpp" #include "sky.hpp"
@ -35,6 +37,7 @@
#include "pathgrid.hpp" #include "pathgrid.hpp"
#include "camera.hpp" #include "camera.hpp"
#include "water.hpp" #include "water.hpp"
#include "terrainstorage.hpp"
namespace MWRender namespace MWRender
{ {
@ -132,7 +135,10 @@ namespace MWRender
mEffectManager.reset(new EffectManager(lightRoot, mResourceSystem)); mEffectManager.reset(new EffectManager(lightRoot, mResourceSystem));
mWater.reset(new Water(lightRoot, mResourceSystem)); mWater.reset(new Water(lightRoot, mResourceSystem, mViewer->getIncrementalCompileOperation()));
mTerrain.reset(new Terrain::TerrainGrid(lightRoot, mResourceSystem, mViewer->getIncrementalCompileOperation(),
new TerrainStorage(mResourceSystem->getVFS(), false), Mask_Terrain));
mCamera.reset(new Camera(mViewer->getCamera())); mCamera.reset(new Camera(mViewer->getCamera()));
@ -236,12 +242,18 @@ namespace MWRender
mPathgrid->addCell(store); mPathgrid->addCell(store);
mWater->changeCell(store); mWater->changeCell(store);
if (store->getCell()->isExterior())
mTerrain->loadCell(store->getCell()->getGridX(), store->getCell()->getGridY());
} }
void RenderingManager::removeCell(const MWWorld::CellStore *store) void RenderingManager::removeCell(const MWWorld::CellStore *store)
{ {
mPathgrid->removeCell(store); mPathgrid->removeCell(store);
mObjects->removeCell(store); mObjects->removeCell(store);
if (store->getCell()->isExterior())
mTerrain->unloadCell(store->getCell()->getGridX(), store->getCell()->getGridY());
} }
void RenderingManager::setSkyEnabled(bool enabled) void RenderingManager::setSkyEnabled(bool enabled)
@ -482,7 +494,6 @@ namespace MWRender
void RenderingManager::clear() void RenderingManager::clear()
{ {
//mLocalMap->clear();
notifyWorldSpaceChanged(); notifyWorldSpaceChanged();
} }
@ -595,6 +606,11 @@ namespace MWRender
return mNearClip; return mNearClip;
} }
float RenderingManager::getTerrainHeightAt(const osg::Vec3f &pos)
{
return mTerrain->getHeightAt(pos);
}
bool RenderingManager::vanityRotateCamera(const float *rot) bool RenderingManager::vanityRotateCamera(const float *rot)
{ {
if(!mCamera->isVanityOrPreviewModeEnabled()) if(!mCamera->isVanityOrPreviewModeEnabled())

@ -32,6 +32,11 @@ namespace ESM
struct Cell; struct Cell;
} }
namespace Terrain
{
class World;
}
namespace MWRender namespace MWRender
{ {
@ -119,6 +124,8 @@ namespace MWRender
float getNearClipDistance() const; float getNearClipDistance() const;
float getTerrainHeightAt(const osg::Vec3f& pos);
// camera stuff // camera stuff
bool vanityRotateCamera(const float *rot); bool vanityRotateCamera(const float *rot);
void setCameraDistance(float dist, bool adjust, bool override); void setCameraDistance(float dist, bool adjust, bool override);
@ -147,6 +154,7 @@ namespace MWRender
std::auto_ptr<Pathgrid> mPathgrid; std::auto_ptr<Pathgrid> mPathgrid;
std::auto_ptr<Objects> mObjects; std::auto_ptr<Objects> mObjects;
std::auto_ptr<Water> mWater; std::auto_ptr<Water> mWater;
std::auto_ptr<Terrain::World> mTerrain;
std::auto_ptr<SkyManager> mSky; std::auto_ptr<SkyManager> mSky;
std::auto_ptr<EffectManager> mEffectManager; std::auto_ptr<EffectManager> mEffectManager;
std::auto_ptr<NpcAnimation> mPlayerAnimation; std::auto_ptr<NpcAnimation> mPlayerAnimation;

@ -9,7 +9,8 @@
namespace MWRender namespace MWRender
{ {
TerrainStorage::TerrainStorage(bool preload) TerrainStorage::TerrainStorage(const VFS::Manager* vfs, bool preload)
: ESMTerrain::Storage(vfs)
{ {
if (preload) if (preload)
{ {

@ -16,7 +16,7 @@ namespace MWRender
///@param preload Preload all Land records at startup? If using the multithreaded terrain component, this ///@param preload Preload all Land records at startup? If using the multithreaded terrain component, this
/// should be set to "true" in order to avoid race conditions. /// should be set to "true" in order to avoid race conditions.
TerrainStorage(bool preload); TerrainStorage(const VFS::Manager* vfs, bool preload);
/// Get bounds of the whole terrain in cell units /// Get bounds of the whole terrain in cell units
virtual void getBounds(float& minX, float& maxX, float& minY, float& maxY); virtual void getBounds(float& minX, float& maxX, float& minY, float& maxY);

@ -16,13 +16,14 @@ namespace MWRender
Mask_Player = (1<<4), Mask_Player = (1<<4),
Mask_Sky = (1<<5), Mask_Sky = (1<<5),
Mask_Water = (1<<6), Mask_Water = (1<<6),
Mask_Terrain = (1<<7),
// top level masks // top level masks
Mask_Scene = (1<<7), Mask_Scene = (1<<8),
Mask_GUI = (1<<8), Mask_GUI = (1<<9),
// Set on cameras within the main scene graph // Set on cameras within the main scene graph
Mask_RenderToTexture = (1<<9) Mask_RenderToTexture = (1<<10)
// reserved: (1<<16) for SceneUtil::Mask_Lit // reserved: (1<<16) for SceneUtil::Mask_Lit
}; };

@ -9,6 +9,8 @@
#include <osg/PositionAttitudeTransform> #include <osg/PositionAttitudeTransform>
#include <osg/Depth> #include <osg/Depth>
#include <osgUtil/IncrementalCompileOperation>
#include <components/resource/resourcesystem.hpp> #include <components/resource/resourcesystem.hpp>
#include <components/resource/texturemanager.hpp> #include <components/resource/texturemanager.hpp>
@ -105,7 +107,7 @@ namespace MWRender
// -------------------------------------------------------------------------------------------------------------------------------- // --------------------------------------------------------------------------------------------------------------------------------
Water::Water(osg::Group *parent, Resource::ResourceSystem *resourceSystem) Water::Water(osg::Group *parent, Resource::ResourceSystem *resourceSystem, osgUtil::IncrementalCompileOperation *ico)
: mParent(parent) : mParent(parent)
, mResourceSystem(resourceSystem) , mResourceSystem(resourceSystem)
, mEnabled(true) , mEnabled(true)
@ -118,6 +120,9 @@ Water::Water(osg::Group *parent, Resource::ResourceSystem *resourceSystem)
geode->addDrawable(waterGeom); geode->addDrawable(waterGeom);
geode->setNodeMask(Mask_Water); geode->setNodeMask(Mask_Water);
if (ico)
ico->add(geode);
createWaterStateSet(mResourceSystem, geode); createWaterStateSet(mResourceSystem, geode);
mWaterNode = new osg::PositionAttitudeTransform; mWaterNode = new osg::PositionAttitudeTransform;

@ -11,6 +11,11 @@ namespace osg
class PositionAttitudeTransform; class PositionAttitudeTransform;
} }
namespace osgUtil
{
class IncrementalCompileOperation;
}
namespace Resource namespace Resource
{ {
class ResourceSystem; class ResourceSystem;
@ -27,6 +32,7 @@ namespace MWRender
osg::ref_ptr<osg::Group> mParent; osg::ref_ptr<osg::Group> mParent;
osg::ref_ptr<osg::PositionAttitudeTransform> mWaterNode; osg::ref_ptr<osg::PositionAttitudeTransform> mWaterNode;
Resource::ResourceSystem* mResourceSystem; Resource::ResourceSystem* mResourceSystem;
osg::ref_ptr<osgUtil::IncrementalCompileOperation> mIncrementalCompileOperation;
bool mEnabled; bool mEnabled;
bool mToggled; bool mToggled;
@ -36,7 +42,7 @@ namespace MWRender
void updateVisible(); void updateVisible();
public: public:
Water(osg::Group* parent, Resource::ResourceSystem* resourceSystem); Water(osg::Group* parent, Resource::ResourceSystem* resourceSystem, osgUtil::IncrementalCompileOperation* ico);
~Water(); ~Water();
void setEnabled(bool enabled); void setEnabled(bool enabled);

@ -1298,7 +1298,9 @@ namespace MWWorld
return; return;
} }
float terrainHeight = -FLT_MAX;//mRendering->getTerrainHeightAt(Ogre::Vector3(pos.pos)); float terrainHeight = -std::numeric_limits<float>::max();
if (isCellExterior())
terrainHeight = mRendering->getTerrainHeightAt(pos.asVec3());
if (pos.pos[2] < terrainHeight) if (pos.pos[2] < terrainHeight)
pos.pos[2] = terrainHeight; pos.pos[2] = terrainHeight;

@ -104,10 +104,8 @@ add_component_dir (translation
translation translation
) )
#add_definitions(-DTERRAIN_USE_SHADER=1)
add_definitions(-DTERRAIN_USE_SHADER=0)
add_component_dir (terrain add_component_dir (terrain
quadtreenode chunk world defaultworld terraingrid storage material buffercache defs storage world buffercache defs terraingrid material
) )
add_component_dir (loadinglistener add_component_dir (loadinglistener

@ -80,7 +80,7 @@ struct Land
VNML mNormals[LAND_NUM_VERTS * 3]; VNML mNormals[LAND_NUM_VERTS * 3];
uint16_t mTextures[LAND_NUM_TEXTURES]; uint16_t mTextures[LAND_NUM_TEXTURES];
char mColours[3 * LAND_NUM_VERTS]; unsigned char mColours[3 * LAND_NUM_VERTS];
int mDataTypes; int mDataTypes;
// low-LOD heightmap (used for rendering the global map) // low-LOD heightmap (used for rendering the global map)

@ -1,34 +1,38 @@
#include "storage.hpp" #include "storage.hpp"
#include <OgreVector2.h> #include <set>
#include <OgreTextureManager.h>
#include <OgreStringConverter.h> #include <osg/Image>
#include <OgreRenderSystem.h> #include <osg/Plane>
#include <OgreResourceGroupManager.h>
#include <OgreResourceBackgroundQueue.h> #include <iostream>
#include <OgreRoot.h>
#include <boost/algorithm/string.hpp> #include <boost/algorithm/string.hpp>
#include <components/terrain/quadtreenode.hpp>
#include <components/misc/resourcehelpers.hpp> #include <components/misc/resourcehelpers.hpp>
#include <components/vfs/manager.hpp>
namespace ESMTerrain namespace ESMTerrain
{ {
bool Storage::getMinMaxHeights(float size, const Ogre::Vector2 &center, float &min, float &max) Storage::Storage(const VFS::Manager *vfs)
: mVFS(vfs)
{
}
bool Storage::getMinMaxHeights(float size, const osg::Vec2f &center, float &min, float &max)
{ {
assert (size <= 1 && "Storage::getMinMaxHeights, chunk size should be <= 1 cell"); 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 /// \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); osg::Vec2f origin = center - osg::Vec2f(size/2.f, size/2.f);
assert(origin.x == (int) origin.x); assert(origin.x == (int) origin.x());
assert(origin.y == (int) origin.y); assert(origin.y == (int) origin.y());
int cellX = static_cast<int>(origin.x); int cellX = static_cast<int>(origin.x());
int cellY = static_cast<int>(origin.y); int cellY = static_cast<int>(origin.y());
const ESM::Land* land = getLand(cellX, cellY); const ESM::Land* land = getLand(cellX, cellY);
if (!land || !(land->mDataTypes&ESM::Land::DATA_VHGT)) if (!land || !(land->mDataTypes&ESM::Land::DATA_VHGT))
@ -50,7 +54,7 @@ namespace ESMTerrain
return true; return true;
} }
void Storage::fixNormal (Ogre::Vector3& normal, int cellX, int cellY, int col, int row) void Storage::fixNormal (osg::Vec3f& normal, int cellX, int cellY, int col, int row)
{ {
while (col >= ESM::Land::LAND_SIZE-1) while (col >= ESM::Land::LAND_SIZE-1)
{ {
@ -75,27 +79,27 @@ namespace ESMTerrain
ESM::Land* land = getLand(cellX, cellY); ESM::Land* land = getLand(cellX, cellY);
if (land && land->mDataTypes&ESM::Land::DATA_VNML) if (land && land->mDataTypes&ESM::Land::DATA_VNML)
{ {
normal.x = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3]; 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.y() = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+1];
normal.z = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+2]; normal.z() = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+2];
normal.normalise(); normal.normalize();
} }
else else
normal = Ogre::Vector3(0,0,1); normal = osg::Vec3f(0,0,1);
} }
void Storage::averageNormal(Ogre::Vector3 &normal, int cellX, int cellY, int col, int row) void Storage::averageNormal(osg::Vec3f &normal, int cellX, int cellY, int col, int row)
{ {
Ogre::Vector3 n1,n2,n3,n4; osg::Vec3f n1,n2,n3,n4;
fixNormal(n1, cellX, cellY, col+1, row); fixNormal(n1, cellX, cellY, col+1, row);
fixNormal(n2, cellX, cellY, col-1, row); fixNormal(n2, cellX, cellY, col-1, row);
fixNormal(n3, cellX, cellY, col, row+1); fixNormal(n3, cellX, cellY, col, row+1);
fixNormal(n4, cellX, cellY, col, row-1); fixNormal(n4, cellX, cellY, col, row-1);
normal = (n1+n2+n3+n4); normal = (n1+n2+n3+n4);
normal.normalise(); normal.normalize();
} }
void Storage::fixColour (Ogre::ColourValue& color, int cellX, int cellY, int col, int row) void Storage::fixColour (osg::Vec4f& color, int cellX, int cellY, int col, int row)
{ {
if (col == ESM::Land::LAND_SIZE-1) if (col == ESM::Land::LAND_SIZE-1)
{ {
@ -110,42 +114,42 @@ namespace ESMTerrain
ESM::Land* land = getLand(cellX, cellY); ESM::Land* land = getLand(cellX, cellY);
if (land && land->mDataTypes&ESM::Land::DATA_VCLR) if (land && land->mDataTypes&ESM::Land::DATA_VCLR)
{ {
color.r = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3] / 255.f; 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.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; color.b() = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3+2] / 255.f;
} }
else else
{ {
color.r = 1; color.r() = 1;
color.g = 1; color.g() = 1;
color.b = 1; color.b() = 1;
} }
} }
void Storage::fillVertexBuffers (int lodLevel, float size, const Ogre::Vector2& center, Terrain::Alignment align, void Storage::fillVertexBuffers (int lodLevel, float size, const osg::Vec2f& center,
std::vector<float>& positions, osg::ref_ptr<osg::Vec3Array> positions,
std::vector<float>& normals, osg::ref_ptr<osg::Vec3Array> normals,
std::vector<Ogre::uint8>& colours) osg::ref_ptr<osg::Vec4Array> colours)
{ {
// LOD level n means every 2^n-th vertex is kept // LOD level n means every 2^n-th vertex is kept
size_t increment = 1 << lodLevel; size_t increment = 1 << lodLevel;
Ogre::Vector2 origin = center - Ogre::Vector2(size/2.f, size/2.f); osg::Vec2f origin = center - osg::Vec2f(size/2.f, size/2.f);
assert(origin.x == (int) origin.x); assert(origin.x() == (int) origin.x());
assert(origin.y == (int) origin.y); assert(origin.y() == (int) origin.y());
int startX = static_cast<int>(origin.x); int startX = static_cast<int>(origin.x());
int startY = static_cast<int>(origin.y); int startY = static_cast<int>(origin.y());
size_t numVerts = static_cast<size_t>(size*(ESM::Land::LAND_SIZE - 1) / increment + 1); size_t numVerts = static_cast<size_t>(size*(ESM::Land::LAND_SIZE - 1) / increment + 1);
colours.resize(numVerts*numVerts*4); positions->resize(numVerts*numVerts);
positions.resize(numVerts*numVerts*3); normals->resize(numVerts*numVerts);
normals.resize(numVerts*numVerts*3); colours->resize(numVerts*numVerts);
Ogre::Vector3 normal; osg::Vec3f normal;
Ogre::ColourValue color; osg::Vec4f color;
float vertY = 0; float vertY = 0;
float vertX = 0; float vertX = 0;
@ -175,22 +179,24 @@ namespace ESMTerrain
vertX = vertX_; vertX = vertX_;
for (int row=rowStart; row<ESM::Land::LAND_SIZE; row += increment) for (int row=rowStart; row<ESM::Land::LAND_SIZE; row += increment)
{ {
positions[static_cast<unsigned int>(vertX*numVerts * 3 + vertY * 3)] = ((vertX / float(numVerts - 1) - 0.5f) * size * 8192); float height = -2048;
positions[static_cast<unsigned int>(vertX*numVerts * 3 + vertY * 3 + 1)] = ((vertY / float(numVerts - 1) - 0.5f) * size * 8192);
if (land) if (land)
positions[static_cast<unsigned int>(vertX*numVerts * 3 + vertY * 3 + 2)] = land->mLandData->mHeights[col*ESM::Land::LAND_SIZE + row]; height = land->mLandData->mHeights[col*ESM::Land::LAND_SIZE + row];
else
positions[static_cast<unsigned int>(vertX*numVerts * 3 + vertY * 3 + 2)] = -2048; (*positions)[static_cast<unsigned int>(vertX*numVerts + vertY)]
= osg::Vec3f((vertX / float(numVerts - 1) - 0.5f) * size * 8192,
(vertY / float(numVerts - 1) - 0.5f) * size * 8192,
height);
if (land && land->mDataTypes&ESM::Land::DATA_VNML) if (land && land->mDataTypes&ESM::Land::DATA_VNML)
{ {
normal.x = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3]; 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.y() = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+1];
normal.z = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+2]; normal.z() = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+2];
normal.normalise(); normal.normalize();
} }
else else
normal = Ogre::Vector3(0,0,1); normal = osg::Vec3f(0,0,1);
// Normals apparently don't connect seamlessly between cells // Normals apparently don't connect seamlessly between cells
if (col == ESM::Land::LAND_SIZE-1 || row == ESM::Land::LAND_SIZE-1) if (col == ESM::Land::LAND_SIZE-1 || row == ESM::Land::LAND_SIZE-1)
@ -200,33 +206,30 @@ namespace ESMTerrain
if ((row == 0 || row == ESM::Land::LAND_SIZE-1) && (col == 0 || col == ESM::Land::LAND_SIZE-1)) if ((row == 0 || row == ESM::Land::LAND_SIZE-1) && (col == 0 || col == ESM::Land::LAND_SIZE-1))
averageNormal(normal, cellX, cellY, col, row); averageNormal(normal, cellX, cellY, col, row);
assert(normal.z > 0); assert(normal.z() > 0);
normals[static_cast<unsigned int>(vertX*numVerts * 3 + vertY * 3)] = normal.x; (*normals)[static_cast<unsigned int>(vertX*numVerts + vertY)] = normal;
normals[static_cast<unsigned int>(vertX*numVerts * 3 + vertY * 3 + 1)] = normal.y;
normals[static_cast<unsigned int>(vertX*numVerts * 3 + vertY * 3 + 2)] = normal.z;
if (land && land->mDataTypes&ESM::Land::DATA_VCLR) if (land && land->mDataTypes&ESM::Land::DATA_VCLR)
{ {
color.r = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3] / 255.f; 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.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; color.b() = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3+2] / 255.f;
} }
else else
{ {
color.r = 1; color.r() = 1;
color.g = 1; color.g() = 1;
color.b = 1; color.b() = 1;
} }
// Unlike normals, colors mostly connect seamlessly between cells, but not always... // Unlike normals, colors mostly connect seamlessly between cells, but not always...
if (col == ESM::Land::LAND_SIZE-1 || row == ESM::Land::LAND_SIZE-1) if (col == ESM::Land::LAND_SIZE-1 || row == ESM::Land::LAND_SIZE-1)
fixColour(color, cellX, cellY, col, row); fixColour(color, cellX, cellY, col, row);
color.a = 1; color.a() = 1;
Ogre::uint32 rsColor;
Ogre::Root::getSingleton().getRenderSystem()->convertColourValue(color, &rsColor); (*colours)[static_cast<unsigned int>(vertX*numVerts + vertY)] = color;
memcpy(&colours[static_cast<unsigned int>(vertX*numVerts * 4 + vertY * 4)], &rsColor, sizeof(Ogre::uint32));
++vertX; ++vertX;
} }
@ -281,39 +284,22 @@ namespace ESMTerrain
// NB: All vtex ids are +1 compared to the ltex ids // NB: All vtex ids are +1 compared to the ltex ids
const ESM::LandTexture* ltex = getLandTexture(id.first-1, id.second); const ESM::LandTexture* ltex = getLandTexture(id.first-1, id.second);
//TODO this is needed due to MWs messed up texture handling // this is needed due to MWs messed up texture handling
assert(0 && "no vfs here yet"); std::string texture = Misc::ResourceHelpers::correctTexturePath(ltex->mTexture, mVFS);
std::string texture = ltex->mTexture; //Misc::ResourceHelpers::correctTexturePath(ltex->mTexture);
return texture; return texture;
} }
void Storage::getBlendmaps (const std::vector<Terrain::QuadTreeNode*>& nodes, std::vector<Terrain::LayerCollection>& out, bool pack) void Storage::getBlendmaps(float chunkSize, const osg::Vec2f &chunkCenter,
{ bool pack, ImageVector &blendmaps, std::vector<Terrain::LayerInfo> &layerList)
for (std::vector<Terrain::QuadTreeNode*>::const_iterator it = nodes.begin(); it != nodes.end(); ++it)
{
out.push_back(Terrain::LayerCollection());
out.back().mTarget = *it;
getBlendmapsImpl(static_cast<float>((*it)->getSize()), (*it)->getCenter(), pack, out.back().mBlendmaps, out.back().mLayers);
}
}
void Storage::getBlendmaps(float chunkSize, const Ogre::Vector2 &chunkCenter,
bool pack, std::vector<Ogre::PixelBox> &blendmaps, std::vector<Terrain::LayerInfo> &layerList)
{
getBlendmapsImpl(chunkSize, chunkCenter, pack, blendmaps, layerList);
}
void Storage::getBlendmapsImpl(float chunkSize, const Ogre::Vector2 &chunkCenter,
bool pack, std::vector<Ogre::PixelBox> &blendmaps, std::vector<Terrain::LayerInfo> &layerList)
{ {
// TODO - blending isn't completely right yet; the blending radius appears to be // TODO - blending isn't completely right yet; the blending radius appears to be
// different at a cell transition (2 vertices, not 4), so we may need to create a larger blendmap // different at a cell transition (2 vertices, not 4), so we may need to create a larger blendmap
// and interpolate the rest of the cell by hand? :/ // and interpolate the rest of the cell by hand? :/
Ogre::Vector2 origin = chunkCenter - Ogre::Vector2(chunkSize/2.f, chunkSize/2.f); osg::Vec2f origin = chunkCenter - osg::Vec2f(chunkSize/2.f, chunkSize/2.f);
int cellX = static_cast<int>(origin.x); int cellX = static_cast<int>(origin.x());
int cellY = static_cast<int>(origin.y); int cellY = static_cast<int>(origin.y());
// Save the used texture indices so we know the total number of textures // Save the used texture indices so we know the total number of textures
// and number of required blend maps // and number of required blend maps
@ -353,11 +339,11 @@ namespace ESMTerrain
for (int i=0; i<numBlendmaps; ++i) for (int i=0; i<numBlendmaps; ++i)
{ {
Ogre::PixelFormat format = pack ? Ogre::PF_A8B8G8R8 : Ogre::PF_A8; GLenum format = pack ? GL_RGBA : GL_ALPHA;
Ogre::uchar* pData = osg::ref_ptr<osg::Image> image (new osg::Image);
OGRE_ALLOC_T(Ogre::uchar, blendmapSize*blendmapSize*channels, Ogre::MEMCATEGORY_GENERAL); image->allocateImage(blendmapSize, blendmapSize, 1, format, GL_UNSIGNED_BYTE);
memset(pData, 0, blendmapSize*blendmapSize*channels); unsigned char* pData = image->data();
for (int y=0; y<blendmapSize; ++y) for (int y=0; y<blendmapSize; ++y)
{ {
@ -374,14 +360,15 @@ namespace ESMTerrain
pData[y*blendmapSize*channels + x*channels + channel] = 0; pData[y*blendmapSize*channels + x*channels + channel] = 0;
} }
} }
blendmaps.push_back(Ogre::PixelBox(blendmapSize, blendmapSize, 1, format, pData));
blendmaps.push_back(image);
} }
} }
float Storage::getHeightAt(const Ogre::Vector3 &worldPos) float Storage::getHeightAt(const osg::Vec3f &worldPos)
{ {
int cellX = static_cast<int>(std::floor(worldPos.x / 8192.f)); int cellX = static_cast<int>(std::floor(worldPos.x() / 8192.f));
int cellY = static_cast<int>(std::floor(worldPos.y / 8192.f)); int cellY = static_cast<int>(std::floor(worldPos.y() / 8192.f));
ESM::Land* land = getLand(cellX, cellY); ESM::Land* land = getLand(cellX, cellY);
if (!land || !(land->mDataTypes&ESM::Land::DATA_VHGT)) if (!land || !(land->mDataTypes&ESM::Land::DATA_VHGT))
@ -390,8 +377,8 @@ namespace ESMTerrain
// Mostly lifted from Ogre::Terrain::getHeightAtTerrainPosition // Mostly lifted from Ogre::Terrain::getHeightAtTerrainPosition
// Normalized position in the cell // Normalized position in the cell
float nX = (worldPos.x - (cellX * 8192))/8192.f; float nX = (worldPos.x() - (cellX * 8192))/8192.f;
float nY = (worldPos.y - (cellY * 8192))/8192.f; float nY = (worldPos.y() - (cellY * 8192))/8192.f;
// get left / bottom points (rounded down) // get left / bottom points (rounded down)
float factor = ESM::Land::LAND_SIZE - 1.0f; float factor = ESM::Land::LAND_SIZE - 1.0f;
@ -423,22 +410,23 @@ namespace ESMTerrain
*/ */
// Build all 4 positions in normalized cell space, using point-sampled height // Build all 4 positions in normalized cell space, using point-sampled height
Ogre::Vector3 v0 (startXTS, startYTS, getVertexHeight(land, startX, startY) / 8192.f); osg::Vec3f v0 (startXTS, startYTS, getVertexHeight(land, startX, startY) / 8192.f);
Ogre::Vector3 v1 (endXTS, startYTS, getVertexHeight(land, endX, startY) / 8192.f); osg::Vec3f v1 (endXTS, startYTS, getVertexHeight(land, endX, startY) / 8192.f);
Ogre::Vector3 v2 (endXTS, endYTS, getVertexHeight(land, endX, endY) / 8192.f); osg::Vec3f v2 (endXTS, endYTS, getVertexHeight(land, endX, endY) / 8192.f);
Ogre::Vector3 v3 (startXTS, endYTS, getVertexHeight(land, startX, endY) / 8192.f); osg::Vec3f v3 (startXTS, endYTS, getVertexHeight(land, startX, endY) / 8192.f);
// define this plane in terrain space // define this plane in terrain space
Ogre::Plane plane; osg::Plane plane;
// (At the moment, all rows have the same triangle alignment) // FIXME: deal with differing triangle alignment
if (true) if (true)
{ {
// odd row // odd row
bool secondTri = ((1.0 - yParam) > xParam); bool secondTri = ((1.0 - yParam) > xParam);
if (secondTri) if (secondTri)
plane.redefine(v0, v1, v3); plane = osg::Plane(v0, v1, v3);
else else
plane.redefine(v1, v2, v3); plane = osg::Plane(v1, v2, v3);
} }
/*
else else
{ {
// even row // even row
@ -448,11 +436,12 @@ namespace ESMTerrain
else else
plane.redefine(v0, v1, v2); plane.redefine(v0, v1, v2);
} }
*/
// Solve plane equation for z // Solve plane equation for z
return (-plane.normal.x * nX return (-plane.getNormal().x() * nX
-plane.normal.y * nY -plane.getNormal().y() * nY
- plane.d) / plane.normal.z * 8192; - plane[3]) / plane.getNormal().z() * 8192;
} }
@ -477,7 +466,7 @@ namespace ESMTerrain
std::string texture_ = texture; std::string texture_ = texture;
boost::replace_last(texture_, ".", "_nh."); boost::replace_last(texture_, ".", "_nh.");
if (Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup(texture_)) if (mVFS->exists(texture_))
{ {
info.mNormalMap = texture_; info.mNormalMap = texture_;
info.mParallax = true; info.mParallax = true;
@ -486,24 +475,18 @@ namespace ESMTerrain
{ {
texture_ = texture; texture_ = texture;
boost::replace_last(texture_, ".", "_n."); boost::replace_last(texture_, ".", "_n.");
if (Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup(texture_)) if (mVFS->exists(texture_))
info.mNormalMap = texture_; info.mNormalMap = texture_;
} }
texture_ = texture; texture_ = texture;
boost::replace_last(texture_, ".", "_diffusespec."); boost::replace_last(texture_, ".", "_diffusespec.");
if (Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup(texture_)) if (mVFS->exists(texture_))
{ {
info.mDiffuseMap = texture_; info.mDiffuseMap = texture_;
info.mSpecular = true; info.mSpecular = true;
} }
// This wasn't cached, so the textures are probably not loaded either.
// Background load them so they are hopefully already loaded once we need them!
Ogre::ResourceBackgroundQueue::getSingleton().load("Texture", info.mDiffuseMap, "General");
if (!info.mNormalMap.empty())
Ogre::ResourceBackgroundQueue::getSingleton().load("Texture", info.mNormalMap, "General");
mLayerInfoMap[texture] = info; mLayerInfoMap[texture] = info;
return info; return info;

@ -6,6 +6,11 @@
#include <components/esm/loadland.hpp> #include <components/esm/loadland.hpp>
#include <components/esm/loadltex.hpp> #include <components/esm/loadltex.hpp>
namespace VFS
{
class Manager;
}
namespace ESMTerrain namespace ESMTerrain
{ {
@ -20,6 +25,7 @@ namespace ESMTerrain
virtual const ESM::LandTexture* getLandTexture(int index, short plugin) = 0; virtual const ESM::LandTexture* getLandTexture(int index, short plugin) = 0;
public: public:
Storage(const VFS::Manager* vfs);
// Not implemented in this class, because we need different Store implementations for game and editor // Not implemented in this class, because we need different Store implementations for game and editor
/// Get bounds of the whole terrain in cell units /// Get bounds of the whole terrain in cell units
@ -33,11 +39,10 @@ namespace ESMTerrain
/// @param min min height will be stored here /// @param min min height will be stored here
/// @param max max height will be stored here /// @param max max height will be stored here
/// @return true if there was data available for this terrain chunk /// @return true if there was data available for this terrain chunk
virtual bool getMinMaxHeights (float size, const Ogre::Vector2& center, float& min, float& max); virtual bool getMinMaxHeights (float size, const osg::Vec2f& center, float& min, float& max);
/// Fill vertex buffers for a terrain chunk. /// Fill vertex buffers for a terrain chunk.
/// @note May be called from background threads. Make sure to only call thread-safe functions from here! /// @note May be called from background threads. Make sure to only call thread-safe functions from here!
/// @note returned colors need to be in render-system specific format! Use RenderSystem::convertColourValue.
/// @note Vertices should be written in row-major order (a row is defined as parallel to the x-axis). /// @note Vertices should be written in row-major order (a row is defined as parallel to the x-axis).
/// The specified positions should be in local space, i.e. relative to the center of the terrain chunk. /// The specified positions should be in local space, i.e. relative to the center of the terrain chunk.
/// @param lodLevel LOD level, 0 = most detailed /// @param lodLevel LOD level, 0 = most detailed
@ -46,10 +51,10 @@ namespace ESMTerrain
/// @param positions buffer to write vertices /// @param positions buffer to write vertices
/// @param normals buffer to write vertex normals /// @param normals buffer to write vertex normals
/// @param colours buffer to write vertex colours /// @param colours buffer to write vertex colours
virtual void fillVertexBuffers (int lodLevel, float size, const Ogre::Vector2& center, Terrain::Alignment align, virtual void fillVertexBuffers (int lodLevel, float size, const osg::Vec2f& center,
std::vector<float>& positions, osg::ref_ptr<osg::Vec3Array> positions,
std::vector<float>& normals, osg::ref_ptr<osg::Vec3Array> normals,
std::vector<Ogre::uint8>& colours); osg::ref_ptr<osg::Vec4Array> colours);
/// Create textures holding layer blend values for a terrain chunk. /// 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 /// @note The terrain chunk shouldn't be larger than one cell since otherwise we might
@ -62,23 +67,11 @@ namespace ESMTerrain
/// can utilize packing, FFP can't. /// can utilize packing, FFP can't.
/// @param blendmaps created blendmaps will be written here /// @param blendmaps created blendmaps will be written here
/// @param layerList names of the layer textures used will be written here /// @param layerList names of the layer textures used will be written here
virtual void getBlendmaps (float chunkSize, const Ogre::Vector2& chunkCenter, bool pack, virtual void getBlendmaps (float chunkSize, const osg::Vec2f& chunkCenter, bool pack,
std::vector<Ogre::PixelBox>& blendmaps, ImageVector& blendmaps,
std::vector<Terrain::LayerInfo>& layerList); std::vector<Terrain::LayerInfo>& layerList);
/// Retrieve pixel data for textures holding layer blend values for terrain chunks and layer texture information. virtual float getHeightAt (const osg::Vec3f& worldPos);
/// This variant is provided to eliminate the overhead of virtual function calls when retrieving a large number of blendmaps at once.
/// @note The terrain chunks 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.
/// @note May be called from background threads.
/// @param nodes A collection of nodes for which to retrieve the aforementioned data
/// @param out Output vector
/// @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.
virtual void getBlendmaps (const std::vector<Terrain::QuadTreeNode*>& nodes, std::vector<Terrain::LayerCollection>& out, bool pack);
virtual float getHeightAt (const Ogre::Vector3& worldPos);
virtual Terrain::LayerInfo getDefaultLayer(); virtual Terrain::LayerInfo getDefaultLayer();
@ -89,9 +82,11 @@ namespace ESMTerrain
virtual int getCellVertices(); virtual int getCellVertices();
private: private:
void fixNormal (Ogre::Vector3& normal, int cellX, int cellY, int col, int row); const VFS::Manager* mVFS;
void fixColour (Ogre::ColourValue& colour, int cellX, int cellY, int col, int row);
void averageNormal (Ogre::Vector3& normal, int cellX, int cellY, int col, int row); void fixNormal (osg::Vec3f& normal, int cellX, int cellY, int col, int row);
void fixColour (osg::Vec4f& colour, int cellX, int cellY, int col, int row);
void averageNormal (osg::Vec3f& normal, int cellX, int cellY, int col, int row);
float getVertexHeight (const ESM::Land* land, int x, int y); float getVertexHeight (const ESM::Land* land, int x, int y);
@ -107,11 +102,6 @@ namespace ESMTerrain
std::map<std::string, Terrain::LayerInfo> mLayerInfoMap; std::map<std::string, Terrain::LayerInfo> mLayerInfoMap;
Terrain::LayerInfo getLayerInfo(const std::string& texture); Terrain::LayerInfo getLayerInfo(const std::string& texture);
// Non-virtual
void getBlendmapsImpl (float chunkSize, const Ogre::Vector2& chunkCenter, bool pack,
std::vector<Ogre::PixelBox>& blendmaps,
std::vector<Terrain::LayerInfo>& layerList);
}; };
} }

@ -21,15 +21,17 @@
*/ */
#include "buffercache.hpp" #include "buffercache.hpp"
#include <OgreHardwareBufferManager.h> #include <cassert>
#include <osg/PrimitiveSet>
#include "defs.hpp" #include "defs.hpp"
namespace namespace
{ {
template <typename IndexType> template <typename IndexArrayType>
Ogre::HardwareIndexBufferSharedPtr createIndexBuffer(unsigned int flags, unsigned int verts, Ogre::HardwareIndexBuffer::IndexType type) osg::ref_ptr<IndexArrayType> createIndexBuffer(unsigned int flags, unsigned int verts)
{ {
// LOD level n means every 2^n-th vertex is kept // LOD level n means every 2^n-th vertex is kept
size_t lodLevel = (flags >> (4*4)); size_t lodLevel = (flags >> (4*4));
@ -42,8 +44,9 @@ Ogre::HardwareIndexBufferSharedPtr createIndexBuffer(unsigned int flags, unsigne
size_t increment = 1 << lodLevel; size_t increment = 1 << lodLevel;
assert(increment < verts); assert(increment < verts);
std::vector<IndexType> indices;
indices.reserve((verts-1)*(verts-1)*2*3 / increment); osg::ref_ptr<IndexArrayType> indices (new IndexArrayType(osg::PrimitiveSet::TRIANGLES));
indices->reserve((verts-1)*(verts-1)*2*3 / increment);
size_t rowStart = 0, colStart = 0, rowEnd = verts-1, colEnd = verts-1; size_t rowStart = 0, colStart = 0, rowEnd = verts-1, colEnd = verts-1;
// If any edge needs stitching we'll skip all edges at this point, // If any edge needs stitching we'll skip all edges at this point,
@ -62,23 +65,23 @@ Ogre::HardwareIndexBufferSharedPtr createIndexBuffer(unsigned int flags, unsigne
// diamond pattern // diamond pattern
if ((row + col%2) % 2 == 1) if ((row + col%2) % 2 == 1)
{ {
indices.push_back(verts*(col+increment)+row); indices->push_back(verts*(col+increment)+row);
indices.push_back(verts*(col+increment)+row+increment); indices->push_back(verts*(col+increment)+row+increment);
indices.push_back(verts*col+row+increment); indices->push_back(verts*col+row+increment);
indices.push_back(verts*col+row); indices->push_back(verts*col+row);
indices.push_back(verts*(col+increment)+row); indices->push_back(verts*(col+increment)+row);
indices.push_back(verts*(col)+row+increment); indices->push_back(verts*(col)+row+increment);
} }
else else
{ {
indices.push_back(verts*col+row); indices->push_back(verts*col+row);
indices.push_back(verts*(col+increment)+row+increment); indices->push_back(verts*(col+increment)+row+increment);
indices.push_back(verts*col+row+increment); indices->push_back(verts*col+row+increment);
indices.push_back(verts*col+row); indices->push_back(verts*col+row);
indices.push_back(verts*(col+increment)+row); indices->push_back(verts*(col+increment)+row);
indices.push_back(verts*(col+increment)+row+increment); indices->push_back(verts*(col+increment)+row+increment);
} }
} }
} }
@ -94,22 +97,22 @@ Ogre::HardwareIndexBufferSharedPtr createIndexBuffer(unsigned int flags, unsigne
size_t outerStep = 1 << (lodDeltas[Terrain::South] + lodLevel); size_t outerStep = 1 << (lodDeltas[Terrain::South] + lodLevel);
for (size_t col = 0; col < verts-1; col += outerStep) for (size_t col = 0; col < verts-1; col += outerStep)
{ {
indices.push_back(verts*col+row); indices->push_back(verts*col+row);
indices.push_back(verts*(col+outerStep)+row); indices->push_back(verts*(col+outerStep)+row);
// Make sure not to touch the right edge // Make sure not to touch the right edge
if (col+outerStep == verts-1) if (col+outerStep == verts-1)
indices.push_back(verts*(col+outerStep-innerStep)+row+innerStep); indices->push_back(verts*(col+outerStep-innerStep)+row+innerStep);
else else
indices.push_back(verts*(col+outerStep)+row+innerStep); indices->push_back(verts*(col+outerStep)+row+innerStep);
for (size_t i = 0; i < outerStep; i += innerStep) for (size_t i = 0; i < outerStep; i += innerStep)
{ {
// Make sure not to touch the left or right edges // Make sure not to touch the left or right edges
if (col+i == 0 || col+i == verts-1-innerStep) if (col+i == 0 || col+i == verts-1-innerStep)
continue; continue;
indices.push_back(verts*(col)+row); indices->push_back(verts*(col)+row);
indices.push_back(verts*(col+i+innerStep)+row+innerStep); indices->push_back(verts*(col+i+innerStep)+row+innerStep);
indices.push_back(verts*(col+i)+row+innerStep); indices->push_back(verts*(col+i)+row+innerStep);
} }
} }
@ -118,22 +121,22 @@ Ogre::HardwareIndexBufferSharedPtr createIndexBuffer(unsigned int flags, unsigne
outerStep = size_t(1) << (lodDeltas[Terrain::North] + lodLevel); outerStep = size_t(1) << (lodDeltas[Terrain::North] + lodLevel);
for (size_t col = 0; col < verts-1; col += outerStep) for (size_t col = 0; col < verts-1; col += outerStep)
{ {
indices.push_back(verts*(col+outerStep)+row); indices->push_back(verts*(col+outerStep)+row);
indices.push_back(verts*col+row); indices->push_back(verts*col+row);
// Make sure not to touch the left edge // Make sure not to touch the left edge
if (col == 0) if (col == 0)
indices.push_back(verts*(col+innerStep)+row-innerStep); indices->push_back(verts*(col+innerStep)+row-innerStep);
else else
indices.push_back(verts*col+row-innerStep); indices->push_back(verts*col+row-innerStep);
for (size_t i = 0; i < outerStep; i += innerStep) for (size_t i = 0; i < outerStep; i += innerStep)
{ {
// Make sure not to touch the left or right edges // Make sure not to touch the left or right edges
if (col+i == 0 || col+i == verts-1-innerStep) if (col+i == 0 || col+i == verts-1-innerStep)
continue; continue;
indices.push_back(verts*(col+i)+row-innerStep); indices->push_back(verts*(col+i)+row-innerStep);
indices.push_back(verts*(col+i+innerStep)+row-innerStep); indices->push_back(verts*(col+i+innerStep)+row-innerStep);
indices.push_back(verts*(col+outerStep)+row); indices->push_back(verts*(col+outerStep)+row);
} }
} }
@ -142,22 +145,22 @@ Ogre::HardwareIndexBufferSharedPtr createIndexBuffer(unsigned int flags, unsigne
outerStep = size_t(1) << (lodDeltas[Terrain::West] + lodLevel); outerStep = size_t(1) << (lodDeltas[Terrain::West] + lodLevel);
for (size_t row = 0; row < verts-1; row += outerStep) for (size_t row = 0; row < verts-1; row += outerStep)
{ {
indices.push_back(verts*col+row+outerStep); indices->push_back(verts*col+row+outerStep);
indices.push_back(verts*col+row); indices->push_back(verts*col+row);
// Make sure not to touch the top edge // Make sure not to touch the top edge
if (row+outerStep == verts-1) if (row+outerStep == verts-1)
indices.push_back(verts*(col+innerStep)+row+outerStep-innerStep); indices->push_back(verts*(col+innerStep)+row+outerStep-innerStep);
else else
indices.push_back(verts*(col+innerStep)+row+outerStep); indices->push_back(verts*(col+innerStep)+row+outerStep);
for (size_t i = 0; i < outerStep; i += innerStep) for (size_t i = 0; i < outerStep; i += innerStep)
{ {
// Make sure not to touch the top or bottom edges // Make sure not to touch the top or bottom edges
if (row+i == 0 || row+i == verts-1-innerStep) if (row+i == 0 || row+i == verts-1-innerStep)
continue; continue;
indices.push_back(verts*col+row); indices->push_back(verts*col+row);
indices.push_back(verts*(col+innerStep)+row+i); indices->push_back(verts*(col+innerStep)+row+i);
indices.push_back(verts*(col+innerStep)+row+i+innerStep); indices->push_back(verts*(col+innerStep)+row+i+innerStep);
} }
} }
@ -166,31 +169,27 @@ Ogre::HardwareIndexBufferSharedPtr createIndexBuffer(unsigned int flags, unsigne
outerStep = size_t(1) << (lodDeltas[Terrain::East] + lodLevel); outerStep = size_t(1) << (lodDeltas[Terrain::East] + lodLevel);
for (size_t row = 0; row < verts-1; row += outerStep) for (size_t row = 0; row < verts-1; row += outerStep)
{ {
indices.push_back(verts*col+row); indices->push_back(verts*col+row);
indices.push_back(verts*col+row+outerStep); indices->push_back(verts*col+row+outerStep);
// Make sure not to touch the bottom edge // Make sure not to touch the bottom edge
if (row == 0) if (row == 0)
indices.push_back(verts*(col-innerStep)+row+innerStep); indices->push_back(verts*(col-innerStep)+row+innerStep);
else else
indices.push_back(verts*(col-innerStep)+row); indices->push_back(verts*(col-innerStep)+row);
for (size_t i = 0; i < outerStep; i += innerStep) for (size_t i = 0; i < outerStep; i += innerStep)
{ {
// Make sure not to touch the top or bottom edges // Make sure not to touch the top or bottom edges
if (row+i == 0 || row+i == verts-1-innerStep) if (row+i == 0 || row+i == verts-1-innerStep)
continue; continue;
indices.push_back(verts*col+row+outerStep); indices->push_back(verts*col+row+outerStep);
indices.push_back(verts*(col-innerStep)+row+i+innerStep); indices->push_back(verts*(col-innerStep)+row+i+innerStep);
indices.push_back(verts*(col-innerStep)+row+i); indices->push_back(verts*(col-innerStep)+row+i);
} }
} }
} }
Ogre::HardwareBufferManager* mgr = Ogre::HardwareBufferManager::getSingletonPtr(); return indices;
Ogre::HardwareIndexBufferSharedPtr buffer = mgr->createIndexBuffer(type,
indices.size(), Ogre::HardwareBuffer::HBU_STATIC);
buffer->writeData(0, buffer->getSizeInBytes(), &indices[0], true);
return buffer;
} }
} }
@ -198,7 +197,7 @@ Ogre::HardwareIndexBufferSharedPtr createIndexBuffer(unsigned int flags, unsigne
namespace Terrain namespace Terrain
{ {
Ogre::HardwareVertexBufferSharedPtr BufferCache::getUVBuffer() osg::ref_ptr<osg::Vec2Array> BufferCache::getUVBuffer()
{ {
if (mUvBufferMap.find(mNumVerts) != mUvBufferMap.end()) if (mUvBufferMap.find(mNumVerts) != mUvBufferMap.end())
{ {
@ -207,30 +206,23 @@ namespace Terrain
int vertexCount = mNumVerts * mNumVerts; int vertexCount = mNumVerts * mNumVerts;
std::vector<float> uvs; osg::ref_ptr<osg::Vec2Array> uvs (new osg::Vec2Array);
uvs.reserve(vertexCount*2); uvs->reserve(vertexCount);
for (unsigned int col = 0; col < mNumVerts; ++col) for (unsigned int col = 0; col < mNumVerts; ++col)
{ {
for (unsigned int row = 0; row < mNumVerts; ++row) for (unsigned int row = 0; row < mNumVerts; ++row)
{ {
uvs.push_back(col / static_cast<float>(mNumVerts-1)); // U uvs->push_back(osg::Vec2f(col / static_cast<float>(mNumVerts-1),
uvs.push_back(row / static_cast<float>(mNumVerts-1)); // V row / static_cast<float>(mNumVerts-1)));
} }
} }
Ogre::HardwareBufferManager* mgr = Ogre::HardwareBufferManager::getSingletonPtr(); mUvBufferMap[mNumVerts] = uvs;
Ogre::HardwareVertexBufferSharedPtr buffer = mgr->createVertexBuffer( return uvs;
Ogre::VertexElement::getTypeSize(Ogre::VET_FLOAT2),
vertexCount, Ogre::HardwareBuffer::HBU_STATIC);
buffer->writeData(0, buffer->getSizeInBytes(), &uvs[0], true);
mUvBufferMap[mNumVerts] = buffer;
return buffer;
} }
Ogre::HardwareIndexBufferSharedPtr BufferCache::getIndexBuffer(unsigned int flags) osg::ref_ptr<osg::DrawElements> BufferCache::getIndexBuffer(unsigned int flags)
{ {
unsigned int verts = mNumVerts; unsigned int verts = mNumVerts;
@ -239,11 +231,12 @@ namespace Terrain
return mIndexBufferMap[flags]; return mIndexBufferMap[flags];
} }
Ogre::HardwareIndexBufferSharedPtr buffer; osg::ref_ptr<osg::DrawElements> buffer;
if (verts*verts > (0xffffu)) if (verts*verts > (0xffffu))
buffer = createIndexBuffer<unsigned int>(flags, verts, Ogre::HardwareIndexBuffer::IT_32BIT); buffer = createIndexBuffer<osg::DrawElementsUShort>(flags, verts);
else else
buffer = createIndexBuffer<unsigned short>(flags, verts, Ogre::HardwareIndexBuffer::IT_16BIT); buffer = createIndexBuffer<osg::DrawElementsUInt>(flags, verts);
mIndexBufferMap[flags] = buffer; mIndexBufferMap[flags] = buffer;
return buffer; return buffer;

@ -22,8 +22,8 @@
#ifndef COMPONENTS_TERRAIN_BUFFERCACHE_H #ifndef COMPONENTS_TERRAIN_BUFFERCACHE_H
#define COMPONENTS_TERRAIN_BUFFERCACHE_H #define COMPONENTS_TERRAIN_BUFFERCACHE_H
#include <OgreHardwareIndexBuffer.h> #include <osg/ref_ptr>
#include <OgreHardwareVertexBuffer.h> #include <osg/Array>
#include <map> #include <map>
@ -38,16 +38,18 @@ namespace Terrain
/// @param flags first 4*4 bits are LOD deltas on each edge, respectively (4 bits each) /// @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) /// next 4 bits are LOD level of the index buffer (LOD 0 = don't omit any vertices)
Ogre::HardwareIndexBufferSharedPtr getIndexBuffer (unsigned int flags); osg::ref_ptr<osg::DrawElements> getIndexBuffer (unsigned int flags);
Ogre::HardwareVertexBufferSharedPtr getUVBuffer (); osg::ref_ptr<osg::Vec2Array> getUVBuffer();
// TODO: add releaseGLObjects() for our vertex/element buffer objects
private: private:
// Index buffers are shared across terrain batches where possible. There is one index buffer for each // 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. // combination of LOD deltas and index buffer LOD we may need.
std::map<int, Ogre::HardwareIndexBufferSharedPtr> mIndexBufferMap; std::map<int, osg::ref_ptr<osg::DrawElements> > mIndexBufferMap;
std::map<int, Ogre::HardwareVertexBufferSharedPtr> mUvBufferMap; std::map<int, osg::ref_ptr<osg::Vec2Array> > mUvBufferMap;
unsigned int mNumVerts; unsigned int mNumVerts;
}; };

@ -1,169 +0,0 @@
/*
* Copyright (c) 2015 scrawl <scrawl@baseoftrash.de>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include "chunk.hpp"
#include <OgreSceneNode.h>
#include <OgreHardwareBufferManager.h>
#include <OgreRenderQueue.h>
#include <OgreMaterialManager.h>
#include <OgreStringConverter.h>
namespace Terrain
{
Chunk::Chunk(Ogre::HardwareVertexBufferSharedPtr uvBuffer, const Ogre::AxisAlignedBox& bounds,
const std::vector<float>& positions, const std::vector<float>& normals, const std::vector<Ogre::uint8>& colours)
: mBounds(bounds)
, mOwnMaterial(false)
{
mVertexData = OGRE_NEW Ogre::VertexData;
mVertexData->vertexStart = 0;
mVertexData->vertexCount = positions.size()/3;
// Set up the vertex declaration, which specifies the info for each vertex (normals, colors, UVs, etc)
Ogre::VertexDeclaration* vertexDecl = mVertexData->vertexDeclaration;
Ogre::HardwareBufferManager* mgr = Ogre::HardwareBufferManager::getSingletonPtr();
size_t nextBuffer = 0;
// Positions
vertexDecl->addElement(nextBuffer++, 0, Ogre::VET_FLOAT3, Ogre::VES_POSITION);
Ogre::HardwareVertexBufferSharedPtr vertexBuffer = mgr->createVertexBuffer(Ogre::VertexElement::getTypeSize(Ogre::VET_FLOAT3),
mVertexData->vertexCount, Ogre::HardwareBuffer::HBU_STATIC);
// Normals
vertexDecl->addElement(nextBuffer++, 0, Ogre::VET_FLOAT3, Ogre::VES_NORMAL);
Ogre::HardwareVertexBufferSharedPtr normalBuffer = mgr->createVertexBuffer(Ogre::VertexElement::getTypeSize(Ogre::VET_FLOAT3),
mVertexData->vertexCount, Ogre::HardwareBuffer::HBU_STATIC);
// UV texture coordinates
vertexDecl->addElement(nextBuffer++, 0, Ogre::VET_FLOAT2,
Ogre::VES_TEXTURE_COORDINATES, 0);
// Colours
vertexDecl->addElement(nextBuffer++, 0, Ogre::VET_COLOUR, Ogre::VES_DIFFUSE);
Ogre::HardwareVertexBufferSharedPtr colourBuffer = mgr->createVertexBuffer(Ogre::VertexElement::getTypeSize(Ogre::VET_COLOUR),
mVertexData->vertexCount, Ogre::HardwareBuffer::HBU_STATIC);
vertexBuffer->writeData(0, vertexBuffer->getSizeInBytes(), &positions[0], true);
normalBuffer->writeData(0, normalBuffer->getSizeInBytes(), &normals[0], true);
colourBuffer->writeData(0, colourBuffer->getSizeInBytes(), &colours[0], true);
mVertexData->vertexBufferBinding->setBinding(0, vertexBuffer);
mVertexData->vertexBufferBinding->setBinding(1, normalBuffer);
mVertexData->vertexBufferBinding->setBinding(2, uvBuffer);
mVertexData->vertexBufferBinding->setBinding(3, colourBuffer);
// Assign a default material in case terrain material fails to be created
mMaterial = Ogre::MaterialManager::getSingleton().getByName("BaseWhite");
mIndexData = OGRE_NEW Ogre::IndexData();
mIndexData->indexStart = 0;
}
void Chunk::setIndexBuffer(Ogre::HardwareIndexBufferSharedPtr buffer)
{
mIndexData->indexBuffer = buffer;
mIndexData->indexCount = buffer->getNumIndexes();
}
Chunk::~Chunk()
{
if (!mMaterial.isNull() && mOwnMaterial)
{
#if TERRAIN_USE_SHADER
sh::Factory::getInstance().destroyMaterialInstance(mMaterial->getName());
#endif
Ogre::MaterialManager::getSingleton().remove(mMaterial->getName());
}
OGRE_DELETE mVertexData;
OGRE_DELETE mIndexData;
}
void Chunk::setMaterial(const Ogre::MaterialPtr &material, bool own)
{
// Clean up the previous material, if we own it
if (!mMaterial.isNull() && mOwnMaterial)
{
#if TERRAIN_USE_SHADER
sh::Factory::getInstance().destroyMaterialInstance(mMaterial->getName());
#endif
Ogre::MaterialManager::getSingleton().remove(mMaterial->getName());
}
mMaterial = material;
mOwnMaterial = own;
}
const Ogre::AxisAlignedBox& Chunk::getBoundingBox(void) const
{
return mBounds;
}
Ogre::Real Chunk::getBoundingRadius(void) const
{
return mBounds.getHalfSize().length();
}
void Chunk::_updateRenderQueue(Ogre::RenderQueue* queue)
{
queue->addRenderable(this, mRenderQueueID);
}
void Chunk::visitRenderables(Ogre::Renderable::Visitor* visitor,
bool debugRenderables)
{
visitor->visit(this, 0, false);
}
const Ogre::MaterialPtr& Chunk::getMaterial(void) const
{
return mMaterial;
}
void Chunk::getRenderOperation(Ogre::RenderOperation& op)
{
assert (!mIndexData->indexBuffer.isNull() && "Trying to render, but no index buffer set!");
assert(!mMaterial.isNull() && "Trying to render, but no material set!");
op.useIndexes = true;
op.operationType = Ogre::RenderOperation::OT_TRIANGLE_LIST;
op.vertexData = mVertexData;
op.indexData = mIndexData;
}
void Chunk::getWorldTransforms(Ogre::Matrix4* xform) const
{
*xform = getParentSceneNode()->_getFullTransform();
}
Ogre::Real Chunk::getSquaredViewDepth(const Ogre::Camera* cam) const
{
return getParentSceneNode()->getSquaredViewDepth(cam);
}
const Ogre::LightList& Chunk::getLights(void) const
{
return queryLights();
}
}

@ -1,75 +0,0 @@
/*
* Copyright (c) 2015 scrawl <scrawl@baseoftrash.de>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#ifndef COMPONENTS_TERRAIN_TERRAINBATCH_H
#define COMPONENTS_TERRAIN_TERRAINBATCH_H
#include <OgreRenderable.h>
#include <OgreMovableObject.h>
namespace Terrain
{
/**
* @brief A movable object representing a chunk of terrain.
*/
class Chunk : public Ogre::Renderable, public Ogre::MovableObject
{
public:
Chunk (Ogre::HardwareVertexBufferSharedPtr uvBuffer, const Ogre::AxisAlignedBox& bounds,
const std::vector<float>& positions,
const std::vector<float>& normals,
const std::vector<Ogre::uint8>& colours);
virtual ~Chunk();
/// @param own Should we take ownership of the material?
void setMaterial (const Ogre::MaterialPtr& material, bool own=true);
void setIndexBuffer(Ogre::HardwareIndexBufferSharedPtr buffer);
// Inherited from MovableObject
virtual const Ogre::String& getMovableType(void) const { static Ogre::String t = "MW_TERRAIN"; return t; }
virtual const Ogre::AxisAlignedBox& getBoundingBox(void) const;
virtual Ogre::Real getBoundingRadius(void) const;
virtual void _updateRenderQueue(Ogre::RenderQueue* queue);
virtual void visitRenderables(Renderable::Visitor* visitor,
bool debugRenderables = false);
// Inherited from Renderable
virtual const Ogre::MaterialPtr& getMaterial(void) const;
virtual void getRenderOperation(Ogre::RenderOperation& op);
virtual void getWorldTransforms(Ogre::Matrix4* xform) const;
virtual Ogre::Real getSquaredViewDepth(const Ogre::Camera* cam) const;
virtual const Ogre::LightList& getLights(void) const;
private:
Ogre::AxisAlignedBox mBounds;
Ogre::MaterialPtr mMaterial;
bool mOwnMaterial; // Should we remove mMaterial on destruction?
Ogre::VertexData* mVertexData;
Ogre::IndexData* mIndexData;
};
}
#endif

@ -1,336 +0,0 @@
/*
* Copyright (c) 2015 scrawl <scrawl@baseoftrash.de>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include "defaultworld.hpp"
#include <OgreAxisAlignedBox.h>
#include <OgreCamera.h>
#include <OgreHardwarePixelBuffer.h>
#include <OgreTextureManager.h>
#include <OgreRenderTexture.h>
#include <OgreSceneNode.h>
#include <OgreRoot.h>
#include "storage.hpp"
#include "quadtreenode.hpp"
namespace
{
bool isPowerOfTwo(int x)
{
return ( (x > 0) && ((x & (x - 1)) == 0) );
}
int nextPowerOfTwo (int v)
{
if (isPowerOfTwo(v)) return v;
int depth=0;
while(v)
{
v >>= 1;
depth++;
}
return 1 << depth;
}
Terrain::QuadTreeNode* findNode (const Ogre::Vector2& center, Terrain::QuadTreeNode* node)
{
if (center == node->getCenter())
return node;
if (center.x > node->getCenter().x && center.y > node->getCenter().y)
return findNode(center, node->getChild(Terrain::NE));
else if (center.x > node->getCenter().x && center.y < node->getCenter().y)
return findNode(center, node->getChild(Terrain::SE));
else if (center.x < node->getCenter().x && center.y > node->getCenter().y)
return findNode(center, node->getChild(Terrain::NW));
else //if (center.x < node->getCenter().x && center.y < node->getCenter().y)
return findNode(center, node->getChild(Terrain::SW));
}
}
namespace Terrain
{
const Ogre::uint REQ_ID_CHUNK = 1;
const Ogre::uint REQ_ID_LAYERS = 2;
DefaultWorld::DefaultWorld(Ogre::SceneManager* sceneMgr,
Storage* storage, int visibilityFlags, bool shaders, Alignment align, float minBatchSize, float maxBatchSize)
: World(sceneMgr, storage, visibilityFlags, shaders, align)
, mWorkQueueChannel(0)
, mVisible(true)
, mChunksLoading(0)
, mMinX(0)
, mMaxX(0)
, mMinY(0)
, mMaxY(0)
, mMinBatchSize(minBatchSize)
, mMaxBatchSize(maxBatchSize)
, mLayerLoadPending(true)
{
#if TERRAIN_USE_SHADER == 0
if (mShaders)
std::cerr << "Compiled Terrain without shader support, disabling..." << std::endl;
mShaders = false;
#endif
mCompositeMapSceneMgr = Ogre::Root::getSingleton().createSceneManager(Ogre::ST_GENERIC);
/// \todo make composite map size configurable
Ogre::Camera* compositeMapCam = mCompositeMapSceneMgr->createCamera("a");
mCompositeMapRenderTexture = Ogre::TextureManager::getSingleton().createManual(
"terrain/comp/rt", Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME,
Ogre::TEX_TYPE_2D, 128, 128, 0, Ogre::PF_A8B8G8R8, Ogre::TU_RENDERTARGET);
mCompositeMapRenderTarget = mCompositeMapRenderTexture->getBuffer()->getRenderTarget();
mCompositeMapRenderTarget->setAutoUpdated(false);
mCompositeMapRenderTarget->addViewport(compositeMapCam);
storage->getBounds(mMinX, mMaxX, mMinY, mMaxY);
int origSizeX = static_cast<int>(mMaxX - mMinX);
int origSizeY = static_cast<int>(mMaxY - mMinY);
// Dividing a quad tree only works well for powers of two, so round up to the nearest one
int size = nextPowerOfTwo(std::max(origSizeX, origSizeY));
// Adjust the center according to the new size
float centerX = (mMinX+mMaxX)/2.f + (size-origSizeX)/2.f;
float centerY = (mMinY+mMaxY)/2.f + (size-origSizeY)/2.f;
mRootSceneNode = mSceneMgr->getRootSceneNode()->createChildSceneNode();
// While building the quadtree, remember leaf nodes since we need to load their layers
LayersRequestData data;
data.mPack = getShadersEnabled();
mRootNode = new QuadTreeNode(this, Root, static_cast<float>(size), Ogre::Vector2(centerX, centerY), NULL);
buildQuadTree(mRootNode, data.mNodes);
//loadingListener->indicateProgress();
mRootNode->initAabb();
//loadingListener->indicateProgress();
mRootNode->initNeighbours();
//loadingListener->indicateProgress();
Ogre::WorkQueue* wq = Ogre::Root::getSingleton().getWorkQueue();
mWorkQueueChannel = wq->getChannel("LargeTerrain");
wq->addRequestHandler(mWorkQueueChannel, this);
wq->addResponseHandler(mWorkQueueChannel, this);
// Start loading layers in the background (for leaf nodes)
wq->addRequest(mWorkQueueChannel, REQ_ID_LAYERS, Ogre::Any(data));
}
DefaultWorld::~DefaultWorld()
{
Ogre::WorkQueue* wq = Ogre::Root::getSingleton().getWorkQueue();
wq->removeRequestHandler(mWorkQueueChannel, this);
wq->removeResponseHandler(mWorkQueueChannel, this);
delete mRootNode;
}
void DefaultWorld::buildQuadTree(QuadTreeNode *node, std::vector<QuadTreeNode*>& leafs)
{
float halfSize = node->getSize()/2.f;
if (node->getSize() <= mMinBatchSize)
{
// We arrived at a leaf
float minZ,maxZ;
Ogre::Vector2 center = node->getCenter();
float cellWorldSize = getStorage()->getCellWorldSize();
if (mStorage->getMinMaxHeights(static_cast<float>(node->getSize()), center, minZ, maxZ))
{
Ogre::AxisAlignedBox bounds(Ogre::Vector3(-halfSize*cellWorldSize, -halfSize*cellWorldSize, minZ),
Ogre::Vector3(halfSize*cellWorldSize, halfSize*cellWorldSize, maxZ));
convertBounds(bounds);
node->setBoundingBox(bounds);
leafs.push_back(node);
}
else
node->markAsDummy(); // no data available for this node, skip it
return;
}
if (node->getCenter().x - halfSize > mMaxX
|| node->getCenter().x + halfSize < mMinX
|| node->getCenter().y - halfSize > mMaxY
|| node->getCenter().y + halfSize < mMinY )
// Out of bounds of the actual terrain - this will happen because
// we rounded the size up to the next power of two
{
node->markAsDummy();
return;
}
// Not a leaf, create its children
node->createChild(SW, halfSize, node->getCenter() - halfSize/2.f);
node->createChild(SE, halfSize, node->getCenter() + Ogre::Vector2(halfSize/2.f, -halfSize/2.f));
node->createChild(NW, halfSize, node->getCenter() + Ogre::Vector2(-halfSize/2.f, halfSize/2.f));
node->createChild(NE, halfSize, node->getCenter() + halfSize/2.f);
buildQuadTree(node->getChild(SW), leafs);
buildQuadTree(node->getChild(SE), leafs);
buildQuadTree(node->getChild(NW), leafs);
buildQuadTree(node->getChild(NE), leafs);
// if all children are dummy, we are also dummy
for (int i=0; i<4; ++i)
{
if (!node->getChild((ChildDirection)i)->isDummy())
return;
}
node->markAsDummy();
}
void DefaultWorld::update(const Ogre::Vector3& cameraPos)
{
if (!mVisible)
return;
mRootNode->update(cameraPos);
mRootNode->updateIndexBuffers();
}
Ogre::AxisAlignedBox DefaultWorld::getWorldBoundingBox (const Ogre::Vector2& center)
{
if (center.x > mMaxX
|| center.x < mMinX
|| center.y > mMaxY
|| center.y < mMinY)
return Ogre::AxisAlignedBox::BOX_NULL;
QuadTreeNode* node = findNode(center, mRootNode);
return node->getWorldBoundingBox();
}
void DefaultWorld::renderCompositeMap(Ogre::TexturePtr target)
{
mCompositeMapRenderTarget->update();
target->getBuffer()->blit(mCompositeMapRenderTexture->getBuffer());
}
void DefaultWorld::clearCompositeMapSceneManager()
{
mCompositeMapSceneMgr->destroyAllManualObjects();
mCompositeMapSceneMgr->clearScene();
}
void DefaultWorld::applyMaterials(bool shadows, bool splitShadows)
{
mShadows = shadows;
mSplitShadows = splitShadows;
mRootNode->applyMaterials();
}
void DefaultWorld::setVisible(bool visible)
{
if (visible && !mVisible)
mSceneMgr->getRootSceneNode()->addChild(mRootSceneNode);
else if (!visible && mVisible)
mSceneMgr->getRootSceneNode()->removeChild(mRootSceneNode);
mVisible = visible;
}
bool DefaultWorld::getVisible()
{
return mVisible;
}
void DefaultWorld::syncLoad()
{
while (mChunksLoading || mLayerLoadPending)
{
OGRE_THREAD_SLEEP(0);
Ogre::Root::getSingleton().getWorkQueue()->processResponses();
}
}
Ogre::WorkQueue::Response* DefaultWorld::handleRequest(const Ogre::WorkQueue::Request *req, const Ogre::WorkQueue *srcQ)
{
if (req->getType() == REQ_ID_CHUNK)
{
const LoadRequestData data = Ogre::any_cast<LoadRequestData>(req->getData());
QuadTreeNode* node = data.mNode;
LoadResponseData* responseData = new LoadResponseData();
getStorage()->fillVertexBuffers(node->getNativeLodLevel(), static_cast<float>(node->getSize()), node->getCenter(), getAlign(),
responseData->mPositions, responseData->mNormals, responseData->mColours);
return OGRE_NEW Ogre::WorkQueue::Response(req, true, Ogre::Any(responseData));
}
else // REQ_ID_LAYERS
{
const LayersRequestData data = Ogre::any_cast<LayersRequestData>(req->getData());
LayersResponseData* responseData = new LayersResponseData();
getStorage()->getBlendmaps(data.mNodes, responseData->mLayerCollections, data.mPack);
return OGRE_NEW Ogre::WorkQueue::Response(req, true, Ogre::Any(responseData));
}
}
void DefaultWorld::handleResponse(const Ogre::WorkQueue::Response *res, const Ogre::WorkQueue *srcQ)
{
assert(res->succeeded() && "Response failure not handled");
if (res->getRequest()->getType() == REQ_ID_CHUNK)
{
LoadResponseData* data = Ogre::any_cast<LoadResponseData*>(res->getData());
const LoadRequestData requestData = Ogre::any_cast<LoadRequestData>(res->getRequest()->getData());
requestData.mNode->load(*data);
delete data;
--mChunksLoading;
}
else // REQ_ID_LAYERS
{
LayersResponseData* data = Ogre::any_cast<LayersResponseData*>(res->getData());
for (std::vector<LayerCollection>::iterator it = data->mLayerCollections.begin(); it != data->mLayerCollections.end(); ++it)
{
it->mTarget->loadLayers(*it);
}
delete data;
mRootNode->loadMaterials();
mLayerLoadPending = false;
}
}
void DefaultWorld::queueLoad(QuadTreeNode *node)
{
LoadRequestData data;
data.mNode = node;
Ogre::Root::getSingleton().getWorkQueue()->addRequest(mWorkQueueChannel, REQ_ID_CHUNK, Ogre::Any(data));
++mChunksLoading;
}
}

@ -1,177 +0,0 @@
/*
* Copyright (c) 2015 scrawl <scrawl@baseoftrash.de>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#ifndef COMPONENTS_TERRAIN_H
#define COMPONENTS_TERRAIN_H
#include <OgreAxisAlignedBox.h>
#include <OgreTexture.h>
#include <OgreWorkQueue.h>
#include "world.hpp"
namespace Ogre
{
class Camera;
}
namespace Terrain
{
class QuadTreeNode;
class Storage;
/**
* @brief A quadtree-based terrain implementation suitable for large data sets. \n
* Near cells are rendered with alpha splatting, distant cells are merged
* together in batches and have their layers pre-rendered onto a composite map. \n
* Cracks at LOD transitions are avoided using stitching.
* @note Multiple cameras are not supported yet
*/
class DefaultWorld : public World, public Ogre::WorkQueue::RequestHandler, public Ogre::WorkQueue::ResponseHandler
{
public:
/// @note takes ownership of \a storage
/// @param sceneMgr scene manager to use
/// @param storage Storage instance to get terrain data from (heights, normals, colors, textures..)
/// @param visbilityFlags visibility flags for the created meshes
/// @param shaders Whether to use splatting shader, or multi-pass fixed function splatting. Shader is usually
/// faster so this is just here for compatibility.
/// @param align The align of the terrain, see Alignment enum
/// @param minBatchSize Minimum size of a terrain batch along one side (in cell units). Used for building the quad tree.
/// @param maxBatchSize Maximum size of a terrain batch along one side (in cell units). Used when traversing the quad tree.
DefaultWorld(Ogre::SceneManager* sceneMgr,
Storage* storage, int visibilityFlags, bool shaders, Alignment align, float minBatchSize, float maxBatchSize);
~DefaultWorld();
/// Update chunk LODs according to this camera position
/// @note Calling this method might lead to composite textures being rendered, so it is best
/// not to call it when render commands are still queued, since that would cause a flush.
virtual void update (const Ogre::Vector3& cameraPos);
/// Get the world bounding box of a chunk of terrain centered at \a center
virtual Ogre::AxisAlignedBox getWorldBoundingBox (const Ogre::Vector2& center);
Ogre::SceneNode* getRootSceneNode() { return mRootSceneNode; }
/// Show or hide the whole terrain
/// @note this setting will be invalidated once you call Terrain::update, so do not call it while the terrain should be hidden
virtual void setVisible(bool visible);
virtual bool getVisible();
/// Recreate materials used by terrain chunks. This should be called whenever settings of
/// the material factory are changed. (Relying on the factory to update those materials is not
/// enough, since turning a feature on/off can change the number of texture units available for layer/blend
/// textures, and to properly respond to this we may need to change the structure of the material, such as
/// adding or removing passes. This can only be achieved by a full rebuild.)
virtual void applyMaterials(bool shadows, bool splitShadows);
int getMaxBatchSize() { return static_cast<int>(mMaxBatchSize); }
/// Wait until all background loading is complete.
void syncLoad();
private:
// Called from a background worker thread
virtual Ogre::WorkQueue::Response* handleRequest(const Ogre::WorkQueue::Request* req, const Ogre::WorkQueue* srcQ);
// Called from the main thread
virtual void handleResponse(const Ogre::WorkQueue::Response* res, const Ogre::WorkQueue* srcQ);
Ogre::uint16 mWorkQueueChannel;
bool mVisible;
QuadTreeNode* mRootNode;
Ogre::SceneNode* mRootSceneNode;
/// The number of chunks currently loading in a background thread. If 0, we have finished loading!
int mChunksLoading;
Ogre::SceneManager* mCompositeMapSceneMgr;
/// Bounds in cell units
float mMinX, mMaxX, mMinY, mMaxY;
/// Minimum size of a terrain batch along one side (in cell units)
float mMinBatchSize;
/// Maximum size of a terrain batch along one side (in cell units)
float mMaxBatchSize;
void buildQuadTree(QuadTreeNode* node, std::vector<QuadTreeNode*>& leafs);
// Are layers for leaf nodes loaded? This is done once at startup (but in a background thread)
bool mLayerLoadPending;
public:
// ----INTERNAL----
Ogre::SceneManager* getCompositeMapSceneManager() { return mCompositeMapSceneMgr; }
bool areLayersLoaded() { return !mLayerLoadPending; }
// Delete all quads
void clearCompositeMapSceneManager();
void renderCompositeMap (Ogre::TexturePtr target);
// Adds a WorkQueue request to load a chunk for this node in the background.
void queueLoad (QuadTreeNode* node);
private:
Ogre::RenderTarget* mCompositeMapRenderTarget;
Ogre::TexturePtr mCompositeMapRenderTexture;
};
struct LoadRequestData
{
QuadTreeNode* mNode;
friend std::ostream& operator<<(std::ostream& o, const LoadRequestData& r)
{ return o; }
};
struct LoadResponseData
{
std::vector<float> mPositions;
std::vector<float> mNormals;
std::vector<Ogre::uint8> mColours;
friend std::ostream& operator<<(std::ostream& o, const LoadResponseData& r)
{ return o; }
};
struct LayersRequestData
{
std::vector<QuadTreeNode*> mNodes;
bool mPack;
friend std::ostream& operator<<(std::ostream& o, const LayersRequestData& r)
{ return o; }
};
struct LayersResponseData
{
std::vector<LayerCollection> mLayerCollections;
friend std::ostream& operator<<(std::ostream& o, const LayersResponseData& r)
{ return o; }
};
}
#endif

@ -22,41 +22,10 @@
#ifndef COMPONENTS_TERRAIN_DEFS_HPP #ifndef COMPONENTS_TERRAIN_DEFS_HPP
#define COMPONENTS_TERRAIN_DEFS_HPP #define COMPONENTS_TERRAIN_DEFS_HPP
#include <string>
namespace Terrain namespace Terrain
{ {
class QuadTreeNode;
/// The alignment of the terrain
enum Alignment
{
/// Terrain is in the X/Z plane
Align_XZ = 0,
/// Terrain is in the X/Y plane
Align_XY = 1,
/// Terrain is in the Y/Z plane.
/// UNTESTED - use at own risk.
/// Besides, X as up axis? What is wrong with you? ;)
Align_YZ = 2
};
inline void convertPosition(Alignment align, float &x, float &y, float &z)
{
switch (align)
{
case Align_XY:
return;
case Align_XZ:
std::swap(y, z);
// This is since -Z should be going *into* the screen
// If not doing this, we'd get wrong vertex winding
z *= -1;
return;
case Align_YZ:
std::swap(x, y);
std::swap(y, z);
return;
}
}
enum Direction enum Direction
{ {
@ -74,13 +43,6 @@ namespace Terrain
bool mSpecular; // Specular info in diffuse map alpha channel? bool mSpecular; // Specular info in diffuse map alpha channel?
}; };
struct LayerCollection
{
QuadTreeNode* mTarget;
// Since we can't create a texture from a different thread, this only holds the raw texel data
std::vector<Ogre::PixelBox> mBlendmaps;
std::vector<LayerInfo> mLayers;
};
} }
#endif #endif

@ -21,353 +21,86 @@
*/ */
#include "material.hpp" #include "material.hpp"
#include <OgreMaterialManager.h> #include <iostream>
#include <OgreTechnique.h>
#include <OgrePass.h>
#include <boost/functional/hash.hpp> #include <osg/Depth>
#include <osg/TexEnvCombine>
#include <osg/Texture2D>
#include <osg/TexMat>
#include <osg/Material>
#if TERRAIN_USE_SHADER #include <osg/io_utils>
#include <extern/shiny/Main/Factory.hpp>
#endif
namespace
{
int getBlendmapIndexForLayer (int layerIndex)
{
return static_cast<int>(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 namespace Terrain
{ {
MaterialGenerator::MaterialGenerator() FixedFunctionTechnique::FixedFunctionTechnique(const std::vector<osg::ref_ptr<osg::Texture2D> >& layers,
: mShaders(true) const std::vector<osg::ref_ptr<osg::Texture2D> >& blendmaps)
, mShadows(false)
, mSplitShadows(false)
, mNormalMapping(true)
, mParallaxMapping(true)
{
}
Ogre::MaterialPtr MaterialGenerator::generate()
{
assert(!mLayerList.empty() && "Can't create material with no layers");
return create(false, false);
}
Ogre::MaterialPtr MaterialGenerator::generateForCompositeMapRTT()
{
assert(!mLayerList.empty() && "Can't create material with no layers");
return create(true, false);
}
Ogre::MaterialPtr MaterialGenerator::generateForCompositeMap()
{ {
return create(false, true); bool firstLayer = true;
} int i=0;
for (std::vector<osg::ref_ptr<osg::Texture2D> >::const_iterator it = layers.begin(); it != layers.end(); ++it)
Ogre::MaterialPtr MaterialGenerator::create(bool renderCompositeMap, bool displayCompositeMap)
{
assert(!renderCompositeMap || !displayCompositeMap);
static int count = 0;
std::stringstream name;
name << "terrain/mat" << count++;
if (!mShaders)
{ {
Ogre::MaterialPtr mat = Ogre::MaterialManager::getSingleton().create(name.str(), osg::ref_ptr<osg::StateSet> stateset (new osg::StateSet);
Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
Ogre::Technique* technique = mat->getTechnique(0);
technique->removeAllPasses();
if (displayCompositeMap) if (!firstLayer)
{ {
Ogre::Pass* pass = technique->createPass(); stateset->setMode(GL_BLEND, osg::StateAttribute::ON);
pass->setVertexColourTracking(Ogre::TVC_AMBIENT|Ogre::TVC_DIFFUSE); osg::ref_ptr<osg::Depth> depth (new osg::Depth);
pass->createTextureUnitState(mCompositeMap)->setTextureAddressingMode(Ogre::TextureUnitState::TAM_CLAMP); depth->setFunction(osg::Depth::EQUAL);
stateset->setAttributeAndModes(depth, osg::StateAttribute::ON);
} }
else
{
assert(mLayerList.size() == mBlendmapList.size()+1);
std::vector<Ogre::TexturePtr>::iterator blend = mBlendmapList.begin();
for (std::vector<LayerInfo>::iterator layer = mLayerList.begin(); layer != mLayerList.end(); ++layer)
{
Ogre::Pass* pass = technique->createPass();
pass->setLightingEnabled(false);
pass->setVertexColourTracking(Ogre::TVC_NONE);
// TODO: How to handle fog?
pass->setFog(true, Ogre::FOG_NONE);
bool first = (layer == mLayerList.begin());
Ogre::TextureUnitState* tus;
if (!first) int texunit = 0;
{ if(!firstLayer)
pass->setSceneBlending(Ogre::SBT_TRANSPARENT_ALPHA);
pass->setDepthFunction(Ogre::CMPF_EQUAL);
tus = pass->createTextureUnitState((*blend)->getName());
tus->setAlphaOperation(Ogre::LBX_BLEND_TEXTURE_ALPHA,
Ogre::LBS_TEXTURE,
Ogre::LBS_TEXTURE);
tus->setColourOperationEx(Ogre::LBX_BLEND_DIFFUSE_ALPHA,
Ogre::LBS_TEXTURE,
Ogre::LBS_TEXTURE);
tus->setIsAlpha(true);
tus->setTextureAddressingMode(Ogre::TextureUnitState::TAM_CLAMP);
float scale = (16/(16.f+1.f));
tus->setTextureScale(1.f/scale,1.f/scale);
}
// Add the actual layer texture on top of the alpha map.
tus = pass->createTextureUnitState(layer->mDiffuseMap);
if (!first)
tus->setColourOperationEx(Ogre::LBX_BLEND_DIFFUSE_ALPHA,
Ogre::LBS_TEXTURE,
Ogre::LBS_CURRENT);
tus->setTextureScale(1/16.f,1/16.f);
if (!first)
++blend;
}
if (!renderCompositeMap)
{
Ogre::Pass* lightingPass = technique->createPass();
lightingPass->setSceneBlending(Ogre::SBT_MODULATE);
lightingPass->setVertexColourTracking(Ogre::TVC_AMBIENT|Ogre::TVC_DIFFUSE);
lightingPass->setFog(true, Ogre::FOG_NONE);
}
}
return mat;
}
#if TERRAIN_USE_SHADER
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 (); osg::ref_ptr<osg::Texture2D> blendmap = blendmaps.at(i++);
p->setProperty ("vertex_program", sh::makeProperty<sh::StringValue>(new sh::StringValue("terrain_vertex"))); stateset->setTextureAttributeAndModes(texunit, blendmap.get());
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")));
p->mShaderProperties.setProperty ("normal_map_enabled", sh::makeProperty (new sh::BooleanValue(false)));
p->mShaderProperties.setProperty ("parallax_enabled", sh::makeProperty (new sh::BooleanValue(false)));
p->mShaderProperties.setProperty ("normal_maps",
sh::makeProperty (new sh::IntValue(0)));
sh::MaterialInstanceTextureUnit* tex = p->createTextureUnit ("compositeMap"); // This is to map corner vertices directly to the center of a blendmap texel.
tex->setProperty ("direct_texture", sh::makeProperty (new sh::StringValue(mCompositeMap))); osg::Matrixf texMat;
tex->setProperty ("tex_address_mode", sh::makeProperty (new sh::StringValue("clamp"))); float scale = (16/(16.f+1.f));
texMat.preMultTranslate(osg::Vec3f(0.5f, 0.5f, 0.f));
texMat.preMultScale(osg::Vec3f(scale, scale, 1.f));
texMat.preMultTranslate(osg::Vec3f(-0.5f, -0.5f, 0.f));
// shadow. TODO: repeated, put in function stateset->setTextureAttributeAndModes(texunit, new osg::TexMat(texMat));
if (mShadows)
{
for (int i = 0; i < (mSplitShadows ? 3 : 1); ++i)
{
sh::MaterialInstanceTextureUnit* shadowTex = p->createTextureUnit ("shadowMap" + Ogre::StringConverter::toString(i));
shadowTex->setProperty ("content_type", sh::makeProperty<sh::StringValue> (new sh::StringValue("shadow")));
}
}
p->mShaderProperties.setProperty ("shadowtexture_offset", sh::makeProperty (new sh::StringValue(
Ogre::StringConverter::toString(1))));
p->mShaderProperties.setProperty ("pass_index", sh::makeProperty(new sh::IntValue(0))); ++texunit;
} }
else
{
bool shadows = mShadows && !renderCompositeMap;
int layerOffset = 0;
while (layerOffset < (int)mLayerList.size())
{
int blendmapOffset = (layerOffset == 0) ? 1 : 0; // the first layer of the first pass is the base layer and does not need a blend map
// Check how many layers we can fit in this pass
int numLayersInThisPass = 0;
int numBlendTextures = 0;
std::vector<std::string> blendTextures;
int remainingTextureUnits = OGRE_MAX_TEXTURE_LAYERS;
if (shadows)
remainingTextureUnits -= (mSplitShadows ? 3 : 1);
while (remainingTextureUnits && layerOffset + numLayersInThisPass < (int)mLayerList.size())
{
int layerIndex = numLayersInThisPass + layerOffset;
int neededTextureUnits=0; // Add the actual layer texture multiplied by the alpha map.
int neededBlendTextures=0; osg::ref_ptr<osg::Texture2D> tex = *it;
stateset->setTextureAttributeAndModes(texunit, tex.get());
if (layerIndex != 0) osg::ref_ptr<osg::TexMat> texMat (new osg::TexMat);
{ float scale = 16.f;
std::string blendTextureName = mBlendmapList[getBlendmapIndexForLayer(layerIndex)]->getName(); texMat->setMatrix(osg::Matrix::scale(osg::Vec3f(scale,scale,1.f)));
if (std::find(blendTextures.begin(), blendTextures.end(), blendTextureName) == blendTextures.end()) stateset->setTextureAttributeAndModes(texunit, texMat, osg::StateAttribute::ON);
{
blendTextures.push_back(blendTextureName);
++neededBlendTextures;
++neededTextureUnits; // blend texture
}
}
++neededTextureUnits; // layer texture
// Check if this layer has a normal map firstLayer = false;
if (mNormalMapping && !mLayerList[layerIndex].mNormalMap.empty() && !renderCompositeMap)
++neededTextureUnits; // normal map
if (neededTextureUnits <= remainingTextureUnits)
{
// We can fit another!
remainingTextureUnits -= neededTextureUnits;
numBlendTextures += neededBlendTextures;
++numLayersInThisPass;
}
else
break; // We're full
}
addPass(stateset);
}
}
sh::MaterialInstancePass* p = material->createPass (); Effect::Effect(const std::vector<osg::ref_ptr<osg::Texture2D> > &layers, const std::vector<osg::ref_ptr<osg::Texture2D> > &blendmaps)
p->setProperty ("vertex_program", sh::makeProperty<sh::StringValue>(new sh::StringValue("terrain_vertex"))); : mLayers(layers)
p->setProperty ("fragment_program", sh::makeProperty<sh::StringValue>(new sh::StringValue("terrain_fragment"))); , mBlendmaps(blendmaps)
if (layerOffset != 0) {
{ osg::ref_ptr<osg::Material> material (new osg::Material);
p->setProperty ("scene_blend", sh::makeProperty(new sh::StringValue("alpha_blend"))); material->setColorMode(osg::Material::AMBIENT_AND_DIFFUSE);
// Only write if depth is equal to the depth value written by the previous pass. getOrCreateStateSet()->setAttributeAndModes(material, osg::StateAttribute::ON);
p->setProperty ("depth_func", sh::makeProperty(new sh::StringValue("equal")));
}
p->mShaderProperties.setProperty ("render_composite_map", sh::makeProperty(new sh::BooleanValue(renderCompositeMap)));
p->mShaderProperties.setProperty ("display_composite_map", sh::makeProperty(new sh::BooleanValue(displayCompositeMap)));
p->mShaderProperties.setProperty ("num_layers", sh::makeProperty (new sh::StringValue(Ogre::StringConverter::toString(numLayersInThisPass))));
p->mShaderProperties.setProperty ("num_blendmaps", sh::makeProperty (new sh::StringValue(Ogre::StringConverter::toString(numBlendTextures))));
p->mShaderProperties.setProperty ("normal_map_enabled",
sh::makeProperty (new sh::BooleanValue(false)));
// blend maps
// the index of the first blend map used in this pass
int blendmapStart;
if (mLayerList.size() == 1) // special case. if there's only one layer, we don't need blend maps at all
blendmapStart = 0;
else
blendmapStart = getBlendmapIndexForLayer(layerOffset+blendmapOffset);
for (int i = 0; i < numBlendTextures; ++i)
{
sh::MaterialInstanceTextureUnit* blendTex = p->createTextureUnit ("blendMap" + Ogre::StringConverter::toString(i));
blendTex->setProperty ("direct_texture", sh::makeProperty (new sh::StringValue(mBlendmapList[blendmapStart+i]->getName())));
blendTex->setProperty ("tex_address_mode", sh::makeProperty (new sh::StringValue("clamp")));
}
// layer maps
bool anyNormalMaps = false;
bool anyParallax = false;
size_t normalMaps = 0;
for (int i = 0; i < numLayersInThisPass; ++i)
{
const LayerInfo& layer = mLayerList[layerOffset+i];
// diffuse map
sh::MaterialInstanceTextureUnit* diffuseTex = p->createTextureUnit ("diffuseMap" + Ogre::StringConverter::toString(i));
diffuseTex->setProperty ("direct_texture", sh::makeProperty (new sh::StringValue(layer.mDiffuseMap)));
// normal map (optional)
bool useNormalMap = mNormalMapping && !mLayerList[layerOffset+i].mNormalMap.empty() && !renderCompositeMap;
bool useParallax = useNormalMap && mParallaxMapping && layer.mParallax;
bool useSpecular = layer.mSpecular;
if (useNormalMap)
{
anyNormalMaps = true;
anyParallax = anyParallax || useParallax;
sh::MaterialInstanceTextureUnit* normalTex = p->createTextureUnit ("normalMap" + Ogre::StringConverter::toString(i));
normalTex->setProperty ("direct_texture", sh::makeProperty (new sh::StringValue(layer.mNormalMap)));
}
p->mShaderProperties.setProperty ("use_normal_map_" + Ogre::StringConverter::toString(i),
sh::makeProperty (new sh::BooleanValue(useNormalMap)));
p->mShaderProperties.setProperty ("use_parallax_" + Ogre::StringConverter::toString(i),
sh::makeProperty (new sh::BooleanValue(useParallax)));
p->mShaderProperties.setProperty ("use_specular_" + Ogre::StringConverter::toString(i),
sh::makeProperty (new sh::BooleanValue(useSpecular)));
boost::hash_combine(normalMaps, useNormalMap);
boost::hash_combine(normalMaps, useNormalMap && layer.mParallax);
boost::hash_combine(normalMaps, useSpecular);
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("")));
}
}
p->mShaderProperties.setProperty ("normal_map_enabled",
sh::makeProperty (new sh::BooleanValue(anyNormalMaps)));
p->mShaderProperties.setProperty ("parallax_enabled",
sh::makeProperty (new sh::BooleanValue(anyParallax)));
// Since the permutation handler can't handle dynamic property names,
// combine normal map settings for all layers into one value
p->mShaderProperties.setProperty ("normal_maps",
sh::makeProperty (new sh::IntValue(normalMaps)));
// shadow
if (shadows)
{
for (int i = 0; i < (mSplitShadows ? 3 : 1); ++i)
{
sh::MaterialInstanceTextureUnit* shadowTex = p->createTextureUnit ("shadowMap" + Ogre::StringConverter::toString(i));
shadowTex->setProperty ("content_type", sh::makeProperty<sh::StringValue> (new sh::StringValue("shadow")));
}
}
p->mShaderProperties.setProperty ("shadowtexture_offset", sh::makeProperty (new sh::StringValue(
Ogre::StringConverter::toString(numBlendTextures + numLayersInThisPass))));
// Make sure the pass index is fed to the permutation handler, because blendmap components may be different selectTechnique(0);
p->mShaderProperties.setProperty ("pass_index", sh::makeProperty(new sh::IntValue(layerOffset))); }
assert ((int)p->mTexUnits.size() == OGRE_MAX_TEXTURE_LAYERS - remainingTextureUnits); bool Effect::define_techniques()
{
addTechnique(new FixedFunctionTechnique(mLayers, mBlendmaps));
layerOffset += numLayersInThisPass; return true;
}
}
}
#endif
return Ogre::MaterialManager::getSingleton().getByName(name.str());
} }
} }

@ -22,51 +22,55 @@
#ifndef COMPONENTS_TERRAIN_MATERIAL_H #ifndef COMPONENTS_TERRAIN_MATERIAL_H
#define COMPONENTS_TERRAIN_MATERIAL_H #define COMPONENTS_TERRAIN_MATERIAL_H
#include <OgreMaterial.h> #include <osgFX/Technique>
#include <osgFX/Effect>
#include "storage.hpp" #include "defs.hpp"
namespace osg
{
class Texture2D;
}
namespace Terrain namespace Terrain
{ {
class MaterialGenerator class FixedFunctionTechnique : public osgFX::Technique
{ {
public: public:
MaterialGenerator (); FixedFunctionTechnique(
const std::vector<osg::ref_ptr<osg::Texture2D> >& layers,
void setLayerList (const std::vector<LayerInfo>& layerList) { mLayerList = layerList; } const std::vector<osg::ref_ptr<osg::Texture2D> >& blendmaps);
bool hasLayers() { return mLayerList.size() > 0; }
void setBlendmapList (const std::vector<Ogre::TexturePtr>& blendmapList) { mBlendmapList = blendmapList; }
const std::vector<Ogre::TexturePtr>& getBlendmapList() { return mBlendmapList; }
void setCompositeMap (const std::string& name) { mCompositeMap = name; }
void enableShaders(bool shaders) { mShaders = shaders; } protected:
void enableShadows(bool shadows) { mShadows = shadows; } virtual void define_passes() {}
void enableNormalMapping(bool normalMapping) { mNormalMapping = normalMapping; } };
void enableParallaxMapping(bool parallaxMapping) { mParallaxMapping = parallaxMapping; }
void enableSplitShadows(bool splitShadows) { mSplitShadows = splitShadows; }
/// Creates a material suitable for displaying a chunk of terrain using alpha-blending. class Effect : public osgFX::Effect
Ogre::MaterialPtr generate (); {
public:
Effect(
const std::vector<osg::ref_ptr<osg::Texture2D> >& layers,
const std::vector<osg::ref_ptr<osg::Texture2D> >& blendmaps);
/// Creates a material suitable for displaying a chunk of terrain using a ready-made composite map. virtual bool define_techniques();
Ogre::MaterialPtr generateForCompositeMap ();
/// Creates a material suitable for rendering composite maps, i.e. for "baking" several layer textures virtual const char *effectName() const
/// into one. The main difference compared to a normal material is that no shading is applied at this point. {
Ogre::MaterialPtr generateForCompositeMapRTT (); return NULL;
}
virtual const char *effectDescription() const
{
return NULL;
}
virtual const char *effectAuthor() const
{
return NULL;
}
private: private:
Ogre::MaterialPtr create (bool renderCompositeMap, bool displayCompositeMap); std::vector<osg::ref_ptr<osg::Texture2D> > mLayers;
std::vector<osg::ref_ptr<osg::Texture2D> > mBlendmaps;
std::vector<LayerInfo> mLayerList;
std::vector<Ogre::TexturePtr> mBlendmapList;
std::string mCompositeMap;
bool mShaders;
bool mShadows;
bool mSplitShadows;
bool mNormalMapping;
bool mParallaxMapping;
}; };
} }

@ -1,611 +0,0 @@
/*
* Copyright (c) 2015 scrawl <scrawl@baseoftrash.de>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include "quadtreenode.hpp"
#include <OgreSceneManager.h>
#include <OgreManualObject.h>
#include <OgreSceneNode.h>
#include <OgreMaterialManager.h>
#include <OgreTextureManager.h>
#include "defaultworld.hpp"
#include "chunk.hpp"
#include "storage.hpp"
#include "buffercache.hpp"
#include "material.hpp"
using namespace Terrain;
namespace
{
int Log2( int n )
{
assert(n > 0);
int targetlevel = 0;
while (n >>= 1) ++targetlevel;
return targetlevel;
}
// Utility functions for neighbour finding algorithm
ChildDirection reflect(ChildDirection dir, Direction dir2)
{
assert(dir != Root);
const int lookupTable[4][4] =
{
// NW NE SW SE
{ SW, SE, NW, NE }, // N
{ NE, NW, SE, SW }, // E
{ SW, SE, NW, NE }, // S
{ NE, NW, SE, SW } // W
};
return (ChildDirection)lookupTable[dir2][dir];
}
bool adjacent(ChildDirection dir, Direction dir2)
{
assert(dir != Root);
const bool lookupTable[4][4] =
{
// NW NE SW SE
{ true, true, false, false }, // N
{ false, true, false, true }, // E
{ false, false, true, true }, // S
{ true, false, true, false } // W
};
return lookupTable[dir2][dir];
}
// Algorithm described by Hanan Samet - 'Neighbour Finding in Quadtrees'
// http://www.cs.umd.edu/~hjs/pubs/SametPRIP81.pdf
QuadTreeNode* searchNeighbourRecursive (QuadTreeNode* currentNode, Direction dir)
{
if (!currentNode->getParent())
return NULL; // Arrived at root node, the root node does not have neighbours
QuadTreeNode* nextNode;
if (adjacent(currentNode->getDirection(), dir))
nextNode = searchNeighbourRecursive(currentNode->getParent(), dir);
else
nextNode = currentNode->getParent();
if (nextNode && nextNode->hasChildren())
return nextNode->getChild(reflect(currentNode->getDirection(), dir));
else
return NULL;
}
// Create a 2D quad
void makeQuad(Ogre::SceneManager* sceneMgr, float left, float top, float right, float bottom, Ogre::MaterialPtr material)
{
Ogre::ManualObject* manual = sceneMgr->createManualObject();
// Use identity view/projection matrices to get a 2d quad
manual->setUseIdentityProjection(true);
manual->setUseIdentityView(true);
manual->begin(material->getName());
float normLeft = left*2-1;
float normTop = top*2-1;
float normRight = right*2-1;
float normBottom = bottom*2-1;
manual->position(normLeft, normTop, 0.0);
manual->textureCoord(0, 1);
manual->position(normRight, normTop, 0.0);
manual->textureCoord(1, 1);
manual->position(normRight, normBottom, 0.0);
manual->textureCoord(1, 0);
manual->position(normLeft, normBottom, 0.0);
manual->textureCoord(0, 0);
manual->quad(0,1,2,3);
manual->end();
Ogre::AxisAlignedBox aabInf;
aabInf.setInfinite();
manual->setBoundingBox(aabInf);
sceneMgr->getRootSceneNode()->createChildSceneNode()->attachObject(manual);
}
}
QuadTreeNode::QuadTreeNode(DefaultWorld* terrain, ChildDirection dir, float size, const Ogre::Vector2 &center, QuadTreeNode* parent)
: mMaterialGenerator(NULL)
, mLoadState(LS_Unloaded)
, mIsDummy(false)
, mSize(size)
, mLodLevel(Log2(static_cast<int>(mSize)))
, mBounds(Ogre::AxisAlignedBox::BOX_NULL)
, mWorldBounds(Ogre::AxisAlignedBox::BOX_NULL)
, mDirection(dir)
, mCenter(center)
, mSceneNode(NULL)
, mParent(parent)
, mChunk(NULL)
, mTerrain(terrain)
{
mBounds.setNull();
for (int i=0; i<4; ++i)
mChildren[i] = NULL;
for (int i=0; i<4; ++i)
mNeighbours[i] = NULL;
if (mDirection == Root)
mSceneNode = mTerrain->getRootSceneNode();
else
mSceneNode = mTerrain->getSceneManager()->createSceneNode();
Ogre::Vector2 pos (0,0);
if (mParent)
pos = mParent->getCenter();
pos = mCenter - pos;
float cellWorldSize = mTerrain->getStorage()->getCellWorldSize();
Ogre::Vector3 sceneNodePos (pos.x*cellWorldSize, pos.y*cellWorldSize, 0);
mTerrain->convertPosition(sceneNodePos);
mSceneNode->setPosition(sceneNodePos);
mMaterialGenerator = new MaterialGenerator();
mMaterialGenerator->enableShaders(mTerrain->getShadersEnabled());
}
void QuadTreeNode::createChild(ChildDirection id, float size, const Ogre::Vector2 &center)
{
mChildren[id] = new QuadTreeNode(mTerrain, id, size, center, this);
}
QuadTreeNode::~QuadTreeNode()
{
for (int i=0; i<4; ++i)
delete mChildren[i];
delete mChunk;
delete mMaterialGenerator;
}
QuadTreeNode* QuadTreeNode::getNeighbour(Direction dir)
{
return mNeighbours[static_cast<int>(dir)];
}
void QuadTreeNode::initNeighbours()
{
for (int i=0; i<4; ++i)
mNeighbours[i] = searchNeighbourRecursive(this, (Direction)i);
if (hasChildren())
for (int i=0; i<4; ++i)
mChildren[i]->initNeighbours();
}
void QuadTreeNode::initAabb()
{
float cellWorldSize = mTerrain->getStorage()->getCellWorldSize();
if (hasChildren())
{
for (int i=0; i<4; ++i)
{
mChildren[i]->initAabb();
mBounds.merge(mChildren[i]->getBoundingBox());
}
float minH, maxH;
switch (mTerrain->getAlign())
{
case Terrain::Align_XY:
minH = mBounds.getMinimum().z;
maxH = mBounds.getMaximum().z;
break;
case Terrain::Align_XZ:
minH = mBounds.getMinimum().y;
maxH = mBounds.getMaximum().y;
break;
case Terrain::Align_YZ:
minH = mBounds.getMinimum().x;
maxH = mBounds.getMaximum().x;
break;
}
Ogre::Vector3 min(-mSize/2*cellWorldSize, -mSize/2*cellWorldSize, minH);
Ogre::Vector3 max(Ogre::Vector3(mSize/2*cellWorldSize, mSize/2*cellWorldSize, maxH));
mBounds = Ogre::AxisAlignedBox (min, max);
mTerrain->convertBounds(mBounds);
}
Ogre::Vector3 offset(mCenter.x*cellWorldSize, mCenter.y*cellWorldSize, 0);
mTerrain->convertPosition(offset);
mWorldBounds = Ogre::AxisAlignedBox(mBounds.getMinimum() + offset,
mBounds.getMaximum() + offset);
}
void QuadTreeNode::setBoundingBox(const Ogre::AxisAlignedBox &box)
{
mBounds = box;
}
const Ogre::AxisAlignedBox& QuadTreeNode::getBoundingBox()
{
return mBounds;
}
const Ogre::AxisAlignedBox& QuadTreeNode::getWorldBoundingBox()
{
return mWorldBounds;
}
bool QuadTreeNode::update(const Ogre::Vector3 &cameraPos)
{
if (isDummy())
return true;
if (mBounds.isNull())
return true;
float dist = mWorldBounds.distance(cameraPos);
// Make sure our scene node is attached
if (!mSceneNode->isInSceneGraph())
{
mParent->getSceneNode()->addChild(mSceneNode);
}
// Simple LOD selection
/// \todo use error metrics?
size_t wantedLod = 0;
float cellWorldSize = mTerrain->getStorage()->getCellWorldSize();
if (dist > cellWorldSize*64)
wantedLod = 6;
else if (dist > cellWorldSize*32)
wantedLod = 5;
else if (dist > cellWorldSize*12)
wantedLod = 4;
else if (dist > cellWorldSize*5)
wantedLod = 3;
else if (dist > cellWorldSize*2)
wantedLod = 2;
else if (dist > cellWorldSize * 1.42) // < sqrt2 so the 3x3 grid around player is always highest lod
wantedLod = 1;
bool wantToDisplay = mSize <= mTerrain->getMaxBatchSize() && mLodLevel <= wantedLod;
if (wantToDisplay)
{
// Wanted LOD is small enough to render this node in one chunk
if (mLoadState == LS_Unloaded)
{
mLoadState = LS_Loading;
mTerrain->queueLoad(this);
return false;
}
if (mLoadState == LS_Loaded)
{
// Additional (index buffer) LOD is currently disabled.
// This is due to a problem with the LOD selection when a node splits.
// After splitting, the distance is measured from the children's bounding boxes, which are possibly
// further away than the original node's bounding box, possibly causing a child to switch to a *lower* LOD
// than the original node.
// In short, we'd sometimes get a switch to a lesser detail when actually moving closer.
// This wouldn't be so bad, but unfortunately it also breaks LOD edge connections if a neighbour
// node hasn't split yet, and has a higher LOD than our node's child:
// ----- ----- ------------
// | LOD | LOD | |
// | 1 | 1 | |
// |-----|-----| LOD 0 |
// | LOD | LOD | |
// | 0 | 0 | |
// ----- ----- ------------
// To prevent this, nodes of the same size need to always select the same LOD, which is basically what we're
// doing here.
// But this "solution" does increase triangle overhead, so eventually we need to find a more clever way.
//mChunk->setAdditionalLod(wantedLod - mLodLevel);
if (!mChunk->getVisible() && hasChildren())
{
for (int i=0; i<4; ++i)
mChildren[i]->unload(true);
}
mChunk->setVisible(true);
return true;
}
return false; // LS_Loading
}
// We do not want to display this node - delegate to children if they are already loaded
if (!wantToDisplay && hasChildren())
{
if (mChunk)
{
// Are children already loaded?
bool childrenLoaded = true;
for (int i=0; i<4; ++i)
if (!mChildren[i]->update(cameraPos))
childrenLoaded = false;
if (!childrenLoaded)
{
mChunk->setVisible(true);
// Make sure child scene nodes are detached until all children are loaded
mSceneNode->removeAllChildren();
}
else
{
// Delegation went well, we can unload now
unload();
for (int i=0; i<4; ++i)
{
if (!mChildren[i]->getSceneNode()->isInSceneGraph())
mSceneNode->addChild(mChildren[i]->getSceneNode());
}
}
return true;
}
else
{
bool success = true;
for (int i=0; i<4; ++i)
success = mChildren[i]->update(cameraPos) & success;
return success;
}
}
return false;
}
void QuadTreeNode::load(const LoadResponseData &data)
{
assert (!mChunk);
mChunk = new Chunk(mTerrain->getBufferCache().getUVBuffer(), mBounds, data.mPositions, data.mNormals, data.mColours);
mChunk->setVisibilityFlags(mTerrain->getVisibilityFlags());
mChunk->setCastShadows(true);
mSceneNode->attachObject(mChunk);
mMaterialGenerator->enableShadows(mTerrain->getShadowsEnabled());
mMaterialGenerator->enableSplitShadows(mTerrain->getSplitShadowsEnabled());
if (mTerrain->areLayersLoaded())
{
if (mSize == 1)
{
mChunk->setMaterial(mMaterialGenerator->generate());
}
else
{
ensureCompositeMap();
mMaterialGenerator->setCompositeMap(mCompositeMap->getName());
mChunk->setMaterial(mMaterialGenerator->generateForCompositeMap());
}
}
// else: will be loaded in loadMaterials() after background thread has finished loading layers
mChunk->setVisible(false);
mLoadState = LS_Loaded;
}
void QuadTreeNode::unload(bool recursive)
{
if (mChunk)
{
mSceneNode->detachObject(mChunk);
delete mChunk;
mChunk = NULL;
if (!mCompositeMap.isNull())
{
Ogre::TextureManager::getSingleton().remove(mCompositeMap->getName());
mCompositeMap.setNull();
}
// Do *not* set this when we are still loading!
mLoadState = LS_Unloaded;
}
if (recursive && hasChildren())
{
for (int i=0; i<4; ++i)
mChildren[i]->unload(true);
}
}
void QuadTreeNode::updateIndexBuffers()
{
if (hasChunk())
{
// Fetch a suitable index buffer (which may be shared)
size_t ourLod = getActualLodLevel();
unsigned int flags = 0;
for (int i=0; i<4; ++i)
{
QuadTreeNode* neighbour = getNeighbour((Direction)i);
// If the neighbour isn't currently rendering itself,
// go up until we find one. NOTE: We don't need to go down,
// because in that case neighbour's detail would be higher than
// our detail and the neighbour would handle stitching by itself.
while (neighbour && !neighbour->hasChunk())
neighbour = neighbour->getParent();
size_t lod = 0;
if (neighbour)
lod = neighbour->getActualLodLevel();
if (lod <= ourLod) // We only need to worry about neighbours less detailed than we are -
lod = 0; // neighbours with more detail will do the stitching themselves
// Use 4 bits for each LOD delta
if (lod > 0)
{
assert (lod - ourLod < (1 << 4));
flags |= static_cast<unsigned int>(lod - ourLod) << (4*i);
}
}
flags |= 0 /*((int)mAdditionalLod)*/ << (4*4);
mChunk->setIndexBuffer(mTerrain->getBufferCache().getIndexBuffer(flags));
}
else if (hasChildren())
{
for (int i=0; i<4; ++i)
mChildren[i]->updateIndexBuffers();
}
}
bool QuadTreeNode::hasChunk()
{
return mSceneNode->isInSceneGraph() && mChunk && mChunk->getVisible();
}
size_t QuadTreeNode::getActualLodLevel()
{
assert(hasChunk() && "Can't get actual LOD level if this node has no render chunk");
return mLodLevel /* + mChunk->getAdditionalLod() */;
}
void QuadTreeNode::loadLayers(const LayerCollection& collection)
{
assert (!mMaterialGenerator->hasLayers());
std::vector<Ogre::TexturePtr> blendTextures;
for (std::vector<Ogre::PixelBox>::const_iterator it = collection.mBlendmaps.begin(); it != collection.mBlendmaps.end(); ++it)
{
// TODO: clean up blend textures on destruction
static int count=0;
Ogre::TexturePtr map = Ogre::TextureManager::getSingleton().createManual("terrain/blend/"
+ Ogre::StringConverter::toString(count++), Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME,
Ogre::TEX_TYPE_2D, it->getWidth(), it->getHeight(), 0, it->format);
Ogre::DataStreamPtr stream(new Ogre::MemoryDataStream(it->data, it->getWidth()*it->getHeight()*Ogre::PixelUtil::getNumElemBytes(it->format), true));
map->loadRawData(stream, it->getWidth(), it->getHeight(), it->format);
blendTextures.push_back(map);
}
mMaterialGenerator->setLayerList(collection.mLayers);
mMaterialGenerator->setBlendmapList(blendTextures);
}
void QuadTreeNode::loadMaterials()
{
if (isDummy())
return;
// Load children first since we depend on them when creating a composite map
if (hasChildren())
{
for (int i=0; i<4; ++i)
mChildren[i]->loadMaterials();
}
if (mChunk)
{
if (mSize == 1)
{
mChunk->setMaterial(mMaterialGenerator->generate());
}
else
{
ensureCompositeMap();
mMaterialGenerator->setCompositeMap(mCompositeMap->getName());
mChunk->setMaterial(mMaterialGenerator->generateForCompositeMap());
}
}
}
void QuadTreeNode::prepareForCompositeMap(Ogre::TRect<float> area)
{
Ogre::SceneManager* sceneMgr = mTerrain->getCompositeMapSceneManager();
if (mIsDummy)
{
// TODO - store this default material somewhere instead of creating one for each empty cell
MaterialGenerator matGen;
matGen.enableShaders(mTerrain->getShadersEnabled());
std::vector<LayerInfo> layer;
layer.push_back(mTerrain->getStorage()->getDefaultLayer());
matGen.setLayerList(layer);
makeQuad(sceneMgr, area.left, area.top, area.right, area.bottom, matGen.generateForCompositeMapRTT());
return;
}
if (mSize > 1)
{
assert(hasChildren());
// 0,0 -------- 1,0
// | | |
// |-----|------|
// | | |
// 0,1 -------- 1,1
float halfW = area.width()/2.f;
float halfH = area.height()/2.f;
mChildren[NW]->prepareForCompositeMap(Ogre::TRect<float>(area.left, area.top, area.right-halfW, area.bottom-halfH));
mChildren[NE]->prepareForCompositeMap(Ogre::TRect<float>(area.left+halfW, area.top, area.right, area.bottom-halfH));
mChildren[SW]->prepareForCompositeMap(Ogre::TRect<float>(area.left, area.top+halfH, area.right-halfW, area.bottom));
mChildren[SE]->prepareForCompositeMap(Ogre::TRect<float>(area.left+halfW, area.top+halfH, area.right, area.bottom));
}
else
{
// TODO: when to destroy?
Ogre::MaterialPtr material = mMaterialGenerator->generateForCompositeMapRTT();
makeQuad(sceneMgr, area.left, area.top, area.right, area.bottom, material);
}
}
void QuadTreeNode::ensureCompositeMap()
{
if (!mCompositeMap.isNull())
return;
static int i=0;
std::stringstream name;
name << "terrain/comp" << i++;
const int size = 128;
mCompositeMap = Ogre::TextureManager::getSingleton().createManual(
name.str(), Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME,
Ogre::TEX_TYPE_2D, size, size, Ogre::MIP_DEFAULT, Ogre::PF_A8B8G8R8);
// Create quads for each cell
prepareForCompositeMap(Ogre::TRect<float>(0,0,1,1));
mTerrain->renderCompositeMap(mCompositeMap);
mTerrain->clearCompositeMapSceneManager();
}
void QuadTreeNode::applyMaterials()
{
if (mChunk)
{
mMaterialGenerator->enableShadows(mTerrain->getShadowsEnabled());
mMaterialGenerator->enableSplitShadows(mTerrain->getSplitShadowsEnabled());
if (mSize <= 1)
mChunk->setMaterial(mMaterialGenerator->generate());
else
mChunk->setMaterial(mMaterialGenerator->generateForCompositeMap());
}
if (hasChildren())
for (int i=0; i<4; ++i)
mChildren[i]->applyMaterials();
}

@ -1,189 +0,0 @@
/*
* Copyright (c) 2015 scrawl <scrawl@baseoftrash.de>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#ifndef COMPONENTS_TERRAIN_QUADTREENODE_H
#define COMPONENTS_TERRAIN_QUADTREENODE_H
#include <OgreAxisAlignedBox.h>
#include <OgreVector2.h>
#include <OgreTexture.h>
#include "defs.hpp"
namespace Ogre
{
class Rectangle2D;
}
namespace Terrain
{
class DefaultWorld;
class Chunk;
class MaterialGenerator;
struct LoadResponseData;
enum ChildDirection
{
NW = 0,
NE = 1,
SW = 2,
SE = 3,
Root
};
enum LoadState
{
LS_Unloaded,
LS_Loading,
LS_Loaded
};
/**
* @brief A node in the quad tree for our terrain. Depending on LOD,
* a node can either choose to render itself in one batch (merging its children),
* or delegate the render process to its children, rendering each child in at least one batch.
*/
class QuadTreeNode
{
public:
/// @param terrain
/// @param dir relative to parent, or Root if we are the root node
/// @param size size (in *cell* units!)
/// @param center center (in *cell* units!)
/// @param parent parent node
QuadTreeNode (DefaultWorld* terrain, ChildDirection dir, float size, const Ogre::Vector2& center, QuadTreeNode* parent);
~QuadTreeNode();
/// Rebuild all materials
void applyMaterials();
/// Initialize neighbours - do this after the quadtree is built
void initNeighbours();
/// Initialize bounding boxes of non-leafs by merging children bounding boxes.
/// Do this after the quadtree is built - note that leaf bounding boxes
/// need to be set first via setBoundingBox!
void initAabb();
/// @note takes ownership of \a child
void createChild (ChildDirection id, float size, const Ogre::Vector2& center);
/// Mark this node as a dummy node. This can happen if the terrain size isn't a power of two.
/// For the QuadTree to work, we need to round the size up to a power of two, which means we'll
/// end up with empty nodes that don't actually render anything.
void markAsDummy() { mIsDummy = true; }
bool isDummy() { return mIsDummy; }
QuadTreeNode* getParent() { return mParent; }
Ogre::SceneNode* getSceneNode() { return mSceneNode; }
int getSize() { return static_cast<int>(mSize); }
Ogre::Vector2 getCenter() { return mCenter; }
bool hasChildren() { return mChildren[0] != 0; }
QuadTreeNode* getChild(ChildDirection dir) { return mChildren[dir]; }
/// Get neighbour node in this direction
QuadTreeNode* getNeighbour (Direction dir);
/// Returns our direction relative to the parent node, or Root if we are the root node.
ChildDirection getDirection() { return mDirection; }
/// Set bounding box in local coordinates. Should be done at load time for leaf nodes.
/// Other nodes can merge AABB of child nodes.
void setBoundingBox (const Ogre::AxisAlignedBox& box);
/// Get bounding box in local coordinates
const Ogre::AxisAlignedBox& getBoundingBox();
const Ogre::AxisAlignedBox& getWorldBoundingBox();
DefaultWorld* getTerrain() { return mTerrain; }
/// Adjust LODs for the given camera position, possibly splitting up chunks or merging them.
/// @return Did we (or all of our children) choose to render?
bool update (const Ogre::Vector3& cameraPos);
/// Adjust index buffers of chunks to stitch together chunks of different LOD, so that cracks are avoided.
/// Call after QuadTreeNode::update!
void updateIndexBuffers();
/// Destroy chunks rendered by this node *and* its children (if param is true)
void destroyChunks(bool children);
/// Get the effective LOD level if this node was rendered in one chunk
/// with Storage::getCellVertices^2 vertices
size_t getNativeLodLevel() { return mLodLevel; }
/// Get the effective current LOD level used by the chunk rendering this node
size_t getActualLodLevel();
/// Is this node currently configured to render itself?
bool hasChunk();
/// Add a textured quad to a specific 2d area in the composite map scenemanager.
/// Only nodes with size <= 1 can be rendered with alpha blending, so larger nodes will simply
/// call this method on their children.
/// @note Do not call this before World::areLayersLoaded() == true
/// @param area area in image space to put the quad
void prepareForCompositeMap(Ogre::TRect<float> area);
/// Create a chunk for this node from the given data.
void load (const LoadResponseData& data);
void unload(bool recursive=false);
void loadLayers (const LayerCollection& collection);
/// This is recursive! Call it once on the root node after all leafs have loaded layers.
void loadMaterials();
LoadState getLoadState() { return mLoadState; }
private:
// Stored here for convenience in case we need layer list again
MaterialGenerator* mMaterialGenerator;
LoadState mLoadState;
bool mIsDummy;
float mSize;
size_t mLodLevel; // LOD if we were to render this node in one chunk
Ogre::AxisAlignedBox mBounds;
Ogre::AxisAlignedBox mWorldBounds;
ChildDirection mDirection;
Ogre::Vector2 mCenter;
Ogre::SceneNode* mSceneNode;
QuadTreeNode* mParent;
QuadTreeNode* mChildren[4];
QuadTreeNode* mNeighbours[4];
Chunk* mChunk;
DefaultWorld* mTerrain;
Ogre::TexturePtr mCompositeMap;
void ensureCompositeMap();
};
}
#endif

@ -19,3 +19,5 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE. * THE SOFTWARE.
*/ */
#include "storage.hpp"

@ -22,10 +22,20 @@
#ifndef COMPONENTS_TERRAIN_STORAGE_H #ifndef COMPONENTS_TERRAIN_STORAGE_H
#define COMPONENTS_TERRAIN_STORAGE_H #define COMPONENTS_TERRAIN_STORAGE_H
#include <OgreHardwareVertexBuffer.h> #include <vector>
#include <osg/Vec2f>
#include <osg/Vec3f>
#include <osg/ref_ptr>
#include <osg/Array>
#include "defs.hpp" #include "defs.hpp"
namespace osg
{
class Image;
}
namespace Terrain namespace Terrain
{ {
/// We keep storage of terrain data abstract here since we need different implementations for game and editor /// We keep storage of terrain data abstract here since we need different implementations for game and editor
@ -46,7 +56,7 @@ namespace Terrain
/// @param min min height will be stored here /// @param min min height will be stored here
/// @param max max height will be stored here /// @param max max height will be stored here
/// @return true if there was data available for this terrain chunk /// @return true if there was data available for this terrain chunk
virtual bool getMinMaxHeights (float size, const Ogre::Vector2& center, float& min, float& max) = 0; virtual bool getMinMaxHeights (float size, const osg::Vec2f& center, float& min, float& max) = 0;
/// Fill vertex buffers for a terrain chunk. /// Fill vertex buffers for a terrain chunk.
/// @note May be called from background threads. Make sure to only call thread-safe functions from here! /// @note May be called from background threads. Make sure to only call thread-safe functions from here!
@ -59,11 +69,12 @@ namespace Terrain
/// @param positions buffer to write vertices /// @param positions buffer to write vertices
/// @param normals buffer to write vertex normals /// @param normals buffer to write vertex normals
/// @param colours buffer to write vertex colours /// @param colours buffer to write vertex colours
virtual void fillVertexBuffers (int lodLevel, float size, const Ogre::Vector2& center, Terrain::Alignment align, virtual void fillVertexBuffers (int lodLevel, float size, const osg::Vec2f& center,
std::vector<float>& positions, osg::ref_ptr<osg::Vec3Array> positions,
std::vector<float>& normals, osg::ref_ptr<osg::Vec3Array> normals,
std::vector<Ogre::uint8>& colours) = 0; osg::ref_ptr<osg::Vec4Array> colours) = 0;
typedef std::vector<osg::ref_ptr<osg::Image> > ImageVector;
/// Create textures holding layer blend values for a terrain chunk. /// 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 /// @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. /// have to do a ridiculous amount of different layers. For larger chunks, composite maps should be used.
@ -75,23 +86,11 @@ namespace Terrain
/// can utilize packing, FFP can't. /// can utilize packing, FFP can't.
/// @param blendmaps created blendmaps will be written here /// @param blendmaps created blendmaps will be written here
/// @param layerList names of the layer textures used will be written here /// @param layerList names of the layer textures used will be written here
virtual void getBlendmaps (float chunkSize, const Ogre::Vector2& chunkCenter, bool pack, virtual void getBlendmaps (float chunkSize, const osg::Vec2f& chunkCenter, bool pack,
std::vector<Ogre::PixelBox>& blendmaps, ImageVector& blendmaps,
std::vector<LayerInfo>& layerList) = 0; std::vector<LayerInfo>& layerList) = 0;
/// Retrieve pixel data for textures holding layer blend values for terrain chunks and layer texture information. virtual float getHeightAt (const osg::Vec3f& worldPos) = 0;
/// This variant is provided to eliminate the overhead of virtual function calls when retrieving a large number of blendmaps at once.
/// @note The terrain chunks 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.
/// @note May be called from background threads. Make sure to only call thread-safe functions from here!
/// @param nodes A collection of nodes for which to retrieve the aforementioned data
/// @param out Output vector
/// @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.
virtual void getBlendmaps (const std::vector<QuadTreeNode*>& nodes, std::vector<LayerCollection>& out, bool pack) = 0;
virtual float getHeightAt (const Ogre::Vector3& worldPos) = 0;
virtual LayerInfo getDefaultLayer() = 0; virtual LayerInfo getDefaultLayer() = 0;

@ -19,22 +19,54 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE. * THE SOFTWARE.
*/ */
#include "terraingrid.hpp" #include "terraingrid.hpp"
#include <OgreSceneManager.h> #include <memory>
#include <OgreSceneNode.h>
#include <OgreAxisAlignedBox.h> #include <components/resource/resourcesystem.hpp>
#include <components/resource/texturemanager.hpp>
#include <components/sceneutil/lightmanager.hpp>
#include <osg/PositionAttitudeTransform>
#include <osg/Geometry>
#include <osg/Geode>
#include <osgFX/Effect>
#include <osgUtil/IncrementalCompileOperation>
#include "chunk.hpp" #include "material.hpp"
#include "storage.hpp"
namespace
{
class StaticBoundingBoxCallback : public osg::Drawable::ComputeBoundingBoxCallback
{
public:
StaticBoundingBoxCallback(const osg::BoundingBox& bounds)
: mBoundingBox(bounds)
{
}
virtual osg::BoundingBox computeBound(const osg::Drawable&) const
{
return mBoundingBox;
}
private:
osg::BoundingBox mBoundingBox;
};
}
namespace Terrain namespace Terrain
{ {
TerrainGrid::TerrainGrid(Ogre::SceneManager *sceneMgr, Terrain::Storage *storage, int visibilityFlags, bool shaders, Terrain::Alignment align) TerrainGrid::TerrainGrid(osg::Group* parent, Resource::ResourceSystem* resourceSystem, osgUtil::IncrementalCompileOperation* ico,
: Terrain::World(sceneMgr, storage, visibilityFlags, shaders, align) Storage* storage, int nodeMask)
, mVisible(true) : Terrain::World(parent, resourceSystem, ico, storage, nodeMask)
{ {
mRootNode = mSceneMgr->getRootSceneNode()->createChildSceneNode();
} }
TerrainGrid::~TerrainGrid() TerrainGrid::~TerrainGrid()
@ -43,143 +75,118 @@ TerrainGrid::~TerrainGrid()
{ {
unloadCell(mGrid.begin()->first.first, mGrid.begin()->first.second); unloadCell(mGrid.begin()->first.first, mGrid.begin()->first.second);
} }
mSceneMgr->destroySceneNode(mRootNode);
} }
void TerrainGrid::update(const Ogre::Vector3 &cameraPos) class GridElement
{ {
} public:
osg::ref_ptr<osg::PositionAttitudeTransform> mNode;
};
void TerrainGrid::loadCell(int x, int y) void TerrainGrid::loadCell(int x, int y)
{ {
if (mGrid.find(std::make_pair(x, y)) != mGrid.end()) if (mGrid.find(std::make_pair(x, y)) != mGrid.end())
return; // already loaded return; // already loaded
Ogre::Vector2 center(x+0.5f, y+0.5f); osg::Vec2f center(x+0.5f, y+0.5f);
float minH, maxH; float minH, maxH;
if (!mStorage->getMinMaxHeights(1, center, minH, maxH)) if (!mStorage->getMinMaxHeights(1, center, minH, maxH))
return; // no terrain defined return; // no terrain defined
Ogre::Vector3 min (-0.5f*mStorage->getCellWorldSize(), std::auto_ptr<GridElement> element (new GridElement);
-0.5f*mStorage->getCellWorldSize(),
minH);
Ogre::Vector3 max (0.5f*mStorage->getCellWorldSize(),
0.5f*mStorage->getCellWorldSize(),
maxH);
Ogre::AxisAlignedBox bounds(min, max);
GridElement element; osg::Vec2f worldCenter = center*mStorage->getCellWorldSize();
element->mNode = new osg::PositionAttitudeTransform;
element->mNode->setPosition(osg::Vec3f(worldCenter.x(), worldCenter.y(), 0.f));
mTerrainRoot->addChild(element->mNode);
Ogre::Vector2 worldCenter = center*mStorage->getCellWorldSize(); osg::ref_ptr<osg::Vec3Array> positions (new osg::Vec3Array);
element.mSceneNode = mRootNode->createChildSceneNode(Ogre::Vector3(worldCenter.x, worldCenter.y, 0)); osg::ref_ptr<osg::Vec3Array> normals (new osg::Vec3Array);
osg::ref_ptr<osg::Vec4Array> colors (new osg::Vec4Array);
std::vector<float> positions; mStorage->fillVertexBuffers(0, 1, center, positions, normals, colors);
std::vector<float> normals;
std::vector<Ogre::uint8> colours;
mStorage->fillVertexBuffers(0, 1, center, mAlign, positions, normals, colours);
element.mChunk = new Terrain::Chunk(mCache.getUVBuffer(), bounds, positions, normals, colours); osg::ref_ptr<osg::Geometry> geometry (new osg::Geometry);
element.mChunk->setIndexBuffer(mCache.getIndexBuffer(0)); geometry->setVertexArray(positions);
element.mChunk->setVisibilityFlags(mVisibilityFlags); geometry->setNormalArray(normals, osg::Array::BIND_PER_VERTEX);
element.mChunk->setCastShadows(true); geometry->setColorArray(colors, osg::Array::BIND_PER_VERTEX);
geometry->setUseDisplayList(false);
geometry->setUseVertexBufferObjects(true);
std::vector<Ogre::PixelBox> blendmaps; geometry->addPrimitiveSet(mCache.getIndexBuffer(0));
std::vector<Terrain::LayerInfo> layerList;
mStorage->getBlendmaps(1, center, mShaders, blendmaps, layerList);
element.mMaterialGenerator.setLayerList(layerList); // we already know the bounding box, so no need to let OSG compute it.
osg::Vec3f min(-0.5f*mStorage->getCellWorldSize(),
// upload blendmaps to GPU -0.5f*mStorage->getCellWorldSize(),
std::vector<Ogre::TexturePtr> blendTextures; minH);
for (std::vector<Ogre::PixelBox>::const_iterator it = blendmaps.begin(); it != blendmaps.end(); ++it) osg::Vec3f max (0.5f*mStorage->getCellWorldSize(),
{ 0.5f*mStorage->getCellWorldSize(),
static int count=0; maxH);
Ogre::TexturePtr map = Ogre::TextureManager::getSingleton().createManual("terrain/blend/" osg::BoundingBox bounds(min, max);
+ Ogre::StringConverter::toString(count++), Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, geometry->setComputeBoundingBoxCallback(new StaticBoundingBoxCallback(bounds));
Ogre::TEX_TYPE_2D, it->getWidth(), it->getHeight(), 0, it->format);
Ogre::DataStreamPtr stream(new Ogre::MemoryDataStream(it->data, it->getWidth()*it->getHeight()*Ogre::PixelUtil::getNumElemBytes(it->format), true));
map->loadRawData(stream, it->getWidth(), it->getHeight(), it->format);
blendTextures.push_back(map);
}
element.mMaterialGenerator.setBlendmapList(blendTextures); osg::ref_ptr<osg::Geode> geode (new osg::Geode);
geode->addDrawable(geometry);
element.mSceneNode->attachObject(element.mChunk); std::vector<LayerInfo> layerList;
updateMaterial(element); std::vector<osg::ref_ptr<osg::Image> > blendmaps;
mStorage->getBlendmaps(1.f, center, false, blendmaps, layerList);
mGrid[std::make_pair(x,y)] = element; // For compiling textures, I don't think the osgFX::Effect does it correctly
} osg::ref_ptr<osg::Node> textureCompileDummy (new osg::Node);
void TerrainGrid::updateMaterial(GridElement &element) std::vector<osg::ref_ptr<osg::Texture2D> > layerTextures;
{ for (std::vector<LayerInfo>::const_iterator it = layerList.begin(); it != layerList.end(); ++it)
element.mMaterialGenerator.enableShadows(getShadowsEnabled()); {
element.mMaterialGenerator.enableSplitShadows(getSplitShadowsEnabled()); layerTextures.push_back(mResourceSystem->getTextureManager()->getTexture2D(it->mDiffuseMap, osg::Texture::REPEAT, osg::Texture::REPEAT));
element.mChunk->setMaterial(element.mMaterialGenerator.generate()); textureCompileDummy->getOrCreateStateSet()->setTextureAttributeAndModes(0, layerTextures.back());
} }
void TerrainGrid::unloadCell(int x, int y) std::vector<osg::ref_ptr<osg::Texture2D> > blendmapTextures;
{ for (std::vector<osg::ref_ptr<osg::Image> >::const_iterator it = blendmaps.begin(); it != blendmaps.end(); ++it)
Grid::iterator it = mGrid.find(std::make_pair(x,y)); {
if (it == mGrid.end()) osg::ref_ptr<osg::Texture2D> texture (new osg::Texture2D);
return; texture->setImage(*it);
texture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE);
texture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE);
texture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR);
texture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR);
texture->setResizeNonPowerOfTwoHint(false);
blendmapTextures.push_back(texture);
textureCompileDummy->getOrCreateStateSet()->setTextureAttributeAndModes(0, layerTextures.back());
}
GridElement& element = it->second; for (unsigned int i=0; i<blendmapTextures.size()+1; ++i)
delete element.mChunk; geometry->setTexCoordArray(i, mCache.getUVBuffer());
element.mChunk = NULL;
const std::vector<Ogre::TexturePtr>& blendmaps = element.mMaterialGenerator.getBlendmapList(); osg::ref_ptr<osgFX::Effect> effect (new Terrain::Effect(layerTextures, blendmapTextures));
for (std::vector<Ogre::TexturePtr>::const_iterator it = blendmaps.begin(); it != blendmaps.end(); ++it)
Ogre::TextureManager::getSingleton().remove((*it)->getName());
mSceneMgr->destroySceneNode(element.mSceneNode); effect->addCullCallback(new SceneUtil::LightListCallback);
element.mSceneNode = NULL;
mGrid.erase(it); effect->addChild(geode);
} element->mNode->addChild(effect);
void TerrainGrid::applyMaterials(bool shadows, bool splitShadows) if (mIncrementalCompileOperation)
{
mShadows = shadows;
mSplitShadows = splitShadows;
for (Grid::iterator it = mGrid.begin(); it != mGrid.end(); ++it)
{ {
updateMaterial(it->second); mIncrementalCompileOperation->add(geode);
mIncrementalCompileOperation->add(textureCompileDummy);
} }
}
bool TerrainGrid::getVisible() mGrid[std::make_pair(x,y)] = element.release();
{
return mVisible;
}
void TerrainGrid::setVisible(bool visible)
{
mVisible = visible;
mRootNode->setVisible(visible);
} }
Ogre::AxisAlignedBox TerrainGrid::getWorldBoundingBox (const Ogre::Vector2& center) void TerrainGrid::unloadCell(int x, int y)
{ {
int cellX = static_cast<int>(std::floor(center.x)); Grid::iterator it = mGrid.find(std::make_pair(x,y));
int cellY = static_cast<int>(std::floor(center.y));
Grid::iterator it = mGrid.find(std::make_pair(cellX, cellY));
if (it == mGrid.end()) if (it == mGrid.end())
return Ogre::AxisAlignedBox::BOX_NULL; return;
Terrain::Chunk* chunk = it->second.mChunk;
Ogre::SceneNode* node = it->second.mSceneNode;
Ogre::AxisAlignedBox box = chunk->getBoundingBox();
box = Ogre::AxisAlignedBox(box.getMinimum() + node->getPosition(), box.getMaximum() + node->getPosition());
return box;
}
void TerrainGrid::syncLoad() GridElement* element = it->second;
{ mTerrainRoot->removeChild(element->mNode);
delete element;
mGrid.erase(it);
} }
} }

@ -27,64 +27,23 @@
namespace Terrain namespace Terrain
{ {
class Chunk;
struct GridElement class GridElement;
{
Ogre::SceneNode* mSceneNode;
Terrain::MaterialGenerator mMaterialGenerator;
Terrain::Chunk* mChunk;
};
/// @brief Simple terrain implementation that loads cells in a grid, with no LOD /// @brief Simple terrain implementation that loads cells in a grid, with no LOD
class TerrainGrid : public Terrain::World class TerrainGrid : public Terrain::World
{ {
public: public:
/// @note takes ownership of \a storage TerrainGrid(osg::Group* parent, Resource::ResourceSystem* resourceSystem, osgUtil::IncrementalCompileOperation* ico,
/// @param sceneMgr scene manager to use Storage* storage, int nodeMask);
/// @param storage Storage instance to get terrain data from (heights, normals, colors, textures..)
/// @param visbilityFlags visibility flags for the created meshes
/// @param shaders Whether to use splatting shader, or multi-pass fixed function splatting. Shader is usually
/// faster so this is just here for compatibility.
/// @param align The align of the terrain, see Alignment enum
TerrainGrid(Ogre::SceneManager* sceneMgr,
Terrain::Storage* storage, int visibilityFlags, bool shaders, Terrain::Alignment align);
~TerrainGrid(); ~TerrainGrid();
/// Update chunk LODs according to this camera position
virtual void update (const Ogre::Vector3& cameraPos);
virtual void loadCell(int x, int y); virtual void loadCell(int x, int y);
virtual void unloadCell(int x, int y); virtual void unloadCell(int x, int y);
/// Get the world bounding box of a chunk of terrain centered at \a center
virtual Ogre::AxisAlignedBox getWorldBoundingBox (const Ogre::Vector2& center);
/// Show or hide the whole terrain
/// @note this setting may be invalidated once you call Terrain::update, so do not call it while the terrain should be hidden
virtual void setVisible(bool visible);
virtual bool getVisible();
/// Recreate materials used by terrain chunks. This should be called whenever settings of
/// the material factory are changed. (Relying on the factory to update those materials is not
/// enough, since turning a feature on/off can change the number of texture units available for layer/blend
/// textures, and to properly respond to this we may need to change the structure of the material, such as
/// adding or removing passes. This can only be achieved by a full rebuild.)
virtual void applyMaterials(bool shadows, bool splitShadows);
/// Wait until all background loading is complete.
virtual void syncLoad();
private: private:
void updateMaterial (GridElement& element); typedef std::map<std::pair<int, int>, GridElement*> Grid;
typedef std::map<std::pair<int, int>, GridElement> Grid;
Grid mGrid; Grid mGrid;
Ogre::SceneNode* mRootNode;
bool mVisible;
}; };
} }

@ -21,63 +21,39 @@
*/ */
#include "world.hpp" #include "world.hpp"
#include <OgreAxisAlignedBox.h> #include <osg/Group>
#include <osgUtil/IncrementalCompileOperation>
#include "storage.hpp" #include "storage.hpp"
namespace Terrain namespace Terrain
{ {
World::World(Ogre::SceneManager* sceneMgr, World::World(osg::Group* parent, Resource::ResourceSystem* resourceSystem, osgUtil::IncrementalCompileOperation* ico,
Storage* storage, int visibilityFlags, bool shaders, Alignment align) Storage* storage, int nodeMask)
: mShaders(shaders) : mStorage(storage)
, mShadows(false)
, mSplitShadows(false)
, mAlign(align)
, mStorage(storage)
, mVisibilityFlags(visibilityFlags)
, mSceneMgr(sceneMgr)
, mCache(storage->getCellVertices()) , mCache(storage->getCellVertices())
, mParent(parent)
, mResourceSystem(resourceSystem)
, mIncrementalCompileOperation(ico)
{ {
mTerrainRoot = new osg::Group;
mTerrainRoot->setNodeMask(nodeMask);
mTerrainRoot->getOrCreateStateSet()->setRenderingHint(osg::StateSet::OPAQUE_BIN);
mParent->addChild(mTerrainRoot);
} }
World::~World() World::~World()
{ {
mParent->removeChild(mTerrainRoot);
delete mStorage; delete mStorage;
} }
float World::getHeightAt(const Ogre::Vector3 &worldPos) float World::getHeightAt(const osg::Vec3f &worldPos)
{ {
return mStorage->getHeightAt(worldPos); return mStorage->getHeightAt(worldPos);
} }
void World::convertPosition(float &x, float &y, float &z)
{
Terrain::convertPosition(mAlign, x, y, z);
}
void World::convertPosition(Ogre::Vector3 &pos)
{
convertPosition(pos.x, pos.y, pos.z);
}
void World::convertBounds(Ogre::AxisAlignedBox& bounds)
{
switch (mAlign)
{
case Align_XY:
return;
case Align_XZ:
convertPosition(bounds.getMinimum());
convertPosition(bounds.getMaximum());
// Because we changed sign of Z
std::swap(bounds.getMinimum().z, bounds.getMaximum().z);
return;
case Align_YZ:
convertPosition(bounds.getMinimum());
convertPosition(bounds.getMaximum());
return;
}
}
} }

@ -22,14 +22,24 @@
#ifndef COMPONENTS_TERRAIN_WORLD_H #ifndef COMPONENTS_TERRAIN_WORLD_H
#define COMPONENTS_TERRAIN_WORLD_H #define COMPONENTS_TERRAIN_WORLD_H
#include <OgreVector3.h> #include <osg/ref_ptr>
#include "defs.hpp" #include "defs.hpp"
#include "buffercache.hpp" #include "buffercache.hpp"
namespace Ogre namespace osg
{ {
class SceneManager; class Group;
}
namespace osgUtil
{
class IncrementalCompileOperation;
}
namespace Resource
{
class ResourceSystem;
} }
namespace Terrain namespace Terrain
@ -44,79 +54,31 @@ namespace Terrain
{ {
public: public:
/// @note takes ownership of \a storage /// @note takes ownership of \a storage
/// @param sceneMgr scene manager to use
/// @param storage Storage instance to get terrain data from (heights, normals, colors, textures..) /// @param storage Storage instance to get terrain data from (heights, normals, colors, textures..)
/// @param visbilityFlags visibility flags for the created meshes /// @param nodeMask mask for the terrain root
/// @param shaders Whether to use splatting shader, or multi-pass fixed function splatting. Shader is usually World(osg::Group* parent, Resource::ResourceSystem* resourceSystem, osgUtil::IncrementalCompileOperation* ico,
/// faster so this is just here for compatibility. Storage* storage, int nodeMask);
/// @param align The align of the terrain, see Alignment enum
World(Ogre::SceneManager* sceneMgr,
Storage* storage, int visiblityFlags, bool shaders, Alignment align);
virtual ~World(); virtual ~World();
bool getShadersEnabled() { return mShaders; } float getHeightAt (const osg::Vec3f& worldPos);
bool getShadowsEnabled() { return mShadows; }
bool getSplitShadowsEnabled() { return mSplitShadows; }
float getHeightAt (const Ogre::Vector3& worldPos);
/// Update chunk LODs according to this camera position
/// @note Calling this method might lead to composite textures being rendered, so it is best
/// not to call it when render commands are still queued, since that would cause a flush.
virtual void update (const Ogre::Vector3& cameraPos) = 0;
// This is only a hint and may be ignored by the implementation. // This is only a hint and may be ignored by the implementation.
virtual void loadCell(int x, int y) {} virtual void loadCell(int x, int y) {}
virtual void unloadCell(int x, int y) {} virtual void unloadCell(int x, int y) {}
/// Get the world bounding box of a chunk of terrain centered at \a center
virtual Ogre::AxisAlignedBox getWorldBoundingBox (const Ogre::Vector2& center) = 0;
Ogre::SceneManager* getSceneManager() { return mSceneMgr; }
Storage* getStorage() { return mStorage; } Storage* getStorage() { return mStorage; }
/// Show or hide the whole terrain
/// @note this setting may be invalidated once you call Terrain::update, so do not call it while the terrain should be hidden
virtual void setVisible(bool visible) = 0;
virtual bool getVisible() = 0;
/// Recreate materials used by terrain chunks. This should be called whenever settings of
/// the material factory are changed. (Relying on the factory to update those materials is not
/// enough, since turning a feature on/off can change the number of texture units available for layer/blend
/// textures, and to properly respond to this we may need to change the structure of the material, such as
/// adding or removing passes. This can only be achieved by a full rebuild.)
virtual void applyMaterials(bool shadows, bool splitShadows) = 0;
int getVisibilityFlags() { return mVisibilityFlags; }
Alignment getAlign() { return mAlign; }
/// Wait until all background loading is complete.
virtual void syncLoad() {}
protected: protected:
bool mShaders;
bool mShadows;
bool mSplitShadows;
Alignment mAlign;
Storage* mStorage; Storage* mStorage;
int mVisibilityFlags;
Ogre::SceneManager* mSceneMgr;
BufferCache mCache; BufferCache mCache;
public: osg::ref_ptr<osg::Group> mParent;
// ----INTERNAL---- osg::ref_ptr<osg::Group> mTerrainRoot;
BufferCache& getBufferCache() { return mCache; }
Resource::ResourceSystem* mResourceSystem;
// Convert the given position from Z-up align, i.e. Align_XY to the wanted align set in mAlign osg::ref_ptr<osgUtil::IncrementalCompileOperation> mIncrementalCompileOperation;
void convertPosition (float& x, float& y, float& z);
void convertPosition (Ogre::Vector3& pos);
void convertBounds (Ogre::AxisAlignedBox& bounds);
}; };
} }

Loading…
Cancel
Save