H. Utku Maden
9339295378
I have had a long break from this project due to other higher priority things going on in my life. Big changes inbound.
1574 lines
62 KiB
C#
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;
|
|
}
|
|
} |