2023-04-25 17:13:15 +00:00
|
|
|
/************************************************************//**
|
|
|
|
*
|
|
|
|
* @file: tmp_gl_canvas.c
|
|
|
|
* @author: Martin Fouilleul
|
|
|
|
* @date: 25/04/2023
|
|
|
|
*
|
|
|
|
*****************************************************************/
|
|
|
|
|
|
|
|
|
2023-04-25 20:15:56 +00:00
|
|
|
/*
|
|
|
|
mp_rect clip;
|
|
|
|
mg_mat2x3 transform;
|
|
|
|
mg_image image;
|
|
|
|
mp_rect srcRegion;
|
|
|
|
|
|
|
|
u32 nextShapeIndex;
|
|
|
|
u32 vertexCount;
|
|
|
|
u32 indexCount;
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
2023-04-25 17:13:15 +00:00
|
|
|
void mg_reset_shape_index(mg_canvas_data* canvas)
|
|
|
|
{
|
|
|
|
canvas->nextShapeIndex = 0;
|
|
|
|
canvas->shapeExtents = (vec4){FLT_MAX, FLT_MAX, -FLT_MAX, -FLT_MAX};
|
|
|
|
}
|
|
|
|
|
|
|
|
void mg_finalize_shape(mg_canvas_data* canvas)
|
|
|
|
{
|
|
|
|
if(canvas->nextShapeIndex)
|
|
|
|
{
|
|
|
|
//NOTE: set shape's uv transform for the _current_ shape
|
|
|
|
vec2 texSize = mg_image_size(canvas->image);
|
|
|
|
|
|
|
|
mp_rect srcRegion = canvas->srcRegion;
|
|
|
|
|
|
|
|
mp_rect destRegion = {canvas->shapeExtents.x,
|
|
|
|
canvas->shapeExtents.y,
|
|
|
|
canvas->shapeExtents.z - canvas->shapeExtents.x,
|
|
|
|
canvas->shapeExtents.w - canvas->shapeExtents.y};
|
|
|
|
|
|
|
|
mg_mat2x3 srcRegionToImage = {1/texSize.x, 0, srcRegion.x/texSize.x,
|
|
|
|
0, 1/texSize.y, srcRegion.y/texSize.y};
|
|
|
|
mg_mat2x3 destRegionToSrcRegion = {srcRegion.w/destRegion.w, 0, 0,
|
|
|
|
0, srcRegion.h/destRegion.h, 0};
|
|
|
|
mg_mat2x3 userToDestRegion = {1, 0, -destRegion.x,
|
|
|
|
0, 1, -destRegion.y};
|
|
|
|
|
|
|
|
mg_mat2x3 screenToUser = mg_mat2x3_inv(canvas->transform);
|
|
|
|
|
|
|
|
mg_mat2x3 uvTransform = srcRegionToImage;
|
|
|
|
uvTransform = mg_mat2x3_mul_m(uvTransform, destRegionToSrcRegion);
|
|
|
|
uvTransform = mg_mat2x3_mul_m(uvTransform, userToDestRegion);
|
|
|
|
uvTransform = mg_mat2x3_mul_m(uvTransform, screenToUser);
|
|
|
|
|
|
|
|
int index = canvas->nextShapeIndex-1;
|
|
|
|
mg_vertex_layout* layout = &canvas->backend->vertexLayout;
|
|
|
|
*(mg_mat2x3*)(layout->uvTransformBuffer + index*layout->uvTransformStride) = uvTransform;
|
|
|
|
|
|
|
|
//TODO: transform extents before clipping
|
|
|
|
mp_rect clip = {maximum(canvas->clip.x, canvas->shapeScreenExtents.x),
|
|
|
|
maximum(canvas->clip.y, canvas->shapeScreenExtents.y),
|
|
|
|
minimum(canvas->clip.x + canvas->clip.w, canvas->shapeScreenExtents.z),
|
|
|
|
minimum(canvas->clip.y + canvas->clip.h, canvas->shapeScreenExtents.w)};
|
|
|
|
|
|
|
|
*(mp_rect*)(((char*)layout->clipBuffer) + index*layout->clipStride) = clip;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
u32 mg_next_shape(mg_canvas_data* canvas, mg_attributes* attributes)
|
|
|
|
{
|
|
|
|
mg_finalize_shape(canvas);
|
|
|
|
|
|
|
|
canvas->clip = attributes->clip;
|
|
|
|
canvas->transform = attributes->transform;
|
|
|
|
canvas->srcRegion = attributes->srcRegion;
|
|
|
|
canvas->shapeExtents = (vec4){FLT_MAX, FLT_MAX, -FLT_MAX, -FLT_MAX};
|
|
|
|
canvas->shapeScreenExtents = (vec4){FLT_MAX, FLT_MAX, -FLT_MAX, -FLT_MAX};
|
|
|
|
|
|
|
|
mg_vertex_layout* layout = &canvas->backend->vertexLayout;
|
|
|
|
int index = canvas->nextShapeIndex;
|
|
|
|
canvas->nextShapeIndex++;
|
|
|
|
|
|
|
|
*(mg_color*)(((char*)layout->colorBuffer) + index*layout->colorStride) = attributes->color;
|
|
|
|
*(bool*)(((char*)layout->texturedBuffer) + index*layout->texturedStride) = !mg_image_is_nil(attributes->image);
|
|
|
|
|
|
|
|
return(index);
|
|
|
|
}
|
|
|
|
|
|
|
|
//TODO(martin): rename with something more explicit
|
|
|
|
u32 mg_vertices_base_index(mg_canvas_data* canvas)
|
|
|
|
{
|
|
|
|
return(canvas->vertexCount);
|
|
|
|
}
|
|
|
|
|
|
|
|
int* mg_reserve_indices(mg_canvas_data* canvas, u32 indexCount)
|
|
|
|
{
|
|
|
|
mg_vertex_layout* layout = &canvas->backend->vertexLayout;
|
|
|
|
|
|
|
|
//TODO: do something here...
|
|
|
|
ASSERT(canvas->indexCount + indexCount < layout->maxIndexCount);
|
|
|
|
|
|
|
|
int* base = ((int*)layout->indexBuffer) + canvas->indexCount;
|
|
|
|
canvas->indexCount += indexCount;
|
|
|
|
return(base);
|
|
|
|
}
|
|
|
|
|
|
|
|
void mg_push_vertex_cubic(mg_canvas_data* canvas, vec2 pos, vec4 cubic)
|
|
|
|
{
|
|
|
|
canvas->shapeExtents.x = minimum(canvas->shapeExtents.x, pos.x);
|
|
|
|
canvas->shapeExtents.y = minimum(canvas->shapeExtents.y, pos.y);
|
|
|
|
canvas->shapeExtents.z = maximum(canvas->shapeExtents.z, pos.x);
|
|
|
|
canvas->shapeExtents.w = maximum(canvas->shapeExtents.w, pos.y);
|
|
|
|
|
|
|
|
vec2 screenPos = mg_mat2x3_mul(canvas->transform, pos);
|
|
|
|
|
|
|
|
canvas->shapeScreenExtents.x = minimum(canvas->shapeScreenExtents.x, screenPos.x);
|
|
|
|
canvas->shapeScreenExtents.y = minimum(canvas->shapeScreenExtents.y, screenPos.y);
|
|
|
|
canvas->shapeScreenExtents.z = maximum(canvas->shapeScreenExtents.z, screenPos.x);
|
|
|
|
canvas->shapeScreenExtents.w = maximum(canvas->shapeScreenExtents.w, screenPos.y);
|
|
|
|
|
|
|
|
mg_vertex_layout* layout = &canvas->backend->vertexLayout;
|
|
|
|
ASSERT(canvas->vertexCount < layout->maxVertexCount);
|
|
|
|
ASSERT(canvas->nextShapeIndex > 0);
|
|
|
|
|
|
|
|
int shapeIndex = maximum(0, canvas->nextShapeIndex-1);
|
|
|
|
u32 index = canvas->vertexCount;
|
|
|
|
canvas->vertexCount++;
|
|
|
|
|
|
|
|
*(vec2*)(((char*)layout->posBuffer) + index*layout->posStride) = screenPos;
|
|
|
|
*(vec4*)(((char*)layout->cubicBuffer) + index*layout->cubicStride) = cubic;
|
|
|
|
*(u32*)(((char*)layout->shapeIndexBuffer) + index*layout->shapeIndexStride) = shapeIndex;
|
|
|
|
}
|
|
|
|
|
|
|
|
void mg_push_vertex(mg_canvas_data* canvas, vec2 pos)
|
|
|
|
{
|
|
|
|
mg_push_vertex_cubic(canvas, pos, (vec4){1, 1, 1, 1});
|
|
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------------------------------------
|
|
|
|
// Path Filling
|
|
|
|
//-----------------------------------------------------------------------------------------------------------
|
|
|
|
//NOTE(martin): forward declarations
|
|
|
|
void mg_render_fill_cubic(mg_canvas_data* canvas, vec2 p[4]);
|
|
|
|
|
|
|
|
//NOTE(martin): quadratics filling
|
|
|
|
|
|
|
|
void mg_render_fill_quadratic(mg_canvas_data* canvas, vec2 p[3])
|
|
|
|
{
|
|
|
|
u32 baseIndex = mg_vertices_base_index(canvas);
|
|
|
|
|
|
|
|
i32* indices = mg_reserve_indices(canvas, 3);
|
|
|
|
|
|
|
|
mg_push_vertex_cubic(canvas, (vec2){p[0].x, p[0].y}, (vec4){0, 0, 0, 1});
|
|
|
|
mg_push_vertex_cubic(canvas, (vec2){p[1].x, p[1].y}, (vec4){0.5, 0, 0.5, 1});
|
|
|
|
mg_push_vertex_cubic(canvas, (vec2){p[2].x, p[2].y}, (vec4){1, 1, 1, 1});
|
|
|
|
|
|
|
|
indices[0] = baseIndex + 0;
|
|
|
|
indices[1] = baseIndex + 1;
|
|
|
|
indices[2] = baseIndex + 2;
|
|
|
|
}
|
|
|
|
|
|
|
|
//NOTE(martin): cubic filling
|
|
|
|
|
|
|
|
void mg_split_and_fill_cubic(mg_canvas_data* canvas, vec2 p[4], f32 tSplit)
|
|
|
|
{
|
|
|
|
//DEBUG
|
|
|
|
__mgCurrentCanvas->splitCount++;
|
|
|
|
|
|
|
|
int subVertexCount = 0;
|
|
|
|
int subIndexCount = 0;
|
|
|
|
|
|
|
|
f32 OneMinusTSplit = 1-tSplit;
|
|
|
|
|
|
|
|
vec2 q0 = {OneMinusTSplit*p[0].x + tSplit*p[1].x,
|
|
|
|
OneMinusTSplit*p[0].y + tSplit*p[1].y};
|
|
|
|
|
|
|
|
vec2 q1 = {OneMinusTSplit*p[1].x + tSplit*p[2].x,
|
|
|
|
OneMinusTSplit*p[1].y + tSplit*p[2].y};
|
|
|
|
|
|
|
|
vec2 q2 = {OneMinusTSplit*p[2].x + tSplit*p[3].x,
|
|
|
|
OneMinusTSplit*p[2].y + tSplit*p[3].y};
|
|
|
|
|
|
|
|
vec2 r0 = {OneMinusTSplit*q0.x + tSplit*q1.x,
|
|
|
|
OneMinusTSplit*q0.y + tSplit*q1.y};
|
|
|
|
|
|
|
|
vec2 r1 = {OneMinusTSplit*q1.x + tSplit*q2.x,
|
|
|
|
OneMinusTSplit*q1.y + tSplit*q2.y};
|
|
|
|
|
|
|
|
vec2 split = {OneMinusTSplit*r0.x + tSplit*r1.x,
|
|
|
|
OneMinusTSplit*r0.y + tSplit*r1.y};;
|
|
|
|
|
|
|
|
vec2 subPointsLow[4] = {p[0], q0, r0, split};
|
|
|
|
vec2 subPointsHigh[4] = {split, r1, q2, p[3]};
|
|
|
|
|
|
|
|
//NOTE(martin): add base triangle
|
|
|
|
u32 baseIndex = mg_vertices_base_index(canvas);
|
|
|
|
i32* indices = mg_reserve_indices(canvas, 3);
|
|
|
|
|
|
|
|
mg_push_vertex(canvas, (vec2){p[0].x, p[0].y});
|
|
|
|
mg_push_vertex(canvas, (vec2){split.x, split.y});
|
|
|
|
mg_push_vertex(canvas, (vec2){p[3].x, p[3].y});
|
|
|
|
|
|
|
|
indices[0] = baseIndex + 0;
|
|
|
|
indices[1] = baseIndex + 1;
|
|
|
|
indices[2] = baseIndex + 2;
|
|
|
|
|
|
|
|
mg_render_fill_cubic(canvas, subPointsLow);
|
|
|
|
mg_render_fill_cubic(canvas, subPointsHigh);
|
|
|
|
}
|
|
|
|
|
|
|
|
int mg_cubic_outside_test(vec4 c)
|
|
|
|
{
|
|
|
|
int res = (c.x*c.x*c.x - c.y*c.z < 0) ? -1 : 1;
|
|
|
|
return(res);
|
|
|
|
}
|
|
|
|
|
|
|
|
void mg_render_fill_cubic(mg_canvas_data* canvas, vec2 p[4])
|
|
|
|
{
|
|
|
|
vec4 testCoords[4];
|
|
|
|
|
|
|
|
/*NOTE(martin): first convert the control points to power basis, multiplying by M3
|
|
|
|
|
|
|
|
| 1 0 0 0|
|
|
|
|
M3 = |-3 3 0 0|
|
|
|
|
| 3 -6 3 0|
|
|
|
|
|-1 3 -3 1|
|
|
|
|
ie:
|
|
|
|
c0 = p0
|
|
|
|
c1 = -3*p0 + 3*p1
|
|
|
|
c2 = 3*p0 - 6*p1 + 3*p2
|
|
|
|
c3 = -p0 + 3*p1 - 3*p2 + p3
|
|
|
|
*/
|
|
|
|
f32 c1x = 3.0*p[1].x - 3.0*p[0].x;
|
|
|
|
f32 c1y = 3.0*p[1].y - 3.0*p[0].y;
|
|
|
|
|
|
|
|
f32 c2x = 3.0*p[0].x + 3.0*p[2].x - 6.0*p[1].x;
|
|
|
|
f32 c2y = 3.0*p[0].y + 3.0*p[2].y - 6.0*p[1].y;
|
|
|
|
|
|
|
|
f32 c3x = 3.0*p[1].x - 3.0*p[2].x + p[3].x - p[0].x;
|
|
|
|
f32 c3y = 3.0*p[1].y - 3.0*p[2].y + p[3].y - p[0].y;
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
//TODO(martin): we shouldn't need scaling here since now we're doing our shader math in fixed point?
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
c1x /= 10;
|
|
|
|
c1y /= 10;
|
|
|
|
c2x /= 10;
|
|
|
|
c2y /= 10;
|
|
|
|
c3x /= 10;
|
|
|
|
c3y /= 10;
|
|
|
|
|
|
|
|
/*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)
|
|
|
|
*/
|
|
|
|
|
|
|
|
f32 d1 = c3y*c2x - c3x*c2y;
|
|
|
|
f32 d2 = c3x*c1y - c3y*c1x;
|
|
|
|
f32 d3 = c2y*c1x - c2x*c1y;
|
|
|
|
|
|
|
|
//NOTE(martin): compute the second factor of the discriminant discr(I) = d1^2*(3*d2^2 - 4*d3*d1)
|
|
|
|
f32 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(fabs(d1) < 0.1 && fabs(d2) < 0.1 && d3 != 0)
|
|
|
|
{
|
|
|
|
//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
|
|
|
|
vec2 quadControlPoints[3] = { p[0],
|
|
|
|
{1.5*p[1].x - 0.5*p[0].x, 1.5*p[1].y - 0.5*p[0].y},
|
|
|
|
p[3]};
|
|
|
|
|
|
|
|
mg_render_fill_quadratic(canvas, quadControlPoints);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
else if( (discrFactor2 > 0 && d1 != 0)
|
|
|
|
||(discrFactor2 == 0 && d1 != 0))
|
|
|
|
{
|
|
|
|
//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
|
|
|
|
f32 tl = d2 + sqrt(discrFactor2/3);
|
|
|
|
f32 sl = 2*d1;
|
|
|
|
f32 tm = d2 - sqrt(discrFactor2/3);
|
|
|
|
f32 sm = sl;
|
|
|
|
|
|
|
|
/*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 |
|
|
|
|
|
|
|
|
This matrix is then multiplied by M3^(-1) on the left which yelds the bezier coefficients of k, l, m, n
|
|
|
|
which are assigned as a 4D image coordinates to control points.
|
|
|
|
|
|
|
|
|
|
|
|
| 1 0 0 0 |
|
|
|
|
M3^(-1) = | 1 1/3 0 0 |
|
|
|
|
| 1 2/3 1/3 0 |
|
|
|
|
| 1 1 1 1 |
|
|
|
|
*/
|
|
|
|
testCoords[0].x = tl*tm;
|
|
|
|
testCoords[0].y = Cube(tl);
|
|
|
|
testCoords[0].z = Cube(tm);
|
|
|
|
|
|
|
|
testCoords[1].x = tl*tm - (sm*tl + sl*tm)/3;
|
|
|
|
testCoords[1].y = Cube(tl) - sl*Square(tl);
|
|
|
|
testCoords[1].z = Cube(tm) - sm*Square(tm);
|
|
|
|
|
|
|
|
testCoords[2].x = tl*tm - (sm*tl + sl*tm)*2/3 + sl*sm/3;
|
|
|
|
testCoords[2].y = Cube(tl) - 2*sl*Square(tl) + Square(sl)*tl;
|
|
|
|
testCoords[2].z = Cube(tm) - 2*sm*Square(tm) + Square(sm)*tm;
|
|
|
|
|
|
|
|
testCoords[3].x = tl*tm - (sm*tl + sl*tm) + sl*sm;
|
|
|
|
testCoords[3].y = Cube(tl) - 3*sl*Square(tl) + 3*Square(sl)*tl - Cube(sl);
|
|
|
|
testCoords[3].z = Cube(tm) - 3*sm*Square(tm) + 3*Square(sm)*tm - Cube(sm);
|
|
|
|
}
|
|
|
|
else if(discrFactor2 < 0 && d1 != 0)
|
|
|
|
{
|
|
|
|
//NOTE(martin): loop curve
|
|
|
|
f32 td = d2 + sqrt(-discrFactor2);
|
|
|
|
f32 sd = 2*d1;
|
|
|
|
f32 te = d2 - sqrt(-discrFactor2);
|
|
|
|
f32 se = sd;
|
|
|
|
|
|
|
|
//NOTE(martin): if one of the parameters (td/sd) or (te/se) is in the interval [0,1], the double point
|
|
|
|
// is inside the control points convex hull and would cause a shading anomaly. If this is
|
|
|
|
// the case, subdivide the curve at that point
|
|
|
|
|
|
|
|
//TODO: study edge case where td/sd ~ 1 or 0 (which causes an infinite recursion in split and fill).
|
|
|
|
// quick fix for now is adding a little slop in the check...
|
|
|
|
|
|
|
|
if(sd != 0 && td/sd < 0.99 && td/sd > 0.01)
|
|
|
|
{
|
|
|
|
mg_split_and_fill_cubic(canvas, p, td/sd);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if(se != 0 && te/se < 0.99 && te/se > 0.01)
|
|
|
|
{
|
|
|
|
mg_split_and_fill_cubic(canvas, p, te/se);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*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 |
|
|
|
|
|
|
|
|
This matrix is then multiplied by M3^(-1) on the left which yelds the bezier coefficients of k, l, m, n
|
|
|
|
which are assigned as a 4D image coordinates to control points.
|
|
|
|
|
|
|
|
|
|
|
|
| 1 0 0 0 |
|
|
|
|
M3^(-1) = | 1 1/3 0 0 |
|
|
|
|
| 1 2/3 1/3 0 |
|
|
|
|
| 1 1 1 1 |
|
|
|
|
*/
|
|
|
|
testCoords[0].x = td*te;
|
|
|
|
testCoords[0].y = Square(td)*te;
|
|
|
|
testCoords[0].z = td*Square(te);
|
|
|
|
|
|
|
|
testCoords[1].x = td*te - (se*td + sd*te)/3.0;
|
|
|
|
testCoords[1].y = Square(td)*te - (se*Square(td) + 2.*sd*te*td)/3.0;
|
|
|
|
testCoords[1].z = td*Square(te) - (sd*Square(te) + 2*se*td*te)/3.0;
|
|
|
|
|
|
|
|
testCoords[2].x = td*te - 2.0*(se*td + sd*te)/3.0 + sd*se/3.0;
|
|
|
|
testCoords[2].y = Square(td)*te - 2.0*(se*Square(td) + 2.0*sd*te*td)/3.0 + (te*Square(sd) + 2.0*se*td*sd)/3.0;
|
|
|
|
testCoords[2].z = td*Square(te) - 2.0*(sd*Square(te) + 2.0*se*td*te)/3.0 + (td*Square(se) + 2.0*sd*te*se)/3.0;
|
|
|
|
|
|
|
|
testCoords[3].x = td*te - (se*td + sd*te) + sd*se;
|
|
|
|
testCoords[3].y = Square(td)*te - (se*Square(td) + 2.0*sd*te*td) + (te*Square(sd) + 2.0*se*td*sd) - Square(sd)*se;
|
|
|
|
testCoords[3].z = td*Square(te) - (sd*Square(te) + 2.0*se*td*te) + (td*Square(se) + 2.0*sd*te*se) - sd*Square(se);
|
|
|
|
}
|
|
|
|
else if(d1 == 0 && d2 != 0)
|
|
|
|
{
|
|
|
|
//NOTE(martin): cusp with cusp at infinity
|
|
|
|
|
|
|
|
f32 tl = d3;
|
|
|
|
f32 sl = 3*d2;
|
|
|
|
|
|
|
|
/*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 |
|
|
|
|
|
|
|
|
This matrix is then multiplied by M3^(-1) on the left which yelds the bezier coefficients of k, l, m, n
|
|
|
|
which are assigned as a 4D image coordinates to control points.
|
|
|
|
|
|
|
|
|
|
|
|
| 1 0 0 0 |
|
|
|
|
M3^(-1) = | 1 1/3 0 0 |
|
|
|
|
| 1 2/3 1/3 0 |
|
|
|
|
| 1 1 1 1 |
|
|
|
|
*/
|
|
|
|
|
|
|
|
testCoords[0].x = tl;
|
|
|
|
testCoords[0].y = Cube(tl);
|
|
|
|
testCoords[0].z = 1;
|
|
|
|
|
|
|
|
testCoords[1].x = tl - sl/3;
|
|
|
|
testCoords[1].y = Cube(tl) - sl*Square(tl);
|
|
|
|
testCoords[1].z = 1;
|
|
|
|
|
|
|
|
testCoords[2].x = tl - sl*2/3;
|
|
|
|
testCoords[2].y = Cube(tl) - 2*sl*Square(tl) + Square(sl)*tl;
|
|
|
|
testCoords[2].z = 1;
|
|
|
|
|
|
|
|
testCoords[3].x = tl - sl;
|
|
|
|
testCoords[3].y = Cube(tl) - 3*sl*Square(tl) + 3*Square(sl)*tl - Cube(sl);
|
|
|
|
testCoords[3].z = 1;
|
|
|
|
}
|
|
|
|
else if(d1 == 0 && d2 == 0 && d3 == 0)
|
|
|
|
{
|
|
|
|
//NOTE(martin): line or point degenerate case, ignored
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
//TODO(martin): handle error ? put some epsilon slack on the conditions ?
|
|
|
|
ASSERT(0, "not implemented yet !");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
//NOTE(martin): compute convex hull indices using Gift wrapping / Jarvis' march algorithm
|
|
|
|
int convexHullIndices[4];
|
|
|
|
int leftMostPointIndex = 0;
|
|
|
|
|
|
|
|
for(int i=0; i<4; i++)
|
|
|
|
{
|
|
|
|
if(p[i].x < p[leftMostPointIndex].x)
|
|
|
|
{
|
|
|
|
leftMostPointIndex = i;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
int currentPointIndex = leftMostPointIndex;
|
|
|
|
int i=0;
|
|
|
|
int convexHullCount = 0;
|
|
|
|
|
|
|
|
do
|
|
|
|
{
|
|
|
|
convexHullIndices[i] = currentPointIndex;
|
|
|
|
convexHullCount++;
|
|
|
|
int bestGuessIndex = 0;
|
|
|
|
|
|
|
|
for(int j=0; j<4; j++)
|
|
|
|
{
|
|
|
|
vec2 bestGuessEdge = {.x = p[bestGuessIndex].x - p[currentPointIndex].x,
|
|
|
|
.y = p[bestGuessIndex].y - p[currentPointIndex].y};
|
|
|
|
|
|
|
|
vec2 nextGuessEdge = {.x = p[j].x - p[currentPointIndex].x,
|
|
|
|
.y = p[j].y - p[currentPointIndex].y};
|
|
|
|
|
|
|
|
//NOTE(martin): if control point j is on the right of current edge, it is a best guess
|
|
|
|
// (in case of colinearity we choose the point which is farthest from the current point)
|
|
|
|
|
|
|
|
f32 crossProduct = bestGuessEdge.x*nextGuessEdge.y - bestGuessEdge.y*nextGuessEdge.x;
|
|
|
|
|
|
|
|
if( bestGuessIndex == currentPointIndex
|
|
|
|
|| crossProduct < 0)
|
|
|
|
{
|
|
|
|
bestGuessIndex = j;
|
|
|
|
}
|
|
|
|
else if(crossProduct == 0)
|
|
|
|
{
|
|
|
|
|
|
|
|
//NOTE(martin): if vectors v1, v2 are colinear and distinct, and ||v1|| > ||v2||,
|
|
|
|
// either abs(v1.x) > abs(v2.x) or abs(v1.y) > abs(v2.y)
|
|
|
|
// so we don't actually need to compute their norm to select the greatest
|
|
|
|
// (and if v1 and v2 are equal we don't have to update our best guess.)
|
|
|
|
|
|
|
|
//TODO(martin): in case of colinearity we should rather select the edge that has the greatest dot product with last edge ??
|
|
|
|
|
|
|
|
if(fabs(nextGuessEdge.x) > fabs(bestGuessEdge.x)
|
|
|
|
|| fabs(nextGuessEdge.y) > fabs(bestGuessEdge.y))
|
|
|
|
{
|
|
|
|
bestGuessIndex = j;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
i++;
|
|
|
|
currentPointIndex = bestGuessIndex;
|
|
|
|
|
|
|
|
} while(currentPointIndex != leftMostPointIndex && i<4);
|
|
|
|
|
|
|
|
//NOTE(martin): triangulation and inside/outside tests. In the shader, the outside is defined by s*(k^3 - lm) > 0
|
|
|
|
// ie the 4th coordinate s flips the inside/outside test.
|
|
|
|
// We affect s such that the covered are is between the curve and the line joining p0 and p3.
|
|
|
|
|
|
|
|
//TODO: quick fix, maybe later cull degenerate hulls beforehand
|
|
|
|
if(convexHullCount <= 2)
|
|
|
|
{
|
|
|
|
//NOTE(martin): if convex hull has only two point, we have a degenerate cubic that displays nothing.
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
else if(convexHullCount == 3)
|
|
|
|
{
|
|
|
|
/*NOTE(martin):
|
|
|
|
We have 3 case here:
|
|
|
|
1) Endpoints are coincidents. We push on triangle, and test an intermediate point for orientation.
|
|
|
|
2) The point not on the hull is an endpoint. We push two triangle (p0, p3, p1) and (p0, p3, p2). We test the intermediate
|
|
|
|
points to know if we must flip the orientation of the curve.
|
|
|
|
3) The point not on the hull is an intermediate point: we emit one triangle. We test the intermediate point on the hull
|
|
|
|
to know if we must flip the orientation of the curve.
|
|
|
|
*/
|
|
|
|
if( p[0].x == p[3].x
|
|
|
|
&& p[0].y == p[3].y)
|
|
|
|
{
|
|
|
|
//NOTE: case 1: endpoints are coincidents
|
|
|
|
int outsideTest = mg_cubic_outside_test(testCoords[1]);
|
|
|
|
|
|
|
|
//NOTE: push triangle
|
|
|
|
u32 baseIndex = mg_vertices_base_index(canvas);
|
|
|
|
i32* indices = mg_reserve_indices(canvas, 3);
|
|
|
|
|
|
|
|
mg_push_vertex_cubic(canvas, p[0], (vec4){vec4_expand_xyz(testCoords[0]), outsideTest});
|
|
|
|
mg_push_vertex_cubic(canvas, p[1], (vec4){vec4_expand_xyz(testCoords[1]), outsideTest});
|
|
|
|
mg_push_vertex_cubic(canvas, p[2], (vec4){vec4_expand_xyz(testCoords[2]), outsideTest});
|
|
|
|
|
|
|
|
for(int i=0; i<3; i++)
|
|
|
|
{
|
|
|
|
indices[i] = baseIndex + i;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
//NOTE: find point not on the hull
|
|
|
|
int insidePointIndex = -1;
|
|
|
|
{
|
|
|
|
bool present[4] = {0};
|
|
|
|
for(int i=0; i<3; i++)
|
|
|
|
{
|
|
|
|
present[convexHullIndices[i]] = true;
|
|
|
|
}
|
|
|
|
for(int i=0; i<4; i++)
|
|
|
|
{
|
|
|
|
if(!present[i])
|
|
|
|
{
|
|
|
|
insidePointIndex = i;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
DEBUG_ASSERT(insidePointIndex >= 0 && insidePointIndex < 4);
|
|
|
|
|
|
|
|
if(insidePointIndex == 0 || insidePointIndex == 3)
|
|
|
|
{
|
|
|
|
//NOTE: case 2: the point inside the hull is an endpoint
|
|
|
|
|
|
|
|
int outsideTest0 = mg_cubic_outside_test(testCoords[1]);
|
|
|
|
int outsideTest1 = mg_cubic_outside_test(testCoords[2]);
|
|
|
|
|
|
|
|
//NOTE: push triangles
|
|
|
|
u32 baseIndex = mg_vertices_base_index(canvas);
|
|
|
|
i32* indices = mg_reserve_indices(canvas, 6);
|
|
|
|
|
|
|
|
mg_push_vertex_cubic(canvas, p[0], (vec4){vec4_expand_xyz(testCoords[0]), outsideTest0});
|
|
|
|
mg_push_vertex_cubic(canvas, p[3], (vec4){vec4_expand_xyz(testCoords[3]), outsideTest0});
|
|
|
|
mg_push_vertex_cubic(canvas, p[1], (vec4){vec4_expand_xyz(testCoords[1]), outsideTest0});
|
|
|
|
mg_push_vertex_cubic(canvas, p[0], (vec4){vec4_expand_xyz(testCoords[0]), outsideTest1});
|
|
|
|
mg_push_vertex_cubic(canvas, p[3], (vec4){vec4_expand_xyz(testCoords[3]), outsideTest1});
|
|
|
|
mg_push_vertex_cubic(canvas, p[2], (vec4){vec4_expand_xyz(testCoords[2]), outsideTest1});
|
|
|
|
|
|
|
|
for(int i=0; i<6; i++)
|
|
|
|
{
|
|
|
|
indices[i] = baseIndex + i;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
int testIndex = (insidePointIndex == 1) ? 2 : 1;
|
|
|
|
int outsideTest = mg_cubic_outside_test(testCoords[testIndex]);
|
|
|
|
|
|
|
|
//NOTE: push triangle
|
|
|
|
u32 baseIndex = mg_vertices_base_index(canvas);
|
|
|
|
i32* indices = mg_reserve_indices(canvas, 3);
|
|
|
|
|
|
|
|
for(int i=0; i<3; i++)
|
|
|
|
{
|
|
|
|
mg_push_vertex_cubic(canvas,
|
|
|
|
p[convexHullIndices[i]],
|
|
|
|
(vec4){vec4_expand_xyz(testCoords[convexHullIndices[i]]),
|
|
|
|
outsideTest});
|
|
|
|
}
|
|
|
|
|
|
|
|
for(int i=0; i<3; i++)
|
|
|
|
{
|
|
|
|
indices[i] = baseIndex + i;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
DEBUG_ASSERT(convexHullCount == 4);
|
|
|
|
/*NOTE(martin):
|
|
|
|
We build a fan from the hull, starting from an endpoint. For each triangle, we test the vertex that is an intermediate
|
|
|
|
control point for orientation
|
|
|
|
*/
|
|
|
|
int endPointIndex = -1;
|
|
|
|
for(int i=0; i<4; i++)
|
|
|
|
{
|
|
|
|
if(convexHullIndices[i] == 0 || convexHullIndices[i] == 3)
|
|
|
|
{
|
|
|
|
endPointIndex = i;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
ASSERT(endPointIndex >= 0);
|
|
|
|
|
|
|
|
int fanIndices[6] = {convexHullIndices[endPointIndex],
|
|
|
|
convexHullIndices[(endPointIndex + 1)%4],
|
|
|
|
convexHullIndices[(endPointIndex + 2)%4],
|
|
|
|
convexHullIndices[endPointIndex],
|
|
|
|
convexHullIndices[(endPointIndex + 2)%4],
|
|
|
|
convexHullIndices[(endPointIndex + 3)%4]};
|
|
|
|
|
|
|
|
//NOTE: fan indices on the hull are (0,1,2)(0,2,3). So if the 3rd vertex of the hull is an intermediate point it works
|
|
|
|
// as a test vertex for both triangles. Otherwise, the test vertices on the fan are 1 and 5.
|
|
|
|
int outsideTest0 = 1;
|
|
|
|
int outsideTest1 = 1;
|
|
|
|
|
|
|
|
if( fanIndices[2] == 1
|
|
|
|
||fanIndices[2] == 2)
|
|
|
|
{
|
|
|
|
outsideTest0 = outsideTest1 = mg_cubic_outside_test(testCoords[fanIndices[2]]);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
DEBUG_ASSERT(fanIndices[1] == 1 || fanIndices[1] == 2);
|
|
|
|
DEBUG_ASSERT(fanIndices[5] == 1 || fanIndices[5] == 2);
|
|
|
|
|
|
|
|
outsideTest0 = mg_cubic_outside_test(testCoords[fanIndices[1]]);
|
|
|
|
outsideTest1 = mg_cubic_outside_test(testCoords[fanIndices[5]]);
|
|
|
|
}
|
|
|
|
|
|
|
|
//NOTE: push triangles
|
|
|
|
u32 baseIndex = mg_vertices_base_index(canvas);
|
|
|
|
i32* indices = mg_reserve_indices(canvas, 6);
|
|
|
|
|
|
|
|
for(int i=0; i<3; i++)
|
|
|
|
{
|
|
|
|
mg_push_vertex_cubic(canvas, p[fanIndices[i]], (vec4){vec4_expand_xyz(testCoords[fanIndices[i]]), outsideTest0});
|
|
|
|
}
|
|
|
|
for(int i=0; i<3; i++)
|
|
|
|
{
|
|
|
|
mg_push_vertex_cubic(canvas, p[fanIndices[i+3]], (vec4){vec4_expand_xyz(testCoords[fanIndices[i+3]]), outsideTest1});
|
|
|
|
}
|
|
|
|
|
|
|
|
for(int i=0; i<6; i++)
|
|
|
|
{
|
|
|
|
indices[i] = baseIndex + i;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//NOTE(martin): global path fill
|
|
|
|
|
|
|
|
void mg_render_fill(mg_canvas_data* canvas, mg_path_elt* elements, mg_path_descriptor* path)
|
|
|
|
{
|
|
|
|
u32 eltCount = path->count;
|
|
|
|
vec2 startPoint = path->startPoint;
|
|
|
|
vec2 endPoint = path->startPoint;
|
|
|
|
vec2 currentPoint = path->startPoint;
|
|
|
|
|
|
|
|
for(int eltIndex=0; eltIndex<eltCount; eltIndex++)
|
|
|
|
{
|
|
|
|
mg_path_elt* elt = &(elements[eltIndex]);
|
|
|
|
|
|
|
|
vec2 controlPoints[4] = {currentPoint, elt->p[0], elt->p[1], elt->p[2]};
|
|
|
|
|
|
|
|
switch(elt->type)
|
|
|
|
{
|
|
|
|
case MG_PATH_MOVE:
|
|
|
|
{
|
|
|
|
startPoint = elt->p[0];
|
|
|
|
endPoint = elt->p[0];
|
|
|
|
currentPoint = endPoint;
|
|
|
|
continue;
|
|
|
|
} break;
|
|
|
|
|
|
|
|
case MG_PATH_LINE:
|
|
|
|
{
|
|
|
|
endPoint = controlPoints[1];
|
|
|
|
} break;
|
|
|
|
|
|
|
|
case MG_PATH_QUADRATIC:
|
|
|
|
{
|
|
|
|
mg_render_fill_quadratic(canvas, controlPoints);
|
|
|
|
endPoint = controlPoints[2];
|
|
|
|
|
|
|
|
} break;
|
|
|
|
|
|
|
|
case MG_PATH_CUBIC:
|
|
|
|
{
|
|
|
|
mg_render_fill_cubic(canvas, controlPoints);
|
|
|
|
endPoint = controlPoints[3];
|
|
|
|
} break;
|
|
|
|
}
|
|
|
|
|
|
|
|
//NOTE(martin): now fill interior triangle
|
|
|
|
u32 baseIndex = mg_vertices_base_index(canvas);
|
|
|
|
int* indices = mg_reserve_indices(canvas, 3);
|
|
|
|
|
|
|
|
mg_push_vertex(canvas, startPoint);
|
|
|
|
mg_push_vertex(canvas, currentPoint);
|
|
|
|
mg_push_vertex(canvas, endPoint);
|
|
|
|
|
|
|
|
indices[0] = baseIndex;
|
|
|
|
indices[1] = baseIndex + 1;
|
|
|
|
indices[2] = baseIndex + 2;
|
|
|
|
|
|
|
|
currentPoint = endPoint;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------------------------------------
|
|
|
|
// Path Stroking
|
|
|
|
//-----------------------------------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
void mg_render_stroke_line(mg_canvas_data* canvas, vec2 p[2], mg_attributes* attributes)
|
|
|
|
{
|
|
|
|
//NOTE(martin): get normals multiplied by halfWidth
|
|
|
|
f32 halfW = attributes->width/2;
|
|
|
|
|
|
|
|
vec2 n0 = {p[0].y - p[1].y,
|
|
|
|
p[1].x - p[0].x};
|
|
|
|
f32 norm0 = sqrt(n0.x*n0.x + n0.y*n0.y);
|
|
|
|
n0.x *= halfW/norm0;
|
|
|
|
n0.y *= halfW/norm0;
|
|
|
|
|
|
|
|
u32 baseIndex = mg_vertices_base_index(canvas);
|
|
|
|
i32* indices = mg_reserve_indices(canvas, 6);
|
|
|
|
|
|
|
|
mg_push_vertex(canvas, (vec2){p[0].x + n0.x, p[0].y + n0.y});
|
|
|
|
mg_push_vertex(canvas, (vec2){p[1].x + n0.x, p[1].y + n0.y});
|
|
|
|
mg_push_vertex(canvas, (vec2){p[1].x - n0.x, p[1].y - n0.y});
|
|
|
|
mg_push_vertex(canvas, (vec2){p[0].x - n0.x, p[0].y - n0.y});
|
|
|
|
|
|
|
|
indices[0] = baseIndex;
|
|
|
|
indices[1] = baseIndex + 1;
|
|
|
|
indices[2] = baseIndex + 2;
|
|
|
|
indices[3] = baseIndex;
|
|
|
|
indices[4] = baseIndex + 2;
|
|
|
|
indices[5] = baseIndex + 3;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool mg_intersect_hull_legs(vec2 p0, vec2 p1, vec2 p2, vec2 p3, vec2* intersection)
|
|
|
|
{
|
|
|
|
/*NOTE: check intersection of lines (p0-p1) and (p2-p3)
|
|
|
|
|
|
|
|
P = p0 + u(p1-p0)
|
|
|
|
P = p2 + w(p3-p2)
|
|
|
|
*/
|
|
|
|
bool found = false;
|
|
|
|
|
|
|
|
f32 den = (p0.x - p1.x)*(p2.y - p3.y) - (p0.y - p1.y)*(p2.x - p3.x);
|
|
|
|
if(fabs(den) > 0.0001)
|
|
|
|
{
|
|
|
|
f32 u = ((p0.x - p2.x)*(p2.y - p3.y) - (p0.y - p2.y)*(p2.x - p3.x))/den;
|
|
|
|
f32 w = ((p0.x - p2.x)*(p0.y - p1.y) - (p0.y - p2.y)*(p0.x - p1.x))/den;
|
|
|
|
|
|
|
|
intersection->x = p0.x + u*(p1.x - p0.x);
|
|
|
|
intersection->y = p0.y + u*(p1.y - p0.y);
|
|
|
|
found = true;
|
|
|
|
}
|
|
|
|
return(found);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool mg_offset_hull(int count, vec2* p, vec2* result, f32 offset)
|
|
|
|
{
|
|
|
|
//NOTE: we should have no more than two coincident points here. This means the leg between
|
|
|
|
// those two points can't be offset, but we can set a double point at the start of first leg,
|
|
|
|
// end of first leg, or we can join the first and last leg to create a missing middle one
|
|
|
|
|
|
|
|
vec2 legs[3][2] = {0};
|
|
|
|
bool valid[3] = {0};
|
|
|
|
|
|
|
|
for(int i=0; i<count-1; i++)
|
|
|
|
{
|
|
|
|
vec2 n = {p[i].y - p[i+1].y,
|
|
|
|
p[i+1].x - p[i].x};
|
|
|
|
|
|
|
|
f32 norm = sqrt(n.x*n.x + n.y*n.y);
|
|
|
|
if(norm >= 1e-6)
|
|
|
|
{
|
|
|
|
n = vec2_mul(offset/norm, n);
|
|
|
|
legs[i][0] = vec2_add(p[i], n);
|
|
|
|
legs[i][1] = vec2_add(p[i+1], n);
|
|
|
|
valid[i] = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//NOTE: now we find intersections
|
|
|
|
|
|
|
|
// first point is either the start of the first or second leg
|
|
|
|
if(valid[0])
|
|
|
|
{
|
|
|
|
result[0] = legs[0][0];
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
ASSERT(valid[1]);
|
|
|
|
result[0] = legs[1][0];
|
|
|
|
}
|
|
|
|
|
|
|
|
for(int i=1; i<count-1; i++)
|
|
|
|
{
|
|
|
|
//NOTE: we're computing the control point i, at the end of leg (i-1)
|
|
|
|
|
|
|
|
if(!valid[i-1])
|
|
|
|
{
|
|
|
|
ASSERT(valid[i]);
|
|
|
|
result[i] = legs[i][0];
|
|
|
|
}
|
|
|
|
else if(!valid[i])
|
|
|
|
{
|
|
|
|
ASSERT(valid[i-1]);
|
|
|
|
result[i] = legs[i-1][0];
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if(!mg_intersect_hull_legs(legs[i-1][0], legs[i-1][1], legs[i][0], legs[i][1], &result[i]))
|
|
|
|
{
|
|
|
|
// legs don't intersect.
|
|
|
|
return(false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if(valid[count-2])
|
|
|
|
{
|
|
|
|
result[count-1] = legs[count-2][1];
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
ASSERT(valid[count-3]);
|
|
|
|
result[count-1] = legs[count-3][1];
|
|
|
|
}
|
|
|
|
|
|
|
|
return(true);
|
|
|
|
}
|
|
|
|
|
|
|
|
vec2 mg_quadratic_get_point(vec2 p[3], f32 t)
|
|
|
|
{
|
|
|
|
vec2 r;
|
|
|
|
|
|
|
|
f32 oneMt = 1-t;
|
|
|
|
f32 oneMt2 = Square(oneMt);
|
|
|
|
f32 t2 = Square(t);
|
|
|
|
|
|
|
|
r.x = oneMt2*p[0].x + 2*oneMt*t*p[1].x + t2*p[2].x;
|
|
|
|
r.y = oneMt2*p[0].y + 2*oneMt*t*p[1].y + t2*p[2].y;
|
|
|
|
|
|
|
|
return(r);
|
|
|
|
}
|
|
|
|
|
|
|
|
void mg_quadratic_split(vec2 p[3], f32 t, vec2 outLeft[3], vec2 outRight[3])
|
|
|
|
{
|
|
|
|
//DEBUG
|
|
|
|
__mgCurrentCanvas->splitCount++;
|
|
|
|
|
|
|
|
//NOTE(martin): split bezier curve p at parameter t, using De Casteljau's algorithm
|
|
|
|
// the q_n are the points along the hull's segments at parameter t
|
|
|
|
// s is the split point.
|
|
|
|
|
|
|
|
f32 oneMt = 1-t;
|
|
|
|
|
|
|
|
vec2 q0 = {oneMt*p[0].x + t*p[1].x,
|
|
|
|
oneMt*p[0].y + t*p[1].y};
|
|
|
|
|
|
|
|
vec2 q1 = {oneMt*p[1].x + t*p[2].x,
|
|
|
|
oneMt*p[1].y + t*p[2].y};
|
|
|
|
|
|
|
|
vec2 s = {oneMt*q0.x + t*q1.x,
|
|
|
|
oneMt*q0.y + t*q1.y};
|
|
|
|
|
|
|
|
outLeft[0] = p[0];
|
|
|
|
outLeft[1] = q0;
|
|
|
|
outLeft[2] = s;
|
|
|
|
|
|
|
|
outRight[0] = s;
|
|
|
|
outRight[1] = q1;
|
|
|
|
outRight[2] = p[2];
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void mg_render_stroke_quadratic(mg_canvas_data* canvas, vec2 p[3], mg_attributes* attributes)
|
|
|
|
{
|
|
|
|
//NOTE: check for degenerate line case
|
|
|
|
const f32 equalEps = 1e-3;
|
|
|
|
if(vec2_close(p[0], p[1], equalEps))
|
|
|
|
{
|
|
|
|
mg_render_stroke_line(canvas, p+1, attributes);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
else if(vec2_close(p[1], p[2], equalEps))
|
|
|
|
{
|
|
|
|
mg_render_stroke_line(canvas, p, attributes);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
#define CHECK_SAMPLE_COUNT 5
|
|
|
|
f32 checkSamples[CHECK_SAMPLE_COUNT] = {1./6, 2./6, 3./6, 4./6, 5./6};
|
|
|
|
|
|
|
|
vec2 positiveOffsetHull[3];
|
|
|
|
vec2 negativeOffsetHull[3];
|
|
|
|
|
|
|
|
if( !mg_offset_hull(3, p, positiveOffsetHull, 0.5 * attributes->width)
|
|
|
|
||!mg_offset_hull(3, p, negativeOffsetHull, -0.5 * attributes->width))
|
|
|
|
{
|
|
|
|
//NOTE: offsetting the hull failed, split the curve
|
|
|
|
vec2 splitLeft[3];
|
|
|
|
vec2 splitRight[3];
|
|
|
|
mg_quadratic_split(p, 0.5, splitLeft, splitRight);
|
|
|
|
mg_render_stroke_quadratic(canvas, splitLeft, attributes);
|
|
|
|
mg_render_stroke_quadratic(canvas, splitRight, attributes);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
//NOTE(martin): the distance d between the offset curve and the path must be between w/2-tolerance and w/2+tolerance
|
|
|
|
// thus, by constraining tolerance to be at most, 0.5*width, we can rewrite this condition like this:
|
|
|
|
//
|
|
|
|
// (w/2-tolerance)^2 < d^2 < (w/2+tolerance)^2
|
|
|
|
//
|
|
|
|
// we compute the maximum overshoot outside these bounds and split the curve at the corresponding parameter
|
|
|
|
|
|
|
|
//TODO: maybe refactor by using tolerance in the _check_, not in the computation of the overshoot
|
|
|
|
f32 tolerance = minimum(attributes->tolerance, 0.5 * attributes->width);
|
|
|
|
f32 d2LowBound = Square(0.5 * attributes->width - attributes->tolerance);
|
|
|
|
f32 d2HighBound = Square(0.5 * attributes->width + attributes->tolerance);
|
|
|
|
|
|
|
|
f32 maxOvershoot = 0;
|
|
|
|
f32 maxOvershootParameter = 0;
|
|
|
|
|
|
|
|
for(int i=0; i<CHECK_SAMPLE_COUNT; i++)
|
|
|
|
{
|
|
|
|
f32 t = checkSamples[i];
|
|
|
|
|
|
|
|
vec2 c = mg_quadratic_get_point(p, t);
|
|
|
|
vec2 cp = mg_quadratic_get_point(positiveOffsetHull, t);
|
|
|
|
vec2 cn = mg_quadratic_get_point(negativeOffsetHull, t);
|
|
|
|
|
|
|
|
f32 positiveDistSquare = Square(c.x - cp.x) + Square(c.y - cp.y);
|
|
|
|
f32 negativeDistSquare = Square(c.x - cn.x) + Square(c.y - cn.y);
|
|
|
|
|
|
|
|
f32 positiveOvershoot = maximum(positiveDistSquare - d2HighBound, d2LowBound - positiveDistSquare);
|
|
|
|
f32 negativeOvershoot = maximum(negativeDistSquare - d2HighBound, d2LowBound - negativeDistSquare);
|
|
|
|
|
|
|
|
f32 overshoot = maximum(positiveOvershoot, negativeOvershoot);
|
|
|
|
|
|
|
|
if(overshoot > maxOvershoot)
|
|
|
|
{
|
|
|
|
maxOvershoot = overshoot;
|
|
|
|
maxOvershootParameter = t;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if(maxOvershoot > 0)
|
|
|
|
{
|
|
|
|
vec2 splitLeft[3];
|
|
|
|
vec2 splitRight[3];
|
|
|
|
mg_quadratic_split(p, maxOvershootParameter, splitLeft, splitRight);
|
|
|
|
mg_render_stroke_quadratic(canvas, splitLeft, attributes);
|
|
|
|
mg_render_stroke_quadratic(canvas, splitRight, attributes);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
//NOTE(martin): push the actual fill commands for the offset contour
|
|
|
|
|
|
|
|
mg_next_shape(canvas, attributes);
|
|
|
|
|
|
|
|
mg_render_fill_quadratic(canvas, positiveOffsetHull);
|
|
|
|
mg_render_fill_quadratic(canvas, negativeOffsetHull);
|
|
|
|
|
|
|
|
//NOTE(martin): add base triangles
|
|
|
|
u32 baseIndex = mg_vertices_base_index(canvas);
|
|
|
|
i32* indices = mg_reserve_indices(canvas, 6);
|
|
|
|
|
|
|
|
mg_push_vertex(canvas, positiveOffsetHull[0]);
|
|
|
|
mg_push_vertex(canvas, positiveOffsetHull[2]);
|
|
|
|
mg_push_vertex(canvas, negativeOffsetHull[2]);
|
|
|
|
mg_push_vertex(canvas, negativeOffsetHull[0]);
|
|
|
|
|
|
|
|
indices[0] = baseIndex + 0;
|
|
|
|
indices[1] = baseIndex + 1;
|
|
|
|
indices[2] = baseIndex + 2;
|
|
|
|
indices[3] = baseIndex + 0;
|
|
|
|
indices[4] = baseIndex + 2;
|
|
|
|
indices[5] = baseIndex + 3;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#undef CHECK_SAMPLE_COUNT
|
|
|
|
}
|
|
|
|
|
|
|
|
vec2 mg_cubic_get_point(vec2 p[4], f32 t)
|
|
|
|
{
|
|
|
|
vec2 r;
|
|
|
|
|
|
|
|
f32 oneMt = 1-t;
|
|
|
|
f32 oneMt2 = Square(oneMt);
|
|
|
|
f32 oneMt3 = oneMt2*oneMt;
|
|
|
|
f32 t2 = Square(t);
|
|
|
|
f32 t3 = t2*t;
|
|
|
|
|
|
|
|
r.x = oneMt3*p[0].x + 3*oneMt2*t*p[1].x + 3*oneMt*t2*p[2].x + t3*p[3].x;
|
|
|
|
r.y = oneMt3*p[0].y + 3*oneMt2*t*p[1].y + 3*oneMt*t2*p[2].y + t3*p[3].y;
|
|
|
|
|
|
|
|
return(r);
|
|
|
|
}
|
|
|
|
|
|
|
|
void mg_cubic_split(vec2 p[4], f32 t, vec2 outLeft[4], vec2 outRight[4])
|
|
|
|
{
|
|
|
|
//NOTE(martin): split bezier curve p at parameter t, using De Casteljau's algorithm
|
|
|
|
// the q_n are the points along the hull's segments at parameter t
|
|
|
|
// the r_n are the points along the (q_n, q_n+1) segments at parameter t
|
|
|
|
// s is the split point.
|
|
|
|
|
|
|
|
vec2 q0 = {(1-t)*p[0].x + t*p[1].x,
|
|
|
|
(1-t)*p[0].y + t*p[1].y};
|
|
|
|
|
|
|
|
vec2 q1 = {(1-t)*p[1].x + t*p[2].x,
|
|
|
|
(1-t)*p[1].y + t*p[2].y};
|
|
|
|
|
|
|
|
vec2 q2 = {(1-t)*p[2].x + t*p[3].x,
|
|
|
|
(1-t)*p[2].y + t*p[3].y};
|
|
|
|
|
|
|
|
vec2 r0 = {(1-t)*q0.x + t*q1.x,
|
|
|
|
(1-t)*q0.y + t*q1.y};
|
|
|
|
|
|
|
|
vec2 r1 = {(1-t)*q1.x + t*q2.x,
|
|
|
|
(1-t)*q1.y + t*q2.y};
|
|
|
|
|
|
|
|
vec2 s = {(1-t)*r0.x + t*r1.x,
|
|
|
|
(1-t)*r0.y + t*r1.y};;
|
|
|
|
|
|
|
|
outLeft[0] = p[0];
|
|
|
|
outLeft[1] = q0;
|
|
|
|
outLeft[2] = r0;
|
|
|
|
outLeft[3] = s;
|
|
|
|
|
|
|
|
outRight[0] = s;
|
|
|
|
outRight[1] = r1;
|
|
|
|
outRight[2] = q2;
|
|
|
|
outRight[3] = p[3];
|
|
|
|
}
|
|
|
|
|
|
|
|
void mg_render_stroke_cubic(mg_canvas_data* canvas, vec2 p[4], mg_attributes* attributes)
|
|
|
|
{
|
|
|
|
//NOTE: check degenerate line cases
|
|
|
|
f32 equalEps = 1e-3;
|
|
|
|
|
|
|
|
if( (vec2_close(p[0], p[1], equalEps) && vec2_close(p[2], p[3], equalEps))
|
|
|
|
||(vec2_close(p[0], p[1], equalEps) && vec2_close(p[1], p[2], equalEps))
|
|
|
|
||(vec2_close(p[1], p[2], equalEps) && vec2_close(p[2], p[3], equalEps)))
|
|
|
|
{
|
|
|
|
vec2 line[2] = {p[0], p[3]};
|
|
|
|
mg_render_stroke_line(canvas, line, attributes);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
else if(vec2_close(p[0], p[1], equalEps) && vec2_close(p[1], p[3], equalEps))
|
|
|
|
{
|
|
|
|
vec2 line[2] = {p[0], vec2_add(vec2_mul(5./9, p[0]), vec2_mul(4./9, p[2]))};
|
|
|
|
mg_render_stroke_line(canvas, line, attributes);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
else if(vec2_close(p[0], p[2], equalEps) && vec2_close(p[2], p[3], equalEps))
|
|
|
|
{
|
|
|
|
vec2 line[2] = {p[0], vec2_add(vec2_mul(5./9, p[0]), vec2_mul(4./9, p[1]))};
|
|
|
|
mg_render_stroke_line(canvas, line, attributes);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
#define CHECK_SAMPLE_COUNT 5
|
|
|
|
f32 checkSamples[CHECK_SAMPLE_COUNT] = {1./6, 2./6, 3./6, 4./6, 5./6};
|
|
|
|
|
|
|
|
vec2 positiveOffsetHull[4];
|
|
|
|
vec2 negativeOffsetHull[4];
|
|
|
|
|
|
|
|
if( !mg_offset_hull(4, p, positiveOffsetHull, 0.5 * attributes->width)
|
|
|
|
|| !mg_offset_hull(4, p, negativeOffsetHull, -0.5 * attributes->width))
|
|
|
|
{
|
|
|
|
vec2 splitLeft[4];
|
|
|
|
vec2 splitRight[4];
|
|
|
|
mg_cubic_split(p, 0.5, splitLeft, splitRight);
|
|
|
|
mg_render_stroke_cubic(canvas, splitLeft, attributes);
|
|
|
|
mg_render_stroke_cubic(canvas, splitRight, attributes);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
//NOTE(martin): the distance d between the offset curve and the path must be between w/2-tolerance and w/2+tolerance
|
|
|
|
// thus, by constraining tolerance to be at most, 0.5*width, we can rewrite this condition like this:
|
|
|
|
//
|
|
|
|
// (w/2-tolerance)^2 < d^2 < (w/2+tolerance)^2
|
|
|
|
//
|
|
|
|
// we compute the maximum overshoot outside these bounds and split the curve at the corresponding parameter
|
|
|
|
|
|
|
|
//TODO: maybe refactor by using tolerance in the _check_, not in the computation of the overshoot
|
|
|
|
f32 tolerance = minimum(attributes->tolerance, 0.5 * attributes->width);
|
|
|
|
f32 d2LowBound = Square(0.5 * attributes->width - attributes->tolerance);
|
|
|
|
f32 d2HighBound = Square(0.5 * attributes->width + attributes->tolerance);
|
|
|
|
|
|
|
|
f32 maxOvershoot = 0;
|
|
|
|
f32 maxOvershootParameter = 0;
|
|
|
|
|
|
|
|
for(int i=0; i<CHECK_SAMPLE_COUNT; i++)
|
|
|
|
{
|
|
|
|
f32 t = checkSamples[i];
|
|
|
|
|
|
|
|
vec2 c = mg_cubic_get_point(p, t);
|
|
|
|
vec2 cp = mg_cubic_get_point(positiveOffsetHull, t);
|
|
|
|
vec2 cn = mg_cubic_get_point(negativeOffsetHull, t);
|
|
|
|
|
|
|
|
f32 positiveDistSquare = Square(c.x - cp.x) + Square(c.y - cp.y);
|
|
|
|
f32 negativeDistSquare = Square(c.x - cn.x) + Square(c.y - cn.y);
|
|
|
|
|
|
|
|
f32 positiveOvershoot = maximum(positiveDistSquare - d2HighBound, d2LowBound - positiveDistSquare);
|
|
|
|
f32 negativeOvershoot = maximum(negativeDistSquare - d2HighBound, d2LowBound - negativeDistSquare);
|
|
|
|
|
|
|
|
f32 overshoot = maximum(positiveOvershoot, negativeOvershoot);
|
|
|
|
|
|
|
|
if(overshoot > maxOvershoot)
|
|
|
|
{
|
|
|
|
maxOvershoot = overshoot;
|
|
|
|
maxOvershootParameter = t;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if(maxOvershoot > 0)
|
|
|
|
{
|
|
|
|
vec2 splitLeft[4];
|
|
|
|
vec2 splitRight[4];
|
|
|
|
mg_cubic_split(p, maxOvershootParameter, splitLeft, splitRight);
|
|
|
|
mg_render_stroke_cubic(canvas, splitLeft, attributes);
|
|
|
|
mg_render_stroke_cubic(canvas, splitRight, attributes);
|
|
|
|
|
|
|
|
//TODO: render joint between the split curves
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
//NOTE(martin): push the actual fill commands for the offset contour
|
|
|
|
mg_next_shape(canvas, attributes);
|
|
|
|
|
|
|
|
mg_render_fill_cubic(canvas, positiveOffsetHull);
|
|
|
|
mg_render_fill_cubic(canvas, negativeOffsetHull);
|
|
|
|
|
|
|
|
//NOTE(martin): add base triangles
|
|
|
|
u32 baseIndex = mg_vertices_base_index(canvas);
|
|
|
|
i32* indices = mg_reserve_indices(canvas, 6);
|
|
|
|
|
|
|
|
mg_push_vertex(canvas, positiveOffsetHull[0]);
|
|
|
|
mg_push_vertex(canvas, positiveOffsetHull[3]);
|
|
|
|
mg_push_vertex(canvas, negativeOffsetHull[3]);
|
|
|
|
mg_push_vertex(canvas, negativeOffsetHull[0]);
|
|
|
|
|
|
|
|
indices[0] = baseIndex + 0;
|
|
|
|
indices[1] = baseIndex + 1;
|
|
|
|
indices[2] = baseIndex + 2;
|
|
|
|
indices[3] = baseIndex + 0;
|
|
|
|
indices[4] = baseIndex + 2;
|
|
|
|
indices[5] = baseIndex + 3;
|
|
|
|
}
|
|
|
|
#undef CHECK_SAMPLE_COUNT
|
|
|
|
}
|
|
|
|
|
|
|
|
void mg_stroke_cap(mg_canvas_data* canvas, vec2 p0, vec2 direction, mg_attributes* attributes)
|
|
|
|
{
|
|
|
|
//NOTE(martin): compute the tangent and normal vectors (multiplied by half width) at the cap point
|
|
|
|
|
|
|
|
f32 dn = sqrt(Square(direction.x) + Square(direction.y));
|
|
|
|
f32 alpha = 0.5 * attributes->width/dn;
|
|
|
|
|
|
|
|
vec2 n0 = {-alpha*direction.y,
|
|
|
|
alpha*direction.x};
|
|
|
|
|
|
|
|
vec2 m0 = {alpha*direction.x,
|
|
|
|
alpha*direction.y};
|
|
|
|
|
|
|
|
mg_next_shape(canvas, attributes);
|
|
|
|
|
|
|
|
u32 baseIndex = mg_vertices_base_index(canvas);
|
|
|
|
i32* indices = mg_reserve_indices(canvas, 6);
|
|
|
|
|
|
|
|
mg_push_vertex(canvas, (vec2){p0.x + n0.x, p0.y + n0.y});
|
|
|
|
mg_push_vertex(canvas, (vec2){p0.x + n0.x + m0.x, p0.y + n0.y + m0.y});
|
|
|
|
mg_push_vertex(canvas, (vec2){p0.x - n0.x + m0.x, p0.y - n0.y + m0.y});
|
|
|
|
mg_push_vertex(canvas, (vec2){p0.x - n0.x, p0.y - n0.y});
|
|
|
|
|
|
|
|
indices[0] = baseIndex;
|
|
|
|
indices[1] = baseIndex + 1;
|
|
|
|
indices[2] = baseIndex + 2;
|
|
|
|
indices[3] = baseIndex;
|
|
|
|
indices[4] = baseIndex + 2;
|
|
|
|
indices[5] = baseIndex + 3;
|
|
|
|
}
|
|
|
|
|
|
|
|
void mg_stroke_joint(mg_canvas_data* canvas,
|
|
|
|
vec2 p0,
|
|
|
|
vec2 t0,
|
|
|
|
vec2 t1,
|
|
|
|
mg_attributes* attributes)
|
|
|
|
{
|
|
|
|
//NOTE(martin): compute the normals at the joint point
|
|
|
|
f32 norm_t0 = sqrt(Square(t0.x) + Square(t0.y));
|
|
|
|
f32 norm_t1 = sqrt(Square(t1.x) + Square(t1.y));
|
|
|
|
|
|
|
|
vec2 n0 = {-t0.y, t0.x};
|
|
|
|
n0.x /= norm_t0;
|
|
|
|
n0.y /= norm_t0;
|
|
|
|
|
|
|
|
vec2 n1 = {-t1.y, t1.x};
|
|
|
|
n1.x /= norm_t1;
|
|
|
|
n1.y /= norm_t1;
|
|
|
|
|
|
|
|
//NOTE(martin): the sign of the cross product determines if the normals are facing outwards or inwards the angle.
|
|
|
|
// we flip them to face outwards if needed
|
|
|
|
f32 crossZ = n0.x*n1.y - n0.y*n1.x;
|
|
|
|
if(crossZ > 0)
|
|
|
|
{
|
|
|
|
n0.x *= -1;
|
|
|
|
n0.y *= -1;
|
|
|
|
n1.x *= -1;
|
|
|
|
n1.y *= -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
mg_next_shape(canvas, attributes);
|
|
|
|
|
|
|
|
//NOTE(martin): use the same code as hull offset to find mitter point...
|
|
|
|
/*NOTE(martin): let vector u = (n0+n1) and vector v = pIntersect - p1
|
|
|
|
then v = u * (2*offset / norm(u)^2)
|
|
|
|
(this can be derived from writing the pythagoras theorems in the triangles of the joint)
|
|
|
|
*/
|
|
|
|
f32 halfW = 0.5 * attributes->width;
|
|
|
|
vec2 u = {n0.x + n1.x, n0.y + n1.y};
|
|
|
|
f32 uNormSquare = u.x*u.x + u.y*u.y;
|
|
|
|
f32 alpha = attributes->width / uNormSquare;
|
|
|
|
vec2 v = {u.x * alpha, u.y * alpha};
|
|
|
|
|
|
|
|
f32 excursionSquare = uNormSquare * Square(alpha - attributes->width/4);
|
|
|
|
|
|
|
|
if( attributes->joint == MG_JOINT_MITER
|
|
|
|
&& excursionSquare <= Square(attributes->maxJointExcursion))
|
|
|
|
{
|
|
|
|
vec2 mitterPoint = {p0.x + v.x, p0.y + v.y};
|
|
|
|
|
|
|
|
u32 baseIndex = mg_vertices_base_index(canvas);
|
|
|
|
i32* indices = mg_reserve_indices(canvas, 6);
|
|
|
|
|
|
|
|
mg_push_vertex(canvas, p0);
|
|
|
|
mg_push_vertex(canvas, (vec2){p0.x + n0.x*halfW, p0.y + n0.y*halfW});
|
|
|
|
mg_push_vertex(canvas, mitterPoint);
|
|
|
|
mg_push_vertex(canvas, (vec2){p0.x + n1.x*halfW, p0.y + n1.y*halfW});
|
|
|
|
|
|
|
|
indices[0] = baseIndex;
|
|
|
|
indices[1] = baseIndex + 1;
|
|
|
|
indices[2] = baseIndex + 2;
|
|
|
|
indices[3] = baseIndex;
|
|
|
|
indices[4] = baseIndex + 2;
|
|
|
|
indices[5] = baseIndex + 3;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
//NOTE(martin): add a bevel joint
|
|
|
|
u32 baseIndex = mg_vertices_base_index(canvas);
|
|
|
|
i32* indices = mg_reserve_indices(canvas, 3);
|
|
|
|
|
|
|
|
mg_push_vertex(canvas, p0);
|
|
|
|
mg_push_vertex(canvas, (vec2){p0.x + n0.x*halfW, p0.y + n0.y*halfW});
|
|
|
|
mg_push_vertex(canvas, (vec2){p0.x + n1.x*halfW, p0.y + n1.y*halfW});
|
|
|
|
|
|
|
|
DEBUG_ASSERT(!isnan(n0.x) && !isnan(n0.y) && !isnan(n1.x) && !isnan(n1.y));
|
|
|
|
|
|
|
|
indices[0] = baseIndex;
|
|
|
|
indices[1] = baseIndex + 1;
|
|
|
|
indices[2] = baseIndex + 2;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void mg_render_stroke_element(mg_canvas_data* canvas,
|
|
|
|
mg_path_elt* element,
|
|
|
|
mg_attributes* attributes,
|
|
|
|
vec2 currentPoint,
|
|
|
|
vec2* startTangent,
|
|
|
|
vec2* endTangent,
|
|
|
|
vec2* endPoint)
|
|
|
|
{
|
|
|
|
vec2 controlPoints[4] = {currentPoint, element->p[0], element->p[1], element->p[2]};
|
|
|
|
int endPointIndex = 0;
|
|
|
|
mg_next_shape(canvas, attributes);
|
|
|
|
|
|
|
|
switch(element->type)
|
|
|
|
{
|
|
|
|
case MG_PATH_LINE:
|
|
|
|
mg_render_stroke_line(canvas, controlPoints, attributes);
|
|
|
|
endPointIndex = 1;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case MG_PATH_QUADRATIC:
|
|
|
|
mg_render_stroke_quadratic(canvas, controlPoints, attributes);
|
|
|
|
endPointIndex = 2;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case MG_PATH_CUBIC:
|
|
|
|
mg_render_stroke_cubic(canvas, controlPoints, attributes);
|
|
|
|
endPointIndex = 3;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case MG_PATH_MOVE:
|
|
|
|
ASSERT(0, "should be unreachable");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
//NOTE: ensure tangents are properly computed even in presence of coincident points
|
|
|
|
//TODO: see if we can do this in a less hacky way
|
|
|
|
|
|
|
|
for(int i=1; i<4; i++)
|
|
|
|
{
|
|
|
|
if( controlPoints[i].x != controlPoints[0].x
|
|
|
|
|| controlPoints[i].y != controlPoints[0].y)
|
|
|
|
{
|
|
|
|
*startTangent = (vec2){.x = controlPoints[i].x - controlPoints[0].x,
|
|
|
|
.y = controlPoints[i].y - controlPoints[0].y};
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
*endPoint = controlPoints[endPointIndex];
|
|
|
|
|
|
|
|
for(int i=endPointIndex-1; i>=0; i++)
|
|
|
|
{
|
|
|
|
if( controlPoints[i].x != endPoint->x
|
|
|
|
|| controlPoints[i].y != endPoint->y)
|
|
|
|
{
|
|
|
|
*endTangent = (vec2){.x = endPoint->x - controlPoints[i].x,
|
|
|
|
.y = endPoint->y - controlPoints[i].y};
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
DEBUG_ASSERT(startTangent->x != 0 || startTangent->y != 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
u32 mg_render_stroke_subpath(mg_canvas_data* canvas,
|
|
|
|
mg_path_elt* elements,
|
|
|
|
mg_path_descriptor* path,
|
|
|
|
mg_attributes* attributes,
|
|
|
|
u32 startIndex,
|
|
|
|
vec2 startPoint)
|
|
|
|
{
|
|
|
|
u32 eltCount = path->count;
|
|
|
|
DEBUG_ASSERT(startIndex < eltCount);
|
|
|
|
|
|
|
|
vec2 currentPoint = startPoint;
|
|
|
|
vec2 endPoint = {0, 0};
|
|
|
|
vec2 previousEndTangent = {0, 0};
|
|
|
|
vec2 firstTangent = {0, 0};
|
|
|
|
vec2 startTangent = {0, 0};
|
|
|
|
vec2 endTangent = {0, 0};
|
|
|
|
|
|
|
|
//NOTE(martin): render first element and compute first tangent
|
|
|
|
mg_render_stroke_element(canvas, elements + startIndex, attributes, currentPoint, &startTangent, &endTangent, &endPoint);
|
|
|
|
|
|
|
|
firstTangent = startTangent;
|
|
|
|
previousEndTangent = endTangent;
|
|
|
|
currentPoint = endPoint;
|
|
|
|
|
|
|
|
//NOTE(martin): render subsequent elements along with their joints
|
|
|
|
u32 eltIndex = startIndex + 1;
|
|
|
|
for(;
|
|
|
|
eltIndex<eltCount && elements[eltIndex].type != MG_PATH_MOVE;
|
|
|
|
eltIndex++)
|
|
|
|
{
|
|
|
|
mg_render_stroke_element(canvas, elements + eltIndex, attributes, currentPoint, &startTangent, &endTangent, &endPoint);
|
|
|
|
|
|
|
|
if(attributes->joint != MG_JOINT_NONE)
|
|
|
|
{
|
|
|
|
mg_stroke_joint(canvas, currentPoint, previousEndTangent, startTangent, attributes);
|
|
|
|
}
|
|
|
|
previousEndTangent = endTangent;
|
|
|
|
currentPoint = endPoint;
|
|
|
|
}
|
|
|
|
u32 subPathEltCount = eltIndex - (startIndex+1);
|
|
|
|
|
|
|
|
//NOTE(martin): draw end cap / joint. We ensure there's at least two segments to draw a closing joint
|
|
|
|
if( subPathEltCount > 1
|
|
|
|
&& startPoint.x == endPoint.x
|
|
|
|
&& startPoint.y == endPoint.y)
|
|
|
|
{
|
|
|
|
if(attributes->joint != MG_JOINT_NONE)
|
|
|
|
{
|
|
|
|
//NOTE(martin): add a closing joint if the path is closed
|
|
|
|
mg_stroke_joint(canvas, endPoint, endTangent, firstTangent, attributes);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if(attributes->cap == MG_CAP_SQUARE)
|
|
|
|
{
|
|
|
|
//NOTE(martin): add start and end cap
|
|
|
|
mg_stroke_cap(canvas, startPoint, (vec2){-startTangent.x, -startTangent.y}, attributes);
|
|
|
|
mg_stroke_cap(canvas, endPoint, startTangent, attributes);
|
|
|
|
}
|
|
|
|
|
|
|
|
return(eltIndex);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void mg_render_stroke(mg_canvas_data* canvas,
|
|
|
|
mg_path_elt* elements,
|
|
|
|
mg_path_descriptor* path,
|
|
|
|
mg_attributes* attributes)
|
|
|
|
{
|
|
|
|
u32 eltCount = path->count;
|
|
|
|
DEBUG_ASSERT(eltCount);
|
|
|
|
|
|
|
|
vec2 startPoint = path->startPoint;
|
|
|
|
u32 startIndex = 0;
|
|
|
|
|
|
|
|
while(startIndex < eltCount)
|
|
|
|
{
|
|
|
|
//NOTE(martin): eliminate leading moves
|
|
|
|
while(startIndex < eltCount && elements[startIndex].type == MG_PATH_MOVE)
|
|
|
|
{
|
|
|
|
startPoint = elements[startIndex].p[0];
|
|
|
|
startIndex++;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(startIndex < eltCount)
|
|
|
|
{
|
|
|
|
startIndex = mg_render_stroke_subpath(canvas, elements, path, attributes, startIndex, startPoint);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
mg_canvas mg_canvas_prepare(mg_canvas canvas)
|
|
|
|
{
|
|
|
|
mg_canvas old = __mgCurrentCanvasHandle;
|
|
|
|
|
|
|
|
__mgCurrentCanvasHandle = canvas;
|
|
|
|
__mgCurrentCanvas = mg_canvas_data_from_handle(canvas);
|
|
|
|
|
|
|
|
if(__mgCurrentCanvas)
|
|
|
|
{
|
|
|
|
mg_surface_prepare(__mgCurrentCanvas->surface);
|
|
|
|
}
|
|
|
|
return(old);
|
|
|
|
}
|
|
|
|
|
|
|
|
vec2 mg_canvas_size(void)
|
|
|
|
{
|
|
|
|
vec2 res = {0};
|
|
|
|
if(__mgCurrentCanvas)
|
|
|
|
{
|
|
|
|
mp_rect frame = mg_surface_get_frame(__mgCurrentCanvas->surface);
|
|
|
|
res = (vec2){frame.w, frame.h};
|
|
|
|
}
|
|
|
|
return(res);
|
|
|
|
}
|
|
|
|
|
|
|
|
void mg_draw_batch(mg_canvas_data* canvas, mg_image_data* image)
|
|
|
|
{
|
|
|
|
mg_finalize_shape(canvas);
|
|
|
|
|
|
|
|
if(canvas->backend && canvas->backend->drawBatch && canvas->indexCount)
|
|
|
|
{
|
|
|
|
canvas->backend->drawBatch(canvas->backend, image, canvas->nextShapeIndex, canvas->vertexCount, canvas->indexCount);
|
|
|
|
}
|
|
|
|
mg_reset_shape_index(canvas);
|
|
|
|
|
|
|
|
canvas->vertexCount = 0;
|
|
|
|
canvas->indexCount = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void mg_flush_commands(int primitiveCount, mg_primitive* primitives, mg_path_elt* pathElements)
|
|
|
|
{
|
|
|
|
mg_canvas_data* canvas = __mgCurrentCanvas;
|
|
|
|
if(!canvas)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if(canvas->backend && canvas->backend->render)
|
|
|
|
{
|
|
|
|
int eltCount = canvas->path.startIndex + canvas->path.count;
|
|
|
|
canvas->backend->render(canvas->backend, canvas->clearColor, primitiveCount, primitives, eltCount, pathElements);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
u32 nextIndex = 0;
|
|
|
|
|
|
|
|
mg_reset_shape_index(canvas);
|
|
|
|
|
|
|
|
canvas->clip = (mp_rect){-FLT_MAX/2, -FLT_MAX/2, FLT_MAX, FLT_MAX};
|
|
|
|
canvas->image = mg_image_nil();
|
|
|
|
|
|
|
|
canvas->backend->begin(canvas->backend, canvas->clearColor);
|
|
|
|
|
|
|
|
//DEBUG
|
|
|
|
canvas->splitCount = 0;
|
|
|
|
|
|
|
|
for(int i=0; i<primitiveCount; i++)
|
|
|
|
{
|
|
|
|
if(nextIndex >= primitiveCount)
|
|
|
|
{
|
|
|
|
log_error("invalid location '%i' in graphics command buffer would cause an overrun\n", nextIndex);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
mg_primitive* primitive = &(primitives[nextIndex]);
|
|
|
|
nextIndex++;
|
|
|
|
|
|
|
|
if(i && primitive->attributes.image.h != canvas->image.h)
|
|
|
|
{
|
|
|
|
mg_image_data* imageData = mg_image_data_from_handle(canvas->image);
|
|
|
|
mg_draw_batch(canvas, imageData);
|
|
|
|
canvas->image = primitive->attributes.image;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch(primitive->cmd)
|
|
|
|
{
|
|
|
|
case MG_CMD_FILL:
|
|
|
|
{
|
|
|
|
mg_next_shape(canvas, &primitive->attributes);
|
|
|
|
mg_render_fill(canvas,
|
|
|
|
pathElements + primitive->path.startIndex,
|
|
|
|
&primitive->path);
|
|
|
|
} break;
|
|
|
|
|
|
|
|
case MG_CMD_STROKE:
|
|
|
|
{
|
|
|
|
mg_render_stroke(canvas,
|
|
|
|
pathElements + primitive->path.startIndex,
|
|
|
|
&primitive->path,
|
|
|
|
&primitive->attributes);
|
|
|
|
} break;
|
|
|
|
|
|
|
|
case MG_CMD_JUMP:
|
|
|
|
{
|
|
|
|
if(primitive->jump == ~0)
|
|
|
|
{
|
|
|
|
//NOTE(martin): normal end of stream marker
|
|
|
|
goto exit_command_loop;
|
|
|
|
}
|
|
|
|
else if(primitive->jump >= primitiveCount)
|
|
|
|
{
|
|
|
|
log_error("invalid jump location '%i' in graphics command buffer\n", primitive->jump);
|
|
|
|
goto exit_command_loop;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
nextIndex = primitive->jump;
|
|
|
|
}
|
|
|
|
} break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
exit_command_loop: ;
|
|
|
|
|
|
|
|
printf("path elements: %i, splitCount = %i\n", canvas->path.startIndex + canvas->path.count, canvas->splitCount);
|
|
|
|
|
|
|
|
|
|
|
|
mg_image_data* imageData = mg_image_data_from_handle(canvas->image);
|
|
|
|
mg_draw_batch(canvas, imageData);
|
|
|
|
|
|
|
|
canvas->backend->end(canvas->backend);
|
|
|
|
|
|
|
|
//NOTE(martin): clear buffers
|
|
|
|
canvas->vertexCount = 0;
|
|
|
|
canvas->indexCount = 0;
|
|
|
|
}
|