[wip, win32, canvas] quadratics
This commit is contained in:
parent
899ad4c030
commit
01aa4f838f
|
@ -117,16 +117,18 @@ int main()
|
|||
mg_set_color_rgba(0, 1, 0, 1);
|
||||
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_move_to(400, 400);
|
||||
mg_quadratic_to(600, 601, 800, 400);
|
||||
mg_set_color_rgba(1, 0, 0.5, 1);
|
||||
mg_rectangle_fill(700, 500, 200, 200);
|
||||
|
||||
mg_move_to(300, 300);
|
||||
mg_quadratic_to(400, 500, 500, 300);
|
||||
mg_close_path();
|
||||
mg_set_color_rgba(0, 0, 1, 1);
|
||||
mg_fill();
|
||||
|
||||
/*
|
||||
mg_move_to(2*400, 2*400);
|
||||
mg_cubic_to(2*400, 2*200, 2*600, 2*500, 2*600, 2*400);
|
||||
mg_close_path();
|
||||
|
|
|
@ -72,3 +72,99 @@ struct mg_gl_tile_queue
|
|||
int first;
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -33,101 +33,6 @@ layout(location = 0) uniform float scale;
|
|||
|
||||
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()
|
||||
{
|
||||
uvec2 nTiles = gl_NumWorkGroups.xy;
|
||||
|
|
|
@ -42,161 +42,6 @@ layout(binding = 6) restrict buffer tileOpBufferSSBO
|
|||
layout(location = 0) uniform float scale;
|
||||
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)
|
||||
{
|
||||
//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)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
int eltIndex = int(gl_WorkGroupID.x);
|
||||
|
@ -310,6 +348,12 @@ void main()
|
|||
line_setup(p, elt.pathIndex);
|
||||
} 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:
|
||||
break;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue