You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1645 lines
70 KiB
C#
1645 lines
70 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.IO.Compression;
|
|
using System.Linq;
|
|
using System.Net;
|
|
using System.Reflection;
|
|
using System.Runtime.Serialization.Formatters.Binary;
|
|
using System.Security.Cryptography;
|
|
using System.Text;
|
|
using System.Text.RegularExpressions;
|
|
using System.Xml;
|
|
using System.Xml.Linq;
|
|
|
|
namespace Otter {
|
|
/// <summary>
|
|
/// Main utility function class. Various useful functions for 2d game development and Otter stuff.
|
|
/// </summary>
|
|
public static class Util {
|
|
|
|
#region Constants
|
|
|
|
/// <summary>
|
|
/// Right
|
|
/// </summary>
|
|
public const float RIGHT = 0;
|
|
|
|
/// <summary>
|
|
/// Up
|
|
/// </summary>
|
|
public const float UP = (float)Math.PI * -.5f;
|
|
|
|
/// <summary>
|
|
/// Left
|
|
/// </summary>
|
|
public const float LEFT = (float)Math.PI;
|
|
|
|
/// <summary>
|
|
/// Down
|
|
/// </summary>
|
|
public const float DOWN = (float)Math.PI * .5f;
|
|
|
|
/// <summary>
|
|
/// Up Right
|
|
/// </summary>
|
|
public const float UP_RIGHT = (float)Math.PI * -.25f;
|
|
|
|
/// <summary>
|
|
/// Up Left
|
|
/// </summary>
|
|
public const float UP_LEFT = (float)Math.PI * -.75f;
|
|
|
|
/// <summary>
|
|
/// Down Right
|
|
/// </summary>
|
|
public const float DOWN_RIGHT = (float)Math.PI * .25f;
|
|
|
|
/// <summary>
|
|
/// Down Left
|
|
/// </summary>
|
|
public const float DOWN_LEFT = (float)Math.PI * .75f;
|
|
|
|
/// <summary>
|
|
/// Degrees to Radians
|
|
/// </summary>
|
|
public const float DEG_TO_RAD = (float)Math.PI / 180f;
|
|
|
|
/// <summary>
|
|
/// Radians to Degrees
|
|
/// </summary>
|
|
public const float RAD_TO_DEG = 180f / (float)Math.PI;
|
|
|
|
private const string HEX = "0123456789ABCDEF";
|
|
|
|
#endregion
|
|
|
|
#region Static Methods
|
|
|
|
/// <summary>
|
|
/// A shortcut function to send text to the debugger log.
|
|
/// </summary>
|
|
/// <param name="str">The string to send.</param>
|
|
public static void Log(object str) {
|
|
if (Debugger.Instance == null) return;
|
|
Debugger.Instance.Log(str);
|
|
}
|
|
|
|
/// <summary>
|
|
/// A shortcut function to send text to the debugger log.
|
|
/// </summary>
|
|
/// <param name="tag">The tag to log with.</param>
|
|
/// <param name="str">The string to send.</param>
|
|
public static void LogTag(string tag, object str) {
|
|
if (Debugger.Instance == null) return;
|
|
Debugger.Instance.Log(tag, str);
|
|
}
|
|
|
|
public static void LogTag(string tag, string str, params object[] obj) {
|
|
if (Debugger.Instance == null) return;
|
|
Debugger.Instance.Log(tag, string.Format(str, obj));
|
|
}
|
|
|
|
public static void Log(string str, params object[] obj) {
|
|
if (Debugger.Instance == null) return;
|
|
Debugger.Instance.Log("", string.Format(str, obj));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Summon the Debugger.
|
|
/// </summary>
|
|
public static void ShowDebugger() {
|
|
if (Debugger.Instance == null) return;
|
|
Debugger.Instance.Summon();
|
|
}
|
|
|
|
/// <summary>
|
|
/// A shortcut function to watch a value in the debugger. This must be called every update to
|
|
/// keep the value updated (not an automatic watch.)
|
|
/// </summary>
|
|
/// <param name="str">The name of the value.</param>
|
|
/// <param name="obj">The value.</param>
|
|
public static void Watch(string str, object obj) {
|
|
if (Debugger.Instance == null) return;
|
|
Debugger.Instance.Watch(str, obj);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Interpolate between two values.
|
|
/// </summary>
|
|
/// <param name="a">The first value.</param>
|
|
/// <param name="b">The second value.</param>
|
|
/// <param name="t">The progress of the interpolation.</param>
|
|
/// <returns>The interpolated value.</returns>
|
|
public static float Lerp(float a, float b, float t = 1) {
|
|
return a + (b - a) * t;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Interpolate through a set of numbers.
|
|
/// </summary>
|
|
/// <param name="amount">The amount of completion of the lerp. (0 - 1)</param>
|
|
/// <param name="numbers">The numbers to interpolate through.</param>
|
|
/// <returns>The interpolated number.</returns>
|
|
public static float LerpSet(float amount, params float[] numbers) {
|
|
if (amount <= 0) return numbers[0];
|
|
if (amount >= 1) return numbers[numbers.Length - 1];
|
|
|
|
int fromIndex = (int)Util.ScaleClamp(amount, 0, 1, 0, numbers.Length - 1);
|
|
int toIndex = fromIndex + 1;
|
|
|
|
float length = 1f / (numbers.Length - 1);
|
|
float lerp = Util.ScaleClamp(amount % length, 0, length, 0, 1);
|
|
|
|
// This is a fix for odd numbered color amounts. When fromIndex was
|
|
// odd, lerp would evaluate to 1 when it should be 0.
|
|
if (lerp >= 0.9999f && fromIndex % 2 == 1) {
|
|
lerp = 0;
|
|
}
|
|
|
|
return Lerp(numbers[fromIndex], numbers[toIndex], lerp);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Interpolate through a looping set of numbers.
|
|
/// </summary>
|
|
/// <param name="amount">The amount of completion of the lerp. (0 - 1)</param>
|
|
/// <param name="numbers">The numbers to interpolate through.</param>
|
|
/// <returns>The interpolated number.</returns>
|
|
public static float LerpSetLoop(float amount, params float[] numbers) {
|
|
//convert numbers to looping set
|
|
|
|
List<float> set = new List<float>();
|
|
List<float> numberSet = new List<float>(numbers);
|
|
numberSet.Add(numbers[0]);
|
|
|
|
for (var i = 0; i < numberSet.Count; i++) {
|
|
var current = numberSet[i];
|
|
set.Add(current);
|
|
|
|
if (i + 1 < numberSet.Count) {
|
|
var next = numberSet[i + 1];
|
|
var nextSet = (current + next) / 2f;
|
|
set.Add(nextSet);
|
|
}
|
|
}
|
|
|
|
return LerpSet(amount, set.ToArray());
|
|
}
|
|
|
|
/// <summary>
|
|
/// Interpolate from one Color to another.
|
|
/// </summary>
|
|
/// <param name="from">The start Color.</param>
|
|
/// <param name="to">The end Color.</param>
|
|
/// <param name="amount">The amount of completion on the lerp. (0 - 1)</param>
|
|
/// <returns>The interpolated Color.</returns>
|
|
public static Color LerpColor(Color from, Color to, float amount) {
|
|
if (amount <= 0) return new Color(from);
|
|
if (amount >= 1) return new Color(to);
|
|
|
|
var c = new Color(from);
|
|
c.R = from.R + (to.R - from.R) * amount;
|
|
c.G = from.G + (to.G - from.G) * amount;
|
|
c.B = from.B + (to.B - from.B) * amount;
|
|
c.A = from.A + (to.A - from.A) * amount;
|
|
|
|
return c;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Interpolate through a set of Colors.
|
|
/// </summary>
|
|
/// <param name="amount">The amount of completion on the lerp. (0 - 1)</param>
|
|
/// <param name="colors">The Colors to interpolate through.</param>
|
|
/// <returns>The interpolated Color.</returns>
|
|
public static Color LerpColor(float amount, params Color[] colors) {
|
|
if (amount <= 0) return colors[0];
|
|
if (amount >= 1) return colors[colors.Length - 1];
|
|
|
|
int fromIndex = (int)Util.ScaleClamp(amount, 0, 1, 0, colors.Length - 1);
|
|
int toIndex = fromIndex + 1;
|
|
|
|
float length = 1f / (colors.Length - 1);
|
|
float lerp = Util.ScaleClamp(amount % length, 0, length, 0, 1);
|
|
|
|
// This is a fix for odd numbered color amounts. When fromIndex was
|
|
// odd, lerp would evaluate to 1 when it should be 0.
|
|
if (lerp >= 0.9999f && fromIndex % 2 == 1) {
|
|
lerp = 0;
|
|
}
|
|
|
|
return LerpColor(colors[fromIndex], colors[toIndex], lerp);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Clamps a value inside a range.
|
|
/// </summary>
|
|
/// <param name="value">The value to clamp.</param>
|
|
/// <param name="min">Min clamp.</param>
|
|
/// <param name="max">Max clamp.</param>
|
|
/// <returns>The new value between min and max.</returns>
|
|
public static float Clamp(float value, float min, float max) {
|
|
if (value < min) return min;
|
|
if (value > max) return max;
|
|
return value;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Clamps a value inside a range.
|
|
/// </summary>
|
|
/// <param name="value">The value to clamp.</param>
|
|
/// <param name="min">Min clamp.</param>
|
|
/// <param name="max">Max clamp.</param>
|
|
/// <returns>The new value between min and max.</returns>
|
|
public static Vector2 Clamp(Vector2 value, Vector2 min, Vector2 max) {
|
|
return new Vector2(
|
|
Util.Clamp(value.X, min.X, max.X),
|
|
Util.Clamp(value.Y, min.Y, max.Y)
|
|
);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Clamps a value inside a range.
|
|
/// </summary>
|
|
/// <param name="value">The value to clamp.</param>
|
|
/// <param name="max">Max clamp.</param>
|
|
/// <returns>The new value between 0 and max.</returns>
|
|
public static Vector2 Clamp(Vector2 value, Vector2 max) {
|
|
return Clamp(value, Vector2.Zero, max);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Clamps a value inside a range.
|
|
/// </summary>
|
|
/// <param name="value">The value to clamp.</param>
|
|
/// <param name="max">Max clamp</param>
|
|
/// <returns>The new value between 0 and max.</returns>
|
|
public static float Clamp(float value, float max) {
|
|
return Clamp(value, 0, max);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Clamps a value inside a range.
|
|
/// </summary>
|
|
/// <param name="value">The value to clamp.</param>
|
|
/// <param name="range">The range.</param>
|
|
/// <returns>The clamped value in the range.</returns>
|
|
public static float Clamp(float value, Range range) {
|
|
return Clamp(value, range.Min, range.Max);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Steps an angle value toward a target based on a certain amount.
|
|
/// </summary>
|
|
/// <param name="from">The angle value to step.</param>
|
|
/// <param name="to">The target value to approach.</param>
|
|
/// <param name="amount">The amount to approach by.</param>
|
|
/// <returns>The new angle value approaching the target from 0 to 360.</returns>
|
|
public static float ApproachAngle(float from, float to, float amount) {
|
|
var sign = AngleDifferenceSign(from, to);
|
|
var diff = AngleDifference(from, to);
|
|
var maxMove = Math.Min(Math.Abs(diff), amount);
|
|
return (from + maxMove * sign);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Steps a value toward a target based on a certain amount.
|
|
/// </summary>
|
|
/// <param name="val">The value to step.</param>
|
|
/// <param name="target">The target to approach.</param>
|
|
/// <param name="maxMove">The maximum increment toward the target.</param>
|
|
/// <returns>The new value approaching the target.</returns>
|
|
static public float Approach(float val, float target, float maxMove) {
|
|
return val > target ? Math.Max(val - maxMove, target) : Math.Min(val + maxMove, target);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Steps a value toward a target based on a certain amount.
|
|
/// </summary>
|
|
/// <param name="val">The value to step.</param>
|
|
/// <param name="target">The target to approach.</param>
|
|
/// <param name="maxMove">The maximum increment toward the target.</param>
|
|
/// <returns>The new value approaching the target.</returns>
|
|
static public Vector2 Approach(Vector2 val, Vector2 target, Vector2 maxMove) {
|
|
return new Vector2(
|
|
Approach(val.X, target.X, maxMove.X),
|
|
Approach(val.Y, target.Y, maxMove.Y)
|
|
);
|
|
}
|
|
|
|
static public Vector2 Approach(Vector2 val, Vector2 target, float maxMove) {
|
|
return Approach(val, target, new Vector2(maxMove, maxMove));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Snaps a value to the nearest value on a grid.
|
|
/// </summary>
|
|
/// <param name="value">The input value.</param>
|
|
/// <param name="increment">The size of each grid space.</param>
|
|
/// <param name="offset">The offset to apply after the snap.</param>
|
|
/// <returns>The snapped value.</returns>
|
|
static public float SnapToGrid(float value, float increment, float offset = 0) {
|
|
return ((float)Math.Floor(value / increment) * increment) + offset;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Converts a hex character to a byte.
|
|
/// </summary>
|
|
/// <param name="c">The input character.</param>
|
|
/// <returns>The byte.</returns>
|
|
static public byte HexToByte(char c) {
|
|
return (byte)HEX.IndexOf(char.ToUpper(c));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the minimum value from a set of values.
|
|
/// </summary>
|
|
/// <param name="values">The values to test.</param>
|
|
/// <returns>The minimum value.</returns>
|
|
static public float Min(params float[] values) {
|
|
float min = values[0];
|
|
for (int i = 1; i < values.Length; i++)
|
|
min = Math.Min(values[i], min);
|
|
return min;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the maximum value from a set of values.
|
|
/// </summary>
|
|
/// <param name="values">The values to test.</param>
|
|
/// <returns>The maximum value.</returns>
|
|
static public float Max(params float[] values) {
|
|
float max = values[0];
|
|
for (int i = 1; i < values.Length; i++)
|
|
max = Math.Max(values[i], max);
|
|
return max;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Convert a number in a range of min, max to a number in the range min2, max2.
|
|
/// Also known as RemapRange in some frameworks.
|
|
/// </summary>
|
|
/// <param name="value">The input value.</param>
|
|
/// <param name="min">The original minimum.</param>
|
|
/// <param name="max">The original maximum.</param>
|
|
/// <param name="min2">The new minimum.</param>
|
|
/// <param name="max2">The new maximum.</param>
|
|
/// <returns>A value scaled from the original min and max to the new min and max.</returns>
|
|
public static float Scale(float value, float min, float max, float min2, float max2) {
|
|
return min2 + ((value - min) / (max - min)) * (max2 - min2);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Convert a number in range of min, max to a number in the range min2, max2, but also
|
|
/// clamp the result inside min2 and max2.
|
|
/// </summary>
|
|
/// <param name="value">The input value.</param>
|
|
/// <param name="min">The original minimum.</param>
|
|
/// <param name="max">The original maximum.</param>
|
|
/// <param name="min2">The new minimum.</param>
|
|
/// <param name="max2">The new maximum.</param>
|
|
/// <returns>A value scaled from the original min and max to the new min and max, and clamped to the new min and max.</returns>
|
|
public static float ScaleClamp(float value, float min, float max, float min2, float max2) {
|
|
value = min2 + ((value - min) / (max - min)) * (max2 - min2);
|
|
if (max2 > min2) {
|
|
value = value < max2 ? value : max2;
|
|
return value > min2 ? value : min2;
|
|
}
|
|
value = value < min2 ? value : min2;
|
|
return value > max2 ? value : max2;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Shortcut to Scale the value from a sine wave. Original min and max are -1 and 1.
|
|
/// </summary>
|
|
/// <param name="value">The input value to sine.</param>
|
|
/// <param name="min">The new minimum.</param>
|
|
/// <param name="max">The new maximum.</param>
|
|
/// <returns>A value scaled from -1 and 1 to the new min and max.</returns>
|
|
public static float SinScale(float value, float min, float max) {
|
|
return Scale((float)Math.Sin(value * DEG_TO_RAD), -1f, 1f, min, max);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Shortcut to ScaleClamp the value from a sine wave. Original min and max are -1 and 1.
|
|
/// </summary>
|
|
/// <param name="value">The input value to sine.</param>
|
|
/// <param name="min">The new minimum.</param>
|
|
/// <param name="max">The new maximum.</param>
|
|
/// <returns>A value scaled from -1 and 1 to the new min and max, and clamped to the new min and max.</returns>
|
|
public static float SinScaleClamp(float value, float min, float max) {
|
|
return ScaleClamp((float)Math.Sin(value * DEG_TO_RAD), -1f, 1f, min, max);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Round down.
|
|
/// </summary>
|
|
/// <param name="value">The value.</param>
|
|
/// <returns>The value rounded down.</returns>
|
|
public static float Floor(float value) {
|
|
return (float)Math.Floor(value);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Round up.
|
|
/// </summary>
|
|
/// <param name="value">The value.</param>
|
|
/// <returns>The value rounded up.</returns>
|
|
public static float Ceil(float value) {
|
|
return (float)Math.Ceiling(value);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Round.
|
|
/// </summary>
|
|
/// <param name="value">The value.</param>
|
|
/// <returns>The value rounded.</returns>
|
|
public static float Round(float value) {
|
|
return (float)Math.Round(value);
|
|
}
|
|
|
|
/// <summary>
|
|
/// The angle of a x and y coordinate.
|
|
/// </summary>
|
|
/// <param name="x">The X position.</param>
|
|
/// <param name="y">The Y position.</param>
|
|
/// <returns>An angle between 0 - 360 degrees.</returns>
|
|
public static float Angle(float x, float y) {
|
|
//y is negative since y is DOWN in video games
|
|
//return degrees by default
|
|
return (float)Math.Atan2(-y, x) * Util.RAD_TO_DEG;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the angle between two Entities.
|
|
/// </summary>
|
|
/// <param name="e1">The first Entity.</param>
|
|
/// <param name="e2">The second Entity.</param>
|
|
/// <returns>The angle between the two Entity's positions between 0 - 360 degrees.</returns>
|
|
public static float Angle(Entity e1, Entity e2) {
|
|
return Angle(e1.X, e1.Y, e2.X, e2.Y);
|
|
}
|
|
|
|
/// <summary>
|
|
/// The angle of a vector.
|
|
/// </summary>
|
|
/// <param name="vector">The vector.</param>
|
|
/// <returns>An angle between 0 - 360 degrees.</returns>
|
|
public static float Angle(Vector2 vector) {
|
|
return Angle((float)vector.X, (float)vector.Y);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the angle between two vectors.
|
|
/// </summary>
|
|
/// <param name="from">The first vector.</param>
|
|
/// <param name="to">The second vector.</param>
|
|
/// <returns>The angle between the two vectors.</returns>
|
|
public static float Angle(Vector2 from, Vector2 to) {
|
|
return Angle((float)(to.X - from.X), (float)(to.Y - from.Y));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the angle between two positions.
|
|
/// </summary>
|
|
/// <param name="x1">The first X position.</param>
|
|
/// <param name="y1">The first Y position.</param>
|
|
/// <param name="x2">The second X position.</param>
|
|
/// <param name="y2">The second Y position.</param>
|
|
/// <returns>The angle between the two positions.</returns>
|
|
public static float Angle(float x1, float y1, float x2, float y2) {
|
|
return Angle(x2 - x1, y2 - y1);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the difference between two angles from -180 to 180.
|
|
/// </summary>
|
|
/// <param name="a">The first angle.</param>
|
|
/// <param name="b">The second angle.</param>
|
|
/// <returns>The difference between the angles from -180 to 180.</returns>
|
|
public static float AngleDifference(float a, float b) {
|
|
var diff = b - a;
|
|
|
|
while (diff > 180) { diff -= 360; }
|
|
while (diff <= -180) { diff += 360; }
|
|
|
|
return diff;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the shortest direction from angle a to angle b.
|
|
/// </summary>
|
|
/// <param name="a">The first angle.</param>
|
|
/// <param name="b">The second angle..</param>
|
|
/// <returns>1 for clockwise, -1 for counter clockwise, 0 if angles are the same.</returns>
|
|
public static int AngleDifferenceSign(float a, float b) {
|
|
if (a == b) return 0;
|
|
var dif = AngleDifference(a, b);
|
|
return (int)Math.Sign(dif);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Rotate a position by an angle.
|
|
/// </summary>
|
|
/// <param name="vector">The position to rotate.</param>
|
|
/// <param name="amount">The amount to rotate the position in degrees.</param>
|
|
/// <returns>The new rotated position.</returns>
|
|
public static Vector2 Rotate(Vector2 vector, float amount) {
|
|
return Rotate(vector.X, vector.Y, amount);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Rotate a position by an angle.
|
|
/// </summary>
|
|
/// <param name="x">The X position to rotate.</param>
|
|
/// <param name="y">The Y position to rotate.</param>
|
|
/// <param name="amount">The amount to rotate the position in degrees.</param>
|
|
/// <returns>The new rotated position.</returns>
|
|
public static Vector2 Rotate(float x, float y, float amount) {
|
|
amount *= -1; // Flip Y because of video game coordinates.
|
|
return new Vector2(x * Cos(amount) - y * Sin(amount), x * Sin(amount) + y * Cos(amount)); // Wow this is fancy
|
|
/*
|
|
// old rotate code just in case I broke something
|
|
var v = new Vector2(x, y);
|
|
var length = v.Length;
|
|
var angle = Angle(x, y) + amount;
|
|
v.X = PolarX(angle, length);
|
|
v.Y = PolarY(angle, length);
|
|
return v;
|
|
*/
|
|
}
|
|
|
|
/// <summary>
|
|
/// Rotate a position by an angle around an anchor point.
|
|
/// </summary>
|
|
/// <param name="x">The X position to rotate.</param>
|
|
/// <param name="y">The Y position to rotate.</param>
|
|
/// <param name="aroundX">The X position to rotate around.</param>
|
|
/// <param name="aroundY">The Y position to rotate around.</param>
|
|
/// <param name="amount">The amount to rotate the position in degrees.</param>
|
|
/// <returns>The new rotated position.</returns>
|
|
public static Vector2 RotateAround(float x, float y, float aroundX, float aroundY, float amount) {
|
|
var vec = Rotate(x - aroundX, y - aroundY, amount);
|
|
vec.X += aroundX;
|
|
vec.Y += aroundY;
|
|
return vec;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Rotate a position by an angle around an anchor point.
|
|
/// </summary>
|
|
/// <param name="point">The position to rotate.</param>
|
|
/// <param name="around">The position to rotate around.</param>
|
|
/// <param name="amount">The amount to rotate the position in degrees.</param>
|
|
/// <returns>The new rotated position.</returns>
|
|
public static Vector2 RotateAround(Vector2 point, Vector2 around, float amount) {
|
|
return RotateAround(point.X, point.Y, around.X, around.Y, amount);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Scale a position by an amount around an anchor point.
|
|
/// </summary>
|
|
/// <param name="x">The X position to scale.</param>
|
|
/// <param name="y">The Y position to scale.</param>
|
|
/// <param name="aroundX">The X position to scale around.</param>
|
|
/// <param name="aroundY">The Y position to scale around.</param>
|
|
/// <param name="amountX">The X amount to scale by.</param>
|
|
/// <param name="amountY">The Y amount to scale by.</param>
|
|
/// <returns>The new scaled position.</returns>
|
|
public static Vector2 ScaleAround(float x, float y, float aroundX, float aroundY, float amountX, float amountY) {
|
|
x -= aroundX;
|
|
y -= aroundY;
|
|
|
|
x *= amountX;
|
|
y *= amountY;
|
|
|
|
x += aroundX;
|
|
y += aroundY;
|
|
|
|
return new Vector2(x, y);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Scale a position by an amount around an anchor point.
|
|
/// </summary>
|
|
/// <param name="point">The position to scale.</param>
|
|
/// <param name="around">The position to scale around.</param>
|
|
/// <param name="amountX">The X amount to scale by.</param>
|
|
/// <param name="amountY">The Y amount to scale by.</param>
|
|
/// <returns>The new scaled position.</returns>
|
|
public static Vector2 ScaleAround(Vector2 point, Vector2 around, float amountX, float amountY) {
|
|
return ScaleAround(point.X, point.Y, around.X, around.Y, amountX, amountY);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Scale a position by an amount around an anchor point.
|
|
/// </summary>
|
|
/// <param name="point">The position to scale.</param>
|
|
/// <param name="around">The position to scale around.</param>
|
|
/// <param name="amount">The amount to scale by.</param>
|
|
/// <returns>The new scaled position.</returns>
|
|
public static Vector2 ScaleAround(Vector2 point, Vector2 around, float amount) {
|
|
return ScaleAround(point, around, amount, amount);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Scale a position by an amount around an anchor point.
|
|
/// </summary>
|
|
/// <param name="point">The position to scale.</param>
|
|
/// <param name="around">The position to scale around.</param>
|
|
/// <param name="amount">The amount to scale by.</param>
|
|
/// <returns>The new scaled position.</returns>
|
|
public static Vector2 ScaleAround(Vector2 point, Vector2 around, Vector2 amount) {
|
|
return ScaleAround(point.X, point.Y, around.X, around.Y, amount.X, amount.Y);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Distance check.
|
|
/// </summary>
|
|
/// <param name="x1">The first X position.</param>
|
|
/// <param name="y1">The first Y position.</param>
|
|
/// <param name="x2">The second X position.</param>
|
|
/// <param name="y2">The second Y position.</param>
|
|
/// <returns>The distance between the two points.</returns>
|
|
public static float Distance(float x1, float y1, float x2, float y2) {
|
|
return (float)Math.Sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Distance check.
|
|
/// </summary>
|
|
/// <param name="from">The first position.</param>
|
|
/// <param name="to">The second position.</param>
|
|
/// <returns>The distance between the two positions.</returns>
|
|
public static float Distance(Vector2 from, Vector2 to) {
|
|
return Distance(from.X, from.Y, to.X, to.Y);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Distance check.
|
|
/// </summary>
|
|
/// <param name="e1">The first Entity.</param>
|
|
/// <param name="e2">The second Entity.</param>
|
|
/// <returns>The distance between the Entities.</returns>
|
|
public static float Distance(Entity e1, Entity e2) {
|
|
return Distance(e1.X, e1.Y, e2.X, e2.Y);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Check for a point in a rectangle defined by min and max points.
|
|
/// </summary>
|
|
/// <param name="p">The point to check.</param>
|
|
/// <param name="min">The top left corner of the rectangle.</param>
|
|
/// <param name="max">The bottom right corner of the rectangle.</param>
|
|
/// <returns>True if the point is in the rectangle.</returns>
|
|
public static bool InRect(Vector2 p, Vector2 min, Vector2 max) {
|
|
return p.X > min.X && p.Y > min.Y && p.X < max.X && p.Y < max.Y;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Check for a point in a rectangle.
|
|
/// </summary>
|
|
/// <param name="x">The X position of the point to check.</param>
|
|
/// <param name="y">The Y position of the point to check.</param>
|
|
/// <param name="rect">The rectangle.</param>
|
|
/// <returns>True if the point is in the rectangle.</returns>
|
|
public static bool InRect(float x, float y, Rectangle rect) {
|
|
if (x <= rect.X) return false;
|
|
if (x >= rect.X + rect.Width) return false;
|
|
if (y <= rect.Y) return false;
|
|
if (y >= rect.Y + rect.Height) return false;
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Check for a point in a rectangle.
|
|
/// </summary>
|
|
/// <param name="x">The X position of the point to check.</param>
|
|
/// <param name="y">The Y position of the point to check.</param>
|
|
/// <param name="rx">The left of the rectangle.</param>
|
|
/// <param name="ry">The top of the rectangle.</param>
|
|
/// <param name="rw">The width of the rectangle.</param>
|
|
/// <param name="rh">The height of the rectangle.</param>
|
|
/// <returns>True if the point is in the rectangle.</returns>
|
|
public static bool InRect(float x, float y, float rx, float ry, float rw, float rh) {
|
|
if (x <= rx) return false;
|
|
if (x >= rx + rw) return false;
|
|
if (y <= ry) return false;
|
|
if (y >= ry + rh) return false;
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Check for a point in a rectangle.
|
|
/// </summary>
|
|
/// <param name="xy">The X and Y position of the point to check.</param>
|
|
/// <param name="rect">The rectangle.</param>
|
|
/// <returns>True if the point is in the rectangle.</returns>
|
|
public static bool InRect(Vector2 xy, Rectangle rect) {
|
|
return InRect((float)xy.X, (float)xy.Y, rect);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Check for a point inside of a circle.
|
|
/// </summary>
|
|
/// <param name="p">The point to check.</param>
|
|
/// <param name="circleP">The center point of the circle.</param>
|
|
/// <param name="radius">The radius of the circle.</param>
|
|
/// <returns>True if the point is inside the circle.</returns>
|
|
public static bool InCircle(Vector2 p, Vector2 circleP, float radius) {
|
|
return Distance((float)p.X, (float)p.Y, (float)circleP.X, (float)circleP.Y) <= radius;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Check for a point inside of a circle.
|
|
/// </summary>
|
|
/// <param name="x">The X position to check.</param>
|
|
/// <param name="y">The Y position to check.</param>
|
|
/// <param name="circleX">The center X position of the circle.</param>
|
|
/// <param name="circleY">The center Y position of the check.</param>
|
|
/// <param name="radius">The radius of the circle.</param>
|
|
/// <returns>True if the point is inside the circle.</returns>
|
|
public static bool InCircle(float x, float y, float circleX, float circleY, float radius) {
|
|
return Distance(x, y, circleX, circleY) <= radius;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Check an intersection between two rectangles.
|
|
/// </summary>
|
|
/// <param name="x1">The left of the first rectangle.</param>
|
|
/// <param name="y1">The top of the first rectangle.</param>
|
|
/// <param name="w1">The width of the first rectangle.</param>
|
|
/// <param name="h1">The height of the first rectangle.</param>
|
|
/// <param name="x2">The left of the second rectangle.</param>
|
|
/// <param name="y2">The top of the second rectangle.</param>
|
|
/// <param name="w2">The width of the second rectangle.</param>
|
|
/// <param name="h2">The height of the second rectangle.</param>
|
|
/// <returns>True if the rectangles intersect.</returns>
|
|
public static bool IntersectRectangles(float x1, float y1, float w1, float h1, float x2, float y2, float w2, float h2) {
|
|
if (x1 + w1 <= x2) return false;
|
|
if (x1 >= x2 + w2) return false;
|
|
if (y1 + h1 <= y2) return false;
|
|
if (y1 >= y2 + h2) return false;
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// The X component of a vector represented by an angle and radius.
|
|
/// </summary>
|
|
/// <param name="angle">The angle of the vector.</param>
|
|
/// <param name="length">The length of the vector.</param>
|
|
/// <returns>The X component.</returns>
|
|
public static float PolarX(float angle, float length) {
|
|
return Cos(angle) * length;
|
|
}
|
|
|
|
/// <summary>
|
|
/// The Y component of a vector represented by an angle and radius.
|
|
/// </summary>
|
|
/// <param name="angle">The angle of the vector.</param>
|
|
/// <param name="length">The length of the vector.</param>
|
|
/// <returns>The Y component.</returns>
|
|
public static float PolarY(float angle, float length) {
|
|
//Radius is negative since Y positive is DOWN in video games.
|
|
return Sin(angle) * -length;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Wrapper for the sin function that uses degrees.
|
|
/// </summary>
|
|
/// <param name="degrees">The angle.</param>
|
|
/// <returns>The sine of the angle.</returns>
|
|
public static float Sin(float degrees) {
|
|
return (float)Math.Sin(degrees * DEG_TO_RAD);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Wrapper for the cos function that uses degrees.
|
|
/// </summary>
|
|
/// <param name="degrees">The angle.</param>
|
|
/// <returns>The cosine of the angle.</returns>
|
|
public static float Cos(float degrees) {
|
|
return (float)Math.Cos(degrees * DEG_TO_RAD);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Convert a two dimensional position to a one dimensional index.
|
|
/// </summary>
|
|
/// <param name="width">The width of the two dimensional set.</param>
|
|
/// <param name="x">The X position in the two dimensional set.</param>
|
|
/// <param name="y">The Y position in the two dimensional set.</param>
|
|
/// <returns>The one dimensional index in a two dimensional set.</returns>
|
|
public static int OneDee(int width, int x, int y) {
|
|
return y * width + x;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Convert a one dimensional index to a two dimensional position.
|
|
/// </summary>
|
|
/// <param name="index">The one dimensional index in the two dimensional set.</param>
|
|
/// <param name="width">The width of the two dimensional set.</param>
|
|
/// <returns>The X and Y position in the two dimensional set.</returns>
|
|
public static Vector2 TwoDee(int index, int width) {
|
|
if (width == 0) {
|
|
return new Vector2(index, 0);
|
|
}
|
|
return new Vector2(index % width, index / width);
|
|
}
|
|
|
|
/// <summary>
|
|
/// The X position from converting an index to a two dimensional position.
|
|
/// </summary>
|
|
/// <param name="index">The one dimensional index in the two dimensional set.</param>
|
|
/// <param name="width">The width of the two dimensional set.</param>
|
|
/// <returns>The X position in the two dimensional set.</returns>
|
|
public static int TwoDeeX(int index, int width) {
|
|
return (int)TwoDee(index, width).X;
|
|
}
|
|
|
|
/// <summary>
|
|
/// The Y position from converting an index to a two dimensional position.
|
|
/// </summary>
|
|
/// <param name="index">The one dimensional index in the two dimensional set.</param>
|
|
/// <param name="width">The width of the two dimensional set.</param>
|
|
/// <returns>The Y position in the two dimensional set.</returns>
|
|
public static int TwoDeeY(int index, int width) {
|
|
return (int)TwoDee(index, width).Y;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Normal vector.
|
|
/// </summary>
|
|
/// <param name="angle">The angle.</param>
|
|
/// <returns>The normal vector of that angle.</returns>
|
|
public static Vector2 Normal(float angle) {
|
|
return new Vector2(Cos(angle), Sin(angle));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Checks if an object contains a method.
|
|
/// </summary>
|
|
/// <param name="objectToCheck">The object to check for the method on.</param>
|
|
/// <param name="methodName">The name of the method to check for.</param>
|
|
/// <returns>True if the object has that method.</returns>
|
|
public static bool HasMethod(object objectToCheck, string methodName) {
|
|
var type = objectToCheck.GetType();
|
|
return type.GetMethod(methodName) != null || type.BaseType.GetMethod(methodName) != null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Checks if an object contains a property.
|
|
/// </summary>
|
|
/// <param name="objectToCheck">The object to check for the property on.</param>
|
|
/// <param name="propertyName">The name of the property.</param>
|
|
/// <returns>True if the object has that property.</returns>
|
|
public static bool HasProperty(object objectToCheck, string propertyName) {
|
|
var type = objectToCheck.GetType();
|
|
return type.GetProperty(propertyName) != null || type.BaseType.GetProperty(propertyName) != null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the value of a property from an object.
|
|
/// </summary>
|
|
/// <param name="source">The object to get the property from.</param>
|
|
/// <param name="propName">The name of the property.</param>
|
|
/// <returns>The property value.</returns>
|
|
public static object GetPropValue(object source, string propName) {
|
|
return source.GetType().GetProperty(propName).GetValue(source, null);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Checks to see if an object has a field by name.
|
|
/// </summary>
|
|
/// <param name="objectToCheck">The object to check for the field on.</param>
|
|
/// <param name="fieldName">The name of the field.</param>
|
|
/// <returns>True if the object has that property.</returns>
|
|
public static bool HasField(object objectToCheck, string fieldName) {
|
|
return objectToCheck.GetType().GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public) != null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the value of a field by name from an object.
|
|
/// </summary>
|
|
/// <param name="source">The object to get the field from.</param>
|
|
/// <param name="fieldName">The name of the field.</param>
|
|
/// <returns>The value of the field.</returns>
|
|
public static object GetFieldValue(object source, string fieldName) {
|
|
return source.GetType().GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public).GetValue(source);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the static value of a field by name from an object.
|
|
/// </summary>
|
|
/// <param name="type">The type to look for the field in.</param>
|
|
/// <param name="fieldName">The name of the static field.</param>
|
|
/// <returns>The value of the static field.</returns>
|
|
public static object GetFieldValue(Type type, string fieldName) {
|
|
return type.GetField(fieldName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static).GetValue(null);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the value of a field by name from an object with a default return.
|
|
/// </summary>
|
|
/// <param name="source">The object to get the field from.</param>
|
|
/// <param name="fieldName">The name of the field.</param>
|
|
/// <param name="returnOnNull">The value to return if the field is not found.</param>
|
|
/// <returns>The field value or the value to return if the field is not found.</returns>
|
|
public static object GetFieldValue(object src, string fieldName, object returnOnNull) {
|
|
if (src == null) return returnOnNull;
|
|
if (!Util.HasField(src, fieldName)) return returnOnNull;
|
|
return GetFieldValue(src, fieldName);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Set the value of a field on an object by name.
|
|
/// </summary>
|
|
/// <param name="src">The object to set the field on.</param>
|
|
/// <param name="fieldName">The name of the field.</param>
|
|
/// <param name="value">The new value of the field.</param>
|
|
public static void SetFieldValue(object src, string fieldName, object value) {
|
|
var fi = src.GetType().GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public);
|
|
fi.SetValue(src, value);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Checks if a value is in a specified range.
|
|
/// </summary>
|
|
/// <param name="value">The value to check.</param>
|
|
/// <param name="min">The minimum.</param>
|
|
/// <param name="max">The maximum.</param>
|
|
/// <returns>True if the value is in the range.</returns>
|
|
public static bool InRange(float value, float min, float max) {
|
|
if (value >= min && value <= max) return true;
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns a value according to a value's position in a range.
|
|
/// </summary>
|
|
/// <param name="value">The value to check.</param>
|
|
/// <param name="min">The minimum.</param>
|
|
/// <param name="max">The maximum.</param>
|
|
/// <returns>0 if the value is in the set. -1 if the value is below the minimum. 1 if the value is above the maximum.</returns>
|
|
public static int Subset(float value, float min, float max) {
|
|
if (value < min) return -1;
|
|
if (value > max) return 1;
|
|
return 0;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Wrap and angle and keep it within the range of 0 to 360.
|
|
/// </summary>
|
|
/// <param name="angle">The angle.</param>
|
|
/// <returns>The angle wrapped to be in the range of 0 to 360.</returns>
|
|
public static float WrapAngle(float angle) {
|
|
angle %= 360;
|
|
if (angle > 180)
|
|
return angle - 360;
|
|
else if (angle <= -180)
|
|
return angle + 360;
|
|
else
|
|
return angle;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Find the distance between a point and a rectangle.
|
|
/// </summary>
|
|
/// <param name="px">The X position of the point.</param>
|
|
/// <param name="py">The Y position of the point.</param>
|
|
/// <param name="rx">The X position of the rectangle.</param>
|
|
/// <param name="ry">The Y position of the rectangle.</param>
|
|
/// <param name="rw">The width of the rectangle.</param>
|
|
/// <param name="rh">The height of the rectangle.</param>
|
|
/// <returns>The distance. Returns 0 if the point is within the rectangle.</returns>
|
|
public static float DistanceRectPoint(float px, float py, float rx, float ry, float rw, float rh) {
|
|
if (px >= rx && px <= rx + rw) {
|
|
if (py >= ry && py <= ry + rh) return 0;
|
|
if (py > ry) return py - (ry + rh);
|
|
return ry - py;
|
|
}
|
|
if (py >= ry && py <= ry + rh) {
|
|
if (px > rx) return px - (rx + rw);
|
|
return rx - px;
|
|
}
|
|
if (px > rx) {
|
|
if (py > ry) return Distance(px, py, rx + rw, ry + rh);
|
|
return Distance(px, py, rx + rw, ry);
|
|
}
|
|
if (py > ry) return Distance(px, py, rx, ry + rh);
|
|
return Distance(px, py, rx, ry);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Find the distance between a point and a rectangle.
|
|
/// </summary>
|
|
/// <param name="px">The X position of the point.</param>
|
|
/// <param name="py">The Y position of the point.</param>
|
|
/// <param name="rect">The rectangle.</param>
|
|
/// <returns>The distance. Returns 0 if the point is within the rectangle.</returns>
|
|
public static float DistanceRectPoint(float px, float py, Rectangle rect) {
|
|
return DistanceRectPoint(px, py, rect.X, rect.Y, rect.Width, rect.Height);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Convert XML attributes to a dictionary.
|
|
/// </summary>
|
|
/// <param name="attributes">The attributes to convert.</param>
|
|
/// <returns>A dictionary of string, string with the attribute names and values.</returns>
|
|
public static Dictionary<string, string> XmlAttributesToDictionary(XmlAttributeCollection attributes) {
|
|
var d = new Dictionary<string, string>();
|
|
foreach (XmlAttribute attr in attributes) {
|
|
d.Add(attr.Name, attr.Value);
|
|
}
|
|
return d;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Searches all known assemblies for a type and returns that type.
|
|
/// </summary>
|
|
/// <param name="type">The type to search for.</param>
|
|
/// <returns>The type found. Null if no match.</returns>
|
|
public static Type GetTypeFromAllAssemblies(string type, bool ignoreCase = false) {
|
|
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
|
|
foreach (var assembly in assemblies) {
|
|
var types = assembly.GetTypes();
|
|
foreach (var t in types) {
|
|
if (t.Name == type) {
|
|
return t;
|
|
}
|
|
if (ignoreCase) {
|
|
if (t.Name.ToLower() == type) {
|
|
return t;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the list of all types in all known assemblies.
|
|
/// </summary>
|
|
/// <returns>The list of all types in all known assemblies.</returns>
|
|
public static List<Type> GetTypesFromAllAssemblies() {
|
|
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
|
|
var allTypes = new List<Type>();
|
|
foreach (var assembly in assemblies) {
|
|
var types = assembly.GetTypes();
|
|
foreach (var t in types) {
|
|
allTypes.Add(t);
|
|
}
|
|
}
|
|
return allTypes;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the basic type name of an object.
|
|
/// </summary>
|
|
/// <param name="obj">The object.</param>
|
|
/// <returns>The basic type name of the object.</returns>
|
|
public static string GetBasicTypeName(object obj) {
|
|
var strarr = obj.GetType().ToString().Split('.');
|
|
return strarr[strarr.Length - 1];
|
|
}
|
|
|
|
/// <summary>
|
|
/// Compresses a string and base64 encodes it. Use "DecompressString" to restore it.
|
|
/// </summary>
|
|
/// <param name="str">The string to compress.</param>
|
|
/// <returns>The compressed string.</returns>
|
|
public static string CompressString(string str) {
|
|
var bytes = Encoding.UTF8.GetBytes(str);
|
|
|
|
using (var msi = new MemoryStream(bytes))
|
|
using (var mso = new MemoryStream()) {
|
|
using (var gs = new GZipStream(mso, CompressionMode.Compress)) {
|
|
CopyStream(msi, gs);
|
|
}
|
|
|
|
return Convert.ToBase64String(mso.ToArray());
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Copies a stream from source to destination.
|
|
/// </summary>
|
|
/// <param name="src">The string to copy.</param>
|
|
/// <param name="dest">The stream to copy the string to.</param>
|
|
public static void CopyStream(Stream src, Stream dest) {
|
|
byte[] bytes = new byte[4096];
|
|
|
|
int cnt;
|
|
|
|
while ((cnt = src.Read(bytes, 0, bytes.Length)) != 0) {
|
|
dest.Write(bytes, 0, cnt);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Decompresses a string compressed with "CompressString"
|
|
/// </summary>
|
|
/// <param name="base64str">The compressed string.</param>
|
|
/// <returns>The uncompressed string.</returns>
|
|
public static string DecompressString(string base64str) {
|
|
byte[] bytes;
|
|
try {
|
|
bytes = Convert.FromBase64String(base64str);
|
|
}
|
|
catch {
|
|
return null;
|
|
}
|
|
|
|
using (var msi = new MemoryStream(bytes))
|
|
using (var mso = new MemoryStream()) {
|
|
using (var gs = new GZipStream(msi, CompressionMode.Decompress)) {
|
|
CopyStream(gs, mso);
|
|
}
|
|
|
|
return Encoding.UTF8.GetString(mso.ToArray());
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Compresses a byte array using GZip
|
|
/// </summary>
|
|
/// <param name="data">The data to compress</param>
|
|
/// <returns>The compressed byte array</returns>
|
|
public static byte[] CompressBytes(byte[] data) {
|
|
using (var compressedStream = new MemoryStream())
|
|
using (var zipStream = new GZipStream(compressedStream, CompressionLevel.Optimal)) {
|
|
zipStream.Write(data, 0, data.Length);
|
|
zipStream.Close();
|
|
return compressedStream.ToArray();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Decompresses a byte array using GZip
|
|
/// </summary>
|
|
/// <param name="data">The data to decompress</param>
|
|
/// <returns>The decompressed byte array</returns>
|
|
public static byte[] DecompressBytes(byte[] data) {
|
|
using (var compressedStream = new MemoryStream(data))
|
|
using (var zipStream = new GZipStream(compressedStream, CompressionMode.Decompress))
|
|
using (var resultStream = new MemoryStream()) {
|
|
zipStream.CopyTo(resultStream);
|
|
return resultStream.ToArray();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Converts a dictionary of string, string into a string of data.
|
|
/// </summary>
|
|
/// <param name="dictionary">The dictionary.</param>
|
|
/// <param name="keydelim">The string that separates keys.</param>
|
|
/// <param name="valuedelim">The string that separates values.</param>
|
|
/// <returns>A string with all of the dictionary's data.</returns>
|
|
public static string DictionaryToString(Dictionary<string, string> dictionary, string keydelim, string valuedelim) {
|
|
string str = "";
|
|
|
|
foreach (var s in dictionary) {
|
|
str += s.Key + keydelim + s.Value + valuedelim;
|
|
}
|
|
|
|
str = str.Substring(0, str.Length - valuedelim.Length);
|
|
|
|
return str;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Convert a string into a dictionary of string, string.
|
|
/// </summary>
|
|
/// <param name="source">The string to convert into a dictionary.</param>
|
|
/// <param name="keydelim">The string that separates keys.</param>
|
|
/// <param name="valuedelim">The string that separates values.</param>
|
|
/// <returns>A dictionary with all the string's data.</returns>
|
|
public static Dictionary<string, string> StringToDictionary(string source, string keydelim, string valuedelim) {
|
|
var d = new Dictionary<string, string>();
|
|
|
|
string[] split = Regex.Split(source, valuedelim);
|
|
|
|
foreach (var s in split) {
|
|
string[] entry = Regex.Split(s, keydelim);
|
|
d.Add(entry[0], entry[1]);
|
|
}
|
|
|
|
return d;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Calculate an MD5 hash of a string.
|
|
/// </summary>
|
|
/// <param name="input">The string to calculate the has for.</param>
|
|
/// <returns>The MD5 hash of the string.</returns>
|
|
public static string MD5Hash(string input) {
|
|
// step 1, calculate MD5 hash from input
|
|
MD5 md5 = System.Security.Cryptography.MD5.Create();
|
|
byte[] inputBytes = System.Text.Encoding.ASCII.GetBytes(input);
|
|
byte[] hash = md5.ComputeHash(inputBytes);
|
|
|
|
// step 2, convert byte array to hex string
|
|
StringBuilder sb = new StringBuilder();
|
|
for (int i = 0; i < hash.Length; i++) {
|
|
sb.Append(hash[i].ToString("X2"));
|
|
}
|
|
return sb.ToString();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get all the values of an enum without a giant mess of code.
|
|
/// </summary>
|
|
/// <typeparam name="T">The type of the Enum.</typeparam>
|
|
/// <returns>An enumerable containing all the enum values.</returns>
|
|
public static IEnumerable<T> EnumValues<T>() {
|
|
return Enum.GetValues(typeof(T)).Cast<T>();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Convert a string number to a float. If the string contains a % char it will be parsed as a percentage.
|
|
/// For example "50%" => 0.5f, or 50 => 50f.
|
|
/// </summary>
|
|
/// <param name="percent">The string to parse.</param>
|
|
/// <returns>If the string contained % a float of the percent on the scale of 0 to 1. Otherwise the float.</returns>
|
|
public static float ParsePercent(string percent) {
|
|
if (percent.Contains('%')) {
|
|
percent = percent.TrimEnd('%');
|
|
return float.Parse(percent) * 0.01f;
|
|
}
|
|
return float.Parse(percent);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Download data from a URL. This method will stall the thread. Probably
|
|
/// should use something like a BackgroundWorker or something but I haven't
|
|
/// figured that out yet.
|
|
/// </summary>
|
|
/// <param name="url">The url to download.</param>
|
|
/// <returns>The string downloaded from the url.</returns>
|
|
public static string GetURLString(string url) {
|
|
string s = "";
|
|
using (WebClient client = new WebClient()) {
|
|
s = client.DownloadString(url);
|
|
}
|
|
return s;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse an enum value from a string.
|
|
/// </summary>
|
|
/// <typeparam name="T">The type of the enum.</typeparam>
|
|
/// <param name="str">The string to parse.</param>
|
|
/// <returns>The enum value.</returns>
|
|
public static T ParseEnum<T>(string str) {
|
|
return (T)Enum.Parse(typeof(T), str);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Convert a generic enum's value to a string.
|
|
/// </summary>
|
|
/// <param name="value">The enum value.</param>
|
|
/// <returns>The string of the enum value.</returns>
|
|
public static string EnumValueToString(Enum value) {
|
|
if (enumStringCache.ContainsKey(value)) return enumStringCache[value];
|
|
var str = string.Format("{0}.{1}", value.GetType(), value);
|
|
enumStringCache.Add(value, str);
|
|
return enumStringCache[value];
|
|
}
|
|
static Dictionary<Enum, string> enumStringCache = new Dictionary<Enum, string>();
|
|
|
|
/// <summary>
|
|
/// Convert a generic enum's value into a string of just its value. (No type included!)
|
|
/// </summary>
|
|
/// <param name="e">The enum value.</param>
|
|
/// <returns>The value of the enum following the final period.</returns>
|
|
public static string EnumValueToBasicString(Enum e) {
|
|
var split = Util.EnumValueToString(e).Split('.');
|
|
return split[split.Length - 1];
|
|
}
|
|
|
|
/// <summary>
|
|
/// Convert an array of generic enums to ints.
|
|
/// </summary>
|
|
/// <param name="enums">The enums to convert.</param>
|
|
/// <returns>An array of ints.</returns>
|
|
public static int[] EnumToIntArray(params Enum[] enums) {
|
|
int[] ints = new int[enums.Length];
|
|
for (var i = 0; i < enums.Length; i++) {
|
|
ints[i] = Convert.ToInt32(enums[i]);
|
|
}
|
|
return ints;
|
|
}
|
|
|
|
public static int[] EnumToIntArray(List<Enum> enums) {
|
|
int[] ints = new int[enums.Count];
|
|
for (var i = 0; i < enums.Count; i++) {
|
|
ints[i] = Convert.ToInt32(enums[i]);
|
|
}
|
|
return ints;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Distance between a line and a point.
|
|
/// </summary>
|
|
/// <param name="x">The X position of the point.</param>
|
|
/// <param name="y">The Y position of the point.</param>
|
|
/// <param name="line">The line.</param>
|
|
/// <returns>The distance from the point to the line.</returns>
|
|
public static float DistanceLinePoint(float x, float y, Line2 line) {
|
|
return (DistanceLinePoint(x, y, line.X1, line.Y1, line.X2, line.Y2));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Distance between a line and a point.
|
|
/// </summary>
|
|
/// <param name="x">The X position of the point.</param>
|
|
/// <param name="y">The Y position of the point.</param>
|
|
/// <param name="x1">The first X position of the line.</param>
|
|
/// <param name="y1">The first Y position of the line.</param>
|
|
/// <param name="x2">The second X position of the line.</param>
|
|
/// <param name="y2">The second Y position of the line.</param>
|
|
/// <returns>The distance from the point to the line.</returns>
|
|
public static float DistanceLinePoint(float x, float y, float x1, float y1, float x2, float y2) {
|
|
if (x1 == x2 && y1 == y2) return Util.Distance(x, y, x1, y1);
|
|
|
|
var px = x2 - x1;
|
|
var py = y2 - y1;
|
|
|
|
float something = px * px + py * py;
|
|
|
|
var u = ((x - x1) * px + (y - y1) * py) / something;
|
|
|
|
if (u > 1) u = 1;
|
|
if (u < 0) u = 0;
|
|
|
|
var xx = x1 + u * px;
|
|
var yy = y1 + u * py;
|
|
|
|
var dx = xx - x;
|
|
var dy = yy - y;
|
|
|
|
return (float)Math.Sqrt(dx * dx + dy * dy);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets every possible combination of a List.
|
|
/// </summary>
|
|
/// <typeparam name="T">The type of object.</typeparam>
|
|
/// <param name="list">A list of objects.</param>
|
|
/// <returns>An IEnumerable of every possible combination in the List.</returns>
|
|
public static IEnumerable<IEnumerable<T>> GetPowerSet<T>(List<T> list) {
|
|
return from m in Enumerable.Range(0, 1 << list.Count)
|
|
select
|
|
from i in Enumerable.Range(0, list.Count)
|
|
where (m & (1 << i)) != 0
|
|
select list[i];
|
|
}
|
|
|
|
/// <summary>
|
|
/// Shortcut to serialize an object to a file.
|
|
/// </summary>
|
|
/// <typeparam name="T">The type of object.</typeparam>
|
|
/// <param name="obj">The object to serialize.</param>
|
|
/// <param name="path">The file to write to.</param>
|
|
public static void SerializeToFile<T>(T obj, string path) {
|
|
Stream stream = File.Open(path, FileMode.Create);
|
|
BinaryFormatter bformatter = new BinaryFormatter();
|
|
|
|
bformatter.Serialize(stream, obj);
|
|
stream.Close();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Shortcut to deserialize an object from a file.
|
|
/// </summary>
|
|
/// <typeparam name="T">The type of object.</typeparam>
|
|
/// <param name="path">The file to read from.</param>
|
|
/// <returns>The deserialized object.</returns>
|
|
public static T DeserializeFromFile<T>(string path) {
|
|
Stream stream = File.Open(path, FileMode.Open);
|
|
BinaryFormatter bformatter = new BinaryFormatter();
|
|
|
|
var obj = (T)bformatter.Deserialize(stream);
|
|
stream.Close();
|
|
|
|
return obj;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Calculates the point in a cubic Bezier curve at t (0 to 1) using 4 control points.
|
|
/// </summary>
|
|
/// <param name="t">Distance along the curve 0 to 1.</param>
|
|
/// <param name="p0">The first control point.</param>
|
|
/// <param name="p1">The second control point.</param>
|
|
/// <param name="p2">The third control point.</param>
|
|
/// <param name="p3">The fourth control point.</param>
|
|
/// <returns>A Vector2 point along the curve at distance t.</returns>
|
|
public static Vector2 BezierCurvePoint(float t, Vector2 p0, Vector2 p1, Vector2 p2, Vector2 p3) {
|
|
// http://devmag.org.za/2011/04/05/bzier-curves-a-tutorial/
|
|
float u = 1 - t;
|
|
float uu = u * u;
|
|
float uuu = uu * u;
|
|
|
|
float tt = t * t;
|
|
float ttt = tt * t;
|
|
|
|
Vector2 p = uuu * p0; // first term
|
|
p += 3 * uu * t * p1; // second term
|
|
p += 3 * u * tt * p2; // third term
|
|
p += ttt * p3; // fourth term
|
|
|
|
return p;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Find the point on a Bezier path at t.
|
|
/// </summary>
|
|
/// <param name="t">The progress along the path 0 to 1.</param>
|
|
/// <param name="points">The points that make up the cubic Bezier path. (4 points per curve, 1 point of overlap.)</param>
|
|
/// <returns>The position on the curve at t.</returns>
|
|
static public Vector2 BezierPathPoint(float t, params Vector2[] points) {
|
|
var length = points.Length - (points.Length - 1) % 3;
|
|
|
|
var steps = length / 3;
|
|
var step = (int)Math.Floor(ScaleClamp(t, 0, 1, 0, steps));
|
|
|
|
int i = step * 3;
|
|
|
|
var p0 = points[i];
|
|
var p1 = points[i + 1];
|
|
var p2 = points[i + 2];
|
|
var p3 = points[i + 3];
|
|
|
|
var segment = 1 / (float)(steps);
|
|
|
|
var st = ScaleClamp(t - segment * step, 0, segment, 0, 1);
|
|
|
|
return BezierCurvePoint(st, p0, p1, p2, p3);
|
|
}
|
|
|
|
static float CatmullRom(float a, float b, float c, float d, float t) {
|
|
// Hermite Spline... I think?
|
|
return 0.5f * (2 * b + (c - a) * t + (2 * a - 5 * b + 4 * c - d) * (t * t) + (3 * b - a - 3 * c + d) * (t * t * t));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the spline path point.
|
|
/// </summary>
|
|
/// <param name="t">The progress on the path from 0 to 1.</param>
|
|
/// <param name="path">The set of points making the path.</param>
|
|
/// <returns>The position on the spline path for the progress t.</returns>
|
|
public static Vector2 SplinePathPoint(float t, params Vector2[] path) {
|
|
// Shoutouts to Chevy Ray
|
|
int b = (int)((path.Length - 1) * t);
|
|
int a = b - 1;
|
|
int c = b + 1;
|
|
int d = c + 1;
|
|
a = (int)Util.Clamp(a, 0, path.Length - 1);
|
|
b = (int)Util.Clamp(b, 0, path.Length - 1);
|
|
c = (int)Util.Clamp(c, 0, path.Length - 1);
|
|
d = (int)Util.Clamp(d, 0, path.Length - 1);
|
|
float i = 1f / (path.Length - 1);
|
|
t = (t - b * i) / i;
|
|
return new Vector2(
|
|
CatmullRom(path[a].X, path[b].X, path[c].X, path[d].X, t),
|
|
CatmullRom(path[a].Y, path[b].Y, path[c].Y, path[d].Y, t)
|
|
);
|
|
}
|
|
|
|
/// <summary>
|
|
/// The Width of the desktop. Note that this is for the display that the game was initialized in.
|
|
/// </summary>
|
|
public static int DesktopWidth {
|
|
get {
|
|
return (int)SFML.Window.VideoMode.DesktopMode.Width;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// The height of the desktop. Note that this is for the display that the game was initialized in.
|
|
/// </summary>
|
|
public static int DesktopHeight {
|
|
get {
|
|
return (int)SFML.Window.VideoMode.DesktopMode.Height;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Combines Lists of the same type into one list. Does not remove duplicates.
|
|
/// </summary>
|
|
/// <typeparam name="T">The type of object.</typeparam>
|
|
/// <param name="lists">The Lists to combine.</param>
|
|
/// <returns>One List to rule them all.</returns>
|
|
public static List<T> ListCombine<T>(params List<T>[] lists) {
|
|
if (lists.Length == 0) return null;
|
|
if (lists.Length == 1) return lists[0];
|
|
|
|
var list = lists[0];
|
|
for (int i = 1; i < lists.Length; i++) {
|
|
list.AddRange(lists[i]);
|
|
}
|
|
return list;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get all types with a specific Attribute.
|
|
/// </summary>
|
|
/// <typeparam name="T">The type of Attribute.</typeparam>
|
|
/// <param name="inherit">Check inherited classes.</param>
|
|
/// <returns>All types with the Attribute T.</returns>
|
|
public static IEnumerable<Type> GetTypesWithAttribute<T>(bool inherit = true) where T : Attribute {
|
|
return from a in AppDomain.CurrentDomain.GetAssemblies()
|
|
from t in a.GetTypes()
|
|
where t.IsDefined(typeof(T), inherit)
|
|
select t;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Download the json feed of a Google Spreadsheet from the key and sheet id.
|
|
/// Only works on publicly shared sheets.
|
|
/// </summary>
|
|
/// <param name="docKey">The long id of the sheet. Usually a big set of numbers and letters in the url.</param>
|
|
/// <param name="sheetId">The sheet id. Usually a shorter set of letters and numbers. Default is "od6".</param>
|
|
/// <returns>A json feed of the spreadsheet.</returns>
|
|
public static string GetSpreadsheetJsonById(string docKey, string sheetId = "od6") {
|
|
var url = string.Format("https://spreadsheets.google.com/feeds/list/{0}/{1}/public/values?alt=json", docKey, sheetId);
|
|
return DownloadData(url);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Download the json feed of a Google Spreadsheet from the key and the sheet name.
|
|
/// Only works on publicly shared sheets.
|
|
/// </summary>
|
|
/// <param name="docKey">The long id of the sheet. Usually a big set of numbers and letters in the url.</param>
|
|
/// <param name="sheetName">The name of the sheet as it appears on the bottom left of the spreadsheet window.</param>
|
|
/// <returns>A json feed of the spreadsheet.</returns>
|
|
public static string GetSpreadsheetJsonByName(string docKey, string sheetName) {
|
|
return GetSpreadsheetJsonById(docKey, GetSpreadsheetId(docKey, sheetName));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the internal id of a Google Spreadsheet sheet.
|
|
/// Only works on publicly shared sheets.
|
|
/// </summary>
|
|
/// <param name="docKey">The long id of the sheet. Usually a big set of numbers and letters in the url.</param>
|
|
/// <param name="sheetName">The name of the sheet as it appears on the bottom left of the spreadsheet window.</param>
|
|
/// <returns>The internal id of the sheet.</returns>
|
|
public static string GetSpreadsheetId(string docKey, string sheetName) {
|
|
var url = string.Format("https://spreadsheets.google.com/feeds/worksheets/{0}/public/full", docKey);
|
|
var xml = DownloadData(url);
|
|
XElement xDoc = XElement.Parse(xml);
|
|
var ns = "{" + xDoc.GetDefaultNamespace() + "}";
|
|
var id = "od6";
|
|
|
|
xDoc.Elements(ns + "entry")
|
|
.Where(x => x.Element(ns + "title").Value == sheetName)
|
|
.Each(x => x.Elements(ns + "id")
|
|
.Each(xx => {
|
|
id = xx.Value.Split('/').Last();
|
|
}));
|
|
|
|
return id;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the internal ids of all sheets in a Google Spreadsheet.
|
|
/// Only works on publicly shared sheets.
|
|
/// </summary>
|
|
/// <param name="docKey">The long id of the sheet. Usually a big set of numbers and letters in the url.</param>
|
|
/// <returns>A dictionary with sheet names as the keys, and ids as the values.</returns>
|
|
public static Dictionary<string, string> GetSpreadsheetIds(string docKey) {
|
|
var url = string.Format("https://spreadsheets.google.com/feeds/worksheets/{0}/public/full", docKey);
|
|
var xml = DownloadData(url);
|
|
XElement xDoc = XElement.Parse(xml);
|
|
var ns = "{" + xDoc.GetDefaultNamespace() + "}";
|
|
var dict = new Dictionary<string, string>();
|
|
var name = "";
|
|
var id = "";
|
|
|
|
xDoc.Elements(ns + "entry")
|
|
.Each(e => {
|
|
name = e.Element(ns + "title").Value;
|
|
id = e.Element(ns + "id").Value.Split('/').Last();
|
|
dict.Add(name, id);
|
|
});
|
|
|
|
return dict;
|
|
}
|
|
|
|
// Download url cache stuff
|
|
static string downloadData;
|
|
static string downloadUrl = "";
|
|
|
|
static string DownloadData(string url) {
|
|
if (downloadUrl != url) {
|
|
downloadData = new WebClient().DownloadString(url);
|
|
downloadUrl = url;
|
|
}
|
|
return downloadData;
|
|
}
|
|
|
|
#endregion
|
|
|
|
}
|
|
}
|