A very rough set of routines to triangulate by ear clipping. This

is O(n^2), not perfectly robust, and the bridge-finding code is
particularly bad. But it works, triangulates, and shouldn't ever
generate zero-area triangles like gl does.

[git-p4: depot-paths = "//depot/solvespace/": change = 1900]
solver
Jonathan Westhues 2009-01-20 21:04:38 -08:00
parent ebca6130ec
commit 7cf3a06274
7 changed files with 347 additions and 9 deletions

5
dsc.h
View File

@ -47,7 +47,8 @@ public:
Vector n2, double d2); Vector n2, double d2);
static Vector AtIntersectionOfLines(Vector a0, Vector a1, static Vector AtIntersectionOfLines(Vector a0, Vector a1,
Vector b0, Vector b1, Vector b0, Vector b1,
bool *skew); bool *skew,
double *pa=NULL, double *pb=NULL);
double Element(int i); double Element(int i);
bool Equals(Vector v, double tol=LENGTH_EPS); bool Equals(Vector v, double tol=LENGTH_EPS);
@ -73,6 +74,8 @@ public:
Vector ProjectVectorInto(hEntity wrkpl); Vector ProjectVectorInto(hEntity wrkpl);
double DivPivoting(Vector delta); double DivPivoting(Vector delta);
Vector ClosestOrtho(void); Vector ClosestOrtho(void);
void MakeMaxMin(Vector *maxv, Vector *minv);
bool OutsideAndNotOn(Vector maxv, Vector minv);
Point2d Project2d(Vector u, Vector v); Point2d Project2d(Vector u, Vector v);
}; };

View File

@ -181,7 +181,7 @@ void glxFillMesh(int specColor, SMesh *m, DWORD h, DWORD s1, DWORD s2)
glBegin(GL_TRIANGLES); glBegin(GL_TRIANGLES);
} }
if(tr->an.EqualsExactly(Vector::From(0, 0, 0))) { if(1 || tr->an.EqualsExactly(Vector::From(0, 0, 0))) {
// Compute the normal from the vertices // Compute the normal from the vertices
Vector n = tr->Normal(); Vector n = tr->Normal();
glNormal3d(n.x, n.y, n.z); glNormal3d(n.x, n.y, n.z);

View File

@ -136,6 +136,66 @@ bool SEdgeList::AssemblePolygon(SPolygon *dest, SEdge *errorAt) {
} }
} }
//-----------------------------------------------------------------------------
// Test if the specified edge crosses any of the edges in our list. Two edges
// are not considered to cross if they share an endpoint (within LENGTH_EPS),
// but they are considered to cross if they are coincident and overlapping.
//-----------------------------------------------------------------------------
bool SEdgeList::AnyEdgeCrosses(Vector a, Vector b) {
Vector d = b.Minus(a);
double t_eps = LENGTH_EPS/d.Magnitude();
SEdge *se;
for(se = l.First(); se; se = l.NextAfter(se)) {
Vector dse = (se->b).Minus(se->a);
double tse_eps = LENGTH_EPS/dse.Magnitude();
if(a.Equals(se->a) && b.Equals(se->b)) return true;
if(b.Equals(se->a) && a.Equals(se->b)) return true;
double dist_a = (se->a).DistanceToLine(a, d),
dist_b = (se->b).DistanceToLine(a, d);
if(fabs(dist_a - dist_b) < LENGTH_EPS) {
// The edges are parallel.
if(fabs(dist_a) > LENGTH_EPS) {
// and not coincident, so can't be interesecting
continue;
}
// The edges are coincident. Make sure that neither endpoint lies
// on the other
double t;
t = ((se->a).Minus(a)).DivPivoting(d);
if(t > t_eps && t < (1 - t_eps)) return true;
t = ((se->b).Minus(a)).DivPivoting(d);
if(t > t_eps && t < (1 - t_eps)) return true;
t = a.Minus(se->a).DivPivoting(dse);
if(t > tse_eps && t < (1 - tse_eps)) return true;
t = b.Minus(se->a).DivPivoting(dse);
if(t > tse_eps && t < (1 - tse_eps)) return true;
// So coincident but disjoint, okay.
continue;
}
// Lines are not parallel, so look for an intersection.
double t, tse;
bool skew;
Vector pi = Vector::AtIntersectionOfLines(a, b,
se->a, se->b,
&skew,
&t, &tse);
if(skew) continue;
if(t > t_eps && t < (1 - t_eps) &&
tse > tse_eps && tse < (1 - tse_eps))
{
return true;
}
}
return false;
}
void SContour::AddPoint(Vector p) { void SContour::AddPoint(Vector p) {
SPoint sp; SPoint sp;
sp.tag = 0; sp.tag = 0;
@ -155,6 +215,13 @@ void SContour::MakeEdgesInto(SEdgeList *el) {
} }
} }
void SContour::CopyInto(SContour *dest) {
SPoint *sp;
for(sp = l.First(); sp; sp = l.NextAfter(sp)) {
dest->AddPoint(sp->p);
}
}
Vector SContour::ComputeNormal(void) { Vector SContour::ComputeNormal(void) {
Vector n = Vector::From(0, 0, 0); Vector n = Vector::From(0, 0, 0);
@ -286,12 +353,14 @@ void SPolygon::FixContourDirections(void) {
if(sc->l.n < 1) continue; if(sc->l.n < 1) continue;
Vector pt = (sc->l.elem[0]).p; Vector pt = (sc->l.elem[0]).p;
sc->timesEnclosed = 0;
bool outer = true; bool outer = true;
for(j = 0; j < l.n; j++) { for(j = 0; j < l.n; j++) {
if(i == j) continue; if(i == j) continue;
SContour *sct = &(l.elem[j]); SContour *sct = &(l.elem[j]);
if(sct->ContainsPointProjdToNormal(normal, pt)) { if(sct->ContainsPointProjdToNormal(normal, pt)) {
outer = !outer; outer = !outer;
(sc->timesEnclosed)++;
} }
} }

View File

@ -24,17 +24,25 @@ public:
bool AssemblePolygon(SPolygon *dest, SEdge *errorAt); bool AssemblePolygon(SPolygon *dest, SEdge *errorAt);
bool AssembleContour(Vector first, Vector last, SContour *dest, bool AssembleContour(Vector first, Vector last, SContour *dest,
SEdge *errorAt); SEdge *errorAt);
bool AnyEdgeCrosses(Vector a, Vector b);
}; };
class SPoint { class SPoint {
public: public:
int tag; int tag;
static const int UNKNOWN = 0;
static const int NOT_EAR = 1;
static const int EAR = 2;
int ear;
Vector p; Vector p;
}; };
class SContour { class SContour {
public: public:
int tag; int tag;
int timesEnclosed;
List<SPoint> l; List<SPoint> l;
void AddPoint(Vector p); void AddPoint(Vector p);
@ -45,6 +53,12 @@ public:
bool ContainsPointProjdToNormal(Vector n, Vector p); bool ContainsPointProjdToNormal(Vector n, Vector p);
bool AllPointsInPlane(Vector n, double d, Vector *notCoplanarAt); bool AllPointsInPlane(Vector n, double d, Vector *notCoplanarAt);
void OffsetInto(SContour *dest, double r); void OffsetInto(SContour *dest, double r);
void CopyInto(SContour *dest);
bool IsEar(int bp);
bool BridgeToContour(SContour *sc, SEdgeList *el, List<Vector> *vl);
void ClipEarInto(SMesh *m, int bp);
void UvTriangulateInto(SMesh *m);
}; };
typedef struct { typedef struct {
@ -70,6 +84,7 @@ public:
bool IsEmpty(void); bool IsEmpty(void);
Vector AnyPoint(void); Vector AnyPoint(void);
void OffsetInto(SPolygon *dest, double r); void OffsetInto(SPolygon *dest, double r);
void UvTriangulateInto(SMesh *m);
}; };
class STriangle { class STriangle {

View File

@ -482,11 +482,12 @@ void SSurface::TriangulateInto(SShell *shell, SMesh *sm) {
} }
int i, start = sm->l.n; int i, start = sm->l.n;
poly.UvTriangulateInto(sm);
STriMeta meta = { 0, 0x888888 }; STriMeta meta = { 0, 0x888888 };
poly.normal = Vector::From(0, 0, 1);
poly.TriangulateInto(sm, meta);
for(i = start; i < sm->l.n; i++) { for(i = start; i < sm->l.n; i++) {
STriangle *st = &(sm->l.elem[i]); STriangle *st = &(sm->l.elem[i]);
st->meta = meta;
st->an = NormalAt(st->a.x, st->a.y); st->an = NormalAt(st->a.x, st->a.y);
st->bn = NormalAt(st->b.x, st->b.y); st->bn = NormalAt(st->b.x, st->b.y);
st->cn = NormalAt(st->c.x, st->c.y); st->cn = NormalAt(st->c.x, st->c.y);
@ -562,8 +563,8 @@ SShell SShell::FromExtrusionOf(SBezierLoopSet *sbls, Vector t0, Vector t1) {
(ret.surface.FindById(hs1))->trim.Add(&stb1); (ret.surface.FindById(hs1))->trim.Add(&stb1);
// The translated curves also trim the surface of extrusion. // The translated curves also trim the surface of extrusion.
(ret.surface.FindById(hsext))->trim.Add(&stb0); // (ret.surface.FindById(hsext))->trim.Add(&stb0);
(ret.surface.FindById(hsext))->trim.Add(&stb1); // (ret.surface.FindById(hsext))->trim.Add(&stb1);
// And form the trim line // And form the trim line
Vector pt = sb->Finish(); Vector pt = sb->Finish();
@ -586,8 +587,8 @@ SShell SShell::FromExtrusionOf(SBezierLoopSet *sbls, Vector t0, Vector t1) {
TrimLine *tlp = &(trimLines.elem[WRAP(i-1, trimLines.n)]); TrimLine *tlp = &(trimLines.elem[WRAP(i-1, trimLines.n)]);
ss->trim.Add(&(tl->trim)); // ss->trim.Add(&(tl->trim));
ss->trim.Add(&(tlp->trim)); // ss->trim.Add(&(tlp->trim));
} }
trimLines.Clear(); trimLines.Clear();
} }

View File

@ -1,3 +1,234 @@
#include "../solvespace.h" #include "../solvespace.h"
void SPolygon::UvTriangulateInto(SMesh *m) {
if(l.n <= 0) return;
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);
}
}
bool holesExist, mergedHole;
do {
holesExist = false;
mergedHole = false;
for(sc = l.First(); sc; sc = l.NextAfter(sc)) {
if(sc->tag != 2) continue;
holesExist = true;
if(merged.BridgeToContour(sc, &el, &vl)) {
// Merged it in, so we're done with it.
sc->tag = 3;
mergedHole = true;
} else {
// Looks like we can't merge it yet, but that can happen;
// we may have to merge the holes in a specific order.
}
}
if(holesExist && !mergedHole) {
dbp("holes exist, yet couldn't merge one");
return;
}
} while(holesExist);
merged.UvTriangulateInto(m);
merged.l.Clear();
el.Clear();
vl.Clear();
l.RemoveTagged();
}
}
bool SContour::BridgeToContour(SContour *sc,
SEdgeList *avoidEdges, List<Vector> *avoidPts)
{
int thisp, scp;
Vector a, b, *f;
for(thisp = 0; thisp < l.n; thisp++) {
a = l.elem[thisp].p;
for(f = avoidPts->First(); f; f = avoidPts->NextAfter(f)) {
if(f->Equals(a)) break;
}
if(f) continue;
for(scp = 0; scp < (sc->l.n - 1); scp++) {
b = sc->l.elem[scp].p;
for(f = avoidPts->First(); f; f = avoidPts->NextAfter(f)) {
if(f->Equals(b)) break;
}
if(f) continue;
if(avoidEdges->AnyEdgeCrosses(a, b)) {
// 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);
int i, j;
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);
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;
for(i = 0; i < l.n; i++) {
(l.elem[i]).ear = IsEar(i) ? SPoint::EAR : SPoint::NOT_EAR;
}
while(l.n > 3) {
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;
}
}
for(i = 0; i < l.n; i++) {
if(l.elem[i].ear == SPoint::EAR) {
break;
}
}
if(i >= l.n) {
dbp("couldn't find an ear! fail");
return;
}
ClipEarInto(m, i);
}
ClipEarInto(m, 0); // add the last triangle
}

View File

@ -539,6 +539,22 @@ Vector Vector::ClosestOrtho(void) {
} }
} }
void Vector::MakeMaxMin(Vector *maxv, Vector *minv) {
maxv->x = max(maxv->x, x);
maxv->y = max(maxv->y, y);
maxv->z = max(maxv->z, z);
minv->x = min(minv->x, x);
minv->y = min(minv->y, y);
minv->z = min(minv->z, z);
}
bool Vector::OutsideAndNotOn(Vector maxv, Vector minv) {
return (x > maxv.x + LENGTH_EPS) || (x < minv.x - LENGTH_EPS) ||
(y > maxv.y + LENGTH_EPS) || (y < minv.y - LENGTH_EPS) ||
(z > maxv.z + LENGTH_EPS) || (z < minv.z - LENGTH_EPS);
}
Vector Vector::AtIntersectionOfPlanes(Vector n1, double d1, Vector Vector::AtIntersectionOfPlanes(Vector n1, double d1,
Vector n2, double d2) Vector n2, double d2)
{ {
@ -552,7 +568,8 @@ Vector Vector::AtIntersectionOfPlanes(Vector n1, double d1,
Vector Vector::AtIntersectionOfLines(Vector a0, Vector a1, Vector Vector::AtIntersectionOfLines(Vector a0, Vector a1,
Vector b0, Vector b1, Vector b0, Vector b1,
bool *skew) bool *skew,
double *parama, double *paramb)
{ {
Vector da = a1.Minus(a0), db = b1.Minus(b0); Vector da = a1.Minus(a0), db = b1.Minus(b0);
@ -567,6 +584,8 @@ Vector Vector::AtIntersectionOfLines(Vector a0, Vector a1,
// to solve for da and db // to solve for da and db
double pb = ((a0.Minus(b0)).Dot(dna))/(db.Dot(dna)); double pb = ((a0.Minus(b0)).Dot(dna))/(db.Dot(dna));
double pa = -((a0.Minus(b0)).Dot(dnb))/(da.Dot(dnb)); double pa = -((a0.Minus(b0)).Dot(dnb))/(da.Dot(dnb));
if(parama) *parama = pa;
if(paramb) *paramb = pb;
// And from either of those, we get the intersection point. // And from either of those, we get the intersection point.
Vector pi = a0.Plus(da.ScaledBy(pa)); Vector pi = a0.Plus(da.ScaledBy(pa));