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/loadcell.hpp>
#include <components/esm/loadland.hpp> #include <components/esm/loadland.hpp>
#include <components/sceneutil/pathgridutil.hpp> #include <components/sceneutil/pathgridutil.hpp>
#include <components/terrain/terraingrid.hpp>
#include "../../model/world/idtable.hpp" #include "../../model/world/idtable.hpp"
#include "../../model/world/columns.hpp" #include "../../model/world/columns.hpp"
@ -17,9 +18,13 @@
#include "../../model/world/cellcoordinates.hpp" #include "../../model/world/cellcoordinates.hpp"
#include "cellwater.hpp" #include "cellwater.hpp"
#include "cellborder.hpp"
#include "cellarrow.hpp"
#include "cellmarker.hpp"
#include "mask.hpp" #include "mask.hpp"
#include "pathgrid.hpp" #include "pathgrid.hpp"
#include "terrainstorage.hpp" #include "terrainstorage.hpp"
#include "object.hpp"
bool CSVRender::Cell::removeObject (const std::string& id) 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)) 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, mTerrain->loadCell(esmLand.mX,
esmLand.mY); esmLand.mY);

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

@ -20,6 +20,7 @@
#include "editmode.hpp" #include "editmode.hpp"
#include "mask.hpp" #include "mask.hpp"
#include "cameracontroller.hpp" #include "cameracontroller.hpp"
#include "cellarrow.hpp"
bool CSVRender::PagedWorldspaceWidget::adjustCells() 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; std::ostringstream stream;
stream << "#" << cellX << " " << cellY; stream << "#" << cellX << " " << cellY;
@ -21,9 +21,7 @@ namespace CSVRender
return NULL; return NULL;
const ESM::Land& land = mData.getLand().getRecord(index).get(); 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; return new ESMTerrain::LandObject(&land, ESM::Land::DATA_VHGT | ESM::Land::DATA_VNML | ESM::Land::DATA_VCLR | ESM::Land::DATA_VTEX);
land.loadData (mask);
return &land;
} }
const ESM::LandTexture* TerrainStorage::getLandTexture(int index, short plugin) const ESM::LandTexture* TerrainStorage::getLandTexture(int index, short plugin)

@ -18,7 +18,7 @@ namespace CSVRender
private: private:
const CSMWorld::Data& mData; 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 const ESM::LandTexture* getLandTexture(int index, short plugin);
virtual void getBounds(float& minX, float& maxX, float& minY, float& maxY); virtual void getBounds(float& minX, float& maxX, float& minY, float& maxY);

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

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

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

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

@ -15,6 +15,7 @@
namespace osg namespace osg
{ {
class Group; class Group;
class Object;
} }
namespace MWRender namespace MWRender
@ -80,7 +81,7 @@ namespace MWPhysics
void updatePosition (const MWWorld::Ptr& ptr); 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); void removeHeightField (int x, int y);

@ -2,6 +2,7 @@
#include <iostream> #include <iostream>
#include <osg/Material>
#include <osg/Fog> #include <osg/Fog>
#include <osg/BlendFunc> #include <osg/BlendFunc>
#include <osg/Texture2D> #include <osg/Texture2D>
@ -142,6 +143,13 @@ namespace MWRender
stateset->setMode(GL_LIGHTING, osg::StateAttribute::ON); stateset->setMode(GL_LIGHTING, osg::StateAttribute::ON);
stateset->setMode(GL_NORMALIZE, osg::StateAttribute::ON); stateset->setMode(GL_NORMALIZE, osg::StateAttribute::ON);
stateset->setMode(GL_CULL_FACE, 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 // assign large value to effectively turn off fog
// shaders don't respect glDisable(GL_FOG) // shaders don't respect glDisable(GL_FOG)
osg::ref_ptr<osg::Fog> fog (new osg::Fog); osg::ref_ptr<osg::Fog> fog (new osg::Fog);

@ -238,6 +238,9 @@ namespace MWRender
removeCamera(*it); removeCamera(*it);
for (CameraVector::iterator it = mActiveCameras.begin(); it != mActiveCameras.end(); ++it) for (CameraVector::iterator it = mActiveCameras.begin(); it != mActiveCameras.end(); ++it)
removeCamera(*it); removeCamera(*it);
if (mWorkItem)
mWorkItem->waitTillDone();
} }
void GlobalMap::render () 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); camera->setNodeMask(Mask_RenderToTexture);
osg::ref_ptr<osg::StateSet> stateset = new osg::StateSet; 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 // assign large value to effectively turn off fog
// shaders don't respect glDisable(GL_FOG) // shaders don't respect glDisable(GL_FOG)
osg::ref_ptr<osg::Fog> fog (new osg::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::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); 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()); setupRenderToTexture(camera, cell->getCell()->getGridX(), cell->getCell()->getGridY());
MapSegment& segment = mSegments[std::make_pair(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; class Group;
} }
namespace osgUtil
{
class IncrementalCompileOperation;
}
namespace Resource namespace Resource
{ {
class ResourceSystem; class ResourceSystem;

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

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

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

@ -6,12 +6,22 @@
#include "../mwbase/environment.hpp" #include "../mwbase/environment.hpp"
#include "../mwworld/esmstore.hpp" #include "../mwworld/esmstore.hpp"
#include "landmanager.hpp"
namespace MWRender namespace MWRender
{ {
TerrainStorage::TerrainStorage(const VFS::Manager* vfs, const std::string& normalMapPattern, const std::string& normalHeightMapPattern, bool autoUseNormalMaps, const std::string& specularMapPattern, bool autoUseSpecularMaps) TerrainStorage::TerrainStorage(Resource::ResourceSystem* resourceSystem, const std::string& normalMapPattern, const std::string& normalHeightMapPattern, bool autoUseNormalMaps, const std::string& specularMapPattern, bool autoUseSpecularMaps)
: ESMTerrain::Storage(vfs, normalMapPattern, normalHeightMapPattern, autoUseNormalMaps, specularMapPattern, 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) void TerrainStorage::getBounds(float& minX, float& maxX, float& minY, float& maxY)
@ -21,17 +31,17 @@ namespace MWRender
const MWWorld::ESMStore &esmStore = const MWWorld::ESMStore &esmStore =
MWBase::Environment::get().getWorld()->getStore(); MWBase::Environment::get().getWorld()->getStore();
MWWorld::Store<ESM::Cell>::iterator it = esmStore.get<ESM::Cell>().extBegin(); MWWorld::Store<ESM::Land>::iterator it = esmStore.get<ESM::Land>().begin();
for (; it != esmStore.get<ESM::Cell>().extEnd(); ++it) for (; it != esmStore.get<ESM::Land>().end(); ++it)
{ {
if (it->getGridX() < minX) if (it->mX < minX)
minX = static_cast<float>(it->getGridX()); minX = static_cast<float>(it->mX);
if (it->getGridX() > maxX) if (it->mX > maxX)
maxX = static_cast<float>(it->getGridX()); maxX = static_cast<float>(it->mX);
if (it->getGridY() < minY) if (it->mY < minY)
minY = static_cast<float>(it->getGridY()); minY = static_cast<float>(it->mY);
if (it->getGridY() > maxY) if (it->mY > maxY)
maxY = static_cast<float>(it->getGridY()); maxY = static_cast<float>(it->mY);
} }
// since grid coords are at cell origin, we need to add 1 cell // since grid coords are at cell origin, we need to add 1 cell
@ -39,22 +49,14 @@ namespace MWRender
maxY += 1; maxY += 1;
} }
const ESM::Land* TerrainStorage::getLand(int cellX, int cellY) LandManager *TerrainStorage::getLandManager() const
{ {
const MWWorld::ESMStore &esmStore = return mLandManager.get();
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 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) const ESM::LandTexture* TerrainStorage::getLandTexture(int index, short plugin)
@ -64,4 +66,5 @@ namespace MWRender
return esmStore.get<ESM::LandTexture>().search(index, plugin); return esmStore.get<ESM::LandTexture>().search(index, plugin);
} }
} }

@ -1,23 +1,37 @@
#ifndef MWRENDER_TERRAINSTORAGE_H #ifndef MWRENDER_TERRAINSTORAGE_H
#define MWRENDER_TERRAINSTORAGE_H #define MWRENDER_TERRAINSTORAGE_H
#include <memory>
#include <components/esmterrain/storage.hpp> #include <components/esmterrain/storage.hpp>
#include <components/resource/resourcesystem.hpp>
namespace MWRender namespace MWRender
{ {
class LandManager;
/// @brief Connects the ESM Store used in OpenMW with the ESMTerrain storage. /// @brief Connects the ESM Store used in OpenMW with the ESMTerrain storage.
class TerrainStorage : public 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: 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 /// Get bounds of the whole terrain in cell units
virtual void getBounds(float& minX, float& maxX, float& minY, float& maxY); virtual void getBounds(float& minX, float& maxX, float& minY, float& maxY);
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 // Set on cameras within the main scene graph
Mask_RenderToTexture = (1<<15), Mask_RenderToTexture = (1<<15),
Mask_PreCompile = (1<<16),
// Set on a camera's cull mask to enable the LightManager // 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/bulletshapemanager.hpp>
#include <components/resource/keyframemanager.hpp> #include <components/resource/keyframemanager.hpp>
#include <components/misc/resourcehelpers.hpp> #include <components/misc/resourcehelpers.hpp>
#include <components/misc/stringops.hpp>
#include <components/nifosg/nifloader.hpp> #include <components/nifosg/nifloader.hpp>
#include <components/terrain/world.hpp> #include <components/terrain/world.hpp>
#include <components/esmterrain/storage.hpp>
#include <components/sceneutil/unrefqueue.hpp> #include <components/sceneutil/unrefqueue.hpp>
#include <components/esm/loadcell.hpp>
#include "../mwbase/environment.hpp" #include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp" #include "../mwbase/world.hpp"
#include "../mwworld/inventorystore.hpp" #include "../mwrender/landmanager.hpp"
#include "../mwworld/esmstore.hpp"
#include "cellstore.hpp" #include "cellstore.hpp"
#include "manualref.hpp" #include "manualref.hpp"
@ -46,7 +48,7 @@ namespace MWWorld
{ {
public: public:
/// Constructor to be called from the main thread. /// 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()) : mIsExterior(cell->getCell()->isExterior())
, mX(cell->getCell()->getGridX()) , mX(cell->getCell()->getGridX())
, mY(cell->getCell()->getGridY()) , mY(cell->getCell()->getGridY())
@ -54,9 +56,12 @@ namespace MWWorld
, mBulletShapeManager(bulletShapeManager) , mBulletShapeManager(bulletShapeManager)
, mKeyframeManager(keyframeManager) , mKeyframeManager(keyframeManager)
, mTerrain(terrain) , mTerrain(terrain)
, mLandManager(landManager)
, mPreloadInstances(preloadInstances) , mPreloadInstances(preloadInstances)
, mAbort(false) , mAbort(false)
{ {
mTerrainView = mTerrain->createView();
ListModelsVisitor visitor (mMeshes); ListModelsVisitor visitor (mMeshes);
if (cell->getState() == MWWorld::CellStore::State_Loaded) if (cell->getState() == MWWorld::CellStore::State_Loaded)
{ {
@ -89,7 +94,8 @@ namespace MWWorld
{ {
try 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) catch(std::exception& e)
{ {
@ -151,10 +157,13 @@ namespace MWWorld
Resource::BulletShapeManager* mBulletShapeManager; Resource::BulletShapeManager* mBulletShapeManager;
Resource::KeyframeManager* mKeyframeManager; Resource::KeyframeManager* mKeyframeManager;
Terrain::World* mTerrain; Terrain::World* mTerrain;
MWRender::LandManager* mLandManager;
bool mPreloadInstances; bool mPreloadInstances;
volatile bool mAbort; 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 // 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; std::vector<osg::ref_ptr<const osg::Object> > mPreloadedObjects;
}; };
@ -163,30 +172,27 @@ namespace MWWorld
class UpdateCacheItem : public SceneUtil::WorkItem class UpdateCacheItem : public SceneUtil::WorkItem
{ {
public: public:
UpdateCacheItem(Resource::ResourceSystem* resourceSystem, Terrain::World* terrain, double referenceTime) UpdateCacheItem(Resource::ResourceSystem* resourceSystem, double referenceTime)
: mReferenceTime(referenceTime) : mReferenceTime(referenceTime)
, mResourceSystem(resourceSystem) , mResourceSystem(resourceSystem)
, mTerrain(terrain)
{ {
} }
virtual void doWork() virtual void doWork()
{ {
mResourceSystem->updateCache(mReferenceTime); mResourceSystem->updateCache(mReferenceTime);
mTerrain->updateCache();
} }
private: private:
double mReferenceTime; double mReferenceTime;
Resource::ResourceSystem* mResourceSystem; 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) : mResourceSystem(resourceSystem)
, mBulletShapeManager(bulletShapeManager) , mBulletShapeManager(bulletShapeManager)
, mTerrain(terrain) , mTerrain(terrain)
, mLandManager(landManager)
, mExpiryDelay(0.0) , mExpiryDelay(0.0)
, mMinCacheSize(0) , mMinCacheSize(0)
, mMaxCacheSize(0) , mMaxCacheSize(0)
@ -197,11 +203,25 @@ namespace MWWorld
CellPreloader::~CellPreloader() 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(); it->second.mWorkItem->abort();
for (PreloadMap::iterator it = mPreloadCells.begin(); it != mPreloadCells.end();++it)
it->second.mWorkItem->waitTillDone(); it->second.mWorkItem->waitTillDone();
}
mPreloadCells.clear(); mPreloadCells.clear();
} }
@ -250,7 +270,7 @@ namespace MWWorld
return; 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); mWorkQueue->addWorkItem(item);
mPreloadCells[cell] = PreloadEntry(timestamp, item); mPreloadCells[cell] = PreloadEntry(timestamp, item);
@ -303,10 +323,11 @@ namespace MWWorld
++it; ++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 // 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; mLastResourceCacheUpdate = timestamp;
} }
} }
@ -346,4 +367,64 @@ namespace MWWorld
mUnrefQueue = unrefQueue; 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 <map>
#include <osg/ref_ptr> #include <osg/ref_ptr>
#include <osg/Vec3f>
#include <components/sceneutil/workqueue.hpp> #include <components/sceneutil/workqueue.hpp>
namespace Resource namespace Resource
@ -14,6 +15,7 @@ namespace Resource
namespace Terrain namespace Terrain
{ {
class World; class World;
class View;
} }
namespace SceneUtil namespace SceneUtil
@ -21,6 +23,11 @@ namespace SceneUtil
class UnrefQueue; class UnrefQueue;
} }
namespace MWRender
{
class LandManager;
}
namespace MWWorld namespace MWWorld
{ {
class CellStore; class CellStore;
@ -28,7 +35,7 @@ namespace MWWorld
class CellPreloader class CellPreloader
{ {
public: 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(); ~CellPreloader();
/// Ask a background thread to preload rendering meshes and collision shapes for objects in this cell. /// 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 setUnrefQueue(SceneUtil::UnrefQueue* unrefQueue);
void setTerrainPreloadPositions(const std::vector<osg::Vec3f>& positions);
private: private:
Resource::ResourceSystem* mResourceSystem; Resource::ResourceSystem* mResourceSystem;
Resource::BulletShapeManager* mBulletShapeManager; Resource::BulletShapeManager* mBulletShapeManager;
Terrain::World* mTerrain; Terrain::World* mTerrain;
MWRender::LandManager* mLandManager;
osg::ref_ptr<SceneUtil::WorkQueue> mWorkQueue; osg::ref_ptr<SceneUtil::WorkQueue> mWorkQueue;
osg::ref_ptr<SceneUtil::UnrefQueue> mUnrefQueue; osg::ref_ptr<SceneUtil::UnrefQueue> mUnrefQueue;
double mExpiryDelay; double mExpiryDelay;
@ -92,6 +102,11 @@ namespace MWWorld
// Cells that are currently being preloaded, or have already finished preloading // Cells that are currently being preloaded, or have already finished preloading
PreloadMap mPreloadCells; 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 "../mwbase/windowmanager.hpp"
#include "../mwrender/renderingmanager.hpp" #include "../mwrender/renderingmanager.hpp"
#include "../mwrender/landmanager.hpp"
#include "../mwphysics/physicssystem.hpp" #include "../mwphysics/physicssystem.hpp"
@ -211,14 +212,11 @@ namespace MWWorld
void Scene::update (float duration, bool paused) void Scene::update (float duration, bool paused)
{ {
if (mPreloadEnabled) mPreloadTimer += duration;
if (mPreloadTimer > 0.1f)
{ {
mPreloadTimer += duration; preloadCells(0.1f);
if (mPreloadTimer > 0.1f) mPreloadTimer = 0.f;
{
preloadCells(0.1f);
mPreloadTimer = 0.f;
}
} }
mRendering.update (duration, paused); mRendering.update (duration, paused);
@ -284,26 +282,19 @@ namespace MWWorld
// Load terrain physics first... // Load terrain physics first...
if (cell->getCell()->isExterior()) if (cell->getCell()->isExterior())
{ {
const ESM::Land* land = int cellX = cell->getCell()->getGridX();
MWBase::Environment::get().getWorld()->getStore().get<ESM::Land>().search( int cellY = cell->getCell()->getGridY();
cell->getCell()->getGridX(), osg::ref_ptr<const ESMTerrain::LandObject> land = mRendering.getLandManager()->getLand(cellX, cellY);
cell->getCell()->getGridY() const ESM::Land::LandData* data = land ? land->getData(ESM::Land::DATA_VHGT) : 0;
); if (data)
if (land && land->mDataTypes&ESM::Land::DATA_VHGT) { {
// Actually only VHGT is needed here, but we'll need the rest for rendering anyway. mPhysics->addHeightField (data->mHeights, cellX, cell->getCell()->getGridY(), worldsize / (verts-1), verts, data->mMinHeight, data->mMaxHeight, land.get());
// 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);
} }
else else
{ {
static std::vector<float> defaultHeight; static std::vector<float> defaultHeight;
defaultHeight.resize(verts*verts, ESM::Land::DEFAULT_HEIGHT); defaultHeight.resize(verts*verts, ESM::Land::DEFAULT_HEIGHT);
mPhysics->addHeightField (&defaultHeight[0], cell->getCell()->getGridX(), cell->getCell()->getGridY(), mPhysics->addHeightField (&defaultHeight[0], cell->getCell()->getGridX(), cell->getCell()->getGridY(), worldsize / (verts-1), verts, ESM::Land::DEFAULT_HEIGHT, ESM::Land::DEFAULT_HEIGHT, land.get());
worldsize / (verts-1), verts);
} }
} }
@ -379,7 +370,6 @@ namespace MWWorld
int newX, newY; int newX, newY;
MWBase::Environment::get().getWorld()->positionToIndex(pos.x(), pos.y(), newX, newY); MWBase::Environment::get().getWorld()->positionToIndex(pos.x(), pos.y(), newX, newY);
changeCellGrid(newX, newY); changeCellGrid(newX, newY);
//mRendering.updateTerrain();
} }
} }
@ -388,8 +378,6 @@ namespace MWWorld
Loading::Listener* loadingListener = MWBase::Environment::get().getWindowManager()->getLoadingScreen(); Loading::Listener* loadingListener = MWBase::Environment::get().getWindowManager()->getLoadingScreen();
Loading::ScopedLoad load(loadingListener); Loading::ScopedLoad load(loadingListener);
//mRendering.enableTerrain(true);
std::string loadingExteriorText = "#{sLoadingMessage3}"; std::string loadingExteriorText = "#{sLoadingMessage3}";
loadingListener->setLabel(loadingExteriorText); loadingListener->setLabel(loadingExteriorText);
@ -488,6 +476,8 @@ namespace MWWorld
{ {
mCurrentCell = cell; mCurrentCell = cell;
mRendering.enableTerrain(cell->isExterior());
MWBase::World *world = MWBase::Environment::get().getWorld(); MWBase::World *world = MWBase::Environment::get().getWorld();
MWWorld::Ptr old = world->getPlayerPtr(); MWWorld::Ptr old = world->getPlayerPtr();
world->getPlayer().setCell(cell); world->getPlayer().setCell(cell);
@ -529,7 +519,7 @@ namespace MWWorld
, mPreloadDoors(Settings::Manager::getBool("preload doors", "Cells")) , mPreloadDoors(Settings::Manager::getBool("preload doors", "Cells"))
, mPreloadFastTravel(Settings::Manager::getBool("preload fast travel", "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->setWorkQueue(mRendering.getWorkQueue());
mPreloader->setUnrefQueue(rendering.getUnrefQueue()); mPreloader->setUnrefQueue(rendering.getUnrefQueue());
@ -571,8 +561,6 @@ namespace MWWorld
loadingListener->setLabel(loadingInteriorText); loadingListener->setLabel(loadingInteriorText);
Loading::ScopedLoad load(loadingListener); Loading::ScopedLoad load(loadingListener);
//mRendering.enableTerrain(false);
if(!loadcell) if(!loadcell)
{ {
MWBase::World *world = MWBase::Environment::get().getWorld(); MWBase::World *world = MWBase::Environment::get().getWorld();
@ -648,8 +636,6 @@ namespace MWWorld
CellStore* current = MWBase::Environment::get().getWorld()->getExterior(x, y); CellStore* current = MWBase::Environment::get().getWorld()->getExterior(x, y);
changePlayerCell(current, position, adjustPlayerPos); changePlayerCell(current, position, adjustPlayerPos);
//mRendering.updateTerrain();
} }
CellStore* Scene::getCurrentCell () CellStore* Scene::getCurrentCell ()
@ -753,22 +739,32 @@ namespace MWWorld
void Scene::preloadCells(float dt) void Scene::preloadCells(float dt)
{ {
std::vector<osg::Vec3f> exteriorPositions;
const MWWorld::ConstPtr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); const MWWorld::ConstPtr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
osg::Vec3f playerPos = player.getRefData().getPosition().asVec3(); osg::Vec3f playerPos = player.getRefData().getPosition().asVec3();
osg::Vec3f moved = playerPos - mLastPlayerPos; osg::Vec3f moved = playerPos - mLastPlayerPos;
osg::Vec3f predictedPos = playerPos + moved / dt; osg::Vec3f predictedPos = playerPos + moved / dt;
if (mCurrentCell->isExterior())
exteriorPositions.push_back(predictedPos);
mLastPlayerPos = playerPos; mLastPlayerPos = playerPos;
if (mPreloadDoors) if (mPreloadEnabled)
preloadTeleportDoorDestinations(playerPos, predictedPos); {
if (mPreloadExteriorGrid) if (mPreloadDoors)
preloadExteriorGrid(playerPos, predictedPos); preloadTeleportDoorDestinations(playerPos, predictedPos, exteriorPositions);
if (mPreloadFastTravel) if (mPreloadExteriorGrid)
preloadFastTravelDestinations(playerPos, predictedPos); 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; std::vector<MWWorld::ConstPtr> teleportDoors;
for (CellStoreCollection::const_iterator iter (mActiveCells.begin()); for (CellStoreCollection::const_iterator iter (mActiveCells.begin());
@ -800,9 +796,11 @@ namespace MWWorld
preloadCell(MWBase::Environment::get().getWorld()->getInterior(door.getCellRef().getDestCell())); preloadCell(MWBase::Environment::get().getWorld()->getInterior(door.getCellRef().getDestCell()));
else else
{ {
osg::Vec3f pos = door.getCellRef().getDoorDest().asVec3();
int x,y; 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); preloadCell(MWBase::Environment::get().getWorld()->getExterior(x,y), true);
exteriorPositions.push_back(pos);
} }
} }
catch (std::exception& e) catch (std::exception& e)
@ -868,6 +866,13 @@ namespace MWWorld
mPreloader->preload(cell, mRendering.getReferenceTime()); 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 struct ListFastTravelDestinationsVisitor
{ {
ListFastTravelDestinationsVisitor(float preloadDist, const osg::Vec3f& playerPos) ListFastTravelDestinationsVisitor(float preloadDist, const osg::Vec3f& playerPos)
@ -898,7 +903,7 @@ namespace MWWorld
std::vector<ESM::Transport::Dest> mList; 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(); const MWWorld::ConstPtr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
ListFastTravelDestinationsVisitor listVisitor(mPreloadDistance, player.getRefData().getPosition().asVec3()); ListFastTravelDestinationsVisitor listVisitor(mPreloadDistance, player.getRefData().getPosition().asVec3());
@ -916,9 +921,11 @@ namespace MWWorld
preloadCell(MWBase::Environment::get().getWorld()->getInterior(it->mCellName)); preloadCell(MWBase::Environment::get().getWorld()->getInterior(it->mCellName));
else else
{ {
osg::Vec3f pos = it->mPos.asVec3();
int x,y; 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); 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 getGridCenter(int& cellX, int& cellY);
void preloadCells(float dt); 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 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: public:
@ -89,6 +89,7 @@ namespace MWWorld
~Scene(); ~Scene();
void preloadCell(MWWorld::CellStore* cell, bool preloadSurrounding=false); void preloadCell(MWWorld::CellStore* cell, bool preloadSurrounding=false);
void preloadTerrain(const osg::Vec3f& pos);
void unloadCell (CellStoreCollection::iterator iter); void unloadCell (CellStoreCollection::iterator iter);

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

@ -118,7 +118,7 @@ add_component_dir (translation
) )
add_component_dir (terrain 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 add_component_dir (loadinglistener
@ -243,7 +243,6 @@ target_link_libraries(components
${OSGVIEWER_LIBRARIES} ${OSGVIEWER_LIBRARIES}
${OSGTEXT_LIBRARIES} ${OSGTEXT_LIBRARIES}
${OSGGA_LIBRARIES} ${OSGGA_LIBRARIES}
${OSGFX_LIBRARIES}
${OSGANIMATION_LIBRARIES} ${OSGANIMATION_LIBRARIES}
${Bullet_LIBRARIES} ${Bullet_LIBRARIES}
${SDL2_LIBRARIES} ${SDL2_LIBRARIES}

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

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

@ -16,6 +16,45 @@
namespace ESMTerrain 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; 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) 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) bool Storage::getMinMaxHeights(float size, const osg::Vec2f &center, float &min, float &max)
{ {
assert (size <= 1 && "Storage::getMinMaxHeights, chunk size should be <= 1 cell"); assert (size <= 1 && "Storage::getMinMaxHeights, chunk size should be <= 1 cell");
/// \todo investigate if min/max heights should be stored at load time in ESM::Land instead
osg::Vec2f origin = center - osg::Vec2f(size/2.f, size/2.f); osg::Vec2f origin = center - osg::Vec2f(size/2.f, size/2.f);
int cellX = static_cast<int>(std::floor(origin.x())); 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 endRow = startRow + size * (ESM::Land::LAND_SIZE-1) + 1;
int endColumn = startColumn + 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(); min = std::numeric_limits<float>::max();
max = -std::numeric_limits<float>::max(); max = -std::numeric_limits<float>::max();
@ -73,10 +104,10 @@ namespace ESMTerrain
min = defaultHeight; min = defaultHeight;
max = 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) while (col >= ESM::Land::LAND_SIZE-1)
{ {
@ -99,7 +130,9 @@ namespace ESMTerrain
row += ESM::Land::LAND_SIZE-1; 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.x() = data->mNormals[col*ESM::Land::LAND_SIZE*3+row*3];
normal.y() = data->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+1]; 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); 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; osg::Vec3f n1,n2,n3,n4;
fixNormal(n1, cellX, cellY, col+1, row); fixNormal(n1, cellX, cellY, col+1, row, cache);
fixNormal(n2, cellX, cellY, col-1, row); fixNormal(n2, cellX, cellY, col-1, row, cache);
fixNormal(n3, cellX, cellY, col, row+1); fixNormal(n3, cellX, cellY, col, row+1, cache);
fixNormal(n4, cellX, cellY, col, row-1); fixNormal(n4, cellX, cellY, col, row-1, cache);
normal = (n1+n2+n3+n4); normal = (n1+n2+n3+n4);
normal.normalize(); 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) if (col == ESM::Land::LAND_SIZE-1)
{ {
@ -134,7 +167,9 @@ namespace ESMTerrain
row = 0; 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.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; 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.g() = 1;
color.b() = 1; color.b() = 1;
} }
} }
void Storage::fillVertexBuffers (int lodLevel, float size, const osg::Vec2f& center, void Storage::fillVertexBuffers (int lodLevel, float size, const osg::Vec2f& center,
@ -174,15 +208,24 @@ namespace ESMTerrain
float vertY = 0; float vertY = 0;
float vertX = 0; float vertX = 0;
LandCache cache;
float vertY_ = 0; // of current cell corner float vertY_ = 0; // of current cell corner
for (int cellY = startCellY; cellY < startCellY + std::ceil(size); ++cellY) for (int cellY = startCellY; cellY < startCellY + std::ceil(size); ++cellY)
{ {
float vertX_ = 0; // of current cell corner float vertX_ = 0; // of current cell corner
for (int cellX = startCellX; cellX < startCellX + std::ceil(size); ++cellX) for (int cellX = startCellX; cellX < startCellX + std::ceil(size); ++cellX)
{ {
const ESM::Land::LandData *heightData = getLandData (cellX, cellY, ESM::Land::DATA_VHGT); const LandObject* land = getLand(cellX, cellY, cache);
const ESM::Land::LandData *normalData = getLandData (cellX, cellY, ESM::Land::DATA_VNML); const ESM::Land::LandData *heightData = 0;
const ESM::Land::LandData *colourData = getLandData (cellX, cellY, ESM::Land::DATA_VCLR); 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 rowStart = 0;
int colStart = 0; int colStart = 0;
@ -197,8 +240,8 @@ namespace ESMTerrain
// Only relevant for chunks smaller than (contained in) one cell // Only relevant for chunks smaller than (contained in) one cell
rowStart += (origin.x() - startCellX) * ESM::Land::LAND_SIZE; rowStart += (origin.x() - startCellX) * ESM::Land::LAND_SIZE;
colStart += (origin.y() - startCellY) * 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 rowEnd = std::min(static_cast<int>(rowStart + std::min(1.f, size) * (ESM::Land::LAND_SIZE-1) + 1), ESM::Land::LAND_SIZE);
int colEnd = colStart + std::min(1.f, size) * (ESM::Land::LAND_SIZE-1) + 1; 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_; vertY = vertY_;
for (int col=colStart; col<colEnd; col += increment) for (int col=colStart; col<colEnd; col += increment)
@ -235,11 +278,11 @@ namespace ESMTerrain
// Normals apparently don't connect seamlessly between cells // Normals apparently don't connect seamlessly between cells
if (col == ESM::Land::LAND_SIZE-1 || row == ESM::Land::LAND_SIZE-1) if (col == ESM::Land::LAND_SIZE-1 || row == ESM::Land::LAND_SIZE-1)
fixNormal(normal, cellX, cellY, col, row); fixNormal(normal, cellX, cellY, col, row, cache);
// some corner normals appear to be complete garbage (z < 0) // 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)) 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); assert(normal.z() > 0);
@ -259,7 +302,7 @@ namespace ESMTerrain
// Unlike normals, colors mostly connect seamlessly between cells, but not always... // Unlike normals, colors mostly connect seamlessly between cells, but not always...
if (col == ESM::Land::LAND_SIZE-1 || row == ESM::Land::LAND_SIZE-1) if (col == ESM::Land::LAND_SIZE-1 || row == ESM::Land::LAND_SIZE-1)
fixColour(color, cellX, cellY, col, row); fixColour(color, cellX, cellY, col, row, cache);
color.a() = 1; color.a() = 1;
@ -279,7 +322,7 @@ namespace ESMTerrain
} }
Storage::UniqueTextureId Storage::getVtexIndexAt(int cellX, int cellY, 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 // For the first/last row/column, we need to get the texture from the neighbour cell
// to get consistent blending at the borders // to get consistent blending at the borders
@ -289,7 +332,12 @@ namespace ESMTerrain
--cellX; --cellX;
x += ESM::Land::LAND_TEXTURE_SIZE; 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; ++cellY;
y -= ESM::Land::LAND_TEXTURE_SIZE; y -= ESM::Land::LAND_TEXTURE_SIZE;
@ -298,15 +346,17 @@ namespace ESMTerrain
assert(x<ESM::Land::LAND_TEXTURE_SIZE); assert(x<ESM::Land::LAND_TEXTURE_SIZE);
assert(y<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]; int tex = data->mTextures[y * ESM::Land::LAND_TEXTURE_SIZE + x];
if (tex == 0) if (tex == 0)
return std::make_pair(0,0); // vtex 0 is always the base texture, regardless of plugin 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) std::string Storage::getTextureName(UniqueTextureId id)
@ -332,10 +382,6 @@ namespace ESMTerrain
void Storage::getBlendmaps(float chunkSize, const osg::Vec2f &chunkCenter, void Storage::getBlendmaps(float chunkSize, const osg::Vec2f &chunkCenter,
bool pack, ImageVector &blendmaps, std::vector<Terrain::LayerInfo> &layerList) 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); osg::Vec2f origin = chunkCenter - osg::Vec2f(chunkSize/2.f, chunkSize/2.f);
int cellX = static_cast<int>(std::floor(origin.x())); int cellX = static_cast<int>(std::floor(origin.x()));
int cellY = static_cast<int>(std::floor(origin.y())); int cellY = static_cast<int>(std::floor(origin.y()));
@ -347,10 +393,6 @@ namespace ESMTerrain
int rowEnd = rowStart + chunkSize * (realTextureSize-1) + 1; int rowEnd = rowStart + chunkSize * (realTextureSize-1) + 1;
int colEnd = colStart + 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 // Save the used texture indices so we know the total number of textures
// and number of required blend maps // and number of required blend maps
std::set<UniqueTextureId> textureIndices; 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. // 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)); textureIndices.insert(std::make_pair(0,0));
LandCache cache;
for (int y=colStart; y<colEnd; ++y) for (int y=colStart; y<colEnd; ++y)
for (int x=rowStart; x<rowEnd; ++x) 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); textureIndices.insert(id);
} }
@ -399,7 +443,7 @@ namespace ESMTerrain
{ {
for (int x=0; x<blendmapSize; ++x) 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()); assert(textureIndicesMap.find(id) != textureIndicesMap.end());
int layerIndex = textureIndicesMap.find(id)->second; int layerIndex = textureIndicesMap.find(id)->second;
int blendIndex = (pack ? static_cast<int>(std::floor((layerIndex - 1) / 4.f)) : layerIndex - 1); 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 cellX = static_cast<int>(std::floor(worldPos.x() / 8192.f));
int cellY = static_cast<int>(std::floor(worldPos.y() / 8192.f)); int cellY = static_cast<int>(std::floor(worldPos.y() / 8192.f));
const ESM::Land* land = getLand(cellX, cellY); osg::ref_ptr<const LandObject> land = getLand(cellX, cellY);
if (!land || !(land->mDataTypes&ESM::Land::DATA_VHGT)) if (!land)
return defaultHeight;
const ESM::Land::LandData* data = land->getData(ESM::Land::DATA_VHGT);
if (!data)
return defaultHeight; return defaultHeight;
// Mostly lifted from Ogre::Terrain::getHeightAtTerrainPosition // Mostly lifted from Ogre::Terrain::getHeightAtTerrainPosition
@ -461,10 +509,10 @@ namespace ESMTerrain
*/ */
// Build all 4 positions in normalized cell space, using point-sampled height // Build all 4 positions in normalized cell space, using point-sampled height
osg::Vec3f v0 (startXTS, startYTS, getVertexHeight(land, startX, startY) / 8192.f); osg::Vec3f v0 (startXTS, startYTS, getVertexHeight(data, startX, startY) / 8192.f);
osg::Vec3f v1 (endXTS, startYTS, getVertexHeight(land, endX, startY) / 8192.f); osg::Vec3f v1 (endXTS, startYTS, getVertexHeight(data, endX, startY) / 8192.f);
osg::Vec3f v2 (endXTS, endYTS, getVertexHeight(land, endX, endY) / 8192.f); osg::Vec3f v2 (endXTS, endYTS, getVertexHeight(data, endX, endY) / 8192.f);
osg::Vec3f v3 (startXTS, endYTS, getVertexHeight(land, startX, endY) / 8192.f); osg::Vec3f v3 (startXTS, endYTS, getVertexHeight(data, startX, endY) / 8192.f);
// define this plane in terrain space // define this plane in terrain space
osg::Plane plane; osg::Plane plane;
// FIXME: deal with differing triangle alignment // 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(x < ESM::Land::LAND_SIZE);
assert(y < 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) Terrain::LayerInfo Storage::getLayerInfo(const std::string& texture)
@ -570,4 +630,9 @@ namespace ESMTerrain
return ESM::Land::LAND_SIZE; 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 namespace ESMTerrain
{ {
/// @brief Feeds data from ESM terrain records (ESM::Land, ESM::LandTexture) class LandCache;
/// into the terrain component, converting it on the fly as needed.
class Storage : public Terrain::Storage /// @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: private:
const ESM::Land* mLand;
int mLoadFlags;
// Not implemented in this class, because we need different Store implementations for game and editor ESM::Land::LandData mData;
virtual const ESM::Land* getLand (int cellX, int cellY)= 0; };
virtual const ESM::LandTexture* getLandTexture(int index, short plugin) = 0;
/// @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: public:
Storage(const VFS::Manager* vfs, const std::string& normalMapPattern = "", const std::string& normalHeightMapPattern = "", bool autoUseNormalMaps = false, const std::string& specularMapPattern = "", bool autoUseSpecularMaps = false); 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 // 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 /// Get bounds of the whole terrain in cell units
virtual void getBounds(float& minX, float& maxX, float& minY, float& maxY) = 0; 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 /// Get the number of vertices on one side for each cell. Should be (power of two)+1
virtual int getCellVertices(); virtual int getCellVertices();
virtual int getBlendmapScale(float chunkSize);
private: private:
const VFS::Manager* mVFS; const VFS::Manager* mVFS;
void fixNormal (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); 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); 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 // Since plugins can define new texture palettes, we need to know the plugin index too
// in order to retrieve the correct texture name. // in order to retrieve the correct texture name.
@ -103,7 +121,7 @@ namespace ESMTerrain
typedef std::pair<short, short> UniqueTextureId; typedef std::pair<short, short> UniqueTextureId;
UniqueTextureId getVtexIndexAt(int cellX, int cellY, UniqueTextureId getVtexIndexAt(int cellX, int cellY,
int x, int y); int x, int y, LandCache&);
std::string getTextureName (UniqueTextureId id); std::string getTextureName (UniqueTextureId id);
std::map<std::string, Terrain::LayerInfo> mLayerInfoMap; std::map<std::string, Terrain::LayerInfo> mLayerInfoMap;

@ -185,7 +185,7 @@ void BulletShapeManager::updateCache(double referenceTime)
mInstanceCache->removeUnreferencedObjectsInCache(); 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", mCache->getCacheSize());
stats->setAttribute(frameNumber, "Shape Instance", mInstanceCache->getCacheSize()); stats->setAttribute(frameNumber, "Shape Instance", mInstanceCache->getCacheSize());

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

@ -141,7 +141,7 @@ namespace Resource
return mWarningImage; 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()); stats->setAttribute(frameNumber, "Image", mCache->getCacheSize());
} }

@ -32,7 +32,7 @@ namespace Resource
osg::Image* getWarningImage(); osg::Image* getWarningImage();
void reportStats(unsigned int frameNumber, osg::Stats* stats); void reportStats(unsigned int frameNumber, osg::Stats* stats) const;
private: private:
osg::ref_ptr<osg::Image> mWarningImage; 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()); stats->setAttribute(frameNumber, "Keyframe", mCache->getCacheSize());
} }

@ -23,7 +23,7 @@ namespace Resource
/// @note Throws an exception if the resource is not found. /// @note Throws an exception if the resource is not found.
osg::ref_ptr<const NifOsg::KeyframeHolder> get(const std::string& name); 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()); stats->setAttribute(frameNumber, "Nif", mCache->getCacheSize());
} }

@ -23,7 +23,7 @@ namespace Resource
/// to be done in advance by other managers accessing the NifFileManager. /// to be done in advance by other managers accessing the NifFileManager.
Nif::NIFFilePtr get(const std::string& name); 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. */ /** call node->accept(nv); for all nodes in the objectCache. */
void accept(osg::NodeVisitor& nv); 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. */ /** Get the number of objects in the cache. */
unsigned int getCacheSize() const; unsigned int getCacheSize() const;

@ -33,7 +33,7 @@ namespace Resource
const VFS::Manager* getVFS() const; 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: protected:
const VFS::Manager* mVFS; const VFS::Manager* mVFS;

@ -85,9 +85,9 @@ namespace Resource
return mVFS; 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); (*it)->reportStats(frameNumber, stats);
} }

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

@ -621,6 +621,11 @@ namespace Resource
mIncrementalCompileOperation = ico; mIncrementalCompileOperation = ico;
} }
osgUtil::IncrementalCompileOperation *SceneManager::getIncrementalCompileOperation()
{
return mIncrementalCompileOperation.get();
}
Resource::ImageManager* SceneManager::getImageManager() Resource::ImageManager* SceneManager::getImageManager()
{ {
return mImageManager; return mImageManager;
@ -689,16 +694,16 @@ namespace Resource
void SceneManager::updateCache(double referenceTime) void SceneManager::updateCache(double referenceTime)
{ {
mSharedStateMutex.lock();
mSharedStateManager->prune();
mSharedStateMutex.unlock();
ResourceManager::updateCache(referenceTime); ResourceManager::updateCache(referenceTime);
mInstanceCache->removeUnreferencedObjectsInCache(); 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()); OpenThreads::ScopedLock<OpenThreads::Mutex> lock(*mIncrementalCompileOperation->getToCompiledMutex());

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

@ -259,7 +259,7 @@ void StatsHandler::setUpScene(osgViewer::ViewerBase *viewer)
_resourceStatsChildNum = _switch->getNumChildren(); _resourceStatsChildNum = _switch->getNumChildren();
_switch->addChild(group, false); _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]); int numLines = sizeof(statNames) / sizeof(statNames[0]);

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

@ -9,6 +9,11 @@
#include <osg/NodeVisitor> #include <osg/NodeVisitor>
#include <osg/observer_ptr> #include <osg/observer_ptr>
namespace osgUtil
{
class CullVisitor;
}
namespace SceneUtil 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 /// 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. /// starting point is to attach a LightListCallback to each game object's base node.
/// @note Not thread safe for CullThreadPerCamera threading mode. /// @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 class LightListCallback : public osg::NodeCallback
{ {
public: public:
@ -166,6 +172,8 @@ namespace SceneUtil
void operator()(osg::Node* node, osg::NodeVisitor* nv); void operator()(osg::Node* node, osg::NodeVisitor* nv);
bool pushLightState(osg::Node* node, osgUtil::CullVisitor* nv);
std::set<SceneUtil::LightSource*>& getIgnoredLightSources() { return mIgnoredLightSources; } std::set<SceneUtil::LightSource*>& getIgnoredLightSources() { return mIgnoredLightSources; }
private: private:

@ -329,8 +329,8 @@ InputWrapper::InputWrapper(SDL_Window* window, osg::ref_ptr<osgViewer::Viewer> v
SDL_GetWindowSize(mSDLWindow, &width, &height); SDL_GetWindowSize(mSDLWindow, &width, &height);
const int FUDGE_FACTOR_X = width; const int FUDGE_FACTOR_X = width/4;
const int FUDGE_FACTOR_Y = height; const int FUDGE_FACTOR_Y = height/4;
//warp the mouse if it's about to go outside the window //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 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 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); 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); osg::ref_ptr<osg::Vec2Array> uvs (new osg::Vec2Array);
uvs->reserve(vertexCount); 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), uvs->push_back(osg::Vec2f(col / static_cast<float>(numVerts-1),
((mNumVerts-1) - row) / static_cast<float>(mNumVerts-1))); ((numVerts-1) - row) / static_cast<float>(numVerts-1)));
} }
} }
// Assign a VBO here to enable state sharing between different Geometries. // Assign a VBO here to enable state sharing between different Geometries.
uvs->setVertexBufferObject(new osg::VertexBufferObject); uvs->setVertexBufferObject(new osg::VertexBufferObject);
mUvBufferMap[mNumVerts] = uvs; mUvBufferMap[numVerts] = uvs;
return 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); 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; osg::ref_ptr<osg::DrawElements> buffer;
if (verts*verts <= (0xffffu)) if (numVerts*numVerts <= (0xffffu))
buffer = createIndexBuffer<osg::DrawElementsUShort>(flags, verts); buffer = createIndexBuffer<osg::DrawElementsUShort>(flags, numVerts);
else else
buffer = createIndexBuffer<osg::DrawElementsUInt>(flags, verts); buffer = createIndexBuffer<osg::DrawElementsUInt>(flags, numVerts);
// Assign a EBO here to enable state sharing between different Geometries. // Assign a EBO here to enable state sharing between different Geometries.
buffer->setElementBufferObject(new osg::ElementBufferObject); buffer->setElementBufferObject(new osg::ElementBufferObject);
mIndexBufferMap[flags] = buffer; mIndexBufferMap[id] = buffer;
return buffer; return buffer;
} }

@ -14,28 +14,24 @@ namespace Terrain
class BufferCache class BufferCache
{ {
public: public:
BufferCache(unsigned int numVerts) : mNumVerts(numVerts) {}
/// @param flags first 4*4 bits are LOD deltas on each edge, respectively (4 bits each) /// @param flags first 4*4 bits are LOD deltas on each edge, respectively (4 bits each)
/// next 4 bits are LOD level of the index buffer (LOD 0 = don't omit any vertices) /// next 4 bits are LOD level of the index buffer (LOD 0 = don't omit any vertices)
/// @note Thread safe. /// @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. /// @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 // TODO: add releaseGLObjects() for our vertex/element buffer objects
private: private:
// Index buffers are shared across terrain batches where possible. There is one index buffer for each // Index buffers are shared across terrain batches where possible. There is one index buffer for each
// combination of LOD deltas and index buffer LOD we may need. // combination of LOD deltas and index buffer LOD we may need.
std::map<int, osg::ref_ptr<osg::DrawElements> > mIndexBufferMap; std::map<std::pair<int, int>, osg::ref_ptr<osg::DrawElements> > mIndexBufferMap;
OpenThreads::Mutex mIndexBufferMutex; OpenThreads::Mutex mIndexBufferMutex;
std::map<int, osg::ref_ptr<osg::Vec2Array> > mUvBufferMap; std::map<int, osg::ref_ptr<osg::Vec2Array> > mUvBufferMap;
OpenThreads::Mutex mUvBufferMutex; 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 "material.hpp"
#include <iostream>
#include <stdexcept> #include <stdexcept>
#include <osg/Depth> #include <osg/Depth>
@ -58,11 +57,14 @@ namespace Terrain
return depth; return depth;
} }
FixedFunctionTechnique::FixedFunctionTechnique(const std::vector<TextureLayer>& layers, 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) const std::vector<osg::ref_ptr<osg::Texture2D> > &blendmaps, int blendmapScale, float layerTileSize)
{ {
std::vector<osg::ref_ptr<osg::StateSet> > passes;
bool firstLayer = true; 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) for (std::vector<TextureLayer>::const_iterator it = layers.begin(); it != layers.end(); ++it)
{ {
osg::ref_ptr<osg::StateSet> stateset (new osg::StateSet); osg::ref_ptr<osg::StateSet> stateset (new osg::StateSet);
@ -70,138 +72,93 @@ namespace Terrain
if (!firstLayer) if (!firstLayer)
{ {
stateset->setMode(GL_BLEND, osg::StateAttribute::ON); stateset->setMode(GL_BLEND, osg::StateAttribute::ON);
stateset->setAttributeAndModes(getEqualDepth(), osg::StateAttribute::ON); stateset->setAttributeAndModes(getEqualDepth(), osg::StateAttribute::ON);
} }
int texunit = 0; 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->addUniform(new osg::Uniform("diffuseMap", texunit));
stateset->setTextureAttributeAndModes(texunit, getBlendmapTexMat(blendmapScale));
static osg::ref_ptr<osg::TexEnvCombine> texEnvCombine; if(!firstLayer)
if (!texEnvCombine)
{ {
texEnvCombine = new osg::TexEnvCombine; ++texunit;
texEnvCombine->setCombine_RGB(osg::TexEnvCombine::REPLACE); osg::ref_ptr<osg::Texture2D> blendmap = blendmaps.at(blendmapIndex++);
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);
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, if (it->mNormalMap)
const std::vector<osg::ref_ptr<osg::Texture2D> >& blendmaps, int blendmapScale, float layerTileSize) {
{ ++texunit;
bool firstLayer = true; stateset->setTextureAttributeAndModes(texunit, it->mNormalMap);
int i=0; stateset->addUniform(new osg::Uniform("normalMap", texunit));
for (std::vector<TextureLayer>::const_iterator it = layers.begin(); it != layers.end(); ++it) }
{
osg::ref_ptr<osg::StateSet> stateset (new osg::StateSet);
if (!firstLayer) Shader::ShaderManager::DefineMap defineMap;
{ defineMap["forcePPL"] = forcePerPixelLighting ? "1" : "0";
stateset->setMode(GL_BLEND, osg::StateAttribute::ON); defineMap["clamp"] = clampLighting ? "1" : "0";
stateset->setAttributeAndModes(getEqualDepth(), osg::StateAttribute::ON); 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, blendmap.get());
stateset->setTextureAttributeAndModes(texunit, it->mDiffuseMap);
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) stateset->setTextureAttributeAndModes(texunit, texEnvCombine, osg::StateAttribute::ON);
{
++texunit;
osg::ref_ptr<osg::Texture2D> blendmap = blendmaps.at(i++);
stateset->setTextureAttributeAndModes(texunit, blendmap.get()); ++texunit;
}
stateset->setTextureAttributeAndModes(texunit, getBlendmapTexMat(blendmapScale)); // Add the actual layer texture multiplied by the alpha map.
stateset->addUniform(new osg::Uniform("blendMap", texunit)); osg::ref_ptr<osg::Texture2D> tex = it->mDiffuseMap;
} stateset->setTextureAttributeAndModes(texunit, tex.get());
if (it->mNormalMap) if (layerTileSize != 1.f)
{ stateset->setTextureAttributeAndModes(texunit, getLayerTexMat(layerTileSize), osg::StateAttribute::ON);
++texunit;
stateset->setTextureAttributeAndModes(texunit, it->mNormalMap);
stateset->addUniform(new osg::Uniform("normalMap", texunit));
} }
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; firstLayer = false;
addPass(stateset); stateset->setRenderBinDetails(passIndex++, "RenderBin");
}
}
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);
}
bool Effect::define_techniques() passes.push_back(stateset);
{
try
{
if (mUseShaders && mShaderManager)
addTechnique(new ShaderTechnique(*mShaderManager, mForcePerPixelLighting, mClampLighting, mLayers, mBlendmaps, mBlendmapScale, mLayerTileSize));
else
addTechnique(new FixedFunctionTechnique(mLayers, mBlendmaps, mBlendmapScale, mLayerTileSize));
} }
catch (std::exception& e) return passes;
{
std::cerr << "Error: " << e.what() << std::endl;
addTechnique(new FixedFunctionTechnique(mLayers, mBlendmaps, mBlendmapScale, mLayerTileSize));
}
return true;
} }
} }

@ -1,8 +1,7 @@
#ifndef COMPONENTS_TERRAIN_MATERIAL_H #ifndef COMPONENTS_TERRAIN_MATERIAL_H
#define COMPONENTS_TERRAIN_MATERIAL_H #define COMPONENTS_TERRAIN_MATERIAL_H
#include <osgFX/Technique> #include <osg/StateSet>
#include <osgFX/Effect>
#include "defs.hpp" #include "defs.hpp"
@ -27,60 +26,9 @@ namespace Terrain
bool mSpecular; bool mSpecular;
}; };
class FixedFunctionTechnique : public osgFX::Technique std::vector<osg::ref_ptr<osg::StateSet> > createPasses(bool useShaders, bool forcePerPixelLighting, bool clampLighting, Shader::ShaderManager* shaderManager,
{ const std::vector<TextureLayer>& layers,
public: const std::vector<osg::ref_ptr<osg::Texture2D> >& blendmaps, int blendmapScale, float layerTileSize);
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;
};
} }

@ -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 namespace Terrain
{ {
/// We keep storage of terrain data abstract here since we need different implementations for game and editor /// We keep storage of terrain data abstract here since we need different implementations for game and editor
/// @note The implementation must be thread safe.
class Storage class Storage
{ {
public: public:
@ -78,6 +79,8 @@ namespace Terrain
/// Get the number of vertices on one side for each cell. Should be (power of two)+1 /// Get the number of vertices on one side for each cell. Should be (power of two)+1
virtual int getCellVertices() = 0; 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 <memory>
#include <osg/Material> #include <osg/Group>
#include <OpenThreads/ScopedLock> #include "chunkmanager.hpp"
#include <components/resource/resourcesystem.hpp> namespace Terrain
#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
{ {
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) TerrainGrid::TerrainGrid(osg::Group* parent, osg::Group* compileRoot, Resource::ResourceSystem* resourceSystem, Storage* storage, int nodeMask, int preCompileMask)
: Terrain::World(parent, resourceSystem, ico, storage, nodeMask) : Terrain::World(parent, compileRoot, resourceSystem, storage, nodeMask, preCompileMask)
, mNumSplits(4) , 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() 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)
{ {
{ osg::Vec2f center(x+0.5f, y+0.5f);
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(mGridCacheMutex); static_cast<MyView*>(view)->mLoaded = buildTerrain(NULL, 1.f, center);
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::ref_ptr<osg::Node> TerrainGrid::buildTerrain (osg::Group* parent, float chunkSize, const osg::Vec2f& chunkCenter) 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 else
{ {
float minH, maxH; osg::ref_ptr<osg::Node> node = mChunkManager->getChunk(chunkSize, chunkCenter, 0, 0);
if (!mStorage->getMinMaxHeights(chunkSize, chunkCenter, minH, maxH)) if (!node)
return NULL; // no terrain defined return NULL;
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));
if (parent) if (parent)
parent->addChild(transform); parent->addChild(node);
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);
}
}
std::vector<osg::ref_ptr<osg::Texture2D> > blendmapTextures; return node;
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;
} }
} }
@ -240,27 +70,10 @@ void TerrainGrid::loadCell(int x, int y)
if (mGrid.find(std::make_pair(x, y)) != mGrid.end()) if (mGrid.find(std::make_pair(x, y)) != mGrid.end())
return; // already loaded return; // already loaded
// try to get it from the cache osg::Vec2f center(x+0.5f, y+0.5f);
osg::ref_ptr<osg::Node> terrainNode; osg::ref_ptr<osg::Node> terrainNode = buildTerrain(NULL, 1.f, center);
{
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
if (!terrainNode) if (!terrainNode)
{ return; // no terrain defined
osg::Vec2f center(x+0.5f, y+0.5f);
terrainNode = buildTerrain(NULL, 1.f, center);
if (!terrainNode)
return; // no terrain defined
}
mTerrainRoot->addChild(terrainNode); mTerrainRoot->addChild(terrainNode);
@ -276,54 +89,12 @@ void TerrainGrid::unloadCell(int x, int y)
osg::ref_ptr<osg::Node> terrainNode = it->second; osg::ref_ptr<osg::Node> terrainNode = it->second;
mTerrainRoot->removeChild(terrainNode); mTerrainRoot->removeChild(terrainNode);
if (mUnrefQueue.get())
mUnrefQueue->push(terrainNode);
mGrid.erase(it); mGrid.erase(it);
} }
void TerrainGrid::updateCache() View *TerrainGrid::createView()
{ {
{ return new MyView;
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());
}
} }
} }

@ -1,39 +1,23 @@
#ifndef COMPONENTS_TERRAIN_TERRAINGRID_H #ifndef COMPONENTS_TERRAIN_TERRAINGRID_H
#define COMPONENTS_TERRAIN_TERRAINGRID_H #define COMPONENTS_TERRAIN_TERRAINGRID_H
#include <map>
#include <osg/Vec2f> #include <osg/Vec2f>
#include "world.hpp" #include "world.hpp"
namespace SceneUtil
{
class UnrefQueue;
}
namespace Shader
{
class ShaderManager;
}
namespace osg
{
class Texture2D;
}
namespace Terrain 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 class TerrainGrid : public Terrain::World
{ {
public: 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(); ~TerrainGrid();
/// Load a terrain cell and store it in cache for later use. virtual void cacheCell(View* view, int x, int y);
/// @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);
/// @note Not thread safe. /// @note Not thread safe.
virtual void loadCell(int x, int y); virtual void loadCell(int x, int y);
@ -41,15 +25,7 @@ namespace Terrain
/// @note Not thread safe. /// @note Not thread safe.
virtual void unloadCell(int x, int y); virtual void unloadCell(int x, int y);
/// Clear cached objects that are no longer referenced View* createView();
/// @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);
private: private:
osg::ref_ptr<osg::Node> buildTerrain (osg::Group* parent, float chunkSize, const osg::Vec2f& chunkCenter); 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 // split each ESM::Cell into mNumSplits*mNumSplits terrain chunks
unsigned int mNumSplits; 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; typedef std::map<std::pair<int, int>, osg::ref_ptr<osg::Node> > Grid;
Grid mGrid; 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 "world.hpp"
#include <osg/Group> #include <osg/Group>
#include <osgUtil/IncrementalCompileOperation> #include <osg/Material>
#include <osg/Camera>
#include <components/resource/resourcesystem.hpp>
#include "storage.hpp" #include "storage.hpp"
#include "texturemanager.hpp"
#include "chunkmanager.hpp"
#include "compositemaprenderer.hpp"
namespace Terrain namespace Terrain
{ {
World::World(osg::Group* parent, Resource::ResourceSystem* resourceSystem, osgUtil::IncrementalCompileOperation* ico, World::World(osg::Group* parent, osg::Group* compileRoot, Resource::ResourceSystem* resourceSystem, Storage* storage, int nodeMask, int preCompileMask)
Storage* storage, int nodeMask)
: mStorage(storage) : mStorage(storage)
, mParent(parent) , mParent(parent)
, mResourceSystem(resourceSystem) , mResourceSystem(resourceSystem)
, mIncrementalCompileOperation(ico)
{ {
mTerrainRoot = new osg::Group; mTerrainRoot = new osg::Group;
mTerrainRoot->setNodeMask(nodeMask); mTerrainRoot->setNodeMask(nodeMask);
mTerrainRoot->getOrCreateStateSet()->setRenderingHint(osg::StateSet::OPAQUE_BIN); 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"); 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); 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() World::~World()
{ {
mResourceSystem->removeResourceManager(mChunkManager.get());
mResourceSystem->removeResourceManager(mTextureManager.get());
mParent->removeChild(mTerrainRoot); mParent->removeChild(mTerrainRoot);
mCompositeMapCamera->removeChild(mCompositeMapRenderer);
mCompositeMapCamera->getParent(0)->removeChild(mCompositeMapCamera);
delete mStorage; delete mStorage;
} }
@ -35,4 +70,9 @@ float World::getHeightAt(const osg::Vec3f &worldPos)
return mStorage->getHeightAt(worldPos); return mStorage->getHeightAt(worldPos);
} }
void World::updateTextureFiltering()
{
mTextureManager->updateTextureFiltering();
}
} }

@ -2,19 +2,18 @@
#define COMPONENTS_TERRAIN_WORLD_H #define COMPONENTS_TERRAIN_WORLD_H
#include <osg/ref_ptr> #include <osg/ref_ptr>
#include <osg/Referenced>
#include <osg/Vec3f>
#include <memory>
#include "defs.hpp" #include "defs.hpp"
#include "buffercache.hpp"
namespace osg namespace osg
{ {
class Group; class Group;
class Stats; class Stats;
} class Node;
namespace osgUtil
{
class IncrementalCompileOperation;
} }
namespace Resource namespace Resource
@ -26,6 +25,23 @@ namespace Terrain
{ {
class Storage; 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 * @brief The basic interface for a terrain world. How the terrain chunks are paged and displayed
* is up to the implementation. * is up to the implementation.
@ -36,24 +52,41 @@ namespace Terrain
/// @note takes ownership of \a storage /// @note takes ownership of \a storage
/// @param storage Storage instance to get terrain data from (heights, normals, colors, textures..) /// @param storage Storage instance to get terrain data from (heights, normals, colors, textures..)
/// @param nodeMask mask for the terrain root /// @param nodeMask mask for the terrain root
World(osg::Group* parent, Resource::ResourceSystem* resourceSystem, osgUtil::IncrementalCompileOperation* ico, /// @param preCompileMask mask for pre compiling textures
Storage* storage, int nodeMask); World(osg::Group* parent, osg::Group* compileRoot, Resource::ResourceSystem* resourceSystem, Storage* storage, int nodeMask, int preCompileMask);
virtual ~World(); virtual ~World();
virtual void updateTextureFiltering() {} /// Apply the scene manager's texture filtering settings to all cached textures.
/// @note Thread safe.
virtual void updateCache() {} void updateTextureFiltering();
virtual void reportStats(unsigned int frameNumber, osg::Stats* stats) {}
float getHeightAt (const osg::Vec3f& worldPos); 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) {} 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 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; } Storage* getStorage() { return mStorage; }
protected: protected:
@ -62,9 +95,13 @@ namespace Terrain
osg::ref_ptr<osg::Group> mParent; osg::ref_ptr<osg::Group> mParent;
osg::ref_ptr<osg::Group> mTerrainRoot; osg::ref_ptr<osg::Group> mTerrainRoot;
osg::ref_ptr<osg::Group> mCompositeMapCamera;
osg::ref_ptr<CompositeMapRenderer> mCompositeMapRenderer;
Resource::ResourceSystem* mResourceSystem; 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. 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. 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 input
saves saves
sound sound
terrain
video video
water water
windows 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) # How long to keep models/textures/collision shapes in cache after they're no longer referenced/required (in seconds)
cache expiry delay = 5 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] [Map]
# Size of each exterior cell in pixels in the world map. (e.g. 12 to 24). # Size of each exterior cell in pixels in the world map. (e.g. 12 to 24).

Loading…
Cancel
Save