[wip, win32, canvas] quadratics

This commit is contained in:
martinfouilleul 2023-07-03 12:14:21 +02:00
parent 899ad4c030
commit 01aa4f838f
4 changed files with 302 additions and 255 deletions

View File

@ -117,16 +117,18 @@ int main()
mg_set_color_rgba(0, 1, 0, 1); mg_set_color_rgba(0, 1, 0, 1);
mg_fill(); mg_fill();
mg_set_color_rgba(0, 1, 1, 1); mg_set_color_rgba(0, 1, 1, 0.5);
mg_rectangle_fill(120, 120, 200, 200); mg_rectangle_fill(120, 120, 200, 200);
/* mg_set_color_rgba(1, 0, 0.5, 1);
mg_move_to(400, 400); mg_rectangle_fill(700, 500, 200, 200);
mg_quadratic_to(600, 601, 800, 400);
mg_move_to(300, 300);
mg_quadratic_to(400, 500, 500, 300);
mg_close_path(); mg_close_path();
mg_set_color_rgba(0, 0, 1, 1); mg_set_color_rgba(0, 0, 1, 1);
mg_fill(); mg_fill();
/*
mg_move_to(2*400, 2*400); mg_move_to(2*400, 2*400);
mg_cubic_to(2*400, 2*200, 2*600, 2*500, 2*600, 2*400); mg_cubic_to(2*400, 2*200, 2*600, 2*500, 2*600, 2*400);
mg_close_path(); mg_close_path();

View File

@ -72,3 +72,99 @@ struct mg_gl_tile_queue
int first; int first;
int last; int last;
}; };
float ccw(vec2 a, vec2 b, vec2 c)
{
return((b.x-a.x)*(c.y-a.y) - (b.y-a.y)*(c.x-a.x));
}
int side_of_segment(vec2 p, mg_gl_segment seg)
{
int side = 0;
if(p.y > seg.box.w || p.y <= seg.box.y)
{
if(p.x > seg.box.x && p.x <= seg.box.z)
{
if(p.y > seg.box.w)
{
side = (seg.config == MG_GL_TL || seg.config == MG_GL_BR)? -1 : 1;
}
else
{
side = (seg.config == MG_GL_TL || seg.config == MG_GL_BR)? 1 : -1;
}
}
}
else if(p.x > seg.box.z)
{
side = 1;
}
else if(p.x <= seg.box.x)
{
side = -1;
}
else
{
vec2 a, b, c;
switch(seg.config)
{
case MG_GL_TL:
a = seg.box.xy;
b = seg.box.zw;
break;
case MG_GL_BR:
a = seg.box.zw;
b = seg.box.xy;
break;
case MG_GL_TR:
a = seg.box.xw;
b = seg.box.zy;
break;
case MG_GL_BL:
a = seg.box.zy;
b = seg.box.xw;
break;
}
c = seg.hullVertex;
if(ccw(a, b, p) < 0)
{
// other side of the diagonal
side = (seg.config == MG_GL_BR || seg.config == MG_GL_TR) ? -1 : 1;
}
else if(ccw(b, c, p) < 0 || ccw(c, a, p) < 0)
{
// same side of the diagonal, but outside curve hull
side = (seg.config == MG_GL_BL || seg.config == MG_GL_TL) ? -1 : 1;
}
else
{
// inside curve hull
switch(seg.kind)
{
case MG_GL_LINE:
side = 1;
break;
case MG_GL_QUADRATIC:
{
vec3 ph = {p.x, p.y, 1};
vec3 klm = seg.implicitMatrix * ph;
side = ((klm.x*klm.x - klm.y)*klm.z < 0)? -1 : 1;
} break;
case MG_GL_CUBIC:
{
vec3 ph = {p.x, p.y, 1};
vec3 klm = seg.implicitMatrix * ph;
side = (seg.sign*(klm.x*klm.x*klm.x - klm.y*klm.z) < 0)? -1 : 1;
} break;
}
}
}
return(side);
}

View File

@ -33,101 +33,6 @@ layout(location = 0) uniform float scale;
layout(rgba8, binding = 0) uniform restrict writeonly image2D outTexture; layout(rgba8, binding = 0) uniform restrict writeonly image2D outTexture;
float ccw(vec2 a, vec2 b, vec2 c)
{
return((b.x-a.x)*(c.y-a.y) - (b.y-a.y)*(c.x-a.x));
}
int side_of_segment(vec2 p, mg_gl_segment seg)
{
int side = 0;
if(p.y > seg.box.w || p.y <= seg.box.y)
{
if(p.x > seg.box.x && p.x <= seg.box.z)
{
if(p.y > seg.box.w)
{
side = (seg.config == MG_GL_TL || seg.config == MG_GL_BR)? -1 : 1;
}
else
{
side = (seg.config == MG_GL_TL || seg.config == MG_GL_BR)? 1 : -1;
}
}
}
else if(p.x > seg.box.z)
{
side = 1;
}
else if(p.x <= seg.box.x)
{
side = -1;
}
else
{
vec2 a, b, c;
switch(seg.config)
{
case MG_GL_TL:
a = seg.box.xy;
b = seg.box.zw;
break;
case MG_GL_BR:
a = seg.box.zw;
b = seg.box.xy;
break;
case MG_GL_TR:
a = seg.box.xw;
b = seg.box.zy;
break;
case MG_GL_BL:
a = seg.box.zy;
b = seg.box.xw;
break;
}
c = seg.hullVertex;
if(ccw(a, b, p) < 0)
{
// other side of the diagonal
side = (seg.config == MG_GL_BR || seg.config == MG_GL_TR) ? -1 : 1;
}
else if(ccw(b, c, p) < 0 || ccw(c, a, p) < 0)
{
// same side of the diagonal, but outside curve hull
side = (seg.config == MG_GL_BL || seg.config == MG_GL_TL) ? -1 : 1;
}
else
{
// inside curve hull
switch(seg.kind)
{
case MG_GL_LINE:
side = 1;
break;
case MG_GL_QUADRATIC:
{
vec3 ph = {p.x, p.y, 1};
vec3 klm = seg.implicitMatrix * ph;
side = ((klm.x*klm.x - klm.y)*klm.z < 0)? -1 : 1;
} break;
case MG_GL_CUBIC:
{
vec3 ph = {p.x, p.y, 1};
vec3 klm = seg.implicitMatrix * ph;
side = (seg.sign*(klm.x*klm.x*klm.x - klm.y*klm.z) < 0)? -1 : 1;
} break;
}
}
}
return(side);
}
void main() void main()
{ {
uvec2 nTiles = gl_NumWorkGroups.xy; uvec2 nTiles = gl_NumWorkGroups.xy;

View File

@ -42,161 +42,6 @@ layout(binding = 6) restrict buffer tileOpBufferSSBO
layout(location = 0) uniform float scale; layout(location = 0) uniform float scale;
layout(location = 1) uniform uint tileSize; layout(location = 1) uniform uint tileSize;
int push_segment(in vec2 p[4], int kind, int pathIndex)
{
int segIndex = atomicAdd(segmentCountBuffer.elements[0], 1);
vec2 s = p[0];
vec2 c = p[0];
vec2 e = p[1];
bool goingUp = e.y >= s.y;
bool goingRight = e.x >= s.x;
vec4 box = vec4(min(s.x, e.x),
min(s.y, e.y),
max(s.x, e.x),
max(s.y, e.y));
segmentBuffer.elements[segIndex].kind = kind;
segmentBuffer.elements[segIndex].pathIndex = pathIndex;
segmentBuffer.elements[segIndex].windingIncrement = goingUp ? 1 : -1;
segmentBuffer.elements[segIndex].box = box;
float dx = c.x - box.x;
float dy = c.y - box.y;
float alpha = (box.w - box.y)/(box.z - box.x);
float ofs = box.w - box.y;
if(goingUp == goingRight)
{
if(kind == MG_GL_LINE)
{
segmentBuffer.elements[segIndex].config = MG_GL_BR;
}
else if(dy > alpha*dx)
{
segmentBuffer.elements[segIndex].config = MG_GL_TL;
}
else
{
segmentBuffer.elements[segIndex].config = MG_GL_BR;
}
}
else
{
if(kind == MG_GL_LINE)
{
segmentBuffer.elements[segIndex].config = MG_GL_TR;
}
else if(dy < ofs - alpha*dx)
{
segmentBuffer.elements[segIndex].config = MG_GL_BL;
}
else
{
segmentBuffer.elements[segIndex].config = MG_GL_TR;
}
}
return(segIndex);
}
float ccw(vec2 a, vec2 b, vec2 c)
{
return((b.x-a.x)*(c.y-a.y) - (b.y-a.y)*(c.x-a.x));
}
int side_of_segment(vec2 p, mg_gl_segment seg)
{
int side = 0;
if(p.y > seg.box.w || p.y <= seg.box.y)
{
if(p.x > seg.box.x && p.x <= seg.box.z)
{
if(p.y > seg.box.w)
{
side = (seg.config == MG_GL_TL || seg.config == MG_GL_BR)? -1 : 1;
}
else
{
side = (seg.config == MG_GL_TL || seg.config == MG_GL_BR)? 1 : -1;
}
}
}
else if(p.x > seg.box.z)
{
side = 1;
}
else if(p.x <= seg.box.x)
{
side = -1;
}
else
{
vec2 a, b, c;
switch(seg.config)
{
case MG_GL_TL:
a = seg.box.xy;
b = seg.box.zw;
break;
case MG_GL_BR:
a = seg.box.zw;
b = seg.box.xy;
break;
case MG_GL_TR:
a = seg.box.xw;
b = seg.box.zy;
break;
case MG_GL_BL:
a = seg.box.zy;
b = seg.box.xw;
break;
}
c = seg.hullVertex;
if(ccw(a, b, p) < 0)
{
// other side of the diagonal
side = (seg.config == MG_GL_BR || seg.config == MG_GL_TR) ? -1 : 1;
}
else if(ccw(b, c, p) < 0 || ccw(c, a, p) < 0)
{
// same side of the diagonal, but outside curve hull
side = (seg.config == MG_GL_BL || seg.config == MG_GL_TL) ? -1 : 1;
}
else
{
// inside curve hull
switch(seg.kind)
{
case MG_GL_LINE:
side = 1;
break;
case MG_GL_QUADRATIC:
{
vec3 ph = {p.x, p.y, 1};
vec3 klm = seg.implicitMatrix * ph;
side = ((klm.x*klm.x - klm.y)*klm.z < 0)? -1 : 1;
} break;
case MG_GL_CUBIC:
{
vec3 ph = {p.x, p.y, 1};
vec3 klm = seg.implicitMatrix * ph;
side = (seg.sign*(klm.x*klm.x*klm.x - klm.y*klm.z) < 0)? -1 : 1;
} break;
}
}
}
return(side);
}
void bin_to_tiles(int segIndex) void bin_to_tiles(int segIndex)
{ {
//NOTE: add segment index to the queues of tiles it overlaps with //NOTE: add segment index to the queues of tiles it overlaps with
@ -288,6 +133,98 @@ void bin_to_tiles(int segIndex)
} }
} }
int push_segment(in vec2 p[4], int kind, int pathIndex)
{
int segIndex = atomicAdd(segmentCountBuffer.elements[0], 1);
vec2 s, c, e;
switch(kind)
{
case MG_GL_LINE:
s = p[0];
c = p[0];
e = p[1];
break;
case MG_GL_QUADRATIC:
s = p[0];
c = p[1];
e = p[2];
break;
case MG_GL_CUBIC:
{
s = p[0];
float sqrNorm0 = dot(p[1]-p[0], p[1]-p[0]);
float sqrNorm1 = dot(p[3]-p[2], p[3]-p[2]);
if(sqrNorm0 < sqrNorm1)
{
c = p[2];
}
else
{
c = p[1];
}
e = p[3];
} break;
}
bool goingUp = e.y >= s.y;
bool goingRight = e.x >= s.x;
vec4 box = vec4(min(s.x, e.x),
min(s.y, e.y),
max(s.x, e.x),
max(s.y, e.y));
segmentBuffer.elements[segIndex].kind = kind;
segmentBuffer.elements[segIndex].pathIndex = pathIndex;
segmentBuffer.elements[segIndex].windingIncrement = goingUp ? 1 : -1;
segmentBuffer.elements[segIndex].box = box;
float dx = c.x - box.x;
float dy = c.y - box.y;
float alpha = (box.w - box.y)/(box.z - box.x);
float ofs = box.w - box.y;
if(goingUp == goingRight)
{
if(kind == MG_GL_LINE)
{
segmentBuffer.elements[segIndex].config = MG_GL_BR;
}
else if(dy > alpha*dx)
{
segmentBuffer.elements[segIndex].config = MG_GL_TL;
}
else
{
segmentBuffer.elements[segIndex].config = MG_GL_BR;
}
}
else
{
if(kind == MG_GL_LINE)
{
segmentBuffer.elements[segIndex].config = MG_GL_TR;
}
else if(dy < ofs - alpha*dx)
{
segmentBuffer.elements[segIndex].config = MG_GL_BL;
}
else
{
segmentBuffer.elements[segIndex].config = MG_GL_TR;
}
}
return(segIndex);
}
#define square(x) ((x)*(x))
#define cube(x) ((x)*(x)*(x))
void line_setup(vec2 p[4], int pathIndex) void line_setup(vec2 p[4], int pathIndex)
{ {
int segIndex = push_segment(p, MG_GL_LINE, pathIndex); int segIndex = push_segment(p, MG_GL_LINE, pathIndex);
@ -296,6 +233,107 @@ void line_setup(vec2 p[4], int pathIndex)
bin_to_tiles(segIndex); bin_to_tiles(segIndex);
} }
vec2 quadratic_blossom(vec2 p[4], float u, float v)
{
vec2 b10 = u*p[1] + (1-u)*p[0];
vec2 b11 = u*p[2] + (1-u)*p[1];
vec2 b20 = v*b11 + (1-v)*b10;
return(b20);
}
void quadratic_slice(vec2 p[4], float s0, float s1, out vec2 sp[4])
{
/*NOTE: using blossoms to compute sub-curve control points ensure that the fourth point
of sub-curve (s0, s1) and the first point of sub-curve (s1, s3) match.
However, due to numerical errors, the evaluation of B(s=0) might not be equal to
p[0] (and likewise, B(s=1) might not equal p[3]).
We handle that case explicitly to ensure that we don't create gaps in the paths.
*/
sp[0] = (s0 == 0) ? p[0] : quadratic_blossom(p, s0, s0);
sp[1] = quadratic_blossom(p, s0, s1);
sp[2] = (s1 == 1) ? p[2] : quadratic_blossom(p, s1, s1);
}
int quadratic_monotonize(vec2 p[4], out float splits[4])
{
//NOTE: compute split points
int count = 0;
splits[0] = 0;
count++;
vec2 r = (p[0] - p[1])/(p[2] - 2*p[1] + p[0]);
if(r.x > r.y)
{
float tmp = r.x;
r.x = r.y;
r.y = tmp;
}
if(r.x > 0 && r.x < 1)
{
splits[count] = r.x;
count++;
}
if(r.y > 0 && r.y < 1)
{
splits[count] = r.y;
count++;
}
splits[count] = 1;
count++;
return(count);
}
mat3 barycentric_matrix(vec2 v0, vec2 v1, vec2 v2)
{
float det = v0.x*(v1.y-v2.y) + v1.x*(v2.y-v0.y) + v2.x*(v0.y - v1.y);
mat3 B = {{v1.y - v2.y, v2.y-v0.y, v0.y-v1.y},
{v2.x - v1.x, v0.x-v2.x, v1.x-v0.x},
{v1.x*v2.y-v2.x*v1.y, v2.x*v0.y-v0.x*v2.y, v0.x*v1.y-v1.x*v0.y}};
B *= (1/det);
return(B);
}
void quadratic_emit(vec2 p[4], int pathIndex)
{
int segIndex = push_segment(p, MG_GL_QUADRATIC, pathIndex);
//NOTE: compute implicit equation matrix
float det = p[0].x*(p[1].y-p[2].y) + p[1].x*(p[2].y-p[0].y) + p[2].x*(p[0].y - p[1].y);
float a = p[0].y - p[1].y + 0.5*(p[2].y - p[0].y);
float b = p[1].x - p[0].x + 0.5*(p[0].x - p[2].x);
float c = p[0].x*p[1].y - p[1].x*p[0].y + 0.5*(p[2].x*p[0].y - p[0].x*p[2].y);
float d = p[0].y - p[1].y;
float e = p[1].x - p[0].x;
float f = p[0].x*p[1].y - p[1].x*p[0].y;
float flip = ( segmentBuffer.elements[segIndex].config == MG_GL_TL
|| segmentBuffer.elements[segIndex].config == MG_GL_BL)? -1 : 1;
float g = flip*(p[2].x*(p[0].y - p[1].y) + p[0].x*(p[1].y - p[2].y) + p[1].x*(p[2].y - p[0].y));
segmentBuffer.elements[segIndex].implicitMatrix = (1/det)*mat3(a, d, 0.,
b, e, 0.,
c, f, g);
segmentBuffer.elements[segIndex].hullVertex = p[1];
bin_to_tiles(segIndex);
}
void quadratic_setup(vec2 p[4], int pathIndex)
{
float splits[4];
int splitCount = quadratic_monotonize(p, splits);
//NOTE: produce bézier curve for each consecutive pair of roots
for(int sliceIndex=0; sliceIndex<splitCount-1; sliceIndex++)
{
vec2 sp[4];
quadratic_slice(p, splits[sliceIndex], splits[sliceIndex+1], sp);
quadratic_emit(sp, pathIndex);
}
}
void main() void main()
{ {
int eltIndex = int(gl_WorkGroupID.x); int eltIndex = int(gl_WorkGroupID.x);
@ -310,6 +348,12 @@ void main()
line_setup(p, elt.pathIndex); line_setup(p, elt.pathIndex);
} break; } break;
case MG_GL_QUADRATIC:
{
vec2 p[4] = {elt.p[0]*scale, elt.p[1]*scale, elt.p[2]*scale, vec2(0)};
quadratic_setup(p, elt.pathIndex);
} break;
default: default:
break; break;
} }