source: GTP/trunk/Lib/Vis/Preprocessing/src/VssPreprocessor.cpp @ 1966

Revision 1966, 16.8 KB checked in by bittner, 17 years ago (diff)

samplign preprocessor updates, merge

RevLine 
[372]1#include "SceneGraph.h"
2#include "KdTree.h"
3#include "VssPreprocessor.h"
4#include "X3dExporter.h"
5#include "Environment.h"
6#include "MutualVisibility.h"
7#include "Polygon3.h"
8#include "ViewCell.h"
[376]9#include "VssRay.h"
[382]10#include "VssTree.h"
[439]11#include "ViewCellsManager.h"
[452]12#include "RenderSimulator.h"
[531]13#include "Beam.h"
14#include "GlRenderer.h"
[1221]15#include "Intersectable.h"
[1521]16#include "RayCaster.h"
[1883]17#include "SamplingStrategy.h"
[372]18
[1521]19
[1577]20
[863]21namespace GtpVisibilityPreprocessor {
[860]22
[1577]23
[427]24bool use2dSampling = false;
[534]25bool useViewspacePlane = false;
[427]26
[372]27VssPreprocessor::VssPreprocessor():
[1486]28  mVssRays()
[372]29{
30  // this should increase coherence of the samples
[1966]31  //  Environment::GetSingleton()->GetIntValue("VssPreprocessor.samplesPerPass", mSamplesPerPass);
32  //  Environment::GetSingleton()->GetIntValue("VssPreprocessor.initialSamples", mInitialSamples);
33  //  Environment::GetSingleton()->GetIntValue("VssPreprocessor.vssSamples", mVssSamples);
34  //  Environment::GetSingleton()->GetIntValue("VssPreprocessor.vssSamplesPerPass", mVssSamplesPerPass);
[1004]35  Environment::GetSingleton()->GetBoolValue("VssPreprocessor.useImportanceSampling", mUseImportanceSampling);
[574]36 
[1004]37  Environment::GetSingleton()->GetBoolValue("VssPreprocessor.loadInitialSamples", mLoadInitialSamples);
38  Environment::GetSingleton()->GetBoolValue("VssPreprocessor.storeInitialSamples", mStoreInitialSamples);
39  Environment::GetSingleton()->GetBoolValue("VssPreprocessor.testBeamSampling", mTestBeamSampling);
40  Environment::GetSingleton()->GetBoolValue("VssPreprocessor.enlargeViewSpace", mEnlargeViewSpace);
41  Environment::GetSingleton()->GetBoolValue("Preprocessor.detectEmptyViewSpace", mDetectEmptyViewSpace);
[694]42 
[508]43  useViewspacePlane = mUseViewSpaceBox; //hack
[675]44 
45  Debug << "*********** vss preprocessor options **************" << endl;
46  Debug << "use view space box=" << mUseViewSpaceBox << endl;
47  Debug << "enlarge view space=" << mEnlargeViewSpace << endl;
48  Debug << "*********** end vss preprocessor options **************" << endl;
49
[372]50}
51
[685]52
[372]53VssPreprocessor::~VssPreprocessor()
54{
[674]55        CLEAR_CONTAINER(mVssRays);
[372]56}
57
[685]58
[376]59Vector3
[382]60VssPreprocessor::GetViewpoint(AxisAlignedBox3 *viewSpaceBox)
[372]61{
[434]62  AxisAlignedBox3 box;
[468]63
[434]64  if (viewSpaceBox)
65        box =*viewSpaceBox;
[468]66  else
[434]67        box = mKdTree->GetBox();
[468]68
[434]69  // shrink the box in the y direction
70  return box.GetRandomPoint();
[372]71}
72
[376]73Vector3
[427]74VssPreprocessor::GetDirection(const Vector3 &viewpoint,
[434]75                                                          AxisAlignedBox3 *viewSpaceBox
76                                                          )
[372]77{
[434]78  Vector3 point;
[600]79  if (!use2dSampling)
80  {
81          if (0)
82          {
83                  Vector3 normal;
84                  int i = Random((int)mObjects.size());
85                  Intersectable *object = mObjects[i];
86                  object->GetRandomSurfacePoint(point, normal);
87          }
88          else
89                  point = mKdTree->GetBox().GetRandomPoint();
[534]90        //        point = viewpoint + UniformRandomVector();
[600]91  }
92  else
93  {
94          AxisAlignedBox3 box;
[534]95
[600]96          if (viewSpaceBox)
97                  box =*viewSpaceBox;
98          else
99                  box = mKdTree->GetBox();
[468]100
[600]101          point = box.GetRandomPoint();
102          point.y = viewpoint.y;
[434]103  }
[468]104
[434]105  return point - viewpoint;
[372]106}
107
[401]108int
[427]109VssPreprocessor::GenerateImportanceRays(VssTree *vssTree,
[434]110                                                                                const int desiredSamples,
111                                                                                SimpleRayContainer &rays
112                                                                                )
[401]113{
[434]114  int num;
115  if (0) {
116        float minRayContribution;
117        float maxRayContribution;
118        float avgRayContribution;
[468]119
[434]120        vssTree->GetRayContributionStatistics(minRayContribution,
121                                                                                  maxRayContribution,
122                                                                                  avgRayContribution);
[468]123
[434]124        cout<<
125          "#MIN_RAY_CONTRIB\n"<<minRayContribution<<endl<<
126          "#MAX_RAY_CONTRIB\n"<<maxRayContribution<<endl<<
127          "#AVG_RAY_CONTRIB\n"<<avgRayContribution<<endl;
[468]128
[434]129        float p = desiredSamples/(float)(avgRayContribution*vssTree->stat.Leaves());
130        num = vssTree->GenerateRays(p, rays);
131  } else {
[534]132        int leaves = vssTree->stat.Leaves();
[434]133        num = vssTree->GenerateRays(desiredSamples, leaves, rays);
134  }
[468]135
[434]136  cout<<"Generated "<<num<<" rays."<<endl;
[468]137
[434]138  return num;
[427]139}
[376]140
141
[427]142bool
143VssPreprocessor::ExportRays(const char *filename,
[434]144                                                        const VssRayContainer &vssRays,
145                                                        const int number
146                                                        )
[427]147{
[434]148  cout<<"Exporting vss rays..."<<endl<<flush;
[468]149
[434]150  Exporter *exporter = NULL;
151  exporter = Exporter::GetExporter(filename);
[1074]152  exporter->SetWireframe();
153  exporter->ExportKdTree(*mKdTree);
[434]154  exporter->SetFilled();
[1328]155  exporter->ExportScene(mSceneGraph->GetRoot());
[434]156  exporter->SetWireframe();
[427]157
[1486]158  exporter->SetForcedMaterial(RgbColor(1,0,1));
[1563]159  exporter->ExportBox(mViewCellsManager->GetViewSpaceBox());
[1486]160  exporter->ResetForcedMaterial();
[468]161
[1486]162
[534]163  VssRayContainer rays;
164  vssRays.SelectRays(number, rays);
165 
[685]166  //exporter->ExportRays(rays, RgbColor(1, 0, 0));
[468]167
[434]168  delete exporter;
[401]169
[434]170  cout<<"done."<<endl<<flush;
[427]171
[434]172  return true;
[401]173}
174
175
[372]176bool
[434]177VssPreprocessor::ExportVssTree(char *filename,
[438]178                                                           VssTree *tree,
179                                                           const Vector3 &dir
180                                                           )
[434]181{
182  Exporter *exporter = Exporter::GetExporter(filename);
183  exporter->SetFilled();
[1328]184  exporter->ExportScene(mSceneGraph->GetRoot());
[438]185  //  exporter->SetWireframe();
186  bool result = exporter->ExportVssTree2( *tree, dir );
[434]187  delete exporter;
188  return result;
189}
190
191bool
[427]192VssPreprocessor::ExportVssTreeLeaf(char *filename,
[434]193                                                                   VssTree *tree,
194                                                                   VssTreeLeaf *leaf)
[427]195{
[434]196  Exporter *exporter = NULL;
197  exporter = Exporter::GetExporter(filename);
198  exporter->SetWireframe();
199  exporter->ExportKdTree(*mKdTree);
[468]200
[1486]201  exporter->SetForcedMaterial(RgbColor(1,0,0));
[1563]202  exporter->ExportBox(mViewCellsManager->GetViewSpaceBox());
[1486]203  exporter->ResetForcedMaterial();
[468]204
[434]205  exporter->SetForcedMaterial(RgbColor(0,0,1));
206  exporter->ExportBox(tree->GetBBox(leaf));
207  exporter->ResetForcedMaterial();
[468]208
[434]209  VssRayContainer rays[4];
210  for (int i=0; i < leaf->rays.size(); i++) {
211        int k = leaf->rays[i].GetRayClass();
212        rays[k].push_back(leaf->rays[i].mRay);
213  }
[468]214
[434]215  // SOURCE RAY
216  exporter->ExportRays(rays[0], RgbColor(1, 0, 0));
217  // TERMINATION RAY
218  exporter->ExportRays(rays[1], RgbColor(1, 1, 1));
219  // PASSING_RAY
220  exporter->ExportRays(rays[2], RgbColor(1, 1, 0));
221  // CONTAINED_RAY
222  exporter->ExportRays(rays[3], RgbColor(0, 0, 1));
[427]223
[434]224  delete exporter;
225  return true;
[427]226}
227
228void
229VssPreprocessor::ExportVssTreeLeaves(VssTree *tree, const int number)
230{
[434]231  vector<VssTreeLeaf *> leaves;
232  tree->CollectLeaves(leaves);
[427]233
[434]234  int num = 0;
235  int i;
236  float p = number / (float)leaves.size();
237  for (i=0; i < leaves.size(); i++) {
238        if (RandomValue(0,1) < p) {
239          char filename[64];
240          sprintf(filename, "vss-leaf-%04d.x3d", num);
241          ExportVssTreeLeaf(filename, tree, leaves[i]);
242          num++;
[427]243        }
[434]244        if (num >= number)
245          break;
246  }
[427]247}
248
[685]249
[535]250void VssPreprocessor::TestBeamCasting(VssTree *tree,
251                                                                          ViewCellsManager *vm,
252                                                                          const ObjectContainer &objects)
[530]253{
[540]254        //debuggerWidget = new GlDebuggerWidget(renderer);
255        //  renderer->resize(640, 480);
256        //debuggerWidget->resize(640, 480);
257
[531]258        vector<VssTreeLeaf *> leaves;
259        tree->CollectLeaves(leaves);
[530]260
[535]261        Exporter *exporter = Exporter::GetExporter("shafts.x3d");
262
263        exporter->SetWireframe();
[540]264        exporter->ExportGeometry(objects);
[535]265        exporter->SetFilled();
266        //Randomize();
[1145]267// §§matt
268//      debuggerWidget = new GlDebuggerWidget(renderer);
[540]269
270        /*debuggerWidget->mBeam = beam;
271        debuggerWidget->mSourceObject = sourceObj;
272        debuggerWidget->mSamples = 10000;
273       
274        Debug << "showing window" << endl;
[1145]275        debuggerWidget->show();
[540]276       
[1145]277        renderer->makeCurrent();*/
[540]278
279        for (int i = 0; i < 10; ++ i)
[530]280        {
[540]281                Beam beam;
282                Intersectable *sourceObj = mObjects[5];
283
[531]284                const int index = (int)RandomValue(0, (Real)((int)leaves.size() - 1));
285                VssTreeLeaf *leaf = leaves[index];
[530]286
[535]287                AxisAlignedBox3 dirBox = tree->GetDirBBox(leaf);
[531]288                AxisAlignedBox3 box = tree->GetBBox(leaf);
[532]289               
[535]290                beam.Construct(box, dirBox);
[532]291
292                // collect kd leaves and view cells
293                mKdTree->CastBeam(beam);
294                vm->CastBeam(beam);
295
[577]296                Debug << "found " << (int)beam.mViewCells.size() << " view cells and "
297                          << (int)beam.mKdNodes.size() << " kd nodes" << endl;
[532]298
[530]299                BeamSampleStatistics stats;
[1145]300// §§matt
301/*              renderer->SampleBeamContributions(sourceObj,
[589]302                                                                                  beam,
303                                                                                  200000,
[531]304                                                                                  stats);
[530]305
[540]306                char s[64]; sprintf(s, "shaft%04d.png", i);
307
308                QImage image = renderer->toImage();
309                image.save(s, "PNG");
[532]310                Debug << "beam statistics: " << stats << endl << endl;
[1145]311*/
[540]312                if (1)
313                {
314                        AxisAlignedBox3 sbox = mSceneGraph->GetBox();
315                        Vector3 bmin = sbox.Min() - 150.0f;
316                        Vector3 bmax = sbox.Max() + 150.0f;
317                        AxisAlignedBox3 vbox(bmin, bmax);
[535]318               
[540]319                        exporter->ExportBeam(beam, vbox);
320                }
[535]321
[540]322                bool exportViewCells = false;
[535]323                if (exportViewCells)
324                {
325                        ViewCellContainer::const_iterator it, it_end = beam.mViewCells.end();
326                       
327                        for (it = beam.mViewCells.begin(); it != beam.mViewCells.end(); ++ it)
328                        {
329                                BspNodeGeometry geom;
330                                AxisAlignedBox3 vbox;
331                                vbox.Initialize();
[538]332                                vbox.Include((*it)->GetMesh());
333                       
334                                exporter->SetWireframe();
[535]335                                exporter->ExportBox(vbox);
[538]336                                exporter->SetFilled();
337                                exporter->ExportViewCell(*it);
[535]338                        }
[538]339
340                        /*vector<KdNode *>::const_iterator it, it_end = beam.mKdNodes.end();
341                       
342                        for (it = beam.mKdNodes.begin(); it != beam.mKdNodes.end(); ++ it)
343                        {
344                                exporter->ExportBox(mKdTree->GetBox((*it)));
345                        }*/
[535]346                }
[530]347        }
[540]348        /*while (1)
349        { debuggerWidget->repaint();
350        };*/
[535]351        delete exporter;
[530]352}
353
[540]354
[434]355float
356VssPreprocessor::GetAvgPvsSize(VssTree *tree,
357                                                           const vector<AxisAlignedBox3> &viewcells
358                                                           )
359{
360  vector<AxisAlignedBox3>::const_iterator it, it_end = viewcells.end();
361
362  int sum = 0;
363  for (it = viewcells.begin(); it != it_end; ++ it)
364        sum += tree->GetPvsSize(*it);
[468]365
[434]366  return sum/(float)viewcells.size();
367}
368
[427]369bool
[372]370VssPreprocessor::ComputeVisibility()
371{
[579]372        Debug << "type: vss" << endl;
[468]373
[1563]374        const long startTime = GetTime();
375        int totalSamples = 0;
[468]376
[1723]377        //mSceneGraph->CollectObjects(&mObjects);
[372]378
[1563]379        if (!mLoadViewCells)
380        {
381                //-- generate new view cells from the scratch
382                //-- for construction the manager uses it's own set of samples
383                ConstructViewCells();
384        }
385#if 0
386        else
387        {       
388                //-- load view cells from file
389                //-- test successful view cells loading by exporting them again
390                VssRayContainer dummies;
391                mViewCellsManager->Visualize(mObjects, dummies);
392                mViewCellsManager->ExportViewCells("test.xml.zip", mViewCellsManager->GetExportPvs(), mObjects);
393        }
394#endif
[579]395
[1563]396        VssTree *vssTree = NULL;
397        const long initialTime = GetTime();
[677]398
[1563]399        if (mLoadInitialSamples)
400        {
401                cout << "Loading samples from file ... ";
402                LoadSamples(mVssRays, mObjects);
403                cout << "finished\n" << endl;
404                totalSamples = (int)mVssRays.size();
405        }
406        else
407        {
408                while (totalSamples < mInitialSamples) {
409                        int passContributingSamples = 0;
410                        int passSampleContributions = 0;
411                        int passSamples = 0;
[598]412
[1563]413                        int index = 0;
[1052]414
[1563]415                        int sampleContributions;
[590]416
[1563]417                        int s = Min(mSamplesPerPass, mInitialSamples);
418                        for (int k=0; k < s; k++)
419                        {
420                                Vector3 viewpoint;
[1404]421
[1563]422                                mViewCellsManager->GetViewPoint(viewpoint);
423                                const Vector3 direction = GetDirection(viewpoint, &mViewCellsManager->GetViewSpaceBox());
[468]424
[1883]425                                const SimpleRay sray(viewpoint, direction,
426                                                                         SamplingStrategy::DIRECTION_BOX_BASED_DISTRIBUTION,
427                                                                         1.0f);
428                                sampleContributions = mRayCaster->CastRay(sray,
429                                                                                                                  mVssRays,
430                                                                                                                  mViewCellsManager->GetViewSpaceBox(),
431                                                                                                                  true);
[468]432
[1563]433                                if (sampleContributions) {
434                                        passContributingSamples ++;
435                                        passSampleContributions += sampleContributions;
436                                }
437                                passSamples++;
438                                totalSamples++;
439                        }
[468]440
[1563]441                        mPass++;
442                        int pvsSize = 0;
443                        float avgRayContrib = (passContributingSamples > 0) ?
444                                passSampleContributions/(float)passContributingSamples : 0;
[468]445
[1563]446                        cout << "#Pass " << mPass << " : t = " << TimeDiff(startTime, GetTime())*1e-3 << "s" << endl;
447                        cout << "#TotalSamples=" << totalSamples/1000
448                                << "#SampleContributions=" << passSampleContributions << " ("
449                                << 100*passContributingSamples/(float)passSamples<<"%)" << " avgPVS="
450                                << pvsSize/(float)mObjects.size() << endl
451                                << "avg ray contrib=" << avgRayContrib << endl;
[534]452
[1563]453                        mStats <<
454                                "#Pass\n" <<mPass<<endl<<
455                                "#Time\n" << TimeDiff(startTime, GetTime())*1e-3 << endl<<
456                                "#TotalSamples\n" << totalSamples<< endl<<
457                                "#SampleContributions\n" << passSampleContributions << endl <<
458                                "#PContributingSamples\n"<<100*passContributingSamples/(float)passSamples<<endl <<
459                                "#AvgPVS\n"<< pvsSize/(float)mObjects.size() << endl <<
460                                "#AvgRayContrib\n" << avgRayContrib << endl;
[490]461                }
[468]462
[1563]463                cout << "#totalPvsSize=" << mKdTree->CollectLeafPvs() << endl;
[468]464
465
[534]466
[1563]467        }
[534]468
[564]469
[1563]470        cout << "#totalRayStackSize=" << (int)mVssRays.size() << endl << flush;
471        Debug << (int)mVssRays.size() << " rays generated in "
472                << TimeDiff(initialTime, GetTime()) * 1e-3 << " seconds" << endl;
[401]473
[1563]474        if (mStoreInitialSamples)
475        {
476                cout << "Writing " << (int)mVssRays.size() << " samples to file ... ";
477                ExportSamples(mVssRays);
478                cout << "finished\n" << endl;
[491]479
[1563]480                /*VssRayContainer dummyRays;
481                LoadSamples(dummyRays, mObjects);
482                Debug << "rays " << (int)mVssRays.size() << " " << dummyRays.size() << endl;
[491]483
[1563]484                for (int i = 0; i < (int)mVssRays.size(); ++ i)
485                {
486                Debug << mVssRays[i]->GetOrigin() << " " << mVssRays[i]->GetTermination() << " " << mVssRays[i]->mOriginObject << " " << mVssRays[i]->mTerminationObject << endl;
487                Debug << dummyRays[i]->GetOrigin() << " " << dummyRays[i]->GetTermination() << " " << dummyRays[i]->mOriginObject << " " << dummyRays[i]->mTerminationObject << endl << endl;
488                }*/
489        }
[468]490
[372]491
[1563]492        //int numExportRays = 2000;
493        int numExportRays = 0;
[468]494
[1563]495        if (numExportRays) {
496                char filename[64];
497                sprintf(filename, "vss-rays-initial.x3d");
498                ExportRays(filename, mVssRays, numExportRays);
499        }
[468]500
[1563]501        vssTree = new VssTree;
502        // viewcells = Construct(mVssRays);
[468]503
[1563]504        vssTree->Construct(mVssRays, NULL);
505        cout<<"VssTree root PVS size = "<<vssTree->GetRootPvsSize()<<endl;
[1074]506
[1563]507        if (0) ExportRays("kdtree.x3d", mVssRays, 10);
[430]508
[1563]509        if (0)
510        {
511                ExportVssTree("vss-tree-100.x3d", vssTree, Vector3(1,0,0));
512                ExportVssTree("vss-tree-001.x3d", vssTree, Vector3(0,0,1));
513                ExportVssTree("vss-tree-101.x3d", vssTree, Vector3(1,0,1));
514                ExportVssTree("vss-tree-101m.x3d", vssTree, Vector3(-1,0,-1));
515                ExportVssTreeLeaves(vssTree, 10);
516        }
[468]517
[1563]518        // viewcells->UpdatePVS(newVssRays);
519        // get viewcells as kd tree boxes
520        vector<AxisAlignedBox3> kdViewcells;
521        if (0) {
522                vector<KdLeaf *> leaves;
523                mKdTree->CollectLeaves(leaves);
524                vector<KdLeaf *>::const_iterator it;
525                int targetLeaves = 50;
526                float prob = targetLeaves/(float)leaves.size();
527                for (it = leaves.begin(); it != leaves.end(); ++it)
528                        if (RandomValue(0.0f,1.0f) < prob)
529                                kdViewcells.push_back(mKdTree->GetBox(*it));
[434]530
[1563]531                float avgPvs = GetAvgPvsSize(vssTree, kdViewcells);
532                cout<<"Initial average PVS size = "<<avgPvs<<endl;
533        }
[468]534
[448]535
[1563]536        int samples = 0;
537        int pass = 0;
[468]538
539
[1563]540        // cast view cell samples
541        while (samples < mVssSamples)
542        {
[468]543
[1563]544                int num = mVssSamplesPerPass;
545                SimpleRayContainer rays;
546                VssRayContainer vssRays;
[427]547
[1563]548                if (!mUseImportanceSampling) {
549                        for (int j=0; j < num; j++) {
550                                Vector3 viewpoint;
551                                mViewCellsManager->GetViewPoint(viewpoint);
552                                Vector3 direction = GetDirection(viewpoint, NULL);
[1883]553                                rays.push_back(SimpleRay(viewpoint, direction,
554                                                                                 SamplingStrategy::DIRECTION_BOX_BASED_DISTRIBUTION,
555                                                                                 1.0f)
556                                                           );
[1563]557                        }
558                } else {
559                        num = GenerateImportanceRays(vssTree, num, rays);
560                }
[430]561
[1563]562                CastRays(rays, vssRays, true);
563                vssTree->AddRays(vssRays);
[468]564
[1563]565                if (0) {
566                        int subdivided = vssTree->UpdateSubdivision();
567                        cout<<"subdivided leafs = "<<subdivided<<endl;
568                }
569
570                float avgPvs = GetAvgPvsSize(vssTree, kdViewcells);
571                cout<<"Average PVS size = "<<avgPvs<<endl;
572
573                /// compute view cell contribution of rays
574                mViewCellsManager->ComputeSampleContributions(vssRays, true, false);
575
576                if (numExportRays) {
577                        char filename[64];
578                        if (mUseImportanceSampling)
579                                sprintf(filename, "vss-rays-i%04d.x3d", pass);
580                        else
581                                sprintf(filename, "vss-rays-%04d.x3d", pass);
582
583                        ExportRays(filename, vssRays, numExportRays);
584                }
585
586                samples+=num;
587                float pvs = vssTree->GetAvgPvsSize();
588                cout<<"*****************************\n";
589                cout<<samples<<" avgPVS ="<<pvs<<endl;
590                cout<<"VssTree root PVS size = "<<vssTree->GetRootPvsSize()<<endl;
591                cout<<"*****************************\n";
592                //      if (samples >= mVssSamples) break;
593                pass ++;
[401]594        }
[386]595
[1563]596        if (mTestBeamSampling && mUseGlRenderer)
597        {       
598                TestBeamCasting(vssTree, mViewCellsManager, mObjects);
599        }
[448]600
[1563]601        if (0)  Debug << vssTree->stat << endl;
[553]602
[1563]603        if (0)
604        {
605                VssRayContainer viewCellRays;
606                // compute rays used for view cells construction
607                const int numRays = mViewCellsManager->GetVisualizationSamples();
608                vssTree->CollectRays(viewCellRays, numRays);
609        }
[468]610
[535]611
[1563]612        ////////////////////
613        //-- render simulation after construction
[605]614
[1563]615        mRenderSimulator->RenderScene();
616        SimulationStatistics ss;
617        mRenderSimulator->GetStatistics(ss);
618        Debug << "\nFinal view cells partition render time\n" << ss << endl;
619        cout << "\nFinal view cells partition render time\n" << ss << endl;
[1414]620
[1563]621        delete vssTree;
[468]622
[1563]623        return true;
[466]624}
[697]625
[1743]626}
Note: See TracBrowser for help on using the repository browser.