From 2f115ec950d38f596b56f9ce503c154fd71f0a24 Mon Sep 17 00:00:00 2001 From: Jonathan Westhues Date: Wed, 28 Oct 2009 23:16:28 -0800 Subject: [PATCH] A monster change to add support for filled paths. This requires us to assemble Beziers into outer and inner loops, and find those loops made up of entities with filled styles. The open paths are maintained in a separate list, and we assemble as many closed paths as possible even when open paths exist. This changes many things. The coplanar check is now performed on the Beziers, not the resulting polygon. The way that the polygon is used to determine loop directions is also modified. Also fix the mouse behavior when dragging a point: drop it when the mouse is released, even if it is released outside the window, but don't drop it if the pointer is dragged out of and then back into our window. Also special-case SSurface::ClosestPointTo() for planes, for speed. [git-p4: depot-paths = "//depot/solvespace/": change = 2058] --- draw.cpp | 11 ++- drawentity.cpp | 6 +- dsc.h | 6 ++ exportstep.cpp | 15 ++- graphicswin.cpp | 4 +- group.cpp | 8 +- groupmesh.cpp | 247 ++++++++++++++++++++++++++++++++++-------------- mouse.cpp | 7 +- polygon.cpp | 26 ----- polygon.h | 2 - sketch.h | 12 ++- srf/curve.cpp | 246 ++++++++++++++++++++++++++++++++++++----------- srf/ratpoly.cpp | 51 ++++++---- srf/surface.cpp | 3 - srf/surface.h | 17 +++- style.cpp | 3 +- undoredo.cpp | 10 +- wishlist.txt | 2 + 18 files changed, 475 insertions(+), 201 deletions(-) diff --git a/draw.cpp b/draw.cpp index db18be5e..bad26ba5 100644 --- a/draw.cpp +++ b/draw.cpp @@ -490,8 +490,15 @@ void GraphicsWindow::Paint(int w, int h) { nogrid:; } - // Draw the active group; this fills the polygons in a drawing group, and - // draws the solid mesh. + // Draw filled paths in all groups, when those filled paths were requested + // specially by assigning a style with a fill color. + Group *g; + for(g = SK.group.First(); g; g = SK.group.NextAfter(g)) { + if(!(g->IsVisible())) continue; + g->DrawFilledPaths(); + } + + // Draw the active group; this does stuff like the mesh and edges. (SK.GetGroup(activeGroup))->Draw(); // Now draw the entities diff --git a/drawentity.cpp b/drawentity.cpp index c7043589..862d7d60 100644 --- a/drawentity.cpp +++ b/drawentity.cpp @@ -53,8 +53,7 @@ void Entity::DrawAll(void) { for(i = 0; i < SK.entity.n; i++) { Entity *e = &(SK.entity.elem[i]); if(!e->IsPoint()) continue; - if(!(SK.GetGroup(e->group)->visible)) continue; - if(SS.GroupsInOrder(SS.GW.activeGroup, e->group)) continue; + if(!(SK.GetGroup(e->group)->IsVisible())) continue; if(e->forceHidden) continue; Vector v = e->PointGetNum(); @@ -164,8 +163,7 @@ bool Entity::IsVisible(void) { // The reference normals are always shown return true; } - if(!g->visible) return false; - if(SS.GroupsInOrder(SS.GW.activeGroup, group)) return false; + if(!(g->IsVisible())) return false; // Don't check if points are hidden; this gets called only for // selected or hovered points, and those should always be shown. diff --git a/dsc.h b/dsc.h index 9c2727ef..a5413d55 100644 --- a/dsc.h +++ b/dsc.h @@ -202,6 +202,12 @@ public: // and elemsAllocated is untouched, because we didn't resize } + void RemoveLast(int cnt) { + if(n < cnt) oops(); + n -= cnt; + // and elemsAllocated is untouched, same as in RemoveTagged + } + void Reverse(void) { int i; for(i = 0; i < (n/2); i++) { diff --git a/exportstep.cpp b/exportstep.cpp index 8bfac1e3..3007c1ea 100644 --- a/exportstep.cpp +++ b/exportstep.cpp @@ -212,7 +212,19 @@ void StepFileWriter::ExportSurface(SSurface *ss, SBezierList *sbl) { // along with its inner faces, so do that now. SBezierLoopSetSet sblss; ZERO(&sblss); - sblss.FindOuterFacesFrom(sbl, ss); + SPolygon spxyz; + ZERO(&spxyz); + bool allClosed; + SEdge notClosedAt; + // We specify a surface, so it doesn't check for coplanarity; and we + // don't want it to give us any open contours. The polygon and chord + // tolerance are required, because they are used to calculate the + // contour directions and determine inner vs. outer contours. + sblss.FindOuterFacesFrom(sbl, &spxyz, ss, + SS.ChordTolMm() / SS.exportScale, + &allClosed, ¬ClosedAt, + NULL, NULL, + NULL); // So in our list of SBezierLoopSet, each set contains at least one loop // (the outer boundary), plus any inner loops associated with that outer @@ -253,6 +265,7 @@ void StepFileWriter::ExportSurface(SSurface *ss, SBezierList *sbl) { listOfLoops.Clear(); } sblss.Clear(); + spxyz.Clear(); } void StepFileWriter::WriteFooter(void) { diff --git a/graphicswin.cpp b/graphicswin.cpp index 406e1fb8..b7ff5612 100644 --- a/graphicswin.cpp +++ b/graphicswin.cpp @@ -273,8 +273,8 @@ void GraphicsWindow::LoopOverPoints(Point2d *pmax, Point2d *pmin, double *wmin, HandlePointForZoomToFit(tr->b, pmax, pmin, wmin, div); HandlePointForZoomToFit(tr->c, pmax, pmin, wmin, div); } - for(i = 0; i < g->poly.l.n; i++) { - SContour *sc = &(g->poly.l.elem[i]); + for(i = 0; i < g->polyLoops.l.n; i++) { + SContour *sc = &(g->polyLoops.l.elem[i]); for(j = 0; j < sc->l.n; j++) { HandlePointForZoomToFit(sc->l.elem[j].p, pmax, pmin, wmin, div); } diff --git a/group.cpp b/group.cpp index fa087f9b..89b17c40 100644 --- a/group.cpp +++ b/group.cpp @@ -16,6 +16,12 @@ void Group::AddParam(IdList *param, hParam hp, double v) { param->Add(&pa); } +bool Group::IsVisible(void) { + if(!visible) return false; + if(SS.GroupsInOrder(SS.GW.activeGroup, h)) return false; + return true; +} + void Group::MenuGroup(int id) { Group g; ZERO(&g); @@ -610,7 +616,7 @@ void Group::MakeExtrusionTopBottomFaces(IdList *el, hEntity pt) { if(pt.v == 0) return; Group *src = SK.GetGroup(opA); - Vector n = src->poly.normal; + Vector n = src->polyLoops.normal; Entity en; ZERO(&en); diff --git a/groupmesh.cpp b/groupmesh.cpp index baaaca4d..66ab8b9c 100644 --- a/groupmesh.cpp +++ b/groupmesh.cpp @@ -2,7 +2,7 @@ #define gs (SS.GW.gs) -bool Group::AssembleLoops(void) { +void Group::AssembleLoops(bool *allClosed, bool *allCoplanar) { SBezierList sbl; ZERO(&sbl); @@ -16,38 +16,40 @@ bool Group::AssembleLoops(void) { e->GenerateBezierCurves(&sbl); } - bool allClosed; - bezierLoopSet = SBezierLoopSet::From(&sbl, &poly, - &allClosed, &(polyError.notClosedAt)); + // Try to assemble all these Beziers into loops. The closed loops go into + // bezierLoops, with the outer loops grouped with their holes. The + // leftovers, if any, go in bezierOpens. + bezierLoops.FindOuterFacesFrom(&sbl, &polyLoops, NULL, + SS.ChordTolMm(), + allClosed, &(polyError.notClosedAt), + allCoplanar, &(polyError.errorPointAt), + &bezierOpens); sbl.Clear(); - return allClosed; } void Group::GenerateLoops(void) { - poly.Clear(); - bezierLoopSet.Clear(); + polyLoops.Clear(); + bezierLoops.Clear(); + bezierOpens.Clear(); if(type == DRAWING_3D || type == DRAWING_WORKPLANE || type == ROTATE || type == TRANSLATE || type == IMPORTED) { - if(AssembleLoops()) { - polyError.how = POLY_GOOD; - - 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 { + bool allClosed, allCoplanar; + AssembleLoops(&allClosed, &allCoplanar); + if(!allCoplanar) { + polyError.how = POLY_NOT_COPLANAR; + } else if(!allClosed) { polyError.how = POLY_NOT_CLOSED; - poly.Clear(); - bezierLoopSet.Clear(); + } else { + polyError.how = POLY_GOOD; + // The self-intersecting check is kind of slow, so don't run it + // unless requested. + if(SS.checkClosedContour) { + if(polyLoops.SelfIntersecting(&(polyError.errorPointAt))) { + polyError.how = POLY_SELF_INTERSECTING; + } + } } } } @@ -159,6 +161,16 @@ void Group::GenerateShellAndMesh(void) { runningShell.Clear(); runningMesh.Clear(); + // Don't attempt a lathe or extrusion unless the source section is good: + // planar and not self-intersecting. + bool haveSrc = true; + if(type == EXTRUDE || type == LATHE) { + Group *src = SK.GetGroup(opA); + if(src->polyError.how != POLY_GOOD) { + haveSrc = false; + } + } + if(type == TRANSLATE || type == ROTATE) { // A step and repeat gets merged against the group's prevous group, // not our own previous group. @@ -166,7 +178,7 @@ void Group::GenerateShellAndMesh(void) { GenerateForStepAndRepeat(&(srcg->thisShell), &thisShell); GenerateForStepAndRepeat (&(srcg->thisMesh), &thisMesh); - } else if(type == EXTRUDE) { + } else if(type == EXTRUDE && haveSrc) { Group *src = SK.GetGroup(opA); Vector translate = Vector::From(h.param(0), h.param(1), h.param(2)); @@ -176,65 +188,76 @@ void Group::GenerateShellAndMesh(void) { } else { tbot = translate.ScaledBy(-1); ttop = translate.ScaledBy(1); } - - thisShell.MakeFromExtrusionOf(&(src->bezierLoopSet), tbot, ttop, color); - Vector onOrig = src->bezierLoopSet.point; - // And for any plane faces, annotate the model with the entity for - // that face, so that the user can select them with the mouse. - int i; - for(i = 0; i < thisShell.surface.n; i++) { - SSurface *ss = &(thisShell.surface.elem[i]); - hEntity face = Entity::NO_ENTITY; + + SBezierLoopSetSet *sblss = &(src->bezierLoops); + SBezierLoopSet *sbls; + for(sbls = sblss->l.First(); sbls; sbls = sblss->l.NextAfter(sbls)) { + int is = thisShell.surface.n; + // Extrude this outer contour (plus its inner contours, if present) + thisShell.MakeFromExtrusionOf(sbls, tbot, ttop, color); - Vector p = ss->PointAt(0, 0), - n = ss->NormalAt(0, 0).WithMagnitude(1); - double d = n.Dot(p); + // And for any plane faces, annotate the model with the entity for + // that face, so that the user can select them with the mouse. + Vector onOrig = sbls->point; + int i; + for(i = is; i < thisShell.surface.n; i++) { + SSurface *ss = &(thisShell.surface.elem[i]); + hEntity face = Entity::NO_ENTITY; - if(i == 0 || i == 1) { - // These are the top and bottom of the shell. - if(fabs((onOrig.Plus(ttop)).Dot(n) - d) < LENGTH_EPS) { - face = Remap(Entity::NO_ENTITY, REMAP_TOP); - ss->face = face.v; + Vector p = ss->PointAt(0, 0), + n = ss->NormalAt(0, 0).WithMagnitude(1); + double d = n.Dot(p); + + if(i == is || i == (is + 1)) { + // These are the top and bottom of the shell. + if(fabs((onOrig.Plus(ttop)).Dot(n) - d) < LENGTH_EPS) { + face = Remap(Entity::NO_ENTITY, REMAP_TOP); + ss->face = face.v; + } + if(fabs((onOrig.Plus(tbot)).Dot(n) - d) < LENGTH_EPS) { + face = Remap(Entity::NO_ENTITY, REMAP_BOTTOM); + ss->face = face.v; + } + continue; } - if(fabs((onOrig.Plus(tbot)).Dot(n) - d) < LENGTH_EPS) { - face = Remap(Entity::NO_ENTITY, REMAP_BOTTOM); - ss->face = face.v; - } - continue; - } - // So these are the sides - if(ss->degm != 1 || ss->degn != 1) continue; + // So these are the sides + if(ss->degm != 1 || ss->degn != 1) continue; - Entity *e; - for(e = SK.entity.First(); e; e = SK.entity.NextAfter(e)) { - if(e->group.v != opA.v) continue; - if(e->type != Entity::LINE_SEGMENT) continue; + Entity *e; + for(e = SK.entity.First(); e; e = SK.entity.NextAfter(e)) { + if(e->group.v != opA.v) continue; + if(e->type != Entity::LINE_SEGMENT) continue; - Vector a = SK.GetEntity(e->point[0])->PointGetNum(), - b = SK.GetEntity(e->point[1])->PointGetNum(); - a = a.Plus(ttop); - b = b.Plus(ttop); - // Could get taken backwards, so check all cases. - if((a.Equals(ss->ctrl[0][0]) && b.Equals(ss->ctrl[1][0])) || - (b.Equals(ss->ctrl[0][0]) && a.Equals(ss->ctrl[1][0])) || - (a.Equals(ss->ctrl[0][1]) && b.Equals(ss->ctrl[1][1])) || - (b.Equals(ss->ctrl[0][1]) && a.Equals(ss->ctrl[1][1]))) - { - face = Remap(e->h, REMAP_LINE_TO_FACE); - ss->face = face.v; - break; + Vector a = SK.GetEntity(e->point[0])->PointGetNum(), + b = SK.GetEntity(e->point[1])->PointGetNum(); + a = a.Plus(ttop); + b = b.Plus(ttop); + // Could get taken backwards, so check all cases. + if((a.Equals(ss->ctrl[0][0]) && b.Equals(ss->ctrl[1][0])) || + (b.Equals(ss->ctrl[0][0]) && a.Equals(ss->ctrl[1][0])) || + (a.Equals(ss->ctrl[0][1]) && b.Equals(ss->ctrl[1][1])) || + (b.Equals(ss->ctrl[0][1]) && a.Equals(ss->ctrl[1][1]))) + { + face = Remap(e->h, REMAP_LINE_TO_FACE); + ss->face = face.v; + break; + } } } } - } else if(type == LATHE) { + } else if(type == LATHE && haveSrc) { Group *src = SK.GetGroup(opA); Vector pt = SK.GetEntity(predef.origin)->PointGetNum(), axis = SK.GetEntity(predef.entityB)->VectorGetNum(); axis = axis.WithMagnitude(1); - thisShell.MakeFromRevolutionOf(&(src->bezierLoopSet), pt, axis, color); + SBezierLoopSetSet *sblss = &(src->bezierLoops); + SBezierLoopSet *sbls; + for(sbls = sblss->l.First(); sbls; sbls = sblss->l.NextAfter(sbls)) { + thisShell.MakeFromRevolutionOf(sbls, pt, axis, color); + } } else if(type == IMPORTED) { // The imported shell or mesh are copied over, with the appropriate // transformation applied. We also must remap the face entities. @@ -465,10 +488,88 @@ void Group::Draw(void) { glEnable(GL_DEPTH_TEST); } } else { - glxColorRGBa(Style::Color(Style::CONTOUR_FILL), 0.5); - glxDepthRangeOffset(1); - glxFillPolygon(&poly); - glxDepthRangeOffset(0); + // The contours will get filled in DrawFilledPaths. + } +} + +//----------------------------------------------------------------------------- +// Verify that the Beziers in this loop set all have the same auxA, and return +// that value. If they don't, then set allSame to be false, and indicate a +// point on the non-matching curve. +//----------------------------------------------------------------------------- +DWORD Group::GetLoopSetFillColor(SBezierLoopSet *sbls, + bool *allSame, Vector *errorAt) +{ + bool first = true; + DWORD fillRgb = (DWORD)-1; + + SBezierLoop *sbl; + for(sbl = sbls->l.First(); sbl; sbl = sbls->l.NextAfter(sbl)) { + SBezier *sb; + for(sb = sbl->l.First(); sb; sb = sbl->l.NextAfter(sb)) { + DWORD thisRgb = (DWORD)-1; + if(sb->auxA != 0) { + hStyle hs = { sb->auxA }; + Style *s = Style::Get(hs); + if(s->filled) { + thisRgb = s->fillColor; + } + } + if(first) { + fillRgb = thisRgb; + first = false; + } else { + if(fillRgb != thisRgb) { + *allSame = false; + *errorAt = sb->Start(); + return fillRgb; + } + } + } + } + *allSame = true; + return fillRgb; +} + +void Group::FillLoopSetAsPolygon(SBezierLoopSet *sbls) { + SPolygon sp; + ZERO(&sp); + sbls->MakePwlInto(&sp); + glxDepthRangeOffset(1); + glxFillPolygon(&sp); + glxDepthRangeOffset(0); + sp.Clear(); +} + +void Group::DrawFilledPaths(void) { + SBezierLoopSet *sbls; + SBezierLoopSetSet *sblss = &bezierLoops; + for(sbls = sblss->l.First(); sbls; sbls = sblss->l.NextAfter(sbls)) { + bool allSame; + Vector errorPt; + DWORD fillRgb = GetLoopSetFillColor(sbls, &allSame, &errorPt); + if(allSame && fillRgb != (DWORD)-1) { + glxColorRGBa(fillRgb, 1); + FillLoopSetAsPolygon(sbls); + } else if(!allSame) { + glDisable(GL_DEPTH_TEST); + glxColorRGB(Style::Color(Style::DRAW_ERROR)); + glxWriteText("not all same fill color!", DEFAULT_TEXT_HEIGHT, + errorPt, SS.GW.projRight, SS.GW.projUp, NULL, NULL); + glEnable(GL_DEPTH_TEST); + } else { + if(h.v == SS.GW.activeGroup.v && SS.checkClosedContour && + polyError.how == POLY_GOOD) + { + // If this is the active group, and we are supposed to check + // for closed contours, and we do indeed have a closed and + // non-intersecting contour, then fill it dimly. + glxColorRGBa(Style::Color(Style::CONTOUR_FILL), 0.5); + glxDepthRangeOffset(1); + FillLoopSetAsPolygon(sbls); + glxDepthRangeOffset(0); + } + } } } diff --git a/mouse.cpp b/mouse.cpp index 886655a2..86644bdd 100644 --- a/mouse.cpp +++ b/mouse.cpp @@ -37,6 +37,10 @@ void GraphicsWindow::MouseMoved(double x, double y, bool leftDown, } } + if(!leftDown && pending.operation == DRAGGING_POINT) { + ClearPending(); + } + Point2d mp = { x, y }; if(rightDown && orig.mouse.DistanceTo(mp) < 5 && !orig.startedMoving) { @@ -980,9 +984,6 @@ void GraphicsWindow::MouseLeave(void) { toolbarHovered = 0; PaintGraphics(); } - if(pending.operation == DRAGGING_POINT) { - ClearPending(); - } } void GraphicsWindow::SpaceNavigatorMoved(double tx, double ty, double tz, diff --git a/polygon.cpp b/polygon.cpp index 8e99db60..040eb229 100644 --- a/polygon.cpp +++ b/polygon.cpp @@ -447,18 +447,6 @@ bool SContour::ContainsPointProjdToNormal(Vector n, Vector p) { return inside; } -bool SContour::AllPointsInPlane(Vector n, double d, Vector *notCoplanarAt) { - for(int i = 0; i < l.n; i++) { - Vector p = l.elem[i].p; - double dd = n.Dot(p) - d; - if(fabs(dd) > 10*LENGTH_EPS) { - *notCoplanarAt = p; - return false; - } - } - return true; -} - void SContour::Reverse(void) { l.Reverse(); } @@ -549,20 +537,6 @@ Vector SPolygon::AnyPoint(void) { return l.elem[0].l.elem[0].p; } -bool SPolygon::AllPointsInPlane(Vector *notCoplanarAt) { - if(IsEmpty()) return true; - - Vector p0 = AnyPoint(); - double d = normal.Dot(p0); - - for(int i = 0; i < l.n; i++) { - if(!(l.elem[i]).AllPointsInPlane(normal, d, notCoplanarAt)) { - return false; - } - } - return true; -} - bool SPolygon::SelfIntersecting(Vector *intersectsAt) { SEdgeList el; ZERO(&el); diff --git a/polygon.h b/polygon.h index 790f6df9..6744561c 100644 --- a/polygon.h +++ b/polygon.h @@ -71,7 +71,6 @@ public: Vector ComputeNormal(void); bool IsClockwiseProjdToNormal(Vector n); bool ContainsPointProjdToNormal(Vector n, Vector p); - bool AllPointsInPlane(Vector n, double d, Vector *notCoplanarAt); void OffsetInto(SContour *dest, double r); void CopyInto(SContour *dest); void FindPointWithMinX(void); @@ -102,7 +101,6 @@ public: void TriangulateInto(SMesh *m); void TriangulateInto(SMesh *m, STriMeta meta); void Clear(void); - bool AllPointsInPlane(Vector *notCoplanarAt); bool SelfIntersecting(Vector *intersectsAt); bool IsEmpty(void); Vector AnyPoint(void); diff --git a/sketch.h b/sketch.h index 525a5579..80f3112c 100644 --- a/sketch.h +++ b/sketch.h @@ -141,8 +141,9 @@ public: bool negateV; } predef; - SPolygon poly; - SBezierLoopSet bezierLoopSet; + SPolygon polyLoops; + SBezierLoopSetSet bezierLoops; + SBezierList bezierOpens; static const int POLY_GOOD = 0; static const int POLY_NOT_CLOSED = 1; static const int POLY_NOT_COPLANAR = 2; @@ -211,10 +212,11 @@ public: void AddEq(IdList *l, Expr *expr, int index); void GenerateEquations(IdList *l); + bool IsVisible(void); // Assembling the curves into loops, and into a piecewise linear polygon // at the same time. - bool AssembleLoops(void); + void AssembleLoops(bool *allClosed, bool *allCoplanar); void GenerateLoops(void); // And the mesh stuff Group *PreviousGroup(void); @@ -225,6 +227,10 @@ public: void GenerateDisplayItems(void); void DrawDisplayItems(int t); void Draw(void); + DWORD GetLoopSetFillColor(SBezierLoopSet *sbls, + bool *allSame, Vector *errorAt); + void FillLoopSetAsPolygon(SBezierLoopSet *sbls); + void DrawFilledPaths(void); SPolygon GetPolygon(void); diff --git a/srf/curve.cpp b/srf/curve.cpp index dbf697ff..4bac32cf 100644 --- a/srf/curve.cpp +++ b/srf/curve.cpp @@ -299,6 +299,95 @@ void SBezier::AllIntersectionsWith(SBezier *sbb, SPointList *spl) { splRaw.Clear(); } +//----------------------------------------------------------------------------- +// Find a plane that contains all of the curves in this list. If the curves +// are all colinear (or coincident, or empty), then that plane is not exactly +// determined but we choose the additional degree(s) of freedom arbitrarily. +// Returns true if all the curves are coplanar, otherwise false. +//----------------------------------------------------------------------------- +bool SBezierList::GetPlaneContainingBeziers(Vector *p, Vector *u, Vector *v, + Vector *notCoplanarAt) +{ + Vector pt, ptFar, ptOffLine, dp, n; + double farMax, offLineMax; + int i; + SBezier *sb; + + // Get any point on any Bezier; or an arbitrary point if list is empty. + if(l.n > 0) { + pt = l.elem[0].Start(); + } else { + pt = Vector::From(0, 0, 0); + } + ptFar = ptOffLine = pt; + + // Get the point farthest from our arbitrary point. + farMax = VERY_NEGATIVE; + for(sb = l.First(); sb; sb = l.NextAfter(sb)) { + for(i = 0; i <= sb->deg; i++) { + double m = (pt.Minus(sb->ctrl[i])).Magnitude(); + if(m > farMax) { + ptFar = sb->ctrl[i]; + farMax = m; + } + } + } + if(ptFar.Equals(pt)) { + // The points are all coincident. So neither basis vector matters. + *p = pt; + *u = Vector::From(1, 0, 0); + *v = Vector::From(0, 1, 0); + return true; + } + + // Get the point farthest from the line between pt and ptFar + dp = ptFar.Minus(pt); + offLineMax = VERY_NEGATIVE; + for(sb = l.First(); sb; sb = l.NextAfter(sb)) { + for(i = 0; i <= sb->deg; i++) { + double m = (sb->ctrl[i]).DistanceToLine(pt, dp); + if(m > offLineMax) { + ptOffLine = sb->ctrl[i]; + offLineMax = m; + } + } + } + + *p = pt; + if(offLineMax < LENGTH_EPS) { + // The points are all colinear; so choose the second basis vector + // arbitrarily. + *u = (ptFar.Minus(pt)).WithMagnitude(1); + *v = (u->Normal(0)).WithMagnitude(1); + } else { + // The points actually define a plane. + n = (ptFar.Minus(pt)).Cross(ptOffLine.Minus(pt)); + *u = (n.Normal(0)).WithMagnitude(1); + *v = (n.Normal(1)).WithMagnitude(1); + } + + // So we have a plane; but check whether all of the points lie in that + // plane. + n = u->Cross(*v); + n = n.WithMagnitude(1); + double d = p->Dot(n); + for(sb = l.First(); sb; sb = l.NextAfter(sb)) { + for(i = 0; i <= sb->deg; i++) { + if(fabs(n.Dot(sb->ctrl[i]) - d) > LENGTH_EPS) { + if(notCoplanarAt) *notCoplanarAt = sb->ctrl[i]; + return false; + } + } + } + return true; +} + +//----------------------------------------------------------------------------- +// Assemble curves in sbl into a single loop. The curves may appear in any +// direction (start to finish, or finish to start), and will be reversed if +// necessary. The curves in the returned loop are removed from sbl, even if +// the loop cannot be closed. +//----------------------------------------------------------------------------- SBezierLoop SBezierLoop::FromCurves(SBezierList *sbl, bool *allClosed, SEdge *errorAt) { @@ -375,24 +464,20 @@ void SBezierLoop::GetBoundingProjd(Vector u, Vector orig, } } -void SBezierLoop::MakePwlInto(SContour *sc) { - List lv; - ZERO(&lv); - - int i, j; - for(i = 0; i < l.n; i++) { - SBezier *sb = &(l.elem[i]); - sb->MakePwlInto(&lv); - - // Each curve's piecewise linearization includes its endpoints, - // which we don't want to duplicate (creating zero-len edges). - for(j = (i == 0 ? 0 : 1); j < lv.n; j++) { - sc->AddPoint(lv.elem[j]); +void SBezierLoop::MakePwlInto(SContour *sc, double chordTol) { + SBezier *sb; + for(sb = l.First(); sb; sb = l.NextAfter(sb)) { + sb->MakePwlInto(sc, chordTol); + // Avoid double points at join between Beziers; except that + // first and last points should be identical. + if(l.NextAfter(sb) != NULL) { + sc->l.RemoveLast(1); } - lv.Clear(); } // Ensure that it's exactly closed, not just within a numerical tolerance. - sc->l.elem[sc->l.n - 1] = sc->l.elem[0]; + if((sc->l.elem[sc->l.n - 1].p).Equals(sc->l.elem[0].p)) { + sc->l.elem[sc->l.n - 1] = sc->l.elem[0]; + } } bool SBezierLoop::IsClosed(void) { @@ -403,26 +488,40 @@ bool SBezierLoop::IsClosed(void) { } +//----------------------------------------------------------------------------- +// Assemble the curves in sbl into multiple loops, and piecewise linearize the +// curves into poly. If we can't close a contour, then we add it to +// openContours (if that isn't NULL) and keep going; so this works even if the +// input contains a mix of open and closed curves. +//----------------------------------------------------------------------------- SBezierLoopSet SBezierLoopSet::From(SBezierList *sbl, SPolygon *poly, - bool *allClosed, SEdge *errorAt) + double chordTol, + bool *allClosed, SEdge *errorAt, + SBezierList *openContours) { - int i; SBezierLoopSet ret; ZERO(&ret); + *allClosed = true; while(sbl->l.n > 0) { bool thisClosed; SBezierLoop loop; loop = SBezierLoop::FromCurves(sbl, &thisClosed, errorAt); if(!thisClosed) { - ret.Clear(); + // Record open loops in a separate list, if requested. *allClosed = false; - return ret; + if(openContours) { + SBezier *sb; + for(sb = loop.l.First(); sb; sb = loop.l.NextAfter(sb)) { + openContours->l.Add(sb); + } + } + loop.Clear(); + } else { + ret.l.Add(&loop); + poly->AddEmptyContour(); + loop.MakePwlInto(&(poly->l.elem[poly->l.n-1]), chordTol); } - - ret.l.Add(&loop); - poly->AddEmptyContour(); - loop.MakePwlInto(&(poly->l.elem[poly->l.n-1])); } poly->normal = poly->ComputeNormal(); @@ -432,17 +531,7 @@ SBezierLoopSet SBezierLoopSet::From(SBezierList *sbl, SPolygon *poly, } else { ret.point = Vector::From(0, 0, 0); } - poly->FixContourDirections(); - for(i = 0; i < poly->l.n; i++) { - if(poly->l.elem[i].tag) { - // We had to reverse this contour in order to fix the poly - // contour directions; so need to do the same with the curves. - ret.l.elem[i].Reverse(); - } - } - - *allClosed = true; return ret; } @@ -455,6 +544,18 @@ void SBezierLoopSet::GetBoundingProjd(Vector u, Vector orig, } } +//----------------------------------------------------------------------------- +// Convert all the Beziers into piecewise linear form, and assemble that into +// a polygon, one contour per loop. +//----------------------------------------------------------------------------- +void SBezierLoopSet::MakePwlInto(SPolygon *sp) { + SBezierLoop *sbl; + for(sbl = l.First(); sbl; sbl = l.NextAfter(sbl)) { + sp->AddEmptyContour(); + sbl->MakePwlInto(&(sp->l.elem[sp->l.n - 1])); + } +} + void SBezierLoopSet::Clear(void) { int i; for(i = 0; i < l.n; i++) { @@ -468,44 +569,73 @@ void SBezierLoopSet::Clear(void) { // assemble them into loops. We find the outer loops, and find the outer loops' // inner loops, and group them accordingly. //----------------------------------------------------------------------------- -void SBezierLoopSetSet::FindOuterFacesFrom(SBezierList *sbl, SSurface *srfuv) { +void SBezierLoopSetSet::FindOuterFacesFrom(SBezierList *sbl, SPolygon *spxyz, + SSurface *srfuv, + double chordTol, + bool *allClosed, SEdge *notClosedAt, + bool *allCoplanar, Vector *notCoplanarAt, + SBezierList *openContours) +{ + SSurface srfPlane; + if(!srfuv) { + Vector p, u, v; + *allCoplanar = + sbl->GetPlaneContainingBeziers(&p, &u, &v, notCoplanarAt); + if(!*allCoplanar) { + // Don't even try to assemble them into loops if they're not + // all coplanar. + if(openContours) { + SBezier *sb; + for(sb = sbl->l.First(); sb; sb = sbl->l.NextAfter(sb)) { + openContours->l.Add(sb); + } + } + return; + } + // All the curves lie in a plane through p with basis vectors u and v. + srfPlane = SSurface::FromPlane(p, u, v); + srfuv = &srfPlane; + } + int i, j; - bool allClosed; - SEdge errorAt; - SPolygon sp; - ZERO(&sp); // Assemble the Bezier trim curves into closed loops; we also get the - // piecewise linearization of the curves (in the SPolygon sp), as a + // piecewise linearization of the curves (in the SPolygon spxyz), as a // calculation aid for the loop direction. - SBezierLoopSet sbls = SBezierLoopSet::From(sbl, &sp, &allClosed, &errorAt); + SBezierLoopSet sbls = SBezierLoopSet::From(sbl, spxyz, chordTol, + allClosed, notClosedAt, + openContours); + if(sbls.l.n != spxyz->l.n) return; // Convert the xyz piecewise linear to uv piecewise linear. - SContour *contour; - for(contour = sp.l.First(); contour; contour = sp.l.NextAfter(contour)) { + SPolygon spuv; + ZERO(&spuv); + SContour *sc; + for(sc = spxyz->l.First(); sc; sc = spxyz->l.NextAfter(sc)) { + spuv.AddEmptyContour(); SPoint *pt; - for(pt = contour->l.First(); pt; pt = contour->l.NextAfter(pt)) { + for(pt = sc->l.First(); pt; pt = sc->l.NextAfter(pt)) { double u, v; srfuv->ClosestPointTo(pt->p, &u, &v); - pt->p = Vector::From(u, v, 0); + spuv.l.elem[spuv.l.n - 1].AddPoint(Vector::From(u, v, 0)); } } - sp.normal = Vector::From(0, 0, 1); + spuv.normal = Vector::From(0, 0, 1); // must be, since it's in xy plane now static const int OUTER_LOOP = 10; static const int INNER_LOOP = 20; static const int USED_LOOP = 30; - // Fix the contour directions; SBezierLoopSet::From() works only for - // planes, since it uses the polygon xyz space. - sp.FixContourDirections(); - for(i = 0; i < sp.l.n; i++) { - SContour *contour = &(sp.l.elem[i]); + // Fix the contour directions; we do this properly, in uv space, so it + // works for curved surfaces too (important for STEP export). + spuv.FixContourDirections(); + for(i = 0; i < spuv.l.n; i++) { + SContour *contour = &(spuv.l.elem[i]); SBezierLoop *bl = &(sbls.l.elem[i]); if(contour->tag) { // This contour got reversed in the polygon to make the directions // consistent, so the same must be necessary for the Bezier loop. bl->Reverse(); } - if(contour->IsClockwiseProjdToNormal(sp.normal)) { + if(contour->IsClockwiseProjdToNormal(spuv.normal)) { bl->tag = INNER_LOOP; } else { bl->tag = OUTER_LOOP; @@ -528,8 +658,8 @@ void SBezierLoopSetSet::FindOuterFacesFrom(SBezierList *sbl, SSurface *srfuv) { if(i == j) continue; if(outer->tag != OUTER_LOOP) continue; - Vector p = sp.l.elem[j].AnyEdgeMidpoint(); - if(sp.l.elem[i].ContainsPointProjdToNormal(sp.normal, p)) { + Vector p = spuv.l.elem[j].AnyEdgeMidpoint(); + if(spuv.l.elem[i].ContainsPointProjdToNormal(spuv.normal, p)) { break; } } @@ -548,17 +678,19 @@ void SBezierLoopSetSet::FindOuterFacesFrom(SBezierList *sbl, SSurface *srfuv) { SBezierLoop *inner = &(sbls.l.elem[j]); if(inner->tag != INNER_LOOP) continue; - Vector p = sp.l.elem[j].AnyEdgeMidpoint(); - if(sp.l.elem[i].ContainsPointProjdToNormal(sp.normal, p)) { + Vector p = spuv.l.elem[j].AnyEdgeMidpoint(); + if(spuv.l.elem[i].ContainsPointProjdToNormal(spuv.normal, p)) { outerAndInners.l.Add(inner); inner->tag = USED_LOOP; } } - + + outerAndInners.point = srfuv->PointAt(0, 0); + outerAndInners.normal = srfuv->NormalAt(0, 0); l.Add(&outerAndInners); } } - sp.Clear(); + spuv.Clear(); // Don't free sbls; we've shallow-copied all of its members to ourself. } diff --git a/srf/ratpoly.cpp b/srf/ratpoly.cpp index 5d117cd8..f32f86b1 100644 --- a/srf/ratpoly.cpp +++ b/srf/ratpoly.cpp @@ -250,6 +250,16 @@ void SBezier::MakePwlInto(List *l, double chordTol) { } lv.Clear(); } +void SBezier::MakePwlInto(SContour *sc, double chordTol) { + List lv; + ZERO(&lv); + MakePwlInto(&lv, chordTol); + int i; + for(i = 0; i < lv.n; i++) { + sc->AddPoint(lv.elem[i]); + } + lv.Clear(); +} void SBezier::MakePwlInto(List *l, double chordTol) { if(chordTol == 0) { // Use the default chord tolerance. @@ -362,6 +372,19 @@ void SSurface::ClosestPointTo(Vector p, double *u, double *v, bool converge) { if(p.Equals(ctrl[degm][degn])) { *u = 1; *v = 1; return; } if(p.Equals(ctrl[0] [degn])) { *u = 0; *v = 1; return; } + // And planes are trivial, so don't waste time iterating over those. + if(degm == 1 && degn == 1) { + Vector orig = ctrl[0][0], + bu = (ctrl[1][0]).Minus(orig), + bv = (ctrl[0][1]).Minus(orig); + if((ctrl[1][1]).Equals(orig.Plus(bu).Plus(bv))) { + Vector dp = p.Minus(orig); + *u = dp.Dot(bu) / bu.MagSquared(); + *v = dp.Dot(bv) / bv.MagSquared(); + return; + } + } + // Try whatever the previous guess was. This is likely to do something // good if we're working our way along a curve or something else where // we project successive points that are close to each other; something @@ -377,22 +400,18 @@ void SSurface::ClosestPointTo(Vector p, double *u, double *v, bool converge) { // Search for a reasonable initial guess int i, j; - if(degm == 1 && degn == 1) { - *u = *v = 0; // a plane, perfect no matter what the initial guess - } else { - double minDist = VERY_POSITIVE; - int res = (max(degm, degn) == 2) ? 7 : 20; - for(i = 0; i < res; i++) { - for(j = 0; j < res; j++) { - double tryu = (i + 0.5)/res, tryv = (j + 0.5)/res; - - Vector tryp = PointAt(tryu, tryv); - double d = (tryp.Minus(p)).Magnitude(); - if(d < minDist) { - *u = tryu; - *v = tryv; - minDist = d; - } + double minDist = VERY_POSITIVE; + int res = (max(degm, degn) == 2) ? 7 : 20; + for(i = 0; i < res; i++) { + for(j = 0; j < res; j++) { + double tryu = (i + 0.5)/res, tryv = (j + 0.5)/res; + + Vector tryp = PointAt(tryu, tryv); + double d = (tryp.Minus(p)).Magnitude(); + if(d < minDist) { + *u = tryu; + *v = tryv; + minDist = d; } } } diff --git a/srf/surface.cpp b/srf/surface.cpp index c6051afe..15e4283f 100644 --- a/srf/surface.cpp +++ b/srf/surface.cpp @@ -487,8 +487,6 @@ void SSurface::Clear(void) { void SShell::MakeFromExtrusionOf(SBezierLoopSet *sbls, Vector t0, Vector t1, int color) { - ZERO(this); - // Make the extrusion direction consistent with respect to the normal // of the sketch we're extruding. if((t0.Minus(t1)).Dot(sbls->normal) < 0) { @@ -610,7 +608,6 @@ void SShell::MakeFromExtrusionOf(SBezierLoopSet *sbls, Vector t0, Vector t1, void SShell::MakeFromRevolutionOf(SBezierLoopSet *sbls, Vector pt, Vector axis, int color) { - ZERO(this); SBezierLoop *sbl; int i0 = surface.n, i; diff --git a/srf/surface.h b/srf/surface.h index bd6ad985..09fdf1ab 100644 --- a/srf/surface.h +++ b/srf/surface.h @@ -79,6 +79,7 @@ public: bool Equals(SBezier *b); void MakePwlInto(SEdgeList *sel, double chordTol=0); void MakePwlInto(List *l, double chordTol=0); + void MakePwlInto(SContour *sc, double chordTol=0); void MakePwlInto(List *l, double chordTol=0); void MakePwlWorker(List *l, double ta, double tb, double chordTol); @@ -111,6 +112,8 @@ public: void ScaleSelfBy(double s); void CullIdenticalBeziers(void); void AllIntersectionsWith(SBezierList *sblb, SPointList *spl); + bool GetPlaneContainingBeziers(Vector *p, Vector *u, Vector *v, + Vector *notCoplanarAt); }; class SBezierLoop { @@ -121,7 +124,7 @@ public: inline void Clear(void) { l.Clear(); } bool IsClosed(void); void Reverse(void); - void MakePwlInto(SContour *sc); + void MakePwlInto(SContour *sc, double chordTol=0); void GetBoundingProjd(Vector u, Vector orig, double *umin, double *umax); static SBezierLoop FromCurves(SBezierList *spcl, @@ -135,9 +138,13 @@ public: Vector point; static SBezierLoopSet From(SBezierList *spcl, SPolygon *poly, - bool *allClosed, SEdge *errorAt); + double chordTol, + bool *allClosed, SEdge *errorAt, + SBezierList *openContours); void GetBoundingProjd(Vector u, Vector orig, double *umin, double *umax); + void MakePwlInto(SPolygon *sp); + int GetAuxA(bool *allSame, Vector *errorAt); void Clear(void); }; @@ -145,7 +152,11 @@ class SBezierLoopSetSet { public: List l; - void FindOuterFacesFrom(SBezierList *sbl, SSurface *srfuv); + void FindOuterFacesFrom(SBezierList *sbl, SPolygon *spxyz, SSurface *srfuv, + double chordTol, + bool *allClosed, SEdge *notClosedAt, + bool *allCoplanar, Vector *notCoplanarAt, + SBezierList *openContours); void Clear(void); }; diff --git a/style.cpp b/style.cpp index 8cbe2060..b54357ad 100644 --- a/style.cpp +++ b/style.cpp @@ -142,7 +142,7 @@ void Style::AssignSelectionToStyle(DWORD v) { hRequest hr = he.request(); Request *r = SK.GetRequest(hr); r->style.v = v; - SS.later.generateAll = true; + SS.MarkGroupDirty(r->group); } for(i = 0; i < SS.GW.gs.constraints; i++) { hConstraint hc = SS.GW.gs.constraint[i]; @@ -159,6 +159,7 @@ void Style::AssignSelectionToStyle(DWORD v) { SS.GW.ClearSelection(); InvalidateGraphics(); + SS.later.generateAll = true; // And show that style's info screen in the text window. SS.TW.GoToScreen(TextWindow::SCREEN_STYLE_INFO); diff --git a/undoredo.cpp b/undoredo.cpp index f48f59ee..ac76a004 100644 --- a/undoredo.cpp +++ b/undoredo.cpp @@ -47,8 +47,9 @@ void SolveSpace::PushFromCurrentOnto(UndoStack *uk) { // and zero out all the dynamic stuff that will get regenerated. dest.clean = false; ZERO(&(dest.solved)); - ZERO(&(dest.poly)); - ZERO(&(dest.bezierLoopSet)); + ZERO(&(dest.polyLoops)); + ZERO(&(dest.bezierLoops)); + ZERO(&(dest.bezierOpens)); ZERO(&(dest.polyError)); ZERO(&(dest.thisMesh)); ZERO(&(dest.runningMesh)); @@ -96,8 +97,9 @@ void SolveSpace::PopOntoCurrentFrom(UndoStack *uk) { // Free everything in the main copy of the program before replacing it for(i = 0; i < SK.group.n; i++) { Group *g = &(SK.group.elem[i]); - g->poly.Clear(); - g->bezierLoopSet.Clear(); + g->polyLoops.Clear(); + g->bezierLoops.Clear(); + g->bezierOpens.Clear(); g->thisMesh.Clear(); g->runningMesh.Clear(); g->thisShell.Clear(); diff --git a/wishlist.txt b/wishlist.txt index 48abf981..5f618d49 100644 --- a/wishlist.txt +++ b/wishlist.txt @@ -1,7 +1,9 @@ multi-drag +select loop, all in group, others copy and paste filled contours for export +background image associative entities from solid model, as a special group -----