diff --git a/Makefile b/Makefile index 3df3ed45..09d21fb4 100644 --- a/Makefile +++ b/Makefile @@ -41,6 +41,7 @@ SSOBJS = $(OBJDIR)\solvespace.obj \ SRFOBJS = $(OBJDIR)\ratpoly.obj \ $(OBJDIR)\triangulate.obj \ + $(OBJDIR)\boolean.obj \ RES = $(OBJDIR)\resource.res diff --git a/export.cpp b/export.cpp index adb32076..4b12c1e4 100644 --- a/export.cpp +++ b/export.cpp @@ -86,7 +86,6 @@ void SolveSpace::ExportSectionTo(char *filename) { // Select the naked edges in our resulting open mesh. SKdNode *root = SKdNode::From(&m); - root->SnapToMesh(&m); SEdgeList el; ZERO(&el); root->MakeCertainEdgesInto(&el, false); @@ -294,16 +293,10 @@ void SolveSpace::ExportMeshTo(char *filename) { Error("Active group mesh is empty; nothing to export."); return; } - SKdNode *root = SKdNode::From(m); - root->SnapToMesh(m); - SMesh vvm; - ZERO(&vvm); - root->MakeMeshInto(&vvm); FILE *f = fopen(filename, "wb"); if(!f) { Error("Couldn't write to '%s'", filename); - vvm.Clear(); return; } char str[80]; @@ -311,13 +304,13 @@ void SolveSpace::ExportMeshTo(char *filename) { strcpy(str, "STL exported mesh"); fwrite(str, 1, 80, f); - DWORD n = vvm.l.n; + DWORD n = m->l.n; fwrite(&n, 4, 1, f); double s = SS.exportScale; int i; - for(i = 0; i < vvm.l.n; i++) { - STriangle *tr = &(vvm.l.elem[i]); + for(i = 0; i < m->l.n; i++) { + STriangle *tr = &(m->l.elem[i]); Vector n = tr->Normal().WithMagnitude(1); float w; w = (float)n.x; fwrite(&w, 4, 1, f); @@ -336,7 +329,6 @@ void SolveSpace::ExportMeshTo(char *filename) { fputc(0, f); } - vvm.Clear(); fclose(f); } diff --git a/generate.cpp b/generate.cpp index fedb847a..9984af5a 100644 --- a/generate.cpp +++ b/generate.cpp @@ -208,22 +208,11 @@ void SolveSpace::GenerateAll(int first, int last, bool andFindFree) { g->clean = true; } else { if(i >= first && i <= last) { - // See if we have to do the vertex-to-vertex mesh, that - // we used for emphasized edges. - if(first == i && - (g->type == Group::DRAWING_3D || - g->type == Group::DRAWING_WORKPLANE)) - { - // Special case--if the first dirty group doesn't change - // the mesh, then no need to regen edges for it. - } else { - g->vvMeshClean = false; // so we'll regen it - } // The group falls inside the range, so really solve it, // and then regenerate the mesh based on the solved stuff. SolveGroup(g->h, andFindFree); g->GenerateLoops(); - g->GenerateMesh(); + g->GenerateShellAndMesh(); g->clean = true; } else { // The group falls outside the range, so just assume that diff --git a/groupmesh.cpp b/groupmesh.cpp index 12698b37..8d7f442a 100644 --- a/groupmesh.cpp +++ b/groupmesh.cpp @@ -32,12 +32,17 @@ void Group::GenerateLoops(void) { if(AssembleLoops()) { polyError.how = POLY_GOOD; - if(!poly.AllPointsInPlane(&(polyError.notCoplanarAt))) { + if(!poly.AllPointsInPlane(&(polyError.errorPointAt))) { // The edges aren't all coplanar; so not a good polygon polyError.how = POLY_NOT_COPLANAR; poly.Clear(); bezierLoopSet.Clear(); } + if(poly.SelfIntersecting(&(polyError.errorPointAt))) { + polyError.how = POLY_SELF_INTERSECTING; + poly.Clear(); + bezierLoopSet.Clear(); + } } else { polyError.how = POLY_NOT_CLOSED; poly.Clear(); @@ -46,40 +51,9 @@ void Group::GenerateLoops(void) { } } -void Group::AddQuadWithNormal(STriMeta meta, Vector out, - Vector a, Vector b, Vector c, Vector d) -{ - // The quad becomes two triangles - STriangle quad1 = STriangle::From(meta, a, b, c), - quad2 = STriangle::From(meta, c, d, a); - - // Could be only one of the triangles has area; be sure - // to use that one for normal checking, then. - Vector n1 = quad1.Normal(), n2 = quad2.Normal(); - Vector n = (n1.Magnitude() > n2.Magnitude()) ? n1 : n2; - if(n.Dot(out) < 0) { - quad1.FlipNormal(); - quad2.FlipNormal(); - } - // One or both of the endpoints might lie on the axis of - // rotation, in which case its triangle is zero-area. - if(n1.Magnitude() > LENGTH_EPS) thisMesh.AddTriangle(&quad1); - if(n2.Magnitude() > LENGTH_EPS) thisMesh.AddTriangle(&quad2); -} - -void Group::GenerateMeshForStepAndRepeat(void) { +void Group::GenerateShellForStepAndRepeat(void) { Group *src = SS.GetGroup(opA); - SMesh *srcm = &(src->thisMesh); // the mesh to step and repeat - - if(srcm->l.n == 0) { - runningMesh.Clear(); - runningMesh.MakeFromCopy(PreviousGroupMesh()); - return; - } - - SMesh origm; - ZERO(&origm); - origm.MakeFromCopy(src->PreviousGroupMesh()); + SShell *srcs = &(src->thisShell); // the shell to step and repeat int n = (int)valA, a0 = 0; if(subtype == ONE_SIDED && skipFirst) { @@ -90,21 +64,10 @@ void Group::GenerateMeshForStepAndRepeat(void) { int ap = a*2 - (subtype == ONE_SIDED ? 0 : (n-1)); int remap = (a == (n - 1)) ? REMAP_LAST : a; - thisMesh.Clear(); if(type == TRANSLATE) { Vector trans = Vector::From(h.param(0), h.param(1), h.param(2)); trans = trans.ScaledBy(ap); - for(int i = 0; i < srcm->l.n; i++) { - STriangle tr = srcm->l.elem[i]; - tr.a = (tr.a).Plus(trans); - tr.b = (tr.b).Plus(trans); - tr.c = (tr.c).Plus(trans); - if(tr.meta.face != 0) { - hEntity he = { tr.meta.face }; - tr.meta.face = Remap(he, remap).v; - } - thisMesh.AddTriangle(&tr); - } + } else { Vector trans = Vector::From(h.param(0), h.param(1), h.param(2)); double theta = ap * SS.GetParam(h.param(3))->val; @@ -112,46 +75,26 @@ void Group::GenerateMeshForStepAndRepeat(void) { Vector axis = Vector::From(h.param(4), h.param(5), h.param(6)); Quaternion q = Quaternion::From(c, s*axis.x, s*axis.y, s*axis.z); - for(int i = 0; i < srcm->l.n; i++) { - STriangle tr = srcm->l.elem[i]; - tr.a = (q.Rotate((tr.a).Minus(trans))).Plus(trans); - tr.b = (q.Rotate((tr.b).Minus(trans))).Plus(trans); - tr.c = (q.Rotate((tr.c).Minus(trans))).Plus(trans); - if(tr.meta.face != 0) { - hEntity he = { tr.meta.face }; - tr.meta.face = Remap(he, remap).v; - } - thisMesh.AddTriangle(&tr); - } } - runningMesh.Clear(); if(src->meshCombine == COMBINE_AS_DIFFERENCE) { - runningMesh.MakeFromDifference(&origm, &thisMesh); + } else { - runningMesh.MakeFromUnion(&origm, &thisMesh); + } - origm.Clear(); - origm.MakeFromCopy(&runningMesh); } - origm.Clear(); - thisMesh.Clear(); } -void Group::GenerateMesh(void) { - thisMesh.Clear(); +void Group::GenerateShellAndMesh(void) { thisShell.Clear(); STriMeta meta = { 0, color }; if(type == TRANSLATE || type == ROTATE) { - GenerateMeshForStepAndRepeat(); + GenerateShellForStepAndRepeat(); goto done; } if(type == EXTRUDE) { - SEdgeList edges; - ZERO(&edges); - int i; Group *src = SS.GetGroup(opA); Vector translate = Vector::From(h.param(0), h.param(1), h.param(2)); @@ -162,112 +105,14 @@ void Group::GenerateMesh(void) { tbot = translate.ScaledBy(-1); ttop = translate.ScaledBy(1); } - thisShell = SShell::FromExtrusionOf(&(src->bezierLoopSet), tbot, ttop); - thisShell.TriangulateInto(&thisMesh); -/* - bool flipBottom = translate.Dot(src->poly.normal) > 0; - - // Get a triangulation of the source poly; this is not a closed mesh. - SMesh srcm; ZERO(&srcm); - (src->poly).TriangulateInto(&srcm); - - // Do the bottom; that has normal pointing opposite from translate - meta.face = Remap(Entity::NO_ENTITY, REMAP_BOTTOM).v; - for(i = 0; i < srcm.l.n; i++) { - STriangle *st = &(srcm.l.elem[i]); - Vector at = (st->a).Plus(tbot), - bt = (st->b).Plus(tbot), - ct = (st->c).Plus(tbot); - if(flipBottom) { - thisMesh.AddTriangle(meta, ct, bt, at); - } else { - thisMesh.AddTriangle(meta, at, bt, ct); - } - } - // And the top; that has the normal pointing the same dir as translate - meta.face = Remap(Entity::NO_ENTITY, REMAP_TOP).v; - for(i = 0; i < srcm.l.n; i++) { - STriangle *st = &(srcm.l.elem[i]); - Vector at = (st->a).Plus(ttop), - bt = (st->b).Plus(ttop), - ct = (st->c).Plus(ttop); - if(flipBottom) { - thisMesh.AddTriangle(meta, at, bt, ct); - } else { - thisMesh.AddTriangle(meta, ct, bt, at); - } - } - srcm.Clear(); - // Get the source polygon to extrude, and break it down to edges - edges.Clear(); - (src->poly).MakeEdgesInto(&edges); - - edges.l.ClearTags(); - TagEdgesFromLineSegments(&edges); - // The sides; these are quads, represented as two triangles. - for(i = 0; i < edges.l.n; i++) { - SEdge *edge = &(edges.l.elem[i]); - Vector abot = (edge->a).Plus(tbot), bbot = (edge->b).Plus(tbot); - Vector atop = (edge->a).Plus(ttop), btop = (edge->b).Plus(ttop); - // We tagged the edges that came from line segments; their - // triangles should be associated with that plane face. - if(edge->tag) { - hEntity hl = { edge->tag }; - hEntity hf = Remap(hl, REMAP_LINE_TO_FACE); - meta.face = hf.v; - } else { - meta.face = 0; - } - if(flipBottom) { - thisMesh.AddTriangle(meta, bbot, abot, atop); - thisMesh.AddTriangle(meta, bbot, atop, btop); - } else { - thisMesh.AddTriangle(meta, abot, bbot, atop); - thisMesh.AddTriangle(meta, bbot, btop, atop); - } - } - edges.Clear(); */ + thisShell.MakeFromExtrusionOf(&(src->bezierLoopSet), tbot, ttop); } else if(type == LATHE) { - SEdgeList edges; - ZERO(&edges); - int a, i; - Group *src = SS.GetGroup(opA); - (src->poly).MakeEdgesInto(&edges); Vector orig = SS.GetEntity(predef.origin)->PointGetNum(); Vector axis = SS.GetEntity(predef.entityB)->VectorGetNum(); axis = axis.WithMagnitude(1); - // Calculate the max radius, to determine fineness of mesh - double r, rmax = 0; - for(i = 0; i < edges.l.n; i++) { - SEdge *edge = &(edges.l.elem[i]); - r = (edge->a).DistanceToLine(orig, axis); - rmax = max(r, rmax); - r = (edge->b).DistanceToLine(orig, axis); - rmax = max(r, rmax); - } - - int n = SS.CircleSides(rmax); - for(a = 0; a < n; a++) { - double thetai = (2*PI*WRAP(a-1, n))/n, thetaf = (2*PI*a)/n; - for(i = 0; i < edges.l.n; i++) { - SEdge *edge = &(edges.l.elem[i]); - - Vector ai = (edge->a).RotatedAbout(orig, axis, thetai); - Vector bi = (edge->b).RotatedAbout(orig, axis, thetai); - Vector af = (edge->a).RotatedAbout(orig, axis, thetaf); - Vector bf = (edge->b).RotatedAbout(orig, axis, thetaf); - - Vector ab = (edge->b).Minus(edge->a); - Vector out = ((src->poly).normal).Cross(ab); - // This is a vector, not a point, so no origin for rotation - out = out.RotatedAbout(axis, thetai); - - AddQuadWithNormal(meta, out, ai, bi, bf, af); - } - } } else if(type == IMPORTED) { // Triangles are just copied over, with the appropriate transformation // applied. @@ -291,7 +136,6 @@ void Group::GenerateMesh(void) { st.a = q.Rotate(st.a).Plus(offset); st.b = q.Rotate(st.b).Plus(offset); st.c = q.Rotate(st.c).Plus(offset); - thisMesh.AddTriangle(&st); } } @@ -301,26 +145,25 @@ void Group::GenerateMesh(void) { // If this group contributes no new mesh, then our running mesh is the // same as last time, no combining required. Likewise if we have a mesh // but it's suppressed. - if(thisMesh.l.n == 0 || suppress) { - runningMesh.MakeFromCopy(PreviousGroupMesh()); + if(suppress) { + runningShell.MakeFromCopyOf(PreviousGroupShell()); goto done; } // So our group's mesh appears in thisMesh. Combine this with the previous // group's mesh, using the requested operation. bool prevMeshError = meshError.yes; + meshError.yes = false; meshError.interferesAt.Clear(); - SMesh *a = PreviousGroupMesh(); + + SShell *a = PreviousGroupShell(); if(meshCombine == COMBINE_AS_UNION) { - runningMesh.MakeFromUnion(a, &thisMesh); - runningMesh = thisMesh; - ZERO(&thisMesh); + runningShell.MakeFromUnionOf(a, &thisShell); } else if(meshCombine == COMBINE_AS_DIFFERENCE) { - runningMesh.MakeFromDifference(a, &thisMesh); + runningShell.MakeFromDifferenceOf(a, &thisShell); } else { - if(!runningMesh.MakeFromInterferenceCheck(a, &thisMesh, - &(meshError.interferesAt))) + if(0) //&(meshError.interferesAt) { meshError.yes = true; // And the list of failed triangles goes in meshError.interferesAt @@ -332,25 +175,21 @@ void Group::GenerateMesh(void) { } done: - if(!vvMeshClean) { - emphEdges.Clear(); - if(h.v == SS.GW.activeGroup.v && SS.edgeColor != 0) { - SKdNode *root = SKdNode::From(&runningMesh); - root->SnapToMesh(&runningMesh); - root->MakeCertainEdgesInto(&emphEdges, true); - } - vvMeshClean = true; + runningShell.TriangulateInto(&runningMesh); + emphEdges.Clear(); + if(h.v == SS.GW.activeGroup.v && SS.edgeColor != 0) { + thisShell.MakeEdgesInto(&emphEdges); } } -SMesh *Group::PreviousGroupMesh(void) { +SShell *Group::PreviousGroupShell(void) { int i; for(i = 0; i < SS.group.n; i++) { Group *g = &(SS.group.elem[i]); if(g->h.v == h.v) break; } if(i == 0 || i >= SS.group.n) oops(); - return &(SS.group.elem[i-1].runningMesh); + return &(SS.group.elem[i-1].runningShell); } void Group::Draw(void) { @@ -428,15 +267,21 @@ void Group::Draw(void) { glPopMatrix(); glEnable(GL_DEPTH_TEST); } - } else if(polyError.how == POLY_NOT_COPLANAR) { - // And this one too + } else if(polyError.how == POLY_NOT_COPLANAR || + polyError.how == POLY_SELF_INTERSECTING) + { + // These errors occur at points, not lines if(type == DRAWING_WORKPLANE) { glDisable(GL_DEPTH_TEST); glxColor3d(1, 0, 0); glPushMatrix(); - glxTranslatev(polyError.notCoplanarAt); + glxTranslatev(polyError.errorPointAt); glxOntoWorkplane(SS.GW.projRight, SS.GW.projUp); - glxWriteText("points not all coplanar!"); + if(polyError.how == POLY_NOT_COPLANAR) { + glxWriteText("points not all coplanar!"); + } else { + glxWriteText("contour is self-intersecting!"); + } glPopMatrix(); glEnable(GL_DEPTH_TEST); } diff --git a/mesh.cpp b/mesh.cpp index c68ab2fe..e39bb8bc 100644 --- a/mesh.cpp +++ b/mesh.cpp @@ -476,95 +476,6 @@ void SKdNode::MakeMeshInto(SMesh *m) { } } -void SKdNode::SnapToVertex(Vector v, SMesh *extras) { - if(gt && lt) { - double vc = v.Element(which); - if(vc < c + KDTREE_EPS) { - lt->SnapToVertex(v, extras); - } - if(vc > c - KDTREE_EPS) { - gt->SnapToVertex(v, extras); - } - // Nothing bad happens if the triangle to be split appears in both - // branches; the first call will split the triangle, so that the - // second call will do nothing, because the modified triangle will - // already contain v - } else { - STriangleLl *ll; - for(ll = tris; ll; ll = ll->next) { - STriangle *tr = ll->tri; - - // Do a cheap bbox test first - int k; - bool mightHit = true; - - for(k = 0; k < 3; k++) { - if((tr->a).Element(k) < v.Element(k) - KDTREE_EPS && - (tr->b).Element(k) < v.Element(k) - KDTREE_EPS && - (tr->c).Element(k) < v.Element(k) - KDTREE_EPS) - { - mightHit = false; - break; - } - if((tr->a).Element(k) > v.Element(k) + KDTREE_EPS && - (tr->b).Element(k) > v.Element(k) + KDTREE_EPS && - (tr->c).Element(k) > v.Element(k) + KDTREE_EPS) - { - mightHit = false; - break; - } - } - if(!mightHit) continue; - - if(tr->a.Equals(v)) { tr->a = v; continue; } - if(tr->b.Equals(v)) { tr->b = v; continue; } - if(tr->c.Equals(v)) { tr->c = v; continue; } - - if(v.OnLineSegment(tr->a, tr->b)) { - STriangle nt = STriangle::From(tr->meta, tr->a, v, tr->c); - extras->AddTriangle(&nt); - tr->a = v; - continue; - } - if(v.OnLineSegment(tr->b, tr->c)) { - STriangle nt = STriangle::From(tr->meta, tr->b, v, tr->a); - extras->AddTriangle(&nt); - tr->b = v; - continue; - } - if(v.OnLineSegment(tr->c, tr->a)) { - STriangle nt = STriangle::From(tr->meta, tr->c, v, tr->b); - extras->AddTriangle(&nt); - tr->c = v; - continue; - } - } - } -} - -void SKdNode::SnapToMesh(SMesh *m) { - int i, j, k; - for(i = 0; i < m->l.n; i++) { - STriangle *tr = &(m->l.elem[i]); - for(j = 0; j < 3; j++) { - Vector v = ((j == 0) ? tr->a : - ((j == 1) ? tr->b : - tr->c)); - - SMesh extra; - ZERO(&extra); - SnapToVertex(v, &extra); - - for(k = 0; k < extra.l.n; k++) { - STriangle *tra = (STriangle *)AllocTemporary(sizeof(*tra)); - *tra = extra.l.elem[k]; - AddTriangle(tra); - } - extra.Clear(); - } - } -} - void SKdNode::FindEdgeOn(Vector a, Vector b, int *n, int *nOther, STriMeta m, int cnt) { diff --git a/polygon.cpp b/polygon.cpp index df7ad343..aefafaa0 100644 --- a/polygon.cpp +++ b/polygon.cpp @@ -140,22 +140,29 @@ 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. +// If pi is not NULL, then a crossing is returned in that. //----------------------------------------------------------------------------- -bool SEdgeList::AnyEdgeCrosses(Vector a, Vector b) { +int SEdgeList::AnyEdgeCrossings(Vector a, Vector b, Vector *ppi) { Vector d = b.Minus(a); double t_eps = LENGTH_EPS/d.Magnitude(); + int cnt = 0; SEdge *se; for(se = l.First(); se; se = l.NextAfter(se)) { + double dist_a, dist_b; + double t, tse; + bool skew; + Vector pi; + bool inOrEdge0, inOrEdge1; 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; + if(a.Equals(se->a) && b.Equals(se->b)) goto intersects; + if(b.Equals(se->a) && a.Equals(se->b)) goto intersects; - double dist_a = (se->a).DistanceToLine(a, d), - dist_b = (se->b).DistanceToLine(a, d); + 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. @@ -167,28 +174,25 @@ bool SEdgeList::AnyEdgeCrosses(Vector a, Vector b) { // on the other double t; t = ((se->a).Minus(a)).DivPivoting(d); - if(t > t_eps && t < (1 - t_eps)) return true; + if(t > t_eps && t < (1 - t_eps)) goto intersects; t = ((se->b).Minus(a)).DivPivoting(d); - if(t > t_eps && t < (1 - t_eps)) return true; + if(t > t_eps && t < (1 - t_eps)) goto intersects; t = a.Minus(se->a).DivPivoting(dse); - if(t > tse_eps && t < (1 - tse_eps)) return true; + if(t > tse_eps && t < (1 - tse_eps)) goto intersects; t = b.Minus(se->a).DivPivoting(dse); - if(t > tse_eps && t < (1 - tse_eps)) return true; + if(t > tse_eps && t < (1 - tse_eps)) goto intersects; // 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); + pi = Vector::AtIntersectionOfLines(a, b, se->a, se->b, + &skew, + &t, &tse); if(skew) continue; - bool inOrEdge0 = (t > -t_eps) && (t < (1 + t_eps)); - bool inOrEdge1 = (tse > -tse_eps) && (tse < (1 + tse_eps)); + inOrEdge0 = (t > -t_eps) && (t < (1 + t_eps)); + inOrEdge1 = (tse > -tse_eps) && (tse < (1 + tse_eps)); if(inOrEdge0 && inOrEdge1) { if((se->a).Equals(a) || (se->b).Equals(a) || @@ -200,10 +204,16 @@ bool SEdgeList::AnyEdgeCrosses(Vector a, Vector b) { // But it's an intersection if a vertex of one edge lies on the // inside of the other (or if they cross away from either's // vertex). - return true; + if(ppi) *ppi = pi; + goto intersects; } + continue; + +intersects: + cnt++; + // and continue with the loop } - return false; + return cnt; } void SContour::AddPoint(Vector p) { @@ -416,6 +426,19 @@ bool SPolygon::AllPointsInPlane(Vector *notCoplanarAt) { return true; } +bool SPolygon::SelfIntersecting(Vector *intersectsAt) { + SEdgeList el; + ZERO(&el); + MakeEdgesInto(&el); + + SEdge *se; + for(se = el.l.First(); se; se = el.l.NextAfter(se)) { + int inters = el.AnyEdgeCrossings(se->a, se->b, intersectsAt); + if(inters != 1) return true; + } + return false; +} + static int TriMode, TriVertexCount; static Vector Tri1, TriNMinus1, TriNMinus2; static Vector TriNormal; diff --git a/polygon.h b/polygon.h index 77170120..25e05642 100644 --- a/polygon.h +++ b/polygon.h @@ -24,7 +24,7 @@ public: bool AssemblePolygon(SPolygon *dest, SEdge *errorAt); bool AssembleContour(Vector first, Vector last, SContour *dest, SEdge *errorAt); - bool AnyEdgeCrosses(Vector a, Vector b); + int AnyEdgeCrossings(Vector a, Vector b, Vector *pi=NULL); }; class SPoint { @@ -83,6 +83,7 @@ public: void TriangulateInto(SMesh *m, STriMeta meta); void Clear(void); bool AllPointsInPlane(Vector *notCoplanarAt); + bool SelfIntersecting(Vector *intersectsAt); bool IsEmpty(void); Vector AnyPoint(void); void OffsetInto(SPolygon *dest, double r); @@ -220,9 +221,6 @@ public: void FindEdgeOn(Vector a, Vector b, int *n, int *nOther, STriMeta m, int cnt); void MakeCertainEdgesInto(SEdgeList *sel, bool emphasized); - - void SnapToMesh(SMesh *m); - void SnapToVertex(Vector v, SMesh *extras); }; #endif diff --git a/sketch.h b/sketch.h index 5af35bc3..f6d71ca3 100644 --- a/sketch.h +++ b/sketch.h @@ -138,24 +138,24 @@ public: SPolygon poly; SBezierLoopSet bezierLoopSet; - static const int POLY_GOOD = 0; - static const int POLY_NOT_CLOSED = 1; - static const int POLY_NOT_COPLANAR = 2; + static const int POLY_GOOD = 0; + static const int POLY_NOT_CLOSED = 1; + static const int POLY_NOT_COPLANAR = 2; + static const int POLY_SELF_INTERSECTING = 3; struct { int how; SEdge notClosedAt; - Vector notCoplanarAt; + Vector errorPointAt; } polyError; - SMesh thisMesh; + SShell thisShell; + SShell runningShell; SMesh runningMesh; struct { SMesh interferesAt; bool yes; } meshError; SEdgeList emphEdges; - SShell thisShell; - SShell runningShell; static const int COMBINE_AS_UNION = 0; static const int COMBINE_AS_DIFFERENCE = 1; @@ -206,11 +206,9 @@ public: bool AssembleLoops(void); void GenerateLoops(void); // And the mesh stuff - SMesh *PreviousGroupMesh(void); - void AddQuadWithNormal(STriMeta meta, Vector out, - Vector a, Vector b, Vector c, Vector d); - void GenerateMeshForStepAndRepeat(void); - void GenerateMesh(void); + SShell *PreviousGroupShell(void); + void GenerateShellForStepAndRepeat(void); + void GenerateShellAndMesh(void); void Draw(void); SPolygon GetPolygon(void); diff --git a/srf/boolean.cpp b/srf/boolean.cpp new file mode 100644 index 00000000..02995fe9 --- /dev/null +++ b/srf/boolean.cpp @@ -0,0 +1,10 @@ +#include "solvespace.h" + +void SShell::MakeFromUnionOf(SShell *a, SShell *b) { + MakeFromCopyOf(b); +} + +void SShell::MakeFromDifferenceOf(SShell *a, SShell *b) { + MakeFromCopyOf(b); +} + diff --git a/srf/ratpoly.cpp b/srf/ratpoly.cpp index 31e315c9..37a769ff 100644 --- a/srf/ratpoly.cpp +++ b/srf/ratpoly.cpp @@ -147,6 +147,14 @@ void SBezier::Reverse(void) { } } +SBezier SBezier::TransformedBy(Vector t, Quaternion q) { + SBezier ret = *this; + int i; + for(i = 0; i <= deg; i++) { + ret.ctrl[i] = (q.Rotate(ret.ctrl[i])).Plus(t); + } + return ret; +} void SBezierList::Clear(void) { l.Clear(); @@ -292,6 +300,22 @@ void SBezierLoopSet::Clear(void) { l.Clear(); } +SCurve SCurve::FromTransformationOf(SCurve *a, Vector t, Quaternion q) { + SCurve ret; + ZERO(&ret); + + ret.h = a->h; + ret.isExact = a->isExact; + ret.exact = (a->exact).TransformedBy(t, q); + + Vector *p; + for(p = a->pts.First(); p; p = a->pts.NextAfter(p)) { + Vector pp = (q.Rotate(*p)).Plus(t); + ret.pts.Add(&pp); + } + return ret; +} + void SCurve::Clear(void) { pts.Clear(); } @@ -346,6 +370,37 @@ SSurface SSurface::FromPlane(Vector pt, Vector n) { return ret; } +SSurface SSurface::FromTransformationOf(SSurface *a, Vector t, Quaternion q, + bool includingTrims) +{ + SSurface ret; + ZERO(&ret); + + ret.h = a->h; + + ret.degm = a->degm; + ret.degn = a->degn; + int i, j; + for(i = 0; i <= 3; i++) { + for(j = 0; j <= 3; j++) { + ret.ctrl[i][j] = (q.Rotate(a->ctrl[i][j])).Plus(t); + ret.weight[i][j] = a->weight[i][j]; + } + } + + if(includingTrims) { + STrimBy *stb; + for(stb = a->trim.First(); stb; stb = a->trim.NextAfter(stb)) { + STrimBy n = *stb; + n.start = (q.Rotate(n.start)) .Plus(t); + n.finish = (q.Rotate(n.finish)).Plus(t); + ret.trim.Add(&n); + } + } + + return ret; +} + Vector SSurface::PointAt(double u, double v) { Vector num = Vector::From(0, 0, 0); double den = 0; @@ -449,32 +504,42 @@ void SSurface::ClosestPointTo(Vector p, double *u, double *v) { } } -void SSurface::TriangulateInto(SShell *shell, SMesh *sm) { - SEdgeList el; - ZERO(&el); - +void SSurface::MakeEdgesInto(SShell *shell, SEdgeList *sel, bool asUv) { STrimBy *stb; for(stb = trim.First(); stb; stb = trim.NextAfter(stb)) { SCurve *sc = shell->curve.FindById(stb->curve); - Vector prevuv, ptuv; + Vector prev, prevuv, ptuv; bool inCurve = false; Vector *pt; double u = 0, v = 0; for(pt = sc->pts.First(); pt; pt = sc->pts.NextAfter(pt)) { - ClosestPointTo(*pt, &u, &v); - ptuv = Vector::From(u, v, 0); - - if(inCurve) { - el.AddEdge(prevuv, ptuv); + if(asUv) { + ClosestPointTo(*pt, &u, &v); + ptuv = Vector::From(u, v, 0); + if(inCurve) { + sel->AddEdge(prevuv, ptuv); + } + prevuv = ptuv; + } else { + if(inCurve) { + sel->AddEdge(prev, *pt); + } + prev = *pt; } - prevuv = ptuv; if(pt->EqualsExactly(stb->start)) inCurve = true; if(pt->EqualsExactly(stb->finish)) inCurve = false; } } - +} + +void SSurface::TriangulateInto(SShell *shell, SMesh *sm) { + SEdgeList el; + ZERO(&el); + + MakeEdgesInto(shell, &el, true); + SPolygon poly; ZERO(&poly); if(!el.AssemblePolygon(&poly, NULL)) { @@ -508,9 +573,8 @@ void SSurface::Clear(void) { trim.Clear(); } -SShell SShell::FromExtrusionOf(SBezierLoopSet *sbls, Vector t0, Vector t1) { - SShell ret; - ZERO(&ret); +void SShell::MakeFromExtrusionOf(SBezierLoopSet *sbls, Vector t0, Vector t1) { + ZERO(this); // Make the extrusion direction consistent with respect to the normal // of the sketch we're extruding. @@ -523,8 +587,8 @@ SShell SShell::FromExtrusionOf(SBezierLoopSet *sbls, Vector t0, Vector t1) { SSurface s0, s1; s0 = SSurface::FromPlane(sbls->point.Plus(t0), sbls->normal.ScaledBy(-1)); s1 = SSurface::FromPlane(sbls->point.Plus(t1), sbls->normal.ScaledBy( 1)); - hSSurface hs0 = ret.surface.AddAndAssignId(&s0), - hs1 = ret.surface.AddAndAssignId(&s1); + hSSurface hs0 = surface.AddAndAssignId(&s0), + hs1 = surface.AddAndAssignId(&s1); // Now go through the input curves. For each one, generate its surface // of extrusion, its two translated trim curves, and one trim line. We @@ -544,27 +608,27 @@ SShell SShell::FromExtrusionOf(SBezierLoopSet *sbls, Vector t0, Vector t1) { // Generate the surface of extrusion of this curve, and add // it to the list SSurface ss = SSurface::FromExtrusionOf(sb, t0, t1); - hSSurface hsext = ret.surface.AddAndAssignId(&ss); + hSSurface hsext = surface.AddAndAssignId(&ss); // Translate the curve by t0 and t1 to produce two trim curves SCurve sc; ZERO(&sc); sb->MakePwlInto(&(sc.pts), t0); - hSCurve hc0 = ret.curve.AddAndAssignId(&sc); - STrimBy stb0 = STrimBy::EntireCurve(&ret, hc0); + hSCurve hc0 = curve.AddAndAssignId(&sc); + STrimBy stb0 = STrimBy::EntireCurve(this, hc0); ZERO(&sc); sb->MakePwlInto(&(sc.pts), t1); - hSCurve hc1 = ret.curve.AddAndAssignId(&sc); - STrimBy stb1 = STrimBy::EntireCurve(&ret, hc1); + hSCurve hc1 = curve.AddAndAssignId(&sc); + STrimBy stb1 = STrimBy::EntireCurve(this, hc1); // The translated curves trim the flat top and bottom surfaces. -// (ret.surface.FindById(hs0))->trim.Add(&stb0); - (ret.surface.FindById(hs1))->trim.Add(&stb1); + (surface.FindById(hs0))->trim.Add(&stb0); + (surface.FindById(hs1))->trim.Add(&stb1); // The translated curves also trim the surface of extrusion. -// (ret.surface.FindById(hsext))->trim.Add(&stb0); -// (ret.surface.FindById(hsext))->trim.Add(&stb1); + (surface.FindById(hsext))->trim.Add(&stb0); + (surface.FindById(hsext))->trim.Add(&stb1); // And form the trim line Vector pt = sb->Finish(); @@ -572,10 +636,10 @@ SShell SShell::FromExtrusionOf(SBezierLoopSet *sbls, Vector t0, Vector t1) { ZERO(&sc); sc.pts.Add(&p0); sc.pts.Add(&p1); - hSCurve hl = ret.curve.AddAndAssignId(&sc); + hSCurve hl = curve.AddAndAssignId(&sc); // save this for later TrimLine tl; - tl.trim = STrimBy::EntireCurve(&ret, hl); + tl.trim = STrimBy::EntireCurve(this, hl); tl.hs = hsext; trimLines.Add(&tl); } @@ -583,17 +647,45 @@ SShell SShell::FromExtrusionOf(SBezierLoopSet *sbls, Vector t0, Vector t1) { int i; for(i = 0; i < trimLines.n; i++) { TrimLine *tl = &(trimLines.elem[i]); - SSurface *ss = ret.surface.FindById(tl->hs); + SSurface *ss = surface.FindById(tl->hs); TrimLine *tlp = &(trimLines.elem[WRAP(i-1, trimLines.n)]); -// ss->trim.Add(&(tl->trim)); -// ss->trim.Add(&(tlp->trim)); + ss->trim.Add(&(tl->trim)); + ss->trim.Add(&(tlp->trim)); } trimLines.Clear(); } +} - return ret; +void SShell::MakeFromCopyOf(SShell *a) { + Vector t = Vector::From(0, 0, 0); + Quaternion q = Quaternion::From(1, 0, 0, 0); + + MakeFromTransformationOf(a, t, q); +} + +void SShell::MakeFromTransformationOf(SShell *a, Vector t, Quaternion q) { + SSurface *s; + for(s = a->surface.First(); s; s = a->surface.NextAfter(s)) { + SSurface n; + n = SSurface::FromTransformationOf(s, t, q, true); + surface.Add(&n); // keeping the old ID + } + + SCurve *c; + for(c = a->curve.First(); c; c = a->curve.NextAfter(c)) { + SCurve n; + n = SCurve::FromTransformationOf(c, t, q); + curve.Add(&n); // keeping the old ID + } +} + +void SShell::MakeEdgesInto(SEdgeList *sel) { + SSurface *s; + for(s = surface.First(); s; s = surface.NextAfter(s)) { + s->MakeEdgesInto(this, sel, false); + } } void SShell::TriangulateInto(SMesh *sm) { diff --git a/srf/surface.h b/srf/surface.h index 9564f0bf..7fd4f8b5 100644 --- a/srf/surface.h +++ b/srf/surface.h @@ -36,6 +36,8 @@ public: void Reverse(void); + SBezier TransformedBy(Vector t, Quaternion q); + static SBezier From(Vector p0, Vector p1, Vector p2, Vector p3); static SBezier From(Vector p0, Vector p1, Vector p2); static SBezier From(Vector p0, Vector p1); @@ -82,6 +84,9 @@ public: List pts; + static SCurve SCurve::FromTransformationOf(SCurve *a, + Vector t, Quaternion q); + void Clear(void); }; @@ -112,6 +117,8 @@ public: static SSurface FromExtrusionOf(SBezier *spc, Vector t0, Vector t1); static SSurface FromPlane(Vector pt, Vector n); + static SSurface FromTransformationOf(SSurface *a, Vector t, Quaternion q, + bool includingTrims); void ClosestPointTo(Vector p, double *u, double *v); Vector PointAt(double u, double v); @@ -119,6 +126,7 @@ public: Vector NormalAt(double u, double v); void TriangulateInto(SShell *shell, SMesh *sm); + void MakeEdgesInto(SShell *shell, SEdgeList *sel, bool asUv); void Clear(void); }; @@ -128,11 +136,14 @@ public: IdList curve; IdList surface; - static SShell FromExtrusionOf(SBezierLoopSet *sbls, Vector t0, Vector t1); - - static SShell FromUnionOf(SShell *a, SShell *b); + void MakeFromExtrusionOf(SBezierLoopSet *sbls, Vector t0, Vector t1); + void MakeFromUnionOf(SShell *a, SShell *b); + void MakeFromDifferenceOf(SShell *a, SShell *b); + void MakeFromCopyOf(SShell *a); + void MakeFromTransformationOf(SShell *a, Vector trans, Quaternion q); void TriangulateInto(SMesh *sm); + void MakeEdgesInto(SEdgeList *sel); void Clear(void); }; diff --git a/srf/triangulate.cpp b/srf/triangulate.cpp index ea7d1463..75e586a2 100644 --- a/srf/triangulate.cpp +++ b/srf/triangulate.cpp @@ -133,7 +133,7 @@ bool SContour::BridgeToContour(SContour *sc, } if(f) continue; - if(avoidEdges->AnyEdgeCrosses(a, b)) { + if(avoidEdges->AnyEdgeCrossings(a, b) > 0) { // doesn't work, bridge crosses an existing edge } else { goto haveEdge; diff --git a/undoredo.cpp b/undoredo.cpp index dd48ee7e..f8413597 100644 --- a/undoredo.cpp +++ b/undoredo.cpp @@ -46,12 +46,10 @@ void SolveSpace::PushFromCurrentOnto(UndoStack *uk) { // And then clean up all the stuff that needs to be a deep copy, // and zero out all the dynamic stuff that will get regenerated. dest.clean = false; - dest.vvMeshClean = false; ZERO(&(dest.solved)); ZERO(&(dest.poly)); ZERO(&(dest.bezierLoopSet)); ZERO(&(dest.polyError)); - ZERO(&(dest.thisMesh)); ZERO(&(dest.runningMesh)); ZERO(&(dest.thisShell)); ZERO(&(dest.runningShell)); @@ -95,7 +93,6 @@ void SolveSpace::PopOntoCurrentFrom(UndoStack *uk) { Group *g = &(group.elem[i]); g->poly.Clear(); g->bezierLoopSet.Clear(); - g->thisMesh.Clear(); g->runningMesh.Clear(); g->thisShell.Clear(); g->runningShell.Clear();