Merge pull request #185 from OpenMW/master

Add OpenMW commits up to 14 Mar 2017
0.6.1
David Cernat 8 years ago committed by GitHub
commit 60037e4081

@ -9,6 +9,7 @@
#include <components/esm/loadcell.hpp>
#include <components/esm/loadland.hpp>
#include <components/sceneutil/pathgridutil.hpp>
#include <components/terrain/terraingrid.hpp>
#include "../../model/world/idtable.hpp"
#include "../../model/world/columns.hpp"
@ -17,9 +18,13 @@
#include "../../model/world/cellcoordinates.hpp"
#include "cellwater.hpp"
#include "cellborder.hpp"
#include "cellarrow.hpp"
#include "cellmarker.hpp"
#include "mask.hpp"
#include "pathgrid.hpp"
#include "terrainstorage.hpp"
#include "object.hpp"
bool CSVRender::Cell::removeObject (const std::string& id)
{
@ -102,7 +107,7 @@ CSVRender::Cell::Cell (CSMWorld::Data& data, osg::Group* rootNode, const std::st
if (esmLand.getLandData (ESM::Land::DATA_VHGT))
{
mTerrain.reset(new Terrain::TerrainGrid(mCellNode, data.getResourceSystem().get(), NULL, new TerrainStorage(mData), Mask_Terrain));
mTerrain.reset(new Terrain::TerrainGrid(mCellNode, mCellNode, data.getResourceSystem().get(), new TerrainStorage(mData), Mask_Terrain));
mTerrain->loadCell(esmLand.mX,
esmLand.mY);

@ -4,19 +4,11 @@
#include <string>
#include <map>
#include <memory>
#include <boost/shared_ptr.hpp>
#include <vector>
#include <osg/ref_ptr>
#ifndef Q_MOC_RUN
#include <components/terrain/terraingrid.hpp>
#endif
#include "object.hpp"
#include "cellarrow.hpp"
#include "cellmarker.hpp"
#include "cellborder.hpp"
#include "../../model/world/cellcoordinates.hpp"
class QModelIndex;
@ -30,7 +22,11 @@ namespace osg
namespace CSMWorld
{
class Data;
class CellCoordinates;
}
namespace Terrain
{
class TerrainGrid;
}
namespace CSVRender
@ -38,6 +34,12 @@ namespace CSVRender
class CellWater;
class Pathgrid;
class TagBase;
class Object;
class CellArrow;
class CellBorder;
class CellMarker;
class CellWater;
class Cell
{

@ -20,6 +20,7 @@
#include "editmode.hpp"
#include "mask.hpp"
#include "cameracontroller.hpp"
#include "cellarrow.hpp"
bool CSVRender::PagedWorldspaceWidget::adjustCells()
{

@ -9,7 +9,7 @@ namespace CSVRender
{
}
const ESM::Land* TerrainStorage::getLand(int cellX, int cellY)
osg::ref_ptr<const ESMTerrain::LandObject> TerrainStorage::getLand(int cellX, int cellY)
{
std::ostringstream stream;
stream << "#" << cellX << " " << cellY;
@ -21,9 +21,7 @@ namespace CSVRender
return NULL;
const ESM::Land& land = mData.getLand().getRecord(index).get();
int mask = ESM::Land::DATA_VHGT | ESM::Land::DATA_VNML | ESM::Land::DATA_VCLR | ESM::Land::DATA_VTEX;
land.loadData (mask);
return &land;
return new ESMTerrain::LandObject(&land, ESM::Land::DATA_VHGT | ESM::Land::DATA_VNML | ESM::Land::DATA_VCLR | ESM::Land::DATA_VTEX);
}
const ESM::LandTexture* TerrainStorage::getLandTexture(int index, short plugin)

@ -18,7 +18,7 @@ namespace CSVRender
private:
const CSMWorld::Data& mData;
virtual const ESM::Land* getLand (int cellX, int cellY);
virtual osg::ref_ptr<const ESMTerrain::LandObject> getLand (int cellX, int cellY);
virtual const ESM::LandTexture* getLandTexture(int index, short plugin);
virtual void getBounds(float& minX, float& maxX, float& minY, float& maxY);

@ -16,6 +16,7 @@
#include "../widget/scenetooltoggle2.hpp"
#include "mask.hpp"
#include "tagbase.hpp"
void CSVRender::UnpagedWorldspaceWidget::update()
{

@ -23,7 +23,7 @@ add_openmw_dir (mwrender
actors objects renderingmanager animation rotatecontroller sky npcanimation vismask
creatureanimation effectmanager util renderinginterface pathgrid rendermode weaponanimation
bulletdebugdraw globalmap characterpreview camera localmap water terrainstorage ripplesimulation
renderbin actoranimation
renderbin actoranimation landmanager
)
add_openmw_dir (mwinput

@ -297,8 +297,8 @@ namespace MWGui
// Turn off rendering except the GUI
int oldUpdateMask = mViewer->getUpdateVisitor()->getTraversalMask();
int oldCullMask = mViewer->getCamera()->getCullMask();
mViewer->getUpdateVisitor()->setTraversalMask(MWRender::Mask_GUI);
mViewer->getCamera()->setCullMask(MWRender::Mask_GUI);
mViewer->getUpdateVisitor()->setTraversalMask(MWRender::Mask_GUI|MWRender::Mask_PreCompile);
mViewer->getCamera()->setCullMask(MWRender::Mask_GUI|MWRender::Mask_PreCompile);
MWBase::Environment::get().getInputManager()->update(0, true, true);

@ -516,21 +516,11 @@ namespace MWPhysics
class HeightField
{
public:
HeightField(const float* heights, int x, int y, float triSize, float sqrtVerts)
HeightField(const float* heights, int x, int y, float triSize, float sqrtVerts, float minH, float maxH, const osg::Object* holdObject)
{
// find the minimum and maximum heights (needed for bullet)
float minh = heights[0];
float maxh = heights[0];
for(int i = 1;i < sqrtVerts*sqrtVerts;++i)
{
float h = heights[i];
if(h > maxh) maxh = h;
if(h < minh) minh = h;
}
mShape = new btHeightfieldTerrainShape(
sqrtVerts, sqrtVerts, heights, 1,
minh, maxh, 2,
minH, maxH, 2,
PHY_FLOAT, false
);
mShape->setUseDiamondSubdivision(true);
@ -539,11 +529,13 @@ namespace MWPhysics
btTransform transform(btQuaternion::getIdentity(),
btVector3((x+0.5f) * triSize * (sqrtVerts-1),
(y+0.5f) * triSize * (sqrtVerts-1),
(maxh+minh)*0.5f));
(maxH+minH)*0.5f));
mCollisionObject = new btCollisionObject;
mCollisionObject->setCollisionShape(mShape);
mCollisionObject->setWorldTransform(transform);
mHoldObject = holdObject;
}
~HeightField()
{
@ -558,6 +550,7 @@ namespace MWPhysics
private:
btHeightfieldTerrainShape* mShape;
btCollisionObject* mCollisionObject;
osg::ref_ptr<const osg::Object> mHoldObject;
void operator=(const HeightField&);
HeightField(const HeightField&);
@ -1140,9 +1133,9 @@ namespace MWPhysics
return MovementSolver::traceDown(ptr, position, found->second, mCollisionWorld, maxHeight);
}
void PhysicsSystem::addHeightField (const float* heights, int x, int y, float triSize, float sqrtVerts)
void PhysicsSystem::addHeightField (const float* heights, int x, int y, float triSize, float sqrtVerts, float minH, float maxH, const osg::Object* holdObject)
{
HeightField *heightfield = new HeightField(heights, x, y, triSize, sqrtVerts);
HeightField *heightfield = new HeightField(heights, x, y, triSize, sqrtVerts, minH, maxH, holdObject);
mHeightFields[std::make_pair(x,y)] = heightfield;
mCollisionWorld->addCollisionObject(heightfield->getCollisionObject(), CollisionType_HeightMap,

@ -15,6 +15,7 @@
namespace osg
{
class Group;
class Object;
}
namespace MWRender
@ -80,7 +81,7 @@ namespace MWPhysics
void updatePosition (const MWWorld::Ptr& ptr);
void addHeightField (const float* heights, int x, int y, float triSize, float sqrtVerts);
void addHeightField (const float* heights, int x, int y, float triSize, float sqrtVerts, float minH, float maxH, const osg::Object* holdObject);
void removeHeightField (int x, int y);

@ -2,6 +2,7 @@
#include <iostream>
#include <osg/Material>
#include <osg/Fog>
#include <osg/BlendFunc>
#include <osg/Texture2D>
@ -142,6 +143,13 @@ namespace MWRender
stateset->setMode(GL_LIGHTING, osg::StateAttribute::ON);
stateset->setMode(GL_NORMALIZE, osg::StateAttribute::ON);
stateset->setMode(GL_CULL_FACE, osg::StateAttribute::ON);
osg::ref_ptr<osg::Material> defaultMat (new osg::Material);
defaultMat->setColorMode(osg::Material::OFF);
defaultMat->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(1,1,1,1));
defaultMat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(1,1,1,1));
defaultMat->setSpecular(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f, 0.f, 0.f, 0.f));
stateset->setAttribute(defaultMat);
// assign large value to effectively turn off fog
// shaders don't respect glDisable(GL_FOG)
osg::ref_ptr<osg::Fog> fog (new osg::Fog);

@ -238,6 +238,9 @@ namespace MWRender
removeCamera(*it);
for (CameraVector::iterator it = mActiveCameras.begin(); it != mActiveCameras.end(); ++it)
removeCamera(*it);
if (mWorkItem)
mWorkItem->waitTillDone();
}
void GlobalMap::render ()

@ -0,0 +1,48 @@
#include "landmanager.hpp"
#include <osg/Stats>
#include <sstream>
#include <components/resource/objectcache.hpp>
#include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp"
#include "../mwworld/esmstore.hpp"
namespace MWRender
{
LandManager::LandManager(int loadFlags)
: ResourceManager(NULL)
, mLoadFlags(loadFlags)
{
}
osg::ref_ptr<ESMTerrain::LandObject> LandManager::getLand(int x, int y)
{
std::ostringstream id;
id << x << " " << y;
std::string idstr = id.str();
osg::ref_ptr<osg::Object> obj = mCache->getRefFromObjectCache(idstr);
if (obj)
return static_cast<ESMTerrain::LandObject*>(obj.get());
else
{
const ESM::Land* land = MWBase::Environment::get().getWorld()->getStore().get<ESM::Land>().search(x,y);
if (!land)
return NULL;
osg::ref_ptr<ESMTerrain::LandObject> landObj (new ESMTerrain::LandObject(land, mLoadFlags));
mCache->addEntryToObjectCache(idstr, landObj.get());
return landObj;
}
}
void LandManager::reportStats(unsigned int frameNumber, osg::Stats *stats) const
{
stats->setAttribute(frameNumber, "Land", mCache->getCacheSize());
}
}

@ -0,0 +1,33 @@
#ifndef OPENMW_COMPONENTS_ESMTERRAIN_LANDMANAGER_H
#define OPENMW_COMPONENTS_ESMTERRAIN_LANDMANAGER_H
#include <osg/Object>
#include <components/resource/resourcemanager.hpp>
#include <components/esmterrain/storage.hpp>
namespace ESM
{
struct Land;
}
namespace MWRender
{
class LandManager : public Resource::ResourceManager
{
public:
LandManager(int loadFlags);
/// @note Will return NULL if not found.
osg::ref_ptr<ESMTerrain::LandObject> getLand(int x, int y);
virtual void reportStats(unsigned int frameNumber, osg::Stats* stats) const;
private:
int mLoadFlags;
};
}
#endif

@ -174,9 +174,7 @@ osg::ref_ptr<osg::Camera> LocalMap::createOrthographicCamera(float x, float y, f
camera->setNodeMask(Mask_RenderToTexture);
osg::ref_ptr<osg::StateSet> stateset = new osg::StateSet;
stateset->setMode(GL_LIGHTING, osg::StateAttribute::ON);
stateset->setMode(GL_NORMALIZE, osg::StateAttribute::ON);
stateset->setMode(GL_CULL_FACE, osg::StateAttribute::ON);
// assign large value to effectively turn off fog
// shaders don't respect glDisable(GL_FOG)
osg::ref_ptr<osg::Fog> fog (new osg::Fog);
@ -351,6 +349,11 @@ void LocalMap::requestExteriorMap(const MWWorld::CellStore* cell)
osg::ref_ptr<osg::Camera> camera = createOrthographicCamera(x*mMapWorldSize + mMapWorldSize/2.f, y*mMapWorldSize + mMapWorldSize/2.f, mMapWorldSize, mMapWorldSize,
osg::Vec3d(0,1,0), zmin, zmax);
camera->getOrCreateUserDataContainer()->addDescription("NoTerrainLod");
std::ostringstream stream;
stream << x << " " << y;
camera->getOrCreateUserDataContainer()->addDescription(stream.str());
setupRenderToTexture(camera, cell->getCell()->getGridX(), cell->getCell()->getGridY());
MapSegment& segment = mSegments[std::make_pair(cell->getCell()->getGridX(), cell->getCell()->getGridY())];

@ -15,11 +15,6 @@ namespace osg
class Group;
}
namespace osgUtil
{
class IncrementalCompileOperation;
}
namespace Resource
{
class ResourceSystem;

@ -34,6 +34,7 @@
#include <components/sceneutil/writescene.hpp>
#include <components/terrain/terraingrid.hpp>
#include <components/terrain/quadtreeworld.hpp>
#include <components/esm/loadcell.hpp>
#include <components/fallback/fallback.hpp>
@ -212,11 +213,15 @@ namespace MWRender
mWater.reset(new Water(mRootNode, sceneRoot, mResourceSystem, mViewer->getIncrementalCompileOperation(), fallback, resourcePath));
mTerrain.reset(new Terrain::TerrainGrid(sceneRoot, mResourceSystem, mViewer->getIncrementalCompileOperation(),
new TerrainStorage(mResourceSystem->getVFS(), Settings::Manager::getString("normal map pattern", "Shaders"), Settings::Manager::getString("normal height map pattern", "Shaders"),
Settings::Manager::getBool("auto use terrain normal maps", "Shaders"),
Settings::Manager::getString("terrain specular map pattern", "Shaders"), Settings::Manager::getBool("auto use terrain specular maps", "Shaders")),
Mask_Terrain, &mResourceSystem->getSceneManager()->getShaderManager(), mUnrefQueue.get()));
const bool distantTerrain = Settings::Manager::getBool("distant terrain", "Terrain");
mTerrainStorage = new TerrainStorage(mResourceSystem, Settings::Manager::getString("normal map pattern", "Shaders"), Settings::Manager::getString("normal height map pattern", "Shaders"),
Settings::Manager::getBool("auto use terrain normal maps", "Shaders"), Settings::Manager::getString("terrain specular map pattern", "Shaders"),
Settings::Manager::getBool("auto use terrain specular maps", "Shaders"));
if (distantTerrain)
mTerrain.reset(new Terrain::QuadTreeWorld(sceneRoot, mRootNode, mResourceSystem, mTerrainStorage, Mask_Terrain, Mask_PreCompile));
else
mTerrain.reset(new Terrain::TerrainGrid(sceneRoot, mRootNode, mResourceSystem, mTerrainStorage, Mask_Terrain, Mask_PreCompile));
mCamera.reset(new Camera(mViewer->getCamera()));
@ -420,6 +425,11 @@ namespace MWRender
mWater->removeCell(store);
}
void RenderingManager::enableTerrain(bool enable)
{
mTerrain->enable(enable);
}
void RenderingManager::setSkyEnabled(bool enabled)
{
mSky->setEnabled(enabled);
@ -493,9 +503,11 @@ namespace MWRender
mCurrentCameraPos = cameraPos;
if (mWater->isUnderwater(cameraPos))
{
float viewDistance = mViewDistance;
viewDistance = std::min(viewDistance, 6666.f);
setFogColor(mUnderwaterColor * mUnderwaterWeight + mFogColor * (1.f-mUnderwaterWeight));
mStateUpdater->setFogStart(mViewDistance * (1 - mUnderwaterFog));
mStateUpdater->setFogEnd(mViewDistance);
mStateUpdater->setFogStart(viewDistance * (1 - mUnderwaterFog));
mStateUpdater->setFogEnd(viewDistance);
}
else
{
@ -718,18 +730,23 @@ namespace MWRender
}
osg::ref_ptr<osgUtil::IntersectionVisitor> createIntersectionVisitor(osgUtil::Intersector* intersector, bool ignorePlayer, bool ignoreActors)
osg::ref_ptr<osgUtil::IntersectionVisitor> RenderingManager::getIntersectionVisitor(osgUtil::Intersector *intersector, bool ignorePlayer, bool ignoreActors)
{
osg::ref_ptr<osgUtil::IntersectionVisitor> intersectionVisitor( new osgUtil::IntersectionVisitor(intersector));
int mask = intersectionVisitor->getTraversalMask();
if (!mIntersectionVisitor)
mIntersectionVisitor = new osgUtil::IntersectionVisitor;
mIntersectionVisitor->setTraversalNumber(mViewer->getFrameStamp()->getFrameNumber());
mIntersectionVisitor->setIntersector(intersector);
int mask = ~0;
mask &= ~(Mask_RenderToTexture|Mask_Sky|Mask_Debug|Mask_Effect|Mask_Water|Mask_SimpleWater);
if (ignorePlayer)
mask &= ~(Mask_Player);
if (ignoreActors)
mask &= ~(Mask_Actor|Mask_Player);
intersectionVisitor->setTraversalMask(mask);
return intersectionVisitor;
mIntersectionVisitor->setTraversalMask(mask);
return mIntersectionVisitor;
}
RenderingManager::RayResult RenderingManager::castRay(const osg::Vec3f& origin, const osg::Vec3f& dest, bool ignorePlayer, bool ignoreActors)
@ -738,7 +755,7 @@ namespace MWRender
origin, dest));
intersector->setIntersectionLimit(osgUtil::LineSegmentIntersector::LIMIT_NEAREST);
mRootNode->accept(*createIntersectionVisitor(intersector, ignorePlayer, ignoreActors));
mRootNode->accept(*getIntersectionVisitor(intersector, ignorePlayer, ignoreActors));
return getIntersectionResult(intersector);
}
@ -757,7 +774,7 @@ namespace MWRender
intersector->setEnd(end);
intersector->setIntersectionLimit(osgUtil::LineSegmentIntersector::LIMIT_NEAREST);
mViewer->getCamera()->accept(*createIntersectionVisitor(intersector, ignorePlayer, ignoreActors));
mViewer->getCamera()->accept(*getIntersectionVisitor(intersector, ignorePlayer, ignoreActors));
return getIntersectionResult(intersector);
}
@ -903,7 +920,7 @@ namespace MWRender
mStateUpdater->setFogColor(color);
}
void RenderingManager::reportStats()
void RenderingManager::reportStats() const
{
osg::Stats* stats = mViewer->getViewerStats();
unsigned int frameNumber = mViewer->getFrameStamp()->getFrameNumber();
@ -1053,4 +1070,10 @@ namespace MWRender
SceneUtil::writeScene(node, filename, format);
}
LandManager *RenderingManager::getLandManager() const
{
return mTerrainStorage->getLandManager();
}
}

@ -17,6 +17,12 @@ namespace osg
class PositionAttitudeTransform;
}
namespace osgUtil
{
class IntersectionVisitor;
class Intersector;
}
namespace Resource
{
class ResourceSystem;
@ -59,6 +65,8 @@ namespace MWRender
class Pathgrid;
class Camera;
class Water;
class TerrainStorage;
class LandManager;
class RenderingManager : public MWRender::RenderingInterface
{
@ -100,6 +108,8 @@ namespace MWRender
void addCell(const MWWorld::CellStore* store);
void removeCell(const MWWorld::CellStore* store);
void enableTerrain(bool enable);
void updatePtr(const MWWorld::Ptr& old, const MWWorld::Ptr& updated);
void rotateObject(const MWWorld::Ptr& ptr, const osg::Quat& rot);
@ -190,13 +200,19 @@ namespace MWRender
void exportSceneGraph(const MWWorld::Ptr& ptr, const std::string& filename, const std::string& format);
LandManager* getLandManager() const;
private:
void updateProjectionMatrix();
void updateTextureFiltering();
void updateAmbient();
void setFogColor(const osg::Vec4f& color);
void reportStats();
void reportStats() const;
osg::ref_ptr<osgUtil::IntersectionVisitor> getIntersectionVisitor(osgUtil::Intersector* intersector, bool ignorePlayer, bool ignoreActors);
osg::ref_ptr<osgUtil::IntersectionVisitor> mIntersectionVisitor;
osg::ref_ptr<osgViewer::Viewer> mViewer;
osg::ref_ptr<osg::Group> mRootNode;
@ -212,6 +228,7 @@ namespace MWRender
std::auto_ptr<Objects> mObjects;
std::auto_ptr<Water> mWater;
std::auto_ptr<Terrain::World> mTerrain;
TerrainStorage* mTerrainStorage;
std::auto_ptr<SkyManager> mSky;
std::auto_ptr<EffectManager> mEffectManager;
osg::ref_ptr<NpcAnimation> mPlayerAnimation;

@ -428,10 +428,12 @@ private:
class CelestialBody
{
public:
CelestialBody(osg::Group* parentNode, float scaleFactor, int numUvSets)
CelestialBody(osg::Group* parentNode, float scaleFactor, int numUvSets, unsigned int visibleMask=~0)
: mVisibleMask(visibleMask)
{
mGeom = createTexturedQuad(numUvSets);
mTransform = new osg::PositionAttitudeTransform;
mTransform->setNodeMask(mVisibleMask);
mTransform->setScale(osg::Vec3f(450,450,450) * scaleFactor);
mTransform->addChild(mGeom);
@ -444,10 +446,11 @@ public:
void setVisible(bool visible)
{
mTransform->setNodeMask(visible ? ~0 : 0);
mTransform->setNodeMask(visible ? mVisibleMask : 0);
}
protected:
unsigned int mVisibleMask;
static const float mDistance;
osg::ref_ptr<osg::PositionAttitudeTransform> mTransform;
osg::ref_ptr<osg::Geometry> mGeom;
@ -459,11 +462,10 @@ class Sun : public CelestialBody
{
public:
Sun(osg::Group* parentNode, Resource::ImageManager& imageManager)
: CelestialBody(parentNode, 1.0f, 1)
: CelestialBody(parentNode, 1.0f, 1, Mask_Sun)
, mUpdater(new Updater)
{
mTransform->addUpdateCallback(mUpdater);
mTransform->setNodeMask(Mask_Sun);
osg::ref_ptr<osg::Texture2D> sunTex (new osg::Texture2D(imageManager.getImage("textures/tx_sun_05.dds")));
sunTex->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE);

@ -6,12 +6,22 @@
#include "../mwbase/environment.hpp"
#include "../mwworld/esmstore.hpp"
#include "landmanager.hpp"
namespace MWRender
{
TerrainStorage::TerrainStorage(const VFS::Manager* vfs, const std::string& normalMapPattern, const std::string& normalHeightMapPattern, bool autoUseNormalMaps, const std::string& specularMapPattern, bool autoUseSpecularMaps)
: ESMTerrain::Storage(vfs, normalMapPattern, normalHeightMapPattern, autoUseNormalMaps, specularMapPattern, autoUseSpecularMaps)
TerrainStorage::TerrainStorage(Resource::ResourceSystem* resourceSystem, const std::string& normalMapPattern, const std::string& normalHeightMapPattern, bool autoUseNormalMaps, const std::string& specularMapPattern, bool autoUseSpecularMaps)
: ESMTerrain::Storage(resourceSystem->getVFS(), normalMapPattern, normalHeightMapPattern, autoUseNormalMaps, specularMapPattern, autoUseSpecularMaps)
, mLandManager(new LandManager(ESM::Land::DATA_VCLR|ESM::Land::DATA_VHGT|ESM::Land::DATA_VNML|ESM::Land::DATA_VTEX))
, mResourceSystem(resourceSystem)
{
mResourceSystem->addResourceManager(mLandManager.get());
}
TerrainStorage::~TerrainStorage()
{
mResourceSystem->removeResourceManager(mLandManager.get());
}
void TerrainStorage::getBounds(float& minX, float& maxX, float& minY, float& maxY)
@ -21,17 +31,17 @@ namespace MWRender
const MWWorld::ESMStore &esmStore =
MWBase::Environment::get().getWorld()->getStore();
MWWorld::Store<ESM::Cell>::iterator it = esmStore.get<ESM::Cell>().extBegin();
for (; it != esmStore.get<ESM::Cell>().extEnd(); ++it)
MWWorld::Store<ESM::Land>::iterator it = esmStore.get<ESM::Land>().begin();
for (; it != esmStore.get<ESM::Land>().end(); ++it)
{
if (it->getGridX() < minX)
minX = static_cast<float>(it->getGridX());
if (it->getGridX() > maxX)
maxX = static_cast<float>(it->getGridX());
if (it->getGridY() < minY)
minY = static_cast<float>(it->getGridY());
if (it->getGridY() > maxY)
maxY = static_cast<float>(it->getGridY());
if (it->mX < minX)
minX = static_cast<float>(it->mX);
if (it->mX > maxX)
maxX = static_cast<float>(it->mX);
if (it->mY < minY)
minY = static_cast<float>(it->mY);
if (it->mY > maxY)
maxY = static_cast<float>(it->mY);
}
// since grid coords are at cell origin, we need to add 1 cell
@ -39,22 +49,14 @@ namespace MWRender
maxY += 1;
}
const ESM::Land* TerrainStorage::getLand(int cellX, int cellY)
LandManager *TerrainStorage::getLandManager() const
{
const MWWorld::ESMStore &esmStore =
MWBase::Environment::get().getWorld()->getStore();
const ESM::Land* land = esmStore.get<ESM::Land>().search(cellX, cellY);
if (!land)
return NULL;
const int flags = ESM::Land::DATA_VCLR|ESM::Land::DATA_VHGT|ESM::Land::DATA_VNML|ESM::Land::DATA_VTEX;
if (!land->isDataLoaded(flags))
land->loadData(flags);
// TODO: unload land data when it's no longer needed
return mLandManager.get();
}
return land;
osg::ref_ptr<const ESMTerrain::LandObject> TerrainStorage::getLand(int cellX, int cellY)
{
return mLandManager->getLand(cellX, cellY);
}
const ESM::LandTexture* TerrainStorage::getLandTexture(int index, short plugin)
@ -64,4 +66,5 @@ namespace MWRender
return esmStore.get<ESM::LandTexture>().search(index, plugin);
}
}

@ -1,23 +1,37 @@
#ifndef MWRENDER_TERRAINSTORAGE_H
#define MWRENDER_TERRAINSTORAGE_H
#include <memory>
#include <components/esmterrain/storage.hpp>
#include <components/resource/resourcesystem.hpp>
namespace MWRender
{
class LandManager;
/// @brief Connects the ESM Store used in OpenMW with the ESMTerrain storage.
class TerrainStorage : public ESMTerrain::Storage
{
private:
virtual const ESM::Land* getLand (int cellX, int cellY);
virtual const ESM::LandTexture* getLandTexture(int index, short plugin);
public:
TerrainStorage(const VFS::Manager* vfs, const std::string& normalMapPattern = "", const std::string& normalHeightMapPatteern = "", bool autoUseNormalMaps = false, const std::string& specularMapPattern = "", bool autoUseSpecularMaps = false);
TerrainStorage(Resource::ResourceSystem* resourceSystem, const std::string& normalMapPattern = "", const std::string& normalHeightMapPatteern = "", bool autoUseNormalMaps = false, const std::string& specularMapPattern = "", bool autoUseSpecularMaps = false);
~TerrainStorage();
virtual osg::ref_ptr<const ESMTerrain::LandObject> getLand (int cellX, int cellY);
virtual const ESM::LandTexture* getLandTexture(int index, short plugin);
/// Get bounds of the whole terrain in cell units
virtual void getBounds(float& minX, float& maxX, float& minY, float& maxY);
LandManager* getLandManager() const;
private:
std::auto_ptr<LandManager> mLandManager;
Resource::ResourceSystem* mResourceSystem;
};
}

@ -48,8 +48,10 @@ namespace MWRender
// Set on cameras within the main scene graph
Mask_RenderToTexture = (1<<15),
Mask_PreCompile = (1<<16),
// Set on a camera's cull mask to enable the LightManager
Mask_Lighting = (1<<16)
Mask_Lighting = (1<<17)
};
}

@ -7,15 +7,17 @@
#include <components/resource/bulletshapemanager.hpp>
#include <components/resource/keyframemanager.hpp>
#include <components/misc/resourcehelpers.hpp>
#include <components/misc/stringops.hpp>
#include <components/nifosg/nifloader.hpp>
#include <components/terrain/world.hpp>
#include <components/esmterrain/storage.hpp>
#include <components/sceneutil/unrefqueue.hpp>
#include <components/esm/loadcell.hpp>
#include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp"
#include "../mwworld/inventorystore.hpp"
#include "../mwworld/esmstore.hpp"
#include "../mwrender/landmanager.hpp"
#include "cellstore.hpp"
#include "manualref.hpp"
@ -46,7 +48,7 @@ namespace MWWorld
{
public:
/// Constructor to be called from the main thread.
PreloadItem(MWWorld::CellStore* cell, Resource::SceneManager* sceneManager, Resource::BulletShapeManager* bulletShapeManager, Resource::KeyframeManager* keyframeManager, Terrain::World* terrain, bool preloadInstances)
PreloadItem(MWWorld::CellStore* cell, Resource::SceneManager* sceneManager, Resource::BulletShapeManager* bulletShapeManager, Resource::KeyframeManager* keyframeManager, Terrain::World* terrain, MWRender::LandManager* landManager, bool preloadInstances)
: mIsExterior(cell->getCell()->isExterior())
, mX(cell->getCell()->getGridX())
, mY(cell->getCell()->getGridY())
@ -54,9 +56,12 @@ namespace MWWorld
, mBulletShapeManager(bulletShapeManager)
, mKeyframeManager(keyframeManager)
, mTerrain(terrain)
, mLandManager(landManager)
, mPreloadInstances(preloadInstances)
, mAbort(false)
{
mTerrainView = mTerrain->createView();
ListModelsVisitor visitor (mMeshes);
if (cell->getState() == MWWorld::CellStore::State_Loaded)
{
@ -89,7 +94,8 @@ namespace MWWorld
{
try
{
mPreloadedObjects.push_back(mTerrain->cacheCell(mX, mY));
mTerrain->cacheCell(mTerrainView.get(), mX, mY);
mPreloadedObjects.push_back(mLandManager->getLand(mX, mY));
}
catch(std::exception& e)
{
@ -151,10 +157,13 @@ namespace MWWorld
Resource::BulletShapeManager* mBulletShapeManager;
Resource::KeyframeManager* mKeyframeManager;
Terrain::World* mTerrain;
MWRender::LandManager* mLandManager;
bool mPreloadInstances;
volatile bool mAbort;
osg::ref_ptr<Terrain::View> mTerrainView;
// keep a ref to the loaded objects to make sure it stays loaded as long as this cell is in the preloaded state
std::vector<osg::ref_ptr<const osg::Object> > mPreloadedObjects;
};
@ -163,30 +172,27 @@ namespace MWWorld
class UpdateCacheItem : public SceneUtil::WorkItem
{
public:
UpdateCacheItem(Resource::ResourceSystem* resourceSystem, Terrain::World* terrain, double referenceTime)
UpdateCacheItem(Resource::ResourceSystem* resourceSystem, double referenceTime)
: mReferenceTime(referenceTime)
, mResourceSystem(resourceSystem)
, mTerrain(terrain)
{
}
virtual void doWork()
{
mResourceSystem->updateCache(mReferenceTime);
mTerrain->updateCache();
}
private:
double mReferenceTime;
Resource::ResourceSystem* mResourceSystem;
Terrain::World* mTerrain;
};
CellPreloader::CellPreloader(Resource::ResourceSystem* resourceSystem, Resource::BulletShapeManager* bulletShapeManager, Terrain::World* terrain)
CellPreloader::CellPreloader(Resource::ResourceSystem* resourceSystem, Resource::BulletShapeManager* bulletShapeManager, Terrain::World* terrain, MWRender::LandManager* landManager)
: mResourceSystem(resourceSystem)
, mBulletShapeManager(bulletShapeManager)
, mTerrain(terrain)
, mLandManager(landManager)
, mExpiryDelay(0.0)
, mMinCacheSize(0)
, mMaxCacheSize(0)
@ -197,11 +203,25 @@ namespace MWWorld
CellPreloader::~CellPreloader()
{
for (PreloadMap::iterator it = mPreloadCells.begin(); it != mPreloadCells.end();++it)
if (mTerrainPreloadItem)
{
mTerrainPreloadItem->abort();
mTerrainPreloadItem->waitTillDone();
mTerrainPreloadItem = NULL;
}
if (mUpdateCacheItem)
{
mUpdateCacheItem->waitTillDone();
mUpdateCacheItem = NULL;
}
for (PreloadMap::iterator it = mPreloadCells.begin(); it != mPreloadCells.end();++it)
it->second.mWorkItem->abort();
for (PreloadMap::iterator it = mPreloadCells.begin(); it != mPreloadCells.end();++it)
it->second.mWorkItem->waitTillDone();
}
mPreloadCells.clear();
}
@ -250,7 +270,7 @@ namespace MWWorld
return;
}
osg::ref_ptr<PreloadItem> item (new PreloadItem(cell, mResourceSystem->getSceneManager(), mBulletShapeManager, mResourceSystem->getKeyframeManager(), mTerrain, mPreloadInstances));
osg::ref_ptr<PreloadItem> item (new PreloadItem(cell, mResourceSystem->getSceneManager(), mBulletShapeManager, mResourceSystem->getKeyframeManager(), mTerrain, mLandManager, mPreloadInstances));
mWorkQueue->addWorkItem(item);
mPreloadCells[cell] = PreloadEntry(timestamp, item);
@ -303,10 +323,11 @@ namespace MWWorld
++it;
}
if (timestamp - mLastResourceCacheUpdate > 1.0)
if (timestamp - mLastResourceCacheUpdate > 1.0 && (!mUpdateCacheItem || mUpdateCacheItem->isDone()))
{
// the resource cache is cleared from the worker thread so that we're not holding up the main thread with delete operations
mWorkQueue->addWorkItem(new UpdateCacheItem(mResourceSystem, mTerrain, timestamp), true);
mUpdateCacheItem = new UpdateCacheItem(mResourceSystem, timestamp);
mWorkQueue->addWorkItem(mUpdateCacheItem, true);
mLastResourceCacheUpdate = timestamp;
}
}
@ -346,4 +367,64 @@ namespace MWWorld
mUnrefQueue = unrefQueue;
}
class TerrainPreloadItem : public SceneUtil::WorkItem
{
public:
TerrainPreloadItem(const std::vector<osg::ref_ptr<Terrain::View> >& views, Terrain::World* world, const std::vector<osg::Vec3f>& preloadPositions)
: mAbort(false)
, mTerrainViews(views)
, mWorld(world)
, mPreloadPositions(preloadPositions)
{
}
virtual void doWork()
{
for (unsigned int i=0; i<mTerrainViews.size() && i<mPreloadPositions.size() && !mAbort; ++i)
{
mWorld->preload(mTerrainViews[i], mPreloadPositions[i]);
mTerrainViews[i]->reset(0);
}
}
virtual void abort()
{
mAbort = true;
}
private:
volatile bool mAbort;
std::vector<osg::ref_ptr<Terrain::View> > mTerrainViews;
Terrain::World* mWorld;
std::vector<osg::Vec3f> mPreloadPositions;
};
void CellPreloader::setTerrainPreloadPositions(const std::vector<osg::Vec3f> &positions)
{
if (mTerrainPreloadItem && !mTerrainPreloadItem->isDone())
return;
else if (positions == mTerrainPreloadPositions)
return;
else
{
if (mTerrainViews.size() > positions.size())
{
for (unsigned int i=positions.size(); i<mTerrainViews.size(); ++i)
mUnrefQueue->push(mTerrainViews[i]);
mTerrainViews.resize(positions.size());
}
else if (mTerrainViews.size() < positions.size())
{
for (unsigned int i=mTerrainViews.size(); i<positions.size(); ++i)
mTerrainViews.push_back(mTerrain->createView());
}
// TODO: provide some way of giving the preloaded view to the main thread when we enter the cell
// right now, we just use it to make sure the resources are preloaded
mTerrainPreloadPositions = positions;
mTerrainPreloadItem = new TerrainPreloadItem(mTerrainViews, mTerrain, positions);
mWorkQueue->addWorkItem(mTerrainPreloadItem);
}
}
}

@ -3,6 +3,7 @@
#include <map>
#include <osg/ref_ptr>
#include <osg/Vec3f>
#include <components/sceneutil/workqueue.hpp>
namespace Resource
@ -14,6 +15,7 @@ namespace Resource
namespace Terrain
{
class World;
class View;
}
namespace SceneUtil
@ -21,6 +23,11 @@ namespace SceneUtil
class UnrefQueue;
}
namespace MWRender
{
class LandManager;
}
namespace MWWorld
{
class CellStore;
@ -28,7 +35,7 @@ namespace MWWorld
class CellPreloader
{
public:
CellPreloader(Resource::ResourceSystem* resourceSystem, Resource::BulletShapeManager* bulletShapeManager, Terrain::World* terrain);
CellPreloader(Resource::ResourceSystem* resourceSystem, Resource::BulletShapeManager* bulletShapeManager, Terrain::World* terrain, MWRender::LandManager* landManager);
~CellPreloader();
/// Ask a background thread to preload rendering meshes and collision shapes for objects in this cell.
@ -60,10 +67,13 @@ namespace MWWorld
void setUnrefQueue(SceneUtil::UnrefQueue* unrefQueue);
void setTerrainPreloadPositions(const std::vector<osg::Vec3f>& positions);
private:
Resource::ResourceSystem* mResourceSystem;
Resource::BulletShapeManager* mBulletShapeManager;
Terrain::World* mTerrain;
MWRender::LandManager* mLandManager;
osg::ref_ptr<SceneUtil::WorkQueue> mWorkQueue;
osg::ref_ptr<SceneUtil::UnrefQueue> mUnrefQueue;
double mExpiryDelay;
@ -92,6 +102,11 @@ namespace MWWorld
// Cells that are currently being preloaded, or have already finished preloading
PreloadMap mPreloadCells;
std::vector<osg::ref_ptr<Terrain::View> > mTerrainViews;
std::vector<osg::Vec3f> mTerrainPreloadPositions;
osg::ref_ptr<SceneUtil::WorkItem> mTerrainPreloadItem;
osg::ref_ptr<SceneUtil::WorkItem> mUpdateCacheItem;
};
}

@ -19,6 +19,7 @@
#include "../mwbase/windowmanager.hpp"
#include "../mwrender/renderingmanager.hpp"
#include "../mwrender/landmanager.hpp"
#include "../mwphysics/physicssystem.hpp"
@ -211,14 +212,11 @@ namespace MWWorld
void Scene::update (float duration, bool paused)
{
if (mPreloadEnabled)
mPreloadTimer += duration;
if (mPreloadTimer > 0.1f)
{
mPreloadTimer += duration;
if (mPreloadTimer > 0.1f)
{
preloadCells(0.1f);
mPreloadTimer = 0.f;
}
preloadCells(0.1f);
mPreloadTimer = 0.f;
}
mRendering.update (duration, paused);
@ -284,26 +282,19 @@ namespace MWWorld
// Load terrain physics first...
if (cell->getCell()->isExterior())
{
const ESM::Land* land =
MWBase::Environment::get().getWorld()->getStore().get<ESM::Land>().search(
cell->getCell()->getGridX(),
cell->getCell()->getGridY()
);
if (land && land->mDataTypes&ESM::Land::DATA_VHGT) {
// Actually only VHGT is needed here, but we'll need the rest for rendering anyway.
// Load everything now to reduce IO overhead.
const int flags = ESM::Land::DATA_VCLR|ESM::Land::DATA_VHGT|ESM::Land::DATA_VNML|ESM::Land::DATA_VTEX;
const ESM::Land::LandData *data = land->getLandData (flags);
mPhysics->addHeightField (data->mHeights, cell->getCell()->getGridX(), cell->getCell()->getGridY(),
worldsize / (verts-1), verts);
int cellX = cell->getCell()->getGridX();
int cellY = cell->getCell()->getGridY();
osg::ref_ptr<const ESMTerrain::LandObject> land = mRendering.getLandManager()->getLand(cellX, cellY);
const ESM::Land::LandData* data = land ? land->getData(ESM::Land::DATA_VHGT) : 0;
if (data)
{
mPhysics->addHeightField (data->mHeights, cellX, cell->getCell()->getGridY(), worldsize / (verts-1), verts, data->mMinHeight, data->mMaxHeight, land.get());
}
else
{
static std::vector<float> defaultHeight;
defaultHeight.resize(verts*verts, ESM::Land::DEFAULT_HEIGHT);
mPhysics->addHeightField (&defaultHeight[0], cell->getCell()->getGridX(), cell->getCell()->getGridY(),
worldsize / (verts-1), verts);
mPhysics->addHeightField (&defaultHeight[0], cell->getCell()->getGridX(), cell->getCell()->getGridY(), worldsize / (verts-1), verts, ESM::Land::DEFAULT_HEIGHT, ESM::Land::DEFAULT_HEIGHT, land.get());
}
}
@ -379,7 +370,6 @@ namespace MWWorld
int newX, newY;
MWBase::Environment::get().getWorld()->positionToIndex(pos.x(), pos.y(), newX, newY);
changeCellGrid(newX, newY);
//mRendering.updateTerrain();
}
}
@ -388,8 +378,6 @@ namespace MWWorld
Loading::Listener* loadingListener = MWBase::Environment::get().getWindowManager()->getLoadingScreen();
Loading::ScopedLoad load(loadingListener);
//mRendering.enableTerrain(true);
std::string loadingExteriorText = "#{sLoadingMessage3}";
loadingListener->setLabel(loadingExteriorText);
@ -488,6 +476,8 @@ namespace MWWorld
{
mCurrentCell = cell;
mRendering.enableTerrain(cell->isExterior());
MWBase::World *world = MWBase::Environment::get().getWorld();
MWWorld::Ptr old = world->getPlayerPtr();
world->getPlayer().setCell(cell);
@ -529,7 +519,7 @@ namespace MWWorld
, mPreloadDoors(Settings::Manager::getBool("preload doors", "Cells"))
, mPreloadFastTravel(Settings::Manager::getBool("preload fast travel", "Cells"))
{
mPreloader.reset(new CellPreloader(rendering.getResourceSystem(), physics->getShapeManager(), rendering.getTerrain()));
mPreloader.reset(new CellPreloader(rendering.getResourceSystem(), physics->getShapeManager(), rendering.getTerrain(), rendering.getLandManager()));
mPreloader->setWorkQueue(mRendering.getWorkQueue());
mPreloader->setUnrefQueue(rendering.getUnrefQueue());
@ -571,8 +561,6 @@ namespace MWWorld
loadingListener->setLabel(loadingInteriorText);
Loading::ScopedLoad load(loadingListener);
//mRendering.enableTerrain(false);
if(!loadcell)
{
MWBase::World *world = MWBase::Environment::get().getWorld();
@ -648,8 +636,6 @@ namespace MWWorld
CellStore* current = MWBase::Environment::get().getWorld()->getExterior(x, y);
changePlayerCell(current, position, adjustPlayerPos);
//mRendering.updateTerrain();
}
CellStore* Scene::getCurrentCell ()
@ -753,22 +739,32 @@ namespace MWWorld
void Scene::preloadCells(float dt)
{
std::vector<osg::Vec3f> exteriorPositions;
const MWWorld::ConstPtr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
osg::Vec3f playerPos = player.getRefData().getPosition().asVec3();
osg::Vec3f moved = playerPos - mLastPlayerPos;
osg::Vec3f predictedPos = playerPos + moved / dt;
if (mCurrentCell->isExterior())
exteriorPositions.push_back(predictedPos);
mLastPlayerPos = playerPos;
if (mPreloadDoors)
preloadTeleportDoorDestinations(playerPos, predictedPos);
if (mPreloadExteriorGrid)
preloadExteriorGrid(playerPos, predictedPos);
if (mPreloadFastTravel)
preloadFastTravelDestinations(playerPos, predictedPos);
if (mPreloadEnabled)
{
if (mPreloadDoors)
preloadTeleportDoorDestinations(playerPos, predictedPos, exteriorPositions);
if (mPreloadExteriorGrid)
preloadExteriorGrid(playerPos, predictedPos);
if (mPreloadFastTravel)
preloadFastTravelDestinations(playerPos, predictedPos, exteriorPositions);
}
mPreloader->setTerrainPreloadPositions(exteriorPositions);
}
void Scene::preloadTeleportDoorDestinations(const osg::Vec3f& playerPos, const osg::Vec3f& predictedPos)
void Scene::preloadTeleportDoorDestinations(const osg::Vec3f& playerPos, const osg::Vec3f& predictedPos, std::vector<osg::Vec3f>& exteriorPositions)
{
std::vector<MWWorld::ConstPtr> teleportDoors;
for (CellStoreCollection::const_iterator iter (mActiveCells.begin());
@ -800,9 +796,11 @@ namespace MWWorld
preloadCell(MWBase::Environment::get().getWorld()->getInterior(door.getCellRef().getDestCell()));
else
{
osg::Vec3f pos = door.getCellRef().getDoorDest().asVec3();
int x,y;
MWBase::Environment::get().getWorld()->positionToIndex (door.getCellRef().getDoorDest().pos[0], door.getCellRef().getDoorDest().pos[1], x, y);
MWBase::Environment::get().getWorld()->positionToIndex (pos.x(), pos.y(), x, y);
preloadCell(MWBase::Environment::get().getWorld()->getExterior(x,y), true);
exteriorPositions.push_back(pos);
}
}
catch (std::exception& e)
@ -868,6 +866,13 @@ namespace MWWorld
mPreloader->preload(cell, mRendering.getReferenceTime());
}
void Scene::preloadTerrain(const osg::Vec3f &pos)
{
std::vector<osg::Vec3f> vec;
vec.push_back(pos);
mPreloader->setTerrainPreloadPositions(vec);
}
struct ListFastTravelDestinationsVisitor
{
ListFastTravelDestinationsVisitor(float preloadDist, const osg::Vec3f& playerPos)
@ -898,7 +903,7 @@ namespace MWWorld
std::vector<ESM::Transport::Dest> mList;
};
void Scene::preloadFastTravelDestinations(const osg::Vec3f& playerPos, const osg::Vec3f& /*predictedPos*/) // ignore predictedPos here since opening dialogue with travel service takes extra time
void Scene::preloadFastTravelDestinations(const osg::Vec3f& playerPos, const osg::Vec3f& /*predictedPos*/, std::vector<osg::Vec3f>& exteriorPositions) // ignore predictedPos here since opening dialogue with travel service takes extra time
{
const MWWorld::ConstPtr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
ListFastTravelDestinationsVisitor listVisitor(mPreloadDistance, player.getRefData().getPosition().asVec3());
@ -916,9 +921,11 @@ namespace MWWorld
preloadCell(MWBase::Environment::get().getWorld()->getInterior(it->mCellName));
else
{
osg::Vec3f pos = it->mPos.asVec3();
int x,y;
MWBase::Environment::get().getWorld()->positionToIndex( it->mPos.pos[0], it->mPos.pos[1], x, y);
MWBase::Environment::get().getWorld()->positionToIndex( pos.x(), pos.y(), x, y);
preloadCell(MWBase::Environment::get().getWorld()->getExterior(x,y), true);
exteriorPositions.push_back(pos);
}
}
}

@ -78,9 +78,9 @@ namespace MWWorld
void getGridCenter(int& cellX, int& cellY);
void preloadCells(float dt);
void preloadTeleportDoorDestinations(const osg::Vec3f& playerPos, const osg::Vec3f& predictedPos);
void preloadTeleportDoorDestinations(const osg::Vec3f& playerPos, const osg::Vec3f& predictedPos, std::vector<osg::Vec3f>& exteriorPositions);
void preloadExteriorGrid(const osg::Vec3f& playerPos, const osg::Vec3f& predictedPos);
void preloadFastTravelDestinations(const osg::Vec3f& playerPos, const osg::Vec3f& predictedPos);
void preloadFastTravelDestinations(const osg::Vec3f& playerPos, const osg::Vec3f& predictedPos, std::vector<osg::Vec3f>& exteriorPositions);
public:
@ -89,6 +89,7 @@ namespace MWWorld
~Scene();
void preloadCell(MWWorld::CellStore* cell, bool preloadSurrounding=false);
void preloadTerrain(const osg::Vec3f& pos);
void unloadCell (CellStoreCollection::iterator iter);

@ -399,6 +399,7 @@ namespace MWWorld
case ESM::REC_PLAY:
mPlayer->readRecord(reader, type);
mWorldScene->preloadCell(getPlayerPtr().getCell(), true);
mWorldScene->preloadTerrain(getPlayerPtr().getRefData().getPosition().asVec3());
break;
default:
if (!mStore.readRecord (reader, type) &&

@ -118,7 +118,7 @@ add_component_dir (translation
)
add_component_dir (terrain
storage world buffercache defs terraingrid material
storage world buffercache defs terraingrid material terraindrawable texturemanager chunkmanager compositemaprenderer quadtreeworld quadtreenode viewdata
)
add_component_dir (loadinglistener
@ -243,7 +243,6 @@ target_link_libraries(components
${OSGVIEWER_LIBRARIES}
${OSGTEXT_LIBRARIES}
${OSGGA_LIBRARIES}
${OSGFX_LIBRARIES}
${OSGANIMATION_LIBRARIES}
${Bullet_LIBRARIES}
${SDL2_LIBRARIES}

@ -2,8 +2,6 @@
#include <utility>
#include <OpenThreads/ScopedLock>
#include "esmreader.hpp"
#include "esmwriter.hpp"
#include "defs.hpp"
@ -18,7 +16,6 @@ namespace ESM
, mY(0)
, mPlugin(0)
, mDataTypes(0)
, mDataLoaded(false)
, mLandData(NULL)
{
}
@ -76,7 +73,6 @@ namespace ESM
mContext = esm.getContext();
mDataLoaded = 0;
mLandData = NULL;
// Skip the land data here. Load it when the cell is loaded.
@ -179,45 +175,59 @@ namespace ESM
}
void Land::loadData(int flags) const
void Land::loadData(int flags, LandData* target) const
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(mMutex);
// Create storage if nothing is loaded
if (!target && !mLandData)
{
mLandData = new LandData;
}
if (!target)
target = mLandData;
// Try to load only available data
flags = flags & mDataTypes;
// Return if all required data is loaded
if ((mDataLoaded & flags) == flags) {
if ((target->mDataLoaded & flags) == flags) {
return;
}
// Create storage if nothing is loaded
if (mLandData == NULL) {
mLandData = new LandData;
}
ESM::ESMReader reader;
reader.restoreContext(mContext);
if (reader.isNextSub("VNML")) {
condLoad(reader, flags, DATA_VNML, mLandData->mNormals, sizeof(mLandData->mNormals));
condLoad(reader, flags, target->mDataLoaded, DATA_VNML, target->mNormals, sizeof(target->mNormals));
}
if (reader.isNextSub("VHGT")) {
VHGT vhgt;
if (condLoad(reader, flags, DATA_VHGT, &vhgt, sizeof(vhgt))) {
if (condLoad(reader, flags, target->mDataLoaded, DATA_VHGT, &vhgt, sizeof(vhgt))) {
target->mMinHeight = FLT_MAX;
target->mMaxHeight = -FLT_MAX;
float rowOffset = vhgt.mHeightOffset;
for (int y = 0; y < LAND_SIZE; y++) {
rowOffset += vhgt.mHeightData[y * LAND_SIZE];
mLandData->mHeights[y * LAND_SIZE] = rowOffset * HEIGHT_SCALE;
target->mHeights[y * LAND_SIZE] = rowOffset * HEIGHT_SCALE;
if (rowOffset * HEIGHT_SCALE > target->mMaxHeight)
target->mMaxHeight = rowOffset * HEIGHT_SCALE;
if (rowOffset * HEIGHT_SCALE < target->mMinHeight)
target->mMinHeight = rowOffset * HEIGHT_SCALE;
float colOffset = rowOffset;
for (int x = 1; x < LAND_SIZE; x++) {
colOffset += vhgt.mHeightData[y * LAND_SIZE + x];
mLandData->mHeights[x + y * LAND_SIZE] = colOffset * HEIGHT_SCALE;
target->mHeights[x + y * LAND_SIZE] = colOffset * HEIGHT_SCALE;
if (colOffset * HEIGHT_SCALE > target->mMaxHeight)
target->mMaxHeight = colOffset * HEIGHT_SCALE;
if (colOffset * HEIGHT_SCALE < target->mMinHeight)
target->mMinHeight = colOffset * HEIGHT_SCALE;
}
}
mLandData->mUnk1 = vhgt.mUnk1;
mLandData->mUnk2 = vhgt.mUnk2;
target->mUnk1 = vhgt.mUnk1;
target->mUnk2 = vhgt.mUnk2;
}
}
@ -225,30 +235,29 @@ namespace ESM
reader.skipHSub();
if (reader.isNextSub("VCLR"))
condLoad(reader, flags, DATA_VCLR, mLandData->mColours, 3 * LAND_NUM_VERTS);
condLoad(reader, flags, target->mDataLoaded, DATA_VCLR, target->mColours, 3 * LAND_NUM_VERTS);
if (reader.isNextSub("VTEX")) {
uint16_t vtex[LAND_NUM_TEXTURES];
if (condLoad(reader, flags, DATA_VTEX, vtex, sizeof(vtex))) {
transposeTextureData(vtex, mLandData->mTextures);
if (condLoad(reader, flags, target->mDataLoaded, DATA_VTEX, vtex, sizeof(vtex))) {
transposeTextureData(vtex, target->mTextures);
}
}
}
void Land::unloadData() const
{
if (mDataLoaded)
if (mLandData)
{
delete mLandData;
mLandData = NULL;
mDataLoaded = 0;
}
}
bool Land::condLoad(ESM::ESMReader& reader, int flags, int dataFlag, void *ptr, unsigned int size) const
bool Land::condLoad(ESM::ESMReader& reader, int flags, int& targetFlags, int dataFlag, void *ptr, unsigned int size) const
{
if ((mDataLoaded & dataFlag) == 0 && (flags & dataFlag) != 0) {
if ((targetFlags & dataFlag) == 0 && (flags & dataFlag) != 0) {
reader.getHExact(ptr, size);
mDataLoaded |= dataFlag;
targetFlags |= dataFlag;
return true;
}
reader.skipHSubSize(size);
@ -257,14 +266,12 @@ namespace ESM
bool Land::isDataLoaded(int flags) const
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(mMutex);
return (mDataLoaded & flags) == (flags & mDataTypes);
return mLandData && (mLandData->mDataLoaded & flags) == (flags & mDataTypes);
}
Land::Land (const Land& land)
: mFlags (land.mFlags), mX (land.mX), mY (land.mY), mPlugin (land.mPlugin),
mContext (land.mContext), mDataTypes (land.mDataTypes),
mDataLoaded (land.mDataLoaded),
mLandData (land.mLandData ? new LandData (*land.mLandData) : 0)
{}
@ -282,7 +289,6 @@ namespace ESM
std::swap (mPlugin, land.mPlugin);
std::swap (mContext, land.mContext);
std::swap (mDataTypes, land.mDataTypes);
std::swap (mDataLoaded, land.mDataLoaded);
std::swap (mLandData, land.mLandData);
}
@ -311,18 +317,25 @@ namespace ESM
mLandData = new LandData;
mDataTypes |= flags;
mDataLoaded |= flags;
mLandData->mDataLoaded |= flags;
}
void Land::remove (int flags)
{
mDataTypes &= ~flags;
mDataLoaded &= ~flags;
if (!mDataLoaded)
if (mLandData)
{
delete mLandData;
mLandData = 0;
mLandData->mDataLoaded &= ~flags;
if (!mLandData->mDataLoaded)
{
delete mLandData;
mLandData = 0;
}
}
}
const int Land::LAND_SIZE;
}

@ -3,8 +3,6 @@
#include <stdint.h>
#include <OpenThreads/Mutex>
#include "esmcommon.hpp"
namespace ESM
@ -80,10 +78,17 @@ struct Land
struct LandData
{
LandData()
: mDataLoaded(0)
{
}
// Initial reference height for the first vertex, only needed for filling mHeights
float mHeightOffset;
// Height in world space for each vertex
float mHeights[LAND_NUM_VERTS];
float mMinHeight;
float mMaxHeight;
// 24-bit normals, these aren't always correct though. Edge and corner normals may be garbage.
VNML mNormals[LAND_NUM_VERTS * 3];
@ -99,6 +104,8 @@ struct Land
// ???
short mUnk1;
uint8_t mUnk2;
int mDataLoaded;
};
// low-LOD heightmap (used for rendering the global map)
@ -110,12 +117,13 @@ struct Land
void blank() {}
/**
* Actually loads data
* Actually loads data into target
* If target is NULL, assumed target is mLandData
*/
void loadData(int flags) const;
void loadData(int flags, LandData* target = NULL) const;
/**
* Frees memory allocated for land data
* Frees memory allocated for mLandData
*/
void unloadData() const;
@ -153,11 +161,7 @@ struct Land
/// Loads data and marks it as loaded
/// \return true if data is actually loaded from file, false otherwise
/// including the case when data is already loaded
bool condLoad(ESM::ESMReader& reader, int flags, int dataFlag, void *ptr, unsigned int size) const;
mutable OpenThreads::Mutex mMutex;
mutable int mDataLoaded;
bool condLoad(ESM::ESMReader& reader, int flags, int& targetFlags, int dataFlag, void *ptr, unsigned int size) const;
mutable LandData *mLandData;
};

@ -16,6 +16,45 @@
namespace ESMTerrain
{
class LandCache
{
public:
typedef std::map<std::pair<int, int>, osg::ref_ptr<const LandObject> > Map;
Map mMap;
};
LandObject::LandObject()
{
}
LandObject::LandObject(const ESM::Land *land, int loadFlags)
: mLand(land)
, mLoadFlags(loadFlags)
{
mLand->loadData(mLoadFlags, &mData);
}
LandObject::LandObject(const LandObject &copy, const osg::CopyOp &copyop)
{
}
LandObject::~LandObject()
{
}
const ESM::Land::LandData *LandObject::getData(int flags) const
{
if ((mData.mDataLoaded & flags) != flags)
return NULL;
return &mData;
}
int LandObject::getPlugin() const
{
return mLand->mPlugin;
}
const float defaultHeight = ESM::Land::DEFAULT_HEIGHT;
Storage::Storage(const VFS::Manager *vfs, const std::string& normalMapPattern, const std::string& normalHeightMapPattern, bool autoUseNormalMaps, const std::string& specularMapPattern, bool autoUseSpecularMaps)
@ -28,20 +67,10 @@ namespace ESMTerrain
{
}
const ESM::Land::LandData *Storage::getLandData (int cellX, int cellY, int flags)
{
if (const ESM::Land *land = getLand (cellX, cellY))
return land->getLandData (flags);
return 0;
}
bool Storage::getMinMaxHeights(float size, const osg::Vec2f &center, float &min, float &max)
{
assert (size <= 1 && "Storage::getMinMaxHeights, chunk size should be <= 1 cell");
/// \todo investigate if min/max heights should be stored at load time in ESM::Land instead
osg::Vec2f origin = center - osg::Vec2f(size/2.f, size/2.f);
int cellX = static_cast<int>(std::floor(origin.x()));
@ -53,7 +82,9 @@ namespace ESMTerrain
int endRow = startRow + size * (ESM::Land::LAND_SIZE-1) + 1;
int endColumn = startColumn + size * (ESM::Land::LAND_SIZE-1) + 1;
if (const ESM::Land::LandData *data = getLandData (cellX, cellY, ESM::Land::DATA_VHGT))
osg::ref_ptr<const LandObject> land = getLand (cellX, cellY);
const ESM::Land::LandData* data = land ? land->getData(ESM::Land::DATA_VHGT) : 0;
if (data)
{
min = std::numeric_limits<float>::max();
max = -std::numeric_limits<float>::max();
@ -73,10 +104,10 @@ namespace ESMTerrain
min = defaultHeight;
max = defaultHeight;
return true;
return false;
}
void Storage::fixNormal (osg::Vec3f& normal, int cellX, int cellY, int col, int row)
void Storage::fixNormal (osg::Vec3f& normal, int cellX, int cellY, int col, int row, LandCache& cache)
{
while (col >= ESM::Land::LAND_SIZE-1)
{
@ -99,7 +130,9 @@ namespace ESMTerrain
row += ESM::Land::LAND_SIZE-1;
}
if (const ESM::Land::LandData *data = getLandData (cellX, cellY, ESM::Land::DATA_VNML))
const LandObject* land = getLand(cellX, cellY, cache);
const ESM::Land::LandData* data = land ? land->getData(ESM::Land::DATA_VNML) : 0;
if (data)
{
normal.x() = data->mNormals[col*ESM::Land::LAND_SIZE*3+row*3];
normal.y() = data->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+1];
@ -110,18 +143,18 @@ namespace ESMTerrain
normal = osg::Vec3f(0,0,1);
}
void Storage::averageNormal(osg::Vec3f &normal, int cellX, int cellY, int col, int row)
void Storage::averageNormal(osg::Vec3f &normal, int cellX, int cellY, int col, int row, LandCache& cache)
{
osg::Vec3f n1,n2,n3,n4;
fixNormal(n1, cellX, cellY, col+1, row);
fixNormal(n2, cellX, cellY, col-1, row);
fixNormal(n3, cellX, cellY, col, row+1);
fixNormal(n4, cellX, cellY, col, row-1);
fixNormal(n1, cellX, cellY, col+1, row, cache);
fixNormal(n2, cellX, cellY, col-1, row, cache);
fixNormal(n3, cellX, cellY, col, row+1, cache);
fixNormal(n4, cellX, cellY, col, row-1, cache);
normal = (n1+n2+n3+n4);
normal.normalize();
}
void Storage::fixColour (osg::Vec4f& color, int cellX, int cellY, int col, int row)
void Storage::fixColour (osg::Vec4f& color, int cellX, int cellY, int col, int row, LandCache& cache)
{
if (col == ESM::Land::LAND_SIZE-1)
{
@ -134,7 +167,9 @@ namespace ESMTerrain
row = 0;
}
if (const ESM::Land::LandData *data = getLandData (cellX, cellY, ESM::Land::DATA_VCLR))
const LandObject* land = getLand(cellX, cellY, cache);
const ESM::Land::LandData* data = land ? land->getData(ESM::Land::DATA_VCLR) : 0;
if (data)
{
color.r() = data->mColours[col*ESM::Land::LAND_SIZE*3+row*3] / 255.f;
color.g() = data->mColours[col*ESM::Land::LAND_SIZE*3+row*3+1] / 255.f;
@ -146,7 +181,6 @@ namespace ESMTerrain
color.g() = 1;
color.b() = 1;
}
}
void Storage::fillVertexBuffers (int lodLevel, float size, const osg::Vec2f& center,
@ -174,15 +208,24 @@ namespace ESMTerrain
float vertY = 0;
float vertX = 0;
LandCache cache;
float vertY_ = 0; // of current cell corner
for (int cellY = startCellY; cellY < startCellY + std::ceil(size); ++cellY)
{
float vertX_ = 0; // of current cell corner
for (int cellX = startCellX; cellX < startCellX + std::ceil(size); ++cellX)
{
const ESM::Land::LandData *heightData = getLandData (cellX, cellY, ESM::Land::DATA_VHGT);
const ESM::Land::LandData *normalData = getLandData (cellX, cellY, ESM::Land::DATA_VNML);
const ESM::Land::LandData *colourData = getLandData (cellX, cellY, ESM::Land::DATA_VCLR);
const LandObject* land = getLand(cellX, cellY, cache);
const ESM::Land::LandData *heightData = 0;
const ESM::Land::LandData *normalData = 0;
const ESM::Land::LandData *colourData = 0;
if (land)
{
heightData = land->getData(ESM::Land::DATA_VHGT);
normalData = land->getData(ESM::Land::DATA_VNML);
colourData = land->getData(ESM::Land::DATA_VCLR);
}
int rowStart = 0;
int colStart = 0;
@ -197,8 +240,8 @@ namespace ESMTerrain
// Only relevant for chunks smaller than (contained in) one cell
rowStart += (origin.x() - startCellX) * ESM::Land::LAND_SIZE;
colStart += (origin.y() - startCellY) * ESM::Land::LAND_SIZE;
int rowEnd = rowStart + std::min(1.f, size) * (ESM::Land::LAND_SIZE-1) + 1;
int colEnd = colStart + std::min(1.f, size) * (ESM::Land::LAND_SIZE-1) + 1;
int rowEnd = std::min(static_cast<int>(rowStart + std::min(1.f, size) * (ESM::Land::LAND_SIZE-1) + 1), ESM::Land::LAND_SIZE);
int colEnd = std::min(static_cast<int>(colStart + std::min(1.f, size) * (ESM::Land::LAND_SIZE-1) + 1), ESM::Land::LAND_SIZE);
vertY = vertY_;
for (int col=colStart; col<colEnd; col += increment)
@ -235,11 +278,11 @@ namespace ESMTerrain
// Normals apparently don't connect seamlessly between cells
if (col == ESM::Land::LAND_SIZE-1 || row == ESM::Land::LAND_SIZE-1)
fixNormal(normal, cellX, cellY, col, row);
fixNormal(normal, cellX, cellY, col, row, cache);
// some corner normals appear to be complete garbage (z < 0)
if ((row == 0 || row == ESM::Land::LAND_SIZE-1) && (col == 0 || col == ESM::Land::LAND_SIZE-1))
averageNormal(normal, cellX, cellY, col, row);
averageNormal(normal, cellX, cellY, col, row, cache);
assert(normal.z() > 0);
@ -259,7 +302,7 @@ namespace ESMTerrain
// Unlike normals, colors mostly connect seamlessly between cells, but not always...
if (col == ESM::Land::LAND_SIZE-1 || row == ESM::Land::LAND_SIZE-1)
fixColour(color, cellX, cellY, col, row);
fixColour(color, cellX, cellY, col, row, cache);
color.a() = 1;
@ -279,7 +322,7 @@ namespace ESMTerrain
}
Storage::UniqueTextureId Storage::getVtexIndexAt(int cellX, int cellY,
int x, int y)
int x, int y, LandCache& cache)
{
// For the first/last row/column, we need to get the texture from the neighbour cell
// to get consistent blending at the borders
@ -289,7 +332,12 @@ namespace ESMTerrain
--cellX;
x += ESM::Land::LAND_TEXTURE_SIZE;
}
if (y >= ESM::Land::LAND_TEXTURE_SIZE) // Y appears to be wrapped from the other side because why the hell not?
while (x >= ESM::Land::LAND_TEXTURE_SIZE)
{
++cellX;
x -= ESM::Land::LAND_TEXTURE_SIZE;
}
while (y >= ESM::Land::LAND_TEXTURE_SIZE) // Y appears to be wrapped from the other side because why the hell not?
{
++cellY;
y -= ESM::Land::LAND_TEXTURE_SIZE;
@ -298,15 +346,17 @@ namespace ESMTerrain
assert(x<ESM::Land::LAND_TEXTURE_SIZE);
assert(y<ESM::Land::LAND_TEXTURE_SIZE);
if (const ESM::Land::LandData *data = getLandData (cellX, cellY, ESM::Land::DATA_VTEX))
const LandObject* land = getLand(cellX, cellY, cache);
const ESM::Land::LandData *data = land ? land->getData(ESM::Land::DATA_VTEX) : 0;
if (data)
{
int tex = data->mTextures[y * ESM::Land::LAND_TEXTURE_SIZE + x];
if (tex == 0)
return std::make_pair(0,0); // vtex 0 is always the base texture, regardless of plugin
return std::make_pair(tex, getLand (cellX, cellY)->mPlugin);
return std::make_pair(tex, land->getPlugin());
}
else
return std::make_pair(0,0);
return std::make_pair(0,0);
}
std::string Storage::getTextureName(UniqueTextureId id)
@ -332,10 +382,6 @@ namespace ESMTerrain
void Storage::getBlendmaps(float chunkSize, const osg::Vec2f &chunkCenter,
bool pack, ImageVector &blendmaps, std::vector<Terrain::LayerInfo> &layerList)
{
// TODO - blending isn't completely right yet; the blending radius appears to be
// different at a cell transition (2 vertices, not 4), so we may need to create a larger blendmap
// and interpolate the rest of the cell by hand? :/
osg::Vec2f origin = chunkCenter - osg::Vec2f(chunkSize/2.f, chunkSize/2.f);
int cellX = static_cast<int>(std::floor(origin.x()));
int cellY = static_cast<int>(std::floor(origin.y()));
@ -347,10 +393,6 @@ namespace ESMTerrain
int rowEnd = rowStart + chunkSize * (realTextureSize-1) + 1;
int colEnd = colStart + chunkSize * (realTextureSize-1) + 1;
assert (rowStart >= 0 && colStart >= 0);
assert (rowEnd <= realTextureSize);
assert (colEnd <= realTextureSize);
// Save the used texture indices so we know the total number of textures
// and number of required blend maps
std::set<UniqueTextureId> textureIndices;
@ -360,10 +402,12 @@ namespace ESMTerrain
// So we're always adding _land_default.dds as the base layer here, even if it's not referenced in this cell.
textureIndices.insert(std::make_pair(0,0));
LandCache cache;
for (int y=colStart; y<colEnd; ++y)
for (int x=rowStart; x<rowEnd; ++x)
{
UniqueTextureId id = getVtexIndexAt(cellX, cellY, x, y);
UniqueTextureId id = getVtexIndexAt(cellX, cellY, x, y, cache);
textureIndices.insert(id);
}
@ -399,7 +443,7 @@ namespace ESMTerrain
{
for (int x=0; x<blendmapSize; ++x)
{
UniqueTextureId id = getVtexIndexAt(cellX, cellY, x+rowStart, y+colStart);
UniqueTextureId id = getVtexIndexAt(cellX, cellY, x+rowStart, y+colStart, cache);
assert(textureIndicesMap.find(id) != textureIndicesMap.end());
int layerIndex = textureIndicesMap.find(id)->second;
int blendIndex = (pack ? static_cast<int>(std::floor((layerIndex - 1) / 4.f)) : layerIndex - 1);
@ -421,8 +465,12 @@ namespace ESMTerrain
int cellX = static_cast<int>(std::floor(worldPos.x() / 8192.f));
int cellY = static_cast<int>(std::floor(worldPos.y() / 8192.f));
const ESM::Land* land = getLand(cellX, cellY);
if (!land || !(land->mDataTypes&ESM::Land::DATA_VHGT))
osg::ref_ptr<const LandObject> land = getLand(cellX, cellY);
if (!land)
return defaultHeight;
const ESM::Land::LandData* data = land->getData(ESM::Land::DATA_VHGT);
if (!data)
return defaultHeight;
// Mostly lifted from Ogre::Terrain::getHeightAtTerrainPosition
@ -461,10 +509,10 @@ namespace ESMTerrain
*/
// Build all 4 positions in normalized cell space, using point-sampled height
osg::Vec3f v0 (startXTS, startYTS, getVertexHeight(land, startX, startY) / 8192.f);
osg::Vec3f v1 (endXTS, startYTS, getVertexHeight(land, endX, startY) / 8192.f);
osg::Vec3f v2 (endXTS, endYTS, getVertexHeight(land, endX, endY) / 8192.f);
osg::Vec3f v3 (startXTS, endYTS, getVertexHeight(land, startX, endY) / 8192.f);
osg::Vec3f v0 (startXTS, startYTS, getVertexHeight(data, startX, startY) / 8192.f);
osg::Vec3f v1 (endXTS, startYTS, getVertexHeight(data, endX, startY) / 8192.f);
osg::Vec3f v2 (endXTS, endYTS, getVertexHeight(data, endX, endY) / 8192.f);
osg::Vec3f v3 (startXTS, endYTS, getVertexHeight(data, startX, endY) / 8192.f);
// define this plane in terrain space
osg::Plane plane;
// FIXME: deal with differing triangle alignment
@ -496,11 +544,23 @@ namespace ESMTerrain
}
float Storage::getVertexHeight(const ESM::Land *land, int x, int y)
float Storage::getVertexHeight(const ESM::Land::LandData* data, int x, int y)
{
assert(x < ESM::Land::LAND_SIZE);
assert(y < ESM::Land::LAND_SIZE);
return land->getLandData()->mHeights[y * ESM::Land::LAND_SIZE + x];
return data->mHeights[y * ESM::Land::LAND_SIZE + x];
}
const LandObject* Storage::getLand(int cellX, int cellY, LandCache& cache)
{
LandCache::Map::iterator found = cache.mMap.find(std::make_pair(cellX, cellY));
if (found != cache.mMap.end())
return found->second;
else
{
found = cache.mMap.insert(std::make_pair(std::make_pair(cellX, cellY), getLand(cellX, cellY))).first;
return found->second;
}
}
Terrain::LayerInfo Storage::getLayerInfo(const std::string& texture)
@ -570,4 +630,9 @@ namespace ESMTerrain
return ESM::Land::LAND_SIZE;
}
int Storage::getBlendmapScale(float chunkSize)
{
return ESM::Land::LAND_TEXTURE_SIZE*chunkSize;
}
}

@ -16,25 +16,39 @@ namespace VFS
namespace ESMTerrain
{
/// @brief Feeds data from ESM terrain records (ESM::Land, ESM::LandTexture)
/// into the terrain component, converting it on the fly as needed.
class Storage : public Terrain::Storage
class LandCache;
/// @brief Wrapper around Land Data with reference counting. The wrapper needs to be held as long as the data is still in use
class LandObject : public osg::Object
{
public:
LandObject();
LandObject(const ESM::Land* land, int loadFlags);
LandObject(const LandObject& copy, const osg::CopyOp& copyop);
virtual ~LandObject();
META_Object(ESMTerrain, LandObject)
const ESM::Land::LandData* getData(int flags) const;
int getPlugin() const;
private:
const ESM::Land* mLand;
int mLoadFlags;
// Not implemented in this class, because we need different Store implementations for game and editor
virtual const ESM::Land* getLand (int cellX, int cellY)= 0;
virtual const ESM::LandTexture* getLandTexture(int index, short plugin) = 0;
ESM::Land::LandData mData;
};
/// @brief Feeds data from ESM terrain records (ESM::Land, ESM::LandTexture)
/// into the terrain component, converting it on the fly as needed.
class Storage : public Terrain::Storage
{
public:
Storage(const VFS::Manager* vfs, const std::string& normalMapPattern = "", const std::string& normalHeightMapPattern = "", bool autoUseNormalMaps = false, const std::string& specularMapPattern = "", bool autoUseSpecularMaps = false);
/// Data is loaded first, if necessary. Will return a 0-pointer if there is no data for
/// any of the data types specified via \a flags. Will also return a 0-pointer if there
/// is no land record for the coordinates \a cellX / \a cellY.
const ESM::Land::LandData *getLandData (int cellX, int cellY, int flags);
// Not implemented in this class, because we need different Store implementations for game and editor
virtual osg::ref_ptr<const LandObject> getLand (int cellX, int cellY)= 0;
virtual const ESM::LandTexture* getLandTexture(int index, short plugin) = 0;
/// Get bounds of the whole terrain in cell units
virtual void getBounds(float& minX, float& maxX, float& minY, float& maxY) = 0;
@ -88,14 +102,18 @@ namespace ESMTerrain
/// Get the number of vertices on one side for each cell. Should be (power of two)+1
virtual int getCellVertices();
virtual int getBlendmapScale(float chunkSize);
private:
const VFS::Manager* mVFS;
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);
void fixNormal (osg::Vec3f& normal, int cellX, int cellY, int col, int row, LandCache& cache);
void fixColour (osg::Vec4f& colour, int cellX, int cellY, int col, int row, LandCache& cache);
void averageNormal (osg::Vec3f& normal, int cellX, int cellY, int col, int row, LandCache& cache);
float getVertexHeight (const ESM::Land::LandData* data, int x, int y);
float getVertexHeight (const ESM::Land* land, int x, int y);
const LandObject* getLand(int cellX, int cellY, LandCache& cache);
// Since plugins can define new texture palettes, we need to know the plugin index too
// in order to retrieve the correct texture name.
@ -103,7 +121,7 @@ namespace ESMTerrain
typedef std::pair<short, short> UniqueTextureId;
UniqueTextureId getVtexIndexAt(int cellX, int cellY,
int x, int y);
int x, int y, LandCache&);
std::string getTextureName (UniqueTextureId id);
std::map<std::string, Terrain::LayerInfo> mLayerInfoMap;

@ -185,7 +185,7 @@ void BulletShapeManager::updateCache(double referenceTime)
mInstanceCache->removeUnreferencedObjectsInCache();
}
void BulletShapeManager::reportStats(unsigned int frameNumber, osg::Stats *stats)
void BulletShapeManager::reportStats(unsigned int frameNumber, osg::Stats *stats) const
{
stats->setAttribute(frameNumber, "Shape", mCache->getCacheSize());
stats->setAttribute(frameNumber, "Shape Instance", mInstanceCache->getCacheSize());

@ -42,7 +42,7 @@ namespace Resource
/// @see ResourceManager::updateCache
virtual void updateCache(double referenceTime);
void reportStats(unsigned int frameNumber, osg::Stats *stats);
void reportStats(unsigned int frameNumber, osg::Stats *stats) const;
private:
osg::ref_ptr<BulletShapeInstance> createInstance(const std::string& name);

@ -141,7 +141,7 @@ namespace Resource
return mWarningImage;
}
void ImageManager::reportStats(unsigned int frameNumber, osg::Stats *stats)
void ImageManager::reportStats(unsigned int frameNumber, osg::Stats *stats) const
{
stats->setAttribute(frameNumber, "Image", mCache->getCacheSize());
}

@ -32,7 +32,7 @@ namespace Resource
osg::Image* getWarningImage();
void reportStats(unsigned int frameNumber, osg::Stats* stats);
void reportStats(unsigned int frameNumber, osg::Stats* stats) const;
private:
osg::ref_ptr<osg::Image> mWarningImage;

@ -34,7 +34,7 @@ namespace Resource
}
}
void KeyframeManager::reportStats(unsigned int frameNumber, osg::Stats *stats)
void KeyframeManager::reportStats(unsigned int frameNumber, osg::Stats *stats) const
{
stats->setAttribute(frameNumber, "Keyframe", mCache->getCacheSize());
}

@ -23,7 +23,7 @@ namespace Resource
/// @note Throws an exception if the resource is not found.
osg::ref_ptr<const NifOsg::KeyframeHolder> get(const std::string& name);
void reportStats(unsigned int frameNumber, osg::Stats* stats);
void reportStats(unsigned int frameNumber, osg::Stats* stats) const;
};
}

@ -56,7 +56,7 @@ namespace Resource
}
}
void NifFileManager::reportStats(unsigned int frameNumber, osg::Stats *stats)
void NifFileManager::reportStats(unsigned int frameNumber, osg::Stats *stats) const
{
stats->setAttribute(frameNumber, "Nif", mCache->getCacheSize());
}

@ -23,7 +23,7 @@ namespace Resource
/// to be done in advance by other managers accessing the NifFileManager.
Nif::NIFFilePtr get(const std::string& name);
void reportStats(unsigned int frameNumber, osg::Stats *stats);
void reportStats(unsigned int frameNumber, osg::Stats *stats) const;
};
}

@ -73,6 +73,15 @@ class ObjectCache : public osg::Referenced
/** call node->accept(nv); for all nodes in the objectCache. */
void accept(osg::NodeVisitor& nv);
/** call operator()(osg::Object*) for each object in the cache. */
template <class Functor>
void call(Functor& f)
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_objectCacheMutex);
for (ObjectCacheMap::iterator it = _objectCache.begin(); it != _objectCache.end(); ++it)
f(it->second.first.get());
}
/** Get the number of objects in the cache. */
unsigned int getCacheSize() const;

@ -33,7 +33,7 @@ namespace Resource
const VFS::Manager* getVFS() const;
virtual void reportStats(unsigned int frameNumber, osg::Stats* stats) {}
virtual void reportStats(unsigned int frameNumber, osg::Stats* stats) const {}
protected:
const VFS::Manager* mVFS;

@ -85,9 +85,9 @@ namespace Resource
return mVFS;
}
void ResourceSystem::reportStats(unsigned int frameNumber, osg::Stats *stats)
void ResourceSystem::reportStats(unsigned int frameNumber, osg::Stats *stats) const
{
for (std::vector<ResourceManager*>::iterator it = mResourceManagers.begin(); it != mResourceManagers.end(); ++it)
for (std::vector<ResourceManager*>::const_iterator it = mResourceManagers.begin(); it != mResourceManagers.end(); ++it)
(*it)->reportStats(frameNumber, stats);
}

@ -54,7 +54,7 @@ namespace Resource
/// @note May be called from any thread.
const VFS::Manager* getVFS() const;
void reportStats(unsigned int frameNumber, osg::Stats* stats);
void reportStats(unsigned int frameNumber, osg::Stats* stats) const;
private:
std::auto_ptr<SceneManager> mSceneManager;

@ -621,6 +621,11 @@ namespace Resource
mIncrementalCompileOperation = ico;
}
osgUtil::IncrementalCompileOperation *SceneManager::getIncrementalCompileOperation()
{
return mIncrementalCompileOperation.get();
}
Resource::ImageManager* SceneManager::getImageManager()
{
return mImageManager;
@ -689,16 +694,16 @@ namespace Resource
void SceneManager::updateCache(double referenceTime)
{
mSharedStateMutex.lock();
mSharedStateManager->prune();
mSharedStateMutex.unlock();
ResourceManager::updateCache(referenceTime);
mInstanceCache->removeUnreferencedObjectsInCache();
mSharedStateMutex.lock();
mSharedStateManager->prune();
mSharedStateMutex.unlock();
}
void SceneManager::reportStats(unsigned int frameNumber, osg::Stats *stats)
void SceneManager::reportStats(unsigned int frameNumber, osg::Stats *stats) const
{
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(*mIncrementalCompileOperation->getToCompiledMutex());

@ -121,6 +121,8 @@ namespace Resource
/// Set up an IncrementalCompileOperation for background compiling of loaded scenes.
void setIncrementalCompileOperation(osgUtil::IncrementalCompileOperation* ico);
osgUtil::IncrementalCompileOperation* getIncrementalCompileOperation();
Resource::ImageManager* getImageManager();
/// @param mask The node mask to apply to loaded particle system nodes.
@ -141,7 +143,7 @@ namespace Resource
/// @see ResourceManager::updateCache
virtual void updateCache(double referenceTime);
virtual void reportStats(unsigned int frameNumber, osg::Stats* stats);
virtual void reportStats(unsigned int frameNumber, osg::Stats* stats) const;
private:
@ -158,7 +160,7 @@ namespace Resource
osg::ref_ptr<MultiObjectCache> mInstanceCache;
osg::ref_ptr<Resource::SharedStateManager> mSharedStateManager;
OpenThreads::Mutex mSharedStateMutex;
mutable OpenThreads::Mutex mSharedStateMutex;
Resource::ImageManager* mImageManager;
Resource::NifFileManager* mNifFileManager;

@ -259,7 +259,7 @@ void StatsHandler::setUpScene(osgViewer::ViewerBase *viewer)
_resourceStatsChildNum = _switch->getNumChildren();
_switch->addChild(group, false);
const char* statNames[] = {"Compiling", "WorkQueue", "WorkThread", "", "Texture", "StateSet", "Node", "Node Instance", "Shape", "Shape Instance", "Image", "Nif", "Keyframe", "Terrain Cell", "Terrain Texture", "", "UnrefQueue"};
const char* statNames[] = {"Compiling", "WorkQueue", "WorkThread", "", "Texture", "StateSet", "Node", "Node Instance", "Shape", "Shape Instance", "Image", "Nif", "Keyframe", "", "Terrain Chunk", "Terrain Texture", "Land", "Composite", "", "UnrefQueue"};
int numLines = sizeof(statNames) / sizeof(statNames[0]);

@ -381,21 +381,23 @@ namespace SceneUtil
{
osgUtil::CullVisitor* cv = static_cast<osgUtil::CullVisitor*>(nv);
bool pushedState = pushLightState(node, cv);
traverse(node, nv);
if (pushedState)
cv->popStateSet();
}
bool LightListCallback::pushLightState(osg::Node *node, osgUtil::CullVisitor *cv)
{
if (!mLightManager)
{
mLightManager = findLightManager(nv->getNodePath());
mLightManager = findLightManager(cv->getNodePath());
if (!mLightManager)
{
traverse(node, nv);
return;
}
return false;
}
if (!(cv->getCurrentCamera()->getCullMask() & mLightManager->getLightingMask()))
{
traverse(node, nv);
return;
}
return false;
// Possible optimizations:
// - cull list of lights by the camera frustum
@ -404,9 +406,9 @@ namespace SceneUtil
// update light list if necessary
// makes sure we don't update it more than once per frame when rendering with multiple cameras
if (mLastFrameNumber != nv->getTraversalNumber())
if (mLastFrameNumber != cv->getTraversalNumber())
{
mLastFrameNumber = nv->getTraversalNumber();
mLastFrameNumber = cv->getTraversalNumber();
// Don't use Camera::getViewMatrix, that one might be relative to another camera!
const osg::RefMatrix* viewMatrix = cv->getCurrentRenderStage()->getInitialViewMatrix();
@ -415,12 +417,14 @@ namespace SceneUtil
// get the node bounds in view space
// NB do not node->getBound() * modelView, that would apply the node's transformation twice
osg::BoundingSphere nodeBound;
osg::Group* group = node->asGroup();
if (group)
osg::Transform* transform = node->asTransform();
if (transform)
{
for (unsigned int i=0; i<group->getNumChildren(); ++i)
nodeBound.expandBy(group->getChild(i)->getBound());
for (unsigned int i=0; i<transform->getNumChildren(); ++i)
nodeBound.expandBy(transform->getChild(i)->getBound());
}
else
nodeBound = node->getBound();
osg::Matrixf mat = *cv->getModelViewMatrix();
transformBoundingSphere(mat, nodeBound);
@ -469,20 +473,16 @@ namespace SceneUtil
while (lightList.size() > maxLights)
lightList.pop_back();
}
stateset = mLightManager->getLightListStateSet(lightList, nv->getTraversalNumber());
stateset = mLightManager->getLightListStateSet(lightList, cv->getTraversalNumber());
}
else
stateset = mLightManager->getLightListStateSet(mLightList, nv->getTraversalNumber());
stateset = mLightManager->getLightListStateSet(mLightList, cv->getTraversalNumber());
cv->pushStateSet(stateset);
traverse(node, nv);
cv->popStateSet();
return true;
}
else
traverse(node, nv);
return false;
}
}

@ -9,6 +9,11 @@
#include <osg/NodeVisitor>
#include <osg/observer_ptr>
namespace osgUtil
{
class CullVisitor;
}
namespace SceneUtil
{
@ -148,6 +153,7 @@ namespace SceneUtil
/// rendering when the size of a light list exceeds the OpenGL limit on the number of concurrent lights (8). A good
/// starting point is to attach a LightListCallback to each game object's base node.
/// @note Not thread safe for CullThreadPerCamera threading mode.
/// @note Due to lack of OSG support, the callback does not work on Drawables.
class LightListCallback : public osg::NodeCallback
{
public:
@ -166,6 +172,8 @@ namespace SceneUtil
void operator()(osg::Node* node, osg::NodeVisitor* nv);
bool pushLightState(osg::Node* node, osgUtil::CullVisitor* nv);
std::set<SceneUtil::LightSource*>& getIgnoredLightSources() { return mIgnoredLightSources; }
private:

@ -329,8 +329,8 @@ InputWrapper::InputWrapper(SDL_Window* window, osg::ref_ptr<osgViewer::Viewer> v
SDL_GetWindowSize(mSDLWindow, &width, &height);
const int FUDGE_FACTOR_X = width;
const int FUDGE_FACTOR_Y = height;
const int FUDGE_FACTOR_X = width/4;
const int FUDGE_FACTOR_Y = height/4;
//warp the mouse if it's about to go outside the window
if(evt.x - FUDGE_FACTOR_X < 0 || evt.x + FUDGE_FACTOR_X > width

@ -178,56 +178,56 @@ osg::ref_ptr<IndexArrayType> createIndexBuffer(unsigned int flags, unsigned int
namespace Terrain
{
osg::ref_ptr<osg::Vec2Array> BufferCache::getUVBuffer()
osg::ref_ptr<osg::Vec2Array> BufferCache::getUVBuffer(unsigned int numVerts)
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(mUvBufferMutex);
if (mUvBufferMap.find(mNumVerts) != mUvBufferMap.end())
if (mUvBufferMap.find(numVerts) != mUvBufferMap.end())
{
return mUvBufferMap[mNumVerts];
return mUvBufferMap[numVerts];
}
int vertexCount = mNumVerts * mNumVerts;
int vertexCount = numVerts * numVerts;
osg::ref_ptr<osg::Vec2Array> uvs (new osg::Vec2Array);
uvs->reserve(vertexCount);
for (unsigned int col = 0; col < mNumVerts; ++col)
for (unsigned int col = 0; col < numVerts; ++col)
{
for (unsigned int row = 0; row < mNumVerts; ++row)
for (unsigned int row = 0; row < numVerts; ++row)
{
uvs->push_back(osg::Vec2f(col / static_cast<float>(mNumVerts-1),
((mNumVerts-1) - row) / static_cast<float>(mNumVerts-1)));
uvs->push_back(osg::Vec2f(col / static_cast<float>(numVerts-1),
((numVerts-1) - row) / static_cast<float>(numVerts-1)));
}
}
// Assign a VBO here to enable state sharing between different Geometries.
uvs->setVertexBufferObject(new osg::VertexBufferObject);
mUvBufferMap[mNumVerts] = uvs;
mUvBufferMap[numVerts] = uvs;
return uvs;
}
osg::ref_ptr<osg::DrawElements> BufferCache::getIndexBuffer(unsigned int flags)
osg::ref_ptr<osg::DrawElements> BufferCache::getIndexBuffer(unsigned int numVerts, unsigned int flags)
{
std::pair<int, int> id = std::make_pair(numVerts, flags);
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(mIndexBufferMutex);
unsigned int verts = mNumVerts;
if (mIndexBufferMap.find(flags) != mIndexBufferMap.end())
if (mIndexBufferMap.find(id) != mIndexBufferMap.end())
{
return mIndexBufferMap[flags];
return mIndexBufferMap[id];
}
osg::ref_ptr<osg::DrawElements> buffer;
if (verts*verts <= (0xffffu))
buffer = createIndexBuffer<osg::DrawElementsUShort>(flags, verts);
if (numVerts*numVerts <= (0xffffu))
buffer = createIndexBuffer<osg::DrawElementsUShort>(flags, numVerts);
else
buffer = createIndexBuffer<osg::DrawElementsUInt>(flags, verts);
buffer = createIndexBuffer<osg::DrawElementsUInt>(flags, numVerts);
// Assign a EBO here to enable state sharing between different Geometries.
buffer->setElementBufferObject(new osg::ElementBufferObject);
mIndexBufferMap[flags] = buffer;
mIndexBufferMap[id] = buffer;
return buffer;
}

@ -14,28 +14,24 @@ namespace Terrain
class BufferCache
{
public:
BufferCache(unsigned int numVerts) : mNumVerts(numVerts) {}
/// @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)
/// @note Thread safe.
osg::ref_ptr<osg::DrawElements> getIndexBuffer (unsigned int flags);
osg::ref_ptr<osg::DrawElements> getIndexBuffer (unsigned int numVerts, unsigned int flags);
/// @note Thread safe.
osg::ref_ptr<osg::Vec2Array> getUVBuffer();
osg::ref_ptr<osg::Vec2Array> getUVBuffer(unsigned int numVerts);
// TODO: add releaseGLObjects() for our vertex/element buffer objects
private:
// Index buffers are shared across terrain batches where possible. There is one index buffer for each
// combination of LOD deltas and index buffer LOD we may need.
std::map<int, osg::ref_ptr<osg::DrawElements> > mIndexBufferMap;
std::map<std::pair<int, int>, osg::ref_ptr<osg::DrawElements> > mIndexBufferMap;
OpenThreads::Mutex mIndexBufferMutex;
std::map<int, osg::ref_ptr<osg::Vec2Array> > mUvBufferMap;
OpenThreads::Mutex mUvBufferMutex;
unsigned int mNumVerts;
};
}

@ -0,0 +1,235 @@
#include "chunkmanager.hpp"
#include <sstream>
#include <osg/Texture2D>
#include <osgUtil/IncrementalCompileOperation>
#include <components/resource/objectcache.hpp>
#include <components/resource/scenemanager.hpp>
#include <components/sceneutil/positionattitudetransform.hpp>
#include <components/sceneutil/lightmanager.hpp>
#include "terraindrawable.hpp"
#include "material.hpp"
#include "storage.hpp"
#include "texturemanager.hpp"
#include "compositemaprenderer.hpp"
namespace Terrain
{
ChunkManager::ChunkManager(Storage *storage, Resource::SceneManager *sceneMgr, TextureManager* textureManager, CompositeMapRenderer* renderer)
: ResourceManager(NULL)
, mStorage(storage)
, mSceneManager(sceneMgr)
, mTextureManager(textureManager)
, mCompositeMapRenderer(renderer)
, mCompositeMapSize(512)
, mCullingActive(true)
{
}
osg::ref_ptr<osg::Node> ChunkManager::getChunk(float size, const osg::Vec2f &center, int lod, unsigned int lodFlags)
{
std::ostringstream stream;
stream << size << " " << center.x() << " " << center.y() << " " << lod << " " << lodFlags;
std::string id = stream.str();
osg::ref_ptr<osg::Object> obj = mCache->getRefFromObjectCache(id);
if (obj)
return obj->asNode();
else
{
osg::ref_ptr<osg::Node> node = createChunk(size, center, lod, lodFlags);
mCache->addEntryToObjectCache(id, node.get());
return node;
}
}
void ChunkManager::reportStats(unsigned int frameNumber, osg::Stats *stats) const
{
stats->setAttribute(frameNumber, "Terrain Chunk", mCache->getCacheSize());
}
void ChunkManager::setCullingActive(bool active)
{
mCullingActive = active;
}
osg::ref_ptr<osg::Texture2D> ChunkManager::createCompositeMapRTT()
{
osg::ref_ptr<osg::Texture2D> texture = new osg::Texture2D;
texture->setTextureWidth(mCompositeMapSize);
texture->setTextureHeight(mCompositeMapSize);
texture->setInternalFormat(GL_RGB);
texture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR);
texture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR);
texture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE);
texture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE);
return texture;
}
void ChunkManager::createCompositeMapGeometry(float chunkSize, const osg::Vec2f& chunkCenter, const osg::Vec4f& texCoords, CompositeMap& compositeMap)
{
if (chunkSize > 1.f)
{
createCompositeMapGeometry(chunkSize/2.f, chunkCenter + osg::Vec2f(chunkSize/4.f, chunkSize/4.f), osg::Vec4f(texCoords.x() + texCoords.z()/2.f, texCoords.y(), texCoords.z()/2.f, texCoords.w()/2.f), compositeMap);
createCompositeMapGeometry(chunkSize/2.f, chunkCenter + osg::Vec2f(-chunkSize/4.f, chunkSize/4.f), osg::Vec4f(texCoords.x(), texCoords.y(), texCoords.z()/2.f, texCoords.w()/2.f), compositeMap);
createCompositeMapGeometry(chunkSize/2.f, chunkCenter + osg::Vec2f(chunkSize/4.f, -chunkSize/4.f), osg::Vec4f(texCoords.x() + texCoords.z()/2.f, texCoords.y()+texCoords.w()/2.f, texCoords.z()/2.f, texCoords.w()/2.f), compositeMap);
createCompositeMapGeometry(chunkSize/2.f, chunkCenter + osg::Vec2f(-chunkSize/4.f, -chunkSize/4.f), osg::Vec4f(texCoords.x(), texCoords.y()+texCoords.w()/2.f, texCoords.z()/2.f, texCoords.w()/2.f), compositeMap);
}
else
{
float left = texCoords.x()*2.f-1;
float top = texCoords.y()*2.f-1;
float width = texCoords.z()*2.f;
float height = texCoords.w()*2.f;
std::vector<osg::ref_ptr<osg::StateSet> > passes = createPasses(chunkSize, chunkCenter, true);
for (std::vector<osg::ref_ptr<osg::StateSet> >::iterator it = passes.begin(); it != passes.end(); ++it)
{
osg::ref_ptr<osg::Geometry> geom = osg::createTexturedQuadGeometry(osg::Vec3(left,top,0), osg::Vec3(width,0,0), osg::Vec3(0,height,0));
geom->setUseDisplayList(false); // don't bother making a display list for an object that is just rendered once.
geom->setUseVertexBufferObjects(false);
geom->setTexCoordArray(1, geom->getTexCoordArray(0), osg::Array::BIND_PER_VERTEX);
geom->setStateSet(*it);
compositeMap.mDrawables.push_back(geom);
}
}
}
std::vector<osg::ref_ptr<osg::StateSet> > ChunkManager::createPasses(float chunkSize, const osg::Vec2f &chunkCenter, bool forCompositeMap)
{
std::vector<LayerInfo> layerList;
std::vector<osg::ref_ptr<osg::Image> > blendmaps;
mStorage->getBlendmaps(chunkSize, chunkCenter, false, blendmaps, layerList);
bool useShaders = mSceneManager->getForceShaders();
if (!mSceneManager->getClampLighting())
useShaders = true; // always use shaders when lighting is unclamped, this is to avoid lighting seams between a terrain chunk with normal maps and one without normal maps
std::vector<TextureLayer> layers;
{
for (std::vector<LayerInfo>::const_iterator it = layerList.begin(); it != layerList.end(); ++it)
{
TextureLayer textureLayer;
textureLayer.mParallax = it->mParallax;
textureLayer.mSpecular = it->mSpecular;
textureLayer.mDiffuseMap = mTextureManager->getTexture(it->mDiffuseMap);
if (!forCompositeMap && !it->mNormalMap.empty())
textureLayer.mNormalMap = mTextureManager->getTexture(it->mNormalMap);
if (it->requiresShaders())
useShaders = true;
layers.push_back(textureLayer);
}
}
if (forCompositeMap)
useShaders = false;
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)
{
osg::ref_ptr<osg::Texture2D> texture (new osg::Texture2D);
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->setResizeNonPowerOfTwoHint(false);
blendmapTextures.push_back(texture);
}
float blendmapScale = mStorage->getBlendmapScale(chunkSize);
return ::Terrain::createPasses(useShaders, mSceneManager->getForcePerPixelLighting(),
mSceneManager->getClampLighting(), &mSceneManager->getShaderManager(), layers, blendmapTextures, blendmapScale, blendmapScale);
}
osg::ref_ptr<osg::Node> ChunkManager::createChunk(float chunkSize, const osg::Vec2f &chunkCenter, int lod, unsigned int lodFlags)
{
osg::Vec2f worldCenter = chunkCenter*mStorage->getCellWorldSize();
osg::ref_ptr<SceneUtil::PositionAttitudeTransform> transform (new SceneUtil::PositionAttitudeTransform);
transform->setPosition(osg::Vec3f(worldCenter.x(), worldCenter.y(), 0.f));
osg::ref_ptr<osg::Vec3Array> positions (new osg::Vec3Array);
osg::ref_ptr<osg::Vec3Array> normals (new osg::Vec3Array);
osg::ref_ptr<osg::Vec4Array> colors (new osg::Vec4Array);
osg::ref_ptr<osg::VertexBufferObject> vbo (new osg::VertexBufferObject);
positions->setVertexBufferObject(vbo);
normals->setVertexBufferObject(vbo);
colors->setVertexBufferObject(vbo);
mStorage->fillVertexBuffers(lod, chunkSize, chunkCenter, positions, normals, colors);
osg::ref_ptr<TerrainDrawable> geometry (new TerrainDrawable);
geometry->setVertexArray(positions);
geometry->setNormalArray(normals, osg::Array::BIND_PER_VERTEX);
geometry->setColorArray(colors, osg::Array::BIND_PER_VERTEX);
geometry->setUseDisplayList(false);
geometry->setUseVertexBufferObjects(true);
if (chunkSize <= 2.f)
geometry->setLightListCallback(new SceneUtil::LightListCallback);
unsigned int numVerts = (mStorage->getCellVertices()-1) * chunkSize / (1 << lod) + 1;
geometry->addPrimitiveSet(mBufferCache.getIndexBuffer(numVerts, lodFlags));
bool useCompositeMap = chunkSize >= 1.f;
unsigned int numUvSets = useCompositeMap ? 1 : 2;
for (unsigned int i=0; i<numUvSets; ++i)
geometry->setTexCoordArray(i, mBufferCache.getUVBuffer(numVerts));
if (useCompositeMap)
{
osg::ref_ptr<CompositeMap> compositeMap = new CompositeMap;
compositeMap->mTexture = createCompositeMapRTT();
createCompositeMapGeometry(chunkSize, chunkCenter, osg::Vec4f(0,0,1,1), *compositeMap);
mCompositeMapRenderer->addCompositeMap(compositeMap.get(), false);
transform->getOrCreateUserDataContainer()->setUserData(compositeMap);
TextureLayer layer;
layer.mDiffuseMap = compositeMap->mTexture;
layer.mParallax = false;
layer.mSpecular = false;
geometry->setPasses(::Terrain::createPasses(mSceneManager->getForceShaders() || !mSceneManager->getClampLighting(), mSceneManager->getForcePerPixelLighting(),
mSceneManager->getClampLighting(), &mSceneManager->getShaderManager(), std::vector<TextureLayer>(1, layer), std::vector<osg::ref_ptr<osg::Texture2D> >(), 1.f, 1.f));
}
else
{
geometry->setPasses(createPasses(chunkSize, chunkCenter, false));
}
transform->addChild(geometry);
if (!mCullingActive)
{
transform->setCullingActive(false);
geometry->setCullingActive(false);
}
else
transform->getBound();
if (mSceneManager->getIncrementalCompileOperation())
{
mSceneManager->getIncrementalCompileOperation()->add(geometry);
}
return transform;
}
}

@ -0,0 +1,61 @@
#ifndef OPENMW_COMPONENTS_TERRAIN_CHUNKMANAGER_H
#define OPENMW_COMPONENTS_TERRAIN_CHUNKMANAGER_H
#include <components/resource/resourcemanager.hpp>
#include "buffercache.hpp"
namespace osg
{
class Group;
class Texture2D;
}
namespace Resource
{
class SceneManager;
}
namespace Terrain
{
class TextureManager;
class CompositeMapRenderer;
class Storage;
class CompositeMap;
/// @brief Handles loading and caching of terrain chunks
class ChunkManager : public Resource::ResourceManager
{
public:
ChunkManager(Storage* storage, Resource::SceneManager* sceneMgr, TextureManager* textureManager, CompositeMapRenderer* renderer);
osg::ref_ptr<osg::Node> getChunk(float size, const osg::Vec2f& center, int lod, unsigned int lodFlags);
virtual void reportStats(unsigned int frameNumber, osg::Stats* stats) const;
void setCullingActive(bool active);
private:
osg::ref_ptr<osg::Node> createChunk(float size, const osg::Vec2f& center, int lod, unsigned int lodFlags);
osg::ref_ptr<osg::Texture2D> createCompositeMapRTT();
void createCompositeMapGeometry(float chunkSize, const osg::Vec2f& chunkCenter, const osg::Vec4f& texCoords, CompositeMap& map);
std::vector<osg::ref_ptr<osg::StateSet> > createPasses(float chunkSize, const osg::Vec2f& chunkCenter, bool forCompositeMap);
Terrain::Storage* mStorage;
Resource::SceneManager* mSceneManager;
TextureManager* mTextureManager;
CompositeMapRenderer* mCompositeMapRenderer;
BufferCache mBufferCache;
unsigned int mCompositeMapSize;
bool mCullingActive;
};
}
#endif

@ -0,0 +1,170 @@
#include "compositemaprenderer.hpp"
#include <OpenThreads/ScopedLock>
#include <osg/FrameBufferObject>
#include <osg/Texture2D>
#include <osg/RenderInfo>
namespace Terrain
{
CompositeMapRenderer::CompositeMapRenderer()
: mTimeAvailable(0.0005)
{
setSupportsDisplayList(false);
setCullingActive(false);
mFBO = new osg::FrameBufferObject;
getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
}
void CompositeMapRenderer::drawImplementation(osg::RenderInfo &renderInfo) const
{
mCompiled.clear();
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(mMutex);
if (mImmediateCompileSet.empty() && mCompileSet.empty())
return;
while (!mImmediateCompileSet.empty())
{
CompositeMap* node = *mImmediateCompileSet.begin();
mCompiled.insert(node);
compile(*node, renderInfo, NULL);
mImmediateCompileSet.erase(mImmediateCompileSet.begin());
}
double timeLeft = mTimeAvailable;
while (!mCompileSet.empty() && timeLeft > 0)
{
CompositeMap* node = *mCompileSet.begin();
compile(*node, renderInfo, &timeLeft);
if (node->mCompiled >= node->mDrawables.size())
{
mCompiled.insert(node);
mCompileSet.erase(mCompileSet.begin());
}
}
}
void CompositeMapRenderer::compile(CompositeMap &compositeMap, osg::RenderInfo &renderInfo, double* timeLeft) const
{
// if there are no more external references we can assume the texture is no longer required
if (compositeMap.mTexture->referenceCount() <= 1)
{
compositeMap.mCompiled = compositeMap.mDrawables.size();
return;
}
osg::Timer timer;
osg::State& state = *renderInfo.getState();
osg::GLExtensions* ext = state.get<osg::GLExtensions>();
if (!mFBO)
return;
if (!ext->isFrameBufferObjectSupported)
return;
osg::FrameBufferAttachment attach (compositeMap.mTexture);
mFBO->setAttachment(osg::Camera::COLOR_BUFFER, attach);
mFBO->apply(state, osg::FrameBufferObject::DRAW_FRAMEBUFFER);
GLenum status = ext->glCheckFramebufferStatus(GL_FRAMEBUFFER_EXT);
if (status != GL_FRAMEBUFFER_COMPLETE_EXT)
{
GLuint fboId = state.getGraphicsContext() ? state.getGraphicsContext()->getDefaultFboId() : 0;
ext->glBindFramebuffer(GL_FRAMEBUFFER_EXT, fboId);
OSG_ALWAYS << "Error attaching FBO" << std::endl;
return;
}
for (unsigned int i=compositeMap.mCompiled; i<compositeMap.mDrawables.size(); ++i)
{
osg::Drawable* drw = compositeMap.mDrawables[i];
osg::StateSet* stateset = drw->getStateSet();
if (stateset)
renderInfo.getState()->pushStateSet(stateset);
renderInfo.getState()->apply();
glViewport(0,0,compositeMap.mTexture->getTextureWidth(), compositeMap.mTexture->getTextureHeight());
drw->drawImplementation(renderInfo);
if (stateset)
renderInfo.getState()->popStateSet();
++compositeMap.mCompiled;
if (timeLeft)
{
*timeLeft -= timer.time_s();
timer.setStartTick();
if (*timeLeft <= 0)
break;
}
}
state.haveAppliedAttribute(osg::StateAttribute::VIEWPORT);
GLuint fboId = state.getGraphicsContext() ? state.getGraphicsContext()->getDefaultFboId() : 0;
ext->glBindFramebuffer(GL_FRAMEBUFFER_EXT, fboId);
}
void CompositeMapRenderer::setTimeAvailableForCompile(double time)
{
mTimeAvailable = time;
}
void CompositeMapRenderer::addCompositeMap(CompositeMap* compositeMap, bool immediate)
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(mMutex);
if (immediate)
mImmediateCompileSet.insert(compositeMap);
else
mCompileSet.insert(compositeMap);
}
void CompositeMapRenderer::setImmediate(CompositeMap* compositeMap)
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(mMutex);
CompileSet::iterator found = mCompileSet.find(compositeMap);
if (found == mCompileSet.end())
return;
else
{
mImmediateCompileSet.insert(compositeMap);
mCompileSet.erase(found);
}
}
unsigned int CompositeMapRenderer::getCompileSetSize() const
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(mMutex);
return mCompileSet.size();
}
CompositeMap::CompositeMap()
: mCompiled(0)
{
}
CompositeMap::~CompositeMap()
{
}
}

@ -0,0 +1,70 @@
#ifndef OPENMW_COMPONENTS_TERRAIN_COMPOSITEMAPRENDERER_H
#define OPENMW_COMPONENTS_TERRAIN_COMPOSITEMAPRENDERER_H
#include <osg/Drawable>
#include <OpenThreads/Mutex>
#include <set>
namespace osg
{
class FrameBufferObject;
class RenderInfo;
class Texture2D;
}
namespace Terrain
{
class CompositeMap : public osg::Referenced
{
public:
CompositeMap();
~CompositeMap();
std::vector<osg::ref_ptr<osg::Drawable> > mDrawables;
osg::ref_ptr<osg::Texture2D> mTexture;
unsigned int mCompiled;
};
/**
* @brief The CompositeMapRenderer is responsible for updating composite map textures in a blocking or non-blocking way.
*/
class CompositeMapRenderer : public osg::Drawable
{
public:
CompositeMapRenderer();
virtual void drawImplementation(osg::RenderInfo& renderInfo) const;
void compile(CompositeMap& compositeMap, osg::RenderInfo& renderInfo, double* timeLeft) const;
/// Set the available time in seconds for compiling (non-immediate) composite maps each frame
void setTimeAvailableForCompile(double time);
/// Add a composite map to be rendered
void addCompositeMap(CompositeMap* map, bool immediate=false);
/// Mark this composite map to be required for the current frame
void setImmediate(CompositeMap* map);
unsigned int getCompileSetSize() const;
private:
double mTimeAvailable;
typedef std::set<osg::ref_ptr<CompositeMap> > CompileSet;
mutable CompileSet mCompileSet;
mutable CompileSet mImmediateCompileSet;
mutable CompileSet mCompiled;
mutable OpenThreads::Mutex mMutex;
osg::ref_ptr<osg::FrameBufferObject> mFBO;
};
}
#endif

@ -1,6 +1,5 @@
#include "material.hpp"
#include <iostream>
#include <stdexcept>
#include <osg/Depth>
@ -58,11 +57,14 @@ namespace Terrain
return depth;
}
FixedFunctionTechnique::FixedFunctionTechnique(const std::vector<TextureLayer>& layers,
const std::vector<osg::ref_ptr<osg::Texture2D> >& blendmaps, int blendmapScale, float layerTileSize)
std::vector<osg::ref_ptr<osg::StateSet> > createPasses(bool useShaders, bool forcePerPixelLighting, bool clampLighting, Shader::ShaderManager* shaderManager, const std::vector<TextureLayer> &layers,
const std::vector<osg::ref_ptr<osg::Texture2D> > &blendmaps, int blendmapScale, float layerTileSize)
{
std::vector<osg::ref_ptr<osg::StateSet> > passes;
bool firstLayer = true;
int i=0;
unsigned int blendmapIndex = 0;
unsigned int passIndex = 0;
for (std::vector<TextureLayer>::const_iterator it = layers.begin(); it != layers.end(); ++it)
{
osg::ref_ptr<osg::StateSet> stateset (new osg::StateSet);
@ -70,138 +72,93 @@ namespace Terrain
if (!firstLayer)
{
stateset->setMode(GL_BLEND, osg::StateAttribute::ON);
stateset->setAttributeAndModes(getEqualDepth(), osg::StateAttribute::ON);
}
int texunit = 0;
if(!firstLayer)
if (useShaders)
{
osg::ref_ptr<osg::Texture2D> blendmap = blendmaps.at(i++);
stateset->setTextureAttributeAndModes(texunit, it->mDiffuseMap);
stateset->setTextureAttributeAndModes(texunit, blendmap.get());
if (layerTileSize != 1.f)
stateset->setTextureAttributeAndModes(texunit, getLayerTexMat(layerTileSize), osg::StateAttribute::ON);
// This is to map corner vertices directly to the center of a blendmap texel.
stateset->setTextureAttributeAndModes(texunit, getBlendmapTexMat(blendmapScale));
stateset->addUniform(new osg::Uniform("diffuseMap", texunit));
static osg::ref_ptr<osg::TexEnvCombine> texEnvCombine;
if (!texEnvCombine)
if(!firstLayer)
{
texEnvCombine = new osg::TexEnvCombine;
texEnvCombine->setCombine_RGB(osg::TexEnvCombine::REPLACE);
texEnvCombine->setSource0_RGB(osg::TexEnvCombine::PREVIOUS);
}
stateset->setTextureAttributeAndModes(texunit, texEnvCombine, osg::StateAttribute::ON);
++texunit;
}
// Add the actual layer texture multiplied by the alpha map.
osg::ref_ptr<osg::Texture2D> tex = it->mDiffuseMap;
stateset->setTextureAttributeAndModes(texunit, tex.get());
stateset->setTextureAttributeAndModes(texunit, getLayerTexMat(layerTileSize), osg::StateAttribute::ON);
++texunit;
osg::ref_ptr<osg::Texture2D> blendmap = blendmaps.at(blendmapIndex++);
firstLayer = false;
stateset->setTextureAttributeAndModes(texunit, blendmap.get());
addPass(stateset);
}
}
stateset->setTextureAttributeAndModes(texunit, getBlendmapTexMat(blendmapScale));
stateset->addUniform(new osg::Uniform("blendMap", texunit));
}
ShaderTechnique::ShaderTechnique(Shader::ShaderManager& shaderManager, bool forcePerPixelLighting, bool clampLighting, const std::vector<TextureLayer>& layers,
const std::vector<osg::ref_ptr<osg::Texture2D> >& blendmaps, int blendmapScale, float layerTileSize)
{
bool firstLayer = true;
int i=0;
for (std::vector<TextureLayer>::const_iterator it = layers.begin(); it != layers.end(); ++it)
{
osg::ref_ptr<osg::StateSet> stateset (new osg::StateSet);
if (it->mNormalMap)
{
++texunit;
stateset->setTextureAttributeAndModes(texunit, it->mNormalMap);
stateset->addUniform(new osg::Uniform("normalMap", texunit));
}
if (!firstLayer)
{
stateset->setMode(GL_BLEND, osg::StateAttribute::ON);
stateset->setAttributeAndModes(getEqualDepth(), osg::StateAttribute::ON);
Shader::ShaderManager::DefineMap defineMap;
defineMap["forcePPL"] = forcePerPixelLighting ? "1" : "0";
defineMap["clamp"] = clampLighting ? "1" : "0";
defineMap["normalMap"] = (it->mNormalMap) ? "1" : "0";
defineMap["blendMap"] = !firstLayer ? "1" : "0";
defineMap["colorMode"] = "2";
defineMap["specularMap"] = it->mSpecular ? "1" : "0";
defineMap["parallax"] = (it->mNormalMap && it->mParallax) ? "1" : "0";
osg::ref_ptr<osg::Shader> vertexShader = shaderManager->getShader("terrain_vertex.glsl", defineMap, osg::Shader::VERTEX);
osg::ref_ptr<osg::Shader> fragmentShader = shaderManager->getShader("terrain_fragment.glsl", defineMap, osg::Shader::FRAGMENT);
if (!vertexShader || !fragmentShader)
throw std::runtime_error("Unable to create shader");
stateset->setAttributeAndModes(shaderManager->getProgram(vertexShader, fragmentShader));
}
else
{
if(!firstLayer)
{
osg::ref_ptr<osg::Texture2D> blendmap = blendmaps.at(blendmapIndex++);
int texunit = 0;
stateset->setTextureAttributeAndModes(texunit, it->mDiffuseMap);
stateset->setTextureAttributeAndModes(texunit, blendmap.get());
stateset->setTextureAttributeAndModes(texunit, getLayerTexMat(layerTileSize), osg::StateAttribute::ON);
// This is to map corner vertices directly to the center of a blendmap texel.
stateset->setTextureAttributeAndModes(texunit, getBlendmapTexMat(blendmapScale));
stateset->addUniform(new osg::Uniform("diffuseMap", texunit));
static osg::ref_ptr<osg::TexEnvCombine> texEnvCombine;
if (!texEnvCombine)
{
texEnvCombine = new osg::TexEnvCombine;
texEnvCombine->setCombine_RGB(osg::TexEnvCombine::REPLACE);
texEnvCombine->setSource0_RGB(osg::TexEnvCombine::PREVIOUS);
}
if(!firstLayer)
{
++texunit;
osg::ref_ptr<osg::Texture2D> blendmap = blendmaps.at(i++);
stateset->setTextureAttributeAndModes(texunit, texEnvCombine, osg::StateAttribute::ON);
stateset->setTextureAttributeAndModes(texunit, blendmap.get());
++texunit;
}
stateset->setTextureAttributeAndModes(texunit, getBlendmapTexMat(blendmapScale));
stateset->addUniform(new osg::Uniform("blendMap", texunit));
}
// Add the actual layer texture multiplied by the alpha map.
osg::ref_ptr<osg::Texture2D> tex = it->mDiffuseMap;
stateset->setTextureAttributeAndModes(texunit, tex.get());
if (it->mNormalMap)
{
++texunit;
stateset->setTextureAttributeAndModes(texunit, it->mNormalMap);
stateset->addUniform(new osg::Uniform("normalMap", texunit));
if (layerTileSize != 1.f)
stateset->setTextureAttributeAndModes(texunit, getLayerTexMat(layerTileSize), osg::StateAttribute::ON);
}
Shader::ShaderManager::DefineMap defineMap;
defineMap["forcePPL"] = forcePerPixelLighting ? "1" : "0";
defineMap["clamp"] = clampLighting ? "1" : "0";
defineMap["normalMap"] = (it->mNormalMap) ? "1" : "0";
defineMap["blendMap"] = !firstLayer ? "1" : "0";
defineMap["colorMode"] = "2";
defineMap["specularMap"] = it->mSpecular ? "1" : "0";
defineMap["parallax"] = (it->mNormalMap && it->mParallax) ? "1" : "0";
osg::ref_ptr<osg::Shader> vertexShader = shaderManager.getShader("terrain_vertex.glsl", defineMap, osg::Shader::VERTEX);
osg::ref_ptr<osg::Shader> fragmentShader = shaderManager.getShader("terrain_fragment.glsl", defineMap, osg::Shader::FRAGMENT);
if (!vertexShader || !fragmentShader)
throw std::runtime_error("Unable to create shader");
stateset->setAttributeAndModes(shaderManager.getProgram(vertexShader, fragmentShader));
firstLayer = false;
addPass(stateset);
}
}
Effect::Effect(bool useShaders, bool forcePerPixelLighting, bool clampLighting, Shader::ShaderManager* shaderManager, const std::vector<TextureLayer> &layers, const std::vector<osg::ref_ptr<osg::Texture2D> > &blendmaps,
int blendmapScale, float layerTileSize)
: mShaderManager(shaderManager)
, mUseShaders(useShaders)
, mForcePerPixelLighting(forcePerPixelLighting)
, mClampLighting(clampLighting)
, mLayers(layers)
, mBlendmaps(blendmaps)
, mBlendmapScale(blendmapScale)
, mLayerTileSize(layerTileSize)
{
selectTechnique(0);
}
stateset->setRenderBinDetails(passIndex++, "RenderBin");
bool Effect::define_techniques()
{
try
{
if (mUseShaders && mShaderManager)
addTechnique(new ShaderTechnique(*mShaderManager, mForcePerPixelLighting, mClampLighting, mLayers, mBlendmaps, mBlendmapScale, mLayerTileSize));
else
addTechnique(new FixedFunctionTechnique(mLayers, mBlendmaps, mBlendmapScale, mLayerTileSize));
passes.push_back(stateset);
}
catch (std::exception& e)
{
std::cerr << "Error: " << e.what() << std::endl;
addTechnique(new FixedFunctionTechnique(mLayers, mBlendmaps, mBlendmapScale, mLayerTileSize));
}
return true;
return passes;
}
}

@ -1,8 +1,7 @@
#ifndef COMPONENTS_TERRAIN_MATERIAL_H
#define COMPONENTS_TERRAIN_MATERIAL_H
#include <osgFX/Technique>
#include <osgFX/Effect>
#include <osg/StateSet>
#include "defs.hpp"
@ -27,60 +26,9 @@ namespace Terrain
bool mSpecular;
};
class FixedFunctionTechnique : public osgFX::Technique
{
public:
FixedFunctionTechnique(
const std::vector<TextureLayer>& layers,
const std::vector<osg::ref_ptr<osg::Texture2D> >& blendmaps, int blendmapScale, float layerTileSize);
protected:
virtual void define_passes() {}
};
class ShaderTechnique : public osgFX::Technique
{
public:
ShaderTechnique(Shader::ShaderManager& shaderManager, bool forcePerPixelLighting, bool clampLighting,
const std::vector<TextureLayer>& layers,
const std::vector<osg::ref_ptr<osg::Texture2D> >& blendmaps, int blendmapScale, float layerTileSize);
protected:
virtual void define_passes() {}
};
class Effect : public osgFX::Effect
{
public:
Effect(bool useShaders, bool forcePerPixelLighting, bool clampLighting, Shader::ShaderManager* shaderManager,
const std::vector<TextureLayer>& layers,
const std::vector<osg::ref_ptr<osg::Texture2D> >& blendmaps, int blendmapScale, float layerTileSize);
virtual bool define_techniques();
virtual const char *effectName() const
{
return NULL;
}
virtual const char *effectDescription() const
{
return NULL;
}
virtual const char *effectAuthor() const
{
return NULL;
}
private:
Shader::ShaderManager* mShaderManager;
bool mUseShaders;
bool mForcePerPixelLighting;
bool mClampLighting;
std::vector<TextureLayer> mLayers;
std::vector<osg::ref_ptr<osg::Texture2D> > mBlendmaps;
int mBlendmapScale;
float mLayerTileSize;
};
std::vector<osg::ref_ptr<osg::StateSet> > createPasses(bool useShaders, bool forcePerPixelLighting, bool clampLighting, Shader::ShaderManager* shaderManager,
const std::vector<TextureLayer>& layers,
const std::vector<osg::ref_ptr<osg::Texture2D> >& blendmaps, int blendmapScale, float layerTileSize);
}

@ -0,0 +1,169 @@
#include "quadtreenode.hpp"
#include <cassert>
#include <osgUtil/CullVisitor>
#include "defs.hpp"
#include "viewdata.hpp"
namespace Terrain
{
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];
}
QuadTreeNode* searchNeighbour (QuadTreeNode* currentNode, Direction dir)
{
if (currentNode->getDirection() == Root)
return NULL; // Arrived at root node, the root node does not have neighbours
QuadTreeNode* nextNode;
if (adjacent(currentNode->getDirection(), dir))
nextNode = searchNeighbour(currentNode->getParent(), dir);
else
nextNode = currentNode->getParent();
if (nextNode && nextNode->getNumChildren())
return nextNode->getChild(reflect(currentNode->getDirection(), dir));
else
return NULL;
}
QuadTreeNode::QuadTreeNode(QuadTreeNode* parent, ChildDirection direction, float size, const osg::Vec2f& center)
: mParent(parent)
, mDirection(direction)
, mValidBounds(false)
, mSize(size)
, mCenter(center)
{
for (unsigned int i=0; i<4; ++i)
mNeighbours[i] = 0;
}
QuadTreeNode::~QuadTreeNode()
{
}
QuadTreeNode* QuadTreeNode::getParent()
{
return mParent;
}
QuadTreeNode *QuadTreeNode::getChild(unsigned int i)
{
return static_cast<QuadTreeNode*>(Group::getChild(i));
}
QuadTreeNode *QuadTreeNode::getNeighbour(Direction dir)
{
return mNeighbours[dir];
}
void QuadTreeNode::initNeighbours()
{
for (int i=0; i<4; ++i)
mNeighbours[i] = searchNeighbour(this, (Direction)i);
for (unsigned int i=0; i<getNumChildren(); ++i)
getChild(i)->initNeighbours();
}
void QuadTreeNode::traverse(osg::NodeVisitor &nv)
{
if (!hasValidBounds())
return;
if ((mLodCallback && mLodCallback->isSufficientDetail(this, nv.getEyePoint())) || !getNumChildren())
getView(nv)->add(this, true);
else
osg::Group::traverse(nv);
}
void QuadTreeNode::setLodCallback(LodCallback *lodCallback)
{
mLodCallback = lodCallback;
}
LodCallback *QuadTreeNode::getLodCallback()
{
return mLodCallback;
}
void QuadTreeNode::setViewDataMap(ViewDataMap *map)
{
mViewDataMap = map;
}
ViewDataMap *QuadTreeNode::getViewDataMap()
{
return mViewDataMap;
}
ViewData* QuadTreeNode::getView(osg::NodeVisitor &nv)
{
if (nv.getVisitorType() == osg::NodeVisitor::CULL_VISITOR)
{
osgUtil::CullVisitor* cv = static_cast<osgUtil::CullVisitor*>(&nv);
return mViewDataMap->getViewData(cv->getCurrentCamera(), true);
}
else // INTERSECTION_VISITOR
{
return mViewDataMap->getViewData(&nv, (nv.referenceCount() > 0)); // if no referenceCount, the visitor was allocated on the stack
}
}
void QuadTreeNode::setBoundingBox(const osg::BoundingBox &boundingBox)
{
mBoundingBox = boundingBox;
mValidBounds = boundingBox.valid();
dirtyBound();
getBound();
}
const osg::BoundingBox &QuadTreeNode::getBoundingBox() const
{
return mBoundingBox;
}
osg::BoundingSphere QuadTreeNode::computeBound() const
{
return osg::BoundingSphere(mBoundingBox);
}
float QuadTreeNode::getSize() const
{
return mSize;
}
const osg::Vec2f &QuadTreeNode::getCenter() const
{
return mCenter;
}
}

@ -0,0 +1,98 @@
#ifndef OPENMW_COMPONENTS_TERRAIN_QUADTREENODE_H
#define OPENMW_COMPONENTS_TERRAIN_QUADTREENODE_H
#include <osg/Group>
#include "defs.hpp"
namespace Terrain
{
enum ChildDirection
{
NW = 0,
NE = 1,
SW = 2,
SE = 3,
Root
};
class QuadTreeNode;
class LodCallback : public osg::Referenced
{
public:
virtual ~LodCallback() {}
virtual bool isSufficientDetail(QuadTreeNode *node, const osg::Vec3f& eyePoint) = 0;
};
class ViewDataMap;
class ViewData;
class QuadTreeNode : public osg::Group
{
public:
QuadTreeNode(QuadTreeNode* parent, ChildDirection dir, float size, const osg::Vec2f& center);
virtual ~QuadTreeNode();
QuadTreeNode* getParent();
QuadTreeNode* getChild(unsigned int i);
using osg::Group::getNumChildren;
/// Returns our direction relative to the parent node, or Root if we are the root node.
ChildDirection getDirection() { return mDirection; }
/// Get neighbour node in this direction
QuadTreeNode* getNeighbour (Direction dir);
/// Initialize neighbours - do this after the quadtree is built
void initNeighbours();
void setBoundingBox(const osg::BoundingBox& boundingBox);
const osg::BoundingBox& getBoundingBox() const;
bool hasValidBounds() const { return mValidBounds; }
virtual osg::BoundingSphere computeBound() const;
/// size in cell coordinates
float getSize() const;
/// center in cell coordinates
const osg::Vec2f& getCenter() const;
virtual void traverse(osg::NodeVisitor& nv);
/// Set the Lod callback to use for determining when to stop traversing further down the quad tree.
void setLodCallback(LodCallback* lodCallback);
LodCallback* getLodCallback();
/// Set the view data map that the finally used nodes for a given camera/intersection are pushed onto.
void setViewDataMap(ViewDataMap* map);
ViewDataMap* getViewDataMap();
/// Create or retrieve a view for the given traversal.
ViewData* getView(osg::NodeVisitor& nv);
private:
QuadTreeNode* mParent;
QuadTreeNode* mNeighbours[4];
ChildDirection mDirection;
osg::BoundingBox mBoundingBox;
bool mValidBounds;
float mSize;
osg::Vec2f mCenter;
osg::ref_ptr<LodCallback> mLodCallback;
ViewDataMap* mViewDataMap;
};
}
#endif

@ -0,0 +1,460 @@
#include "quadtreeworld.hpp"
#include <osgUtil/CullVisitor>
#include <sstream>
#include "quadtreenode.hpp"
#include "storage.hpp"
#include "viewdata.hpp"
#include "chunkmanager.hpp"
#include "compositemaprenderer.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;
}
int Log2( unsigned int n )
{
int targetlevel = 0;
while (n >>= 1) ++targetlevel;
return targetlevel;
}
float distance(const osg::BoundingBox& box, const osg::Vec3f& v)
{
if (box.contains(v))
return 0;
else
{
osg::Vec3f maxDist(0,0,0);
if (v.x() < box.xMin())
maxDist.x() = box.xMin() - v.x();
else if (v.x() > box.xMax())
maxDist.x() = v.x() - box.xMax();
if (v.y() < box.yMin())
maxDist.y() = box.yMin() - v.y();
else if (v.y() > box.yMax())
maxDist.y() = v.y() - box.yMax();
if (v.z() < box.zMin())
maxDist.z() = box.zMin() - v.z();
else if (v.z() > box.zMax())
maxDist.z() = v.z() - box.zMax();
return maxDist.length();
}
}
}
namespace Terrain
{
class DefaultLodCallback : public LodCallback
{
public:
DefaultLodCallback(float minSize)
: mMinSize(minSize)
{
}
virtual bool isSufficientDetail(QuadTreeNode* node, const osg::Vec3f& eyePoint)
{
float dist = distance(node->getBoundingBox(), eyePoint);
int nativeLodLevel = Log2(static_cast<unsigned int>(node->getSize()/mMinSize));
int lodLevel = Log2(static_cast<unsigned int>(dist/(8192*mMinSize)));
return nativeLodLevel <= lodLevel;
}
private:
float mMinSize;
};
class RootNode : public QuadTreeNode
{
public:
RootNode(float size, const osg::Vec2f& center)
: QuadTreeNode(NULL, Root, size, center)
, mWorld(NULL)
{
}
void setWorld(QuadTreeWorld* world)
{
mWorld = world;
}
virtual void accept(osg::NodeVisitor &nv)
{
if (!nv.validNodeMask(*this))
return;
nv.pushOntoNodePath(this);
mWorld->accept(nv);
nv.popFromNodePath();
}
private:
QuadTreeWorld* mWorld;
};
class QuadTreeBuilder
{
public:
QuadTreeBuilder(Terrain::Storage* storage, ViewDataMap* viewDataMap, float minSize)
: mStorage(storage)
, mMinX(0.f), mMaxX(0.f), mMinY(0.f), mMaxY(0.f)
, mMinSize(minSize)
, mViewDataMap(viewDataMap)
{
}
void build()
{
mStorage->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));
float centerX = (mMinX+mMaxX)/2.f + (size-origSizeX)/2.f;
float centerY = (mMinY+mMaxY)/2.f + (size-origSizeY)/2.f;
mRootNode = new RootNode(size, osg::Vec2f(centerX, centerY));
mRootNode->setViewDataMap(mViewDataMap);
mRootNode->setLodCallback(new DefaultLodCallback(mMinSize));
addChildren(mRootNode);
mRootNode->initNeighbours();
}
void addChildren(QuadTreeNode* parent)
{
float halfSize = parent->getSize()/2.f;
osg::BoundingBox boundingBox;
for (unsigned int i=0; i<4; ++i)
{
QuadTreeNode* child = addChild(parent, static_cast<ChildDirection>(i), halfSize);
if (child)
boundingBox.expandBy(child->getBoundingBox());
}
parent->setBoundingBox(boundingBox);
}
QuadTreeNode* addChild(QuadTreeNode* parent, ChildDirection direction, float size)
{
osg::Vec2f center;
switch (direction)
{
case SW:
center = parent->getCenter() + osg::Vec2f(-size/2.f,-size/2.f);
break;
case SE:
center = parent->getCenter() + osg::Vec2f(size/2.f, -size/2.f);
break;
case NW:
center = parent->getCenter() + osg::Vec2f(-size/2.f, size/2.f);
break;
case NE:
center = parent->getCenter() + osg::Vec2f(size/2.f, size/2.f);
break;
default:
break;
}
osg::ref_ptr<QuadTreeNode> node = new QuadTreeNode(parent, direction, size, center);
node->setLodCallback(parent->getLodCallback());
node->setViewDataMap(mViewDataMap);
parent->addChild(node);
if (center.x() - size > mMaxX
|| center.x() + size < mMinX
|| center.y() - size > mMaxY
|| center.y() + size < mMinY )
// Out of bounds of the actual terrain - this will happen because
// we rounded the size up to the next power of two
{
// Still create and return an empty node so as to not break the assumption that each QuadTreeNode has either 4 or 0 children.
return node;
}
if (node->getSize() <= mMinSize)
{
// We arrived at a leaf
float minZ,maxZ;
if (mStorage->getMinMaxHeights(size, center, minZ, maxZ))
{
float cellWorldSize = mStorage->getCellWorldSize();
osg::BoundingBox boundingBox(osg::Vec3f((center.x()-size)*cellWorldSize, (center.y()-size)*cellWorldSize, minZ),
osg::Vec3f((center.x()+size)*cellWorldSize, (center.y()+size)*cellWorldSize, maxZ));
node->setBoundingBox(boundingBox);
}
return node;
}
else
{
addChildren(node);
return node;
}
}
osg::ref_ptr<RootNode> getRootNode()
{
return mRootNode;
}
private:
Terrain::Storage* mStorage;
float mMinX, mMaxX, mMinY, mMaxY;
float mMinSize;
ViewDataMap* mViewDataMap;
osg::ref_ptr<RootNode> mRootNode;
};
QuadTreeWorld::QuadTreeWorld(osg::Group *parent, osg::Group *compileRoot, Resource::ResourceSystem *resourceSystem, Storage *storage, int nodeMask, int preCompileMask)
: World(parent, compileRoot, resourceSystem, storage, nodeMask, preCompileMask)
, mViewDataMap(new ViewDataMap)
, mQuadTreeBuilt(false)
{
// No need for culling on the Drawable / Transform level as the quad tree performs the culling already.
mChunkManager->setCullingActive(false);
}
QuadTreeWorld::~QuadTreeWorld()
{
ensureQuadTreeBuilt();
mViewDataMap->clear();
}
void traverse(QuadTreeNode* node, ViewData* vd, osg::NodeVisitor* nv, LodCallback* lodCallback, const osg::Vec3f& eyePoint, bool visible)
{
if (!node->hasValidBounds())
return;
if (nv && nv->getVisitorType() == osg::NodeVisitor::CULL_VISITOR)
visible = visible && !static_cast<osgUtil::CullVisitor*>(nv)->isCulled(node->getBoundingBox());
bool stopTraversal = (lodCallback && lodCallback->isSufficientDetail(node, eyePoint)) || !node->getNumChildren();
if (stopTraversal)
vd->add(node, visible);
else
{
for (unsigned int i=0; i<node->getNumChildren(); ++i)
traverse(node->getChild(i), vd, nv, lodCallback, eyePoint, visible);
}
}
void traverseToCell(QuadTreeNode* node, ViewData* vd, int cellX, int cellY)
{
if (!node->hasValidBounds())
return;
if (node->getCenter().x() + node->getSize()/2.f <= cellX
|| node->getCenter().x() - node->getSize()/2.f >= cellX+1
|| node->getCenter().y() + node->getSize()/2.f <= cellY
|| node->getCenter().y() - node->getSize()/2.f >= cellY+1)
return;
bool stopTraversal = !node->getNumChildren();
if (stopTraversal)
vd->add(node, true);
else
{
for (unsigned int i=0; i<node->getNumChildren(); ++i)
traverseToCell(node->getChild(i), vd, cellX, cellY);
}
}
unsigned int getLodFlags(QuadTreeNode* node, int ourLod, ViewData* vd)
{
unsigned int lodFlags = 0;
for (unsigned int i=0; i<4; ++i)
{
QuadTreeNode* neighbour = node->getNeighbour(static_cast<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 && !vd->contains(neighbour))
neighbour = neighbour->getParent();
int lod = 0;
if (neighbour)
lod = Log2(int(neighbour->getSize()));
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)
{
lodFlags |= static_cast<unsigned int>(lod - ourLod) << (4*i);
}
}
return lodFlags;
}
void loadRenderingNode(ViewData::Entry& entry, ViewData* vd, ChunkManager* chunkManager)
{
if (vd->hasChanged())
{
// have to recompute the lodFlags in case a neighbour has changed LOD.
int ourLod = Log2(int(entry.mNode->getSize()));
unsigned int lodFlags = getLodFlags(entry.mNode, ourLod, vd);
if (lodFlags != entry.mLodFlags)
{
entry.mRenderingNode = NULL;
entry.mLodFlags = lodFlags;
}
}
if (!entry.mRenderingNode)
{
int ourLod = Log2(int(entry.mNode->getSize()));
entry.mRenderingNode = chunkManager->getChunk(entry.mNode->getSize(), entry.mNode->getCenter(), ourLod, entry.mLodFlags);
}
}
void QuadTreeWorld::accept(osg::NodeVisitor &nv)
{
if (nv.getVisitorType() != osg::NodeVisitor::CULL_VISITOR && nv.getVisitorType() != osg::NodeVisitor::INTERSECTION_VISITOR)
return;
ViewData* vd = mRootNode->getView(nv);
if (nv.getVisitorType() == osg::NodeVisitor::CULL_VISITOR)
{
osgUtil::CullVisitor* cv = static_cast<osgUtil::CullVisitor*>(&nv);
osg::UserDataContainer* udc = cv->getCurrentCamera()->getUserDataContainer();
if (udc && udc->getNumDescriptions() >= 2 && udc->getDescriptions()[0] == "NoTerrainLod")
{
std::istringstream stream(udc->getDescriptions()[1]);
int x,y;
stream >> x;
stream >> y;
traverseToCell(mRootNode.get(), vd, x,y);
}
else
traverse(mRootNode.get(), vd, cv, mRootNode->getLodCallback(), cv->getEyePoint(), true);
}
else
mRootNode->traverse(nv);
for (unsigned int i=0; i<vd->getNumEntries(); ++i)
{
ViewData::Entry& entry = vd->getEntry(i);
loadRenderingNode(entry, vd, mChunkManager.get());
if (entry.mVisible)
{
osg::UserDataContainer* udc = entry.mRenderingNode->getUserDataContainer();
if (udc && udc->getUserData())
{
mCompositeMapRenderer->setImmediate(static_cast<CompositeMap*>(udc->getUserData()));
udc->setUserData(NULL);
}
entry.mRenderingNode->accept(nv);
}
}
vd->reset(nv.getTraversalNumber());
mRootNode->getViewDataMap()->clearUnusedViews(nv.getTraversalNumber());
}
void QuadTreeWorld::ensureQuadTreeBuilt()
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(mQuadTreeMutex);
if (mQuadTreeBuilt)
return;
const float minSize = 1/8.f;
QuadTreeBuilder builder(mStorage, mViewDataMap.get(), minSize);
builder.build();
mRootNode = builder.getRootNode();
mRootNode->setWorld(this);
mQuadTreeBuilt = true;
}
void QuadTreeWorld::enable(bool enabled)
{
if (enabled)
{
ensureQuadTreeBuilt();
if (!mRootNode->getNumParents())
mTerrainRoot->addChild(mRootNode);
}
if (mRootNode)
mRootNode->setNodeMask(enabled ? ~0 : 0);
}
void QuadTreeWorld::cacheCell(View *view, int x, int y)
{
ensureQuadTreeBuilt();
ViewData* vd = static_cast<ViewData*>(view);
traverseToCell(mRootNode.get(), vd, x, y);
for (unsigned int i=0; i<vd->getNumEntries(); ++i)
{
ViewData::Entry& entry = vd->getEntry(i);
loadRenderingNode(entry, vd, mChunkManager.get());
}
}
View* QuadTreeWorld::createView()
{
return new ViewData;
}
void QuadTreeWorld::preload(View *view, const osg::Vec3f &eyePoint)
{
ensureQuadTreeBuilt();
ViewData* vd = static_cast<ViewData*>(view);
traverse(mRootNode.get(), vd, NULL, mRootNode->getLodCallback(), eyePoint, false);
for (unsigned int i=0; i<vd->getNumEntries(); ++i)
{
ViewData::Entry& entry = vd->getEntry(i);
loadRenderingNode(entry, vd, mChunkManager.get());
}
}
void QuadTreeWorld::reportStats(unsigned int frameNumber, osg::Stats *stats)
{
stats->setAttribute(frameNumber, "Composite", mCompositeMapRenderer->getCompileSetSize());
}
}

@ -0,0 +1,49 @@
#ifndef COMPONENTS_TERRAIN_QUADTREEWORLD_H
#define COMPONENTS_TERRAIN_QUADTREEWORLD_H
#include "world.hpp"
#include <OpenThreads/Mutex>
namespace osg
{
class NodeVisitor;
}
namespace Terrain
{
class RootNode;
class ViewDataMap;
/// @brief Terrain implementation that loads cells into a Quad Tree, with geometry LOD and texture LOD. The entire world is displayed at all times.
class QuadTreeWorld : public Terrain::World
{
public:
QuadTreeWorld(osg::Group* parent, osg::Group* compileRoot, Resource::ResourceSystem* resourceSystem, Storage* storage, int nodeMask, int preCompileMask=~0);
~QuadTreeWorld();
void accept(osg::NodeVisitor& nv);
virtual void enable(bool enabled);
void cacheCell(View *view, int x, int y);
View* createView();
void preload(View* view, const osg::Vec3f& eyePoint);
void reportStats(unsigned int frameNumber, osg::Stats* stats);
private:
void ensureQuadTreeBuilt();
osg::ref_ptr<RootNode> mRootNode;
osg::ref_ptr<ViewDataMap> mViewDataMap;
OpenThreads::Mutex mQuadTreeMutex;
bool mQuadTreeBuilt;
};
}
#endif

@ -18,6 +18,7 @@ namespace osg
namespace Terrain
{
/// We keep storage of terrain data abstract here since we need different implementations for game and editor
/// @note The implementation must be thread safe.
class Storage
{
public:
@ -78,6 +79,8 @@ namespace Terrain
/// Get the number of vertices on one side for each cell. Should be (power of two)+1
virtual int getCellVertices() = 0;
virtual int getBlendmapScale(float chunkSize) = 0;
};
}

@ -0,0 +1,89 @@
#include "terraindrawable.hpp"
#include <osgUtil/CullVisitor>
#include <components/sceneutil/lightmanager.hpp>
namespace Terrain
{
TerrainDrawable::TerrainDrawable()
{
}
TerrainDrawable::TerrainDrawable(const TerrainDrawable &copy, const osg::CopyOp &copyop)
: osg::Geometry(copy, copyop)
, mPasses(copy.mPasses)
, mLightListCallback(copy.mLightListCallback)
{
}
void TerrainDrawable::accept(osg::NodeVisitor &nv)
{
if (nv.getVisitorType() != osg::NodeVisitor::CULL_VISITOR)
{
osg::Geometry::accept(nv);
}
else if (nv.validNodeMask(*this))
{
nv.pushOntoNodePath(this);
cull(static_cast<osgUtil::CullVisitor*>(&nv));
nv.popFromNodePath();
}
}
inline float distance(const osg::Vec3& coord,const osg::Matrix& matrix)
{
return -((float)coord[0]*(float)matrix(0,2)+(float)coord[1]*(float)matrix(1,2)+(float)coord[2]*(float)matrix(2,2)+matrix(3,2));
}
void TerrainDrawable::cull(osgUtil::CullVisitor *cv)
{
const osg::BoundingBox& bb = getBoundingBox();
if (_cullingActive && cv->isCulled(getBoundingBox()))
return;
osg::RefMatrix& matrix = *cv->getModelViewMatrix();
float depth = bb.valid() ? distance(bb.center(),matrix) : 0.0f;
if (osg::isNaN(depth))
return;
bool pushedLight = mLightListCallback && mLightListCallback->pushLightState(this, cv);
for (PassVector::const_iterator it = mPasses.begin(); it != mPasses.end(); ++it)
{
cv->pushStateSet(*it);
cv->addDrawableAndDepth(this, &matrix, depth);
cv->popStateSet();
}
if (pushedLight)
cv->popStateSet();
}
void TerrainDrawable::setPasses(const TerrainDrawable::PassVector &passes)
{
mPasses = passes;
}
void TerrainDrawable::setLightListCallback(SceneUtil::LightListCallback *lightListCallback)
{
mLightListCallback = lightListCallback;
}
void TerrainDrawable::compileGLObjects(osg::RenderInfo &renderInfo) const
{
for (PassVector::const_iterator it = mPasses.begin(); it != mPasses.end(); ++it)
{
osg::StateSet* stateset = *it;
stateset->compileGLObjects(*renderInfo.getState());
}
osg::Geometry::compileGLObjects(renderInfo);
}
}

@ -0,0 +1,53 @@
#ifndef OPENMW_COMPONENTS_TERRAIN_DRAWABLE_H
#define OPENMW_COMPONENTS_TERRAIN_DRAWABLE_H
#include <osg/Geometry>
namespace osgUtil
{
class CullVisitor;
}
namespace SceneUtil
{
class LightListCallback;
}
namespace Terrain
{
/**
* Subclass of Geometry that supports built in multi-pass rendering and built in LightListCallback.
*/
class TerrainDrawable : public osg::Geometry
{
public:
virtual osg::Object* cloneType() const { return new TerrainDrawable (); }
virtual osg::Object* clone(const osg::CopyOp& copyop) const { return new TerrainDrawable (*this,copyop); }
virtual bool isSameKindAs(const osg::Object* obj) const { return dynamic_cast<const TerrainDrawable *>(obj)!=NULL; }
virtual const char* className() const { return "TerrainDrawable"; }
virtual const char* libraryName() const { return "Terrain"; }
TerrainDrawable();
TerrainDrawable(const TerrainDrawable& copy, const osg::CopyOp& copyop);
virtual void accept(osg::NodeVisitor &nv);
void cull(osgUtil::CullVisitor* cv);
typedef std::vector<osg::ref_ptr<osg::StateSet> > PassVector;
void setPasses (const PassVector& passes);
void setLightListCallback(SceneUtil::LightListCallback* lightListCallback);
virtual void compileGLObjects(osg::RenderInfo& renderInfo) const;
private:
PassVector mPasses;
osg::ref_ptr<SceneUtil::LightListCallback> mLightListCallback;
};
}
#endif

@ -2,63 +2,25 @@
#include <memory>
#include <osg/Material>
#include <osg/Group>
#include <OpenThreads/ScopedLock>
#include "chunkmanager.hpp"
#include <components/resource/resourcesystem.hpp>
#include <components/resource/imagemanager.hpp>
#include <components/resource/scenemanager.hpp>
#include <components/sceneutil/lightmanager.hpp>
#include <components/sceneutil/positionattitudetransform.hpp>
#include <components/sceneutil/unrefqueue.hpp>
#include <components/esm/loadland.hpp>
#include <osg/Geometry>
#include <osg/KdTree>
#include <osgFX/Effect>
#include <osgUtil/IncrementalCompileOperation>
#include "material.hpp"
#include "storage.hpp"
namespace
namespace Terrain
{
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
class MyView : public View
{
public:
osg::ref_ptr<osg::Node> mLoaded;
virtual void reset(unsigned int frame) {}
};
TerrainGrid::TerrainGrid(osg::Group* parent, Resource::ResourceSystem* resourceSystem, osgUtil::IncrementalCompileOperation* ico, Storage* storage, int nodeMask, Shader::ShaderManager* shaderManager, SceneUtil::UnrefQueue* unrefQueue)
: Terrain::World(parent, resourceSystem, ico, storage, nodeMask)
TerrainGrid::TerrainGrid(osg::Group* parent, osg::Group* compileRoot, Resource::ResourceSystem* resourceSystem, Storage* storage, int nodeMask, int preCompileMask)
: Terrain::World(parent, compileRoot, resourceSystem, storage, nodeMask, preCompileMask)
, mNumSplits(4)
, mCache((storage->getCellVertices()-1)/static_cast<float>(mNumSplits) + 1)
, mUnrefQueue(unrefQueue)
, mShaderManager(shaderManager)
{
osg::ref_ptr<osg::Material> material (new osg::Material);
material->setColorMode(osg::Material::AMBIENT_AND_DIFFUSE);
mTerrainRoot->getOrCreateStateSet()->setAttributeAndModes(material, osg::StateAttribute::ON);
}
TerrainGrid::~TerrainGrid()
@ -69,19 +31,10 @@ TerrainGrid::~TerrainGrid()
}
}
osg::ref_ptr<osg::Node> TerrainGrid::cacheCell(int x, int y)
void TerrainGrid::cacheCell(View* view, int x, int y)
{
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(mGridCacheMutex);
Grid::iterator found = mGridCache.find(std::make_pair(x,y));
if (found != mGridCache.end())
return found->second;
}
osg::ref_ptr<osg::Node> node = buildTerrain(NULL, 1.f, osg::Vec2f(x+0.5, y+0.5));
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(mGridCacheMutex);
mGridCache.insert(std::make_pair(std::make_pair(x,y), node));
return node;
osg::Vec2f center(x+0.5f, y+0.5f);
static_cast<MyView*>(view)->mLoaded = buildTerrain(NULL, 1.f, center);
}
osg::ref_ptr<osg::Node> TerrainGrid::buildTerrain (osg::Group* parent, float chunkSize, const osg::Vec2f& chunkCenter)
@ -102,136 +55,13 @@ osg::ref_ptr<osg::Node> TerrainGrid::buildTerrain (osg::Group* parent, float chu
}
else
{
float minH, maxH;
if (!mStorage->getMinMaxHeights(chunkSize, chunkCenter, minH, maxH))
return NULL; // no terrain defined
osg::Vec2f worldCenter = chunkCenter*mStorage->getCellWorldSize();
osg::ref_ptr<SceneUtil::PositionAttitudeTransform> transform (new SceneUtil::PositionAttitudeTransform);
transform->setPosition(osg::Vec3f(worldCenter.x(), worldCenter.y(), 0.f));
osg::ref_ptr<osg::Node> node = mChunkManager->getChunk(chunkSize, chunkCenter, 0, 0);
if (!node)
return NULL;
if (parent)
parent->addChild(transform);
osg::ref_ptr<osg::Vec3Array> positions (new osg::Vec3Array);
osg::ref_ptr<osg::Vec3Array> normals (new osg::Vec3Array);
osg::ref_ptr<osg::Vec4Array> colors (new osg::Vec4Array);
osg::ref_ptr<osg::VertexBufferObject> vbo (new osg::VertexBufferObject);
positions->setVertexBufferObject(vbo);
normals->setVertexBufferObject(vbo);
colors->setVertexBufferObject(vbo);
mStorage->fillVertexBuffers(0, chunkSize, chunkCenter, positions, normals, colors);
osg::ref_ptr<osg::Geometry> geometry (new osg::Geometry);
geometry->setVertexArray(positions);
geometry->setNormalArray(normals, osg::Array::BIND_PER_VERTEX);
geometry->setColorArray(colors, osg::Array::BIND_PER_VERTEX);
geometry->setUseDisplayList(false);
geometry->setUseVertexBufferObjects(true);
geometry->addPrimitiveSet(mCache.getIndexBuffer(0));
// we already know the bounding box, so no need to let OSG compute it.
osg::Vec3f min(-0.5f*mStorage->getCellWorldSize()*chunkSize,
-0.5f*mStorage->getCellWorldSize()*chunkSize,
minH);
osg::Vec3f max (0.5f*mStorage->getCellWorldSize()*chunkSize,
0.5f*mStorage->getCellWorldSize()*chunkSize,
maxH);
osg::BoundingBox bounds(min, max);
geometry->setComputeBoundingBoxCallback(new StaticBoundingBoxCallback(bounds));
std::vector<LayerInfo> layerList;
std::vector<osg::ref_ptr<osg::Image> > blendmaps;
mStorage->getBlendmaps(chunkSize, chunkCenter, false, blendmaps, layerList);
// For compiling textures, I don't think the osgFX::Effect does it correctly
osg::ref_ptr<osg::Node> textureCompileDummy (new osg::Node);
unsigned int dummyTextureCounter = 0;
bool useShaders = mResourceSystem->getSceneManager()->getForceShaders();
if (!mResourceSystem->getSceneManager()->getClampLighting())
useShaders = true; // always use shaders when lighting is unclamped, this is to avoid lighting seams between a terrain chunk with normal maps and one without normal maps
std::vector<TextureLayer> layers;
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(mTextureCacheMutex);
for (std::vector<LayerInfo>::const_iterator it = layerList.begin(); it != layerList.end(); ++it)
{
TextureLayer textureLayer;
textureLayer.mParallax = it->mParallax;
textureLayer.mSpecular = it->mSpecular;
osg::ref_ptr<osg::Texture2D> texture = mTextureCache[it->mDiffuseMap];
if (!texture)
{
texture = new osg::Texture2D(mResourceSystem->getImageManager()->getImage(it->mDiffuseMap));
texture->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT);
texture->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT);
mResourceSystem->getSceneManager()->applyFilterSettings(texture);
mTextureCache[it->mDiffuseMap] = texture;
}
textureLayer.mDiffuseMap = texture;
textureCompileDummy->getOrCreateStateSet()->setTextureAttributeAndModes(dummyTextureCounter++, texture);
if (!it->mNormalMap.empty())
{
texture = mTextureCache[it->mNormalMap];
if (!texture)
{
texture = new osg::Texture2D(mResourceSystem->getImageManager()->getImage(it->mNormalMap));
texture->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT);
texture->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT);
mResourceSystem->getSceneManager()->applyFilterSettings(texture);
mTextureCache[it->mNormalMap] = texture;
}
textureCompileDummy->getOrCreateStateSet()->setTextureAttributeAndModes(dummyTextureCounter++, texture);
textureLayer.mNormalMap = texture;
}
if (it->requiresShaders())
useShaders = true;
layers.push_back(textureLayer);
}
}
parent->addChild(node);
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)
{
osg::ref_ptr<osg::Texture2D> texture (new osg::Texture2D);
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->setResizeNonPowerOfTwoHint(false);
blendmapTextures.push_back(texture);
textureCompileDummy->getOrCreateStateSet()->setTextureAttributeAndModes(dummyTextureCounter++, blendmapTextures.back());
}
// use texture coordinates for both texture units, the layer texture and blend texture
for (unsigned int i=0; i<2; ++i)
geometry->setTexCoordArray(i, mCache.getUVBuffer());
float blendmapScale = ESM::Land::LAND_TEXTURE_SIZE*chunkSize;
osg::ref_ptr<osgFX::Effect> effect (new Terrain::Effect(mShaderManager ? useShaders : false, mResourceSystem->getSceneManager()->getForcePerPixelLighting(), mResourceSystem->getSceneManager()->getClampLighting(),
mShaderManager, layers, blendmapTextures, blendmapScale, blendmapScale));
effect->addCullCallback(new SceneUtil::LightListCallback);
transform->addChild(effect);
osg::Node* toAttach = geometry.get();
effect->addChild(toAttach);
if (mIncrementalCompileOperation)
{
mIncrementalCompileOperation->add(toAttach);
mIncrementalCompileOperation->add(textureCompileDummy);
}
return transform;
return node;
}
}
@ -240,27 +70,10 @@ void TerrainGrid::loadCell(int x, int y)
if (mGrid.find(std::make_pair(x, y)) != mGrid.end())
return; // already loaded
// try to get it from the cache
osg::ref_ptr<osg::Node> terrainNode;
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(mGridCacheMutex);
Grid::const_iterator found = mGridCache.find(std::make_pair(x,y));
if (found != mGridCache.end())
{
terrainNode = found->second;
if (!terrainNode)
return; // no terrain defined
}
}
// didn't find in cache, build it
osg::Vec2f center(x+0.5f, y+0.5f);
osg::ref_ptr<osg::Node> terrainNode = buildTerrain(NULL, 1.f, center);
if (!terrainNode)
{
osg::Vec2f center(x+0.5f, y+0.5f);
terrainNode = buildTerrain(NULL, 1.f, center);
if (!terrainNode)
return; // no terrain defined
}
return; // no terrain defined
mTerrainRoot->addChild(terrainNode);
@ -276,54 +89,12 @@ void TerrainGrid::unloadCell(int x, int y)
osg::ref_ptr<osg::Node> terrainNode = it->second;
mTerrainRoot->removeChild(terrainNode);
if (mUnrefQueue.get())
mUnrefQueue->push(terrainNode);
mGrid.erase(it);
}
void TerrainGrid::updateCache()
View *TerrainGrid::createView()
{
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(mGridCacheMutex);
for (Grid::iterator it = mGridCache.begin(); it != mGridCache.end();)
{
if (it->second->referenceCount() <= 1)
mGridCache.erase(it++);
else
++it;
}
}
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(mTextureCacheMutex);
for (TextureCache::iterator it = mTextureCache.begin(); it != mTextureCache.end();)
{
if (it->second->referenceCount() <= 1)
mTextureCache.erase(it++);
else
++it;
}
}
}
void TerrainGrid::updateTextureFiltering()
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(mTextureCacheMutex);
for (TextureCache::iterator it = mTextureCache.begin(); it != mTextureCache.end(); ++it)
mResourceSystem->getSceneManager()->applyFilterSettings(it->second);
}
void TerrainGrid::reportStats(unsigned int frameNumber, osg::Stats *stats)
{
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(mGridCacheMutex);
stats->setAttribute(frameNumber, "Terrain Cell", mGridCache.size());
}
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(mTextureCacheMutex);
stats->setAttribute(frameNumber, "Terrain Texture", mTextureCache.size());
}
return new MyView;
}
}

@ -1,39 +1,23 @@
#ifndef COMPONENTS_TERRAIN_TERRAINGRID_H
#define COMPONENTS_TERRAIN_TERRAINGRID_H
#include <map>
#include <osg/Vec2f>
#include "world.hpp"
namespace SceneUtil
{
class UnrefQueue;
}
namespace Shader
{
class ShaderManager;
}
namespace osg
{
class Texture2D;
}
namespace Terrain
{
/// @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. Only requested cells are loaded.
class TerrainGrid : public Terrain::World
{
public:
TerrainGrid(osg::Group* parent, Resource::ResourceSystem* resourceSystem, osgUtil::IncrementalCompileOperation* ico, Storage* storage, int nodeMask, Shader::ShaderManager* shaderManager = NULL, SceneUtil::UnrefQueue* unrefQueue = NULL);
TerrainGrid(osg::Group* parent, osg::Group* compileRoot, Resource::ResourceSystem* resourceSystem, Storage* storage, int nodeMask, int preCompileMask=~0);
~TerrainGrid();
/// Load a terrain cell and store it in cache for later use.
/// @note The returned ref_ptr should be kept by the caller to ensure that the terrain stays in cache for as long as needed.
/// @note Thread safe.
virtual osg::ref_ptr<osg::Node> cacheCell(int x, int y);
virtual void cacheCell(View* view, int x, int y);
/// @note Not thread safe.
virtual void loadCell(int x, int y);
@ -41,15 +25,7 @@ namespace Terrain
/// @note Not thread safe.
virtual void unloadCell(int x, int y);
/// Clear cached objects that are no longer referenced
/// @note Thread safe.
void updateCache();
/// Apply the scene manager's texture filtering settings to all cached textures.
/// @note Thread safe.
void updateTextureFiltering();
void reportStats(unsigned int frameNumber, osg::Stats *stats);
View* createView();
private:
osg::ref_ptr<osg::Node> buildTerrain (osg::Group* parent, float chunkSize, const osg::Vec2f& chunkCenter);
@ -57,21 +33,8 @@ namespace Terrain
// split each ESM::Cell into mNumSplits*mNumSplits terrain chunks
unsigned int mNumSplits;
typedef std::map<std::string, osg::ref_ptr<osg::Texture2D> > TextureCache;
TextureCache mTextureCache;
OpenThreads::Mutex mTextureCacheMutex;
typedef std::map<std::pair<int, int>, osg::ref_ptr<osg::Node> > Grid;
Grid mGrid;
Grid mGridCache;
OpenThreads::Mutex mGridCacheMutex;
BufferCache mCache;
osg::ref_ptr<SceneUtil::UnrefQueue> mUnrefQueue;
Shader::ShaderManager* mShaderManager;
};
}

@ -0,0 +1,64 @@
#include "texturemanager.hpp"
#include <osg/Stats>
#include <osg/Texture2D>
#include <components/resource/scenemanager.hpp>
#include <components/resource/imagemanager.hpp>
#include <components/resource/objectcache.hpp>
namespace Terrain
{
TextureManager::TextureManager(Resource::SceneManager *sceneMgr)
: ResourceManager(sceneMgr->getVFS())
, mSceneManager(sceneMgr)
{
}
struct UpdateTextureFilteringFunctor
{
UpdateTextureFilteringFunctor(Resource::SceneManager* sceneMgr)
: mSceneManager(sceneMgr)
{
}
Resource::SceneManager* mSceneManager;
void operator()(osg::Object* obj)
{
mSceneManager->applyFilterSettings(static_cast<osg::Texture2D*>(obj));
}
};
void TextureManager::updateTextureFiltering()
{
UpdateTextureFilteringFunctor f(mSceneManager);
mCache->call(f);
}
osg::ref_ptr<osg::Texture2D> TextureManager::getTexture(const std::string &name)
{
// don't bother with case folding, since there is only one way of referring to terrain textures we can assume the case is always the same
osg::ref_ptr<osg::Object> obj = mCache->getRefFromObjectCache(name);
if (obj)
return static_cast<osg::Texture2D*>(obj.get());
else
{
osg::ref_ptr<osg::Texture2D> texture (new osg::Texture2D(mSceneManager->getImageManager()->getImage(name)));
texture->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT);
texture->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT);
mSceneManager->applyFilterSettings(texture);
mCache->addEntryToObjectCache(name, texture.get());
return texture;
}
}
void TextureManager::reportStats(unsigned int frameNumber, osg::Stats *stats) const
{
stats->setAttribute(frameNumber, "Terrain Texture", mCache->getCacheSize());
}
}

@ -0,0 +1,39 @@
#ifndef OPENMW_COMPONENTS_TERRAIN_TEXTUREMANAGER_H
#define OPENMW_COMPONENTS_TERRAIN_TEXTUREMANAGER_H
#include <string>
#include <components/resource/resourcemanager.hpp>
namespace Resource
{
class SceneManager;
}
namespace osg
{
class Texture2D;
}
namespace Terrain
{
class TextureManager : public Resource::ResourceManager
{
public:
TextureManager(Resource::SceneManager* sceneMgr);
void updateTextureFiltering();
osg::ref_ptr<osg::Texture2D> getTexture(const std::string& name);
virtual void reportStats(unsigned int frameNumber, osg::Stats* stats) const;
private:
Resource::SceneManager* mSceneManager;
};
}
#endif

@ -0,0 +1,154 @@
#include "viewdata.hpp"
namespace Terrain
{
ViewData::ViewData()
: mNumEntries(0)
, mFrameLastUsed(0)
, mChanged(false)
{
}
ViewData::~ViewData()
{
}
void ViewData::add(QuadTreeNode *node, bool visible)
{
unsigned int index = mNumEntries++;
if (index+1 > mEntries.size())
mEntries.resize(index+1);
Entry& entry = mEntries[index];
if (entry.set(node, visible))
mChanged = true;
}
unsigned int ViewData::getNumEntries() const
{
return mNumEntries;
}
ViewData::Entry &ViewData::getEntry(unsigned int i)
{
return mEntries[i];
}
bool ViewData::hasChanged() const
{
return mChanged;
}
void ViewData::reset(unsigned int frame)
{
// clear any unused entries
for (unsigned int i=mNumEntries; i<mEntries.size(); ++i)
mEntries[i].set(NULL, false);
// reset index for next frame
mNumEntries = 0;
mChanged = false;
mFrameLastUsed = frame;
}
void ViewData::clear()
{
for (unsigned int i=0; i<mEntries.size(); ++i)
mEntries[i].set(NULL, false);
mNumEntries = 0;
mFrameLastUsed = 0;
mChanged = false;
}
bool ViewData::contains(QuadTreeNode *node)
{
for (unsigned int i=0; i<mNumEntries; ++i)
if (mEntries[i].mNode == node)
return true;
return false;
}
ViewData::Entry::Entry()
: mNode(NULL)
, mVisible(true)
, mLodFlags(0)
{
}
bool ViewData::Entry::set(QuadTreeNode *node, bool visible)
{
mVisible = visible;
if (node == mNode)
return false;
else
{
mNode = node;
// clear cached data
mRenderingNode = NULL;
return true;
}
}
ViewData *ViewDataMap::getViewData(osg::Object *viewer, bool ref)
{
Map::const_iterator found = mViews.find(viewer);
if (found == mViews.end())
{
ViewData* vd = createOrReuseView();
if (ref)
vd->setViewer(viewer);
mViews[viewer] = vd;
return vd;
}
else
return found->second;
}
ViewData *ViewDataMap::createOrReuseView()
{
if (mUnusedViews.size())
{
ViewData* vd = mUnusedViews.front();
mUnusedViews.pop_front();
return vd;
}
else
{
mViewVector.push_back(ViewData());
return &mViewVector.back();
}
}
void ViewDataMap::clearUnusedViews(unsigned int frame)
{
for (Map::iterator it = mViews.begin(); it != mViews.end(); )
{
ViewData* vd = it->second;
if ((!vd->getViewer() // if no ref was held, always need to clear to avoid holding a dangling ref.
|| vd->getFrameLastUsed() + 2 < frame))
{
vd->setViewer(NULL);
vd->clear();
mUnusedViews.push_back(vd);
mViews.erase(it++);
}
else
++it;
}
}
void ViewDataMap::clear()
{
mViews.clear();
mUnusedViews.clear();
mViewVector.clear();
}
}

@ -0,0 +1,85 @@
#ifndef OPENMW_COMPONENTS_TERRAIN_VIEWDATA_H
#define OPENMW_COMPONENTS_TERRAIN_VIEWDATA_H
#include <vector>
#include <deque>
#include <osg/Node>
#include "world.hpp"
namespace Terrain
{
class QuadTreeNode;
class ViewData : public View
{
public:
ViewData();
~ViewData();
void add(QuadTreeNode* node, bool visible);
void reset(unsigned int frame);
void clear();
bool contains(QuadTreeNode* node);
struct Entry
{
Entry();
bool set(QuadTreeNode* node, bool visible);
QuadTreeNode* mNode;
bool mVisible;
unsigned int mLodFlags;
osg::ref_ptr<osg::Node> mRenderingNode;
};
unsigned int getNumEntries() const;
Entry& getEntry(unsigned int i);
osg::Object* getViewer() const { return mViewer.get(); }
void setViewer(osg::Object* viewer) { mViewer = viewer; }
unsigned int getFrameLastUsed() const { return mFrameLastUsed; }
/// @return Have any nodes changed since the last frame
bool hasChanged() const;
private:
std::vector<Entry> mEntries;
unsigned int mNumEntries;
unsigned int mFrameLastUsed;
bool mChanged;
osg::ref_ptr<osg::Object> mViewer;
};
class ViewDataMap : public osg::Referenced
{
public:
ViewData* getViewData(osg::Object* viewer, bool ref);
ViewData* createOrReuseView();
void clearUnusedViews(unsigned int frame);
void clear();
private:
std::list<ViewData> mViewVector;
typedef std::map<osg::Object*, ViewData*> Map;
Map mViews;
std::deque<ViewData*> mUnusedViews;
};
}
#endif

@ -1,32 +1,67 @@
#include "world.hpp"
#include <osg/Group>
#include <osgUtil/IncrementalCompileOperation>
#include <osg/Material>
#include <osg/Camera>
#include <components/resource/resourcesystem.hpp>
#include "storage.hpp"
#include "texturemanager.hpp"
#include "chunkmanager.hpp"
#include "compositemaprenderer.hpp"
namespace Terrain
{
World::World(osg::Group* parent, Resource::ResourceSystem* resourceSystem, osgUtil::IncrementalCompileOperation* ico,
Storage* storage, int nodeMask)
World::World(osg::Group* parent, osg::Group* compileRoot, Resource::ResourceSystem* resourceSystem, Storage* storage, int nodeMask, int preCompileMask)
: mStorage(storage)
, mParent(parent)
, mResourceSystem(resourceSystem)
, mIncrementalCompileOperation(ico)
{
mTerrainRoot = new osg::Group;
mTerrainRoot->setNodeMask(nodeMask);
mTerrainRoot->getOrCreateStateSet()->setRenderingHint(osg::StateSet::OPAQUE_BIN);
osg::ref_ptr<osg::Material> material (new osg::Material);
material->setColorMode(osg::Material::AMBIENT_AND_DIFFUSE);
mTerrainRoot->getOrCreateStateSet()->setAttributeAndModes(material, osg::StateAttribute::ON);
mTerrainRoot->setName("Terrain Root");
osg::ref_ptr<osg::Camera> compositeCam = new osg::Camera;
compositeCam->setRenderOrder(osg::Camera::PRE_RENDER, -1);
compositeCam->setProjectionMatrix(osg::Matrix::identity());
compositeCam->setViewMatrix(osg::Matrix::identity());
compositeCam->setReferenceFrame(osg::Camera::ABSOLUTE_RF);
compositeCam->setClearMask(0);
compositeCam->setNodeMask(preCompileMask);
mCompositeMapCamera = compositeCam;
compileRoot->addChild(compositeCam);
mCompositeMapRenderer = new CompositeMapRenderer;
compositeCam->addChild(mCompositeMapRenderer);
mParent->addChild(mTerrainRoot);
mTextureManager.reset(new TextureManager(mResourceSystem->getSceneManager()));
mChunkManager.reset(new ChunkManager(mStorage, mResourceSystem->getSceneManager(), mTextureManager.get(), mCompositeMapRenderer));
mResourceSystem->addResourceManager(mChunkManager.get());
mResourceSystem->addResourceManager(mTextureManager.get());
}
World::~World()
{
mResourceSystem->removeResourceManager(mChunkManager.get());
mResourceSystem->removeResourceManager(mTextureManager.get());
mParent->removeChild(mTerrainRoot);
mCompositeMapCamera->removeChild(mCompositeMapRenderer);
mCompositeMapCamera->getParent(0)->removeChild(mCompositeMapCamera);
delete mStorage;
}
@ -35,4 +70,9 @@ float World::getHeightAt(const osg::Vec3f &worldPos)
return mStorage->getHeightAt(worldPos);
}
void World::updateTextureFiltering()
{
mTextureManager->updateTextureFiltering();
}
}

@ -2,19 +2,18 @@
#define COMPONENTS_TERRAIN_WORLD_H
#include <osg/ref_ptr>
#include <osg/Referenced>
#include <osg/Vec3f>
#include <memory>
#include "defs.hpp"
#include "buffercache.hpp"
namespace osg
{
class Group;
class Stats;
}
namespace osgUtil
{
class IncrementalCompileOperation;
class Node;
}
namespace Resource
@ -26,6 +25,23 @@ namespace Terrain
{
class Storage;
class TextureManager;
class ChunkManager;
class CompositeMapRenderer;
/**
* @brief A View is a collection of rendering objects that are visible from a given camera/intersection.
* The base View class is part of the interface for usage in conjunction with preload feature.
*/
class View : public osg::Referenced
{
public:
virtual ~View() {}
/// Reset internal structure so that the next addition to the view will override the previous frame's contents.
virtual void reset(unsigned int frame) = 0;
};
/**
* @brief The basic interface for a terrain world. How the terrain chunks are paged and displayed
* is up to the implementation.
@ -36,24 +52,41 @@ namespace Terrain
/// @note takes ownership of \a storage
/// @param storage Storage instance to get terrain data from (heights, normals, colors, textures..)
/// @param nodeMask mask for the terrain root
World(osg::Group* parent, Resource::ResourceSystem* resourceSystem, osgUtil::IncrementalCompileOperation* ico,
Storage* storage, int nodeMask);
/// @param preCompileMask mask for pre compiling textures
World(osg::Group* parent, osg::Group* compileRoot, Resource::ResourceSystem* resourceSystem, Storage* storage, int nodeMask, int preCompileMask);
virtual ~World();
virtual void updateTextureFiltering() {}
virtual void updateCache() {}
virtual void reportStats(unsigned int frameNumber, osg::Stats* stats) {}
/// Apply the scene manager's texture filtering settings to all cached textures.
/// @note Thread safe.
void updateTextureFiltering();
float getHeightAt (const osg::Vec3f& worldPos);
virtual osg::ref_ptr<osg::Node> cacheCell(int x, int y) {return NULL;}
/// Load a terrain cell at maximum LOD and store it in the View for later use.
/// @note Thread safe.
virtual void cacheCell(View* view, int x, int y) {}
// This is only a hint and may be ignored by the implementation.
/// Load the cell into the scene graph.
/// @note Not thread safe.
/// @note May be ignored by derived implementations that don't organize the terrain into cells.
virtual void loadCell(int x, int y) {}
/// Remove the cell from the scene graph.
/// @note Not thread safe.
/// @note May be ignored by derived implementations that don't organize the terrain into cells.
virtual void unloadCell(int x, int y) {}
virtual void enable(bool enabled) {}
/// Create a View to use with preload feature. The caller is responsible for deleting the view.
/// @note Thread safe.
virtual View* createView() { return NULL; }
/// @note Thread safe, as long as you do not attempt to load into the same view from multiple threads.
virtual void preload(View* view, const osg::Vec3f& eyePoint) {}
virtual void reportStats(unsigned int frameNumber, osg::Stats* stats) {}
Storage* getStorage() { return mStorage; }
protected:
@ -62,9 +95,13 @@ namespace Terrain
osg::ref_ptr<osg::Group> mParent;
osg::ref_ptr<osg::Group> mTerrainRoot;
osg::ref_ptr<osg::Group> mCompositeMapCamera;
osg::ref_ptr<CompositeMapRenderer> mCompositeMapRenderer;
Resource::ResourceSystem* mResourceSystem;
osg::ref_ptr<osgUtil::IncrementalCompileOperation> mIncrementalCompileOperation;
std::auto_ptr<TextureManager> mTextureManager;
std::auto_ptr<ChunkManager> mChunkManager;
};
}

@ -59,7 +59,7 @@ The constant 8192 is the size of a cell, and 1024 is the threshold distance for
Reductions of up to 25% or more can be required to completely eliminate pop-in for wide fields of view and long viewing distances near the edges of the screen, but such situations are unusual and probably not worth the performance penalty introduced by loading geometry obscured by fog in the center of the screen. See RenderingManager::configureFog for the relevant source code.
Enabling the distant land setting is an alternative to increasing exterior cell load distance. Note that the distant land setting does not include rendering of distant static objects, so the resulting visual effect is not the same.
Enabling the distant terrain setting is an alternative to increasing exterior cell load distance. Note that the distant land setting does not include rendering of distant static objects, so the resulting visual effect is not the same.
The default value is 6666.0. This setting can be adjusted in game from the ridiculously low value of 2000.0 to a maximum of 6666.0, using the View Distance slider in the Detail tab of the Video panel of the Options menu.

@ -23,6 +23,7 @@ Although this guide attempts to be comprehensive and up to date, you will always
input
saves
sound
terrain
video
water
windows

@ -0,0 +1,15 @@
Terrain Settings
###############
distant terrain
---------------
:Type: boolean
:Range: True/False
:Default: False
Controls whether the engine will use paging and LOD algorithms to render the terrain of the entire world at all times. Otherwise, only the terrain of the loaded cells is displayed. This setting is best used together with the 'viewing distance' setting in the camera section.
To avoid frame drops as the player moves around, nearby terrain pages are always preloaded in the background, regardless of the preloading settings in the 'Cells' section, but the preloading of terrain behind a door or a travel destination, for example, will still be controlled by cell preloading settings.
The distant terrain engine is currently considered experimental and may receive updates and/or further configuration options in the future. The glaring omission of non-terrain objects in the distance somewhat limits this setting's usefulness.

@ -76,6 +76,11 @@ preload cell expiry delay = 5
# How long to keep models/textures/collision shapes in cache after they're no longer referenced/required (in seconds)
cache expiry delay = 5
[Terrain]
# If true, use paging and LOD algorithms to display the entire terrain. If false, only display terrain of the loaded cells
distant terrain = false
[Map]
# Size of each exterior cell in pixels in the world map. (e.g. 12 to 24).

Loading…
Cancel
Save