517 lines
14 KiB
C#
517 lines
14 KiB
C#
using System;
|
|
using System.ComponentModel.DataAnnotations;
|
|
using System.Diagnostics;
|
|
|
|
namespace Quik
|
|
{
|
|
/// <summary>
|
|
/// A 2 dimensional Vector.
|
|
/// </summary>
|
|
[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})";
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// A RGBA color value.
|
|
/// </summary>
|
|
[DebuggerDisplay("({R}, {G}, {B}, {A})")]
|
|
public struct QColor
|
|
{
|
|
/// <summary>
|
|
/// Red channel.
|
|
/// </summary>
|
|
public byte R;
|
|
/// <summary>
|
|
/// Green channel.
|
|
/// </summary>
|
|
public byte G;
|
|
/// <summary>
|
|
/// Blue channel.
|
|
/// </summary>
|
|
public byte B;
|
|
/// <summary>
|
|
/// Alpha channel.
|
|
/// </summary>
|
|
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
|
|
{
|
|
/// <summary>
|
|
/// Red channel.
|
|
/// </summary>
|
|
public float R;
|
|
/// <summary>
|
|
/// Green channel.
|
|
/// </summary>
|
|
public float G;
|
|
/// <summary>
|
|
/// Blue channel.
|
|
/// </summary>
|
|
public float B;
|
|
/// <summary>
|
|
/// Alpha channel.
|
|
/// </summary>
|
|
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));
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// A bezier curve segment.
|
|
/// </summary>
|
|
[DebuggerDisplay("{Start} -- {ControlA} -- {ControlB} -- {End}")]
|
|
public struct QBezier
|
|
{
|
|
/// <summary>
|
|
/// Segment start point.
|
|
/// </summary>
|
|
public QVec2 Start;
|
|
|
|
/// <summary>
|
|
/// Start point control point.
|
|
/// </summary>
|
|
public QVec2 ControlA;
|
|
|
|
/// <summary>
|
|
/// End point control point.
|
|
/// </summary>
|
|
public QVec2 ControlB;
|
|
|
|
/// <summary>
|
|
/// Segment end point.
|
|
/// </summary>
|
|
public QVec2 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 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))
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get a point in the curve segment.
|
|
/// </summary>
|
|
/// <param name="t">Control parameter (between 0 and 1)</param>
|
|
/// <returns>The point on the curve.</returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the tangent on the curve.
|
|
/// </summary>
|
|
/// <param name="t">Control parameter (between 0 and 1)</param>
|
|
/// <returns>The tangent curve.</returns>
|
|
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);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// A line segment.
|
|
/// </summary>
|
|
[DebuggerDisplay("{Start} -- {End}")]
|
|
public struct QLine
|
|
{
|
|
/// <summary>
|
|
/// Start point.
|
|
/// </summary>
|
|
public QVec2 Start;
|
|
|
|
/// <summary>
|
|
/// End point.
|
|
/// </summary>
|
|
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();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// A rectangle.
|
|
/// </summary>
|
|
[DebuggerDisplay("({Left}, {Top}, {Right}, {Bottom})")]
|
|
public struct QRectangle
|
|
{
|
|
/// <summary>
|
|
/// Position maximum point.
|
|
/// </summary>
|
|
public QVec2 Max;
|
|
|
|
/// <summary>
|
|
/// Position minimum point.
|
|
/// </summary>
|
|
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));
|
|
}
|
|
|
|
/// <summary>
|
|
/// An ellipse.
|
|
/// </summary>
|
|
/// <remarks>It is undefined to have an ellipse with non-orthogonal axes.</remarks>
|
|
[DebuggerDisplay("{Center} ellipse {AxisA}; {AxisB}")]
|
|
public struct QEllipse
|
|
{
|
|
/// <summary>
|
|
/// Ellipse center point.
|
|
/// </summary>
|
|
public QVec2 Center;
|
|
|
|
/// <summary>
|
|
/// First ellipse axis.
|
|
/// </summary>
|
|
public QVec2 AxisA;
|
|
|
|
/// <summary>
|
|
/// Second ellipse axis.
|
|
/// </summary>
|
|
public QVec2 AxisB;
|
|
}
|
|
|
|
/// <summary>
|
|
/// A triangle.
|
|
/// </summary>
|
|
[DebuggerDisplay("{A} -- {B} -- {C}")]
|
|
public struct QTriangle
|
|
{
|
|
/// <summary>
|
|
/// First vertex.
|
|
/// </summary>
|
|
public QVec2 A;
|
|
|
|
/// <summary>
|
|
/// Second vertex.
|
|
/// </summary>
|
|
public QVec2 B;
|
|
|
|
/// <summary>
|
|
/// Third vertex.
|
|
/// </summary>
|
|
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.M41 = x;
|
|
mat.M42 = y;
|
|
mat.M43 = 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;
|
|
}
|
|
}
|
|
} |