Preserve stipple phase across separate piecewise linear segments.

This significantly increases visual clarity, especially for curves
with a low chord tolerance value.
pull/97/head
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 DoLine(const Vector &a, const Vector &b, hStroke hcs);
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 BeginFrame();

View File

@ -42,9 +42,10 @@ void CairoRenderer::SelectStroke(hStroke hcs) {
current.hcs = hcs;
RgbaColor color = stroke->color;
std::vector<double> dashes =
StipplePatternDashes(stroke->stipplePattern,
stroke->StippleScalePx(camera));
std::vector<double> dashes = StipplePatternDashes(stroke->stipplePattern);
for(double &dash : dashes) {
dash *= stroke->StippleScalePx(camera);
}
cairo_set_line_width(context, stroke->WidthPx(camera));
cairo_set_dash(context, dashes.data(), dashes.size(), 0);
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);
const char *patternSeq;
switch(stroke->stipplePattern) {
case StipplePattern::CONTINUOUS: DoLine(a, b, hcs); return;
case StipplePattern::SHORT_DASH: patternSeq = "- "; break;
case StipplePattern::DASH: patternSeq = "- "; break;
case StipplePattern::LONG_DASH: patternSeq = "_ "; break;
case StipplePattern::DASH_DOT: patternSeq = "-."; break;
case StipplePattern::DASH_DOT_DOT: patternSeq = "-.."; break;
case StipplePattern::DOT: patternSeq = "."; break;
case StipplePattern::FREEHAND: patternSeq = "~"; break;
case StipplePattern::ZIGZAG: patternSeq = "~__"; break;
if(stroke->stipplePattern == StipplePattern::CONTINUOUS) {
DoLine(a, b, hcs);
return;
}
double scale = stroke->StippleScaleMm(camera);
const std::vector<double> &dashes = StipplePatternDashes(stroke->stipplePattern);
double length = StipplePatternLength(stroke->stipplePattern) * scale;
phase -= floor(phase / length) * length;
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);
double len = dir.Magnitude();
dir = dir.WithMagnitude(1.0);
const char *si = patternSeq;
double end = len;
double ss = stroke->StippleScaleMm(camera) / 2.0;
do {
double start = end;
switch(*si) {
case ' ':
end -= 1.0 * ss;
break;
double cur = 0.0;
Vector curPos = a;
double width = stroke->WidthMm(camera);
case '-':
start = max(start - 0.5 * ss, 0.0);
end = max(start - 2.0 * ss, 0.0);
if(start == end) break;
DoLine(a.Plus(dir.ScaledBy(start)), a.Plus(dir.ScaledBy(end)), hcs);
end = max(end - 0.5 * ss, 0.0);
break;
case '_':
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;
double curDashLen = (curPhase - phase) / scale;
while(cur < len) {
double next = std::min(len, cur + curDashLen * scale);
Vector nextPos = curPos.Plus(dir.ScaledBy(next - cur));
if(curDash % 2 == 0) {
if(curDashLen <= LENGTH_EPS) {
DoPoint(curPos, width);
} else {
DoLine(curPos, nextPos, hcs);
}
default: ssassert(false, "Unexpected stipple pattern element");
}
if(*(++si) == 0) si = patternSeq;
} while(end > 0.0);
cur = next;
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) {
double phase = 0.0;
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) {
Vector projDir = camera.projRight.Cross(camera.projUp);
double phase = 0.0;
switch(drawAs) {
case DrawOutlinesAs::EMPHASIZED_AND_CONTOUR:
for(const SOutline &o : ol.l) {
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;
case DrawOutlinesAs::EMPHASIZED_WITHOUT_CONTOUR:
for(const SOutline &o : ol.l) {
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;
case DrawOutlinesAs::CONTOUR_ONLY:
for(const SOutline &o : ol.l) {
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;
}

View File

@ -43,7 +43,8 @@ enum class StipplePattern : uint32_t {
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;

View File

@ -1133,38 +1133,48 @@ bool BBox::Contains(const Point2d &p, double r) const {
p.y <= (maxp.y + r);
}
std::vector<double> SolveSpace::StipplePatternDashes(StipplePattern pattern, double scale) {
// Inkscape ignores all elements that are exactly zero instead of drawing
// them as dots.
double zero = 1e-6;
std::vector<double> result;
switch(pattern) {
case StipplePattern::CONTINUOUS:
break;
case StipplePattern::SHORT_DASH:
result = { scale, scale * 2.0 };
break;
case StipplePattern::DASH:
result = { scale, scale };
break;
case StipplePattern::DASH_DOT:
result = { scale, scale * 0.5, zero, scale * 0.5 };
break;
case StipplePattern::DASH_DOT_DOT:
result = { scale, scale * 0.5, zero, scale * 0.5, scale * 0.5, zero };
break;
case StipplePattern::DOT:
result = { zero, scale * 0.5 };
break;
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");
const std::vector<double>& SolveSpace::StipplePatternDashes(StipplePattern pattern) {
static bool initialized;
static std::vector<double> dashes[(size_t)StipplePattern::LAST + 1];
if(!initialized) {
// Inkscape ignores all elements that are exactly zero instead of drawing
// them as dots, so set those to 1e-6.
dashes[(size_t)StipplePattern::CONTINUOUS] =
{};
dashes[(size_t)StipplePattern::SHORT_DASH] =
{ 1.0, 2.0 };
dashes[(size_t)StipplePattern::DASH] =
{ 1.0, 1.0 };
dashes[(size_t)StipplePattern::DASH_DOT] =
{ 1.0, 0.5, 1e-6, 0.5 };
dashes[(size_t)StipplePattern::DASH_DOT_DOT] =
{ 1.0, 0.5, 1e-6, 0.5, 0.5, 1e-6 };
dashes[(size_t)StipplePattern::DOT] =
{ 1e-6, 0.5 };
dashes[(size_t)StipplePattern::LONG_DASH] =
{ 2.0, 0.5 };
dashes[(size_t)StipplePattern::FREEHAND] =
{ 1.0, 2.0 };
dashes[(size_t)StipplePattern::ZIGZAG] =
{ 1.0, 2.0 };
}
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];
}