#if SPECIAL #include "main.h" #endif // nanovg code adapted by me for my data structures, I'm primarily using the // functions related to shape/path triangulation. // Copyright (c) 2013 Mikko Mononen memon@inside.org // // This software is provided 'as-is', without any express or implied // warranty. In no event will the authors be held liable for any damages // arising from the use of this software. // Permission is granted to anyone to use this software for any purpose, // including commercial applications, and to alter it and redistribute it // freely, subject to the following restrictions: // 1. The origin of this software must not be misrepresented; you must not // claim that you wrote the original software. If you use this software // in a product, an acknowledgment in the product documentation would be // appreciated but is not required. // 2. Altered source versions must be plainly marked as such, and must not be // misrepresented as being the original software. // 3. This notice may not be removed or altered from any source distribution. // 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); } else { float rx0,ry0,rx1,ry1,a0,a1; NVG_ChooseBevel(NextPoint->Flags & NVG_PR_INNERBEVEL, Point, NextPoint, -rw, &rx0,&ry0, &rx1,&ry1); a0 = atan2f(dly0, dlx0); a1 = atan2f(dly1, dlx1); if (a1 < a0) a1 += PI*2; StrokeData = NVG_Point(StrokeData, NextPoint->x + dlx0*rw, NextPoint->y + dly0*rw, 0, 0); StrokeData = NVG_Point(StrokeData, rx0, ry0, 0, 0); n = NVG_Clampi((int)ceilf(((a1 - a0) / PI) * ncap), 2, ncap); for (i = 0; i < n; i++) { float u = i/(float)(n-1); float a = a0 + u*(a1-a0); float lx = NextPoint->x + cosf(a) * lw; float ly = NextPoint->y + sinf(a) * lw; StrokeData = NVG_Point(StrokeData, lx, ly, 0, 0); StrokeData = NVG_Point(StrokeData, NextPoint->x, NextPoint->y, 0, 0); } StrokeData = NVG_Point(StrokeData, NextPoint->x + dlx1*rw, NextPoint->y + dly1*rw, 0, 0); StrokeData = NVG_Point(StrokeData, rx1, ry1, 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; } static real32 * NVG_ButtCap(nvg_point *Point, real32 *StrokeData, float dx, float dy, float w, float d, float u0, float u1, int Mode) { float px = (Mode == 0) ? Point->x - dx*d : Point->x + dx*d; float py = (Mode == 0) ? Point->y - dy*d : Point->y + dy*d; float dlx = dy; float dly = -dx; if (Mode == 0) { StrokeData = NVG_Point(StrokeData, px + dlx*w - dx, py + dly*w - dy, 0, 0); StrokeData = NVG_Point(StrokeData, px - dlx*w - dx, py - dly*w - dy, 0, 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); } else { StrokeData = NVG_Point(StrokeData, px + dlx*w, py + dly*w, 0, 0); StrokeData = NVG_Point(StrokeData, px - dlx*w, py - dly*w, 0, 0); StrokeData = NVG_Point(StrokeData, px + dlx*w + dx, py + dly*w + dy, 0, 0); StrokeData = NVG_Point(StrokeData, px - dlx*w + dx, py - dly*w + dy, 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, shape_options ShapeOpt, nvg_point *PointData, project_state *State, layer_transforms T, int Width, int Height, int CompWidth, int CompHeight, bool32 Interact, v2 *Min, v2 *Max, bool32 *IsConvex) { nvg_point *PointPlayhead = PointData; bezier_point *BezierPointData = (bezier_point *)Memory_PushScratch(Memory, sizeof(bezier_point) * 128); uint32 BezierCount = Bezier_Shape_Sort(Memory, Shape, BezierPointData, State, T, Width, Height, CompWidth, CompHeight, ShapeOpt.Roundness, Interact); for (int i = 0; i < BezierCount; i++) { bezier_point *Point = &BezierPointData[i]; if (i == 0 || Point->Type == interpolation_type_linear) { v2 Pos = Point->Pos[0]; *(v2 *)PointPlayhead = Pos; if (Shape->IsClosed || (i != 0 && i != (Shape->Point_Count - 1))) { PointPlayhead->Flags |= NVG_PT_CORNER; } PointPlayhead++; } else if (Point->Type == interpolation_type_bezier) { bezier_point *Point_1 = &BezierPointData[i-1]; v2 Pos[2] = { Point_1->Pos[0], Point->Pos[0] }; PointPlayhead = (nvg_point *)Bezier_CubicCalcPoints(Pos[0], Pos[0] + Point_1->Pos[1], Pos[1] + Point->Pos[2], Pos[1], PointPlayhead, sizeof(nvg_point)); // The point at the end is also returned, so we remove it. if (i != (Shape->Point_Count - 1)) PointPlayhead--; } else { Assert(0); } } Memory_PopScratch(Memory, sizeof(bezier_point) * 128); int NumberOfVerts = PointPlayhead - PointData; nvg_point *Point = &PointData[NumberOfVerts - 1]; nvg_point *NextPoint = PointData; *Min = V2(10000, 10000); *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++; } // Tell whether we need concave filling or not. ExpandStroke also does this. if (ShapeOpt.Visibility == 2) { Point = &PointData[NumberOfVerts - 1]; NextPoint = PointData; int LeftCount = 0; for (int i = 0; i < NumberOfVerts; i++) { real32 cross = NextPoint->dx * Point->dy - Point->dx * NextPoint->dy; if (cross > 0.0f) { LeftCount++; } Point = NextPoint++; } if (LeftCount == NumberOfVerts) *IsConvex = true; } return NumberOfVerts; } real32 MiterLimit = 2.4f; static uint32 NVG_ExpandStroke(void *Memory, int NumberOfVerts, real32 StartWidth, nvg_line_cap LineCap, nvg_line_cap LineJoin, bool32 IsClosed, nvg_point *PointData, real32 *StrokeData, bool32 *IsConvex) { 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; if (LineCap == NVG_ROUND) { StrokeData = NVG_RoundCap(Point, StrokeData, Point->dx, Point->dy, Width, ncap, 0.5, 0.5, 0); } else { StrokeData = NVG_ButtCap(Point, StrokeData, Point->dx, Point->dy, Width, Width-1, 0.5, 0.5, 0); } } int LeftCount = 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; } // Clear flags, but keep the corner. NextPoint->Flags = (NextPoint->Flags & NVG_PT_CORNER) ? NVG_PT_CORNER : 0; // Keep track of left turns. cross = NextPoint->dx * Point->dy - Point->dx * NextPoint->dy; if (cross > 0.0f) { LeftCount++; NextPoint->Flags |= NVG_PT_LEFT; } // Calculate if we should use bevel or miter for inner join. float iw = (Width > 0.0f) ? 1.0f / Width : 0.0f; limit = Max(1.01f, Min(Point->Length, NextPoint->Length) * iw); if ((dmr2 * limit*limit) < 1.0f) NextPoint->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) || 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 (LeftCount == LoopAmount) *IsConvex = true; if (!IsClosed) { if (LineCap == NVG_ROUND) { StrokeData = NVG_RoundCap(NextPoint, StrokeData, Point->dx, Point->dy, Width, ncap, 0.5, 0.5, 1); } else { StrokeData = NVG_ButtCap(NextPoint, StrokeData, Point->dx, Point->dy, Width, Width-1, 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++; } }