310 lines
11 KiB
C#
310 lines
11 KiB
C#
|
using System;
|
||
|
|
||
|
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 _vertexBufferUsage = 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 _elementBufferUsage;
|
||
|
private long _bufferUsageCounter;
|
||
|
|
||
|
/// <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 float CurveGranularity { get; set; } = 0.5f;
|
||
|
|
||
|
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 _vertexBufferUsage, _bufferUsageCounter, _elementBufferPointer);
|
||
|
MovingAverage(ref _elementBufferUsage, _bufferUsageCounter, _elementBufferPointer);
|
||
|
|
||
|
newVertexSize = (int)(Math.Ceiling(_vertexBufferUsage / BufferGranularity) * BufferGranularity);
|
||
|
newElementSize = (int)(Math.Ceiling(_elementBufferUsage / BufferGranularity) * BufferGranularity);
|
||
|
|
||
|
Array.Resize(ref _vertexBuffer, newVertexSize);
|
||
|
Array.Resize(ref _elementBuffer, newElementSize);
|
||
|
|
||
|
_vertexBufferPointer = 0;
|
||
|
_elementBufferPointer = 0;
|
||
|
}
|
||
|
|
||
|
public QuikDrawCall? ConsumeCommand(QuikCommand command)
|
||
|
{
|
||
|
QuikDrawCall call = new QuikDrawCall()
|
||
|
{
|
||
|
Target = _renderStencilMask ? QuikRenderTarget.Stencil : QuikRenderTarget.Color
|
||
|
};
|
||
|
|
||
|
switch (command.Type)
|
||
|
{
|
||
|
case QuikCommandType.StencilMaskClear:
|
||
|
call.ClearStencil = true;
|
||
|
break;
|
||
|
case QuikCommandType.StencilMaskBegin:
|
||
|
_renderStencilMask = true;
|
||
|
call.Target = QuikRenderTarget.Stencil;
|
||
|
break;
|
||
|
case QuikCommandType.StencilMaskEnd:
|
||
|
_renderStencilMask = false;
|
||
|
call.Target = QuikRenderTarget.Color;
|
||
|
break;
|
||
|
case QuikCommandType.Line:
|
||
|
RenderLine(ref call, command as QuikCommandLine);
|
||
|
return call;
|
||
|
case QuikCommandType.Lines:
|
||
|
RenderLine(ref call, command as QuikCommandLines);
|
||
|
return call;
|
||
|
}
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
/// <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(ref QuikDrawCall call, QuikCommandLine line)
|
||
|
{
|
||
|
// Skip over stipple patterns for now.
|
||
|
QuikStrokeStyle style = line.Style ?? Context.DefaultStroke;
|
||
|
int endCapResolution; // Resolution of the end cap.
|
||
|
short startOffset = (short) _vertexBufferPointer; // Starting index pointer.
|
||
|
QuikVec2 tangent;
|
||
|
QuikVec2 normal;
|
||
|
|
||
|
tangent = (line.Line.End - line.Line.Start).Normalize();
|
||
|
normal = new QuikVec2() {X = -tangent.Y, Y = tangent.X};
|
||
|
|
||
|
QuikVertex baseVertex = new QuikVertex() {Color = style.Color };
|
||
|
QuikVertex startA = baseVertex, startB = baseVertex;
|
||
|
QuikVertex endA = baseVertex, endB = baseVertex;
|
||
|
|
||
|
startA.Position = line.Line.Start + style.Width / 2 * normal;
|
||
|
startB.Position = line.Line.Start - style.Width / 2 * normal;
|
||
|
endA.Position = line.Line.End + style.Width / 2 * normal;
|
||
|
endB.Position = line.Line.End - style.Width / 2 * normal;
|
||
|
|
||
|
// Add the major line vertices.
|
||
|
AddVertex(startA, startB, endA, endB);
|
||
|
|
||
|
// Add the line indices.
|
||
|
AddElement(
|
||
|
(short) (startOffset + 1),
|
||
|
(short) (startOffset + 2),
|
||
|
(short) (startOffset + 0),
|
||
|
(short) (startOffset + 1),
|
||
|
(short) (startOffset + 3),
|
||
|
(short) (startOffset + 2)
|
||
|
);
|
||
|
|
||
|
// Now calculate the end caps.
|
||
|
endCapResolution = (int)Math.Ceiling(MathF.PI * style.Width * CurveGranularity);
|
||
|
|
||
|
// Construct start cap.
|
||
|
QuikVertex circlePoint = baseVertex;
|
||
|
short lastIndex = startOffset;
|
||
|
for (int i = 0; i < endCapResolution; i++)
|
||
|
{
|
||
|
float angle = (float) (i + 1) / (endCapResolution + 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
|
||
|
} * (style.Width / 2);
|
||
|
|
||
|
circlePoint.Position = line.Line.Start + displacement;
|
||
|
|
||
|
AddVertex(circlePoint);
|
||
|
AddElement(
|
||
|
(short)(startOffset + 1),
|
||
|
lastIndex,
|
||
|
(short)(_vertexBufferPointer - 1));
|
||
|
|
||
|
lastIndex = (short) (_vertexBufferPointer - 1);
|
||
|
}
|
||
|
|
||
|
// Construct end cap.
|
||
|
lastIndex = (short)(startOffset + 2);
|
||
|
for (int i = 0; i < endCapResolution; i++)
|
||
|
{
|
||
|
float angle = -(float) (i + 1) / (endCapResolution + 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
|
||
|
} * (style.Width / 2);
|
||
|
|
||
|
circlePoint.Position = line.Line.End + displacement;
|
||
|
|
||
|
AddVertex(circlePoint);
|
||
|
AddElement(
|
||
|
(short)(startOffset + 3),
|
||
|
lastIndex,
|
||
|
(short)(_vertexBufferPointer - 1));
|
||
|
|
||
|
lastIndex = (short) (_vertexBufferPointer - 1);
|
||
|
}
|
||
|
|
||
|
call.Offset =(short) (startOffset * 2);
|
||
|
call.Count = (short) (_elementBufferPointer - startOffset);
|
||
|
}
|
||
|
|
||
|
private void RenderLine(ref QuikDrawCall call, QuikCommandLines lines)
|
||
|
{
|
||
|
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public enum QuikRenderTarget
|
||
|
{
|
||
|
Color,
|
||
|
Stencil
|
||
|
}
|
||
|
|
||
|
public struct QuikDrawCall
|
||
|
{
|
||
|
public QuikRenderTarget Target;
|
||
|
public short Offset;
|
||
|
public short Count;
|
||
|
public QuikRectangle Bounds;
|
||
|
public bool ClearStencil;
|
||
|
}
|
||
|
}
|