Files
crystall-punk-14/Content.Shared/FixedPoint/FixedPoint2.cs

334 lines
9.1 KiB
C#
Raw Permalink Normal View History

using System.Globalization;
2020-04-05 11:36:12 +02:00
using System.Linq;
using Robust.Shared.Serialization;
2023-08-25 20:40:42 +02:00
using Robust.Shared.Utility;
2020-04-05 11:36:12 +02:00
namespace Content.Shared.FixedPoint
2020-04-05 11:36:12 +02:00
{
/// <summary>
/// Represents a quantity of something, to a precision of 0.01.
/// To enforce this level of precision, floats are shifted by 2 decimal points, rounded, and converted to an int.
/// </summary>
[Serializable, CopyByRef]
public struct FixedPoint2 : ISelfSerialize, IComparable<FixedPoint2>, IEquatable<FixedPoint2>, IFormattable
2020-04-05 11:36:12 +02:00
{
2023-01-12 16:41:40 +13:00
public int Value { get; private set; }
private const int Shift = 2;
2023-08-25 20:40:42 +02:00
private const int ShiftConstant = 100; // Must be equal to pow(10, Shift)
2020-04-05 11:36:12 +02:00
public static FixedPoint2 MaxValue { get; } = new(int.MaxValue);
public static FixedPoint2 Epsilon { get; } = new(1);
public static FixedPoint2 Zero { get; } = new(0);
2020-04-05 11:36:12 +02:00
// This value isn't picked by any proper testing, don't @ me.
private const float FloatEpsilon = 0.00001f;
2023-08-25 20:40:42 +02:00
#if DEBUG
static FixedPoint2()
{
// ReSharper disable once CompareOfFloatsByEqualityOperator
DebugTools.Assert(Math.Pow(10, Shift) == ShiftConstant, "ShiftConstant must be equal to pow(10, Shift)");
}
#endif
private readonly double ShiftDown()
2020-04-05 11:36:12 +02:00
{
2023-08-25 20:40:42 +02:00
return Value / (double) ShiftConstant;
2020-04-05 11:36:12 +02:00
}
private FixedPoint2(int value)
2020-04-05 11:36:12 +02:00
{
2023-01-12 16:41:40 +13:00
Value = value;
2020-04-05 11:36:12 +02:00
}
public static FixedPoint2 New(int value)
2020-04-05 11:36:12 +02:00
{
2023-08-25 20:40:42 +02:00
return new(value * ShiftConstant);
2020-04-05 11:36:12 +02:00
}
2023-01-12 16:41:40 +13:00
public static FixedPoint2 FromCents(int value) => new(value);
public static FixedPoint2 FromHundredths(int value) => new(value);
public static FixedPoint2 New(float value)
2020-04-05 11:36:12 +02:00
{
return new((int) ApplyFloatEpsilon(value * ShiftConstant));
}
private static float ApplyFloatEpsilon(float value)
{
return value + FloatEpsilon * Math.Sign(value);
}
private static double ApplyFloatEpsilon(double value)
{
return value + FloatEpsilon * Math.Sign(value);
2020-04-08 19:07:33 +02:00
}
2023-08-25 20:40:42 +02:00
/// <summary>
/// Create the closest <see cref="FixedPoint2"/> for a float value, always rounding up.
/// </summary>
public static FixedPoint2 NewCeiling(float value)
2020-04-08 19:07:33 +02:00
{
2023-08-25 20:40:42 +02:00
return new((int) MathF.Ceiling(value * ShiftConstant));
2020-04-05 11:36:12 +02:00
}
public static FixedPoint2 New(double value)
2020-04-05 11:36:12 +02:00
{
return new((int) ApplyFloatEpsilon(value * ShiftConstant));
2020-04-05 11:36:12 +02:00
}
public static FixedPoint2 New(string value)
2020-04-08 19:07:33 +02:00
{
return New(Parse.Float(value));
2020-04-08 19:07:33 +02:00
}
public static FixedPoint2 operator +(FixedPoint2 a) => a;
2020-04-05 11:36:12 +02:00
2023-01-12 16:41:40 +13:00
public static FixedPoint2 operator -(FixedPoint2 a) => new(-a.Value);
2020-04-05 11:36:12 +02:00
public static FixedPoint2 operator +(FixedPoint2 a, FixedPoint2 b)
2023-01-12 16:41:40 +13:00
=> new(a.Value + b.Value);
2020-04-05 11:36:12 +02:00
public static FixedPoint2 operator -(FixedPoint2 a, FixedPoint2 b)
2023-01-12 16:41:40 +13:00
=> new(a.Value - b.Value);
2020-04-05 11:36:12 +02:00
public static FixedPoint2 operator *(FixedPoint2 a, FixedPoint2 b)
2020-04-05 11:36:12 +02:00
{
return new(b.Value * a.Value / ShiftConstant);
2020-04-05 11:36:12 +02:00
}
public static FixedPoint2 operator *(FixedPoint2 a, float b)
2020-04-05 11:36:12 +02:00
{
return new((int) ApplyFloatEpsilon(a.Value * b));
2020-04-05 11:36:12 +02:00
}
public static FixedPoint2 operator *(FixedPoint2 a, double b)
2020-04-05 11:36:12 +02:00
{
return new((int) ApplyFloatEpsilon(a.Value * b));
2020-04-05 11:36:12 +02:00
}
public static FixedPoint2 operator *(FixedPoint2 a, int b)
{
2023-01-12 16:41:40 +13:00
return new(a.Value * b);
}
public static FixedPoint2 operator /(FixedPoint2 a, FixedPoint2 b)
2020-04-05 11:36:12 +02:00
{
return new((int) (ShiftConstant * (long) a.Value / b.Value));
2020-04-05 11:36:12 +02:00
}
public static FixedPoint2 operator /(FixedPoint2 a, float b)
{
return new((int) ApplyFloatEpsilon(a.Value / b));
}
public static bool operator <=(FixedPoint2 a, int b)
2020-04-05 11:36:12 +02:00
{
return a <= New(b);
2020-04-05 11:36:12 +02:00
}
public static bool operator >=(FixedPoint2 a, int b)
2020-04-05 11:36:12 +02:00
{
return a >= New(b);
2020-04-05 11:36:12 +02:00
}
public static bool operator <(FixedPoint2 a, int b)
{
return a < New(b);
}
public static bool operator >(FixedPoint2 a, int b)
{
return a > New(b);
}
public static bool operator ==(FixedPoint2 a, int b)
2020-04-05 11:36:12 +02:00
{
return a == New(b);
2020-04-05 11:36:12 +02:00
}
public static bool operator !=(FixedPoint2 a, int b)
2020-04-05 11:36:12 +02:00
{
return a != New(b);
2020-04-05 11:36:12 +02:00
}
public static bool operator ==(FixedPoint2 a, FixedPoint2 b)
2020-04-22 04:23:12 +10:00
{
return a.Equals(b);
}
public static bool operator !=(FixedPoint2 a, FixedPoint2 b)
2020-04-22 04:23:12 +10:00
{
return !a.Equals(b);
}
public static bool operator <=(FixedPoint2 a, FixedPoint2 b)
2020-04-05 11:36:12 +02:00
{
2023-01-12 16:41:40 +13:00
return a.Value <= b.Value;
2020-04-05 11:36:12 +02:00
}
public static bool operator >=(FixedPoint2 a, FixedPoint2 b)
2020-04-05 11:36:12 +02:00
{
2023-01-12 16:41:40 +13:00
return a.Value >= b.Value;
2020-04-05 11:36:12 +02:00
}
public static bool operator <(FixedPoint2 a, FixedPoint2 b)
2020-04-05 11:36:12 +02:00
{
2023-01-12 16:41:40 +13:00
return a.Value < b.Value;
2020-04-05 11:36:12 +02:00
}
public static bool operator >(FixedPoint2 a, FixedPoint2 b)
2020-04-05 11:36:12 +02:00
{
2023-01-12 16:41:40 +13:00
return a.Value > b.Value;
2020-04-05 11:36:12 +02:00
}
public readonly float Float()
2020-04-05 11:36:12 +02:00
{
return (float) ShiftDown();
}
public readonly double Double()
2020-04-05 11:36:12 +02:00
{
2020-04-08 19:07:33 +02:00
return ShiftDown();
2020-04-05 11:36:12 +02:00
}
public readonly int Int()
2020-04-05 11:36:12 +02:00
{
return Value / ShiftConstant;
2020-04-05 11:36:12 +02:00
}
// Implicit operators ftw
public static implicit operator FixedPoint2(float n) => FixedPoint2.New(n);
public static implicit operator FixedPoint2(double n) => FixedPoint2.New(n);
public static implicit operator FixedPoint2(int n) => FixedPoint2.New(n);
public static explicit operator float(FixedPoint2 n) => n.Float();
public static explicit operator double(FixedPoint2 n) => n.Double();
public static explicit operator int(FixedPoint2 n) => n.Int();
public static FixedPoint2 Min(params FixedPoint2[] fixedPoints)
2020-04-05 11:36:12 +02:00
{
return fixedPoints.Min();
}
public static FixedPoint2 Min(FixedPoint2 a, FixedPoint2 b)
{
return a < b ? a : b;
2020-04-05 11:36:12 +02:00
}
public static FixedPoint2 Max(FixedPoint2 a, FixedPoint2 b)
{
return a > b ? a : b;
}
public static int Sign(FixedPoint2 value)
{
if (value < Zero)
{
return -1;
}
if (value > Zero)
{
return 1;
}
return 0;
}
2021-11-10 03:06:27 -07:00
public static FixedPoint2 Abs(FixedPoint2 a)
{
return FromCents(Math.Abs(a.Value));
2021-11-10 03:06:27 -07:00
}
public static FixedPoint2 Dist(FixedPoint2 a, FixedPoint2 b)
{
return FixedPoint2.Abs(a - b);
}
public static FixedPoint2 Clamp(FixedPoint2 number, FixedPoint2 min, FixedPoint2 max)
{
if (min > max)
{
throw new ArgumentException($"{nameof(min)} {min} cannot be larger than {nameof(max)} {max}");
}
return number < min ? min : number > max ? max : number;
}
public override readonly bool Equals(object? obj)
2020-04-05 11:36:12 +02:00
{
return obj is FixedPoint2 unit &&
2023-01-12 16:41:40 +13:00
Value == unit.Value;
2020-04-05 11:36:12 +02:00
}
public override readonly int GetHashCode()
2020-04-05 11:36:12 +02:00
{
// ReSharper disable once NonReadonlyMemberInGetHashCode
2023-01-12 16:41:40 +13:00
return HashCode.Combine(Value);
2020-04-05 11:36:12 +02:00
}
2020-04-08 19:07:33 +02:00
public void Deserialize(string value)
{
// TODO implement "lossless" serializer.
// I.e., dont use floats.
if (value == "MaxValue")
Value = int.MaxValue;
else
this = New(Parse.Double(value));
2020-04-08 19:07:33 +02:00
}
public override readonly string ToString() => $"{ShiftDown().ToString(CultureInfo.InvariantCulture)}";
public string ToString(string? format, IFormatProvider? formatProvider)
{
return ToString();
}
public readonly string Serialize()
2020-04-08 19:07:33 +02:00
{
// TODO implement "lossless" serializer.
// I.e., dont use floats.
if (Value == int.MaxValue)
return "MaxValue";
2020-04-08 19:07:33 +02:00
return ToString();
}
public readonly bool Equals(FixedPoint2 other)
{
2023-01-12 16:41:40 +13:00
return Value == other.Value;
}
public readonly int CompareTo(FixedPoint2 other)
{
2023-01-12 16:41:40 +13:00
if (other.Value > Value)
{
return -1;
}
2023-01-12 16:41:40 +13:00
if (other.Value < Value)
{
return 1;
}
return 0;
}
}
public static class FixedPoint2EnumerableExt
{
public static FixedPoint2 Sum(this IEnumerable<FixedPoint2> source)
{
var acc = FixedPoint2.Zero;
foreach (var n in source)
{
acc += n;
}
return acc;
}
2020-04-05 11:36:12 +02:00
}
}