Client LuaCsForBarotrauma
BarotraumaClient/ClientSource/Utils/ToolBox.cs
1 #nullable enable
2 using Microsoft.Xna.Framework;
3 using System;
4 using System.Collections.Generic;
5 using System.Collections.Immutable;
6 using System.Diagnostics;
7 using System.Linq;
8 using System.Text;
10 using Color = Microsoft.Xna.Framework.Color;
11 
12 namespace Barotrauma
13 {
14  static partial class ToolBox
15  {
27  public static bool PointIntersectsWithPolygon(Vector2 point, Vector2[] verts, bool checkBoundingBox = true)
28  {
29  var (x, y) = point;
30 
31  if (checkBoundingBox)
32  {
33  float minX = verts[0].X;
34  float maxX = verts[0].X;
35  float minY = verts[0].Y;
36  float maxY = verts[0].Y;
37 
38  foreach (var (vertX, vertY) in verts)
39  {
40  minX = Math.Min(vertX, minX);
41  maxX = Math.Max(vertX, maxX);
42  minY = Math.Min(vertY, minY);
43  maxY = Math.Max(vertY, maxY);
44  }
45 
46  if (x < minX || x > maxX || y < minY || y > maxY ) { return false; }
47  }
48 
49  bool isInside = false;
50 
51  for (int i = 0, j = verts.Length - 1; i < verts.Length; j = i++ )
52  {
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 )
54  {
55  isInside = !isInside;
56  }
57  }
58 
59  return isInside;
60  }
61 
62  public static Vector2 GetPolygonBoundingBoxSize(List<Vector2> verticess)
63  {
64  float minX = verticess[0].X;
65  float maxX = verticess[0].X;
66  float minY = verticess[0].Y;
67  float maxY = verticess[0].Y;
68 
69  foreach (var (vertX, vertY) in verticess)
70  {
71  minX = Math.Min(vertX, minX);
72  maxX = Math.Max(vertX, maxX);
73  minY = Math.Min(vertY, minY);
74  maxY = Math.Max(vertY, maxY);
75  }
76 
77  return new Vector2(maxX - minX, maxY - minY);
78  }
79 
80  public static List<Vector2> ScalePolygon(List<Vector2> vertices, Vector2 scale)
81  {
82  List<Vector2> newVertices = new List<Vector2>();
83 
84  Vector2 center = GetPolygonCentroid(vertices);
85 
86  foreach (Vector2 vert in vertices)
87  {
88  Vector2 centerVector = vert - center;
89  Vector2 centerVectorScale = centerVector * scale;
90  Vector2 scaledVector = centerVectorScale + center;
91  newVertices.Add(scaledVector);
92  }
93 
94  return newVertices;
95  }
96 
97  public static Vector2 GetPolygonCentroid(List<Vector2> poly)
98  {
99  float accumulatedArea = 0.0f;
100  float centerX = 0.0f;
101  float centerY = 0.0f;
102 
103  for (int i = 0, j = poly.Count - 1; i < poly.Count; j = i++)
104  {
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;
109  }
110 
111  if (Math.Abs(accumulatedArea) < 1E-7f) { return Vector2.Zero; } // Avoid division by zero
112 
113  accumulatedArea *= 3f;
114  return new Vector2(centerX / accumulatedArea, centerY / accumulatedArea);
115  }
116 
117  public static List<Vector2> SnapVertices(List<Vector2> points, int treshold = 1)
118  {
119  Stack<Vector2> toCheck = new Stack<Vector2>();
120  List<Vector2> newPoints = new List<Vector2>();
121 
122  foreach (Vector2 point in points)
123  {
124  toCheck.Push(point);
125  }
126 
127  while (toCheck.TryPop(out Vector2 point))
128  {
129  Vector2 newPoint = new Vector2(point.X, point.Y);
130  foreach (Vector2 otherPoint in toCheck.Concat(newPoints))
131  {
132  float diffX = Math.Abs(newPoint.X - otherPoint.X),
133  diffY = Math.Abs(newPoint.Y - otherPoint.Y);
134 
135  if (diffX <= treshold)
136  {
137  newPoint.X = Math.Max(newPoint.X, otherPoint.X);
138  }
139 
140  if (diffY <= treshold)
141  {
142  newPoint.Y = Math.Max(newPoint.Y, otherPoint.Y);
143  }
144  }
145  newPoints.Add(newPoint);
146  }
147 
148  return newPoints;
149  }
150 
151  public static ImmutableArray<RectangleF> SnapRectangles(IEnumerable<RectangleF> rects, int treshold = 1)
152  {
153  List<RectangleF> list = new List<RectangleF>();
154 
155  List<Vector2> points = new List<Vector2>();
156 
157  foreach (RectangleF rect in rects)
158  {
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));
163  }
164 
165  points = SnapVertices(points, treshold);
166 
167  for (int i = 0; i < points.Count; i += 4)
168  {
169  Vector2 topLeft = points[i];
170  Vector2 bottomRight = points[i + 2];
171 
172  list.Add(new RectangleF(topLeft, bottomRight - topLeft));
173  }
174 
175  return list.ToImmutableArray();
176  }
177 
178  public static List<List<Vector2>> CombineRectanglesIntoShape(IEnumerable<RectangleF> rectangles)
179  {
180  List<Vector2> points =
181  (from point in rectangles.SelectMany(RectangleToPoints)
182  group point by point
183  into g
184  where g.Count() % 2 == 1
185  select g.Key)
186  .ToList();
187 
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();
190 
191  Dictionary<Vector2, Vector2> edgesH = new Dictionary<Vector2, Vector2>();
192  Dictionary<Vector2, Vector2> edgesV = new Dictionary<Vector2, Vector2>();
193 
194  int i = 0;
195  while (i < points.Count)
196  {
197  float currY = sortedY[i].Y;
198 
199  while (i < points.Count && Math.Abs(sortedY[i].Y - currY) < 0.01f)
200  {
201  edgesH[sortedY[i]] = sortedY[i + 1];
202  edgesH[sortedY[i + 1]] = sortedY[i];
203  i += 2;
204  }
205 
206  }
207 
208  i = 0;
209 
210  while (i < points.Count)
211  {
212  float currX = sortedX[i].X;
213  while (i < points.Count && Math.Abs(sortedX[i].X - currX) < 0.01f)
214  {
215  edgesV[sortedX[i]] = sortedX[i + 1];
216  edgesV[sortedX[i + 1]] = sortedX[i];
217  i += 2;
218  }
219  }
220 
221  List<List<Vector2>> polygons = new List<List<Vector2>>();
222 
223  while (edgesH.Any())
224  {
225  var (key, _) = edgesH.First();
226  List<(Vector2 Point, int Direction)> polygon = new List<(Vector2 Point, int Direction)> { (key, 0) };
227  edgesH.Remove(key);
228 
229  while (true)
230  {
231  var (curr, direction) = polygon[^1];
232 
233  if (direction == 0)
234  {
235  Vector2 nextVertex = edgesV[curr];
236  edgesV.Remove(curr);
237  polygon.Add((nextVertex, 1));
238  }
239  else
240  {
241  Vector2 nextVertex = edgesH[curr];
242  edgesH.Remove(curr);
243  polygon.Add((nextVertex, 0));
244  }
245 
246  if (polygon[^1] == polygon[0])
247  {
248  polygon.Remove(polygon[^1]);
249  break;
250  }
251  }
252 
253  List<Vector2> poly = polygon.Select(t => t.Point).ToList();
254 
255  foreach (Vector2 vertex in poly)
256  {
257  if (edgesH.ContainsKey(vertex))
258  {
259  edgesH.Remove(vertex);
260  }
261 
262  if (edgesV.ContainsKey(vertex))
263  {
264  edgesV.Remove(vertex);
265  }
266  }
267 
268  polygons.Add(poly);
269  }
270 
271  return polygons;
272 
273  static IEnumerable<Vector2> RectangleToPoints(RectangleF rect)
274  {
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) };
277  return pts;
278  }
279  }
280 
281  // Convert an RGB value into an HLS value.
282  public static Vector3 RgbToHLS(this Color color)
283  {
284  return RgbToHLS(color.ToVector3());
285  }
286 
287  // Convert an HLS value into an RGB value.
288  public static Color HLSToRGB(Vector3 hls)
289  {
290  double h = hls.X, l = hls.Y, s = hls.Z;
291 
292  double p2;
293  if (l <= 0.5) p2 = l * (1 + s);
294  else p2 = l + s - l * s;
295 
296  double p1 = 2 * l - p2;
297  double double_r, double_g, double_b;
298  if (s == 0)
299  {
300  double_r = l;
301  double_g = l;
302  double_b = l;
303  }
304  else
305  {
306  double_r = QqhToRgb(p1, p2, h + 120);
307  double_g = QqhToRgb(p1, p2, h);
308  double_b = QqhToRgb(p1, p2, h - 120);
309  }
310 
311  // Convert RGB to the 0 to 255 range.
312  return new Color((byte)(double_r * 255.0), (byte)(double_g * 255.0), (byte)(double_b * 255.0));
313  }
314 
315  private static double QqhToRgb(double q1, double q2, double hue)
316  {
317  if (hue > 360) hue -= 360;
318  else if (hue < 0) hue += 360;
319 
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;
323  return q1;
324  }
325 
336  public static Vector3 RGBToHSV(Color color)
337  {
338  float r = color.R / 255f,
339  g = color.G / 255f,
340  b = color.B / 255f;
341 
342  float h, s;
343 
344  float min = Math.Min(r, Math.Min(g, b));
345  float max = Math.Max(r, Math.Max(g, b));
346 
347  float v = max;
348 
349  float delta = max - min;
350 
351  if (max != 0)
352  {
353  s = delta / max;
354  }
355  else
356  {
357  s = 0;
358  h = -1;
359  return new Vector3(h, s, v);
360  }
361 
362  if (MathUtils.NearlyEqual(r, max))
363  {
364  h = (g - b) / delta;
365  }
366  else if (MathUtils.NearlyEqual(g, max))
367  {
368  h = 2 + (b - r) / delta;
369  }
370  else
371  {
372  h = 4 + (r - g) / delta;
373  }
374 
375  h *= 60;
376  if (h < 0) { h += 360; }
377 
378  return new Vector3(h, s, v);
379  }
380 
381 
382  public static Color Add(this Color sourceColor, Color color)
383  {
384  return new Color(
385  sourceColor.R + color.R,
386  sourceColor.G + color.G,
387  sourceColor.B + color.B,
388  sourceColor.A + color.A);
389  }
390 
391  public static Color Subtract(this Color sourceColor, Color color)
392  {
393  return new Color(
394  sourceColor.R - color.R,
395  sourceColor.G - color.G,
396  sourceColor.B - color.B,
397  sourceColor.A - color.A);
398  }
399 
400  public static LocalizedString LimitString(LocalizedString str, GUIFont font, int maxWidth)
401  {
402  return new LimitLString(str, font, maxWidth);
403  }
404 
405  public static LocalizedString LimitString(string str, GUIFont font, int maxWidth)
406  => LimitString((LocalizedString)str, font, maxWidth);
407 
408  public static string LimitString(string str, ScalableFont font, int maxWidth)
409  {
410  if (maxWidth <= 0 || string.IsNullOrWhiteSpace(str)) { return ""; }
411 
412  float currWidth = font.MeasureString("...").X;
413  for (int i = 0; i < str.Length; i++)
414  {
415  currWidth += font.MeasureString(str[i].ToString()).X;
416 
417  if (currWidth > maxWidth)
418  {
419  return str.Substring(0, Math.Max(i - 2, 1)) + "...";
420  }
421  }
422 
423  return str;
424  }
425 
430  public static string LimitStringHeight(string str, ScalableFont font, int maxHeight)
431  {
432  if (maxHeight <= 0 || string.IsNullOrWhiteSpace(str)) { return string.Empty; }
433 
434  float currHeight = font.MeasureString("...").Y;
435  var lines = str.Split('\n');
436 
437  var sb = new StringBuilder();
438  foreach (string line in lines)
439  {
440  var (lineX, lineY) = font.MeasureString(line);
441  currHeight += lineY;
442  if (currHeight > maxHeight)
443  {
444  var modifiedLine = line;
445  while (font.MeasureString($"{modifiedLine}...").X > lineX)
446  {
447  if (modifiedLine.Length == 0) { break; }
448  modifiedLine = modifiedLine[..^1];
449  }
450  sb.AppendLine($"{modifiedLine}...");
451  return sb.ToString();
452  }
453  sb.AppendLine(line);
454  }
455 
456  return str;
457  }
458 
459  public static Color GradientLerp(float t, params Color[] gradient)
460  {
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)
464  {
465 #if DEBUG
466  DebugConsole.ThrowError("Empty color array passed to the GradientLerp method.\n" + Environment.StackTrace.CleanupStackTrace());
467 #endif
468  GameAnalyticsManager.AddErrorEventOnce("ToolBox.GradientLerp:EmptyColorArray", GameAnalyticsManager.ErrorSeverity.Error,
469  "Empty color array passed to the GradientLerp method.\n" + Environment.StackTrace.CleanupStackTrace());
470  return Color.Black;
471  }
472 
473  if (t <= 0.0f || !MathUtils.IsValid(t)) { return gradient[0]; }
474  if (t >= 1.0f) { return gradient[gradient.Length - 1]; }
475 
476  float scaledT = t * (gradient.Length - 1);
477 
478  return Color.Lerp(gradient[(int)scaledT], gradient[(int)Math.Min(scaledT + 1, gradient.Length - 1)], (scaledT - (int)scaledT));
479  }
480 
481  public static LocalizedString WrapText(LocalizedString text, float lineLength, GUIFont font, float textScale = 1.0f)
482  {
483  return new WrappedLString(text, lineLength, font, textScale);
484  }
485 
486  public static string WrapText(string text, float lineLength, ScalableFont font, float textScale = 1.0f)
487  => font.WrapText(text, lineLength / textScale);
488 
489  public static bool VersionNewerIgnoreRevision(Version a, Version b)
490  {
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; }
497  return false;
498  }
499 
500  public static void OpenFileWithShell(string filename)
501  {
502  ProcessStartInfo startInfo = new ProcessStartInfo()
503  {
504  FileName = filename,
505  UseShellExecute = true
506  };
507  Process.Start(startInfo);
508  }
509 
510  public static Vector2 PaddingSizeParentRelative(RectTransform parent, float padding)
511  {
512  var (sizeX, sizeY) = parent.NonScaledSize.ToVector2();
513 
514  float higher = sizeX,
515  lower = sizeY;
516  bool swap = lower > higher;
517  if (swap) { (higher, lower) = (lower, higher); }
518 
519  float diffY = lower - lower * padding;
520 
521  float paddingX = (higher - diffY) / higher,
522  paddingY = padding;
523 
524  if (swap) { (paddingX, paddingY) = (paddingY, paddingX); }
525 
526  return new Vector2(paddingX, paddingY);
527  }
528 
529  public static string ColorSectionOfString(string text, int start, int length, Color color)
530  {
531  int end = start + length;
532 
533  if (start < 0 || length < 0 || end > text.Length)
534  {
535  throw new ArgumentOutOfRangeException($"Invalid start ({start}) or length ({length}) for text \"{text}\".");
536  }
537 
538  string stichedString = string.Empty;
539 
540  if (start > 0)
541  {
542  stichedString += text[..start];
543  }
544 
545  // this is the highlighted part
546  stichedString += ColorString(text[start..end], color);
547 
548  if (end < text.Length)
549  {
550  stichedString += text[end..];
551  }
552 
553  return stichedString;
554 
555  static string ColorString(string text, Color color) => $"‖color:{color.ToStringHex()}‖{text}‖end‖";
556  }
557 
566  public static byte[] HexStringToBytes(string raw)
567  {
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)
571  {
572  string hex = value.Substring(i, 2);
573  byte b = Convert.ToByte(hex, 16);
574  bytes.Add(b);
575 
576  static bool IsHexChar(char c) => c is
577  >= '0' and <= '9' or
578  >= 'A' and <= 'F' or
579  >= 'a' and <= 'f';
580  }
581 
582  return bytes.ToArray();
583  }
584  }
585 }