source: GTP/trunk/Lib/Vis/Preprocessing/src/GvsPreprocessor.cpp @ 1976

Revision 1976, 18.7 KB checked in by mattausch, 17 years ago (diff)

valid function for degenerated obj-triangles

  • Property svn:executable set to *
Line 
1#include "Environment.h"
2#include "GvsPreprocessor.h"
3#include "GlRenderer.h"
4#include "VssRay.h"
5#include "ViewCellsManager.h"
6#include "Triangle3.h"
7#include "IntersectableWrapper.h"
8#include "Plane3.h"
9#include "RayCaster.h"
10#include "Exporter.h"
11#include "SamplingStrategy.h"
12
13
14namespace GtpVisibilityPreprocessor
15{
16 
17#define GVS_DEBUG 1
18
19struct VizStruct
20{
21        Polygon3 *enlargedTriangle;
22        Triangle3 originalTriangle;
23        VssRay *ray;
24};
25
26static vector<VizStruct> vizContainer;
27
28GvsPreprocessor::GvsPreprocessor():
29Preprocessor(),
30//mSamplingType(SamplingStrategy::DIRECTION_BASED_DISTRIBUTION),
31mSamplingType(SamplingStrategy::DIRECTION_BOX_BASED_DISTRIBUTION)
32{
33        Environment::GetSingleton()->GetIntValue("GvsPreprocessor.totalSamples", mTotalSamples);
34        Environment::GetSingleton()->GetIntValue("GvsPreprocessor.initialSamples", mInitialSamples);
35        Environment::GetSingleton()->GetIntValue("GvsPreprocessor.samplesPerPass", mSamplesPerPass);
36        Environment::GetSingleton()->GetFloatValue("GvsPreprocessor.epsilon", mEps);
37        Environment::GetSingleton()->GetFloatValue("GvsPreprocessor.threshold", mThreshold);   
38        Environment::GetSingleton()->GetBoolValue("GvsPreprocessor.UsePerViewCellSampling", mPerViewCell);
39
40        char gvsStatsLog[100];
41        Environment::GetSingleton()->GetStringValue("GvsPreprocessor.stats", gvsStatsLog);
42        mGvsStatsStream.open(gvsStatsLog);
43
44        Debug << "Gvs preprocessor options" << endl;
45        Debug << "number of total samples: " << mTotalSamples << endl;
46        Debug << "number of initial samples: " << mInitialSamples << endl;
47        Debug << "number of samples per pass: " << mSamplesPerPass << endl;
48        Debug << "threshold: " << mThreshold << endl;
49        Debug << "epsilon: " << mEps << endl;
50        Debug << "stats: " << gvsStatsLog << endl;
51
52        if (0)
53                mOnlyRandomSampling = true;
54        else
55                mOnlyRandomSampling = false;
56
57        //mGvsStatsStream.open("gvspreprocessor.log");
58        mGvsStats.Reset();
59}
60
61
62bool GvsPreprocessor::CheckDiscontinuity(const VssRay &currentRay,
63                                                                                 const Triangle3 &hitTriangle,
64                                                                                 const VssRay &oldRay)
65{
66        // the predicted hitpoint: we expect to hit the same mesh again
67        const Vector3 predictedHit = CalcPredictedHitPoint(currentRay, hitTriangle, oldRay);
68
69        const float predictedLen = Magnitude(predictedHit - currentRay.mOrigin);
70        const float len = Magnitude(currentRay.mTermination - currentRay.mOrigin);
71       
72        // distance large => this is likely to be a discontinuity
73#if 1
74        if ((predictedLen - len) > mThreshold)
75#else // rather use relative distance
76        if ((predictedLen / len) > mThreshold)
77#endif
78        {
79                //cout << "r";
80                // apply reverse sampling to find the gap
81                VssRay *newRay = ReverseSampling(currentRay, hitTriangle, oldRay);
82
83                if (!newRay)
84                        return false;
85
86                // set flag for visualization
87                newRay->mFlags |= VssRay::ReverseSample;
88               
89                // if ray is not further processed => can delete ray
90                if (!HandleRay(newRay))
91                {
92                        delete newRay;
93                }
94                else if (GVS_DEBUG && (mVssRays.size() < 9))
95                {
96                        mVssRays.push_back(new VssRay(oldRay));
97                        mVssRays.push_back(new VssRay(currentRay));
98                        mVssRays.push_back(new VssRay(*newRay));
99                }
100
101                return true;
102        }
103
104        return false;
105}
106
107
108bool GvsPreprocessor::HandleRay(VssRay *vssRay)
109{
110        // compute the contribution to the view cells
111        const bool storeRaysForViz = GVS_DEBUG;
112
113        mViewCellsManager->ComputeSampleContribution(*vssRay,
114                                                                                                 true,
115                                                                                                 storeRaysForViz);
116
117        // some pvs contribution for this ray?
118        if (vssRay->mPvsContribution > 0)
119        {
120                // add new ray to ray queue
121                mRayQueue.push(vssRay);
122
123                if (storeRaysForViz)
124                {
125                        VssRay *nray = new VssRay(*vssRay);
126                        nray->mFlags = vssRay->mFlags;
127
128                        // store ray in contributing view cell
129                        ViewCellContainer::const_iterator vit, vit_end = vssRay->mViewCells.end();
130                        for (vit = vssRay->mViewCells.begin(); vit != vit_end; ++ vit)
131                        {                       
132                                (*vit)->GetOrCreateRays()->push_back(nray);                             
133                        }
134                }
135
136        ++ mGvsStats.mPassContribution;
137
138                return true;
139        }
140
141        return false;
142}
143
144
145/** Creates 3 new vertices for triangle vertex with specified index.
146*/
147void GvsPreprocessor::CreateDisplacedVertices(VertexContainer &vertices,
148                                                                                          const Triangle3 &hitTriangle,
149                                                                                          const VssRay &ray,
150                                                                                          const int index) const
151{
152        const int indexU = (index + 1) % 3;
153        const int indexL = (index == 0) ? 2 : index - 1;
154
155        const Vector3 a = hitTriangle.mVertices[index] - ray.GetOrigin();
156        const Vector3 b = hitTriangle.mVertices[indexU] - hitTriangle.mVertices[index];
157        const Vector3 c = hitTriangle.mVertices[index] - hitTriangle.mVertices[indexL];
158       
159        const float len = Magnitude(a);
160       
161        const Vector3 dir1 = Normalize(CrossProd(a, b)); //N((pi-xp)×(pi+1- pi));
162        const Vector3 dir2 = Normalize(CrossProd(a, c)); // N((pi-xp)×(pi- pi-1))
163        const Vector3 dir3 = DotProd(dir2, dir1) > 0 ? // N((pi-xp)×di,i-1+di,i+1×(pi-xp))
164                Normalize(dir2 + dir1) : Normalize(CrossProd(a, dir1) + CrossProd(dir2, a));
165
166        // compute the new three hit points
167        // pi, i + 1:  pi+ e·|pi-xp|·di, j
168        const Vector3 pt1 = hitTriangle.mVertices[index] + mEps * len * dir1;
169        // pi, i - 1:  pi+ e·|pi-xp|·di, j
170    const Vector3 pt2 = hitTriangle.mVertices[index] + mEps * len * dir2;
171        // pi, i:  pi+ e·|pi-xp|·di, j
172        const Vector3 pt3 = hitTriangle.mVertices[index] + mEps * len * dir3;
173       
174        vertices.push_back(pt2);
175        vertices.push_back(pt3);
176        vertices.push_back(pt1);
177}
178
179
180void GvsPreprocessor::EnlargeTriangle(VertexContainer &vertices,
181                                                                          const Triangle3 &hitTriangle,
182                                                                          const VssRay &ray) const
183{
184        CreateDisplacedVertices(vertices, hitTriangle, ray, 0);
185        CreateDisplacedVertices(vertices, hitTriangle, ray, 1);
186        CreateDisplacedVertices(vertices, hitTriangle, ray, 2);
187}
188
189
190Vector3 GvsPreprocessor::CalcPredictedHitPoint(const VssRay &newRay,
191                                                                                           const Triangle3 &hitTriangle,
192                                                                                           const VssRay &oldRay) const
193{
194        // find the intersection of the plane induced by the
195        // hit triangle with the new ray
196        Plane3 plane(hitTriangle.GetNormal(), hitTriangle.mVertices[0]);
197
198        const Vector3 hitPt =
199                plane.FindIntersection(newRay.mTermination, newRay.mOrigin);
200       
201        return hitPt;
202}
203
204
205static bool EqualVisibility(const VssRay &a, const VssRay &b)
206{
207        return a.mTerminationObject == b.mTerminationObject;
208}
209
210
211int GvsPreprocessor::SubdivideEdge(const Triangle3 &hitTriangle,
212                                                                   const Vector3 &p1,
213                                                                   const Vector3 &p2,
214                                                                   const VssRay &ray1,
215                                                                   const VssRay &ray2,
216                                                                   const VssRay &oldRay)
217{
218        CheckDiscontinuity(ray1, hitTriangle, oldRay);
219        CheckDiscontinuity(ray2, hitTriangle, oldRay);
220
221        if (EqualVisibility(ray1, ray2) || (Magnitude(p1 - p2) <= mEps))
222        {
223                return 0;
224        }
225        else
226        {
227                // the new subdivision point
228                const Vector3 p = (p1 + p2) * 0.5f;
229       
230                //cout << "tobj " << ray1.mTerminationObject << " " << ray2.mTerminationObject << " " << p1 << " " << p2 << endl;
231                //cout << "term " << ray1.mTermination << " " << ray2.mTermination << endl;
232
233                // cast ray into the new point
234                SimpleRay sray(oldRay.mOrigin, p - oldRay.mOrigin, SamplingStrategy::GVS, 1.0f);
235       
236                VssRay *newRay = mRayCaster->CastRay(sray, mViewCellsManager->GetViewSpaceBox());
237
238                if (!newRay) return 0;
239
240                newRay->mFlags |= VssRay::BorderSample;
241
242                // add new ray to queue
243                const bool enqueued = HandleRay(newRay);
244               
245                // subdivide further
246                const int samples1 = SubdivideEdge(hitTriangle, p1, p, ray1, *newRay, oldRay);
247                const int samples2 = SubdivideEdge(hitTriangle, p, p2, *newRay, ray2, oldRay);
248                       
249                // this ray will not be further processed
250                if (!enqueued)
251                        delete newRay;
252               
253                return samples1 + samples2 + 1;
254        }
255}
256
257
258int GvsPreprocessor::AdaptiveBorderSampling(const VssRay &currentRay)
259{
260        Intersectable *tObj = currentRay.mTerminationObject;
261        Triangle3 hitTriangle;
262
263        // other types not implemented yet
264        if (tObj->Type() == Intersectable::TRIANGLE_INTERSECTABLE)
265        {
266                hitTriangle = dynamic_cast<TriangleIntersectable *>(tObj)->GetItem();
267        }
268        else
269        {
270                cout << "not yet implemented" << endl;
271        }
272
273        VertexContainer enlargedTriangle;
274       
275        /// create 3 new hit points for each vertex
276        EnlargeTriangle(enlargedTriangle, hitTriangle, currentRay);
277       
278        /// create rays from sample points and handle them
279        SimpleRayContainer simpleRays;
280        simpleRays.reserve(9);
281
282        //cout << "currentRay: " << currentRay.mOrigin << " dir: " << currentRay.GetDir() << endl;
283
284        VertexContainer::const_iterator vit, vit_end = enlargedTriangle.end();
285
286        for (vit = enlargedTriangle.begin(); vit != vit_end; ++ vit)
287        {
288                const Vector3 rayDir = (*vit) - currentRay.GetOrigin();
289                SimpleRay sr(currentRay.GetOrigin(), rayDir, SamplingStrategy::GVS, 1.0f);
290                simpleRays.AddRay(sr);
291
292                //cout << "new: " << sr.mOrigin << " dist: " << sr.mDirection << endl;
293        }
294
295        if (0)
296        {
297                // visualize enlarged triangles
298                VizStruct dummy;
299                dummy.enlargedTriangle = new Polygon3(enlargedTriangle);
300                dummy.originalTriangle = hitTriangle;
301                vizContainer.push_back(dummy);
302        }
303
304        // cast rays to triangle vertices and determine visibility
305        VssRayContainer vssRays;
306
307        // don't cast double rays as we need only the forward rays
308        const bool castDoubleRays = false;
309        // cannot prune invalid rays because we have to
310        // compare adjacent  rays.
311        const bool pruneInvalidRays = false;
312
313        CastRays(simpleRays, vssRays, castDoubleRays, pruneInvalidRays);
314
315        // set flags
316        VssRayContainer::const_iterator rit, rit_end = vssRays.end();
317        for (rit = vssRays.begin(); rit != rit_end; ++ rit)
318        {
319                (*rit)->mFlags |= VssRay::BorderSample;
320        }
321
322        // handle rays
323        EnqueueRays(vssRays);
324       
325        const int n = (int)enlargedTriangle.size();
326        int castRays = (int)vssRays.size();
327
328    // recursivly subdivide each edge
329        for (int i = 0; i < n; ++ i)
330        {
331                castRays += SubdivideEdge(hitTriangle,
332                                                                  enlargedTriangle[i],
333                                                                  enlargedTriangle[(i + 1) % n],
334                                                                  *vssRays[i],
335                                                                  *vssRays[(i + 1) % n],
336                                                                  currentRay);
337        }
338
339        mGvsStats.mBorderSamples += castRays;
340
341        return castRays;
342}
343
344
345/*Vector3 GvsPreprocessor::GetPassingPoint(const VssRay &currentRay,
346                                                                                 const Triangle3 &hitTriangle,
347                                                                                 const VssRay &oldRay) const
348{
349        //-- intersect triangle plane with plane spanned by current samples
350        Plane3 plane(currentRay.GetOrigin(), currentRay.GetTermination(), oldRay.GetTermination());
351        Plane3 triPlane(hitTriangle.GetNormal(), hitTriangle.mVertices[0]);
352
353        SimpleRay intersectLine = GetPlaneIntersection(plane, triPlane);
354
355        // Evaluate new hitpoint just outside the triangle
356        const float factor = 0.95f;
357        const float t = triPlane.FindT(intersectLine);
358        const Vector3 newPoint = intersectLine.mOrigin + t * factor * intersectLine.mDirection;
359
360        return newPoint;
361}*/
362
363
364Vector3 GvsPreprocessor::GetPassingPoint(const VssRay &currentRay,
365                                                                                 const Triangle3 &occluder,
366                                                                                 const VssRay &oldRay) const
367{
368        //-- The plane p = (xp, hit(x), hit(xold)) is intersected
369        //-- with the newly found occluder (xold is the previous ray from
370        //-- which x was generated). On the intersecting line, we select a point
371        //-- pnew which lies just outside of the new triangle so the ray
372        //-- just passes through the gap
373
374        const Plane3 plane(currentRay.GetOrigin(),
375                                           currentRay.GetTermination(),
376                                           oldRay.GetTermination());
377       
378        Vector3 pt1, pt2;
379
380        const bool intersects = occluder.GetPlaneIntersection(plane, pt1, pt2);
381
382        if (!intersects)
383                cerr << "big error!! no intersection" << endl;
384
385        // get the intersection point on the old ray
386        const Plane3 triPlane(occluder.GetNormal(), occluder.mVertices[0]);
387
388        const float t = triPlane.FindT(oldRay.mOrigin, oldRay.mTermination);
389        const Vector3 pt3 = oldRay.mOrigin + t * (oldRay.mTermination - oldRay.mOrigin);
390
391        // Evaluate new hitpoint just outside the triangle
392        Vector3 newPoint;
393
394        const float eps = mEps;
395        // the point is chosen to be on the side closer to the original ray
396        if (Distance(pt1, pt3) < Distance(pt2, pt3))
397        {
398                newPoint = pt1 + eps * (pt1 - pt2);
399        }       
400        else
401        {
402                newPoint = pt2 + eps * (pt2 - pt1);
403        }
404
405        //cout << "passing point: " << newPoint << endl << endl;
406        return newPoint;
407}
408
409
410VssRay *GvsPreprocessor::ReverseSampling(const VssRay &currentRay,
411                                                                                 const Triangle3 &hitTriangle,
412                                                                                 const VssRay &oldRay)
413{
414        // get triangle occluding the path to the hit mesh
415        Triangle3 occluder;
416        Intersectable *tObj = currentRay.mTerminationObject;
417
418        // q: why can this happen?
419        if (!tObj)
420                return NULL;
421
422        // other types not implemented yet
423        if (tObj->Type() == Intersectable::TRIANGLE_INTERSECTABLE)
424                occluder = dynamic_cast<TriangleIntersectable *>(tObj)->GetItem();
425        else
426                cout << "not yet implemented" << endl;
427       
428        // get a point which is passing just outside of the occluder
429    const Vector3 newPoint = GetPassingPoint(currentRay, occluder, oldRay);
430        const Vector3 predicted = CalcPredictedHitPoint(currentRay, hitTriangle, oldRay);
431
432        //-- Construct the mutated ray with xnew,
433        //-- dir = predicted(x)- pnew as direction vector
434        const Vector3 newDir = predicted - newPoint;
435
436        // take xnew, p = intersect(viewcell, line(pnew, predicted(x)) as origin ?
437        // difficult to say!!
438        const float offset = 0.5f;
439        const Vector3 newOrigin = newPoint - newDir * offset;
440
441        const SimpleRay simpleRay(newOrigin, newDir, SamplingStrategy::GVS, 1.0f);
442
443        VssRay *reverseRay =
444                mRayCaster->CastRay(simpleRay, mViewCellsManager->GetViewSpaceBox());
445
446    ++ mGvsStats.mReverseSamples;
447
448        return reverseRay;
449}
450
451
452int GvsPreprocessor::CastInitialSamples(const int numSamples,
453                                                                                const int sampleType)
454{       
455        const long startTime = GetTime();
456
457        // generate simple rays
458        SimpleRayContainer simpleRays;
459        GenerateRays(numSamples, sampleType, simpleRays);
460
461        // generate vss rays
462        VssRayContainer samples;
463        CastRays(simpleRays, samples, true);
464        // add to ray queue
465        EnqueueRays(samples);
466
467        //Debug << "generated " <<  numSamples << " samples in " << TimeDiff(startTime, GetTime()) * 1e-3 << " secs" << endl;
468        return (int)samples.size();
469}
470
471
472void GvsPreprocessor::EnqueueRays(VssRayContainer &samples)
473{
474        // add samples to ray queue
475        VssRayContainer::const_iterator vit, vit_end = samples.end();
476        for (vit = samples.begin(); vit != vit_end; ++ vit)
477        {
478                HandleRay(*vit);
479        }
480}
481
482
483int GvsPreprocessor::Pass()
484{
485        // reset samples
486        int castSamples = 0;
487        mGvsStats.mPassContribution = 0;
488
489        while (castSamples < mSamplesPerPass)
490        {       
491                // Ray queue empty =>
492                // cast a number of uniform samples to fill ray queue
493                castSamples += CastInitialSamples(mInitialSamples, mSamplingType);
494
495                if (!mOnlyRandomSampling)
496                        castSamples += ProcessQueue();
497        }
498
499        mGvsStats.mTotalContribution += mGvsStats.mPassContribution;
500        return castSamples;
501}
502
503
504int GvsPreprocessor::ProcessQueue()
505{
506        int castSamples = 0;
507        ++ mGvsStats.mGvsPass;
508
509        while (!mRayQueue.empty())
510        {
511                // handle next ray
512                VssRay *ray = mRayQueue.top();
513                mRayQueue.pop();
514
515                castSamples += AdaptiveBorderSampling(*ray);
516                delete ray;
517        }
518       
519        return castSamples;
520}
521
522
523bool GvsPreprocessor::ComputeVisibility()
524{
525        cout << "Gvs Preprocessor started\n" << flush;
526        const long startTime = GetTime();
527
528        Randomize(0);
529       
530        mGvsStats.Reset();
531        mGvsStats.Start();
532
533        if (!mLoadViewCells)
534        {       
535                /// construct the view cells from the scratch
536                ConstructViewCells();
537                // reset pvs already gathered during view cells construction
538                mViewCellsManager->ResetPvs();
539                cout << "finished view cell construction" << endl;
540        }
541        else if (0)
542        {       
543                //-- test successful view cells loading by exporting them again
544                VssRayContainer dummies;
545                mViewCellsManager->Visualize(mObjects, dummies);
546                mViewCellsManager->ExportViewCells("test.xml.gz", mViewCellsManager->GetExportPvs(), mObjects);
547        }
548
549        mGvsStats.Stop();
550        mGvsStats.Print(mGvsStatsStream);
551
552        while (mGvsStats.mTotalSamples < mTotalSamples)
553        {
554                ++ mPass;
555
556                mGvsStats.mTotalSamples += Pass();
557                               
558                ////////
559                //-- stats
560
561                cout << "\nPass " << mPass << " #samples: " << mGvsStats.mTotalSamples << " of " << mTotalSamples << endl;
562                mGvsStats.mPass = mPass;
563                mGvsStats.Stop();
564                mGvsStats.Print(mGvsStatsStream);
565                //mViewCellsManager->PrintPvsStatistics(mGvsStats);
566
567                if (GVS_DEBUG)
568                {
569                        char str[64]; sprintf(str, "tmp/pass%04d-", mPass);
570               
571                        // visualization
572                        if (mGvsStats.mPassContribution > 0)
573                        {
574                                const bool exportRays = true;
575                                const bool exportPvs = true;
576
577                                mViewCellsManager->ExportSingleViewCells(mObjects,
578                                                                                                                 10,
579                                                                                                                 false,
580                                                                                                                 exportPvs,
581                                                                                                                 exportRays,
582                                                                                                                 1000,
583                                                                                                                 str);
584                        }
585
586                        // remove pass samples
587                        ViewCellContainer::const_iterator vit, vit_end = mViewCellsManager->GetViewCells().end();
588                        for (vit = mViewCellsManager->GetViewCells().begin(); vit != vit_end; ++ vit)
589                        {
590                                (*vit)->DelRayRefs();
591                        }
592                }
593
594                // ComputeRenderError();
595        }
596
597        cout << "cast " << 2 * mGvsStats.mTotalSamples / (1e3f * TimeDiff(startTime, GetTime())) << "M rays/s" << endl;
598
599        if (GVS_DEBUG)
600        {
601                Visualize();
602                CLEAR_CONTAINER(mVssRays);
603        }
604
605        return true;
606}
607
608
609void GvsPreprocessor::Visualize()
610{
611        Exporter *exporter = Exporter::GetExporter("gvs.wrl");
612
613        if (!exporter)
614                return;
615       
616        vector<VizStruct>::const_iterator vit, vit_end = vizContainer.end();
617        for (vit = vizContainer.begin(); vit != vit_end; ++ vit)
618        {
619                exporter->SetWireframe();
620                exporter->ExportPolygon((*vit).enlargedTriangle);
621                //Material m;
622                exporter->SetFilled();
623                Polygon3 poly = Polygon3((*vit).originalTriangle);
624                exporter->ExportPolygon(&poly);
625        }
626
627        VssRayContainer::const_iterator rit, rit_end = mVssRays.end();
628        for (rit = mVssRays.begin(); rit != rit_end; ++ rit)
629        {
630                Intersectable *obj = (*rit)->mTerminationObject;
631                exporter->ExportIntersectable(obj);
632        }
633
634        VssRayContainer vcRays, vcRays2, vcRays3;
635
636        // prepare some rays for output
637        for (rit = mVssRays.begin(); rit != rit_end; ++ rit)
638        {
639                const float p = RandomValue(0.0f, (float)mVssRays.size());
640                if (1)//(p < raysOut)
641                {
642                        if ((*rit)->mFlags & VssRay::BorderSample)
643                        {
644                                vcRays.push_back(*rit);
645                        }
646                        else if ((*rit)->mFlags & VssRay::ReverseSample)
647                        {
648                                vcRays2.push_back(*rit);
649                        }
650                        else
651                        {
652                                vcRays3.push_back(*rit);
653                        }       
654                }
655        }
656
657        exporter->ExportRays(vcRays, RgbColor(1, 0, 0));
658        exporter->ExportRays(vcRays2, RgbColor(0, 1, 0));
659        exporter->ExportRays(vcRays3, RgbColor(1, 1, 1));
660
661        //exporter->ExportRays(mVssRays);
662        delete exporter;
663}
664
665
666void GvsStatistics::Print(ostream &app) const
667{
668        app << "#Pass\n" << mPass << endl;
669        app << "#Time\n" << Time() << endl;
670        app << "#TotalSamples\n" << mTotalSamples << endl;
671        app << "#ScDiff\n" << mPassContribution << endl;
672        app     << "#SamplesContri\n" << mTotalContribution << endl;
673        app << "#ReverseSamples\n" << mReverseSamples << endl;
674        app << "#BorderSamples\n" << mBorderSamples << endl;           
675        app << "#GvsRuns\n" << mGvsPass << endl;
676}
677
678
679}
Note: See TracBrowser for help on using the repository browser.