forked from teamnwah/openmw-tes3coop
Terrain rendering
This commit is contained in:
parent
10f938ff87
commit
cdd0623009
37 changed files with 541 additions and 2501 deletions
|
@ -174,7 +174,7 @@ if (${OGRE_VERSION} VERSION_LESS "1.9")
|
|||
message(FATAL_ERROR "OpenMW requires Ogre 1.9 or later, please install the latest stable version from http://ogre3d.org")
|
||||
endif()
|
||||
|
||||
find_package(OpenSceneGraph 3.2.0 REQUIRED osgDB osgViewer osgGA osgAnimation osgParticle osgQt osgUtil)
|
||||
find_package(OpenSceneGraph 3.2.0 REQUIRED osgDB osgViewer osgGA osgAnimation osgParticle osgQt osgUtil osgFX)
|
||||
include_directories(${OPENSCENEGRAPH_INCLUDE_DIRS})
|
||||
|
||||
find_package(MyGUI REQUIRED)
|
||||
|
|
|
@ -488,6 +488,11 @@ Resource::ResourceSystem* CSMWorld::Data::getResourceSystem()
|
|||
return &mResourceSystem;
|
||||
}
|
||||
|
||||
const Resource::ResourceSystem* CSMWorld::Data::getResourceSystem() const
|
||||
{
|
||||
return &mResourceSystem;
|
||||
}
|
||||
|
||||
const CSMWorld::IdCollection<ESM::Global>& CSMWorld::Data::getGlobals() const
|
||||
{
|
||||
return mGlobals;
|
||||
|
|
|
@ -140,6 +140,8 @@ namespace CSMWorld
|
|||
|
||||
Resource::ResourceSystem* getResourceSystem();
|
||||
|
||||
const Resource::ResourceSystem* getResourceSystem() const;
|
||||
|
||||
const IdCollection<ESM::Global>& getGlobals() const;
|
||||
|
||||
IdCollection<ESM::Global>& getGlobals();
|
||||
|
|
|
@ -64,7 +64,6 @@ CSVRender::Cell::Cell (CSMWorld::Data& data, osg::Group* rootNode, const std::st
|
|||
|
||||
addObjects (0, rows-1);
|
||||
|
||||
/*
|
||||
const CSMWorld::IdCollection<CSMWorld::Land>& land = mData.getLand();
|
||||
int landIndex = land.searchId(mId);
|
||||
if (landIndex != -1)
|
||||
|
@ -72,27 +71,18 @@ CSVRender::Cell::Cell (CSMWorld::Data& data, osg::Group* rootNode, const std::st
|
|||
const ESM::Land* esmLand = land.getRecord(mId).get().mLand.get();
|
||||
if(esmLand && esmLand->mDataTypes&ESM::Land::DATA_VHGT)
|
||||
{
|
||||
mTerrain.reset(new Terrain::TerrainGrid(sceneManager, new TerrainStorage(mData), Element_Terrain, true,
|
||||
Terrain::Align_XY));
|
||||
mTerrain.reset(new Terrain::TerrainGrid(mCellNode, data.getResourceSystem(), NULL, new TerrainStorage(mData), Element_Terrain<<1));
|
||||
mTerrain->loadCell(esmLand->mX,
|
||||
esmLand->mY);
|
||||
|
||||
//float verts = ESM::Land::LAND_SIZE;
|
||||
//float worldsize = ESM::Land::REAL_SIZE;
|
||||
mX = esmLand->mX;
|
||||
mY = esmLand->mY;
|
||||
//mPhysics->addHeightField(sceneManager,
|
||||
// esmLand->mLandData->mHeights, mX, mY, 0, worldsize / (verts-1), verts);
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
CSVRender::Cell::~Cell()
|
||||
{
|
||||
//if (mTerrain.get())
|
||||
// mPhysics->removeHeightField(mSceneMgr, mX, mY);
|
||||
|
||||
for (std::map<std::string, Object *>::iterator iter (mObjects.begin());
|
||||
iter!=mObjects.end(); ++iter)
|
||||
delete iter->second;
|
||||
|
@ -221,11 +211,3 @@ bool CSVRender::Cell::referenceAdded (const QModelIndex& parent, int start, int
|
|||
|
||||
return addObjects (start, end);
|
||||
}
|
||||
|
||||
float CSVRender::Cell::getTerrainHeightAt(const Ogre::Vector3 &pos) const
|
||||
{
|
||||
if(mTerrain.get() != NULL)
|
||||
return mTerrain->getHeightAt(pos);
|
||||
else
|
||||
return -std::numeric_limits<float>::max();
|
||||
}
|
||||
|
|
|
@ -75,8 +75,6 @@ namespace CSVRender
|
|||
/// \return Did this call result in a modification of the visual representation of
|
||||
/// this cell?
|
||||
bool referenceAdded (const QModelIndex& parent, int start, int end);
|
||||
|
||||
float getTerrainHeightAt(const Ogre::Vector3 &pos) const;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -4,7 +4,8 @@ namespace CSVRender
|
|||
{
|
||||
|
||||
TerrainStorage::TerrainStorage(const CSMWorld::Data &data)
|
||||
: mData(data)
|
||||
: ESMTerrain::Storage(data.getResourceSystem()->getVFS())
|
||||
, mData(data)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
|
@ -22,8 +22,8 @@ source_group(game FILES ${GAME} ${GAME_HEADER})
|
|||
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
|
||||
# occlusionquery shadows ripplesimulation terrainstorage
|
||||
bulletdebugdraw globalmap characterpreview camera localmap water terrainstorage
|
||||
# occlusionquery shadows ripplesimulation
|
||||
)
|
||||
|
||||
add_openmw_dir (mwinput
|
||||
|
|
|
@ -157,7 +157,7 @@ osg::ref_ptr<osg::Camera> LocalMap::createOrthographicCamera(float x, float y, f
|
|||
camera->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||
camera->setRenderOrder(osg::Camera::PRE_RENDER);
|
||||
|
||||
camera->setCullMask(MWRender::Mask_Scene|MWRender::Mask_Water);
|
||||
camera->setCullMask(Mask_Scene|Mask_Water|Mask_Terrain);
|
||||
camera->setNodeMask(Mask_RenderToTexture);
|
||||
|
||||
osg::ref_ptr<osg::StateSet> stateset = new osg::StateSet;
|
||||
|
@ -309,7 +309,7 @@ void LocalMap::requestExteriorMap(MWWorld::CellStore* cell)
|
|||
void LocalMap::requestInteriorMap(MWWorld::CellStore* cell)
|
||||
{
|
||||
osg::ComputeBoundsVisitor computeBoundsVisitor;
|
||||
computeBoundsVisitor.setTraversalMask(Mask_Scene);
|
||||
computeBoundsVisitor.setTraversalMask(Mask_Scene|Mask_Terrain);
|
||||
mSceneRoot->accept(computeBoundsVisitor);
|
||||
|
||||
osg::BoundingBox bounds = computeBoundsVisitor.getBoundingBox();
|
||||
|
|
|
@ -26,6 +26,8 @@
|
|||
#include <components/sceneutil/lightmanager.hpp>
|
||||
#include <components/sceneutil/statesetupdater.hpp>
|
||||
|
||||
#include <components/terrain/terraingrid.hpp>
|
||||
|
||||
#include <components/esm/loadcell.hpp>
|
||||
|
||||
#include "sky.hpp"
|
||||
|
@ -35,6 +37,7 @@
|
|||
#include "pathgrid.hpp"
|
||||
#include "camera.hpp"
|
||||
#include "water.hpp"
|
||||
#include "terrainstorage.hpp"
|
||||
|
||||
namespace MWRender
|
||||
{
|
||||
|
@ -132,7 +135,10 @@ namespace MWRender
|
|||
|
||||
mEffectManager.reset(new EffectManager(lightRoot, mResourceSystem));
|
||||
|
||||
mWater.reset(new Water(lightRoot, mResourceSystem));
|
||||
mWater.reset(new Water(lightRoot, mResourceSystem, mViewer->getIncrementalCompileOperation()));
|
||||
|
||||
mTerrain.reset(new Terrain::TerrainGrid(lightRoot, mResourceSystem, mViewer->getIncrementalCompileOperation(),
|
||||
new TerrainStorage(mResourceSystem->getVFS(), false), Mask_Terrain));
|
||||
|
||||
mCamera.reset(new Camera(mViewer->getCamera()));
|
||||
|
||||
|
@ -236,12 +242,18 @@ namespace MWRender
|
|||
mPathgrid->addCell(store);
|
||||
|
||||
mWater->changeCell(store);
|
||||
|
||||
if (store->getCell()->isExterior())
|
||||
mTerrain->loadCell(store->getCell()->getGridX(), store->getCell()->getGridY());
|
||||
}
|
||||
|
||||
void RenderingManager::removeCell(const MWWorld::CellStore *store)
|
||||
{
|
||||
mPathgrid->removeCell(store);
|
||||
mObjects->removeCell(store);
|
||||
|
||||
if (store->getCell()->isExterior())
|
||||
mTerrain->unloadCell(store->getCell()->getGridX(), store->getCell()->getGridY());
|
||||
}
|
||||
|
||||
void RenderingManager::setSkyEnabled(bool enabled)
|
||||
|
@ -482,7 +494,6 @@ namespace MWRender
|
|||
|
||||
void RenderingManager::clear()
|
||||
{
|
||||
//mLocalMap->clear();
|
||||
notifyWorldSpaceChanged();
|
||||
}
|
||||
|
||||
|
@ -595,6 +606,11 @@ namespace MWRender
|
|||
return mNearClip;
|
||||
}
|
||||
|
||||
float RenderingManager::getTerrainHeightAt(const osg::Vec3f &pos)
|
||||
{
|
||||
return mTerrain->getHeightAt(pos);
|
||||
}
|
||||
|
||||
bool RenderingManager::vanityRotateCamera(const float *rot)
|
||||
{
|
||||
if(!mCamera->isVanityOrPreviewModeEnabled())
|
||||
|
|
|
@ -32,6 +32,11 @@ namespace ESM
|
|||
struct Cell;
|
||||
}
|
||||
|
||||
namespace Terrain
|
||||
{
|
||||
class World;
|
||||
}
|
||||
|
||||
namespace MWRender
|
||||
{
|
||||
|
||||
|
@ -119,6 +124,8 @@ namespace MWRender
|
|||
|
||||
float getNearClipDistance() const;
|
||||
|
||||
float getTerrainHeightAt(const osg::Vec3f& pos);
|
||||
|
||||
// camera stuff
|
||||
bool vanityRotateCamera(const float *rot);
|
||||
void setCameraDistance(float dist, bool adjust, bool override);
|
||||
|
@ -147,6 +154,7 @@ namespace MWRender
|
|||
std::auto_ptr<Pathgrid> mPathgrid;
|
||||
std::auto_ptr<Objects> mObjects;
|
||||
std::auto_ptr<Water> mWater;
|
||||
std::auto_ptr<Terrain::World> mTerrain;
|
||||
std::auto_ptr<SkyManager> mSky;
|
||||
std::auto_ptr<EffectManager> mEffectManager;
|
||||
std::auto_ptr<NpcAnimation> mPlayerAnimation;
|
||||
|
|
|
@ -9,7 +9,8 @@
|
|||
namespace MWRender
|
||||
{
|
||||
|
||||
TerrainStorage::TerrainStorage(bool preload)
|
||||
TerrainStorage::TerrainStorage(const VFS::Manager* vfs, bool preload)
|
||||
: ESMTerrain::Storage(vfs)
|
||||
{
|
||||
if (preload)
|
||||
{
|
||||
|
|
|
@ -16,7 +16,7 @@ namespace MWRender
|
|||
|
||||
///@param preload Preload all Land records at startup? If using the multithreaded terrain component, this
|
||||
/// should be set to "true" in order to avoid race conditions.
|
||||
TerrainStorage(bool preload);
|
||||
TerrainStorage(const VFS::Manager* vfs, bool preload);
|
||||
|
||||
/// Get bounds of the whole terrain in cell units
|
||||
virtual void getBounds(float& minX, float& maxX, float& minY, float& maxY);
|
||||
|
|
|
@ -16,13 +16,14 @@ namespace MWRender
|
|||
Mask_Player = (1<<4),
|
||||
Mask_Sky = (1<<5),
|
||||
Mask_Water = (1<<6),
|
||||
Mask_Terrain = (1<<7),
|
||||
|
||||
// top level masks
|
||||
Mask_Scene = (1<<7),
|
||||
Mask_GUI = (1<<8),
|
||||
Mask_Scene = (1<<8),
|
||||
Mask_GUI = (1<<9),
|
||||
|
||||
// Set on cameras within the main scene graph
|
||||
Mask_RenderToTexture = (1<<9)
|
||||
Mask_RenderToTexture = (1<<10)
|
||||
|
||||
// reserved: (1<<16) for SceneUtil::Mask_Lit
|
||||
};
|
||||
|
|
|
@ -9,6 +9,8 @@
|
|||
#include <osg/PositionAttitudeTransform>
|
||||
#include <osg/Depth>
|
||||
|
||||
#include <osgUtil/IncrementalCompileOperation>
|
||||
|
||||
#include <components/resource/resourcesystem.hpp>
|
||||
#include <components/resource/texturemanager.hpp>
|
||||
|
||||
|
@ -105,7 +107,7 @@ namespace MWRender
|
|||
|
||||
// --------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
Water::Water(osg::Group *parent, Resource::ResourceSystem *resourceSystem)
|
||||
Water::Water(osg::Group *parent, Resource::ResourceSystem *resourceSystem, osgUtil::IncrementalCompileOperation *ico)
|
||||
: mParent(parent)
|
||||
, mResourceSystem(resourceSystem)
|
||||
, mEnabled(true)
|
||||
|
@ -118,6 +120,9 @@ Water::Water(osg::Group *parent, Resource::ResourceSystem *resourceSystem)
|
|||
geode->addDrawable(waterGeom);
|
||||
geode->setNodeMask(Mask_Water);
|
||||
|
||||
if (ico)
|
||||
ico->add(geode);
|
||||
|
||||
createWaterStateSet(mResourceSystem, geode);
|
||||
|
||||
mWaterNode = new osg::PositionAttitudeTransform;
|
||||
|
|
|
@ -11,6 +11,11 @@ namespace osg
|
|||
class PositionAttitudeTransform;
|
||||
}
|
||||
|
||||
namespace osgUtil
|
||||
{
|
||||
class IncrementalCompileOperation;
|
||||
}
|
||||
|
||||
namespace Resource
|
||||
{
|
||||
class ResourceSystem;
|
||||
|
@ -27,6 +32,7 @@ namespace MWRender
|
|||
osg::ref_ptr<osg::Group> mParent;
|
||||
osg::ref_ptr<osg::PositionAttitudeTransform> mWaterNode;
|
||||
Resource::ResourceSystem* mResourceSystem;
|
||||
osg::ref_ptr<osgUtil::IncrementalCompileOperation> mIncrementalCompileOperation;
|
||||
|
||||
bool mEnabled;
|
||||
bool mToggled;
|
||||
|
@ -36,7 +42,7 @@ namespace MWRender
|
|||
void updateVisible();
|
||||
|
||||
public:
|
||||
Water(osg::Group* parent, Resource::ResourceSystem* resourceSystem);
|
||||
Water(osg::Group* parent, Resource::ResourceSystem* resourceSystem, osgUtil::IncrementalCompileOperation* ico);
|
||||
~Water();
|
||||
|
||||
void setEnabled(bool enabled);
|
||||
|
|
|
@ -1298,7 +1298,9 @@ namespace MWWorld
|
|||
return;
|
||||
}
|
||||
|
||||
float terrainHeight = -FLT_MAX;//mRendering->getTerrainHeightAt(Ogre::Vector3(pos.pos));
|
||||
float terrainHeight = -std::numeric_limits<float>::max();
|
||||
if (isCellExterior())
|
||||
terrainHeight = mRendering->getTerrainHeightAt(pos.asVec3());
|
||||
|
||||
if (pos.pos[2] < terrainHeight)
|
||||
pos.pos[2] = terrainHeight;
|
||||
|
|
|
@ -104,10 +104,8 @@ add_component_dir (translation
|
|||
translation
|
||||
)
|
||||
|
||||
#add_definitions(-DTERRAIN_USE_SHADER=1)
|
||||
add_definitions(-DTERRAIN_USE_SHADER=0)
|
||||
add_component_dir (terrain
|
||||
quadtreenode chunk world defaultworld terraingrid storage material buffercache defs
|
||||
storage world buffercache defs terraingrid material
|
||||
)
|
||||
|
||||
add_component_dir (loadinglistener
|
||||
|
|
|
@ -80,7 +80,7 @@ struct Land
|
|||
VNML mNormals[LAND_NUM_VERTS * 3];
|
||||
uint16_t mTextures[LAND_NUM_TEXTURES];
|
||||
|
||||
char mColours[3 * LAND_NUM_VERTS];
|
||||
unsigned char mColours[3 * LAND_NUM_VERTS];
|
||||
int mDataTypes;
|
||||
|
||||
// low-LOD heightmap (used for rendering the global map)
|
||||
|
|
|
@ -1,34 +1,38 @@
|
|||
#include "storage.hpp"
|
||||
|
||||
#include <OgreVector2.h>
|
||||
#include <OgreTextureManager.h>
|
||||
#include <OgreStringConverter.h>
|
||||
#include <OgreRenderSystem.h>
|
||||
#include <OgreResourceGroupManager.h>
|
||||
#include <OgreResourceBackgroundQueue.h>
|
||||
#include <OgreRoot.h>
|
||||
#include <set>
|
||||
|
||||
#include <osg/Image>
|
||||
#include <osg/Plane>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include <boost/algorithm/string.hpp>
|
||||
|
||||
#include <components/terrain/quadtreenode.hpp>
|
||||
#include <components/misc/resourcehelpers.hpp>
|
||||
#include <components/vfs/manager.hpp>
|
||||
|
||||
namespace ESMTerrain
|
||||
{
|
||||
|
||||
bool Storage::getMinMaxHeights(float size, const Ogre::Vector2 ¢er, float &min, float &max)
|
||||
Storage::Storage(const VFS::Manager *vfs)
|
||||
: mVFS(vfs)
|
||||
{
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
Ogre::Vector2 origin = center - Ogre::Vector2(size/2.f, size/2.f);
|
||||
osg::Vec2f origin = center - osg::Vec2f(size/2.f, size/2.f);
|
||||
|
||||
assert(origin.x == (int) origin.x);
|
||||
assert(origin.y == (int) origin.y);
|
||||
assert(origin.x == (int) origin.x());
|
||||
assert(origin.y == (int) origin.y());
|
||||
|
||||
int cellX = static_cast<int>(origin.x);
|
||||
int cellY = static_cast<int>(origin.y);
|
||||
int cellX = static_cast<int>(origin.x());
|
||||
int cellY = static_cast<int>(origin.y());
|
||||
|
||||
const ESM::Land* land = getLand(cellX, cellY);
|
||||
if (!land || !(land->mDataTypes&ESM::Land::DATA_VHGT))
|
||||
|
@ -50,7 +54,7 @@ namespace ESMTerrain
|
|||
return true;
|
||||
}
|
||||
|
||||
void Storage::fixNormal (Ogre::Vector3& normal, int cellX, int cellY, int col, int row)
|
||||
void Storage::fixNormal (osg::Vec3f& normal, int cellX, int cellY, int col, int row)
|
||||
{
|
||||
while (col >= ESM::Land::LAND_SIZE-1)
|
||||
{
|
||||
|
@ -75,27 +79,27 @@ namespace ESMTerrain
|
|||
ESM::Land* land = getLand(cellX, cellY);
|
||||
if (land && land->mDataTypes&ESM::Land::DATA_VNML)
|
||||
{
|
||||
normal.x = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3];
|
||||
normal.y = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+1];
|
||||
normal.z = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+2];
|
||||
normal.normalise();
|
||||
normal.x() = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3];
|
||||
normal.y() = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+1];
|
||||
normal.z() = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+2];
|
||||
normal.normalize();
|
||||
}
|
||||
else
|
||||
normal = Ogre::Vector3(0,0,1);
|
||||
normal = osg::Vec3f(0,0,1);
|
||||
}
|
||||
|
||||
void Storage::averageNormal(Ogre::Vector3 &normal, int cellX, int cellY, int col, int row)
|
||||
void Storage::averageNormal(osg::Vec3f &normal, int cellX, int cellY, int col, int row)
|
||||
{
|
||||
Ogre::Vector3 n1,n2,n3,n4;
|
||||
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);
|
||||
normal = (n1+n2+n3+n4);
|
||||
normal.normalise();
|
||||
normal.normalize();
|
||||
}
|
||||
|
||||
void Storage::fixColour (Ogre::ColourValue& color, int cellX, int cellY, int col, int row)
|
||||
void Storage::fixColour (osg::Vec4f& color, int cellX, int cellY, int col, int row)
|
||||
{
|
||||
if (col == ESM::Land::LAND_SIZE-1)
|
||||
{
|
||||
|
@ -110,42 +114,42 @@ namespace ESMTerrain
|
|||
ESM::Land* land = getLand(cellX, cellY);
|
||||
if (land && land->mDataTypes&ESM::Land::DATA_VCLR)
|
||||
{
|
||||
color.r = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3] / 255.f;
|
||||
color.g = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3+1] / 255.f;
|
||||
color.b = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3+2] / 255.f;
|
||||
color.r() = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3] / 255.f;
|
||||
color.g() = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3+1] / 255.f;
|
||||
color.b() = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3+2] / 255.f;
|
||||
}
|
||||
else
|
||||
{
|
||||
color.r = 1;
|
||||
color.g = 1;
|
||||
color.b = 1;
|
||||
color.r() = 1;
|
||||
color.g() = 1;
|
||||
color.b() = 1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void Storage::fillVertexBuffers (int lodLevel, float size, const Ogre::Vector2& center, Terrain::Alignment align,
|
||||
std::vector<float>& positions,
|
||||
std::vector<float>& normals,
|
||||
std::vector<Ogre::uint8>& colours)
|
||||
void Storage::fillVertexBuffers (int lodLevel, float size, const osg::Vec2f& center,
|
||||
osg::ref_ptr<osg::Vec3Array> positions,
|
||||
osg::ref_ptr<osg::Vec3Array> normals,
|
||||
osg::ref_ptr<osg::Vec4Array> colours)
|
||||
{
|
||||
// LOD level n means every 2^n-th vertex is kept
|
||||
size_t increment = 1 << lodLevel;
|
||||
|
||||
Ogre::Vector2 origin = center - Ogre::Vector2(size/2.f, size/2.f);
|
||||
assert(origin.x == (int) origin.x);
|
||||
assert(origin.y == (int) origin.y);
|
||||
osg::Vec2f origin = center - osg::Vec2f(size/2.f, size/2.f);
|
||||
assert(origin.x() == (int) origin.x());
|
||||
assert(origin.y() == (int) origin.y());
|
||||
|
||||
int startX = static_cast<int>(origin.x);
|
||||
int startY = static_cast<int>(origin.y);
|
||||
int startX = static_cast<int>(origin.x());
|
||||
int startY = static_cast<int>(origin.y());
|
||||
|
||||
size_t numVerts = static_cast<size_t>(size*(ESM::Land::LAND_SIZE - 1) / increment + 1);
|
||||
|
||||
colours.resize(numVerts*numVerts*4);
|
||||
positions.resize(numVerts*numVerts*3);
|
||||
normals.resize(numVerts*numVerts*3);
|
||||
positions->resize(numVerts*numVerts);
|
||||
normals->resize(numVerts*numVerts);
|
||||
colours->resize(numVerts*numVerts);
|
||||
|
||||
Ogre::Vector3 normal;
|
||||
Ogre::ColourValue color;
|
||||
osg::Vec3f normal;
|
||||
osg::Vec4f color;
|
||||
|
||||
float vertY = 0;
|
||||
float vertX = 0;
|
||||
|
@ -175,22 +179,24 @@ namespace ESMTerrain
|
|||
vertX = vertX_;
|
||||
for (int row=rowStart; row<ESM::Land::LAND_SIZE; row += increment)
|
||||
{
|
||||
positions[static_cast<unsigned int>(vertX*numVerts * 3 + vertY * 3)] = ((vertX / float(numVerts - 1) - 0.5f) * size * 8192);
|
||||
positions[static_cast<unsigned int>(vertX*numVerts * 3 + vertY * 3 + 1)] = ((vertY / float(numVerts - 1) - 0.5f) * size * 8192);
|
||||
float height = -2048;
|
||||
if (land)
|
||||
positions[static_cast<unsigned int>(vertX*numVerts * 3 + vertY * 3 + 2)] = land->mLandData->mHeights[col*ESM::Land::LAND_SIZE + row];
|
||||
else
|
||||
positions[static_cast<unsigned int>(vertX*numVerts * 3 + vertY * 3 + 2)] = -2048;
|
||||
height = land->mLandData->mHeights[col*ESM::Land::LAND_SIZE + row];
|
||||
|
||||
(*positions)[static_cast<unsigned int>(vertX*numVerts + vertY)]
|
||||
= osg::Vec3f((vertX / float(numVerts - 1) - 0.5f) * size * 8192,
|
||||
(vertY / float(numVerts - 1) - 0.5f) * size * 8192,
|
||||
height);
|
||||
|
||||
if (land && land->mDataTypes&ESM::Land::DATA_VNML)
|
||||
{
|
||||
normal.x = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3];
|
||||
normal.y = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+1];
|
||||
normal.z = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+2];
|
||||
normal.normalise();
|
||||
normal.x() = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3];
|
||||
normal.y() = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+1];
|
||||
normal.z() = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+2];
|
||||
normal.normalize();
|
||||
}
|
||||
else
|
||||
normal = Ogre::Vector3(0,0,1);
|
||||
normal = osg::Vec3f(0,0,1);
|
||||
|
||||
// Normals apparently don't connect seamlessly between cells
|
||||
if (col == ESM::Land::LAND_SIZE-1 || row == ESM::Land::LAND_SIZE-1)
|
||||
|
@ -200,33 +206,30 @@ namespace ESMTerrain
|
|||
if ((row == 0 || row == ESM::Land::LAND_SIZE-1) && (col == 0 || col == ESM::Land::LAND_SIZE-1))
|
||||
averageNormal(normal, cellX, cellY, col, row);
|
||||
|
||||
assert(normal.z > 0);
|
||||
assert(normal.z() > 0);
|
||||
|
||||
normals[static_cast<unsigned int>(vertX*numVerts * 3 + vertY * 3)] = normal.x;
|
||||
normals[static_cast<unsigned int>(vertX*numVerts * 3 + vertY * 3 + 1)] = normal.y;
|
||||
normals[static_cast<unsigned int>(vertX*numVerts * 3 + vertY * 3 + 2)] = normal.z;
|
||||
(*normals)[static_cast<unsigned int>(vertX*numVerts + vertY)] = normal;
|
||||
|
||||
if (land && land->mDataTypes&ESM::Land::DATA_VCLR)
|
||||
{
|
||||
color.r = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3] / 255.f;
|
||||
color.g = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3+1] / 255.f;
|
||||
color.b = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3+2] / 255.f;
|
||||
color.r() = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3] / 255.f;
|
||||
color.g() = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3+1] / 255.f;
|
||||
color.b() = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3+2] / 255.f;
|
||||
}
|
||||
else
|
||||
{
|
||||
color.r = 1;
|
||||
color.g = 1;
|
||||
color.b = 1;
|
||||
color.r() = 1;
|
||||
color.g() = 1;
|
||||
color.b() = 1;
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
color.a = 1;
|
||||
Ogre::uint32 rsColor;
|
||||
Ogre::Root::getSingleton().getRenderSystem()->convertColourValue(color, &rsColor);
|
||||
memcpy(&colours[static_cast<unsigned int>(vertX*numVerts * 4 + vertY * 4)], &rsColor, sizeof(Ogre::uint32));
|
||||
color.a() = 1;
|
||||
|
||||
(*colours)[static_cast<unsigned int>(vertX*numVerts + vertY)] = color;
|
||||
|
||||
++vertX;
|
||||
}
|
||||
|
@ -281,39 +284,22 @@ namespace ESMTerrain
|
|||
// NB: All vtex ids are +1 compared to the ltex ids
|
||||
const ESM::LandTexture* ltex = getLandTexture(id.first-1, id.second);
|
||||
|
||||
//TODO this is needed due to MWs messed up texture handling
|
||||
assert(0 && "no vfs here yet");
|
||||
std::string texture = ltex->mTexture; //Misc::ResourceHelpers::correctTexturePath(ltex->mTexture);
|
||||
// this is needed due to MWs messed up texture handling
|
||||
std::string texture = Misc::ResourceHelpers::correctTexturePath(ltex->mTexture, mVFS);
|
||||
|
||||
return texture;
|
||||
}
|
||||
|
||||
void Storage::getBlendmaps (const std::vector<Terrain::QuadTreeNode*>& nodes, std::vector<Terrain::LayerCollection>& out, bool pack)
|
||||
{
|
||||
for (std::vector<Terrain::QuadTreeNode*>::const_iterator it = nodes.begin(); it != nodes.end(); ++it)
|
||||
{
|
||||
out.push_back(Terrain::LayerCollection());
|
||||
out.back().mTarget = *it;
|
||||
getBlendmapsImpl(static_cast<float>((*it)->getSize()), (*it)->getCenter(), pack, out.back().mBlendmaps, out.back().mLayers);
|
||||
}
|
||||
}
|
||||
|
||||
void Storage::getBlendmaps(float chunkSize, const Ogre::Vector2 &chunkCenter,
|
||||
bool pack, std::vector<Ogre::PixelBox> &blendmaps, std::vector<Terrain::LayerInfo> &layerList)
|
||||
{
|
||||
getBlendmapsImpl(chunkSize, chunkCenter, pack, blendmaps, layerList);
|
||||
}
|
||||
|
||||
void Storage::getBlendmapsImpl(float chunkSize, const Ogre::Vector2 &chunkCenter,
|
||||
bool pack, std::vector<Ogre::PixelBox> &blendmaps, std::vector<Terrain::LayerInfo> &layerList)
|
||||
void Storage::getBlendmaps(float chunkSize, const osg::Vec2f &chunkCenter,
|
||||
bool pack, ImageVector &blendmaps, std::vector<Terrain::LayerInfo> &layerList)
|
||||
{
|
||||
// TODO - blending isn't completely right yet; the blending radius appears to be
|
||||
// different at a cell transition (2 vertices, not 4), so we may need to create a larger blendmap
|
||||
// and interpolate the rest of the cell by hand? :/
|
||||
|
||||
Ogre::Vector2 origin = chunkCenter - Ogre::Vector2(chunkSize/2.f, chunkSize/2.f);
|
||||
int cellX = static_cast<int>(origin.x);
|
||||
int cellY = static_cast<int>(origin.y);
|
||||
osg::Vec2f origin = chunkCenter - osg::Vec2f(chunkSize/2.f, chunkSize/2.f);
|
||||
int cellX = static_cast<int>(origin.x());
|
||||
int cellY = static_cast<int>(origin.y());
|
||||
|
||||
// Save the used texture indices so we know the total number of textures
|
||||
// and number of required blend maps
|
||||
|
@ -353,11 +339,11 @@ namespace ESMTerrain
|
|||
|
||||
for (int i=0; i<numBlendmaps; ++i)
|
||||
{
|
||||
Ogre::PixelFormat format = pack ? Ogre::PF_A8B8G8R8 : Ogre::PF_A8;
|
||||
GLenum format = pack ? GL_RGBA : GL_ALPHA;
|
||||
|
||||
Ogre::uchar* pData =
|
||||
OGRE_ALLOC_T(Ogre::uchar, blendmapSize*blendmapSize*channels, Ogre::MEMCATEGORY_GENERAL);
|
||||
memset(pData, 0, blendmapSize*blendmapSize*channels);
|
||||
osg::ref_ptr<osg::Image> image (new osg::Image);
|
||||
image->allocateImage(blendmapSize, blendmapSize, 1, format, GL_UNSIGNED_BYTE);
|
||||
unsigned char* pData = image->data();
|
||||
|
||||
for (int y=0; y<blendmapSize; ++y)
|
||||
{
|
||||
|
@ -374,14 +360,15 @@ namespace ESMTerrain
|
|||
pData[y*blendmapSize*channels + x*channels + channel] = 0;
|
||||
}
|
||||
}
|
||||
blendmaps.push_back(Ogre::PixelBox(blendmapSize, blendmapSize, 1, format, pData));
|
||||
|
||||
blendmaps.push_back(image);
|
||||
}
|
||||
}
|
||||
|
||||
float Storage::getHeightAt(const Ogre::Vector3 &worldPos)
|
||||
float Storage::getHeightAt(const osg::Vec3f &worldPos)
|
||||
{
|
||||
int cellX = static_cast<int>(std::floor(worldPos.x / 8192.f));
|
||||
int cellY = static_cast<int>(std::floor(worldPos.y / 8192.f));
|
||||
int cellX = static_cast<int>(std::floor(worldPos.x() / 8192.f));
|
||||
int cellY = static_cast<int>(std::floor(worldPos.y() / 8192.f));
|
||||
|
||||
ESM::Land* land = getLand(cellX, cellY);
|
||||
if (!land || !(land->mDataTypes&ESM::Land::DATA_VHGT))
|
||||
|
@ -390,8 +377,8 @@ namespace ESMTerrain
|
|||
// Mostly lifted from Ogre::Terrain::getHeightAtTerrainPosition
|
||||
|
||||
// Normalized position in the cell
|
||||
float nX = (worldPos.x - (cellX * 8192))/8192.f;
|
||||
float nY = (worldPos.y - (cellY * 8192))/8192.f;
|
||||
float nX = (worldPos.x() - (cellX * 8192))/8192.f;
|
||||
float nY = (worldPos.y() - (cellY * 8192))/8192.f;
|
||||
|
||||
// get left / bottom points (rounded down)
|
||||
float factor = ESM::Land::LAND_SIZE - 1.0f;
|
||||
|
@ -423,22 +410,23 @@ namespace ESMTerrain
|
|||
*/
|
||||
|
||||
// Build all 4 positions in normalized cell space, using point-sampled height
|
||||
Ogre::Vector3 v0 (startXTS, startYTS, getVertexHeight(land, startX, startY) / 8192.f);
|
||||
Ogre::Vector3 v1 (endXTS, startYTS, getVertexHeight(land, endX, startY) / 8192.f);
|
||||
Ogre::Vector3 v2 (endXTS, endYTS, getVertexHeight(land, endX, endY) / 8192.f);
|
||||
Ogre::Vector3 v3 (startXTS, endYTS, getVertexHeight(land, startX, endY) / 8192.f);
|
||||
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);
|
||||
// define this plane in terrain space
|
||||
Ogre::Plane plane;
|
||||
// (At the moment, all rows have the same triangle alignment)
|
||||
osg::Plane plane;
|
||||
// FIXME: deal with differing triangle alignment
|
||||
if (true)
|
||||
{
|
||||
// odd row
|
||||
bool secondTri = ((1.0 - yParam) > xParam);
|
||||
if (secondTri)
|
||||
plane.redefine(v0, v1, v3);
|
||||
plane = osg::Plane(v0, v1, v3);
|
||||
else
|
||||
plane.redefine(v1, v2, v3);
|
||||
plane = osg::Plane(v1, v2, v3);
|
||||
}
|
||||
/*
|
||||
else
|
||||
{
|
||||
// even row
|
||||
|
@ -448,11 +436,12 @@ namespace ESMTerrain
|
|||
else
|
||||
plane.redefine(v0, v1, v2);
|
||||
}
|
||||
*/
|
||||
|
||||
// Solve plane equation for z
|
||||
return (-plane.normal.x * nX
|
||||
-plane.normal.y * nY
|
||||
- plane.d) / plane.normal.z * 8192;
|
||||
return (-plane.getNormal().x() * nX
|
||||
-plane.getNormal().y() * nY
|
||||
- plane[3]) / plane.getNormal().z() * 8192;
|
||||
|
||||
}
|
||||
|
||||
|
@ -477,7 +466,7 @@ namespace ESMTerrain
|
|||
std::string texture_ = texture;
|
||||
boost::replace_last(texture_, ".", "_nh.");
|
||||
|
||||
if (Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup(texture_))
|
||||
if (mVFS->exists(texture_))
|
||||
{
|
||||
info.mNormalMap = texture_;
|
||||
info.mParallax = true;
|
||||
|
@ -486,24 +475,18 @@ namespace ESMTerrain
|
|||
{
|
||||
texture_ = texture;
|
||||
boost::replace_last(texture_, ".", "_n.");
|
||||
if (Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup(texture_))
|
||||
if (mVFS->exists(texture_))
|
||||
info.mNormalMap = texture_;
|
||||
}
|
||||
|
||||
texture_ = texture;
|
||||
boost::replace_last(texture_, ".", "_diffusespec.");
|
||||
if (Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup(texture_))
|
||||
if (mVFS->exists(texture_))
|
||||
{
|
||||
info.mDiffuseMap = texture_;
|
||||
info.mSpecular = true;
|
||||
}
|
||||
|
||||
// This wasn't cached, so the textures are probably not loaded either.
|
||||
// Background load them so they are hopefully already loaded once we need them!
|
||||
Ogre::ResourceBackgroundQueue::getSingleton().load("Texture", info.mDiffuseMap, "General");
|
||||
if (!info.mNormalMap.empty())
|
||||
Ogre::ResourceBackgroundQueue::getSingleton().load("Texture", info.mNormalMap, "General");
|
||||
|
||||
mLayerInfoMap[texture] = info;
|
||||
|
||||
return info;
|
||||
|
|
|
@ -6,6 +6,11 @@
|
|||
#include <components/esm/loadland.hpp>
|
||||
#include <components/esm/loadltex.hpp>
|
||||
|
||||
namespace VFS
|
||||
{
|
||||
class Manager;
|
||||
}
|
||||
|
||||
namespace ESMTerrain
|
||||
{
|
||||
|
||||
|
@ -20,6 +25,7 @@ namespace ESMTerrain
|
|||
virtual const ESM::LandTexture* getLandTexture(int index, short plugin) = 0;
|
||||
|
||||
public:
|
||||
Storage(const VFS::Manager* vfs);
|
||||
|
||||
// Not implemented in this class, because we need different Store implementations for game and editor
|
||||
/// Get bounds of the whole terrain in cell units
|
||||
|
@ -33,11 +39,10 @@ namespace ESMTerrain
|
|||
/// @param min min height will be stored here
|
||||
/// @param max max height will be stored here
|
||||
/// @return true if there was data available for this terrain chunk
|
||||
virtual bool getMinMaxHeights (float size, const Ogre::Vector2& center, float& min, float& max);
|
||||
virtual bool getMinMaxHeights (float size, const osg::Vec2f& center, float& min, float& max);
|
||||
|
||||
/// Fill vertex buffers for a terrain chunk.
|
||||
/// @note May be called from background threads. Make sure to only call thread-safe functions from here!
|
||||
/// @note returned colors need to be in render-system specific format! Use RenderSystem::convertColourValue.
|
||||
/// @note Vertices should be written in row-major order (a row is defined as parallel to the x-axis).
|
||||
/// The specified positions should be in local space, i.e. relative to the center of the terrain chunk.
|
||||
/// @param lodLevel LOD level, 0 = most detailed
|
||||
|
@ -46,10 +51,10 @@ namespace ESMTerrain
|
|||
/// @param positions buffer to write vertices
|
||||
/// @param normals buffer to write vertex normals
|
||||
/// @param colours buffer to write vertex colours
|
||||
virtual void fillVertexBuffers (int lodLevel, float size, const Ogre::Vector2& center, Terrain::Alignment align,
|
||||
std::vector<float>& positions,
|
||||
std::vector<float>& normals,
|
||||
std::vector<Ogre::uint8>& colours);
|
||||
virtual void fillVertexBuffers (int lodLevel, float size, const osg::Vec2f& center,
|
||||
osg::ref_ptr<osg::Vec3Array> positions,
|
||||
osg::ref_ptr<osg::Vec3Array> normals,
|
||||
osg::ref_ptr<osg::Vec4Array> colours);
|
||||
|
||||
/// Create textures holding layer blend values for a terrain chunk.
|
||||
/// @note The terrain chunk shouldn't be larger than one cell since otherwise we might
|
||||
|
@ -62,23 +67,11 @@ namespace ESMTerrain
|
|||
/// can utilize packing, FFP can't.
|
||||
/// @param blendmaps created blendmaps will be written here
|
||||
/// @param layerList names of the layer textures used will be written here
|
||||
virtual void getBlendmaps (float chunkSize, const Ogre::Vector2& chunkCenter, bool pack,
|
||||
std::vector<Ogre::PixelBox>& blendmaps,
|
||||
virtual void getBlendmaps (float chunkSize, const osg::Vec2f& chunkCenter, bool pack,
|
||||
ImageVector& blendmaps,
|
||||
std::vector<Terrain::LayerInfo>& layerList);
|
||||
|
||||
/// Retrieve pixel data for textures holding layer blend values for terrain chunks and layer texture information.
|
||||
/// This variant is provided to eliminate the overhead of virtual function calls when retrieving a large number of blendmaps at once.
|
||||
/// @note The terrain chunks shouldn't be larger than one cell since otherwise we might
|
||||
/// have to do a ridiculous amount of different layers. For larger chunks, composite maps should be used.
|
||||
/// @note May be called from background threads.
|
||||
/// @param nodes A collection of nodes for which to retrieve the aforementioned data
|
||||
/// @param out Output vector
|
||||
/// @param pack Whether to pack blend values for up to 4 layers into one texture (one in each channel) -
|
||||
/// otherwise, each texture contains blend values for one layer only. Shader-based rendering
|
||||
/// can utilize packing, FFP can't.
|
||||
virtual void getBlendmaps (const std::vector<Terrain::QuadTreeNode*>& nodes, std::vector<Terrain::LayerCollection>& out, bool pack);
|
||||
|
||||
virtual float getHeightAt (const Ogre::Vector3& worldPos);
|
||||
virtual float getHeightAt (const osg::Vec3f& worldPos);
|
||||
|
||||
virtual Terrain::LayerInfo getDefaultLayer();
|
||||
|
||||
|
@ -89,9 +82,11 @@ namespace ESMTerrain
|
|||
virtual int getCellVertices();
|
||||
|
||||
private:
|
||||
void fixNormal (Ogre::Vector3& normal, int cellX, int cellY, int col, int row);
|
||||
void fixColour (Ogre::ColourValue& colour, int cellX, int cellY, int col, int row);
|
||||
void averageNormal (Ogre::Vector3& normal, int cellX, int cellY, int col, int row);
|
||||
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);
|
||||
|
||||
float getVertexHeight (const ESM::Land* land, int x, int y);
|
||||
|
||||
|
@ -107,11 +102,6 @@ namespace ESMTerrain
|
|||
std::map<std::string, Terrain::LayerInfo> mLayerInfoMap;
|
||||
|
||||
Terrain::LayerInfo getLayerInfo(const std::string& texture);
|
||||
|
||||
// Non-virtual
|
||||
void getBlendmapsImpl (float chunkSize, const Ogre::Vector2& chunkCenter, bool pack,
|
||||
std::vector<Ogre::PixelBox>& blendmaps,
|
||||
std::vector<Terrain::LayerInfo>& layerList);
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -21,15 +21,17 @@
|
|||
*/
|
||||
#include "buffercache.hpp"
|
||||
|
||||
#include <OgreHardwareBufferManager.h>
|
||||
#include <cassert>
|
||||
|
||||
#include <osg/PrimitiveSet>
|
||||
|
||||
#include "defs.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
template <typename IndexType>
|
||||
Ogre::HardwareIndexBufferSharedPtr createIndexBuffer(unsigned int flags, unsigned int verts, Ogre::HardwareIndexBuffer::IndexType type)
|
||||
template <typename IndexArrayType>
|
||||
osg::ref_ptr<IndexArrayType> createIndexBuffer(unsigned int flags, unsigned int verts)
|
||||
{
|
||||
// LOD level n means every 2^n-th vertex is kept
|
||||
size_t lodLevel = (flags >> (4*4));
|
||||
|
@ -42,8 +44,9 @@ Ogre::HardwareIndexBufferSharedPtr createIndexBuffer(unsigned int flags, unsigne
|
|||
|
||||
size_t increment = 1 << lodLevel;
|
||||
assert(increment < verts);
|
||||
std::vector<IndexType> indices;
|
||||
indices.reserve((verts-1)*(verts-1)*2*3 / increment);
|
||||
|
||||
osg::ref_ptr<IndexArrayType> indices (new IndexArrayType(osg::PrimitiveSet::TRIANGLES));
|
||||
indices->reserve((verts-1)*(verts-1)*2*3 / increment);
|
||||
|
||||
size_t rowStart = 0, colStart = 0, rowEnd = verts-1, colEnd = verts-1;
|
||||
// If any edge needs stitching we'll skip all edges at this point,
|
||||
|
@ -62,23 +65,23 @@ Ogre::HardwareIndexBufferSharedPtr createIndexBuffer(unsigned int flags, unsigne
|
|||
// diamond pattern
|
||||
if ((row + col%2) % 2 == 1)
|
||||
{
|
||||
indices.push_back(verts*(col+increment)+row);
|
||||
indices.push_back(verts*(col+increment)+row+increment);
|
||||
indices.push_back(verts*col+row+increment);
|
||||
indices->push_back(verts*(col+increment)+row);
|
||||
indices->push_back(verts*(col+increment)+row+increment);
|
||||
indices->push_back(verts*col+row+increment);
|
||||
|
||||
indices.push_back(verts*col+row);
|
||||
indices.push_back(verts*(col+increment)+row);
|
||||
indices.push_back(verts*(col)+row+increment);
|
||||
indices->push_back(verts*col+row);
|
||||
indices->push_back(verts*(col+increment)+row);
|
||||
indices->push_back(verts*(col)+row+increment);
|
||||
}
|
||||
else
|
||||
{
|
||||
indices.push_back(verts*col+row);
|
||||
indices.push_back(verts*(col+increment)+row+increment);
|
||||
indices.push_back(verts*col+row+increment);
|
||||
indices->push_back(verts*col+row);
|
||||
indices->push_back(verts*(col+increment)+row+increment);
|
||||
indices->push_back(verts*col+row+increment);
|
||||
|
||||
indices.push_back(verts*col+row);
|
||||
indices.push_back(verts*(col+increment)+row);
|
||||
indices.push_back(verts*(col+increment)+row+increment);
|
||||
indices->push_back(verts*col+row);
|
||||
indices->push_back(verts*(col+increment)+row);
|
||||
indices->push_back(verts*(col+increment)+row+increment);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -94,22 +97,22 @@ Ogre::HardwareIndexBufferSharedPtr createIndexBuffer(unsigned int flags, unsigne
|
|||
size_t outerStep = 1 << (lodDeltas[Terrain::South] + lodLevel);
|
||||
for (size_t col = 0; col < verts-1; col += outerStep)
|
||||
{
|
||||
indices.push_back(verts*col+row);
|
||||
indices.push_back(verts*(col+outerStep)+row);
|
||||
indices->push_back(verts*col+row);
|
||||
indices->push_back(verts*(col+outerStep)+row);
|
||||
// Make sure not to touch the right edge
|
||||
if (col+outerStep == verts-1)
|
||||
indices.push_back(verts*(col+outerStep-innerStep)+row+innerStep);
|
||||
indices->push_back(verts*(col+outerStep-innerStep)+row+innerStep);
|
||||
else
|
||||
indices.push_back(verts*(col+outerStep)+row+innerStep);
|
||||
indices->push_back(verts*(col+outerStep)+row+innerStep);
|
||||
|
||||
for (size_t i = 0; i < outerStep; i += innerStep)
|
||||
{
|
||||
// Make sure not to touch the left or right edges
|
||||
if (col+i == 0 || col+i == verts-1-innerStep)
|
||||
continue;
|
||||
indices.push_back(verts*(col)+row);
|
||||
indices.push_back(verts*(col+i+innerStep)+row+innerStep);
|
||||
indices.push_back(verts*(col+i)+row+innerStep);
|
||||
indices->push_back(verts*(col)+row);
|
||||
indices->push_back(verts*(col+i+innerStep)+row+innerStep);
|
||||
indices->push_back(verts*(col+i)+row+innerStep);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -118,22 +121,22 @@ Ogre::HardwareIndexBufferSharedPtr createIndexBuffer(unsigned int flags, unsigne
|
|||
outerStep = size_t(1) << (lodDeltas[Terrain::North] + lodLevel);
|
||||
for (size_t col = 0; col < verts-1; col += outerStep)
|
||||
{
|
||||
indices.push_back(verts*(col+outerStep)+row);
|
||||
indices.push_back(verts*col+row);
|
||||
indices->push_back(verts*(col+outerStep)+row);
|
||||
indices->push_back(verts*col+row);
|
||||
// Make sure not to touch the left edge
|
||||
if (col == 0)
|
||||
indices.push_back(verts*(col+innerStep)+row-innerStep);
|
||||
indices->push_back(verts*(col+innerStep)+row-innerStep);
|
||||
else
|
||||
indices.push_back(verts*col+row-innerStep);
|
||||
indices->push_back(verts*col+row-innerStep);
|
||||
|
||||
for (size_t i = 0; i < outerStep; i += innerStep)
|
||||
{
|
||||
// Make sure not to touch the left or right edges
|
||||
if (col+i == 0 || col+i == verts-1-innerStep)
|
||||
continue;
|
||||
indices.push_back(verts*(col+i)+row-innerStep);
|
||||
indices.push_back(verts*(col+i+innerStep)+row-innerStep);
|
||||
indices.push_back(verts*(col+outerStep)+row);
|
||||
indices->push_back(verts*(col+i)+row-innerStep);
|
||||
indices->push_back(verts*(col+i+innerStep)+row-innerStep);
|
||||
indices->push_back(verts*(col+outerStep)+row);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -142,22 +145,22 @@ Ogre::HardwareIndexBufferSharedPtr createIndexBuffer(unsigned int flags, unsigne
|
|||
outerStep = size_t(1) << (lodDeltas[Terrain::West] + lodLevel);
|
||||
for (size_t row = 0; row < verts-1; row += outerStep)
|
||||
{
|
||||
indices.push_back(verts*col+row+outerStep);
|
||||
indices.push_back(verts*col+row);
|
||||
indices->push_back(verts*col+row+outerStep);
|
||||
indices->push_back(verts*col+row);
|
||||
// Make sure not to touch the top edge
|
||||
if (row+outerStep == verts-1)
|
||||
indices.push_back(verts*(col+innerStep)+row+outerStep-innerStep);
|
||||
indices->push_back(verts*(col+innerStep)+row+outerStep-innerStep);
|
||||
else
|
||||
indices.push_back(verts*(col+innerStep)+row+outerStep);
|
||||
indices->push_back(verts*(col+innerStep)+row+outerStep);
|
||||
|
||||
for (size_t i = 0; i < outerStep; i += innerStep)
|
||||
{
|
||||
// Make sure not to touch the top or bottom edges
|
||||
if (row+i == 0 || row+i == verts-1-innerStep)
|
||||
continue;
|
||||
indices.push_back(verts*col+row);
|
||||
indices.push_back(verts*(col+innerStep)+row+i);
|
||||
indices.push_back(verts*(col+innerStep)+row+i+innerStep);
|
||||
indices->push_back(verts*col+row);
|
||||
indices->push_back(verts*(col+innerStep)+row+i);
|
||||
indices->push_back(verts*(col+innerStep)+row+i+innerStep);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -166,31 +169,27 @@ Ogre::HardwareIndexBufferSharedPtr createIndexBuffer(unsigned int flags, unsigne
|
|||
outerStep = size_t(1) << (lodDeltas[Terrain::East] + lodLevel);
|
||||
for (size_t row = 0; row < verts-1; row += outerStep)
|
||||
{
|
||||
indices.push_back(verts*col+row);
|
||||
indices.push_back(verts*col+row+outerStep);
|
||||
indices->push_back(verts*col+row);
|
||||
indices->push_back(verts*col+row+outerStep);
|
||||
// Make sure not to touch the bottom edge
|
||||
if (row == 0)
|
||||
indices.push_back(verts*(col-innerStep)+row+innerStep);
|
||||
indices->push_back(verts*(col-innerStep)+row+innerStep);
|
||||
else
|
||||
indices.push_back(verts*(col-innerStep)+row);
|
||||
indices->push_back(verts*(col-innerStep)+row);
|
||||
|
||||
for (size_t i = 0; i < outerStep; i += innerStep)
|
||||
{
|
||||
// Make sure not to touch the top or bottom edges
|
||||
if (row+i == 0 || row+i == verts-1-innerStep)
|
||||
continue;
|
||||
indices.push_back(verts*col+row+outerStep);
|
||||
indices.push_back(verts*(col-innerStep)+row+i+innerStep);
|
||||
indices.push_back(verts*(col-innerStep)+row+i);
|
||||
indices->push_back(verts*col+row+outerStep);
|
||||
indices->push_back(verts*(col-innerStep)+row+i+innerStep);
|
||||
indices->push_back(verts*(col-innerStep)+row+i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ogre::HardwareBufferManager* mgr = Ogre::HardwareBufferManager::getSingletonPtr();
|
||||
Ogre::HardwareIndexBufferSharedPtr buffer = mgr->createIndexBuffer(type,
|
||||
indices.size(), Ogre::HardwareBuffer::HBU_STATIC);
|
||||
buffer->writeData(0, buffer->getSizeInBytes(), &indices[0], true);
|
||||
return buffer;
|
||||
return indices;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -198,7 +197,7 @@ Ogre::HardwareIndexBufferSharedPtr createIndexBuffer(unsigned int flags, unsigne
|
|||
namespace Terrain
|
||||
{
|
||||
|
||||
Ogre::HardwareVertexBufferSharedPtr BufferCache::getUVBuffer()
|
||||
osg::ref_ptr<osg::Vec2Array> BufferCache::getUVBuffer()
|
||||
{
|
||||
if (mUvBufferMap.find(mNumVerts) != mUvBufferMap.end())
|
||||
{
|
||||
|
@ -207,30 +206,23 @@ namespace Terrain
|
|||
|
||||
int vertexCount = mNumVerts * mNumVerts;
|
||||
|
||||
std::vector<float> uvs;
|
||||
uvs.reserve(vertexCount*2);
|
||||
osg::ref_ptr<osg::Vec2Array> uvs (new osg::Vec2Array);
|
||||
uvs->reserve(vertexCount);
|
||||
|
||||
for (unsigned int col = 0; col < mNumVerts; ++col)
|
||||
{
|
||||
for (unsigned int row = 0; row < mNumVerts; ++row)
|
||||
{
|
||||
uvs.push_back(col / static_cast<float>(mNumVerts-1)); // U
|
||||
uvs.push_back(row / static_cast<float>(mNumVerts-1)); // V
|
||||
uvs->push_back(osg::Vec2f(col / static_cast<float>(mNumVerts-1),
|
||||
row / static_cast<float>(mNumVerts-1)));
|
||||
}
|
||||
}
|
||||
|
||||
Ogre::HardwareBufferManager* mgr = Ogre::HardwareBufferManager::getSingletonPtr();
|
||||
Ogre::HardwareVertexBufferSharedPtr buffer = mgr->createVertexBuffer(
|
||||
Ogre::VertexElement::getTypeSize(Ogre::VET_FLOAT2),
|
||||
vertexCount, Ogre::HardwareBuffer::HBU_STATIC);
|
||||
|
||||
buffer->writeData(0, buffer->getSizeInBytes(), &uvs[0], true);
|
||||
|
||||
mUvBufferMap[mNumVerts] = buffer;
|
||||
return buffer;
|
||||
mUvBufferMap[mNumVerts] = uvs;
|
||||
return uvs;
|
||||
}
|
||||
|
||||
Ogre::HardwareIndexBufferSharedPtr BufferCache::getIndexBuffer(unsigned int flags)
|
||||
osg::ref_ptr<osg::DrawElements> BufferCache::getIndexBuffer(unsigned int flags)
|
||||
{
|
||||
unsigned int verts = mNumVerts;
|
||||
|
||||
|
@ -239,11 +231,12 @@ namespace Terrain
|
|||
return mIndexBufferMap[flags];
|
||||
}
|
||||
|
||||
Ogre::HardwareIndexBufferSharedPtr buffer;
|
||||
osg::ref_ptr<osg::DrawElements> buffer;
|
||||
|
||||
if (verts*verts > (0xffffu))
|
||||
buffer = createIndexBuffer<unsigned int>(flags, verts, Ogre::HardwareIndexBuffer::IT_32BIT);
|
||||
buffer = createIndexBuffer<osg::DrawElementsUShort>(flags, verts);
|
||||
else
|
||||
buffer = createIndexBuffer<unsigned short>(flags, verts, Ogre::HardwareIndexBuffer::IT_16BIT);
|
||||
buffer = createIndexBuffer<osg::DrawElementsUInt>(flags, verts);
|
||||
|
||||
mIndexBufferMap[flags] = buffer;
|
||||
return buffer;
|
||||
|
|
|
@ -22,8 +22,8 @@
|
|||
#ifndef COMPONENTS_TERRAIN_BUFFERCACHE_H
|
||||
#define COMPONENTS_TERRAIN_BUFFERCACHE_H
|
||||
|
||||
#include <OgreHardwareIndexBuffer.h>
|
||||
#include <OgreHardwareVertexBuffer.h>
|
||||
#include <osg/ref_ptr>
|
||||
#include <osg/Array>
|
||||
|
||||
#include <map>
|
||||
|
||||
|
@ -38,16 +38,18 @@ namespace Terrain
|
|||
|
||||
/// @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)
|
||||
Ogre::HardwareIndexBufferSharedPtr getIndexBuffer (unsigned int flags);
|
||||
osg::ref_ptr<osg::DrawElements> getIndexBuffer (unsigned int flags);
|
||||
|
||||
Ogre::HardwareVertexBufferSharedPtr getUVBuffer ();
|
||||
osg::ref_ptr<osg::Vec2Array> getUVBuffer();
|
||||
|
||||
// TODO: add releaseGLObjects() for our vertex/element buffer objects
|
||||
|
||||
private:
|
||||
// Index buffers are shared across terrain batches where possible. There is one index buffer for each
|
||||
// combination of LOD deltas and index buffer LOD we may need.
|
||||
std::map<int, Ogre::HardwareIndexBufferSharedPtr> mIndexBufferMap;
|
||||
std::map<int, osg::ref_ptr<osg::DrawElements> > mIndexBufferMap;
|
||||
|
||||
std::map<int, Ogre::HardwareVertexBufferSharedPtr> mUvBufferMap;
|
||||
std::map<int, osg::ref_ptr<osg::Vec2Array> > mUvBufferMap;
|
||||
|
||||
unsigned int mNumVerts;
|
||||
};
|
||||
|
|
|
@ -1,169 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2015 scrawl <scrawl@baseoftrash.de>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
#include "chunk.hpp"
|
||||
|
||||
#include <OgreSceneNode.h>
|
||||
#include <OgreHardwareBufferManager.h>
|
||||
#include <OgreRenderQueue.h>
|
||||
#include <OgreMaterialManager.h>
|
||||
#include <OgreStringConverter.h>
|
||||
|
||||
namespace Terrain
|
||||
{
|
||||
|
||||
Chunk::Chunk(Ogre::HardwareVertexBufferSharedPtr uvBuffer, const Ogre::AxisAlignedBox& bounds,
|
||||
const std::vector<float>& positions, const std::vector<float>& normals, const std::vector<Ogre::uint8>& colours)
|
||||
: mBounds(bounds)
|
||||
, mOwnMaterial(false)
|
||||
{
|
||||
mVertexData = OGRE_NEW Ogre::VertexData;
|
||||
mVertexData->vertexStart = 0;
|
||||
mVertexData->vertexCount = positions.size()/3;
|
||||
|
||||
// Set up the vertex declaration, which specifies the info for each vertex (normals, colors, UVs, etc)
|
||||
Ogre::VertexDeclaration* vertexDecl = mVertexData->vertexDeclaration;
|
||||
|
||||
Ogre::HardwareBufferManager* mgr = Ogre::HardwareBufferManager::getSingletonPtr();
|
||||
size_t nextBuffer = 0;
|
||||
|
||||
// Positions
|
||||
vertexDecl->addElement(nextBuffer++, 0, Ogre::VET_FLOAT3, Ogre::VES_POSITION);
|
||||
Ogre::HardwareVertexBufferSharedPtr vertexBuffer = mgr->createVertexBuffer(Ogre::VertexElement::getTypeSize(Ogre::VET_FLOAT3),
|
||||
mVertexData->vertexCount, Ogre::HardwareBuffer::HBU_STATIC);
|
||||
|
||||
// Normals
|
||||
vertexDecl->addElement(nextBuffer++, 0, Ogre::VET_FLOAT3, Ogre::VES_NORMAL);
|
||||
Ogre::HardwareVertexBufferSharedPtr normalBuffer = mgr->createVertexBuffer(Ogre::VertexElement::getTypeSize(Ogre::VET_FLOAT3),
|
||||
mVertexData->vertexCount, Ogre::HardwareBuffer::HBU_STATIC);
|
||||
|
||||
|
||||
// UV texture coordinates
|
||||
vertexDecl->addElement(nextBuffer++, 0, Ogre::VET_FLOAT2,
|
||||
Ogre::VES_TEXTURE_COORDINATES, 0);
|
||||
|
||||
// Colours
|
||||
vertexDecl->addElement(nextBuffer++, 0, Ogre::VET_COLOUR, Ogre::VES_DIFFUSE);
|
||||
Ogre::HardwareVertexBufferSharedPtr colourBuffer = mgr->createVertexBuffer(Ogre::VertexElement::getTypeSize(Ogre::VET_COLOUR),
|
||||
mVertexData->vertexCount, Ogre::HardwareBuffer::HBU_STATIC);
|
||||
|
||||
vertexBuffer->writeData(0, vertexBuffer->getSizeInBytes(), &positions[0], true);
|
||||
normalBuffer->writeData(0, normalBuffer->getSizeInBytes(), &normals[0], true);
|
||||
colourBuffer->writeData(0, colourBuffer->getSizeInBytes(), &colours[0], true);
|
||||
|
||||
mVertexData->vertexBufferBinding->setBinding(0, vertexBuffer);
|
||||
mVertexData->vertexBufferBinding->setBinding(1, normalBuffer);
|
||||
mVertexData->vertexBufferBinding->setBinding(2, uvBuffer);
|
||||
mVertexData->vertexBufferBinding->setBinding(3, colourBuffer);
|
||||
|
||||
// Assign a default material in case terrain material fails to be created
|
||||
mMaterial = Ogre::MaterialManager::getSingleton().getByName("BaseWhite");
|
||||
|
||||
mIndexData = OGRE_NEW Ogre::IndexData();
|
||||
mIndexData->indexStart = 0;
|
||||
}
|
||||
|
||||
void Chunk::setIndexBuffer(Ogre::HardwareIndexBufferSharedPtr buffer)
|
||||
{
|
||||
mIndexData->indexBuffer = buffer;
|
||||
mIndexData->indexCount = buffer->getNumIndexes();
|
||||
}
|
||||
|
||||
Chunk::~Chunk()
|
||||
{
|
||||
if (!mMaterial.isNull() && mOwnMaterial)
|
||||
{
|
||||
#if TERRAIN_USE_SHADER
|
||||
sh::Factory::getInstance().destroyMaterialInstance(mMaterial->getName());
|
||||
#endif
|
||||
Ogre::MaterialManager::getSingleton().remove(mMaterial->getName());
|
||||
}
|
||||
OGRE_DELETE mVertexData;
|
||||
OGRE_DELETE mIndexData;
|
||||
}
|
||||
|
||||
void Chunk::setMaterial(const Ogre::MaterialPtr &material, bool own)
|
||||
{
|
||||
// Clean up the previous material, if we own it
|
||||
if (!mMaterial.isNull() && mOwnMaterial)
|
||||
{
|
||||
#if TERRAIN_USE_SHADER
|
||||
sh::Factory::getInstance().destroyMaterialInstance(mMaterial->getName());
|
||||
#endif
|
||||
Ogre::MaterialManager::getSingleton().remove(mMaterial->getName());
|
||||
}
|
||||
|
||||
mMaterial = material;
|
||||
mOwnMaterial = own;
|
||||
}
|
||||
|
||||
const Ogre::AxisAlignedBox& Chunk::getBoundingBox(void) const
|
||||
{
|
||||
return mBounds;
|
||||
}
|
||||
|
||||
Ogre::Real Chunk::getBoundingRadius(void) const
|
||||
{
|
||||
return mBounds.getHalfSize().length();
|
||||
}
|
||||
|
||||
void Chunk::_updateRenderQueue(Ogre::RenderQueue* queue)
|
||||
{
|
||||
queue->addRenderable(this, mRenderQueueID);
|
||||
}
|
||||
|
||||
void Chunk::visitRenderables(Ogre::Renderable::Visitor* visitor,
|
||||
bool debugRenderables)
|
||||
{
|
||||
visitor->visit(this, 0, false);
|
||||
}
|
||||
|
||||
const Ogre::MaterialPtr& Chunk::getMaterial(void) const
|
||||
{
|
||||
return mMaterial;
|
||||
}
|
||||
|
||||
void Chunk::getRenderOperation(Ogre::RenderOperation& op)
|
||||
{
|
||||
assert (!mIndexData->indexBuffer.isNull() && "Trying to render, but no index buffer set!");
|
||||
assert(!mMaterial.isNull() && "Trying to render, but no material set!");
|
||||
op.useIndexes = true;
|
||||
op.operationType = Ogre::RenderOperation::OT_TRIANGLE_LIST;
|
||||
op.vertexData = mVertexData;
|
||||
op.indexData = mIndexData;
|
||||
}
|
||||
|
||||
void Chunk::getWorldTransforms(Ogre::Matrix4* xform) const
|
||||
{
|
||||
*xform = getParentSceneNode()->_getFullTransform();
|
||||
}
|
||||
|
||||
Ogre::Real Chunk::getSquaredViewDepth(const Ogre::Camera* cam) const
|
||||
{
|
||||
return getParentSceneNode()->getSquaredViewDepth(cam);
|
||||
}
|
||||
|
||||
const Ogre::LightList& Chunk::getLights(void) const
|
||||
{
|
||||
return queryLights();
|
||||
}
|
||||
|
||||
}
|
|
@ -1,75 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2015 scrawl <scrawl@baseoftrash.de>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
#ifndef COMPONENTS_TERRAIN_TERRAINBATCH_H
|
||||
#define COMPONENTS_TERRAIN_TERRAINBATCH_H
|
||||
|
||||
#include <OgreRenderable.h>
|
||||
#include <OgreMovableObject.h>
|
||||
|
||||
namespace Terrain
|
||||
{
|
||||
|
||||
/**
|
||||
* @brief A movable object representing a chunk of terrain.
|
||||
*/
|
||||
class Chunk : public Ogre::Renderable, public Ogre::MovableObject
|
||||
{
|
||||
public:
|
||||
Chunk (Ogre::HardwareVertexBufferSharedPtr uvBuffer, const Ogre::AxisAlignedBox& bounds,
|
||||
const std::vector<float>& positions,
|
||||
const std::vector<float>& normals,
|
||||
const std::vector<Ogre::uint8>& colours);
|
||||
|
||||
virtual ~Chunk();
|
||||
|
||||
/// @param own Should we take ownership of the material?
|
||||
void setMaterial (const Ogre::MaterialPtr& material, bool own=true);
|
||||
|
||||
void setIndexBuffer(Ogre::HardwareIndexBufferSharedPtr buffer);
|
||||
|
||||
// Inherited from MovableObject
|
||||
virtual const Ogre::String& getMovableType(void) const { static Ogre::String t = "MW_TERRAIN"; return t; }
|
||||
virtual const Ogre::AxisAlignedBox& getBoundingBox(void) const;
|
||||
virtual Ogre::Real getBoundingRadius(void) const;
|
||||
virtual void _updateRenderQueue(Ogre::RenderQueue* queue);
|
||||
virtual void visitRenderables(Renderable::Visitor* visitor,
|
||||
bool debugRenderables = false);
|
||||
|
||||
// Inherited from Renderable
|
||||
virtual const Ogre::MaterialPtr& getMaterial(void) const;
|
||||
virtual void getRenderOperation(Ogre::RenderOperation& op);
|
||||
virtual void getWorldTransforms(Ogre::Matrix4* xform) const;
|
||||
virtual Ogre::Real getSquaredViewDepth(const Ogre::Camera* cam) const;
|
||||
virtual const Ogre::LightList& getLights(void) const;
|
||||
|
||||
private:
|
||||
Ogre::AxisAlignedBox mBounds;
|
||||
Ogre::MaterialPtr mMaterial;
|
||||
bool mOwnMaterial; // Should we remove mMaterial on destruction?
|
||||
|
||||
Ogre::VertexData* mVertexData;
|
||||
Ogre::IndexData* mIndexData;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
|
@ -1,336 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2015 scrawl <scrawl@baseoftrash.de>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
#include "defaultworld.hpp"
|
||||
|
||||
#include <OgreAxisAlignedBox.h>
|
||||
#include <OgreCamera.h>
|
||||
#include <OgreHardwarePixelBuffer.h>
|
||||
#include <OgreTextureManager.h>
|
||||
#include <OgreRenderTexture.h>
|
||||
#include <OgreSceneNode.h>
|
||||
#include <OgreRoot.h>
|
||||
|
||||
#include "storage.hpp"
|
||||
#include "quadtreenode.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;
|
||||
}
|
||||
|
||||
Terrain::QuadTreeNode* findNode (const Ogre::Vector2& center, Terrain::QuadTreeNode* node)
|
||||
{
|
||||
if (center == node->getCenter())
|
||||
return node;
|
||||
|
||||
if (center.x > node->getCenter().x && center.y > node->getCenter().y)
|
||||
return findNode(center, node->getChild(Terrain::NE));
|
||||
else if (center.x > node->getCenter().x && center.y < node->getCenter().y)
|
||||
return findNode(center, node->getChild(Terrain::SE));
|
||||
else if (center.x < node->getCenter().x && center.y > node->getCenter().y)
|
||||
return findNode(center, node->getChild(Terrain::NW));
|
||||
else //if (center.x < node->getCenter().x && center.y < node->getCenter().y)
|
||||
return findNode(center, node->getChild(Terrain::SW));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace Terrain
|
||||
{
|
||||
|
||||
const Ogre::uint REQ_ID_CHUNK = 1;
|
||||
const Ogre::uint REQ_ID_LAYERS = 2;
|
||||
|
||||
DefaultWorld::DefaultWorld(Ogre::SceneManager* sceneMgr,
|
||||
Storage* storage, int visibilityFlags, bool shaders, Alignment align, float minBatchSize, float maxBatchSize)
|
||||
: World(sceneMgr, storage, visibilityFlags, shaders, align)
|
||||
, mWorkQueueChannel(0)
|
||||
, mVisible(true)
|
||||
, mChunksLoading(0)
|
||||
, mMinX(0)
|
||||
, mMaxX(0)
|
||||
, mMinY(0)
|
||||
, mMaxY(0)
|
||||
, mMinBatchSize(minBatchSize)
|
||||
, mMaxBatchSize(maxBatchSize)
|
||||
, mLayerLoadPending(true)
|
||||
{
|
||||
#if TERRAIN_USE_SHADER == 0
|
||||
if (mShaders)
|
||||
std::cerr << "Compiled Terrain without shader support, disabling..." << std::endl;
|
||||
mShaders = false;
|
||||
#endif
|
||||
|
||||
mCompositeMapSceneMgr = Ogre::Root::getSingleton().createSceneManager(Ogre::ST_GENERIC);
|
||||
|
||||
/// \todo make composite map size configurable
|
||||
Ogre::Camera* compositeMapCam = mCompositeMapSceneMgr->createCamera("a");
|
||||
mCompositeMapRenderTexture = Ogre::TextureManager::getSingleton().createManual(
|
||||
"terrain/comp/rt", Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME,
|
||||
Ogre::TEX_TYPE_2D, 128, 128, 0, Ogre::PF_A8B8G8R8, Ogre::TU_RENDERTARGET);
|
||||
mCompositeMapRenderTarget = mCompositeMapRenderTexture->getBuffer()->getRenderTarget();
|
||||
mCompositeMapRenderTarget->setAutoUpdated(false);
|
||||
mCompositeMapRenderTarget->addViewport(compositeMapCam);
|
||||
|
||||
storage->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));
|
||||
|
||||
// Adjust the center according to the new size
|
||||
float centerX = (mMinX+mMaxX)/2.f + (size-origSizeX)/2.f;
|
||||
float centerY = (mMinY+mMaxY)/2.f + (size-origSizeY)/2.f;
|
||||
|
||||
mRootSceneNode = mSceneMgr->getRootSceneNode()->createChildSceneNode();
|
||||
|
||||
// While building the quadtree, remember leaf nodes since we need to load their layers
|
||||
LayersRequestData data;
|
||||
data.mPack = getShadersEnabled();
|
||||
|
||||
mRootNode = new QuadTreeNode(this, Root, static_cast<float>(size), Ogre::Vector2(centerX, centerY), NULL);
|
||||
buildQuadTree(mRootNode, data.mNodes);
|
||||
//loadingListener->indicateProgress();
|
||||
mRootNode->initAabb();
|
||||
//loadingListener->indicateProgress();
|
||||
mRootNode->initNeighbours();
|
||||
//loadingListener->indicateProgress();
|
||||
|
||||
Ogre::WorkQueue* wq = Ogre::Root::getSingleton().getWorkQueue();
|
||||
mWorkQueueChannel = wq->getChannel("LargeTerrain");
|
||||
wq->addRequestHandler(mWorkQueueChannel, this);
|
||||
wq->addResponseHandler(mWorkQueueChannel, this);
|
||||
|
||||
// Start loading layers in the background (for leaf nodes)
|
||||
wq->addRequest(mWorkQueueChannel, REQ_ID_LAYERS, Ogre::Any(data));
|
||||
}
|
||||
|
||||
DefaultWorld::~DefaultWorld()
|
||||
{
|
||||
Ogre::WorkQueue* wq = Ogre::Root::getSingleton().getWorkQueue();
|
||||
wq->removeRequestHandler(mWorkQueueChannel, this);
|
||||
wq->removeResponseHandler(mWorkQueueChannel, this);
|
||||
|
||||
delete mRootNode;
|
||||
}
|
||||
|
||||
void DefaultWorld::buildQuadTree(QuadTreeNode *node, std::vector<QuadTreeNode*>& leafs)
|
||||
{
|
||||
float halfSize = node->getSize()/2.f;
|
||||
|
||||
if (node->getSize() <= mMinBatchSize)
|
||||
{
|
||||
// We arrived at a leaf
|
||||
float minZ,maxZ;
|
||||
Ogre::Vector2 center = node->getCenter();
|
||||
float cellWorldSize = getStorage()->getCellWorldSize();
|
||||
if (mStorage->getMinMaxHeights(static_cast<float>(node->getSize()), center, minZ, maxZ))
|
||||
{
|
||||
Ogre::AxisAlignedBox bounds(Ogre::Vector3(-halfSize*cellWorldSize, -halfSize*cellWorldSize, minZ),
|
||||
Ogre::Vector3(halfSize*cellWorldSize, halfSize*cellWorldSize, maxZ));
|
||||
convertBounds(bounds);
|
||||
node->setBoundingBox(bounds);
|
||||
leafs.push_back(node);
|
||||
}
|
||||
else
|
||||
node->markAsDummy(); // no data available for this node, skip it
|
||||
return;
|
||||
}
|
||||
|
||||
if (node->getCenter().x - halfSize > mMaxX
|
||||
|| node->getCenter().x + halfSize < mMinX
|
||||
|| node->getCenter().y - halfSize > mMaxY
|
||||
|| node->getCenter().y + halfSize < mMinY )
|
||||
// Out of bounds of the actual terrain - this will happen because
|
||||
// we rounded the size up to the next power of two
|
||||
{
|
||||
node->markAsDummy();
|
||||
return;
|
||||
}
|
||||
|
||||
// Not a leaf, create its children
|
||||
node->createChild(SW, halfSize, node->getCenter() - halfSize/2.f);
|
||||
node->createChild(SE, halfSize, node->getCenter() + Ogre::Vector2(halfSize/2.f, -halfSize/2.f));
|
||||
node->createChild(NW, halfSize, node->getCenter() + Ogre::Vector2(-halfSize/2.f, halfSize/2.f));
|
||||
node->createChild(NE, halfSize, node->getCenter() + halfSize/2.f);
|
||||
buildQuadTree(node->getChild(SW), leafs);
|
||||
buildQuadTree(node->getChild(SE), leafs);
|
||||
buildQuadTree(node->getChild(NW), leafs);
|
||||
buildQuadTree(node->getChild(NE), leafs);
|
||||
|
||||
// if all children are dummy, we are also dummy
|
||||
for (int i=0; i<4; ++i)
|
||||
{
|
||||
if (!node->getChild((ChildDirection)i)->isDummy())
|
||||
return;
|
||||
}
|
||||
node->markAsDummy();
|
||||
}
|
||||
|
||||
void DefaultWorld::update(const Ogre::Vector3& cameraPos)
|
||||
{
|
||||
if (!mVisible)
|
||||
return;
|
||||
mRootNode->update(cameraPos);
|
||||
mRootNode->updateIndexBuffers();
|
||||
}
|
||||
|
||||
Ogre::AxisAlignedBox DefaultWorld::getWorldBoundingBox (const Ogre::Vector2& center)
|
||||
{
|
||||
if (center.x > mMaxX
|
||||
|| center.x < mMinX
|
||||
|| center.y > mMaxY
|
||||
|| center.y < mMinY)
|
||||
return Ogre::AxisAlignedBox::BOX_NULL;
|
||||
QuadTreeNode* node = findNode(center, mRootNode);
|
||||
return node->getWorldBoundingBox();
|
||||
}
|
||||
|
||||
void DefaultWorld::renderCompositeMap(Ogre::TexturePtr target)
|
||||
{
|
||||
mCompositeMapRenderTarget->update();
|
||||
target->getBuffer()->blit(mCompositeMapRenderTexture->getBuffer());
|
||||
}
|
||||
|
||||
void DefaultWorld::clearCompositeMapSceneManager()
|
||||
{
|
||||
mCompositeMapSceneMgr->destroyAllManualObjects();
|
||||
mCompositeMapSceneMgr->clearScene();
|
||||
}
|
||||
|
||||
void DefaultWorld::applyMaterials(bool shadows, bool splitShadows)
|
||||
{
|
||||
mShadows = shadows;
|
||||
mSplitShadows = splitShadows;
|
||||
mRootNode->applyMaterials();
|
||||
}
|
||||
|
||||
void DefaultWorld::setVisible(bool visible)
|
||||
{
|
||||
if (visible && !mVisible)
|
||||
mSceneMgr->getRootSceneNode()->addChild(mRootSceneNode);
|
||||
else if (!visible && mVisible)
|
||||
mSceneMgr->getRootSceneNode()->removeChild(mRootSceneNode);
|
||||
|
||||
mVisible = visible;
|
||||
}
|
||||
|
||||
bool DefaultWorld::getVisible()
|
||||
{
|
||||
return mVisible;
|
||||
}
|
||||
|
||||
void DefaultWorld::syncLoad()
|
||||
{
|
||||
while (mChunksLoading || mLayerLoadPending)
|
||||
{
|
||||
OGRE_THREAD_SLEEP(0);
|
||||
Ogre::Root::getSingleton().getWorkQueue()->processResponses();
|
||||
}
|
||||
}
|
||||
|
||||
Ogre::WorkQueue::Response* DefaultWorld::handleRequest(const Ogre::WorkQueue::Request *req, const Ogre::WorkQueue *srcQ)
|
||||
{
|
||||
if (req->getType() == REQ_ID_CHUNK)
|
||||
{
|
||||
const LoadRequestData data = Ogre::any_cast<LoadRequestData>(req->getData());
|
||||
|
||||
QuadTreeNode* node = data.mNode;
|
||||
|
||||
LoadResponseData* responseData = new LoadResponseData();
|
||||
|
||||
getStorage()->fillVertexBuffers(node->getNativeLodLevel(), static_cast<float>(node->getSize()), node->getCenter(), getAlign(),
|
||||
responseData->mPositions, responseData->mNormals, responseData->mColours);
|
||||
|
||||
return OGRE_NEW Ogre::WorkQueue::Response(req, true, Ogre::Any(responseData));
|
||||
}
|
||||
else // REQ_ID_LAYERS
|
||||
{
|
||||
const LayersRequestData data = Ogre::any_cast<LayersRequestData>(req->getData());
|
||||
|
||||
LayersResponseData* responseData = new LayersResponseData();
|
||||
|
||||
getStorage()->getBlendmaps(data.mNodes, responseData->mLayerCollections, data.mPack);
|
||||
|
||||
return OGRE_NEW Ogre::WorkQueue::Response(req, true, Ogre::Any(responseData));
|
||||
}
|
||||
}
|
||||
|
||||
void DefaultWorld::handleResponse(const Ogre::WorkQueue::Response *res, const Ogre::WorkQueue *srcQ)
|
||||
{
|
||||
assert(res->succeeded() && "Response failure not handled");
|
||||
|
||||
if (res->getRequest()->getType() == REQ_ID_CHUNK)
|
||||
{
|
||||
LoadResponseData* data = Ogre::any_cast<LoadResponseData*>(res->getData());
|
||||
|
||||
const LoadRequestData requestData = Ogre::any_cast<LoadRequestData>(res->getRequest()->getData());
|
||||
|
||||
requestData.mNode->load(*data);
|
||||
|
||||
delete data;
|
||||
|
||||
--mChunksLoading;
|
||||
}
|
||||
else // REQ_ID_LAYERS
|
||||
{
|
||||
LayersResponseData* data = Ogre::any_cast<LayersResponseData*>(res->getData());
|
||||
|
||||
for (std::vector<LayerCollection>::iterator it = data->mLayerCollections.begin(); it != data->mLayerCollections.end(); ++it)
|
||||
{
|
||||
it->mTarget->loadLayers(*it);
|
||||
}
|
||||
|
||||
delete data;
|
||||
|
||||
mRootNode->loadMaterials();
|
||||
|
||||
mLayerLoadPending = false;
|
||||
}
|
||||
}
|
||||
|
||||
void DefaultWorld::queueLoad(QuadTreeNode *node)
|
||||
{
|
||||
LoadRequestData data;
|
||||
data.mNode = node;
|
||||
|
||||
Ogre::Root::getSingleton().getWorkQueue()->addRequest(mWorkQueueChannel, REQ_ID_CHUNK, Ogre::Any(data));
|
||||
++mChunksLoading;
|
||||
}
|
||||
}
|
|
@ -1,177 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2015 scrawl <scrawl@baseoftrash.de>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
#ifndef COMPONENTS_TERRAIN_H
|
||||
#define COMPONENTS_TERRAIN_H
|
||||
|
||||
#include <OgreAxisAlignedBox.h>
|
||||
#include <OgreTexture.h>
|
||||
#include <OgreWorkQueue.h>
|
||||
|
||||
#include "world.hpp"
|
||||
|
||||
namespace Ogre
|
||||
{
|
||||
class Camera;
|
||||
}
|
||||
|
||||
namespace Terrain
|
||||
{
|
||||
|
||||
class QuadTreeNode;
|
||||
class Storage;
|
||||
|
||||
/**
|
||||
* @brief A quadtree-based terrain implementation suitable for large data sets. \n
|
||||
* Near cells are rendered with alpha splatting, distant cells are merged
|
||||
* together in batches and have their layers pre-rendered onto a composite map. \n
|
||||
* Cracks at LOD transitions are avoided using stitching.
|
||||
* @note Multiple cameras are not supported yet
|
||||
*/
|
||||
class DefaultWorld : public World, public Ogre::WorkQueue::RequestHandler, public Ogre::WorkQueue::ResponseHandler
|
||||
{
|
||||
public:
|
||||
/// @note takes ownership of \a storage
|
||||
/// @param sceneMgr scene manager to use
|
||||
/// @param storage Storage instance to get terrain data from (heights, normals, colors, textures..)
|
||||
/// @param visbilityFlags visibility flags for the created meshes
|
||||
/// @param shaders Whether to use splatting shader, or multi-pass fixed function splatting. Shader is usually
|
||||
/// faster so this is just here for compatibility.
|
||||
/// @param align The align of the terrain, see Alignment enum
|
||||
/// @param minBatchSize Minimum size of a terrain batch along one side (in cell units). Used for building the quad tree.
|
||||
/// @param maxBatchSize Maximum size of a terrain batch along one side (in cell units). Used when traversing the quad tree.
|
||||
DefaultWorld(Ogre::SceneManager* sceneMgr,
|
||||
Storage* storage, int visibilityFlags, bool shaders, Alignment align, float minBatchSize, float maxBatchSize);
|
||||
~DefaultWorld();
|
||||
|
||||
/// Update chunk LODs according to this camera position
|
||||
/// @note Calling this method might lead to composite textures being rendered, so it is best
|
||||
/// not to call it when render commands are still queued, since that would cause a flush.
|
||||
virtual void update (const Ogre::Vector3& cameraPos);
|
||||
|
||||
/// Get the world bounding box of a chunk of terrain centered at \a center
|
||||
virtual Ogre::AxisAlignedBox getWorldBoundingBox (const Ogre::Vector2& center);
|
||||
|
||||
Ogre::SceneNode* getRootSceneNode() { return mRootSceneNode; }
|
||||
|
||||
/// Show or hide the whole terrain
|
||||
/// @note this setting will be invalidated once you call Terrain::update, so do not call it while the terrain should be hidden
|
||||
virtual void setVisible(bool visible);
|
||||
virtual bool getVisible();
|
||||
|
||||
/// Recreate materials used by terrain chunks. This should be called whenever settings of
|
||||
/// the material factory are changed. (Relying on the factory to update those materials is not
|
||||
/// enough, since turning a feature on/off can change the number of texture units available for layer/blend
|
||||
/// textures, and to properly respond to this we may need to change the structure of the material, such as
|
||||
/// adding or removing passes. This can only be achieved by a full rebuild.)
|
||||
virtual void applyMaterials(bool shadows, bool splitShadows);
|
||||
|
||||
int getMaxBatchSize() { return static_cast<int>(mMaxBatchSize); }
|
||||
|
||||
/// Wait until all background loading is complete.
|
||||
void syncLoad();
|
||||
|
||||
private:
|
||||
// Called from a background worker thread
|
||||
virtual Ogre::WorkQueue::Response* handleRequest(const Ogre::WorkQueue::Request* req, const Ogre::WorkQueue* srcQ);
|
||||
// Called from the main thread
|
||||
virtual void handleResponse(const Ogre::WorkQueue::Response* res, const Ogre::WorkQueue* srcQ);
|
||||
Ogre::uint16 mWorkQueueChannel;
|
||||
|
||||
bool mVisible;
|
||||
|
||||
QuadTreeNode* mRootNode;
|
||||
Ogre::SceneNode* mRootSceneNode;
|
||||
|
||||
/// The number of chunks currently loading in a background thread. If 0, we have finished loading!
|
||||
int mChunksLoading;
|
||||
|
||||
Ogre::SceneManager* mCompositeMapSceneMgr;
|
||||
|
||||
/// Bounds in cell units
|
||||
float mMinX, mMaxX, mMinY, mMaxY;
|
||||
|
||||
/// Minimum size of a terrain batch along one side (in cell units)
|
||||
float mMinBatchSize;
|
||||
/// Maximum size of a terrain batch along one side (in cell units)
|
||||
float mMaxBatchSize;
|
||||
|
||||
void buildQuadTree(QuadTreeNode* node, std::vector<QuadTreeNode*>& leafs);
|
||||
|
||||
// Are layers for leaf nodes loaded? This is done once at startup (but in a background thread)
|
||||
bool mLayerLoadPending;
|
||||
|
||||
public:
|
||||
// ----INTERNAL----
|
||||
Ogre::SceneManager* getCompositeMapSceneManager() { return mCompositeMapSceneMgr; }
|
||||
|
||||
bool areLayersLoaded() { return !mLayerLoadPending; }
|
||||
|
||||
// Delete all quads
|
||||
void clearCompositeMapSceneManager();
|
||||
void renderCompositeMap (Ogre::TexturePtr target);
|
||||
|
||||
// Adds a WorkQueue request to load a chunk for this node in the background.
|
||||
void queueLoad (QuadTreeNode* node);
|
||||
|
||||
private:
|
||||
Ogre::RenderTarget* mCompositeMapRenderTarget;
|
||||
Ogre::TexturePtr mCompositeMapRenderTexture;
|
||||
};
|
||||
|
||||
struct LoadRequestData
|
||||
{
|
||||
QuadTreeNode* mNode;
|
||||
|
||||
friend std::ostream& operator<<(std::ostream& o, const LoadRequestData& r)
|
||||
{ return o; }
|
||||
};
|
||||
|
||||
struct LoadResponseData
|
||||
{
|
||||
std::vector<float> mPositions;
|
||||
std::vector<float> mNormals;
|
||||
std::vector<Ogre::uint8> mColours;
|
||||
|
||||
friend std::ostream& operator<<(std::ostream& o, const LoadResponseData& r)
|
||||
{ return o; }
|
||||
};
|
||||
|
||||
struct LayersRequestData
|
||||
{
|
||||
std::vector<QuadTreeNode*> mNodes;
|
||||
bool mPack;
|
||||
|
||||
friend std::ostream& operator<<(std::ostream& o, const LayersRequestData& r)
|
||||
{ return o; }
|
||||
};
|
||||
|
||||
struct LayersResponseData
|
||||
{
|
||||
std::vector<LayerCollection> mLayerCollections;
|
||||
|
||||
friend std::ostream& operator<<(std::ostream& o, const LayersResponseData& r)
|
||||
{ return o; }
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
|
@ -22,41 +22,10 @@
|
|||
#ifndef COMPONENTS_TERRAIN_DEFS_HPP
|
||||
#define COMPONENTS_TERRAIN_DEFS_HPP
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace Terrain
|
||||
{
|
||||
class QuadTreeNode;
|
||||
|
||||
/// The alignment of the terrain
|
||||
enum Alignment
|
||||
{
|
||||
/// Terrain is in the X/Z plane
|
||||
Align_XZ = 0,
|
||||
/// Terrain is in the X/Y plane
|
||||
Align_XY = 1,
|
||||
/// Terrain is in the Y/Z plane.
|
||||
/// UNTESTED - use at own risk.
|
||||
/// Besides, X as up axis? What is wrong with you? ;)
|
||||
Align_YZ = 2
|
||||
};
|
||||
|
||||
inline void convertPosition(Alignment align, float &x, float &y, float &z)
|
||||
{
|
||||
switch (align)
|
||||
{
|
||||
case Align_XY:
|
||||
return;
|
||||
case Align_XZ:
|
||||
std::swap(y, z);
|
||||
// This is since -Z should be going *into* the screen
|
||||
// If not doing this, we'd get wrong vertex winding
|
||||
z *= -1;
|
||||
return;
|
||||
case Align_YZ:
|
||||
std::swap(x, y);
|
||||
std::swap(y, z);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
enum Direction
|
||||
{
|
||||
|
@ -74,13 +43,6 @@ namespace Terrain
|
|||
bool mSpecular; // Specular info in diffuse map alpha channel?
|
||||
};
|
||||
|
||||
struct LayerCollection
|
||||
{
|
||||
QuadTreeNode* mTarget;
|
||||
// Since we can't create a texture from a different thread, this only holds the raw texel data
|
||||
std::vector<Ogre::PixelBox> mBlendmaps;
|
||||
std::vector<LayerInfo> mLayers;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
@ -21,353 +21,86 @@
|
|||
*/
|
||||
#include "material.hpp"
|
||||
|
||||
#include <OgreMaterialManager.h>
|
||||
#include <OgreTechnique.h>
|
||||
#include <OgrePass.h>
|
||||
#include <iostream>
|
||||
|
||||
#include <boost/functional/hash.hpp>
|
||||
#include <osg/Depth>
|
||||
#include <osg/TexEnvCombine>
|
||||
#include <osg/Texture2D>
|
||||
#include <osg/TexMat>
|
||||
#include <osg/Material>
|
||||
|
||||
#if TERRAIN_USE_SHADER
|
||||
#include <extern/shiny/Main/Factory.hpp>
|
||||
#endif
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
int getBlendmapIndexForLayer (int layerIndex)
|
||||
{
|
||||
return static_cast<int>(std::floor((layerIndex - 1) / 4.f));
|
||||
}
|
||||
|
||||
std::string getBlendmapComponentForLayer (int layerIndex)
|
||||
{
|
||||
int n = (layerIndex-1)%4;
|
||||
if (n == 0)
|
||||
return "x";
|
||||
if (n == 1)
|
||||
return "y";
|
||||
if (n == 2)
|
||||
return "z";
|
||||
else
|
||||
return "w";
|
||||
}
|
||||
|
||||
}
|
||||
#include <osg/io_utils>
|
||||
|
||||
namespace Terrain
|
||||
{
|
||||
|
||||
MaterialGenerator::MaterialGenerator()
|
||||
: mShaders(true)
|
||||
, mShadows(false)
|
||||
, mSplitShadows(false)
|
||||
, mNormalMapping(true)
|
||||
, mParallaxMapping(true)
|
||||
FixedFunctionTechnique::FixedFunctionTechnique(const std::vector<osg::ref_ptr<osg::Texture2D> >& layers,
|
||||
const std::vector<osg::ref_ptr<osg::Texture2D> >& blendmaps)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
Ogre::MaterialPtr MaterialGenerator::generate()
|
||||
{
|
||||
assert(!mLayerList.empty() && "Can't create material with no layers");
|
||||
|
||||
return create(false, false);
|
||||
}
|
||||
|
||||
Ogre::MaterialPtr MaterialGenerator::generateForCompositeMapRTT()
|
||||
{
|
||||
assert(!mLayerList.empty() && "Can't create material with no layers");
|
||||
|
||||
return create(true, false);
|
||||
}
|
||||
|
||||
Ogre::MaterialPtr MaterialGenerator::generateForCompositeMap()
|
||||
{
|
||||
return create(false, true);
|
||||
}
|
||||
|
||||
Ogre::MaterialPtr MaterialGenerator::create(bool renderCompositeMap, bool displayCompositeMap)
|
||||
{
|
||||
assert(!renderCompositeMap || !displayCompositeMap);
|
||||
|
||||
static int count = 0;
|
||||
std::stringstream name;
|
||||
name << "terrain/mat" << count++;
|
||||
|
||||
if (!mShaders)
|
||||
bool firstLayer = true;
|
||||
int i=0;
|
||||
for (std::vector<osg::ref_ptr<osg::Texture2D> >::const_iterator it = layers.begin(); it != layers.end(); ++it)
|
||||
{
|
||||
Ogre::MaterialPtr mat = Ogre::MaterialManager::getSingleton().create(name.str(),
|
||||
Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
|
||||
Ogre::Technique* technique = mat->getTechnique(0);
|
||||
technique->removeAllPasses();
|
||||
osg::ref_ptr<osg::StateSet> stateset (new osg::StateSet);
|
||||
|
||||
if (displayCompositeMap)
|
||||
if (!firstLayer)
|
||||
{
|
||||
Ogre::Pass* pass = technique->createPass();
|
||||
pass->setVertexColourTracking(Ogre::TVC_AMBIENT|Ogre::TVC_DIFFUSE);
|
||||
pass->createTextureUnitState(mCompositeMap)->setTextureAddressingMode(Ogre::TextureUnitState::TAM_CLAMP);
|
||||
}
|
||||
else
|
||||
{
|
||||
assert(mLayerList.size() == mBlendmapList.size()+1);
|
||||
std::vector<Ogre::TexturePtr>::iterator blend = mBlendmapList.begin();
|
||||
for (std::vector<LayerInfo>::iterator layer = mLayerList.begin(); layer != mLayerList.end(); ++layer)
|
||||
{
|
||||
Ogre::Pass* pass = technique->createPass();
|
||||
pass->setLightingEnabled(false);
|
||||
pass->setVertexColourTracking(Ogre::TVC_NONE);
|
||||
// TODO: How to handle fog?
|
||||
pass->setFog(true, Ogre::FOG_NONE);
|
||||
|
||||
bool first = (layer == mLayerList.begin());
|
||||
|
||||
Ogre::TextureUnitState* tus;
|
||||
|
||||
if (!first)
|
||||
{
|
||||
pass->setSceneBlending(Ogre::SBT_TRANSPARENT_ALPHA);
|
||||
pass->setDepthFunction(Ogre::CMPF_EQUAL);
|
||||
|
||||
tus = pass->createTextureUnitState((*blend)->getName());
|
||||
tus->setAlphaOperation(Ogre::LBX_BLEND_TEXTURE_ALPHA,
|
||||
Ogre::LBS_TEXTURE,
|
||||
Ogre::LBS_TEXTURE);
|
||||
tus->setColourOperationEx(Ogre::LBX_BLEND_DIFFUSE_ALPHA,
|
||||
Ogre::LBS_TEXTURE,
|
||||
Ogre::LBS_TEXTURE);
|
||||
tus->setIsAlpha(true);
|
||||
tus->setTextureAddressingMode(Ogre::TextureUnitState::TAM_CLAMP);
|
||||
|
||||
float scale = (16/(16.f+1.f));
|
||||
tus->setTextureScale(1.f/scale,1.f/scale);
|
||||
}
|
||||
|
||||
// Add the actual layer texture on top of the alpha map.
|
||||
tus = pass->createTextureUnitState(layer->mDiffuseMap);
|
||||
if (!first)
|
||||
tus->setColourOperationEx(Ogre::LBX_BLEND_DIFFUSE_ALPHA,
|
||||
Ogre::LBS_TEXTURE,
|
||||
Ogre::LBS_CURRENT);
|
||||
|
||||
tus->setTextureScale(1/16.f,1/16.f);
|
||||
|
||||
if (!first)
|
||||
++blend;
|
||||
}
|
||||
|
||||
if (!renderCompositeMap)
|
||||
{
|
||||
Ogre::Pass* lightingPass = technique->createPass();
|
||||
lightingPass->setSceneBlending(Ogre::SBT_MODULATE);
|
||||
lightingPass->setVertexColourTracking(Ogre::TVC_AMBIENT|Ogre::TVC_DIFFUSE);
|
||||
lightingPass->setFog(true, Ogre::FOG_NONE);
|
||||
}
|
||||
stateset->setMode(GL_BLEND, osg::StateAttribute::ON);
|
||||
osg::ref_ptr<osg::Depth> depth (new osg::Depth);
|
||||
depth->setFunction(osg::Depth::EQUAL);
|
||||
stateset->setAttributeAndModes(depth, osg::StateAttribute::ON);
|
||||
}
|
||||
|
||||
return mat;
|
||||
int texunit = 0;
|
||||
if(!firstLayer)
|
||||
{
|
||||
osg::ref_ptr<osg::Texture2D> blendmap = blendmaps.at(i++);
|
||||
|
||||
stateset->setTextureAttributeAndModes(texunit, blendmap.get());
|
||||
|
||||
// This is to map corner vertices directly to the center of a blendmap texel.
|
||||
osg::Matrixf texMat;
|
||||
float scale = (16/(16.f+1.f));
|
||||
texMat.preMultTranslate(osg::Vec3f(0.5f, 0.5f, 0.f));
|
||||
texMat.preMultScale(osg::Vec3f(scale, scale, 1.f));
|
||||
texMat.preMultTranslate(osg::Vec3f(-0.5f, -0.5f, 0.f));
|
||||
|
||||
stateset->setTextureAttributeAndModes(texunit, new osg::TexMat(texMat));
|
||||
|
||||
++texunit;
|
||||
}
|
||||
|
||||
// Add the actual layer texture multiplied by the alpha map.
|
||||
osg::ref_ptr<osg::Texture2D> tex = *it;
|
||||
stateset->setTextureAttributeAndModes(texunit, tex.get());
|
||||
|
||||
osg::ref_ptr<osg::TexMat> texMat (new osg::TexMat);
|
||||
float scale = 16.f;
|
||||
texMat->setMatrix(osg::Matrix::scale(osg::Vec3f(scale,scale,1.f)));
|
||||
stateset->setTextureAttributeAndModes(texunit, texMat, osg::StateAttribute::ON);
|
||||
|
||||
firstLayer = false;
|
||||
|
||||
addPass(stateset);
|
||||
}
|
||||
#if TERRAIN_USE_SHADER
|
||||
else
|
||||
{
|
||||
sh::MaterialInstance* material = sh::Factory::getInstance().createMaterialInstance (name.str());
|
||||
material->setProperty ("allow_fixed_function", sh::makeProperty<sh::BooleanValue>(new sh::BooleanValue(false)));
|
||||
}
|
||||
|
||||
if (displayCompositeMap)
|
||||
{
|
||||
sh::MaterialInstancePass* p = material->createPass ();
|
||||
Effect::Effect(const std::vector<osg::ref_ptr<osg::Texture2D> > &layers, const std::vector<osg::ref_ptr<osg::Texture2D> > &blendmaps)
|
||||
: mLayers(layers)
|
||||
, mBlendmaps(blendmaps)
|
||||
{
|
||||
osg::ref_ptr<osg::Material> material (new osg::Material);
|
||||
material->setColorMode(osg::Material::AMBIENT_AND_DIFFUSE);
|
||||
getOrCreateStateSet()->setAttributeAndModes(material, osg::StateAttribute::ON);
|
||||
|
||||
p->setProperty ("vertex_program", sh::makeProperty<sh::StringValue>(new sh::StringValue("terrain_vertex")));
|
||||
p->setProperty ("fragment_program", sh::makeProperty<sh::StringValue>(new sh::StringValue("terrain_fragment")));
|
||||
p->mShaderProperties.setProperty ("is_first_pass", sh::makeProperty(new sh::BooleanValue(true)));
|
||||
p->mShaderProperties.setProperty ("render_composite_map", sh::makeProperty(new sh::BooleanValue(false)));
|
||||
p->mShaderProperties.setProperty ("display_composite_map", sh::makeProperty(new sh::BooleanValue(true)));
|
||||
p->mShaderProperties.setProperty ("num_layers", sh::makeProperty (new sh::StringValue("0")));
|
||||
p->mShaderProperties.setProperty ("num_blendmaps", sh::makeProperty (new sh::StringValue("0")));
|
||||
p->mShaderProperties.setProperty ("normal_map_enabled", sh::makeProperty (new sh::BooleanValue(false)));
|
||||
p->mShaderProperties.setProperty ("parallax_enabled", sh::makeProperty (new sh::BooleanValue(false)));
|
||||
p->mShaderProperties.setProperty ("normal_maps",
|
||||
sh::makeProperty (new sh::IntValue(0)));
|
||||
selectTechnique(0);
|
||||
}
|
||||
|
||||
sh::MaterialInstanceTextureUnit* tex = p->createTextureUnit ("compositeMap");
|
||||
tex->setProperty ("direct_texture", sh::makeProperty (new sh::StringValue(mCompositeMap)));
|
||||
tex->setProperty ("tex_address_mode", sh::makeProperty (new sh::StringValue("clamp")));
|
||||
bool Effect::define_techniques()
|
||||
{
|
||||
addTechnique(new FixedFunctionTechnique(mLayers, mBlendmaps));
|
||||
|
||||
// shadow. TODO: repeated, put in function
|
||||
if (mShadows)
|
||||
{
|
||||
for (int i = 0; i < (mSplitShadows ? 3 : 1); ++i)
|
||||
{
|
||||
sh::MaterialInstanceTextureUnit* shadowTex = p->createTextureUnit ("shadowMap" + Ogre::StringConverter::toString(i));
|
||||
shadowTex->setProperty ("content_type", sh::makeProperty<sh::StringValue> (new sh::StringValue("shadow")));
|
||||
}
|
||||
}
|
||||
p->mShaderProperties.setProperty ("shadowtexture_offset", sh::makeProperty (new sh::StringValue(
|
||||
Ogre::StringConverter::toString(1))));
|
||||
|
||||
p->mShaderProperties.setProperty ("pass_index", sh::makeProperty(new sh::IntValue(0)));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
bool shadows = mShadows && !renderCompositeMap;
|
||||
|
||||
int layerOffset = 0;
|
||||
while (layerOffset < (int)mLayerList.size())
|
||||
{
|
||||
int blendmapOffset = (layerOffset == 0) ? 1 : 0; // the first layer of the first pass is the base layer and does not need a blend map
|
||||
|
||||
// Check how many layers we can fit in this pass
|
||||
int numLayersInThisPass = 0;
|
||||
int numBlendTextures = 0;
|
||||
std::vector<std::string> blendTextures;
|
||||
int remainingTextureUnits = OGRE_MAX_TEXTURE_LAYERS;
|
||||
if (shadows)
|
||||
remainingTextureUnits -= (mSplitShadows ? 3 : 1);
|
||||
while (remainingTextureUnits && layerOffset + numLayersInThisPass < (int)mLayerList.size())
|
||||
{
|
||||
int layerIndex = numLayersInThisPass + layerOffset;
|
||||
|
||||
int neededTextureUnits=0;
|
||||
int neededBlendTextures=0;
|
||||
|
||||
if (layerIndex != 0)
|
||||
{
|
||||
std::string blendTextureName = mBlendmapList[getBlendmapIndexForLayer(layerIndex)]->getName();
|
||||
if (std::find(blendTextures.begin(), blendTextures.end(), blendTextureName) == blendTextures.end())
|
||||
{
|
||||
blendTextures.push_back(blendTextureName);
|
||||
++neededBlendTextures;
|
||||
++neededTextureUnits; // blend texture
|
||||
}
|
||||
}
|
||||
++neededTextureUnits; // layer texture
|
||||
|
||||
// Check if this layer has a normal map
|
||||
if (mNormalMapping && !mLayerList[layerIndex].mNormalMap.empty() && !renderCompositeMap)
|
||||
++neededTextureUnits; // normal map
|
||||
if (neededTextureUnits <= remainingTextureUnits)
|
||||
{
|
||||
// We can fit another!
|
||||
remainingTextureUnits -= neededTextureUnits;
|
||||
numBlendTextures += neededBlendTextures;
|
||||
++numLayersInThisPass;
|
||||
}
|
||||
else
|
||||
break; // We're full
|
||||
}
|
||||
|
||||
|
||||
sh::MaterialInstancePass* p = material->createPass ();
|
||||
p->setProperty ("vertex_program", sh::makeProperty<sh::StringValue>(new sh::StringValue("terrain_vertex")));
|
||||
p->setProperty ("fragment_program", sh::makeProperty<sh::StringValue>(new sh::StringValue("terrain_fragment")));
|
||||
if (layerOffset != 0)
|
||||
{
|
||||
p->setProperty ("scene_blend", sh::makeProperty(new sh::StringValue("alpha_blend")));
|
||||
// Only write if depth is equal to the depth value written by the previous pass.
|
||||
p->setProperty ("depth_func", sh::makeProperty(new sh::StringValue("equal")));
|
||||
}
|
||||
|
||||
p->mShaderProperties.setProperty ("render_composite_map", sh::makeProperty(new sh::BooleanValue(renderCompositeMap)));
|
||||
p->mShaderProperties.setProperty ("display_composite_map", sh::makeProperty(new sh::BooleanValue(displayCompositeMap)));
|
||||
|
||||
p->mShaderProperties.setProperty ("num_layers", sh::makeProperty (new sh::StringValue(Ogre::StringConverter::toString(numLayersInThisPass))));
|
||||
p->mShaderProperties.setProperty ("num_blendmaps", sh::makeProperty (new sh::StringValue(Ogre::StringConverter::toString(numBlendTextures))));
|
||||
p->mShaderProperties.setProperty ("normal_map_enabled",
|
||||
sh::makeProperty (new sh::BooleanValue(false)));
|
||||
|
||||
// blend maps
|
||||
// the index of the first blend map used in this pass
|
||||
int blendmapStart;
|
||||
if (mLayerList.size() == 1) // special case. if there's only one layer, we don't need blend maps at all
|
||||
blendmapStart = 0;
|
||||
else
|
||||
blendmapStart = getBlendmapIndexForLayer(layerOffset+blendmapOffset);
|
||||
for (int i = 0; i < numBlendTextures; ++i)
|
||||
{
|
||||
sh::MaterialInstanceTextureUnit* blendTex = p->createTextureUnit ("blendMap" + Ogre::StringConverter::toString(i));
|
||||
blendTex->setProperty ("direct_texture", sh::makeProperty (new sh::StringValue(mBlendmapList[blendmapStart+i]->getName())));
|
||||
blendTex->setProperty ("tex_address_mode", sh::makeProperty (new sh::StringValue("clamp")));
|
||||
}
|
||||
|
||||
// layer maps
|
||||
bool anyNormalMaps = false;
|
||||
bool anyParallax = false;
|
||||
size_t normalMaps = 0;
|
||||
for (int i = 0; i < numLayersInThisPass; ++i)
|
||||
{
|
||||
const LayerInfo& layer = mLayerList[layerOffset+i];
|
||||
// diffuse map
|
||||
sh::MaterialInstanceTextureUnit* diffuseTex = p->createTextureUnit ("diffuseMap" + Ogre::StringConverter::toString(i));
|
||||
diffuseTex->setProperty ("direct_texture", sh::makeProperty (new sh::StringValue(layer.mDiffuseMap)));
|
||||
|
||||
// normal map (optional)
|
||||
bool useNormalMap = mNormalMapping && !mLayerList[layerOffset+i].mNormalMap.empty() && !renderCompositeMap;
|
||||
bool useParallax = useNormalMap && mParallaxMapping && layer.mParallax;
|
||||
bool useSpecular = layer.mSpecular;
|
||||
if (useNormalMap)
|
||||
{
|
||||
anyNormalMaps = true;
|
||||
anyParallax = anyParallax || useParallax;
|
||||
sh::MaterialInstanceTextureUnit* normalTex = p->createTextureUnit ("normalMap" + Ogre::StringConverter::toString(i));
|
||||
normalTex->setProperty ("direct_texture", sh::makeProperty (new sh::StringValue(layer.mNormalMap)));
|
||||
}
|
||||
p->mShaderProperties.setProperty ("use_normal_map_" + Ogre::StringConverter::toString(i),
|
||||
sh::makeProperty (new sh::BooleanValue(useNormalMap)));
|
||||
p->mShaderProperties.setProperty ("use_parallax_" + Ogre::StringConverter::toString(i),
|
||||
sh::makeProperty (new sh::BooleanValue(useParallax)));
|
||||
p->mShaderProperties.setProperty ("use_specular_" + Ogre::StringConverter::toString(i),
|
||||
sh::makeProperty (new sh::BooleanValue(useSpecular)));
|
||||
boost::hash_combine(normalMaps, useNormalMap);
|
||||
boost::hash_combine(normalMaps, useNormalMap && layer.mParallax);
|
||||
boost::hash_combine(normalMaps, useSpecular);
|
||||
|
||||
if (i+layerOffset > 0)
|
||||
{
|
||||
int blendTextureIndex = getBlendmapIndexForLayer(layerOffset+i);
|
||||
std::string blendTextureComponent = getBlendmapComponentForLayer(layerOffset+i);
|
||||
p->mShaderProperties.setProperty ("blendmap_component_" + Ogre::StringConverter::toString(i),
|
||||
sh::makeProperty (new sh::StringValue(Ogre::StringConverter::toString(blendTextureIndex-blendmapStart) + "." + blendTextureComponent)));
|
||||
}
|
||||
else
|
||||
{
|
||||
// just to make it shut up about blendmap_component_0 not existing in the first pass.
|
||||
// it might be retrieved, but will never survive the preprocessing step.
|
||||
p->mShaderProperties.setProperty ("blendmap_component_" + Ogre::StringConverter::toString(i),
|
||||
sh::makeProperty (new sh::StringValue("")));
|
||||
}
|
||||
}
|
||||
p->mShaderProperties.setProperty ("normal_map_enabled",
|
||||
sh::makeProperty (new sh::BooleanValue(anyNormalMaps)));
|
||||
p->mShaderProperties.setProperty ("parallax_enabled",
|
||||
sh::makeProperty (new sh::BooleanValue(anyParallax)));
|
||||
// Since the permutation handler can't handle dynamic property names,
|
||||
// combine normal map settings for all layers into one value
|
||||
p->mShaderProperties.setProperty ("normal_maps",
|
||||
sh::makeProperty (new sh::IntValue(normalMaps)));
|
||||
|
||||
// shadow
|
||||
if (shadows)
|
||||
{
|
||||
for (int i = 0; i < (mSplitShadows ? 3 : 1); ++i)
|
||||
{
|
||||
sh::MaterialInstanceTextureUnit* shadowTex = p->createTextureUnit ("shadowMap" + Ogre::StringConverter::toString(i));
|
||||
shadowTex->setProperty ("content_type", sh::makeProperty<sh::StringValue> (new sh::StringValue("shadow")));
|
||||
}
|
||||
}
|
||||
p->mShaderProperties.setProperty ("shadowtexture_offset", sh::makeProperty (new sh::StringValue(
|
||||
Ogre::StringConverter::toString(numBlendTextures + numLayersInThisPass))));
|
||||
|
||||
// Make sure the pass index is fed to the permutation handler, because blendmap components may be different
|
||||
p->mShaderProperties.setProperty ("pass_index", sh::makeProperty(new sh::IntValue(layerOffset)));
|
||||
|
||||
assert ((int)p->mTexUnits.size() == OGRE_MAX_TEXTURE_LAYERS - remainingTextureUnits);
|
||||
|
||||
layerOffset += numLayersInThisPass;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return Ogre::MaterialManager::getSingleton().getByName(name.str());
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -22,51 +22,55 @@
|
|||
#ifndef COMPONENTS_TERRAIN_MATERIAL_H
|
||||
#define COMPONENTS_TERRAIN_MATERIAL_H
|
||||
|
||||
#include <OgreMaterial.h>
|
||||
#include <osgFX/Technique>
|
||||
#include <osgFX/Effect>
|
||||
|
||||
#include "storage.hpp"
|
||||
#include "defs.hpp"
|
||||
|
||||
namespace osg
|
||||
{
|
||||
class Texture2D;
|
||||
}
|
||||
|
||||
namespace Terrain
|
||||
{
|
||||
|
||||
class MaterialGenerator
|
||||
class FixedFunctionTechnique : public osgFX::Technique
|
||||
{
|
||||
public:
|
||||
MaterialGenerator ();
|
||||
FixedFunctionTechnique(
|
||||
const std::vector<osg::ref_ptr<osg::Texture2D> >& layers,
|
||||
const std::vector<osg::ref_ptr<osg::Texture2D> >& blendmaps);
|
||||
|
||||
void setLayerList (const std::vector<LayerInfo>& layerList) { mLayerList = layerList; }
|
||||
bool hasLayers() { return mLayerList.size() > 0; }
|
||||
void setBlendmapList (const std::vector<Ogre::TexturePtr>& blendmapList) { mBlendmapList = blendmapList; }
|
||||
const std::vector<Ogre::TexturePtr>& getBlendmapList() { return mBlendmapList; }
|
||||
void setCompositeMap (const std::string& name) { mCompositeMap = name; }
|
||||
protected:
|
||||
virtual void define_passes() {}
|
||||
};
|
||||
|
||||
void enableShaders(bool shaders) { mShaders = shaders; }
|
||||
void enableShadows(bool shadows) { mShadows = shadows; }
|
||||
void enableNormalMapping(bool normalMapping) { mNormalMapping = normalMapping; }
|
||||
void enableParallaxMapping(bool parallaxMapping) { mParallaxMapping = parallaxMapping; }
|
||||
void enableSplitShadows(bool splitShadows) { mSplitShadows = splitShadows; }
|
||||
class Effect : public osgFX::Effect
|
||||
{
|
||||
public:
|
||||
Effect(
|
||||
const std::vector<osg::ref_ptr<osg::Texture2D> >& layers,
|
||||
const std::vector<osg::ref_ptr<osg::Texture2D> >& blendmaps);
|
||||
|
||||
/// Creates a material suitable for displaying a chunk of terrain using alpha-blending.
|
||||
Ogre::MaterialPtr generate ();
|
||||
virtual bool define_techniques();
|
||||
|
||||
/// Creates a material suitable for displaying a chunk of terrain using a ready-made composite map.
|
||||
Ogre::MaterialPtr generateForCompositeMap ();
|
||||
|
||||
/// Creates a material suitable for rendering composite maps, i.e. for "baking" several layer textures
|
||||
/// into one. The main difference compared to a normal material is that no shading is applied at this point.
|
||||
Ogre::MaterialPtr generateForCompositeMapRTT ();
|
||||
virtual const char *effectName() const
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
virtual const char *effectDescription() const
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
virtual const char *effectAuthor() const
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
private:
|
||||
Ogre::MaterialPtr create (bool renderCompositeMap, bool displayCompositeMap);
|
||||
|
||||
std::vector<LayerInfo> mLayerList;
|
||||
std::vector<Ogre::TexturePtr> mBlendmapList;
|
||||
std::string mCompositeMap;
|
||||
bool mShaders;
|
||||
bool mShadows;
|
||||
bool mSplitShadows;
|
||||
bool mNormalMapping;
|
||||
bool mParallaxMapping;
|
||||
std::vector<osg::ref_ptr<osg::Texture2D> > mLayers;
|
||||
std::vector<osg::ref_ptr<osg::Texture2D> > mBlendmaps;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -1,611 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2015 scrawl <scrawl@baseoftrash.de>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
#include "quadtreenode.hpp"
|
||||
|
||||
#include <OgreSceneManager.h>
|
||||
#include <OgreManualObject.h>
|
||||
#include <OgreSceneNode.h>
|
||||
#include <OgreMaterialManager.h>
|
||||
#include <OgreTextureManager.h>
|
||||
|
||||
#include "defaultworld.hpp"
|
||||
#include "chunk.hpp"
|
||||
#include "storage.hpp"
|
||||
#include "buffercache.hpp"
|
||||
#include "material.hpp"
|
||||
|
||||
using namespace Terrain;
|
||||
|
||||
namespace
|
||||
{
|
||||
int Log2( int n )
|
||||
{
|
||||
assert(n > 0);
|
||||
int targetlevel = 0;
|
||||
while (n >>= 1) ++targetlevel;
|
||||
return targetlevel;
|
||||
}
|
||||
|
||||
// Utility functions for neighbour finding algorithm
|
||||
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];
|
||||
}
|
||||
|
||||
// Algorithm described by Hanan Samet - 'Neighbour Finding in Quadtrees'
|
||||
// http://www.cs.umd.edu/~hjs/pubs/SametPRIP81.pdf
|
||||
QuadTreeNode* searchNeighbourRecursive (QuadTreeNode* currentNode, Direction dir)
|
||||
{
|
||||
if (!currentNode->getParent())
|
||||
return NULL; // Arrived at root node, the root node does not have neighbours
|
||||
|
||||
QuadTreeNode* nextNode;
|
||||
if (adjacent(currentNode->getDirection(), dir))
|
||||
nextNode = searchNeighbourRecursive(currentNode->getParent(), dir);
|
||||
else
|
||||
nextNode = currentNode->getParent();
|
||||
|
||||
if (nextNode && nextNode->hasChildren())
|
||||
return nextNode->getChild(reflect(currentNode->getDirection(), dir));
|
||||
else
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Create a 2D quad
|
||||
void makeQuad(Ogre::SceneManager* sceneMgr, float left, float top, float right, float bottom, Ogre::MaterialPtr material)
|
||||
{
|
||||
Ogre::ManualObject* manual = sceneMgr->createManualObject();
|
||||
|
||||
// Use identity view/projection matrices to get a 2d quad
|
||||
manual->setUseIdentityProjection(true);
|
||||
manual->setUseIdentityView(true);
|
||||
|
||||
manual->begin(material->getName());
|
||||
|
||||
float normLeft = left*2-1;
|
||||
float normTop = top*2-1;
|
||||
float normRight = right*2-1;
|
||||
float normBottom = bottom*2-1;
|
||||
|
||||
manual->position(normLeft, normTop, 0.0);
|
||||
manual->textureCoord(0, 1);
|
||||
manual->position(normRight, normTop, 0.0);
|
||||
manual->textureCoord(1, 1);
|
||||
manual->position(normRight, normBottom, 0.0);
|
||||
manual->textureCoord(1, 0);
|
||||
manual->position(normLeft, normBottom, 0.0);
|
||||
manual->textureCoord(0, 0);
|
||||
|
||||
manual->quad(0,1,2,3);
|
||||
|
||||
manual->end();
|
||||
|
||||
Ogre::AxisAlignedBox aabInf;
|
||||
aabInf.setInfinite();
|
||||
manual->setBoundingBox(aabInf);
|
||||
|
||||
sceneMgr->getRootSceneNode()->createChildSceneNode()->attachObject(manual);
|
||||
}
|
||||
}
|
||||
|
||||
QuadTreeNode::QuadTreeNode(DefaultWorld* terrain, ChildDirection dir, float size, const Ogre::Vector2 ¢er, QuadTreeNode* parent)
|
||||
: mMaterialGenerator(NULL)
|
||||
, mLoadState(LS_Unloaded)
|
||||
, mIsDummy(false)
|
||||
, mSize(size)
|
||||
, mLodLevel(Log2(static_cast<int>(mSize)))
|
||||
, mBounds(Ogre::AxisAlignedBox::BOX_NULL)
|
||||
, mWorldBounds(Ogre::AxisAlignedBox::BOX_NULL)
|
||||
, mDirection(dir)
|
||||
, mCenter(center)
|
||||
, mSceneNode(NULL)
|
||||
, mParent(parent)
|
||||
, mChunk(NULL)
|
||||
, mTerrain(terrain)
|
||||
{
|
||||
mBounds.setNull();
|
||||
for (int i=0; i<4; ++i)
|
||||
mChildren[i] = NULL;
|
||||
for (int i=0; i<4; ++i)
|
||||
mNeighbours[i] = NULL;
|
||||
|
||||
if (mDirection == Root)
|
||||
mSceneNode = mTerrain->getRootSceneNode();
|
||||
else
|
||||
mSceneNode = mTerrain->getSceneManager()->createSceneNode();
|
||||
Ogre::Vector2 pos (0,0);
|
||||
if (mParent)
|
||||
pos = mParent->getCenter();
|
||||
pos = mCenter - pos;
|
||||
float cellWorldSize = mTerrain->getStorage()->getCellWorldSize();
|
||||
|
||||
Ogre::Vector3 sceneNodePos (pos.x*cellWorldSize, pos.y*cellWorldSize, 0);
|
||||
mTerrain->convertPosition(sceneNodePos);
|
||||
|
||||
mSceneNode->setPosition(sceneNodePos);
|
||||
|
||||
mMaterialGenerator = new MaterialGenerator();
|
||||
mMaterialGenerator->enableShaders(mTerrain->getShadersEnabled());
|
||||
}
|
||||
|
||||
void QuadTreeNode::createChild(ChildDirection id, float size, const Ogre::Vector2 ¢er)
|
||||
{
|
||||
mChildren[id] = new QuadTreeNode(mTerrain, id, size, center, this);
|
||||
}
|
||||
|
||||
QuadTreeNode::~QuadTreeNode()
|
||||
{
|
||||
for (int i=0; i<4; ++i)
|
||||
delete mChildren[i];
|
||||
delete mChunk;
|
||||
delete mMaterialGenerator;
|
||||
}
|
||||
|
||||
QuadTreeNode* QuadTreeNode::getNeighbour(Direction dir)
|
||||
{
|
||||
return mNeighbours[static_cast<int>(dir)];
|
||||
}
|
||||
|
||||
void QuadTreeNode::initNeighbours()
|
||||
{
|
||||
for (int i=0; i<4; ++i)
|
||||
mNeighbours[i] = searchNeighbourRecursive(this, (Direction)i);
|
||||
|
||||
if (hasChildren())
|
||||
for (int i=0; i<4; ++i)
|
||||
mChildren[i]->initNeighbours();
|
||||
}
|
||||
|
||||
void QuadTreeNode::initAabb()
|
||||
{
|
||||
float cellWorldSize = mTerrain->getStorage()->getCellWorldSize();
|
||||
if (hasChildren())
|
||||
{
|
||||
for (int i=0; i<4; ++i)
|
||||
{
|
||||
mChildren[i]->initAabb();
|
||||
mBounds.merge(mChildren[i]->getBoundingBox());
|
||||
}
|
||||
float minH, maxH;
|
||||
switch (mTerrain->getAlign())
|
||||
{
|
||||
case Terrain::Align_XY:
|
||||
minH = mBounds.getMinimum().z;
|
||||
maxH = mBounds.getMaximum().z;
|
||||
break;
|
||||
case Terrain::Align_XZ:
|
||||
minH = mBounds.getMinimum().y;
|
||||
maxH = mBounds.getMaximum().y;
|
||||
break;
|
||||
case Terrain::Align_YZ:
|
||||
minH = mBounds.getMinimum().x;
|
||||
maxH = mBounds.getMaximum().x;
|
||||
break;
|
||||
}
|
||||
Ogre::Vector3 min(-mSize/2*cellWorldSize, -mSize/2*cellWorldSize, minH);
|
||||
Ogre::Vector3 max(Ogre::Vector3(mSize/2*cellWorldSize, mSize/2*cellWorldSize, maxH));
|
||||
mBounds = Ogre::AxisAlignedBox (min, max);
|
||||
mTerrain->convertBounds(mBounds);
|
||||
}
|
||||
Ogre::Vector3 offset(mCenter.x*cellWorldSize, mCenter.y*cellWorldSize, 0);
|
||||
mTerrain->convertPosition(offset);
|
||||
mWorldBounds = Ogre::AxisAlignedBox(mBounds.getMinimum() + offset,
|
||||
mBounds.getMaximum() + offset);
|
||||
}
|
||||
|
||||
void QuadTreeNode::setBoundingBox(const Ogre::AxisAlignedBox &box)
|
||||
{
|
||||
mBounds = box;
|
||||
}
|
||||
|
||||
const Ogre::AxisAlignedBox& QuadTreeNode::getBoundingBox()
|
||||
{
|
||||
return mBounds;
|
||||
}
|
||||
|
||||
const Ogre::AxisAlignedBox& QuadTreeNode::getWorldBoundingBox()
|
||||
{
|
||||
return mWorldBounds;
|
||||
}
|
||||
|
||||
bool QuadTreeNode::update(const Ogre::Vector3 &cameraPos)
|
||||
{
|
||||
if (isDummy())
|
||||
return true;
|
||||
|
||||
if (mBounds.isNull())
|
||||
return true;
|
||||
|
||||
float dist = mWorldBounds.distance(cameraPos);
|
||||
|
||||
// Make sure our scene node is attached
|
||||
if (!mSceneNode->isInSceneGraph())
|
||||
{
|
||||
mParent->getSceneNode()->addChild(mSceneNode);
|
||||
}
|
||||
|
||||
// Simple LOD selection
|
||||
/// \todo use error metrics?
|
||||
size_t wantedLod = 0;
|
||||
float cellWorldSize = mTerrain->getStorage()->getCellWorldSize();
|
||||
|
||||
if (dist > cellWorldSize*64)
|
||||
wantedLod = 6;
|
||||
else if (dist > cellWorldSize*32)
|
||||
wantedLod = 5;
|
||||
else if (dist > cellWorldSize*12)
|
||||
wantedLod = 4;
|
||||
else if (dist > cellWorldSize*5)
|
||||
wantedLod = 3;
|
||||
else if (dist > cellWorldSize*2)
|
||||
wantedLod = 2;
|
||||
else if (dist > cellWorldSize * 1.42) // < sqrt2 so the 3x3 grid around player is always highest lod
|
||||
wantedLod = 1;
|
||||
|
||||
bool wantToDisplay = mSize <= mTerrain->getMaxBatchSize() && mLodLevel <= wantedLod;
|
||||
|
||||
if (wantToDisplay)
|
||||
{
|
||||
// Wanted LOD is small enough to render this node in one chunk
|
||||
if (mLoadState == LS_Unloaded)
|
||||
{
|
||||
mLoadState = LS_Loading;
|
||||
mTerrain->queueLoad(this);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mLoadState == LS_Loaded)
|
||||
{
|
||||
// Additional (index buffer) LOD is currently disabled.
|
||||
// This is due to a problem with the LOD selection when a node splits.
|
||||
// After splitting, the distance is measured from the children's bounding boxes, which are possibly
|
||||
// further away than the original node's bounding box, possibly causing a child to switch to a *lower* LOD
|
||||
// than the original node.
|
||||
// In short, we'd sometimes get a switch to a lesser detail when actually moving closer.
|
||||
// This wouldn't be so bad, but unfortunately it also breaks LOD edge connections if a neighbour
|
||||
// node hasn't split yet, and has a higher LOD than our node's child:
|
||||
// ----- ----- ------------
|
||||
// | LOD | LOD | |
|
||||
// | 1 | 1 | |
|
||||
// |-----|-----| LOD 0 |
|
||||
// | LOD | LOD | |
|
||||
// | 0 | 0 | |
|
||||
// ----- ----- ------------
|
||||
// To prevent this, nodes of the same size need to always select the same LOD, which is basically what we're
|
||||
// doing here.
|
||||
// But this "solution" does increase triangle overhead, so eventually we need to find a more clever way.
|
||||
//mChunk->setAdditionalLod(wantedLod - mLodLevel);
|
||||
|
||||
if (!mChunk->getVisible() && hasChildren())
|
||||
{
|
||||
for (int i=0; i<4; ++i)
|
||||
mChildren[i]->unload(true);
|
||||
}
|
||||
mChunk->setVisible(true);
|
||||
|
||||
return true;
|
||||
}
|
||||
return false; // LS_Loading
|
||||
}
|
||||
|
||||
// We do not want to display this node - delegate to children if they are already loaded
|
||||
if (!wantToDisplay && hasChildren())
|
||||
{
|
||||
if (mChunk)
|
||||
{
|
||||
// Are children already loaded?
|
||||
bool childrenLoaded = true;
|
||||
for (int i=0; i<4; ++i)
|
||||
if (!mChildren[i]->update(cameraPos))
|
||||
childrenLoaded = false;
|
||||
|
||||
if (!childrenLoaded)
|
||||
{
|
||||
mChunk->setVisible(true);
|
||||
// Make sure child scene nodes are detached until all children are loaded
|
||||
mSceneNode->removeAllChildren();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Delegation went well, we can unload now
|
||||
unload();
|
||||
|
||||
for (int i=0; i<4; ++i)
|
||||
{
|
||||
if (!mChildren[i]->getSceneNode()->isInSceneGraph())
|
||||
mSceneNode->addChild(mChildren[i]->getSceneNode());
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
bool success = true;
|
||||
for (int i=0; i<4; ++i)
|
||||
success = mChildren[i]->update(cameraPos) & success;
|
||||
return success;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void QuadTreeNode::load(const LoadResponseData &data)
|
||||
{
|
||||
assert (!mChunk);
|
||||
|
||||
mChunk = new Chunk(mTerrain->getBufferCache().getUVBuffer(), mBounds, data.mPositions, data.mNormals, data.mColours);
|
||||
mChunk->setVisibilityFlags(mTerrain->getVisibilityFlags());
|
||||
mChunk->setCastShadows(true);
|
||||
mSceneNode->attachObject(mChunk);
|
||||
|
||||
mMaterialGenerator->enableShadows(mTerrain->getShadowsEnabled());
|
||||
mMaterialGenerator->enableSplitShadows(mTerrain->getSplitShadowsEnabled());
|
||||
|
||||
if (mTerrain->areLayersLoaded())
|
||||
{
|
||||
if (mSize == 1)
|
||||
{
|
||||
mChunk->setMaterial(mMaterialGenerator->generate());
|
||||
}
|
||||
else
|
||||
{
|
||||
ensureCompositeMap();
|
||||
mMaterialGenerator->setCompositeMap(mCompositeMap->getName());
|
||||
mChunk->setMaterial(mMaterialGenerator->generateForCompositeMap());
|
||||
}
|
||||
}
|
||||
// else: will be loaded in loadMaterials() after background thread has finished loading layers
|
||||
mChunk->setVisible(false);
|
||||
|
||||
mLoadState = LS_Loaded;
|
||||
}
|
||||
|
||||
void QuadTreeNode::unload(bool recursive)
|
||||
{
|
||||
if (mChunk)
|
||||
{
|
||||
mSceneNode->detachObject(mChunk);
|
||||
|
||||
delete mChunk;
|
||||
mChunk = NULL;
|
||||
|
||||
if (!mCompositeMap.isNull())
|
||||
{
|
||||
Ogre::TextureManager::getSingleton().remove(mCompositeMap->getName());
|
||||
mCompositeMap.setNull();
|
||||
}
|
||||
|
||||
// Do *not* set this when we are still loading!
|
||||
mLoadState = LS_Unloaded;
|
||||
}
|
||||
|
||||
if (recursive && hasChildren())
|
||||
{
|
||||
for (int i=0; i<4; ++i)
|
||||
mChildren[i]->unload(true);
|
||||
}
|
||||
}
|
||||
|
||||
void QuadTreeNode::updateIndexBuffers()
|
||||
{
|
||||
if (hasChunk())
|
||||
{
|
||||
// Fetch a suitable index buffer (which may be shared)
|
||||
size_t ourLod = getActualLodLevel();
|
||||
|
||||
unsigned int flags = 0;
|
||||
|
||||
for (int i=0; i<4; ++i)
|
||||
{
|
||||
QuadTreeNode* neighbour = getNeighbour((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 && !neighbour->hasChunk())
|
||||
neighbour = neighbour->getParent();
|
||||
size_t lod = 0;
|
||||
if (neighbour)
|
||||
lod = neighbour->getActualLodLevel();
|
||||
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)
|
||||
{
|
||||
assert (lod - ourLod < (1 << 4));
|
||||
flags |= static_cast<unsigned int>(lod - ourLod) << (4*i);
|
||||
}
|
||||
}
|
||||
flags |= 0 /*((int)mAdditionalLod)*/ << (4*4);
|
||||
|
||||
mChunk->setIndexBuffer(mTerrain->getBufferCache().getIndexBuffer(flags));
|
||||
}
|
||||
else if (hasChildren())
|
||||
{
|
||||
for (int i=0; i<4; ++i)
|
||||
mChildren[i]->updateIndexBuffers();
|
||||
}
|
||||
}
|
||||
|
||||
bool QuadTreeNode::hasChunk()
|
||||
{
|
||||
return mSceneNode->isInSceneGraph() && mChunk && mChunk->getVisible();
|
||||
}
|
||||
|
||||
size_t QuadTreeNode::getActualLodLevel()
|
||||
{
|
||||
assert(hasChunk() && "Can't get actual LOD level if this node has no render chunk");
|
||||
return mLodLevel /* + mChunk->getAdditionalLod() */;
|
||||
}
|
||||
|
||||
void QuadTreeNode::loadLayers(const LayerCollection& collection)
|
||||
{
|
||||
assert (!mMaterialGenerator->hasLayers());
|
||||
|
||||
std::vector<Ogre::TexturePtr> blendTextures;
|
||||
for (std::vector<Ogre::PixelBox>::const_iterator it = collection.mBlendmaps.begin(); it != collection.mBlendmaps.end(); ++it)
|
||||
{
|
||||
// TODO: clean up blend textures on destruction
|
||||
static int count=0;
|
||||
Ogre::TexturePtr map = Ogre::TextureManager::getSingleton().createManual("terrain/blend/"
|
||||
+ Ogre::StringConverter::toString(count++), Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME,
|
||||
Ogre::TEX_TYPE_2D, it->getWidth(), it->getHeight(), 0, it->format);
|
||||
|
||||
Ogre::DataStreamPtr stream(new Ogre::MemoryDataStream(it->data, it->getWidth()*it->getHeight()*Ogre::PixelUtil::getNumElemBytes(it->format), true));
|
||||
map->loadRawData(stream, it->getWidth(), it->getHeight(), it->format);
|
||||
blendTextures.push_back(map);
|
||||
}
|
||||
|
||||
mMaterialGenerator->setLayerList(collection.mLayers);
|
||||
mMaterialGenerator->setBlendmapList(blendTextures);
|
||||
}
|
||||
|
||||
void QuadTreeNode::loadMaterials()
|
||||
{
|
||||
if (isDummy())
|
||||
return;
|
||||
|
||||
// Load children first since we depend on them when creating a composite map
|
||||
if (hasChildren())
|
||||
{
|
||||
for (int i=0; i<4; ++i)
|
||||
mChildren[i]->loadMaterials();
|
||||
}
|
||||
|
||||
if (mChunk)
|
||||
{
|
||||
if (mSize == 1)
|
||||
{
|
||||
mChunk->setMaterial(mMaterialGenerator->generate());
|
||||
}
|
||||
else
|
||||
{
|
||||
ensureCompositeMap();
|
||||
mMaterialGenerator->setCompositeMap(mCompositeMap->getName());
|
||||
mChunk->setMaterial(mMaterialGenerator->generateForCompositeMap());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void QuadTreeNode::prepareForCompositeMap(Ogre::TRect<float> area)
|
||||
{
|
||||
Ogre::SceneManager* sceneMgr = mTerrain->getCompositeMapSceneManager();
|
||||
|
||||
if (mIsDummy)
|
||||
{
|
||||
// TODO - store this default material somewhere instead of creating one for each empty cell
|
||||
MaterialGenerator matGen;
|
||||
matGen.enableShaders(mTerrain->getShadersEnabled());
|
||||
std::vector<LayerInfo> layer;
|
||||
layer.push_back(mTerrain->getStorage()->getDefaultLayer());
|
||||
matGen.setLayerList(layer);
|
||||
makeQuad(sceneMgr, area.left, area.top, area.right, area.bottom, matGen.generateForCompositeMapRTT());
|
||||
return;
|
||||
}
|
||||
if (mSize > 1)
|
||||
{
|
||||
assert(hasChildren());
|
||||
|
||||
// 0,0 -------- 1,0
|
||||
// | | |
|
||||
// |-----|------|
|
||||
// | | |
|
||||
// 0,1 -------- 1,1
|
||||
|
||||
float halfW = area.width()/2.f;
|
||||
float halfH = area.height()/2.f;
|
||||
mChildren[NW]->prepareForCompositeMap(Ogre::TRect<float>(area.left, area.top, area.right-halfW, area.bottom-halfH));
|
||||
mChildren[NE]->prepareForCompositeMap(Ogre::TRect<float>(area.left+halfW, area.top, area.right, area.bottom-halfH));
|
||||
mChildren[SW]->prepareForCompositeMap(Ogre::TRect<float>(area.left, area.top+halfH, area.right-halfW, area.bottom));
|
||||
mChildren[SE]->prepareForCompositeMap(Ogre::TRect<float>(area.left+halfW, area.top+halfH, area.right, area.bottom));
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: when to destroy?
|
||||
Ogre::MaterialPtr material = mMaterialGenerator->generateForCompositeMapRTT();
|
||||
makeQuad(sceneMgr, area.left, area.top, area.right, area.bottom, material);
|
||||
}
|
||||
}
|
||||
|
||||
void QuadTreeNode::ensureCompositeMap()
|
||||
{
|
||||
if (!mCompositeMap.isNull())
|
||||
return;
|
||||
|
||||
static int i=0;
|
||||
std::stringstream name;
|
||||
name << "terrain/comp" << i++;
|
||||
|
||||
const int size = 128;
|
||||
mCompositeMap = Ogre::TextureManager::getSingleton().createManual(
|
||||
name.str(), Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME,
|
||||
Ogre::TEX_TYPE_2D, size, size, Ogre::MIP_DEFAULT, Ogre::PF_A8B8G8R8);
|
||||
|
||||
// Create quads for each cell
|
||||
prepareForCompositeMap(Ogre::TRect<float>(0,0,1,1));
|
||||
|
||||
mTerrain->renderCompositeMap(mCompositeMap);
|
||||
|
||||
mTerrain->clearCompositeMapSceneManager();
|
||||
|
||||
}
|
||||
|
||||
void QuadTreeNode::applyMaterials()
|
||||
{
|
||||
if (mChunk)
|
||||
{
|
||||
mMaterialGenerator->enableShadows(mTerrain->getShadowsEnabled());
|
||||
mMaterialGenerator->enableSplitShadows(mTerrain->getSplitShadowsEnabled());
|
||||
if (mSize <= 1)
|
||||
mChunk->setMaterial(mMaterialGenerator->generate());
|
||||
else
|
||||
mChunk->setMaterial(mMaterialGenerator->generateForCompositeMap());
|
||||
}
|
||||
if (hasChildren())
|
||||
for (int i=0; i<4; ++i)
|
||||
mChildren[i]->applyMaterials();
|
||||
}
|
|
@ -1,189 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2015 scrawl <scrawl@baseoftrash.de>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
#ifndef COMPONENTS_TERRAIN_QUADTREENODE_H
|
||||
#define COMPONENTS_TERRAIN_QUADTREENODE_H
|
||||
|
||||
#include <OgreAxisAlignedBox.h>
|
||||
#include <OgreVector2.h>
|
||||
#include <OgreTexture.h>
|
||||
|
||||
#include "defs.hpp"
|
||||
|
||||
namespace Ogre
|
||||
{
|
||||
class Rectangle2D;
|
||||
}
|
||||
|
||||
namespace Terrain
|
||||
{
|
||||
class DefaultWorld;
|
||||
class Chunk;
|
||||
class MaterialGenerator;
|
||||
struct LoadResponseData;
|
||||
|
||||
enum ChildDirection
|
||||
{
|
||||
NW = 0,
|
||||
NE = 1,
|
||||
SW = 2,
|
||||
SE = 3,
|
||||
Root
|
||||
};
|
||||
|
||||
enum LoadState
|
||||
{
|
||||
LS_Unloaded,
|
||||
LS_Loading,
|
||||
LS_Loaded
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief A node in the quad tree for our terrain. Depending on LOD,
|
||||
* a node can either choose to render itself in one batch (merging its children),
|
||||
* or delegate the render process to its children, rendering each child in at least one batch.
|
||||
*/
|
||||
class QuadTreeNode
|
||||
{
|
||||
public:
|
||||
/// @param terrain
|
||||
/// @param dir relative to parent, or Root if we are the root node
|
||||
/// @param size size (in *cell* units!)
|
||||
/// @param center center (in *cell* units!)
|
||||
/// @param parent parent node
|
||||
QuadTreeNode (DefaultWorld* terrain, ChildDirection dir, float size, const Ogre::Vector2& center, QuadTreeNode* parent);
|
||||
~QuadTreeNode();
|
||||
|
||||
/// Rebuild all materials
|
||||
void applyMaterials();
|
||||
|
||||
/// Initialize neighbours - do this after the quadtree is built
|
||||
void initNeighbours();
|
||||
/// Initialize bounding boxes of non-leafs by merging children bounding boxes.
|
||||
/// Do this after the quadtree is built - note that leaf bounding boxes
|
||||
/// need to be set first via setBoundingBox!
|
||||
void initAabb();
|
||||
|
||||
/// @note takes ownership of \a child
|
||||
void createChild (ChildDirection id, float size, const Ogre::Vector2& center);
|
||||
|
||||
/// Mark this node as a dummy node. This can happen if the terrain size isn't a power of two.
|
||||
/// For the QuadTree to work, we need to round the size up to a power of two, which means we'll
|
||||
/// end up with empty nodes that don't actually render anything.
|
||||
void markAsDummy() { mIsDummy = true; }
|
||||
bool isDummy() { return mIsDummy; }
|
||||
|
||||
QuadTreeNode* getParent() { return mParent; }
|
||||
|
||||
Ogre::SceneNode* getSceneNode() { return mSceneNode; }
|
||||
|
||||
int getSize() { return static_cast<int>(mSize); }
|
||||
Ogre::Vector2 getCenter() { return mCenter; }
|
||||
|
||||
bool hasChildren() { return mChildren[0] != 0; }
|
||||
QuadTreeNode* getChild(ChildDirection dir) { return mChildren[dir]; }
|
||||
|
||||
/// Get neighbour node in this direction
|
||||
QuadTreeNode* getNeighbour (Direction dir);
|
||||
|
||||
/// Returns our direction relative to the parent node, or Root if we are the root node.
|
||||
ChildDirection getDirection() { return mDirection; }
|
||||
|
||||
/// Set bounding box in local coordinates. Should be done at load time for leaf nodes.
|
||||
/// Other nodes can merge AABB of child nodes.
|
||||
void setBoundingBox (const Ogre::AxisAlignedBox& box);
|
||||
|
||||
/// Get bounding box in local coordinates
|
||||
const Ogre::AxisAlignedBox& getBoundingBox();
|
||||
|
||||
const Ogre::AxisAlignedBox& getWorldBoundingBox();
|
||||
|
||||
DefaultWorld* getTerrain() { return mTerrain; }
|
||||
|
||||
/// Adjust LODs for the given camera position, possibly splitting up chunks or merging them.
|
||||
/// @return Did we (or all of our children) choose to render?
|
||||
bool update (const Ogre::Vector3& cameraPos);
|
||||
|
||||
/// Adjust index buffers of chunks to stitch together chunks of different LOD, so that cracks are avoided.
|
||||
/// Call after QuadTreeNode::update!
|
||||
void updateIndexBuffers();
|
||||
|
||||
/// Destroy chunks rendered by this node *and* its children (if param is true)
|
||||
void destroyChunks(bool children);
|
||||
|
||||
/// Get the effective LOD level if this node was rendered in one chunk
|
||||
/// with Storage::getCellVertices^2 vertices
|
||||
size_t getNativeLodLevel() { return mLodLevel; }
|
||||
|
||||
/// Get the effective current LOD level used by the chunk rendering this node
|
||||
size_t getActualLodLevel();
|
||||
|
||||
/// Is this node currently configured to render itself?
|
||||
bool hasChunk();
|
||||
|
||||
/// Add a textured quad to a specific 2d area in the composite map scenemanager.
|
||||
/// Only nodes with size <= 1 can be rendered with alpha blending, so larger nodes will simply
|
||||
/// call this method on their children.
|
||||
/// @note Do not call this before World::areLayersLoaded() == true
|
||||
/// @param area area in image space to put the quad
|
||||
void prepareForCompositeMap(Ogre::TRect<float> area);
|
||||
|
||||
/// Create a chunk for this node from the given data.
|
||||
void load (const LoadResponseData& data);
|
||||
void unload(bool recursive=false);
|
||||
void loadLayers (const LayerCollection& collection);
|
||||
/// This is recursive! Call it once on the root node after all leafs have loaded layers.
|
||||
void loadMaterials();
|
||||
|
||||
LoadState getLoadState() { return mLoadState; }
|
||||
|
||||
private:
|
||||
// Stored here for convenience in case we need layer list again
|
||||
MaterialGenerator* mMaterialGenerator;
|
||||
|
||||
LoadState mLoadState;
|
||||
|
||||
bool mIsDummy;
|
||||
float mSize;
|
||||
size_t mLodLevel; // LOD if we were to render this node in one chunk
|
||||
Ogre::AxisAlignedBox mBounds;
|
||||
Ogre::AxisAlignedBox mWorldBounds;
|
||||
ChildDirection mDirection;
|
||||
Ogre::Vector2 mCenter;
|
||||
|
||||
Ogre::SceneNode* mSceneNode;
|
||||
|
||||
QuadTreeNode* mParent;
|
||||
QuadTreeNode* mChildren[4];
|
||||
QuadTreeNode* mNeighbours[4];
|
||||
|
||||
Chunk* mChunk;
|
||||
|
||||
DefaultWorld* mTerrain;
|
||||
|
||||
Ogre::TexturePtr mCompositeMap;
|
||||
|
||||
void ensureCompositeMap();
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
|
@ -19,3 +19,5 @@
|
|||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "storage.hpp"
|
||||
|
|
|
@ -22,10 +22,20 @@
|
|||
#ifndef COMPONENTS_TERRAIN_STORAGE_H
|
||||
#define COMPONENTS_TERRAIN_STORAGE_H
|
||||
|
||||
#include <OgreHardwareVertexBuffer.h>
|
||||
#include <vector>
|
||||
|
||||
#include <osg/Vec2f>
|
||||
#include <osg/Vec3f>
|
||||
#include <osg/ref_ptr>
|
||||
#include <osg/Array>
|
||||
|
||||
#include "defs.hpp"
|
||||
|
||||
namespace osg
|
||||
{
|
||||
class Image;
|
||||
}
|
||||
|
||||
namespace Terrain
|
||||
{
|
||||
/// We keep storage of terrain data abstract here since we need different implementations for game and editor
|
||||
|
@ -46,7 +56,7 @@ namespace Terrain
|
|||
/// @param min min height will be stored here
|
||||
/// @param max max height will be stored here
|
||||
/// @return true if there was data available for this terrain chunk
|
||||
virtual bool getMinMaxHeights (float size, const Ogre::Vector2& center, float& min, float& max) = 0;
|
||||
virtual bool getMinMaxHeights (float size, const osg::Vec2f& center, float& min, float& max) = 0;
|
||||
|
||||
/// Fill vertex buffers for a terrain chunk.
|
||||
/// @note May be called from background threads. Make sure to only call thread-safe functions from here!
|
||||
|
@ -59,11 +69,12 @@ namespace Terrain
|
|||
/// @param positions buffer to write vertices
|
||||
/// @param normals buffer to write vertex normals
|
||||
/// @param colours buffer to write vertex colours
|
||||
virtual void fillVertexBuffers (int lodLevel, float size, const Ogre::Vector2& center, Terrain::Alignment align,
|
||||
std::vector<float>& positions,
|
||||
std::vector<float>& normals,
|
||||
std::vector<Ogre::uint8>& colours) = 0;
|
||||
virtual void fillVertexBuffers (int lodLevel, float size, const osg::Vec2f& center,
|
||||
osg::ref_ptr<osg::Vec3Array> positions,
|
||||
osg::ref_ptr<osg::Vec3Array> normals,
|
||||
osg::ref_ptr<osg::Vec4Array> colours) = 0;
|
||||
|
||||
typedef std::vector<osg::ref_ptr<osg::Image> > ImageVector;
|
||||
/// Create textures holding layer blend values for a terrain chunk.
|
||||
/// @note The terrain chunk shouldn't be larger than one cell since otherwise we might
|
||||
/// have to do a ridiculous amount of different layers. For larger chunks, composite maps should be used.
|
||||
|
@ -75,23 +86,11 @@ namespace Terrain
|
|||
/// can utilize packing, FFP can't.
|
||||
/// @param blendmaps created blendmaps will be written here
|
||||
/// @param layerList names of the layer textures used will be written here
|
||||
virtual void getBlendmaps (float chunkSize, const Ogre::Vector2& chunkCenter, bool pack,
|
||||
std::vector<Ogre::PixelBox>& blendmaps,
|
||||
virtual void getBlendmaps (float chunkSize, const osg::Vec2f& chunkCenter, bool pack,
|
||||
ImageVector& blendmaps,
|
||||
std::vector<LayerInfo>& layerList) = 0;
|
||||
|
||||
/// Retrieve pixel data for textures holding layer blend values for terrain chunks and layer texture information.
|
||||
/// This variant is provided to eliminate the overhead of virtual function calls when retrieving a large number of blendmaps at once.
|
||||
/// @note The terrain chunks shouldn't be larger than one cell since otherwise we might
|
||||
/// have to do a ridiculous amount of different layers. For larger chunks, composite maps should be used.
|
||||
/// @note May be called from background threads. Make sure to only call thread-safe functions from here!
|
||||
/// @param nodes A collection of nodes for which to retrieve the aforementioned data
|
||||
/// @param out Output vector
|
||||
/// @param pack Whether to pack blend values for up to 4 layers into one texture (one in each channel) -
|
||||
/// otherwise, each texture contains blend values for one layer only. Shader-based rendering
|
||||
/// can utilize packing, FFP can't.
|
||||
virtual void getBlendmaps (const std::vector<QuadTreeNode*>& nodes, std::vector<LayerCollection>& out, bool pack) = 0;
|
||||
|
||||
virtual float getHeightAt (const Ogre::Vector3& worldPos) = 0;
|
||||
virtual float getHeightAt (const osg::Vec3f& worldPos) = 0;
|
||||
|
||||
virtual LayerInfo getDefaultLayer() = 0;
|
||||
|
||||
|
|
|
@ -19,22 +19,54 @@
|
|||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "terraingrid.hpp"
|
||||
|
||||
#include <OgreSceneManager.h>
|
||||
#include <OgreSceneNode.h>
|
||||
#include <OgreAxisAlignedBox.h>
|
||||
#include <memory>
|
||||
|
||||
#include "chunk.hpp"
|
||||
#include <components/resource/resourcesystem.hpp>
|
||||
#include <components/resource/texturemanager.hpp>
|
||||
|
||||
#include <components/sceneutil/lightmanager.hpp>
|
||||
|
||||
#include <osg/PositionAttitudeTransform>
|
||||
#include <osg/Geometry>
|
||||
#include <osg/Geode>
|
||||
|
||||
#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(Ogre::SceneManager *sceneMgr, Terrain::Storage *storage, int visibilityFlags, bool shaders, Terrain::Alignment align)
|
||||
: Terrain::World(sceneMgr, storage, visibilityFlags, shaders, align)
|
||||
, mVisible(true)
|
||||
TerrainGrid::TerrainGrid(osg::Group* parent, Resource::ResourceSystem* resourceSystem, osgUtil::IncrementalCompileOperation* ico,
|
||||
Storage* storage, int nodeMask)
|
||||
: Terrain::World(parent, resourceSystem, ico, storage, nodeMask)
|
||||
{
|
||||
mRootNode = mSceneMgr->getRootSceneNode()->createChildSceneNode();
|
||||
}
|
||||
|
||||
TerrainGrid::~TerrainGrid()
|
||||
|
@ -43,81 +75,105 @@ TerrainGrid::~TerrainGrid()
|
|||
{
|
||||
unloadCell(mGrid.begin()->first.first, mGrid.begin()->first.second);
|
||||
}
|
||||
|
||||
mSceneMgr->destroySceneNode(mRootNode);
|
||||
}
|
||||
|
||||
void TerrainGrid::update(const Ogre::Vector3 &cameraPos)
|
||||
class GridElement
|
||||
{
|
||||
}
|
||||
public:
|
||||
osg::ref_ptr<osg::PositionAttitudeTransform> mNode;
|
||||
};
|
||||
|
||||
void TerrainGrid::loadCell(int x, int y)
|
||||
{
|
||||
if (mGrid.find(std::make_pair(x, y)) != mGrid.end())
|
||||
return; // already loaded
|
||||
|
||||
Ogre::Vector2 center(x+0.5f, y+0.5f);
|
||||
osg::Vec2f center(x+0.5f, y+0.5f);
|
||||
float minH, maxH;
|
||||
if (!mStorage->getMinMaxHeights(1, center, minH, maxH))
|
||||
return; // no terrain defined
|
||||
|
||||
Ogre::Vector3 min (-0.5f*mStorage->getCellWorldSize(),
|
||||
-0.5f*mStorage->getCellWorldSize(),
|
||||
minH);
|
||||
Ogre::Vector3 max (0.5f*mStorage->getCellWorldSize(),
|
||||
std::auto_ptr<GridElement> element (new GridElement);
|
||||
|
||||
osg::Vec2f worldCenter = center*mStorage->getCellWorldSize();
|
||||
element->mNode = new osg::PositionAttitudeTransform;
|
||||
element->mNode->setPosition(osg::Vec3f(worldCenter.x(), worldCenter.y(), 0.f));
|
||||
mTerrainRoot->addChild(element->mNode);
|
||||
|
||||
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);
|
||||
|
||||
mStorage->fillVertexBuffers(0, 1, center, 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(),
|
||||
-0.5f*mStorage->getCellWorldSize(),
|
||||
minH);
|
||||
osg::Vec3f max (0.5f*mStorage->getCellWorldSize(),
|
||||
0.5f*mStorage->getCellWorldSize(),
|
||||
maxH);
|
||||
osg::BoundingBox bounds(min, max);
|
||||
geometry->setComputeBoundingBoxCallback(new StaticBoundingBoxCallback(bounds));
|
||||
|
||||
Ogre::AxisAlignedBox bounds(min, max);
|
||||
osg::ref_ptr<osg::Geode> geode (new osg::Geode);
|
||||
geode->addDrawable(geometry);
|
||||
|
||||
GridElement element;
|
||||
std::vector<LayerInfo> layerList;
|
||||
std::vector<osg::ref_ptr<osg::Image> > blendmaps;
|
||||
mStorage->getBlendmaps(1.f, center, false, blendmaps, layerList);
|
||||
|
||||
Ogre::Vector2 worldCenter = center*mStorage->getCellWorldSize();
|
||||
element.mSceneNode = mRootNode->createChildSceneNode(Ogre::Vector3(worldCenter.x, worldCenter.y, 0));
|
||||
// For compiling textures, I don't think the osgFX::Effect does it correctly
|
||||
osg::ref_ptr<osg::Node> textureCompileDummy (new osg::Node);
|
||||
|
||||
std::vector<float> positions;
|
||||
std::vector<float> normals;
|
||||
std::vector<Ogre::uint8> colours;
|
||||
mStorage->fillVertexBuffers(0, 1, center, mAlign, positions, normals, colours);
|
||||
|
||||
element.mChunk = new Terrain::Chunk(mCache.getUVBuffer(), bounds, positions, normals, colours);
|
||||
element.mChunk->setIndexBuffer(mCache.getIndexBuffer(0));
|
||||
element.mChunk->setVisibilityFlags(mVisibilityFlags);
|
||||
element.mChunk->setCastShadows(true);
|
||||
|
||||
std::vector<Ogre::PixelBox> blendmaps;
|
||||
std::vector<Terrain::LayerInfo> layerList;
|
||||
mStorage->getBlendmaps(1, center, mShaders, blendmaps, layerList);
|
||||
|
||||
element.mMaterialGenerator.setLayerList(layerList);
|
||||
|
||||
// upload blendmaps to GPU
|
||||
std::vector<Ogre::TexturePtr> blendTextures;
|
||||
for (std::vector<Ogre::PixelBox>::const_iterator it = blendmaps.begin(); it != blendmaps.end(); ++it)
|
||||
std::vector<osg::ref_ptr<osg::Texture2D> > layerTextures;
|
||||
for (std::vector<LayerInfo>::const_iterator it = layerList.begin(); it != layerList.end(); ++it)
|
||||
{
|
||||
static int count=0;
|
||||
Ogre::TexturePtr map = Ogre::TextureManager::getSingleton().createManual("terrain/blend/"
|
||||
+ Ogre::StringConverter::toString(count++), Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME,
|
||||
Ogre::TEX_TYPE_2D, it->getWidth(), it->getHeight(), 0, it->format);
|
||||
|
||||
Ogre::DataStreamPtr stream(new Ogre::MemoryDataStream(it->data, it->getWidth()*it->getHeight()*Ogre::PixelUtil::getNumElemBytes(it->format), true));
|
||||
map->loadRawData(stream, it->getWidth(), it->getHeight(), it->format);
|
||||
blendTextures.push_back(map);
|
||||
layerTextures.push_back(mResourceSystem->getTextureManager()->getTexture2D(it->mDiffuseMap, osg::Texture::REPEAT, osg::Texture::REPEAT));
|
||||
textureCompileDummy->getOrCreateStateSet()->setTextureAttributeAndModes(0, layerTextures.back());
|
||||
}
|
||||
|
||||
element.mMaterialGenerator.setBlendmapList(blendTextures);
|
||||
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->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR);
|
||||
texture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR);
|
||||
texture->setResizeNonPowerOfTwoHint(false);
|
||||
blendmapTextures.push_back(texture);
|
||||
|
||||
element.mSceneNode->attachObject(element.mChunk);
|
||||
updateMaterial(element);
|
||||
textureCompileDummy->getOrCreateStateSet()->setTextureAttributeAndModes(0, layerTextures.back());
|
||||
}
|
||||
|
||||
mGrid[std::make_pair(x,y)] = element;
|
||||
}
|
||||
for (unsigned int i=0; i<blendmapTextures.size()+1; ++i)
|
||||
geometry->setTexCoordArray(i, mCache.getUVBuffer());
|
||||
|
||||
void TerrainGrid::updateMaterial(GridElement &element)
|
||||
{
|
||||
element.mMaterialGenerator.enableShadows(getShadowsEnabled());
|
||||
element.mMaterialGenerator.enableSplitShadows(getSplitShadowsEnabled());
|
||||
element.mChunk->setMaterial(element.mMaterialGenerator.generate());
|
||||
osg::ref_ptr<osgFX::Effect> effect (new Terrain::Effect(layerTextures, blendmapTextures));
|
||||
|
||||
effect->addCullCallback(new SceneUtil::LightListCallback);
|
||||
|
||||
effect->addChild(geode);
|
||||
element->mNode->addChild(effect);
|
||||
|
||||
if (mIncrementalCompileOperation)
|
||||
{
|
||||
mIncrementalCompileOperation->add(geode);
|
||||
mIncrementalCompileOperation->add(textureCompileDummy);
|
||||
}
|
||||
|
||||
mGrid[std::make_pair(x,y)] = element.release();
|
||||
}
|
||||
|
||||
void TerrainGrid::unloadCell(int x, int y)
|
||||
|
@ -126,60 +182,11 @@ void TerrainGrid::unloadCell(int x, int y)
|
|||
if (it == mGrid.end())
|
||||
return;
|
||||
|
||||
GridElement& element = it->second;
|
||||
delete element.mChunk;
|
||||
element.mChunk = NULL;
|
||||
|
||||
const std::vector<Ogre::TexturePtr>& blendmaps = element.mMaterialGenerator.getBlendmapList();
|
||||
for (std::vector<Ogre::TexturePtr>::const_iterator it = blendmaps.begin(); it != blendmaps.end(); ++it)
|
||||
Ogre::TextureManager::getSingleton().remove((*it)->getName());
|
||||
|
||||
mSceneMgr->destroySceneNode(element.mSceneNode);
|
||||
element.mSceneNode = NULL;
|
||||
GridElement* element = it->second;
|
||||
mTerrainRoot->removeChild(element->mNode);
|
||||
delete element;
|
||||
|
||||
mGrid.erase(it);
|
||||
}
|
||||
|
||||
void TerrainGrid::applyMaterials(bool shadows, bool splitShadows)
|
||||
{
|
||||
mShadows = shadows;
|
||||
mSplitShadows = splitShadows;
|
||||
for (Grid::iterator it = mGrid.begin(); it != mGrid.end(); ++it)
|
||||
{
|
||||
updateMaterial(it->second);
|
||||
}
|
||||
}
|
||||
|
||||
bool TerrainGrid::getVisible()
|
||||
{
|
||||
return mVisible;
|
||||
}
|
||||
|
||||
void TerrainGrid::setVisible(bool visible)
|
||||
{
|
||||
mVisible = visible;
|
||||
mRootNode->setVisible(visible);
|
||||
}
|
||||
|
||||
Ogre::AxisAlignedBox TerrainGrid::getWorldBoundingBox (const Ogre::Vector2& center)
|
||||
{
|
||||
int cellX = static_cast<int>(std::floor(center.x));
|
||||
int cellY = static_cast<int>(std::floor(center.y));
|
||||
|
||||
Grid::iterator it = mGrid.find(std::make_pair(cellX, cellY));
|
||||
if (it == mGrid.end())
|
||||
return Ogre::AxisAlignedBox::BOX_NULL;
|
||||
|
||||
Terrain::Chunk* chunk = it->second.mChunk;
|
||||
Ogre::SceneNode* node = it->second.mSceneNode;
|
||||
Ogre::AxisAlignedBox box = chunk->getBoundingBox();
|
||||
box = Ogre::AxisAlignedBox(box.getMinimum() + node->getPosition(), box.getMaximum() + node->getPosition());
|
||||
return box;
|
||||
}
|
||||
|
||||
void TerrainGrid::syncLoad()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -27,64 +27,23 @@
|
|||
|
||||
namespace Terrain
|
||||
{
|
||||
class Chunk;
|
||||
|
||||
struct GridElement
|
||||
{
|
||||
Ogre::SceneNode* mSceneNode;
|
||||
|
||||
Terrain::MaterialGenerator mMaterialGenerator;
|
||||
|
||||
Terrain::Chunk* mChunk;
|
||||
};
|
||||
class GridElement;
|
||||
|
||||
/// @brief Simple terrain implementation that loads cells in a grid, with no LOD
|
||||
class TerrainGrid : public Terrain::World
|
||||
{
|
||||
public:
|
||||
/// @note takes ownership of \a storage
|
||||
/// @param sceneMgr scene manager to use
|
||||
/// @param storage Storage instance to get terrain data from (heights, normals, colors, textures..)
|
||||
/// @param visbilityFlags visibility flags for the created meshes
|
||||
/// @param shaders Whether to use splatting shader, or multi-pass fixed function splatting. Shader is usually
|
||||
/// faster so this is just here for compatibility.
|
||||
/// @param align The align of the terrain, see Alignment enum
|
||||
TerrainGrid(Ogre::SceneManager* sceneMgr,
|
||||
Terrain::Storage* storage, int visibilityFlags, bool shaders, Terrain::Alignment align);
|
||||
TerrainGrid(osg::Group* parent, Resource::ResourceSystem* resourceSystem, osgUtil::IncrementalCompileOperation* ico,
|
||||
Storage* storage, int nodeMask);
|
||||
~TerrainGrid();
|
||||
|
||||
/// Update chunk LODs according to this camera position
|
||||
virtual void update (const Ogre::Vector3& cameraPos);
|
||||
|
||||
virtual void loadCell(int x, int y);
|
||||
virtual void unloadCell(int x, int y);
|
||||
|
||||
/// Get the world bounding box of a chunk of terrain centered at \a center
|
||||
virtual Ogre::AxisAlignedBox getWorldBoundingBox (const Ogre::Vector2& center);
|
||||
|
||||
/// Show or hide the whole terrain
|
||||
/// @note this setting may be invalidated once you call Terrain::update, so do not call it while the terrain should be hidden
|
||||
virtual void setVisible(bool visible);
|
||||
virtual bool getVisible();
|
||||
|
||||
/// Recreate materials used by terrain chunks. This should be called whenever settings of
|
||||
/// the material factory are changed. (Relying on the factory to update those materials is not
|
||||
/// enough, since turning a feature on/off can change the number of texture units available for layer/blend
|
||||
/// textures, and to properly respond to this we may need to change the structure of the material, such as
|
||||
/// adding or removing passes. This can only be achieved by a full rebuild.)
|
||||
virtual void applyMaterials(bool shadows, bool splitShadows);
|
||||
|
||||
/// Wait until all background loading is complete.
|
||||
virtual void syncLoad();
|
||||
|
||||
private:
|
||||
void updateMaterial (GridElement& element);
|
||||
|
||||
typedef std::map<std::pair<int, int>, GridElement> Grid;
|
||||
typedef std::map<std::pair<int, int>, GridElement*> Grid;
|
||||
Grid mGrid;
|
||||
|
||||
Ogre::SceneNode* mRootNode;
|
||||
bool mVisible;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -21,63 +21,39 @@
|
|||
*/
|
||||
#include "world.hpp"
|
||||
|
||||
#include <OgreAxisAlignedBox.h>
|
||||
#include <osg/Group>
|
||||
#include <osgUtil/IncrementalCompileOperation>
|
||||
|
||||
#include "storage.hpp"
|
||||
|
||||
namespace Terrain
|
||||
{
|
||||
|
||||
World::World(Ogre::SceneManager* sceneMgr,
|
||||
Storage* storage, int visibilityFlags, bool shaders, Alignment align)
|
||||
: mShaders(shaders)
|
||||
, mShadows(false)
|
||||
, mSplitShadows(false)
|
||||
, mAlign(align)
|
||||
, mStorage(storage)
|
||||
, mVisibilityFlags(visibilityFlags)
|
||||
, mSceneMgr(sceneMgr)
|
||||
World::World(osg::Group* parent, Resource::ResourceSystem* resourceSystem, osgUtil::IncrementalCompileOperation* ico,
|
||||
Storage* storage, int nodeMask)
|
||||
: mStorage(storage)
|
||||
, mCache(storage->getCellVertices())
|
||||
, mParent(parent)
|
||||
, mResourceSystem(resourceSystem)
|
||||
, mIncrementalCompileOperation(ico)
|
||||
{
|
||||
mTerrainRoot = new osg::Group;
|
||||
mTerrainRoot->setNodeMask(nodeMask);
|
||||
mTerrainRoot->getOrCreateStateSet()->setRenderingHint(osg::StateSet::OPAQUE_BIN);
|
||||
|
||||
mParent->addChild(mTerrainRoot);
|
||||
}
|
||||
|
||||
World::~World()
|
||||
{
|
||||
mParent->removeChild(mTerrainRoot);
|
||||
|
||||
delete mStorage;
|
||||
}
|
||||
|
||||
float World::getHeightAt(const Ogre::Vector3 &worldPos)
|
||||
float World::getHeightAt(const osg::Vec3f &worldPos)
|
||||
{
|
||||
return mStorage->getHeightAt(worldPos);
|
||||
}
|
||||
|
||||
void World::convertPosition(float &x, float &y, float &z)
|
||||
{
|
||||
Terrain::convertPosition(mAlign, x, y, z);
|
||||
}
|
||||
|
||||
void World::convertPosition(Ogre::Vector3 &pos)
|
||||
{
|
||||
convertPosition(pos.x, pos.y, pos.z);
|
||||
}
|
||||
|
||||
void World::convertBounds(Ogre::AxisAlignedBox& bounds)
|
||||
{
|
||||
switch (mAlign)
|
||||
{
|
||||
case Align_XY:
|
||||
return;
|
||||
case Align_XZ:
|
||||
convertPosition(bounds.getMinimum());
|
||||
convertPosition(bounds.getMaximum());
|
||||
// Because we changed sign of Z
|
||||
std::swap(bounds.getMinimum().z, bounds.getMaximum().z);
|
||||
return;
|
||||
case Align_YZ:
|
||||
convertPosition(bounds.getMinimum());
|
||||
convertPosition(bounds.getMaximum());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -22,14 +22,24 @@
|
|||
#ifndef COMPONENTS_TERRAIN_WORLD_H
|
||||
#define COMPONENTS_TERRAIN_WORLD_H
|
||||
|
||||
#include <OgreVector3.h>
|
||||
#include <osg/ref_ptr>
|
||||
|
||||
#include "defs.hpp"
|
||||
#include "buffercache.hpp"
|
||||
|
||||
namespace Ogre
|
||||
namespace osg
|
||||
{
|
||||
class SceneManager;
|
||||
class Group;
|
||||
}
|
||||
|
||||
namespace osgUtil
|
||||
{
|
||||
class IncrementalCompileOperation;
|
||||
}
|
||||
|
||||
namespace Resource
|
||||
{
|
||||
class ResourceSystem;
|
||||
}
|
||||
|
||||
namespace Terrain
|
||||
|
@ -44,79 +54,31 @@ namespace Terrain
|
|||
{
|
||||
public:
|
||||
/// @note takes ownership of \a storage
|
||||
/// @param sceneMgr scene manager to use
|
||||
/// @param storage Storage instance to get terrain data from (heights, normals, colors, textures..)
|
||||
/// @param visbilityFlags visibility flags for the created meshes
|
||||
/// @param shaders Whether to use splatting shader, or multi-pass fixed function splatting. Shader is usually
|
||||
/// faster so this is just here for compatibility.
|
||||
/// @param align The align of the terrain, see Alignment enum
|
||||
World(Ogre::SceneManager* sceneMgr,
|
||||
Storage* storage, int visiblityFlags, bool shaders, Alignment align);
|
||||
/// @param nodeMask mask for the terrain root
|
||||
World(osg::Group* parent, Resource::ResourceSystem* resourceSystem, osgUtil::IncrementalCompileOperation* ico,
|
||||
Storage* storage, int nodeMask);
|
||||
virtual ~World();
|
||||
|
||||
bool getShadersEnabled() { return mShaders; }
|
||||
bool getShadowsEnabled() { return mShadows; }
|
||||
bool getSplitShadowsEnabled() { return mSplitShadows; }
|
||||
|
||||
float getHeightAt (const Ogre::Vector3& worldPos);
|
||||
|
||||
/// Update chunk LODs according to this camera position
|
||||
/// @note Calling this method might lead to composite textures being rendered, so it is best
|
||||
/// not to call it when render commands are still queued, since that would cause a flush.
|
||||
virtual void update (const Ogre::Vector3& cameraPos) = 0;
|
||||
float getHeightAt (const osg::Vec3f& worldPos);
|
||||
|
||||
// This is only a hint and may be ignored by the implementation.
|
||||
virtual void loadCell(int x, int y) {}
|
||||
virtual void unloadCell(int x, int y) {}
|
||||
|
||||
/// Get the world bounding box of a chunk of terrain centered at \a center
|
||||
virtual Ogre::AxisAlignedBox getWorldBoundingBox (const Ogre::Vector2& center) = 0;
|
||||
|
||||
Ogre::SceneManager* getSceneManager() { return mSceneMgr; }
|
||||
|
||||
Storage* getStorage() { return mStorage; }
|
||||
|
||||
/// Show or hide the whole terrain
|
||||
/// @note this setting may be invalidated once you call Terrain::update, so do not call it while the terrain should be hidden
|
||||
virtual void setVisible(bool visible) = 0;
|
||||
virtual bool getVisible() = 0;
|
||||
|
||||
/// Recreate materials used by terrain chunks. This should be called whenever settings of
|
||||
/// the material factory are changed. (Relying on the factory to update those materials is not
|
||||
/// enough, since turning a feature on/off can change the number of texture units available for layer/blend
|
||||
/// textures, and to properly respond to this we may need to change the structure of the material, such as
|
||||
/// adding or removing passes. This can only be achieved by a full rebuild.)
|
||||
virtual void applyMaterials(bool shadows, bool splitShadows) = 0;
|
||||
|
||||
int getVisibilityFlags() { return mVisibilityFlags; }
|
||||
|
||||
Alignment getAlign() { return mAlign; }
|
||||
|
||||
/// Wait until all background loading is complete.
|
||||
virtual void syncLoad() {}
|
||||
|
||||
protected:
|
||||
bool mShaders;
|
||||
bool mShadows;
|
||||
bool mSplitShadows;
|
||||
Alignment mAlign;
|
||||
|
||||
Storage* mStorage;
|
||||
|
||||
int mVisibilityFlags;
|
||||
|
||||
Ogre::SceneManager* mSceneMgr;
|
||||
|
||||
BufferCache mCache;
|
||||
|
||||
public:
|
||||
// ----INTERNAL----
|
||||
BufferCache& getBufferCache() { return mCache; }
|
||||
osg::ref_ptr<osg::Group> mParent;
|
||||
osg::ref_ptr<osg::Group> mTerrainRoot;
|
||||
|
||||
// Convert the given position from Z-up align, i.e. Align_XY to the wanted align set in mAlign
|
||||
void convertPosition (float& x, float& y, float& z);
|
||||
void convertPosition (Ogre::Vector3& pos);
|
||||
void convertBounds (Ogre::AxisAlignedBox& bounds);
|
||||
Resource::ResourceSystem* mResourceSystem;
|
||||
|
||||
osg::ref_ptr<osgUtil::IncrementalCompileOperation> mIncrementalCompileOperation;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue