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))