Many changes:

* Rewrite surface handles in curves, so that Booleans beyond
      the first don't screw up.

    * If an intersection curve is identical to an existing curve
      (as happens when faces are coincident), take the piecewise
      linearization of the existing curve; this stops us from
      screwing up when different shells are pwl'd at different
      chord tols.

    * Hook up the plane faces again.

    * Remove coincident (parallel or anti-parallel) edges from the
      coincident-face edge lists when doing Booleans; those may
      happen if two faces are coincident with ours.

    * Miscellaneous bugfixes.

It doesn't seem to screw up very much now, although tangent edges
(and insufficient pwl resolution) may still cause problems.

[git-p4: depot-paths = "//depot/solvespace/": change = 1929]
This commit is contained in:
Jonathan Westhues 2009-03-15 15:04:45 -08:00
parent adc910185c
commit acadc0a918
12 changed files with 267 additions and 102 deletions

View File

@ -1023,14 +1023,8 @@ void GraphicsWindow::Paint(int w, int h) {
// And the naked edges, if the user did Analyze -> Show Naked Edges. // And the naked edges, if the user did Analyze -> Show Naked Edges.
glLineWidth(7); glLineWidth(7);
glEnable(GL_LINE_STIPPLE);
glLineStipple(1, 0x5555);
glColor3d(1, 0, 0); glColor3d(1, 0, 0);
glxDrawEdges(&(SS.nakedEdges)); glxDrawEdges(&(SS.nakedEdges));
glLineStipple(1, 0xaaaa);
glColor3d(0, 0, 0);
glxDrawEdges(&(SS.nakedEdges));
glDisable(GL_LINE_STIPPLE);
// Then redraw whatever the mouse is hovering over, highlighted. // Then redraw whatever the mouse is hovering over, highlighted.
glDisable(GL_DEPTH_TEST); glDisable(GL_DEPTH_TEST);

5
dsc.h
View File

@ -15,6 +15,8 @@ public:
// a + (vx)*i + (vy)*j + (vz)*k // a + (vx)*i + (vy)*j + (vz)*k
double w, vx, vy, vz; double w, vx, vy, vz;
static const Quaternion IDENTITY;
static Quaternion From(double w, double vx, double vy, double vz); static Quaternion From(double w, double vx, double vy, double vz);
static Quaternion From(hParam w, hParam vx, hParam vy, hParam vz); static Quaternion From(hParam w, hParam vx, hParam vy, hParam vz);
static Quaternion From(Vector u, Vector v); static Quaternion From(Vector u, Vector v);
@ -54,8 +56,7 @@ public:
bool *parallel); bool *parallel);
static Vector AtIntersectionOfPlanes(Vector na, double da, static Vector AtIntersectionOfPlanes(Vector na, double da,
Vector nb, double db, Vector nb, double db,
Vector nc, double dc, Vector nc, double dc, bool *parallel);
bool *parallel);
double Element(int i); double Element(int i);
bool Equals(Vector v, double tol=LENGTH_EPS); bool Equals(Vector v, double tol=LENGTH_EPS);

View File

@ -307,8 +307,8 @@ void GraphicsWindow::ZoomToFit(void) {
if(dy != 0) scaley = 0.9*height/dy; if(dy != 0) scaley = 0.9*height/dy;
scale = min(scalex, scaley); scale = min(scalex, scaley);
scale = min(100, scale); scale = min(300, scale);
scale = max(0.001, scale); scale = max(0.003, scale);
} }
// Then do another run, considering the perspective. // Then do another run, considering the perspective.

View File

@ -55,6 +55,12 @@ void Group::GenerateShellForStepAndRepeat(void) {
Group *src = SS.GetGroup(opA); Group *src = SS.GetGroup(opA);
SShell *srcs = &(src->thisShell); // the shell to step and repeat SShell *srcs = &(src->thisShell); // the shell to step and repeat
SShell workA, workB;
ZERO(&workA);
ZERO(&workB);
SShell *soFar = &workA, *scratch = &workB;
soFar->MakeFromCopyOf(src->PreviousGroupShell());
int n = (int)valA, a0 = 0; int n = (int)valA, a0 = 0;
if(subtype == ONE_SIDED && skipFirst) { if(subtype == ONE_SIDED && skipFirst) {
a0++; n++; a0++; n++;
@ -64,25 +70,37 @@ void Group::GenerateShellForStepAndRepeat(void) {
int ap = a*2 - (subtype == ONE_SIDED ? 0 : (n-1)); int ap = a*2 - (subtype == ONE_SIDED ? 0 : (n-1));
int remap = (a == (n - 1)) ? REMAP_LAST : a; int remap = (a == (n - 1)) ? REMAP_LAST : a;
SShell transd;
ZERO(&transd);
if(type == TRANSLATE) { if(type == TRANSLATE) {
Vector trans = Vector::From(h.param(0), h.param(1), h.param(2)); Vector trans = Vector::From(h.param(0), h.param(1), h.param(2));
trans = trans.ScaledBy(ap); trans = trans.ScaledBy(ap);
Quaternion q = Quaternion::From(1, 0, 0, 0);
transd.MakeFromTransformationOf(srcs, trans, q);
} else { } else {
Vector trans = Vector::From(h.param(0), h.param(1), h.param(2)); Vector trans = Vector::From(h.param(0), h.param(1), h.param(2));
double theta = ap * SS.GetParam(h.param(3))->val; double theta = ap * SS.GetParam(h.param(3))->val;
double c = cos(theta), s = sin(theta); double c = cos(theta), s = sin(theta);
Vector axis = Vector::From(h.param(4), h.param(5), h.param(6)); 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); Quaternion q = Quaternion::From(c, s*axis.x, s*axis.y, s*axis.z);
// Rotation is centered at t; so A(x - t) + t = Ax + (t - At)
transd.MakeFromTransformationOf(srcs,
trans.Minus(q.Rotate(trans)), q);
} }
if(src->meshCombine == COMBINE_AS_DIFFERENCE) { if(src->meshCombine == COMBINE_AS_DIFFERENCE) {
scratch->MakeFromDifferenceOf(soFar, &transd);
} else { } else {
scratch->MakeFromUnionOf(soFar, &transd);
}
SWAP(SShell *, scratch, soFar);
scratch->Clear();
transd.Clear();
} }
}
runningShell.Clear();
runningShell = *soFar;
} }
void Group::GenerateShellAndMesh(void) { void Group::GenerateShellAndMesh(void) {
@ -105,6 +123,55 @@ void Group::GenerateShellAndMesh(void) {
} }
thisShell.MakeFromExtrusionOf(&(src->bezierLoopSet), tbot, ttop, color); 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;
Vector p = ss->PointAt(0, 0),
n = ss->NormalAt(0, 0).WithMagnitude(1);
double d = n.Dot(p);
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;
}
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;
Entity *e;
for(e = SS.entity.First(); e; e = SS.entity.NextAfter(e)) {
if(e->group.v != opA.v) continue;
if(e->type != Entity::LINE_SEGMENT) continue;
Vector a = SS.GetEntity(e->point[0])->PointGetNum(),
b = SS.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) {
Group *src = SS.GetGroup(opA); Group *src = SS.GetGroup(opA);
@ -138,7 +205,6 @@ void Group::GenerateShellAndMesh(void) {
} }
} }
runningMesh.Clear();
runningShell.Clear(); runningShell.Clear();
// If this group contributes no new mesh, then our running mesh is the // If this group contributes no new mesh, then our running mesh is the
@ -174,6 +240,7 @@ void Group::GenerateShellAndMesh(void) {
} }
done: done:
runningMesh.Clear();
runningShell.TriangulateInto(&runningMesh); runningShell.TriangulateInto(&runningMesh);
emphEdges.Clear(); emphEdges.Clear();
if(h.v == SS.GW.activeGroup.v && SS.edgeColor != 0) { if(h.v == SS.GW.activeGroup.v && SS.edgeColor != 0) {

View File

@ -222,6 +222,31 @@ intersects:
return cnt; return cnt;
} }
//-----------------------------------------------------------------------------
// Remove unnecessary edges: if two are anti-parallel then remove both, and if
// two are parallel then remove one.
//-----------------------------------------------------------------------------
void SEdgeList::CullExtraneousEdges(void) {
l.ClearTags();
int i, j;
for(i = 0; i < l.n; i++) {
SEdge *se = &(l.elem[i]);
for(j = i+1; j < l.n; j++) {
SEdge *set = &(l.elem[j]);
if((set->a).Equals(se->a) && (set->b).Equals(se->b)) {
// Two parallel edges exist; so keep only the first one.
set->tag = 1;
}
if((set->a).Equals(se->b) && (set->b).Equals(se->a)) {
// Two anti-parallel edges exist; so keep neither.
se->tag = 1;
set->tag = 1;
}
}
}
l.RemoveTagged();
}
void SContour::AddPoint(Vector p) { void SContour::AddPoint(Vector p) {
SPoint sp; SPoint sp;
sp.tag = 0; sp.tag = 0;

View File

@ -26,6 +26,7 @@ public:
bool AssembleContour(Vector first, Vector last, SContour *dest, bool AssembleContour(Vector first, Vector last, SContour *dest,
SEdge *errorAt, bool keepDir); SEdge *errorAt, bool keepDir);
int AnyEdgeCrossings(Vector a, Vector b, Vector *pi=NULL); int AnyEdgeCrossings(Vector a, Vector b, Vector *pi=NULL);
void CullExtraneousEdges(void);
}; };
class SPoint { class SPoint {

View File

@ -302,8 +302,12 @@ SSurface SSurface::MakeCopyTrimAgainst(SShell *agnst, SShell *parent,
SEdgeList sameNormal, oppositeNormal; SEdgeList sameNormal, oppositeNormal;
ZERO(&sameNormal); ZERO(&sameNormal);
ZERO(&oppositeNormal); ZERO(&oppositeNormal);
agnst->MakeCoincidentEdgesInto(&ret, true, &sameNormal); agnst->MakeCoincidentEdgesInto(&ret, true, &sameNormal, into);
agnst->MakeCoincidentEdgesInto(&ret, false, &oppositeNormal); agnst->MakeCoincidentEdgesInto(&ret, false, &oppositeNormal, into);
// and cull parallel or anti-parallel pairs, which may occur if multiple
// surfaces are coincident with ours
sameNormal.CullExtraneousEdges();
oppositeNormal.CullExtraneousEdges();
// and build the trees for quick in-polygon testing // and build the trees for quick in-polygon testing
SBspUv *sameBsp = SBspUv::From(&sameNormal); SBspUv *sameBsp = SBspUv::From(&sameNormal);
SBspUv *oppositeBsp = SBspUv::From(&oppositeNormal); SBspUv *oppositeBsp = SBspUv::From(&oppositeNormal);
@ -318,9 +322,9 @@ SSurface SSurface::MakeCopyTrimAgainst(SShell *agnst, SShell *parent,
for(sc = into->curve.First(); sc; sc = into->curve.NextAfter(sc)) { for(sc = into->curve.First(); sc; sc = into->curve.NextAfter(sc)) {
if(sc->source != SCurve::FROM_INTERSECTION) continue; if(sc->source != SCurve::FROM_INTERSECTION) continue;
if(opA) { if(opA) {
if(sc->surfB.v != h.v || sc->surfA.v != ss->h.v) continue;
} else {
if(sc->surfA.v != h.v || sc->surfB.v != ss->h.v) continue; if(sc->surfA.v != h.v || sc->surfB.v != ss->h.v) continue;
} else {
if(sc->surfB.v != h.v || sc->surfA.v != ss->h.v) continue;
} }
int i; int i;
@ -403,11 +407,6 @@ SSurface SSurface::MakeCopyTrimAgainst(SShell *agnst, SShell *parent,
if(KeepEdge(type, opA, tag)) { if(KeepEdge(type, opA, tag)) {
final.AddEdge(se->a, se->b, se->auxA, se->auxB); final.AddEdge(se->a, se->b, se->auxA, se->auxB);
} else {
if(I == 1) {
dbp("orig vs. shell: %d (l=%g)",
c_shell, ((se->b).Minus(se->a)).Magnitude());
}
} }
} }
@ -436,33 +435,23 @@ SSurface SSurface::MakeCopyTrimAgainst(SShell *agnst, SShell *parent,
} }
} }
// Cull extraneous edges; duplicates or anti-parallel pairs // Cull extraneous edges; duplicates or anti-parallel pairs. In particular,
final.l.ClearTags(); // we can get duplicate edges if our surface intersects the other shell
int i, j; // at an edge, so that both surfaces intersect coincident (and both
for(i = 0; i < final.l.n; i++) { // generate an intersection edge).
se = &(final.l.elem[i]); final.CullExtraneousEdges();
for(j = i+1; j < final.l.n; j++) {
SEdge *set = &(final.l.elem[j]);
if((set->a).Equals(se->a) && (set->b).Equals(se->b)) {
// Two parallel edges exist; so keep only the first one. This
// can happen if our surface intersects the shell at an edge,
// so that we get two copies of the intersection edge.
set->tag = 1;
}
if((set->a).Equals(se->b) && (set->b).Equals(se->a)) {
// Two anti-parallel edges exist; so keep neither.
se->tag = 1;
set->tag = 1;
}
}
}
final.l.RemoveTagged();
// if(I == 0) DEBUGEDGELIST(&final, &ret);
// Use our reassembled edges to trim the new surface. // Use our reassembled edges to trim the new surface.
ret.TrimFromEdgeList(&final); ret.TrimFromEdgeList(&final);
SPolygon poly;
ZERO(&poly);
final.l.ClearTags();
if(!final.AssemblePolygon(&poly, NULL, true)) {
DEBUGEDGELIST(&inter, &ret);
}
poly.Clear();
sameNormal.Clear(); sameNormal.Clear();
oppositeNormal.Clear(); oppositeNormal.Clear();
final.Clear(); final.Clear();
@ -478,20 +467,20 @@ void SShell::CopySurfacesTrimAgainst(SShell *against, SShell *into,
for(ss = surface.First(); ss; ss = surface.NextAfter(ss)) { for(ss = surface.First(); ss; ss = surface.NextAfter(ss)) {
SSurface ssn; SSurface ssn;
ssn = ss->MakeCopyTrimAgainst(against, this, into, type, opA); ssn = ss->MakeCopyTrimAgainst(against, this, into, type, opA);
into->surface.AddAndAssignId(&ssn); ss->newH = into->surface.AddAndAssignId(&ssn);
I++; I++;
} }
} }
void SShell::MakeIntersectionCurvesAgainst(SShell *agnst, SShell *into) { void SShell::MakeIntersectionCurvesAgainst(SShell *agnst, SShell *into) {
SSurface *sa; SSurface *sa;
for(sa = agnst->surface.First(); sa; sa = agnst->surface.NextAfter(sa)) { for(sa = surface.First(); sa; sa = surface.NextAfter(sa)) {
SSurface *sb; SSurface *sb;
for(sb = surface.First(); sb; sb = surface.NextAfter(sb)) { for(sb = agnst->surface.First(); sb; sb = agnst->surface.NextAfter(sb)){
// Intersect every surface from our shell against every surface // Intersect every surface from our shell against every surface
// from agnst; this will add zero or more curves to the curve // from agnst; this will add zero or more curves to the curve
// list for into. // list for into.
sa->IntersectAgainst(sb, agnst, this, into); sa->IntersectAgainst(sb, this, agnst, into);
} }
FLAG++; FLAG++;
} }
@ -517,17 +506,34 @@ void SShell::MakeFromBoolean(SShell *a, SShell *b, int type) {
// the surfaces in B (which is all of the intersection curves). // the surfaces in B (which is all of the intersection curves).
a->MakeIntersectionCurvesAgainst(b, this); a->MakeIntersectionCurvesAgainst(b, this);
I = 100;
if(b->surface.n == 0 || a->surface.n == 0) { if(b->surface.n == 0 || a->surface.n == 0) {
// Then trim and copy the surfaces // Then trim and copy the surfaces
a->CopySurfacesTrimAgainst(b, this, type, true); a->CopySurfacesTrimAgainst(b, this, type, true);
b->CopySurfacesTrimAgainst(a, this, type, false); b->CopySurfacesTrimAgainst(a, this, type, false);
} else { } else {
I = 0;
a->CopySurfacesTrimAgainst(b, this, type, true); a->CopySurfacesTrimAgainst(b, this, type, true);
b->CopySurfacesTrimAgainst(a, this, type, false); b->CopySurfacesTrimAgainst(a, this, type, false);
} }
// Now that we've copied the surfaces, we know their new hSurfaces, so
// rewrite the curves to refer to the surfaces by their handles in the
// result.
SCurve *sc;
for(sc = curve.First(); sc; sc = curve.NextAfter(sc)) {
if(sc->source == SCurve::FROM_A) {
sc->surfA = a->surface.FindById(sc->surfA)->newH;
sc->surfB = a->surface.FindById(sc->surfB)->newH;
} else if(sc->source == SCurve::FROM_B) {
sc->surfA = b->surface.FindById(sc->surfA)->newH;
sc->surfB = b->surface.FindById(sc->surfB)->newH;
} else if(sc->source == SCurve::FROM_INTERSECTION) {
sc->surfA = a->surface.FindById(sc->surfA)->newH;
sc->surfB = b->surface.FindById(sc->surfB)->newH;
} else {
oops();
}
}
// And clean up the piecewise linear things we made as a calculation aid // And clean up the piecewise linear things we made as a calculation aid
a->CleanupAfterBoolean(); a->CleanupAfterBoolean();
b->CleanupAfterBoolean(); b->CleanupAfterBoolean();
@ -682,6 +688,13 @@ int SBspUv::ClassifyPoint(Point2d p, Point2d eb) {
} }
int SBspUv::ClassifyEdge(Point2d ea, Point2d eb) { int SBspUv::ClassifyEdge(Point2d ea, Point2d eb) {
return ClassifyPoint((ea.Plus(eb)).ScaledBy(0.5), eb); int ret = ClassifyPoint((ea.Plus(eb)).ScaledBy(0.5), eb);
if(ret == EDGE_OTHER) {
// Perhaps the edge is tangent at its midpoint (and we screwed up
// somewhere earlier and failed to split it); try a different
// point on the edge.
ret = ClassifyPoint(ea.Plus((eb.Minus(ea)).ScaledBy(0.294)), eb);
}
return ret;
} }

View File

@ -144,15 +144,10 @@ Vector SBezier::PointAt(double t) {
} }
void SBezier::MakePwlInto(List<Vector> *l) { void SBezier::MakePwlInto(List<Vector> *l) {
MakePwlInto(l, Vector::From(0, 0, 0)); l->Add(&(ctrl[0]));
MakePwlWorker(l, 0.0, 1.0);
} }
void SBezier::MakePwlInto(List<Vector> *l, Vector offset) { void SBezier::MakePwlWorker(List<Vector> *l, double ta, double tb) {
Vector p = (ctrl[0]).Plus(offset);
l->Add(&p);
MakePwlWorker(l, 0.0, 1.0, offset);
}
void SBezier::MakePwlWorker(List<Vector> *l, double ta, double tb, Vector off) {
Vector pa = PointAt(ta); Vector pa = PointAt(ta);
Vector pb = PointAt(tb); Vector pb = PointAt(tb);
@ -169,12 +164,11 @@ void SBezier::MakePwlWorker(List<Vector> *l, double ta, double tb, Vector off) {
double step = 1.0/SS.maxSegments; double step = 1.0/SS.maxSegments;
if((tb - ta) < step || d < SS.ChordTolMm()) { if((tb - ta) < step || d < SS.ChordTolMm()) {
// A previous call has already added the beginning of our interval. // A previous call has already added the beginning of our interval.
pb = pb.Plus(off);
l->Add(&pb); l->Add(&pb);
} else { } else {
double tm = (ta + tb) / 2; double tm = (ta + tb) / 2;
MakePwlWorker(l, ta, tm, off); MakePwlWorker(l, ta, tm);
MakePwlWorker(l, tm, tb, off); MakePwlWorker(l, tm, tb);
} }
} }
@ -206,6 +200,18 @@ SBezier SBezier::TransformedBy(Vector t, Quaternion q) {
return ret; return ret;
} }
bool SBezier::Equals(SBezier *b) {
// We just test of identical degree and control points, even though two
// curves could still be coincident (even sharing endpoints).
if(deg != b->deg) return false;
int i;
for(i = 0; i <= deg; i++) {
if(!(ctrl[i]).Equals(b->ctrl[i])) return false;
if(fabs(weight[i] - b->weight[i]) > LENGTH_EPS) return false;
}
return true;
}
void SBezierList::Clear(void) { void SBezierList::Clear(void) {
l.Clear(); l.Clear();
} }
@ -375,6 +381,8 @@ SCurve SCurve::FromTransformationOf(SCurve *a, Vector t, Quaternion q) {
ret.h = a->h; ret.h = a->h;
ret.isExact = a->isExact; ret.isExact = a->isExact;
ret.exact = (a->exact).TransformedBy(t, q); ret.exact = (a->exact).TransformedBy(t, q);
ret.surfA = a->surfA;
ret.surfB = a->surfB;
Vector *p; Vector *p;
for(p = a->pts.First(); p; p = a->pts.NextAfter(p)) { for(p = a->pts.First(); p; p = a->pts.NextAfter(p)) {
@ -454,7 +462,7 @@ bool SSurface::IsExtrusion(SBezier *of, Vector *alongp) {
} }
bool SSurface::IsCylinder(Vector *center, Vector *axis, double *r, bool SSurface::IsCylinder(Vector *center, Vector *axis, double *r,
double *dtheta) Vector *start, Vector *finish)
{ {
SBezier sb; SBezier sb;
if(!IsExtrusion(&sb, axis)) return false; if(!IsExtrusion(&sb, axis)) return false;
@ -480,10 +488,13 @@ bool SSurface::IsCylinder(Vector *center, Vector *axis, double *r,
pb2 = (sb.ctrl[2]).Project2d(u, v).Minus(c2); pb2 = (sb.ctrl[2]).Project2d(u, v).Minus(c2);
double thetaa = atan2(pa2.y, pa2.x), // in fact always zero due to csys double thetaa = atan2(pa2.y, pa2.x), // in fact always zero due to csys
thetab = atan2(pb2.y, pb2.x); thetab = atan2(pb2.y, pb2.x),
*dtheta = WRAP_NOT_0(thetab - thetaa, 2*PI); dtheta = WRAP_NOT_0(thetab - thetaa, 2*PI);
if(fabs(sb.weight[1] - cos(*dtheta/2)) > LENGTH_EPS) return false; if(fabs(sb.weight[1] - cos(dtheta/2)) > LENGTH_EPS) return false;
*start = (sb.ctrl[0]).Minus(*center);
*finish = (sb.ctrl[2]).Minus(*center);
return true; return true;
} }
@ -743,11 +754,20 @@ void SSurface::GetAxisAlignedBounding(Vector *ptMax, Vector *ptMin) {
} }
} }
void SSurface::MakeEdgesInto(SShell *shell, SEdgeList *sel, bool asUv) { void SSurface::MakeEdgesInto(SShell *shell, SEdgeList *sel, bool asUv,
SShell *useCurvesFrom) {
STrimBy *stb; STrimBy *stb;
for(stb = trim.First(); stb; stb = trim.NextAfter(stb)) { for(stb = trim.First(); stb; stb = trim.NextAfter(stb)) {
SCurve *sc = shell->curve.FindById(stb->curve); SCurve *sc = shell->curve.FindById(stb->curve);
// We have the option to use the curves from another shell; this
// is relevant when generating the coincident edges while doing the
// Booleans, since the curves from the output shell will be split
// against any intersecting surfaces (and the originals aren't).
if(useCurvesFrom) {
sc = useCurvesFrom->curve.FindById(sc->newH);
}
Vector prev, prevuv, ptuv; Vector prev, prevuv, ptuv;
bool inCurve = false, empty = true; bool inCurve = false, empty = true;
double u = 0, v = 0; double u = 0, v = 0;
@ -783,9 +803,8 @@ void SSurface::MakeEdgesInto(SShell *shell, SEdgeList *sel, bool asUv) {
if(pt->Equals(stb->start)) inCurve = true; if(pt->Equals(stb->start)) inCurve = true;
if(pt->Equals(stb->finish)) inCurve = false; if(pt->Equals(stb->finish)) inCurve = false;
} }
if(inCurve || empty) { if(inCurve) dbp("trim was unterminated");
dbp("trim was empty or unterminated"); if(empty) dbp("trim was empty");
}
} }
} }
@ -910,13 +929,17 @@ void SShell::MakeFromExtrusionOf(SBezierLoopSet *sbls, Vector t0, Vector t1,
// Translate the curve by t0 and t1 to produce two trim curves // Translate the curve by t0 and t1 to produce two trim curves
SCurve sc; SCurve sc;
ZERO(&sc); ZERO(&sc);
sb->MakePwlInto(&(sc.pts), t0); sc.isExact = true;
sc.exact = sb->TransformedBy(t0, Quaternion::IDENTITY);
(sc.exact).MakePwlInto(&(sc.pts));
sc.surfA = hs0; sc.surfA = hs0;
sc.surfB = hsext; sc.surfB = hsext;
hSCurve hc0 = curve.AddAndAssignId(&sc); hSCurve hc0 = curve.AddAndAssignId(&sc);
ZERO(&sc); ZERO(&sc);
sb->MakePwlInto(&(sc.pts), t1); sc.isExact = true;
sc.exact = sb->TransformedBy(t1, Quaternion::IDENTITY);
(sc.exact).MakePwlInto(&(sc.pts));
sc.surfA = hs1; sc.surfA = hs1;
sc.surfB = hsext; sc.surfB = hsext;
hSCurve hc1 = curve.AddAndAssignId(&sc); hSCurve hc1 = curve.AddAndAssignId(&sc);
@ -936,10 +959,10 @@ void SShell::MakeFromExtrusionOf(SBezierLoopSet *sbls, Vector t0, Vector t1,
// And form the trim line // And form the trim line
Vector pt = sb->Finish(); Vector pt = sb->Finish();
Vector p0 = pt.Plus(t0), p1 = pt.Plus(t1);
ZERO(&sc); ZERO(&sc);
sc.pts.Add(&p0); sc.isExact = true;
sc.pts.Add(&p1); sc.exact = SBezier::From(pt.Plus(t0), pt.Plus(t1));
(sc.exact).MakePwlInto(&(sc.pts));
hSCurve hl = curve.AddAndAssignId(&sc); hSCurve hl = curve.AddAndAssignId(&sc);
// save this for later // save this for later
TrimLine tl; TrimLine tl;
@ -969,10 +992,7 @@ void SShell::MakeFromExtrusionOf(SBezierLoopSet *sbls, Vector t0, Vector t1,
} }
void SShell::MakeFromCopyOf(SShell *a) { void SShell::MakeFromCopyOf(SShell *a) {
Vector t = Vector::From(0, 0, 0); MakeFromTransformationOf(a, Vector::From(0, 0, 0), Quaternion::IDENTITY);
Quaternion q = Quaternion::From(1, 0, 0, 0);
MakeFromTransformationOf(a, t, q);
} }
void SShell::MakeFromTransformationOf(SShell *a, Vector t, Quaternion q) { void SShell::MakeFromTransformationOf(SShell *a, Vector t, Quaternion q) {

View File

@ -61,9 +61,9 @@ public:
Vector PointAt(double t); Vector PointAt(double t);
Vector Start(void); Vector Start(void);
Vector Finish(void); Vector Finish(void);
bool Equals(SBezier *b);
void MakePwlInto(List<Vector> *l); void MakePwlInto(List<Vector> *l);
void MakePwlInto(List<Vector> *l, Vector offset); void MakePwlWorker(List<Vector> *l, double ta, double tb);
void MakePwlWorker(List<Vector> *l, double ta, double tb, Vector offset);
void GetBoundingProjd(Vector u, Vector orig, double *umin, double *umax); void GetBoundingProjd(Vector u, Vector orig, double *umin, double *umax);
void Reverse(void); void Reverse(void);
@ -170,6 +170,10 @@ class SSurface {
public: public:
hSSurface h; hSSurface h;
// Same as newH for the curves; record what a surface gets renamed to
// when I copy things over.
hSSurface newH;
int color; int color;
DWORD face; DWORD face;
@ -222,10 +226,12 @@ public:
bool CoincidentWithPlane(Vector n, double d); bool CoincidentWithPlane(Vector n, double d);
bool CoincidentWith(SSurface *ss, bool sameNormal); bool CoincidentWith(SSurface *ss, bool sameNormal);
bool IsExtrusion(SBezier *of, Vector *along); bool IsExtrusion(SBezier *of, Vector *along);
bool IsCylinder(Vector *center, Vector *axis, double *r, double *dtheta); bool IsCylinder(Vector *center, Vector *axis, double *r,
Vector *start, Vector *finish);
void TriangulateInto(SShell *shell, SMesh *sm); void TriangulateInto(SShell *shell, SMesh *sm);
void MakeEdgesInto(SShell *shell, SEdgeList *sel, bool asUv); void MakeEdgesInto(SShell *shell, SEdgeList *sel, bool asUv,
SShell *useCurvesFrom=NULL);
void MakeClassifyingBsp(SShell *shell); void MakeClassifyingBsp(SShell *shell);
double ChordToleranceForEdge(Vector a, Vector b); double ChordToleranceForEdge(Vector a, Vector b);
@ -254,7 +260,7 @@ public:
void AllPointsIntersecting(Vector a, Vector b, List<SInter> *il, void AllPointsIntersecting(Vector a, Vector b, List<SInter> *il,
bool seg, bool trimmed); bool seg, bool trimmed);
void MakeCoincidentEdgesInto(SSurface *proto, bool sameNormal, void MakeCoincidentEdgesInto(SSurface *proto, bool sameNormal,
SEdgeList *el); SEdgeList *el, SShell *useCurvesFrom);
void CleanupAfterBoolean(void); void CleanupAfterBoolean(void);
static const int INSIDE = 100; static const int INSIDE = 100;

View File

@ -7,13 +7,47 @@ void SSurface::AddExactIntersectionCurve(SBezier *sb, SSurface *srfB,
{ {
SCurve sc; SCurve sc;
ZERO(&sc); ZERO(&sc);
// Important to keep the order of (surfA, surfB) consistent; when we later
// rewrite the identifiers, we rewrite surfA from A and surfB from B.
sc.surfA = h; sc.surfA = h;
sc.surfB = srfB->h; sc.surfB = srfB->h;
sb->MakePwlInto(&(sc.pts)); sc.exact = *sb;
sc.isExact = true;
// Now split the line where it intersects our existing surfaces // Now we have to piecewise linearize the curve. If there's already an
SCurve split = sc.MakeCopySplitAgainst(agnstA, agnstB, this, srfB); // identical curve in the shell, then follow that pwl exactly, otherwise
// calculate from scratch.
SCurve split, *existing = NULL, *se;
SBezier sbrev = *sb;
sbrev.Reverse();
bool backwards = false;
for(se = into->curve.First(); se; se = into->curve.NextAfter(se)) {
if(se->isExact) {
if(sb->Equals(&(se->exact))) {
existing = se;
break;
}
if(sbrev.Equals(&(se->exact))) {
existing = se;
backwards = true;
break;
}
}
}
if(existing) {
Vector *v;
for(v = existing->pts.First(); v; v = existing->pts.NextAfter(v)) {
sc.pts.Add(v);
}
if(backwards) sc.pts.Reverse();
split = sc;
ZERO(&sc);
} else {
sb->MakePwlInto(&(sc.pts));
// and split the line where it intersects our existing surfaces
split = sc.MakeCopySplitAgainst(agnstA, agnstB, this, srfB);
sc.Clear(); sc.Clear();
}
if(0 && sb->deg == 1) { if(0 && sb->deg == 1) {
dbp(" "); dbp(" ");
@ -26,6 +60,8 @@ void SSurface::AddExactIntersectionCurve(SBezier *sb, SSurface *srfB,
prev = v; prev = v;
} }
} }
// Nothing should be generating zero-len edges.
if((sb->Start()).Equals(sb->Finish())) oops();
split.source = SCurve::FROM_INTERSECTION; split.source = SCurve::FROM_INTERSECTION;
into->curve.AddAndAssignId(&split); into->curve.AddAndAssignId(&split);
@ -98,7 +134,7 @@ void SSurface::IntersectAgainst(SSurface *b, SShell *agnstA, SShell *agnstB,
} }
} }
if(tmax > tmin) { if(tmax > tmin + LENGTH_EPS) {
SBezier bezier = SBezier::From(p.Plus(dl.ScaledBy(tmin)), SBezier bezier = SBezier::From(p.Plus(dl.ScaledBy(tmin)),
p.Plus(dl.ScaledBy(tmax))); p.Plus(dl.ScaledBy(tmax)));
AddExactIntersectionCurve(&bezier, b, agnstA, agnstB, into); AddExactIntersectionCurve(&bezier, b, agnstA, agnstB, into);
@ -415,8 +451,8 @@ void SSurface::AllPointsIntersecting(Vector a, Vector b,
// All the intersections between the line and the surface; either special // All the intersections between the line and the surface; either special
// cases that we can quickly solve in closed form, or general numerical. // cases that we can quickly solve in closed form, or general numerical.
Vector center, axis; Vector center, axis, start, finish;
double radius, dtheta; double radius;
if(degm == 1 && degn == 1) { if(degm == 1 && degn == 1) {
// Against a plane, easy. // Against a plane, easy.
Vector n = NormalAt(0, 0).WithMagnitude(1); Vector n = NormalAt(0, 0).WithMagnitude(1);
@ -432,7 +468,7 @@ void SSurface::AllPointsIntersecting(Vector a, Vector b,
ClosestPointTo(p, &(inter.p.x), &(inter.p.y)); ClosestPointTo(p, &(inter.p.x), &(inter.p.y));
inters.Add(&inter); inters.Add(&inter);
} }
} else if(IsCylinder(&center, &axis, &radius, &dtheta) && 0) { } else if(IsCylinder(&center, &axis, &radius, &start, &finish) && 0) {
// XXX, cylinder is easy in closed form // XXX, cylinder is easy in closed form
} else { } else {
// General numerical solution by subdivision, fallback // General numerical solution by subdivision, fallback
@ -564,8 +600,9 @@ int SShell::ClassifyPoint(Vector p, Vector pout) {
// then our ray always lies on edge, and that's okay. Otherwise // then our ray always lies on edge, and that's okay. Otherwise
// try again in a different random direction. // try again in a different random direction.
if((edge_inters == 2) || !onEdge) break; if((edge_inters == 2) || !onEdge) break;
if(cnt++ > 20) { if(cnt++ > 5) {
dbp("can't find a ray that doesn't hit on edge!"); dbp("can't find a ray that doesn't hit on edge!");
dbp("on edge = %d, edge_inters = %d", onEdge, edge_inters);
break; break;
} }
} }
@ -626,12 +663,12 @@ bool SSurface::CoincidentWithPlane(Vector n, double d) {
// the prototype surface. // the prototype surface.
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
void SShell::MakeCoincidentEdgesInto(SSurface *proto, bool sameNormal, void SShell::MakeCoincidentEdgesInto(SSurface *proto, bool sameNormal,
SEdgeList *el) SEdgeList *el, SShell *useCurvesFrom)
{ {
SSurface *ss; SSurface *ss;
for(ss = surface.First(); ss; ss = surface.NextAfter(ss)) { for(ss = surface.First(); ss; ss = surface.NextAfter(ss)) {
if(proto->CoincidentWith(ss, sameNormal)) { if(proto->CoincidentWith(ss, sameNormal)) {
ss->MakeEdgesInto(this, el, false); ss->MakeEdgesInto(this, el, false, useCurvesFrom);
} }
} }

View File

@ -100,6 +100,8 @@ void MakeMatrix(double *mat, double a11, double a12, double a13, double a14,
mat[15] = a44; mat[15] = a44;
} }
const Quaternion Quaternion::IDENTITY = { 1, 0, 0, 0 };
Quaternion Quaternion::From(double w, double vx, double vy, double vz) { Quaternion Quaternion::From(double w, double vx, double vy, double vz) {
Quaternion q; Quaternion q;
q.w = w; q.w = w;

View File

@ -3,17 +3,16 @@ marching algorithm for surface intersection
surfaces of revolution (lathed) surfaces of revolution (lathed)
cylinder-line special cases cylinder-line special cases
exact boundaries when near pwl trim exact boundaries when near pwl trim
step and repeat rotate/translate
tangent intersections tangent intersections
short pwl edge avoidance short pwl edge avoidance
faces
take consistent pwl with coincident faces
exact curve export (at least for dxf) exact curve export (at least for dxf)
hidden line removal from mesh hidden line removal from mesh
line styles (color, thickness) line styles (color, thickness)
assembly
----- -----
loop detection loop detection
incremental regen of entities? incremental regen of entities?
IGES and STEP export