mirror of
https://github.com/thorvg/thorvg.git
synced 2025-06-29 19:33:55 +00:00
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
This commit is contained in:
parent
43f94f8b32
commit
8543099fcd
5 changed files with 91 additions and 108 deletions
|
@ -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
|
void Bezier::split(Bezier& left, Bezier& right) const
|
||||||
{
|
{
|
||||||
auto c = (ctrl1.x + ctrl2.x) * 0.5f;
|
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);
|
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};
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)
|
static inline void scale(Matrix* m, const Point& p)
|
||||||
{
|
{
|
||||||
m->e11 *= p.x;
|
m->e11 *= p.x;
|
||||||
|
@ -330,6 +336,21 @@ static inline Point operator-(const Point& a)
|
||||||
return {-a.x, -a.y};
|
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)
|
static inline void log(const Point& pt)
|
||||||
{
|
{
|
||||||
|
@ -362,6 +383,13 @@ struct Bezier
|
||||||
Point ctrl2;
|
Point ctrl2;
|
||||||
Point end;
|
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(float t, Bezier& left);
|
||||||
void split(Bezier& left, Bezier& right) const;
|
void split(Bezier& left, Bezier& right) const;
|
||||||
void split(float at, 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;
|
Point at(float t) const;
|
||||||
float angle(float t) const;
|
float angle(float t) const;
|
||||||
void bounds(Point& min, Point& max) const;
|
void bounds(Point& min, Point& max) const;
|
||||||
};
|
bool flatten() const;
|
||||||
|
uint32_t segments() const;
|
||||||
|
|
||||||
|
Bezier operator*(const Matrix& m);
|
||||||
|
};
|
||||||
|
|
||||||
/************************************************************************/
|
/************************************************************************/
|
||||||
/* Geometry functions */
|
/* Geometry functions */
|
||||||
|
|
|
@ -81,11 +81,6 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
static inline float getScaleFactor(const Matrix& m)
|
|
||||||
{
|
|
||||||
return sqrtf(m.e11 * m.e11 + m.e21 * m.e21);
|
|
||||||
}
|
|
||||||
|
|
||||||
enum class GlStencilMode {
|
enum class GlStencilMode {
|
||||||
None,
|
None,
|
||||||
FillNonZero,
|
FillNonZero,
|
||||||
|
|
|
@ -214,7 +214,7 @@ void GlRenderer::drawPrimitive(GlShape& sdata, const RenderColor& c, RenderUpdat
|
||||||
auto a = MULTIPLY(c.a, sdata.opacity);
|
auto a = MULTIPLY(c.a, sdata.opacity);
|
||||||
|
|
||||||
if (flag & RenderUpdateFlag::Stroke) {
|
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) {
|
if (strokeWidth < MIN_GL_STROKE_WIDTH) {
|
||||||
float alpha = strokeWidth / MIN_GL_STROKE_WIDTH;
|
float alpha = strokeWidth / MIN_GL_STROKE_WIDTH;
|
||||||
a = MULTIPLY(a, static_cast<uint8_t>(alpha * 255));
|
a = MULTIPLY(a, static_cast<uint8_t>(alpha * 255));
|
||||||
|
|
|
@ -25,55 +25,6 @@
|
||||||
namespace tvg
|
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<float>& array, float x, float y)
|
static uint32_t _pushVertex(Array<float>& array, float x, float y)
|
||||||
{
|
{
|
||||||
|
@ -83,22 +34,6 @@ static uint32_t _pushVertex(Array<float>& 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)
|
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();
|
mStrokeWidth = rshape->strokeWidth();
|
||||||
|
|
||||||
if (isinf(mMatrix.e11)) {
|
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;
|
if (strokeWidth <= MIN_GL_STROKE_WIDTH) strokeWidth = MIN_GL_STROKE_WIDTH;
|
||||||
mStrokeWidth = strokeWidth / mMatrix.e11;
|
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)
|
void Stroker::strokeCubicTo(const Point& cnt1, const Point& cnt2, const Point& end)
|
||||||
{
|
{
|
||||||
Bezier curve{};
|
Bezier curve{ mStrokeState.prevPt, cnt1, cnt2, end };
|
||||||
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 relCurve {curve.start, curve.ctrl1, curve.ctrl2, curve.end};
|
auto count = (curve * mMatrix).segments();
|
||||||
relCurve.start *= mMatrix;
|
|
||||||
relCurve.ctrl1 *= mMatrix;
|
|
||||||
relCurve.ctrl2 *= mMatrix;
|
|
||||||
relCurve.end *= mMatrix;
|
|
||||||
|
|
||||||
auto count = _bezierCurveCount(relCurve);
|
|
||||||
auto step = 1.f / count;
|
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));
|
strokeLineTo(curve.at(step * i));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -299,9 +224,9 @@ void Stroker::strokeClose()
|
||||||
|
|
||||||
void Stroker::strokeJoin(const Point& dir)
|
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 (mStrokeState.prevPtDir == dir) return; // check is same direction
|
||||||
if (mStrokeJoin != StrokeJoin::Round) return; // opposite 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};
|
auto prevNormal = Point{-mStrokeState.prevPtDir.y, mStrokeState.prevPtDir.x};
|
||||||
Point prevJoin, currJoin;
|
Point prevJoin, currJoin;
|
||||||
|
|
||||||
if (orientation == Orientation::CounterClockwise) {
|
if (orient == Orientation::CounterClockwise) {
|
||||||
prevJoin = mStrokeState.prevPt + prevNormal * strokeRadius();
|
prevJoin = mStrokeState.prevPt + prevNormal * strokeRadius();
|
||||||
currJoin = mStrokeState.prevPt + normal * strokeRadius();
|
currJoin = mStrokeState.prevPt + normal * strokeRadius();
|
||||||
} else {
|
} else {
|
||||||
|
@ -335,7 +260,7 @@ void Stroker::strokeJoin(const Point& dir)
|
||||||
|
|
||||||
void Stroker::strokeRound(const Point &prev, const Point& curr, const Point& center)
|
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.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)));
|
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)));
|
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
|
// 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 c = _pushVertex(mBuffer->vertex, center.x, center.y);
|
||||||
auto pi = _pushVertex(mBuffer->vertex, prev.x, prev.y);
|
auto pi = _pushVertex(mBuffer->vertex, prev.x, prev.y);
|
||||||
auto step = 1.f / (count - 1);
|
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)
|
void Stroker::strokeRoundPoint(const Point &p)
|
||||||
{
|
{
|
||||||
// Fixme: just use bezier curve to calculate step count
|
// 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 c = _pushVertex(mBuffer->vertex, p.x, p.y);
|
||||||
auto step = 2 * MATH_PI / (count - 1);
|
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)
|
void DashStroke::dashCubicTo(const Point& cnt1, const Point& cnt2, const Point& end, bool validPoint)
|
||||||
{
|
{
|
||||||
Bezier cur;
|
Bezier cur{ mPtCur, cnt1, cnt2, end };
|
||||||
cur.start = {mPtCur.x, mPtCur.y};
|
|
||||||
cur.ctrl1 = {cnt1.x, cnt1.y};
|
|
||||||
cur.ctrl2 = {cnt2.x, cnt2.y};
|
|
||||||
cur.end = {end.x, end.y};
|
|
||||||
|
|
||||||
auto len = cur.length();
|
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)
|
void DashStroke::moveTo(const Point& pt)
|
||||||
{
|
{
|
||||||
mPts->push(Point{pt.x, pt.y});
|
mPts->push(pt);
|
||||||
mCmds->push(PathCommand::MoveTo);
|
mCmds->push(PathCommand::MoveTo);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void DashStroke::lineTo(const Point& pt)
|
void DashStroke::lineTo(const Point& pt)
|
||||||
{
|
{
|
||||||
mPts->push(Point{pt.x, pt.y});
|
mPts->push(pt);
|
||||||
mCmds->push(PathCommand::LineTo);
|
mCmds->push(PathCommand::LineTo);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void DashStroke::cubicTo(const Point& cnt1, const Point& cnt2, const Point& end)
|
void DashStroke::cubicTo(const Point& cnt1, const Point& cnt2, const Point& end)
|
||||||
{
|
{
|
||||||
mPts->push({cnt1.x, cnt1.y});
|
mPts->push(cnt1);
|
||||||
mPts->push({cnt2.x, cnt2.y});
|
mPts->push(cnt2);
|
||||||
mPts->push({end.x, end.y});
|
mPts->push(end);
|
||||||
mCmds->push(PathCommand::CubicTo);
|
mCmds->push(PathCommand::CubicTo);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -782,13 +703,8 @@ void BWTessellator::tessellate(const RenderPath& path, const Matrix& matrix)
|
||||||
} break;
|
} break;
|
||||||
case PathCommand::CubicTo: {
|
case PathCommand::CubicTo: {
|
||||||
Bezier curve{pts[-1], pts[0], pts[1], pts[2]};
|
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;
|
if (stepCount <= 1) stepCount = 2;
|
||||||
|
|
||||||
float step = 1.f / stepCount;
|
float step = 1.f / stepCount;
|
||||||
|
|
Loading…
Add table
Reference in a new issue