A game framework written with osu! in mind.
1// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
2// See the LICENCE file in the repository root for full licence text.
3
4using System;
5using System.Runtime.CompilerServices;
6using System.Runtime.InteropServices;
7using osuTK;
8using osu.Framework.Utils;
9
10namespace osu.Framework.Graphics.Primitives
11{
12 [StructLayout(LayoutKind.Sequential)]
13 public readonly struct Quad : IConvexPolygon, IEquatable<Quad>
14 {
15 // Note: Do not change the order of vertices. They are ordered in screen-space counter-clockwise fashion.
16 // See: IPolygon.GetVertices()
17 public readonly Vector2 TopLeft;
18 public readonly Vector2 BottomLeft;
19 public readonly Vector2 BottomRight;
20 public readonly Vector2 TopRight;
21
22 public Quad(Vector2 topLeft, Vector2 topRight, Vector2 bottomLeft, Vector2 bottomRight)
23 {
24 TopLeft = topLeft;
25 TopRight = topRight;
26 BottomLeft = bottomLeft;
27 BottomRight = bottomRight;
28 }
29
30 public Quad(float x, float y, float width, float height)
31 : this()
32 {
33 TopLeft = new Vector2(x, y);
34 TopRight = new Vector2(x + width, y);
35 BottomLeft = new Vector2(x, y + height);
36 BottomRight = new Vector2(x + width, y + height);
37 }
38
39 public static implicit operator Quad(RectangleI r) => FromRectangle(r);
40 public static implicit operator Quad(RectangleF r) => FromRectangle(r);
41
42 public static Quad FromRectangle(RectangleF rectangle) =>
43 new Quad(new Vector2(rectangle.Left, rectangle.Top),
44 new Vector2(rectangle.Right, rectangle.Top),
45 new Vector2(rectangle.Left, rectangle.Bottom),
46 new Vector2(rectangle.Right, rectangle.Bottom));
47
48 public static Quad operator *(Quad r, Matrix3 m) =>
49 new Quad(
50 Vector2Extensions.Transform(r.TopLeft, m),
51 Vector2Extensions.Transform(r.TopRight, m),
52 Vector2Extensions.Transform(r.BottomLeft, m),
53 Vector2Extensions.Transform(r.BottomRight, m));
54
55 public Matrix2 BasisTransform
56 {
57 get
58 {
59 Vector2 row0 = TopRight - TopLeft;
60 Vector2 row1 = BottomLeft - TopLeft;
61
62 if (row0 != Vector2.Zero)
63 row0 /= row0.LengthSquared;
64
65 if (row1 != Vector2.Zero)
66 row1 /= row1.LengthSquared;
67
68 return new Matrix2(
69 row0.X, row0.Y,
70 row1.X, row1.Y);
71 }
72 }
73
74 public Vector2 Centre => (TopLeft + TopRight + BottomLeft + BottomRight) / 4;
75 public Vector2 Size => new Vector2(Width, Height);
76
77 public float Width => Vector2Extensions.Distance(TopLeft, TopRight);
78 public float Height => Vector2Extensions.Distance(TopLeft, BottomLeft);
79
80 public RectangleI AABB
81 {
82 get
83 {
84 int xMin = (int)Math.Floor(Math.Min(TopLeft.X, Math.Min(TopRight.X, Math.Min(BottomLeft.X, BottomRight.X))));
85 int yMin = (int)Math.Floor(Math.Min(TopLeft.Y, Math.Min(TopRight.Y, Math.Min(BottomLeft.Y, BottomRight.Y))));
86 int xMax = (int)Math.Ceiling(Math.Max(TopLeft.X, Math.Max(TopRight.X, Math.Max(BottomLeft.X, BottomRight.X))));
87 int yMax = (int)Math.Ceiling(Math.Max(TopLeft.Y, Math.Max(TopRight.Y, Math.Max(BottomLeft.Y, BottomRight.Y))));
88
89 return new RectangleI(xMin, yMin, xMax - xMin, yMax - yMin);
90 }
91 }
92
93 public RectangleF AABBFloat
94 {
95 get
96 {
97 float xMin = Math.Min(TopLeft.X, Math.Min(TopRight.X, Math.Min(BottomLeft.X, BottomRight.X)));
98 float yMin = Math.Min(TopLeft.Y, Math.Min(TopRight.Y, Math.Min(BottomLeft.Y, BottomRight.Y)));
99 float xMax = Math.Max(TopLeft.X, Math.Max(TopRight.X, Math.Max(BottomLeft.X, BottomRight.X)));
100 float yMax = Math.Max(TopLeft.Y, Math.Max(TopRight.Y, Math.Max(BottomLeft.Y, BottomRight.Y)));
101
102 return new RectangleF(xMin, yMin, xMax - xMin, yMax - yMin);
103 }
104 }
105
106 public ReadOnlySpan<Vector2> GetAxisVertices() => GetVertices();
107
108 public ReadOnlySpan<Vector2> GetVertices() => MemoryMarshal.CreateReadOnlySpan(ref Unsafe.AsRef(in TopLeft), 4);
109
110 public bool Contains(Vector2 pos) =>
111 new Triangle(BottomRight, BottomLeft, TopRight).Contains(pos) ||
112 new Triangle(TopLeft, TopRight, BottomLeft).Contains(pos);
113
114 /// <summary>
115 /// Computes the area of this <see cref="Quad"/>.
116 /// </summary>
117 /// <remarks>
118 /// If the quad is self-intersecting the area is interpreted as the sum of all positive and negative areas and not the "visible area" enclosed by the <see cref="Quad"/>.
119 /// </remarks>
120 public float Area => 0.5f * Math.Abs(Vector2Extensions.GetOrientation(GetVertices()));
121
122 public bool Equals(Quad other) =>
123 TopLeft == other.TopLeft &&
124 TopRight == other.TopRight &&
125 BottomLeft == other.BottomLeft &&
126 BottomRight == other.BottomRight;
127
128 public bool AlmostEquals(Quad other) =>
129 Precision.AlmostEquals(TopLeft.X, other.TopLeft.X) &&
130 Precision.AlmostEquals(TopLeft.Y, other.TopLeft.Y) &&
131 Precision.AlmostEquals(TopRight.X, other.TopRight.X) &&
132 Precision.AlmostEquals(TopRight.Y, other.TopRight.Y) &&
133 Precision.AlmostEquals(BottomLeft.X, other.BottomLeft.X) &&
134 Precision.AlmostEquals(BottomLeft.Y, other.BottomLeft.Y) &&
135 Precision.AlmostEquals(BottomRight.X, other.BottomRight.X) &&
136 Precision.AlmostEquals(BottomRight.Y, other.BottomRight.Y);
137
138 public override string ToString() => $"{TopLeft} {TopRight} {BottomLeft} {BottomRight}";
139 }
140}