#include "Environment.h" #include "GvsPreprocessor.h" #include "GlRenderer.h" #include "VssRay.h" #include "ViewCellsManager.h" #include "Triangle3.h" #include "IntersectableWrapper.h" #include "Plane3.h" #include "RayCaster.h" #include "Exporter.h" #include "SamplingStrategy.h" #include "BvHierarchy.h" #include "Polygon3.h" #include "IntersectableWrapper.h" #include "Timer/PerfTimer.h" #include "Vector2.h" #include "RndGauss.h" namespace GtpVisibilityPreprocessor { #define GVS_DEBUG 0 #define NOT_ACCOUNTED_OBJECT 0 #define ACCOUNTED_OBJECT 2 #define DETERMINISTIC_GVS 0 static const float MIN_DIST = 0.001f; static ObjectContainer myobjects; static int sInvalidSamples = 0; /////////// //-- timers static PerfTimer rayTimer; static PerfTimer kdPvsTimer; static PerfTimer gvsTimer; static PerfTimer initialTimer; static PerfTimer intersectionTimer; static PerfTimer preparationTimer; static PerfTimer mainLoopTimer; static PerfTimer contributionTimer; static PerfTimer castTimer; static PerfTimer generationTimer; ///////////////////// /** Visualization structure for the GVS algorithm. */ struct VizStruct { Polygon3 *enlargedTriangle; Triangle3 originalTriangle; VssRay *ray; }; static vector vizContainer; GvsPreprocessor::GvsPreprocessor(): Preprocessor(), mProcessedViewCells(0), mCurrentViewCell(NULL), mCurrentViewPoint(Vector3(0.0f, 0.0f, 0.0f)), mOnlyRandomSampling(false) //mOnlyRandomSampling(true) { Environment::GetSingleton()->GetIntValue("GvsPreprocessor.totalSamples", mTotalSamples); Environment::GetSingleton()->GetIntValue("GvsPreprocessor.initialSamples", mInitialSamples); Environment::GetSingleton()->GetIntValue("GvsPreprocessor.gvsSamplesPerPass", mGvsSamplesPerPass); Environment::GetSingleton()->GetIntValue("GvsPreprocessor.minContribution", mMinContribution); Environment::GetSingleton()->GetFloatValue("GvsPreprocessor.epsilon", mEps); Environment::GetSingleton()->GetFloatValue("GvsPreprocessor.threshold", mThreshold); Environment::GetSingleton()->GetBoolValue("GvsPreprocessor.perViewCell", mPerViewCell); Environment::GetSingleton()->GetIntValue("GvsPreprocessor.maxViewCells", mMaxViewCells); Environment::GetSingleton()->GetBoolValue("Preprocessor.evaluatePixelError", mEvaluatePixelError); Environment::GetSingleton()->GetBoolValue("ViewCells.useKdPvs", mUseKdPvs); char gvsStatsLog[100]; Environment::GetSingleton()->GetStringValue("GvsPreprocessor.stats", gvsStatsLog); mGvsStatsStream.open(gvsStatsLog); cout << "number of gvs samples per pass: " << mGvsSamplesPerPass << endl; cout << "number of samples per pass: " << mSamplesPerPass << endl; cout << "only random sampling: " << mOnlyRandomSampling << endl; Debug << "Gvs preprocessor options" << endl; Debug << "number of total samples: " << mTotalSamples << endl; Debug << "number of initial samples: " << mInitialSamples << endl; Debug << "threshold: " << mThreshold << endl; Debug << "epsilon: " << mEps << endl; Debug << "stats: " << gvsStatsLog << endl; Debug << "per view cell: " << mPerViewCell << endl; Debug << "max view cells: " << mMaxViewCells << endl; Debug << "min contribution: " << mMinContribution << endl; mDistribution = new ViewCellBasedDistribution(*this, NULL); mGvsStats.Reset(); // hack: some generic statistical values that can be used // by an application using the preprocessor for (int i = 0; i < 2; ++ i) mGenericStats[i] = 0; } GvsPreprocessor::~GvsPreprocessor() { delete mDistribution; ClearRayQueue(); } void GvsPreprocessor::ClearRayQueue() { // clean ray queue while (!mRayQueue.empty()) { // handle next ray VssRay *ray = mRayQueue.top(); mRayQueue.pop(); // note: deletion of the ray is handled by ray pool } } ViewCell *GvsPreprocessor::NextViewCell() { if (mProcessedViewCells == (int)mViewCells.size()) return NULL; // no more view cells ViewCell *vc = mViewCells[mProcessedViewCells]; if (!vc->GetMesh()) mViewCellsManager->CreateMesh(vc); mGvsStats.mViewCellId = vc->GetId(); Debug << "vc: " << vc->GetId() << endl; ++ mProcessedViewCells; return vc; } int GvsPreprocessor::CheckDiscontinuity(const VssRay ¤tRay, const Triangle3 &hitTriangle, const VssRay &oldRay) { // the predicted hitpoint: we expect to hit the same mesh again Vector3 predictedHit = CalcPredictedHitPoint(currentRay, hitTriangle, oldRay); float predictedLen = Magnitude(predictedHit - currentRay.mOrigin); float len = Magnitude(currentRay.mTermination - currentRay.mOrigin); // distance large => this is likely to be a discontinuity if (!((predictedLen - len) > mThreshold)) // q: rather use relative distance? //if ((predictedLen / len) > mThreshold) return 0; SimpleRay simpleRay; // apply reverse sampling to find the gap if (!ComputeReverseRay(currentRay, hitTriangle, oldRay, simpleRay)) return 0; static VssRayContainer reverseRays; reverseRays.clear(); if (DETERMINISTIC_GVS) { VssRay *reverseRay = mRayCaster->CastRay(simpleRay, mViewCellsManager->GetViewSpaceBox(), !mPerViewCell); reverseRays.push_back(reverseRay); } else { if (0) CastRayBundle4(simpleRay, reverseRays, mViewCellsManager->GetViewSpaceBox()); else CastRayBundle16(simpleRay, reverseRays); } mGvsStats.mReverseSamples += (int)reverseRays.size(); EnqueueRays(reverseRays); return (int)reverseRays.size(); } int GvsPreprocessor::CountObject(Intersectable *triObj) { int numObjects = 0; if ((triObj->mCounter != (NOT_ACCOUNTED_OBJECT + 1)) && (triObj->mCounter != (ACCOUNTED_OBJECT + 1))) { ++ triObj->mCounter; ++ numObjects; } mGenericStats[1] += numObjects; return numObjects; } void GvsPreprocessor::UpdateStatsForVisualization(KdIntersectable *kdInt) { int numObj = 0; // count new objects in pvs due to kd node conversion myobjects.clear(); // also gather duplicates to avoid mailing mKdTree->CollectObjectsWithDublicates(kdInt->GetItem(), myobjects); ObjectContainer::const_iterator oit, oit_end = myobjects.end(); for (oit = myobjects.begin(); oit != oit_end; ++ oit) numObj += CountObject(*oit); mViewCellsManager->UpdateStatsForViewCell(mCurrentViewCell, kdInt, numObj); } void GvsPreprocessor::AddKdNodeToPvs(const Vector3 &termination) { kdPvsTimer.Entry(); // exchange the triangle with the node in the pvs for kd pvs KdNode *node = mKdTree->GetPvsNode(termination); //KdNode *node = mKdTree->GetPvsNodeCheck(ray.mTermination, obj); //if (!node) cerr << "e " << obj->GetBox() << " " << ray.mTermination << endl; else if (!node->Mailed()) { // add to pvs node->Mail(); KdIntersectable *kdInt = mKdTree->GetOrCreateKdIntersectable(node); mCurrentViewCell->GetPvs().AddSampleDirty(kdInt, 1.0f); if (QT_VISUALIZATION_SHOWN) UpdateStatsForVisualization(kdInt); } kdPvsTimer.Exit(); } bool GvsPreprocessor::HasContribution(VssRay &ray) { if (!ray.mTerminationObject) return false; contributionTimer.Entry(); bool result; if (!mPerViewCell) { // store the rays + the intersected view cells const bool storeViewCells = false; mViewCellsManager->ComputeSampleContribution(ray, true, storeViewCells, true); result = ray.mPvsContribution > 0; } else // original per view cell gvs { // test if triangle was accounted for yet result = AddTriangleObject(ray.mTerminationObject); if (mUseKdPvs) AddKdNodeToPvs(ray.mTermination); } contributionTimer.Exit(); return result; } bool GvsPreprocessor::HandleRay(VssRay *vssRay) { if (!vssRay || !HasContribution(*vssRay)) return false; if (0 && GVS_DEBUG) mVssRays.push_back(new VssRay(*vssRay)); // add new ray to ray queue mRayQueue.push(vssRay); ++ mGvsStats.mTotalContribution; return true; } /** Creates 3 new vertices for triangle vertex with specified index. */ void GvsPreprocessor::CreateDisplacedVertices(VertexContainer &vertices, const Triangle3 &hitTriangle, const VssRay &ray, const int index) const { const int indexU = (index + 1) % 3; const int indexL = (index == 0) ? 2 : index - 1; const Vector3 a = hitTriangle.mVertices[index] - ray.GetOrigin(); const Vector3 b = hitTriangle.mVertices[indexU] - hitTriangle.mVertices[index]; const Vector3 c = hitTriangle.mVertices[index] - hitTriangle.mVertices[indexL]; const float len = Magnitude(a); const Vector3 dir1 = Normalize(CrossProd(a, b)); //N((pi-xp)×(pi+1- pi)); const Vector3 dir2 = Normalize(CrossProd(a, c)); // N((pi-xp)×(pi- pi-1)) const Vector3 dir3 = DotProd(dir2, dir1) > 0 ? // N((pi-xp)×di,i-1+di,i+1×(pi-xp)) Normalize(dir2 + dir1) : Normalize(CrossProd(a, dir1) + CrossProd(dir2, a)); // compute the new three hit points // pi, i + 1: pi+ e·|pi-xp|·di, j const Vector3 pt1 = hitTriangle.mVertices[index] + mEps * len * dir1; // pi, i - 1: pi+ e·|pi-xp|·di, j const Vector3 pt2 = hitTriangle.mVertices[index] + mEps * len * dir2; // pi, i: pi+ e·|pi-xp|·di, j const Vector3 pt3 = hitTriangle.mVertices[index] + mEps * len * dir3; vertices.push_back(pt2); vertices.push_back(pt3); vertices.push_back(pt1); } void GvsPreprocessor::EnlargeTriangle(VertexContainer &vertices, const Triangle3 &hitTriangle, const VssRay &ray) const { CreateDisplacedVertices(vertices, hitTriangle, ray, 0); CreateDisplacedVertices(vertices, hitTriangle, ray, 1); CreateDisplacedVertices(vertices, hitTriangle, ray, 2); } Vector3 GvsPreprocessor::CalcPredictedHitPoint(const VssRay &newRay, const Triangle3 &hitTriangle, const VssRay &oldRay) const { // find the intersection of the plane induced by the // hit triangle with the new ray Plane3 plane(hitTriangle.GetNormal(), hitTriangle.mVertices[0]); const Vector3 hitPt = plane.FindIntersection(newRay.mTermination, newRay.mOrigin); return hitPt; } static bool EqualVisibility(const VssRay &a, const VssRay &b) { return a.mTerminationObject == b.mTerminationObject; } int GvsPreprocessor::SubdivideEdge(const Triangle3 &hitTriangle, const Vector3 &p1, const Vector3 &p2, const VssRay &ray1, const VssRay &ray2, const VssRay &oldRay) { //cout <<"y"<CastRay(sray, mViewCellsManager->GetViewSpaceBox(), !mPerViewCell); castTimer.Exit(); ++ castRays; // this ray will not be further processed // note: ray deletion is handled by ray pool if (!newRay) return castRays; rayTimer.Entry(); // new triangle discovered? => add new ray to queue HandleRay(newRay); rayTimer.Exit(); //newRay->mFlags |= VssRay::BorderSample; // subdivide further castRays += SubdivideEdge(hitTriangle, p1, p, ray1, *newRay, oldRay); castRays += SubdivideEdge(hitTriangle, p, p2, *newRay, ray2, oldRay); return castRays; } int GvsPreprocessor::AdaptiveBorderSampling(const VssRay ¤tRay) { Intersectable *tObj = currentRay.mTerminationObject; // question matt: can this be possible? if (!tObj) return 0; Triangle3 hitTriangle; // other types not implemented yet if (tObj->Type() == Intersectable::TRIANGLE_INTERSECTABLE) hitTriangle = static_cast(tObj)->GetItem(); else cout << "border sampling: " << Intersectable::GetTypeName(tObj) << " not yet implemented" << endl; //cout << "type: " << Intersectable::GetTypeName(tObj) << endl; generationTimer.Entry(); static VertexContainer enlargedTriangle; enlargedTriangle.clear(); /// create 3 new hit points for each vertex EnlargeTriangle(enlargedTriangle, hitTriangle, currentRay); /// create rays from sample points and handle them static SimpleRayContainer simpleRays; simpleRays.clear(); VertexContainer::const_iterator vit, vit_end = enlargedTriangle.end(); for (vit = enlargedTriangle.begin(); vit != vit_end; ++ vit) { const Vector3 rayDir = (*vit) - currentRay.GetOrigin(); SimpleRay sr(currentRay.GetOrigin(), rayDir, SamplingStrategy::GVS, 1.0f); simpleRays.AddRay(sr); } if (0) { // visualize enlarged triangles VizStruct dummy; dummy.enlargedTriangle = new Polygon3(enlargedTriangle); dummy.originalTriangle = hitTriangle; vizContainer.push_back(dummy); } ////////// //-- fill up simple rays with random rays so we can cast 16 rays at a time const int numRandomRays = 16 - (int)simpleRays.size(); if (!DETERMINISTIC_GVS) { if (0) { SimpleRay mainRay = SimpleRay(currentRay.GetOrigin(), currentRay.GetNormalizedDir(), SamplingStrategy::GVS, 1.0f); GenerateJitteredRays(simpleRays, mainRay, numRandomRays, 0); } else GenerateImportanceSamples(currentRay, hitTriangle, numRandomRays, simpleRays); } else GenerateRays(numRandomRays, *mDistribution, simpleRays, sInvalidSamples); generationTimer.Exit(); ///////////////////// //-- cast rays to vertices and determine visibility static VssRayContainer vssRays; vssRays.clear(); // for the original implementation we don't cast double rays const bool castDoubleRays = !mPerViewCell; // cannot prune invalid rays because we have to compare adjacent rays const bool pruneInvalidRays = false; //gvsTimer.Entry(); castTimer.Entry(); CastRays(simpleRays, vssRays, castDoubleRays, pruneInvalidRays); castTimer.Exit(); //gvsTimer.Exit(); const int numBorderSamples = (int)vssRays.size() - numRandomRays; int castRays = (int)simpleRays.size(); // handle cast rays EnqueueRays(vssRays); if (0) { // set flags VssRayContainer::const_iterator rit, rit_end = vssRays.end(); for (rit = vssRays.begin(); rit != rit_end; ++ rit) (*rit)->mFlags |= VssRay::BorderSample; } // recursivly subdivide each edge for (int i = 0; i < numBorderSamples; ++ i) { castRays += SubdivideEdge(hitTriangle, enlargedTriangle[i], enlargedTriangle[(i + 1) % numBorderSamples], *vssRays[i], *vssRays[(i + 1) % numBorderSamples], currentRay); } mGvsStats.mBorderSamples += castRays; return castRays; } int GvsPreprocessor::AdaptiveBorderSamplingOpt(const VssRay ¤tRay) { Intersectable *tObj = currentRay.mTerminationObject; // question matt: can this be possible? if (!tObj) return 0; Triangle3 hitTriangle; // other types not implemented yet if (tObj->Type() == Intersectable::TRIANGLE_INTERSECTABLE) hitTriangle = static_cast(tObj)->GetItem(); else cout << "border sampling: " << Intersectable::GetTypeName(tObj) << " not yet implemented" << endl; generationTimer.Entry(); static VertexContainer enlargedTriangle; enlargedTriangle.clear(); /// create 3 new hit points for each vertex EnlargeTriangle(enlargedTriangle, hitTriangle, currentRay); static VertexContainer subdivEnlargedTri; subdivEnlargedTri.clear(); for (size_t i = 0; i < enlargedTriangle.size(); ++ i) { const Vector3 p1 = enlargedTriangle[i]; const Vector3 p2 = enlargedTriangle[(i + 1) % enlargedTriangle.size()]; // the new subdivision point const Vector3 p = (p1 + p2) * 0.5f; subdivEnlargedTri.push_back(p1); subdivEnlargedTri.push_back(p); } /// create rays from sample points and handle them static SimpleRayContainer simpleRays; simpleRays.clear(); VertexContainer::const_iterator vit, vit_end = subdivEnlargedTri.end(); for (vit = subdivEnlargedTri.begin(); vit != vit_end; ++ vit) { const Vector3 rayDir = (*vit) - currentRay.GetOrigin(); SimpleRay sr(currentRay.GetOrigin(), rayDir, SamplingStrategy::GVS, 1.0f); simpleRays.AddRay(sr); } ////////// //-- fill up simple rays with random rays so we can cast 16 rays at a time const int numRandomRays = 16 - ((int)simpleRays.size() % 16); SimpleRay mainRay = SimpleRay(currentRay.GetOrigin(), currentRay.GetNormalizedDir(), SamplingStrategy::GVS, 1.0f); GenerateJitteredRays(simpleRays, mainRay, numRandomRays, 0); //GenerateRays(numRandomRays, *mDistribution, simpleRays, sInvalidSamples); generationTimer.Exit(); ///////////////////// //-- cast rays to vertices and determine visibility static VssRayContainer vssRays; vssRays.clear(); // for the original implementation we don't cast double rays const bool castDoubleRays = !mPerViewCell; // cannot prune invalid rays because we have to compare adjacent rays const bool pruneInvalidRays = false; //if ((simpleRays.size() % 16) != 0) cerr << "ray size not aligned" << endl; //gvsTimer.Entry(); castTimer.Entry(); CastRays(simpleRays, vssRays, castDoubleRays, pruneInvalidRays); castTimer.Exit(); //gvsTimer.Exit(); const int numBorderSamples = (int)vssRays.size() - numRandomRays; int castRays = (int)simpleRays.size(); // handle cast rays EnqueueRays(vssRays); #if 0 // recursivly subdivide each edge for (int i = 0; i < numBorderSamples; ++ i) { castRays += SubdivideEdge(hitTriangle, subdivEnlargedTri[i], subdivEnlargedTri[(i + 1) % numBorderSamples], *vssRays[i], *vssRays[(i + 1) % numBorderSamples], currentRay); } #endif mGvsStats.mBorderSamples += castRays; return castRays; } bool GvsPreprocessor::GetPassingPoint(const VssRay ¤tRay, const Triangle3 &occluder, const VssRay &oldRay, Vector3 &newPoint) const { ///////////////////////// //-- We interserct the plane p = (xp, hit(x), hit(xold)) //-- with the newly found occluder (xold is the previous ray from //-- which x was generated). On the intersecting line, we select a point //-- pnew which lies just outside of the new triangle so the ray //-- just passes through the gap const Plane3 plane(currentRay.GetOrigin(), currentRay.GetTermination(), oldRay.GetTermination()); Vector3 pt1, pt2; if (!occluder.GetPlaneIntersection(plane, pt1, pt2)) return false; // get the intersection point on the old ray const Plane3 triPlane(occluder.GetNormal(), occluder.mVertices[0]); const float t = triPlane.FindT(oldRay.mOrigin, oldRay.mTermination); const Vector3 pt3 = oldRay.mOrigin + t * (oldRay.mTermination - oldRay.mOrigin); // evaluate new hitpoint just outside the triangle // the point is chosen to be on the side closer to the original ray if (SqrDistance(pt1, pt3) < SqrDistance(pt2, pt3)) newPoint = pt1 + mEps * (pt1 - pt2); else newPoint = pt2 + mEps * (pt2 - pt1); //cout << "passing point: " << newPoint << endl << endl; return true; } bool GvsPreprocessor::ComputeReverseRay(const VssRay ¤tRay, const Triangle3 &hitTriangle, const VssRay &oldRay, SimpleRay &reverseRay) { // get triangle occluding the path to the hit mesh Triangle3 occluder; Intersectable *tObj = currentRay.mTerminationObject; // q: why can this happen? if (!tObj) return false; // other types not implemented yet if (tObj->Type() == Intersectable::TRIANGLE_INTERSECTABLE) occluder = static_cast(tObj)->GetItem(); else cout << "reverse sampling: " << tObj->Type() << " not yet implemented" << endl; // get a point which is passing just outside of the occluder Vector3 newPoint; // q: why is there sometimes no intersecton found? if (!GetPassingPoint(currentRay, occluder, oldRay, newPoint)) return false; const Vector3 predicted = CalcPredictedHitPoint(currentRay, hitTriangle, oldRay); Vector3 newDir, newOrigin; ////////////// //-- Construct the mutated ray with xnew, //-- dir = predicted(x)- pnew as direction vector newDir = predicted - newPoint; // take xnew, p = intersect(viewcell, line(pnew, predicted(x)) as origin ? // difficult to say!! const float offset = 0.5f; newOrigin = newPoint - newDir * offset; ////////////// //-- for per view cell sampling, we must check for intersection //-- with the current view cell if (mPerViewCell) { // send ray to view cell static Ray ray; ray.Clear(); ray.Init(newOrigin, -newDir, Ray::LOCAL_RAY); // check if ray intersects view cell if (!mCurrentViewCell->CastRay(ray)) return NULL; Ray::Intersection &hit = ray.intersections[0]; // the ray starts from the view cell newOrigin = ray.Extrap(hit.mT); } reverseRay = SimpleRay(newOrigin, newDir, SamplingStrategy::GVS, 1.0f); return true; } int GvsPreprocessor::CastInitialSamples(int numSamples) { initialTimer.Entry(); // generate simple rays static SimpleRayContainer simpleRays; simpleRays.clear(); generationTimer.Entry(); ViewCellBorderBasedDistribution vcStrat(*this, mCurrentViewCell); GenerateRays(numSamples, *mDistribution, simpleRays, sInvalidSamples); generationTimer.Exit(); // cast generated samples static VssRayContainer vssRays; vssRays.clear(); castTimer.Entry(); if (DETERMINISTIC_GVS) { const bool castDoubleRays = !mPerViewCell; const bool pruneInvalidRays = false; //const bool pruneInvalidRays = true; CastRays(simpleRays, vssRays, castDoubleRays, pruneInvalidRays); } else { if (0) // casting bundles of 4 rays generated from the simple rays CastRayBundles4(simpleRays, vssRays); else CastRayBundles16(simpleRays, vssRays); } castTimer.Exit(); // add to ray queue EnqueueRays(vssRays); initialTimer.Exit(); return (int)vssRays.size(); } void GvsPreprocessor::EnqueueRays(VssRayContainer &samples) { rayTimer.Entry(); // add samples to ray queue VssRayContainer::const_iterator vit, vit_end = samples.end(); for (vit = samples.begin(); vit != vit_end; ++ vit) { VssRay *ray = *vit; HandleRay(ray); // note: deletion of invalid samples handked by ray pool } rayTimer.Exit(); } int GvsPreprocessor::ProcessQueue() { gvsTimer.Entry(); ++ mGvsStats.mGvsRuns; int castSamples = 0; while (!mRayQueue.empty()) { // handle next ray VssRay *ray = mRayQueue.top(); mRayQueue.pop(); if (1) castSamples += AdaptiveBorderSampling(*ray); else castSamples += AdaptiveBorderSamplingOpt(*ray); // note: don't have to delete because handled by ray pool } gvsTimer.Exit(); return castSamples; } void ExportVssRays(Exporter *exporter, const VssRayContainer &vssRays) { VssRayContainer vcRays, vcRays2, vcRays3; VssRayContainer::const_iterator rit, rit_end = vssRays.end(); // prepare some rays for output for (rit = vssRays.begin(); rit != rit_end; ++ rit) { //const float p = RandomValue(0.0f, (float)vssRays.size()); if (1)//(p < raysOut) { if ((*rit)->mFlags & VssRay::BorderSample) vcRays.push_back(*rit); else if ((*rit)->mFlags & VssRay::ReverseSample) vcRays2.push_back(*rit); else vcRays3.push_back(*rit); } } exporter->ExportRays(vcRays, RgbColor(1, 0, 0)); exporter->ExportRays(vcRays2, RgbColor(0, 1, 0)); exporter->ExportRays(vcRays3, RgbColor(1, 1, 1)); } void GvsPreprocessor::VisualizeViewCell(const ObjectContainer &objects) { Intersectable::NewMail(); Material m; char str[64]; sprintf_s(str, "pass%06d.wrl", mProcessedViewCells); Exporter *exporter = Exporter::GetExporter(str); if (!exporter) return; ObjectContainer::const_iterator oit, oit_end = objects.end(); for (oit = objects.begin(); oit != oit_end; ++ oit) { Intersectable *intersect = *oit; m = RandomMaterial(); exporter->SetForcedMaterial(m); exporter->ExportIntersectable(intersect); } cout << "vssrays: " << (int)mVssRays.size() << endl; ExportVssRays(exporter, mVssRays); ///////////////// //-- export view cell geometry //exporter->SetWireframe(); m.mDiffuseColor = RgbColor(0, 1, 0); exporter->SetForcedMaterial(m); AxisAlignedBox3 bbox = mCurrentViewCell->GetMesh()->mBox; exporter->ExportBox(bbox); //exporter->SetFilled(); delete exporter; } void GvsPreprocessor::VisualizeViewCells() { char str[64]; sprintf_s(str, "tmp/pass%06d_%04d-", mProcessedViewCells, mPass); // visualization if (mGvsStats.mPassContribution > 0) { const bool exportRays = true; const bool exportPvs = true; mViewCellsManager->ExportSingleViewCells(mObjects, 10, false, exportPvs, exportRays, 1000, str); } // remove pass samples ViewCellContainer::const_iterator vit, vit_end = mViewCellsManager->GetViewCells().end(); for (vit = mViewCellsManager->GetViewCells().begin(); vit != vit_end; ++ vit) { (*vit)->DelRayRefs(); } } void GvsPreprocessor::CompileViewCellsFromPointList() { ViewCell::NewMail(); // Receive list of view cells from view cells manager ViewCellPointsList *vcPoints = mViewCellsManager->GetViewCellPointsList(); vector::const_iterator vit, vit_end = vcPoints->end(); for (vit = vcPoints->begin(); vit != vit_end; ++ vit) { ViewCellPoints *vp = *vit; if (vp->first) // view cell specified { ViewCell *viewCell = vp->first; if (!viewCell->Mailed()) { viewCell->Mail(); mViewCells.push_back(viewCell); if (mViewCells.size() >= mMaxViewCells) break; } } else // no view cell specified => compute view cells using view point { SimpleRayContainer::const_iterator rit, rit_end = vp->second.end(); for (rit = vp->second.begin(); rit != rit_end; ++ rit) { SimpleRay ray = *rit; ViewCell *viewCell = mViewCellsManager->GetViewCell(ray.mOrigin); if (viewCell && !viewCell->Mailed()) { viewCell->Mail(); mViewCells.push_back(viewCell); if (mViewCells.size() >= mMaxViewCells) break; } } } } } void GvsPreprocessor::CompileViewCellsList() { if (!mViewCellsManager->GetViewCellPointsList()->empty()) { cout << "processing view point list" << endl; CompileViewCellsFromPointList(); } else { ViewCell::NewMail(); while ((int)mViewCells.size() < mMaxViewCells) { const int tries = 10000; int i = 0; for (i = 0; i < tries; ++ i) { const int idx = (int)RandomValue(0.0f, (float)mViewCellsManager->GetNumViewCells() - 0.5f); ViewCell *viewCell = mViewCellsManager->GetViewCell(idx); if (!viewCell->Mailed()) { viewCell->Mail(); break; } mViewCells.push_back(viewCell); } if (i == tries) { cerr << "big error! no view cell found" << endl; break; } } } cout << "\ncomputing list of " << mViewCells.size() << " view cells" << endl; } void GvsPreprocessor::ComputeViewCellGeometryIntersection() { intersectionTimer.Entry(); AxisAlignedBox3 box = mCurrentViewCell->GetBox(); int oldTrianglePvs = (int)mTrianglePvs.size(); int newKdObj = 0; // compute pvs kd nodes that intersect view cell ObjectContainer kdobjects; // find intersecting objects not in pvs (i.e., only unmailed) mKdTree->CollectKdObjects(box, kdobjects); ObjectContainer pvsKdObjects; ObjectContainer::const_iterator oit, oit_end = kdobjects.end(); for (oit = kdobjects.begin(); oit != oit_end; ++ oit) { KdIntersectable *kdInt = static_cast(*oit); myobjects.clear(); mKdTree->CollectObjectsWithDublicates(kdInt->GetItem(), myobjects); // account for kd object pvs bool addkdobj = false; ObjectContainer::const_iterator oit, oit_end = myobjects.end(); for (oit = myobjects.begin(); oit != oit_end; ++ oit) { TriangleIntersectable *triObj = static_cast(*oit); // test if the triangle itself intersects if (box.Intersects(triObj->GetItem())) addkdobj = AddTriangleObject(triObj); } // add node to kd pvs if (1 && addkdobj) { ++ newKdObj; mCurrentViewCell->GetPvs().AddSampleDirty(kdInt, 1.0f); //mCurrentViewCell->GetPvs().AddSampleDirtyCheck(kdInt, 1.0f); if (QT_VISUALIZATION_SHOWN) UpdateStatsForVisualization(kdInt); } } cout << "added " << (int)mTrianglePvs.size() - oldTrianglePvs << " triangles (" << newKdObj << " objects) by intersection" << endl; intersectionTimer.Exit(); } void GvsPreprocessor::PerViewCellComputation() { while (mCurrentViewCell = NextViewCell()) { preparationTimer.Entry(); // hack: reset counters (could be done with a mailing-like approach ObjectContainer::const_iterator oit, oit_end = mObjects.end(); for (oit = mObjects.begin(); oit != oit_end; ++ oit) (*oit)->mCounter = NOT_ACCOUNTED_OBJECT; preparationTimer.Exit(); mDistribution->SetViewCell(mCurrentViewCell); ComputeViewCell(); } } void GvsPreprocessor::PerViewCellComputation2() { while (1) { if (!mRendererWidget) continue; mCurrentViewCell = mViewCellsManager->GetViewCell(mRendererWidget->GetViewPoint()); // no valid view cell or view cell already computed if (!mCurrentViewCell || !mCurrentViewCell->GetPvs().Empty() || !mRendererWidget->mComputeGVS) continue; mRendererWidget->mComputeGVS = false; // hack: reset counters ObjectContainer::const_iterator oit, oit_end = mObjects.end(); for (oit = mObjects.begin(); oit != oit_end; ++ oit) (*oit)->mCounter = NOT_ACCOUNTED_OBJECT; ComputeViewCell(); ++ mProcessedViewCells; } } void GvsPreprocessor::StorePvs(const ObjectContainer &objectPvs) { ObjectContainer::const_iterator oit, oit_end = objectPvs.end(); for (oit = objectPvs.begin(); oit != oit_end; ++ oit) { mCurrentViewCell->GetPvs().AddSample(*oit, 1); } } void GvsPreprocessor::UpdatePvs(ViewCell *currentViewCell) { ObjectPvs newPvs; BvhLeaf::NewMail(); ObjectPvsIterator pit = currentViewCell->GetPvs().GetIterator(); // output PVS of view cell while (pit.HasMoreEntries()) { Intersectable *intersect = pit.Next(); BvhLeaf *bv = intersect->mBvhLeaf; if (!bv || bv->Mailed()) continue; bv->Mail(); //m.mDiffuseColor = RgbColor(1, 0, 0); newPvs.AddSampleDirty(bv, 1.0f); } newPvs.SimpleSort(); currentViewCell->SetPvs(newPvs); } void GvsPreprocessor::GetObjectPvs(ObjectContainer &objectPvs) const { BvhLeaf::NewMail(); objectPvs.reserve((int)mTrianglePvs.size()); ObjectContainer::const_iterator oit, oit_end = mTrianglePvs.end(); for (oit = mTrianglePvs.begin(); oit != oit_end; ++ oit) { Intersectable *intersect = *oit; BvhLeaf *bv = intersect->mBvhLeaf; // hack: reset counter (*oit)->mCounter = NOT_ACCOUNTED_OBJECT; if (!bv || bv->Mailed()) continue; bv->Mail(); objectPvs.push_back(bv); } } void GvsPreprocessor::GlobalComputation() { int passSamples = 0; int randomSamples = 0; int gvsSamples = 0; int oldContribution = 0; while (mGvsStats.mTotalSamples < mTotalSamples) { mRayCaster->InitPass(); int newRandomSamples, newGvsSamples; // Ray queue empty => // cast a number of uniform samples to fill ray queue newRandomSamples = CastInitialSamples(mInitialSamples); if (!mOnlyRandomSampling) newGvsSamples = ProcessQueue(); passSamples += newRandomSamples + newGvsSamples; mGvsStats.mTotalSamples += newRandomSamples + newGvsSamples; mGvsStats.mRandomSamples += newRandomSamples; mGvsStats.mGvsSamples += newGvsSamples; if (passSamples % (mGvsSamplesPerPass + 1) == mGvsSamplesPerPass) { ++ mPass; mGvsStats.mPassContribution = mGvsStats.mTotalContribution - oldContribution; //////// //-- stats //cout << "\nPass " << mPass << " #samples: " << mGvsStats.mTotalSamples << " of " << mTotalSamples << endl; mGvsStats.mPass = mPass; mGvsStats.Stop(); mGvsStats.Print(mGvsStatsStream); // reset oldContribution = mGvsStats.mTotalContribution; mGvsStats.mPassContribution = 0; passSamples = 0; if (GVS_DEBUG) VisualizeViewCells(); } } } bool GvsPreprocessor::ComputeVisibility() { cout << "Gvs Preprocessor started\n" << flush; const long startTime = GetTime(); //Randomize(0); mGvsStats.Reset(); mGvsStats.Start(); if (!mLoadViewCells) { /// construct the view cells from the scratch ConstructViewCells(); // reset pvs already gathered during view cells construction mViewCellsManager->ResetPvs(); cout << "finished view cell construction" << endl; } if (mPerViewCell) { #if 1 // provide list of view cells to compute CompileViewCellsList(); // start per view cell gvs PerViewCellComputation(); #else PerViewCellComputation2(); #endif } else { GlobalComputation(); } cout << "cast " << mGvsStats.mTotalSamples / (1e3f * TimeDiff(startTime, GetTime())) << "M single rays/s" << endl; if (GVS_DEBUG) { Visualize(); CLEAR_CONTAINER(mVssRays); } // export the preprocessed information to a file if (0 && mExportVisibility) ExportPreprocessedData(mVisibilityFileName); // compute the pixel error of this visibility solution if (0 && mEvaluatePixelError) ComputeRenderError(); return true; } void GvsPreprocessor::DeterminePvsObjects(VssRayContainer &rays) { // store triangle directly mViewCellsManager->DeterminePvsObjects(rays, true); } void GvsPreprocessor::Visualize() { Exporter *exporter = Exporter::GetExporter("gvs.wrl"); if (!exporter) return; vector::const_iterator vit, vit_end = vizContainer.end(); for (vit = vizContainer.begin(); vit != vit_end; ++ vit) { exporter->SetWireframe(); exporter->ExportPolygon((*vit).enlargedTriangle); //Material m; exporter->SetFilled(); Polygon3 poly = Polygon3((*vit).originalTriangle); exporter->ExportPolygon(&poly); } VssRayContainer::const_iterator rit, rit_end = mVssRays.end(); for (rit = mVssRays.begin(); rit != rit_end; ++ rit) { Intersectable *obj = (*rit)->mTerminationObject; exporter->ExportIntersectable(obj); } ExportVssRays(exporter, mVssRays); delete exporter; } void GvsStatistics::Print(ostream &app) const { app << "#ViewCells\n" << mViewCells << endl; app << "#Id\n" << mViewCellId << endl; app << "#Time\n" << mTimePerViewCell << endl;; app << "#TriPvs\n" << mTrianglePvs << endl; app << "#ObjectPvs\n" << mPerViewCellPvs << endl; app << "#UpdatedPvs\n" << (int)mPvsCost << endl; app << "#PerViewCellSamples\n" << mPerViewCellSamples << endl; app << "#RaysPerSec\n" << RaysPerSec() << endl; app << "#TotalPvs\n" << mTotalPvs << endl; app << "#TotalTime\n" << mTotalTime << endl; app << "#TotalSamples\n" << mTotalSamples << endl; app << "#AvgPvs: " << mPvsCost / mViewCells << endl; app << "#TotalTrianglePvs\n" << mTotalTrianglePvs << endl; if (0) { app << "#ReverseSamples\n" << mReverseSamples << endl; app << "#BorderSamples\n" << mBorderSamples << endl; app << "#Pass\n" << mPass << endl; app << "#ScDiff\n" << mPassContribution << endl; app << "#GvsRuns\n" << mGvsRuns << endl; app << "#SamplesContri\n" << mTotalContribution << endl; } app << endl; } void GvsPreprocessor::ComputeViewCell() { const long startTime = GetTime(); // initialise mGvsStats.mPerViewCellSamples = 0; mGvsStats.mRandomSamples = 0; mGvsStats.mGvsSamples = 0; //renderer->mPvsStat.currentPass = 0; mPass = 0; int oldContribution = 0; int passSamples = 0; for (int i = 0; i < 2; ++ i) mGenericStats[i] = 0; mTrianglePvs.clear(); // hack: take bounding box of view cell as view cell bounds mCurrentViewCell->GetMesh()->ComputeBoundingBox(); // use mailing for kd node pvs KdNode::NewMail(); cout << "\n***********************\n" << "computing view cell " << mProcessedViewCells << " (id: " << mCurrentViewCell->GetId() << ")" << endl; cout << "bb: " << mCurrentViewCell->GetBox() << endl; mainLoopTimer.Entry(); while (1) { sInvalidSamples = 0; int oldPvsSize = mCurrentViewCell->GetPvs().GetSize(); mRayCaster->InitPass(); int newRandomSamples, newGvsSamples, newSamples; // Ray queue empty => // cast a number of uniform samples to fill ray queue newRandomSamples = CastInitialSamples(mInitialSamples); if (!mOnlyRandomSampling) newGvsSamples = ProcessQueue(); newSamples = newRandomSamples + newGvsSamples; passSamples += newSamples; mGvsStats.mPerViewCellSamples += newSamples; mGvsStats.mRandomSamples += newRandomSamples; mGvsStats.mGvsSamples += newGvsSamples; if (passSamples >= mGvsSamplesPerPass) { if (GVS_DEBUG) VisualizeViewCell(mTrianglePvs); //const bool convertPerPass = true; const bool convertPerPass = false; if (convertPerPass) { int newObjects = ConvertObjectPvs(); cout << "added " << newObjects << " triangles to triangle pvs" << endl; mGvsStats.mTotalContribution += newObjects; } ++ mPass; mGvsStats.mPassContribution = mGvsStats.mTotalContribution - oldContribution; //////// //-- stats mGvsStats.mPass = mPass; cout << "\nPass " << mPass << " #samples: " << mGvsStats.mPerViewCellSamples << endl; cout << "contribution=" << mGvsStats.mPassContribution << " (of " << mMinContribution << ")" << endl; cout << "obj contri=" << mCurrentViewCell->GetPvs().GetSize() - oldPvsSize << " (of " << mMinContribution << ")" << endl; // termination criterium if (mGvsStats.mPassContribution < mMinContribution) break; // reset stats oldContribution = mGvsStats.mTotalContribution; mGvsStats.mPassContribution = 0; passSamples = 0; // compute the pixel error of this visibility solution if (0 && mEvaluatePixelError) ComputeRenderError(); } } mainLoopTimer.Exit(); // at last compute objects that directly intersect view cell ComputeViewCellGeometryIntersection(); // compute the pixel error of this visibility solution if (mEvaluatePixelError) ComputeRenderError(); //////// //-- stats // timing mGvsStats.mTimePerViewCell = TimeDiff(startTime, GetTime()) * 1e-3f; ComputeStats(); } void GvsPreprocessor::ComputeStats() { // compute pvs size using larger (bvh objects) // note: for kd pvs we had to do this during gvs computation if (!mUseKdPvs) { ObjectContainer objectPvs; // optain object pvs GetObjectPvs(objectPvs); // add pvs ObjectContainer::const_iterator it, it_end = objectPvs.end(); for (it = objectPvs.begin(); it != it_end; ++ it) { mCurrentViewCell->GetPvs().AddSampleDirty(*it, 1.0f); } cout << "triangle pvs of " << (int)mTrianglePvs.size() << " was converted to object pvs of " << (int)objectPvs.size() << endl; mGvsStats.mPerViewCellPvs = (int)objectPvs.size(); mGvsStats.mPvsCost = 0; // todo objectPvs.EvalPvsCost(); } else { if (0) // compute pvs kd nodes that intersect view cell { ObjectContainer mykdobjects; // extract kd pvs ObjectContainer::const_iterator it, it_end = mTrianglePvs.end(); for (it = mTrianglePvs.begin(); it != it_end; ++ it) { Intersectable *obj = *it; // find intersecting objects not yet in pvs (i.e., only unmailed) mKdTree->CollectKdObjects(obj->GetBox(), mykdobjects); } // add pvs it_end = mykdobjects.end(); for (it = mykdobjects.begin(); it != it_end; ++ it) { mCurrentViewCell->GetPvs().AddSampleDirty(*it, 1.0f); } cout << "added " << (int)mykdobjects.size() << " new objects " << endl; } mGvsStats.mPerViewCellPvs = mCurrentViewCell->GetPvs().GetSize(); mGvsStats.mPvsCost = mCurrentViewCell->GetPvs().EvalPvsCost(); } mGvsStats.mTrianglePvs = (int)mTrianglePvs.size(); //////////////// // global values mGvsStats.mViewCells = mProcessedViewCells; mGvsStats.mTotalTrianglePvs += mGvsStats.mTrianglePvs; mGvsStats.mTotalTime += mGvsStats.mTimePerViewCell; mGvsStats.mTotalPvs += mGvsStats.mPerViewCellPvs; mGvsStats.mTotalSamples += mGvsStats.mPerViewCellSamples; cout << "id: " << mCurrentViewCell->GetId() << endl; cout << "pvs cost " << mGvsStats.mPvsCost / 1000000 << " M" << endl; cout << "pvs tri: " << mTrianglePvs.size() << endl; cout << "samples: " << mGvsStats.mTotalSamples / 1000000 << " M" << endl; printf("contri: %f tri / K rays\n", 1000.0f * (float)mTrianglePvs.size() / mGvsStats.mTotalSamples); //cout << "invalid samples ratio: " << (float)sInvalidSamples / mGvsStats.mPerViewCellSamples << endl; double rayTime = rayTimer.TotalTime(); double kdPvsTime = kdPvsTimer.TotalTime(); double gvsTime = gvsTimer.TotalTime(); double initialTime = initialTimer.TotalTime(); double intersectionTime = intersectionTimer.TotalTime(); double preparationTime = preparationTimer.TotalTime(); double mainLoopTime = mainLoopTimer.TotalTime(); double contributionTime = contributionTimer.TotalTime(); double castTime = castTimer.TotalTime(); double generationTime = generationTimer.TotalTime(); double rawCastTime = mRayCaster->rawCastTimer.TotalTime(); cout << "handleRay : " << rayTime << endl; cout << "kd pvs : " << kdPvsTime << endl; cout << "gvs sampling : " << gvsTime << endl; cout << "initial sampling : " << initialTime << endl; cout << "view cell intersection : " << intersectionTime << endl; cout << "preparation : " << preparationTime << endl; cout << "main loop : " << mainLoopTime << endl; cout << "has contribution : " << contributionTime << endl; cout << "cast time : " << castTime << endl; cout << "generation time : " << generationTime << endl; cout << "raw cast time : " << rawCastTime << endl; double randomRaysPerSec = mGvsStats.mRandomSamples / (1e6 * initialTime); double gvsRaysPerSec = mGvsStats.mGvsSamples / (1e6 * gvsTime); double rawRaysPerSec = mGvsStats.mPerViewCellSamples / (1e6 * rawCastTime); cout << "cast " << randomRaysPerSec << " M random rays/s: " << endl; cout << "cast " << gvsRaysPerSec << " M gvs rays/s: " << endl; cout << "cast " << rawRaysPerSec << " M raw rays/s: " << endl; mGvsStats.Stop(); mGvsStats.Print(mGvsStatsStream); } int GvsPreprocessor::ConvertObjectPvs() { static ObjectContainer triangles; int newObjects = 0; ObjectPvsIterator pit = mCurrentViewCell->GetPvs().GetIterator(); triangles.clear(); // output PVS of view cell while (pit.HasMoreEntries()) { KdIntersectable *kdInt = static_cast(pit.Next()); mKdTree->CollectObjectsWithDublicates(kdInt->GetItem(), triangles); ObjectContainer::const_iterator oit, oit_end = triangles.end(); for (oit = triangles.begin(); oit != oit_end; ++ oit) { if (AddTriangleObject(*oit)) ++ newObjects; } } return newObjects; } bool GvsPreprocessor::AddTriangleObject(Intersectable *triObj) { if ((triObj->mCounter < ACCOUNTED_OBJECT)) { triObj->mCounter += ACCOUNTED_OBJECT; mTrianglePvs.push_back(triObj); mGenericStats[0] = (int)mTrianglePvs.size(); return true; } return false; } void GvsPreprocessor::CastRayBundles4(const SimpleRayContainer &rays, VssRayContainer &vssRays) { AxisAlignedBox3 box = mViewCellsManager->GetViewSpaceBox(); SimpleRayContainer::const_iterator it, it_end = rays.end(); for (it = rays.begin(); it != it_end; ++ it) CastRayBundle4(*it, vssRays, box); } void GvsPreprocessor::CastRayBundle4(const SimpleRay &ray, VssRayContainer &vssRays, const AxisAlignedBox3 &box) { const float pertubDir = 0.01f; static Vector3 pertub; static int hit_triangles[4]; static float dist[4]; static Vector3 dirs[4]; static Vector3 origs[4]; origs[0] = ray.mOrigin; dirs[0] = ray.mDirection; for (int i = 1; i < 4; ++ i) { origs[i] = ray.mOrigin; pertub.x = RandomValue(-pertubDir, pertubDir); pertub.y = RandomValue(-pertubDir, pertubDir); pertub.z = RandomValue(-pertubDir, pertubDir); dirs[i] = ray.mDirection + pertub; dirs[i] *= 1.0f / Magnitude(dirs[i]); } Cast4Rays(dist, dirs, origs, vssRays, box); } void GvsPreprocessor::CastRays4(const SimpleRayContainer &rays, VssRayContainer &vssRays) { SimpleRayContainer::const_iterator it, it_end = rays.end(); static float dist[4]; static Vector3 dirs[4]; static Vector3 origs[4]; AxisAlignedBox3 box = mViewCellsManager->GetViewSpaceBox(); for (it = rays.begin(); it != it_end; ++ it) { for (int i = 1; i < 4; ++ i) { origs[i] = rays[i].mOrigin; dirs[i] = rays[i].mDirection; } } Cast4Rays(dist, dirs, origs, vssRays, box); } void GvsPreprocessor::Cast4Rays(float *dist, Vector3 *dirs, Vector3 *origs, VssRayContainer &vssRays, const AxisAlignedBox3 &box) { static int hit_triangles[4]; mRayCaster->CastRaysPacket4(box.Min(), box.Max(), origs, dirs, hit_triangles, dist); VssRay *ray; for (int i = 0; i < 4; ++ i) { if (hit_triangles[i] != -1) { TriangleIntersectable *triObj = static_cast(mObjects[hit_triangles[i]]); if (DotProd(triObj->GetItem().GetNormal(), dirs[i] >= 0)) ray = NULL; else ray = mRayCaster->RequestRay(origs[i], origs[i] + dirs[i] * dist[i], NULL, triObj, mPass, 1.0f); } else { ray = NULL; } vssRays.push_back(ray); } } void GvsPreprocessor::CastRayBundle16(const SimpleRay &ray, VssRayContainer &vssRays) { static SimpleRayContainer simpleRays; simpleRays.clear(); simpleRays.push_back(ray); GenerateJitteredRays(simpleRays, ray, 15, 0); CastRays(simpleRays, vssRays, false, false); } void GvsPreprocessor::CastRayBundles16(const SimpleRayContainer &rays, VssRayContainer &vssRays) { SimpleRayContainer::const_iterator it, it_end = rays.end(); for (it = rays.begin(); it != it_end; ++ it) CastRayBundle16(*it, vssRays); } void GvsPreprocessor::ComputeRenderError() { // compute rendering error if (renderer && renderer->mPvsStatFrames) { if (!mViewCellsManager->GetViewCellPointsList()->empty()) { ViewCellPointsList *vcPoints = mViewCellsManager->GetViewCellPointsList(); ViewCellPointsList::const_iterator vit = vcPoints->begin(), vit_end = vcPoints->end(); SimpleRayContainer viewPoints; for (; vit != vit_end; ++ vit) { ViewCellPoints *vp = *vit; SimpleRayContainer::const_iterator rit = vp->second.begin(), rit_end = vp->second.end(); for (; rit!=rit_end; ++rit) { ViewCell *vc = mViewCellsManager->GetViewCell((*rit).mOrigin); if (vc == mCurrentViewCell) viewPoints.push_back(*rit); } } renderer->mPvsErrorBuffer.clear(); if (viewPoints.size() != renderer->mPvsErrorBuffer.size()) { renderer->mPvsErrorBuffer.resize(viewPoints.size()); renderer->ClearErrorBuffer(); } cout << "evaluating list of " << viewPoints.size() << " pts" << endl; renderer->EvalPvsStat(viewPoints); } else { cout << "evaluating random points" << endl; renderer->EvalPvsStat(); } mStats << "#AvgPvsRenderError\n" <mPvsStat.GetAvgError()<GetAvgPixelError()<GetMaxPixelError()<mPvsStat.GetMaxError()<mPvsStat.GetErrorFreeFrames()<mPvsStat.GetAvgPvs()<