diff --git a/Quik/QuikGeometry.cs b/Quik/QuikGeometry.cs
index b8e8304..1df0546 100644
--- a/Quik/QuikGeometry.cs
+++ b/Quik/QuikGeometry.cs
@@ -1,5 +1,4 @@
using System;
-using Quik.VertexGenerator;
namespace Quik
{
@@ -153,6 +152,38 @@ namespace Quik
///
public QuikVec2 End;
+ ///
+ /// An approximation of the arc length of the bezier curve, for calculating rasterization resolution.
+ ///
+ 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))
+ {
+ }
+
///
/// Get a point in the curve segment.
///
@@ -160,11 +191,12 @@ namespace Quik
/// The point on the curve.
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;
}
///
@@ -174,10 +206,11 @@ namespace Quik
/// The tangent curve.
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);
}
}
diff --git a/Quik/VertexGenerator/QuikVertexGenerator.cs b/Quik/VertexGenerator/QuikVertexGenerator.cs
index 082120b..d75e2b4 100644
--- a/Quik/VertexGenerator/QuikVertexGenerator.cs
+++ b/Quik/VertexGenerator/QuikVertexGenerator.cs
@@ -65,7 +65,7 @@ namespace Quik.VertexGenerator
///
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
diff --git a/QuikTestApplication/Program.cs b/QuikTestApplication/Program.cs
index fd201c8..2941e52 100644
--- a/QuikTestApplication/Program.cs
+++ b/QuikTestApplication/Program.cs
@@ -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))