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:
Sergii Liebodkin 2024-09-16 14:28:52 +00:00 committed by Hermet Park
parent 79e5a0f3eb
commit 1daf6a010c
4 changed files with 48 additions and 43 deletions

View file

@ -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);
}
}
}

View file

@ -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_

View file

@ -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) {

View file

@ -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; };