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.
This commit is contained in:
Sergii Liebodkin 2024-03-14 22:51:40 +02:00 committed by Hermet Park
parent fc1e1cee39
commit 1a28f837a8
3 changed files with 160 additions and 1 deletions

View file

@ -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) void WgGeometryData::appendCubic(WgPoint p1, WgPoint p2, WgPoint p3)
{ {
WgPoint p0 = positions.count > 0 ? positions.last() : WgPoint(0.0f, 0.0f); 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() void WgGeometryData::close()
{ {
if (positions.count > 1) { if (positions.count > 1) {
@ -166,6 +288,14 @@ void WgGeometryData::close()
} }
}; };
void WgGeometryData::clear()
{
indexes.clear();
positions.clear();
texCoords.clear();
}
//*********************************************************************** //***********************************************************************
// WgGeometryDataGroup // 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() void WgGeometryDataGroup::release()
{ {
for (uint32_t i = 0; i < geometries.count; i++) for (uint32_t i = 0; i < geometries.count; i++)

View file

@ -84,6 +84,7 @@ struct WgGeometryData
// webgpu did not support triangle fans primitives type // webgpu did not support triangle fans primitives type
// so we can emulate triangle fans using indexing // so we can emulate triangle fans using indexing
void computeTriFansIndexes(); void computeTriFansIndexes();
void computeContour(WgGeometryData* data);
void appendCubic(WgPoint p1, WgPoint p2, WgPoint p3); void appendCubic(WgPoint p1, WgPoint p2, WgPoint p3);
void appendBox(WgPoint pmin, WgPoint pmax); void appendBox(WgPoint pmin, WgPoint pmax);
@ -92,7 +93,17 @@ struct WgGeometryData
void appendImageBox(float w, float h); void appendImageBox(float w, float h);
void appendBlitBox(); void appendBlitBox();
void appendMesh(const RenderMesh* rmesh); 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 close();
void clear();
}; };
struct WgGeometryDataGroup struct WgGeometryDataGroup
@ -103,6 +114,7 @@ struct WgGeometryDataGroup
void getBBox(WgPoint& pmin, WgPoint& pmax); void getBBox(WgPoint& pmin, WgPoint& pmax);
void tesselate(const RenderShape& rshape); void tesselate(const RenderShape& rshape);
void stroke(const RenderShape& rshape); void stroke(const RenderShape& rshape);
void contours(WgGeometryDataGroup& outlines);
void release(); void release();
private: private:
static void decodePath(const RenderShape& rshape, WgGeometryDataGroup* outlines); static void decodePath(const RenderShape& rshape, WgGeometryDataGroup* outlines);

View file

@ -216,7 +216,13 @@ void WgRenderDataShape::updateMeshes(WgContext &context, const RenderShape &rsha
releaseMeshes(context); releaseMeshes(context);
// update shapes geometry // update shapes geometry
WgGeometryDataGroup shapes; WgGeometryDataGroup shapes;
if(rshape.rule == tvg::FillRule::EvenOdd) {
shapes.tesselate(rshape); shapes.tesselate(rshape);
} else if(rshape.rule == tvg::FillRule::Winding) {
WgGeometryDataGroup lines;
lines.tesselate(rshape);
shapes.contours(lines);
}
meshGroupShapes.update(context, &shapes); meshGroupShapes.update(context, &shapes);
// update shapes bbox // update shapes bbox
WgPoint pmin{}, pmax{}; WgPoint pmin{}, pmax{};