From 91729a6c7e35fde292630979315356551b457316 Mon Sep 17 00:00:00 2001 From: apistol78 Date: Mon, 10 Jun 2024 14:20:48 +0200 Subject: [PATCH] Traktor: Smooth edges in graph control. --- code/Core/Math/Bezier3rd.cpp | 9 +++++ code/Core/Math/Bezier3rd.h | 4 +- code/Ui/Graph/Edge.cpp | 25 +++++++++--- code/Ui/Graph/GraphCanvas.cpp | 13 +++++++ code/Ui/Graph/GraphCanvas.h | 2 + code/Ui/Win32/CanvasDirect2DWin32.cpp | 56 ++++++++++++++++++++++++--- 6 files changed, 97 insertions(+), 12 deletions(-) diff --git a/code/Core/Math/Bezier3rd.cpp b/code/Core/Math/Bezier3rd.cpp index 6317aad21..7e2d48a7b 100644 --- a/code/Core/Math/Bezier3rd.cpp +++ b/code/Core/Math/Bezier3rd.cpp @@ -122,4 +122,13 @@ void Bezier3rd::approximate(float errorThreshold, int maxSubdivisions, AlignedVe approximateSubdivide(*this, errorThreshold, maxSubdivisions, outQuadratic); } +Bezier3rd Bezier3rd::fromCatmullRom(const Vector2& cp0, const Vector2& cp1, const Vector2& cp2, const Vector2& cp3, float tension) +{ + const Vector2 bcp0 = cp1; + const Vector2 bcp1 = cp1 + (cp2 - cp0) / (6.0f * tension); + const Vector2 bcp2 = cp2 - (cp3 - cp1) / (6.0f * tension); + const Vector2 bcp3 = cp2; + return Bezier3rd(bcp0, bcp1, bcp2, bcp3); +} + } diff --git a/code/Core/Math/Bezier3rd.h b/code/Core/Math/Bezier3rd.h index bb20904bc..06cb1c1b5 100644 --- a/code/Core/Math/Bezier3rd.h +++ b/code/Core/Math/Bezier3rd.h @@ -45,7 +45,7 @@ class T_DLLCLASS Bezier3rd float flatness() const; - /*! Check if curve is sufficently flat. + /*! Check if curve is sufficiently flat. * * \param tolerance Tolerance value as defined by PostScript. */ @@ -54,6 +54,8 @@ class T_DLLCLASS Bezier3rd void split(float t, Bezier3rd& outLeft, Bezier3rd& outRight) const; void approximate(float errorThreshold, int maxSubdivisions, AlignedVector< Bezier2nd >& outQuadratic) const; + + static Bezier3rd fromCatmullRom(const Vector2& cp0, const Vector2& cp1, const Vector2& cp2, const Vector2& cp3, float tension); }; } diff --git a/code/Ui/Graph/Edge.cpp b/code/Ui/Graph/Edge.cpp index 9b0cb9306..f79a74e1a 100644 --- a/code/Ui/Graph/Edge.cpp +++ b/code/Ui/Graph/Edge.cpp @@ -229,24 +229,37 @@ void Edge::paint(GraphControl* graph, GraphCanvas* canvas, const Size& offset, I const Point s = graph->pixel(m_source->getPosition()) + offset; const Point d = graph->pixel(m_destination->getPosition()) + offset; - calculateLinearSpline(graph, s, d, m_spline); + // calculateLinearSpline(graph, s, d, m_spline); + // canvas->drawLines(m_spline, graph->pixel(hot ? 4_ut : m_thickness)); + + int32_t dx = (d.x - s.x) / 4; + int32_t dy = (d.y - s.y) / 8; + + dx = std::max(dx, 30); + + m_spline.resize(0); + m_spline.push_back(Point(s.x - 10, s.y)); + m_spline.push_back(s); + m_spline.push_back(Point(s.x + dx, s.y + dy)); + m_spline.push_back(Point(d.x - dx, d.y - dy)); + m_spline.push_back(d); + m_spline.push_back(Point(d.x + 10, d.y)); + canvas->drawSpline(m_spline, graph->pixel(m_thickness)); #if defined(_DEBUG) - canvas->setBackground(Color4ub(255, 255, 255, 255)); + canvas->setBackground(Color4ub(255, 255, 255, 180)); for (const auto& p : m_spline) { canvas->fillRect( Rect( - Point(p.x - 1, p.y - 1), - Size(2, 2) + Point(p.x - 4, p.y - 4), + Size(8, 8) ) ); } canvas->setBackground(color); #endif - canvas->drawLines(m_spline, graph->pixel(hot ? 4_ut : m_thickness)); - const Point at = graph->pixel(m_destination->getPosition()) + offset; const Point arrow[] = { diff --git a/code/Ui/Graph/GraphCanvas.cpp b/code/Ui/Graph/GraphCanvas.cpp index 0c2969c7d..eb6ce798a 100644 --- a/code/Ui/Graph/GraphCanvas.cpp +++ b/code/Ui/Graph/GraphCanvas.cpp @@ -120,6 +120,19 @@ void GraphCanvas::drawCurve(const Point& start, const Point& control, const Poin m_canvas->setPenThickness(1); } +void GraphCanvas::drawSpline(const AlignedVector< Point >& pnts, int32_t thickness) +{ + std::vector< Point > tpnts; + tpnts.reserve(pnts.size()); + + for (const auto& pnt : pnts) + tpnts.push_back(pnt * m_scale); + + m_canvas->setPenThickness((int32_t)std::ceil(thickness * m_scale)); + m_canvas->drawSpline(tpnts); + m_canvas->setPenThickness(1); +} + void GraphCanvas::drawRect(const Rect& rc) { m_canvas->drawRect(rc * m_scale); diff --git a/code/Ui/Graph/GraphCanvas.h b/code/Ui/Graph/GraphCanvas.h index c1ff32101..4a59c860b 100644 --- a/code/Ui/Graph/GraphCanvas.h +++ b/code/Ui/Graph/GraphCanvas.h @@ -47,6 +47,8 @@ class GraphCanvas : public Object void drawCurve(const Point& start, const Point& control, const Point& end, int32_t thickness); + void drawSpline(const AlignedVector< Point >& pnts, int32_t thickness); + void drawRect(const Rect& rc); void fillRect(const Rect& rc); diff --git a/code/Ui/Win32/CanvasDirect2DWin32.cpp b/code/Ui/Win32/CanvasDirect2DWin32.cpp index 7f631fa5f..ff183ac70 100644 --- a/code/Ui/Win32/CanvasDirect2DWin32.cpp +++ b/code/Ui/Win32/CanvasDirect2DWin32.cpp @@ -10,6 +10,7 @@ #include #include "Core/Log/Log.h" +#include "Core/Math/Bezier3rd.h" #include "Core/Misc/String.h" #include "Core/Thread/Atomic.h" #include "Ui/Application.h" @@ -29,6 +30,11 @@ ComRef< ID2D1Factory > s_d2dFactory; ComRef< IDWriteFactory > s_dwFactory; int32_t s_instanceCount = 0; +Vector2 pnt2vec(const Point& pt) +{ + return Vector2(pt.x, pt.y); +} + } CanvasDirect2DWin32::CanvasDirect2DWin32() @@ -506,6 +512,50 @@ void CanvasDirect2DWin32::drawEllipticArc(int x, int y, int w, int h, float star void CanvasDirect2DWin32::drawSpline(const Point* pnts, int npnts) { + HRESULT hr; + + if (npnts < 4) + return; + + ComRef< ID2D1PathGeometry > d2dPathGeometry; + s_d2dFactory->CreatePathGeometry(&d2dPathGeometry.getAssign()); + + ComRef< ID2D1GeometrySink > d2dGeometrySink; + hr = d2dPathGeometry->Open(&d2dGeometrySink.getAssign()); + if (FAILED(hr)) + return; + + for (int32_t i = 0; i <= npnts - 4; ++i) + { + const Bezier3rd b = Bezier3rd::fromCatmullRom( + pnt2vec(pnts[i + 0]), + pnt2vec(pnts[i + 1]), + pnt2vec(pnts[i + 2]), + pnt2vec(pnts[i + 3]), + 1.0f + ); + + if (i == 0) + { + d2dGeometrySink->BeginFigure( + D2D1::Point2F(b.cp0.x, b.cp0.y), + D2D1_FIGURE_BEGIN_HOLLOW + ); + } + + D2D1_BEZIER_SEGMENT bs; + bs.point1 = D2D1::Point2F(b.cp1.x, b.cp1.y); + bs.point2 = D2D1::Point2F(b.cp2.x, b.cp2.y); + bs.point3 = D2D1::Point2F(b.cp3.x, b.cp3.y); + d2dGeometrySink->AddBezier(&bs); + } + + d2dGeometrySink->EndFigure(D2D1_FIGURE_END_OPEN); + d2dGeometrySink->Close(); + + m_d2dRenderTarget->SetAntialiasMode(D2D1_ANTIALIAS_MODE_PER_PRIMITIVE); + m_d2dRenderTarget->DrawGeometry(d2dPathGeometry, m_d2dForegroundBrush, m_strokeWidth); + m_d2dRenderTarget->SetAntialiasMode(D2D1_ANTIALIAS_MODE_ALIASED); } void CanvasDirect2DWin32::fillRect(const Rect& rc) @@ -630,11 +680,7 @@ void CanvasDirect2DWin32::drawPolygon(const Point* pnts, int npnts) d2dGeometrySink->Close(); m_d2dRenderTarget->SetAntialiasMode(D2D1_ANTIALIAS_MODE_PER_PRIMITIVE); - m_d2dRenderTarget->DrawGeometry( - d2dPathGeometry, - m_d2dForegroundBrush, - m_strokeWidth - ); + m_d2dRenderTarget->DrawGeometry(d2dPathGeometry, m_d2dForegroundBrush, m_strokeWidth); m_d2dRenderTarget->SetAntialiasMode(D2D1_ANTIALIAS_MODE_ALIASED); }