2009-01-19 10:37:10 +00:00
|
|
|
#include "../solvespace.h"
|
|
|
|
|
2009-01-21 05:04:38 +00:00
|
|
|
void SPolygon::UvTriangulateInto(SMesh *m) {
|
|
|
|
if(l.n <= 0) return;
|
|
|
|
|
2009-01-22 10:02:46 +00:00
|
|
|
SDWORD in = GetMilliseconds();
|
|
|
|
|
2009-01-21 05:04:38 +00:00
|
|
|
normal = Vector::From(0, 0, 1);
|
|
|
|
|
|
|
|
while(l.n > 0) {
|
|
|
|
FixContourDirections();
|
|
|
|
l.ClearTags();
|
|
|
|
|
|
|
|
// Find a top-level contour, and start with that. Then build bridges
|
|
|
|
// in order to merge all its islands into a single contour.
|
|
|
|
SContour *top;
|
|
|
|
for(top = l.First(); top; top = l.NextAfter(top)) {
|
|
|
|
if(top->timesEnclosed == 0) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if(!top) {
|
|
|
|
dbp("polygon has no top-level contours?");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Start with the outer contour
|
|
|
|
SContour merged;
|
|
|
|
ZERO(&merged);
|
|
|
|
top->tag = 1;
|
|
|
|
top->CopyInto(&merged);
|
|
|
|
(merged.l.n)--;
|
|
|
|
|
|
|
|
// List all of the edges, for testing whether bridges work.
|
|
|
|
SEdgeList el;
|
|
|
|
ZERO(&el);
|
|
|
|
top->MakeEdgesInto(&el);
|
|
|
|
List<Vector> vl;
|
|
|
|
ZERO(&vl);
|
|
|
|
|
|
|
|
// And now find all of its holes;
|
|
|
|
SContour *sc;
|
|
|
|
for(sc = l.First(); sc; sc = l.NextAfter(sc)) {
|
|
|
|
if(sc->timesEnclosed != 1) continue;
|
|
|
|
if(sc->l.n < 1) continue;
|
|
|
|
|
|
|
|
if(top->ContainsPointProjdToNormal(normal, sc->l.elem[0].p)) {
|
|
|
|
sc->tag = 2;
|
|
|
|
sc->MakeEdgesInto(&el);
|
2009-01-22 10:02:46 +00:00
|
|
|
sc->FindPointWithMinX();
|
2009-01-21 05:04:38 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-01-25 09:19:59 +00:00
|
|
|
// dbp("finished finding holes: %d ms", GetMilliseconds() - in);
|
2009-01-22 10:02:46 +00:00
|
|
|
for(;;) {
|
|
|
|
double xmin = 1e10;
|
|
|
|
SContour *scmin = NULL;
|
2009-01-21 05:04:38 +00:00
|
|
|
|
|
|
|
for(sc = l.First(); sc; sc = l.NextAfter(sc)) {
|
|
|
|
if(sc->tag != 2) continue;
|
|
|
|
|
2009-01-22 10:02:46 +00:00
|
|
|
if(sc->xminPt.x < xmin) {
|
|
|
|
xmin = sc->xminPt.x;
|
|
|
|
scmin = sc;
|
2009-01-21 05:04:38 +00:00
|
|
|
}
|
|
|
|
}
|
2009-01-22 10:02:46 +00:00
|
|
|
if(!scmin) break;
|
|
|
|
|
|
|
|
if(!merged.BridgeToContour(scmin, &el, &vl)) {
|
|
|
|
dbp("couldn't merge our hole");
|
2009-01-21 05:04:38 +00:00
|
|
|
return;
|
|
|
|
}
|
2009-01-25 09:19:59 +00:00
|
|
|
// dbp(" bridged to contour: %d ms", GetMilliseconds() - in);
|
2009-01-22 10:02:46 +00:00
|
|
|
scmin->tag = 3;
|
|
|
|
}
|
2009-01-25 09:19:59 +00:00
|
|
|
// dbp("finished merging holes: %d ms", GetMilliseconds() - in);
|
2009-01-21 05:04:38 +00:00
|
|
|
|
|
|
|
merged.UvTriangulateInto(m);
|
2009-01-25 09:19:59 +00:00
|
|
|
// dbp("finished ear clippping: %d ms", GetMilliseconds() - in);
|
2009-01-21 05:04:38 +00:00
|
|
|
merged.l.Clear();
|
|
|
|
el.Clear();
|
|
|
|
vl.Clear();
|
|
|
|
|
|
|
|
l.RemoveTagged();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool SContour::BridgeToContour(SContour *sc,
|
|
|
|
SEdgeList *avoidEdges, List<Vector> *avoidPts)
|
|
|
|
{
|
2009-01-22 10:02:46 +00:00
|
|
|
int i, j;
|
|
|
|
|
|
|
|
// Start looking for a bridge on our new hole near its leftmost (min x)
|
|
|
|
// point.
|
|
|
|
int sco = 0;
|
|
|
|
for(i = 0; i < (sc->l.n - 1); i++) {
|
|
|
|
if((sc->l.elem[i].p).EqualsExactly(sc->xminPt)) {
|
|
|
|
sco = i;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// And start looking on our merged contour at whichever point is nearest
|
|
|
|
// to the leftmost point of the new segment.
|
|
|
|
int thiso = 0;
|
|
|
|
double dmin = 1e10;
|
|
|
|
for(i = 0; i < l.n; i++) {
|
|
|
|
Vector p = l.elem[i].p;
|
|
|
|
double d = (p.Minus(sc->xminPt)).MagSquared();
|
|
|
|
if(d < dmin) {
|
|
|
|
dmin = d;
|
|
|
|
thiso = i;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-01-21 05:04:38 +00:00
|
|
|
int thisp, scp;
|
|
|
|
|
|
|
|
Vector a, b, *f;
|
2009-01-22 10:02:46 +00:00
|
|
|
for(i = 0; i < l.n; i++) {
|
|
|
|
thisp = WRAP(i+thiso, l.n);
|
2009-01-21 05:04:38 +00:00
|
|
|
a = l.elem[thisp].p;
|
|
|
|
|
|
|
|
for(f = avoidPts->First(); f; f = avoidPts->NextAfter(f)) {
|
|
|
|
if(f->Equals(a)) break;
|
|
|
|
}
|
|
|
|
if(f) continue;
|
|
|
|
|
2009-01-22 10:02:46 +00:00
|
|
|
for(j = 0; j < (sc->l.n - 1); j++) {
|
|
|
|
scp = WRAP(j+sco, (sc->l.n - 1));
|
2009-01-21 05:04:38 +00:00
|
|
|
b = sc->l.elem[scp].p;
|
|
|
|
|
|
|
|
for(f = avoidPts->First(); f; f = avoidPts->NextAfter(f)) {
|
|
|
|
if(f->Equals(b)) break;
|
|
|
|
}
|
|
|
|
if(f) continue;
|
|
|
|
|
2009-01-23 03:30:30 +00:00
|
|
|
if(avoidEdges->AnyEdgeCrossings(a, b) > 0) {
|
2009-01-21 05:04:38 +00:00
|
|
|
// doesn't work, bridge crosses an existing edge
|
|
|
|
} else {
|
|
|
|
goto haveEdge;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Tried all the possibilities, didn't find an edge
|
|
|
|
return false;
|
|
|
|
|
|
|
|
haveEdge:
|
|
|
|
SContour merged;
|
|
|
|
ZERO(&merged);
|
|
|
|
for(i = 0; i < l.n; i++) {
|
|
|
|
merged.AddPoint(l.elem[i].p);
|
|
|
|
if(i == thisp) {
|
|
|
|
// less than or equal; need to duplicate the join point
|
|
|
|
for(j = 0; j <= (sc->l.n - 1); j++) {
|
|
|
|
int jp = WRAP(j + scp, (sc->l.n - 1));
|
|
|
|
merged.AddPoint((sc->l.elem[jp]).p);
|
|
|
|
}
|
|
|
|
// and likewise duplicate join point for the outer curve
|
|
|
|
merged.AddPoint(l.elem[i].p);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// and future bridges mustn't cross our bridge, and it's tricky to get
|
|
|
|
// things right if two bridges come from the same point
|
|
|
|
avoidEdges->AddEdge(a, b);
|
|
|
|
avoidPts->Add(&a);
|
|
|
|
avoidPts->Add(&b);
|
|
|
|
|
|
|
|
l.Clear();
|
|
|
|
l = merged.l;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool SContour::IsEar(int bp) {
|
|
|
|
int ap = WRAP(bp-1, l.n),
|
|
|
|
cp = WRAP(bp+1, l.n);
|
|
|
|
|
|
|
|
STriangle tr;
|
|
|
|
ZERO(&tr);
|
|
|
|
tr.a = l.elem[ap].p;
|
|
|
|
tr.b = l.elem[bp].p;
|
|
|
|
tr.c = l.elem[cp].p;
|
|
|
|
|
|
|
|
Vector n = Vector::From(0, 0, -1);
|
|
|
|
if((tr.Normal()).Dot(n) < LENGTH_EPS) {
|
|
|
|
// This vertex is reflex, or between two collinear edges; either way,
|
|
|
|
// it's not an ear.
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Accelerate with an axis-aligned bounding box test
|
|
|
|
Vector maxv = tr.a, minv = tr.a;
|
|
|
|
(tr.b).MakeMaxMin(&maxv, &minv);
|
|
|
|
(tr.c).MakeMaxMin(&maxv, &minv);
|
|
|
|
|
|
|
|
int i;
|
|
|
|
for(i = 0; i < l.n; i++) {
|
|
|
|
if(i == ap || i == bp || i == cp) continue;
|
|
|
|
|
|
|
|
Vector p = l.elem[i].p;
|
|
|
|
if(p.OutsideAndNotOn(maxv, minv)) continue;
|
|
|
|
|
|
|
|
// A point on the edge of the triangle is considered to be inside,
|
|
|
|
// and therefore makes it a non-ear; but a point on the vertex is
|
|
|
|
// "outside", since that's necessary to make bridges work.
|
|
|
|
if(p.EqualsExactly(tr.a)) continue;
|
|
|
|
if(p.EqualsExactly(tr.b)) continue;
|
|
|
|
if(p.EqualsExactly(tr.c)) continue;
|
|
|
|
|
|
|
|
if(tr.ContainsPointProjd(n, p)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void SContour::ClipEarInto(SMesh *m, int bp) {
|
|
|
|
int ap = WRAP(bp-1, l.n),
|
|
|
|
cp = WRAP(bp+1, l.n);
|
2009-01-22 10:02:46 +00:00
|
|
|
|
2009-01-21 05:04:38 +00:00
|
|
|
STriangle tr;
|
|
|
|
ZERO(&tr);
|
|
|
|
tr.a = l.elem[ap].p;
|
|
|
|
tr.b = l.elem[bp].p;
|
|
|
|
tr.c = l.elem[cp].p;
|
|
|
|
m->AddTriangle(&tr);
|
|
|
|
|
|
|
|
// By deleting the point at bp, we may change the ear-ness of the points
|
|
|
|
// on either side.
|
|
|
|
l.elem[ap].ear = SPoint::UNKNOWN;
|
|
|
|
l.elem[cp].ear = SPoint::UNKNOWN;
|
|
|
|
|
|
|
|
l.ClearTags();
|
|
|
|
l.elem[bp].tag = 1;
|
|
|
|
l.RemoveTagged();
|
|
|
|
}
|
|
|
|
|
|
|
|
void SContour::UvTriangulateInto(SMesh *m) {
|
|
|
|
int i;
|
2009-01-22 10:02:46 +00:00
|
|
|
// First, calculate the ear-ness of all the points
|
2009-01-21 05:04:38 +00:00
|
|
|
for(i = 0; i < l.n; i++) {
|
|
|
|
(l.elem[i]).ear = IsEar(i) ? SPoint::EAR : SPoint::NOT_EAR;
|
|
|
|
}
|
|
|
|
|
2009-01-22 10:02:46 +00:00
|
|
|
bool toggle = false;
|
2009-01-21 05:04:38 +00:00
|
|
|
while(l.n > 3) {
|
2009-01-22 10:02:46 +00:00
|
|
|
// Some points may have changed ear-ness, so recalculate
|
2009-01-21 05:04:38 +00:00
|
|
|
for(i = 0; i < l.n; i++) {
|
|
|
|
if(l.elem[i].ear == SPoint::UNKNOWN) {
|
|
|
|
(l.elem[i]).ear = IsEar(i) ? SPoint::EAR : SPoint::NOT_EAR;
|
|
|
|
}
|
|
|
|
}
|
2009-01-22 10:02:46 +00:00
|
|
|
|
|
|
|
// And find a candidate ear; alternate the starting position so
|
|
|
|
// we generate strip-like triangulations instead of fan-like
|
|
|
|
int ear = -1;
|
|
|
|
toggle = !toggle;
|
|
|
|
int offset = toggle ? -1 : 0;
|
2009-01-21 05:04:38 +00:00
|
|
|
for(i = 0; i < l.n; i++) {
|
2009-01-22 10:02:46 +00:00
|
|
|
ear = WRAP(i+offset, l.n);
|
|
|
|
if(l.elem[ear].ear == SPoint::EAR) {
|
2009-01-21 05:04:38 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2009-01-22 10:02:46 +00:00
|
|
|
if(ear < 0) {
|
2009-01-21 05:04:38 +00:00
|
|
|
dbp("couldn't find an ear! fail");
|
|
|
|
return;
|
|
|
|
}
|
2009-01-22 10:02:46 +00:00
|
|
|
ClipEarInto(m, ear);
|
2009-01-21 05:04:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
ClipEarInto(m, 0); // add the last triangle
|
|
|
|
}
|
2009-01-19 10:37:10 +00:00
|
|
|
|