From 1a28f837a89ba64b76aabdd52c9dd3b0e9679d6a Mon Sep 17 00:00:00 2001 From: Sergii Liebodkin Date: Thu, 14 Mar 2024 22:51:40 +0200 Subject: [PATCH] wg_engine: winding fill rule [issues 1479: FillRule](#1479) Introduced fill rule winding This rule makes sense only if path have some self intersections. In all other cases shapes are filled by even-odd behavor. --- src/renderer/wg_engine/tvgWgGeometry.cpp | 141 +++++++++++++++++++++ src/renderer/wg_engine/tvgWgGeometry.h | 12 ++ src/renderer/wg_engine/tvgWgRenderData.cpp | 8 +- 3 files changed, 160 insertions(+), 1 deletion(-) diff --git a/src/renderer/wg_engine/tvgWgGeometry.cpp b/src/renderer/wg_engine/tvgWgGeometry.cpp index ff4cd6cb..8d3016a8 100644 --- a/src/renderer/wg_engine/tvgWgGeometry.cpp +++ b/src/renderer/wg_engine/tvgWgGeometry.cpp @@ -38,6 +38,51 @@ void WgGeometryData::computeTriFansIndexes() }; +void WgGeometryData::computeContour(WgGeometryData* data) +{ + assert(data); + assert(data->positions.count > 1); + clear(); + uint32_t istart = data->getIndexMinX(); // index start + uint32_t icnt = data->positions.count; // index count + + uint32_t iprev = istart == 0 ? icnt - 1 : istart - 1; + uint32_t inext = (istart + 1) % icnt; // index current + WgPoint p0 = data->positions[istart]; // current segment start + bool isIntersected = false; + bool isClockWise = !isCW(data->positions[iprev], data->positions[istart], data->positions[inext]); + + uint32_t ii{}; // intersected segment index + WgPoint pi{}; // intersection point + positions.push(p0); + while(!((inext == istart) && (!isIntersected))) { + if (data->getClosestIntersection(p0, data->positions[inext], pi, ii)) { + isIntersected = true; + p0 = pi; + // operate intersection point + if (isClockWise) { // clock wise behavior + if (isCW(p0, pi, data->positions[ii])) { + inext = ii; + } else { + inext = ((ii + 1) % icnt); + } + } else { // contr-clock wise behavior + if (isCW(p0, pi, data->positions[ii])) { + inext = ((ii + 1) % icnt); + } else { + inext = ii; + } + } + } else { // simple next point + isIntersected = false; + p0 = data->positions[inext]; + inext = (inext + 1) % icnt; + } + positions.push(p0); + } +} + + void WgGeometryData::appendCubic(WgPoint p1, WgPoint p2, WgPoint p3) { WgPoint p0 = positions.count > 0 ? positions.last() : WgPoint(0.0f, 0.0f); @@ -159,6 +204,83 @@ void WgGeometryData::appendMesh(const RenderMesh* rmesh) }; +bool WgGeometryData::getClosestIntersection(WgPoint p1, WgPoint p2, WgPoint& pi, uint32_t& index) +{ + bool finded = false; + pi = p2; + float dist = p1.dist(p2); + for (uint32_t i = 0; i < positions.count - 1; i++) { + WgPoint p3 = positions[i+0]; + WgPoint p4 = positions[i+1]; + float bot = (p1.x - p2.x)*(p3.y - p4.y) - (p1.y - p2.y)*(p3.x - p4.x); + if (bot != 0) { + float top0 = (p1.x - p3.x)*(p3.y - p4.y) - (p1.y - p3.y)*(p3.x - p4.x); + float top1 = (p1.x - p2.x)*(p1.y - p3.y) - (p1.y - p2.y)*(p1.x - p3.x); + float t0 = +top0 / bot; + float t1 = -top1 / bot; + if ((t0 > 0.0f) && (t0 < 1.0f) && (t1 > 0.0f) && (t1 < 1.0f)) { + WgPoint p = { p1.x + (p2.x - p1.x) * top0 / bot, p1.y + (p2.y - p1.y) * top0 / bot }; + float d = p.dist(p1); + if (d < dist) { + pi = p; + index = i; + dist = d; + finded = true; + } + } + } + } + return finded; +} + +bool WgGeometryData::isCW(WgPoint p1, WgPoint p2, WgPoint p3) +{ + WgPoint v1 = p2 - p1; + WgPoint v2 = p3 - p1; + return (v1.x*v2.y - v1.y*v2.x) < 0.0; +} + + +uint32_t WgGeometryData::getIndexMinX() +{ + assert(positions.count > 0); + uint32_t index = 0; + for (uint32_t i = 1; i < positions.count; i++) + if (positions[index].x > positions[i].x) index = i; + return index; +} + + +uint32_t WgGeometryData::getIndexMaxX() +{ + assert(positions.count > 0); + uint32_t index = 0; + for (uint32_t i = 1; i < positions.count; i++) + if (positions[index].x < positions[i].x) index = i; + return index; +} + + +uint32_t WgGeometryData::getIndexMinY() +{ + assert(positions.count > 0); + uint32_t index = 0; + for (uint32_t i = 1; i < positions.count; i++) + if (positions[index].y > positions[i].y) index = i; + return index; +} + + +uint32_t WgGeometryData::getIndexMaxY() +{ + assert(positions.count > 0); + uint32_t index = 0; + for (uint32_t i = 1; i < positions.count; i++) + if (positions[index].y < positions[i].y) index = i; + return index; +} + + void WgGeometryData::close() { if (positions.count > 1) { @@ -166,6 +288,14 @@ void WgGeometryData::close() } }; + +void WgGeometryData::clear() +{ + indexes.clear(); + positions.clear(); + texCoords.clear(); +} + //*********************************************************************** // WgGeometryDataGroup //*********************************************************************** @@ -222,6 +352,17 @@ void WgGeometryDataGroup::stroke(const RenderShape& rshape) } +void WgGeometryDataGroup::contours(WgGeometryDataGroup& outlines) +{ + for (uint32_t i = 0 ; i < outlines.geometries.count; i++) { + WgGeometryData* geometry = new WgGeometryData(); + geometry->computeContour(outlines.geometries[i]); + geometry->computeTriFansIndexes(); + this->geometries.push(geometry); + } +} + + void WgGeometryDataGroup::release() { for (uint32_t i = 0; i < geometries.count; i++) diff --git a/src/renderer/wg_engine/tvgWgGeometry.h b/src/renderer/wg_engine/tvgWgGeometry.h index dcba0ab4..8f01d247 100644 --- a/src/renderer/wg_engine/tvgWgGeometry.h +++ b/src/renderer/wg_engine/tvgWgGeometry.h @@ -84,6 +84,7 @@ struct WgGeometryData // webgpu did not support triangle fans primitives type // so we can emulate triangle fans using indexing void computeTriFansIndexes(); + void computeContour(WgGeometryData* data); void appendCubic(WgPoint p1, WgPoint p2, WgPoint p3); void appendBox(WgPoint pmin, WgPoint pmax); @@ -92,7 +93,17 @@ struct WgGeometryData void appendImageBox(float w, float h); void appendBlitBox(); void appendMesh(const RenderMesh* rmesh); + + bool getClosestIntersection(WgPoint p1, WgPoint p2, WgPoint& pi, uint32_t& index); + bool isCW(WgPoint p1, WgPoint p2, WgPoint p3); + + uint32_t getIndexMinX(); + uint32_t getIndexMaxX(); + uint32_t getIndexMinY(); + uint32_t getIndexMaxY(); + void close(); + void clear(); }; struct WgGeometryDataGroup @@ -103,6 +114,7 @@ struct WgGeometryDataGroup void getBBox(WgPoint& pmin, WgPoint& pmax); void tesselate(const RenderShape& rshape); void stroke(const RenderShape& rshape); + void contours(WgGeometryDataGroup& outlines); void release(); private: static void decodePath(const RenderShape& rshape, WgGeometryDataGroup* outlines); diff --git a/src/renderer/wg_engine/tvgWgRenderData.cpp b/src/renderer/wg_engine/tvgWgRenderData.cpp index e65c3593..68730200 100644 --- a/src/renderer/wg_engine/tvgWgRenderData.cpp +++ b/src/renderer/wg_engine/tvgWgRenderData.cpp @@ -216,7 +216,13 @@ void WgRenderDataShape::updateMeshes(WgContext &context, const RenderShape &rsha releaseMeshes(context); // update shapes geometry WgGeometryDataGroup shapes; - shapes.tesselate(rshape); + if(rshape.rule == tvg::FillRule::EvenOdd) { + shapes.tesselate(rshape); + } else if(rshape.rule == tvg::FillRule::Winding) { + WgGeometryDataGroup lines; + lines.tesselate(rshape); + shapes.contours(lines); + } meshGroupShapes.update(context, &shapes); // update shapes bbox WgPoint pmin{}, pmax{};