#include "solvespace.h" bool SEdgeList::AssemblePolygon(SPolygon *dest, SEdge *errorAt) { dest->Clear(); l.ClearTags(); for(;;) { Vector first, last; int i; for(i = 0; i < l.n; i++) { if(!l.elem[i].tag) { first = l.elem[i].a; last = l.elem[i].b; l.elem[i].tag = 1; break; } } if(i >= l.n) { return true; } dest->AddEmptyContour(); dest->AddPoint(first); dest->AddPoint(last); do { for(i = 0; i < l.n; i++) { SEdge *se = &(l.elem[i]); if(se->tag) continue; if(se->a.Equals(last)) { dest->AddPoint(se->b); last = se->b; se->tag = 1; break; } if(se->b.Equals(last)) { dest->AddPoint(se->a); last = se->a; se->tag = 1; break; } } if(i >= l.n) { // Couldn't assemble a closed contour; mark where. errorAt->a = first; errorAt->b = last; return false; } } while(!last.Equals(first)); } } void SPolygon::Clear(void) { int i; for(i = 0; i < l.n; i++) { (l.elem[i]).l.Clear(); } l.Clear(); } void SPolygon::AddEmptyContour(void) { SContour c; memset(&c, 0, sizeof(c)); l.Add(&c); } void SPolygon::AddPoint(Vector p) { if(l.n < 1) oops(); SPoint sp; sp.tag = 0; sp.p = p; // Add to the last contour in the list (l.elem[l.n-1]).l.Add(&sp); } void SPolygon::MakeEdgesInto(SEdgeList *el) { int i; for(i = 0; i < l.n; i++) { (l.elem[i]).MakeEdgesInto(el); } } Vector SPolygon::ComputeNormal(void) { if(l.n < 1) return Vector::MakeFrom(0, 0, 0); return (l.elem[0]).ComputeNormal(); } void SPolygon::FixContourDirections(void) { // Outside curve looks counterclockwise, projected against our normal. int i, j; for(i = 0; i < l.n; i++) { SContour *sc = &(l.elem[i]); if(sc->l.n < 1) continue; Vector pt = (sc->l.elem[0]).p; bool outer = true; for(j = 0; j < l.n; j++) { if(i == j) continue; SContour *sct = &(l.elem[j]); if(sct->ContainsPointProjdToNormal(normal, pt)) { outer = !outer; } } bool clockwise = sc->IsClockwiseProjdToNormal(normal); if(clockwise && outer || (!clockwise && !outer)) { sc->Reverse(); } } } void SContour::MakeEdgesInto(SEdgeList *el) { int i; for(i = 0; i < (l.n-1); i++) { SEdge e; e.a = l.elem[i].p; e.b = l.elem[i+1].p; el->l.Add(&e); } } Vector SContour::ComputeNormal(void) { Vector n = Vector::MakeFrom(0, 0, 0); for(int i = 0; i < l.n - 2; i++) { Vector u = (l.elem[i+1].p).Minus(l.elem[i+0].p).WithMagnitude(1); Vector v = (l.elem[i+2].p).Minus(l.elem[i+1].p).WithMagnitude(1); Vector nt = u.Cross(v); if(nt.Magnitude() > n.Magnitude()) { n = nt; } } return n; } bool SContour::IsClockwiseProjdToNormal(Vector n) { if(n.Magnitude() < 0.01) oops(); // An arbitrary 2d coordinate system that has n as its normal Vector u = n.Normal(0); Vector v = n.Normal(1); double area = 0; for(int i = 0; i < (l.n - 1); i++) { double u0 = (l.elem[i ].p).Dot(u); double v0 = (l.elem[i ].p).Dot(v); double u1 = (l.elem[i+1].p).Dot(u); double v1 = (l.elem[i+1].p).Dot(v); area += ((v0 + v1)/2)*(u1 - u0); } return (area < 0); } bool SContour::ContainsPointProjdToNormal(Vector n, Vector p) { Vector u = n.Normal(0); Vector v = n.Normal(1); double up = p.Dot(u); double vp = p.Dot(v); bool inside = false; for(int i = 0; i < (l.n - 1); i++) { double ua = (l.elem[i ].p).Dot(u); double va = (l.elem[i ].p).Dot(v); double ub = (l.elem[i+1].p).Dot(u); double vb = (l.elem[i+1].p).Dot(v); // Write the parametric equation of the line, standardized so that // t = 0 has smaller v than t = 1 double u0, v0, du, dv; if(va < vb) { u0 = ua; v0 = va; du = (ub - ua); dv = (vb - va); } else { u0 = ub; v0 = vb; du = (ua - ub); dv = (va - vb); } if(dv == 0) continue; // intersects our horiz ray either 0 or 2 times double t = (vp - v0)/dv; double ui = u0 + t*du; if(ui > up && t >= 0 && t < 1) inside = !inside; } return inside; } void SContour::Reverse(void) { int i; for(i = 0; i < (l.n / 2); i++) { int i2 = (l.n - 1) - i; SPoint t = l.elem[i2]; l.elem[i2] = l.elem[i]; l.elem[i] = t; } }