443 lines
12 KiB
C++
443 lines
12 KiB
C++
|
|
/*
|
||
|
|
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<MSeg> s1 = face.msegs;
|
||
|
|
vector<MSeg> 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> MFace::SplitCycle() {
|
||
|
|
MSegs ms = face;
|
||
|
|
vector<MFace> 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<MSeg>::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> 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<MFace> 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();
|
||
|
|
|
||
|
|
}
|