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
parent
47288e9a4c
commit
b37aba00e2
|
@ -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();
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
74
src/util.cpp
74
src/util.cpp
|
@ -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];
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue