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 osu.Framework.Utils;
7using osuTK;
8
9namespace osu.Framework.Graphics.Primitives
10{
11 /// <summary>
12 /// Represents a single line segment.
13 /// </summary>
14 public readonly struct Line
15 {
16 /// <summary>
17 /// Begin point of the line.
18 /// </summary>
19 public readonly Vector2 StartPoint;
20
21 /// <summary>
22 /// End point of the line.
23 /// </summary>
24 public readonly Vector2 EndPoint;
25
26 /// <summary>
27 /// The length of the line.
28 /// </summary>
29 public float Rho => (EndPoint - StartPoint).Length;
30
31 /// <summary>
32 /// The direction of the second point from the first.
33 /// </summary>
34 public float Theta => MathF.Atan2(EndPoint.Y - StartPoint.Y, EndPoint.X - StartPoint.X);
35
36 /// <summary>
37 /// The direction of this <see cref="Line"/>.
38 /// </summary>
39 public Vector2 Direction => EndPoint - StartPoint;
40
41 /// <summary>
42 /// The normalized direction of this <see cref="Line"/>.
43 /// </summary>
44 public Vector2 DirectionNormalized => Direction.Normalized();
45
46 public Vector2 OrthogonalDirection
47 {
48 get
49 {
50 Vector2 dir = DirectionNormalized;
51 return new Vector2(-dir.Y, dir.X);
52 }
53 }
54
55 public Line(Vector2 p1, Vector2 p2)
56 {
57 StartPoint = p1;
58 EndPoint = p2;
59 }
60
61 /// <summary>
62 /// Computes a position along this line.
63 /// </summary>
64 /// <param name="t">A parameter representing the position along the line to compute. 0 yields the start point and 1 yields the end point.</param>
65 /// <returns>The position along the line.</returns>
66 [MethodImpl(MethodImplOptions.AggressiveInlining)]
67 public Vector2 At(float t) => new Vector2(StartPoint.X + (EndPoint.X - StartPoint.X) * t, StartPoint.Y + (EndPoint.Y - StartPoint.Y) * t);
68
69 /// <summary>
70 /// Intersects this line with another.
71 /// </summary>
72 /// <param name="other">The line to intersect with.</param>
73 /// <returns>Whether the two lines intersect and, if so, the distance along this line at which the intersection occurs.
74 /// An intersection may occur even if the two lines don't touch, at which point the parameter will be outside the [0, 1] range.
75 /// To compute the point of intersection, <see cref="At"/>.</returns>
76 [MethodImpl(MethodImplOptions.AggressiveInlining)]
77 public (bool success, float distance) IntersectWith(in Line other)
78 {
79 bool success = TryIntersectWith(other, out var distance);
80 return (success, distance);
81 }
82
83 /// <summary>
84 /// Intersects this line with another.
85 /// </summary>
86 /// <param name="other">The line to intersect with.</param>
87 /// <param name="distance">The distance along this line at which the intersection occurs. To compute the point of intersection, <see cref="At"/>.</param>
88 /// <returns>Whether the two lines intersect. An intersection may occur even if the two lines don't touch, at which point the parameter will be outside the [0, 1] range.</returns>
89 [MethodImpl(MethodImplOptions.AggressiveInlining)]
90 public bool TryIntersectWith(in Line other, out float distance)
91 {
92 var startPoint = other.StartPoint;
93 var endPoint = other.EndPoint;
94
95 return TryIntersectWith(ref startPoint, ref endPoint, out distance);
96 }
97
98 /// <summary>
99 /// Intersects this line with another.
100 /// </summary>
101 /// <param name="otherStart">The start point of the other line to intersect with.</param>
102 /// <param name="otherEnd">The end point of the other line to intersect with.</param>
103 /// <param name="distance">The distance along this line at which the intersection occurs. To compute the point of intersection, <see cref="At"/>.</param>
104 /// <returns>Whether the two lines intersect. An intersection may occur even if the two lines don't touch, at which point the parameter will be outside the [0, 1] range.</returns>
105 [MethodImpl(MethodImplOptions.AggressiveInlining)]
106 public bool TryIntersectWith(ref Vector2 otherStart, ref Vector2 otherEnd, out float distance)
107 {
108 float otherYDist = otherEnd.Y - otherStart.Y;
109 float otherXDist = otherEnd.X - otherStart.X;
110
111 float denom = (EndPoint.X - StartPoint.X) * otherYDist - (EndPoint.Y - StartPoint.Y) * otherXDist;
112
113 if (Precision.AlmostEquals(denom, 0))
114 {
115 distance = 0;
116 return false;
117 }
118
119 distance = ((otherStart.X - StartPoint.X) * otherYDist - (otherStart.Y - StartPoint.Y) * otherXDist) / denom;
120 return true;
121 }
122
123 /// <summary>
124 /// Distance squared from an arbitrary point p to this line.
125 /// </summary>
126 public float DistanceSquaredToPoint(Vector2 p) => Vector2Extensions.DistanceSquared(p, ClosestPointTo(p));
127
128 /// <summary>
129 /// Distance from an arbitrary point to this line.
130 /// </summary>
131 public float DistanceToPoint(Vector2 p) => Vector2Extensions.Distance(p, ClosestPointTo(p));
132
133 /// <summary>
134 /// Finds the point closest to the given point on this line.
135 /// </summary>
136 /// <remarks>
137 /// See http://geometryalgorithms.com/Archive/algorithm_0102/algorithm_0102.htm, near the bottom.
138 /// </remarks>
139 public Vector2 ClosestPointTo(Vector2 p)
140 {
141 Vector2 v = EndPoint - StartPoint; // Vector from line's p1 to p2
142 Vector2 w = p - StartPoint; // Vector from line's p1 to p
143
144 // See if p is closer to p1 than to the segment
145 float c1 = Vector2.Dot(w, v);
146 if (c1 <= 0)
147 return StartPoint;
148
149 // See if p is closer to p2 than to the segment
150 float c2 = Vector2.Dot(v, v);
151 if (c2 <= c1)
152 return EndPoint;
153
154 // p is closest to point pB, between p1 and p2
155 float b = c1 / c2;
156 Vector2 pB = StartPoint + b * v;
157
158 return pB;
159 }
160
161 public Matrix4 WorldMatrix() => Matrix4.CreateRotationZ(Theta) * Matrix4.CreateTranslation(StartPoint.X, StartPoint.Y, 0);
162
163 /// <summary>
164 /// It's the end of the world as we know it
165 /// </summary>
166 public Matrix4 EndWorldMatrix() => Matrix4.CreateRotationZ(Theta) * Matrix4.CreateTranslation(EndPoint.X, EndPoint.Y, 0);
167
168 public override string ToString() => $"{StartPoint} -> {EndPoint}";
169 }
170}