source: GTP/trunk/Lib/Vis/Preprocessing/src/ViewCell.h @ 1771

Revision 1771, 20.3 KB checked in by bittner, 18 years ago (diff)

merge, preparing sampling strategy support for mixed distributions, filter changes, histogram output for preprocessor

Line 
1#ifndef _ViewCell_H__
2#define _ViewCell_H__
3
4#include "Mesh.h"
5#include "Containers.h"
6#include "Ray.h"
7#include "Statistics.h"
8#include "Material.h"
9#include "gzstream.h"
10
11
12namespace GtpVisibilityPreprocessor {
13
14struct Triangle3;
15
16class BspInterior;
17class BspPvs;
18class BspLeaf;
19class VspLeaf;
20class KdLeaf;
21class ViewCellInterior;
22class MergeCandidate;
23class ViewCellsManager;
24class ViewCellLeaf;
25
26
27
28/** Statistics for a view cell partition.
29*/
30
31class ViewCellsStatistics: public StatisticsBase
32{
33public:
34
35        /// number of view cells
36        int viewCells;
37        /// cost of the PVS
38        float pvsCost;
39        /// largest PVS of all view cells
40        float maxPvs;
41        /// smallest PVS of all view cells
42        float minPvs;
43        /// view cells with empty PVS
44        int emptyPvs;
45        /// number of leaves covering the view space
46        int leaves;
47        /// largest number of leaves covered by one view cell
48        int maxLeaves;
49        /// number of invalid view cells
50        int invalid;
51
52    // Constructor
53        ViewCellsStatistics()
54        {
55                Reset();
56        }
57
58        double AvgLeaves() const {return (double)leaves / (double)viewCells;};
59        double AvgPvs() const {return (double)pvsCost / (double)viewCells;};
60
61        void Reset()
62        {
63                viewCells = 0;
64                pvsCost = 0;
65                maxPvs = 0;
66
67                minPvs = 999999;
68                emptyPvs = 0;
69                leaves = 0;
70                maxLeaves = 0;
71                invalid = 0;
72        }
73
74        void Print(ostream &app) const;
75
76        friend ostream &operator<<(ostream &s, const ViewCellsStatistics &stat)
77        {
78                stat.Print(s);
79                return s;
80        }
81};
82
83
84class ViewCellsTreeStats
85{
86public:
87        int mPass;
88   
89        int mNumViewCells;
90               
91        float mRenderCostDecrease;
92
93    float mTotalRenderCost;
94   
95        float mCurrentPvsCost;
96                                                       
97        float mExpectedCost;
98   
99        float mAvgRenderCost;
100       
101        float mDeviation;
102                       
103        float mTotalPvsCost;
104       
105        int mEntriesInPvs;
106   
107        float mMemoryCost;
108       
109        int mPvsSizeDecr;
110
111 
112        float mVolume;
113
114
115        void Reset()
116        {
117                mPass = 0;
118                mNumViewCells = 0;
119                mRenderCostDecrease = 0;
120                mTotalRenderCost = 0;
121                mCurrentPvsCost = 0;
122                mExpectedCost = 0;
123                mAvgRenderCost = 0;
124                mDeviation = 0;
125                mTotalPvsCost = 0;
126                mEntriesInPvs = 0;
127                mMemoryCost = 0;
128                mPvsSizeDecr = 0;
129                mVolume = 0;
130        }
131
132
133        void Print(ostream &app) const;
134
135        friend ostream &operator<<(ostream &s, const ViewCellsTreeStats &stat)
136        {
137                stat.Print(s);
138                return s;
139        }
140};
141
142
143/**
144        A view cell. View cells are regions in space. The visibility informations,
145        i.e., the primitives seen by the view cell are stored in a PVs.
146        A view cell can be represented in many different ways, e.g.,
147        a mesh representation.
148*/
149class ViewCell: public MeshInstance
150{
151        friend class ViewCellsTree;
152        friend class ViewCellsManager;
153
154public:
155        ViewCell();
156
157        /** Constructor taking a mesh representing the shape of the viewcell.
158        */
159        ViewCell(Mesh *mesh);
160
161        /** Default destructor.
162        */
163        virtual ~ViewCell();
164        /** Returns Pvs.
165        */
166        const ObjectPvs &GetPvs() const;
167        /** Returns pvs.
168        */
169        ObjectPvs &GetPvs();
170        /** Completely substitutes the pvs.
171        */
172        void SetPvs(const ObjectPvs &pvs);
173        /** Type of view cells.
174        */
175        int Type() const;
176        /** Adds a passing ray to the passing ray container.
177        */
178        void AddPassingRay(const Ray &ray, const int contributions);
179        /** Returns volume of the view cell.
180        */
181        float GetVolume() const;
182        /** Returns area of the view cell.
183        */
184        float GetArea() const;
185        /** Sets the volume of the view cell.
186        */
187        void SetVolume(float volume);
188        /** Sets the area of the view cell.
189        */
190        void SetArea(float area);
191        /** if this view cell is the root of a view cell hierarchy
192        */
193        bool IsRoot() const;
194        /** Returns parent view cell.
195        */
196        ViewCellInterior *GetParent() const;
197        /** Sets parent of this view cell.
198        */
199        void SetParent(ViewCellInterior *parent);
200        /** Sets the mesh for this view cell.
201        */
202        void SetMesh(Mesh *mesh);
203
204        /** Sets this view cell to be a valid view cell according to some criteria.
205        */
206        void SetValid(const bool valid);
207        /** Returns true if this view cell is considered to be valid according to
208                some criteria.
209        */
210        bool GetValid() const;
211
212        /** Returns estimated render cost of this view cell.
213        */
214        float GetRenderCost() const;
215
216        /** set color for visiualizations.
217        */
218        void SetColor(const RgbColor &color);
219
220        /** get color for visualuzations.
221        */
222    RgbColor GetColor() const;
223
224        /** Adds a sample to the pvs.
225                @param sample the sample to be added
226                @param pdf a continuos measure of visibility
227                @param contribution returns the contribution of this sample to the pvs
228        */
229        bool AddPvsSample(Intersectable *sample, const float pdf, float &contribution);
230 
231        /// Rays piercing this view cell.
232        //RayContainer mPiercingRays;
233
234        /** if this is a view cell correspending to a leaf in a hierarchy.
235        */
236        virtual bool IsLeaf() const = 0;
237
238        static bool SmallerPvs(const ViewCell *a, const ViewCell *b)
239        {
240                // HACK: take scalar value because pvs may not have been stored properly
241#if 1
242                return a->mPvsCost < b->mPvsCost;
243#else
244                return a->GetPvs().EvalPvsCost() < b->GetPvs().EvalPvsCost();
245#endif
246        }
247
248  static bool GreaterOrEqualPvs(const ViewCell *a, const ViewCell *b) {
249        return !SmallerPvs(a, b);
250  }
251
252        static bool SmallerRenderCost(const ViewCell *a, const ViewCell *b)
253        {
254                return a->GetRenderCost() < b->GetRenderCost();
255        }
256
257        static bool LargerRenderCost(const ViewCell *a, const ViewCell *b)
258        {
259                return a->GetRenderCost() > b->GetRenderCost();
260        }
261
262        /** Sets merge cost used for merging this view cell from other cells.
263                @hack The function is available for leaves also to have a common interface,
264                but it should be less than zero for leaves.
265                */
266        void SetMergeCost(const float mergeCost);
267
268        /** Returns merge cost needed to merge this leaf from other cells.
269                @hack The function is available for leaves also to have a common interface,
270                but it should be less than zero for leaves.
271        */
272        float GetMergeCost() const;
273
274  void UpdatePvsCost() {
275        mPvsCost = GetPvs().EvalPvsCost();
276  }
277
278  void SetPvsCost(const float c) {
279        mPvsCost = c;
280  }
281
282  float GetPvsCost() const {
283        return mPvsCost;
284  }
285
286        //////////
287        //-- mailing stuff
288
289        static void NewMail(const int reserve = 1)
290        {
291                sMailId += sReservedMailboxes;
292                sReservedMailboxes = reserve;
293        }
294
295        void Mail() { mMailbox = sMailId; }
296        bool Mailed() const { return mMailbox == sMailId; }
297
298        void Mail(const int mailbox) { mMailbox = sMailId + mailbox; }
299        bool Mailed(const int mailbox) const { return mMailbox == sMailId + mailbox; }
300
301        int IncMail() { return ++ mMailbox - sMailId; }
302
303
304        // last mail id -> warning not thread safe!
305        // both mailId and mailbox should be unique for each thread!!!
306        static int sMailId;
307        static int sReservedMailboxes;
308       
309  int GetFilteredPvsSize() const {
310        return mFilteredPvsSize;
311  }
312
313  void SetFilteredPvsSize(const int s) {
314        mFilteredPvsSize = s;
315  }
316
317protected:
318
319        /// parent view cell in the view cell hierarchy
320        ViewCellInterior *mParent;
321        /// the potentially visible objects
322        ObjectPvs mPvs;
323        /// the volume of this view cell
324        float mVolume;
325        /// the area of this view cell
326        float mArea;
327        /// the cost that were paid for merging this view cells from two others.
328        float mMergeCost;
329        /// if the view cell is valid view space
330        bool mValid;
331        /// color used for consistent visualization
332        RgbColor mColor;
333        /// store pvs size, used for evaluation purpose when pvss are stored only in the leaves
334        float mPvsCost;
335        /** stores number of entries in pvs
336            this variable has the same value as mPvsSize for object pvs,
337                but usually not for kd cell based pvs
338        */
339        int mEntriesInPvs;
340        /** if the pvs size scalar (+ entries into pvs)
341                is up to date and corresponding to the real pvs size
342        */
343        bool mPvsSizeValid;
344
345  /// Filter cost of the pvs
346  int mFilteredPvsSize;
347
348};
349
350
351class ViewCellInterior: public ViewCell
352{
353        friend class ViewCellsManager;
354
355public:
356        ViewCellInterior();
357        ~ViewCellInterior();
358
359        ViewCellInterior(Mesh *mesh);
360       
361        /** Sets pointer from parent to child and vice versa.
362        */
363        void SetupChildLink(ViewCell *l);
364        void ReplaceChildLink(ViewCell *prev, ViewCell *cur);
365
366        void RemoveChildLink(ViewCell *l);
367        bool IsLeaf() const;
368
369        void SetCost(const float c) {
370                mCost = c;
371        }
372       
373        float GetCost() const {
374                return mCost;
375        }
376 
377  ViewCellContainer mChildren;
378
379protected:
380  /** overall cost resulting from the merge */
381  float mCost;
382};
383
384
385/**
386        Leaf of the view cell.
387*/
388class ViewCellLeaf: public ViewCell
389{
390public:
391        ViewCellLeaf()  {  mActiveViewCell = this; }
392        ViewCellLeaf(Mesh *mesh):
393        ViewCell(mesh) { mActiveViewCell = this; }
394
395        bool IsLeaf() const
396        {
397                return true;
398        }
399
400        /** Returns active view cell, i.e. this view cell or
401                a parent view cell which is set as active view cell.
402        */
403        ViewCell *GetActiveViewCell() const
404        { return mActiveViewCell; }
405
406        /** Sets this view cell to be an active view cell.
407        */
408        void SetActiveViewCell(ViewCell *vc)
409        { mActiveViewCell = vc;}
410       
411        /** points to the currently active view cell. This is the
412                view cell representing the current brach.
413        */
414        ViewCell *mActiveViewCell;
415};
416
417
418/** Leaf of the view cell hierarchy corresponding
419        to a leaf in a spatial hierarchy.
420*/
421template<typename T>
422class HierarchyLeafViewCell: public ViewCellLeaf
423{
424public:
425
426        HierarchyLeafViewCell<T>(): ViewCellLeaf() {  }
427        HierarchyLeafViewCell<T>(Mesh *mesh):
428        ViewCellLeaf(mesh) {  }
429               
430        bool IsLeaf() const
431        {
432                return true;
433        }
434
435        /// Leaves of some hierarchy which contains this view cell.
436        vector<T> mLeaves;
437};
438
439
440typedef HierarchyLeafViewCell<VspLeaf *> VspViewCell;
441typedef HierarchyLeafViewCell<BspLeaf *> BspViewCell;
442typedef HierarchyLeafViewCell<KdLeaf *> KdViewCell;
443
444
445
446
447class ViewCellsTree
448{
449        friend class ViewCellsManager;
450        friend class ViewCellsParseHandlers;
451
452public:
453        ViewCellsTree();
454        /** View cells tree constructor taking a view cell mnanager as parameter
455        */
456        ViewCellsTree(ViewCellsManager *vcm);
457        ~ViewCellsTree();
458
459        /** Returns number of leaves this view cell consists of.
460        */
461        int GetNumInitialViewCells(ViewCell *vc) const;
462
463        /** Collects leaves corresponding to a view cell.
464        */
465        void CollectLeaves(ViewCell *vc, ViewCellContainer &leaves) const;
466
467        /** Merges view cells according to some cost heuristics.
468        */
469        int ConstructMergeTree(const VssRayContainer &rays, const ObjectContainer &objects);
470       
471        /** Refines view cells using shuffling, i.e., border leaves
472                of two view cells are exchanged if the resulting view cells
473                are tested to be "better" than the old ones.
474                @returns number of refined view cells
475        */
476        int RefineViewCells(const VssRayContainer &rays, const ObjectContainer &objects);
477       
478        /** Assign colors to the viewcells so that they can be renderered interactively without
479            color flickering. 
480        */
481        void AssignRandomColors();
482
483        /** Updates view cell stats for this particular view cell.
484        */
485        void UpdateViewCellsStats(ViewCell *vc, ViewCellsStatistics &vcStat);
486
487        /** Get costs resulting from each merge step.
488        */
489        void GetCostFunction(vector<float> &costFunction);
490       
491        /** Returns storage cost resulting from each merge step.
492        */
493        void GetStorageFunction(vector<int> &storageCost);
494
495        /** Returns optimal set of view cells for a given number of view cells.
496        */
497        void CollectBestViewCellSet(ViewCellContainer &viewCells, const int numViewCells);
498
499        /** Root of view cells tree.
500        */
501        ViewCell *GetRoot() const;
502
503        /** Returns pvs of view cell.
504                @note pvs is returned per reference if tree is not compressed,
505                per copy else.
506        */
507        void GetPvs(ViewCell *vc, ObjectPvs &pvs) const;
508
509        /** Returns pvs size (i.e. the render cost of the stored objects)
510        */
511        float GetPvsCost(ViewCell *vc) const;
512 
513
514        /** Returns number of entries associated with this view cell.
515
516                This returns the same value as the "GetPvsSize" function for object pvs
517                but most likely different values if we use object space grouping.
518                E.g., using bounding volumes.
519        */
520        int GetPvsEntries(ViewCell *vc) const;
521
522        /** Returns the number of physically stored entries in the view cells sub tree.
523                This can vary based on the current storage method
524        */
525        int CountStoredPvsEntries(ViewCell *root) const;
526
527        /** Returns memory cost of this view cell.
528        */
529        float GetMemoryCost(ViewCell *vc) const;
530
531        /** Sets method of storage for view cells.
532        */
533        void SetViewCellsStorage(int type);
534
535        /** pvs storage methods
536        */
537        enum {PVS_IN_INTERIORS, COMPRESSED, PVS_IN_LEAVES};
538
539        /** If view cells in this tree have compressed pvs.
540        */
541        int ViewCellsStorage() const;
542
543        /** Returns active view cell that is in the path of this view cell.
544        */
545        ViewCell *GetActiveViewCell(ViewCellLeaf *vc) const;
546
547        /** Sets the leaves to be the currently active view cells.
548        */
549    void SetActiveSetToLeaves();
550
551        /** Propagates pvs up the tree to the root and downwards the tree.
552        */
553        void PropagatePvs(ViewCell *vc);
554
555        /** Exports view cells to file.
556        */
557        bool Export(OUT_STREAM &stream, const bool exportPvs = false);
558
559        /** Export statistics of this view cell tree.
560        */
561        void ExportStats(const string &mergeStats);
562
563        /** Sets root of hierarchy.
564        */
565        void SetRoot(ViewCell *root);
566
567        /** Assignes unique ids to view cells.
568        */
569        void CreateUniqueViewCellsIds();
570
571        /** Resets pvs of whole tree.
572        */
573        void ResetPvs();
574
575        /** Counts pvs of the view cell taking the kd cells into account.
576        */
577        int CountKdPvs(const ViewCellLeaf *vc) const;
578
579        /** Sets pointer to view cells manager.
580        */
581        void SetViewCellsManager(ViewCellsManager *vcm);
582
583        void Update();
584
585protected:
586
587        /** Reads the environment and sets member variables.
588        */
589        void ReadEnvironment();
590
591        /////////////////////////////////////////////////////////////////
592        //                    merge related stuff                      //
593        /////////////////////////////////////////////////////////////////
594
595        /** Computes render cost of the merged pvs.
596        */
597        float ComputeMergedPvsCost(const ObjectPvs &pvs1, const ObjectPvs &pvs2) const;
598
599        /** Returns cost of this leaf according to current heuristics.
600        */
601        float GetCostHeuristics(ViewCell *vc) const;
602
603        /** Returns cost of leaf.
604        */
605        float GetRenderCost(ViewCell *vc) const;
606
607        /** Evaluates the merge cost of this merge candidate pair.
608        */
609        void EvalMergeCost(MergeCandidate &mc) const;
610
611        /** Variance of leaf.
612        */
613        float GetVariance(ViewCell *vc) const;
614
615        /** Standard deviation of leaf.
616        */
617        float GetDeviation(ViewCell *vc) const;
618
619        /** Tries to set this merge candidate to valid.
620                @returns false if both view cells are the same
621        */
622        bool ValidateMergeCandidate(MergeCandidate &mc) const;
623
624        /** Merge view cells of leaves l1 and l2.
625                @returns difference in pvs size
626        */
627        ViewCellInterior *MergeViewCells(ViewCell *l, ViewCell *r, float &pvsDiff); //const;
628
629        /** Shuffles, i.e. takes border leaf from view cell 1 and adds it
630                to view cell 2.
631        */
632        void ShuffleLeaf(ViewCell *leaf, ViewCellInterior *vc1, ViewCellInterior *vc2) const;   
633               
634        /** Shuffles the leaves, i.e., tests if exchanging
635                the leaves helps in improving the view cells.
636        */
637        bool ShuffleLeaves(MergeCandidate &mc) const;
638
639        /** Calculates cost for merge of view cell 1 and 2.
640        */
641        float EvalShuffleCost(ViewCell *leaf,
642                                                  ViewCellInterior *vc1,
643                                                  ViewCellInterior *vc2) const;
644
645        /** Exports a snapshot of the merged view cells to disc.
646        */
647        void ExportMergedViewCells(ViewCellContainer &viewCells,
648                                                           const ObjectContainer &objects,
649                                                           const int numNewViewCells);
650
651        /** Merge queue must be reset after some time because expected value
652                may not be valid.
653        */
654        void ResetMergeQueue();
655
656        /** Updates the current cut of view cells.
657                @returns number of newly merged view cells
658        */
659        int UpdateActiveViewCells(ViewCellContainer &viewCells);
660
661        /** Helper function pullling pvs as high up in the tree as possible.
662        */
663        void PullUpVisibility(ViewCellInterior *interior);
664
665        /** Compress pvs of view cell and children.
666        */
667        void CompressViewCellsPvs(ViewCell *root);
668
669        /** Returns memory usage of view cells.
670        */
671        float GetMemUsage() const;
672
673        /**     Exports single view cell.
674                NOTE: should be in exporter!!
675        */
676        void ExportViewCell(ViewCell *viewCell, OUT_STREAM &stream, const bool exportPvs);     
677
678        /** Exports pvs of a view cell.
679        */
680        void ExportPvs(ViewCell *viewCell, OUT_STREAM &stream);
681
682        /** Counts the logical number of entries in the pvs this view cell.
683                The pvs is assumed to be stored using lossless compression.
684        */
685        int GetEntriesInPvsForCompressedStorage(ViewCell *vc) const;
686
687        /** Computes pvs size of this view cell.
688                The pvs is assumed to be stored using lossless compression.
689        */
690        float GetPvsCostForCompressedStorage(ViewCell *vc) const;
691       
692        /** Computes pvs size of this view cell.
693                The pvs is assumed to be stored in the leaves.
694        */
695        float GetPvsCostForLeafStorage(ViewCell *vc) const;
696
697        /** Counts the logical number of entries in the pvs this view cell.
698                The pvs is assumed to be stored using the leaves.
699        */
700        int GetEntriesInPvsForLeafStorage(ViewCell *vc) const;
701
702        /** Update stats for the log.
703        */
704        void UpdateStats(ofstream &stats,
705                                         const ViewCellsTreeStats &vcStats);
706
707
708        //////////////////////////////////////
709
710        /// if the view cell tree hold compressed pvs
711        int mViewCellsStorage;
712        /// pointer to the view cells manager
713        ViewCellsManager *mViewCellsManager;
714        /// the root of the view cells hierarchy
715        ViewCell *mRoot;
716
717        /// if merge visualization should be shown
718        bool mExportMergedViewCells;
719        /// intermediate container of merged view cells.
720        ViewCellContainer mMergedViewCells;
721        /// if merged view cells are refined.
722        bool mRefineViewCells;
723        /// weights between variance and render cost increase (must be between zero and one)
724        float mRenderCostWeight;
725
726        /// overall cost used to normalize cost ratio
727        float mOverallCost;
728        float mExpectedCost;
729    float mDeviation;
730        float mAvgRenderCost;
731       
732        /// the area is used for pvs heuristics
733        int mUseAreaForPvs;
734        /// number of currently active view cells (=current cut)
735        int mNumActiveViewCells;
736        /// minimal number of view cells
737        int mMergeMinViewCells;
738        /// maximal cost ratio for the merge
739        float mMergeMaxCostRatio;
740
741        typedef priority_queue<MergeCandidate> MergeQueue;
742
743        MergeQueue mMergeQueue;
744
745        float mMaxMemory;
746
747        int mMaxMergesPerPass;
748        float mAvgCostMaxDeviation;
749};
750
751
752/**
753        Candidate for leaf merging based on priority.
754*/
755class MergeCandidate
756
757        friend class ViewCellsTree;
758
759public:
760
761        MergeCandidate(ViewCell *l, ViewCell *r);
762
763        /** If this merge pair is still valid.
764        */
765        bool IsValid() const;
766
767       
768        friend bool operator<(const MergeCandidate &leafa, const MergeCandidate &leafb)
769        {
770                return leafb.GetMergeCost() < leafa.GetMergeCost();
771        }
772
773        void SetLeftViewCell(ViewCell *l);
774        void SetRightViewCell(ViewCell *l);
775
776        ViewCell *GetLeftViewCell() const;
777        ViewCell *GetRightViewCell() const;
778
779        /** Returns leaf view cell initially associated with this merge candidate.
780        */
781        ViewCell *GetInitialLeftViewCell() const;
782        /** Returns leaf view cell initially associated with this merge candidate.
783        */
784        ViewCell *GetInitialRightViewCell() const;
785
786        /** Returns the increase of the standard deviation of this merge candidate.
787        */
788        float GetDeviationIncr() const;
789
790        /** Merge cost of this candidate pair.
791        */
792        float GetMergeCost() const;
793
794        /** Render cost of this candidate.
795        */
796        float GetRenderCost() const;
797       
798        static float sRenderCostWeight;
799
800protected:
801
802        /// render cost increase by this merge
803        float mRenderCost;
804        /// increase / decrease of standard deviation
805        float mDeviationIncr;
806
807        ViewCell *mLeftViewCell;
808        ViewCell *mRightViewCell;
809
810        ViewCell *mInitialLeftViewCell;
811        ViewCell *mInitialRightViewCell;
812};
813
814
815class MergeStatistics: public StatisticsBase
816{
817public:
818       
819        int merged;
820        int siblings;
821        int candidates;
822        int nodes;
823
824        int accTreeDist;
825        int maxTreeDist;
826       
827        Real collectTime;
828        Real mergeTime;
829
830        Real overallCost;
831
832        Real expectedRenderCost;
833        Real deviation;
834        Real heuristics;
835
836        // Constructor
837        MergeStatistics()
838        {
839                Reset();
840        }
841       
842        double AvgTreeDist() const {return (double)accTreeDist / (double)merged;};
843
844        void Reset()
845        {
846                nodes = 0;
847                merged = 0;
848                siblings = 0;
849                candidates = 0;
850       
851                accTreeDist = 0;
852                maxTreeDist = 0;
853
854                collectTime = 0;
855                mergeTime = 0;
856                overallCost = 0;
857
858                expectedRenderCost = 0;
859                deviation = 0;
860                heuristics = 0;
861
862        }
863
864        void Print(ostream &app) const;
865
866        friend ostream &operator<<(ostream &s, const MergeStatistics &stat)
867        {
868                stat.Print(s);
869                return s;
870        }
871};
872
873}
874
875#endif
Note: See TracBrowser for help on using the repository browser.