/* 1 Class ~MFace~ represents a Moving Face with optional holes */ #include "interpolate.h" MFace::MFace() : needStartRegion(false), needEndRegion(false), half(0) { } /* 1.1 Constructs a Moving Face from a set of Moving Segments */ MFace::MFace(MSegs face) : face(face), needStartRegion(false), needEndRegion(false), half(0) { } /* 1.2 ~SortCycle~ Sort the Moving Segments according to their position in the cycle and perform several sanity-checks. */ bool MFace::SortCycle() { vector s1 = face.msegs; vector s2; if (face.msegs.size() < 3) return true; int cur = 0; unsigned int i = 0; bool ret = true; //Start with the first segment do { s2.push_back(s1[cur]); // Find the following segment cur = face.findNext(face.msegs[cur], cur, true); if (i++ > face.msegs.size()) { // If we have more iterations than the total number of msegments, // then we are stuck in an endless loop. ret = false; DEBUG(3, "Endless loop!"); break; } } while (cur > 0); // We started with msegment 0, so stop here. if (cur == -1) { // We didn't find a following msegment, this is an error ret = false; DEBUG(3, "Error: cycle incomplete!"); } else if (cur == -2) { // findNext returns -2 if two matching msegments were found ret = false; DEBUG(3, "Error: cycle ambiguous!"); } else if (cur < 0) { // Should never happen ret = false; DEBUG(3, "Error: unknown error in cycle!"); } if (s2.size() != face.msegs.size()) { // If the sorted cycle has less msegments than the original list, // then we didn't use all of them. This is an error. ret = false; DEBUG(3, "Unused segments! " << "has " << face.msegs.size() << " used " << s2.size()); } else { face.msegs = s2; } return ret; } /* 1.3 ~SortAndSplitCycle~ Sort the Moving Segments according to their position in the cycle. Split the cycle, if two independent cycles can be built from this cycle. */ vector MFace::SplitCycle() { MSegs ms = face; vector ret; DEBUG(4, "Called SplitCycle on " << this->ToString()); do { bool found = false; for (unsigned int i = 0; i < ms.msegs.size(); i++) { int next = ms.findNext(ms.msegs[i], 0, true); if (next == -2) { // We have found the startpoint of an ambiguous cycle! MSeg cur = ms.msegs[i]; MSegs cycle; DEBUG(4, "Start " << cur.ie.ToString() << " / " << cur.fe.ToString()); next = ms.findNext(cur, 0, false); int c = 0; do { if ((next < 0) || (c++ > (int) ms.msegs.size())) return ret; DEBUG(4, "Cycle: " << ms.msegs[next].ToString()); MSeg n = ms.msegs[next]; cycle.AddMSeg(n); ms.msegs.erase(ms.msegs.begin() + next); if (n.ie == cur.ie && n.fe == cur.fe) break; next = ms.findNext(n, 0, false); } while (1); MFace f = MFace(cycle); f.EliminateSpikes(); f.Check(); f.half = half; ret.push_back(f); found = true; break; } } if (found == false) break; } while (ms.msegs.size() > 0); if (ret.size() > 0) { ms.calculateBBox(); face = ms; } return ret; } /* 1.4 ~EliminateSpikes~ Try to find and eliminate empty spikes in the initial and/or final instant. */ void MFace::EliminateSpikes() { // Search spikes in the initial segments unsigned int i = 0, sz = face.msegs.size(), prev = 0; // Iterate twice over the segments to handle spikes at the wraparound, too while (i < 2*sz) { unsigned int cur = i%sz; // Access the arrays modulo arraysize if (face.msegs[cur].ie == face.msegs[prev].is) { // We have found an empty spike while (prev != cur) { // Degenerate initial segment to the startpoint of the spike face.msegs[prev].is = face.msegs[cur].ie; face.msegs[prev].ie = face.msegs[cur].ie; prev = (prev + 1)%sz; } face.msegs[cur].is = face.msegs[cur].ie; // also for current segment } else if (!(face.msegs[cur].is == face.msegs[cur].ie)) { // This was no spike, continue search from here prev = cur; } i++; } // Repeat the same procedure for the final segments i = 0; prev = 0; while (i < 2*sz) { unsigned int cur = i%sz; if (face.msegs[cur].fe == face.msegs[prev].fs) { while (prev != cur) { face.msegs[prev].fs = face.msegs[cur].fe; face.msegs[prev].fe = face.msegs[cur].fe; prev = (prev + 1)%sz; } face.msegs[cur].fs = face.msegs[cur].fe; } else if (!(face.msegs[cur].fs == face.msegs[cur].fe)) { prev = cur; } i++; } // Now eliminate MSeg-Objects with degenerated initial and final segments std::vector::iterator c = face.msegs.begin(); while (c != face.msegs.end()) { if (c->is == c->ie && c->fs == c->fe) c = face.msegs.erase(c); else c++; } } /* 1.5 ~Check~ Performs several sanity-checks on this object */ bool MFace::Check() { bool ret = true; if (isEmpty()) // an empty MFace is valid per definition return true; if (!SortCycle()) { ret = false; } for (unsigned int i = 0; i < holes.size(); i++) { MFace h1(holes[i]); for (unsigned int j = 0; j < holes.size(); j++) { if (holes[i].intersects(holes[j], false, false)) ret = false; } } if (face.intersects(face, false, true)) { DEBUG(3, "Intersection!"); ret = false; } if (!ret) { DEBUG(2, "Error with MFace " << ToString()); } if (STRICT) assert(ret); // else if (!ret) // *this = MFace(); return ret; } /* 1.6 ~AddConcavity~ Add a new concavity or hole to this MFace-Object. The cycle will be integrated as a Concavity or hole when the function MergeConcavities is called afterwards. */ void MFace::AddConcavity(MFace c) { if (c.face.msegs.size() >= 3) // Ignore invalid or degenerated cycles cvs.push_back(c); } /* 1.7 ~MergeConcavities~ Merge the objects in the concavities-list into the current cycle. These will either be integrated into the cycle if possible, or otherwise be added as a hole. */ vector MFace::MergeConcavities() { Check(); for (unsigned int i = 0; i < cvs.size(); i++) { if (cvs[i].face.msegs.size() < 3) // Ignore invalid or degenerated faces continue; // Inherit the need of a start or endregion needStartRegion |= cvs[i].needStartRegion; needEndRegion |= cvs[i].needEndRegion; DEBUG(4, "Merging " << ToString() << " with " << cvs[i].ToString()); if (face.MergeConcavity(cvs[i].face)) { DEBUG(4, "Success, result: " << ToString()); } else { DEBUG(4, "Failed, adding as hole"); // Merging the concavity into the cycle was not successful, add // this cycle to the list of holes. holes.push_back(cvs[i].face); // If the hole was not a real hole but a concavity in the source- or // destination region, we have to create a start and/or end region. if (!cvs[i].face.sreg.ishole) needStartRegion = true; if (!cvs[i].face.dreg.ishole) needEndRegion = true; } } // All concavities have been handled, clear the list. cvs.erase(cvs.begin(), cvs.end()); // Check and see, if this cycle was split into several cycles due to the // merge. vector split = SplitCycle(); if (split.size() > 0) { DEBUG(3, "Cycle is ambiguous, splitted into "); DEBUG(3, this->ToString()); for (unsigned int i = 0; i < split.size(); i++) { DEBUG(3, split[i].ToString()); } } // Merging the concavity into the cycle was successful. SortCycle(); // Sort the cycle EliminateSpikes(); // Eliminate empty spikes Check(); // Check the validity of the resulting MFace return split; } /* 1.8 ~CycleToListExpr~ takes a cycle of MSegs and constructs a RList suitable to be integrated into the RList-Representation of a moving region. */ static RList CycleToListExpr(MSegs face) { int first = 0, cur = 0; assert(face.msegs.size() > 0); RList cy; do { assert(cur >= 0); RList mseg; mseg.append(face.msegs[cur].ie.x / SCALEOUT); mseg.append(face.msegs[cur].ie.y / SCALEOUT); mseg.append(face.msegs[cur].fe.x / SCALEOUT); mseg.append(face.msegs[cur].fe.y / SCALEOUT); cy.append(mseg); cur = face.findNext(face.msegs[cur], cur, true); } while (cur != first); return cy; } /* 1.9 ~ToListExpr~ converts this face and its holes to a RList-Expression suitable to be embedded into the RList representation of a moving region. */ RList MFace::ToListExpr() { RList ret; ret.append(CycleToListExpr(face)); for (unsigned int i = 0; i < holes.size(); i++) { if (holes[i].msegs.size() < 3) continue; ret.append(CycleToListExpr(holes[i])); } return ret; } /* 1.10 ~PrintMRegionListExpr~ is used for debugging purposes and prints an OBJECT-representation of this MFace suitable to be loaded with "restore" into the Secondo database system */ void MFace::PrintMRegionListExpr() { DEBUG(1, "(OBJECT mr () mregion ( ( " "(\"2013-01-01\" \"2013-01-02\" TRUE TRUE)(" << ToListExpr().ToString() << ") ) ) )"); } /* 1.11 ~divide~ is used to create a MFace from this MFace over a part of the whole time-interval. For example: divide(0.0, 0.5) creates an MFace over the first half of the original time interval. */ MFace MFace::divide(double start, double end) { MFace ret(face.divide(start, end)); for (unsigned int i = 0; i < holes.size(); i++) { MSegs m = holes[i].divide(start, end); ret.AddConcavity(m); } ret.MergeConcavities(); ret.half = half; return ret; } /* 1.11 ~ToString~ creates a textual representation of this face including its holes. */ string MFace::ToString() { std::ostringstream ss; ss << "Face:\n"; ss << face.ToString(); for (unsigned int i = 0; i < holes.size(); i++) { ss << "Hole " << i << ":\n" << holes[i].ToString(); } ss << "\n"; return ss.str(); } /* 1.12 ~CreateBorderFace~ is used to reconstruct a Face from this MFace. If the parameter ~src~ is TRUE, then the face is created from the initial segments of the MSegs (thus representing the state at the start of the time-interval), otherwise from the final segments. */ Face MFace::CreateBorderFace(bool src) { Check(); Face ret = face.CreateBorderFace(src); for (unsigned int h = 0; h < holes.size(); h++) { Face hole = holes[h].CreateBorderFace(src); // AddHole also merges concavities if segments overlap ret.AddHole(hole); } return ret; } /* 1.13 ~isEmpty~ returns true, if this MFace does not contain any moving segments */ bool MFace::isEmpty() { return face.msegs.size() == 0; } /* 1.14 ~reverse~ swaps the initial and final segment of all msegments in this moving face */ void MFace::reverse() { face.reverse(); for (unsigned int i = 0; i < holes.size(); i++) holes[i].reverse(); }