diff --git a/examples/polygon/main.c b/examples/polygon/main.c index c44f71e..35572b4 100644 --- a/examples/polygon/main.c +++ b/examples/polygon/main.c @@ -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(); diff --git a/src/glsl_shaders/common.glsl b/src/glsl_shaders/common.glsl index 93d35ab..a679600 100644 --- a/src/glsl_shaders/common.glsl +++ b/src/glsl_shaders/common.glsl @@ -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); +} diff --git a/src/glsl_shaders/raster.glsl b/src/glsl_shaders/raster.glsl index 7e7e82f..95930ce 100644 --- a/src/glsl_shaders/raster.glsl +++ b/src/glsl_shaders/raster.glsl @@ -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; diff --git a/src/glsl_shaders/segment_setup.glsl b/src/glsl_shaders/segment_setup.glsl index bdac007..90d3743 100644 --- a/src/glsl_shaders/segment_setup.glsl +++ b/src/glsl_shaders/segment_setup.glsl @@ -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