source: GTP/trunk/App/Demos/Vis/FriendlyCulling/src/Bvh.cpp @ 3244

Revision 3244, 35.4 KB checked in by mattausch, 15 years ago (diff)
Line 
1#include "Bvh.h"
2#include "Camera.h"
3#include "Plane3.h"
4#include "glInterface.h"
5#include "Triangle3.h"
6#include "SceneEntity.h"
7#include "Geometry.h"
8#include "RenderState.h"
9#include "Material.h"
10#include "gzstream.h"
11
12#include <queue>
13#include <stack>
14#include <fstream>
15#include <iostream>
16#include <iomanip>
17
18
19#ifdef _CRT_SET
20        #define _CRTDBG_MAP_ALLOC
21        #include <stdlib.h>
22        #include <crtdbg.h>
23
24        // redefine new operator
25        #define DEBUG_NEW new(_NORMAL_BLOCK, __FILE__, __LINE__)
26        #define new DEBUG_NEW
27#endif
28
29#define INVALID_TEST ((unsigned int)-1)
30
31using namespace std;
32
33
34namespace CHCDemoEngine
35{
36
37int BvhNode::sCurrentState = 0;
38
39
40/*
413 x---------x 2
42  |\         \
43  | \         \
44  |  \         \
45  |     4 x---------x 5
46  |   |         |
470 x   |     x 1 |
48   \  |         |
49    \ |         |
50     \|         |
51        7 x---------x 6             
52*/
53
54static unsigned int sIndices[] =       
55{7, // degenerated
56 7, 6, 4, 5, 3, 2, 0, 1,
57 1, 4, // degenerated
58 4, 3, 7, 0, 6, 1, 5, 2,
59 2 // degenerated
60};
61
62
63const static int NUM_INDICES_PER_BOX = 20;
64
65/*      Order of vertices
66        0 = (0, 0, 0)
67        1 = (1, 0, 0)
68        2 = (1, 1, 0)
69        3 = (0, 1, 0)
70        4 = (0, 1, 1)
71        5 = (1, 1, 1)
72        6 = (1, 0, 1)
73        7 = (0, 0, 1)
74*/
75
76static Plane3 sNearPlane;
77static float sNear;
78static Frustum sFrustum;
79
80/// these values are valid for all nodes
81static int sClipPlaneAABBVertexIndices[12];
82
83//#define ALIGN_INDICES
84
85
86BvhNode::BvhNode(BvhNode *parent):
87mParent(parent),
88mAxis(-1),
89mDepth(0),
90mFirst(-1),
91mLast(-1),
92mNumTestNodes(1),
93mTestNodesIdx(-1),
94mIndicesPtr(-1),
95mId(0),
96mIsMaxDepthForVirtualLeaf(false),
97mIsVirtualLeaf(false)
98{
99        for (int i = 0; i < NUM_STATES; ++ i)
100        {
101                mPlaneMask[i] = 0;
102                mPreferredPlane[i]= 0;
103                mLastRenderedFrame[i] = -1;
104        }
105}
106
107
108BvhNode::~BvhNode()
109{
110}
111
112
113void BvhNode::ResetVisibility()
114{
115        for (int i = 0; i < NUM_STATES; ++ i)
116        {
117                mVisibility[i].Reset();
118                mLastRenderedFrame[i] = -1;
119                mPlaneMask[i] = 0;
120                mPreferredPlane[i]= 0;
121        }
122}
123
124
125void BvhNode::VisibilityInfo::Reset()
126{
127        mIsVisible = false;
128        mAssumedVisibleFrameId = 0;
129        mLastVisitedFrame = -1;
130        mTimesInvisible = 0;
131        mIsFrustumCulled = false;
132        mIsNew = true;
133        mLastQueriedFrame = -1;
134}
135
136
137BvhInterior::~BvhInterior()
138{
139        DEL_PTR(mBack);
140        DEL_PTR(mFront);
141}
142
143
144BvhLeaf::~BvhLeaf()
145{
146}
147
148
149/**********************************************************/
150/*                class Bvh implementation                */
151/**********************************************************/
152
153
154
155inline AxisAlignedBox3 ComputeBoundingBox(SceneEntity **entities, int numEntities)
156{
157        AxisAlignedBox3 box;
158       
159        if (!numEntities)
160        {       // no box=> just initialize
161                box.Initialize();
162        }
163        else
164        {
165                box = entities[0]->GetWorldBoundingBox();
166
167                for (int i = 1; i < numEntities; ++ i)
168                {
169                        box.Include(entities[i]->GetWorldBoundingBox());
170                }
171        }
172
173        return box;
174}
175
176
177Bvh::Bvh()
178{
179        Init();
180}
181
182
183Bvh::Bvh(const SceneEntityContainer &staticEntities,
184                 const SceneEntityContainer &dynamicEntities)
185{
186        Init();
187
188        mGeometrySize = staticEntities.size() + dynamicEntities.size();
189        mGeometry = new SceneEntity*[mGeometrySize];
190
191        mStaticGeometrySize = staticEntities.size();
192        mDynamicGeometrySize = dynamicEntities.size();
193
194        for (size_t i = 0; i < mStaticGeometrySize; ++ i)
195        {
196                mGeometry[i] = staticEntities[i];
197        }
198
199        for (size_t i = 0; i < mDynamicGeometrySize; ++ i)
200        {
201                mGeometry[mStaticGeometrySize + i] = dynamicEntities[i];
202        }
203
204        cout << "max depth for testing children" << mMaxDepthForTestingChildren << endl;
205}
206
207
208Bvh::Bvh(const SceneEntityContainer &staticEntities,
209                 const SceneEntityContainer &dynamicEntities,
210                 int maxDepthForTestingChildren)
211{
212        Init();
213
214        mGeometrySize = staticEntities.size() + dynamicEntities.size();
215        mGeometry = new SceneEntity*[mGeometrySize];
216
217        mStaticGeometrySize = staticEntities.size();
218        mDynamicGeometrySize = dynamicEntities.size();
219
220        for (size_t i = 0; i < mStaticGeometrySize; ++ i)
221        {
222                mGeometry[i] = staticEntities[i];
223        }
224
225        for (size_t i = 0; i < mDynamicGeometrySize; ++ i)
226        {
227                mGeometry[mStaticGeometrySize + i] = dynamicEntities[i];
228        }
229
230        mMaxDepthForTestingChildren = maxDepthForTestingChildren;
231        cout << "max depth for testing children" << mMaxDepthForTestingChildren << endl;
232}
233
234
235Bvh::~Bvh()
236{
237        DEL_ARRAY_PTR(mVertices);
238        DEL_ARRAY_PTR(mIndices);
239        DEL_ARRAY_PTR(mTestIndices);
240        DEL_ARRAY_PTR(mGeometry);
241
242        if (mRoot) delete mRoot;
243        // delete vbo
244        glDeleteBuffersARB(1, &mVboId);
245}
246
247
248void Bvh::Init()
249{
250        mStaticRoot = NULL;
251        mDynamicRoot = NULL;
252        mRoot = NULL;
253
254        mVertices = NULL;
255        mIndices = NULL;
256        mTestIndices = NULL;
257        mCurrentIndicesPtr = 0;
258        mNumNodes = 0;
259       
260        // nodes are tested using the subnodes from 3 levels below
261        mMaxDepthForTestingChildren = 3;
262        // the ratio of area between node and subnodes where
263        // testing the subnodes as proxy is still considered feasable
264        mAreaRatioThreshold = 2.0f;
265        //mAreaRatioThreshold = 1.4f;
266
267        mVboId = -1;
268        // bound the maximal depth of the dynamic branch
269        mMaxDepthForDynamicBranch = 1;
270}
271
272
273
274//////////////////////
275//-- functions that are used during the main traversal
276
277void Bvh::PullUpLastVisited(BvhNode *node, int frameId) const
278{               
279        BvhNode *parent = node->GetParent();
280
281        while (parent && (parent->GetLastVisitedFrame() != frameId))
282        {
283                parent->SetLastVisitedFrame(frameId);
284                parent = parent->GetParent();
285        }
286}
287
288
289void Bvh::MakeParentsVisible(BvhNode *node)
290{
291        BvhNode *parent = node->GetParent();
292
293        while (parent && (!parent->IsVisible()))
294        {
295                parent->SetVisible(true);
296                parent = parent->GetParent();
297        }
298}
299
300
301////////////////////////////////
302
303void Bvh::CollectLeaves(BvhNode *node, BvhLeafContainer &leaves)
304{
305        stack<BvhNode *> tStack;
306        tStack.push(node);
307
308        while (!tStack.empty())
309        {
310                BvhNode *node = tStack.top();
311
312                tStack.pop();
313
314                if (!node->IsLeaf())
315                {
316                        BvhInterior *interior = static_cast<BvhInterior *>(node);
317
318                        tStack.push(interior->mFront);
319                        tStack.push(interior->mBack);
320                }
321                else
322                {
323                        leaves.push_back(static_cast<BvhLeaf *>(node));
324                }
325        }
326}
327
328
329void Bvh::CollectNodes(BvhNode *node, BvhNodeContainer &nodes)
330{
331        stack<BvhNode *> tStack;
332
333        tStack.push(node);
334
335        while (!tStack.empty())
336        {
337                BvhNode *node = tStack.top();
338                tStack.pop();
339
340                nodes.push_back(node);
341
342                if (!node->IsLeaf())
343                {
344                        BvhInterior *interior = static_cast<BvhInterior *>(node);
345
346                        tStack.push(interior->mFront);
347                        tStack.push(interior->mBack);
348                }
349        }
350}
351
352
353typedef pair<BvhNode *, int> tPair;
354
355void Bvh::CollectNodes(BvhNode *root, BvhNodeContainer &nodes, int depth)
356{
357        stack<tPair> tStack;
358        tStack.push(tPair(root, 0));
359
360        while (!tStack.empty())
361        {
362                BvhNode *node = tStack.top().first;
363                const int d = tStack.top().second;
364
365                tStack.pop();
366
367                // found node in specified depth => take this node
368                if ((d == depth) || (node->IsLeaf()))
369                {
370                        nodes.push_back(node);
371                }
372                else
373                {
374                        BvhInterior *interior = static_cast<BvhInterior *>(node);
375
376                        tStack.push(tPair(interior->mFront, d + 1));
377                        tStack.push(tPair(interior->mBack, d + 1));
378                }
379        }
380}
381
382
383SceneEntity **Bvh::GetGeometry(BvhNode *node, int &geometrySize) const
384{
385        geometrySize = node->CountPrimitives();
386        return mGeometry + node->mFirst;
387}
388
389
390bool Bvh::TestPlane(BvhNode *node, int i, bool &bIntersect)
391{
392        // do the test only if necessary
393        if (!(node->mPlaneMask[BvhNode::sCurrentState] & (1 << i)))
394        {
395                return true;
396        }
397
398
399        ////////
400        //-- test the n-vertex
401               
402        if ((node->mBox.GetDistance(sClipPlaneAABBVertexIndices[i * 2 + 0], sFrustum.mClipPlanes[i]) > 0.0f))
403        {
404                // outside
405                node->mPreferredPlane[BvhNode::sCurrentState] = i;
406                return false;
407        }
408
409        ////////////
410        //-- test the p-vertex
411
412        if (node->mBox.GetDistance(sClipPlaneAABBVertexIndices[i * 2 + 1], sFrustum.mClipPlanes[i]) <= 0.0f)
413        {
414                // completely inside: children don't need to check against this plane no more
415                node->mPlaneMask[BvhNode::sCurrentState] ^= 1 << i;
416        }
417        else
418        {
419                bIntersect = true;
420        }
421       
422        return true;
423}
424
425
426int     Bvh::IsWithinViewFrustum(BvhNode *node)
427{
428        bool bIntersect = false;
429
430        if (node->GetParent())
431                node->mPlaneMask[BvhNode::sCurrentState] = node->GetParent()->mPlaneMask[BvhNode::sCurrentState];
432
433        ////////
434        //-- apply frustum culling for the planes with index mPreferredPlane to 6
435
436        for (int i = node->mPreferredPlane[BvhNode::sCurrentState]; i < 6; ++ i)
437                if (!TestPlane(node, i, bIntersect)) return 0;
438       
439
440        //////////
441        //-- apply frustum culling for the planes with index 0 to mPreferredPlane
442
443        for (int i = 0; i < node->mPreferredPlane[BvhNode::sCurrentState]; ++ i)
444                if (!TestPlane(node, i, bIntersect)) return 0;
445       
446        return bIntersect ? -1 : 1;
447}
448
449
450static void CalcNPVertexIndices(const Frustum &frustum, int *indices)
451{
452        for (int i = 0; i < 6; ++ i)
453        {
454                // n-vertex
455                indices[i * 2 + 0] = AxisAlignedBox3::GetIndexNearestVertex(frustum.mClipPlanes[i].mNormal);
456                // p-vertex
457                indices[i * 2 + 1] = AxisAlignedBox3::GetIndexFarthestVertex(frustum.mClipPlanes[i].mNormal);   
458        }
459}
460
461
462void Bvh::InitFrame(Camera *cam, RenderState *state)
463{
464        // = 0011 1111 which means that at the beginning, all six planes have to frustum culled
465        mRoot->mPlaneMask[BvhNode::sCurrentState] = 0x3f;
466
467        cam->CalcFrustum(sFrustum);
468        CalcNPVertexIndices(sFrustum, sClipPlaneAABBVertexIndices);
469
470        // store near plane
471        sNearPlane = Plane3(cam->GetDirection(), cam->GetPosition());
472        sNear = cam->GetNear();
473
474        // update dynamic part of the hierarchy
475        //if (!mDynamicEntities.empty())
476        if (mDynamicGeometrySize)
477        {
478                UpdateDynamicBranch(mDynamicRoot);
479                UpdateDynamicBounds(state);
480        }
481}
482
483
484void Bvh::UpdateDistance(BvhNode *node) const
485{
486        // q: should we use distance to center rather than the distance to the near plane?
487        // distance to near plane can also be used for checking near plane intersection
488        //node->mDistance = sNearPlane.Distance(node->GetBox().Center());
489        node->mDistance = node->GetBox().GetMinDistance(sNearPlane);
490}
491
492
493float Bvh::CalcMaxDistance(BvhNode *node) const
494{
495#if 1
496        return node->GetBox().GetMaxDistance(sNearPlane);
497
498#else
499        // use bounding boxes of geometry to determine max dist
500        float maxDist = .0f;
501
502        int geometrySize;
503        SceneEntity **entities = GetGeometry(node, geometrySize);
504
505        for (int i = 0; i < geometrySize; ++ i)
506        {
507                SceneEntity *ent = entities[i];
508                float dist = ent->GetWorldBoundingBox().GetMaxDistance(sNearPlane);
509
510                if (dist > maxDist) maxDist = dist;
511        }
512
513        return maxDist;
514#endif
515}
516
517
518void Bvh::RenderBounds(BvhNode *node, RenderState *state, bool useTightBounds)
519{
520        // hack: use dummy contayiner as wrapper in order to use multibox function
521        static BvhNodeContainer dummy(1);
522        dummy[0] = node;
523        RenderBounds(dummy, state, useTightBounds);
524}
525
526
527int Bvh::RenderBounds(const BvhNodeContainer &nodes,
528                                          RenderState *state,
529                                          bool useTightBounds)
530{
531        int renderedBoxes;
532
533        if (!useTightBounds)
534        {
535                // if not using tight bounds, rendering boxes in immediate mode
536                // is preferable to vertex arrays (less setup time)
537                BvhNodeContainer::const_iterator nit, nit_end = nodes.end();
538                       
539                for (nit = nodes.begin(); nit != nit_end; ++ nit)
540                {
541                        RenderBoundingBoxImmediate((*nit)->GetBox());
542                }
543
544                renderedBoxes = (int)nodes.size();
545        }
546        else
547        {
548                renderedBoxes = PrepareBoundsWithDrawArrays(nodes);
549                RenderBoundsWithDrawArrays(renderedBoxes, state);
550        }
551
552        return renderedBoxes;
553}
554
555
556int Bvh::PrepareBoundsWithDrawArrays(const BvhNodeContainer &nodes)
557{
558        ///////////////////
559        //-- for the first time we come here => create vbo and indices
560
561        if (!mIndices)
562        {       
563                // create list of indices
564                CreateIndices();
565        }
566
567        if (mVboId == -1)
568        {       
569                // prepare the vbo
570                PrepareVertices();
571        }
572
573        ///////////////
574
575        int numNodes = 0;
576
577        BvhNodeContainer::const_iterator nit, nit_end = nodes.end();
578
579        for (nit = nodes.begin(); nit != nit_end; ++ nit)
580        {
581                BvhNode *node = *nit;
582        const int numIndices = node->mNumTestNodes * NUM_INDICES_PER_BOX;
583               
584                // copy indices
585                memcpy(mIndices + numNodes * NUM_INDICES_PER_BOX,
586                           mTestIndices + node->mIndicesPtr,
587                           numIndices * sizeof(unsigned int));
588
589                numNodes += node->mNumTestNodes;
590        }
591
592        return numNodes;
593}
594
595
596void Bvh::RenderBoundsWithDrawArrays(int numNodes, RenderState *state)
597{
598        /////////
599        //-- Render the vbo
600
601        if (state->GetCurrentVboId() != mVboId)
602        {
603                glBindBufferARB(GL_ARRAY_BUFFER_ARB, mVboId);
604                // set the vertex pointer to the vertex buffer
605                glVertexPointer(3, GL_FLOAT, 0, (char *)NULL); 
606
607                state->SetCurrentVboId(mVboId);
608        }
609
610        // we don't use the last or the first index (they are generate and only used to connect strips)
611        int numElements = numNodes * NUM_INDICES_PER_BOX - 1;
612        // don't render first degenerate index
613        glDrawElements(GL_TRIANGLE_STRIP, numElements, GL_UNSIGNED_INT, mIndices + 1);
614}
615
616
617void Bvh::CreateIndices()
618{
619        // collect bvh nodes
620        BvhNodeContainer nodes;
621        // first collect dynamic nodes so we make sure that they are in the beginning
622        CollectNodes(mDynamicRoot, nodes);
623        // then collect static nodes
624        CollectNodes(mStaticRoot, nodes);
625        //CollectNodes(mRoot, nodes);
626
627        cout << "creating new indices" << endl;
628
629        int numMaxIndices = 0;
630
631        BvhNodeContainer::const_iterator lit, lit_end = nodes.end();
632
633        for (lit = nodes.begin(); lit != lit_end; ++ lit)
634        {
635                int offset = (*lit)->mNumTestNodes * NUM_INDICES_PER_BOX;
636#ifdef ALIGN_INDICES
637                // align with 32 in order to speed up memcopy
638                offset = (offset / 32) * 32 + 32;
639#endif
640                numMaxIndices += offset;
641        }
642
643        cout << "creating global indices buffer" << endl;
644
645        if (mIndices)     delete [] mIndices;
646        if (mTestIndices) delete [] mTestIndices;
647
648        // global buffer: create it once so we don't have
649        // to allocate memory for each individual chunk of the node
650        mIndices = new unsigned int[numMaxIndices];
651        // create new index buffer for the individual nodes
652        mTestIndices = new unsigned int[numMaxIndices];
653       
654        mCurrentIndicesPtr = 0;
655
656        for (lit = nodes.begin(); lit != lit_end; ++ lit)
657        {
658                BvhNode *node = *lit;
659               
660                // resize array
661                node->mIndicesPtr = mCurrentIndicesPtr;
662                int numIndices = 0;
663
664                // the bounding boxes of the test nodes are rendered instead of the node itself
665                // => store their indices
666                for (int i = 0; i < node->mNumTestNodes; ++ i, numIndices += NUM_INDICES_PER_BOX)
667                {
668                        BvhNode *testNode = mTestNodes[node->mTestNodesIdx + i];
669
670                        // add vertex indices of boxes to root node
671                        for (int j = 0; j < NUM_INDICES_PER_BOX; ++ j)
672                        {
673                                mTestIndices[mCurrentIndicesPtr + numIndices + j] = sIndices[j] + testNode->GetId() * 8;
674                        }
675                }
676
677                // align with 32
678#ifdef ALIGN_INDICES
679                const int offset = (numIndices / 32) * 32 + 32;
680#else
681                const int offset = numIndices;
682#endif
683                mCurrentIndicesPtr += offset;
684        }
685}
686
687
688void Bvh::ComputeIds()
689{
690        // collect all nodes
691        BvhNodeContainer nodes;
692        //CollectNodes(mRoot, nodes);
693        // first collect dynamic nodes so we make sure that they are in the beginning
694        CollectNodes(mDynamicRoot, nodes);
695        // then collect static nodes
696        CollectNodes(mStaticRoot, nodes);
697        // also add root
698        nodes.push_back(mRoot);
699
700        // assign ids to all nodes of the hierarchy
701        int i = 0;
702        BvhNodeContainer::const_iterator lit, lit_end = nodes.end();
703
704        for (lit = nodes.begin(); lit != lit_end; ++ lit, ++ i)
705        {
706                (*lit)->SetId(i);
707        }
708}
709
710
711void Bvh::PrepareVertices()
712{
713        // collect all nodes
714        BvhNodeContainer nodes;
715
716        nodes.reserve(GetNumNodes());
717        // first collect dynamic nodes so we make sure that they are in the beginning
718        CollectNodes(mDynamicRoot, nodes);
719        // then collect static nodes
720        CollectNodes(mStaticRoot, nodes);
721        // also add root
722        nodes.push_back(mRoot);
723
724        const unsigned int bufferSize = 8 * (int)nodes.size();
725        mVertices = new Vector3[bufferSize];
726       
727        int i = 0;
728       
729        // store bounding box vertices
730        BvhNodeContainer::const_iterator lit, lit_end = nodes.end();
731
732        for (lit = nodes.begin(); lit != lit_end; ++ lit, i += 8)
733        {
734                BvhNode *node = *lit;
735
736                Vector3 v;
737
738                for (int j = 0; j < 8; ++ j)
739                {
740                        node->GetBox().GetVertex2(j, v);
741                        (static_cast<Vector3 *>(mVertices))[node->GetId() * 8 + j] = v;
742                }
743        }
744
745        glGenBuffersARB(1, &mVboId);
746        glBindBufferARB(GL_ARRAY_BUFFER_ARB, mVboId);
747        glVertexPointer(3, GL_FLOAT, 0, (char *)NULL);
748       
749        glBufferDataARB(GL_ARRAY_BUFFER_ARB,
750                            bufferSize * sizeof(Vector3),
751                            mVertices,
752                                        //GL_STATIC_DRAW_ARB);
753                                        GL_DYNAMIC_DRAW_ARB);
754
755        glBindBufferARB(GL_ARRAY_BUFFER_ARB, 0);
756
757        // data handled by graphics driver from now on
758        DEL_ARRAY_PTR(mVertices);
759
760        cout << "******** created vbos for tighter bounds *********" << endl;
761}
762
763
764void Bvh::UpdateDynamicBounds(RenderState *state)
765{
766        // vbos not created yet
767        if (mVboId == -1) return;
768
769        // collect all nodes
770        static BvhNodeContainer nodes;
771        nodes.clear();
772        //nodes.reserve(GetNumNodes());
773        CollectNodes(mDynamicRoot, nodes);
774
775        const unsigned int bufferSize = 8 * (int)nodes.size();
776        if (!mVertices) mVertices = new Vector3[bufferSize];
777       
778        int i = 0;
779       
780        // store bounding box vertices
781        BvhNodeContainer::const_iterator lit, lit_end = nodes.end();
782
783        for (lit = nodes.begin(); lit != lit_end; ++ lit, i += 8)
784        {
785                BvhNode *node = *lit;
786
787                for (int j = 0; j < 8; ++ j)
788                        (static_cast<Vector3 *>(mVertices))[node->GetId() * 8 + j] = node->GetBox().GetVertex(j);
789        }
790       
791        if (state->GetCurrentVboId() != mVboId)
792        {
793                glBindBufferARB(GL_ARRAY_BUFFER_ARB, mVboId);
794                // set the vertex pointer to the vertex buffer
795                glVertexPointer(3, GL_FLOAT, 0, (char *)NULL); 
796                state->SetCurrentVboId(mVboId);
797        }
798
799        glBufferSubDataARB(GL_ARRAY_BUFFER_ARB, 0,
800                                           bufferSize * sizeof(Vector3),
801                                           mVertices);
802}
803
804
805void Bvh::SetMaxDepthForTestingChildren(int maxDepth)
806{
807        if (maxDepth != mMaxDepthForTestingChildren)
808        {
809                mMaxDepthForTestingChildren = maxDepth;
810                RecomputeBounds();
811        }
812}
813
814
815void Bvh::SetAreaRatioThresholdForTestingChildren(float ratio)
816{
817        if (ratio != mAreaRatioThreshold)
818        {
819                mAreaRatioThreshold = ratio;
820                RecomputeBounds();
821        }
822}
823
824
825void Bvh::RecomputeBounds()
826{
827        // collect all nodes
828        BvhNodeContainer nodes;
829        CollectNodes(mRoot, nodes);
830
831        cout << "recomputing bounds, children will be tested in depth " << mMaxDepthForTestingChildren << endl;
832
833        int success = 0;
834        BvhNodeContainer::const_iterator lit, lit_end = nodes.end();
835
836        for (lit = nodes.begin(); lit != lit_end; ++ lit)
837        {
838                BvhNode *node = *lit;
839
840                // recreate list of nodes that will be queried as a proxy
841                if (CreateNodeRenderList(node))
842                        ++ success;
843        }
844
845        float p = 100.0f * (float)success / nodes.size();
846        cout << "created tighter bounds for " << p << " percent of the nodes" << endl;
847
848        // recreate indices used for indirect mode rendering
849        if (mIndices) CreateIndices();
850}
851
852       
853bool Bvh::CreateNodeRenderList(BvhNode *node)
854{
855        BvhNodeContainer children;
856
857        // collect nodes that will be tested instead of the leaf node
858        // in order to get a tighter bounding box test
859        CollectNodes(node, children, mMaxDepthForTestingChildren);
860       
861
862        // using the tighter bounds is not feasable in case
863        // that the tighter bounds represent nearly the same projected area
864        // as the old bounding box. Test this property using either
865        // volume or area heuristics
866
867        float vol = 0;
868        float area = 0;
869
870        BvhNodeContainer::const_iterator cit;
871
872        for (cit = children.begin(); cit != children.end(); ++ cit)
873                area += (*cit)->GetBox().SurfaceArea();
874
875        const float areaRatio = area / node->GetBox().SurfaceArea();
876       
877        bool success;
878
879        if (areaRatio < mAreaRatioThreshold)
880                success = true;
881        else
882        {
883                // hack: only store node itself
884                children.clear();
885                children.push_back(node);
886
887                success = false;
888        }
889
890        // the new test nodes are added at the end of the vector
891        node->mTestNodesIdx = (int)mTestNodes.size();
892
893        // use the collected nodes as proxy for the occlusion tests
894        for (cit = children.begin(); cit != children.end(); ++ cit)
895        {
896                BvhNode *child = *cit;
897                mTestNodes.push_back(child);
898        }
899
900        node->mNumTestNodes = (int)children.size();
901
902        return success;
903}
904
905
906void Bvh::ResetNodeClassifications()
907{
908        BvhNodeContainer nodes;
909
910        nodes.reserve(GetNumNodes());
911        CollectNodes(mRoot, nodes);
912
913        BvhNodeContainer::const_iterator lit, lit_end = nodes.end();
914
915        for (lit = nodes.begin(); lit != lit_end; ++ lit)
916        {
917                (*lit)->ResetVisibility();
918        }
919}
920
921
922void Bvh::ComputeBvhStats(BvhNode *root, BvhStats &bvhStats)
923{
924        bvhStats.Reset();
925        std::stack<BvhNode *> nodeStack;
926        nodeStack.push(root);
927
928        int numVirtualLeaves = 0;
929        int numGeometry = 0;
930
931        while (!nodeStack.empty())
932        {
933                BvhNode *node = nodeStack.top();
934                nodeStack.pop();
935
936                if (node->IsVirtualLeaf())
937                {
938                        ++ numVirtualLeaves;
939                        numGeometry += node->CountPrimitives();
940
941                        BvhLeaf *leaf = static_cast<BvhLeaf *>(node);
942
943                        bvhStats.mTriangles += CountTriangles(leaf);
944                        bvhStats.mLeafSA += leaf->mBox.SurfaceArea();
945                        bvhStats.mLeafVol += leaf->mBox.GetVolume();
946                }
947                else
948                {
949                        bvhStats.mInteriorSA += node->mBox.SurfaceArea();
950                        bvhStats.mInteriorVol += node->mBox.GetVolume();
951
952                        BvhInterior *interior = static_cast<BvhInterior *>(node);
953                       
954                        nodeStack.push(interior->mBack);
955                        nodeStack.push(interior->mFront);
956                }
957        }
958
959        bvhStats.mGeometryRatio = (float)numGeometry / numVirtualLeaves;
960        bvhStats.mTriangleRatio = (float)bvhStats.mTriangles / numVirtualLeaves;
961        bvhStats.mLeaves = numVirtualLeaves;
962}
963
964
965void Bvh::PrintBvhStats(const BvhStats &bvhStats) const
966{
967        cout << "\n============ BVH statistics =============" << endl;
968        cout << "interiorNodesSA = " << bvhStats.mInteriorSA / mRoot->mBox.SurfaceArea() << endl;
969        cout << "leafNodesSA = " << bvhStats.mLeafSA / mRoot->mBox.SurfaceArea() << endl;
970        cout << "interiorNodesVolume = " << bvhStats.mInteriorVol / mRoot->mBox.GetVolume() << endl;
971        cout << "leafNodesVolume = " << bvhStats.mLeafVol / mRoot->mBox.GetVolume() << endl;
972
973        cout << "geometry per leaf: " << bvhStats.mGeometryRatio << endl;
974        cout << "triangles per leaf: " << bvhStats.mTriangleRatio << endl;
975        cout << "===========================================" << endl << endl;
976}
977
978
979int Bvh::CountTriangles(BvhNode *node) const
980{
981        int numTriangles = 0;
982
983        for (int i = node->mFirst; i <= node->mLast; ++ i)
984        {
985                numTriangles += mGeometry[i]->CountNumTriangles(0);
986        }
987
988        return numTriangles;
989}
990
991
992float Bvh::GetArea(BvhNode *node) const
993{
994        return node->mArea;
995}
996
997
998void Bvh::UpdateNumLeaves(BvhNode *node) const
999{
1000        if (node->IsLeaf())
1001        {
1002                node->mNumLeaves = 1;
1003        }
1004        else
1005        {
1006                BvhNode *f = static_cast<BvhInterior *>(node)->GetFront();
1007                BvhNode *b = static_cast<BvhInterior *>(node)->GetBack();
1008
1009                UpdateNumLeaves(f);
1010                UpdateNumLeaves(b);
1011
1012                node->mNumLeaves = f->mNumLeaves + b->mNumLeaves;
1013        }
1014}
1015
1016
1017void Bvh::CollectVirtualLeaves(BvhNode *node, BvhNodeContainer &leaves)
1018{
1019        stack<BvhNode *> tStack;
1020        tStack.push(node);
1021
1022        while (!tStack.empty())
1023        {
1024                BvhNode *node = tStack.top();
1025                tStack.pop();
1026
1027                if (node->mIsVirtualLeaf)
1028                {
1029                        leaves.push_back(node);
1030                }
1031                else if (!node->IsLeaf())
1032                {
1033                        BvhInterior *interior = static_cast<BvhInterior *>(node);
1034
1035                        tStack.push(interior->mFront);
1036                        tStack.push(interior->mBack);
1037                }
1038        }
1039}
1040
1041
1042void Bvh::SetVirtualLeaves(int numTriangles)
1043{
1044        // first invalidate old virtual leaves
1045        BvhNodeContainer leaves;
1046        CollectVirtualLeaves(mRoot, leaves);
1047
1048        BvhNodeContainer::const_iterator bit, bit_end = leaves.end();
1049
1050        for (bit = leaves.begin(); bit != bit_end; ++ bit)
1051        {
1052                (*bit)->mIsVirtualLeaf = false;
1053        }
1054
1055        mNumVirtualNodes = 0;
1056        // assign new virtual leaves based on specified #triangles per leaf
1057        std::stack<BvhNode *> nodeStack;
1058        nodeStack.push(mRoot);
1059
1060        while (!nodeStack.empty())
1061        {
1062                BvhNode *node = nodeStack.top();
1063                nodeStack.pop();
1064
1065                ++ mNumVirtualNodes;
1066
1067                if (node->IsLeaf())
1068                {
1069                        BvhLeaf *leaf = static_cast<BvhLeaf *>(node);
1070                        leaf->mIsVirtualLeaf = true;
1071                }
1072                else
1073                {
1074                        BvhInterior *interior = static_cast<BvhInterior *>(node);
1075
1076                        BvhNode *f = interior->mFront;
1077                        BvhNode *b = interior->mBack;
1078
1079                        if (node->mIsMaxDepthForVirtualLeaf ||
1080                                (CountTriangles(node) <= numTriangles))
1081                        {
1082                                 node->mIsVirtualLeaf = true;
1083                        }
1084                        else
1085                        {
1086                                nodeStack.push(interior->mBack);
1087                                nodeStack.push(interior->mFront);
1088                        }
1089                }
1090        }
1091
1092        /// Reset the node states
1093        ResetNodeClassifications();
1094}
1095
1096
1097void Bvh::ComputeMaxDepthForVirtualLeaves()
1098{
1099        // We initialize the maximal depth for virtual leaves
1100        // of this bvh, i.e., the nodes that are used as
1101        // leaves of the hierarchy during traversal.
1102
1103        // Initially they are set either
1104        // a) to the real leaves of the hierarchy or
1105        // b) the point where the subdivision on object level ends
1106        // and the subsequent nodes are just used to provide tighter bounds
1107        // (see article for the notations)
1108
1109        std::stack<BvhNode *> nodeStack;
1110        nodeStack.push(mRoot);
1111
1112        while (!nodeStack.empty())
1113        {
1114                BvhNode *node = nodeStack.top();
1115                nodeStack.pop();
1116
1117                if (node->IsLeaf())
1118                {
1119                        node->mIsMaxDepthForVirtualLeaf = true;
1120                }
1121                else
1122                {
1123                        BvhInterior *interior = static_cast<BvhInterior *>(node);
1124
1125                        BvhNode *f = interior->mFront;
1126                        BvhNode *b = interior->mBack;
1127
1128                        if ((f->mFirst == b->mFirst) && (f->mLast == b->mLast))
1129                        {
1130                                // point reached where beyond there would be no further reduction
1131                                // as both subtrees contain the same objects => stop here
1132                                // The tree beyond the current node is used to describe
1133                                // tighter bounds on the geometry contained  in it
1134                                node->mIsMaxDepthForVirtualLeaf = true;
1135                        }
1136                        else
1137                        {
1138                                nodeStack.push(f);
1139                                nodeStack.push(b);
1140                        }
1141                }
1142        }
1143}
1144
1145
1146// this function must be called once after hierarchy creation
1147void Bvh::PostProcess()
1148{
1149        CreateRoot();
1150
1151        ComputeMaxDepthForVirtualLeaves();
1152        // set virtual leaves for specified number of triangles
1153        SetVirtualLeaves(INITIAL_TRIANGLES_PER_VIRTUAL_LEAVES);
1154        /// for each node compute the number of leaves under this node
1155        UpdateNumLeaves(mRoot);
1156        // compute new ids
1157        ComputeIds();
1158        // specify bounds for occlusion tests
1159        RecomputeBounds();
1160
1161        mBox = mRoot->GetBox();
1162
1163        // compute and print stats for whole (static + dynamic) bvh
1164        ComputeBvhStats(mRoot, mBvhStats);
1165        PrintBvhStats(mBvhStats);
1166
1167        // also output dynamic stats
1168        if (mDynamicGeometrySize)
1169        {
1170                BvhStats bvhStats;
1171                ComputeBvhStats(mDynamicRoot, bvhStats);
1172
1173                cout << "\n=========== Dynamic BVH statistics: =========" << endl;
1174                cout << "leaves = " << bvhStats.mLeaves << endl;
1175                cout << "interiorNodesSA = " << bvhStats.mInteriorSA << endl;
1176                cout << "leafNodesSA = " << bvhStats.mLeafSA << endl;
1177                cout << "interiorNodesVolume = " << bvhStats.mInteriorVol  << endl;
1178                cout << "leafNodesVolume = " << bvhStats.mLeafVol << endl;
1179
1180                cout << "geometry per leaf: " << bvhStats.mGeometryRatio << endl;
1181                cout << "triangles per leaf: " << bvhStats.mTriangleRatio << endl;
1182                cout << "=============================================" << endl << endl;
1183        }
1184}
1185
1186
1187void Bvh::RenderBoundingBoxImmediate(const AxisAlignedBox3 &box)
1188{
1189        const Vector3 l = box.Min();
1190        const Vector3 u = box.Max();
1191
1192        ///////////
1193        //-- render AABB as triangle strips
1194
1195        glBegin(GL_TRIANGLE_STRIP);
1196
1197        // render first half of AABB
1198        glVertex3f(l.x, l.y, u.z);
1199        glVertex3f(u.x, l.y, u.z);
1200        glVertex3f(l.x, u.y, u.z);
1201        glVertex3f(u.x, u.y, u.z);
1202        glVertex3f(l.x, u.y, l.z);
1203        glVertex3f(u.x, u.y, l.z);
1204        glVertex3f(l.x, l.y, l.z);
1205        glVertex3f(u.x, l.y, l.z);
1206
1207        glPrimitiveRestartNV();
1208
1209        // render second half of AABB
1210        glVertex3f(l.x, u.y, u.z);
1211        glVertex3f(l.x, u.y, l.z);
1212        glVertex3f(l.x, l.y, u.z);
1213        glVertex3f(l.x, l.y, l.z);
1214        glVertex3f(u.x, l.y, u.z);
1215        glVertex3f(u.x, l.y, l.z);
1216        glVertex3f(u.x, u.y, u.z);
1217        glVertex3f(u.x, u.y, l.z);
1218
1219        glEnd();
1220}
1221
1222
1223static void RenderBoxForViz(const AxisAlignedBox3 &box)
1224{
1225        glBegin(GL_LINE_LOOP);
1226        glVertex3d(box.Min().x, box.Max().y, box.Min().z);
1227        glVertex3d(box.Max().x, box.Max().y, box.Min().z);
1228        glVertex3d(box.Max().x, box.Min().y, box.Min().z);
1229        glVertex3d(box.Min().x, box.Min().y, box.Min().z);
1230        glEnd();
1231
1232        glBegin(GL_LINE_LOOP);
1233        glVertex3d(box.Min().x, box.Min().y, box.Max().z);
1234        glVertex3d(box.Max().x, box.Min().y, box.Max().z);
1235        glVertex3d(box.Max().x, box.Max().y, box.Max().z);
1236        glVertex3d(box.Min().x, box.Max().y, box.Max().z);
1237        glEnd();
1238
1239        glBegin(GL_LINE_LOOP);
1240        glVertex3d(box.Max().x, box.Min().y, box.Min().z);
1241        glVertex3d(box.Max().x, box.Min().y, box.Max().z);
1242        glVertex3d(box.Max().x, box.Max().y, box.Max().z);
1243        glVertex3d(box.Max().x, box.Max().y, box.Min().z);
1244        glEnd();
1245
1246        glBegin(GL_LINE_LOOP);
1247        glVertex3d(box.Min().x, box.Min().y, box.Min().z);
1248        glVertex3d(box.Min().x, box.Min().y, box.Max().z);
1249        glVertex3d(box.Min().x, box.Max().y, box.Max().z);
1250        glVertex3d(box.Min().x, box.Max().y, box.Min().z);
1251        glEnd();
1252
1253        glBegin(GL_LINE_LOOP);
1254        glVertex3d(box.Min().x, box.Min().y, box.Min().z);
1255        glVertex3d(box.Max().x, box.Min().y, box.Min().z);
1256        glVertex3d(box.Max().x, box.Min().y, box.Max().z);
1257        glVertex3d(box.Min().x, box.Min().y, box.Max().z);
1258        glEnd();
1259
1260        glBegin(GL_LINE_LOOP);
1261        glVertex3d(box.Min().x, box.Max().y, box.Min().z);
1262        glVertex3d(box.Max().x, box.Max().y, box.Min().z);
1263        glVertex3d(box.Max().x, box.Max().y, box.Max().z);
1264        glVertex3d(box.Min().x, box.Max().y, box.Max().z);
1265
1266        glEnd();
1267}
1268
1269
1270static Technique GetVizTechnique()
1271{
1272        Technique tech;
1273        tech.Init();
1274
1275        //tech.SetLightingEnabled(false);
1276        //tech.SetDepthWriteEnabled(false);
1277
1278        tech.SetEmmisive(RgbaColor(1.0f, 1.0f, 1.0f, 1.0f));
1279        tech.SetDiffuse(RgbaColor(1.0f, 1.0f, 1.0f, 1.0f));
1280        tech.SetAmbient(RgbaColor(1.0f, 1.0f, 1.0f, 1.0f));
1281
1282        return tech;
1283}
1284
1285
1286void Bvh::RenderBoundsForViz(BvhNode *node,
1287                                                         RenderState *state,
1288                                                         bool useTightBounds)
1289{
1290        Technique *oldTech = state->GetState();
1291        // we set a simple material
1292        static Technique boxMat = GetVizTechnique();
1293        boxMat.Render(state);
1294
1295        if (!useTightBounds)
1296        {
1297                RenderBoxForViz(node->GetBox());
1298                /*glPolygonMode(GL_FRONT, GL_LINE);
1299                RenderBoundingBoxImmediate(node->GetBox());
1300                glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);*/
1301        }
1302        else
1303        {
1304                for (int i = 0; i < node->mNumTestNodes; ++ i)
1305                {
1306                        RenderBoxForViz(mTestNodes[node->mTestNodesIdx + i]->GetBox());
1307                }
1308        }
1309
1310        if (oldTech) oldTech->Render(state);
1311}
1312
1313
1314
1315////////////////////////
1316//-- functions for construction of the dynamic hierarchy
1317
1318int Bvh::SortTriangles(BvhLeaf *leaf,
1319                                           int axis,
1320                                           float position
1321                                           )
1322{
1323        int i = leaf->mFirst;
1324        int j = leaf->mLast;
1325
1326    while (1)
1327        {
1328                while (mGeometry[i]->GetWorldCenter()[axis] < position) ++ i;
1329                //while ((j > 0) && (position < mGeometry[j]->GetWorldCenter()[axis])) -- j;
1330                while (position < mGeometry[j]->GetWorldCenter()[axis]) -- j;
1331
1332                // sorting finished
1333                if (i >= j) break;
1334
1335                // swap entities
1336                swap(mGeometry[i], mGeometry[j]);
1337                       
1338                ++ i;
1339                -- j;
1340        }
1341
1342        return j;
1343}
1344
1345
1346int Bvh::SortTrianglesSpatialMedian(BvhLeaf *leaf,
1347                                                                        int axis
1348                                                                        )
1349{
1350        // spatial median
1351        float m = leaf->mBox.Center()[axis];
1352        return SortTriangles(leaf, axis, m);
1353}
1354
1355
1356BvhNode *Bvh::SubdivideLeaf(BvhLeaf *leaf,
1357                                                        int parentAxis
1358                                                        )
1359{
1360        if (TerminationCriteriaMet(leaf))
1361        {
1362                //cout << "leaf constructed:" << leaf->mBox << " " << leaf->mFirst << " " << leaf->mLast << endl;
1363                return leaf;
1364        }
1365
1366        //const int axis = (parentAxis + 1) % 3;
1367        const int axis = leaf->mBox.MajorAxis();
1368
1369        const int scale = 20;
1370
1371        // position of the split in the partailly sorted array of triangles
1372        // corresponding to this leaf
1373        int split = -1;
1374        float pos = -1.0f;
1375       
1376        // Spatial median subdivision
1377        split = SortTrianglesSpatialMedian(leaf, axis);
1378        pos = leaf->mBox.Center()[axis];
1379       
1380        if (split == leaf->mLast)
1381        {
1382                // no split could be achieved => just halve number of objects
1383                split = (leaf->mLast - leaf->mFirst) / 2;
1384                cerr << "no reduction " << leaf->CountPrimitives() << " " << leaf->mFirst << " " << leaf->mLast << endl;
1385        }
1386
1387        // create two more nodes
1388        mNumNodes += 2;
1389        BvhInterior *parent = new BvhInterior(leaf->GetParent());
1390        parent->mFirst = leaf->mFirst;
1391        parent->mLast = leaf->mLast;
1392       
1393        parent->mAxis = axis;
1394        parent->mBox = leaf->mBox;
1395        parent->mDepth = leaf->mDepth;
1396
1397        BvhLeaf *front = new BvhLeaf(parent);
1398
1399        parent->mBack = leaf;
1400        parent->mFront = front;
1401               
1402        // now assign the triangles to the subnodes
1403        front->mFirst = split + 1;
1404        front->mLast = leaf->mLast;
1405        front->mDepth = leaf->mDepth + 1;
1406
1407        leaf->mLast = split;
1408        leaf->mDepth = front->mDepth;
1409        leaf->mParent = parent;
1410       
1411        front->mBox = ComputeBoundingBox(mGeometry + front->mFirst, front->CountPrimitives());
1412        leaf->mBox = ComputeBoundingBox(mGeometry + leaf->mFirst, leaf->CountPrimitives());
1413
1414        // recursively continue subdivision
1415        parent->mBack = SubdivideLeaf(static_cast<BvhLeaf *>(parent->mBack), axis);
1416        parent->mFront = SubdivideLeaf(static_cast<BvhLeaf *>(parent->mFront), axis);
1417       
1418        return parent;
1419}
1420
1421
1422bool Bvh::TerminationCriteriaMet(BvhLeaf *leaf) const
1423{
1424        const bool criteriaMet =
1425                (leaf->mDepth > mMaxDepthForDynamicBranch) ||
1426                (leaf->CountPrimitives() == 1);
1427
1428        return criteriaMet;
1429}
1430
1431
1432void Bvh::UpdateDynamicBranch(BvhNode *node)
1433{
1434        if (node->IsLeaf())
1435        {
1436                int numEntities;
1437                SceneEntity **entities = GetGeometry(node, numEntities);
1438
1439                node->mBox = ComputeBoundingBox(entities, numEntities);
1440                //cout << "box: " << node->mBox << endl;
1441        }
1442        else
1443        {
1444                BvhNode *f = static_cast<BvhInterior *>(node)->GetFront();
1445                BvhNode *b = static_cast<BvhInterior *>(node)->GetBack();
1446
1447                UpdateDynamicBranch(f);
1448                UpdateDynamicBranch(b);
1449
1450                node->mBox = f->mBox;
1451                node->mBox.Include(b->mBox);
1452        }
1453}
1454
1455
1456void Bvh::CreateDynamicBranch()
1457{
1458        // the bvh has two main branches
1459        // a static branch (the old root), and adynamic branch
1460        // we create a 'dynamic' leaf which basically is a container
1461        // for all dynamic objects underneath
1462
1463        // the bounding boxes of the dynamic tree must be updated
1464        // once each frame in order to be able to incorporate
1465        // the movements of the objects within
1466
1467        DEL_PTR(mDynamicRoot);
1468
1469        BvhLeaf *l = new BvhLeaf(mRoot);
1470        mDynamicRoot = l;
1471
1472        l->mBox = ComputeBoundingBox(mGeometry + mStaticGeometrySize, (int)mDynamicGeometrySize);
1473
1474        l->mFirst = (int)mStaticGeometrySize;
1475        l->mLast = (int)mGeometrySize - 1;
1476        l->mArea = l->mBox.SurfaceArea();
1477       
1478        cout << "creating dynamic bvh branch"  << endl;
1479
1480        if (mDynamicGeometrySize)
1481                mDynamicRoot = SubdivideLeaf(l, 0);
1482}
1483
1484
1485bool Bvh::IntersectsNearPlane(BvhNode *node) const
1486{
1487        // note: we have problems with large scale object penetrating the near plane
1488        // (e.g., objects in the distance which are always handled to be visible)
1489        // especially annoying is this problem when using the frustum
1490        // fitting on the visible objects for shadow mapping
1491        // but don't see how to solve this issue without using costly calculations
1492       
1493        // we stored the near plane distance => we can use it also here
1494        float distanceToNearPlane = node->GetDistance();
1495        //float distanceToNearPlane = node->GetBox().GetMinDistance(sNearPlane);
1496       
1497        return (distanceToNearPlane < sNear);
1498}
1499
1500
1501void Bvh::CreateRoot()
1502{
1503        // create new root
1504        mRoot = new BvhInterior(NULL);
1505
1506        // the separation is a purely logical one
1507        // the bounding boxes of the child nodes are
1508        // identical to those of the root node
1509
1510        mRoot->mBox = mStaticRoot->mBox;
1511        mRoot->mBox.Include(mDynamicRoot->mBox);
1512
1513        mRoot->mArea = mRoot->mBox.SurfaceArea();
1514
1515        mRoot->mFirst = 0;
1516        mRoot->mLast = (int)mGeometrySize - 1;
1517
1518        //cout<<"f: " << mRoot->mFirst<< " l: " <<mRoot->mLast << endl;
1519        // add static root on left subtree
1520        mRoot->mFront = mStaticRoot;
1521        mStaticRoot->mParent = mRoot;
1522
1523        // add dynamic root on left subtree
1524        mRoot->mBack = mDynamicRoot;
1525        mDynamicRoot->mParent = mRoot;
1526}
1527
1528
1529}
Note: See TracBrowser for help on using the repository browser.