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