Dashboard/Quik/VertexGenerator/QuikVertexGenerator.cs
H. Utku Maden 9339295378 Push all uncommitted changes.
I have had a long break from this project due to other higher priority
things going on in my life. Big changes inbound.
2023-05-13 16:17:57 +03:00

1574 lines
62 KiB
C#

using System;
using System.Collections.Generic;
using Quik.Typography;
namespace Quik.VertexGenerator
{
/// <summary>
/// Generates vertices from draw commands for GPU APIs like OpenGL.
/// </summary>
public class QuikVertexGenerator
{
// There is a very specific reason I am not using lists like a regular
// person would use. It has to do with the fact that there is no way
// to access the internal pointer of a System.Collections.Generic.List<>
// in older versions of .NET. Avoiding a copy of an entire vertex buffer
// would be very much appreciated by many devs. So please don't be
// "smart" around this code.
// - mixed.
/// <summary>
/// Controls the buffer granularity.
/// </summary>
private const int BufferGranularity = 4096;
/// <summary>
/// List of vertices.
/// </summary>
private QuikVertex[] _vertexBuffer = new QuikVertex[BufferGranularity];
/// <summary>
/// Pointer into the vertex buffer.
/// </summary>
private int _vertexBufferPointer = 0;
private float _vertexUsage = 0;
/// <summary>
/// List of element indices.
/// </summary>
private short[] _elementBuffer = new short[BufferGranularity];
/// <summary>
/// Pointer into the element buffer.
/// </summary>
private int _elementBufferPointer = 0;
private float _elementUsage;
private long _bufferUsageCounter;
/// <summary>
/// Moving average of the vertex count.
/// </summary>
public float VertexUsage => _vertexUsage;
/// <summary>
/// Moving average of the element count.
/// </summary>
public float ElementUsage => _elementUsage;
/// <summary>
/// Get a reference to the vertex buffer.
/// </summary>
public QuikVertex[] VertexBuffer => _vertexBuffer;
/// <summary>
/// Number of vertices in the vertex buffer.
/// </summary>
public int VertexCount => _vertexBufferPointer;
/// <summary>
/// Get a reference to the element buffer.
/// </summary>
public short[] ElementBuffer => _elementBuffer;
/// <summary>
/// Number of elements in the element buffer.
/// </summary>
public int ElementCount => _elementBufferPointer;
public List<QuikDrawCall> DrawCalls { get; } = new List<QuikDrawCall>();
public float CurveGranularity { get; set; } = 1f;
public QuikContext Context { get; }
public QuikVertexGenerator(QuikContext context)
{
Context = context;
}
/// <summary>
/// Expands the vertex buffer by the buffer granularity constant.
/// </summary>
private void ExpandVertexBuffer()
{
Array.Resize(ref _vertexBuffer, _vertexBuffer.Length + BufferGranularity);
}
/// <summary>
/// Expands the element buffer by the buffer granularity constant.
/// </summary>
private void ExpandElementBuffer()
{
Array.Resize(ref _elementBuffer, _elementBuffer.Length + BufferGranularity);
}
/// <summary>
/// Add vertices to the list.
/// </summary>
/// <param name="vertices">The list of vertices to add.</param>
private void AddVertex(params QuikVertex[] vertices)
{
int requiredCapacity = _vertexBufferPointer + vertices.Length;
while (requiredCapacity > _vertexBuffer.Length)
{
ExpandVertexBuffer();
}
Array.Copy(vertices, 0, _vertexBuffer, _vertexBufferPointer, vertices.Length);
_vertexBufferPointer += vertices.Length;
}
/// <summary>
/// Add element indices to the list.
/// </summary>
/// <param name="indices">The list of indices to add.</param>
private void AddElement(params short[] indices)
{
int requiredCapacity = _elementBufferPointer + indices.Length;
while (requiredCapacity > _elementBuffer.Length)
{
ExpandElementBuffer();
}
Array.Copy(indices, 0, _elementBuffer, _elementBufferPointer, indices.Length);
_elementBufferPointer += indices.Length;
}
private void MovingAverage(ref float average, long sampleCounter, int newSample)
{
// Thanks to stackoverflow for a neat formula.
// https://stackoverflow.com/questions/12636613/how-to-calculate-moving-average-without-keeping-the-count-and-data-total
const float order = 4;
average = average + (newSample - average) / Math.Min(sampleCounter + 1, order);
}
private bool _renderStencilMask = false;
private QuikRectangle _bounds = new QuikRectangle(
float.PositiveInfinity, float.PositiveInfinity,
float.NegativeInfinity, float.NegativeInfinity);
/// <summary>
/// Clear the drawing buffers.
/// </summary>
public void Clear()
{
int newVertexSize;
int newElementSize;
_bufferUsageCounter++;
MovingAverage(ref _vertexUsage, _bufferUsageCounter, _vertexBufferPointer);
MovingAverage(ref _elementUsage, _bufferUsageCounter, _elementBufferPointer);
newVertexSize = (int)(Math.Ceiling(_vertexUsage / BufferGranularity) * BufferGranularity);
newElementSize = (int)(Math.Ceiling(_elementUsage / BufferGranularity) * BufferGranularity);
Array.Resize(ref _vertexBuffer, newVertexSize);
Array.Resize(ref _elementBuffer, newElementSize);
_vertexBufferPointer = 0;
_elementBufferPointer = 0;
DrawCalls.Clear();
}
public QuikDrawCall CallTemplate;
public event VertexGeneratorCommandHandler HandleCommand;
protected virtual bool OnHandleCommand(QuikVertexGenerator generator, QuikCommand command)
{
bool anyCalls = false;
HandleCommand?.Invoke(generator, command, ref anyCalls);
return anyCalls;
}
public void ConsumeCommand(QuikCommand command)
{
CallTemplate.Target = _renderStencilMask ? QuikRenderTarget.Stencil : QuikRenderTarget.Color;
switch (command.Type)
{
case QuikCommandType.Mask:
CallTemplate.Bounds = ((QuikCommandMask)command).Bounds;
break;
case QuikCommandType.StencilMaskClear:
CallTemplate.ClearStencil = true;
break;
case QuikCommandType.StencilMaskBegin:
_renderStencilMask = true;
CallTemplate.Target = QuikRenderTarget.Stencil;
break;
case QuikCommandType.StencilMaskEnd:
_renderStencilMask = false;
CallTemplate.Target = QuikRenderTarget.Color;
break;
case QuikCommandType.Line:
RenderLine(command as QuikCommandLine);
goto exit_with_call;
case QuikCommandType.Lines:
RenderLine(command as QuikCommandLines);
goto exit_with_call;
case QuikCommandType.Bezier:
RenderBezier(command as QuikCommandBezier);
goto exit_with_call;
case QuikCommandType.Rectangle:
RenderRectangles(command as QuikCommandRectangle);
goto exit_with_call;
case QuikCommandType.Rectangles:
RenderRectangles(command as QuikCommandRectangles);
goto exit_with_call;
case QuikCommandType.PutChar:
RenderCharacter(command as QuikCommandPutChar);
goto exit_with_call;
case QuikCommandType.PutText:
RenderTextPut(command as QuikCommandPutText);
goto exit_with_call;
case QuikCommandType.EmitTypeset:
RenderTextTypeset(command as QuikCommandEmitText);
goto exit_with_call;
default:
{
if (OnHandleCommand(this, command))
goto exit_with_call;
break;
}
}
return;
exit_with_call:
CallTemplate.ClearStencil = false;
}
#region Lines & Beziers
/// <summary>
/// Generates a basic line segment.
/// </summary>
/// <param name="baseVertex">The vertex base to modify when generating vertices.</param>
/// <param name="line">The line segment.</param>
/// <param name="width">The width of the line.</param>
/// <param name="lineStartIndex">Start index of the generated line.</param>
/// <param name="normal">The line normal.</param>
private void GenerateLineSegment(
QuikVertex baseVertex,
QuikLine line,
float width,
out short lineStartIndex,
out QuikVec2 normal)
{
QuikVec2 tangent = (line.End - line.Start).Normalize();
normal = new QuikVec2() {X = -tangent.Y, Y = tangent.X};
lineStartIndex = (short)_vertexBufferPointer;
QuikVertex startA = baseVertex, startB = baseVertex;
QuikVertex endA = baseVertex, endB = baseVertex;
startA.Position = line.Start + width / 2 * normal;
startB.Position = line.Start - width / 2 * normal;
endA.Position = line.End + width / 2 * normal;
endB.Position = line.End - width / 2 * normal;
// Add the major line vertices.
AddVertex(startA, startB, endA, endB);
// Add the line indices.
AddElement(
(short) (lineStartIndex + 1),
(short) (lineStartIndex + 2),
(short) (lineStartIndex + 0),
(short) (lineStartIndex + 1),
(short) (lineStartIndex + 3),
(short) (lineStartIndex + 2)
);
}
/// <summary>
/// Gets the rounding resolution for a line segment.
/// </summary>
/// <param name="radius">The width of the line.</param>
/// <param name="arc">The angle of the cap or joint arc.</param>
/// <returns>The rounding resolution.</returns>
private int GetRoundingResolution(float radius, float arc)
{
return (int) Math.Ceiling(arc * radius * CurveGranularity);
}
/// <summary>
/// Generate a round start cap on a line.
/// </summary>
/// <param name="baseVertex">The base vertex to modify for generation.</param>
/// <param name="start">The start point.</param>
/// <param name="normal">The line normal.</param>
/// <param name="width">The width of line to generate.</param>
/// <param name="resolution">End cap resolution.</param>
/// <param name="lineStartPositiveIndex">The positive vertex index of the line start.</param>
/// <param name="lineStartNegativeIndex">The negative vertex index of the line start.</param>
public void GenerateStartCap(
QuikVertex baseVertex,
QuikVec2 start,
QuikVec2 normal,
float width,
int resolution,
short lineStartPositiveIndex,
short lineStartNegativeIndex
)
{
QuikVertex circlePoint = baseVertex;
short lastIndex = lineStartPositiveIndex;
for (int i = 0; i < resolution; i++)
{
float angle = (float) (i + 1) / (resolution + 1) * MathF.PI;
float cosT = MathF.Cos(angle);
float sinT = MathF.Sin(angle);
QuikVec2 displacement = new QuikVec2()
{
X = normal.X * cosT - normal.Y * sinT,
Y = normal.X * sinT + normal.Y * cosT
} * (width / 2);
circlePoint.Position = start + displacement;
short segmentIndex = (short)_vertexBufferPointer;
AddVertex(circlePoint);
AddElement(
lineStartNegativeIndex,
lastIndex,
segmentIndex);
lastIndex = segmentIndex;
}
}
/// <summary>
/// Generate a round line end cap.
/// </summary>
/// <param name="baseVertex">Base vertex to modify for generation.</param>
/// <param name="end">The line end.</param>
/// <param name="normal">The line normal.</param>
/// <param name="width">The line width.</param>
/// <param name="resolution">Cap generation resolution.</param>
/// <param name="lineEndPositiveIndex">Vertex index of the positive line end.</param>
/// <param name="lineEndNegativeIndex">Vertex index of the negative line end.</param>
public void GenerateEndCap(
QuikVertex baseVertex,
QuikVec2 end,
QuikVec2 normal,
float width,
int resolution,
short lineEndPositiveIndex,
short lineEndNegativeIndex
)
{
QuikVertex circlePoint = baseVertex;
short lastIndex = lineEndPositiveIndex;
for (int i = 0; i < resolution; i++)
{
float angle = -(float) (i + 1) / (resolution + 1) * MathF.PI;
float cosT = MathF.Cos(angle);
float sinT = MathF.Sin(angle);
QuikVec2 displacement = new QuikVec2()
{
X = normal.X * cosT - normal.Y * sinT,
Y = normal.X * sinT + normal.Y * cosT
} * (width / 2);
circlePoint.Position = end + displacement;
AddVertex(circlePoint);
AddElement(
lineEndNegativeIndex,
lastIndex,
(short) (_vertexBufferPointer - 1));
lastIndex = (short) (_vertexBufferPointer - 1);
}
}
/// <summary>
/// Generate a joint on the line negative edge.
/// </summary>
/// <param name="baseVertex">Base vertex to modify for generation.</param>
/// <param name="focus">The focus of the joint.</param>
/// <param name="width">With of the lines.</param>
/// <param name="prevNegativeLineIndex">Index of the negative end vertex.</param>
/// <param name="nextNegativeLineIndex">Index of the negative start vertex.</param>
/// <param name="resolution">Joint resolution.</param>
/// <param name="arc">Arc length of the joint.</param>
/// <param name="prevNormal">Normal of the previous line.</param>
private void GenerateNegativeJoint(
QuikVertex baseVertex,
QuikVec2 focus,
float width,
short prevNegativeLineIndex,
short nextNegativeLineIndex,
int resolution,
float arc,
QuikVec2 prevNormal)
{
QuikVertex focusVertex = baseVertex;
short focusIndex = (short) _vertexBufferPointer;
short lastIndex = prevNegativeLineIndex;
focusVertex.Position = focus;
AddVertex(focusVertex);
for (int i = 0; i < resolution; i++)
{
float angle = (float) (i + 1) / (resolution + 1) * arc;
float cosT = MathF.Cos(angle);
float sinT = MathF.Sin(angle);
QuikVec2 displacement = new QuikVec2()
{
X = -prevNormal.X * cosT + prevNormal.Y * sinT,
Y = -prevNormal.X * sinT - prevNormal.Y * cosT
} * (width / 2);
QuikVertex segmentVertex = focusVertex;
segmentVertex.Position += displacement;
short segmentIndex = (short) _vertexBufferPointer;
AddVertex(segmentVertex);
AddElement(lastIndex, segmentIndex, focusIndex);
lastIndex = segmentIndex;
}
// Add final triangle.
AddElement(lastIndex, nextNegativeLineIndex, focusIndex);
}
/// <summary>
/// Generate a joint on the line negative edge.
/// </summary>
/// <param name="baseVertex">Base vertex to modify for generation.</param>
/// <param name="focus">The focus of the joint.</param>
/// <param name="width">With of the lines.</param>
/// <param name="prevPositiveLineIndex">Index of the positive end vertex.</param>
/// <param name="nextPositiveLineIndex">Index of the positive start vertex.</param>
/// <param name="resolution">Joint resolution.</param>
/// <param name="arc">Arc length of the joint.</param>
/// <param name="nextNormal">Normal of the next line.</param>
private void GeneratePositiveJoint(
QuikVertex baseVertex,
QuikVec2 focus,
float width,
short prevPositiveLineIndex,
short nextPositiveLineIndex,
int resolution,
float arc,
QuikVec2 nextNormal)
{
QuikVertex focusVertex = baseVertex;
short focusIndex = (short) _vertexBufferPointer;
short lastIndex = nextPositiveLineIndex;
focusVertex.Position = focus;
AddVertex(focusVertex);
for (int i = 0; i < resolution; i++)
{
float angle = (float) (i + 1) / (resolution + 1) * arc;
float cosT = MathF.Cos(angle);
float sinT = MathF.Sin(angle);
QuikVec2 displacement = new QuikVec2()
{
X = nextNormal.X * cosT - nextNormal.Y * sinT,
Y = nextNormal.X * sinT + nextNormal.Y * cosT
} * (width / 2);
QuikVertex segmentVertex = focusVertex;
segmentVertex.Position += displacement;
short segmentIndex = (short) _vertexBufferPointer;
AddVertex(segmentVertex);
AddElement(lastIndex, segmentIndex, focusIndex);
lastIndex = segmentIndex;
}
// Add final triangle.
AddElement(lastIndex, prevPositiveLineIndex, focusIndex);
}
/// <summary>
/// Generate a joint.
/// </summary>
/// <param name="baseVertex">Base vertex to modify.</param>
/// <param name="focus">Focus of the joint.</param>
/// <param name="prevTangent">Tangent of the previous line segment.</param>
/// <param name="nextTangent">Tangent of the next line segment.</param>
/// <param name="width">Width of the lines.</param>
/// <param name="prevPositiveEndIndex">Vertex index of the positive end.</param>
/// <param name="prevNegativeEndIndex">Vertex index of the negative end.</param>
/// <param name="nextPositiveStartIndex">Vertex index of the positive start.</param>
/// <param name="nextNegativeStartIndex">Vertex index of the negative start.</param>
public void GenerateJoint(
QuikVertex baseVertex,
QuikVec2 focus,
QuikVec2 prevTangent,
QuikVec2 nextTangent,
float width,
short prevPositiveEndIndex,
short prevNegativeEndIndex,
short nextPositiveStartIndex,
short nextNegativeStartIndex
)
{
QuikVec2 prevNormal, nextNormal;
QuikVec2 averageTangent;
prevNormal = new QuikVec2() { X = -prevTangent.Y, Y = prevTangent.X };
nextNormal = new QuikVec2() { X = -nextTangent.Y, Y = nextTangent.X };
averageTangent = 0.5f * (prevTangent + nextTangent);
// Figure out which side needs the joint.
QuikVec2 positiveEdge = ((focus + nextNormal) - (focus + prevNormal)).Normalize();
QuikVec2 negativeEdge = ((focus - nextNormal) - (focus - prevNormal)).Normalize();
float positiveDot, negativeDot;
positiveDot = QuikVec2.Dot(averageTangent, positiveEdge);
negativeDot = QuikVec2.Dot(averageTangent, negativeEdge);
float arc = MathF.Acos(QuikVec2.Dot(prevNormal, nextNormal));
int resolution = GetRoundingResolution(width, arc);
if (positiveDot < negativeDot)
{
// Negative edge rounding.
GenerateNegativeJoint(baseVertex, focus, width, prevNegativeEndIndex, nextNegativeStartIndex, resolution, arc, prevNormal);
}
else if (negativeDot < positiveDot)
{
// Positive edge rounding.
GeneratePositiveJoint(baseVertex, focus, width, prevPositiveEndIndex, nextPositiveStartIndex, resolution, arc, nextNormal);
}
else
{
// There is a cusp. Generate an end cap.
GenerateEndCap(baseVertex, focus, prevNormal.Normalize(), width, GetRoundingResolution(width, MathF.PI), prevPositiveEndIndex, prevNegativeEndIndex);
}
}
/// <summary>
/// Renders a line.
/// </summary>
/// <param name="call">The draw call to generate.</param>
/// <param name="line">The line to draw.</param>
private void RenderLine(QuikCommandLine line)
{
// Skip over stipple patterns for now.
QuikStrokeStyle style = line.Style ?? Context.DefaultStroke;
QuikVertex baseVertex = new QuikVertex() { Color = style.Color };
GenerateLineSegment(baseVertex, line.Line, style.Width, out short startOffset, out QuikVec2 normal);
// Now calculate the end caps.
int endCapResolution = GetRoundingResolution(style.Width, MathF.PI);
// Construct start cap.
GenerateStartCap(baseVertex, line.Line.Start, normal, style.Width, endCapResolution, startOffset, (short)
(startOffset + 1));
// Construct end cap.
GenerateEndCap(baseVertex, line.Line.End, normal, style.Width, endCapResolution, (short) (startOffset + 2),(short)
(startOffset + 3));
QuikDrawCall call = CallTemplate;
call.Offset = (short) (startOffset * 2);
call.Count = (short) (_elementBufferPointer - startOffset);
DrawCalls.Add(call);
}
/// <summary>
/// Render lines.
/// </summary>
/// <param name="call">The draw call to generate.</param>
/// <param name="lines">The lines command.</param>
private void RenderLine(QuikCommandLines lines)
{
// Skip over stipple patterns for now.
QuikStrokeStyle style = lines.Style ?? Context.DefaultStroke;
QuikVertex baseVertex = new QuikVertex() { Color = style.Color };
bool isStart;
bool isEnd;
short startOffset = (short)_elementBufferPointer;
short lastStartIndex = 0;
short lineStartIndex;
QuikVec2 normal;
int resolution = GetRoundingResolution(style.Width, MathF.PI);
for (int i = 0; i < lines.Lines.Length; i++)
{
QuikLine line = lines.Lines[i];
isStart = i == 0 || line.Start != lines.Lines[i - 1].End;
isEnd = i == lines.Lines.Length - 1 || line.End != lines.Lines[i + 1].Start;
GenerateLineSegment(baseVertex, line, style.Width, out lineStartIndex, out normal);
if (isStart)
{
GenerateStartCap(baseVertex, line.Start, normal, style.Width, resolution, lineStartIndex,
(short)(lineStartIndex + 1));
}
else
{
QuikLine prev = lines.Lines[i - 1];
QuikVec2 prevTangent = (prev.End - prev.Start).Normalize();
QuikVec2 nextTangent = (line.End - line.Start).Normalize();
GenerateJoint(
baseVertex,
line.Start,
prevTangent,
nextTangent,
style.Width,
(short) (lastStartIndex + 2),
(short) (lastStartIndex + 3),
lineStartIndex,
(short)(lineStartIndex + 1));
}
if (isEnd)
{
GenerateEndCap(baseVertex, line.End, normal, style.Width, resolution, (short)(lineStartIndex + 2), (short)(lineStartIndex + 3));
}
lastStartIndex = lineStartIndex;
}
QuikDrawCall call = CallTemplate;
call.Offset = (short) (startOffset * 2);
call.Count = (short) (_elementBufferPointer - startOffset);
DrawCalls.Add(call);
}
private void RenderBezier(QuikCommandBezier bezier)
{
QuikStrokeStyle style = bezier.Style ?? Context.DefaultStroke;
QuikVertex baseVertex = new QuikVertex() { Color = style.Color };
bool isStart;
bool isEnd;
short startOffset = (short)_elementBufferPointer;
int capResolution = GetRoundingResolution(style.Width, MathF.PI);
short lastEndPositive = 0;
short lastEndNegative = 0;
for (int i = 0; i < bezier.Segments.Length; i++)
{
QuikBezier segment = bezier.Segments[i];
isStart = i == 0 || segment.Start != bezier.Segments[i - 1].End;
isEnd = i == bezier.Segments.Length - 1 || segment.End != bezier.Segments[i + 1].Start;
GenerateBezierSegment(
baseVertex,
segment,
style.Width,
out short startPositive,
out short startNegative,
out short endPositive,
out short endNegative,
out QuikVec2 startNormal,
out QuikVec2 endNormal);
if (isStart)
{
GenerateStartCap(baseVertex, segment.Start, startNormal, style.Width, capResolution,
startPositive, startNegative);
}
else
{
GenerateJoint(
baseVertex,
segment.Start,
bezier.Segments[i-1].GetBezierTangent(1),
segment.GetBezierTangent(0),
style.Width,
lastEndPositive,
lastEndNegative,
startPositive,
startNegative
);
}
if (isEnd)
{
GenerateEndCap(
baseVertex,
segment.End,
endNormal,
style.Width,
capResolution,
endPositive,
endNegative
);
}
lastEndPositive = endPositive;
lastEndNegative = endNegative;
}
QuikDrawCall call = CallTemplate;
call.Offset = (short) (startOffset * 2);
call.Count = (short) (_elementBufferPointer - startOffset);
DrawCalls.Add(call);
}
private void GenerateBezierSegment(
QuikVertex baseVertex,
QuikBezier bezier,
float width,
out short startPositiveIndex,
out short startNegativeIndex,
out short endPositiveIndex,
out short endNegativeIndex,
out QuikVec2 startNormal,
out QuikVec2 endNormal)
{
QuikVec2 startTangent = bezier.GetBezierTangent(0);
QuikVec2 endTangent = bezier.GetBezierTangent(1);
startNormal = new QuikVec2(-startTangent.Y, startTangent.X).Normalize();
endNormal = new QuikVec2(-endTangent.Y, endTangent.X).Normalize();
int resolution = GetRoundingResolution(width, bezier.RasterizationArc);
startPositiveIndex = (short)_vertexBufferPointer;
startNegativeIndex = (short) (startPositiveIndex + 1);
QuikVertex startPositive = baseVertex;
QuikVertex startNegative = baseVertex;
startPositive.Position = bezier.Start + 0.5f * width * startNormal;
startNegative.Position = bezier.Start - 0.5f * width * startNormal;
AddVertex(startPositive, startNegative);
for (int i = 0; i < resolution; i++)
{
float t = (float) (i + 1) / resolution;
QuikVec2 at = bezier.GetBezierTangent(t).Normalize();
QuikVec2 a = bezier.GetBezierPoint(t);
QuikVec2 an = 0.5f * width * new QuikVec2(-at.Y, at.X);
short index = (short) _vertexBufferPointer;
QuikVertex apv = baseVertex, anv = baseVertex;
apv.Position = a + an;
anv.Position = a - an;
AddVertex(apv, anv);
AddElement(
(short)(index - 2),
(short)(index - 1),
(short)(index + 0),
(short)(index - 1),
(short)(index + 1),
(short)(index + 0));
}
endNegativeIndex = (short) (_vertexBufferPointer - 1);
endPositiveIndex = (short) (_vertexBufferPointer - 2);
}
#endregion
#region Rectangles
private void RenderRectangles(QuikCommandRectangle rectangle)
{
QuikStrokeStyle stroke = rectangle.StrokeStyle ?? Context.DefaultStroke;
QuikFillStyle fill = rectangle.FillStyle ?? Context.DefaultFill;
short start = (short) _elementBufferPointer;
GenerateRectangle(rectangle.Rectangle, stroke, fill, rectangle.CornerRadius);
QuikDrawCall call = CallTemplate;
call.Offset = (short) (start * 2);
call.Count = (short) (_elementBufferPointer - start);
DrawCalls.Add(call);
}
private void GenerateRectangle(QuikRectangle rectangle, QuikStrokeStyle stroke, QuikFillStyle fill, float cornerRadius)
{
float semiStroke = 0.5f * stroke.Width;
if (cornerRadius == 0)
{
// If there is no stroke radius, draw a simpler rectangle.
if (stroke.Width == 0)
{
// If there is no border, just 4 points is enough.
GenerateRectangleSimple(rectangle, fill);
}
else
{
// If there is a border, take it into account.
QuikRectangle innerRectangle = rectangle;
innerRectangle.Left += semiStroke;
innerRectangle.Top -= semiStroke;
innerRectangle.Right -= semiStroke;
innerRectangle.Bottom += semiStroke;
GenerateRectangleSimple(innerRectangle, fill);
GenerateRectangleBorderSimple(rectangle, stroke);
}
}
else
{
float innerRadius = cornerRadius - semiStroke;
if (innerRadius <= 0)
{
QuikRectangle innerRectangle = new QuikRectangle(
rectangle.Right - semiStroke,
rectangle.Top - semiStroke,
rectangle.Left + semiStroke,
rectangle.Bottom + semiStroke
);
GenerateRectangleSimple(innerRectangle, fill);
GenerateRectangleBorderNarrow(rectangle, stroke, cornerRadius);
}
else
{
// Generate the inner rectangle.
QuikRectangle innerRectangle = new QuikRectangle(
rectangle.Right - semiStroke * 0.95f,
rectangle.Top - semiStroke * 0.95f,
rectangle.Left + semiStroke * 0.95f,
rectangle.Bottom + semiStroke * 0.95f
);
GenerateRectangleRound(innerRectangle, fill, innerRadius);
if (stroke.Width > 0)
{
GenerateRectangleBorderWide(rectangle, stroke, cornerRadius);
}
}
}
}
private void GenerateRectangleBorderNarrow(
QuikRectangle rectangle,
QuikStrokeStyle stroke,
float cornerRadius)
{
float semiStroke = 0.5f * stroke.Width;
float inset = cornerRadius - semiStroke;
short start = (short) _vertexBufferPointer;
QuikVertex baseVertex = new QuikVertex() {Color = stroke.Color};
QuikVertex a = baseVertex, b = baseVertex, c = baseVertex, d = baseVertex,
e = baseVertex, f = baseVertex, g = baseVertex, h = baseVertex,
i = baseVertex, j = baseVertex, k = baseVertex, l = baseVertex,
q1 = baseVertex, q2 = baseVertex, q3 = baseVertex, q4 = baseVertex;
a.Position = new QuikVec2(rectangle.Left + semiStroke, rectangle.Bottom + semiStroke);
b.Position = new QuikVec2(rectangle.Right - semiStroke, rectangle.Bottom + semiStroke);
c.Position = new QuikVec2(rectangle.Right - semiStroke, rectangle.Top - semiStroke);
d.Position = new QuikVec2(rectangle.Left + semiStroke, rectangle.Top - semiStroke);
q1.Position = new QuikVec2(rectangle.Left + inset, rectangle.Bottom + inset);
q2.Position = new QuikVec2(rectangle.Right - inset, rectangle.Bottom + inset);
q3.Position = new QuikVec2(rectangle.Right - inset, rectangle.Top - inset);
q4.Position = new QuikVec2(rectangle.Left + inset, rectangle.Top - inset);
e.Position = new QuikVec2(q1.Position.X, rectangle.Bottom - semiStroke);
f.Position = new QuikVec2(q2.Position.X, rectangle.Bottom - semiStroke);
g.Position = new QuikVec2(rectangle.Right + semiStroke, q2.Position.Y);
h.Position = new QuikVec2(rectangle.Right + semiStroke, q3.Position.Y);
i.Position = new QuikVec2(q3.Position.X, rectangle.Top + semiStroke);
j.Position = new QuikVec2(q4.Position.X, rectangle.Top + semiStroke);
k.Position = new QuikVec2(rectangle.Left - semiStroke, q4.Position.Y);
l.Position = new QuikVec2(rectangle.Left - semiStroke, q1.Position.Y);
AddVertex(a, b, c, d, e, f, g, h, i, j, k, l, q1, q2, q3, q4);
AddElement(
(short) (start + 4), (short) (start + 5), (short) (start + 13),
(short) (start + 4), (short) (start + 13), (short) (start + 12),
(short) (start + 12), (short) (start + 13), (short) (start + 1),
(short) (start + 12), (short) (start + 1), (short) (start + 0),
(short) (start + 13), (short) (start + 6), (short) (start + 7),
(short) (start + 13), (short) (start + 7), (short) (start + 14),
(short) (start + 13), (short) (start + 14), (short) (start + 2),
(short) (start + 13), (short) (start + 2), (short) (start + 1),
(short) (start + 3), (short) (start + 2), (short) (start + 14),
(short) (start + 3), (short) (start + 14), (short) (start + 15),
(short) (start + 15), (short) (start + 14), (short) (start + 8),
(short) (start + 15), (short) (start + 8), (short) (start + 9),
(short) (start + 0), (short) (start + 3), (short) (start + 15),
(short) (start + 0), (short) (start + 15), (short) (start + 12),
(short) (start + 12), (short) (start + 15), (short) (start + 10),
(short) (start + 12), (short) (start + 10), (short) (start + 11)
);
int resolution = GetRoundingResolution(cornerRadius, 0.5f * MathF.PI);
short last, next;
last = (short) (start + 7);
for (int idx = 0; idx < resolution; idx++)
{
float angle = 0.5f * MathF.PI * (idx + 1) / (resolution + 1);
QuikVertex vertex = baseVertex;
vertex.Position = q3.Position + cornerRadius * new QuikVec2(MathF.Cos(angle), MathF.Sin(angle));
next = (short) _vertexBufferPointer;
AddVertex(vertex);
AddElement((short)(start + 14), last, next);
last = next;
}
AddElement((short)(start + 14), last, (short)(start + 8));
last = (short) (start + 9);
for (int idx = 0; idx < resolution; idx++)
{
float angle = 0.5f * MathF.PI * (idx + 1) / (resolution + 1);
QuikVertex vertex = baseVertex;
vertex.Position = q4.Position + cornerRadius * new QuikVec2(-MathF.Sin(angle), MathF.Cos(angle));
next = (short) _vertexBufferPointer;
AddVertex(vertex);
AddElement((short)(start + 15), last, next);
last = next;
}
AddElement((short)(start + 15), last, (short)(start + 10));
last = (short) (start + 11);
for (int idx = 0; idx < resolution; idx++)
{
float angle = 0.5f * MathF.PI * (idx + 1) / (resolution + 1);
QuikVertex vertex = baseVertex;
vertex.Position = q1.Position + cornerRadius * new QuikVec2(-MathF.Cos(angle), -MathF.Sin(angle));
next = (short) _vertexBufferPointer;
AddVertex(vertex);
AddElement((short)(start + 12), last, next);
last = next;
}
AddElement((short)(start + 12), last, (short)(start + 4));
last = (short) (start + 5);
for (int idx = 0; idx < resolution; idx++)
{
float angle = 0.5f * MathF.PI * (idx + 1) / (resolution + 1);
QuikVertex vertex = baseVertex;
vertex.Position = q2.Position + cornerRadius * new QuikVec2(MathF.Sin(angle), -MathF.Cos(angle));
next = (short) _vertexBufferPointer;
AddVertex(vertex);
AddElement((short)(start + 13), last, next);
last = next;
}
AddElement((short)(start + 13), last, (short)(start + 6));
}
private void GenerateRectangleBorderWide(QuikRectangle rectangle, QuikStrokeStyle stroke, float cornerRadius)
{
float semiStroke = 0.5f * stroke.Width;
float outerRadius = cornerRadius + semiStroke;
float innerRadius = cornerRadius - semiStroke;
short start = (short) _vertexBufferPointer;
// The corner foci.
QuikVec2 q1 = new QuikVec2(rectangle.Min.X + cornerRadius, rectangle.Min.Y + cornerRadius);
QuikVec2 q2 = new QuikVec2(rectangle.Max.X - cornerRadius, rectangle.Min.Y + cornerRadius);
QuikVec2 q3 = new QuikVec2(rectangle.Max.X - cornerRadius, rectangle.Max.Y - cornerRadius);
QuikVec2 q4 = new QuikVec2(rectangle.Min.X + cornerRadius, rectangle.Max.Y - cornerRadius);
QuikVertex baseVertex = new QuikVertex() {Color = stroke.Color};
QuikVertex a = baseVertex, b = baseVertex, c = baseVertex, d = baseVertex,
e = baseVertex, f = baseVertex, g = baseVertex, h = baseVertex,
i = baseVertex, j = baseVertex, k = baseVertex, l = baseVertex,
m = baseVertex, n = baseVertex, o = baseVertex, p = baseVertex;
a.Position = new QuikVec2(q1.X, q1.Y - innerRadius);
b.Position = new QuikVec2(q2.X, q2.Y - innerRadius);
c.Position = new QuikVec2(q2.X + innerRadius, q2.Y);
d.Position = new QuikVec2(q3.X + innerRadius, q3.Y);
e.Position = new QuikVec2(q3.X, q3.Y + innerRadius);
f.Position = new QuikVec2(q4.X, q4.Y + innerRadius);
g.Position = new QuikVec2(q4.X - innerRadius, q4.Y);
h.Position = new QuikVec2(q1.X - innerRadius, q1.Y);
i.Position = new QuikVec2(q1.X, rectangle.Bottom - semiStroke);
j.Position = new QuikVec2(q2.X, rectangle.Bottom - semiStroke);
k.Position = new QuikVec2(rectangle.Right + semiStroke, q2.Y);
l.Position = new QuikVec2(rectangle.Right + semiStroke, q3.Y);
m.Position = new QuikVec2(q3.X, rectangle.Top + semiStroke);
n.Position = new QuikVec2(q4.X, rectangle.Top + semiStroke);
o.Position = new QuikVec2(rectangle.Left - semiStroke, q4.Y);
p.Position = new QuikVec2(rectangle.Left - semiStroke, q1.Y);
AddVertex(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p);
AddElement(
(short)(start + 8), (short)(start + 1), (short)(start + 0),
(short)(start + 8), (short)(start + 9), (short)(start + 1),
(short)(start + 2), (short)(start + 11), (short)(start + 3),
(short)(start + 2), (short)(start + 10), (short)(start + 11),
(short)(start + 5), (short)(start + 12), (short)(start + 13),
(short)(start + 5), (short)(start + 4), (short)(start + 12),
(short)(start + 15), (short)(start + 6), (short)(start + 14),
(short)(start + 15), (short)(start + 7), (short)(start + 6)
);
int resolution = GetRoundingResolution(outerRadius, 0.5f * MathF.PI);
short last0, last1;
short next0, next1;
last0 = (short) (start + 3);
last1 = (short) (start + 11);
for (int idx = 0; idx < resolution; idx++)
{
float angle = 0.5f * MathF.PI * (idx + 1) / (resolution + 1);
QuikVec2 normal = new QuikVec2(MathF.Cos(angle), MathF.Sin(angle));
QuikVertex v0 = baseVertex, v1 = baseVertex;
v0.Position = q3 + innerRadius * normal;
v1.Position = q3 + outerRadius * normal;
next0 = (short) (_vertexBufferPointer + 0);
next1 = (short) (_vertexBufferPointer + 1);
AddVertex(v0, v1);
AddElement(
last0, next1, next0,
last0, last1, next1);
last0 = next0;
last1 = next1;
}
AddElement(
last0, (short)(start + 12), (short)(start + 4),
last0, last1, (short)(start + 12)
);
last0 = (short) (start + 5);
last1 = (short) (start + 13);
for (int idx = 0; idx < resolution; idx++)
{
float angle = 0.5f * MathF.PI * (idx + 1) / (resolution + 1);
QuikVec2 normal = new QuikVec2(-MathF.Sin(angle), MathF.Cos(angle));
QuikVertex v0 = baseVertex, v1 = baseVertex;
v0.Position = q4 + innerRadius * normal;
v1.Position = q4 + outerRadius * normal;
next0 = (short) (_vertexBufferPointer + 0);
next1 = (short) (_vertexBufferPointer + 1);
AddVertex(v0, v1);
AddElement(
last0, next1, next0,
last0, last1, next1);
last0 = next0;
last1 = next1;
}
AddElement(
last0, (short)(start + 14), (short)(start + 6),
last0, last1, (short)(start + 14)
);
last0 = (short) (start + 7);
last1 = (short) (start + 15);
for (int idx = 0; idx < resolution; idx++)
{
float angle = 0.5f * MathF.PI * (idx + 1) / (resolution + 1);
QuikVec2 normal = new QuikVec2(-MathF.Cos(angle), -MathF.Sin(angle));
QuikVertex v0 = baseVertex, v1 = baseVertex;
v0.Position = q1 + innerRadius * normal;
v1.Position = q1 + outerRadius * normal;
next0 = (short) (_vertexBufferPointer + 0);
next1 = (short) (_vertexBufferPointer + 1);
AddVertex(v0, v1);
AddElement(
last0, next1, next0,
last0, last1, next1);
last0 = next0;
last1 = next1;
}
AddElement(
last0, (short)(start + 8), (short)(start + 0),
last0, last1, (short)(start + 8)
);
last0 = (short) (start + 1);
last1 = (short) (start + 9);
for (int idx = 0; idx < resolution; idx++)
{
float angle = 0.5f * MathF.PI * (idx + 1) / (resolution + 1);
QuikVec2 normal = new QuikVec2(MathF.Sin(angle), -MathF.Cos(angle));
QuikVertex v0 = baseVertex, v1 = baseVertex;
v0.Position = q2 + innerRadius * normal;
v1.Position = q2 + outerRadius * normal;
next0 = (short) (_vertexBufferPointer + 0);
next1 = (short) (_vertexBufferPointer + 1);
AddVertex(v0, v1);
AddElement(
last0, next1, next0,
last0, last1, next1);
last0 = next0;
last1 = next1;
}
AddElement(
last0, (short)(start + 10), (short)(start + 2),
last0, last1, (short)(start + 10)
);
}
private void GenerateRectangleRound(
QuikRectangle rectangle,
QuikFillStyle fill,
float radius)
{
short baseElement = (short) _vertexBufferPointer;
QuikVertex baseVertex = new QuikVertex() {Color = fill.Color};
QuikVertex a = baseVertex,
b = baseVertex,
c = baseVertex,
d = baseVertex,
e = baseVertex,
f = baseVertex,
g = baseVertex,
h = baseVertex,
i = baseVertex,
j = baseVertex,
k = baseVertex,
l = baseVertex;
// Generate base 5 patches.
a.Position = new QuikVec2(
rectangle.Left + radius,
rectangle.Bottom + radius);
b.Position = new QuikVec2(
rectangle.Right - radius,
rectangle.Bottom + radius);
c.Position = new QuikVec2(
rectangle.Right - radius,
rectangle.Top - radius);
d.Position = new QuikVec2(
rectangle.Left + radius,
rectangle.Top - radius);
e.Position = new QuikVec2(
rectangle.Left + radius,
rectangle.Bottom);
f.Position = new QuikVec2(
rectangle.Right - radius,
rectangle.Bottom);
g.Position = new QuikVec2(
rectangle.Right,
rectangle.Bottom + radius);
h.Position = new QuikVec2(
rectangle.Right,
rectangle.Top - radius);
i.Position = new QuikVec2(
rectangle.Right - radius,
rectangle.Top);
j.Position = new QuikVec2(
rectangle.Left + radius,
rectangle.Top);
k.Position = new QuikVec2(
rectangle.Left,
rectangle.Top - radius);
l.Position = new QuikVec2(
rectangle.Left,
rectangle.Bottom + radius);
AddVertex(a, b, c, d, e, f, g, h, i, j, k, l);
AddElement(
(short) (baseElement + 0), (short) (baseElement + 1), (short) (baseElement + 2),
(short) (baseElement + 0), (short) (baseElement + 2), (short) (baseElement + 3),
(short) (baseElement + 4), (short) (baseElement + 5), (short) (baseElement + 1),
(short) (baseElement + 4), (short) (baseElement + 1), (short) (baseElement + 0),
(short) (baseElement + 1), (short) (baseElement + 6), (short) (baseElement + 7),
(short) (baseElement + 1), (short) (baseElement + 7), (short) (baseElement + 2),
(short) (baseElement + 3), (short) (baseElement + 2), (short) (baseElement + 8),
(short) (baseElement + 3), (short) (baseElement + 8), (short) (baseElement + 9),
(short) (baseElement + 11), (short) (baseElement + 0), (short) (baseElement + 3),
(short) (baseElement + 11), (short) (baseElement + 3), (short) (baseElement + 10));
// Now generate corner patches.
int resolution = GetRoundingResolution(radius, 0.5f * MathF.PI);
short focus, last, current;
focus = (short) (baseElement + 2);
last = (short) (baseElement + 7);
for (int idx = 0; idx < resolution; idx++)
{
QuikVertex vertex = baseVertex;
float angle = 0.5f * MathF.PI * (idx + 1) / (resolution + 1);
vertex.Position = c.Position + radius * new QuikVec2(MathF.Cos(angle), MathF.Sin(angle));
current = (short) _vertexBufferPointer;
AddVertex(vertex);
AddElement(focus, last, current);
last = current;
}
AddElement(focus, last, (short) (baseElement + 8));
focus = (short) (baseElement + 3);
last = (short) (baseElement + 9);
for (int idx = 0; idx < resolution; idx++)
{
QuikVertex vertex = baseVertex;
float angle = 0.5f * MathF.PI * (idx + 1) / (resolution + 1);
vertex.Position = d.Position + radius * new QuikVec2(-MathF.Sin(angle), MathF.Cos(angle));
current = (short) _vertexBufferPointer;
AddVertex(vertex);
AddElement(focus, last, current);
last = current;
}
AddElement(focus, last, (short) (baseElement + 10));
focus = (short) (baseElement + 0);
last = (short) (baseElement + 11);
for (int idx = 0; idx < resolution; idx++)
{
QuikVertex vertex = baseVertex;
float angle = 0.5f * MathF.PI * (idx + 1) / (resolution + 1);
vertex.Position = a.Position + radius * new QuikVec2(-MathF.Cos(angle), -MathF.Sin(angle));
current = (short) _vertexBufferPointer;
AddVertex(vertex);
AddElement(focus, last, current);
last = current;
}
AddElement(focus, last, (short) (baseElement + 4));
focus = (short) (baseElement + 1);
last = (short) (baseElement + 5);
for (int idx = 0; idx < resolution; idx++)
{
QuikVertex vertex = baseVertex;
float angle = 0.5f * MathF.PI * (idx + 1) / (resolution + 1);
vertex.Position = b.Position + radius * new QuikVec2(MathF.Sin(angle), -MathF.Cos(angle));
current = (short) _vertexBufferPointer;
AddVertex(vertex);
AddElement(focus, last, current);
last = current;
}
AddElement(focus, last, (short) (baseElement + 6));
}
private void GenerateRectangleBorderSimple(QuikRectangle rectangle, QuikStrokeStyle stroke)
{
QuikVertex baseStrokeVertex = new QuikVertex() {Color = stroke.Color};
float semiStroke = 0.5f * stroke.Width;
// AB
GenerateLineSegment(
baseStrokeVertex,
new QuikLine(
rectangle.Min.X + semiStroke,
rectangle.Min.Y,
rectangle.Max.X - semiStroke,
rectangle.Min.Y
),
stroke.Width,
out _,
out _);
// BC
GenerateLineSegment(
baseStrokeVertex,
new QuikLine(
rectangle.Max.X,
rectangle.Min.Y - semiStroke,
rectangle.Max.X,
rectangle.Max.Y + semiStroke
),
stroke.Width,
out _,
out _);
// CD
GenerateLineSegment(
baseStrokeVertex,
new QuikLine(
rectangle.Max.X - semiStroke,
rectangle.Max.Y,
rectangle.Min.X + semiStroke,
rectangle.Max.Y
),
stroke.Width,
out _,
out _);
// DA
GenerateLineSegment(
baseStrokeVertex,
new QuikLine(
rectangle.Min.X,
rectangle.Max.Y + semiStroke,
rectangle.Min.X,
rectangle.Min.Y - semiStroke
),
stroke.Width,
out _,
out _);
}
private void GenerateRectangleSimple(
QuikRectangle rectangle,
QuikFillStyle fill)
{
QuikVertex a, b, c, d;
a = b = c = d = new QuikVertex() {Color = fill.Color};
a.Position = rectangle.Min;
b.Position = new QuikVec2(rectangle.Right, rectangle.Bottom);
c.Position = rectangle.Max;
d.Position = new QuikVec2(rectangle.Left, rectangle.Top);
short idxA = (short) _vertexBufferPointer;
short idxB = (short) (idxA + 1);
short idxC = (short) (idxA + 2);
short idxD = (short) (idxA + 3);
AddVertex(a, b, c, d);
AddElement(
idxA,
idxB,
idxC,
idxA,
idxC,
idxD
);
}
private void RenderRectangles(QuikCommandRectangles rectangles)
{
QuikStrokeStyle stroke = rectangles.StrokeStyle ?? Context.DefaultStroke;
QuikFillStyle fill = rectangles.FillStyle ?? Context.DefaultFill;
short start = (short) _elementBufferPointer;
for (int i = 0; i < rectangles.Rectangles.Length; i++)
{
GenerateRectangle(rectangles.Rectangles[i], stroke, fill, rectangles.CornerRadius);
}
QuikDrawCall call = CallTemplate;
call.Offset = (short) (start * 2);
call.Count = (short) (_elementBufferPointer - start);
DrawCalls.Add(call);
}
#endregion
#region Text
private void RenderCharacter(QuikCommandPutChar chr)
{
Context.DefaultFont.GetCharacter(chr.Character, out IQuikTexture texture, out QuikGlyph metrics);
QuikVertex a, b, c, d;
a = b = c = d = new QuikVertex() {Color = new QuikColor(0xffffffff)};
a.Position = chr.Position + new QuikVec2(0, metrics.HorizontalBearing.Y - metrics.Size.Y);
a.TextureCoordinates = metrics.Location.Min;
b.Position = a.Position + new QuikVec2(metrics.Size.X, 0);
c.Position = a.Position + metrics.Size;
d.Position = a.Position + new QuikVec2(0, metrics.Size.Y);
b.TextureCoordinates = new QuikVec2(metrics.Location.Right, metrics.Location.Bottom);
c.TextureCoordinates = metrics.Location.Max;
d.TextureCoordinates = new QuikVec2(metrics.Location.Left, metrics.Location.Top);
short startVertex = (short)_vertexBufferPointer;
short startElement = (short) _elementBufferPointer;
AddVertex(a, b, c, d);
AddElement(startVertex, (short)(startVertex + 1), (short)(startVertex + 2), startVertex, (short)(startVertex + 2), (short)(startVertex + 3));
QuikDrawCall call = CallTemplate;
call.Texture = texture;
call.Offset = (short) (startElement * 2);
call.Count = 6;
DrawCalls.Add(call);
}
private void RenderTextPut(QuikCommandPutText text)
{
short startElement = (short)_elementBufferPointer;
QuikFont font = Context.DefaultFont;
QuikVertex vertex = new QuikVertex() {Color = new QuikColor(0x000000ff)};
QuikVec2 pointer = text.Position;
IQuikTexture texture = null;
for (int i = 0; i < text.Text.Length; i++)
{
int chr = text.Text[i];
QuikGlyph metrics;
IQuikTexture ntex;
font.GetCharacter(chr, out ntex, out metrics);
if (ntex != texture && texture != null)
{
QuikDrawCall call = CallTemplate;
call.Texture = texture;
call.Offset = (short) (startElement * 2);
call.Count = (short)(_elementBufferPointer - startElement);
DrawCalls.Add(call);
startElement = (short) _elementBufferPointer;
}
texture = ntex;
QuikVertex a, b, c, d;
a = b = c = d = vertex;
a.Position = pointer + new QuikVec2(0, metrics.HorizontalBearing.Y - metrics.Size.Y);
a.TextureCoordinates = metrics.Location.Min;
b.Position = a.Position + new QuikVec2(metrics.Size.X, 0);
c.Position = a.Position + metrics.Size;
d.Position = a.Position + new QuikVec2(0, metrics.Size.Y);
b.TextureCoordinates = new QuikVec2(metrics.Location.Right, metrics.Location.Bottom);
c.TextureCoordinates = metrics.Location.Max;
d.TextureCoordinates = new QuikVec2(metrics.Location.Left, metrics.Location.Top);
pointer.X += metrics.Advance.X;
short startVertex = (short)_vertexBufferPointer;
AddVertex(a, b, c, d);
AddElement(startVertex, (short)(startVertex + 1), (short)(startVertex + 2), startVertex, (short)(startVertex + 2), (short)(startVertex + 3));
}
{
QuikDrawCall call = CallTemplate;
call.Texture = texture;
call.Offset = (short) (startElement * 2);
call.Count = (short)(_elementBufferPointer - startElement);
DrawCalls.Add(call);
}
}
private void RenderTextTypeset(QuikCommandEmitText text)
{
short startElement = (short)_elementBufferPointer;
TypesetGroup group = text.Group;
QuikVertex vertex = new QuikVertex() { Color = new QuikColor(0x000000ff) };
IQuikTexture texture = null;
group.SortBy(TypesetGroup.SortByTexture);
foreach (TypesetCharacter chr in group)
{
if (texture == null)
{
texture = chr.Texture;
}
else if (texture != chr.Texture)
{
EmitCall();
startElement = (short)_elementBufferPointer;
texture = chr.Texture;
CallTemplate.ClearStencil = false;
}
QuikVertex a, b, c, d;
a = b = c = d = vertex;
a.Position = new QuikVec2(chr.Position.Left, chr.Position.Bottom) + text.Offset;
b.Position = new QuikVec2(chr.Position.Right, chr.Position.Bottom) + text.Offset;
c.Position = new QuikVec2(chr.Position.Right, chr.Position.Top) + text.Offset;
d.Position = new QuikVec2(chr.Position.Left, chr.Position.Top) + text.Offset;
a.TextureCoordinates = new QuikVec2(chr.UV.Left, chr.UV.Bottom);
b.TextureCoordinates = new QuikVec2(chr.UV.Right, chr.UV.Bottom);
c.TextureCoordinates = new QuikVec2(chr.UV.Right, chr.UV.Top);
d.TextureCoordinates = new QuikVec2(chr.UV.Left, chr.UV.Top);
short startVertex = (short)_vertexBufferPointer;
AddVertex(a, b, c, d);
AddElement(startVertex, (short)(startVertex + 1), (short)(startVertex + 2), startVertex, (short)(startVertex + 2), (short)(startVertex + 3));
}
EmitCall();
void EmitCall()
{
QuikDrawCall call = CallTemplate;
call.Texture = texture;
call.Offset = (short)(startElement * 2);
call.Count = (short)(_elementBufferPointer - startElement);
DrawCalls.Add(call);
}
}
#endregion
}
public delegate void VertexGeneratorCommandHandler(QuikVertexGenerator generator, QuikCommand command, ref bool anyCalls);
public enum QuikRenderTarget
{
Color,
Stencil
}
public struct QuikDrawCall
{
public QuikRenderTarget Target;
public short Offset;
public short Count;
public QuikRectangle Bounds;
public bool ClearStencil;
public IQuikTexture Texture;
}
}