868 lines
22 KiB
GLSL
868 lines
22 KiB
GLSL
|
|
layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
|
|
|
|
precision mediump float;
|
|
layout(std430) buffer;
|
|
|
|
layout(binding = 0) restrict readonly buffer elementBufferSSBO
|
|
{
|
|
oc_gl_path_elt elements[];
|
|
} elementBuffer;
|
|
|
|
layout(binding = 1) coherent restrict buffer segmentCountBufferSSBO
|
|
{
|
|
int elements[];
|
|
} segmentCountBuffer;
|
|
|
|
layout(binding = 2) restrict buffer segmentBufferSSBO
|
|
{
|
|
oc_gl_segment elements[];
|
|
} segmentBuffer;
|
|
|
|
layout(binding = 3) restrict buffer pathQueueBufferSSBO
|
|
{
|
|
oc_gl_path_queue elements[];
|
|
} pathQueueBuffer;
|
|
|
|
layout(binding = 4) coherent restrict buffer tileQueueBufferSSBO
|
|
{
|
|
oc_gl_tile_queue elements[];
|
|
} tileQueueBuffer;
|
|
|
|
layout(binding = 5) coherent restrict buffer tileOpCountBufferSSBO
|
|
{
|
|
int elements[];
|
|
} tileOpCountBuffer;
|
|
|
|
layout(binding = 6) restrict buffer tileOpBufferSSBO
|
|
{
|
|
oc_gl_tile_op elements[];
|
|
} tileOpBuffer;
|
|
|
|
layout(location = 0) uniform float scale;
|
|
layout(location = 1) uniform uint tileSize;
|
|
layout(location = 2) uniform int elementBufferStart;
|
|
|
|
void bin_to_tiles(int segIndex)
|
|
{
|
|
//NOTE: add segment index to the queues of tiles it overlaps with
|
|
const oc_gl_segment seg = segmentBuffer.elements[segIndex];
|
|
const oc_gl_path_queue pathQueue = pathQueueBuffer.elements[seg.pathIndex];
|
|
|
|
ivec4 pathArea = pathQueue.area;
|
|
ivec4 coveredTiles = ivec4(seg.box)/int(tileSize);
|
|
int xMin = max(0, coveredTiles.x - pathArea.x);
|
|
int yMin = max(0, coveredTiles.y - pathArea.y);
|
|
int xMax = min(coveredTiles.z - pathArea.x, pathArea.z-1);
|
|
int yMax = min(coveredTiles.w - pathArea.y, pathArea.w-1);
|
|
|
|
for(int y = yMin; y <= yMax; y++)
|
|
{
|
|
for(int x = xMin ; x <= xMax; x++)
|
|
{
|
|
vec4 tileBox = vec4(float(x + pathArea.x),
|
|
float(y + pathArea.y),
|
|
float(x + pathArea.x + 1),
|
|
float(y + pathArea.y + 1)) * float(tileSize);
|
|
|
|
vec2 bl = {tileBox.x, tileBox.y};
|
|
vec2 br = {tileBox.z, tileBox.y};
|
|
vec2 tr = {tileBox.z, tileBox.w};
|
|
vec2 tl = {tileBox.x, tileBox.w};
|
|
|
|
int sbl = side_of_segment(bl, seg);
|
|
int sbr = side_of_segment(br, seg);
|
|
int str = side_of_segment(tr, seg);
|
|
int stl = side_of_segment(tl, seg);
|
|
|
|
bool crossL = (stl*sbl < 0);
|
|
bool crossR = (str*sbr < 0);
|
|
bool crossT = (stl*str < 0);
|
|
bool crossB = (sbl*sbr < 0);
|
|
|
|
vec2 s0, s1;
|
|
if(seg.config == OC_GL_TL||seg.config == OC_GL_BR)
|
|
{
|
|
s0 = seg.box.xy;
|
|
s1 = seg.box.zw;
|
|
}
|
|
else
|
|
{
|
|
s0 = seg.box.xw;
|
|
s1 = seg.box.zy;
|
|
}
|
|
bool s0Inside = s0.x >= tileBox.x
|
|
&& s0.x < tileBox.z
|
|
&& s0.y >= tileBox.y
|
|
&& s0.y < tileBox.w;
|
|
|
|
bool s1Inside = s1.x >= tileBox.x
|
|
&& s1.x < tileBox.z
|
|
&& s1.y >= tileBox.y
|
|
&& s1.y < tileBox.w;
|
|
|
|
if(crossL || crossR || crossT || crossB || s0Inside || s1Inside)
|
|
{
|
|
int tileOpIndex = atomicAdd(tileOpCountBuffer.elements[0], 1);
|
|
|
|
if(tileOpIndex < tileOpBuffer.elements.length())
|
|
{
|
|
tileOpBuffer.elements[tileOpIndex].kind = OC_GL_OP_SEGMENT;
|
|
tileOpBuffer.elements[tileOpIndex].index = segIndex;
|
|
tileOpBuffer.elements[tileOpIndex].windingOffsetOrCrossRight = 0;
|
|
tileOpBuffer.elements[tileOpIndex].next = -1;
|
|
|
|
int tileQueueIndex = pathQueue.tileQueues + y*pathArea.z + x;
|
|
|
|
tileOpBuffer.elements[tileOpIndex].next = atomicExchange(tileQueueBuffer.elements[tileQueueIndex].first,
|
|
tileOpIndex);
|
|
if(tileOpBuffer.elements[tileOpIndex].next == -1)
|
|
{
|
|
tileQueueBuffer.elements[tileQueueIndex].last = tileOpIndex;
|
|
}
|
|
|
|
//NOTE: if the segment crosses the tile's bottom boundary, update the tile's winding offset
|
|
if(crossB)
|
|
{
|
|
atomicAdd(tileQueueBuffer.elements[tileQueueIndex].windingOffset, seg.windingIncrement);
|
|
}
|
|
|
|
//NOTE: if the segment crosses the right boundary, mark it.
|
|
if(crossR)
|
|
{
|
|
tileOpBuffer.elements[tileOpIndex].windingOffsetOrCrossRight = 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
int push_segment(in vec2 p[4], int kind, int pathIndex)
|
|
{
|
|
int segIndex = atomicAdd(segmentCountBuffer.elements[0], 1);
|
|
|
|
if(segIndex < segmentBuffer.elements.length())
|
|
{
|
|
vec2 s, c, e;
|
|
|
|
switch(kind)
|
|
{
|
|
case OC_GL_LINE:
|
|
s = p[0];
|
|
c = p[0];
|
|
e = p[1];
|
|
break;
|
|
|
|
case OC_GL_QUADRATIC:
|
|
s = p[0];
|
|
c = p[1];
|
|
e = p[2];
|
|
break;
|
|
|
|
case OC_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 == OC_GL_LINE)
|
|
{
|
|
segmentBuffer.elements[segIndex].config = OC_GL_BR;
|
|
}
|
|
else if(dy > alpha*dx)
|
|
{
|
|
segmentBuffer.elements[segIndex].config = OC_GL_TL;
|
|
}
|
|
else
|
|
{
|
|
segmentBuffer.elements[segIndex].config = OC_GL_BR;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(kind == OC_GL_LINE)
|
|
{
|
|
segmentBuffer.elements[segIndex].config = OC_GL_TR;
|
|
}
|
|
else if(dy < ofs - alpha*dx)
|
|
{
|
|
segmentBuffer.elements[segIndex].config = OC_GL_BL;
|
|
}
|
|
else
|
|
{
|
|
segmentBuffer.elements[segIndex].config = OC_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, OC_GL_LINE, pathIndex);
|
|
if(segIndex < segmentBuffer.elements.length())
|
|
{
|
|
segmentBuffer.elements[segIndex].hullVertex = p[0];
|
|
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, OC_GL_QUADRATIC, pathIndex);
|
|
|
|
if(segIndex < segmentBuffer.elements.length())
|
|
{
|
|
//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 == OC_GL_TL
|
|
|| segmentBuffer.elements[segIndex].config == OC_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);
|
|
}
|
|
}
|
|
|
|
int quadratic_roots_with_det(float a, float b, float c, float det, out float r[2])
|
|
{
|
|
int count = 0;
|
|
|
|
if(a == 0)
|
|
{
|
|
if(b != 0)
|
|
{
|
|
count = 1;
|
|
r[0] = -c/b;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
b /= 2.0;
|
|
|
|
if(det >= 0)
|
|
{
|
|
count = (det == 0) ? 1 : 2;
|
|
|
|
if(b > 0)
|
|
{
|
|
float q = b + sqrt(det);
|
|
r[0] = -c/q;
|
|
r[1] = -q/a;
|
|
}
|
|
else if(b < 0)
|
|
{
|
|
float q = -b + sqrt(det);
|
|
r[0] = q/a;
|
|
r[1] = c/q;
|
|
}
|
|
else
|
|
{
|
|
float q = sqrt(-a*c);
|
|
if(abs(a) >= abs(c))
|
|
{
|
|
r[0] = q/a;
|
|
r[1] = -q/a;
|
|
}
|
|
else
|
|
{
|
|
r[0] = -c/q;
|
|
r[1] = c/q;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if(count>1 && r[0] > r[1])
|
|
{
|
|
float tmp = r[0];
|
|
r[0] = r[1];
|
|
r[1] = tmp;
|
|
}
|
|
return(count);
|
|
}
|
|
|
|
int quadratic_roots(float a, float b, float c, out float r[2])
|
|
{
|
|
float det = square(b)/4. - a*c;
|
|
return(quadratic_roots_with_det(a, b, c, det, r));
|
|
}
|
|
|
|
vec2 cubic_blossom(vec2 p[4], float u, float v, float w)
|
|
{
|
|
vec2 b10 = u*p[1] + (1-u)*p[0];
|
|
vec2 b11 = u*p[2] + (1-u)*p[1];
|
|
vec2 b12 = u*p[3] + (1-u)*p[2];
|
|
vec2 b20 = v*b11 + (1-v)*b10;
|
|
vec2 b21 = v*b12 + (1-v)*b11;
|
|
vec2 b30 = w*b21 + (1-w)*b20;
|
|
return(b30);
|
|
}
|
|
|
|
void cubic_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] : cubic_blossom(p, s0, s0, s0);
|
|
sp[1] = cubic_blossom(p, s0, s0, s1);
|
|
sp[2] = cubic_blossom(p, s0, s1, s1);
|
|
sp[3] = (s1 == 1) ? p[3] : cubic_blossom(p, s1, s1, s1);
|
|
}
|
|
|
|
#define CUBIC_ERROR 0
|
|
#define CUBIC_SERPENTINE 1
|
|
#define CUBIC_CUSP 2
|
|
#define CUBIC_CUSP_INFINITY 3
|
|
#define CUBIC_LOOP 4
|
|
#define CUBIC_DEGENERATE_QUADRATIC 5
|
|
#define CUBIC_DEGENERATE_LINE 6
|
|
|
|
struct cubic_info
|
|
{
|
|
int kind;
|
|
mat4 K;
|
|
vec2 ts[2];
|
|
float d1;
|
|
float d2;
|
|
float d3;
|
|
};
|
|
|
|
cubic_info cubic_classify(vec2 c[4])
|
|
{
|
|
cubic_info result;
|
|
result.kind = CUBIC_ERROR;
|
|
mat4 F;
|
|
|
|
/*NOTE(martin):
|
|
now, compute determinants d0, d1, d2, d3, which gives the coefficients of the
|
|
inflection points polynomial:
|
|
|
|
I(t, s) = d0*t^3 - 3*d1*t^2*s + 3*d2*t*s^2 - d3*s^3
|
|
|
|
The roots of this polynomial are the inflection points of the parametric curve, in homogeneous
|
|
coordinates (ie we can have an inflection point at inifinity with s=0).
|
|
|
|
|x3 y3 w3| |x3 y3 w3| |x3 y3 w3| |x2 y2 w2|
|
|
d0 = det |x2 y2 w2| d1 = -det |x2 y2 w2| d2 = det |x1 y1 w1| d3 = -det |x1 y1 w1|
|
|
|x1 y1 w1| |x0 y0 w0| |x0 y0 w0| |x0 y0 w0|
|
|
|
|
In our case, the pi.w equal 1 (no point at infinity), so _in_the_power_basis_, w1 = w2 = w3 = 0 and w0 = 1
|
|
(which also means d0 = 0)
|
|
|
|
//WARN: there seems to be a mismatch between the signs of the d_i and the orientation test in the Loop-Blinn paper?
|
|
// flipping the sign of the d_i doesn't change the roots (and the implicit matrix), but it does change the orientation.
|
|
// Keeping the signs of the paper puts the interior on the left of parametric travel, unlike what's stated in the paper.
|
|
// this may very well be an error on my part that's cancelled by flipping the signs of the d_i though!
|
|
*/
|
|
|
|
float d1 = -(c[3].y*c[2].x - c[3].x*c[2].y);
|
|
float d2 = -(c[3].x*c[1].y - c[3].y*c[1].x);
|
|
float d3 = -(c[2].y*c[1].x - c[2].x*c[1].y);
|
|
|
|
result.d1 = d1;
|
|
result.d2 = d2;
|
|
result.d3 = d3;
|
|
|
|
//NOTE(martin): compute the second factor of the discriminant discr(I) = d1^2*(3*d2^2 - 4*d3*d1)
|
|
float discrFactor2 = 3.0*square(d2) - 4.0*d3*d1;
|
|
|
|
//NOTE(martin): each following case gives the number of roots, hence the category of the parametric curve
|
|
if(abs(d1) <= 1e-6 && abs(d2) <= 1e-6 && abs(d3) > 1e-6)
|
|
{
|
|
//NOTE(martin): quadratic degenerate case
|
|
//NOTE(martin): compute quadratic curve control point, which is at p0 + 1.5*(p1-p0) = 1.5*p1 - 0.5*p0
|
|
result.kind = CUBIC_DEGENERATE_QUADRATIC;
|
|
}
|
|
else if( (discrFactor2 > 0 && abs(d1) > 1e-6)
|
|
||(discrFactor2 == 0 && abs(d1) > 1e-6))
|
|
{
|
|
//NOTE(martin): serpentine curve or cusp with inflection at infinity
|
|
// (these two cases are handled the same way).
|
|
//NOTE(martin): compute the solutions (tl, sl), (tm, sm), and (tn, sn) of the inflection point equation
|
|
float tmtl[2];
|
|
quadratic_roots_with_det(1, -2*d2, (4./3.*d1*d3), (1./3.)*discrFactor2, tmtl);
|
|
|
|
float tm = tmtl[0];
|
|
float sm = 2*d1;
|
|
float tl = tmtl[1];
|
|
float sl = 2*d1;
|
|
|
|
float invNorm = 1/sqrt(square(tm) + square(sm));
|
|
tm *= invNorm;
|
|
sm *= invNorm;
|
|
|
|
invNorm = 1/sqrt(square(tl) + square(sl));
|
|
tl *= invNorm;
|
|
sl *= invNorm;
|
|
|
|
/*NOTE(martin):
|
|
the power basis coefficients of points k,l,m,n are collected into the rows of the 4x4 matrix F:
|
|
|
|
| tl*tm tl^3 tm^3 1 |
|
|
| -sm*tl - sl*tm -3sl*tl^2 -3*sm*tm^2 0 |
|
|
| sl*sm 3*sl^2*tl 3*sm^2*tm 0 |
|
|
| 0 -sl^3 -sm^3 0 |
|
|
*/
|
|
result.kind = (discrFactor2 > 0 && d1 != 0) ? CUBIC_SERPENTINE : CUBIC_CUSP;
|
|
|
|
F = mat4(tl*tm, -sm*tl-sl*tm, sl*sm, 0,
|
|
cube(tl), -3*sl*square(tl), 3*square(sl)*tl, -cube(sl),
|
|
cube(tm), -3*sm*square(tm), 3*square(sm)*tm, -cube(sm),
|
|
1, 0, 0, 0);
|
|
|
|
result.ts[0] = vec2(tm, sm);
|
|
result.ts[1] = vec2(tl, sl);
|
|
}
|
|
else if(discrFactor2 < 0 && abs(d1) > 1e-6)
|
|
{
|
|
//NOTE(martin): loop curve
|
|
result.kind = CUBIC_LOOP;
|
|
|
|
float tetd[2];
|
|
quadratic_roots_with_det(1, -2*d2, 4*(square(d2)-d1*d3), -discrFactor2, tetd);
|
|
|
|
float td = tetd[1];
|
|
float sd = 2*d1;
|
|
float te = tetd[0];
|
|
float se = 2*d1;
|
|
|
|
float invNorm = 1/sqrt(square(td) + square(sd));
|
|
td *= invNorm;
|
|
sd *= invNorm;
|
|
|
|
invNorm = 1/sqrt(square(te) + square(se));
|
|
te *= invNorm;
|
|
se *= invNorm;
|
|
|
|
/*NOTE(martin):
|
|
the power basis coefficients of points k,l,m,n are collected into the rows of the 4x4 matrix F:
|
|
|
|
| td*te td^2*te td*te^2 1 |
|
|
| -se*td - sd*te -se*td^2 - 2sd*te*td -sd*te^2 - 2*se*td*te 0 |
|
|
| sd*se te*sd^2 + 2*se*td*sd td*se^2 + 2*sd*te*se 0 |
|
|
| 0 -sd^2*se -sd*se^2 0 |
|
|
*/
|
|
F = mat4(td*te, -se*td-sd*te, sd*se, 0,
|
|
square(td)*te, -se*square(td)-2*sd*te*td, te*square(sd)+2*se*td*sd, -square(sd)*se,
|
|
td*square(te), -sd*square(te)-2*se*td*te, td*square(se)+2*sd*te*se, -sd*square(se),
|
|
1, 0, 0, 0);
|
|
|
|
result.ts[0] = vec2(td, sd);
|
|
result.ts[1] = vec2(te, se);
|
|
}
|
|
else if(d2 != 0)
|
|
{
|
|
//NOTE(martin): cusp with cusp at infinity
|
|
float tl = d3;
|
|
float sl = 3*d2;
|
|
|
|
float invNorm = 1/sqrt(square(tl)+square(sl));
|
|
tl *= invNorm;
|
|
sl *= invNorm;
|
|
|
|
/*NOTE(martin):
|
|
the power basis coefficients of points k,l,m,n are collected into the rows of the 4x4 matrix F:
|
|
|
|
| tl tl^3 1 1 |
|
|
| -sl -3sl*tl^2 0 0 |
|
|
| 0 3*sl^2*tl 0 0 |
|
|
| 0 -sl^3 0 0 |
|
|
*/
|
|
result.kind = CUBIC_CUSP_INFINITY;
|
|
|
|
F = mat4(tl, -sl, 0, 0,
|
|
cube(tl), -3*sl*square(tl), 3*square(sl)*tl, -cube(sl),
|
|
1, 0, 0, 0,
|
|
1, 0, 0, 0);
|
|
|
|
result.ts[0] = vec2(tl, sl);
|
|
result.ts[1] = vec2(0, 0);
|
|
}
|
|
else
|
|
{
|
|
//NOTE(martin): line or point degenerate case
|
|
result.kind = CUBIC_DEGENERATE_LINE;
|
|
}
|
|
|
|
/*
|
|
F is then multiplied by M3^(-1) on the left which yelds the bezier coefficients k, l, m, n
|
|
at the control points.
|
|
|
|
| 1 0 0 0 |
|
|
M3^(-1) = | 1 1/3 0 0 |
|
|
| 1 2/3 1/3 0 |
|
|
| 1 1 1 1 |
|
|
*/
|
|
mat4 invM3 = mat4(1, 1, 1, 1,
|
|
0, 1./3., 2./3., 1,
|
|
0, 0, 1./3., 1,
|
|
0, 0, 0, 1);
|
|
|
|
result.K = transpose(invM3*F);
|
|
|
|
return(result);
|
|
}
|
|
|
|
vec2 select_hull_vertex(vec2 p0, vec2 p1, vec2 p2, vec2 p3)
|
|
{
|
|
/*NOTE: check intersection of lines (p1-p0) and (p3-p2)
|
|
P = p0 + u(p1-p0)
|
|
P = p2 + w(p3-p2)
|
|
|
|
control points are inside a right triangle so we should always find an intersection
|
|
*/
|
|
vec2 pm;
|
|
|
|
float det = (p1.x - p0.x)*(p3.y - p2.y) - (p1.y - p0.y)*(p3.x - p2.x);
|
|
float sqrNorm0 = dot(p1-p0, p1-p0);
|
|
float sqrNorm1 = dot(p2-p3, p2-p3);
|
|
|
|
if(abs(det) < 1e-3 || sqrNorm0 < 0.1 || sqrNorm1 < 0.1)
|
|
{
|
|
if(sqrNorm0 < sqrNorm1)
|
|
{
|
|
pm = p2;
|
|
}
|
|
else
|
|
{
|
|
pm = p1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
float u = ((p0.x - p2.x)*(p2.y - p3.y) - (p0.y - p2.y)*(p2.x - p3.x))/det;
|
|
pm = p0 + u*(p1-p0);
|
|
}
|
|
return(pm);
|
|
}
|
|
|
|
void cubic_emit(cubic_info curve, vec2 p[4], float s0, float s1, vec2 sp[4], int pathIndex)
|
|
{
|
|
int segIndex = push_segment(sp, OC_GL_CUBIC, pathIndex);
|
|
|
|
if(segIndex < segmentBuffer.elements.length())
|
|
{
|
|
vec2 v0 = p[0];
|
|
vec2 v1 = p[3];
|
|
vec2 v2;
|
|
mat3 K;
|
|
|
|
//TODO: haul that up in caller
|
|
float sqrNorm0 = dot(p[1]-p[0], p[1]-p[0]);
|
|
float sqrNorm1 = dot(p[2]-p[3], p[2]-p[3]);
|
|
|
|
if(dot(p[0]-p[3], p[0]-p[3]) > 1e-5)
|
|
{
|
|
if(sqrNorm0 >= sqrNorm1)
|
|
{
|
|
v2 = p[1];
|
|
K = mat3(curve.K[0].xyz, curve.K[3].xyz, curve.K[1].xyz);
|
|
}
|
|
else
|
|
{
|
|
v2 = p[2];
|
|
K = mat3(curve.K[0].xyz, curve.K[3].xyz, curve.K[2].xyz);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
v1 = p[1];
|
|
v2 = p[2];
|
|
K = mat3(curve.K[0].xyz, curve.K[1].xyz, curve.K[2].xyz);
|
|
}
|
|
//NOTE: set matrices
|
|
|
|
//TODO: should we compute matrix relative to a base point to avoid loss of precision
|
|
// when computing barycentric matrix?
|
|
|
|
mat3 B = barycentric_matrix(v0, v1, v2);
|
|
|
|
segmentBuffer.elements[segIndex].implicitMatrix = K*B;
|
|
segmentBuffer.elements[segIndex].hullVertex = select_hull_vertex(sp[0], sp[1], sp[2], sp[3]);
|
|
|
|
//NOTE: compute sign flip
|
|
segmentBuffer.elements[segIndex].sign = 1;
|
|
|
|
if( curve.kind == CUBIC_SERPENTINE
|
|
|| curve.kind == CUBIC_CUSP)
|
|
{
|
|
segmentBuffer.elements[segIndex].sign = (curve.d1 < 0)? -1 : 1;
|
|
}
|
|
else if(curve.kind == CUBIC_LOOP)
|
|
{
|
|
float d1 = curve.d1;
|
|
float d2 = curve.d2;
|
|
float d3 = curve.d3;
|
|
|
|
float H0 = d3*d1-square(d2) + d1*d2*s0 - square(d1)*square(s0);
|
|
float H1 = d3*d1-square(d2) + d1*d2*s1 - square(d1)*square(s1);
|
|
float H = (abs(H0) > abs(H1)) ? H0 : H1;
|
|
segmentBuffer.elements[segIndex].sign = (H*d1 > 0) ? -1 : 1;
|
|
}
|
|
|
|
if(sp[3].y > sp[0].y)
|
|
{
|
|
segmentBuffer.elements[segIndex].sign *= -1;
|
|
}
|
|
|
|
//NOTE: bin to tiles
|
|
bin_to_tiles(segIndex);
|
|
}
|
|
}
|
|
|
|
void cubic_setup(vec2 p[4], int pathIndex)
|
|
{
|
|
/*NOTE(martin): first convert the control points to power basis, multiplying by M3
|
|
|
|
| 1 0 0 0| |p0| |c0|
|
|
M3 = |-3 3 0 0|, B = |p1|, C = |c1| = M3*B
|
|
| 3 -6 3 0| |p2| |c2|
|
|
|-1 3 -3 1| |p3| |c3|
|
|
*/
|
|
vec2 c[4] = {
|
|
p[0],
|
|
3.0*(p[1] - p[0]),
|
|
3.0*(p[0] + p[2] - 2*p[1]),
|
|
3.0*(p[1] - p[2]) + p[3] - p[0]};
|
|
|
|
//NOTE: get classification, implicit matrix, double points and inflection points
|
|
cubic_info curve = cubic_classify(c);
|
|
|
|
if(curve.kind == CUBIC_DEGENERATE_LINE)
|
|
{
|
|
vec2 l[4] = {p[0], p[3], vec2(0), vec2(0)};
|
|
line_setup(l, pathIndex);
|
|
return;
|
|
}
|
|
else if(curve.kind == CUBIC_DEGENERATE_QUADRATIC)
|
|
{
|
|
vec2 quadPoint = vec2(1.5*p[1].x - 0.5*p[0].x, 1.5*p[1].y - 0.5*p[0].y);
|
|
vec2 q[4] = {p[0], quadPoint, p[3], vec2(0)};
|
|
quadratic_setup(q, pathIndex);
|
|
return;
|
|
}
|
|
|
|
//NOTE: get the roots of B'(s) = 3.c3.s^2 + 2.c2.s + c1
|
|
float rootsX[2];
|
|
int rootCountX = quadratic_roots(3*c[3].x, 2*c[2].x, c[1].x, rootsX);
|
|
|
|
float rootsY[2];
|
|
int rootCountY = quadratic_roots(3*c[3].y, 2*c[2].y, c[1].y, rootsY);
|
|
|
|
float roots[6];
|
|
for(int i=0; i<rootCountX; i++)
|
|
{
|
|
roots[i] = rootsX[i];
|
|
}
|
|
for(int i=0; i<rootCountY; i++)
|
|
{
|
|
roots[i+rootCountX] = rootsY[i];
|
|
}
|
|
|
|
//NOTE: add double points and inflection points to roots if finite
|
|
int rootCount = rootCountX + rootCountY;
|
|
for(int i=0; i<2; i++)
|
|
{
|
|
if(curve.ts[i].y != 0)
|
|
{
|
|
roots[rootCount] = curve.ts[i].x / curve.ts[i].y;
|
|
rootCount++;
|
|
}
|
|
}
|
|
|
|
//NOTE: sort roots
|
|
for(int i=1; i<rootCount; i++)
|
|
{
|
|
float tmp = roots[i];
|
|
int j = i-1;
|
|
while(j>=0 && roots[j]>tmp)
|
|
{
|
|
roots[j+1] = roots[j];
|
|
j--;
|
|
}
|
|
roots[j+1] = tmp;
|
|
}
|
|
|
|
//NOTE: compute split points
|
|
float splits[8];
|
|
int splitCount = 0;
|
|
splits[0] = 0;
|
|
splitCount++;
|
|
for(int i=0; i<rootCount; i++)
|
|
{
|
|
if(roots[i] > 0 && roots[i] < 1)
|
|
{
|
|
splits[splitCount] = roots[i];
|
|
splitCount++;
|
|
}
|
|
}
|
|
splits[splitCount] = 1;
|
|
splitCount++;
|
|
|
|
//NOTE: for each monotonic segment, compute hull matrix and sign, and emit segment
|
|
for(int sliceIndex=0; sliceIndex<splitCount-1; sliceIndex++)
|
|
{
|
|
float s0 = splits[sliceIndex];
|
|
float s1 = splits[sliceIndex+1];
|
|
vec2 sp[4];
|
|
cubic_slice(p, s0, s1, sp);
|
|
cubic_emit(curve, p, s0, s1, sp, pathIndex);
|
|
}
|
|
}
|
|
|
|
void main()
|
|
{
|
|
int eltIndex = int(gl_WorkGroupID.x);
|
|
|
|
oc_gl_path_elt elt = elementBuffer.elements[elementBufferStart + eltIndex];
|
|
|
|
switch(elt.kind)
|
|
{
|
|
case OC_GL_LINE:
|
|
{
|
|
vec2 p[4] = {elt.p[0]*scale, elt.p[1]*scale, vec2(0), vec2(0)};
|
|
line_setup(p, elt.pathIndex);
|
|
} break;
|
|
|
|
case OC_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;
|
|
|
|
case OC_GL_CUBIC:
|
|
{
|
|
vec2 p[4] = {elt.p[0]*scale, elt.p[1]*scale, elt.p[2]*scale, elt.p[3]*scale};
|
|
cubic_setup(p, elt.pathIndex);
|
|
} break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|