forked from teamnwah/openmw-tes3coop
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
330 lines
12 KiB
C++
330 lines
12 KiB
C++
#include "terraingrid.hpp"
|
|
|
|
#include <memory>
|
|
|
|
#include <osg/Material>
|
|
|
|
#include <OpenThreads/ScopedLock>
|
|
|
|
#include <components/resource/resourcesystem.hpp>
|
|
#include <components/resource/imagemanager.hpp>
|
|
#include <components/resource/scenemanager.hpp>
|
|
|
|
#include <components/sceneutil/lightmanager.hpp>
|
|
#include <components/sceneutil/positionattitudetransform.hpp>
|
|
#include <components/sceneutil/unrefqueue.hpp>
|
|
|
|
#include <components/esm/loadland.hpp>
|
|
|
|
#include <osg/Geometry>
|
|
#include <osg/KdTree>
|
|
|
|
#include <osgFX/Effect>
|
|
|
|
#include <osgUtil/IncrementalCompileOperation>
|
|
|
|
#include "material.hpp"
|
|
#include "storage.hpp"
|
|
|
|
namespace
|
|
{
|
|
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
|
|
{
|
|
|
|
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)
|
|
, mNumSplits(4)
|
|
, mCache((storage->getCellVertices()-1)/static_cast<float>(mNumSplits) + 1)
|
|
, mUnrefQueue(unrefQueue)
|
|
, mShaderManager(shaderManager)
|
|
{
|
|
osg::ref_ptr<osg::Material> material (new osg::Material);
|
|
material->setColorMode(osg::Material::AMBIENT_AND_DIFFUSE);
|
|
mTerrainRoot->getOrCreateStateSet()->setAttributeAndModes(material, osg::StateAttribute::ON);
|
|
}
|
|
|
|
TerrainGrid::~TerrainGrid()
|
|
{
|
|
while (!mGrid.empty())
|
|
{
|
|
unloadCell(mGrid.begin()->first.first, mGrid.begin()->first.second);
|
|
}
|
|
}
|
|
|
|
osg::ref_ptr<osg::Node> TerrainGrid::cacheCell(int x, int y)
|
|
{
|
|
{
|
|
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(mGridCacheMutex);
|
|
Grid::iterator found = mGridCache.find(std::make_pair(x,y));
|
|
if (found != mGridCache.end())
|
|
return found->second;
|
|
}
|
|
osg::ref_ptr<osg::Node> node = buildTerrain(NULL, 1.f, osg::Vec2f(x+0.5, y+0.5));
|
|
|
|
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(mGridCacheMutex);
|
|
mGridCache.insert(std::make_pair(std::make_pair(x,y), node));
|
|
return node;
|
|
}
|
|
|
|
osg::ref_ptr<osg::Node> TerrainGrid::buildTerrain (osg::Group* parent, float chunkSize, const osg::Vec2f& chunkCenter)
|
|
{
|
|
if (chunkSize * mNumSplits > 1.f)
|
|
{
|
|
// keep splitting
|
|
osg::ref_ptr<osg::Group> group (new osg::Group);
|
|
if (parent)
|
|
parent->addChild(group);
|
|
|
|
float newChunkSize = chunkSize/2.f;
|
|
buildTerrain(group, newChunkSize, chunkCenter + osg::Vec2f(newChunkSize/2.f, newChunkSize/2.f));
|
|
buildTerrain(group, newChunkSize, chunkCenter + osg::Vec2f(newChunkSize/2.f, -newChunkSize/2.f));
|
|
buildTerrain(group, newChunkSize, chunkCenter + osg::Vec2f(-newChunkSize/2.f, newChunkSize/2.f));
|
|
buildTerrain(group, newChunkSize, chunkCenter + osg::Vec2f(-newChunkSize/2.f, -newChunkSize/2.f));
|
|
return group;
|
|
}
|
|
else
|
|
{
|
|
float minH, maxH;
|
|
if (!mStorage->getMinMaxHeights(chunkSize, chunkCenter, minH, maxH))
|
|
return NULL; // no terrain defined
|
|
|
|
osg::Vec2f worldCenter = chunkCenter*mStorage->getCellWorldSize();
|
|
osg::ref_ptr<SceneUtil::PositionAttitudeTransform> transform (new SceneUtil::PositionAttitudeTransform);
|
|
transform->setPosition(osg::Vec3f(worldCenter.x(), worldCenter.y(), 0.f));
|
|
|
|
if (parent)
|
|
parent->addChild(transform);
|
|
|
|
osg::ref_ptr<osg::Vec3Array> positions (new osg::Vec3Array);
|
|
osg::ref_ptr<osg::Vec3Array> normals (new osg::Vec3Array);
|
|
osg::ref_ptr<osg::Vec4Array> colors (new osg::Vec4Array);
|
|
|
|
osg::ref_ptr<osg::VertexBufferObject> vbo (new osg::VertexBufferObject);
|
|
positions->setVertexBufferObject(vbo);
|
|
normals->setVertexBufferObject(vbo);
|
|
colors->setVertexBufferObject(vbo);
|
|
|
|
mStorage->fillVertexBuffers(0, chunkSize, chunkCenter, positions, normals, colors);
|
|
|
|
osg::ref_ptr<osg::Geometry> geometry (new osg::Geometry);
|
|
geometry->setVertexArray(positions);
|
|
geometry->setNormalArray(normals, osg::Array::BIND_PER_VERTEX);
|
|
geometry->setColorArray(colors, osg::Array::BIND_PER_VERTEX);
|
|
geometry->setUseDisplayList(false);
|
|
geometry->setUseVertexBufferObjects(true);
|
|
|
|
geometry->addPrimitiveSet(mCache.getIndexBuffer(0));
|
|
|
|
// we already know the bounding box, so no need to let OSG compute it.
|
|
osg::Vec3f min(-0.5f*mStorage->getCellWorldSize()*chunkSize,
|
|
-0.5f*mStorage->getCellWorldSize()*chunkSize,
|
|
minH);
|
|
osg::Vec3f max (0.5f*mStorage->getCellWorldSize()*chunkSize,
|
|
0.5f*mStorage->getCellWorldSize()*chunkSize,
|
|
maxH);
|
|
osg::BoundingBox bounds(min, max);
|
|
geometry->setComputeBoundingBoxCallback(new StaticBoundingBoxCallback(bounds));
|
|
|
|
std::vector<LayerInfo> layerList;
|
|
std::vector<osg::ref_ptr<osg::Image> > blendmaps;
|
|
mStorage->getBlendmaps(chunkSize, chunkCenter, false, blendmaps, layerList);
|
|
|
|
// For compiling textures, I don't think the osgFX::Effect does it correctly
|
|
osg::ref_ptr<osg::Node> textureCompileDummy (new osg::Node);
|
|
unsigned int dummyTextureCounter = 0;
|
|
|
|
bool useShaders = mResourceSystem->getSceneManager()->getForceShaders();
|
|
if (!mResourceSystem->getSceneManager()->getClampLighting())
|
|
useShaders = true; // always use shaders when lighting is unclamped, this is to avoid lighting seams between a terrain chunk with normal maps and one without normal maps
|
|
std::vector<TextureLayer> layers;
|
|
{
|
|
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(mTextureCacheMutex);
|
|
for (std::vector<LayerInfo>::const_iterator it = layerList.begin(); it != layerList.end(); ++it)
|
|
{
|
|
TextureLayer textureLayer;
|
|
textureLayer.mParallax = it->mParallax;
|
|
textureLayer.mSpecular = it->mSpecular;
|
|
osg::ref_ptr<osg::Texture2D> texture = mTextureCache[it->mDiffuseMap];
|
|
if (!texture)
|
|
{
|
|
texture = new osg::Texture2D(mResourceSystem->getImageManager()->getImage(it->mDiffuseMap));
|
|
texture->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT);
|
|
texture->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT);
|
|
mResourceSystem->getSceneManager()->applyFilterSettings(texture);
|
|
mTextureCache[it->mDiffuseMap] = texture;
|
|
}
|
|
textureLayer.mDiffuseMap = texture;
|
|
textureCompileDummy->getOrCreateStateSet()->setTextureAttributeAndModes(dummyTextureCounter++, texture);
|
|
|
|
if (!it->mNormalMap.empty())
|
|
{
|
|
texture = mTextureCache[it->mNormalMap];
|
|
if (!texture)
|
|
{
|
|
texture = new osg::Texture2D(mResourceSystem->getImageManager()->getImage(it->mNormalMap));
|
|
texture->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT);
|
|
texture->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT);
|
|
mResourceSystem->getSceneManager()->applyFilterSettings(texture);
|
|
mTextureCache[it->mNormalMap] = texture;
|
|
}
|
|
textureCompileDummy->getOrCreateStateSet()->setTextureAttributeAndModes(dummyTextureCounter++, texture);
|
|
textureLayer.mNormalMap = texture;
|
|
}
|
|
|
|
if (it->requiresShaders())
|
|
useShaders = true;
|
|
|
|
layers.push_back(textureLayer);
|
|
}
|
|
}
|
|
|
|
std::vector<osg::ref_ptr<osg::Texture2D> > blendmapTextures;
|
|
for (std::vector<osg::ref_ptr<osg::Image> >::const_iterator it = blendmaps.begin(); it != blendmaps.end(); ++it)
|
|
{
|
|
osg::ref_ptr<osg::Texture2D> texture (new osg::Texture2D);
|
|
texture->setImage(*it);
|
|
texture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE);
|
|
texture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE);
|
|
texture->setResizeNonPowerOfTwoHint(false);
|
|
blendmapTextures.push_back(texture);
|
|
|
|
textureCompileDummy->getOrCreateStateSet()->setTextureAttributeAndModes(dummyTextureCounter++, blendmapTextures.back());
|
|
}
|
|
|
|
// use texture coordinates for both texture units, the layer texture and blend texture
|
|
for (unsigned int i=0; i<2; ++i)
|
|
geometry->setTexCoordArray(i, mCache.getUVBuffer());
|
|
|
|
float blendmapScale = ESM::Land::LAND_TEXTURE_SIZE*chunkSize;
|
|
osg::ref_ptr<osgFX::Effect> effect (new Terrain::Effect(mShaderManager ? useShaders : false, mResourceSystem->getSceneManager()->getForcePerPixelLighting(), mResourceSystem->getSceneManager()->getClampLighting(),
|
|
mShaderManager, layers, blendmapTextures, blendmapScale, blendmapScale));
|
|
|
|
effect->addCullCallback(new SceneUtil::LightListCallback);
|
|
|
|
transform->addChild(effect);
|
|
|
|
osg::Node* toAttach = geometry.get();
|
|
|
|
effect->addChild(toAttach);
|
|
|
|
if (mIncrementalCompileOperation)
|
|
{
|
|
mIncrementalCompileOperation->add(toAttach);
|
|
mIncrementalCompileOperation->add(textureCompileDummy);
|
|
}
|
|
|
|
return transform;
|
|
}
|
|
}
|
|
|
|
void TerrainGrid::loadCell(int x, int y)
|
|
{
|
|
if (mGrid.find(std::make_pair(x, y)) != mGrid.end())
|
|
return; // already loaded
|
|
|
|
// try to get it from the cache
|
|
osg::ref_ptr<osg::Node> terrainNode;
|
|
{
|
|
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(mGridCacheMutex);
|
|
Grid::const_iterator found = mGridCache.find(std::make_pair(x,y));
|
|
if (found != mGridCache.end())
|
|
{
|
|
terrainNode = found->second;
|
|
if (!terrainNode)
|
|
return; // no terrain defined
|
|
}
|
|
}
|
|
|
|
// didn't find in cache, build it
|
|
if (!terrainNode)
|
|
{
|
|
osg::Vec2f center(x+0.5f, y+0.5f);
|
|
terrainNode = buildTerrain(NULL, 1.f, center);
|
|
if (!terrainNode)
|
|
return; // no terrain defined
|
|
}
|
|
|
|
mTerrainRoot->addChild(terrainNode);
|
|
|
|
mGrid[std::make_pair(x,y)] = terrainNode;
|
|
}
|
|
|
|
void TerrainGrid::unloadCell(int x, int y)
|
|
{
|
|
Grid::iterator it = mGrid.find(std::make_pair(x,y));
|
|
if (it == mGrid.end())
|
|
return;
|
|
|
|
osg::ref_ptr<osg::Node> terrainNode = it->second;
|
|
mTerrainRoot->removeChild(terrainNode);
|
|
|
|
if (mUnrefQueue.get())
|
|
mUnrefQueue->push(terrainNode);
|
|
|
|
mGrid.erase(it);
|
|
}
|
|
|
|
void TerrainGrid::updateCache()
|
|
{
|
|
{
|
|
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(mGridCacheMutex);
|
|
for (Grid::iterator it = mGridCache.begin(); it != mGridCache.end();)
|
|
{
|
|
if (it->second->referenceCount() <= 1)
|
|
mGridCache.erase(it++);
|
|
else
|
|
++it;
|
|
}
|
|
}
|
|
|
|
{
|
|
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(mTextureCacheMutex);
|
|
for (TextureCache::iterator it = mTextureCache.begin(); it != mTextureCache.end();)
|
|
{
|
|
if (it->second->referenceCount() <= 1)
|
|
mTextureCache.erase(it++);
|
|
else
|
|
++it;
|
|
}
|
|
}
|
|
}
|
|
|
|
void TerrainGrid::updateTextureFiltering()
|
|
{
|
|
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(mTextureCacheMutex);
|
|
for (TextureCache::iterator it = mTextureCache.begin(); it != mTextureCache.end(); ++it)
|
|
mResourceSystem->getSceneManager()->applyFilterSettings(it->second);
|
|
}
|
|
|
|
void TerrainGrid::reportStats(unsigned int frameNumber, osg::Stats *stats)
|
|
{
|
|
{
|
|
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(mGridCacheMutex);
|
|
stats->setAttribute(frameNumber, "Terrain Cell", mGridCache.size());
|
|
}
|
|
{
|
|
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(mTextureCacheMutex);
|
|
stats->setAttribute(frameNumber, "Terrain Texture", mTextureCache.size());
|
|
}
|
|
}
|
|
|
|
}
|