using System; using System.Collections.Generic; using Quik.CommandMachine; namespace Quik.VertexGenerator { public class VertexGeneratorEngine : CommandEngine { public DrawQueue DrawQueue { get; } = new DrawQueue(); /// /// Granularity for rounded geometry. /// protected float CurveGranularity => (Style["-vertex-curve-granularity"] is float value) ? value : 1.0f; protected QuikVertex StrokeVertex => new QuikVertex() { ZIndex = Style.ZIndex ?? this.ZIndex, Color = Style.StrokeColor ?? QColor.Black, }; protected QuikVertex FillVertex => new QuikVertex() { ZIndex = Style.ZIndex ?? this.ZIndex, Color = Style.Color ?? QColor.White, }; public override void Reset() { base.Reset(); DrawQueue.Clear(); } protected override void ChildProcessCommand(Command name, CommandEnumerator queue) { base.ChildProcessCommand(name, queue); switch(name) { case Command.Line: LineProc(queue); break; case Command.Bezier: BezierProc(queue); break; case Command.Rectangle: RectangleProc(queue); break; default: break; } } /// /// Gets the rounding resolution for a line segment or border radius. /// /// The width of the line. /// The angle of the cap or joint arc. /// The rounding resolution. protected int GetRoundingResolution(float radius, float arc) { return (int) Math.Ceiling(arc * radius * CurveGranularity); } private readonly List LineList = new List(); private void LineProc(CommandEnumerator queue) { Frame frame = queue.Dequeue(); // Clear temporary vector list and retreive all line segments. LineList.Clear(); if (frame.Type == FrameType.IVec1) { int count = (int)frame; for (int i = 0; i < count; i++) { frame = queue.Dequeue(); LineList.Add((QLine)frame); } } else { LineList.Add((QLine)frame); } float width = Style.StrokeWidth ?? 1; DrawQueue.StartDrawCall(Viewport); LineInfo prevBase, nextBase = default; for (int i = 0; i < LineList.Count; i++) { QLine line = LineList[i]; // A line segment needs a start cap if it is the first segment in // the list, or the last end point is not the current start point. bool isStart = (i == 0 || line.Start != LineList[i - 1].End); // A line segment needs an end cap if it is the last line segment // in the list or if the next start point is not the current end point. bool isEnd = (i == LineList.Count - 1 || line.End != LineList[i+1].Start); // Generate the main line segment. prevBase = nextBase; nextBase = GenerateLineSegment(line); if (isStart) { // Then a start cap if necessary. GenerateCap(line.Start, line.Normal(), prevBase, false); } else { // Otherwise generate the required joint. GenerateJoint(line.Start, LineList[i-1].Normal(), line.Normal(), prevBase, nextBase); } if (isEnd) { // Then generate the end cap if necessary. GenerateCap(line.End, line.Normal(), nextBase, true); } } DrawQueue.EndDrawCall(); } private LineInfo GenerateLineSegment(in QLine line) { QuikVertex vertex = StrokeVertex; QuikVertex a, b, c, d; QVec2 normal = line.Normal(); float width = Style.StrokeWidth ?? 1; a = b = c = d = vertex; a.Position = line.Start + width / 2 * normal; b.Position = line.Start - width / 2 * normal; c.Position = line.End + width / 2 * normal; d.Position = line.End - width / 2 * normal; DrawQueue.RestoreOffset(); DrawQueue.AddVertex(a); DrawQueue.AddVertex(b); DrawQueue.AddVertex(c); DrawQueue.AddVertex(d); DrawQueue.AddElement(1); DrawQueue.AddElement(2); DrawQueue.AddElement(0); DrawQueue.AddElement(1); DrawQueue.AddElement(3); DrawQueue.AddElement(2); return new LineInfo(DrawQueue.BaseOffset, 0, 1, 2, 3); } private void GenerateJoint( in QVec2 center, in QVec2 prevNormal, in QVec2 nextNormal, in LineInfo prevInfo, in LineInfo nextInfo) { // Figure out which side needs the joint. QVec2 meanNormal = 0.5f * (prevNormal + nextNormal); QVec2 meanTangent = new QVec2(meanNormal.Y, -meanNormal.X); QVec2 positiveEdge = ((center + nextNormal) - (center + prevNormal)).Normalize(); QVec2 negativeEdge = ((center - nextNormal) - (center - prevNormal)).Normalize(); float positive, negative; positive = QVec2.Dot(meanTangent, positiveEdge); negative = QVec2.Dot(meanNormal, negativeEdge); if (positive == negative) { // To be fair this is highly unlikely considering the nature of // floats, but, generate an end cap to handle a cusp. GenerateCap(center, nextNormal, nextInfo, true); return; } QuikVertex vertex = StrokeVertex; float radius = Style.StrokeWidth/2 ?? 0.5f; float arc = MathF.Acos(QVec2.Dot(prevNormal, nextNormal)); int resolution = GetRoundingResolution(radius, arc); bool isNegative = positive < negative; vertex.Position = center; DrawQueue.RestoreOffset(); DrawQueue.AddVertex(vertex); int lastIndex, endIndex; if (isNegative) { lastIndex = DrawQueue.RelativeElement(prevInfo.BaseOffset, prevInfo.EndNegative); endIndex = DrawQueue.RelativeElement(nextInfo.BaseOffset, nextInfo.StartNegative); } else { lastIndex = DrawQueue.RelativeElement(nextInfo.BaseOffset, nextInfo.StartNegative); endIndex = DrawQueue.RelativeElement(prevInfo.BaseOffset, prevInfo.EndPositive); } for (int i = 0; i < resolution; i++) { float angle = (float)(i+1) / (resolution + 1) * arc; float cos = MathF.Cos(angle); float sin = MathF.Sin(angle); QVec2 displacement; if (isNegative) { displacement = new QVec2() { X = -prevNormal.X * cos + prevNormal.Y * sin, Y = -prevNormal.X * sin - prevNormal.Y * cos } * radius; } else { displacement = new QVec2() { X = nextNormal.X * cos - nextNormal.Y * sin, Y = nextNormal.X * sin + nextNormal.Y * cos } * radius; } vertex.Position = center + displacement; DrawQueue.AddVertex(vertex); DrawQueue.AddElement(lastIndex); DrawQueue.AddElement(i); DrawQueue.AddElement(0); lastIndex = i; } DrawQueue.AddElement(lastIndex); DrawQueue.AddElement(endIndex); DrawQueue.AddElement(0); } private void GenerateCap( in QVec2 center, in QVec2 normal, in LineInfo info, bool endCap) { int lastIndex, startIndex; QuikVertex vertex = StrokeVertex; float radius = Style.StrokeWidth ?? 1.0f; int resolution = GetRoundingResolution(radius, MathF.PI); DrawQueue.RestoreOffset(); if (endCap) { lastIndex = DrawQueue.RelativeElement(info.BaseOffset, info.EndPositive); startIndex = DrawQueue.RelativeElement(info.BaseOffset, info.EndNegative); } else { lastIndex = DrawQueue.RelativeElement(info.BaseOffset, info.StartPositive); startIndex = DrawQueue.RelativeElement(info.BaseOffset, info.StartNegative); } for (int i = 0; i < resolution; i++) { float angle = (float) (i + 1) / (resolution + 1) * MathF.PI; float cos = MathF.Cos(angle); float sin = MathF.Sin(angle); QVec2 displacement; if (endCap) { displacement = new QVec2() { X = normal.X * cos + normal.Y * sin, Y = -normal.X * sin + normal.Y * cos } * radius; } else { displacement = new QVec2() { X = normal.X * cos - normal.Y * sin, Y = normal.X * sin + normal.Y * cos } * radius; } vertex.Position = center + displacement; DrawQueue.AddVertex(vertex); DrawQueue.AddElement(startIndex); DrawQueue.AddElement(lastIndex); DrawQueue.AddElement(i); lastIndex = i; } } private readonly List BezierList = new List(); private void BezierProc(CommandEnumerator queue) { Frame a = queue.Dequeue(); Frame b; // Clear temporary vector list and retreive all bezier segments. BezierList.Clear(); if (a.Type == FrameType.IVec1) { int count = (int)a; for (int i = 0; i < count; i++) { a = queue.Dequeue(); b = queue.Dequeue(); BezierList.Add( new QBezier( new QVec2(a.GetF(0), a.GetF(1)), new QVec2(b.GetF(0), b.GetF(1)), new QVec2(b.GetF(2), b.GetF(3)), new QVec2(a.GetF(2), a.GetF(3)) ) ); } } else { b = queue.Dequeue(); BezierList.Add( new QBezier( new QVec2(a.GetF(0), a.GetF(1)), new QVec2(b.GetF(0), b.GetF(1)), new QVec2(b.GetF(2), b.GetF(3)), new QVec2(a.GetF(2), a.GetF(3)) ) ); } float width = Style.StrokeWidth ?? 1; DrawQueue.StartDrawCall(Viewport); LineInfo prevBase, nextBase = default; for (int i = 0; i < LineList.Count; i++) { QBezier bezier = BezierList[i]; // A line segment needs a start cap if it is the first segment in // the list, or the last end point is not the current start point. bool isStart = (i == 0 || bezier.Start != BezierList[i - 1].End); // A line segment needs an end cap if it is the last line segment // in the list or if the next start point is not the current end point. bool isEnd = (i == LineList.Count - 1 || bezier.End != BezierList[i+1].Start); // Generate the main line segment. prevBase = nextBase; nextBase = GenerateBezierSegment(bezier); if (isStart) { // Then a start cap if necessary. GenerateCap(bezier.Start, bezier.GetBezierNormal(0), prevBase, false); } else { // Otherwise generate the required joint. GenerateJoint(bezier.Start, BezierList[i-1].GetBezierNormal(1), bezier.GetBezierNormal(0), prevBase, nextBase); } if (isEnd) { // Then generate the end cap if necessary. GenerateCap(bezier.End, bezier.GetBezierNormal(1), nextBase, true); } } DrawQueue.EndDrawCall(); } private LineInfo GenerateBezierSegment(in QBezier bezier) { QVec2 startTangent = bezier.GetBezierTangent(0); QVec2 endTangent = bezier.GetBezierTangent(1); QVec2 startNormal = new QVec2(-startTangent.Y, startTangent.X).Normalize(); QVec2 endNormal = new QVec2(-endTangent.Y, endTangent.X).Normalize(); float width = Style.StrokeWidth ?? 1; float radius = 0.5f * width; int resolution = GetRoundingResolution(radius, bezier.RasterizationArc); DrawQueue.RestoreOffset(); QuikVertex v = StrokeVertex; int vbase = DrawQueue.BaseOffset; int index = 2; v.Position = bezier.Start + radius * startNormal; DrawQueue.AddVertex(v); v.Position = bezier.Start - radius * startNormal; DrawQueue.AddVertex(v); for (int i = 0; i < resolution; i++, index += 2) { float t = (i + 1.0f) / resolution; QVec2 at = bezier.GetBezierTangent(t).Normalize(); QVec2 a = bezier.GetBezierPoint(t); QVec2 an = radius * new QVec2(-at.Y, at.X); v.Position = a + an; DrawQueue.AddVertex(v); v.Position = a - an; DrawQueue.AddVertex(v); DrawQueue.AddElement(index - 2); DrawQueue.AddElement(index - 1); DrawQueue.AddElement(index + 0); DrawQueue.AddElement(index - 1); DrawQueue.AddElement(index + 1); DrawQueue.AddElement(index + 0); } return new LineInfo(vbase, 0, 1, index - 2, index - 1); } private readonly List RectangleList = new List(); private void RectangleProc(CommandEnumerator queue) { Frame frame = queue.Dequeue(); RectangleList.Clear(); if (frame.Type == FrameType.IVec1) { int count = (int)frame; for (int i = 0; i < count; i++) { frame = queue.Dequeue(); RectangleList.Add((QRectangle)frame); } } else { RectangleList.Add((QRectangle)frame); } float stroke = Style.StrokeWidth ?? 1.0f; float radius = (float?)Style["radius"] ?? 0.0f; // TODO: not this. DrawQueue.StartDrawCall(Viewport); for (int i = 0; i < RectangleList.Count; i++) { QRectangle outer = RectangleList[i]; QRectangle inner = new QRectangle( outer.Right - stroke, outer.Bottom + stroke, outer.Left + stroke, outer.Top - stroke); GenerateRectangleBase(inner, Math.Max(radius - stroke, 0.0f)); if (stroke == 0.0f) continue; if (radius == 0.0f) { GenerateRectangleStripStraight(outer); } else if (radius < stroke) { GenerateRectangleStripNarrow(outer, radius); } else { GenerateRectangleStripWide(outer, radius); } } DrawQueue.EndDrawCall(); } private void GenerateRectangleBase(in QRectangle rectangle, float radius) { /* +--j-------i--+ |NW| N |NE| k--d-------c--h | | | | |W | C | E| | | | | l--a-------b--g |SW| S |SE| +--e-------f--+ a b c d e f g h i j k l 0 1 2 3 4 5 6 7 8 9 10 11 */ DrawQueue.RestoreOffset(); // Draw center rectangle. QVec2 aPos, bPos, cPos, dPos; QuikVertex v = FillVertex; aPos = v.Position = new QVec2(rectangle.Left + radius, rectangle.Bottom + radius); DrawQueue.AddVertex(v); bPos = v.Position = new QVec2(rectangle.Right - radius, rectangle.Bottom + radius); DrawQueue.AddVertex(v); cPos = v.Position = new QVec2(rectangle.Right - radius, rectangle.Top - radius); DrawQueue.AddVertex(v); dPos = v.Position = new QVec2(rectangle.Left + radius, rectangle.Top - radius); DrawQueue.AddVertex(v); DrawQueue.AddElement(0); DrawQueue.AddElement(1); DrawQueue.AddElement(2); DrawQueue.AddElement(0); DrawQueue.AddElement(2); DrawQueue.AddElement(3); if (radius == 0.0f) return; // Draw south rectangle. v.Position = new QVec2(rectangle.Left + radius, rectangle.Bottom); DrawQueue.AddVertex(v); v.Position = new QVec2(rectangle.Right - radius, rectangle.Bottom); DrawQueue.AddVertex(v); DrawQueue.AddElement(4); DrawQueue.AddElement(5); DrawQueue.AddElement(1); DrawQueue.AddElement(4); DrawQueue.AddElement(1); DrawQueue.AddElement(0); // Draw east rectangle. v.Position = new QVec2(rectangle.Right, rectangle.Bottom + radius); DrawQueue.AddVertex(v); v.Position = new QVec2(rectangle.Right, rectangle.Top - radius); DrawQueue.AddVertex(v); DrawQueue.AddElement(1); DrawQueue.AddElement(6); DrawQueue.AddElement(7); DrawQueue.AddElement(1); DrawQueue.AddElement(7); DrawQueue.AddElement(3); // Draw north rectangle. v.Position = new QVec2(rectangle.Right - radius, rectangle.Top); DrawQueue.AddVertex(v); v.Position = new QVec2(rectangle.Left + radius, rectangle.Top); DrawQueue.AddVertex(v); DrawQueue.AddElement(3); DrawQueue.AddElement(2); DrawQueue.AddElement(8); DrawQueue.AddElement(3); DrawQueue.AddElement(8); DrawQueue.AddElement(0); // Draw west rectangle. v.Position = new QVec2(rectangle.Left, rectangle.Top - radius); DrawQueue.AddVertex(v); v.Position = new QVec2(rectangle.Left, rectangle.Bottom + radius); DrawQueue.AddVertex(v); DrawQueue.AddElement(11); DrawQueue.AddElement(0); DrawQueue.AddElement(3); DrawQueue.AddElement(11); DrawQueue.AddElement(3); DrawQueue.AddElement(10); // Draw north east corner. int resolution = GetRoundingResolution(radius, 0.5f * MathF.PI); int previous = 7, current = 12; for (int i = 0; i < resolution - 1; i++) { float theta = (i + 1.0f) / (resolution + 1); float xoff = MathF.Cos(theta) * radius; float yoff = MathF.Sin(theta) * radius; v.Position = cPos + new QVec2(xoff, yoff); DrawQueue.AddVertex(v); DrawQueue.AddElement(2); DrawQueue.AddElement(previous); DrawQueue.AddElement((previous = current++)); } DrawQueue.AddElement(2); DrawQueue.AddElement(previous); DrawQueue.AddElement(8); // Draw the north west corner. previous = 9; for (int i = 0; i < resolution - 1; i++) { float theta = (i + 1.0f) / (resolution + 1); float xoff = -MathF.Sin(theta) * radius; float yoff = MathF.Cos(theta) * radius; v.Position = dPos + new QVec2(xoff, yoff); DrawQueue.AddVertex(v); DrawQueue.AddElement(3); DrawQueue.AddElement(previous); DrawQueue.AddElement((previous = current++)); } DrawQueue.AddElement(3); DrawQueue.AddElement(previous); DrawQueue.AddElement(10); // Draw south west corner. previous = 11; for (int i = 0; i < resolution - 1; i++) { float theta = (i + 1.0f) / (resolution + 1); float xoff = -MathF.Cos(theta) * radius; float yoff = -MathF.Sin(theta) * radius; v.Position = aPos + new QVec2(xoff, yoff); DrawQueue.AddVertex(v); DrawQueue.AddElement(0); DrawQueue.AddElement(previous); DrawQueue.AddElement((previous = current++)); } DrawQueue.AddElement(0); DrawQueue.AddElement(previous); DrawQueue.AddElement(4); // Draw the south east corner. previous = 5; for (int i = 0; i < resolution - 1; i++) { float theta = (i + 1.0f) / (resolution + 1); float xoff = -MathF.Sin(theta) * radius; float yoff = MathF.Cos(theta) * radius; v.Position = bPos + new QVec2(xoff, yoff); DrawQueue.AddVertex(v); DrawQueue.AddElement(1); DrawQueue.AddElement(previous); DrawQueue.AddElement((previous = current++)); } DrawQueue.AddElement(1); DrawQueue.AddElement(previous); DrawQueue.AddElement(6); } private void GenerateRectangleStripStraight(in QRectangle rectangle) { /* h---------g |\ N /| | \ / | | d---c | |W | | E| | a---b | | / \ | |/ S \| e---------f a b c d e f g h 0 1 2 3 4 5 6 7 */ QuikVertex v = StrokeVertex; float stroke = Style.StrokeWidth ?? 1.0f; DrawQueue.RestoreOffset(); v.Position = new QVec2(rectangle.Left + stroke, rectangle.Bottom + stroke); DrawQueue.AddVertex(v); v.Position = new QVec2(rectangle.Right - stroke, rectangle.Bottom + stroke); DrawQueue.AddVertex(v); v.Position = new QVec2(rectangle.Right - stroke, rectangle.Top - stroke); DrawQueue.AddVertex(v); v.Position = new QVec2(rectangle.Left + stroke, rectangle.Top - stroke); DrawQueue.AddVertex(v); v.Position = new QVec2(rectangle.Left, rectangle.Bottom); DrawQueue.AddVertex(v); v.Position = new QVec2(rectangle.Right, rectangle.Bottom); DrawQueue.AddVertex(v); v.Position = new QVec2(rectangle.Right, rectangle.Top); DrawQueue.AddVertex(v); v.Position = new QVec2(rectangle.Left, rectangle.Top); DrawQueue.AddVertex(v); DrawQueue.AddElement(4); DrawQueue.AddElement(5); DrawQueue.AddElement(1); // SSW DrawQueue.AddElement(4); DrawQueue.AddElement(1); DrawQueue.AddElement(0); // SSE DrawQueue.AddElement(1); DrawQueue.AddElement(5); DrawQueue.AddElement(6); // SEE DrawQueue.AddElement(1); DrawQueue.AddElement(6); DrawQueue.AddElement(2); // NEE DrawQueue.AddElement(3); DrawQueue.AddElement(2); DrawQueue.AddElement(6); // NNE DrawQueue.AddElement(3); DrawQueue.AddElement(6); DrawQueue.AddElement(7); // NNW DrawQueue.AddElement(4); DrawQueue.AddElement(0); DrawQueue.AddElement(3); // NWW DrawQueue.AddElement(4); DrawQueue.AddElement(3); DrawQueue.AddElement(7); // SWW } private void GenerateRectangleStripNarrow(in QRectangle rectangle, float radius) { /* v-j---i-u | | | | x-w | N | t-s | \| |/ | k---d---c---h | | | | | W | | E | | | | | l---a---b---g | /| |\ | m-n | S | q-r | | | | o-e---f-p a b c d e f g h i j 00: 0 1 2 3 4 5 6 7 8 9 k l m n o p q r s t 10: 0 1 2 3 4 5 6 7 8 9 u v w x 20: 0 1 2 3 */ QuikVertex v = StrokeVertex; QVec2 nPos, qPos, tPos, wPos; float stroke = Style.StrokeWidth ?? 1.0f; DrawQueue.RestoreOffset(); // a-b-c-d v.Position = new QVec2(rectangle.Left + stroke, rectangle.Bottom + stroke); DrawQueue.AddVertex(v); v.Position = new QVec2(rectangle.Right - stroke, rectangle.Bottom + stroke); DrawQueue.AddVertex(v); v.Position = new QVec2(rectangle.Right - stroke, rectangle.Top - stroke); DrawQueue.AddVertex(v); v.Position = new QVec2(rectangle.Left + stroke, rectangle.Top - stroke); DrawQueue.AddVertex(v); // ef-gh-ij-kl v.Position = new QVec2(rectangle.Left + stroke, rectangle.Bottom); DrawQueue.AddVertex(v); v.Position = new QVec2(rectangle.Left + stroke, rectangle.Bottom); DrawQueue.AddVertex(v); v.Position = new QVec2(rectangle.Right, rectangle.Bottom + stroke); DrawQueue.AddVertex(v); v.Position = new QVec2(rectangle.Right, rectangle.Top - stroke); DrawQueue.AddVertex(v); v.Position = new QVec2(rectangle.Right - stroke, rectangle.Top); DrawQueue.AddVertex(v); v.Position = new QVec2(rectangle.Right - stroke, rectangle.Top); DrawQueue.AddVertex(v); v.Position = new QVec2(rectangle.Left, rectangle.Top - stroke); DrawQueue.AddVertex(v); v.Position = new QVec2(rectangle.Left, rectangle.Bottom + stroke); DrawQueue.AddVertex(v); // mno v.Position = new QVec2(rectangle.Left, rectangle.Bottom + radius); DrawQueue.AddVertex(v); nPos = v.Position = new QVec2(rectangle.Left + radius, rectangle.Bottom + radius); DrawQueue.AddVertex(v); v.Position = new QVec2(rectangle.Left + radius, rectangle.Bottom); DrawQueue.AddVertex(v); // pqr v.Position = new QVec2(rectangle.Right - radius, rectangle.Bottom); DrawQueue.AddVertex(v); qPos = v.Position = new QVec2(rectangle.Right - radius, rectangle.Bottom + radius); DrawQueue.AddVertex(v); v.Position = new QVec2(rectangle.Right, rectangle.Bottom + radius); DrawQueue.AddVertex(v); // stu v.Position = new QVec2(rectangle.Right, rectangle.Top - radius); DrawQueue.AddVertex(v); tPos = v.Position = new QVec2(rectangle.Right - radius, rectangle.Top - radius); DrawQueue.AddVertex(v); v.Position = new QVec2(rectangle.Right - radius, rectangle.Top); DrawQueue.AddVertex(v); // vwx v.Position = new QVec2(rectangle.Left + radius, rectangle.Top); DrawQueue.AddVertex(v); wPos = v.Position = new QVec2(rectangle.Left + radius, rectangle.Top - radius); DrawQueue.AddVertex(v); v.Position = new QVec2(rectangle.Left, rectangle.Top - radius); DrawQueue.AddVertex(v); // E DrawQueue.AddElement(1); DrawQueue.AddElement(6); DrawQueue.AddElement(7); DrawQueue.AddElement(1); DrawQueue.AddElement(7); DrawQueue.AddElement(2); // N DrawQueue.AddElement(3); DrawQueue.AddElement(2); DrawQueue.AddElement(8); DrawQueue.AddElement(3); DrawQueue.AddElement(8); DrawQueue.AddElement(9); // W DrawQueue.AddElement(11); DrawQueue.AddElement(0); DrawQueue.AddElement(3); DrawQueue.AddElement(11); DrawQueue.AddElement(3); DrawQueue.AddElement(10); // S DrawQueue.AddElement(4); DrawQueue.AddElement(5); DrawQueue.AddElement(1); DrawQueue.AddElement(4); DrawQueue.AddElement(1); DrawQueue.AddElement(0); // NEE DrawQueue.AddElement(2); DrawQueue.AddElement(7); DrawQueue.AddElement(18); DrawQueue.AddElement(2); DrawQueue.AddElement(18); DrawQueue.AddElement(19); // NNE DrawQueue.AddElement(2); DrawQueue.AddElement(19); DrawQueue.AddElement(20); DrawQueue.AddElement(2); DrawQueue.AddElement(20); DrawQueue.AddElement(8); // NNW DrawQueue.AddElement(22); DrawQueue.AddElement(3); DrawQueue.AddElement(19); DrawQueue.AddElement(22); DrawQueue.AddElement(19); DrawQueue.AddElement(21); // NWW DrawQueue.AddElement(10); DrawQueue.AddElement(3); DrawQueue.AddElement(22); DrawQueue.AddElement(10); DrawQueue.AddElement(22); DrawQueue.AddElement(23); // SWW DrawQueue.AddElement(12); DrawQueue.AddElement(13); DrawQueue.AddElement(0); DrawQueue.AddElement(12); DrawQueue.AddElement(0); DrawQueue.AddElement(11); // SSW DrawQueue.AddElement(14); DrawQueue.AddElement(4); DrawQueue.AddElement(0); DrawQueue.AddElement(14); DrawQueue.AddElement(0); DrawQueue.AddElement(13); // NE int resolution = GetRoundingResolution(radius, 0.5f * MathF.PI); int previous = 18, current = 24; for (int i = 0; i < resolution - 1; i++) { float theta = (i + 1.0f) / (resolution + 1); float xoff = MathF.Cos(theta) * radius; float yoff = MathF.Sin(theta) * radius; v.Position = tPos + new QVec2(xoff, yoff); DrawQueue.AddVertex(v); DrawQueue.AddElement(19); DrawQueue.AddElement(previous); DrawQueue.AddElement((previous = current++)); } DrawQueue.AddElement(19); DrawQueue.AddElement(previous); DrawQueue.AddElement(20); // NW previous = 21; for (int i = 0; i < resolution - 1; i++) { float theta = (i + 1.0f) / (resolution + 1); float xoff = -MathF.Sin(theta) * radius; float yoff = MathF.Cos(theta) * radius; v.Position = wPos + new QVec2(xoff, yoff); DrawQueue.AddVertex(v); DrawQueue.AddElement(22); DrawQueue.AddElement(previous); DrawQueue.AddElement((previous = current++)); } DrawQueue.AddElement(22); DrawQueue.AddElement(previous); DrawQueue.AddElement(23); // SW previous = 12; for (int i = 0; i < resolution - 1; i++) { float theta = (i + 1.0f) / (resolution + 1); float xoff = -MathF.Cos(theta) * radius; float yoff = -MathF.Sin(theta) * radius; v.Position = nPos + new QVec2(xoff, yoff); DrawQueue.AddVertex(v); DrawQueue.AddElement(23); DrawQueue.AddElement(previous); DrawQueue.AddElement((previous = current++)); } DrawQueue.AddElement(23); DrawQueue.AddElement(previous); DrawQueue.AddElement(14); // SE previous = 15; for (int i = 0; i < resolution - 1; i++) { float theta = (i + 1.0f) / (resolution + 1); float xoff = -MathF.Sin(theta) * radius; float yoff = MathF.Cos(theta) * radius; v.Position = qPos + new QVec2(xoff, yoff); DrawQueue.AddVertex(v); DrawQueue.AddElement(16); DrawQueue.AddElement(previous); DrawQueue.AddElement((previous = current++)); } DrawQueue.AddElement(16); DrawQueue.AddElement(previous); DrawQueue.AddElement(17); } private void GenerateRectangleStripWide(in QRectangle rectangle, float radius) { /* l---k | N | i---j p---o h---g | W | | E | m---n e---f d---c | S | a---b a b c d e f g h i j 00: 0 1 2 3 4 5 6 7 8 9 k l m n o p q r s t 10: 0 1 2 3 4 5 */ QuikVertex v = StrokeVertex; float stroke = Style.StrokeWidth ?? 1.0f; float innerRadius = radius - stroke; DrawQueue.RestoreOffset(); v.Position = new QVec2(rectangle.Left + radius, rectangle.Bottom); DrawQueue.AddVertex(v); v.Position = new QVec2(rectangle.Right - radius, rectangle.Bottom); DrawQueue.AddVertex(v); v.Position = new QVec2(rectangle.Right - radius, rectangle.Bottom + stroke); DrawQueue.AddVertex(v); v.Position = new QVec2(rectangle.Left + radius, rectangle.Bottom + stroke); DrawQueue.AddVertex(v); v.Position = new QVec2(rectangle.Right - stroke, rectangle.Bottom + radius); DrawQueue.AddVertex(v); v.Position = new QVec2(rectangle.Right, rectangle.Top - radius); DrawQueue.AddVertex(v); v.Position = new QVec2(rectangle.Right, rectangle.Top - radius); DrawQueue.AddVertex(v); v.Position = new QVec2(rectangle.Right - stroke, rectangle.Bottom + radius); DrawQueue.AddVertex(v); v.Position = new QVec2(rectangle.Left + radius, rectangle.Top - stroke); DrawQueue.AddVertex(v); v.Position = new QVec2(rectangle.Right - radius, rectangle.Top - stroke); DrawQueue.AddVertex(v); v.Position = new QVec2(rectangle.Right - radius, rectangle.Top); DrawQueue.AddVertex(v); v.Position = new QVec2(rectangle.Left + radius, rectangle.Top); DrawQueue.AddVertex(v); v.Position = new QVec2(rectangle.Left, rectangle.Bottom + radius); DrawQueue.AddVertex(v); v.Position = new QVec2(rectangle.Left + stroke, rectangle.Top - radius); DrawQueue.AddVertex(v); v.Position = new QVec2(rectangle.Left + stroke, rectangle.Top - radius); DrawQueue.AddVertex(v); v.Position = new QVec2(rectangle.Left, rectangle.Bottom + radius); DrawQueue.AddVertex(v); // S DrawQueue.AddElement(0); DrawQueue.AddElement(1); DrawQueue.AddElement(2); DrawQueue.AddElement(0); DrawQueue.AddElement(2); DrawQueue.AddElement(3); // E DrawQueue.AddElement(4); DrawQueue.AddElement(5); DrawQueue.AddElement(6); DrawQueue.AddElement(4); DrawQueue.AddElement(6); DrawQueue.AddElement(7); // N DrawQueue.AddElement(8); DrawQueue.AddElement(9); DrawQueue.AddElement(10); DrawQueue.AddElement(8); DrawQueue.AddElement(10); DrawQueue.AddElement(11); // W DrawQueue.AddElement(12); DrawQueue.AddElement(13); DrawQueue.AddElement(14); DrawQueue.AddElement(12); DrawQueue.AddElement(14); DrawQueue.AddElement(15); // Draw NE arc. int resolution = GetRoundingResolution(radius, 0.5f * MathF.PI); int current = 16; QVec2 center = new QVec2(rectangle.Right - radius, rectangle.Top - radius); int s1 = 7, s2 = 6; for (int i = 0; i < resolution - 1; i++) { float theta = (i + 1.0f) / (resolution + 1); float xoff = MathF.Cos(theta); float yoff = MathF.Sin(theta); v.Position = center + radius * new QVec2(xoff, yoff); DrawQueue.AddVertex(v); v.Position = center + innerRadius * new QVec2(xoff, yoff); DrawQueue.AddVertex(v); DrawQueue.AddElement(s1); DrawQueue.AddElement(s2); DrawQueue.AddElement(current + 0); DrawQueue.AddElement(s1); DrawQueue.AddElement(s2); DrawQueue.AddElement(current + 1); s1 = current; s2 = current + 1; current += 2; } DrawQueue.AddElement(s1); DrawQueue.AddElement(s2); DrawQueue.AddElement(10); DrawQueue.AddElement(s1); DrawQueue.AddElement(s2); DrawQueue.AddElement(9); // Draw NW arc center = new QVec2(rectangle.Left + radius, rectangle.Top - radius); s1 = 8; s2 = 11; for (int i = 0; i < resolution - 1; i++) { float theta = (i + 1.0f) / (resolution + 1); float xoff = -MathF.Sin(theta); float yoff = MathF.Cos(theta); v.Position = center + radius * new QVec2(xoff, yoff); DrawQueue.AddVertex(v); v.Position = center + innerRadius * new QVec2(xoff, yoff); DrawQueue.AddVertex(v); DrawQueue.AddElement(s1); DrawQueue.AddElement(s2); DrawQueue.AddElement(current + 0); DrawQueue.AddElement(s1); DrawQueue.AddElement(s2); DrawQueue.AddElement(current + 1); s1 = current; s2 = current + 1; current += 2; } DrawQueue.AddElement(s1); DrawQueue.AddElement(s2); DrawQueue.AddElement(15); DrawQueue.AddElement(s1); DrawQueue.AddElement(s2); DrawQueue.AddElement(14); // Draw SW arc center = new QVec2(rectangle.Left + radius, rectangle.Bottom + radius); s1 = 13; s2 = 12; for (int i = 0; i < resolution - 1; i++) { float theta = (i + 1.0f) / (resolution + 1); float xoff = -MathF.Cos(theta); float yoff = -MathF.Sin(theta); v.Position = center + radius * new QVec2(xoff, yoff); DrawQueue.AddVertex(v); v.Position = center + innerRadius * new QVec2(xoff, yoff); DrawQueue.AddVertex(v); DrawQueue.AddElement(s1); DrawQueue.AddElement(s2); DrawQueue.AddElement(current + 0); DrawQueue.AddElement(s1); DrawQueue.AddElement(s2); DrawQueue.AddElement(current + 1); s1 = current; s2 = current + 1; current += 2; } DrawQueue.AddElement(s1); DrawQueue.AddElement(s2); DrawQueue.AddElement(0); DrawQueue.AddElement(s1); DrawQueue.AddElement(s2); DrawQueue.AddElement(3); // Draw SW arc center = new QVec2(rectangle.Right - radius, rectangle.Bottom + radius); s1 = 2; s2 = 1; for (int i = 0; i < resolution - 1; i++) { float theta = (i + 1.0f) / (resolution + 1); float xoff = MathF.Sin(theta); float yoff = -MathF.Cos(theta); v.Position = center + radius * new QVec2(xoff, yoff); DrawQueue.AddVertex(v); v.Position = center + innerRadius * new QVec2(xoff, yoff); DrawQueue.AddVertex(v); DrawQueue.AddElement(s1); DrawQueue.AddElement(s2); DrawQueue.AddElement(current + 0); DrawQueue.AddElement(s1); DrawQueue.AddElement(s2); DrawQueue.AddElement(current + 1); s1 = current; s2 = current + 1; current += 2; } DrawQueue.AddElement(s1); DrawQueue.AddElement(s2); DrawQueue.AddElement(5); DrawQueue.AddElement(s1); DrawQueue.AddElement(s2); DrawQueue.AddElement(4); } private struct LineInfo { public int BaseOffset { get; } public int StartPositive { get; } public int StartNegative { get; } public int EndPositive { get; } public int EndNegative { get; } public LineInfo(int baseOffset, int startPositive, int startNegative, int endPositive, int endNegative) { BaseOffset = baseOffset; StartPositive = startPositive; StartNegative = startNegative; EndPositive = endPositive; EndNegative = endNegative; } } } }