1 #include "quadtreeworld.hpp"
2 
3 #include <osgUtil/CullVisitor>
4 #include <osg/ShapeDrawable>
5 #include <osg/PolygonMode>
6 
7 #include <limits>
8 #include <sstream>
9 
10 #include <components/misc/constants.hpp>
11 #include <components/sceneutil/mwshadowtechnique.hpp>
12 #include <components/sceneutil/positionattitudetransform.hpp>
13 
14 #include "quadtreenode.hpp"
15 #include "storage.hpp"
16 #include "viewdata.hpp"
17 #include "chunkmanager.hpp"
18 #include "compositemaprenderer.hpp"
19 #include "terraindrawable.hpp"
20 
21 namespace
22 {
23 
isPowerOfTwo(int x)24     bool isPowerOfTwo(int x)
25     {
26         return ( (x > 0) && ((x & (x - 1)) == 0) );
27     }
28 
nextPowerOfTwo(int v)29     int nextPowerOfTwo (int v)
30     {
31         if (isPowerOfTwo(v)) return v;
32         int depth=0;
33         while(v)
34         {
35             v >>= 1;
36             depth++;
37         }
38         return 1 << depth;
39     }
40 
Log2(unsigned int n)41     int Log2( unsigned int n )
42     {
43         int targetlevel = 0;
44         while (n >>= 1) ++targetlevel;
45         return targetlevel;
46     }
47 
48 }
49 
50 namespace Terrain
51 {
52 
53 class DefaultLodCallback : public LodCallback
54 {
55 public:
DefaultLodCallback(float factor,float minSize,float viewDistance,const osg::Vec4i & grid)56     DefaultLodCallback(float factor, float minSize, float viewDistance, const osg::Vec4i& grid)
57         : mFactor(factor)
58         , mMinSize(minSize)
59         , mViewDistance(viewDistance)
60         , mActiveGrid(grid)
61     {
62     }
63 
isSufficientDetail(QuadTreeNode * node,float dist)64     ReturnValue isSufficientDetail(QuadTreeNode* node, float dist) override
65     {
66         const osg::Vec2f& center = node->getCenter();
67         bool activeGrid = (center.x() > mActiveGrid.x() && center.y() > mActiveGrid.y() && center.x() < mActiveGrid.z() && center.y() < mActiveGrid.w());
68         if (dist > mViewDistance && !activeGrid) // for Scene<->ObjectPaging sync the activegrid must remain loaded
69             return StopTraversal;
70         if (node->getSize()>1)
71         {
72             float halfSize = node->getSize()/2;
73             osg::Vec4i nodeBounds (static_cast<int>(center.x() - halfSize), static_cast<int>(center.y() - halfSize), static_cast<int>(center.x() + halfSize), static_cast<int>(center.y() + halfSize));
74             bool intersects = (std::max(nodeBounds.x(), mActiveGrid.x()) < std::min(nodeBounds.z(), mActiveGrid.z()) && std::max(nodeBounds.y(), mActiveGrid.y()) < std::min(nodeBounds.w(), mActiveGrid.w()));
75             // to prevent making chunks who will cross the activegrid border
76             if (intersects)
77                 return Deeper;
78         }
79 
80         int nativeLodLevel = Log2(static_cast<unsigned int>(node->getSize()/mMinSize));
81         int lodLevel = Log2(static_cast<unsigned int>(dist/(Constants::CellSizeInUnits*mMinSize*mFactor)));
82 
83         return nativeLodLevel <= lodLevel ? StopTraversalAndUse : Deeper;
84     }
85 
86 private:
87     float mFactor;
88     float mMinSize;
89     float mViewDistance;
90     osg::Vec4i mActiveGrid;
91 };
92 
93 class RootNode : public QuadTreeNode
94 {
95 public:
RootNode(float size,const osg::Vec2f & center)96     RootNode(float size, const osg::Vec2f& center)
97         : QuadTreeNode(nullptr, Root, size, center)
98         , mWorld(nullptr)
99     {
100     }
101 
setWorld(QuadTreeWorld * world)102     void setWorld(QuadTreeWorld* world)
103     {
104         mWorld = world;
105     }
106 
accept(osg::NodeVisitor & nv)107     void accept(osg::NodeVisitor &nv) override
108     {
109         if (!nv.validNodeMask(*this))
110             return;
111         nv.pushOntoNodePath(this);
112         mWorld->accept(nv);
113         nv.popFromNodePath();
114     }
115 
116 private:
117     QuadTreeWorld* mWorld;
118 };
119 
120 class QuadTreeBuilder
121 {
122 public:
QuadTreeBuilder(Terrain::Storage * storage,float minSize)123     QuadTreeBuilder(Terrain::Storage* storage, float minSize)
124         : mStorage(storage)
125         , mMinX(0.f), mMaxX(0.f), mMinY(0.f), mMaxY(0.f)
126         , mMinSize(minSize)
127     {
128     }
129 
build()130     void build()
131     {
132         mStorage->getBounds(mMinX, mMaxX, mMinY, mMaxY);
133 
134         int origSizeX = static_cast<int>(mMaxX - mMinX);
135         int origSizeY = static_cast<int>(mMaxY - mMinY);
136 
137         // Dividing a quad tree only works well for powers of two, so round up to the nearest one
138         int size = nextPowerOfTwo(std::max(origSizeX, origSizeY));
139 
140         float centerX = (mMinX+mMaxX)/2.f + (size-origSizeX)/2.f;
141         float centerY = (mMinY+mMaxY)/2.f + (size-origSizeY)/2.f;
142 
143         mRootNode = new RootNode(size, osg::Vec2f(centerX, centerY));
144         addChildren(mRootNode);
145 
146         mRootNode->initNeighbours();
147         float cellWorldSize = mStorage->getCellWorldSize();
148         mRootNode->setInitialBound(osg::BoundingSphere(osg::BoundingBox(osg::Vec3(mMinX*cellWorldSize, mMinY*cellWorldSize, 0), osg::Vec3(mMaxX*cellWorldSize, mMaxY*cellWorldSize, 0))));
149     }
150 
addChildren(QuadTreeNode * parent)151     void addChildren(QuadTreeNode* parent)
152     {
153         float halfSize = parent->getSize()/2.f;
154         osg::BoundingBox boundingBox;
155         for (unsigned int i=0; i<4; ++i)
156         {
157             osg::ref_ptr<QuadTreeNode> child = addChild(parent, static_cast<ChildDirection>(i), halfSize);
158             if (child)
159             {
160                 boundingBox.expandBy(child->getBoundingBox());
161                 parent->addChildNode(child);
162             }
163         }
164 
165         if (!boundingBox.valid())
166             parent->removeChildren(0, 4);
167         else
168             parent->setBoundingBox(boundingBox);
169     }
170 
addChild(QuadTreeNode * parent,ChildDirection direction,float size)171     osg::ref_ptr<QuadTreeNode> addChild(QuadTreeNode* parent, ChildDirection direction, float size)
172     {
173         float halfSize = size/2.f;
174         osg::Vec2f center;
175         switch (direction)
176         {
177         case SW:
178             center = parent->getCenter() + osg::Vec2f(-halfSize,-halfSize);
179             break;
180         case SE:
181             center = parent->getCenter() + osg::Vec2f(halfSize, -halfSize);
182             break;
183         case NW:
184             center = parent->getCenter() + osg::Vec2f(-halfSize, halfSize);
185             break;
186         case NE:
187             center = parent->getCenter() + osg::Vec2f(halfSize, halfSize);
188             break;
189         default:
190             break;
191         }
192 
193         osg::ref_ptr<QuadTreeNode> node = new QuadTreeNode(parent, direction, size, center);
194 
195         if (center.x() - halfSize > mMaxX
196                 || center.x() + halfSize < mMinX
197                 || center.y() - halfSize > mMaxY
198                 || center.y() + halfSize < mMinY )
199             // Out of bounds of the actual terrain - this will happen because
200             // we rounded the size up to the next power of two
201         {
202             // Still create and return an empty node so as to not break the assumption that each QuadTreeNode has either 4 or 0 children.
203             return node;
204         }
205 
206         // Do not add child nodes for default cells without data.
207         // size = 1 means that the single shape covers the whole cell.
208         if (node->getSize() == 1 && !mStorage->hasData(center.x()-0.5, center.y()-0.5))
209             return node;
210 
211         if (node->getSize() <= mMinSize)
212         {
213             // We arrived at a leaf.
214             // Since the tree is used for LOD level selection instead of culling, we do not need to load the actual height data here.
215             constexpr float minZ = -std::numeric_limits<float>::max();
216             constexpr float maxZ = std::numeric_limits<float>::max();
217             float cellWorldSize = mStorage->getCellWorldSize();
218             osg::BoundingBox boundingBox(osg::Vec3f((center.x()-halfSize)*cellWorldSize, (center.y()-halfSize)*cellWorldSize, minZ),
219                                     osg::Vec3f((center.x()+halfSize)*cellWorldSize, (center.y()+halfSize)*cellWorldSize, maxZ));
220             node->setBoundingBox(boundingBox);
221             return node;
222         }
223         else
224         {
225             addChildren(node);
226             return node;
227         }
228     }
229 
getRootNode()230     osg::ref_ptr<RootNode> getRootNode()
231     {
232         return mRootNode;
233     }
234 
235 private:
236     Terrain::Storage* mStorage;
237 
238     float mMinX, mMaxX, mMinY, mMaxY;
239     float mMinSize;
240 
241     osg::ref_ptr<RootNode> mRootNode;
242 };
243 
QuadTreeWorld(osg::Group * parent,osg::Group * compileRoot,Resource::ResourceSystem * resourceSystem,Storage * storage,unsigned int nodeMask,unsigned int preCompileMask,unsigned int borderMask,int compMapResolution,float compMapLevel,float lodFactor,int vertexLodMod,float maxCompGeometrySize)244 QuadTreeWorld::QuadTreeWorld(osg::Group *parent, osg::Group *compileRoot, Resource::ResourceSystem *resourceSystem, Storage *storage, unsigned int nodeMask, unsigned int preCompileMask, unsigned int borderMask, int compMapResolution, float compMapLevel, float lodFactor, int vertexLodMod, float maxCompGeometrySize)
245     : TerrainGrid(parent, compileRoot, resourceSystem, storage, nodeMask, preCompileMask, borderMask)
246     , mViewDataMap(new ViewDataMap)
247     , mQuadTreeBuilt(false)
248     , mLodFactor(lodFactor)
249     , mVertexLodMod(vertexLodMod)
250     , mViewDistance(std::numeric_limits<float>::max())
251     , mMinSize(1/8.f)
252 {
253     mChunkManager->setCompositeMapSize(compMapResolution);
254     mChunkManager->setCompositeMapLevel(compMapLevel);
255     mChunkManager->setMaxCompositeGeometrySize(maxCompGeometrySize);
256     mChunkManagers.push_back(mChunkManager.get());
257 }
258 
QuadTreeWorld(osg::Group * parent,Storage * storage,unsigned int nodeMask,float lodFactor,float chunkSize)259 QuadTreeWorld::QuadTreeWorld(osg::Group *parent, Storage *storage, unsigned int nodeMask, float lodFactor, float chunkSize)
260     : TerrainGrid(parent, storage, nodeMask)
261     , mViewDataMap(new ViewDataMap)
262     , mQuadTreeBuilt(false)
263     , mLodFactor(lodFactor)
264     , mVertexLodMod(0)
265     , mViewDistance(std::numeric_limits<float>::max())
266     , mMinSize(chunkSize)
267 {
268 }
269 
~QuadTreeWorld()270 QuadTreeWorld::~QuadTreeWorld()
271 {
272 }
273 
274 /// get the level of vertex detail to render this node at, expressed relative to the native resolution of the data set.
getVertexLod(QuadTreeNode * node,int vertexLodMod)275 unsigned int getVertexLod(QuadTreeNode* node, int vertexLodMod)
276 {
277     int lod = Log2(int(node->getSize()));
278     if (vertexLodMod > 0)
279     {
280         lod = std::max(0, lod-vertexLodMod);
281     }
282     else if (vertexLodMod < 0)
283     {
284         float size = node->getSize();
285         // Stop to simplify at this level since with size = 1 the node already covers the whole cell and has getCellVertices() vertices.
286         while (size < 1)
287         {
288             size *= 2;
289             vertexLodMod = std::min(0, vertexLodMod+1);
290         }
291         lod += std::abs(vertexLodMod);
292     }
293     return lod;
294 }
295 
296 /// get the flags to use for stitching in the index buffer so that chunks of different LOD connect seamlessly
getLodFlags(QuadTreeNode * node,int ourLod,int vertexLodMod,const ViewData * vd)297 unsigned int getLodFlags(QuadTreeNode* node, int ourLod, int vertexLodMod, const ViewData* vd)
298 {
299     unsigned int lodFlags = 0;
300     for (unsigned int i=0; i<4; ++i)
301     {
302         QuadTreeNode* neighbour = node->getNeighbour(static_cast<Direction>(i));
303 
304         // If the neighbour isn't currently rendering itself,
305         // go up until we find one. NOTE: We don't need to go down,
306         // because in that case neighbour's detail would be higher than
307         // our detail and the neighbour would handle stitching by itself.
308         while (neighbour && !vd->contains(neighbour))
309             neighbour = neighbour->getParent();
310         int lod = 0;
311         if (neighbour)
312             lod = getVertexLod(neighbour, vertexLodMod);
313 
314         if (lod <= ourLod) // We only need to worry about neighbours less detailed than we are -
315             lod = 0;         // neighbours with more detail will do the stitching themselves
316         // Use 4 bits for each LOD delta
317         if (lod > 0)
318         {
319             lodFlags |= static_cast<unsigned int>(lod - ourLod) << (4*i);
320         }
321     }
322     return lodFlags;
323 }
324 
loadRenderingNode(ViewData::Entry & entry,ViewData * vd,int vertexLodMod,float cellWorldSize,const osg::Vec4i & gridbounds,const std::vector<QuadTreeWorld::ChunkManager * > & chunkManagers,bool compile)325 void loadRenderingNode(ViewData::Entry& entry, ViewData* vd, int vertexLodMod, float cellWorldSize, const osg::Vec4i &gridbounds, const std::vector<QuadTreeWorld::ChunkManager*>& chunkManagers, bool compile)
326 {
327     if (!vd->hasChanged() && entry.mRenderingNode)
328         return;
329 
330     int ourLod = getVertexLod(entry.mNode, vertexLodMod);
331 
332     if (vd->hasChanged())
333     {
334         // have to recompute the lodFlags in case a neighbour has changed LOD.
335         unsigned int lodFlags = getLodFlags(entry.mNode, ourLod, vertexLodMod, vd);
336         if (lodFlags != entry.mLodFlags)
337         {
338             entry.mRenderingNode = nullptr;
339             entry.mLodFlags = lodFlags;
340         }
341     }
342 
343     if (!entry.mRenderingNode)
344     {
345         osg::ref_ptr<SceneUtil::PositionAttitudeTransform> pat = new SceneUtil::PositionAttitudeTransform;
346         pat->setPosition(osg::Vec3f(entry.mNode->getCenter().x()*cellWorldSize, entry.mNode->getCenter().y()*cellWorldSize, 0.f));
347 
348         const osg::Vec2f& center = entry.mNode->getCenter();
349         bool activeGrid = (center.x() > gridbounds.x() && center.y() > gridbounds.y() && center.x() < gridbounds.z() && center.y() < gridbounds.w());
350 
351         for (QuadTreeWorld::ChunkManager* m : chunkManagers)
352         {
353             osg::ref_ptr<osg::Node> n = m->getChunk(entry.mNode->getSize(), entry.mNode->getCenter(), ourLod, entry.mLodFlags, activeGrid, vd->getViewPoint(), compile);
354             if (n) pat->addChild(n);
355         }
356         entry.mRenderingNode = pat;
357     }
358 }
359 
updateWaterCullingView(HeightCullCallback * callback,ViewData * vd,osgUtil::CullVisitor * cv,float cellworldsize,bool outofworld)360 void updateWaterCullingView(HeightCullCallback* callback, ViewData* vd, osgUtil::CullVisitor* cv, float cellworldsize, bool outofworld)
361 {
362     if (!(cv->getTraversalMask() & callback->getCullMask()))
363         return;
364     float lowZ = std::numeric_limits<float>::max();
365     float highZ = callback->getHighZ();
366     if (cv->getEyePoint().z() <= highZ || outofworld)
367     {
368         callback->setLowZ(-std::numeric_limits<float>::max());
369         return;
370     }
371     cv->pushCurrentMask();
372     static bool debug = getenv("OPENMW_WATER_CULLING_DEBUG") != nullptr;
373     for (unsigned int i=0; i<vd->getNumEntries(); ++i)
374     {
375         ViewData::Entry& entry = vd->getEntry(i);
376         osg::BoundingBox bb = static_cast<TerrainDrawable*>(entry.mRenderingNode->asGroup()->getChild(0))->getWaterBoundingBox();
377         if (!bb.valid())
378             continue;
379         osg::Vec3f ofs (entry.mNode->getCenter().x()*cellworldsize, entry.mNode->getCenter().y()*cellworldsize, 0.f);
380         bb._min += ofs; bb._max += ofs;
381         bb._min.z() = highZ;
382         bb._max.z() = highZ;
383         if (cv->isCulled(bb))
384             continue;
385         lowZ = bb._min.z();
386 
387         if (!debug)
388             break;
389         osg::Box* b = new osg::Box;
390         b->set(bb.center(), bb._max - bb.center());
391         osg::ShapeDrawable* drw = new osg::ShapeDrawable(b);
392         static osg::ref_ptr<osg::StateSet> stateset = nullptr;
393         if (!stateset)
394         {
395             stateset = new osg::StateSet;
396             stateset->setMode(GL_CULL_FACE, osg::StateAttribute::OFF);
397             stateset->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF);
398             stateset->setAttributeAndModes(new osg::PolygonMode(osg::PolygonMode::FRONT_AND_BACK, osg::PolygonMode::LINE), osg::StateAttribute::ON);
399             osg::Material* m = new osg::Material;
400             m->setEmission(osg::Material::FRONT_AND_BACK, osg::Vec4f(0,0,1,1));
401             m->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(0,0,0,1));
402             m->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(0,0,0,1));
403             stateset->setAttributeAndModes(m, osg::StateAttribute::ON);
404             stateset->setRenderBinDetails(100,"RenderBin");
405         }
406         drw->setStateSet(stateset);
407         drw->accept(*cv);
408     }
409     callback->setLowZ(lowZ);
410     cv->popCurrentMask();
411 }
412 
accept(osg::NodeVisitor & nv)413 void QuadTreeWorld::accept(osg::NodeVisitor &nv)
414 {
415     bool isCullVisitor = nv.getVisitorType() == osg::NodeVisitor::CULL_VISITOR;
416     if (!isCullVisitor && nv.getVisitorType() != osg::NodeVisitor::INTERSECTION_VISITOR)
417     {
418         if (nv.getName().find("AcceptedByComponentsTerrainQuadTreeWorld") != std::string::npos)
419         {
420             if (nv.getName().find("SceneUtil::MWShadowTechnique::ComputeLightSpaceBounds") != std::string::npos)
421             {
422                 SceneUtil::MWShadowTechnique::ComputeLightSpaceBounds* clsb = static_cast<SceneUtil::MWShadowTechnique::ComputeLightSpaceBounds*>(&nv);
423                 clsb->apply(*this);
424             }
425             else
426                 nv.apply(*mRootNode);
427         }
428         return;
429     }
430 
431     osg::Object * viewer = isCullVisitor ? static_cast<osgUtil::CullVisitor*>(&nv)->getCurrentCamera() : nullptr;
432     bool needsUpdate = true;
433     ViewData *vd = mViewDataMap->getViewData(viewer, nv.getViewPoint(), mActiveGrid, needsUpdate);
434 
435     if (needsUpdate)
436     {
437         vd->reset();
438         DefaultLodCallback lodCallback(mLodFactor, mMinSize, mViewDistance, mActiveGrid);
439         mRootNode->traverseNodes(vd, nv.getViewPoint(), &lodCallback);
440     }
441 
442     const float cellWorldSize = mStorage->getCellWorldSize();
443 
444     for (unsigned int i=0; i<vd->getNumEntries(); ++i)
445     {
446         ViewData::Entry& entry = vd->getEntry(i);
447         loadRenderingNode(entry, vd, mVertexLodMod, cellWorldSize, mActiveGrid, mChunkManagers, false);
448         entry.mRenderingNode->accept(nv);
449     }
450 
451     if (mHeightCullCallback && isCullVisitor)
452         updateWaterCullingView(mHeightCullCallback, vd, static_cast<osgUtil::CullVisitor*>(&nv), mStorage->getCellWorldSize(), !isGridEmpty());
453 
454     vd->markUnchanged();
455 
456     double referenceTime = nv.getFrameStamp() ? nv.getFrameStamp()->getReferenceTime() : 0.0;
457     if (referenceTime != 0.0)
458     {
459         vd->setLastUsageTimeStamp(referenceTime);
460         mViewDataMap->clearUnusedViews(referenceTime);
461     }
462 }
463 
ensureQuadTreeBuilt()464 void QuadTreeWorld::ensureQuadTreeBuilt()
465 {
466     std::lock_guard<std::mutex> lock(mQuadTreeMutex);
467     if (mQuadTreeBuilt)
468         return;
469 
470     QuadTreeBuilder builder(mStorage, mMinSize);
471     builder.build();
472 
473     mRootNode = builder.getRootNode();
474     mRootNode->setWorld(this);
475     mQuadTreeBuilt = true;
476 }
477 
enable(bool enabled)478 void QuadTreeWorld::enable(bool enabled)
479 {
480     if (enabled)
481     {
482         ensureQuadTreeBuilt();
483 
484         if (!mRootNode->getNumParents())
485             mTerrainRoot->addChild(mRootNode);
486     }
487 
488     if (mRootNode)
489         mRootNode->setNodeMask(enabled ? ~0 : 0);
490 }
491 
createView()492 View* QuadTreeWorld::createView()
493 {
494     return mViewDataMap->createIndependentView();
495 }
496 
preload(View * view,const osg::Vec3f & viewPoint,const osg::Vec4i & grid,std::atomic<bool> & abort,std::atomic<int> & progress,int & progressTotal)497 void QuadTreeWorld::preload(View *view, const osg::Vec3f &viewPoint, const osg::Vec4i &grid, std::atomic<bool> &abort, std::atomic<int> &progress, int& progressTotal)
498 {
499     ensureQuadTreeBuilt();
500 
501     ViewData* vd = static_cast<ViewData*>(view);
502     vd->setViewPoint(viewPoint);
503     vd->setActiveGrid(grid);
504     DefaultLodCallback lodCallback(mLodFactor, mMinSize, mViewDistance, grid);
505     mRootNode->traverseNodes(vd, viewPoint, &lodCallback);
506 
507     if (!progressTotal)
508         for (unsigned int i=0; i<vd->getNumEntries(); ++i)
509             progressTotal += vd->getEntry(i).mNode->getSize();
510 
511     const float cellWorldSize = mStorage->getCellWorldSize();
512     for (unsigned int i=0; i<vd->getNumEntries() && !abort; ++i)
513     {
514         ViewData::Entry& entry = vd->getEntry(i);
515         loadRenderingNode(entry, vd, mVertexLodMod, cellWorldSize, grid, mChunkManagers, true);
516         progress += entry.mNode->getSize();
517     }
518     vd->markUnchanged();
519 }
520 
storeView(const View * view,double referenceTime)521 bool QuadTreeWorld::storeView(const View* view, double referenceTime)
522 {
523     return mViewDataMap->storeView(static_cast<const ViewData*>(view), referenceTime);
524 }
525 
reportStats(unsigned int frameNumber,osg::Stats * stats)526 void QuadTreeWorld::reportStats(unsigned int frameNumber, osg::Stats *stats)
527 {
528     if (mCompositeMapRenderer)
529         stats->setAttribute(frameNumber, "Composite", mCompositeMapRenderer->getCompileSetSize());
530 }
531 
loadCell(int x,int y)532 void QuadTreeWorld::loadCell(int x, int y)
533 {
534     // fallback behavior only for undefined cells (every other is already handled in quadtree)
535     float dummy;
536     if (mChunkManager && !mStorage->getMinMaxHeights(1, osg::Vec2f(x+0.5, y+0.5), dummy, dummy))
537         TerrainGrid::loadCell(x,y);
538     else
539         World::loadCell(x,y);
540 }
541 
unloadCell(int x,int y)542 void QuadTreeWorld::unloadCell(int x, int y)
543 {
544     // fallback behavior only for undefined cells (every other is already handled in quadtree)
545     float dummy;
546     if (mChunkManager && !mStorage->getMinMaxHeights(1, osg::Vec2f(x+0.5, y+0.5), dummy, dummy))
547         TerrainGrid::unloadCell(x,y);
548     else
549         World::unloadCell(x,y);
550 }
551 
addChunkManager(QuadTreeWorld::ChunkManager * m)552 void QuadTreeWorld::addChunkManager(QuadTreeWorld::ChunkManager* m)
553 {
554     mChunkManagers.push_back(m);
555     mTerrainRoot->setNodeMask(mTerrainRoot->getNodeMask()|m->getNodeMask());
556 }
557 
rebuildViews()558 void QuadTreeWorld::rebuildViews()
559 {
560     mViewDataMap->rebuildViews();
561 }
562 
563 }
564