From 8543099fcd829a3e157d66e4e6e6bdad31eea585 Mon Sep 17 00:00:00 2001 From: Sergii Liebodkin Date: Thu, 26 Jun 2025 18:06:19 +0300 Subject: [PATCH] common: generalize bezier math added new functionality for bezier curves: 1. generate bezier curve as an arc 2. estimate the number of points for optimal tessellation 3. check that bezier curve is close to a straight line This functionality is useful for gl/wg renderers --- src/common/tvgMath.cpp | 41 +++++++ src/common/tvgMath.h | 33 +++++- src/renderer/gl_engine/tvgGlCommon.h | 5 - src/renderer/gl_engine/tvgGlRenderer.cpp | 2 +- src/renderer/gl_engine/tvgGlTessellator.cpp | 118 +++----------------- 5 files changed, 91 insertions(+), 108 deletions(-) diff --git a/src/common/tvgMath.cpp b/src/common/tvgMath.cpp index 511aa2dc..0b510556 100644 --- a/src/common/tvgMath.cpp +++ b/src/common/tvgMath.cpp @@ -293,6 +293,21 @@ void Line::split(float at, Line& left, Line& right) const } +Bezier::Bezier(const Point& st, const Point& ed, float radius) +{ + // Calculate the angle between the start and end points + auto angle = tvg::atan2(ed.y - st.y, ed.x - st.x); + + // Calculate the control points of the cubic bezier curve + auto c = radius * PATH_KAPPA; // c = radius * (4/3) * tan(pi/8) + + start = {st.x, st.y}; + ctrl1 = {st.x + radius * cos(angle), st.y + radius * sin(angle)}; + ctrl2 = {ed.x - c * cos(angle), ed.y - c * sin(angle)}; + end = {ed.x, ed.y}; +} + + void Bezier::split(Bezier& left, Bezier& right) const { auto c = (ctrl1.x + ctrl2.x) * 0.5f; @@ -449,5 +464,31 @@ void Bezier::bounds(Point& min, Point& max) const findMinMax(start.y, ctrl1.y, ctrl2.y, end.y, min.y, max.y); } +bool Bezier::flatten() const +{ + float diff1_x = fabsf((ctrl1.x * 3.f) - (start.x * 2.f) - end.x); + float diff1_y = fabsf((ctrl1.y * 3.f) - (start.y * 2.f) - end.y); + float diff2_x = fabsf((ctrl2.x * 3.f) - (end.x * 2.f) - start.x); + float diff2_y = fabsf((ctrl2.y * 3.f) - (end.y * 2.f) - start.y); + if (diff1_x < diff2_x) diff1_x = diff2_x; + if (diff1_y < diff2_y) diff1_y = diff2_y; + return (diff1_x + diff1_y <= 0.5f); +} + + +uint32_t Bezier::segments() const +{ + if (flatten()) return 1; + Bezier left, right; + split(left, right); + return left.segments() + right.segments(); +} + + +Bezier Bezier::operator*(const Matrix& m) +{ + return Bezier{start * m, ctrl1* m, ctrl2 * m, end * m}; +} + } diff --git a/src/common/tvgMath.h b/src/common/tvgMath.h index 26df7c24..a6f4f763 100644 --- a/src/common/tvgMath.h +++ b/src/common/tvgMath.h @@ -121,6 +121,12 @@ static inline constexpr const Matrix identity() } +static inline float scaling(const Matrix& m) +{ + return sqrtf(m.e11 * m.e11 + m.e21 * m.e21); +} + + static inline void scale(Matrix* m, const Point& p) { m->e11 *= p.x; @@ -330,6 +336,21 @@ static inline Point operator-(const Point& a) return {-a.x, -a.y}; } +enum class Orientation +{ + Linear = 0, + Clockwise, + CounterClockwise, +}; + + +static inline Orientation orientation(const Point& p1, const Point& p2, const Point& p3) +{ + auto val = cross(p2 - p1, p3 - p1); + if (zero(val)) return Orientation::Linear; + else return val > 0 ? Orientation::Clockwise : Orientation::CounterClockwise; +} + static inline void log(const Point& pt) { @@ -362,6 +383,13 @@ struct Bezier Point ctrl2; Point end; + Bezier() {} + Bezier(const Point& p0, const Point& p1, const Point& p2, const Point& p3): + start(p0), ctrl1(p1), ctrl2(p2), end(p3) {} + // Constructor that approximates a quarter-circle segment of arc between 'start' and 'end' points + // using a cubic Bezier curve with a given 'radius'. + Bezier(const Point& start, const Point& end, float radius); + void split(float t, Bezier& left); void split(Bezier& left, Bezier& right) const; void split(float at, Bezier& left, Bezier& right) const; @@ -372,8 +400,11 @@ struct Bezier Point at(float t) const; float angle(float t) const; void bounds(Point& min, Point& max) const; -}; + bool flatten() const; + uint32_t segments() const; + Bezier operator*(const Matrix& m); +}; /************************************************************************/ /* Geometry functions */ diff --git a/src/renderer/gl_engine/tvgGlCommon.h b/src/renderer/gl_engine/tvgGlCommon.h index f073dc50..547d69ce 100644 --- a/src/renderer/gl_engine/tvgGlCommon.h +++ b/src/renderer/gl_engine/tvgGlCommon.h @@ -81,11 +81,6 @@ -static inline float getScaleFactor(const Matrix& m) -{ - return sqrtf(m.e11 * m.e11 + m.e21 * m.e21); -} - enum class GlStencilMode { None, FillNonZero, diff --git a/src/renderer/gl_engine/tvgGlRenderer.cpp b/src/renderer/gl_engine/tvgGlRenderer.cpp index 72601efe..3111277b 100644 --- a/src/renderer/gl_engine/tvgGlRenderer.cpp +++ b/src/renderer/gl_engine/tvgGlRenderer.cpp @@ -214,7 +214,7 @@ void GlRenderer::drawPrimitive(GlShape& sdata, const RenderColor& c, RenderUpdat auto a = MULTIPLY(c.a, sdata.opacity); if (flag & RenderUpdateFlag::Stroke) { - float strokeWidth = sdata.rshape->strokeWidth() * getScaleFactor(sdata.geometry.matrix); + float strokeWidth = sdata.rshape->strokeWidth() * scaling(sdata.geometry.matrix); if (strokeWidth < MIN_GL_STROKE_WIDTH) { float alpha = strokeWidth / MIN_GL_STROKE_WIDTH; a = MULTIPLY(a, static_cast(alpha * 255)); diff --git a/src/renderer/gl_engine/tvgGlTessellator.cpp b/src/renderer/gl_engine/tvgGlTessellator.cpp index d3c8bcbd..459c7cf8 100644 --- a/src/renderer/gl_engine/tvgGlTessellator.cpp +++ b/src/renderer/gl_engine/tvgGlTessellator.cpp @@ -25,55 +25,6 @@ namespace tvg { -static bool _bezIsFlatten(const Bezier& bz) -{ - float diff1_x = fabs((bz.ctrl1.x * 3.f) - (bz.start.x * 2.f) - bz.end.x); - float diff1_y = fabs((bz.ctrl1.y * 3.f) - (bz.start.y * 2.f) - bz.end.y); - float diff2_x = fabs((bz.ctrl2.x * 3.f) - (bz.end.x * 2.f) - bz.start.x); - float diff2_y = fabs((bz.ctrl2.y * 3.f) - (bz.end.y * 2.f) - bz.start.y); - - if (diff1_x < diff2_x) diff1_x = diff2_x; - if (diff1_y < diff2_y) diff1_y = diff2_y; - - if (diff1_x + diff1_y <= 0.5f) return true; - - return false; -} - - -static int32_t _bezierCurveCount(const Bezier &curve) -{ - - if (_bezIsFlatten(curve)) { - return 1; - } - - Bezier left{}; - Bezier right{}; - - curve.split(left, right); - - return _bezierCurveCount(left) + _bezierCurveCount(right); -} - - -static Bezier _bezFromArc(const Point& start, const Point& end, float radius) -{ - // Calculate the angle between the start and end points - auto angle = tvg::atan2(end.y - start.y, end.x - start.x); - - // Calculate the control points of the cubic bezier curve - auto c = radius * 0.552284749831f; // c = radius * (4/3) * tan(pi/8) - - Bezier bz; - bz.start = {start.x, start.y}; - bz.ctrl1 = {start.x + radius * cos(angle), start.y + radius * sin(angle)}; - bz.ctrl2 = {end.x - c * cosf(angle), end.y - c * sinf(angle)}; - bz.end = {end.x, end.y}; - - return bz; -} - static uint32_t _pushVertex(Array& array, float x, float y) { @@ -83,22 +34,6 @@ static uint32_t _pushVertex(Array& array, float x, float y) } -enum class Orientation -{ - Linear, - Clockwise, - CounterClockwise, -}; - - -static Orientation _calcOrientation(const Point& p1, const Point& p2, const Point& p3) -{ - auto val = (p2.x - p1.x) * (p3.y - p1.y) - (p2.y - p1.y) * (p3.x - p1.x); - if (std::abs(val) < 0.0001f) return Orientation::Linear; - else return val > 0 ? Orientation::Clockwise : Orientation::CounterClockwise; -} - - Stroker::Stroker(GlGeometryBuffer* buffer, const Matrix& matrix) : mBuffer(buffer), mMatrix(matrix) { } @@ -112,7 +47,7 @@ void Stroker::stroke(const RenderShape *rshape, const RenderPath& path) mStrokeWidth = rshape->strokeWidth(); if (isinf(mMatrix.e11)) { - auto strokeWidth = rshape->strokeWidth() * getScaleFactor(mMatrix); + auto strokeWidth = rshape->strokeWidth() * scaling(mMatrix); if (strokeWidth <= MIN_GL_STROKE_WIDTH) strokeWidth = MIN_GL_STROKE_WIDTH; mStrokeWidth = strokeWidth / mMatrix.e11; } @@ -265,22 +200,12 @@ void Stroker::strokeLineTo(const Point& curr) void Stroker::strokeCubicTo(const Point& cnt1, const Point& cnt2, const Point& end) { - Bezier curve{}; - curve.start = {mStrokeState.prevPt.x, mStrokeState.prevPt.y}; - curve.ctrl1 = {cnt1.x, cnt1.y}; - curve.ctrl2 = {cnt2.x, cnt2.y}; - curve.end = {end.x, end.y}; + Bezier curve{ mStrokeState.prevPt, cnt1, cnt2, end }; - Bezier relCurve {curve.start, curve.ctrl1, curve.ctrl2, curve.end}; - relCurve.start *= mMatrix; - relCurve.ctrl1 *= mMatrix; - relCurve.ctrl2 *= mMatrix; - relCurve.end *= mMatrix; - - auto count = _bezierCurveCount(relCurve); + auto count = (curve * mMatrix).segments(); auto step = 1.f / count; - for (int32_t i = 0; i <= count; i++) { + for (uint32_t i = 0; i <= count; i++) { strokeLineTo(curve.at(step * i)); } } @@ -299,9 +224,9 @@ void Stroker::strokeClose() void Stroker::strokeJoin(const Point& dir) { - auto orientation = _calcOrientation(mStrokeState.prevPt - mStrokeState.prevPtDir, mStrokeState.prevPt, mStrokeState.prevPt + dir); + auto orient = orientation(mStrokeState.prevPt - mStrokeState.prevPtDir, mStrokeState.prevPt, mStrokeState.prevPt + dir); - if (orientation == Orientation::Linear) { + if (orient == Orientation::Linear) { if (mStrokeState.prevPtDir == dir) return; // check is same direction if (mStrokeJoin != StrokeJoin::Round) return; // opposite direction @@ -318,7 +243,7 @@ void Stroker::strokeJoin(const Point& dir) auto prevNormal = Point{-mStrokeState.prevPtDir.y, mStrokeState.prevPtDir.x}; Point prevJoin, currJoin; - if (orientation == Orientation::CounterClockwise) { + if (orient == Orientation::CounterClockwise) { prevJoin = mStrokeState.prevPt + prevNormal * strokeRadius(); currJoin = mStrokeState.prevPt + normal * strokeRadius(); } else { @@ -335,7 +260,7 @@ void Stroker::strokeJoin(const Point& dir) void Stroker::strokeRound(const Point &prev, const Point& curr, const Point& center) { - if (_calcOrientation(prev, center, curr) == Orientation::Linear) return; + if (orientation(prev, center, curr) == Orientation::Linear) return; mLeftTop.x = std::min(mLeftTop.x, std::min(center.x, std::min(prev.x, curr.x))); mLeftTop.y = std::min(mLeftTop.y, std::min(center.y, std::min(prev.y, curr.y))); @@ -343,7 +268,7 @@ void Stroker::strokeRound(const Point &prev, const Point& curr, const Point& cen mRightBottom.y = std::max(mRightBottom.y, std::max(center.y, std::max(prev.y, curr.y))); // Fixme: just use bezier curve to calculate step count - auto count = _bezierCurveCount(_bezFromArc(prev, curr, strokeRadius())); + auto count = Bezier(prev, curr, strokeRadius()).segments(); auto c = _pushVertex(mBuffer->vertex, center.x, center.y); auto pi = _pushVertex(mBuffer->vertex, prev.x, prev.y); auto step = 1.f / (count - 1); @@ -375,7 +300,7 @@ void Stroker::strokeRound(const Point &prev, const Point& curr, const Point& cen void Stroker::strokeRoundPoint(const Point &p) { // Fixme: just use bezier curve to calculate step count - auto count = _bezierCurveCount(_bezFromArc(p, p, strokeRadius())) * 2; + auto count = Bezier(p, p, strokeRadius()).segments() * 2; auto c = _pushVertex(mBuffer->vertex, p.x, p.y); auto step = 2 * MATH_PI / (count - 1); @@ -655,11 +580,7 @@ void DashStroke::dashLineTo(const Point& to, bool validPoint) void DashStroke::dashCubicTo(const Point& cnt1, const Point& cnt2, const Point& end, bool validPoint) { - Bezier cur; - cur.start = {mPtCur.x, mPtCur.y}; - cur.ctrl1 = {cnt1.x, cnt1.y}; - cur.ctrl2 = {cnt2.x, cnt2.y}; - cur.end = {end.x, end.y}; + Bezier cur{ mPtCur, cnt1, cnt2, end }; auto len = cur.length(); @@ -721,23 +642,23 @@ void DashStroke::dashCubicTo(const Point& cnt1, const Point& cnt2, const Point& void DashStroke::moveTo(const Point& pt) { - mPts->push(Point{pt.x, pt.y}); + mPts->push(pt); mCmds->push(PathCommand::MoveTo); } void DashStroke::lineTo(const Point& pt) { - mPts->push(Point{pt.x, pt.y}); + mPts->push(pt); mCmds->push(PathCommand::LineTo); } void DashStroke::cubicTo(const Point& cnt1, const Point& cnt2, const Point& end) { - mPts->push({cnt1.x, cnt1.y}); - mPts->push({cnt2.x, cnt2.y}); - mPts->push({end.x, end.y}); + mPts->push(cnt1); + mPts->push(cnt2); + mPts->push(end); mCmds->push(PathCommand::CubicTo); } @@ -782,13 +703,8 @@ void BWTessellator::tessellate(const RenderPath& path, const Matrix& matrix) } break; case PathCommand::CubicTo: { Bezier curve{pts[-1], pts[0], pts[1], pts[2]}; - Bezier relCurve {pts[-1], pts[0], pts[1], pts[2]}; - relCurve.start *= matrix; - relCurve.ctrl1 *= matrix; - relCurve.ctrl2 *= matrix; - relCurve.end *= matrix; - auto stepCount = _bezierCurveCount(relCurve); + auto stepCount = (curve * matrix).segments(); if (stepCount <= 1) stepCount = 2; float step = 1.f / stepCount;