forked from mirror/openmw-tes3mp
Add quad tree implementation (no rendering yet)
parent
2d549d088e
commit
ce8c4ad4f5
@ -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
|
Loading…
Reference in New Issue