You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
612 lines
20 KiB
C++
612 lines
20 KiB
C++
/*
|
|
* 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();
|
|
}
|