forked from mirror/openmw-tes3mp
commit
60037e4081
@ -0,0 +1,48 @@
|
|||||||
|
#include "landmanager.hpp"
|
||||||
|
|
||||||
|
#include <osg/Stats>
|
||||||
|
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
#include <components/resource/objectcache.hpp>
|
||||||
|
|
||||||
|
#include "../mwbase/environment.hpp"
|
||||||
|
#include "../mwbase/world.hpp"
|
||||||
|
#include "../mwworld/esmstore.hpp"
|
||||||
|
|
||||||
|
namespace MWRender
|
||||||
|
{
|
||||||
|
|
||||||
|
LandManager::LandManager(int loadFlags)
|
||||||
|
: ResourceManager(NULL)
|
||||||
|
, mLoadFlags(loadFlags)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
osg::ref_ptr<ESMTerrain::LandObject> LandManager::getLand(int x, int y)
|
||||||
|
{
|
||||||
|
std::ostringstream id;
|
||||||
|
id << x << " " << y;
|
||||||
|
std::string idstr = id.str();
|
||||||
|
|
||||||
|
osg::ref_ptr<osg::Object> obj = mCache->getRefFromObjectCache(idstr);
|
||||||
|
if (obj)
|
||||||
|
return static_cast<ESMTerrain::LandObject*>(obj.get());
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const ESM::Land* land = MWBase::Environment::get().getWorld()->getStore().get<ESM::Land>().search(x,y);
|
||||||
|
if (!land)
|
||||||
|
return NULL;
|
||||||
|
osg::ref_ptr<ESMTerrain::LandObject> landObj (new ESMTerrain::LandObject(land, mLoadFlags));
|
||||||
|
mCache->addEntryToObjectCache(idstr, landObj.get());
|
||||||
|
return landObj;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void LandManager::reportStats(unsigned int frameNumber, osg::Stats *stats) const
|
||||||
|
{
|
||||||
|
stats->setAttribute(frameNumber, "Land", mCache->getCacheSize());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,33 @@
|
|||||||
|
#ifndef OPENMW_COMPONENTS_ESMTERRAIN_LANDMANAGER_H
|
||||||
|
#define OPENMW_COMPONENTS_ESMTERRAIN_LANDMANAGER_H
|
||||||
|
|
||||||
|
#include <osg/Object>
|
||||||
|
|
||||||
|
#include <components/resource/resourcemanager.hpp>
|
||||||
|
#include <components/esmterrain/storage.hpp>
|
||||||
|
|
||||||
|
namespace ESM
|
||||||
|
{
|
||||||
|
struct Land;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace MWRender
|
||||||
|
{
|
||||||
|
|
||||||
|
class LandManager : public Resource::ResourceManager
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
LandManager(int loadFlags);
|
||||||
|
|
||||||
|
/// @note Will return NULL if not found.
|
||||||
|
osg::ref_ptr<ESMTerrain::LandObject> getLand(int x, int y);
|
||||||
|
|
||||||
|
virtual void reportStats(unsigned int frameNumber, osg::Stats* stats) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
int mLoadFlags;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
@ -0,0 +1,235 @@
|
|||||||
|
#include "chunkmanager.hpp"
|
||||||
|
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
#include <osg/Texture2D>
|
||||||
|
|
||||||
|
#include <osgUtil/IncrementalCompileOperation>
|
||||||
|
|
||||||
|
#include <components/resource/objectcache.hpp>
|
||||||
|
#include <components/resource/scenemanager.hpp>
|
||||||
|
|
||||||
|
#include <components/sceneutil/positionattitudetransform.hpp>
|
||||||
|
#include <components/sceneutil/lightmanager.hpp>
|
||||||
|
|
||||||
|
#include "terraindrawable.hpp"
|
||||||
|
#include "material.hpp"
|
||||||
|
#include "storage.hpp"
|
||||||
|
#include "texturemanager.hpp"
|
||||||
|
#include "compositemaprenderer.hpp"
|
||||||
|
|
||||||
|
namespace Terrain
|
||||||
|
{
|
||||||
|
|
||||||
|
ChunkManager::ChunkManager(Storage *storage, Resource::SceneManager *sceneMgr, TextureManager* textureManager, CompositeMapRenderer* renderer)
|
||||||
|
: ResourceManager(NULL)
|
||||||
|
, mStorage(storage)
|
||||||
|
, mSceneManager(sceneMgr)
|
||||||
|
, mTextureManager(textureManager)
|
||||||
|
, mCompositeMapRenderer(renderer)
|
||||||
|
, mCompositeMapSize(512)
|
||||||
|
, mCullingActive(true)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
osg::ref_ptr<osg::Node> ChunkManager::getChunk(float size, const osg::Vec2f ¢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<osg::Object> obj = mCache->getRefFromObjectCache(id);
|
||||||
|
if (obj)
|
||||||
|
return obj->asNode();
|
||||||
|
else
|
||||||
|
{
|
||||||
|
osg::ref_ptr<osg::Node> node = createChunk(size, center, lod, lodFlags);
|
||||||
|
mCache->addEntryToObjectCache(id, node.get());
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChunkManager::reportStats(unsigned int frameNumber, osg::Stats *stats) const
|
||||||
|
{
|
||||||
|
stats->setAttribute(frameNumber, "Terrain Chunk", mCache->getCacheSize());
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChunkManager::setCullingActive(bool active)
|
||||||
|
{
|
||||||
|
mCullingActive = active;
|
||||||
|
}
|
||||||
|
|
||||||
|
osg::ref_ptr<osg::Texture2D> ChunkManager::createCompositeMapRTT()
|
||||||
|
{
|
||||||
|
osg::ref_ptr<osg::Texture2D> texture = new osg::Texture2D;
|
||||||
|
texture->setTextureWidth(mCompositeMapSize);
|
||||||
|
texture->setTextureHeight(mCompositeMapSize);
|
||||||
|
texture->setInternalFormat(GL_RGB);
|
||||||
|
texture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR);
|
||||||
|
texture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR);
|
||||||
|
texture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE);
|
||||||
|
texture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE);
|
||||||
|
|
||||||
|
return texture;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChunkManager::createCompositeMapGeometry(float chunkSize, const osg::Vec2f& chunkCenter, const osg::Vec4f& texCoords, CompositeMap& compositeMap)
|
||||||
|
{
|
||||||
|
if (chunkSize > 1.f)
|
||||||
|
{
|
||||||
|
createCompositeMapGeometry(chunkSize/2.f, chunkCenter + osg::Vec2f(chunkSize/4.f, chunkSize/4.f), osg::Vec4f(texCoords.x() + texCoords.z()/2.f, texCoords.y(), texCoords.z()/2.f, texCoords.w()/2.f), compositeMap);
|
||||||
|
createCompositeMapGeometry(chunkSize/2.f, chunkCenter + osg::Vec2f(-chunkSize/4.f, chunkSize/4.f), osg::Vec4f(texCoords.x(), texCoords.y(), texCoords.z()/2.f, texCoords.w()/2.f), compositeMap);
|
||||||
|
createCompositeMapGeometry(chunkSize/2.f, chunkCenter + osg::Vec2f(chunkSize/4.f, -chunkSize/4.f), osg::Vec4f(texCoords.x() + texCoords.z()/2.f, texCoords.y()+texCoords.w()/2.f, texCoords.z()/2.f, texCoords.w()/2.f), compositeMap);
|
||||||
|
createCompositeMapGeometry(chunkSize/2.f, chunkCenter + osg::Vec2f(-chunkSize/4.f, -chunkSize/4.f), osg::Vec4f(texCoords.x(), texCoords.y()+texCoords.w()/2.f, texCoords.z()/2.f, texCoords.w()/2.f), compositeMap);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
float left = texCoords.x()*2.f-1;
|
||||||
|
float top = texCoords.y()*2.f-1;
|
||||||
|
float width = texCoords.z()*2.f;
|
||||||
|
float height = texCoords.w()*2.f;
|
||||||
|
|
||||||
|
std::vector<osg::ref_ptr<osg::StateSet> > passes = createPasses(chunkSize, chunkCenter, true);
|
||||||
|
for (std::vector<osg::ref_ptr<osg::StateSet> >::iterator it = passes.begin(); it != passes.end(); ++it)
|
||||||
|
{
|
||||||
|
osg::ref_ptr<osg::Geometry> geom = osg::createTexturedQuadGeometry(osg::Vec3(left,top,0), osg::Vec3(width,0,0), osg::Vec3(0,height,0));
|
||||||
|
geom->setUseDisplayList(false); // don't bother making a display list for an object that is just rendered once.
|
||||||
|
geom->setUseVertexBufferObjects(false);
|
||||||
|
geom->setTexCoordArray(1, geom->getTexCoordArray(0), osg::Array::BIND_PER_VERTEX);
|
||||||
|
|
||||||
|
geom->setStateSet(*it);
|
||||||
|
|
||||||
|
compositeMap.mDrawables.push_back(geom);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<osg::ref_ptr<osg::StateSet> > ChunkManager::createPasses(float chunkSize, const osg::Vec2f &chunkCenter, bool forCompositeMap)
|
||||||
|
{
|
||||||
|
std::vector<LayerInfo> layerList;
|
||||||
|
std::vector<osg::ref_ptr<osg::Image> > blendmaps;
|
||||||
|
mStorage->getBlendmaps(chunkSize, chunkCenter, false, blendmaps, layerList);
|
||||||
|
|
||||||
|
bool useShaders = mSceneManager->getForceShaders();
|
||||||
|
if (!mSceneManager->getClampLighting())
|
||||||
|
useShaders = true; // always use shaders when lighting is unclamped, this is to avoid lighting seams between a terrain chunk with normal maps and one without normal maps
|
||||||
|
|
||||||
|
std::vector<TextureLayer> layers;
|
||||||
|
{
|
||||||
|
for (std::vector<LayerInfo>::const_iterator it = layerList.begin(); it != layerList.end(); ++it)
|
||||||
|
{
|
||||||
|
TextureLayer textureLayer;
|
||||||
|
textureLayer.mParallax = it->mParallax;
|
||||||
|
textureLayer.mSpecular = it->mSpecular;
|
||||||
|
|
||||||
|
textureLayer.mDiffuseMap = mTextureManager->getTexture(it->mDiffuseMap);
|
||||||
|
|
||||||
|
if (!forCompositeMap && !it->mNormalMap.empty())
|
||||||
|
textureLayer.mNormalMap = mTextureManager->getTexture(it->mNormalMap);
|
||||||
|
|
||||||
|
if (it->requiresShaders())
|
||||||
|
useShaders = true;
|
||||||
|
|
||||||
|
layers.push_back(textureLayer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (forCompositeMap)
|
||||||
|
useShaders = false;
|
||||||
|
|
||||||
|
std::vector<osg::ref_ptr<osg::Texture2D> > blendmapTextures;
|
||||||
|
for (std::vector<osg::ref_ptr<osg::Image> >::const_iterator it = blendmaps.begin(); it != blendmaps.end(); ++it)
|
||||||
|
{
|
||||||
|
osg::ref_ptr<osg::Texture2D> texture (new osg::Texture2D);
|
||||||
|
texture->setImage(*it);
|
||||||
|
texture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE);
|
||||||
|
texture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE);
|
||||||
|
texture->setResizeNonPowerOfTwoHint(false);
|
||||||
|
blendmapTextures.push_back(texture);
|
||||||
|
}
|
||||||
|
|
||||||
|
float blendmapScale = mStorage->getBlendmapScale(chunkSize);
|
||||||
|
|
||||||
|
return ::Terrain::createPasses(useShaders, mSceneManager->getForcePerPixelLighting(),
|
||||||
|
mSceneManager->getClampLighting(), &mSceneManager->getShaderManager(), layers, blendmapTextures, blendmapScale, blendmapScale);
|
||||||
|
}
|
||||||
|
|
||||||
|
osg::ref_ptr<osg::Node> ChunkManager::createChunk(float chunkSize, const osg::Vec2f &chunkCenter, int lod, unsigned int lodFlags)
|
||||||
|
{
|
||||||
|
osg::Vec2f worldCenter = chunkCenter*mStorage->getCellWorldSize();
|
||||||
|
osg::ref_ptr<SceneUtil::PositionAttitudeTransform> transform (new SceneUtil::PositionAttitudeTransform);
|
||||||
|
transform->setPosition(osg::Vec3f(worldCenter.x(), worldCenter.y(), 0.f));
|
||||||
|
|
||||||
|
osg::ref_ptr<osg::Vec3Array> positions (new osg::Vec3Array);
|
||||||
|
osg::ref_ptr<osg::Vec3Array> normals (new osg::Vec3Array);
|
||||||
|
osg::ref_ptr<osg::Vec4Array> colors (new osg::Vec4Array);
|
||||||
|
|
||||||
|
osg::ref_ptr<osg::VertexBufferObject> vbo (new osg::VertexBufferObject);
|
||||||
|
positions->setVertexBufferObject(vbo);
|
||||||
|
normals->setVertexBufferObject(vbo);
|
||||||
|
colors->setVertexBufferObject(vbo);
|
||||||
|
|
||||||
|
mStorage->fillVertexBuffers(lod, chunkSize, chunkCenter, positions, normals, colors);
|
||||||
|
|
||||||
|
osg::ref_ptr<TerrainDrawable> geometry (new TerrainDrawable);
|
||||||
|
geometry->setVertexArray(positions);
|
||||||
|
geometry->setNormalArray(normals, osg::Array::BIND_PER_VERTEX);
|
||||||
|
geometry->setColorArray(colors, osg::Array::BIND_PER_VERTEX);
|
||||||
|
geometry->setUseDisplayList(false);
|
||||||
|
geometry->setUseVertexBufferObjects(true);
|
||||||
|
|
||||||
|
if (chunkSize <= 2.f)
|
||||||
|
geometry->setLightListCallback(new SceneUtil::LightListCallback);
|
||||||
|
|
||||||
|
unsigned int numVerts = (mStorage->getCellVertices()-1) * chunkSize / (1 << lod) + 1;
|
||||||
|
|
||||||
|
geometry->addPrimitiveSet(mBufferCache.getIndexBuffer(numVerts, lodFlags));
|
||||||
|
|
||||||
|
bool useCompositeMap = chunkSize >= 1.f;
|
||||||
|
unsigned int numUvSets = useCompositeMap ? 1 : 2;
|
||||||
|
|
||||||
|
for (unsigned int i=0; i<numUvSets; ++i)
|
||||||
|
geometry->setTexCoordArray(i, mBufferCache.getUVBuffer(numVerts));
|
||||||
|
|
||||||
|
if (useCompositeMap)
|
||||||
|
{
|
||||||
|
osg::ref_ptr<CompositeMap> compositeMap = new CompositeMap;
|
||||||
|
compositeMap->mTexture = createCompositeMapRTT();
|
||||||
|
|
||||||
|
createCompositeMapGeometry(chunkSize, chunkCenter, osg::Vec4f(0,0,1,1), *compositeMap);
|
||||||
|
|
||||||
|
mCompositeMapRenderer->addCompositeMap(compositeMap.get(), false);
|
||||||
|
|
||||||
|
transform->getOrCreateUserDataContainer()->setUserData(compositeMap);
|
||||||
|
|
||||||
|
TextureLayer layer;
|
||||||
|
layer.mDiffuseMap = compositeMap->mTexture;
|
||||||
|
layer.mParallax = false;
|
||||||
|
layer.mSpecular = false;
|
||||||
|
geometry->setPasses(::Terrain::createPasses(mSceneManager->getForceShaders() || !mSceneManager->getClampLighting(), mSceneManager->getForcePerPixelLighting(),
|
||||||
|
mSceneManager->getClampLighting(), &mSceneManager->getShaderManager(), std::vector<TextureLayer>(1, layer), std::vector<osg::ref_ptr<osg::Texture2D> >(), 1.f, 1.f));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
geometry->setPasses(createPasses(chunkSize, chunkCenter, false));
|
||||||
|
}
|
||||||
|
|
||||||
|
transform->addChild(geometry);
|
||||||
|
|
||||||
|
if (!mCullingActive)
|
||||||
|
{
|
||||||
|
transform->setCullingActive(false);
|
||||||
|
geometry->setCullingActive(false);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
transform->getBound();
|
||||||
|
|
||||||
|
if (mSceneManager->getIncrementalCompileOperation())
|
||||||
|
{
|
||||||
|
mSceneManager->getIncrementalCompileOperation()->add(geometry);
|
||||||
|
}
|
||||||
|
return transform;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,61 @@
|
|||||||
|
#ifndef OPENMW_COMPONENTS_TERRAIN_CHUNKMANAGER_H
|
||||||
|
#define OPENMW_COMPONENTS_TERRAIN_CHUNKMANAGER_H
|
||||||
|
|
||||||
|
#include <components/resource/resourcemanager.hpp>
|
||||||
|
|
||||||
|
#include "buffercache.hpp"
|
||||||
|
|
||||||
|
namespace osg
|
||||||
|
{
|
||||||
|
class Group;
|
||||||
|
class Texture2D;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace Resource
|
||||||
|
{
|
||||||
|
class SceneManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace Terrain
|
||||||
|
{
|
||||||
|
|
||||||
|
class TextureManager;
|
||||||
|
class CompositeMapRenderer;
|
||||||
|
class Storage;
|
||||||
|
class CompositeMap;
|
||||||
|
|
||||||
|
/// @brief Handles loading and caching of terrain chunks
|
||||||
|
class ChunkManager : public Resource::ResourceManager
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
ChunkManager(Storage* storage, Resource::SceneManager* sceneMgr, TextureManager* textureManager, CompositeMapRenderer* renderer);
|
||||||
|
|
||||||
|
osg::ref_ptr<osg::Node> getChunk(float size, const osg::Vec2f& center, int lod, unsigned int lodFlags);
|
||||||
|
|
||||||
|
virtual void reportStats(unsigned int frameNumber, osg::Stats* stats) const;
|
||||||
|
|
||||||
|
void setCullingActive(bool active);
|
||||||
|
|
||||||
|
private:
|
||||||
|
osg::ref_ptr<osg::Node> createChunk(float size, const osg::Vec2f& center, int lod, unsigned int lodFlags);
|
||||||
|
|
||||||
|
osg::ref_ptr<osg::Texture2D> createCompositeMapRTT();
|
||||||
|
|
||||||
|
void createCompositeMapGeometry(float chunkSize, const osg::Vec2f& chunkCenter, const osg::Vec4f& texCoords, CompositeMap& map);
|
||||||
|
|
||||||
|
std::vector<osg::ref_ptr<osg::StateSet> > createPasses(float chunkSize, const osg::Vec2f& chunkCenter, bool forCompositeMap);
|
||||||
|
|
||||||
|
Terrain::Storage* mStorage;
|
||||||
|
Resource::SceneManager* mSceneManager;
|
||||||
|
TextureManager* mTextureManager;
|
||||||
|
CompositeMapRenderer* mCompositeMapRenderer;
|
||||||
|
BufferCache mBufferCache;
|
||||||
|
|
||||||
|
unsigned int mCompositeMapSize;
|
||||||
|
|
||||||
|
bool mCullingActive;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
@ -0,0 +1,170 @@
|
|||||||
|
#include "compositemaprenderer.hpp"
|
||||||
|
|
||||||
|
#include <OpenThreads/ScopedLock>
|
||||||
|
|
||||||
|
#include <osg/FrameBufferObject>
|
||||||
|
#include <osg/Texture2D>
|
||||||
|
#include <osg/RenderInfo>
|
||||||
|
|
||||||
|
namespace Terrain
|
||||||
|
{
|
||||||
|
|
||||||
|
CompositeMapRenderer::CompositeMapRenderer()
|
||||||
|
: mTimeAvailable(0.0005)
|
||||||
|
{
|
||||||
|
setSupportsDisplayList(false);
|
||||||
|
setCullingActive(false);
|
||||||
|
|
||||||
|
mFBO = new osg::FrameBufferObject;
|
||||||
|
|
||||||
|
getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CompositeMapRenderer::drawImplementation(osg::RenderInfo &renderInfo) const
|
||||||
|
{
|
||||||
|
mCompiled.clear();
|
||||||
|
|
||||||
|
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(mMutex);
|
||||||
|
|
||||||
|
if (mImmediateCompileSet.empty() && mCompileSet.empty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
while (!mImmediateCompileSet.empty())
|
||||||
|
{
|
||||||
|
CompositeMap* node = *mImmediateCompileSet.begin();
|
||||||
|
mCompiled.insert(node);
|
||||||
|
|
||||||
|
compile(*node, renderInfo, NULL);
|
||||||
|
|
||||||
|
mImmediateCompileSet.erase(mImmediateCompileSet.begin());
|
||||||
|
}
|
||||||
|
|
||||||
|
double timeLeft = mTimeAvailable;
|
||||||
|
|
||||||
|
while (!mCompileSet.empty() && timeLeft > 0)
|
||||||
|
{
|
||||||
|
CompositeMap* node = *mCompileSet.begin();
|
||||||
|
|
||||||
|
compile(*node, renderInfo, &timeLeft);
|
||||||
|
|
||||||
|
if (node->mCompiled >= node->mDrawables.size())
|
||||||
|
{
|
||||||
|
mCompiled.insert(node);
|
||||||
|
mCompileSet.erase(mCompileSet.begin());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CompositeMapRenderer::compile(CompositeMap &compositeMap, osg::RenderInfo &renderInfo, double* timeLeft) const
|
||||||
|
{
|
||||||
|
// if there are no more external references we can assume the texture is no longer required
|
||||||
|
if (compositeMap.mTexture->referenceCount() <= 1)
|
||||||
|
{
|
||||||
|
compositeMap.mCompiled = compositeMap.mDrawables.size();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
osg::Timer timer;
|
||||||
|
osg::State& state = *renderInfo.getState();
|
||||||
|
osg::GLExtensions* ext = state.get<osg::GLExtensions>();
|
||||||
|
|
||||||
|
if (!mFBO)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!ext->isFrameBufferObjectSupported)
|
||||||
|
return;
|
||||||
|
|
||||||
|
osg::FrameBufferAttachment attach (compositeMap.mTexture);
|
||||||
|
mFBO->setAttachment(osg::Camera::COLOR_BUFFER, attach);
|
||||||
|
mFBO->apply(state, osg::FrameBufferObject::DRAW_FRAMEBUFFER);
|
||||||
|
|
||||||
|
GLenum status = ext->glCheckFramebufferStatus(GL_FRAMEBUFFER_EXT);
|
||||||
|
|
||||||
|
if (status != GL_FRAMEBUFFER_COMPLETE_EXT)
|
||||||
|
{
|
||||||
|
GLuint fboId = state.getGraphicsContext() ? state.getGraphicsContext()->getDefaultFboId() : 0;
|
||||||
|
ext->glBindFramebuffer(GL_FRAMEBUFFER_EXT, fboId);
|
||||||
|
OSG_ALWAYS << "Error attaching FBO" << std::endl;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (unsigned int i=compositeMap.mCompiled; i<compositeMap.mDrawables.size(); ++i)
|
||||||
|
{
|
||||||
|
osg::Drawable* drw = compositeMap.mDrawables[i];
|
||||||
|
osg::StateSet* stateset = drw->getStateSet();
|
||||||
|
|
||||||
|
if (stateset)
|
||||||
|
renderInfo.getState()->pushStateSet(stateset);
|
||||||
|
|
||||||
|
renderInfo.getState()->apply();
|
||||||
|
|
||||||
|
glViewport(0,0,compositeMap.mTexture->getTextureWidth(), compositeMap.mTexture->getTextureHeight());
|
||||||
|
drw->drawImplementation(renderInfo);
|
||||||
|
|
||||||
|
if (stateset)
|
||||||
|
renderInfo.getState()->popStateSet();
|
||||||
|
|
||||||
|
++compositeMap.mCompiled;
|
||||||
|
|
||||||
|
if (timeLeft)
|
||||||
|
{
|
||||||
|
*timeLeft -= timer.time_s();
|
||||||
|
timer.setStartTick();
|
||||||
|
|
||||||
|
if (*timeLeft <= 0)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
state.haveAppliedAttribute(osg::StateAttribute::VIEWPORT);
|
||||||
|
|
||||||
|
GLuint fboId = state.getGraphicsContext() ? state.getGraphicsContext()->getDefaultFboId() : 0;
|
||||||
|
ext->glBindFramebuffer(GL_FRAMEBUFFER_EXT, fboId);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CompositeMapRenderer::setTimeAvailableForCompile(double time)
|
||||||
|
{
|
||||||
|
mTimeAvailable = time;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CompositeMapRenderer::addCompositeMap(CompositeMap* compositeMap, bool immediate)
|
||||||
|
{
|
||||||
|
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(mMutex);
|
||||||
|
if (immediate)
|
||||||
|
mImmediateCompileSet.insert(compositeMap);
|
||||||
|
else
|
||||||
|
mCompileSet.insert(compositeMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CompositeMapRenderer::setImmediate(CompositeMap* compositeMap)
|
||||||
|
{
|
||||||
|
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(mMutex);
|
||||||
|
CompileSet::iterator found = mCompileSet.find(compositeMap);
|
||||||
|
if (found == mCompileSet.end())
|
||||||
|
return;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mImmediateCompileSet.insert(compositeMap);
|
||||||
|
mCompileSet.erase(found);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned int CompositeMapRenderer::getCompileSetSize() const
|
||||||
|
{
|
||||||
|
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(mMutex);
|
||||||
|
return mCompileSet.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
CompositeMap::CompositeMap()
|
||||||
|
: mCompiled(0)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
CompositeMap::~CompositeMap()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,70 @@
|
|||||||
|
#ifndef OPENMW_COMPONENTS_TERRAIN_COMPOSITEMAPRENDERER_H
|
||||||
|
#define OPENMW_COMPONENTS_TERRAIN_COMPOSITEMAPRENDERER_H
|
||||||
|
|
||||||
|
#include <osg/Drawable>
|
||||||
|
|
||||||
|
#include <OpenThreads/Mutex>
|
||||||
|
|
||||||
|
#include <set>
|
||||||
|
|
||||||
|
namespace osg
|
||||||
|
{
|
||||||
|
class FrameBufferObject;
|
||||||
|
class RenderInfo;
|
||||||
|
class Texture2D;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace Terrain
|
||||||
|
{
|
||||||
|
|
||||||
|
class CompositeMap : public osg::Referenced
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
CompositeMap();
|
||||||
|
~CompositeMap();
|
||||||
|
std::vector<osg::ref_ptr<osg::Drawable> > mDrawables;
|
||||||
|
osg::ref_ptr<osg::Texture2D> mTexture;
|
||||||
|
unsigned int mCompiled;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The CompositeMapRenderer is responsible for updating composite map textures in a blocking or non-blocking way.
|
||||||
|
*/
|
||||||
|
class CompositeMapRenderer : public osg::Drawable
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
CompositeMapRenderer();
|
||||||
|
|
||||||
|
virtual void drawImplementation(osg::RenderInfo& renderInfo) const;
|
||||||
|
|
||||||
|
void compile(CompositeMap& compositeMap, osg::RenderInfo& renderInfo, double* timeLeft) const;
|
||||||
|
|
||||||
|
/// Set the available time in seconds for compiling (non-immediate) composite maps each frame
|
||||||
|
void setTimeAvailableForCompile(double time);
|
||||||
|
|
||||||
|
/// Add a composite map to be rendered
|
||||||
|
void addCompositeMap(CompositeMap* map, bool immediate=false);
|
||||||
|
|
||||||
|
/// Mark this composite map to be required for the current frame
|
||||||
|
void setImmediate(CompositeMap* map);
|
||||||
|
|
||||||
|
unsigned int getCompileSetSize() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
double mTimeAvailable;
|
||||||
|
|
||||||
|
typedef std::set<osg::ref_ptr<CompositeMap> > CompileSet;
|
||||||
|
|
||||||
|
mutable CompileSet mCompileSet;
|
||||||
|
mutable CompileSet mImmediateCompileSet;
|
||||||
|
|
||||||
|
mutable CompileSet mCompiled;
|
||||||
|
|
||||||
|
mutable OpenThreads::Mutex mMutex;
|
||||||
|
|
||||||
|
osg::ref_ptr<osg::FrameBufferObject> mFBO;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
@ -0,0 +1,169 @@
|
|||||||
|
#include "quadtreenode.hpp"
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
|
||||||
|
#include <osgUtil/CullVisitor>
|
||||||
|
|
||||||
|
#include "defs.hpp"
|
||||||
|
#include "viewdata.hpp"
|
||||||
|
|
||||||
|
namespace Terrain
|
||||||
|
{
|
||||||
|
|
||||||
|
ChildDirection reflect(ChildDirection dir, Direction dir2)
|
||||||
|
{
|
||||||
|
assert(dir != Root);
|
||||||
|
const int lookupTable[4][4] =
|
||||||
|
{
|
||||||
|
// NW NE SW SE
|
||||||
|
{ SW, SE, NW, NE }, // N
|
||||||
|
{ NE, NW, SE, SW }, // E
|
||||||
|
{ SW, SE, NW, NE }, // S
|
||||||
|
{ NE, NW, SE, SW } // W
|
||||||
|
};
|
||||||
|
return (ChildDirection)lookupTable[dir2][dir];
|
||||||
|
}
|
||||||
|
|
||||||
|
bool adjacent(ChildDirection dir, Direction dir2)
|
||||||
|
{
|
||||||
|
assert(dir != Root);
|
||||||
|
const bool lookupTable[4][4] =
|
||||||
|
{
|
||||||
|
// NW NE SW SE
|
||||||
|
{ true, true, false, false }, // N
|
||||||
|
{ false, true, false, true }, // E
|
||||||
|
{ false, false, true, true }, // S
|
||||||
|
{ true, false, true, false } // W
|
||||||
|
};
|
||||||
|
return lookupTable[dir2][dir];
|
||||||
|
}
|
||||||
|
|
||||||
|
QuadTreeNode* searchNeighbour (QuadTreeNode* currentNode, Direction dir)
|
||||||
|
{
|
||||||
|
if (currentNode->getDirection() == Root)
|
||||||
|
return NULL; // Arrived at root node, the root node does not have neighbours
|
||||||
|
|
||||||
|
QuadTreeNode* nextNode;
|
||||||
|
if (adjacent(currentNode->getDirection(), dir))
|
||||||
|
nextNode = searchNeighbour(currentNode->getParent(), dir);
|
||||||
|
else
|
||||||
|
nextNode = currentNode->getParent();
|
||||||
|
|
||||||
|
if (nextNode && nextNode->getNumChildren())
|
||||||
|
return nextNode->getChild(reflect(currentNode->getDirection(), dir));
|
||||||
|
else
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
QuadTreeNode::QuadTreeNode(QuadTreeNode* parent, ChildDirection direction, float size, const osg::Vec2f& center)
|
||||||
|
: mParent(parent)
|
||||||
|
, mDirection(direction)
|
||||||
|
, mValidBounds(false)
|
||||||
|
, mSize(size)
|
||||||
|
, mCenter(center)
|
||||||
|
{
|
||||||
|
for (unsigned int i=0; i<4; ++i)
|
||||||
|
mNeighbours[i] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
QuadTreeNode::~QuadTreeNode()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
QuadTreeNode* QuadTreeNode::getParent()
|
||||||
|
{
|
||||||
|
return mParent;
|
||||||
|
}
|
||||||
|
|
||||||
|
QuadTreeNode *QuadTreeNode::getChild(unsigned int i)
|
||||||
|
{
|
||||||
|
return static_cast<QuadTreeNode*>(Group::getChild(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
QuadTreeNode *QuadTreeNode::getNeighbour(Direction dir)
|
||||||
|
{
|
||||||
|
return mNeighbours[dir];
|
||||||
|
}
|
||||||
|
|
||||||
|
void QuadTreeNode::initNeighbours()
|
||||||
|
{
|
||||||
|
for (int i=0; i<4; ++i)
|
||||||
|
mNeighbours[i] = searchNeighbour(this, (Direction)i);
|
||||||
|
|
||||||
|
for (unsigned int i=0; i<getNumChildren(); ++i)
|
||||||
|
getChild(i)->initNeighbours();
|
||||||
|
}
|
||||||
|
|
||||||
|
void QuadTreeNode::traverse(osg::NodeVisitor &nv)
|
||||||
|
{
|
||||||
|
if (!hasValidBounds())
|
||||||
|
return;
|
||||||
|
|
||||||
|
if ((mLodCallback && mLodCallback->isSufficientDetail(this, nv.getEyePoint())) || !getNumChildren())
|
||||||
|
getView(nv)->add(this, true);
|
||||||
|
else
|
||||||
|
osg::Group::traverse(nv);
|
||||||
|
}
|
||||||
|
|
||||||
|
void QuadTreeNode::setLodCallback(LodCallback *lodCallback)
|
||||||
|
{
|
||||||
|
mLodCallback = lodCallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
LodCallback *QuadTreeNode::getLodCallback()
|
||||||
|
{
|
||||||
|
return mLodCallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
void QuadTreeNode::setViewDataMap(ViewDataMap *map)
|
||||||
|
{
|
||||||
|
mViewDataMap = map;
|
||||||
|
}
|
||||||
|
|
||||||
|
ViewDataMap *QuadTreeNode::getViewDataMap()
|
||||||
|
{
|
||||||
|
return mViewDataMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
ViewData* QuadTreeNode::getView(osg::NodeVisitor &nv)
|
||||||
|
{
|
||||||
|
if (nv.getVisitorType() == osg::NodeVisitor::CULL_VISITOR)
|
||||||
|
{
|
||||||
|
osgUtil::CullVisitor* cv = static_cast<osgUtil::CullVisitor*>(&nv);
|
||||||
|
return mViewDataMap->getViewData(cv->getCurrentCamera(), true);
|
||||||
|
}
|
||||||
|
else // INTERSECTION_VISITOR
|
||||||
|
{
|
||||||
|
return mViewDataMap->getViewData(&nv, (nv.referenceCount() > 0)); // if no referenceCount, the visitor was allocated on the stack
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void QuadTreeNode::setBoundingBox(const osg::BoundingBox &boundingBox)
|
||||||
|
{
|
||||||
|
mBoundingBox = boundingBox;
|
||||||
|
mValidBounds = boundingBox.valid();
|
||||||
|
dirtyBound();
|
||||||
|
getBound();
|
||||||
|
}
|
||||||
|
|
||||||
|
const osg::BoundingBox &QuadTreeNode::getBoundingBox() const
|
||||||
|
{
|
||||||
|
return mBoundingBox;
|
||||||
|
}
|
||||||
|
|
||||||
|
osg::BoundingSphere QuadTreeNode::computeBound() const
|
||||||
|
{
|
||||||
|
return osg::BoundingSphere(mBoundingBox);
|
||||||
|
}
|
||||||
|
|
||||||
|
float QuadTreeNode::getSize() const
|
||||||
|
{
|
||||||
|
return mSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
const osg::Vec2f &QuadTreeNode::getCenter() const
|
||||||
|
{
|
||||||
|
return mCenter;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,98 @@
|
|||||||
|
#ifndef OPENMW_COMPONENTS_TERRAIN_QUADTREENODE_H
|
||||||
|
#define OPENMW_COMPONENTS_TERRAIN_QUADTREENODE_H
|
||||||
|
|
||||||
|
#include <osg/Group>
|
||||||
|
|
||||||
|
#include "defs.hpp"
|
||||||
|
|
||||||
|
namespace Terrain
|
||||||
|
{
|
||||||
|
|
||||||
|
enum ChildDirection
|
||||||
|
{
|
||||||
|
NW = 0,
|
||||||
|
NE = 1,
|
||||||
|
SW = 2,
|
||||||
|
SE = 3,
|
||||||
|
Root
|
||||||
|
};
|
||||||
|
|
||||||
|
class QuadTreeNode;
|
||||||
|
class LodCallback : public osg::Referenced
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
virtual ~LodCallback() {}
|
||||||
|
|
||||||
|
virtual bool isSufficientDetail(QuadTreeNode *node, const osg::Vec3f& eyePoint) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
class ViewDataMap;
|
||||||
|
class ViewData;
|
||||||
|
|
||||||
|
class QuadTreeNode : public osg::Group
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
QuadTreeNode(QuadTreeNode* parent, ChildDirection dir, float size, const osg::Vec2f& center);
|
||||||
|
virtual ~QuadTreeNode();
|
||||||
|
|
||||||
|
QuadTreeNode* getParent();
|
||||||
|
|
||||||
|
QuadTreeNode* getChild(unsigned int i);
|
||||||
|
using osg::Group::getNumChildren;
|
||||||
|
|
||||||
|
/// Returns our direction relative to the parent node, or Root if we are the root node.
|
||||||
|
ChildDirection getDirection() { return mDirection; }
|
||||||
|
|
||||||
|
/// Get neighbour node in this direction
|
||||||
|
QuadTreeNode* getNeighbour (Direction dir);
|
||||||
|
|
||||||
|
/// Initialize neighbours - do this after the quadtree is built
|
||||||
|
void initNeighbours();
|
||||||
|
|
||||||
|
void setBoundingBox(const osg::BoundingBox& boundingBox);
|
||||||
|
const osg::BoundingBox& getBoundingBox() const;
|
||||||
|
bool hasValidBounds() const { return mValidBounds; }
|
||||||
|
|
||||||
|
virtual osg::BoundingSphere computeBound() const;
|
||||||
|
|
||||||
|
/// size in cell coordinates
|
||||||
|
float getSize() const;
|
||||||
|
|
||||||
|
/// center in cell coordinates
|
||||||
|
const osg::Vec2f& getCenter() const;
|
||||||
|
|
||||||
|
virtual void traverse(osg::NodeVisitor& nv);
|
||||||
|
|
||||||
|
/// Set the Lod callback to use for determining when to stop traversing further down the quad tree.
|
||||||
|
void setLodCallback(LodCallback* lodCallback);
|
||||||
|
|
||||||
|
LodCallback* getLodCallback();
|
||||||
|
|
||||||
|
/// Set the view data map that the finally used nodes for a given camera/intersection are pushed onto.
|
||||||
|
void setViewDataMap(ViewDataMap* map);
|
||||||
|
|
||||||
|
ViewDataMap* getViewDataMap();
|
||||||
|
|
||||||
|
/// Create or retrieve a view for the given traversal.
|
||||||
|
ViewData* getView(osg::NodeVisitor& nv);
|
||||||
|
|
||||||
|
private:
|
||||||
|
QuadTreeNode* mParent;
|
||||||
|
|
||||||
|
QuadTreeNode* mNeighbours[4];
|
||||||
|
|
||||||
|
ChildDirection mDirection;
|
||||||
|
|
||||||
|
osg::BoundingBox mBoundingBox;
|
||||||
|
bool mValidBounds;
|
||||||
|
float mSize;
|
||||||
|
osg::Vec2f mCenter;
|
||||||
|
|
||||||
|
osg::ref_ptr<LodCallback> mLodCallback;
|
||||||
|
|
||||||
|
ViewDataMap* mViewDataMap;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
@ -0,0 +1,460 @@
|
|||||||
|
#include "quadtreeworld.hpp"
|
||||||
|
|
||||||
|
#include <osgUtil/CullVisitor>
|
||||||
|
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
#include "quadtreenode.hpp"
|
||||||
|
#include "storage.hpp"
|
||||||
|
#include "viewdata.hpp"
|
||||||
|
#include "chunkmanager.hpp"
|
||||||
|
#include "compositemaprenderer.hpp"
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
|
||||||
|
bool isPowerOfTwo(int x)
|
||||||
|
{
|
||||||
|
return ( (x > 0) && ((x & (x - 1)) == 0) );
|
||||||
|
}
|
||||||
|
|
||||||
|
int nextPowerOfTwo (int v)
|
||||||
|
{
|
||||||
|
if (isPowerOfTwo(v)) return v;
|
||||||
|
int depth=0;
|
||||||
|
while(v)
|
||||||
|
{
|
||||||
|
v >>= 1;
|
||||||
|
depth++;
|
||||||
|
}
|
||||||
|
return 1 << depth;
|
||||||
|
}
|
||||||
|
|
||||||
|
int Log2( unsigned int n )
|
||||||
|
{
|
||||||
|
int targetlevel = 0;
|
||||||
|
while (n >>= 1) ++targetlevel;
|
||||||
|
return targetlevel;
|
||||||
|
}
|
||||||
|
|
||||||
|
float distance(const osg::BoundingBox& box, const osg::Vec3f& v)
|
||||||
|
{
|
||||||
|
if (box.contains(v))
|
||||||
|
return 0;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
osg::Vec3f maxDist(0,0,0);
|
||||||
|
|
||||||
|
if (v.x() < box.xMin())
|
||||||
|
maxDist.x() = box.xMin() - v.x();
|
||||||
|
else if (v.x() > box.xMax())
|
||||||
|
maxDist.x() = v.x() - box.xMax();
|
||||||
|
|
||||||
|
if (v.y() < box.yMin())
|
||||||
|
maxDist.y() = box.yMin() - v.y();
|
||||||
|
else if (v.y() > box.yMax())
|
||||||
|
maxDist.y() = v.y() - box.yMax();
|
||||||
|
|
||||||
|
if (v.z() < box.zMin())
|
||||||
|
maxDist.z() = box.zMin() - v.z();
|
||||||
|
else if (v.z() > box.zMax())
|
||||||
|
maxDist.z() = v.z() - box.zMax();
|
||||||
|
|
||||||
|
return maxDist.length();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace Terrain
|
||||||
|
{
|
||||||
|
|
||||||
|
class DefaultLodCallback : public LodCallback
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
DefaultLodCallback(float minSize)
|
||||||
|
: mMinSize(minSize)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual bool isSufficientDetail(QuadTreeNode* node, const osg::Vec3f& eyePoint)
|
||||||
|
{
|
||||||
|
float dist = distance(node->getBoundingBox(), eyePoint);
|
||||||
|
int nativeLodLevel = Log2(static_cast<unsigned int>(node->getSize()/mMinSize));
|
||||||
|
int lodLevel = Log2(static_cast<unsigned int>(dist/(8192*mMinSize)));
|
||||||
|
|
||||||
|
return nativeLodLevel <= lodLevel;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
float mMinSize;
|
||||||
|
};
|
||||||
|
|
||||||
|
class RootNode : public QuadTreeNode
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
RootNode(float size, const osg::Vec2f& center)
|
||||||
|
: QuadTreeNode(NULL, Root, size, center)
|
||||||
|
, mWorld(NULL)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void setWorld(QuadTreeWorld* world)
|
||||||
|
{
|
||||||
|
mWorld = world;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void accept(osg::NodeVisitor &nv)
|
||||||
|
{
|
||||||
|
if (!nv.validNodeMask(*this))
|
||||||
|
return;
|
||||||
|
nv.pushOntoNodePath(this);
|
||||||
|
mWorld->accept(nv);
|
||||||
|
nv.popFromNodePath();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
QuadTreeWorld* mWorld;
|
||||||
|
};
|
||||||
|
|
||||||
|
class QuadTreeBuilder
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
QuadTreeBuilder(Terrain::Storage* storage, ViewDataMap* viewDataMap, float minSize)
|
||||||
|
: mStorage(storage)
|
||||||
|
, mMinX(0.f), mMaxX(0.f), mMinY(0.f), mMaxY(0.f)
|
||||||
|
, mMinSize(minSize)
|
||||||
|
, mViewDataMap(viewDataMap)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void build()
|
||||||
|
{
|
||||||
|
mStorage->getBounds(mMinX, mMaxX, mMinY, mMaxY);
|
||||||
|
|
||||||
|
int origSizeX = static_cast<int>(mMaxX - mMinX);
|
||||||
|
int origSizeY = static_cast<int>(mMaxY - mMinY);
|
||||||
|
|
||||||
|
// Dividing a quad tree only works well for powers of two, so round up to the nearest one
|
||||||
|
int size = nextPowerOfTwo(std::max(origSizeX, origSizeY));
|
||||||
|
|
||||||
|
float centerX = (mMinX+mMaxX)/2.f + (size-origSizeX)/2.f;
|
||||||
|
float centerY = (mMinY+mMaxY)/2.f + (size-origSizeY)/2.f;
|
||||||
|
|
||||||
|
mRootNode = new RootNode(size, osg::Vec2f(centerX, centerY));
|
||||||
|
mRootNode->setViewDataMap(mViewDataMap);
|
||||||
|
mRootNode->setLodCallback(new DefaultLodCallback(mMinSize));
|
||||||
|
addChildren(mRootNode);
|
||||||
|
|
||||||
|
mRootNode->initNeighbours();
|
||||||
|
}
|
||||||
|
|
||||||
|
void addChildren(QuadTreeNode* parent)
|
||||||
|
{
|
||||||
|
float halfSize = parent->getSize()/2.f;
|
||||||
|
osg::BoundingBox boundingBox;
|
||||||
|
for (unsigned int i=0; i<4; ++i)
|
||||||
|
{
|
||||||
|
QuadTreeNode* child = addChild(parent, static_cast<ChildDirection>(i), halfSize);
|
||||||
|
if (child)
|
||||||
|
boundingBox.expandBy(child->getBoundingBox());
|
||||||
|
}
|
||||||
|
|
||||||
|
parent->setBoundingBox(boundingBox);
|
||||||
|
}
|
||||||
|
|
||||||
|
QuadTreeNode* addChild(QuadTreeNode* parent, ChildDirection direction, float size)
|
||||||
|
{
|
||||||
|
osg::Vec2f center;
|
||||||
|
switch (direction)
|
||||||
|
{
|
||||||
|
case SW:
|
||||||
|
center = parent->getCenter() + osg::Vec2f(-size/2.f,-size/2.f);
|
||||||
|
break;
|
||||||
|
case SE:
|
||||||
|
center = parent->getCenter() + osg::Vec2f(size/2.f, -size/2.f);
|
||||||
|
break;
|
||||||
|
case NW:
|
||||||
|
center = parent->getCenter() + osg::Vec2f(-size/2.f, size/2.f);
|
||||||
|
break;
|
||||||
|
case NE:
|
||||||
|
center = parent->getCenter() + osg::Vec2f(size/2.f, size/2.f);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
osg::ref_ptr<QuadTreeNode> node = new QuadTreeNode(parent, direction, size, center);
|
||||||
|
node->setLodCallback(parent->getLodCallback());
|
||||||
|
node->setViewDataMap(mViewDataMap);
|
||||||
|
parent->addChild(node);
|
||||||
|
|
||||||
|
if (center.x() - size > mMaxX
|
||||||
|
|| center.x() + size < mMinX
|
||||||
|
|| center.y() - size > mMaxY
|
||||||
|
|| center.y() + size < mMinY )
|
||||||
|
// Out of bounds of the actual terrain - this will happen because
|
||||||
|
// we rounded the size up to the next power of two
|
||||||
|
{
|
||||||
|
// Still create and return an empty node so as to not break the assumption that each QuadTreeNode has either 4 or 0 children.
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node->getSize() <= mMinSize)
|
||||||
|
{
|
||||||
|
// We arrived at a leaf
|
||||||
|
float minZ,maxZ;
|
||||||
|
if (mStorage->getMinMaxHeights(size, center, minZ, maxZ))
|
||||||
|
{
|
||||||
|
float cellWorldSize = mStorage->getCellWorldSize();
|
||||||
|
osg::BoundingBox boundingBox(osg::Vec3f((center.x()-size)*cellWorldSize, (center.y()-size)*cellWorldSize, minZ),
|
||||||
|
osg::Vec3f((center.x()+size)*cellWorldSize, (center.y()+size)*cellWorldSize, maxZ));
|
||||||
|
node->setBoundingBox(boundingBox);
|
||||||
|
}
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
addChildren(node);
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
osg::ref_ptr<RootNode> getRootNode()
|
||||||
|
{
|
||||||
|
return mRootNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
Terrain::Storage* mStorage;
|
||||||
|
|
||||||
|
float mMinX, mMaxX, mMinY, mMaxY;
|
||||||
|
float mMinSize;
|
||||||
|
ViewDataMap* mViewDataMap;
|
||||||
|
|
||||||
|
osg::ref_ptr<RootNode> mRootNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
QuadTreeWorld::QuadTreeWorld(osg::Group *parent, osg::Group *compileRoot, Resource::ResourceSystem *resourceSystem, Storage *storage, int nodeMask, int preCompileMask)
|
||||||
|
: World(parent, compileRoot, resourceSystem, storage, nodeMask, preCompileMask)
|
||||||
|
, mViewDataMap(new ViewDataMap)
|
||||||
|
, mQuadTreeBuilt(false)
|
||||||
|
{
|
||||||
|
// No need for culling on the Drawable / Transform level as the quad tree performs the culling already.
|
||||||
|
mChunkManager->setCullingActive(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
QuadTreeWorld::~QuadTreeWorld()
|
||||||
|
{
|
||||||
|
ensureQuadTreeBuilt();
|
||||||
|
mViewDataMap->clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void traverse(QuadTreeNode* node, ViewData* vd, osg::NodeVisitor* nv, LodCallback* lodCallback, const osg::Vec3f& eyePoint, bool visible)
|
||||||
|
{
|
||||||
|
if (!node->hasValidBounds())
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (nv && nv->getVisitorType() == osg::NodeVisitor::CULL_VISITOR)
|
||||||
|
visible = visible && !static_cast<osgUtil::CullVisitor*>(nv)->isCulled(node->getBoundingBox());
|
||||||
|
|
||||||
|
bool stopTraversal = (lodCallback && lodCallback->isSufficientDetail(node, eyePoint)) || !node->getNumChildren();
|
||||||
|
|
||||||
|
if (stopTraversal)
|
||||||
|
vd->add(node, visible);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (unsigned int i=0; i<node->getNumChildren(); ++i)
|
||||||
|
traverse(node->getChild(i), vd, nv, lodCallback, eyePoint, visible);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void traverseToCell(QuadTreeNode* node, ViewData* vd, int cellX, int cellY)
|
||||||
|
{
|
||||||
|
if (!node->hasValidBounds())
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (node->getCenter().x() + node->getSize()/2.f <= cellX
|
||||||
|
|| node->getCenter().x() - node->getSize()/2.f >= cellX+1
|
||||||
|
|| node->getCenter().y() + node->getSize()/2.f <= cellY
|
||||||
|
|| node->getCenter().y() - node->getSize()/2.f >= cellY+1)
|
||||||
|
return;
|
||||||
|
|
||||||
|
bool stopTraversal = !node->getNumChildren();
|
||||||
|
|
||||||
|
if (stopTraversal)
|
||||||
|
vd->add(node, true);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (unsigned int i=0; i<node->getNumChildren(); ++i)
|
||||||
|
traverseToCell(node->getChild(i), vd, cellX, cellY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned int getLodFlags(QuadTreeNode* node, int ourLod, ViewData* vd)
|
||||||
|
{
|
||||||
|
unsigned int lodFlags = 0;
|
||||||
|
for (unsigned int i=0; i<4; ++i)
|
||||||
|
{
|
||||||
|
QuadTreeNode* neighbour = node->getNeighbour(static_cast<Direction>(i));
|
||||||
|
|
||||||
|
// If the neighbour isn't currently rendering itself,
|
||||||
|
// go up until we find one. NOTE: We don't need to go down,
|
||||||
|
// because in that case neighbour's detail would be higher than
|
||||||
|
// our detail and the neighbour would handle stitching by itself.
|
||||||
|
while (neighbour && !vd->contains(neighbour))
|
||||||
|
neighbour = neighbour->getParent();
|
||||||
|
int lod = 0;
|
||||||
|
if (neighbour)
|
||||||
|
lod = Log2(int(neighbour->getSize()));
|
||||||
|
|
||||||
|
if (lod <= ourLod) // We only need to worry about neighbours less detailed than we are -
|
||||||
|
lod = 0; // neighbours with more detail will do the stitching themselves
|
||||||
|
// Use 4 bits for each LOD delta
|
||||||
|
if (lod > 0)
|
||||||
|
{
|
||||||
|
lodFlags |= static_cast<unsigned int>(lod - ourLod) << (4*i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return lodFlags;
|
||||||
|
}
|
||||||
|
|
||||||
|
void loadRenderingNode(ViewData::Entry& entry, ViewData* vd, ChunkManager* chunkManager)
|
||||||
|
{
|
||||||
|
if (vd->hasChanged())
|
||||||
|
{
|
||||||
|
// have to recompute the lodFlags in case a neighbour has changed LOD.
|
||||||
|
int ourLod = Log2(int(entry.mNode->getSize()));
|
||||||
|
unsigned int lodFlags = getLodFlags(entry.mNode, ourLod, vd);
|
||||||
|
if (lodFlags != entry.mLodFlags)
|
||||||
|
{
|
||||||
|
entry.mRenderingNode = NULL;
|
||||||
|
entry.mLodFlags = lodFlags;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!entry.mRenderingNode)
|
||||||
|
{
|
||||||
|
int ourLod = Log2(int(entry.mNode->getSize()));
|
||||||
|
entry.mRenderingNode = chunkManager->getChunk(entry.mNode->getSize(), entry.mNode->getCenter(), ourLod, entry.mLodFlags);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void QuadTreeWorld::accept(osg::NodeVisitor &nv)
|
||||||
|
{
|
||||||
|
if (nv.getVisitorType() != osg::NodeVisitor::CULL_VISITOR && nv.getVisitorType() != osg::NodeVisitor::INTERSECTION_VISITOR)
|
||||||
|
return;
|
||||||
|
|
||||||
|
ViewData* vd = mRootNode->getView(nv);
|
||||||
|
|
||||||
|
if (nv.getVisitorType() == osg::NodeVisitor::CULL_VISITOR)
|
||||||
|
{
|
||||||
|
osgUtil::CullVisitor* cv = static_cast<osgUtil::CullVisitor*>(&nv);
|
||||||
|
|
||||||
|
osg::UserDataContainer* udc = cv->getCurrentCamera()->getUserDataContainer();
|
||||||
|
if (udc && udc->getNumDescriptions() >= 2 && udc->getDescriptions()[0] == "NoTerrainLod")
|
||||||
|
{
|
||||||
|
std::istringstream stream(udc->getDescriptions()[1]);
|
||||||
|
int x,y;
|
||||||
|
stream >> x;
|
||||||
|
stream >> y;
|
||||||
|
traverseToCell(mRootNode.get(), vd, x,y);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
traverse(mRootNode.get(), vd, cv, mRootNode->getLodCallback(), cv->getEyePoint(), true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
mRootNode->traverse(nv);
|
||||||
|
|
||||||
|
for (unsigned int i=0; i<vd->getNumEntries(); ++i)
|
||||||
|
{
|
||||||
|
ViewData::Entry& entry = vd->getEntry(i);
|
||||||
|
|
||||||
|
loadRenderingNode(entry, vd, mChunkManager.get());
|
||||||
|
|
||||||
|
if (entry.mVisible)
|
||||||
|
{
|
||||||
|
osg::UserDataContainer* udc = entry.mRenderingNode->getUserDataContainer();
|
||||||
|
if (udc && udc->getUserData())
|
||||||
|
{
|
||||||
|
mCompositeMapRenderer->setImmediate(static_cast<CompositeMap*>(udc->getUserData()));
|
||||||
|
udc->setUserData(NULL);
|
||||||
|
}
|
||||||
|
entry.mRenderingNode->accept(nv);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
vd->reset(nv.getTraversalNumber());
|
||||||
|
|
||||||
|
mRootNode->getViewDataMap()->clearUnusedViews(nv.getTraversalNumber());
|
||||||
|
}
|
||||||
|
|
||||||
|
void QuadTreeWorld::ensureQuadTreeBuilt()
|
||||||
|
{
|
||||||
|
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(mQuadTreeMutex);
|
||||||
|
if (mQuadTreeBuilt)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const float minSize = 1/8.f;
|
||||||
|
QuadTreeBuilder builder(mStorage, mViewDataMap.get(), minSize);
|
||||||
|
builder.build();
|
||||||
|
|
||||||
|
mRootNode = builder.getRootNode();
|
||||||
|
mRootNode->setWorld(this);
|
||||||
|
mQuadTreeBuilt = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void QuadTreeWorld::enable(bool enabled)
|
||||||
|
{
|
||||||
|
if (enabled)
|
||||||
|
{
|
||||||
|
ensureQuadTreeBuilt();
|
||||||
|
|
||||||
|
if (!mRootNode->getNumParents())
|
||||||
|
mTerrainRoot->addChild(mRootNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mRootNode)
|
||||||
|
mRootNode->setNodeMask(enabled ? ~0 : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void QuadTreeWorld::cacheCell(View *view, int x, int y)
|
||||||
|
{
|
||||||
|
ensureQuadTreeBuilt();
|
||||||
|
ViewData* vd = static_cast<ViewData*>(view);
|
||||||
|
traverseToCell(mRootNode.get(), vd, x, y);
|
||||||
|
|
||||||
|
for (unsigned int i=0; i<vd->getNumEntries(); ++i)
|
||||||
|
{
|
||||||
|
ViewData::Entry& entry = vd->getEntry(i);
|
||||||
|
loadRenderingNode(entry, vd, mChunkManager.get());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
View* QuadTreeWorld::createView()
|
||||||
|
{
|
||||||
|
return new ViewData;
|
||||||
|
}
|
||||||
|
|
||||||
|
void QuadTreeWorld::preload(View *view, const osg::Vec3f &eyePoint)
|
||||||
|
{
|
||||||
|
ensureQuadTreeBuilt();
|
||||||
|
|
||||||
|
ViewData* vd = static_cast<ViewData*>(view);
|
||||||
|
traverse(mRootNode.get(), vd, NULL, mRootNode->getLodCallback(), eyePoint, false);
|
||||||
|
|
||||||
|
for (unsigned int i=0; i<vd->getNumEntries(); ++i)
|
||||||
|
{
|
||||||
|
ViewData::Entry& entry = vd->getEntry(i);
|
||||||
|
loadRenderingNode(entry, vd, mChunkManager.get());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void QuadTreeWorld::reportStats(unsigned int frameNumber, osg::Stats *stats)
|
||||||
|
{
|
||||||
|
stats->setAttribute(frameNumber, "Composite", mCompositeMapRenderer->getCompileSetSize());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,49 @@
|
|||||||
|
#ifndef COMPONENTS_TERRAIN_QUADTREEWORLD_H
|
||||||
|
#define COMPONENTS_TERRAIN_QUADTREEWORLD_H
|
||||||
|
|
||||||
|
#include "world.hpp"
|
||||||
|
|
||||||
|
#include <OpenThreads/Mutex>
|
||||||
|
|
||||||
|
namespace osg
|
||||||
|
{
|
||||||
|
class NodeVisitor;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace Terrain
|
||||||
|
{
|
||||||
|
class RootNode;
|
||||||
|
class ViewDataMap;
|
||||||
|
|
||||||
|
/// @brief Terrain implementation that loads cells into a Quad Tree, with geometry LOD and texture LOD. The entire world is displayed at all times.
|
||||||
|
class QuadTreeWorld : public Terrain::World
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
QuadTreeWorld(osg::Group* parent, osg::Group* compileRoot, Resource::ResourceSystem* resourceSystem, Storage* storage, int nodeMask, int preCompileMask=~0);
|
||||||
|
~QuadTreeWorld();
|
||||||
|
|
||||||
|
void accept(osg::NodeVisitor& nv);
|
||||||
|
|
||||||
|
virtual void enable(bool enabled);
|
||||||
|
|
||||||
|
void cacheCell(View *view, int x, int y);
|
||||||
|
|
||||||
|
View* createView();
|
||||||
|
void preload(View* view, const osg::Vec3f& eyePoint);
|
||||||
|
|
||||||
|
void reportStats(unsigned int frameNumber, osg::Stats* stats);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void ensureQuadTreeBuilt();
|
||||||
|
|
||||||
|
osg::ref_ptr<RootNode> mRootNode;
|
||||||
|
|
||||||
|
osg::ref_ptr<ViewDataMap> mViewDataMap;
|
||||||
|
|
||||||
|
OpenThreads::Mutex mQuadTreeMutex;
|
||||||
|
bool mQuadTreeBuilt;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
@ -0,0 +1,89 @@
|
|||||||
|
#include "terraindrawable.hpp"
|
||||||
|
|
||||||
|
#include <osgUtil/CullVisitor>
|
||||||
|
|
||||||
|
#include <components/sceneutil/lightmanager.hpp>
|
||||||
|
|
||||||
|
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<osgUtil::CullVisitor*>(&nv));
|
||||||
|
nv.popFromNodePath();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline float distance(const osg::Vec3& coord,const osg::Matrix& matrix)
|
||||||
|
{
|
||||||
|
return -((float)coord[0]*(float)matrix(0,2)+(float)coord[1]*(float)matrix(1,2)+(float)coord[2]*(float)matrix(2,2)+matrix(3,2));
|
||||||
|
}
|
||||||
|
|
||||||
|
void TerrainDrawable::cull(osgUtil::CullVisitor *cv)
|
||||||
|
{
|
||||||
|
const osg::BoundingBox& bb = getBoundingBox();
|
||||||
|
|
||||||
|
if (_cullingActive && cv->isCulled(getBoundingBox()))
|
||||||
|
return;
|
||||||
|
|
||||||
|
osg::RefMatrix& matrix = *cv->getModelViewMatrix();
|
||||||
|
|
||||||
|
float depth = bb.valid() ? distance(bb.center(),matrix) : 0.0f;
|
||||||
|
if (osg::isNaN(depth))
|
||||||
|
return;
|
||||||
|
|
||||||
|
bool pushedLight = mLightListCallback && mLightListCallback->pushLightState(this, cv);
|
||||||
|
|
||||||
|
for (PassVector::const_iterator it = mPasses.begin(); it != mPasses.end(); ++it)
|
||||||
|
{
|
||||||
|
cv->pushStateSet(*it);
|
||||||
|
cv->addDrawableAndDepth(this, &matrix, depth);
|
||||||
|
cv->popStateSet();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pushedLight)
|
||||||
|
cv->popStateSet();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TerrainDrawable::setPasses(const TerrainDrawable::PassVector &passes)
|
||||||
|
{
|
||||||
|
mPasses = passes;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TerrainDrawable::setLightListCallback(SceneUtil::LightListCallback *lightListCallback)
|
||||||
|
{
|
||||||
|
mLightListCallback = lightListCallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TerrainDrawable::compileGLObjects(osg::RenderInfo &renderInfo) const
|
||||||
|
{
|
||||||
|
for (PassVector::const_iterator it = mPasses.begin(); it != mPasses.end(); ++it)
|
||||||
|
{
|
||||||
|
osg::StateSet* stateset = *it;
|
||||||
|
stateset->compileGLObjects(*renderInfo.getState());
|
||||||
|
}
|
||||||
|
|
||||||
|
osg::Geometry::compileGLObjects(renderInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,53 @@
|
|||||||
|
#ifndef OPENMW_COMPONENTS_TERRAIN_DRAWABLE_H
|
||||||
|
#define OPENMW_COMPONENTS_TERRAIN_DRAWABLE_H
|
||||||
|
|
||||||
|
#include <osg/Geometry>
|
||||||
|
|
||||||
|
namespace osgUtil
|
||||||
|
{
|
||||||
|
class CullVisitor;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace SceneUtil
|
||||||
|
{
|
||||||
|
class LightListCallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace Terrain
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subclass of Geometry that supports built in multi-pass rendering and built in LightListCallback.
|
||||||
|
*/
|
||||||
|
class TerrainDrawable : public osg::Geometry
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
virtual osg::Object* cloneType() const { return new TerrainDrawable (); }
|
||||||
|
virtual osg::Object* clone(const osg::CopyOp& copyop) const { return new TerrainDrawable (*this,copyop); }
|
||||||
|
virtual bool isSameKindAs(const osg::Object* obj) const { return dynamic_cast<const TerrainDrawable *>(obj)!=NULL; }
|
||||||
|
virtual const char* className() const { return "TerrainDrawable"; }
|
||||||
|
virtual const char* libraryName() const { return "Terrain"; }
|
||||||
|
|
||||||
|
TerrainDrawable();
|
||||||
|
TerrainDrawable(const TerrainDrawable& copy, const osg::CopyOp& copyop);
|
||||||
|
|
||||||
|
virtual void accept(osg::NodeVisitor &nv);
|
||||||
|
void cull(osgUtil::CullVisitor* cv);
|
||||||
|
|
||||||
|
typedef std::vector<osg::ref_ptr<osg::StateSet> > PassVector;
|
||||||
|
void setPasses (const PassVector& passes);
|
||||||
|
|
||||||
|
void setLightListCallback(SceneUtil::LightListCallback* lightListCallback);
|
||||||
|
|
||||||
|
virtual void compileGLObjects(osg::RenderInfo& renderInfo) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
PassVector mPasses;
|
||||||
|
|
||||||
|
osg::ref_ptr<SceneUtil::LightListCallback> mLightListCallback;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#endif
|
@ -0,0 +1,64 @@
|
|||||||
|
#include "texturemanager.hpp"
|
||||||
|
|
||||||
|
#include <osg/Stats>
|
||||||
|
#include <osg/Texture2D>
|
||||||
|
|
||||||
|
#include <components/resource/scenemanager.hpp>
|
||||||
|
#include <components/resource/imagemanager.hpp>
|
||||||
|
#include <components/resource/objectcache.hpp>
|
||||||
|
|
||||||
|
namespace Terrain
|
||||||
|
{
|
||||||
|
|
||||||
|
TextureManager::TextureManager(Resource::SceneManager *sceneMgr)
|
||||||
|
: ResourceManager(sceneMgr->getVFS())
|
||||||
|
, mSceneManager(sceneMgr)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
struct UpdateTextureFilteringFunctor
|
||||||
|
{
|
||||||
|
UpdateTextureFilteringFunctor(Resource::SceneManager* sceneMgr)
|
||||||
|
: mSceneManager(sceneMgr)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
Resource::SceneManager* mSceneManager;
|
||||||
|
|
||||||
|
void operator()(osg::Object* obj)
|
||||||
|
{
|
||||||
|
mSceneManager->applyFilterSettings(static_cast<osg::Texture2D*>(obj));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
void TextureManager::updateTextureFiltering()
|
||||||
|
{
|
||||||
|
UpdateTextureFilteringFunctor f(mSceneManager);
|
||||||
|
mCache->call(f);
|
||||||
|
}
|
||||||
|
|
||||||
|
osg::ref_ptr<osg::Texture2D> TextureManager::getTexture(const std::string &name)
|
||||||
|
{
|
||||||
|
// don't bother with case folding, since there is only one way of referring to terrain textures we can assume the case is always the same
|
||||||
|
osg::ref_ptr<osg::Object> obj = mCache->getRefFromObjectCache(name);
|
||||||
|
if (obj)
|
||||||
|
return static_cast<osg::Texture2D*>(obj.get());
|
||||||
|
else
|
||||||
|
{
|
||||||
|
osg::ref_ptr<osg::Texture2D> texture (new osg::Texture2D(mSceneManager->getImageManager()->getImage(name)));
|
||||||
|
texture->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT);
|
||||||
|
texture->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT);
|
||||||
|
mSceneManager->applyFilterSettings(texture);
|
||||||
|
mCache->addEntryToObjectCache(name, texture.get());
|
||||||
|
return texture;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TextureManager::reportStats(unsigned int frameNumber, osg::Stats *stats) const
|
||||||
|
{
|
||||||
|
stats->setAttribute(frameNumber, "Terrain Texture", mCache->getCacheSize());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,39 @@
|
|||||||
|
#ifndef OPENMW_COMPONENTS_TERRAIN_TEXTUREMANAGER_H
|
||||||
|
#define OPENMW_COMPONENTS_TERRAIN_TEXTUREMANAGER_H
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include <components/resource/resourcemanager.hpp>
|
||||||
|
|
||||||
|
namespace Resource
|
||||||
|
{
|
||||||
|
class SceneManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace osg
|
||||||
|
{
|
||||||
|
class Texture2D;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace Terrain
|
||||||
|
{
|
||||||
|
|
||||||
|
class TextureManager : public Resource::ResourceManager
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
TextureManager(Resource::SceneManager* sceneMgr);
|
||||||
|
|
||||||
|
void updateTextureFiltering();
|
||||||
|
|
||||||
|
osg::ref_ptr<osg::Texture2D> getTexture(const std::string& name);
|
||||||
|
|
||||||
|
virtual void reportStats(unsigned int frameNumber, osg::Stats* stats) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
Resource::SceneManager* mSceneManager;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
@ -0,0 +1,154 @@
|
|||||||
|
#include "viewdata.hpp"
|
||||||
|
|
||||||
|
namespace Terrain
|
||||||
|
{
|
||||||
|
|
||||||
|
ViewData::ViewData()
|
||||||
|
: mNumEntries(0)
|
||||||
|
, mFrameLastUsed(0)
|
||||||
|
, mChanged(false)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
ViewData::~ViewData()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void ViewData::add(QuadTreeNode *node, bool visible)
|
||||||
|
{
|
||||||
|
unsigned int index = mNumEntries++;
|
||||||
|
|
||||||
|
if (index+1 > mEntries.size())
|
||||||
|
mEntries.resize(index+1);
|
||||||
|
|
||||||
|
Entry& entry = mEntries[index];
|
||||||
|
if (entry.set(node, visible))
|
||||||
|
mChanged = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned int ViewData::getNumEntries() const
|
||||||
|
{
|
||||||
|
return mNumEntries;
|
||||||
|
}
|
||||||
|
|
||||||
|
ViewData::Entry &ViewData::getEntry(unsigned int i)
|
||||||
|
{
|
||||||
|
return mEntries[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ViewData::hasChanged() const
|
||||||
|
{
|
||||||
|
return mChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ViewData::reset(unsigned int frame)
|
||||||
|
{
|
||||||
|
// clear any unused entries
|
||||||
|
for (unsigned int i=mNumEntries; i<mEntries.size(); ++i)
|
||||||
|
mEntries[i].set(NULL, false);
|
||||||
|
|
||||||
|
// reset index for next frame
|
||||||
|
mNumEntries = 0;
|
||||||
|
mChanged = false;
|
||||||
|
|
||||||
|
mFrameLastUsed = frame;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ViewData::clear()
|
||||||
|
{
|
||||||
|
for (unsigned int i=0; i<mEntries.size(); ++i)
|
||||||
|
mEntries[i].set(NULL, false);
|
||||||
|
mNumEntries = 0;
|
||||||
|
mFrameLastUsed = 0;
|
||||||
|
mChanged = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ViewData::contains(QuadTreeNode *node)
|
||||||
|
{
|
||||||
|
for (unsigned int i=0; i<mNumEntries; ++i)
|
||||||
|
if (mEntries[i].mNode == node)
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ViewData::Entry::Entry()
|
||||||
|
: mNode(NULL)
|
||||||
|
, mVisible(true)
|
||||||
|
, mLodFlags(0)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ViewData::Entry::set(QuadTreeNode *node, bool visible)
|
||||||
|
{
|
||||||
|
mVisible = visible;
|
||||||
|
if (node == mNode)
|
||||||
|
return false;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mNode = node;
|
||||||
|
// clear cached data
|
||||||
|
mRenderingNode = NULL;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ViewData *ViewDataMap::getViewData(osg::Object *viewer, bool ref)
|
||||||
|
{
|
||||||
|
Map::const_iterator found = mViews.find(viewer);
|
||||||
|
if (found == mViews.end())
|
||||||
|
{
|
||||||
|
ViewData* vd = createOrReuseView();
|
||||||
|
if (ref)
|
||||||
|
vd->setViewer(viewer);
|
||||||
|
mViews[viewer] = vd;
|
||||||
|
return vd;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return found->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
ViewData *ViewDataMap::createOrReuseView()
|
||||||
|
{
|
||||||
|
if (mUnusedViews.size())
|
||||||
|
{
|
||||||
|
ViewData* vd = mUnusedViews.front();
|
||||||
|
mUnusedViews.pop_front();
|
||||||
|
return vd;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mViewVector.push_back(ViewData());
|
||||||
|
return &mViewVector.back();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ViewDataMap::clearUnusedViews(unsigned int frame)
|
||||||
|
{
|
||||||
|
for (Map::iterator it = mViews.begin(); it != mViews.end(); )
|
||||||
|
{
|
||||||
|
ViewData* vd = it->second;
|
||||||
|
if ((!vd->getViewer() // if no ref was held, always need to clear to avoid holding a dangling ref.
|
||||||
|
|| vd->getFrameLastUsed() + 2 < frame))
|
||||||
|
{
|
||||||
|
vd->setViewer(NULL);
|
||||||
|
vd->clear();
|
||||||
|
mUnusedViews.push_back(vd);
|
||||||
|
mViews.erase(it++);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ViewDataMap::clear()
|
||||||
|
{
|
||||||
|
mViews.clear();
|
||||||
|
mUnusedViews.clear();
|
||||||
|
mViewVector.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,85 @@
|
|||||||
|
#ifndef OPENMW_COMPONENTS_TERRAIN_VIEWDATA_H
|
||||||
|
#define OPENMW_COMPONENTS_TERRAIN_VIEWDATA_H
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <deque>
|
||||||
|
|
||||||
|
#include <osg/Node>
|
||||||
|
|
||||||
|
#include "world.hpp"
|
||||||
|
|
||||||
|
namespace Terrain
|
||||||
|
{
|
||||||
|
|
||||||
|
class QuadTreeNode;
|
||||||
|
|
||||||
|
class ViewData : public View
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
ViewData();
|
||||||
|
~ViewData();
|
||||||
|
|
||||||
|
void add(QuadTreeNode* node, bool visible);
|
||||||
|
|
||||||
|
void reset(unsigned int frame);
|
||||||
|
|
||||||
|
void clear();
|
||||||
|
|
||||||
|
bool contains(QuadTreeNode* node);
|
||||||
|
|
||||||
|
struct Entry
|
||||||
|
{
|
||||||
|
Entry();
|
||||||
|
|
||||||
|
bool set(QuadTreeNode* node, bool visible);
|
||||||
|
|
||||||
|
QuadTreeNode* mNode;
|
||||||
|
bool mVisible;
|
||||||
|
|
||||||
|
unsigned int mLodFlags;
|
||||||
|
osg::ref_ptr<osg::Node> mRenderingNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
unsigned int getNumEntries() const;
|
||||||
|
|
||||||
|
Entry& getEntry(unsigned int i);
|
||||||
|
|
||||||
|
osg::Object* getViewer() const { return mViewer.get(); }
|
||||||
|
void setViewer(osg::Object* viewer) { mViewer = viewer; }
|
||||||
|
|
||||||
|
unsigned int getFrameLastUsed() const { return mFrameLastUsed; }
|
||||||
|
|
||||||
|
/// @return Have any nodes changed since the last frame
|
||||||
|
bool hasChanged() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<Entry> mEntries;
|
||||||
|
unsigned int mNumEntries;
|
||||||
|
unsigned int mFrameLastUsed;
|
||||||
|
bool mChanged;
|
||||||
|
osg::ref_ptr<osg::Object> mViewer;
|
||||||
|
};
|
||||||
|
|
||||||
|
class ViewDataMap : public osg::Referenced
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
ViewData* getViewData(osg::Object* viewer, bool ref);
|
||||||
|
|
||||||
|
ViewData* createOrReuseView();
|
||||||
|
|
||||||
|
void clearUnusedViews(unsigned int frame);
|
||||||
|
|
||||||
|
void clear();
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::list<ViewData> mViewVector;
|
||||||
|
|
||||||
|
typedef std::map<osg::Object*, ViewData*> Map;
|
||||||
|
Map mViews;
|
||||||
|
|
||||||
|
std::deque<ViewData*> mUnusedViews;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
@ -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.
|
Loading…
Reference in New Issue