Implement bezier lines.

This commit is contained in:
H. Utku Maden 2022-08-08 10:52:03 +03:00
parent 502eb95278
commit 16bb3f41a7
3 changed files with 180 additions and 10 deletions

@ -1,5 +1,4 @@
using System;
using Quik.VertexGenerator;
namespace Quik
{
@ -153,6 +152,38 @@ namespace Quik
/// </summary>
public QuikVec2 End;
/// <summary>
/// An approximation of the arc length of the bezier curve, for calculating rasterization resolution.
/// </summary>
public float RasterizationArc =>
0.5f * (End - Start).Magnitude +
0.5f * ((ControlA - Start).Magnitude + (ControlB - ControlA).Magnitude + (End - ControlB).Magnitude);
public QuikBezier(QuikVec2 start, QuikVec2 controlA, QuikVec2 controlB, QuikVec2 end)
{
Start = start;
ControlA = controlA;
ControlB = controlB;
End = end;
}
public QuikBezier(
float startX,
float startY,
float controlAx,
float controlAy,
float controlBx,
float controlBy,
float endX,
float endY)
: this(
new QuikVec2(startX, startY),
new QuikVec2(controlAx, controlAy),
new QuikVec2(controlBx, controlBy),
new QuikVec2(endX, endY))
{
}
/// <summary>
/// Get a point in the curve segment.
/// </summary>
@ -160,11 +191,12 @@ namespace Quik
/// <returns>The point on the curve.</returns>
public QuikVec2 GetBezierPoint(float t)
{
float T = 1 - t;
return
(1 - t) * (1 - t) * (1 - t) * Start +
(1 - t) * (1 - t) * t * ControlA +
(1 - t) * t * t * ControlB +
t * t * t * End;
T * T * T * Start +
3 * T * T * t * ControlA +
3 * T * t * t * ControlB +
t * t * t * End;
}
/// <summary>
@ -174,10 +206,11 @@ namespace Quik
/// <returns>The tangent curve.</returns>
public QuikVec2 GetBezierTangent(float t)
{
float T = 1 - t;
return
3 * (1 - t) * (1 - t) * (ControlA - Start) +
6 * (1 - t) * (ControlB - ControlA) +
3 * t * t * (End - ControlB);
3 * T * T * (ControlA - Start) +
6 * T * t * (ControlB - ControlA) +
3 * t * t * (End - ControlB);
}
}

@ -65,7 +65,7 @@ namespace Quik.VertexGenerator
/// </summary>
public int ElementCount => _elementBufferPointer;
public float CurveGranularity { get; set; } = 0.5f;
public float CurveGranularity { get; set; } = 0.2f;
public QuikContext Context { get; }
@ -186,6 +186,9 @@ namespace Quik.VertexGenerator
case QuikCommandType.Lines:
RenderLine(ref call, command as QuikCommandLines);
return call;
case QuikCommandType.Bezier:
RenderBezier(ref call, command as QuikCommandBezier);
return call;
}
return null;
}
@ -502,7 +505,8 @@ namespace Quik.VertexGenerator
}
else
{
// There is no need to generate anything because the lines are the same???
// There is a cusp. Generate an end cap.
GenerateEndCap(baseVertex, focus, prevNormal.Normalize(), width, GetRoundingResolution(width, MathF.PI), prevPositiveEndIndex, prevNegativeEndIndex);
}
}
@ -595,6 +599,134 @@ namespace Quik.VertexGenerator
call.Offset = (short) (startOffset * 2);
call.Count = (short) (_elementBufferPointer - startOffset);
}
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);
}
}
public enum QuikRenderTarget

@ -155,6 +155,11 @@ void main()
new QuikLine(64, 30, 64, 10),
new QuikLine(64, 10, 74, 10)
);
context.Draw.Bezier(
new QuikBezier(46, 10, 56, 10, 46, 30, 56, 30),
new QuikBezier(56, 30, 46, 30, 56, 60, 46, 60),
new QuikBezier(46, 60, 56, 60, 46, 90, 56, 90));
QuikCommand command;
while (context.Draw.Commands.TryDequeue(out command))