2 using Microsoft.Xna.Framework;
4 using System.Collections.Generic;
5 using System.Collections.Immutable;
6 using System.Diagnostics;
10 using Color = Microsoft.Xna.Framework.Color;
14 static partial class ToolBox
27 public static bool PointIntersectsWithPolygon(Vector2 point, Vector2[] verts,
bool checkBoundingBox =
true)
33 float minX = verts[0].X;
34 float maxX = verts[0].X;
35 float minY = verts[0].Y;
36 float maxY = verts[0].Y;
38 foreach (var (vertX, vertY) in verts)
40 minX = Math.Min(vertX, minX);
41 maxX = Math.Max(vertX, maxX);
42 minY = Math.Min(vertY, minY);
43 maxY = Math.Max(vertY, maxY);
46 if (x < minX || x > maxX || y < minY || y > maxY ) {
return false; }
49 bool isInside =
false;
51 for (
int i = 0, j = verts.Length - 1; i < verts.Length; j = i++ )
53 if (verts[i].Y > y != verts[j].Y > y && x < (verts[j].X - verts[i].X) * (y - verts[i].Y) / (verts[j].Y - verts[i].Y) + verts[i].X )
62 public static Vector2 GetPolygonBoundingBoxSize(List<Vector2> verticess)
64 float minX = verticess[0].X;
65 float maxX = verticess[0].X;
66 float minY = verticess[0].Y;
67 float maxY = verticess[0].Y;
69 foreach (var (vertX, vertY) in verticess)
71 minX = Math.Min(vertX, minX);
72 maxX = Math.Max(vertX, maxX);
73 minY = Math.Min(vertY, minY);
74 maxY = Math.Max(vertY, maxY);
77 return new Vector2(maxX - minX, maxY - minY);
80 public static List<Vector2> ScalePolygon(List<Vector2> vertices, Vector2 scale)
82 List<Vector2> newVertices =
new List<Vector2>();
84 Vector2 center = GetPolygonCentroid(vertices);
86 foreach (Vector2 vert
in vertices)
88 Vector2 centerVector = vert - center;
89 Vector2 centerVectorScale = centerVector * scale;
90 Vector2 scaledVector = centerVectorScale + center;
91 newVertices.Add(scaledVector);
97 public static Vector2 GetPolygonCentroid(List<Vector2> poly)
99 float accumulatedArea = 0.0f;
100 float centerX = 0.0f;
101 float centerY = 0.0f;
103 for (
int i = 0, j = poly.Count - 1; i < poly.Count; j = i++)
105 float temp = poly[i].X * poly[j].Y - poly[j].X * poly[i].Y;
106 accumulatedArea += temp;
107 centerX += (poly[i].X + poly[j].X) * temp;
108 centerY += (poly[i].Y + poly[j].Y) * temp;
111 if (Math.Abs(accumulatedArea) < 1E-7f) {
return Vector2.Zero; }
113 accumulatedArea *= 3f;
114 return new Vector2(centerX / accumulatedArea, centerY / accumulatedArea);
117 public static List<Vector2> SnapVertices(List<Vector2> points,
int treshold = 1)
119 Stack<Vector2> toCheck =
new Stack<Vector2>();
120 List<Vector2> newPoints =
new List<Vector2>();
122 foreach (Vector2 point
in points)
127 while (toCheck.TryPop(out Vector2 point))
129 Vector2 newPoint =
new Vector2(point.X, point.Y);
130 foreach (Vector2 otherPoint
in toCheck.Concat(newPoints))
132 float diffX = Math.Abs(newPoint.X - otherPoint.X),
133 diffY = Math.Abs(newPoint.Y - otherPoint.Y);
135 if (diffX <= treshold)
137 newPoint.X = Math.Max(newPoint.X, otherPoint.X);
140 if (diffY <= treshold)
142 newPoint.Y = Math.Max(newPoint.Y, otherPoint.Y);
145 newPoints.Add(newPoint);
151 public static ImmutableArray<RectangleF> SnapRectangles(IEnumerable<RectangleF> rects,
int treshold = 1)
153 List<RectangleF> list =
new List<RectangleF>();
155 List<Vector2> points =
new List<Vector2>();
157 foreach (RectangleF rect
in rects)
159 points.Add(
new Vector2(rect.Left, rect.Top));
160 points.Add(
new Vector2(rect.Right, rect.Top));
161 points.Add(
new Vector2(rect.Right, rect.Bottom));
162 points.Add(
new Vector2(rect.Left, rect.Bottom));
165 points = SnapVertices(points, treshold);
167 for (
int i = 0; i < points.Count; i += 4)
169 Vector2 topLeft = points[i];
170 Vector2 bottomRight = points[i + 2];
172 list.Add(
new RectangleF(topLeft, bottomRight - topLeft));
175 return list.ToImmutableArray();
178 public static List<List<Vector2>> CombineRectanglesIntoShape(IEnumerable<RectangleF> rectangles)
180 List<Vector2> points =
181 (from point in rectangles.SelectMany(RectangleToPoints)
184 where g.Count() % 2 == 1
188 List<Vector2> sortedY = points.OrderBy(p => p.Y).ThenByDescending(p => p.X).ToList();
189 List<Vector2> sortedX = points.OrderBy(p => p.X).ThenByDescending(p => p.Y).ToList();
191 Dictionary<Vector2, Vector2> edgesH =
new Dictionary<Vector2, Vector2>();
192 Dictionary<Vector2, Vector2> edgesV =
new Dictionary<Vector2, Vector2>();
195 while (i < points.Count)
197 float currY = sortedY[i].Y;
199 while (i < points.Count && Math.Abs(sortedY[i].Y - currY) < 0.01f)
201 edgesH[sortedY[i]] = sortedY[i + 1];
202 edgesH[sortedY[i + 1]] = sortedY[i];
210 while (i < points.Count)
212 float currX = sortedX[i].X;
213 while (i < points.Count && Math.Abs(sortedX[i].X - currX) < 0.01f)
215 edgesV[sortedX[i]] = sortedX[i + 1];
216 edgesV[sortedX[i + 1]] = sortedX[i];
221 List<List<Vector2>> polygons =
new List<List<Vector2>>();
225 var (key, _) = edgesH.First();
226 List<(Vector2 Point,
int Direction)> polygon =
new List<(Vector2 Point,
int Direction)> { (key, 0) };
231 var (curr, direction) = polygon[^1];
235 Vector2 nextVertex = edgesV[curr];
237 polygon.Add((nextVertex, 1));
241 Vector2 nextVertex = edgesH[curr];
243 polygon.Add((nextVertex, 0));
246 if (polygon[^1] == polygon[0])
248 polygon.Remove(polygon[^1]);
253 List<Vector2> poly = polygon.Select(t => t.Point).ToList();
255 foreach (Vector2 vertex
in poly)
257 if (edgesH.ContainsKey(vertex))
259 edgesH.Remove(vertex);
262 if (edgesV.ContainsKey(vertex))
264 edgesV.Remove(vertex);
273 static IEnumerable<Vector2> RectangleToPoints(RectangleF rect)
275 (
float x1,
float y1,
float x2,
float y2) = (rect.Left, rect.Top, rect.Right, rect.Bottom);
276 Vector2[] pts = {
new Vector2(x1, y1),
new Vector2(x2, y1),
new Vector2(x2, y2),
new Vector2(x1, y2) };
282 public static Vector3 RgbToHLS(
this Color color)
284 return RgbToHLS(color.ToVector3());
288 public static Color HLSToRGB(Vector3 hls)
290 double h = hls.X, l = hls.Y, s = hls.Z;
293 if (l <= 0.5) p2 = l * (1 + s);
294 else p2 = l + s - l * s;
296 double p1 = 2 * l - p2;
297 double double_r, double_g, double_b;
306 double_r = QqhToRgb(p1, p2, h + 120);
307 double_g = QqhToRgb(p1, p2, h);
308 double_b = QqhToRgb(p1, p2, h - 120);
312 return new Color((
byte)(double_r * 255.0), (
byte)(double_g * 255.0), (
byte)(double_b * 255.0));
315 private static double QqhToRgb(
double q1,
double q2,
double hue)
317 if (hue > 360) hue -= 360;
318 else if (hue < 0) hue += 360;
320 if (hue < 60)
return q1 + (q2 - q1) * hue / 60;
321 if (hue < 180)
return q2;
322 if (hue < 240)
return q1 + (q2 - q1) * (240 - hue) / 60;
336 public static Vector3 RGBToHSV(Color color)
338 float r = color.R / 255f,
344 float min = Math.Min(r, Math.Min(g, b));
345 float max = Math.Max(r, Math.Max(g, b));
349 float delta = max - min;
359 return new Vector3(h, s, v);
362 if (MathUtils.NearlyEqual(r, max))
366 else if (MathUtils.NearlyEqual(g, max))
368 h = 2 + (b - r) / delta;
372 h = 4 + (r - g) / delta;
376 if (h < 0) { h += 360; }
378 return new Vector3(h, s, v);
382 public static Color Add(
this Color sourceColor, Color color)
385 sourceColor.R + color.R,
386 sourceColor.G + color.G,
387 sourceColor.B + color.B,
388 sourceColor.A + color.A);
391 public static Color Subtract(
this Color sourceColor, Color color)
394 sourceColor.R - color.R,
395 sourceColor.G - color.G,
396 sourceColor.B - color.B,
397 sourceColor.A - color.A);
400 public static LocalizedString LimitString(LocalizedString str, GUIFont font,
int maxWidth)
402 return new LimitLString(str, font, maxWidth);
405 public static LocalizedString LimitString(
string str, GUIFont font,
int maxWidth)
406 => LimitString((LocalizedString)str, font, maxWidth);
408 public static string LimitString(
string str, ScalableFont font,
int maxWidth)
410 if (maxWidth <= 0 ||
string.IsNullOrWhiteSpace(str)) {
return ""; }
412 float currWidth = font.MeasureString(
"...").X;
413 for (
int i = 0; i < str.Length; i++)
415 currWidth += font.MeasureString(str[i].ToString()).X;
417 if (currWidth > maxWidth)
419 return str.Substring(0, Math.Max(i - 2, 1)) +
"...";
430 public static string LimitStringHeight(
string str, ScalableFont font,
int maxHeight)
432 if (maxHeight <= 0 ||
string.IsNullOrWhiteSpace(str)) {
return string.Empty; }
434 float currHeight = font.MeasureString(
"...").Y;
435 var lines = str.Split(
'\n');
437 var sb =
new StringBuilder();
438 foreach (
string line
in lines)
440 var (lineX, lineY) = font.MeasureString(line);
442 if (currHeight > maxHeight)
444 var modifiedLine = line;
445 while (font.MeasureString($
"{modifiedLine}...").X > lineX)
447 if (modifiedLine.Length == 0) {
break; }
448 modifiedLine = modifiedLine[..^1];
450 sb.AppendLine($
"{modifiedLine}...");
451 return sb.ToString();
459 public static Color GradientLerp(
float t, params Color[] gradient)
461 if (!MathUtils.IsValid(t)) {
return Color.Purple; }
462 System.Diagnostics.Debug.Assert(gradient.Length > 0,
"Empty color array passed to the GradientLerp method");
463 if (gradient.Length == 0)
466 DebugConsole.ThrowError(
"Empty color array passed to the GradientLerp method.\n" + Environment.StackTrace.CleanupStackTrace());
468 GameAnalyticsManager.AddErrorEventOnce(
"ToolBox.GradientLerp:EmptyColorArray", GameAnalyticsManager.ErrorSeverity.Error,
469 "Empty color array passed to the GradientLerp method.\n" + Environment.StackTrace.CleanupStackTrace());
473 if (t <= 0.0f || !MathUtils.IsValid(t)) {
return gradient[0]; }
474 if (t >= 1.0f) {
return gradient[gradient.Length - 1]; }
476 float scaledT = t * (gradient.Length - 1);
478 return Color.Lerp(gradient[(
int)scaledT], gradient[(int)Math.Min(scaledT + 1, gradient.Length - 1)], (scaledT - (int)scaledT));
481 public static LocalizedString WrapText(LocalizedString text,
float lineLength, GUIFont font,
float textScale = 1.0f)
483 return new WrappedLString(text, lineLength, font, textScale);
486 public static string WrapText(
string text,
float lineLength, ScalableFont font,
float textScale = 1.0f)
487 => font.WrapText(text, lineLength / textScale);
489 public static bool VersionNewerIgnoreRevision(Version a, Version b)
491 if (b.Major > a.Major) {
return true; }
492 if (b.Major < a.Major) {
return false; }
493 if (b.Minor > a.Minor) {
return true; }
494 if (b.Minor < a.Minor) {
return false; }
495 if (b.Build > a.Build) {
return true; }
496 if (b.Build < a.Build) {
return false; }
500 public static void OpenFileWithShell(
string filename)
502 ProcessStartInfo startInfo =
new ProcessStartInfo()
505 UseShellExecute =
true
507 Process.Start(startInfo);
510 public static Vector2 PaddingSizeParentRelative(RectTransform parent,
float padding)
512 var (sizeX, sizeY) = parent.NonScaledSize.ToVector2();
514 float higher = sizeX,
516 bool swap = lower > higher;
517 if (swap) { (higher, lower) = (lower, higher); }
519 float diffY = lower - lower * padding;
521 float paddingX = (higher - diffY) / higher,
524 if (swap) { (paddingX, paddingY) = (paddingY, paddingX); }
526 return new Vector2(paddingX, paddingY);
529 public static string ColorSectionOfString(
string text,
int start,
int length, Color color)
531 int end = start + length;
533 if (start < 0 || length < 0 || end > text.Length)
535 throw new ArgumentOutOfRangeException($
"Invalid start ({start}) or length ({length}) for text \"{text}\".");
538 string stichedString =
string.Empty;
542 stichedString += text[..start];
546 stichedString += ColorString(text[start..end], color);
548 if (end < text.Length)
550 stichedString += text[end..];
553 return stichedString;
555 static string ColorString(
string text, Color color) => $
"‖color:{color.ToStringHex()}‖{text}‖end‖";
566 public static byte[] HexStringToBytes(
string raw)
568 string value =
string.Join(
string.Empty, raw.Split(
" "));
569 List<byte> bytes =
new List<byte>();
570 for (
int i = 0; i < value.Length; i += 2)
572 string hex = value.Substring(i, 2);
573 byte b = Convert.ToByte(hex, 16);
576 static bool IsHexChar(
char c) => c is
582 return bytes.ToArray();