diff --git a/apps/opencs/view/render/cell.cpp b/apps/opencs/view/render/cell.cpp index adc038836..7cf542835 100644 --- a/apps/opencs/view/render/cell.cpp +++ b/apps/opencs/view/render/cell.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include "../../model/world/idtable.hpp" #include "../../model/world/columns.hpp" @@ -17,9 +18,13 @@ #include "../../model/world/cellcoordinates.hpp" #include "cellwater.hpp" +#include "cellborder.hpp" +#include "cellarrow.hpp" +#include "cellmarker.hpp" #include "mask.hpp" #include "pathgrid.hpp" #include "terrainstorage.hpp" +#include "object.hpp" bool CSVRender::Cell::removeObject (const std::string& id) { @@ -102,7 +107,7 @@ CSVRender::Cell::Cell (CSMWorld::Data& data, osg::Group* rootNode, const std::st if (esmLand.getLandData (ESM::Land::DATA_VHGT)) { - mTerrain.reset(new Terrain::TerrainGrid(mCellNode, data.getResourceSystem().get(), NULL, new TerrainStorage(mData), Mask_Terrain)); + mTerrain.reset(new Terrain::TerrainGrid(mCellNode, mCellNode, data.getResourceSystem().get(), new TerrainStorage(mData), Mask_Terrain)); mTerrain->loadCell(esmLand.mX, esmLand.mY); diff --git a/apps/opencs/view/render/cell.hpp b/apps/opencs/view/render/cell.hpp index 8f68e9f53..e8e0d2c63 100644 --- a/apps/opencs/view/render/cell.hpp +++ b/apps/opencs/view/render/cell.hpp @@ -4,19 +4,11 @@ #include #include #include - -#include +#include #include -#ifndef Q_MOC_RUN -#include -#endif - -#include "object.hpp" -#include "cellarrow.hpp" -#include "cellmarker.hpp" -#include "cellborder.hpp" +#include "../../model/world/cellcoordinates.hpp" class QModelIndex; @@ -30,7 +22,11 @@ namespace osg namespace CSMWorld { class Data; - class CellCoordinates; +} + +namespace Terrain +{ + class TerrainGrid; } namespace CSVRender @@ -38,6 +34,12 @@ namespace CSVRender class CellWater; class Pathgrid; class TagBase; + class Object; + + class CellArrow; + class CellBorder; + class CellMarker; + class CellWater; class Cell { diff --git a/apps/opencs/view/render/pagedworldspacewidget.cpp b/apps/opencs/view/render/pagedworldspacewidget.cpp index 901dadc85..e855f7e41 100644 --- a/apps/opencs/view/render/pagedworldspacewidget.cpp +++ b/apps/opencs/view/render/pagedworldspacewidget.cpp @@ -20,6 +20,7 @@ #include "editmode.hpp" #include "mask.hpp" #include "cameracontroller.hpp" +#include "cellarrow.hpp" bool CSVRender::PagedWorldspaceWidget::adjustCells() { diff --git a/apps/opencs/view/render/terrainstorage.cpp b/apps/opencs/view/render/terrainstorage.cpp index 2be4efd73..c63d41be3 100644 --- a/apps/opencs/view/render/terrainstorage.cpp +++ b/apps/opencs/view/render/terrainstorage.cpp @@ -9,7 +9,7 @@ namespace CSVRender { } - const ESM::Land* TerrainStorage::getLand(int cellX, int cellY) + osg::ref_ptr TerrainStorage::getLand(int cellX, int cellY) { std::ostringstream stream; stream << "#" << cellX << " " << cellY; @@ -21,9 +21,7 @@ namespace CSVRender return NULL; const ESM::Land& land = mData.getLand().getRecord(index).get(); - int mask = ESM::Land::DATA_VHGT | ESM::Land::DATA_VNML | ESM::Land::DATA_VCLR | ESM::Land::DATA_VTEX; - land.loadData (mask); - return &land; + return new ESMTerrain::LandObject(&land, ESM::Land::DATA_VHGT | ESM::Land::DATA_VNML | ESM::Land::DATA_VCLR | ESM::Land::DATA_VTEX); } const ESM::LandTexture* TerrainStorage::getLandTexture(int index, short plugin) diff --git a/apps/opencs/view/render/terrainstorage.hpp b/apps/opencs/view/render/terrainstorage.hpp index 16b0f3ec7..949311248 100644 --- a/apps/opencs/view/render/terrainstorage.hpp +++ b/apps/opencs/view/render/terrainstorage.hpp @@ -18,7 +18,7 @@ namespace CSVRender private: const CSMWorld::Data& mData; - virtual const ESM::Land* getLand (int cellX, int cellY); + virtual osg::ref_ptr getLand (int cellX, int cellY); virtual const ESM::LandTexture* getLandTexture(int index, short plugin); virtual void getBounds(float& minX, float& maxX, float& minY, float& maxY); diff --git a/apps/opencs/view/render/unpagedworldspacewidget.cpp b/apps/opencs/view/render/unpagedworldspacewidget.cpp index 03a97bf1a..de999db0f 100644 --- a/apps/opencs/view/render/unpagedworldspacewidget.cpp +++ b/apps/opencs/view/render/unpagedworldspacewidget.cpp @@ -16,6 +16,7 @@ #include "../widget/scenetooltoggle2.hpp" #include "mask.hpp" +#include "tagbase.hpp" void CSVRender::UnpagedWorldspaceWidget::update() { diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 2e90a5a7a..9db679db3 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -23,7 +23,7 @@ add_openmw_dir (mwrender actors objects renderingmanager animation rotatecontroller sky npcanimation vismask creatureanimation effectmanager util renderinginterface pathgrid rendermode weaponanimation bulletdebugdraw globalmap characterpreview camera localmap water terrainstorage ripplesimulation - renderbin actoranimation + renderbin actoranimation landmanager ) add_openmw_dir (mwinput diff --git a/apps/openmw/mwgui/loadingscreen.cpp b/apps/openmw/mwgui/loadingscreen.cpp index 35a825bcd..ca6a0b0a4 100644 --- a/apps/openmw/mwgui/loadingscreen.cpp +++ b/apps/openmw/mwgui/loadingscreen.cpp @@ -297,8 +297,8 @@ namespace MWGui // Turn off rendering except the GUI int oldUpdateMask = mViewer->getUpdateVisitor()->getTraversalMask(); int oldCullMask = mViewer->getCamera()->getCullMask(); - mViewer->getUpdateVisitor()->setTraversalMask(MWRender::Mask_GUI); - mViewer->getCamera()->setCullMask(MWRender::Mask_GUI); + mViewer->getUpdateVisitor()->setTraversalMask(MWRender::Mask_GUI|MWRender::Mask_PreCompile); + mViewer->getCamera()->setCullMask(MWRender::Mask_GUI|MWRender::Mask_PreCompile); MWBase::Environment::get().getInputManager()->update(0, true, true); diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 9844667ae..5e79e2a09 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -516,21 +516,11 @@ namespace MWPhysics class HeightField { public: - HeightField(const float* heights, int x, int y, float triSize, float sqrtVerts) + HeightField(const float* heights, int x, int y, float triSize, float sqrtVerts, float minH, float maxH, const osg::Object* holdObject) { - // find the minimum and maximum heights (needed for bullet) - float minh = heights[0]; - float maxh = heights[0]; - for(int i = 1;i < sqrtVerts*sqrtVerts;++i) - { - float h = heights[i]; - if(h > maxh) maxh = h; - if(h < minh) minh = h; - } - mShape = new btHeightfieldTerrainShape( sqrtVerts, sqrtVerts, heights, 1, - minh, maxh, 2, + minH, maxH, 2, PHY_FLOAT, false ); mShape->setUseDiamondSubdivision(true); @@ -539,11 +529,13 @@ namespace MWPhysics btTransform transform(btQuaternion::getIdentity(), btVector3((x+0.5f) * triSize * (sqrtVerts-1), (y+0.5f) * triSize * (sqrtVerts-1), - (maxh+minh)*0.5f)); + (maxH+minH)*0.5f)); mCollisionObject = new btCollisionObject; mCollisionObject->setCollisionShape(mShape); mCollisionObject->setWorldTransform(transform); + + mHoldObject = holdObject; } ~HeightField() { @@ -558,6 +550,7 @@ namespace MWPhysics private: btHeightfieldTerrainShape* mShape; btCollisionObject* mCollisionObject; + osg::ref_ptr mHoldObject; void operator=(const HeightField&); HeightField(const HeightField&); @@ -1140,9 +1133,9 @@ namespace MWPhysics return MovementSolver::traceDown(ptr, position, found->second, mCollisionWorld, maxHeight); } - void PhysicsSystem::addHeightField (const float* heights, int x, int y, float triSize, float sqrtVerts) + void PhysicsSystem::addHeightField (const float* heights, int x, int y, float triSize, float sqrtVerts, float minH, float maxH, const osg::Object* holdObject) { - HeightField *heightfield = new HeightField(heights, x, y, triSize, sqrtVerts); + HeightField *heightfield = new HeightField(heights, x, y, triSize, sqrtVerts, minH, maxH, holdObject); mHeightFields[std::make_pair(x,y)] = heightfield; mCollisionWorld->addCollisionObject(heightfield->getCollisionObject(), CollisionType_HeightMap, diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index 9730cda74..3312f10a0 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -15,6 +15,7 @@ namespace osg { class Group; + class Object; } namespace MWRender @@ -80,7 +81,7 @@ namespace MWPhysics void updatePosition (const MWWorld::Ptr& ptr); - void addHeightField (const float* heights, int x, int y, float triSize, float sqrtVerts); + void addHeightField (const float* heights, int x, int y, float triSize, float sqrtVerts, float minH, float maxH, const osg::Object* holdObject); void removeHeightField (int x, int y); diff --git a/apps/openmw/mwrender/characterpreview.cpp b/apps/openmw/mwrender/characterpreview.cpp index 84d7932d4..9dc1d6e77 100644 --- a/apps/openmw/mwrender/characterpreview.cpp +++ b/apps/openmw/mwrender/characterpreview.cpp @@ -2,6 +2,7 @@ #include +#include #include #include #include @@ -142,6 +143,13 @@ namespace MWRender stateset->setMode(GL_LIGHTING, osg::StateAttribute::ON); stateset->setMode(GL_NORMALIZE, osg::StateAttribute::ON); stateset->setMode(GL_CULL_FACE, osg::StateAttribute::ON); + osg::ref_ptr defaultMat (new osg::Material); + defaultMat->setColorMode(osg::Material::OFF); + defaultMat->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(1,1,1,1)); + defaultMat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(1,1,1,1)); + defaultMat->setSpecular(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f, 0.f, 0.f, 0.f)); + stateset->setAttribute(defaultMat); + // assign large value to effectively turn off fog // shaders don't respect glDisable(GL_FOG) osg::ref_ptr fog (new osg::Fog); diff --git a/apps/openmw/mwrender/globalmap.cpp b/apps/openmw/mwrender/globalmap.cpp index 2536818ef..24f6de6ce 100644 --- a/apps/openmw/mwrender/globalmap.cpp +++ b/apps/openmw/mwrender/globalmap.cpp @@ -238,6 +238,9 @@ namespace MWRender removeCamera(*it); for (CameraVector::iterator it = mActiveCameras.begin(); it != mActiveCameras.end(); ++it) removeCamera(*it); + + if (mWorkItem) + mWorkItem->waitTillDone(); } void GlobalMap::render () diff --git a/apps/openmw/mwrender/landmanager.cpp b/apps/openmw/mwrender/landmanager.cpp new file mode 100644 index 000000000..5edce2c3f --- /dev/null +++ b/apps/openmw/mwrender/landmanager.cpp @@ -0,0 +1,48 @@ +#include "landmanager.hpp" + +#include + +#include + +#include + +#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 LandManager::getLand(int x, int y) +{ + std::ostringstream id; + id << x << " " << y; + std::string idstr = id.str(); + + osg::ref_ptr obj = mCache->getRefFromObjectCache(idstr); + if (obj) + return static_cast(obj.get()); + else + { + const ESM::Land* land = MWBase::Environment::get().getWorld()->getStore().get().search(x,y); + if (!land) + return NULL; + osg::ref_ptr 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()); +} + + +} diff --git a/apps/openmw/mwrender/landmanager.hpp b/apps/openmw/mwrender/landmanager.hpp new file mode 100644 index 000000000..392a8b406 --- /dev/null +++ b/apps/openmw/mwrender/landmanager.hpp @@ -0,0 +1,33 @@ +#ifndef OPENMW_COMPONENTS_ESMTERRAIN_LANDMANAGER_H +#define OPENMW_COMPONENTS_ESMTERRAIN_LANDMANAGER_H + +#include + +#include +#include + +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 getLand(int x, int y); + + virtual void reportStats(unsigned int frameNumber, osg::Stats* stats) const; + + private: + int mLoadFlags; + }; + +} + +#endif diff --git a/apps/openmw/mwrender/localmap.cpp b/apps/openmw/mwrender/localmap.cpp index 0e854d5dc..02bc5d01f 100644 --- a/apps/openmw/mwrender/localmap.cpp +++ b/apps/openmw/mwrender/localmap.cpp @@ -174,9 +174,7 @@ osg::ref_ptr LocalMap::createOrthographicCamera(float x, float y, f camera->setNodeMask(Mask_RenderToTexture); osg::ref_ptr stateset = new osg::StateSet; - stateset->setMode(GL_LIGHTING, osg::StateAttribute::ON); - stateset->setMode(GL_NORMALIZE, osg::StateAttribute::ON); - stateset->setMode(GL_CULL_FACE, osg::StateAttribute::ON); + // assign large value to effectively turn off fog // shaders don't respect glDisable(GL_FOG) osg::ref_ptr fog (new osg::Fog); @@ -351,6 +349,11 @@ void LocalMap::requestExteriorMap(const MWWorld::CellStore* cell) osg::ref_ptr camera = createOrthographicCamera(x*mMapWorldSize + mMapWorldSize/2.f, y*mMapWorldSize + mMapWorldSize/2.f, mMapWorldSize, mMapWorldSize, osg::Vec3d(0,1,0), zmin, zmax); + camera->getOrCreateUserDataContainer()->addDescription("NoTerrainLod"); + std::ostringstream stream; + stream << x << " " << y; + camera->getOrCreateUserDataContainer()->addDescription(stream.str()); + setupRenderToTexture(camera, cell->getCell()->getGridX(), cell->getCell()->getGridY()); MapSegment& segment = mSegments[std::make_pair(cell->getCell()->getGridX(), cell->getCell()->getGridY())]; diff --git a/apps/openmw/mwrender/objects.hpp b/apps/openmw/mwrender/objects.hpp index cc66ae20d..8864e4483 100644 --- a/apps/openmw/mwrender/objects.hpp +++ b/apps/openmw/mwrender/objects.hpp @@ -15,11 +15,6 @@ namespace osg class Group; } -namespace osgUtil -{ - class IncrementalCompileOperation; -} - namespace Resource { class ResourceSystem; diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 5879c14c6..6ee9b8ae8 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -34,6 +34,7 @@ #include #include +#include #include #include @@ -212,11 +213,15 @@ namespace MWRender mWater.reset(new Water(mRootNode, sceneRoot, mResourceSystem, mViewer->getIncrementalCompileOperation(), fallback, resourcePath)); - mTerrain.reset(new Terrain::TerrainGrid(sceneRoot, mResourceSystem, mViewer->getIncrementalCompileOperation(), - new TerrainStorage(mResourceSystem->getVFS(), Settings::Manager::getString("normal map pattern", "Shaders"), Settings::Manager::getString("normal height map pattern", "Shaders"), - Settings::Manager::getBool("auto use terrain normal maps", "Shaders"), - Settings::Manager::getString("terrain specular map pattern", "Shaders"), Settings::Manager::getBool("auto use terrain specular maps", "Shaders")), - Mask_Terrain, &mResourceSystem->getSceneManager()->getShaderManager(), mUnrefQueue.get())); + const bool distantTerrain = Settings::Manager::getBool("distant terrain", "Terrain"); + mTerrainStorage = new TerrainStorage(mResourceSystem, Settings::Manager::getString("normal map pattern", "Shaders"), Settings::Manager::getString("normal height map pattern", "Shaders"), + Settings::Manager::getBool("auto use terrain normal maps", "Shaders"), Settings::Manager::getString("terrain specular map pattern", "Shaders"), + Settings::Manager::getBool("auto use terrain specular maps", "Shaders")); + + if (distantTerrain) + mTerrain.reset(new Terrain::QuadTreeWorld(sceneRoot, mRootNode, mResourceSystem, mTerrainStorage, Mask_Terrain, Mask_PreCompile)); + else + mTerrain.reset(new Terrain::TerrainGrid(sceneRoot, mRootNode, mResourceSystem, mTerrainStorage, Mask_Terrain, Mask_PreCompile)); mCamera.reset(new Camera(mViewer->getCamera())); @@ -420,6 +425,11 @@ namespace MWRender mWater->removeCell(store); } + void RenderingManager::enableTerrain(bool enable) + { + mTerrain->enable(enable); + } + void RenderingManager::setSkyEnabled(bool enabled) { mSky->setEnabled(enabled); @@ -493,9 +503,11 @@ namespace MWRender mCurrentCameraPos = cameraPos; if (mWater->isUnderwater(cameraPos)) { + float viewDistance = mViewDistance; + viewDistance = std::min(viewDistance, 6666.f); setFogColor(mUnderwaterColor * mUnderwaterWeight + mFogColor * (1.f-mUnderwaterWeight)); - mStateUpdater->setFogStart(mViewDistance * (1 - mUnderwaterFog)); - mStateUpdater->setFogEnd(mViewDistance); + mStateUpdater->setFogStart(viewDistance * (1 - mUnderwaterFog)); + mStateUpdater->setFogEnd(viewDistance); } else { @@ -718,18 +730,23 @@ namespace MWRender } - osg::ref_ptr createIntersectionVisitor(osgUtil::Intersector* intersector, bool ignorePlayer, bool ignoreActors) + osg::ref_ptr RenderingManager::getIntersectionVisitor(osgUtil::Intersector *intersector, bool ignorePlayer, bool ignoreActors) { - osg::ref_ptr intersectionVisitor( new osgUtil::IntersectionVisitor(intersector)); - int mask = intersectionVisitor->getTraversalMask(); + if (!mIntersectionVisitor) + mIntersectionVisitor = new osgUtil::IntersectionVisitor; + + mIntersectionVisitor->setTraversalNumber(mViewer->getFrameStamp()->getFrameNumber()); + mIntersectionVisitor->setIntersector(intersector); + + int mask = ~0; mask &= ~(Mask_RenderToTexture|Mask_Sky|Mask_Debug|Mask_Effect|Mask_Water|Mask_SimpleWater); if (ignorePlayer) mask &= ~(Mask_Player); if (ignoreActors) mask &= ~(Mask_Actor|Mask_Player); - intersectionVisitor->setTraversalMask(mask); - return intersectionVisitor; + mIntersectionVisitor->setTraversalMask(mask); + return mIntersectionVisitor; } RenderingManager::RayResult RenderingManager::castRay(const osg::Vec3f& origin, const osg::Vec3f& dest, bool ignorePlayer, bool ignoreActors) @@ -738,7 +755,7 @@ namespace MWRender origin, dest)); intersector->setIntersectionLimit(osgUtil::LineSegmentIntersector::LIMIT_NEAREST); - mRootNode->accept(*createIntersectionVisitor(intersector, ignorePlayer, ignoreActors)); + mRootNode->accept(*getIntersectionVisitor(intersector, ignorePlayer, ignoreActors)); return getIntersectionResult(intersector); } @@ -757,7 +774,7 @@ namespace MWRender intersector->setEnd(end); intersector->setIntersectionLimit(osgUtil::LineSegmentIntersector::LIMIT_NEAREST); - mViewer->getCamera()->accept(*createIntersectionVisitor(intersector, ignorePlayer, ignoreActors)); + mViewer->getCamera()->accept(*getIntersectionVisitor(intersector, ignorePlayer, ignoreActors)); return getIntersectionResult(intersector); } @@ -903,7 +920,7 @@ namespace MWRender mStateUpdater->setFogColor(color); } - void RenderingManager::reportStats() + void RenderingManager::reportStats() const { osg::Stats* stats = mViewer->getViewerStats(); unsigned int frameNumber = mViewer->getFrameStamp()->getFrameNumber(); @@ -1053,4 +1070,10 @@ namespace MWRender SceneUtil::writeScene(node, filename, format); } + LandManager *RenderingManager::getLandManager() const + { + return mTerrainStorage->getLandManager(); + } + + } diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index b1bb9fe0d..075357335 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -17,6 +17,12 @@ namespace osg class PositionAttitudeTransform; } +namespace osgUtil +{ + class IntersectionVisitor; + class Intersector; +} + namespace Resource { class ResourceSystem; @@ -59,6 +65,8 @@ namespace MWRender class Pathgrid; class Camera; class Water; + class TerrainStorage; + class LandManager; class RenderingManager : public MWRender::RenderingInterface { @@ -100,6 +108,8 @@ namespace MWRender void addCell(const MWWorld::CellStore* store); void removeCell(const MWWorld::CellStore* store); + void enableTerrain(bool enable); + void updatePtr(const MWWorld::Ptr& old, const MWWorld::Ptr& updated); void rotateObject(const MWWorld::Ptr& ptr, const osg::Quat& rot); @@ -190,13 +200,19 @@ namespace MWRender void exportSceneGraph(const MWWorld::Ptr& ptr, const std::string& filename, const std::string& format); + LandManager* getLandManager() const; + private: void updateProjectionMatrix(); void updateTextureFiltering(); void updateAmbient(); void setFogColor(const osg::Vec4f& color); - void reportStats(); + void reportStats() const; + + osg::ref_ptr getIntersectionVisitor(osgUtil::Intersector* intersector, bool ignorePlayer, bool ignoreActors); + + osg::ref_ptr mIntersectionVisitor; osg::ref_ptr mViewer; osg::ref_ptr mRootNode; @@ -212,6 +228,7 @@ namespace MWRender std::auto_ptr mObjects; std::auto_ptr mWater; std::auto_ptr mTerrain; + TerrainStorage* mTerrainStorage; std::auto_ptr mSky; std::auto_ptr mEffectManager; osg::ref_ptr mPlayerAnimation; diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index 13fbbc99c..09c7af6a2 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -428,10 +428,12 @@ private: class CelestialBody { public: - CelestialBody(osg::Group* parentNode, float scaleFactor, int numUvSets) + CelestialBody(osg::Group* parentNode, float scaleFactor, int numUvSets, unsigned int visibleMask=~0) + : mVisibleMask(visibleMask) { mGeom = createTexturedQuad(numUvSets); mTransform = new osg::PositionAttitudeTransform; + mTransform->setNodeMask(mVisibleMask); mTransform->setScale(osg::Vec3f(450,450,450) * scaleFactor); mTransform->addChild(mGeom); @@ -444,10 +446,11 @@ public: void setVisible(bool visible) { - mTransform->setNodeMask(visible ? ~0 : 0); + mTransform->setNodeMask(visible ? mVisibleMask : 0); } protected: + unsigned int mVisibleMask; static const float mDistance; osg::ref_ptr mTransform; osg::ref_ptr mGeom; @@ -459,11 +462,10 @@ class Sun : public CelestialBody { public: Sun(osg::Group* parentNode, Resource::ImageManager& imageManager) - : CelestialBody(parentNode, 1.0f, 1) + : CelestialBody(parentNode, 1.0f, 1, Mask_Sun) , mUpdater(new Updater) { mTransform->addUpdateCallback(mUpdater); - mTransform->setNodeMask(Mask_Sun); osg::ref_ptr sunTex (new osg::Texture2D(imageManager.getImage("textures/tx_sun_05.dds"))); sunTex->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); diff --git a/apps/openmw/mwrender/terrainstorage.cpp b/apps/openmw/mwrender/terrainstorage.cpp index 311eb1621..ff2a8bfc7 100644 --- a/apps/openmw/mwrender/terrainstorage.cpp +++ b/apps/openmw/mwrender/terrainstorage.cpp @@ -6,12 +6,22 @@ #include "../mwbase/environment.hpp" #include "../mwworld/esmstore.hpp" +#include "landmanager.hpp" + namespace MWRender { - TerrainStorage::TerrainStorage(const VFS::Manager* vfs, const std::string& normalMapPattern, const std::string& normalHeightMapPattern, bool autoUseNormalMaps, const std::string& specularMapPattern, bool autoUseSpecularMaps) - : ESMTerrain::Storage(vfs, normalMapPattern, normalHeightMapPattern, autoUseNormalMaps, specularMapPattern, autoUseSpecularMaps) + TerrainStorage::TerrainStorage(Resource::ResourceSystem* resourceSystem, const std::string& normalMapPattern, const std::string& normalHeightMapPattern, bool autoUseNormalMaps, const std::string& specularMapPattern, bool autoUseSpecularMaps) + : ESMTerrain::Storage(resourceSystem->getVFS(), normalMapPattern, normalHeightMapPattern, autoUseNormalMaps, specularMapPattern, autoUseSpecularMaps) + , mLandManager(new LandManager(ESM::Land::DATA_VCLR|ESM::Land::DATA_VHGT|ESM::Land::DATA_VNML|ESM::Land::DATA_VTEX)) + , mResourceSystem(resourceSystem) + { + mResourceSystem->addResourceManager(mLandManager.get()); + } + + TerrainStorage::~TerrainStorage() { + mResourceSystem->removeResourceManager(mLandManager.get()); } void TerrainStorage::getBounds(float& minX, float& maxX, float& minY, float& maxY) @@ -21,17 +31,17 @@ namespace MWRender const MWWorld::ESMStore &esmStore = MWBase::Environment::get().getWorld()->getStore(); - MWWorld::Store::iterator it = esmStore.get().extBegin(); - for (; it != esmStore.get().extEnd(); ++it) + MWWorld::Store::iterator it = esmStore.get().begin(); + for (; it != esmStore.get().end(); ++it) { - if (it->getGridX() < minX) - minX = static_cast(it->getGridX()); - if (it->getGridX() > maxX) - maxX = static_cast(it->getGridX()); - if (it->getGridY() < minY) - minY = static_cast(it->getGridY()); - if (it->getGridY() > maxY) - maxY = static_cast(it->getGridY()); + if (it->mX < minX) + minX = static_cast(it->mX); + if (it->mX > maxX) + maxX = static_cast(it->mX); + if (it->mY < minY) + minY = static_cast(it->mY); + if (it->mY > maxY) + maxY = static_cast(it->mY); } // since grid coords are at cell origin, we need to add 1 cell @@ -39,22 +49,14 @@ namespace MWRender maxY += 1; } - const ESM::Land* TerrainStorage::getLand(int cellX, int cellY) + LandManager *TerrainStorage::getLandManager() const { - const MWWorld::ESMStore &esmStore = - MWBase::Environment::get().getWorld()->getStore(); - - const ESM::Land* land = esmStore.get().search(cellX, cellY); - if (!land) - return NULL; - - const int flags = ESM::Land::DATA_VCLR|ESM::Land::DATA_VHGT|ESM::Land::DATA_VNML|ESM::Land::DATA_VTEX; - if (!land->isDataLoaded(flags)) - land->loadData(flags); - - // TODO: unload land data when it's no longer needed + return mLandManager.get(); + } - return land; + osg::ref_ptr TerrainStorage::getLand(int cellX, int cellY) + { + return mLandManager->getLand(cellX, cellY); } const ESM::LandTexture* TerrainStorage::getLandTexture(int index, short plugin) @@ -64,4 +66,5 @@ namespace MWRender return esmStore.get().search(index, plugin); } + } diff --git a/apps/openmw/mwrender/terrainstorage.hpp b/apps/openmw/mwrender/terrainstorage.hpp index 6fa8b98ce..cf4011d45 100644 --- a/apps/openmw/mwrender/terrainstorage.hpp +++ b/apps/openmw/mwrender/terrainstorage.hpp @@ -1,23 +1,37 @@ #ifndef MWRENDER_TERRAINSTORAGE_H #define MWRENDER_TERRAINSTORAGE_H +#include + #include +#include + namespace MWRender { + class LandManager; + /// @brief Connects the ESM Store used in OpenMW with the ESMTerrain storage. class TerrainStorage : public ESMTerrain::Storage { - private: - virtual const ESM::Land* getLand (int cellX, int cellY); - virtual const ESM::LandTexture* getLandTexture(int index, short plugin); public: - TerrainStorage(const VFS::Manager* vfs, const std::string& normalMapPattern = "", const std::string& normalHeightMapPatteern = "", bool autoUseNormalMaps = false, const std::string& specularMapPattern = "", bool autoUseSpecularMaps = false); + TerrainStorage(Resource::ResourceSystem* resourceSystem, const std::string& normalMapPattern = "", const std::string& normalHeightMapPatteern = "", bool autoUseNormalMaps = false, const std::string& specularMapPattern = "", bool autoUseSpecularMaps = false); + ~TerrainStorage(); + + virtual osg::ref_ptr getLand (int cellX, int cellY); + virtual const ESM::LandTexture* getLandTexture(int index, short plugin); /// Get bounds of the whole terrain in cell units virtual void getBounds(float& minX, float& maxX, float& minY, float& maxY); + + LandManager* getLandManager() const; + + private: + std::auto_ptr mLandManager; + + Resource::ResourceSystem* mResourceSystem; }; } diff --git a/apps/openmw/mwrender/vismask.hpp b/apps/openmw/mwrender/vismask.hpp index c70bcc71b..d52c7c232 100644 --- a/apps/openmw/mwrender/vismask.hpp +++ b/apps/openmw/mwrender/vismask.hpp @@ -48,8 +48,10 @@ namespace MWRender // Set on cameras within the main scene graph Mask_RenderToTexture = (1<<15), + Mask_PreCompile = (1<<16), + // Set on a camera's cull mask to enable the LightManager - Mask_Lighting = (1<<16) + Mask_Lighting = (1<<17) }; } diff --git a/apps/openmw/mwworld/cellpreloader.cpp b/apps/openmw/mwworld/cellpreloader.cpp index 278efa53d..700b91b37 100644 --- a/apps/openmw/mwworld/cellpreloader.cpp +++ b/apps/openmw/mwworld/cellpreloader.cpp @@ -7,15 +7,17 @@ #include #include #include +#include #include #include +#include #include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" -#include "../mwworld/inventorystore.hpp" -#include "../mwworld/esmstore.hpp" +#include "../mwrender/landmanager.hpp" #include "cellstore.hpp" #include "manualref.hpp" @@ -46,7 +48,7 @@ namespace MWWorld { public: /// Constructor to be called from the main thread. - PreloadItem(MWWorld::CellStore* cell, Resource::SceneManager* sceneManager, Resource::BulletShapeManager* bulletShapeManager, Resource::KeyframeManager* keyframeManager, Terrain::World* terrain, bool preloadInstances) + PreloadItem(MWWorld::CellStore* cell, Resource::SceneManager* sceneManager, Resource::BulletShapeManager* bulletShapeManager, Resource::KeyframeManager* keyframeManager, Terrain::World* terrain, MWRender::LandManager* landManager, bool preloadInstances) : mIsExterior(cell->getCell()->isExterior()) , mX(cell->getCell()->getGridX()) , mY(cell->getCell()->getGridY()) @@ -54,9 +56,12 @@ namespace MWWorld , mBulletShapeManager(bulletShapeManager) , mKeyframeManager(keyframeManager) , mTerrain(terrain) + , mLandManager(landManager) , mPreloadInstances(preloadInstances) , mAbort(false) { + mTerrainView = mTerrain->createView(); + ListModelsVisitor visitor (mMeshes); if (cell->getState() == MWWorld::CellStore::State_Loaded) { @@ -89,7 +94,8 @@ namespace MWWorld { try { - mPreloadedObjects.push_back(mTerrain->cacheCell(mX, mY)); + mTerrain->cacheCell(mTerrainView.get(), mX, mY); + mPreloadedObjects.push_back(mLandManager->getLand(mX, mY)); } catch(std::exception& e) { @@ -151,10 +157,13 @@ namespace MWWorld Resource::BulletShapeManager* mBulletShapeManager; Resource::KeyframeManager* mKeyframeManager; Terrain::World* mTerrain; + MWRender::LandManager* mLandManager; bool mPreloadInstances; volatile bool mAbort; + osg::ref_ptr mTerrainView; + // keep a ref to the loaded objects to make sure it stays loaded as long as this cell is in the preloaded state std::vector > mPreloadedObjects; }; @@ -163,30 +172,27 @@ namespace MWWorld class UpdateCacheItem : public SceneUtil::WorkItem { public: - UpdateCacheItem(Resource::ResourceSystem* resourceSystem, Terrain::World* terrain, double referenceTime) + UpdateCacheItem(Resource::ResourceSystem* resourceSystem, double referenceTime) : mReferenceTime(referenceTime) , mResourceSystem(resourceSystem) - , mTerrain(terrain) { } virtual void doWork() { mResourceSystem->updateCache(mReferenceTime); - - mTerrain->updateCache(); } private: double mReferenceTime; Resource::ResourceSystem* mResourceSystem; - Terrain::World* mTerrain; }; - CellPreloader::CellPreloader(Resource::ResourceSystem* resourceSystem, Resource::BulletShapeManager* bulletShapeManager, Terrain::World* terrain) + CellPreloader::CellPreloader(Resource::ResourceSystem* resourceSystem, Resource::BulletShapeManager* bulletShapeManager, Terrain::World* terrain, MWRender::LandManager* landManager) : mResourceSystem(resourceSystem) , mBulletShapeManager(bulletShapeManager) , mTerrain(terrain) + , mLandManager(landManager) , mExpiryDelay(0.0) , mMinCacheSize(0) , mMaxCacheSize(0) @@ -197,11 +203,25 @@ namespace MWWorld CellPreloader::~CellPreloader() { - for (PreloadMap::iterator it = mPreloadCells.begin(); it != mPreloadCells.end();++it) + if (mTerrainPreloadItem) { + mTerrainPreloadItem->abort(); + mTerrainPreloadItem->waitTillDone(); + mTerrainPreloadItem = NULL; + } + + if (mUpdateCacheItem) + { + mUpdateCacheItem->waitTillDone(); + mUpdateCacheItem = NULL; + } + + for (PreloadMap::iterator it = mPreloadCells.begin(); it != mPreloadCells.end();++it) it->second.mWorkItem->abort(); + + for (PreloadMap::iterator it = mPreloadCells.begin(); it != mPreloadCells.end();++it) it->second.mWorkItem->waitTillDone(); - } + mPreloadCells.clear(); } @@ -250,7 +270,7 @@ namespace MWWorld return; } - osg::ref_ptr item (new PreloadItem(cell, mResourceSystem->getSceneManager(), mBulletShapeManager, mResourceSystem->getKeyframeManager(), mTerrain, mPreloadInstances)); + osg::ref_ptr item (new PreloadItem(cell, mResourceSystem->getSceneManager(), mBulletShapeManager, mResourceSystem->getKeyframeManager(), mTerrain, mLandManager, mPreloadInstances)); mWorkQueue->addWorkItem(item); mPreloadCells[cell] = PreloadEntry(timestamp, item); @@ -303,10 +323,11 @@ namespace MWWorld ++it; } - if (timestamp - mLastResourceCacheUpdate > 1.0) + if (timestamp - mLastResourceCacheUpdate > 1.0 && (!mUpdateCacheItem || mUpdateCacheItem->isDone())) { // the resource cache is cleared from the worker thread so that we're not holding up the main thread with delete operations - mWorkQueue->addWorkItem(new UpdateCacheItem(mResourceSystem, mTerrain, timestamp), true); + mUpdateCacheItem = new UpdateCacheItem(mResourceSystem, timestamp); + mWorkQueue->addWorkItem(mUpdateCacheItem, true); mLastResourceCacheUpdate = timestamp; } } @@ -346,4 +367,64 @@ namespace MWWorld mUnrefQueue = unrefQueue; } + class TerrainPreloadItem : public SceneUtil::WorkItem + { + public: + TerrainPreloadItem(const std::vector >& views, Terrain::World* world, const std::vector& preloadPositions) + : mAbort(false) + , mTerrainViews(views) + , mWorld(world) + , mPreloadPositions(preloadPositions) + { + } + + virtual void doWork() + { + for (unsigned int i=0; ipreload(mTerrainViews[i], mPreloadPositions[i]); + mTerrainViews[i]->reset(0); + } + } + + virtual void abort() + { + mAbort = true; + } + + private: + volatile bool mAbort; + std::vector > mTerrainViews; + Terrain::World* mWorld; + std::vector mPreloadPositions; + }; + + void CellPreloader::setTerrainPreloadPositions(const std::vector &positions) + { + if (mTerrainPreloadItem && !mTerrainPreloadItem->isDone()) + return; + else if (positions == mTerrainPreloadPositions) + return; + else + { + if (mTerrainViews.size() > positions.size()) + { + for (unsigned int i=positions.size(); ipush(mTerrainViews[i]); + mTerrainViews.resize(positions.size()); + } + else if (mTerrainViews.size() < positions.size()) + { + for (unsigned int i=mTerrainViews.size(); icreateView()); + } + + // 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); + } + } + } diff --git a/apps/openmw/mwworld/cellpreloader.hpp b/apps/openmw/mwworld/cellpreloader.hpp index e8e59a3a2..df878a55d 100644 --- a/apps/openmw/mwworld/cellpreloader.hpp +++ b/apps/openmw/mwworld/cellpreloader.hpp @@ -3,6 +3,7 @@ #include #include +#include #include namespace Resource @@ -14,6 +15,7 @@ namespace Resource namespace Terrain { class World; + class View; } namespace SceneUtil @@ -21,6 +23,11 @@ namespace SceneUtil class UnrefQueue; } +namespace MWRender +{ + class LandManager; +} + namespace MWWorld { class CellStore; @@ -28,7 +35,7 @@ namespace MWWorld class CellPreloader { public: - CellPreloader(Resource::ResourceSystem* resourceSystem, Resource::BulletShapeManager* bulletShapeManager, Terrain::World* terrain); + CellPreloader(Resource::ResourceSystem* resourceSystem, Resource::BulletShapeManager* bulletShapeManager, Terrain::World* terrain, MWRender::LandManager* landManager); ~CellPreloader(); /// Ask a background thread to preload rendering meshes and collision shapes for objects in this cell. @@ -60,10 +67,13 @@ namespace MWWorld void setUnrefQueue(SceneUtil::UnrefQueue* unrefQueue); + void setTerrainPreloadPositions(const std::vector& positions); + private: Resource::ResourceSystem* mResourceSystem; Resource::BulletShapeManager* mBulletShapeManager; Terrain::World* mTerrain; + MWRender::LandManager* mLandManager; osg::ref_ptr mWorkQueue; osg::ref_ptr mUnrefQueue; double mExpiryDelay; @@ -92,6 +102,11 @@ namespace MWWorld // Cells that are currently being preloaded, or have already finished preloading PreloadMap mPreloadCells; + + std::vector > mTerrainViews; + std::vector mTerrainPreloadPositions; + osg::ref_ptr mTerrainPreloadItem; + osg::ref_ptr mUpdateCacheItem; }; } diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index b8a362c40..351243a84 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -19,6 +19,7 @@ #include "../mwbase/windowmanager.hpp" #include "../mwrender/renderingmanager.hpp" +#include "../mwrender/landmanager.hpp" #include "../mwphysics/physicssystem.hpp" @@ -211,14 +212,11 @@ namespace MWWorld void Scene::update (float duration, bool paused) { - if (mPreloadEnabled) + mPreloadTimer += duration; + if (mPreloadTimer > 0.1f) { - mPreloadTimer += duration; - if (mPreloadTimer > 0.1f) - { - preloadCells(0.1f); - mPreloadTimer = 0.f; - } + preloadCells(0.1f); + mPreloadTimer = 0.f; } mRendering.update (duration, paused); @@ -284,26 +282,19 @@ namespace MWWorld // Load terrain physics first... if (cell->getCell()->isExterior()) { - const ESM::Land* land = - MWBase::Environment::get().getWorld()->getStore().get().search( - cell->getCell()->getGridX(), - cell->getCell()->getGridY() - ); - if (land && land->mDataTypes&ESM::Land::DATA_VHGT) { - // Actually only VHGT is needed here, but we'll need the rest for rendering anyway. - // Load everything now to reduce IO overhead. - const int flags = ESM::Land::DATA_VCLR|ESM::Land::DATA_VHGT|ESM::Land::DATA_VNML|ESM::Land::DATA_VTEX; - - const ESM::Land::LandData *data = land->getLandData (flags); - mPhysics->addHeightField (data->mHeights, cell->getCell()->getGridX(), cell->getCell()->getGridY(), - worldsize / (verts-1), verts); + int cellX = cell->getCell()->getGridX(); + int cellY = cell->getCell()->getGridY(); + osg::ref_ptr land = mRendering.getLandManager()->getLand(cellX, cellY); + const ESM::Land::LandData* data = land ? land->getData(ESM::Land::DATA_VHGT) : 0; + if (data) + { + mPhysics->addHeightField (data->mHeights, cellX, cell->getCell()->getGridY(), worldsize / (verts-1), verts, data->mMinHeight, data->mMaxHeight, land.get()); } else { static std::vector defaultHeight; defaultHeight.resize(verts*verts, ESM::Land::DEFAULT_HEIGHT); - mPhysics->addHeightField (&defaultHeight[0], cell->getCell()->getGridX(), cell->getCell()->getGridY(), - worldsize / (verts-1), verts); + mPhysics->addHeightField (&defaultHeight[0], cell->getCell()->getGridX(), cell->getCell()->getGridY(), worldsize / (verts-1), verts, ESM::Land::DEFAULT_HEIGHT, ESM::Land::DEFAULT_HEIGHT, land.get()); } } @@ -379,7 +370,6 @@ namespace MWWorld int newX, newY; MWBase::Environment::get().getWorld()->positionToIndex(pos.x(), pos.y(), newX, newY); changeCellGrid(newX, newY); - //mRendering.updateTerrain(); } } @@ -388,8 +378,6 @@ namespace MWWorld Loading::Listener* loadingListener = MWBase::Environment::get().getWindowManager()->getLoadingScreen(); Loading::ScopedLoad load(loadingListener); - //mRendering.enableTerrain(true); - std::string loadingExteriorText = "#{sLoadingMessage3}"; loadingListener->setLabel(loadingExteriorText); @@ -488,6 +476,8 @@ namespace MWWorld { mCurrentCell = cell; + mRendering.enableTerrain(cell->isExterior()); + MWBase::World *world = MWBase::Environment::get().getWorld(); MWWorld::Ptr old = world->getPlayerPtr(); world->getPlayer().setCell(cell); @@ -529,7 +519,7 @@ namespace MWWorld , mPreloadDoors(Settings::Manager::getBool("preload doors", "Cells")) , mPreloadFastTravel(Settings::Manager::getBool("preload fast travel", "Cells")) { - mPreloader.reset(new CellPreloader(rendering.getResourceSystem(), physics->getShapeManager(), rendering.getTerrain())); + mPreloader.reset(new CellPreloader(rendering.getResourceSystem(), physics->getShapeManager(), rendering.getTerrain(), rendering.getLandManager())); mPreloader->setWorkQueue(mRendering.getWorkQueue()); mPreloader->setUnrefQueue(rendering.getUnrefQueue()); @@ -571,8 +561,6 @@ namespace MWWorld loadingListener->setLabel(loadingInteriorText); Loading::ScopedLoad load(loadingListener); - //mRendering.enableTerrain(false); - if(!loadcell) { MWBase::World *world = MWBase::Environment::get().getWorld(); @@ -648,8 +636,6 @@ namespace MWWorld CellStore* current = MWBase::Environment::get().getWorld()->getExterior(x, y); changePlayerCell(current, position, adjustPlayerPos); - - //mRendering.updateTerrain(); } CellStore* Scene::getCurrentCell () @@ -753,22 +739,32 @@ namespace MWWorld void Scene::preloadCells(float dt) { + std::vector exteriorPositions; + const MWWorld::ConstPtr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); osg::Vec3f playerPos = player.getRefData().getPosition().asVec3(); osg::Vec3f moved = playerPos - mLastPlayerPos; osg::Vec3f predictedPos = playerPos + moved / dt; + if (mCurrentCell->isExterior()) + exteriorPositions.push_back(predictedPos); + mLastPlayerPos = playerPos; - if (mPreloadDoors) - preloadTeleportDoorDestinations(playerPos, predictedPos); - if (mPreloadExteriorGrid) - preloadExteriorGrid(playerPos, predictedPos); - if (mPreloadFastTravel) - preloadFastTravelDestinations(playerPos, predictedPos); + if (mPreloadEnabled) + { + if (mPreloadDoors) + preloadTeleportDoorDestinations(playerPos, predictedPos, exteriorPositions); + if (mPreloadExteriorGrid) + preloadExteriorGrid(playerPos, predictedPos); + if (mPreloadFastTravel) + preloadFastTravelDestinations(playerPos, predictedPos, exteriorPositions); + } + + mPreloader->setTerrainPreloadPositions(exteriorPositions); } - void Scene::preloadTeleportDoorDestinations(const osg::Vec3f& playerPos, const osg::Vec3f& predictedPos) + void Scene::preloadTeleportDoorDestinations(const osg::Vec3f& playerPos, const osg::Vec3f& predictedPos, std::vector& exteriorPositions) { std::vector teleportDoors; for (CellStoreCollection::const_iterator iter (mActiveCells.begin()); @@ -800,9 +796,11 @@ namespace MWWorld preloadCell(MWBase::Environment::get().getWorld()->getInterior(door.getCellRef().getDestCell())); else { + osg::Vec3f pos = door.getCellRef().getDoorDest().asVec3(); int x,y; - MWBase::Environment::get().getWorld()->positionToIndex (door.getCellRef().getDoorDest().pos[0], door.getCellRef().getDoorDest().pos[1], x, y); + MWBase::Environment::get().getWorld()->positionToIndex (pos.x(), pos.y(), x, y); preloadCell(MWBase::Environment::get().getWorld()->getExterior(x,y), true); + exteriorPositions.push_back(pos); } } catch (std::exception& e) @@ -868,6 +866,13 @@ namespace MWWorld mPreloader->preload(cell, mRendering.getReferenceTime()); } + void Scene::preloadTerrain(const osg::Vec3f &pos) + { + std::vector vec; + vec.push_back(pos); + mPreloader->setTerrainPreloadPositions(vec); + } + struct ListFastTravelDestinationsVisitor { ListFastTravelDestinationsVisitor(float preloadDist, const osg::Vec3f& playerPos) @@ -898,7 +903,7 @@ namespace MWWorld std::vector 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& exteriorPositions) // ignore predictedPos here since opening dialogue with travel service takes extra time { const MWWorld::ConstPtr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); ListFastTravelDestinationsVisitor listVisitor(mPreloadDistance, player.getRefData().getPosition().asVec3()); @@ -916,9 +921,11 @@ namespace MWWorld preloadCell(MWBase::Environment::get().getWorld()->getInterior(it->mCellName)); else { + osg::Vec3f pos = it->mPos.asVec3(); int x,y; - MWBase::Environment::get().getWorld()->positionToIndex( it->mPos.pos[0], it->mPos.pos[1], x, y); + MWBase::Environment::get().getWorld()->positionToIndex( pos.x(), pos.y(), x, y); preloadCell(MWBase::Environment::get().getWorld()->getExterior(x,y), true); + exteriorPositions.push_back(pos); } } } diff --git a/apps/openmw/mwworld/scene.hpp b/apps/openmw/mwworld/scene.hpp index 5f17f8d59..f3c2d31ca 100644 --- a/apps/openmw/mwworld/scene.hpp +++ b/apps/openmw/mwworld/scene.hpp @@ -78,9 +78,9 @@ namespace MWWorld void getGridCenter(int& cellX, int& cellY); void preloadCells(float dt); - void preloadTeleportDoorDestinations(const osg::Vec3f& playerPos, const osg::Vec3f& predictedPos); + void preloadTeleportDoorDestinations(const osg::Vec3f& playerPos, const osg::Vec3f& predictedPos, std::vector& exteriorPositions); void preloadExteriorGrid(const osg::Vec3f& playerPos, const osg::Vec3f& predictedPos); - void preloadFastTravelDestinations(const osg::Vec3f& playerPos, const osg::Vec3f& predictedPos); + void preloadFastTravelDestinations(const osg::Vec3f& playerPos, const osg::Vec3f& predictedPos, std::vector& exteriorPositions); public: @@ -89,6 +89,7 @@ namespace MWWorld ~Scene(); void preloadCell(MWWorld::CellStore* cell, bool preloadSurrounding=false); + void preloadTerrain(const osg::Vec3f& pos); void unloadCell (CellStoreCollection::iterator iter); diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 8820c0f28..c01490481 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -399,6 +399,7 @@ namespace MWWorld case ESM::REC_PLAY: mPlayer->readRecord(reader, type); mWorldScene->preloadCell(getPlayerPtr().getCell(), true); + mWorldScene->preloadTerrain(getPlayerPtr().getRefData().getPosition().asVec3()); break; default: if (!mStore.readRecord (reader, type) && diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 5d94f88b7..9b952f109 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -118,7 +118,7 @@ add_component_dir (translation ) add_component_dir (terrain - storage world buffercache defs terraingrid material + storage world buffercache defs terraingrid material terraindrawable texturemanager chunkmanager compositemaprenderer quadtreeworld quadtreenode viewdata ) add_component_dir (loadinglistener @@ -243,7 +243,6 @@ target_link_libraries(components ${OSGVIEWER_LIBRARIES} ${OSGTEXT_LIBRARIES} ${OSGGA_LIBRARIES} - ${OSGFX_LIBRARIES} ${OSGANIMATION_LIBRARIES} ${Bullet_LIBRARIES} ${SDL2_LIBRARIES} diff --git a/components/esm/loadland.cpp b/components/esm/loadland.cpp index 5e33e6082..0869eb7be 100644 --- a/components/esm/loadland.cpp +++ b/components/esm/loadland.cpp @@ -2,8 +2,6 @@ #include -#include - #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.hpp" @@ -18,7 +16,6 @@ namespace ESM , mY(0) , mPlugin(0) , mDataTypes(0) - , mDataLoaded(false) , mLandData(NULL) { } @@ -76,7 +73,6 @@ namespace ESM mContext = esm.getContext(); - mDataLoaded = 0; mLandData = NULL; // Skip the land data here. Load it when the cell is loaded. @@ -179,45 +175,59 @@ namespace ESM } - void Land::loadData(int flags) const + void Land::loadData(int flags, LandData* target) const { - OpenThreads::ScopedLock lock(mMutex); + // Create storage if nothing is loaded + if (!target && !mLandData) + { + mLandData = new LandData; + } + + if (!target) + target = mLandData; // Try to load only available data flags = flags & mDataTypes; // Return if all required data is loaded - if ((mDataLoaded & flags) == flags) { + if ((target->mDataLoaded & flags) == flags) { return; } - // Create storage if nothing is loaded - if (mLandData == NULL) { - mLandData = new LandData; - } ESM::ESMReader reader; reader.restoreContext(mContext); if (reader.isNextSub("VNML")) { - condLoad(reader, flags, DATA_VNML, mLandData->mNormals, sizeof(mLandData->mNormals)); + condLoad(reader, flags, target->mDataLoaded, DATA_VNML, target->mNormals, sizeof(target->mNormals)); } if (reader.isNextSub("VHGT")) { VHGT vhgt; - if (condLoad(reader, flags, DATA_VHGT, &vhgt, sizeof(vhgt))) { + if (condLoad(reader, flags, target->mDataLoaded, DATA_VHGT, &vhgt, sizeof(vhgt))) { + target->mMinHeight = FLT_MAX; + target->mMaxHeight = -FLT_MAX; float rowOffset = vhgt.mHeightOffset; for (int y = 0; y < LAND_SIZE; y++) { rowOffset += vhgt.mHeightData[y * LAND_SIZE]; - mLandData->mHeights[y * LAND_SIZE] = rowOffset * HEIGHT_SCALE; + target->mHeights[y * LAND_SIZE] = rowOffset * HEIGHT_SCALE; + if (rowOffset * HEIGHT_SCALE > target->mMaxHeight) + target->mMaxHeight = rowOffset * HEIGHT_SCALE; + if (rowOffset * HEIGHT_SCALE < target->mMinHeight) + target->mMinHeight = rowOffset * HEIGHT_SCALE; float colOffset = rowOffset; for (int x = 1; x < LAND_SIZE; x++) { colOffset += vhgt.mHeightData[y * LAND_SIZE + x]; - mLandData->mHeights[x + y * LAND_SIZE] = colOffset * HEIGHT_SCALE; + target->mHeights[x + y * LAND_SIZE] = colOffset * HEIGHT_SCALE; + + if (colOffset * HEIGHT_SCALE > target->mMaxHeight) + target->mMaxHeight = colOffset * HEIGHT_SCALE; + if (colOffset * HEIGHT_SCALE < target->mMinHeight) + target->mMinHeight = colOffset * HEIGHT_SCALE; } } - mLandData->mUnk1 = vhgt.mUnk1; - mLandData->mUnk2 = vhgt.mUnk2; + target->mUnk1 = vhgt.mUnk1; + target->mUnk2 = vhgt.mUnk2; } } @@ -225,30 +235,29 @@ namespace ESM reader.skipHSub(); if (reader.isNextSub("VCLR")) - condLoad(reader, flags, DATA_VCLR, mLandData->mColours, 3 * LAND_NUM_VERTS); + condLoad(reader, flags, target->mDataLoaded, DATA_VCLR, target->mColours, 3 * LAND_NUM_VERTS); if (reader.isNextSub("VTEX")) { uint16_t vtex[LAND_NUM_TEXTURES]; - if (condLoad(reader, flags, DATA_VTEX, vtex, sizeof(vtex))) { - transposeTextureData(vtex, mLandData->mTextures); + if (condLoad(reader, flags, target->mDataLoaded, DATA_VTEX, vtex, sizeof(vtex))) { + transposeTextureData(vtex, target->mTextures); } } } void Land::unloadData() const { - if (mDataLoaded) + if (mLandData) { delete mLandData; mLandData = NULL; - mDataLoaded = 0; } } - bool Land::condLoad(ESM::ESMReader& reader, int flags, int dataFlag, void *ptr, unsigned int size) const + bool Land::condLoad(ESM::ESMReader& reader, int flags, int& targetFlags, int dataFlag, void *ptr, unsigned int size) const { - if ((mDataLoaded & dataFlag) == 0 && (flags & dataFlag) != 0) { + if ((targetFlags & dataFlag) == 0 && (flags & dataFlag) != 0) { reader.getHExact(ptr, size); - mDataLoaded |= dataFlag; + targetFlags |= dataFlag; return true; } reader.skipHSubSize(size); @@ -257,14 +266,12 @@ namespace ESM bool Land::isDataLoaded(int flags) const { - OpenThreads::ScopedLock lock(mMutex); - return (mDataLoaded & flags) == (flags & mDataTypes); + return mLandData && (mLandData->mDataLoaded & flags) == (flags & mDataTypes); } Land::Land (const Land& land) : mFlags (land.mFlags), mX (land.mX), mY (land.mY), mPlugin (land.mPlugin), mContext (land.mContext), mDataTypes (land.mDataTypes), - mDataLoaded (land.mDataLoaded), mLandData (land.mLandData ? new LandData (*land.mLandData) : 0) {} @@ -282,7 +289,6 @@ namespace ESM std::swap (mPlugin, land.mPlugin); std::swap (mContext, land.mContext); std::swap (mDataTypes, land.mDataTypes); - std::swap (mDataLoaded, land.mDataLoaded); std::swap (mLandData, land.mLandData); } @@ -311,18 +317,25 @@ namespace ESM mLandData = new LandData; mDataTypes |= flags; - mDataLoaded |= flags; + mLandData->mDataLoaded |= flags; } void Land::remove (int flags) { mDataTypes &= ~flags; - mDataLoaded &= ~flags; - if (!mDataLoaded) + if (mLandData) { - delete mLandData; - mLandData = 0; + mLandData->mDataLoaded &= ~flags; + + if (!mLandData->mDataLoaded) + { + delete mLandData; + mLandData = 0; + } } } + + const int Land::LAND_SIZE; + } diff --git a/components/esm/loadland.hpp b/components/esm/loadland.hpp index bdaf6cce8..9f33c37da 100644 --- a/components/esm/loadland.hpp +++ b/components/esm/loadland.hpp @@ -3,8 +3,6 @@ #include -#include - #include "esmcommon.hpp" namespace ESM @@ -80,10 +78,17 @@ struct Land struct LandData { + LandData() + : mDataLoaded(0) + { + } + // Initial reference height for the first vertex, only needed for filling mHeights float mHeightOffset; // Height in world space for each vertex float mHeights[LAND_NUM_VERTS]; + float mMinHeight; + float mMaxHeight; // 24-bit normals, these aren't always correct though. Edge and corner normals may be garbage. VNML mNormals[LAND_NUM_VERTS * 3]; @@ -99,6 +104,8 @@ struct Land // ??? short mUnk1; uint8_t mUnk2; + + int mDataLoaded; }; // low-LOD heightmap (used for rendering the global map) @@ -110,12 +117,13 @@ struct Land void blank() {} /** - * Actually loads data + * Actually loads data into target + * If target is NULL, assumed target is mLandData */ - void loadData(int flags) const; + void loadData(int flags, LandData* target = NULL) const; /** - * Frees memory allocated for land data + * Frees memory allocated for mLandData */ void unloadData() const; @@ -153,11 +161,7 @@ struct Land /// Loads data and marks it as loaded /// \return true if data is actually loaded from file, false otherwise /// including the case when data is already loaded - bool condLoad(ESM::ESMReader& reader, int flags, int dataFlag, void *ptr, unsigned int size) const; - - mutable OpenThreads::Mutex mMutex; - - mutable int mDataLoaded; + bool condLoad(ESM::ESMReader& reader, int flags, int& targetFlags, int dataFlag, void *ptr, unsigned int size) const; mutable LandData *mLandData; }; diff --git a/components/esmterrain/storage.cpp b/components/esmterrain/storage.cpp index d1ccbeab5..4af1c4c0d 100644 --- a/components/esmterrain/storage.cpp +++ b/components/esmterrain/storage.cpp @@ -16,6 +16,45 @@ namespace ESMTerrain { + class LandCache + { + public: + typedef std::map, osg::ref_ptr > 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 ©, const osg::CopyOp ©op) + { + } + + LandObject::~LandObject() + { + } + + const ESM::Land::LandData *LandObject::getData(int flags) const + { + if ((mData.mDataLoaded & flags) != flags) + return NULL; + return &mData; + } + + int LandObject::getPlugin() const + { + return mLand->mPlugin; + } + + const float defaultHeight = ESM::Land::DEFAULT_HEIGHT; Storage::Storage(const VFS::Manager *vfs, const std::string& normalMapPattern, const std::string& normalHeightMapPattern, bool autoUseNormalMaps, const std::string& specularMapPattern, bool autoUseSpecularMaps) @@ -28,20 +67,10 @@ namespace ESMTerrain { } - const ESM::Land::LandData *Storage::getLandData (int cellX, int cellY, int flags) - { - if (const ESM::Land *land = getLand (cellX, cellY)) - return land->getLandData (flags); - - return 0; - } - bool Storage::getMinMaxHeights(float size, const osg::Vec2f ¢er, float &min, float &max) { assert (size <= 1 && "Storage::getMinMaxHeights, chunk size should be <= 1 cell"); - /// \todo investigate if min/max heights should be stored at load time in ESM::Land instead - osg::Vec2f origin = center - osg::Vec2f(size/2.f, size/2.f); int cellX = static_cast(std::floor(origin.x())); @@ -53,7 +82,9 @@ namespace ESMTerrain int endRow = startRow + size * (ESM::Land::LAND_SIZE-1) + 1; int endColumn = startColumn + size * (ESM::Land::LAND_SIZE-1) + 1; - if (const ESM::Land::LandData *data = getLandData (cellX, cellY, ESM::Land::DATA_VHGT)) + osg::ref_ptr land = getLand (cellX, cellY); + const ESM::Land::LandData* data = land ? land->getData(ESM::Land::DATA_VHGT) : 0; + if (data) { min = std::numeric_limits::max(); max = -std::numeric_limits::max(); @@ -73,10 +104,10 @@ namespace ESMTerrain min = defaultHeight; max = defaultHeight; - return true; + return false; } - void Storage::fixNormal (osg::Vec3f& normal, int cellX, int cellY, int col, int row) + void Storage::fixNormal (osg::Vec3f& normal, int cellX, int cellY, int col, int row, LandCache& cache) { while (col >= ESM::Land::LAND_SIZE-1) { @@ -99,7 +130,9 @@ namespace ESMTerrain row += ESM::Land::LAND_SIZE-1; } - if (const ESM::Land::LandData *data = getLandData (cellX, cellY, ESM::Land::DATA_VNML)) + const LandObject* land = getLand(cellX, cellY, cache); + const ESM::Land::LandData* data = land ? land->getData(ESM::Land::DATA_VNML) : 0; + if (data) { normal.x() = data->mNormals[col*ESM::Land::LAND_SIZE*3+row*3]; normal.y() = data->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+1]; @@ -110,18 +143,18 @@ namespace ESMTerrain normal = osg::Vec3f(0,0,1); } - void Storage::averageNormal(osg::Vec3f &normal, int cellX, int cellY, int col, int row) + void Storage::averageNormal(osg::Vec3f &normal, int cellX, int cellY, int col, int row, LandCache& cache) { osg::Vec3f n1,n2,n3,n4; - fixNormal(n1, cellX, cellY, col+1, row); - fixNormal(n2, cellX, cellY, col-1, row); - fixNormal(n3, cellX, cellY, col, row+1); - fixNormal(n4, cellX, cellY, col, row-1); + fixNormal(n1, cellX, cellY, col+1, row, cache); + fixNormal(n2, cellX, cellY, col-1, row, cache); + fixNormal(n3, cellX, cellY, col, row+1, cache); + fixNormal(n4, cellX, cellY, col, row-1, cache); normal = (n1+n2+n3+n4); normal.normalize(); } - void Storage::fixColour (osg::Vec4f& color, int cellX, int cellY, int col, int row) + void Storage::fixColour (osg::Vec4f& color, int cellX, int cellY, int col, int row, LandCache& cache) { if (col == ESM::Land::LAND_SIZE-1) { @@ -134,7 +167,9 @@ namespace ESMTerrain row = 0; } - if (const ESM::Land::LandData *data = getLandData (cellX, cellY, ESM::Land::DATA_VCLR)) + const LandObject* land = getLand(cellX, cellY, cache); + const ESM::Land::LandData* data = land ? land->getData(ESM::Land::DATA_VCLR) : 0; + if (data) { color.r() = data->mColours[col*ESM::Land::LAND_SIZE*3+row*3] / 255.f; color.g() = data->mColours[col*ESM::Land::LAND_SIZE*3+row*3+1] / 255.f; @@ -146,7 +181,6 @@ namespace ESMTerrain color.g() = 1; color.b() = 1; } - } void Storage::fillVertexBuffers (int lodLevel, float size, const osg::Vec2f& center, @@ -174,15 +208,24 @@ namespace ESMTerrain float vertY = 0; float vertX = 0; + LandCache cache; + float vertY_ = 0; // of current cell corner for (int cellY = startCellY; cellY < startCellY + std::ceil(size); ++cellY) { float vertX_ = 0; // of current cell corner for (int cellX = startCellX; cellX < startCellX + std::ceil(size); ++cellX) { - const ESM::Land::LandData *heightData = getLandData (cellX, cellY, ESM::Land::DATA_VHGT); - const ESM::Land::LandData *normalData = getLandData (cellX, cellY, ESM::Land::DATA_VNML); - const ESM::Land::LandData *colourData = getLandData (cellX, cellY, ESM::Land::DATA_VCLR); + const LandObject* land = getLand(cellX, cellY, cache); + const ESM::Land::LandData *heightData = 0; + const ESM::Land::LandData *normalData = 0; + const ESM::Land::LandData *colourData = 0; + if (land) + { + heightData = land->getData(ESM::Land::DATA_VHGT); + normalData = land->getData(ESM::Land::DATA_VNML); + colourData = land->getData(ESM::Land::DATA_VCLR); + } int rowStart = 0; int colStart = 0; @@ -197,8 +240,8 @@ namespace ESMTerrain // Only relevant for chunks smaller than (contained in) one cell rowStart += (origin.x() - startCellX) * ESM::Land::LAND_SIZE; colStart += (origin.y() - startCellY) * ESM::Land::LAND_SIZE; - int rowEnd = rowStart + std::min(1.f, size) * (ESM::Land::LAND_SIZE-1) + 1; - int colEnd = colStart + std::min(1.f, size) * (ESM::Land::LAND_SIZE-1) + 1; + int rowEnd = std::min(static_cast(rowStart + std::min(1.f, size) * (ESM::Land::LAND_SIZE-1) + 1), ESM::Land::LAND_SIZE); + int colEnd = std::min(static_cast(colStart + std::min(1.f, size) * (ESM::Land::LAND_SIZE-1) + 1), ESM::Land::LAND_SIZE); vertY = vertY_; for (int col=colStart; col 0); @@ -259,7 +302,7 @@ namespace ESMTerrain // Unlike normals, colors mostly connect seamlessly between cells, but not always... if (col == ESM::Land::LAND_SIZE-1 || row == ESM::Land::LAND_SIZE-1) - fixColour(color, cellX, cellY, col, row); + fixColour(color, cellX, cellY, col, row, cache); color.a() = 1; @@ -279,7 +322,7 @@ namespace ESMTerrain } Storage::UniqueTextureId Storage::getVtexIndexAt(int cellX, int cellY, - int x, int y) + int x, int y, LandCache& cache) { // For the first/last row/column, we need to get the texture from the neighbour cell // to get consistent blending at the borders @@ -289,7 +332,12 @@ namespace ESMTerrain --cellX; x += ESM::Land::LAND_TEXTURE_SIZE; } - if (y >= ESM::Land::LAND_TEXTURE_SIZE) // Y appears to be wrapped from the other side because why the hell not? + while (x >= ESM::Land::LAND_TEXTURE_SIZE) + { + ++cellX; + x -= ESM::Land::LAND_TEXTURE_SIZE; + } + while (y >= ESM::Land::LAND_TEXTURE_SIZE) // Y appears to be wrapped from the other side because why the hell not? { ++cellY; y -= ESM::Land::LAND_TEXTURE_SIZE; @@ -298,15 +346,17 @@ namespace ESMTerrain assert(xgetData(ESM::Land::DATA_VTEX) : 0; + if (data) { int tex = data->mTextures[y * ESM::Land::LAND_TEXTURE_SIZE + x]; if (tex == 0) return std::make_pair(0,0); // vtex 0 is always the base texture, regardless of plugin - return std::make_pair(tex, getLand (cellX, cellY)->mPlugin); + return std::make_pair(tex, land->getPlugin()); } - else - return std::make_pair(0,0); + return std::make_pair(0,0); } std::string Storage::getTextureName(UniqueTextureId id) @@ -332,10 +382,6 @@ namespace ESMTerrain void Storage::getBlendmaps(float chunkSize, const osg::Vec2f &chunkCenter, bool pack, ImageVector &blendmaps, std::vector &layerList) { - // TODO - blending isn't completely right yet; the blending radius appears to be - // different at a cell transition (2 vertices, not 4), so we may need to create a larger blendmap - // and interpolate the rest of the cell by hand? :/ - osg::Vec2f origin = chunkCenter - osg::Vec2f(chunkSize/2.f, chunkSize/2.f); int cellX = static_cast(std::floor(origin.x())); int cellY = static_cast(std::floor(origin.y())); @@ -347,10 +393,6 @@ namespace ESMTerrain int rowEnd = rowStart + chunkSize * (realTextureSize-1) + 1; int colEnd = colStart + chunkSize * (realTextureSize-1) + 1; - assert (rowStart >= 0 && colStart >= 0); - assert (rowEnd <= realTextureSize); - assert (colEnd <= realTextureSize); - // Save the used texture indices so we know the total number of textures // and number of required blend maps std::set textureIndices; @@ -360,10 +402,12 @@ namespace ESMTerrain // So we're always adding _land_default.dds as the base layer here, even if it's not referenced in this cell. textureIndices.insert(std::make_pair(0,0)); + LandCache cache; + for (int y=colStart; ysecond; int blendIndex = (pack ? static_cast(std::floor((layerIndex - 1) / 4.f)) : layerIndex - 1); @@ -421,8 +465,12 @@ namespace ESMTerrain int cellX = static_cast(std::floor(worldPos.x() / 8192.f)); int cellY = static_cast(std::floor(worldPos.y() / 8192.f)); - const ESM::Land* land = getLand(cellX, cellY); - if (!land || !(land->mDataTypes&ESM::Land::DATA_VHGT)) + osg::ref_ptr land = getLand(cellX, cellY); + if (!land) + return defaultHeight; + + const ESM::Land::LandData* data = land->getData(ESM::Land::DATA_VHGT); + if (!data) return defaultHeight; // Mostly lifted from Ogre::Terrain::getHeightAtTerrainPosition @@ -461,10 +509,10 @@ namespace ESMTerrain */ // Build all 4 positions in normalized cell space, using point-sampled height - osg::Vec3f v0 (startXTS, startYTS, getVertexHeight(land, startX, startY) / 8192.f); - osg::Vec3f v1 (endXTS, startYTS, getVertexHeight(land, endX, startY) / 8192.f); - osg::Vec3f v2 (endXTS, endYTS, getVertexHeight(land, endX, endY) / 8192.f); - osg::Vec3f v3 (startXTS, endYTS, getVertexHeight(land, startX, endY) / 8192.f); + osg::Vec3f v0 (startXTS, startYTS, getVertexHeight(data, startX, startY) / 8192.f); + osg::Vec3f v1 (endXTS, startYTS, getVertexHeight(data, endX, startY) / 8192.f); + osg::Vec3f v2 (endXTS, endYTS, getVertexHeight(data, endX, endY) / 8192.f); + osg::Vec3f v3 (startXTS, endYTS, getVertexHeight(data, startX, endY) / 8192.f); // define this plane in terrain space osg::Plane plane; // FIXME: deal with differing triangle alignment @@ -496,11 +544,23 @@ namespace ESMTerrain } - float Storage::getVertexHeight(const ESM::Land *land, int x, int y) + float Storage::getVertexHeight(const ESM::Land::LandData* data, int x, int y) { assert(x < ESM::Land::LAND_SIZE); assert(y < ESM::Land::LAND_SIZE); - return land->getLandData()->mHeights[y * ESM::Land::LAND_SIZE + x]; + return data->mHeights[y * ESM::Land::LAND_SIZE + x]; + } + + const LandObject* Storage::getLand(int cellX, int cellY, LandCache& cache) + { + LandCache::Map::iterator found = cache.mMap.find(std::make_pair(cellX, cellY)); + if (found != cache.mMap.end()) + return found->second; + else + { + found = cache.mMap.insert(std::make_pair(std::make_pair(cellX, cellY), getLand(cellX, cellY))).first; + return found->second; + } } Terrain::LayerInfo Storage::getLayerInfo(const std::string& texture) @@ -570,4 +630,9 @@ namespace ESMTerrain return ESM::Land::LAND_SIZE; } + int Storage::getBlendmapScale(float chunkSize) + { + return ESM::Land::LAND_TEXTURE_SIZE*chunkSize; + } + } diff --git a/components/esmterrain/storage.hpp b/components/esmterrain/storage.hpp index 092998b2e..0bb24a4ab 100644 --- a/components/esmterrain/storage.hpp +++ b/components/esmterrain/storage.hpp @@ -16,25 +16,39 @@ namespace VFS namespace ESMTerrain { - /// @brief Feeds data from ESM terrain records (ESM::Land, ESM::LandTexture) - /// into the terrain component, converting it on the fly as needed. - class Storage : public Terrain::Storage + class LandCache; + + /// @brief Wrapper around Land Data with reference counting. The wrapper needs to be held as long as the data is still in use + class LandObject : public osg::Object { + public: + LandObject(); + LandObject(const ESM::Land* land, int loadFlags); + LandObject(const LandObject& copy, const osg::CopyOp& copyop); + virtual ~LandObject(); + + META_Object(ESMTerrain, LandObject) + + const ESM::Land::LandData* getData(int flags) const; + int getPlugin() const; + private: + const ESM::Land* mLand; + int mLoadFlags; - // Not implemented in this class, because we need different Store implementations for game and editor - virtual const ESM::Land* getLand (int cellX, int cellY)= 0; - virtual const ESM::LandTexture* getLandTexture(int index, short plugin) = 0; + ESM::Land::LandData mData; + }; + /// @brief Feeds data from ESM terrain records (ESM::Land, ESM::LandTexture) + /// into the terrain component, converting it on the fly as needed. + class Storage : public Terrain::Storage + { public: Storage(const VFS::Manager* vfs, const std::string& normalMapPattern = "", const std::string& normalHeightMapPattern = "", bool autoUseNormalMaps = false, const std::string& specularMapPattern = "", bool autoUseSpecularMaps = false); - /// Data is loaded first, if necessary. Will return a 0-pointer if there is no data for - /// any of the data types specified via \a flags. Will also return a 0-pointer if there - /// is no land record for the coordinates \a cellX / \a cellY. - const ESM::Land::LandData *getLandData (int cellX, int cellY, int flags); - // Not implemented in this class, because we need different Store implementations for game and editor + virtual osg::ref_ptr getLand (int cellX, int cellY)= 0; + virtual const ESM::LandTexture* getLandTexture(int index, short plugin) = 0; /// Get bounds of the whole terrain in cell units virtual void getBounds(float& minX, float& maxX, float& minY, float& maxY) = 0; @@ -88,14 +102,18 @@ namespace ESMTerrain /// Get the number of vertices on one side for each cell. Should be (power of two)+1 virtual int getCellVertices(); + virtual int getBlendmapScale(float chunkSize); + private: const VFS::Manager* mVFS; - void fixNormal (osg::Vec3f& normal, int cellX, int cellY, int col, int row); - void fixColour (osg::Vec4f& colour, int cellX, int cellY, int col, int row); - void averageNormal (osg::Vec3f& normal, int cellX, int cellY, int col, int row); + void fixNormal (osg::Vec3f& normal, int cellX, int cellY, int col, int row, LandCache& cache); + void fixColour (osg::Vec4f& colour, int cellX, int cellY, int col, int row, LandCache& cache); + void averageNormal (osg::Vec3f& normal, int cellX, int cellY, int col, int row, LandCache& cache); + + float getVertexHeight (const ESM::Land::LandData* data, int x, int y); - float getVertexHeight (const ESM::Land* land, int x, int y); + const LandObject* getLand(int cellX, int cellY, LandCache& cache); // Since plugins can define new texture palettes, we need to know the plugin index too // in order to retrieve the correct texture name. @@ -103,7 +121,7 @@ namespace ESMTerrain typedef std::pair UniqueTextureId; UniqueTextureId getVtexIndexAt(int cellX, int cellY, - int x, int y); + int x, int y, LandCache&); std::string getTextureName (UniqueTextureId id); std::map mLayerInfoMap; diff --git a/components/resource/bulletshapemanager.cpp b/components/resource/bulletshapemanager.cpp index 18c37d97b..ef35b31ef 100644 --- a/components/resource/bulletshapemanager.cpp +++ b/components/resource/bulletshapemanager.cpp @@ -185,7 +185,7 @@ void BulletShapeManager::updateCache(double referenceTime) mInstanceCache->removeUnreferencedObjectsInCache(); } -void BulletShapeManager::reportStats(unsigned int frameNumber, osg::Stats *stats) +void BulletShapeManager::reportStats(unsigned int frameNumber, osg::Stats *stats) const { stats->setAttribute(frameNumber, "Shape", mCache->getCacheSize()); stats->setAttribute(frameNumber, "Shape Instance", mInstanceCache->getCacheSize()); diff --git a/components/resource/bulletshapemanager.hpp b/components/resource/bulletshapemanager.hpp index e6dba001c..fec7251ac 100644 --- a/components/resource/bulletshapemanager.hpp +++ b/components/resource/bulletshapemanager.hpp @@ -42,7 +42,7 @@ namespace Resource /// @see ResourceManager::updateCache virtual void updateCache(double referenceTime); - void reportStats(unsigned int frameNumber, osg::Stats *stats); + void reportStats(unsigned int frameNumber, osg::Stats *stats) const; private: osg::ref_ptr createInstance(const std::string& name); diff --git a/components/resource/imagemanager.cpp b/components/resource/imagemanager.cpp index 2e1422cf6..db47324fb 100644 --- a/components/resource/imagemanager.cpp +++ b/components/resource/imagemanager.cpp @@ -141,7 +141,7 @@ namespace Resource return mWarningImage; } - void ImageManager::reportStats(unsigned int frameNumber, osg::Stats *stats) + void ImageManager::reportStats(unsigned int frameNumber, osg::Stats *stats) const { stats->setAttribute(frameNumber, "Image", mCache->getCacheSize()); } diff --git a/components/resource/imagemanager.hpp b/components/resource/imagemanager.hpp index 2c7a61828..8eea4a70b 100644 --- a/components/resource/imagemanager.hpp +++ b/components/resource/imagemanager.hpp @@ -32,7 +32,7 @@ namespace Resource osg::Image* getWarningImage(); - void reportStats(unsigned int frameNumber, osg::Stats* stats); + void reportStats(unsigned int frameNumber, osg::Stats* stats) const; private: osg::ref_ptr mWarningImage; diff --git a/components/resource/keyframemanager.cpp b/components/resource/keyframemanager.cpp index 699fdfdd6..8c5c50adc 100644 --- a/components/resource/keyframemanager.cpp +++ b/components/resource/keyframemanager.cpp @@ -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()); } diff --git a/components/resource/keyframemanager.hpp b/components/resource/keyframemanager.hpp index a5c1a7818..3496342fa 100644 --- a/components/resource/keyframemanager.hpp +++ b/components/resource/keyframemanager.hpp @@ -23,7 +23,7 @@ namespace Resource /// @note Throws an exception if the resource is not found. osg::ref_ptr get(const std::string& name); - void reportStats(unsigned int frameNumber, osg::Stats* stats); + void reportStats(unsigned int frameNumber, osg::Stats* stats) const; }; } diff --git a/components/resource/niffilemanager.cpp b/components/resource/niffilemanager.cpp index dcc222ed1..8d932ca64 100644 --- a/components/resource/niffilemanager.cpp +++ b/components/resource/niffilemanager.cpp @@ -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()); } diff --git a/components/resource/niffilemanager.hpp b/components/resource/niffilemanager.hpp index 378daabf7..c783bb7e0 100644 --- a/components/resource/niffilemanager.hpp +++ b/components/resource/niffilemanager.hpp @@ -23,7 +23,7 @@ namespace Resource /// to be done in advance by other managers accessing the NifFileManager. Nif::NIFFilePtr get(const std::string& name); - void reportStats(unsigned int frameNumber, osg::Stats *stats); + void reportStats(unsigned int frameNumber, osg::Stats *stats) const; }; } diff --git a/components/resource/objectcache.hpp b/components/resource/objectcache.hpp index e1caa4123..72db835e8 100644 --- a/components/resource/objectcache.hpp +++ b/components/resource/objectcache.hpp @@ -73,6 +73,15 @@ class ObjectCache : public osg::Referenced /** call node->accept(nv); for all nodes in the objectCache. */ void accept(osg::NodeVisitor& nv); + /** call operator()(osg::Object*) for each object in the cache. */ + template + void call(Functor& f) + { + OpenThreads::ScopedLock lock(_objectCacheMutex); + for (ObjectCacheMap::iterator it = _objectCache.begin(); it != _objectCache.end(); ++it) + f(it->second.first.get()); + } + /** Get the number of objects in the cache. */ unsigned int getCacheSize() const; diff --git a/components/resource/resourcemanager.hpp b/components/resource/resourcemanager.hpp index f07ac6f77..61599cd5e 100644 --- a/components/resource/resourcemanager.hpp +++ b/components/resource/resourcemanager.hpp @@ -33,7 +33,7 @@ namespace Resource const VFS::Manager* getVFS() const; - virtual void reportStats(unsigned int frameNumber, osg::Stats* stats) {} + virtual void reportStats(unsigned int frameNumber, osg::Stats* stats) const {} protected: const VFS::Manager* mVFS; diff --git a/components/resource/resourcesystem.cpp b/components/resource/resourcesystem.cpp index 091614a10..19b280ff1 100644 --- a/components/resource/resourcesystem.cpp +++ b/components/resource/resourcesystem.cpp @@ -85,9 +85,9 @@ namespace Resource return mVFS; } - void ResourceSystem::reportStats(unsigned int frameNumber, osg::Stats *stats) + void ResourceSystem::reportStats(unsigned int frameNumber, osg::Stats *stats) const { - for (std::vector::iterator it = mResourceManagers.begin(); it != mResourceManagers.end(); ++it) + for (std::vector::const_iterator it = mResourceManagers.begin(); it != mResourceManagers.end(); ++it) (*it)->reportStats(frameNumber, stats); } diff --git a/components/resource/resourcesystem.hpp b/components/resource/resourcesystem.hpp index 71541ade5..6e308fd9b 100644 --- a/components/resource/resourcesystem.hpp +++ b/components/resource/resourcesystem.hpp @@ -54,7 +54,7 @@ namespace Resource /// @note May be called from any thread. const VFS::Manager* getVFS() const; - void reportStats(unsigned int frameNumber, osg::Stats* stats); + void reportStats(unsigned int frameNumber, osg::Stats* stats) const; private: std::auto_ptr mSceneManager; diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index 698060597..167ab6221 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -621,6 +621,11 @@ namespace Resource mIncrementalCompileOperation = ico; } + osgUtil::IncrementalCompileOperation *SceneManager::getIncrementalCompileOperation() + { + return mIncrementalCompileOperation.get(); + } + Resource::ImageManager* SceneManager::getImageManager() { return mImageManager; @@ -689,16 +694,16 @@ namespace Resource void SceneManager::updateCache(double referenceTime) { - mSharedStateMutex.lock(); - mSharedStateManager->prune(); - mSharedStateMutex.unlock(); - ResourceManager::updateCache(referenceTime); mInstanceCache->removeUnreferencedObjectsInCache(); + + mSharedStateMutex.lock(); + mSharedStateManager->prune(); + mSharedStateMutex.unlock(); } - void SceneManager::reportStats(unsigned int frameNumber, osg::Stats *stats) + void SceneManager::reportStats(unsigned int frameNumber, osg::Stats *stats) const { { OpenThreads::ScopedLock lock(*mIncrementalCompileOperation->getToCompiledMutex()); diff --git a/components/resource/scenemanager.hpp b/components/resource/scenemanager.hpp index fdd1d6583..2da28eaf4 100644 --- a/components/resource/scenemanager.hpp +++ b/components/resource/scenemanager.hpp @@ -121,6 +121,8 @@ namespace Resource /// Set up an IncrementalCompileOperation for background compiling of loaded scenes. void setIncrementalCompileOperation(osgUtil::IncrementalCompileOperation* ico); + osgUtil::IncrementalCompileOperation* getIncrementalCompileOperation(); + Resource::ImageManager* getImageManager(); /// @param mask The node mask to apply to loaded particle system nodes. @@ -141,7 +143,7 @@ namespace Resource /// @see ResourceManager::updateCache virtual void updateCache(double referenceTime); - virtual void reportStats(unsigned int frameNumber, osg::Stats* stats); + virtual void reportStats(unsigned int frameNumber, osg::Stats* stats) const; private: @@ -158,7 +160,7 @@ namespace Resource osg::ref_ptr mInstanceCache; osg::ref_ptr mSharedStateManager; - OpenThreads::Mutex mSharedStateMutex; + mutable OpenThreads::Mutex mSharedStateMutex; Resource::ImageManager* mImageManager; Resource::NifFileManager* mNifFileManager; diff --git a/components/resource/stats.cpp b/components/resource/stats.cpp index d696c9ecc..bf34d5534 100644 --- a/components/resource/stats.cpp +++ b/components/resource/stats.cpp @@ -259,7 +259,7 @@ void StatsHandler::setUpScene(osgViewer::ViewerBase *viewer) _resourceStatsChildNum = _switch->getNumChildren(); _switch->addChild(group, false); - const char* statNames[] = {"Compiling", "WorkQueue", "WorkThread", "", "Texture", "StateSet", "Node", "Node Instance", "Shape", "Shape Instance", "Image", "Nif", "Keyframe", "Terrain Cell", "Terrain Texture", "", "UnrefQueue"}; + const char* statNames[] = {"Compiling", "WorkQueue", "WorkThread", "", "Texture", "StateSet", "Node", "Node Instance", "Shape", "Shape Instance", "Image", "Nif", "Keyframe", "", "Terrain Chunk", "Terrain Texture", "Land", "Composite", "", "UnrefQueue"}; int numLines = sizeof(statNames) / sizeof(statNames[0]); diff --git a/components/sceneutil/lightmanager.cpp b/components/sceneutil/lightmanager.cpp index bd8ef8af4..341bc8034 100644 --- a/components/sceneutil/lightmanager.cpp +++ b/components/sceneutil/lightmanager.cpp @@ -381,21 +381,23 @@ namespace SceneUtil { osgUtil::CullVisitor* cv = static_cast(nv); + bool pushedState = pushLightState(node, cv); + traverse(node, nv); + if (pushedState) + cv->popStateSet(); + } + + bool LightListCallback::pushLightState(osg::Node *node, osgUtil::CullVisitor *cv) + { if (!mLightManager) { - mLightManager = findLightManager(nv->getNodePath()); + mLightManager = findLightManager(cv->getNodePath()); if (!mLightManager) - { - traverse(node, nv); - return; - } + return false; } if (!(cv->getCurrentCamera()->getCullMask() & mLightManager->getLightingMask())) - { - traverse(node, nv); - return; - } + return false; // Possible optimizations: // - cull list of lights by the camera frustum @@ -404,9 +406,9 @@ namespace SceneUtil // update light list if necessary // makes sure we don't update it more than once per frame when rendering with multiple cameras - if (mLastFrameNumber != nv->getTraversalNumber()) + if (mLastFrameNumber != cv->getTraversalNumber()) { - mLastFrameNumber = nv->getTraversalNumber(); + mLastFrameNumber = cv->getTraversalNumber(); // Don't use Camera::getViewMatrix, that one might be relative to another camera! const osg::RefMatrix* viewMatrix = cv->getCurrentRenderStage()->getInitialViewMatrix(); @@ -415,12 +417,14 @@ namespace SceneUtil // get the node bounds in view space // NB do not node->getBound() * modelView, that would apply the node's transformation twice osg::BoundingSphere nodeBound; - osg::Group* group = node->asGroup(); - if (group) + osg::Transform* transform = node->asTransform(); + if (transform) { - for (unsigned int i=0; igetNumChildren(); ++i) - nodeBound.expandBy(group->getChild(i)->getBound()); + for (unsigned int i=0; igetNumChildren(); ++i) + nodeBound.expandBy(transform->getChild(i)->getBound()); } + else + nodeBound = node->getBound(); osg::Matrixf mat = *cv->getModelViewMatrix(); transformBoundingSphere(mat, nodeBound); @@ -469,20 +473,16 @@ namespace SceneUtil while (lightList.size() > maxLights) lightList.pop_back(); } - stateset = mLightManager->getLightListStateSet(lightList, nv->getTraversalNumber()); + stateset = mLightManager->getLightListStateSet(lightList, cv->getTraversalNumber()); } else - stateset = mLightManager->getLightListStateSet(mLightList, nv->getTraversalNumber()); + stateset = mLightManager->getLightListStateSet(mLightList, cv->getTraversalNumber()); cv->pushStateSet(stateset); - - traverse(node, nv); - - cv->popStateSet(); + return true; } - else - traverse(node, nv); + return false; } } diff --git a/components/sceneutil/lightmanager.hpp b/components/sceneutil/lightmanager.hpp index 2c7a6a3d8..55d0c69cd 100644 --- a/components/sceneutil/lightmanager.hpp +++ b/components/sceneutil/lightmanager.hpp @@ -9,6 +9,11 @@ #include #include +namespace osgUtil +{ + class CullVisitor; +} + namespace SceneUtil { @@ -148,6 +153,7 @@ namespace SceneUtil /// rendering when the size of a light list exceeds the OpenGL limit on the number of concurrent lights (8). A good /// starting point is to attach a LightListCallback to each game object's base node. /// @note Not thread safe for CullThreadPerCamera threading mode. + /// @note Due to lack of OSG support, the callback does not work on Drawables. class LightListCallback : public osg::NodeCallback { public: @@ -166,6 +172,8 @@ namespace SceneUtil void operator()(osg::Node* node, osg::NodeVisitor* nv); + bool pushLightState(osg::Node* node, osgUtil::CullVisitor* nv); + std::set& getIgnoredLightSources() { return mIgnoredLightSources; } private: diff --git a/components/sdlutil/sdlinputwrapper.cpp b/components/sdlutil/sdlinputwrapper.cpp index 204680b44..c88210b0a 100644 --- a/components/sdlutil/sdlinputwrapper.cpp +++ b/components/sdlutil/sdlinputwrapper.cpp @@ -329,8 +329,8 @@ InputWrapper::InputWrapper(SDL_Window* window, osg::ref_ptr v SDL_GetWindowSize(mSDLWindow, &width, &height); - const int FUDGE_FACTOR_X = width; - const int FUDGE_FACTOR_Y = height; + const int FUDGE_FACTOR_X = width/4; + const int FUDGE_FACTOR_Y = height/4; //warp the mouse if it's about to go outside the window if(evt.x - FUDGE_FACTOR_X < 0 || evt.x + FUDGE_FACTOR_X > width diff --git a/components/terrain/buffercache.cpp b/components/terrain/buffercache.cpp index 3bf5d5651..470655539 100644 --- a/components/terrain/buffercache.cpp +++ b/components/terrain/buffercache.cpp @@ -178,56 +178,56 @@ osg::ref_ptr createIndexBuffer(unsigned int flags, unsigned int namespace Terrain { - osg::ref_ptr BufferCache::getUVBuffer() + osg::ref_ptr BufferCache::getUVBuffer(unsigned int numVerts) { OpenThreads::ScopedLock 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 uvs (new osg::Vec2Array); uvs->reserve(vertexCount); - for (unsigned int col = 0; col < mNumVerts; ++col) + for (unsigned int col = 0; col < numVerts; ++col) { - for (unsigned int row = 0; row < mNumVerts; ++row) + for (unsigned int row = 0; row < numVerts; ++row) { - uvs->push_back(osg::Vec2f(col / static_cast(mNumVerts-1), - ((mNumVerts-1) - row) / static_cast(mNumVerts-1))); + uvs->push_back(osg::Vec2f(col / static_cast(numVerts-1), + ((numVerts-1) - row) / static_cast(numVerts-1))); } } // Assign a VBO here to enable state sharing between different Geometries. uvs->setVertexBufferObject(new osg::VertexBufferObject); - mUvBufferMap[mNumVerts] = uvs; + mUvBufferMap[numVerts] = uvs; return uvs; } - osg::ref_ptr BufferCache::getIndexBuffer(unsigned int flags) + osg::ref_ptr BufferCache::getIndexBuffer(unsigned int numVerts, unsigned int flags) { + std::pair id = std::make_pair(numVerts, flags); OpenThreads::ScopedLock 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 buffer; - if (verts*verts <= (0xffffu)) - buffer = createIndexBuffer(flags, verts); + if (numVerts*numVerts <= (0xffffu)) + buffer = createIndexBuffer(flags, numVerts); else - buffer = createIndexBuffer(flags, verts); + buffer = createIndexBuffer(flags, numVerts); // Assign a EBO here to enable state sharing between different Geometries. buffer->setElementBufferObject(new osg::ElementBufferObject); - mIndexBufferMap[flags] = buffer; + mIndexBufferMap[id] = buffer; return buffer; } diff --git a/components/terrain/buffercache.hpp b/components/terrain/buffercache.hpp index 172b9d672..e8963354b 100644 --- a/components/terrain/buffercache.hpp +++ b/components/terrain/buffercache.hpp @@ -14,28 +14,24 @@ namespace Terrain class BufferCache { public: - BufferCache(unsigned int numVerts) : mNumVerts(numVerts) {} - /// @param flags first 4*4 bits are LOD deltas on each edge, respectively (4 bits each) /// next 4 bits are LOD level of the index buffer (LOD 0 = don't omit any vertices) /// @note Thread safe. - osg::ref_ptr getIndexBuffer (unsigned int flags); + osg::ref_ptr getIndexBuffer (unsigned int numVerts, unsigned int flags); /// @note Thread safe. - osg::ref_ptr getUVBuffer(); + osg::ref_ptr getUVBuffer(unsigned int numVerts); // TODO: add releaseGLObjects() for our vertex/element buffer objects private: // Index buffers are shared across terrain batches where possible. There is one index buffer for each // combination of LOD deltas and index buffer LOD we may need. - std::map > mIndexBufferMap; + std::map, osg::ref_ptr > mIndexBufferMap; OpenThreads::Mutex mIndexBufferMutex; std::map > mUvBufferMap; OpenThreads::Mutex mUvBufferMutex; - - unsigned int mNumVerts; }; } diff --git a/components/terrain/chunkmanager.cpp b/components/terrain/chunkmanager.cpp new file mode 100644 index 000000000..e4a7c2d68 --- /dev/null +++ b/components/terrain/chunkmanager.cpp @@ -0,0 +1,235 @@ +#include "chunkmanager.hpp" + +#include + +#include + +#include + +#include +#include + +#include +#include + +#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 ChunkManager::getChunk(float size, const osg::Vec2f ¢er, int lod, unsigned int lodFlags) +{ + std::ostringstream stream; + stream << size << " " << center.x() << " " << center.y() << " " << lod << " " << lodFlags; + std::string id = stream.str(); + + osg::ref_ptr obj = mCache->getRefFromObjectCache(id); + if (obj) + return obj->asNode(); + else + { + osg::ref_ptr 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 ChunkManager::createCompositeMapRTT() +{ + osg::ref_ptr 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 > passes = createPasses(chunkSize, chunkCenter, true); + for (std::vector >::iterator it = passes.begin(); it != passes.end(); ++it) + { + osg::ref_ptr 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 > ChunkManager::createPasses(float chunkSize, const osg::Vec2f &chunkCenter, bool forCompositeMap) +{ + std::vector layerList; + std::vector > 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 layers; + { + for (std::vector::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 > blendmapTextures; + for (std::vector >::const_iterator it = blendmaps.begin(); it != blendmaps.end(); ++it) + { + osg::ref_ptr 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 ChunkManager::createChunk(float chunkSize, const osg::Vec2f &chunkCenter, int lod, unsigned int lodFlags) +{ + osg::Vec2f worldCenter = chunkCenter*mStorage->getCellWorldSize(); + osg::ref_ptr transform (new SceneUtil::PositionAttitudeTransform); + transform->setPosition(osg::Vec3f(worldCenter.x(), worldCenter.y(), 0.f)); + + osg::ref_ptr positions (new osg::Vec3Array); + osg::ref_ptr normals (new osg::Vec3Array); + osg::ref_ptr colors (new osg::Vec4Array); + + osg::ref_ptr vbo (new osg::VertexBufferObject); + positions->setVertexBufferObject(vbo); + normals->setVertexBufferObject(vbo); + colors->setVertexBufferObject(vbo); + + mStorage->fillVertexBuffers(lod, chunkSize, chunkCenter, positions, normals, colors); + + osg::ref_ptr 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; isetTexCoordArray(i, mBufferCache.getUVBuffer(numVerts)); + + if (useCompositeMap) + { + osg::ref_ptr 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(1, layer), std::vector >(), 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; +} + +} diff --git a/components/terrain/chunkmanager.hpp b/components/terrain/chunkmanager.hpp new file mode 100644 index 000000000..553e06d97 --- /dev/null +++ b/components/terrain/chunkmanager.hpp @@ -0,0 +1,61 @@ +#ifndef OPENMW_COMPONENTS_TERRAIN_CHUNKMANAGER_H +#define OPENMW_COMPONENTS_TERRAIN_CHUNKMANAGER_H + +#include + +#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 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 createChunk(float size, const osg::Vec2f& center, int lod, unsigned int lodFlags); + + osg::ref_ptr createCompositeMapRTT(); + + void createCompositeMapGeometry(float chunkSize, const osg::Vec2f& chunkCenter, const osg::Vec4f& texCoords, CompositeMap& map); + + std::vector > 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 diff --git a/components/terrain/compositemaprenderer.cpp b/components/terrain/compositemaprenderer.cpp new file mode 100644 index 000000000..5300aac85 --- /dev/null +++ b/components/terrain/compositemaprenderer.cpp @@ -0,0 +1,170 @@ +#include "compositemaprenderer.hpp" + +#include + +#include +#include +#include + +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 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(); + + 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; igetStateSet(); + + 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 lock(mMutex); + if (immediate) + mImmediateCompileSet.insert(compositeMap); + else + mCompileSet.insert(compositeMap); +} + +void CompositeMapRenderer::setImmediate(CompositeMap* compositeMap) +{ + OpenThreads::ScopedLock 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 lock(mMutex); + return mCompileSet.size(); +} + +CompositeMap::CompositeMap() + : mCompiled(0) +{ +} + +CompositeMap::~CompositeMap() +{ + +} + + + +} diff --git a/components/terrain/compositemaprenderer.hpp b/components/terrain/compositemaprenderer.hpp new file mode 100644 index 000000000..15e939389 --- /dev/null +++ b/components/terrain/compositemaprenderer.hpp @@ -0,0 +1,70 @@ +#ifndef OPENMW_COMPONENTS_TERRAIN_COMPOSITEMAPRENDERER_H +#define OPENMW_COMPONENTS_TERRAIN_COMPOSITEMAPRENDERER_H + +#include + +#include + +#include + +namespace osg +{ + class FrameBufferObject; + class RenderInfo; + class Texture2D; +} + +namespace Terrain +{ + + class CompositeMap : public osg::Referenced + { + public: + CompositeMap(); + ~CompositeMap(); + std::vector > mDrawables; + osg::ref_ptr 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 > CompileSet; + + mutable CompileSet mCompileSet; + mutable CompileSet mImmediateCompileSet; + + mutable CompileSet mCompiled; + + mutable OpenThreads::Mutex mMutex; + + osg::ref_ptr mFBO; + }; + +} + +#endif diff --git a/components/terrain/material.cpp b/components/terrain/material.cpp index 323f41798..8aec54835 100644 --- a/components/terrain/material.cpp +++ b/components/terrain/material.cpp @@ -1,6 +1,5 @@ #include "material.hpp" -#include #include #include @@ -58,11 +57,14 @@ namespace Terrain return depth; } - FixedFunctionTechnique::FixedFunctionTechnique(const std::vector& layers, - const std::vector >& blendmaps, int blendmapScale, float layerTileSize) + std::vector > createPasses(bool useShaders, bool forcePerPixelLighting, bool clampLighting, Shader::ShaderManager* shaderManager, const std::vector &layers, + const std::vector > &blendmaps, int blendmapScale, float layerTileSize) { + std::vector > passes; + bool firstLayer = true; - int i=0; + unsigned int blendmapIndex = 0; + unsigned int passIndex = 0; for (std::vector::const_iterator it = layers.begin(); it != layers.end(); ++it) { osg::ref_ptr stateset (new osg::StateSet); @@ -70,138 +72,93 @@ namespace Terrain if (!firstLayer) { stateset->setMode(GL_BLEND, osg::StateAttribute::ON); - stateset->setAttributeAndModes(getEqualDepth(), osg::StateAttribute::ON); } int texunit = 0; - if(!firstLayer) + + if (useShaders) { - osg::ref_ptr blendmap = blendmaps.at(i++); + stateset->setTextureAttributeAndModes(texunit, it->mDiffuseMap); - stateset->setTextureAttributeAndModes(texunit, blendmap.get()); + if (layerTileSize != 1.f) + stateset->setTextureAttributeAndModes(texunit, getLayerTexMat(layerTileSize), osg::StateAttribute::ON); - // This is to map corner vertices directly to the center of a blendmap texel. - stateset->setTextureAttributeAndModes(texunit, getBlendmapTexMat(blendmapScale)); + stateset->addUniform(new osg::Uniform("diffuseMap", texunit)); - static osg::ref_ptr texEnvCombine; - if (!texEnvCombine) + if(!firstLayer) { - texEnvCombine = new osg::TexEnvCombine; - texEnvCombine->setCombine_RGB(osg::TexEnvCombine::REPLACE); - texEnvCombine->setSource0_RGB(osg::TexEnvCombine::PREVIOUS); - } - - stateset->setTextureAttributeAndModes(texunit, texEnvCombine, osg::StateAttribute::ON); - - ++texunit; - } - - // Add the actual layer texture multiplied by the alpha map. - osg::ref_ptr tex = it->mDiffuseMap; - stateset->setTextureAttributeAndModes(texunit, tex.get()); - - stateset->setTextureAttributeAndModes(texunit, getLayerTexMat(layerTileSize), osg::StateAttribute::ON); + ++texunit; + osg::ref_ptr blendmap = blendmaps.at(blendmapIndex++); - firstLayer = false; + stateset->setTextureAttributeAndModes(texunit, blendmap.get()); - addPass(stateset); - } - } + stateset->setTextureAttributeAndModes(texunit, getBlendmapTexMat(blendmapScale)); + stateset->addUniform(new osg::Uniform("blendMap", texunit)); + } - ShaderTechnique::ShaderTechnique(Shader::ShaderManager& shaderManager, bool forcePerPixelLighting, bool clampLighting, const std::vector& layers, - const std::vector >& blendmaps, int blendmapScale, float layerTileSize) - { - bool firstLayer = true; - int i=0; - for (std::vector::const_iterator it = layers.begin(); it != layers.end(); ++it) - { - osg::ref_ptr stateset (new osg::StateSet); + if (it->mNormalMap) + { + ++texunit; + stateset->setTextureAttributeAndModes(texunit, it->mNormalMap); + stateset->addUniform(new osg::Uniform("normalMap", texunit)); + } - if (!firstLayer) - { - stateset->setMode(GL_BLEND, osg::StateAttribute::ON); - stateset->setAttributeAndModes(getEqualDepth(), osg::StateAttribute::ON); + Shader::ShaderManager::DefineMap defineMap; + defineMap["forcePPL"] = forcePerPixelLighting ? "1" : "0"; + defineMap["clamp"] = clampLighting ? "1" : "0"; + defineMap["normalMap"] = (it->mNormalMap) ? "1" : "0"; + defineMap["blendMap"] = !firstLayer ? "1" : "0"; + defineMap["colorMode"] = "2"; + defineMap["specularMap"] = it->mSpecular ? "1" : "0"; + defineMap["parallax"] = (it->mNormalMap && it->mParallax) ? "1" : "0"; + + osg::ref_ptr vertexShader = shaderManager->getShader("terrain_vertex.glsl", defineMap, osg::Shader::VERTEX); + osg::ref_ptr 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 blendmap = blendmaps.at(blendmapIndex++); - int texunit = 0; - - stateset->setTextureAttributeAndModes(texunit, it->mDiffuseMap); + stateset->setTextureAttributeAndModes(texunit, blendmap.get()); - stateset->setTextureAttributeAndModes(texunit, getLayerTexMat(layerTileSize), osg::StateAttribute::ON); + // This is to map corner vertices directly to the center of a blendmap texel. + stateset->setTextureAttributeAndModes(texunit, getBlendmapTexMat(blendmapScale)); - stateset->addUniform(new osg::Uniform("diffuseMap", texunit)); + static osg::ref_ptr texEnvCombine; + if (!texEnvCombine) + { + texEnvCombine = new osg::TexEnvCombine; + texEnvCombine->setCombine_RGB(osg::TexEnvCombine::REPLACE); + texEnvCombine->setSource0_RGB(osg::TexEnvCombine::PREVIOUS); + } - if(!firstLayer) - { - ++texunit; - osg::ref_ptr blendmap = blendmaps.at(i++); + stateset->setTextureAttributeAndModes(texunit, texEnvCombine, osg::StateAttribute::ON); - stateset->setTextureAttributeAndModes(texunit, blendmap.get()); + ++texunit; + } - stateset->setTextureAttributeAndModes(texunit, getBlendmapTexMat(blendmapScale)); - stateset->addUniform(new osg::Uniform("blendMap", texunit)); - } + // Add the actual layer texture multiplied by the alpha map. + osg::ref_ptr tex = it->mDiffuseMap; + stateset->setTextureAttributeAndModes(texunit, tex.get()); - if (it->mNormalMap) - { - ++texunit; - stateset->setTextureAttributeAndModes(texunit, it->mNormalMap); - stateset->addUniform(new osg::Uniform("normalMap", texunit)); + if (layerTileSize != 1.f) + stateset->setTextureAttributeAndModes(texunit, getLayerTexMat(layerTileSize), osg::StateAttribute::ON); } - Shader::ShaderManager::DefineMap defineMap; - defineMap["forcePPL"] = forcePerPixelLighting ? "1" : "0"; - defineMap["clamp"] = clampLighting ? "1" : "0"; - defineMap["normalMap"] = (it->mNormalMap) ? "1" : "0"; - defineMap["blendMap"] = !firstLayer ? "1" : "0"; - defineMap["colorMode"] = "2"; - defineMap["specularMap"] = it->mSpecular ? "1" : "0"; - defineMap["parallax"] = (it->mNormalMap && it->mParallax) ? "1" : "0"; - - osg::ref_ptr vertexShader = shaderManager.getShader("terrain_vertex.glsl", defineMap, osg::Shader::VERTEX); - osg::ref_ptr fragmentShader = shaderManager.getShader("terrain_fragment.glsl", defineMap, osg::Shader::FRAGMENT); - if (!vertexShader || !fragmentShader) - throw std::runtime_error("Unable to create shader"); - - stateset->setAttributeAndModes(shaderManager.getProgram(vertexShader, fragmentShader)); - firstLayer = false; - addPass(stateset); - } - } - - Effect::Effect(bool useShaders, bool forcePerPixelLighting, bool clampLighting, Shader::ShaderManager* shaderManager, const std::vector &layers, const std::vector > &blendmaps, - int blendmapScale, float layerTileSize) - : mShaderManager(shaderManager) - , mUseShaders(useShaders) - , mForcePerPixelLighting(forcePerPixelLighting) - , mClampLighting(clampLighting) - , mLayers(layers) - , mBlendmaps(blendmaps) - , mBlendmapScale(blendmapScale) - , mLayerTileSize(layerTileSize) - { - selectTechnique(0); - } + stateset->setRenderBinDetails(passIndex++, "RenderBin"); - bool Effect::define_techniques() - { - try - { - if (mUseShaders && mShaderManager) - addTechnique(new ShaderTechnique(*mShaderManager, mForcePerPixelLighting, mClampLighting, mLayers, mBlendmaps, mBlendmapScale, mLayerTileSize)); - else - addTechnique(new FixedFunctionTechnique(mLayers, mBlendmaps, mBlendmapScale, mLayerTileSize)); + passes.push_back(stateset); } - catch (std::exception& e) - { - std::cerr << "Error: " << e.what() << std::endl; - addTechnique(new FixedFunctionTechnique(mLayers, mBlendmaps, mBlendmapScale, mLayerTileSize)); - } - - return true; + return passes; } } diff --git a/components/terrain/material.hpp b/components/terrain/material.hpp index c13b21e8f..25aa69540 100644 --- a/components/terrain/material.hpp +++ b/components/terrain/material.hpp @@ -1,8 +1,7 @@ #ifndef COMPONENTS_TERRAIN_MATERIAL_H #define COMPONENTS_TERRAIN_MATERIAL_H -#include -#include +#include #include "defs.hpp" @@ -27,60 +26,9 @@ namespace Terrain bool mSpecular; }; - class FixedFunctionTechnique : public osgFX::Technique - { - public: - FixedFunctionTechnique( - const std::vector& layers, - const std::vector >& 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& layers, - const std::vector >& 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& layers, - const std::vector >& 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 mLayers; - std::vector > mBlendmaps; - int mBlendmapScale; - float mLayerTileSize; - }; + std::vector > createPasses(bool useShaders, bool forcePerPixelLighting, bool clampLighting, Shader::ShaderManager* shaderManager, + const std::vector& layers, + const std::vector >& blendmaps, int blendmapScale, float layerTileSize); } diff --git a/components/terrain/quadtreenode.cpp b/components/terrain/quadtreenode.cpp new file mode 100644 index 000000000..438303c27 --- /dev/null +++ b/components/terrain/quadtreenode.cpp @@ -0,0 +1,169 @@ +#include "quadtreenode.hpp" + +#include + +#include + +#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(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; iinitNeighbours(); +} + +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(&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; +} + +} diff --git a/components/terrain/quadtreenode.hpp b/components/terrain/quadtreenode.hpp new file mode 100644 index 000000000..30e998119 --- /dev/null +++ b/components/terrain/quadtreenode.hpp @@ -0,0 +1,98 @@ +#ifndef OPENMW_COMPONENTS_TERRAIN_QUADTREENODE_H +#define OPENMW_COMPONENTS_TERRAIN_QUADTREENODE_H + +#include + +#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 mLodCallback; + + ViewDataMap* mViewDataMap; + }; + +} + +#endif diff --git a/components/terrain/quadtreeworld.cpp b/components/terrain/quadtreeworld.cpp new file mode 100644 index 000000000..d65b1d5ed --- /dev/null +++ b/components/terrain/quadtreeworld.cpp @@ -0,0 +1,460 @@ +#include "quadtreeworld.hpp" + +#include + +#include + +#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(node->getSize()/mMinSize)); + int lodLevel = Log2(static_cast(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(mMaxX - mMinX); + int origSizeY = static_cast(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(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 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 getRootNode() + { + return mRootNode; + } + +private: + Terrain::Storage* mStorage; + + float mMinX, mMaxX, mMinY, mMaxY; + float mMinSize; + ViewDataMap* mViewDataMap; + + osg::ref_ptr 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(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; igetNumChildren(); ++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; igetNumChildren(); ++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(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(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(&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; igetNumEntries(); ++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(udc->getUserData())); + udc->setUserData(NULL); + } + entry.mRenderingNode->accept(nv); + } + } + + vd->reset(nv.getTraversalNumber()); + + mRootNode->getViewDataMap()->clearUnusedViews(nv.getTraversalNumber()); +} + +void QuadTreeWorld::ensureQuadTreeBuilt() +{ + OpenThreads::ScopedLock 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(view); + traverseToCell(mRootNode.get(), vd, x, y); + + for (unsigned int i=0; igetNumEntries(); ++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(view); + traverse(mRootNode.get(), vd, NULL, mRootNode->getLodCallback(), eyePoint, false); + + for (unsigned int i=0; igetNumEntries(); ++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()); +} + + +} diff --git a/components/terrain/quadtreeworld.hpp b/components/terrain/quadtreeworld.hpp new file mode 100644 index 000000000..8ec75917b --- /dev/null +++ b/components/terrain/quadtreeworld.hpp @@ -0,0 +1,49 @@ +#ifndef COMPONENTS_TERRAIN_QUADTREEWORLD_H +#define COMPONENTS_TERRAIN_QUADTREEWORLD_H + +#include "world.hpp" + +#include + +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 mRootNode; + + osg::ref_ptr mViewDataMap; + + OpenThreads::Mutex mQuadTreeMutex; + bool mQuadTreeBuilt; + }; + +} + +#endif diff --git a/components/terrain/storage.hpp b/components/terrain/storage.hpp index bd5706b25..a4a8bc9fd 100644 --- a/components/terrain/storage.hpp +++ b/components/terrain/storage.hpp @@ -18,6 +18,7 @@ namespace osg namespace Terrain { /// We keep storage of terrain data abstract here since we need different implementations for game and editor + /// @note The implementation must be thread safe. class Storage { public: @@ -78,6 +79,8 @@ namespace Terrain /// Get the number of vertices on one side for each cell. Should be (power of two)+1 virtual int getCellVertices() = 0; + + virtual int getBlendmapScale(float chunkSize) = 0; }; } diff --git a/components/terrain/terraindrawable.cpp b/components/terrain/terraindrawable.cpp new file mode 100644 index 000000000..f3080e31c --- /dev/null +++ b/components/terrain/terraindrawable.cpp @@ -0,0 +1,89 @@ +#include "terraindrawable.hpp" + +#include + +#include + +namespace Terrain +{ + +TerrainDrawable::TerrainDrawable() +{ +} + +TerrainDrawable::TerrainDrawable(const TerrainDrawable ©, const osg::CopyOp ©op) + : 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(&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); +} + +} + diff --git a/components/terrain/terraindrawable.hpp b/components/terrain/terraindrawable.hpp new file mode 100644 index 000000000..79a28deeb --- /dev/null +++ b/components/terrain/terraindrawable.hpp @@ -0,0 +1,53 @@ +#ifndef OPENMW_COMPONENTS_TERRAIN_DRAWABLE_H +#define OPENMW_COMPONENTS_TERRAIN_DRAWABLE_H + +#include + +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(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 > PassVector; + void setPasses (const PassVector& passes); + + void setLightListCallback(SceneUtil::LightListCallback* lightListCallback); + + virtual void compileGLObjects(osg::RenderInfo& renderInfo) const; + + private: + PassVector mPasses; + + osg::ref_ptr mLightListCallback; + }; + +} + + +#endif diff --git a/components/terrain/terraingrid.cpp b/components/terrain/terraingrid.cpp index 6113178d3..466cddddc 100644 --- a/components/terrain/terraingrid.cpp +++ b/components/terrain/terraingrid.cpp @@ -2,63 +2,25 @@ #include -#include +#include -#include +#include "chunkmanager.hpp" -#include -#include -#include - -#include -#include -#include - -#include - -#include -#include - -#include - -#include - -#include "material.hpp" -#include "storage.hpp" - -namespace +namespace Terrain { - class StaticBoundingBoxCallback : public osg::Drawable::ComputeBoundingBoxCallback - { - public: - StaticBoundingBoxCallback(const osg::BoundingBox& bounds) - : mBoundingBox(bounds) - { - } - - virtual osg::BoundingBox computeBound(const osg::Drawable&) const - { - return mBoundingBox; - } - - private: - osg::BoundingBox mBoundingBox; - }; -} -namespace Terrain +class MyView : public View { +public: + osg::ref_ptr mLoaded; + + virtual void reset(unsigned int frame) {} +}; -TerrainGrid::TerrainGrid(osg::Group* parent, Resource::ResourceSystem* resourceSystem, osgUtil::IncrementalCompileOperation* ico, Storage* storage, int nodeMask, Shader::ShaderManager* shaderManager, SceneUtil::UnrefQueue* unrefQueue) - : Terrain::World(parent, resourceSystem, ico, storage, nodeMask) +TerrainGrid::TerrainGrid(osg::Group* parent, osg::Group* compileRoot, Resource::ResourceSystem* resourceSystem, Storage* storage, int nodeMask, int preCompileMask) + : Terrain::World(parent, compileRoot, resourceSystem, storage, nodeMask, preCompileMask) , mNumSplits(4) - , mCache((storage->getCellVertices()-1)/static_cast(mNumSplits) + 1) - , mUnrefQueue(unrefQueue) - , mShaderManager(shaderManager) { - osg::ref_ptr material (new osg::Material); - material->setColorMode(osg::Material::AMBIENT_AND_DIFFUSE); - mTerrainRoot->getOrCreateStateSet()->setAttributeAndModes(material, osg::StateAttribute::ON); } TerrainGrid::~TerrainGrid() @@ -69,19 +31,10 @@ TerrainGrid::~TerrainGrid() } } -osg::ref_ptr TerrainGrid::cacheCell(int x, int y) +void TerrainGrid::cacheCell(View* view, int x, int y) { - { - OpenThreads::ScopedLock lock(mGridCacheMutex); - Grid::iterator found = mGridCache.find(std::make_pair(x,y)); - if (found != mGridCache.end()) - return found->second; - } - osg::ref_ptr node = buildTerrain(NULL, 1.f, osg::Vec2f(x+0.5, y+0.5)); - - OpenThreads::ScopedLock lock(mGridCacheMutex); - mGridCache.insert(std::make_pair(std::make_pair(x,y), node)); - return node; + osg::Vec2f center(x+0.5f, y+0.5f); + static_cast(view)->mLoaded = buildTerrain(NULL, 1.f, center); } osg::ref_ptr TerrainGrid::buildTerrain (osg::Group* parent, float chunkSize, const osg::Vec2f& chunkCenter) @@ -102,136 +55,13 @@ osg::ref_ptr TerrainGrid::buildTerrain (osg::Group* parent, float chu } else { - float minH, maxH; - if (!mStorage->getMinMaxHeights(chunkSize, chunkCenter, minH, maxH)) - return NULL; // no terrain defined - - osg::Vec2f worldCenter = chunkCenter*mStorage->getCellWorldSize(); - osg::ref_ptr transform (new SceneUtil::PositionAttitudeTransform); - transform->setPosition(osg::Vec3f(worldCenter.x(), worldCenter.y(), 0.f)); - + osg::ref_ptr node = mChunkManager->getChunk(chunkSize, chunkCenter, 0, 0); + if (!node) + return NULL; if (parent) - parent->addChild(transform); - - osg::ref_ptr positions (new osg::Vec3Array); - osg::ref_ptr normals (new osg::Vec3Array); - osg::ref_ptr colors (new osg::Vec4Array); - - osg::ref_ptr vbo (new osg::VertexBufferObject); - positions->setVertexBufferObject(vbo); - normals->setVertexBufferObject(vbo); - colors->setVertexBufferObject(vbo); - - mStorage->fillVertexBuffers(0, chunkSize, chunkCenter, positions, normals, colors); - - osg::ref_ptr 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 layerList; - std::vector > blendmaps; - mStorage->getBlendmaps(chunkSize, chunkCenter, false, blendmaps, layerList); - - // For compiling textures, I don't think the osgFX::Effect does it correctly - osg::ref_ptr 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 layers; - { - OpenThreads::ScopedLock lock(mTextureCacheMutex); - for (std::vector::const_iterator it = layerList.begin(); it != layerList.end(); ++it) - { - TextureLayer textureLayer; - textureLayer.mParallax = it->mParallax; - textureLayer.mSpecular = it->mSpecular; - osg::ref_ptr texture = mTextureCache[it->mDiffuseMap]; - if (!texture) - { - texture = new osg::Texture2D(mResourceSystem->getImageManager()->getImage(it->mDiffuseMap)); - texture->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT); - texture->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT); - mResourceSystem->getSceneManager()->applyFilterSettings(texture); - mTextureCache[it->mDiffuseMap] = texture; - } - textureLayer.mDiffuseMap = texture; - textureCompileDummy->getOrCreateStateSet()->setTextureAttributeAndModes(dummyTextureCounter++, texture); - - if (!it->mNormalMap.empty()) - { - texture = mTextureCache[it->mNormalMap]; - if (!texture) - { - texture = new osg::Texture2D(mResourceSystem->getImageManager()->getImage(it->mNormalMap)); - texture->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT); - texture->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT); - mResourceSystem->getSceneManager()->applyFilterSettings(texture); - mTextureCache[it->mNormalMap] = texture; - } - textureCompileDummy->getOrCreateStateSet()->setTextureAttributeAndModes(dummyTextureCounter++, texture); - textureLayer.mNormalMap = texture; - } - - if (it->requiresShaders()) - useShaders = true; - - layers.push_back(textureLayer); - } - } + parent->addChild(node); - std::vector > blendmapTextures; - for (std::vector >::const_iterator it = blendmaps.begin(); it != blendmaps.end(); ++it) - { - osg::ref_ptr 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 effect (new Terrain::Effect(mShaderManager ? useShaders : false, mResourceSystem->getSceneManager()->getForcePerPixelLighting(), mResourceSystem->getSceneManager()->getClampLighting(), - mShaderManager, layers, blendmapTextures, blendmapScale, blendmapScale)); - - effect->addCullCallback(new SceneUtil::LightListCallback); - - transform->addChild(effect); - - osg::Node* toAttach = geometry.get(); - - effect->addChild(toAttach); - - if (mIncrementalCompileOperation) - { - mIncrementalCompileOperation->add(toAttach); - mIncrementalCompileOperation->add(textureCompileDummy); - } - - return transform; + return node; } } @@ -240,27 +70,10 @@ void TerrainGrid::loadCell(int x, int y) if (mGrid.find(std::make_pair(x, y)) != mGrid.end()) return; // already loaded - // try to get it from the cache - osg::ref_ptr terrainNode; - { - OpenThreads::ScopedLock lock(mGridCacheMutex); - Grid::const_iterator found = mGridCache.find(std::make_pair(x,y)); - if (found != mGridCache.end()) - { - terrainNode = found->second; - if (!terrainNode) - return; // no terrain defined - } - } - - // didn't find in cache, build it + osg::Vec2f center(x+0.5f, y+0.5f); + osg::ref_ptr terrainNode = buildTerrain(NULL, 1.f, center); if (!terrainNode) - { - osg::Vec2f center(x+0.5f, y+0.5f); - terrainNode = buildTerrain(NULL, 1.f, center); - if (!terrainNode) - return; // no terrain defined - } + return; // no terrain defined mTerrainRoot->addChild(terrainNode); @@ -276,54 +89,12 @@ void TerrainGrid::unloadCell(int x, int y) osg::ref_ptr terrainNode = it->second; mTerrainRoot->removeChild(terrainNode); - if (mUnrefQueue.get()) - mUnrefQueue->push(terrainNode); - mGrid.erase(it); } -void TerrainGrid::updateCache() +View *TerrainGrid::createView() { - { - OpenThreads::ScopedLock lock(mGridCacheMutex); - for (Grid::iterator it = mGridCache.begin(); it != mGridCache.end();) - { - if (it->second->referenceCount() <= 1) - mGridCache.erase(it++); - else - ++it; - } - } - - { - OpenThreads::ScopedLock 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 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 lock(mGridCacheMutex); - stats->setAttribute(frameNumber, "Terrain Cell", mGridCache.size()); - } - { - OpenThreads::ScopedLock lock(mTextureCacheMutex); - stats->setAttribute(frameNumber, "Terrain Texture", mTextureCache.size()); - } + return new MyView; } } diff --git a/components/terrain/terraingrid.hpp b/components/terrain/terraingrid.hpp index d619ed8f7..189fe7f63 100644 --- a/components/terrain/terraingrid.hpp +++ b/components/terrain/terraingrid.hpp @@ -1,39 +1,23 @@ #ifndef COMPONENTS_TERRAIN_TERRAINGRID_H #define COMPONENTS_TERRAIN_TERRAINGRID_H +#include + #include #include "world.hpp" -namespace SceneUtil -{ - class UnrefQueue; -} - -namespace Shader -{ - class ShaderManager; -} - -namespace osg -{ - class Texture2D; -} - namespace Terrain { - /// @brief Simple terrain implementation that loads cells in a grid, with no LOD + /// @brief Simple terrain implementation that loads cells in a grid, with no LOD. Only requested cells are loaded. class TerrainGrid : public Terrain::World { public: - TerrainGrid(osg::Group* parent, Resource::ResourceSystem* resourceSystem, osgUtil::IncrementalCompileOperation* ico, Storage* storage, int nodeMask, Shader::ShaderManager* shaderManager = NULL, SceneUtil::UnrefQueue* unrefQueue = NULL); + TerrainGrid(osg::Group* parent, osg::Group* compileRoot, Resource::ResourceSystem* resourceSystem, Storage* storage, int nodeMask, int preCompileMask=~0); ~TerrainGrid(); - /// Load a terrain cell and store it in cache for later use. - /// @note The returned ref_ptr should be kept by the caller to ensure that the terrain stays in cache for as long as needed. - /// @note Thread safe. - virtual osg::ref_ptr cacheCell(int x, int y); + virtual void cacheCell(View* view, int x, int y); /// @note Not thread safe. virtual void loadCell(int x, int y); @@ -41,15 +25,7 @@ namespace Terrain /// @note Not thread safe. virtual void unloadCell(int x, int y); - /// Clear cached objects that are no longer referenced - /// @note Thread safe. - void updateCache(); - - /// Apply the scene manager's texture filtering settings to all cached textures. - /// @note Thread safe. - void updateTextureFiltering(); - - void reportStats(unsigned int frameNumber, osg::Stats *stats); + View* createView(); private: osg::ref_ptr buildTerrain (osg::Group* parent, float chunkSize, const osg::Vec2f& chunkCenter); @@ -57,21 +33,8 @@ namespace Terrain // split each ESM::Cell into mNumSplits*mNumSplits terrain chunks unsigned int mNumSplits; - typedef std::map > TextureCache; - TextureCache mTextureCache; - OpenThreads::Mutex mTextureCacheMutex; - typedef std::map, osg::ref_ptr > Grid; Grid mGrid; - - Grid mGridCache; - OpenThreads::Mutex mGridCacheMutex; - - BufferCache mCache; - - osg::ref_ptr mUnrefQueue; - - Shader::ShaderManager* mShaderManager; }; } diff --git a/components/terrain/texturemanager.cpp b/components/terrain/texturemanager.cpp new file mode 100644 index 000000000..b901fa159 --- /dev/null +++ b/components/terrain/texturemanager.cpp @@ -0,0 +1,64 @@ +#include "texturemanager.hpp" + +#include +#include + +#include +#include +#include + +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(obj)); + } +}; + +void TextureManager::updateTextureFiltering() +{ + UpdateTextureFilteringFunctor f(mSceneManager); + mCache->call(f); +} + +osg::ref_ptr 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 obj = mCache->getRefFromObjectCache(name); + if (obj) + return static_cast(obj.get()); + else + { + osg::ref_ptr 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()); +} + + + +} diff --git a/components/terrain/texturemanager.hpp b/components/terrain/texturemanager.hpp new file mode 100644 index 000000000..e1205606e --- /dev/null +++ b/components/terrain/texturemanager.hpp @@ -0,0 +1,39 @@ +#ifndef OPENMW_COMPONENTS_TERRAIN_TEXTUREMANAGER_H +#define OPENMW_COMPONENTS_TERRAIN_TEXTUREMANAGER_H + +#include + +#include + +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 getTexture(const std::string& name); + + virtual void reportStats(unsigned int frameNumber, osg::Stats* stats) const; + + private: + Resource::SceneManager* mSceneManager; + + }; + +} + +#endif diff --git a/components/terrain/viewdata.cpp b/components/terrain/viewdata.cpp new file mode 100644 index 000000000..7b3df56b9 --- /dev/null +++ b/components/terrain/viewdata.cpp @@ -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; isetViewer(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(); +} + + +} diff --git a/components/terrain/viewdata.hpp b/components/terrain/viewdata.hpp new file mode 100644 index 000000000..30563d566 --- /dev/null +++ b/components/terrain/viewdata.hpp @@ -0,0 +1,85 @@ +#ifndef OPENMW_COMPONENTS_TERRAIN_VIEWDATA_H +#define OPENMW_COMPONENTS_TERRAIN_VIEWDATA_H + +#include +#include + +#include + +#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 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 mEntries; + unsigned int mNumEntries; + unsigned int mFrameLastUsed; + bool mChanged; + osg::ref_ptr 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 mViewVector; + + typedef std::map Map; + Map mViews; + + std::deque mUnusedViews; + }; + +} + +#endif diff --git a/components/terrain/world.cpp b/components/terrain/world.cpp index f699cdc75..b7cc0ae01 100644 --- a/components/terrain/world.cpp +++ b/components/terrain/world.cpp @@ -1,32 +1,67 @@ #include "world.hpp" #include -#include +#include +#include + +#include #include "storage.hpp" +#include "texturemanager.hpp" +#include "chunkmanager.hpp" +#include "compositemaprenderer.hpp" namespace Terrain { -World::World(osg::Group* parent, Resource::ResourceSystem* resourceSystem, osgUtil::IncrementalCompileOperation* ico, - Storage* storage, int nodeMask) +World::World(osg::Group* parent, osg::Group* compileRoot, Resource::ResourceSystem* resourceSystem, Storage* storage, int nodeMask, int preCompileMask) : mStorage(storage) , mParent(parent) , mResourceSystem(resourceSystem) - , mIncrementalCompileOperation(ico) { mTerrainRoot = new osg::Group; mTerrainRoot->setNodeMask(nodeMask); mTerrainRoot->getOrCreateStateSet()->setRenderingHint(osg::StateSet::OPAQUE_BIN); + osg::ref_ptr material (new osg::Material); + material->setColorMode(osg::Material::AMBIENT_AND_DIFFUSE); + mTerrainRoot->getOrCreateStateSet()->setAttributeAndModes(material, osg::StateAttribute::ON); + mTerrainRoot->setName("Terrain Root"); + osg::ref_ptr compositeCam = new osg::Camera; + compositeCam->setRenderOrder(osg::Camera::PRE_RENDER, -1); + compositeCam->setProjectionMatrix(osg::Matrix::identity()); + compositeCam->setViewMatrix(osg::Matrix::identity()); + compositeCam->setReferenceFrame(osg::Camera::ABSOLUTE_RF); + compositeCam->setClearMask(0); + compositeCam->setNodeMask(preCompileMask); + mCompositeMapCamera = compositeCam; + + compileRoot->addChild(compositeCam); + + + mCompositeMapRenderer = new CompositeMapRenderer; + compositeCam->addChild(mCompositeMapRenderer); + mParent->addChild(mTerrainRoot); + + mTextureManager.reset(new TextureManager(mResourceSystem->getSceneManager())); + mChunkManager.reset(new ChunkManager(mStorage, mResourceSystem->getSceneManager(), mTextureManager.get(), mCompositeMapRenderer)); + + mResourceSystem->addResourceManager(mChunkManager.get()); + mResourceSystem->addResourceManager(mTextureManager.get()); } World::~World() { + mResourceSystem->removeResourceManager(mChunkManager.get()); + mResourceSystem->removeResourceManager(mTextureManager.get()); + mParent->removeChild(mTerrainRoot); + mCompositeMapCamera->removeChild(mCompositeMapRenderer); + mCompositeMapCamera->getParent(0)->removeChild(mCompositeMapCamera); + delete mStorage; } @@ -35,4 +70,9 @@ float World::getHeightAt(const osg::Vec3f &worldPos) return mStorage->getHeightAt(worldPos); } +void World::updateTextureFiltering() +{ + mTextureManager->updateTextureFiltering(); +} + } diff --git a/components/terrain/world.hpp b/components/terrain/world.hpp index cc2285e45..6aac963ce 100644 --- a/components/terrain/world.hpp +++ b/components/terrain/world.hpp @@ -2,19 +2,18 @@ #define COMPONENTS_TERRAIN_WORLD_H #include +#include +#include + +#include #include "defs.hpp" -#include "buffercache.hpp" namespace osg { class Group; class Stats; -} - -namespace osgUtil -{ - class IncrementalCompileOperation; + class Node; } namespace Resource @@ -26,6 +25,23 @@ namespace Terrain { class Storage; + class TextureManager; + class ChunkManager; + class CompositeMapRenderer; + + /** + * @brief A View is a collection of rendering objects that are visible from a given camera/intersection. + * The base View class is part of the interface for usage in conjunction with preload feature. + */ + class View : public osg::Referenced + { + public: + virtual ~View() {} + + /// Reset internal structure so that the next addition to the view will override the previous frame's contents. + virtual void reset(unsigned int frame) = 0; + }; + /** * @brief The basic interface for a terrain world. How the terrain chunks are paged and displayed * is up to the implementation. @@ -36,24 +52,41 @@ namespace Terrain /// @note takes ownership of \a storage /// @param storage Storage instance to get terrain data from (heights, normals, colors, textures..) /// @param nodeMask mask for the terrain root - World(osg::Group* parent, Resource::ResourceSystem* resourceSystem, osgUtil::IncrementalCompileOperation* ico, - Storage* storage, int nodeMask); + /// @param preCompileMask mask for pre compiling textures + World(osg::Group* parent, osg::Group* compileRoot, Resource::ResourceSystem* resourceSystem, Storage* storage, int nodeMask, int preCompileMask); virtual ~World(); - virtual void updateTextureFiltering() {} - - virtual void updateCache() {} - - virtual void reportStats(unsigned int frameNumber, osg::Stats* stats) {} + /// Apply the scene manager's texture filtering settings to all cached textures. + /// @note Thread safe. + void updateTextureFiltering(); float getHeightAt (const osg::Vec3f& worldPos); - virtual osg::ref_ptr cacheCell(int x, int y) {return NULL;} + /// Load a terrain cell at maximum LOD and store it in the View for later use. + /// @note Thread safe. + virtual void cacheCell(View* view, int x, int y) {} - // This is only a hint and may be ignored by the implementation. + /// Load the cell into the scene graph. + /// @note Not thread safe. + /// @note May be ignored by derived implementations that don't organize the terrain into cells. virtual void loadCell(int x, int y) {} + + /// Remove the cell from the scene graph. + /// @note Not thread safe. + /// @note May be ignored by derived implementations that don't organize the terrain into cells. virtual void unloadCell(int x, int y) {} + virtual void enable(bool enabled) {} + + /// Create a View to use with preload feature. The caller is responsible for deleting the view. + /// @note Thread safe. + virtual View* createView() { return NULL; } + + /// @note Thread safe, as long as you do not attempt to load into the same view from multiple threads. + virtual void preload(View* view, const osg::Vec3f& eyePoint) {} + + virtual void reportStats(unsigned int frameNumber, osg::Stats* stats) {} + Storage* getStorage() { return mStorage; } protected: @@ -62,9 +95,13 @@ namespace Terrain osg::ref_ptr mParent; osg::ref_ptr mTerrainRoot; + osg::ref_ptr mCompositeMapCamera; + osg::ref_ptr mCompositeMapRenderer; + Resource::ResourceSystem* mResourceSystem; - osg::ref_ptr mIncrementalCompileOperation; + std::auto_ptr mTextureManager; + std::auto_ptr mChunkManager; }; } diff --git a/docs/source/reference/modding/settings/camera.rst b/docs/source/reference/modding/settings/camera.rst index c9b69add6..2edc98d02 100644 --- a/docs/source/reference/modding/settings/camera.rst +++ b/docs/source/reference/modding/settings/camera.rst @@ -59,7 +59,7 @@ The constant 8192 is the size of a cell, and 1024 is the threshold distance for Reductions of up to 25% or more can be required to completely eliminate pop-in for wide fields of view and long viewing distances near the edges of the screen, but such situations are unusual and probably not worth the performance penalty introduced by loading geometry obscured by fog in the center of the screen. See RenderingManager::configureFog for the relevant source code. -Enabling the distant land setting is an alternative to increasing exterior cell load distance. Note that the distant land setting does not include rendering of distant static objects, so the resulting visual effect is not the same. +Enabling the distant terrain setting is an alternative to increasing exterior cell load distance. Note that the distant land setting does not include rendering of distant static objects, so the resulting visual effect is not the same. The default value is 6666.0. This setting can be adjusted in game from the ridiculously low value of 2000.0 to a maximum of 6666.0, using the View Distance slider in the Detail tab of the Video panel of the Options menu. diff --git a/docs/source/reference/modding/settings/index.rst b/docs/source/reference/modding/settings/index.rst index 345c29dda..0d0c9323d 100644 --- a/docs/source/reference/modding/settings/index.rst +++ b/docs/source/reference/modding/settings/index.rst @@ -23,6 +23,7 @@ Although this guide attempts to be comprehensive and up to date, you will always input saves sound + terrain video water windows diff --git a/docs/source/reference/modding/settings/terrain.rst b/docs/source/reference/modding/settings/terrain.rst new file mode 100644 index 000000000..8c550b269 --- /dev/null +++ b/docs/source/reference/modding/settings/terrain.rst @@ -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. diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 91329b8ca..55f550d5a 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -76,6 +76,11 @@ preload cell expiry delay = 5 # How long to keep models/textures/collision shapes in cache after they're no longer referenced/required (in seconds) cache expiry delay = 5 +[Terrain] + +# If true, use paging and LOD algorithms to display the entire terrain. If false, only display terrain of the loaded cells +distant terrain = false + [Map] # Size of each exterior cell in pixels in the world map. (e.g. 12 to 24).