Preserve stipple phase across separate piecewise linear segments.

This significantly increases visual clarity, especially for curves
with a low chord tolerance value.
This commit is contained in:
EvilSpirit 2016-10-13 23:43:13 +07:00 committed by whitequark
parent 47288e9a4c
commit b37aba00e2
5 changed files with 93 additions and 115 deletions

View File

@ -377,7 +377,7 @@ public:
void DoFatLine(const Vector &a, const Vector &b, double width); void DoFatLine(const Vector &a, const Vector &b, double width);
void DoLine(const Vector &a, const Vector &b, hStroke hcs); void DoLine(const Vector &a, const Vector &b, hStroke hcs);
void DoPoint(Vector p, double radius); void DoPoint(Vector p, double radius);
void DoStippledLine(const Vector &a, const Vector &b, hStroke hcs); void DoStippledLine(const Vector &a, const Vector &b, hStroke hcs, double phase = 0.0);
void UpdateProjection(bool flip = FLIP_FRAMEBUFFER); void UpdateProjection(bool flip = FLIP_FRAMEBUFFER);
void BeginFrame(); void BeginFrame();

View File

@ -42,9 +42,10 @@ void CairoRenderer::SelectStroke(hStroke hcs) {
current.hcs = hcs; current.hcs = hcs;
RgbaColor color = stroke->color; RgbaColor color = stroke->color;
std::vector<double> dashes = std::vector<double> dashes = StipplePatternDashes(stroke->stipplePattern);
StipplePatternDashes(stroke->stipplePattern, for(double &dash : dashes) {
stroke->StippleScalePx(camera)); dash *= stroke->StippleScalePx(camera);
}
cairo_set_line_width(context, stroke->WidthPx(camera)); cairo_set_line_width(context, stroke->WidthPx(camera));
cairo_set_dash(context, dashes.data(), dashes.size(), 0); cairo_set_dash(context, dashes.data(), dashes.size(), 0);
cairo_set_source_rgba(context, color.redF(), color.greenF(), color.blueF(), cairo_set_source_rgba(context, color.redF(), color.greenF(), color.blueF(),

View File

@ -354,91 +354,51 @@ void OpenGl1Renderer::DoPoint(Vector p, double d) {
} }
} }
void OpenGl1Renderer::DoStippledLine(const Vector &a, const Vector &b, hStroke hcs) { void OpenGl1Renderer::DoStippledLine(const Vector &a, const Vector &b, hStroke hcs, double phase) {
Stroke *stroke = SelectStroke(hcs); Stroke *stroke = SelectStroke(hcs);
const char *patternSeq; if(stroke->stipplePattern == StipplePattern::CONTINUOUS) {
switch(stroke->stipplePattern) { DoLine(a, b, hcs);
case StipplePattern::CONTINUOUS: DoLine(a, b, hcs); return; return;
case StipplePattern::SHORT_DASH: patternSeq = "- "; break; }
case StipplePattern::DASH: patternSeq = "- "; break;
case StipplePattern::LONG_DASH: patternSeq = "_ "; break; double scale = stroke->StippleScaleMm(camera);
case StipplePattern::DASH_DOT: patternSeq = "-."; break; const std::vector<double> &dashes = StipplePatternDashes(stroke->stipplePattern);
case StipplePattern::DASH_DOT_DOT: patternSeq = "-.."; break; double length = StipplePatternLength(stroke->stipplePattern) * scale;
case StipplePattern::DOT: patternSeq = "."; break;
case StipplePattern::FREEHAND: patternSeq = "~"; break; phase -= floor(phase / length) * length;
case StipplePattern::ZIGZAG: patternSeq = "~__"; break;
double curPhase = 0.0;
size_t curDash;
for(curDash = 0; curDash < dashes.size(); curDash++) {
curPhase += dashes[curDash] * scale;
if(phase < curPhase) break;
} }
Vector dir = b.Minus(a); Vector dir = b.Minus(a);
double len = dir.Magnitude(); double len = dir.Magnitude();
dir = dir.WithMagnitude(1.0); dir = dir.WithMagnitude(1.0);
const char *si = patternSeq; double cur = 0.0;
double end = len; Vector curPos = a;
double ss = stroke->StippleScaleMm(camera) / 2.0; double width = stroke->WidthMm(camera);
do {
double start = end;
switch(*si) {
case ' ':
end -= 1.0 * ss;
break;
case '-': double curDashLen = (curPhase - phase) / scale;
start = max(start - 0.5 * ss, 0.0); while(cur < len) {
end = max(start - 2.0 * ss, 0.0); double next = std::min(len, cur + curDashLen * scale);
if(start == end) break; Vector nextPos = curPos.Plus(dir.ScaledBy(next - cur));
DoLine(a.Plus(dir.ScaledBy(start)), a.Plus(dir.ScaledBy(end)), hcs); if(curDash % 2 == 0) {
end = max(end - 0.5 * ss, 0.0); if(curDashLen <= LENGTH_EPS) {
break; DoPoint(curPos, width);
} else {
case '_': DoLine(curPos, nextPos, hcs);
end = max(end - 4.0 * ss, 0.0);
DoLine(a.Plus(dir.ScaledBy(start)), a.Plus(dir.ScaledBy(end)), hcs);
break;
case '.':
end = max(end - 0.5 * ss, 0.0);
if(end == 0.0) break;
DoPoint(a.Plus(dir.ScaledBy(end)), stroke->WidthPx(camera));
end = max(end - 0.5 * ss, 0.0);
break;
case '~': {
Vector ab = b.Minus(a);
Vector gn = (camera.projRight).Cross(camera.projUp);
Vector abn = (ab.Cross(gn)).WithMagnitude(1);
abn = abn.Minus(gn.ScaledBy(gn.Dot(abn)));
double pws = 2.0 * stroke->width / camera.scale;
end = max(end - 0.5 * ss, 0.0);
Vector aa = a.Plus(dir.ScaledBy(start));
Vector bb = a.Plus(dir.ScaledBy(end))
.Plus(abn.ScaledBy(pws * (start - end) / (0.5 * ss)));
DoLine(aa, bb, hcs);
if(end == 0.0) break;
start = end;
end = max(end - 1.0 * ss, 0.0);
aa = a.Plus(dir.ScaledBy(end))
.Plus(abn.ScaledBy(pws))
.Minus(abn.ScaledBy(2.0 * pws * (start - end) / ss));
DoLine(bb, aa, hcs);
if(end == 0.0) break;
start = end;
end = max(end - 0.5 * ss, 0.0);
bb = a.Plus(dir.ScaledBy(end))
.Minus(abn.ScaledBy(pws))
.Plus(abn.ScaledBy(pws * (start - end) / (0.5 * ss)));
DoLine(aa, bb, hcs);
break;
} }
default: ssassert(false, "Unexpected stipple pattern element");
} }
if(*(++si) == 0) si = patternSeq; cur = next;
} while(end > 0.0); curPos = nextPos;
curDash++;
curDashLen = dashes[curDash % dashes.size()];
}
} }
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
@ -450,35 +410,41 @@ void OpenGl1Renderer::DrawLine(const Vector &a, const Vector &b, hStroke hcs) {
} }
void OpenGl1Renderer::DrawEdges(const SEdgeList &el, hStroke hcs) { void OpenGl1Renderer::DrawEdges(const SEdgeList &el, hStroke hcs) {
double phase = 0.0;
for(const SEdge *e = el.l.First(); e; e = el.l.NextAfter(e)) { for(const SEdge *e = el.l.First(); e; e = el.l.NextAfter(e)) {
DoStippledLine(e->a, e->b, hcs); DoStippledLine(e->a, e->b, hcs, phase);
phase += e->a.Minus(e->b).Magnitude();
} }
} }
void OpenGl1Renderer::DrawOutlines(const SOutlineList &ol, hStroke hcs, DrawOutlinesAs drawAs) { void OpenGl1Renderer::DrawOutlines(const SOutlineList &ol, hStroke hcs, DrawOutlinesAs drawAs) {
Vector projDir = camera.projRight.Cross(camera.projUp); Vector projDir = camera.projRight.Cross(camera.projUp);
double phase = 0.0;
switch(drawAs) { switch(drawAs) {
case DrawOutlinesAs::EMPHASIZED_AND_CONTOUR: case DrawOutlinesAs::EMPHASIZED_AND_CONTOUR:
for(const SOutline &o : ol.l) { for(const SOutline &o : ol.l) {
if(o.IsVisible(projDir) || o.tag != 0) { if(o.IsVisible(projDir) || o.tag != 0) {
DoStippledLine(o.a, o.b, hcs); DoStippledLine(o.a, o.b, hcs, phase);
} }
phase += o.a.Minus(o.b).Magnitude();
} }
break; break;
case DrawOutlinesAs::EMPHASIZED_WITHOUT_CONTOUR: case DrawOutlinesAs::EMPHASIZED_WITHOUT_CONTOUR:
for(const SOutline &o : ol.l) { for(const SOutline &o : ol.l) {
if(!o.IsVisible(projDir) && o.tag != 0) { if(!o.IsVisible(projDir) && o.tag != 0) {
DoStippledLine(o.a, o.b, hcs); DoStippledLine(o.a, o.b, hcs, phase);
} }
phase += o.a.Minus(o.b).Magnitude();
} }
break; break;
case DrawOutlinesAs::CONTOUR_ONLY: case DrawOutlinesAs::CONTOUR_ONLY:
for(const SOutline &o : ol.l) { for(const SOutline &o : ol.l) {
if(o.IsVisible(projDir)) { if(o.IsVisible(projDir)) {
DoStippledLine(o.a, o.b, hcs); DoStippledLine(o.a, o.b, hcs, phase);
} }
phase += o.a.Minus(o.b).Magnitude();
} }
break; break;
} }

View File

@ -43,7 +43,8 @@ enum class StipplePattern : uint32_t {
LAST = ZIGZAG LAST = ZIGZAG
}; };
std::vector<double> StipplePatternDashes(StipplePattern pattern, double scale); const std::vector<double> &StipplePatternDashes(StipplePattern pattern);
double StipplePatternLength(StipplePattern pattern);
enum class Command : uint32_t; enum class Command : uint32_t;

View File

@ -1133,38 +1133,48 @@ bool BBox::Contains(const Point2d &p, double r) const {
p.y <= (maxp.y + r); p.y <= (maxp.y + r);
} }
std::vector<double> SolveSpace::StipplePatternDashes(StipplePattern pattern, double scale) { const std::vector<double>& SolveSpace::StipplePatternDashes(StipplePattern pattern) {
// Inkscape ignores all elements that are exactly zero instead of drawing static bool initialized;
// them as dots. static std::vector<double> dashes[(size_t)StipplePattern::LAST + 1];
double zero = 1e-6; if(!initialized) {
// Inkscape ignores all elements that are exactly zero instead of drawing
std::vector<double> result; // them as dots, so set those to 1e-6.
switch(pattern) { dashes[(size_t)StipplePattern::CONTINUOUS] =
case StipplePattern::CONTINUOUS: {};
break; dashes[(size_t)StipplePattern::SHORT_DASH] =
case StipplePattern::SHORT_DASH: { 1.0, 2.0 };
result = { scale, scale * 2.0 }; dashes[(size_t)StipplePattern::DASH] =
break; { 1.0, 1.0 };
case StipplePattern::DASH: dashes[(size_t)StipplePattern::DASH_DOT] =
result = { scale, scale }; { 1.0, 0.5, 1e-6, 0.5 };
break; dashes[(size_t)StipplePattern::DASH_DOT_DOT] =
case StipplePattern::DASH_DOT: { 1.0, 0.5, 1e-6, 0.5, 0.5, 1e-6 };
result = { scale, scale * 0.5, zero, scale * 0.5 }; dashes[(size_t)StipplePattern::DOT] =
break; { 1e-6, 0.5 };
case StipplePattern::DASH_DOT_DOT: dashes[(size_t)StipplePattern::LONG_DASH] =
result = { scale, scale * 0.5, zero, scale * 0.5, scale * 0.5, zero }; { 2.0, 0.5 };
break; dashes[(size_t)StipplePattern::FREEHAND] =
case StipplePattern::DOT: { 1.0, 2.0 };
result = { zero, scale * 0.5 }; dashes[(size_t)StipplePattern::ZIGZAG] =
break; { 1.0, 2.0 };
case StipplePattern::LONG_DASH:
result = { scale * 2.0, scale * 0.5 };
break;
case StipplePattern::FREEHAND:
case StipplePattern::ZIGZAG:
ssassert(false, "Freehand and zigzag export not implemented");
} }
return result; return dashes[(size_t)pattern];
}
double SolveSpace::StipplePatternLength(StipplePattern pattern) {
static bool initialized;
static double lengths[(size_t)StipplePattern::LAST + 1];
if(!initialized) {
for(size_t i = 0; i < (size_t)StipplePattern::LAST; i++) {
const std::vector<double> &dashes = StipplePatternDashes((StipplePattern)i);
double length = 0.0;
for(double dash : dashes) {
length += dash;
}
lengths[i] = length;
}
}
return lengths[(size_t)pattern];
} }