2022-08-04 15:40:58 +02:00
|
|
|
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;
|
|
|
|
|
2022-08-08 09:52:03 +02:00
|
|
|
public float CurveGranularity { get; set; } = 0.2f;
|
2022-08-04 15:40:58 +02:00
|
|
|
|
|
|
|
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;
|
2022-08-08 09:52:03 +02:00
|
|
|
case QuikCommandType.Bezier:
|
|
|
|
RenderBezier(ref call, command as QuikCommandBezier);
|
|
|
|
return call;
|
2022-08-04 15:40:58 +02:00
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
2022-08-06 09:05:27 +02:00
|
|
|
|
2022-08-04 15:40:58 +02:00
|
|
|
/// <summary>
|
2022-08-06 09:05:27 +02:00
|
|
|
/// Generates a basic line segment.
|
2022-08-04 15:40:58 +02:00
|
|
|
/// </summary>
|
2022-08-06 09:05:27 +02:00
|
|
|
/// <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)
|
2022-08-04 15:40:58 +02:00
|
|
|
{
|
2022-08-06 09:05:27 +02:00
|
|
|
QuikVec2 tangent = (line.End - line.Start).Normalize();
|
2022-08-04 15:40:58 +02:00
|
|
|
normal = new QuikVec2() {X = -tangent.Y, Y = tangent.X};
|
2022-08-06 09:05:27 +02:00
|
|
|
lineStartIndex = (short)_vertexBufferPointer;
|
2022-08-04 15:40:58 +02:00
|
|
|
|
|
|
|
QuikVertex startA = baseVertex, startB = baseVertex;
|
|
|
|
QuikVertex endA = baseVertex, endB = baseVertex;
|
|
|
|
|
2022-08-06 09:05:27 +02:00
|
|
|
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;
|
2022-08-04 15:40:58 +02:00
|
|
|
|
|
|
|
// Add the major line vertices.
|
|
|
|
AddVertex(startA, startB, endA, endB);
|
|
|
|
|
|
|
|
// Add the line indices.
|
|
|
|
AddElement(
|
2022-08-06 09:05:27 +02:00
|
|
|
(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="width">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 width, float arc)
|
|
|
|
{
|
|
|
|
int endCapResolution = (int) Math.Ceiling(arc * width * CurveGranularity);
|
|
|
|
return endCapResolution;
|
|
|
|
}
|
|
|
|
|
|
|
|
/// <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
|
|
|
|
)
|
|
|
|
{
|
2022-08-04 15:40:58 +02:00
|
|
|
QuikVertex circlePoint = baseVertex;
|
2022-08-06 09:05:27 +02:00
|
|
|
short lastIndex = lineStartPositiveIndex;
|
|
|
|
for (int i = 0; i < resolution; i++)
|
2022-08-04 15:40:58 +02:00
|
|
|
{
|
2022-08-06 09:05:27 +02:00
|
|
|
float angle = (float) (i + 1) / (resolution + 1) * MathF.PI;
|
2022-08-04 15:40:58 +02:00
|
|
|
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
|
2022-08-06 09:05:27 +02:00
|
|
|
} * (width / 2);
|
|
|
|
|
|
|
|
circlePoint.Position = start + displacement;
|
|
|
|
|
|
|
|
short segmentIndex = (short)_vertexBufferPointer;
|
2022-08-04 15:40:58 +02:00
|
|
|
|
|
|
|
AddVertex(circlePoint);
|
|
|
|
AddElement(
|
2022-08-06 09:05:27 +02:00
|
|
|
lineStartNegativeIndex,
|
2022-08-04 15:40:58 +02:00
|
|
|
lastIndex,
|
2022-08-06 09:05:27 +02:00
|
|
|
segmentIndex);
|
2022-08-04 15:40:58 +02:00
|
|
|
|
2022-08-06 09:05:27 +02:00
|
|
|
lastIndex = segmentIndex;
|
2022-08-04 15:40:58 +02:00
|
|
|
}
|
2022-08-06 09:05:27 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/// <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++)
|
2022-08-04 15:40:58 +02:00
|
|
|
{
|
2022-08-06 09:05:27 +02:00
|
|
|
float angle = -(float) (i + 1) / (resolution + 1) * MathF.PI;
|
2022-08-04 15:40:58 +02:00
|
|
|
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
|
2022-08-06 09:05:27 +02:00
|
|
|
} * (width / 2);
|
|
|
|
|
|
|
|
circlePoint.Position = end + displacement;
|
2022-08-04 15:40:58 +02:00
|
|
|
|
|
|
|
AddVertex(circlePoint);
|
|
|
|
AddElement(
|
2022-08-06 09:05:27 +02:00
|
|
|
lineEndNegativeIndex,
|
2022-08-04 15:40:58 +02:00
|
|
|
lastIndex,
|
2022-08-06 09:05:27 +02:00
|
|
|
(short) (_vertexBufferPointer - 1));
|
2022-08-04 15:40:58 +02:00
|
|
|
|
|
|
|
lastIndex = (short) (_vertexBufferPointer - 1);
|
|
|
|
}
|
2022-08-06 09:05:27 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/// <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
|
|
|
|
{
|
2022-08-08 09:52:03 +02:00
|
|
|
// There is a cusp. Generate an end cap.
|
|
|
|
GenerateEndCap(baseVertex, focus, prevNormal.Normalize(), width, GetRoundingResolution(width, MathF.PI), prevPositiveEndIndex, prevNegativeEndIndex);
|
2022-08-06 09:05:27 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// <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;
|
|
|
|
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));
|
2022-08-04 15:40:58 +02:00
|
|
|
|
2022-08-06 09:05:27 +02:00
|
|
|
call.Offset = (short) (startOffset * 2);
|
2022-08-04 15:40:58 +02:00
|
|
|
call.Count = (short) (_elementBufferPointer - startOffset);
|
|
|
|
}
|
|
|
|
|
2022-08-06 09:05:27 +02:00
|
|
|
/// <summary>
|
|
|
|
/// Render lines.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="call">The draw call to generate.</param>
|
|
|
|
/// <param name="lines">The lines command.</param>
|
2022-08-04 15:40:58 +02:00
|
|
|
private void RenderLine(ref QuikDrawCall call, QuikCommandLines lines)
|
|
|
|
{
|
2022-08-06 09:05:27 +02:00
|
|
|
// 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;
|
|
|
|
}
|
|
|
|
|
|
|
|
call.Offset = (short) (startOffset * 2);
|
|
|
|
call.Count = (short) (_elementBufferPointer - startOffset);
|
2022-08-04 15:40:58 +02:00
|
|
|
}
|
2022-08-08 09:52:03 +02:00
|
|
|
|
|
|
|
private void RenderBezier(ref QuikDrawCall call, 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;
|
|
|
|
}
|
|
|
|
|
|
|
|
call.Offset = (short) (startOffset * 2);
|
|
|
|
call.Count = (short) (_elementBufferPointer - startOffset);
|
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
2022-08-04 15:40:58 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public enum QuikRenderTarget
|
|
|
|
{
|
|
|
|
Color,
|
|
|
|
Stencil
|
|
|
|
}
|
|
|
|
|
|
|
|
public struct QuikDrawCall
|
|
|
|
{
|
|
|
|
public QuikRenderTarget Target;
|
|
|
|
public short Offset;
|
|
|
|
public short Count;
|
|
|
|
public QuikRectangle Bounds;
|
|
|
|
public bool ClearStencil;
|
|
|
|
}
|
|
|
|
}
|