Add quad tree implementation (no rendering yet)

0.6.1
scrawl 8 years ago
parent 2d549d088e
commit ce8c4ad4f5

@ -114,7 +114,7 @@ add_component_dir (translation
)
add_component_dir (terrain
storage world buffercache defs terraingrid material terraindrawable texturemanager chunkmanager compositemaprenderer
storage world buffercache defs terraingrid material terraindrawable texturemanager chunkmanager compositemaprenderer quadtreeworld quadtreenode
)
add_component_dir (loadinglistener

@ -0,0 +1,169 @@
#include "quadtreenode.hpp"
#include <cassert>
#include <osgUtil/CullVisitor>
#include "defs.hpp"
namespace
{
float distance(const osg::BoundingBox& box, const osg::Vec3f& v)
{
if (box.contains(v))
return 0;
else
{
osg::Vec3f maxDist(0,0,0);
if (v.x() < box.xMin())
maxDist.x() = box.xMin() - v.x();
else if (v.x() > box.xMax())
maxDist.x() = v.x() - box.xMax();
if (v.y() < box.yMin())
maxDist.y() = box.yMin() - v.y();
else if (v.y() > box.yMax())
maxDist.y() = v.y() - box.yMax();
if (v.z() < box.zMin())
maxDist.z() = box.zMin() - v.z();
else if (v.z() > box.zMax())
maxDist.z() = v.z() - box.zMax();
return maxDist.length();
}
}
int Log2( int n )
{
assert(n > 0);
int targetlevel = 0;
while (n >>= 1) ++targetlevel;
return targetlevel;
}
}
namespace Terrain
{
ChildDirection reflect(ChildDirection dir, Direction dir2)
{
assert(dir != Root);
const int lookupTable[4][4] =
{
// NW NE SW SE
{ SW, SE, NW, NE }, // N
{ NE, NW, SE, SW }, // E
{ SW, SE, NW, NE }, // S
{ NE, NW, SE, SW } // W
};
return (ChildDirection)lookupTable[dir2][dir];
}
bool adjacent(ChildDirection dir, Direction dir2)
{
assert(dir != Root);
const bool lookupTable[4][4] =
{
// NW NE SW SE
{ true, true, false, false }, // N
{ false, true, false, true }, // E
{ false, false, true, true }, // S
{ true, false, true, false } // W
};
return lookupTable[dir2][dir];
}
QuadTreeNode* searchNeighbour (QuadTreeNode* currentNode, Direction dir)
{
if (currentNode->getDirection() == Root)
return NULL; // Arrived at root node, the root node does not have neighbours
QuadTreeNode* nextNode;
if (adjacent(currentNode->getDirection(), dir))
nextNode = searchNeighbour(currentNode->getParent(), dir);
else
nextNode = currentNode->getParent();
if (nextNode && nextNode->getNumChildren())
return nextNode->getChild(reflect(currentNode->getDirection(), dir));
else
return NULL;
}
QuadTreeNode::QuadTreeNode(QuadTreeNode* parent, ChildDirection direction, float size, const osg::Vec2f& center)
: mParent(parent)
, mDirection(direction)
, mSize(size)
, mCenter(center)
{
for (unsigned int i=0; i<4; ++i)
mNeighbours[i] = 0;
}
QuadTreeNode* QuadTreeNode::getParent()
{
return mParent;
}
QuadTreeNode *QuadTreeNode::getChild(unsigned int i)
{
return static_cast<QuadTreeNode*>(Group::getChild(i));
}
void QuadTreeNode::initNeighbours()
{
for (int i=0; i<4; ++i)
mNeighbours[i] = searchNeighbour(this, (Direction)i);
for (unsigned int i=0; i<getNumChildren(); ++i)
getChild(i)->initNeighbours();
}
void QuadTreeNode::traverse(osg::NodeVisitor &nv)
{
if (nv.getVisitorType() != osg::NodeVisitor::CULL_VISITOR)
return;
osgUtil::CullVisitor* cv = static_cast<osgUtil::CullVisitor*>(&nv);
// do another culling test against bounding box as its much more accurate than the bounding sphere.
if (cv->isCulled(mBoundingBox))
return;
//float dist = distance(getBoundingBox(), nv.getEyePoint());
osg::Group::traverse(nv);
}
void QuadTreeNode::setBoundingBox(const osg::BoundingBox &boundingBox)
{
mBoundingBox = boundingBox;
dirtyBound();
getBound();
}
const osg::BoundingBox &QuadTreeNode::getBoundingBox() const
{
return mBoundingBox;
}
osg::BoundingSphere QuadTreeNode::computeBound() const
{
return osg::BoundingSphere(mBoundingBox);
}
float QuadTreeNode::getSize() const
{
return mSize;
}
const osg::Vec2f &QuadTreeNode::getCenter() const
{
return mCenter;
}
}

@ -0,0 +1,66 @@
#ifndef OPENMW_COMPONENTS_TERRAIN_QUADTREENODE_H
#define OPENMW_COMPONENTS_TERRAIN_QUADTREENODE_H
#include <osg/Group>
#include "defs.hpp"
namespace Terrain
{
enum ChildDirection
{
NW = 0,
NE = 1,
SW = 2,
SE = 3,
Root
};
class QuadTreeNode : public osg::Group
{
public:
QuadTreeNode(QuadTreeNode* parent, ChildDirection dir, float size, const osg::Vec2f& center);
QuadTreeNode* getParent();
QuadTreeNode* getChild(unsigned int i);
using osg::Group::getNumChildren;
/// Returns our direction relative to the parent node, or Root if we are the root node.
ChildDirection getDirection() { return mDirection; }
/// Get neighbour node in this direction
QuadTreeNode* getNeighbour (Direction dir);
/// Initialize neighbours - do this after the quadtree is built
void initNeighbours();
void setBoundingBox(const osg::BoundingBox& boundingBox);
const osg::BoundingBox& getBoundingBox() const;
virtual osg::BoundingSphere computeBound() const;
/// size in cell coordinates
float getSize() const;
/// center in cell coordinates
const osg::Vec2f& getCenter() const;
virtual void traverse(osg::NodeVisitor& nv);
private:
QuadTreeNode* mParent;
QuadTreeNode* mNeighbours[4];
ChildDirection mDirection;
osg::BoundingBox mBoundingBox;
float mSize;
osg::Vec2f mCenter;
};
}
#endif

@ -0,0 +1,227 @@
#include "quadtreeworld.hpp"
#include <components/sceneutil/workqueue.hpp>
#include "quadtreenode.hpp"
#include "storage.hpp"
#include <osg/io_utils>
#include <osg/Timer>
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;
}
}
namespace Terrain
{
class RootNode : public QuadTreeNode
{
public:
RootNode(float size, const osg::Vec2f& center)
: QuadTreeNode(NULL, Root, size, center)
, mWorld(NULL)
{
}
void setWorld(QuadTreeWorld* world)
{
mWorld = world;
}
virtual void accept(osg::NodeVisitor &nv)
{
nv.pushOntoNodePath(this);
mWorld->accept(nv);
nv.popFromNodePath();
}
private:
QuadTreeWorld* mWorld;
};
class BuildQuadTreeItem : public SceneUtil::WorkItem
{
public:
BuildQuadTreeItem(Terrain::Storage* storage, float minSize)
: mStorage(storage)
, mMinX(0.f), mMaxX(0.f), mMinY(0.f), mMaxY(0.f)
, mMinSize(minSize)
{
}
virtual void doWork()
{
mStorage->getBounds(mMinX, mMaxX, mMinY, mMaxY);
int origSizeX = static_cast<int>(mMaxX - mMinX);
int origSizeY = static_cast<int>(mMaxY - mMinY);
// Dividing a quad tree only works well for powers of two, so round up to the nearest one
int size = nextPowerOfTwo(std::max(origSizeX, origSizeY));
float centerX = (mMinX+mMaxX)/2.f + (size-origSizeX)/2.f;
float centerY = (mMinY+mMaxY)/2.f + (size-origSizeY)/2.f;
mRootNode = new RootNode(size, osg::Vec2f(centerX, centerY));
addChildren(mRootNode);
mRootNode->initNeighbours();
}
void addChildren(QuadTreeNode* parent)
{
float halfSize = parent->getSize()/2.f;
osg::BoundingBox boundingBox;
for (unsigned int i=0; i<4; ++i)
{
QuadTreeNode* child = addChild(parent, static_cast<ChildDirection>(i), halfSize);
if (child)
boundingBox.expandBy(child->getBoundingBox());
}
parent->setBoundingBox(boundingBox);
}
QuadTreeNode* addChild(QuadTreeNode* parent, ChildDirection direction, float size)
{
osg::Vec2f center;
switch (direction)
{
case SW:
center = parent->getCenter() + osg::Vec2f(-size/2.f,-size/2.f);
break;
case SE:
center = parent->getCenter() + osg::Vec2f(size/2.f, -size/2.f);
break;
case NW:
center = parent->getCenter() + osg::Vec2f(-size/2.f, size/2.f);
break;
case NE:
center = parent->getCenter() + osg::Vec2f(size/2.f, size/2.f);
break;
default:
break;
}
osg::ref_ptr<QuadTreeNode> node = new QuadTreeNode(parent, direction, size, center);
parent->addChild(node);
if (center.x() - size > mMaxX
|| center.x() + size < mMinX
|| center.y() - size > mMaxY
|| center.y() + size < mMinY )
// Out of bounds of the actual terrain - this will happen because
// we rounded the size up to the next power of two
{
// Still create and return an empty node so as to not break the assumption that each QuadTreeNode has either 4 or 0 children.
return node;
}
if (node->getSize() <= mMinSize)
{
// We arrived at a leaf
float minZ,maxZ;
if (mStorage->getMinMaxHeights(size, center, minZ, maxZ))
{
float cellWorldSize = mStorage->getCellWorldSize();
osg::BoundingBox boundingBox(osg::Vec3f((center.x()-size)*cellWorldSize, (center.y()-size)*cellWorldSize, minZ),
osg::Vec3f((center.x()+size)*cellWorldSize, (center.y()+size)*cellWorldSize, maxZ));
node->setBoundingBox(boundingBox);
}
return node;
}
else
{
addChildren(node);
return node;
}
}
osg::ref_ptr<RootNode> getRootNode()
{
return mRootNode;
}
private:
Terrain::Storage* mStorage;
float mMinX, mMaxX, mMinY, mMaxY;
float mMinSize;
osg::ref_ptr<RootNode> mRootNode;
};
QuadTreeWorld::QuadTreeWorld(SceneUtil::WorkQueue* workQueue, osg::Group *parent, osg::Group *compileRoot, Resource::ResourceSystem *resourceSystem, Storage *storage, int nodeMask, int preCompileMask)
: World(parent, compileRoot, resourceSystem, storage, nodeMask, preCompileMask)
, mWorkQueue(workQueue)
, mQuadTreeBuilt(false)
{
}
QuadTreeWorld::~QuadTreeWorld()
{
if (mWorkItem)
{
mWorkItem->abort();
mWorkItem->waitTillDone();
}
}
void QuadTreeWorld::accept(osg::NodeVisitor &nv)
{
if (nv.getVisitorType() != osg::NodeVisitor::CULL_VISITOR && nv.getVisitorType() != osg::NodeVisitor::INTERSECTION_VISITOR)
return;
mRootNode->traverse(nv);
}
void QuadTreeWorld::loadCell(int x, int y)
{
if (mQuadTreeBuilt && !mRootNode)
{
mRootNode = mWorkItem->getRootNode();
mRootNode->setWorld(this);
mTerrainRoot->addChild(mRootNode);
mWorkItem = NULL;
}
}
osg::ref_ptr<osg::Node> QuadTreeWorld::cacheCell(int x, int y)
{
if (!mQuadTreeBuilt)
{
const float minSize = 1/4.f;
mWorkItem = new BuildQuadTreeItem(mStorage, minSize);
mWorkQueue->addWorkItem(mWorkItem);
mWorkItem->waitTillDone();
mQuadTreeBuilt = true;
}
return NULL;
}
}

@ -0,0 +1,47 @@
#ifndef COMPONENTS_TERRAIN_QUADTREEWORLD_H
#define COMPONENTS_TERRAIN_QUADTREEWORLD_H
#include <map>
#include "world.hpp"
namespace SceneUtil
{
class WorkQueue;
}
namespace osg
{
class NodeVisitor;
}
namespace Terrain
{
class RootNode;
class BuildQuadTreeItem;
/// @brief Terrain implementation that loads cells into a Quad Tree, with geometry LOD and texture LOD. The entire world is displayed at all times.
class QuadTreeWorld : public Terrain::World
{
public:
QuadTreeWorld(SceneUtil::WorkQueue* workQueue, osg::Group* parent, osg::Group* compileRoot, Resource::ResourceSystem* resourceSystem, Storage* storage, int nodeMask, int preCompileMask=~0);
~QuadTreeWorld();
void accept(osg::NodeVisitor& nv);
virtual void loadCell(int x, int y);
virtual osg::ref_ptr<osg::Node> cacheCell(int x, int y);
private:
osg::ref_ptr<RootNode> mRootNode;
osg::ref_ptr<SceneUtil::WorkQueue> mWorkQueue;
osg::ref_ptr<BuildQuadTreeItem> mWorkItem;
bool mQuadTreeBuilt;
};
}
#endif

@ -18,6 +18,7 @@ namespace osg
namespace Terrain
{
/// We keep storage of terrain data abstract here since we need different implementations for game and editor
/// @note The implementation must be thread safe.
class Storage
{
public:

@ -7,15 +7,10 @@
#include "world.hpp"
namespace osg
{
class Texture2D;
}
namespace Terrain
{
/// @brief Simple terrain implementation that loads cells in a grid, with no LOD
/// @brief Simple terrain implementation that loads cells in a grid, with no LOD. Only requested cells are loaded.
class TerrainGrid : public Terrain::World
{
public:

@ -57,6 +57,11 @@ float World::getHeightAt(const osg::Vec3f &worldPos)
return mStorage->getHeightAt(worldPos);
}
osg::ref_ptr<osg::Node> World::cacheCell(int x, int y)
{
return NULL;
}
void World::updateTextureFiltering()
{
mTextureManager->updateTextureFiltering();

@ -47,10 +47,20 @@ namespace Terrain
float getHeightAt (const osg::Vec3f& worldPos);
virtual osg::ref_ptr<osg::Node> cacheCell(int x, int y) {return NULL;}
/// Load a terrain cell and store it in cache for later use.
/// @note The returned ref_ptr should be kept by the caller to ensure that the terrain stays in cache for as long as needed.
/// @note Thread safe.
/// @note May be ignored by derived implementations that don't organize the terrain into cells.
virtual osg::ref_ptr<osg::Node> cacheCell(int x, int y);
// This is only a hint and may be ignored by the implementation.
/// Load the cell into the scene graph.
/// @note Not thread safe.
/// @note May be ignored by derived implementations that don't organize the terrain into cells.
virtual void loadCell(int x, int y) {}
/// Remove the cell from the scene graph.
/// @note Not thread safe.
/// @note May be ignored by derived implementations that don't organize the terrain into cells.
virtual void unloadCell(int x, int y) {}
Storage* getStorage() { return mStorage; }

Loading…
Cancel
Save