forked from teamnwah/openmw-tes3coop
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