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(memory *Memory, shape_layer *Shape, nvg_point *PointData, int *Width, int *Height) { uint32 NumberOfVerts = 0; nvg_point *PointPlayhead = PointData; for (int i = 0; i < Shape->Point_Count; i++) { bezier_point *Point = Bezier_LookupAddress(Memory, Shape->Block_Bezier_Index, i, 1); #if 0 if (i == 0 || Point->Type == interpolation_type_linear) { *(v2 *)PointPlayhead = Point->Pos[0]; if (i != 0 && i != (Shape->Point_Count - 1)) { PointPlayhead->Flags |= NVG_PT_CORNER; } PointPlayhead++; NumberOfVerts++; } else if (Point->Type == interpolation_type_bezier) { bezier_point *Point_1 = Bezier_LookupAddress(Memory, Shape->Block_Bezier_Index, i-1, 1); v2 Pos[4] = { Point->Pos[0], Point->Pos[1], Point_1->Pos[2], Point_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); } #else *(v2 *)PointPlayhead = Point->Pos[0]; if (i != 0 && i != (Shape->Point_Count - 1)) { PointPlayhead->Flags |= NVG_PT_CORNER; } PointPlayhead++; NumberOfVerts++; #endif } 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, int NumberOfVerts, real32 StartWidth, bool32 IsClosed, nvg_point *PointData, real32 *StrokeData) { real32 Width = StartWidth * 0.5; int ncap = 12; real32 *StartingStrokeData = StrokeData; nvg_point *Point = &PointData[NumberOfVerts - 1]; nvg_point *NextPoint = PointData; int Start = 0; int LoopAmount = NumberOfVerts; if (!IsClosed) { Point = PointData; NextPoint = &PointData[1]; Start = 1; LoopAmount = NumberOfVerts - 1; StrokeData = NVG_RoundCap(Point, StrokeData, Point->dx, Point->dy, Width, ncap, 0.5, 0.5, 0); } for (int i = Start; i < LoopAmount; 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++; } if (!IsClosed) { StrokeData = NVG_RoundCap(NextPoint, StrokeData, Point->dx, Point->dy, Width, ncap, 0.5, 0.5, 1); } else { StrokeData = NVG_Point(StrokeData, StartingStrokeData[0], StartingStrokeData[1], 0, 0); StrokeData = NVG_Point(StrokeData, StartingStrokeData[4], StartingStrokeData[5], 0, 0); } int GL_PointCount = (StrokeData - StartingStrokeData) / 4; return GL_PointCount; } static void NVG_ExpandFill(void *Memory, int NumberOfVerts, nvg_point *PointData, real32 *FillData) { nvg_point *Point = PointData; for (int i = 0; i < NumberOfVerts; i++) { FillData = NVG_Point(FillData, Point->x, Point->y, 0, 0); Point++; } }