using System; using System.ComponentModel.DataAnnotations; using System.Diagnostics; namespace Quik { /// /// A 2 dimensional Vector. /// [DebuggerDisplay("({X}, {Y})")] public struct QVec2 { public float X; public float Y; public float Magnitude => MathF.Sqrt(X * X + Y * Y); public QVec2(float x, float y) { X = x; Y = y; } public QVec2 Normalize() => this * (1.0f / Magnitude); public float Atan2() => MathF.Atan2(Y, X); public static QVec2 operator +(QVec2 a, QVec2 b) { return new QVec2() { X = a.X + b.X, Y = a.Y + b.Y }; } public static QVec2 operator -(QVec2 a) { return new QVec2() { X = -a.X, Y = -a.Y }; } public static QVec2 operator -(QVec2 a, QVec2 b) { return new QVec2() { X = a.X - b.X, Y = a.Y - b.Y }; } public static QVec2 operator *(float a, QVec2 b) { return new QVec2() { X = a * b.X, Y = a * b.Y }; } public static QVec2 operator *(QVec2 a, float b) => b * a; public static bool operator ==(QVec2 a, QVec2 b) => a.X == b.X && a.Y == b.Y; public static bool operator !=(QVec2 a, QVec2 b) => a.X != b.X || a.Y != b.Y; public override bool Equals(object? obj) { if (obj is QVec2) { return (QVec2) obj == this; } else { return false; } } public override int GetHashCode() { return 63671 * X.GetHashCode() ^ 81083 * Y.GetHashCode(); } public static float Dot(QVec2 a, QVec2 b) { return a.X * b.X + a.Y * b.Y; } public override string ToString() { return $"({X}; {Y})"; } public static readonly QVec2 Zero = new QVec2(0, 0); public static readonly QVec2 UnitX = new QVec2(1, 0); public static readonly QVec2 UnitY = new QVec2(0, 1); } /// /// A RGBA color value. /// [DebuggerDisplay("({R}, {G}, {B}, {A})")] public struct QColor { /// /// Red channel. /// public byte R; /// /// Green channel. /// public byte G; /// /// Blue channel. /// public byte B; /// /// Alpha channel. /// public byte A; public QColor(byte r, byte g, byte b, byte a) { R = r; G = g; B = b; A = a; } public QColor(byte r, byte g, byte b) : this(r, g, b, 1) { } public QColor(uint hexCode) { R = (byte)((hexCode >> 24) & 0xFF); G = (byte)((hexCode >> 16) & 0xFF); B = (byte)((hexCode >> 8 ) & 0xFF); A = (byte)((hexCode >> 0 ) & 0xFF); } public QColor(int hexCode) : this((uint)hexCode) { } public static readonly QColor Black = new QColor(0, 0, 0, 255); public static readonly QColor Red = new QColor(255, 0, 0, 255); public static readonly QColor Green = new QColor(0, 255, 0, 255); public static readonly QColor Blue = new QColor(0, 0, 255, 255); public static readonly QColor Yellow = new QColor(255, 255, 0, 255); public static readonly QColor Cyan = new QColor(0, 255, 255, 255); public static readonly QColor Magenta = new QColor(255, 0, 255, 255); public static readonly QColor White = new QColor(255, 255, 255, 255); public static explicit operator QColorF(QColor a) { return new QColorF(a.R/255.0f, a.G/255.0f, a.B/255.0f, a.A/255.0f); } } public struct QColorF { /// /// Red channel. /// public float R; /// /// Green channel. /// public float G; /// /// Blue channel. /// public float B; /// /// Alpha channel. /// public float A; public QColorF(float r, float g, float b, float a) { R = r; G = g; B = b; A = a; } public QColorF(float r, float g, float b) : this(r, g, b, 1.0f) { } public QColorF(uint hexCode) { R = ((hexCode >> 24) & 0xFF)/255.0f; G = ((hexCode >> 16) & 0xFF)/255.0f; B = ((hexCode >> 8 ) & 0xFF)/255.0f; A = ((hexCode >> 0 ) & 0xFF)/255.0f; } public QColorF(int hexCode) : this((uint)hexCode) { } public static readonly QColorF Black = new QColorF(0, 0, 0, 1.0f); public static readonly QColorF Red = new QColorF(1.0f, 0, 0, 1.0f); public static readonly QColorF Green = new QColorF(0, 1, 0, 1); public static readonly QColorF Blue = new QColorF(0, 0, 1, 1); public static readonly QColorF Yellow = new QColorF(1, 1, 0, 1); public static readonly QColorF Cyan = new QColorF(0, 1, 1, 1); public static readonly QColorF Magenta = new QColorF(1, 0, 1, 1); public static readonly QColorF White = new QColorF(1, 1, 1, 1); public static explicit operator QColor(QColorF a) { return new QColor((byte)(a.R * 255), (byte)(a.G * 255), (byte)(a.B * 255), (byte)(a.A * 255)); } } /// /// A bezier curve segment. /// [DebuggerDisplay("{Start} -- {ControlA} -- {ControlB} -- {End}")] public struct QBezier { /// /// Segment start point. /// public QVec2 Start; /// /// Start point control point. /// public QVec2 ControlA; /// /// End point control point. /// public QVec2 ControlB; /// /// Segment end point. /// public QVec2 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 QBezier(QVec2 start, QVec2 controlA, QVec2 controlB, QVec2 end) { Start = start; ControlA = controlA; ControlB = controlB; End = end; } public QBezier( float startX, float startY, float controlAx, float controlAy, float controlBx, float controlBy, float endX, float endY) : this( new QVec2(startX, startY), new QVec2(controlAx, controlAy), new QVec2(controlBx, controlBy), new QVec2(endX, endY)) { } /// /// Get a point in the curve segment. /// /// Control parameter (between 0 and 1) /// The point on the curve. public QVec2 GetBezierPoint(float t) { float T = 1 - t; return T * T * T * Start + 3 * T * T * t * ControlA + 3 * T * t * t * ControlB + t * t * t * End; } /// /// Get the tangent on the curve. /// /// Control parameter (between 0 and 1) /// The tangent curve. public QVec2 GetBezierTangent(float t) { float T = 1 - t; return ( 3 * T * T * (ControlA - Start) + 6 * T * t * (ControlB - ControlA) + 3 * t * t * (End - ControlB) ).Normalize(); } internal QVec2 GetBezierNormal(float t) { QVec2 tangent = GetBezierTangent(t); return new QVec2(-tangent.Y, tangent.X); } } /// /// A line segment. /// [DebuggerDisplay("{Start} -- {End}")] public struct QLine { /// /// Start point. /// public QVec2 Start; /// /// End point. /// public QVec2 End; public QLine(QVec2 start, QVec2 end) { Start = start; End = end; } public QLine(float startX, float startY, float endX, float endY) { Start.X = startX; Start.Y = startY; End.X = endX; End.Y = endY; } public QVec2 Normal() { QVec2 tangent = Tangent(); return new QVec2(-tangent.Y, tangent.X); } public QVec2 Tangent() { return (End - Start).Normalize(); } } /// /// A rectangle. /// [DebuggerDisplay("({Left}, {Top}, {Right}, {Bottom})")] public struct QRectangle { /// /// Position maximum point. /// public QVec2 Max; /// /// Position minimum point. /// public QVec2 Min; public float Left { get => Min.X; set => Min.X = value; } public float Right { get => Max.X; set => Max.X = value; } public float Top { get => Min.Y; set => Min.Y = value; } public float Bottom { get => Max.Y; set => Max.Y = value; } public QVec2 Size { get => Max - Min; set => Max = Min + value; } public QRectangle(QVec2 max, QVec2 min) { Max = max; Min = min; } public QRectangle(float r, float b, float l, float t) { Max = new QVec2() {X = r, Y = b}; Min = new QVec2() {X = l, Y = t}; } public bool Contains(QVec2 point) { return point.X > Left && point.X < Right && point.Y > Bottom && point.Y < Top; } internal void Translate(in QVec2 offset) { Min += offset; Max += offset; } public static QRectangle Intersect(in QRectangle a, in QRectangle b) => new QRectangle( Math.Max(a.Right, b.Right), Math.Max(a.Bottom, b.Bottom) , Math.Min(a.Left, b.Left), Math.Min(a.Top, b.Top)); } /// /// An ellipse. /// /// It is undefined to have an ellipse with non-orthogonal axes. [DebuggerDisplay("{Center} ellipse {AxisA}; {AxisB}")] public struct QEllipse { /// /// Ellipse center point. /// public QVec2 Center; /// /// First ellipse axis. /// public QVec2 AxisA; /// /// Second ellipse axis. /// public QVec2 AxisB; } /// /// A triangle. /// [DebuggerDisplay("{A} -- {B} -- {C}")] public struct QTriangle { /// /// First vertex. /// public QVec2 A; /// /// Second vertex. /// public QVec2 B; /// /// Third vertex. /// public QVec2 C; } [DebuggerDisplay("[{M11} {M12} {M13} {M14}; {M21} {M22} {M23} {M24}; {M31} {M32} {M33} {M34}; {M41} {M42} {M43} {M44}]")] public struct QMat4 { public float M11, M21, M31, M41; public float M12, M22, M32, M42; public float M13, M23, M33, M43; public float M14, M24, M34, M44; public static QMat4 Identity { get; } = new QMat4() { M11 = 1.0f, M22 = 1.0f, M33 = 1.0f, M44 = 1.0f }; public static void Translation(out QMat4 mat, float x, float y, float z) { mat = Identity; mat.M14 = x; mat.M24 = y; mat.M34 = z; } public static void Scale(out QMat4 mat, float x, float y, float z) { mat = default; mat.M11 = x; mat.M22 = y; mat.M33 = z; mat.M44 = 1.0f; } public static void Orthographic(out QMat4 mat, QRectangle bounds, float near = 1, float far = -1) { float a, b, c; mat = Identity; a = 1.0f/(bounds.Right - bounds.Left); b = 1.0f/(bounds.Top - bounds.Bottom); c = 1.0f/(far - near); mat.M11 = 2 * a; mat.M22 = 2 * b; mat.M33 = -2 * c; mat.M14 = -a * (bounds.Left + bounds.Right); mat.M24 = -b * (bounds.Top + bounds.Bottom); mat.M34 = -c * (far + near); mat.M44 = 1.0f; } public static QMat4 operator *(in QMat4 a, in QMat4 b) { QMat4 mat4 = default; mat4.M11 = a.M11 * b.M11 + a.M12 * b.M21 + a.M13 * b.M31 + a.M14 * b.M41; mat4.M12 = a.M11 * b.M12 + a.M12 * b.M22 + a.M13 * b.M32 + a.M14 * b.M42; mat4.M13 = a.M11 * b.M13 + a.M12 * b.M23 + a.M13 * b.M33 + a.M14 * b.M43; mat4.M14 = a.M11 * b.M14 + a.M12 * b.M24 + a.M13 * b.M34 + a.M14 * b.M44; mat4.M21 = a.M21 * b.M11 + a.M22 * b.M21 + a.M23 * b.M31 + a.M24 * b.M41; mat4.M22 = a.M21 * b.M12 + a.M22 * b.M22 + a.M23 * b.M32 + a.M24 * b.M42; mat4.M23 = a.M21 * b.M13 + a.M22 * b.M23 + a.M23 * b.M33 + a.M24 * b.M43; mat4.M24 = a.M21 * b.M14 + a.M22 * b.M24 + a.M23 * b.M34 + a.M24 * b.M44; mat4.M31 = a.M31 * b.M11 + a.M32 * b.M21 + a.M33 * b.M31 + a.M34 * b.M41; mat4.M32 = a.M31 * b.M12 + a.M32 * b.M22 + a.M33 * b.M32 + a.M34 * b.M42; mat4.M33 = a.M31 * b.M13 + a.M32 * b.M23 + a.M33 * b.M33 + a.M34 * b.M43; mat4.M34 = a.M31 * b.M14 + a.M32 * b.M24 + a.M33 * b.M34 + a.M34 * b.M44; mat4.M41 = a.M41 * b.M11 + a.M42 * b.M21 + a.M43 * b.M31 + a.M44 * b.M41; mat4.M42 = a.M41 * b.M12 + a.M42 * b.M22 + a.M43 * b.M32 + a.M44 * b.M42; mat4.M43 = a.M41 * b.M13 + a.M42 * b.M23 + a.M43 * b.M33 + a.M44 * b.M43; mat4.M44 = a.M41 * b.M14 + a.M42 * b.M24 + a.M43 * b.M34 + a.M44 * b.M44; return mat4; } } }