Better helical triangulation - issue 489.

Resolve issue #489 helix has stairsteps.
Force helix axis line to 8 segments.
Grid triangulation to use a minimum of 4 segments for degree>1.
Adds twist dependence for grid triangulation with degree=1.
Added a max_dt parameter for PWL creation and use that for helical edges.
pull/648/head
phkahler 2020-07-01 16:16:27 -04:00
parent 873085edbd
commit 9802b5d1ee
4 changed files with 87 additions and 40 deletions

View File

@ -173,18 +173,18 @@ void SBezier::SplitAt(double t, SBezier *bef, SBezier *aft) const {
}
}
void SBezier::MakePwlInto(SEdgeList *sel, double chordTol) const {
void SBezier::MakePwlInto(SEdgeList *sel, double chordTol, double max_dt) const {
List<Vector> lv = {};
MakePwlInto(&lv, chordTol);
MakePwlInto(&lv, chordTol, max_dt);
int i;
for(i = 1; i < lv.n; i++) {
sel->AddEdge(lv[i-1], lv[i]);
}
lv.Clear();
}
void SBezier::MakePwlInto(List<SCurvePt> *l, double chordTol) const {
void SBezier::MakePwlInto(List<SCurvePt> *l, double chordTol, double max_dt) const {
List<Vector> lv = {};
MakePwlInto(&lv, chordTol);
MakePwlInto(&lv, chordTol, max_dt);
int i;
for(i = 0; i < lv.n; i++) {
SCurvePt scpt;
@ -195,32 +195,42 @@ void SBezier::MakePwlInto(List<SCurvePt> *l, double chordTol) const {
}
lv.Clear();
}
void SBezier::MakePwlInto(SContour *sc, double chordTol) const {
void SBezier::MakePwlInto(SContour *sc, double chordTol, double max_dt) const {
List<Vector> lv = {};
MakePwlInto(&lv, chordTol);
MakePwlInto(&lv, chordTol, max_dt);
int i;
for(i = 0; i < lv.n; i++) {
sc->AddPoint(lv[i]);
}
lv.Clear();
}
void SBezier::MakePwlInto(List<Vector> *l, double chordTol) const {
//--------------------------------------------------------------------------------------
// all variants of MakePwlInto come here. Split a rational Bezier into Piecewise Linear
// segments that don't deviate from the actual curve by more than the chordTol distance.
// max_dt allows to force curves to be split into spans of no more than a certain
// length based on t-parameter. RemoveShortSegments() may delete points when dt <= 0.1
//--------------------------------------------------------------------------------------
void SBezier::MakePwlInto(List<Vector> *l, double chordTol, double max_dt) const {
if(EXACT(chordTol == 0)) {
// Use the default chord tolerance.
chordTol = SS.ChordTolMm();
}
// Never do fewer than three intermediate points for curves; people seem to get
// unhappy when their circles turn into squares, but maybe less
// unhappy with octagons. Now 16-gons.
if (EXACT(max_dt == 0.0)) {
max_dt = (deg == 1) ? 1.0 : 0.25;
}
l->Add(&(ctrl[0]));
if(deg == 1) {
// don't split first degee (lines) unless asked to by the caller via max_dt
if((deg == 1) && (max_dt >= 1.0)) {
l->Add(&(ctrl[1]));
} else {
// Never do fewer than one intermediate point; people seem to get
// unhappy when their circles turn into squares, but maybe less
// unhappy with octagons.
MakePwlInitialWorker(l, 0.0, 0.5, chordTol);
MakePwlInitialWorker(l, 0.5, 1.0, chordTol);
MakePwlInitialWorker(l, 0.0, 0.5, chordTol, max_dt);
MakePwlInitialWorker(l, 0.5, 1.0, chordTol, max_dt);
}
}
void SBezier::MakePwlWorker(List<Vector> *l, double ta, double tb, double chordTol) const
void SBezier::MakePwlWorker(List<Vector> *l, double ta, double tb, double chordTol, double max_dt) const
{
Vector pa = PointAt(ta);
Vector pb = PointAt(tb);
@ -229,16 +239,16 @@ void SBezier::MakePwlWorker(List<Vector> *l, double ta, double tb, double chordT
double d = pm.DistanceToLine(pa, pb.Minus(pa));
double step = 1.0/SS.GetMaxSegments();
if((tb - ta) < step || d < chordTol) {
if(((tb - ta) < step || d < chordTol) && ((tb-ta) <= max_dt) ) {
// A previous call has already added the beginning of our interval.
l->Add(&pb);
} else {
double tm = (ta + tb) / 2;
MakePwlWorker(l, ta, tm, chordTol);
MakePwlWorker(l, tm, tb, chordTol);
MakePwlWorker(l, ta, tm, chordTol, max_dt);
MakePwlWorker(l, tm, tb, chordTol, max_dt);
}
}
void SBezier::MakePwlInitialWorker(List<Vector> *l, double ta, double tb, double chordTol) const
void SBezier::MakePwlInitialWorker(List<Vector> *l, double ta, double tb, double chordTol, double max_dt) const
{
Vector pa = PointAt(ta);
Vector pb = PointAt(tb);
@ -259,13 +269,13 @@ void SBezier::MakePwlInitialWorker(List<Vector> *l, double ta, double tb, double
});
double step = 1.0/SS.GetMaxSegments();
if( ((tb - ta) < step || d < chordTol) && ((tb-ta) < 0.2) ) {
if( ((tb - ta) < step || d < chordTol) && ((tb-ta) <= max_dt) ) {
// A previous call has already added the beginning of our interval.
l->Add(&pb);
} else {
double tm = (ta + tb) / 2;
MakePwlWorker(l, ta, tm, chordTol);
MakePwlWorker(l, tm, tb, chordTol);
MakePwlWorker(l, ta, tm, chordTol, max_dt);
MakePwlWorker(l, tm, tb, chordTol, max_dt);
}
}

View File

@ -746,7 +746,9 @@ void SShell::MakeFromHelicalRevolutionOf(SBezierLoopSet *sbls, Vector pt, Vector
sc = {};
sc.isExact = true;
sc.exact = sb->TransformedBy(ts, qs, 1.0);
(sc.exact).MakePwlInto(&(sc.pts));
double max_dt = 0.5;
if (sc.exact.deg > 1) max_dt = 0.25;
(sc.exact).MakePwlInto(&(sc.pts), 0.0, max_dt);
// the surfaces already exist so trim with this curve
if(j < sections) {
@ -771,7 +773,9 @@ void SShell::MakeFromHelicalRevolutionOf(SBezierLoopSet *sbls, Vector pt, Vector
sc = {};
sc.isExact = true;
sc.exact = sb->TransformedBy(ts, qs, 1.0);
(sc.exact).MakePwlInto(&(sc.pts));
double max_dt = 0.5;
if (sc.exact.deg > 1) max_dt = 0.25;
(sc.exact).MakePwlInto(&(sc.pts), 0.0, max_dt);
sc.surfA = hs1; // end cap
sc.surfB = hs0; // staring cap
hSCurve hcb = curve.AddAndAssignId(&sc);
@ -793,7 +797,9 @@ void SShell::MakeFromHelicalRevolutionOf(SBezierLoopSet *sbls, Vector pt, Vector
sc.isExact = true;
sc.exact = SBezier::From(ss->ctrl[0][0], ss->ctrl[0][1], ss->ctrl[0][2]);
sc.exact.weight[1] = ss->weight[0][1];
(sc.exact).MakePwlInto(&(sc.pts));
double max_dt = 0.5;
if (sc.exact.deg > 1) max_dt = 0.125;
(sc.exact).MakePwlInto(&(sc.pts), 0.0, max_dt);
sc.surfA = revs[j];
sc.surfB = revsp[j];

View File

@ -92,12 +92,12 @@ public:
Vector Start() const;
Vector Finish() const;
bool Equals(SBezier *b) const;
void MakePwlInto(SEdgeList *sel, double chordTol=0) const;
void MakePwlInto(List<SCurvePt> *l, double chordTol=0) const;
void MakePwlInto(SContour *sc, double chordTol=0) const;
void MakePwlInto(List<Vector> *l, double chordTol=0) const;
void MakePwlWorker(List<Vector> *l, double ta, double tb, double chordTol) const;
void MakePwlInitialWorker(List<Vector> *l, double ta, double tb, double chordTol) const;
void MakePwlInto(SEdgeList *sel, double chordTol=0, double max_dt=0.0) const;
void MakePwlInto(List<SCurvePt> *l, double chordTol=0, double max_dt=0.0) const;
void MakePwlInto(SContour *sc, double chordTol=0, double max_dt=0.0) const;
void MakePwlInto(List<Vector> *l, double chordTol=0, double max_dt=0.0) const;
void MakePwlWorker(List<Vector> *l, double ta, double tb, double chordTol, double max_dt) const;
void MakePwlInitialWorker(List<Vector> *l, double ta, double tb, double chordTol, double max_dt) const;
void MakeNonrationalCubicInto(SBezierList *bl, double tolerance, int depth = 0) const;
void AllIntersectionsWith(const SBezier *sbb, SPointList *spl) const;
@ -362,8 +362,9 @@ public:
void MakeClassifyingBsp(SShell *shell, SShell *useCurvesFrom);
double ChordToleranceForEdge(Vector a, Vector b) const;
void MakeTriangulationGridInto(List<double> *l, double vs, double vf,
bool swapped) const;
bool swapped, int depth) const;
Vector PointAtMaybeSwapped(double u, double v, bool swapped) const;
Vector NormalAtMaybeSwapped(double u, double v, bool swapped) const;
void Reverse();
void Clear();

View File

@ -382,13 +382,24 @@ Vector SSurface::PointAtMaybeSwapped(double u, double v, bool swapped) const {
}
}
Vector SSurface::NormalAtMaybeSwapped(double u, double v, bool swapped) const {
Vector du, dv;
if(swapped) {
TangentsAt(v, u, &dv, &du);
} else {
TangentsAt(u, v, &du, &dv);
}
return du.Cross(dv).WithMagnitude(1.0);
}
void SSurface::MakeTriangulationGridInto(List<double> *l, double vs, double vf,
bool swapped) const
bool swapped, int depth) const
{
double worst = 0;
// Try piecewise linearizing four curves, at u = 0, 1/3, 2/3, 1; choose
// the worst chord tolerance of any of those.
double worst_twist = 1.0;
int i;
for(i = 0; i <= 3; i++) {
double u = i/3.0;
@ -405,16 +416,24 @@ void SSurface::MakeTriangulationGridInto(List<double> *l, double vs, double vf,
Vector pm1 = PointAtMaybeSwapped(u, vm1, swapped),
pm2 = PointAtMaybeSwapped(u, vm2, swapped);
// 0.999 is about 2.5 degrees of twist over the middle 1/3 V-span.
// we don't check at the ends because the derivative may not be valid there.
double twist = 1.0;
if (degm == 1) twist = NormalAtMaybeSwapped(u, vm1, swapped).Dot(
NormalAtMaybeSwapped(u, vm2, swapped) );
if (twist < worst_twist) worst_twist = twist;
worst = max(worst, pm1.DistanceToLine(ps, pf.Minus(ps)));
worst = max(worst, pm2.DistanceToLine(ps, pf.Minus(ps)));
}
double step = 1.0/SS.GetMaxSegments();
if((vf - vs) < step || worst < SS.ChordTolMm()) {
if( ((vf - vs) < step || worst < SS.ChordTolMm())
&& ((worst_twist > 0.999) || (depth > 4)) ) {
l->Add(&vf);
} else {
MakeTriangulationGridInto(l, vs, (vs+vf)/2, swapped);
MakeTriangulationGridInto(l, (vs+vf)/2, vf, swapped);
MakeTriangulationGridInto(l, vs, (vs+vf)/2, swapped, depth+1);
MakeTriangulationGridInto(l, (vs+vf)/2, vf, swapped, depth+1);
}
}
@ -432,11 +451,22 @@ void SPolygon::UvGridTriangulateInto(SMesh *mesh, SSurface *srf) {
List<double> li, lj;
li = {};
lj = {};
double v = 0;
li.Add(&v);
srf->MakeTriangulationGridInto(&li, 0, 1, /*swapped=*/true);
lj.Add(&v);
srf->MakeTriangulationGridInto(&lj, 0, 1, /*swapped=*/false);
double v[5] = {0.0, 0.25, 0.5, 0.75, 1.0};
li.Add(&v[0]);
srf->MakeTriangulationGridInto(&li, 0, 1, /*swapped=*/true, 0);
lj.Add(&v[0]);
srf->MakeTriangulationGridInto(&lj, 0, 1, /*swapped=*/false, 0);
// force 2nd order grid to have at least 4 segments in each direction
if ((li.n < 5) && (srf->degm>1)) { // 4 segments minimun
li.Clear();
li.Add(&v[0]);li.Add(&v[1]);li.Add(&v[2]);li.Add(&v[3]);li.Add(&v[4]);
}
if ((lj.n < 5) && (srf->degn>1)) { // 4 segments minimun
lj.Clear();
lj.Add(&v[0]);lj.Add(&v[1]);lj.Add(&v[2]);lj.Add(&v[3]);lj.Add(&v[4]);
}
if ((li.n > 3) && (lj.n > 3)) {
// Now iterate over each quad in the grid. If it's outside the polygon,
// or if it intersects the polygon, then we discard it. Otherwise we