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]
solver
Jonathan Westhues 2009-10-28 23:16:28 -08:00
parent e7c8d31500
commit 2f115ec950
18 changed files with 475 additions and 201 deletions

View File

@ -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

View File

@ -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.

6
dsc.h
View File

@ -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++) {

View File

@ -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, &notClosedAt,
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) {

View File

@ -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);
}

View File

@ -16,6 +16,12 @@ void Group::AddParam(IdList<Param,hParam> *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<Entity,hEntity> *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);

View File

@ -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<SShell>(&(srcg->thisShell), &thisShell);
GenerateForStepAndRepeat<SMesh> (&(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);
}
}
}
}

View File

@ -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,

View File

@ -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);

View File

@ -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);

View File

@ -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<Equation,hEquation> *l, Expr *expr, int index);
void GenerateEquations(IdList<Equation,hEquation> *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);

View File

@ -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<Vector> 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.
}

View File

@ -250,6 +250,16 @@ void SBezier::MakePwlInto(List<SCurvePt> *l, double chordTol) {
}
lv.Clear();
}
void SBezier::MakePwlInto(SContour *sc, double chordTol) {
List<Vector> 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<Vector> *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;
}
}
}

View File

@ -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;

View File

@ -79,6 +79,7 @@ public:
bool Equals(SBezier *b);
void MakePwlInto(SEdgeList *sel, double chordTol=0);
void MakePwlInto(List<SCurvePt> *l, double chordTol=0);
void MakePwlInto(SContour *sc, double chordTol=0);
void MakePwlInto(List<Vector> *l, double chordTol=0);
void MakePwlWorker(List<Vector> *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<SBezierLoopSet> 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);
};

View File

@ -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);

View File

@ -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();

View File

@ -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
-----