diff options
Diffstat (limited to 'src/nanovg.cpp')
-rw-r--r-- | src/nanovg.cpp | 247 |
1 files changed, 247 insertions, 0 deletions
diff --git a/src/nanovg.cpp b/src/nanovg.cpp new file mode 100644 index 0000000..06b2910 --- /dev/null +++ b/src/nanovg.cpp @@ -0,0 +1,247 @@ + +enum nvg_point_flags +{ + NVG_PT_CORNER = 0x01, + NVG_PT_LEFT = 0x02, + NVG_PT_BEVEL = 0x04, + NVG_PR_INNERBEVEL = 0x08, +}; + +struct nvg_point +{ + real32 x; + real32 y; + real32 dx; + real32 dy; + real32 Length; + real32 dmx; + real32 dmy; + uint8 Flags; +}; + +static real32 +NVG_Normalize(real32 *x, float* y) +{ + real32 d = sqrtf((*x)*(*x) + (*y)*(*y)); + if (d > 1e-6f) { + real32 id = 1.0f / d; + *x *= id; + *y *= id; + } + return d; +} + +static real32 * +NVG_Point(real32 *StrokeData, real32 x, real32 y, real32 u, real32 v) +{ + *(v4 *)StrokeData = V4(x, y, u, v); + return StrokeData + 4; +} + +static void NVG_ChooseBevel(int bevel, nvg_point *p0, nvg_point *p1, float w, + float* x0, float* y0, float* x1, float* y1) +{ + if (bevel) { + *x0 = p1->x + p0->dy * w; + *y0 = p1->y - p0->dx * w; + *x1 = p1->x + p1->dy * w; + *y1 = p1->y - p1->dx * w; + } else { + *x0 = p1->x + p1->dmx * w; + *y0 = p1->y + p1->dmy * w; + *x1 = p1->x + p1->dmx * w; + *y1 = p1->y + p1->dmy * w; + } +} + +static int NVG_Clampi(int a, int mn, int mx) { return a < mn ? mn : (a > mx ? mx : a); } + +static real32 * NVG_RoundJoin(nvg_point *Point, nvg_point *NextPoint, real32 *StrokeData, + float lw, float rw, float lu, float ru, int ncap) +{ + int i, n; + float dlx0 = Point->dy; + float dly0 = -Point->dx; + float dlx1 = NextPoint->dy; + float dly1 = -NextPoint->dx; + + if (NextPoint->Flags & NVG_PT_LEFT) { + float lx0,ly0,lx1,ly1,a0,a1; + NVG_ChooseBevel(NextPoint->Flags & NVG_PR_INNERBEVEL, Point, NextPoint, lw, &lx0,&ly0, &lx1,&ly1); + a0 = atan2f(-dly0, -dlx0); + a1 = atan2f(-dly1, -dlx1); + if (a1 > a0) a1 -= PI*2; + + StrokeData = NVG_Point(StrokeData, lx0, ly0, 0, 0); + StrokeData = NVG_Point(StrokeData, NextPoint->x - dlx0*rw, NextPoint->y - dly0*rw, 0, 0); + + n = NVG_Clampi((int)ceilf(((a0 - a1) / PI) * ncap), 2, ncap); + for (i = 0; i < n; i++) { + float u = i/(float)(n-1); + float a = a0 + u*(a1-a0); + float rx = NextPoint->x + cosf(a) * rw; + float ry = NextPoint->y + sinf(a) * rw; + StrokeData = NVG_Point(StrokeData, NextPoint->x, NextPoint->y, 0, 0); + StrokeData = NVG_Point(StrokeData, rx, ry, 0, 0); + } + + StrokeData = NVG_Point(StrokeData, lx1, ly1, 0, 0); + StrokeData = NVG_Point(StrokeData, NextPoint->x - dlx1*rw, NextPoint->y - dly1*rw, 0, 0); + } + return StrokeData; +} + +static real32 * NVG_RoundCap(nvg_point * Point, real32 *StrokeData, + float dx, float dy, float w, int ncap, + float u0, float u1, int Mode) +{ + int i; + float px = Point->x; + float py = Point->y; + float dlx = dy; + float dly = -dx; + float Flip = (Mode == 0) ? 1 : -1; + if (Mode != 0) { + StrokeData = NVG_Point(StrokeData, px + dlx*w, py + dly*w, 0, 0); + StrokeData = NVG_Point(StrokeData, px - dlx*w, py - dly*w, 0, 0); + } + for (i = 0; i < ncap; i++) { + float a = i/(float)(ncap-1)*PI; + float ax = cosf(a) * w, ay = sinf(a) * w; + v2 OuterPoint = V2(px - dlx*ax - dx*ay*Flip, py - dly*ax - dy*ay*Flip); + v2 InnerPoint = V2(px, py); + if (Mode == 0) { + StrokeData = NVG_Point(StrokeData, OuterPoint.x, OuterPoint.y, 0, 0); + StrokeData = NVG_Point(StrokeData, InnerPoint.x, InnerPoint.y, 0, 0); + } else { + StrokeData = NVG_Point(StrokeData, InnerPoint.x, InnerPoint.y, 0, 0); + StrokeData = NVG_Point(StrokeData, OuterPoint.x, OuterPoint.y, 0, 0); + } + } + if (Mode == 0) { + StrokeData = NVG_Point(StrokeData, px + dlx*w, py + dly*w, 0, 0); + StrokeData = NVG_Point(StrokeData, px - dlx*w, py - dly*w, 0, 0); + } + return StrokeData; +} + +// NOTE(fox): We only have to care about winding if we want to do HW accelerated +// shape subtraction with the stencil buffer (I think). +static uint32 +NVG_FlattenPath(void *Memory, block_bezier *Bezier, int PointCount, nvg_point *PointData, int *Width, int *Height) +{ + uint32 NumberOfVerts = 0; + nvg_point *PointPlayhead = PointData; + for (int i = 0; i < PointCount; i++) { + if (i == 0 || Bezier->Point[i].Type == interpolation_type_linear) { + *(v2 *)PointPlayhead = Bezier->Point[i].Pos[0]; + if (i != 0 && i != (PointCount - 1)) { + PointPlayhead->Flags |= NVG_PT_CORNER; + } + PointPlayhead++; + NumberOfVerts++; + } else if (Bezier->Point[i].Type == interpolation_type_bezier) { + v2 Pos[4] = { Bezier->Point[i].Pos[0], Bezier->Point[i].Pos[1], Bezier->Point[i+1].Pos[2], Bezier->Point[i+1].Pos[0] }; + Pos[1] = Pos[1] + Pos[0]; + Pos[2] = Pos[2] + Pos[3]; + NumberOfVerts += Bezier_CubicCalcPoints(Pos[3], Pos[2], Pos[1], Pos[0], PointPlayhead, sizeof(nvg_point)); + // The point at the end is also returned, so we remove it. + NumberOfVerts--; + PointPlayhead--; + } else { + Assert(0); + } + } + nvg_point *Point = &PointData[NumberOfVerts - 1]; + nvg_point *NextPoint = PointData; + v2 Min = V2(10000, 10000); + v2 Max = V2(-10000, -10000); + for (int i = 0; i < NumberOfVerts; i++) { + Point->dx = NextPoint->x - Point->x; + Point->dy = NextPoint->y - Point->y; + Point->Length = NVG_Normalize(&Point->dx, &Point->dy); + if (Point->x > Max.x) + Max.x = Point->x; + if (Point->x < Min.x) + Min.x = Point->x; + if (Point->y > Max.y) + Max.y = Point->y; + if (Point->y < Min.y) + Min.y = Point->y; + Point = NextPoint++; + } + *Width = Max.x - Min.x; + *Height = Max.y - Min.y; + return NumberOfVerts; +} + +real32 MiterLimit = 2.4f; + +static uint32 +NVG_ExpandStroke(void *Memory, block_bezier *Bezier, int NumberOfVerts, nvg_point *PointData, real32 *StrokeData) +{ + real32 Width = 50 * 0.5; + nvg_point *Point = PointData; + nvg_point *NextPoint = &PointData[1]; + int ncap = 12; + real32 *StartingStrokeData = StrokeData; + + StrokeData = NVG_RoundCap(Point, StrokeData, Point->dx, Point->dy, Width, ncap, 0.5, 0.5, 0); + + for (int i = 1; i < (NumberOfVerts - 1); i++) { + + real32 dlx0, dly0, dlx1, dly1, dmr2, cross, limit; + dlx0 = Point->dy; + dly0 = -Point->dx; + dlx1 = NextPoint->dy; + dly1 = -NextPoint->dx; + + // Calculate extrusions + NextPoint->dmx = (dlx0 + dlx1) * 0.5f; + NextPoint->dmy = (dly0 + dly1) * 0.5f; + dmr2 = NextPoint->dmx*NextPoint->dmx + NextPoint->dmy*NextPoint->dmy; + if (dmr2 > 0.000001f) { + float scale = 1.0f / dmr2; + if (scale > 600.0f) { + scale = 600.0f; + } + NextPoint->dmx *= scale; + NextPoint->dmy *= scale; + } + + // Keep track of left turns. + cross = NextPoint->dx * Point->dy - Point->dx * NextPoint->dy; + if (cross > 0.0f) { + NextPoint->Flags |= NVG_PT_LEFT; + } + + // Calculate if we should use bevel or miter for inner join. + // limit = nvg__maxf(1.01f, nvg__minf(p0->len, p1->len) * iw); + // if ((dmr2 * limit*limit) < 1.0f) + // p1->flags |= NVG_PR_INNERBEVEL; + + // Check to see if the corner needs to be beveled. + if (NextPoint->Flags & NVG_PT_CORNER) { + // if ((dmr2 * MiterLimit*MiterLimit) < 1.0f) r // || lineJoin == NVG_BEVEL || lineJoin == NVG_ROUND) { + NextPoint->Flags |= NVG_PT_BEVEL; + // } + } + + if ((NextPoint->Flags & (NVG_PT_BEVEL | NVG_PR_INNERBEVEL)) != 0) { + // if (lineJoin == NVG_ROUND) { + StrokeData = NVG_RoundJoin(Point, NextPoint, StrokeData, Width, Width, 0.5, 0.5, ncap); + // } + } else { + StrokeData = NVG_Point(StrokeData, NextPoint->x + (NextPoint->dmx * Width), NextPoint->y + (NextPoint->dmy * Width), 0, 0); + StrokeData = NVG_Point(StrokeData, NextPoint->x - (NextPoint->dmx * Width), NextPoint->y - (NextPoint->dmy * Width), 0, 0); + } + + Point = NextPoint++; + } + + StrokeData = NVG_RoundCap(NextPoint, StrokeData, Point->dx, Point->dy, Width, ncap, 0.5, 0.5, 1); + + int GL_PointCount = (StrokeData - StartingStrokeData) / 4; + + return GL_PointCount; +} |