diff --git a/src/render/render.h b/src/render/render.h index a0b042b..60e1719 100644 --- a/src/render/render.h +++ b/src/render/render.h @@ -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(); diff --git a/src/render/rendercairo.cpp b/src/render/rendercairo.cpp index ed0accb..8270488 100644 --- a/src/render/rendercairo.cpp +++ b/src/render/rendercairo.cpp @@ -42,9 +42,10 @@ void CairoRenderer::SelectStroke(hStroke hcs) { current.hcs = hcs; RgbaColor color = stroke->color; - std::vector dashes = - StipplePatternDashes(stroke->stipplePattern, - stroke->StippleScalePx(camera)); + std::vector 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(), diff --git a/src/render/rendergl1.cpp b/src/render/rendergl1.cpp index d1d8c8c..f341d19 100644 --- a/src/render/rendergl1.cpp +++ b/src/render/rendergl1.cpp @@ -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 &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; } diff --git a/src/sketch.h b/src/sketch.h index f4f8e42..7316d0f 100644 --- a/src/sketch.h +++ b/src/sketch.h @@ -43,7 +43,8 @@ enum class StipplePattern : uint32_t { LAST = ZIGZAG }; -std::vector StipplePatternDashes(StipplePattern pattern, double scale); +const std::vector &StipplePatternDashes(StipplePattern pattern); +double StipplePatternLength(StipplePattern pattern); enum class Command : uint32_t; diff --git a/src/util.cpp b/src/util.cpp index f22fc1b..f12693b 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -1133,38 +1133,48 @@ bool BBox::Contains(const Point2d &p, double r) const { p.y <= (maxp.y + r); } -std::vector 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 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& SolveSpace::StipplePatternDashes(StipplePattern pattern) { + static bool initialized; + static std::vector 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 &dashes = StipplePatternDashes((StipplePattern)i); + double length = 0.0; + for(double dash : dashes) { + length += dash; + } + lengths[i] = length; + } + } + + return lengths[(size_t)pattern]; }