diff --git a/Quik/QuikGeometry.cs b/Quik/QuikGeometry.cs
index 2cfe0dc..b8e8304 100644
--- a/Quik/QuikGeometry.cs
+++ b/Quik/QuikGeometry.cs
@@ -1,4 +1,5 @@
using System;
+using Quik.VertexGenerator;
namespace Quik
{
@@ -10,9 +11,15 @@ namespace Quik
public float X;
public float Y;
- public float Length() => MathF.Sqrt(X * X + Y * Y);
+ public float Magnitude => MathF.Sqrt(X * X + Y * Y);
- public QuikVec2 Normalize() => this * (1.0f / this.Length());
+ public QuikVec2(float x, float y)
+ {
+ X = x;
+ Y = y;
+ }
+
+ public QuikVec2 Normalize() => this * (1.0f / Magnitude);
public float Atan2() => MathF.Atan2(Y, X);
public static QuikVec2 operator +(QuikVec2 a, QuikVec2 b)
@@ -52,6 +59,32 @@ namespace Quik
}
public static QuikVec2 operator *(QuikVec2 a, float b) => b * a;
+
+ public static bool operator ==(QuikVec2 a, QuikVec2 b) => a.X == b.X && a.Y == b.Y;
+
+ public static bool operator !=(QuikVec2 a, QuikVec2 b) => a.X != b.X || a.Y != b.Y;
+
+ public override bool Equals(object obj)
+ {
+ if (obj is QuikVec2)
+ {
+ return (QuikVec2) obj == this;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ public override int GetHashCode()
+ {
+ return 63671 * X.GetHashCode() ^ 81083 * Y.GetHashCode();
+ }
+
+ public static float Dot(QuikVec2 a, QuikVec2 b)
+ {
+ return a.X * b.X + a.Y * b.Y;
+ }
}
///
@@ -162,6 +195,20 @@ namespace Quik
/// End point.
///
public QuikVec2 End;
+
+ public QuikLine(QuikVec2 start, QuikVec2 end)
+ {
+ Start = start;
+ End = end;
+ }
+
+ public QuikLine(float startX, float startY, float endX, float endY)
+ {
+ Start.X = startX;
+ Start.Y = startY;
+ End.X = endX;
+ End.Y = endY;
+ }
}
///
diff --git a/Quik/VertexGenerator/QuikVertexGenerator.cs b/Quik/VertexGenerator/QuikVertexGenerator.cs
index 599f9d0..082120b 100644
--- a/Quik/VertexGenerator/QuikVertexGenerator.cs
+++ b/Quik/VertexGenerator/QuikVertexGenerator.cs
@@ -189,7 +189,323 @@ namespace Quik.VertexGenerator
}
return null;
}
+
+ ///
+ /// Generates a basic line segment.
+ ///
+ /// The vertex base to modify when generating vertices.
+ /// The line segment.
+ /// The width of the line.
+ /// Start index of the generated line.
+ /// The line normal.
+ private void GenerateLineSegment(
+ QuikVertex baseVertex,
+ QuikLine line,
+ float width,
+ out short lineStartIndex,
+ out QuikVec2 normal)
+ {
+ QuikVec2 tangent = (line.End - line.Start).Normalize();
+ normal = new QuikVec2() {X = -tangent.Y, Y = tangent.X};
+ lineStartIndex = (short)_vertexBufferPointer;
+
+ QuikVertex startA = baseVertex, startB = baseVertex;
+ QuikVertex endA = baseVertex, endB = baseVertex;
+
+ 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;
+
+ // Add the major line vertices.
+ AddVertex(startA, startB, endA, endB);
+
+ // Add the line indices.
+ AddElement(
+ (short) (lineStartIndex + 1),
+ (short) (lineStartIndex + 2),
+ (short) (lineStartIndex + 0),
+ (short) (lineStartIndex + 1),
+ (short) (lineStartIndex + 3),
+ (short) (lineStartIndex + 2)
+ );
+ }
+ ///
+ /// Gets the rounding resolution for a line segment.
+ ///
+ /// The width of the line.
+ /// The angle of the cap or joint arc.
+ /// The rounding resolution.
+ private int GetRoundingResolution(float width, float arc)
+ {
+ int endCapResolution = (int) Math.Ceiling(arc * width * CurveGranularity);
+ return endCapResolution;
+ }
+
+ ///
+ /// Generate a round start cap on a line.
+ ///
+ /// The base vertex to modify for generation.
+ /// The start point.
+ /// The line normal.
+ /// The width of line to generate.
+ /// End cap resolution.
+ /// The positive vertex index of the line start.
+ /// The negative vertex index of the line start.
+ public void GenerateStartCap(
+ QuikVertex baseVertex,
+ QuikVec2 start,
+ QuikVec2 normal,
+ float width,
+ int resolution,
+ short lineStartPositiveIndex,
+ short lineStartNegativeIndex
+ )
+ {
+ QuikVertex circlePoint = baseVertex;
+ short lastIndex = lineStartPositiveIndex;
+ for (int i = 0; i < resolution; i++)
+ {
+ float angle = (float) (i + 1) / (resolution + 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
+ } * (width / 2);
+
+ circlePoint.Position = start + displacement;
+
+ short segmentIndex = (short)_vertexBufferPointer;
+
+ AddVertex(circlePoint);
+ AddElement(
+ lineStartNegativeIndex,
+ lastIndex,
+ segmentIndex);
+
+ lastIndex = segmentIndex;
+ }
+ }
+
+ ///
+ /// Generate a round line end cap.
+ ///
+ /// Base vertex to modify for generation.
+ /// The line end.
+ /// The line normal.
+ /// The line width.
+ /// Cap generation resolution.
+ /// Vertex index of the positive line end.
+ /// Vertex index of the negative line end.
+ 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++)
+ {
+ float angle = -(float) (i + 1) / (resolution + 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
+ } * (width / 2);
+
+ circlePoint.Position = end + displacement;
+
+ AddVertex(circlePoint);
+ AddElement(
+ lineEndNegativeIndex,
+ lastIndex,
+ (short) (_vertexBufferPointer - 1));
+
+ lastIndex = (short) (_vertexBufferPointer - 1);
+ }
+ }
+
+ ///
+ /// Generate a joint on the line negative edge.
+ ///
+ /// Base vertex to modify for generation.
+ /// The focus of the joint.
+ /// With of the lines.
+ /// Index of the negative end vertex.
+ /// Index of the negative start vertex.
+ /// Joint resolution.
+ /// Arc length of the joint.
+ /// Normal of the previous line.
+ 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);
+ }
+
+ ///
+ /// Generate a joint on the line negative edge.
+ ///
+ /// Base vertex to modify for generation.
+ /// The focus of the joint.
+ /// With of the lines.
+ /// Index of the positive end vertex.
+ /// Index of the positive start vertex.
+ /// Joint resolution.
+ /// Arc length of the joint.
+ /// Normal of the next line.
+ 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);
+ }
+
+ ///
+ /// Generate a joint.
+ ///
+ /// Base vertex to modify.
+ /// Focus of the joint.
+ /// Tangent of the previous line segment.
+ /// Tangent of the next line segment.
+ /// Width of the lines.
+ /// Vertex index of the positive end.
+ /// Vertex index of the negative end.
+ /// Vertex index of the positive start.
+ /// Vertex index of the negative start.
+ 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
+ {
+ // There is no need to generate anything because the lines are the same???
+ }
+ }
+
///
/// Renders a line.
///
@@ -198,98 +514,86 @@ namespace Quik.VertexGenerator
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);
+ QuikStrokeStyle style = line.Style ?? Context.DefaultStroke;
+ QuikVertex baseVertex = new QuikVertex() { Color = style.Color };
- // Add the line indices.
- AddElement(
- (short) (startOffset + 1),
- (short) (startOffset + 2),
- (short) (startOffset + 0),
- (short) (startOffset + 1),
- (short) (startOffset + 3),
- (short) (startOffset + 2)
- );
+ GenerateLineSegment(baseVertex, line.Line, style.Width, out short startOffset, out QuikVec2 normal);
// Now calculate the end caps.
- endCapResolution = (int)Math.Ceiling(MathF.PI * style.Width * CurveGranularity);
+ int endCapResolution = GetRoundingResolution(style.Width, MathF.PI);
// 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);
- }
+ GenerateStartCap(baseVertex, line.Line.Start, normal, style.Width, endCapResolution, startOffset, (short)
+ (startOffset + 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);
+ GenerateEndCap(baseVertex, line.Line.End, normal, style.Width, endCapResolution, (short) (startOffset + 2),(short)
+ (startOffset + 3));
- 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.Offset = (short) (startOffset * 2);
call.Count = (short) (_elementBufferPointer - startOffset);
}
+ ///
+ /// Render lines.
+ ///
+ /// The draw call to generate.
+ /// The lines command.
private void RenderLine(ref QuikDrawCall call, QuikCommandLines lines)
{
-
+ // 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);
}
}
diff --git a/QuikTestApplication/Program.cs b/QuikTestApplication/Program.cs
index 4ce6f62..fd201c8 100644
--- a/QuikTestApplication/Program.cs
+++ b/QuikTestApplication/Program.cs
@@ -127,27 +127,34 @@ void main()
Matrix4 matrix = Matrix4.CreateOrthographicOffCenter(
0,
- window.Size.X,
+ (float)window.Size.X / 2,
0,
- window.Size.Y,
+ (float)window.Size.Y / 2,
1,
-1);
GL.UniformMatrix4(loc, false, ref matrix);
+
+ context.DefaultStroke.Color = new QuikColor(40, 128, 60, 255);
context.Draw.Line(
- new QuikLine()
- {
- Start = new QuikVec2()
- {
- X = 20,
- Y = 40
- },
- End = new QuikVec2()
- {
- X=100,
- Y=100
- }
- });
+ // >
+ new QuikLine(10,10, 20, 20),
+ new QuikLine(20, 20, 10, 30),
+
+ // s
+ new QuikLine(28, 10, 38, 10),
+ new QuikLine(38, 10, 38, 20),
+ new QuikLine(38, 20, 28, 20),
+ new QuikLine(28, 20, 28, 30),
+ new QuikLine(28, 30, 38, 30),
+
+ // e
+ new QuikLine(64, 20, 74, 20),
+ new QuikLine(74, 20, 74, 30),
+ new QuikLine(74, 30, 64, 30),
+ new QuikLine(64, 30, 64, 10),
+ new QuikLine(64, 10, 74, 10)
+ );
QuikCommand command;
while (context.Draw.Commands.TryDequeue(out command))