mirror of
https://github.com/thorvg/thorvg.git
synced 2025-06-09 22:23:27 +00:00
wg_engine: fix strokes triangulation artifacts
Fixed detection of close vertices using comparison with epsilon and related artifacts in stencil buffer as extra pixels. Fixed incorrect tessellation of curves using scaling.
This commit is contained in:
parent
79e5a0f3eb
commit
1daf6a010c
4 changed files with 48 additions and 43 deletions
|
@ -65,7 +65,10 @@ void WgPolyline::appendPoint(WgPoint pt)
|
|||
{
|
||||
if (pts.count > 0) {
|
||||
float distance = pts.last().dist(pt);
|
||||
if (distance > 0) {
|
||||
// adjust precision because of real user data points
|
||||
// can be further than the accepted accuracy,
|
||||
// but still be considered tha same
|
||||
if (!tvg::zero(distance*1e-1)) {
|
||||
// update min and max indexes
|
||||
iminx = pts[iminx].x >= pt.x ? pts.count : iminx;
|
||||
imaxx = pts[imaxx].x <= pt.x ? pts.count : imaxx;
|
||||
|
@ -217,13 +220,13 @@ void WgGeometryData::appendRect(WgPoint p0, WgPoint p1, WgPoint p2, WgPoint p3)
|
|||
}
|
||||
|
||||
|
||||
void WgGeometryData::appendCircle(WgPoint center, float radius)
|
||||
void WgGeometryData::appendCircle(WgPoint center, float radius, float scale)
|
||||
{
|
||||
uint32_t indexCenter = positions.pts.count;
|
||||
positions.appendPoint(center);
|
||||
uint32_t index = positions.pts.count;
|
||||
positions.appendPoint({ center.x + gMath->sinus[0] * radius, center.y + gMath->cosin[0] * radius });
|
||||
uint32_t nPoints = (uint32_t)(radius * 2.0f);
|
||||
uint32_t nPoints = (uint32_t)(scale * radius * 2.0f);
|
||||
nPoints = nPoints < 8 ? 8 : nPoints;
|
||||
const uint32_t step = gMath->sinus.count / nPoints;
|
||||
for (uint32_t i = step; i < gMath->sinus.count; i += step) {
|
||||
|
@ -279,7 +282,7 @@ void WgGeometryData::appendBlitBox()
|
|||
}
|
||||
|
||||
|
||||
void WgGeometryData::appendStrokeDashed(const WgPolyline* polyline, const RenderStroke *stroke)
|
||||
void WgGeometryData::appendStrokeDashed(const WgPolyline* polyline, const RenderStroke *stroke, float scale)
|
||||
{
|
||||
assert(stroke);
|
||||
assert(polyline);
|
||||
|
@ -308,7 +311,7 @@ void WgGeometryData::appendStrokeDashed(const WgPolyline* polyline, const Render
|
|||
currentLength += stroke->dashPattern[dashIndex];
|
||||
// append stroke if dash
|
||||
if (dashIndex % 2 != 0) {
|
||||
appendStroke(&dashed, stroke);
|
||||
appendStroke(&dashed, stroke, scale);
|
||||
dashed.clear();
|
||||
}
|
||||
}
|
||||
|
@ -318,14 +321,14 @@ void WgGeometryData::appendStrokeDashed(const WgPolyline* polyline, const Render
|
|||
// draw last subline
|
||||
if (dashIndex % 2 == 0) {
|
||||
dashed.appendPoint(pts.last());
|
||||
appendStroke(&dashed, stroke);
|
||||
appendStroke(&dashed, stroke, scale);
|
||||
dashed.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void WgGeometryData::appendStrokeJoin(const WgPoint& v0, const WgPoint& v1, const WgPoint& v2, StrokeJoin join, float halfWidth, float miterLimit)
|
||||
void WgGeometryData::appendStrokeJoin(const WgPoint& v0, const WgPoint& v1, const WgPoint& v2, StrokeJoin join, float halfWidth, float miterLimit, float scale)
|
||||
{
|
||||
WgPoint dir0 = (v1 - v0).normal();
|
||||
WgPoint dir1 = (v2 - v1).normal();
|
||||
|
@ -334,12 +337,13 @@ void WgGeometryData::appendStrokeJoin(const WgPoint& v0, const WgPoint& v1, cons
|
|||
WgPoint offset0 = nrm0 * halfWidth;
|
||||
WgPoint offset1 = nrm1 * halfWidth;
|
||||
if (join == StrokeJoin::Round) {
|
||||
appendCircle(v1, halfWidth);
|
||||
appendCircle(v1, halfWidth, scale);
|
||||
} else if (join == StrokeJoin::Bevel) {
|
||||
appendRect(v1 - offset0, v1 + offset1, v1 - offset1, v1 + offset0);
|
||||
} else if (join == StrokeJoin::Miter) {
|
||||
WgPoint nrm = (nrm0 + nrm1);
|
||||
if (!tvg::zero(dir0.x * dir1.y - dir0.y * dir1.x)) {
|
||||
// adjust precision because dot product could return above 1 that results acos returns Nan
|
||||
if (!tvg::zero((dir0.x * dir1.y - dir0.y * dir1.x)*1e-1)) {
|
||||
nrm.normalize();
|
||||
float cosine = nrm.dot(nrm0);
|
||||
float angle = std::acos(dir0.dot(dir1.negative()));
|
||||
|
@ -355,7 +359,7 @@ void WgGeometryData::appendStrokeJoin(const WgPoint& v0, const WgPoint& v1, cons
|
|||
}
|
||||
|
||||
|
||||
void WgGeometryData::appendStroke(const WgPolyline* polyline, const RenderStroke *stroke)
|
||||
void WgGeometryData::appendStroke(const WgPolyline* polyline, const RenderStroke *stroke, float scale)
|
||||
{
|
||||
assert(stroke);
|
||||
assert(polyline);
|
||||
|
@ -369,8 +373,8 @@ void WgGeometryData::appendStroke(const WgPolyline* polyline, const RenderStroke
|
|||
WgPoint nrm0 = WgPoint{ -dir0.y, +dir0.x };
|
||||
if (stroke->cap == StrokeCap::Round) {
|
||||
appendRect(v0 - nrm0 * wdt, v0 + nrm0 * wdt, v1 - nrm0 * wdt, v1 + nrm0 * wdt);
|
||||
appendCircle(polyline->pts[0], wdt);
|
||||
appendCircle(polyline->pts[1], wdt);
|
||||
appendCircle(polyline->pts[0], wdt, scale);
|
||||
appendCircle(polyline->pts[1], wdt, scale);
|
||||
} else if (stroke->cap == StrokeCap::Butt) {
|
||||
appendRect(v0 - nrm0 * wdt, v0 + nrm0 * wdt, v1 - nrm0 * wdt, v1 + nrm0 * wdt);
|
||||
} else if (stroke->cap == StrokeCap::Square) {
|
||||
|
@ -384,7 +388,7 @@ void WgGeometryData::appendStroke(const WgPolyline* polyline, const RenderStroke
|
|||
WgPoint v0 = polyline->pts[polyline->pts.count - 2];
|
||||
WgPoint v1 = polyline->pts[0];
|
||||
WgPoint v2 = polyline->pts[1];
|
||||
appendStrokeJoin(v0, v1, v2, stroke->join, wdt, stroke->miterlimit);
|
||||
appendStrokeJoin(v0, v1, v2, stroke->join, wdt, stroke->miterlimit, scale);
|
||||
} else {
|
||||
// append first cap
|
||||
WgPoint v0 = polyline->pts[0];
|
||||
|
@ -392,7 +396,7 @@ void WgGeometryData::appendStroke(const WgPolyline* polyline, const RenderStroke
|
|||
WgPoint dir0 = (v1 - v0) / polyline->dist[1];
|
||||
WgPoint nrm0 = WgPoint{ -dir0.y, +dir0.x };
|
||||
if (stroke->cap == StrokeCap::Round) {
|
||||
appendCircle(v0, wdt);
|
||||
appendCircle(v0, wdt, scale);
|
||||
} else if (stroke->cap == StrokeCap::Butt) {
|
||||
// no cap needed
|
||||
} else if (stroke->cap == StrokeCap::Square) {
|
||||
|
@ -405,7 +409,7 @@ void WgGeometryData::appendStroke(const WgPolyline* polyline, const RenderStroke
|
|||
dir0 = (v1 - v0) / polyline->dist[polyline->pts.count - 1];
|
||||
nrm0 = WgPoint{ -dir0.y, +dir0.x };
|
||||
if (stroke->cap == StrokeCap::Round) {
|
||||
appendCircle(v1, wdt);
|
||||
appendCircle(v1, wdt, scale);
|
||||
} else if (stroke->cap == StrokeCap::Butt) {
|
||||
// no cap needed
|
||||
} else if (stroke->cap == StrokeCap::Square) {
|
||||
|
@ -425,7 +429,7 @@ void WgGeometryData::appendStroke(const WgPolyline* polyline, const RenderStroke
|
|||
|
||||
if (i > 0) {
|
||||
WgPoint v0 = polyline->pts[i - 1];
|
||||
appendStrokeJoin(v0, v1, v2, stroke->join, wdt, stroke->miterlimit);
|
||||
appendStrokeJoin(v0, v1, v2, stroke->join, wdt, stroke->miterlimit, scale);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -111,16 +111,15 @@ struct WgGeometryData
|
|||
WgGeometryData();
|
||||
void clear();
|
||||
|
||||
void appendCubic(WgPoint p1, WgPoint p2, WgPoint p3);
|
||||
void appendBox(WgPoint pmin, WgPoint pmax);
|
||||
void appendRect(WgPoint p0, WgPoint p1, WgPoint p2, WgPoint p3);
|
||||
void appendCircle(WgPoint center, float radius);
|
||||
void appendCircle(WgPoint center, float radius, float scale = 1.0f);
|
||||
void appendImageBox(float w, float h);
|
||||
void appendBlitBox();
|
||||
void appendStrokeDashed(const WgPolyline* polyline, const RenderStroke *stroke);
|
||||
void appendStrokeDashed(const WgPolyline* polyline, const RenderStroke *stroke, float scale);
|
||||
void appendStrokeJoin(const WgPoint& v0, const WgPoint& v1, const WgPoint& v2,
|
||||
StrokeJoin join, float halfWidth, float miterLimit);
|
||||
void appendStroke(const WgPolyline* polyline, const RenderStroke *stroke);
|
||||
StrokeJoin join, float halfWidth, float miterLimit, float scale);
|
||||
void appendStroke(const WgPolyline* polyline, const RenderStroke *stroke, float scale);
|
||||
};
|
||||
|
||||
#endif // _TVG_WG_GEOMETRY_H_
|
|
@ -22,6 +22,7 @@
|
|||
*/
|
||||
|
||||
#include <algorithm>
|
||||
#include "tvgMath.h"
|
||||
#include "tvgWgRenderData.h"
|
||||
#include "tvgWgShaderTypes.h"
|
||||
|
||||
|
@ -327,11 +328,12 @@ void WgRenderDataShape::updateAABB(const Matrix& rt) {
|
|||
}
|
||||
|
||||
|
||||
void WgRenderDataShape::updateMeshes(WgContext &context, const RenderShape &rshape, const Matrix& rt)
|
||||
void WgRenderDataShape::updateMeshes(WgContext &context, const RenderShape &rshape, const Matrix& tr)
|
||||
{
|
||||
releaseMeshes(context);
|
||||
strokeFirst = rshape.stroke ? rshape.stroke->strokeFirst : false;
|
||||
|
||||
float scale = std::max(sqrt(tr.e11*tr.e11 + tr.e21*tr.e21), 1.0f);
|
||||
Array<WgPolyline*> polylines{};
|
||||
// decode path
|
||||
size_t pntIndex = 0;
|
||||
|
@ -349,16 +351,16 @@ void WgRenderDataShape::updateMeshes(WgContext &context, const RenderShape &rsha
|
|||
polylines.last()->close();
|
||||
} else if (cmd == PathCommand::CubicTo) {
|
||||
assert(polylines.last()->pts.count > 0);
|
||||
WgPoint pt0 = polylines.last()->pts.last().trans(rt);
|
||||
WgPoint pt1 = WgPoint(rshape.path.pts[pntIndex + 0]).trans(rt);
|
||||
WgPoint pt2 = WgPoint(rshape.path.pts[pntIndex + 1]).trans(rt);
|
||||
WgPoint pt3 = WgPoint(rshape.path.pts[pntIndex + 2]).trans(rt);
|
||||
uint32_t nsegs = (uint32_t)(pt0.dist(pt1) + pt1.dist(pt2) + pt2.dist(pt3));
|
||||
WgPoint pt0 = polylines.last()->pts.last().trans(tr);
|
||||
WgPoint pt1 = WgPoint(rshape.path.pts[pntIndex + 0]).trans(tr);
|
||||
WgPoint pt2 = WgPoint(rshape.path.pts[pntIndex + 1]).trans(tr);
|
||||
WgPoint pt3 = WgPoint(rshape.path.pts[pntIndex + 2]).trans(tr);
|
||||
uint32_t nsegs = std::max((uint32_t)(pt0.dist(pt1) + pt1.dist(pt2) + pt2.dist(pt3)), 32U);
|
||||
polylines.last()->appendCubic(
|
||||
rshape.path.pts[pntIndex + 0],
|
||||
rshape.path.pts[pntIndex + 1],
|
||||
rshape.path.pts[pntIndex + 2],
|
||||
nsegs / 2);
|
||||
nsegs / 4);
|
||||
pntIndex += 3;
|
||||
}
|
||||
}
|
||||
|
@ -375,13 +377,13 @@ void WgRenderDataShape::updateMeshes(WgContext &context, const RenderShape &rsha
|
|||
if (!rshape.stroke->strokeTrim(trimBegin, trimEnd)) { trimBegin = 0.0f; trimEnd = 1.0f; }
|
||||
if (rshape.stroke->trim.simultaneous) {
|
||||
for (uint32_t i = 0; i < polylines.count; i++)
|
||||
updateStrokes(context, polylines[i], rshape.stroke, trimBegin, trimEnd);
|
||||
updateStrokes(context, polylines[i], rshape.stroke, scale, trimBegin, trimEnd);
|
||||
} else {
|
||||
if (trimBegin <= trimEnd) {
|
||||
updateStrokesList(context, polylines, rshape.stroke, totalLen, trimBegin, trimEnd);
|
||||
updateStrokesList(context, polylines, rshape.stroke, totalLen, scale, trimBegin, trimEnd);
|
||||
} else {
|
||||
updateStrokesList(context, polylines, rshape.stroke, totalLen, 0.0f, trimEnd);
|
||||
updateStrokesList(context, polylines, rshape.stroke, totalLen, trimBegin, 1.0f);
|
||||
updateStrokesList(context, polylines, rshape.stroke, totalLen, scale, 0.0f, trimEnd);
|
||||
updateStrokesList(context, polylines, rshape.stroke, totalLen, scale, trimBegin, 1.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -389,7 +391,7 @@ void WgRenderDataShape::updateMeshes(WgContext &context, const RenderShape &rsha
|
|||
for (uint32_t i = 0; i < polylines.count; i++)
|
||||
delete polylines[i];
|
||||
// update shapes bbox
|
||||
updateAABB(rt);
|
||||
updateAABB(tr);
|
||||
meshDataBBox.update(context, pMin, pMax);
|
||||
}
|
||||
|
||||
|
@ -407,7 +409,7 @@ void WgRenderDataShape::updateShapes(WgContext& context, const WgPolyline* polyl
|
|||
}
|
||||
}
|
||||
|
||||
void WgRenderDataShape::updateStrokesList(WgContext& context, Array<WgPolyline*> polylines, const RenderStroke* rstroke, float totalLen, float trimBegin, float trimEnd)
|
||||
void WgRenderDataShape::updateStrokesList(WgContext& context, Array<WgPolyline*> polylines, const RenderStroke* rstroke, float scale, float totalLen, float trimBegin, float trimEnd)
|
||||
{
|
||||
float tp1 = totalLen * trimBegin; // trim point begin
|
||||
float tp2 = totalLen * trimEnd; // trim point end
|
||||
|
@ -417,7 +419,7 @@ void WgRenderDataShape::updateStrokesList(WgContext& context, Array<WgPolyline*>
|
|||
float trimBegin = ((pc <= tp1) && (pc + pl > tp1)) ? (tp1 - pc) / pl : 0.0f;
|
||||
float trimEnd = ((pc <= tp2) && (pc + pl > tp2)) ? (tp2 - pc) / pl : 1.0f;
|
||||
if ((pc + pl >= tp1) && (pc <= tp2))
|
||||
updateStrokes(context, polylines[i], rstroke, trimBegin, trimEnd);
|
||||
updateStrokes(context, polylines[i], rstroke, scale, trimBegin, trimEnd);
|
||||
pc += pl;
|
||||
// break if reached the tail
|
||||
if (pc > tp2) break;
|
||||
|
@ -425,7 +427,7 @@ void WgRenderDataShape::updateStrokesList(WgContext& context, Array<WgPolyline*>
|
|||
}
|
||||
|
||||
|
||||
void WgRenderDataShape::updateStrokes(WgContext& context, const WgPolyline* polyline, const RenderStroke* rstroke, float trimBegin, float trimEnd)
|
||||
void WgRenderDataShape::updateStrokes(WgContext& context, const WgPolyline* polyline, const RenderStroke* rstroke, float scale, float trimBegin, float trimEnd)
|
||||
{
|
||||
assert(polyline);
|
||||
// generate strokes geometry
|
||||
|
@ -442,7 +444,7 @@ void WgRenderDataShape::updateStrokes(WgContext& context, const WgPolyline* poly
|
|||
polyline->trim(&trimmed, trimBegin, 1.0f);
|
||||
polyline->trim(&trimmed, 0.0f, trimEnd);
|
||||
}
|
||||
geometryData.appendStrokeDashed(&trimmed, rstroke);
|
||||
geometryData.appendStrokeDashed(&trimmed, rstroke, scale);
|
||||
} else // trim -> stroke
|
||||
if ((trimBegin != 0.0f) || (trimEnd != 1.0f)) {
|
||||
trimmed.clear();
|
||||
|
@ -452,12 +454,12 @@ void WgRenderDataShape::updateStrokes(WgContext& context, const WgPolyline* poly
|
|||
polyline->trim(&trimmed, trimBegin, 1.0f);
|
||||
polyline->trim(&trimmed, 0.0f, trimEnd);
|
||||
}
|
||||
geometryData.appendStroke(&trimmed, rstroke);
|
||||
geometryData.appendStroke(&trimmed, rstroke, scale);
|
||||
} else // split -> stroke
|
||||
if (rstroke->dashPattern) {
|
||||
geometryData.appendStrokeDashed(polyline, rstroke);
|
||||
geometryData.appendStrokeDashed(polyline, rstroke, scale);
|
||||
} else { // stroke
|
||||
geometryData.appendStroke(polyline, rstroke);
|
||||
geometryData.appendStroke(polyline, rstroke, scale);
|
||||
}
|
||||
// append render meshes and bboxes
|
||||
if(geometryData.positions.pts.count >= 3) {
|
||||
|
|
|
@ -126,10 +126,10 @@ struct WgRenderDataShape: public WgRenderDataPaint
|
|||
|
||||
void updateBBox(WgPoint pmin, WgPoint pmax);
|
||||
void updateAABB(const Matrix& rt);
|
||||
void updateMeshes(WgContext& context, const RenderShape& rshape, const Matrix& rt);
|
||||
void updateMeshes(WgContext& context, const RenderShape& rshape, const Matrix& tr);
|
||||
void updateShapes(WgContext& context, const WgPolyline* polyline);
|
||||
void updateStrokesList(WgContext& context, Array<WgPolyline*> polylines, const RenderStroke* rstroke, float totalLen, float trimBegin, float trimEnd);
|
||||
void updateStrokes(WgContext& context, const WgPolyline* polyline, const RenderStroke* rstroke, float trimBegin, float trimEnd);
|
||||
void updateStrokesList(WgContext& context, Array<WgPolyline*> polylines, const RenderStroke* rstroke, float scale, float totalLen, float trimBegin, float trimEnd);
|
||||
void updateStrokes(WgContext& context, const WgPolyline* polyline, const RenderStroke* rstroke, float scale, float trimBegin, float trimEnd);
|
||||
void releaseMeshes(WgContext& context);
|
||||
void release(WgContext& context) override;
|
||||
Type type() override { return Type::Shape; };
|
||||
|
|
Loading…
Add table
Reference in a new issue