#region --- License --- /* Licensed under the MIT/X11 license. * Copyright (c) 2006-2008 the OpenTK Team. * This notice may not be removed from any source distribution. * See license.txt for licensing detailed licensing details. * * Contributions by Andy Gill, James Talton and Georg Wächter. */ #endregion --- License --- using System; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.Intrinsics; namespace Robust.Shared.Maths { /// /// Contains common mathematical functions and constants. /// public static class MathHelper { #region Fields /// /// Defines the value of Pi as a . /// public const float Pi = MathF.PI; /// /// Defines the value of Pi divided by two as a . /// public const float PiOver2 = Pi / 2; /// /// Defines the value of Pi divided by three as a . /// public const float PiOver3 = Pi / 3; /// /// Definesthe value of Pi divided by four as a . /// public const float PiOver4 = Pi / 4; /// /// Defines the value of Pi divided by six as a . /// public const float PiOver6 = Pi / 6; /// /// Defines the value of Pi multiplied by two as a . /// public const float TwoPi = 2 * Pi; /// /// Defines the value of Pi multiplied by 3 and divided by two as a . /// public const float ThreePiOver2 = 3 * Pi / 2; /// /// Defines the value of E as a . /// public const float E = MathF.E; /// /// Defines the base-10 logarithm of E. /// public const float Log10E = 0.434294482f; /// /// Defines the base-2 logarithm of E. /// public const float Log2E = 1.442695041f; #endregion Fields #region Public Members #region NextPowerOfTwo /// /// Returns the next power of two that is larger than the specified number. /// /// The specified number. /// The next power of two. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static long NextPowerOfTwo(long n) { if (n <= 0) throw new ArgumentOutOfRangeException(nameof(n), "Must be positive."); return 1L << (BitOperations.Log2((ulong)n) + 1); } /// /// Returns the next power of two that is larger than the specified number. /// /// The specified number. /// The next power of two. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int NextPowerOfTwo(int n) { if (n <= 0) throw new ArgumentOutOfRangeException(nameof(n), "Must be positive."); return 1 << (BitOperations.Log2((uint)n) + 1); } /// /// Returns the next power of two that is larger than the specified number. /// /// The specified number. /// The next power of two. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static float NextPowerOfTwo(float n) { if (!float.IsFinite(n)) throw new ArgumentOutOfRangeException(nameof(n), "Must be a number."); if (n <= 0) throw new ArgumentOutOfRangeException(nameof(n), "Must be positive."); return (float) NextPowerOfTwo((double) n); } /// /// Returns the next power of two that is larger than the specified number. /// /// The specified number. /// The next power of two. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double NextPowerOfTwo(double n) { if (!double.IsFinite(n)) throw new ArgumentOutOfRangeException(nameof(n), "Must be a number."); if (n <= 0) throw new ArgumentOutOfRangeException(nameof(n), "Must be positive."); // Don't return negative powers of two, that's nonsense. if (n < 1) return 1.0; return Math.Pow(2, Math.Floor(Math.Log(n, 2)) + 1); } #endregion NextPowerOfTwo #region NextMultipleOf /// /// Returns the next closest multiple of a number. /// /// Closest value /// Returns the multiple of this number. /// The next closest multiple of a number. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double NextMultipleOf(double value, double of) { return Math.Ceiling(value / of) * of; } /// /// Returns the next closest multiple of a number. /// /// Closest value /// Returns the multiple of this number. /// The next closest multiple of a number. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static float NextMultipleOf(float value, float of) { return MathF.Ceiling(value / of) * of; } /// /// Returns the next closest multiple of a number. /// /// Closest value /// Returns the multiple of this number. /// The next closest multiple of a number. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static long NextMultipleOf(long value, long of) { return ((value + of - 1) / of) * of; } /// /// Returns the next closest multiple of a number. /// /// Closest value /// Returns the multiple of this number. /// The next closest multiple of a number. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int NextMultipleOf(int value, int of) { return ((value + of - 1) / of) * of; } #endregion #region Factorial /// Calculates the factorial of a given natural number. /// /// The number. /// n! [MethodImpl(MethodImplOptions.AggressiveInlining)] public static long Factorial(int n) { long result = 1; for (; n > 1; n--) result *= n; return result; } #endregion Factorial #region BinomialCoefficient /// /// Calculates the binomial coefficient above . /// /// The n. /// The k. /// n! / (k! * (n - k)!) [MethodImpl(MethodImplOptions.AggressiveInlining)] public static long BinomialCoefficient(int n, int k) { return Factorial(n) / (Factorial(k) * Factorial(n - k)); } #endregion BinomialCoefficient #region DegreesToRadians /// /// Convert degrees to radians /// /// An angle in degrees /// The angle expressed in radians [MethodImpl(MethodImplOptions.AggressiveInlining)] public static float DegreesToRadians(float degrees) { const float degToRad = Pi / 180.0f; return degrees * degToRad; } /// /// Convert radians to degrees /// /// An angle in radians /// The angle expressed in degrees [MethodImpl(MethodImplOptions.AggressiveInlining)] public static float RadiansToDegrees(float radians) { const float radToDeg = 180.0f / Pi; return radians * radToDeg; } /// /// Convert degrees to radians /// /// An angle in degrees /// The angle expressed in radians [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double DegreesToRadians(double degrees) { const double degToRad = Math.PI / 180.0; return degrees * degToRad; } /// /// Convert radians to degrees /// /// An angle in radians /// The angle expressed in degrees [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double RadiansToDegrees(double radians) { const double radToDeg = 180.0 / Math.PI; return radians * radToDeg; } #endregion DegreesToRadians #region Swap /// /// Swaps two double values. /// /// The first value. /// The second value. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Swap(ref double a, ref double b) { var temp = a; a = b; b = temp; } /// /// Swaps two float values. /// /// The first value. /// The second value. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Swap(ref float a, ref float b) { var temp = a; a = b; b = temp; } #endregion Swap #region MinMax /// /// Returns the minimum of 4 values /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static float Min(float a, float b, float c, float d) { return MathF.Min(a, MathF.Min(b, MathF.Min(c, d))); } /// /// Returns the maximum of 4 values /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static float Max(float a, float b, float c, float d) { return MathF.Max(a, MathF.Max(b, MathF.Max(c, d))); } /// /// Returns the median value out of a, b and c. /// /// The median. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static float Median(float a, float b, float c) { return MathF.Max(MathF.Min(a, b), MathF.Min(MathF.Max(a, b), c)); } public static TimeSpan Min(TimeSpan a, TimeSpan b) { return a < b ? a : b; } public static TimeSpan Max(TimeSpan a, TimeSpan b) { return a > b ? a : b; } #endregion MinMax #region Mod /// /// This method provides floored modulus. /// C-like languages use truncated modulus for their '%' operator. /// /// The dividend. /// The divisor. /// The remainder. [DebuggerStepThrough] [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double Mod(double n, double d) { return n - Math.Floor(n / d) * d; } /// /// This method provides floored modulus. /// C-like languages use truncated modulus for their '%' operator. /// /// The dividend. /// The divisor. /// The remainder. [DebuggerStepThrough] [MethodImpl(MethodImplOptions.AggressiveInlining)] public static float Mod(float n, float d) { return n - MathF.Floor(n / d) * d; } /// /// This method provides floored modulus. /// C-like languages use truncated modulus for their '%' operator. /// /// The dividend. /// The divisor. /// The remainder. [DebuggerStepThrough] [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int Mod(int n, int d) { var r = n % d; return r < 0 ? r + d : r; } #endregion Mod #region Clamp /// /// Clamps between and . /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static T Clamp(T val, T min, T max) where T : IComparable { if (val.CompareTo(min) < 0) return min; if (val.CompareTo(max) > 0) return max; return val; } /// /// Clamps between and . /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static sbyte Clamp(sbyte val, sbyte min, sbyte max) { return Math.Max(Math.Min(val, max), min); } /// /// Clamps between and . /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static byte Clamp(byte val, byte min, byte max) { return Math.Max(Math.Min(val, max), min); } /// /// Clamps between and . /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static short Clamp(short val, short min, short max) { return Math.Max(Math.Min(val, max), min); } /// /// Clamps between and . /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static ushort Clamp(ushort val, ushort min, ushort max) { return Math.Max(Math.Min(val, max), min); } /// /// Clamps between and . /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int Clamp(int val, int min, int max) { return Math.Max(Math.Min(val, max), min); } /// /// Clamps between and . /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static uint Clamp(uint val, uint min, uint max) { return Math.Max(Math.Min(val, max), min); } /// /// Clamps between and . /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static long Clamp(long val, long min, long max) { return Math.Max(Math.Min(val, max), min); } /// /// Clamps between and . /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static ulong Clamp(ulong val, ulong min, ulong max) { return Math.Max(Math.Min(val, max), min); } /// /// Clamps between and . /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static float Clamp(float val, float min, float max) { return Math.Max(Math.Min(val, max), min); } /// /// Clamps between and . /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double Clamp(double val, double min, double max) { return Math.Max(Math.Min(val, max), min); } /// /// Clamps between 0 and 1. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static float Clamp01(float val) { return Clamp(val, 0, 1); } #endregion Clamp #region CloseToPercent /// /// Returns whether two floating point numbers are within of eachother /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool CloseToPercent(float a, float b, double percentage = .00001) { // .001% of the smaller value for the epsilon check as per MSDN reference suggestion double epsilon = Math.Max(Math.Max(Math.Abs(a), Math.Abs(b)) * percentage, percentage); return Math.Abs(a - b) <= epsilon; } /// /// Returns whether two vectors are within of each other /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool CloseToPercent(Vector4 a, Vector4 b, float percentage = .00001f) { a = Vector4.Abs(a); b = Vector4.Abs(b); var p = new Vector4(percentage); var epsilon = Vector4.Max(Vector4.Max(a, b) * p, p); var delta = Vector4.Abs(a - b); return delta.X <= epsilon.X && delta.Y <= epsilon.Y && delta.Z <= epsilon.Z && delta.W <= epsilon.W; } /// /// Returns whether two colours are within of each other /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool CloseToPercent(Color a, Color b, float percentage = .00001f) => CloseToPercent(a.RGBA, b.RGBA, percentage); /// /// Returns whether two floating point numbers are within of eachother /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool CloseToPercent(float a, double b, double percentage = .00001) { // .001% of the smaller value for the epsilon check as per MSDN reference suggestion double epsilon = Math.Max(Math.Max(Math.Abs(a), Math.Abs(b)) * percentage, percentage); return Math.Abs(a - b) <= epsilon; } /// /// Returns whether two floating point numbers are within of eachother /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool CloseToPercent(double a, float b, double percentage = .00001) { // .001% of the smaller value for the epsilon check as per MSDN reference suggestion double epsilon = Math.Max(Math.Max(Math.Abs(a), Math.Abs(b)) * percentage, percentage); return Math.Abs(a - b) <= epsilon; } /// /// Returns whether two floating point numbers are within of eachother /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool CloseToPercent(double a, double b, double percentage = .00001) { // .001% of the smaller value for the epsilon check as per MSDN reference suggestion double epsilon = Math.Max(Math.Max(Math.Abs(a), Math.Abs(b)) * percentage, percentage); return Math.Abs(a - b) <= epsilon; } /// /// Returns whether two floating point numbers are within of eachother /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool CloseToPercent(Vector128 a, Vector128 b, float percentage = .00001f) { var epsilon = Vector128.Max(Vector128.Max(Vector128.Abs(a), Vector128.Abs(b)) * Vector128.Create(percentage), Vector128.Create(percentage)); var result = Vector128.LessThanOrEqual(Vector128.Abs(a - b), epsilon); return Vector128.EqualsAll(result.AsInt32(), Vector128.AllBitsSet); } #endregion CloseToPercent #region CloseTo /// /// Returns whether two floating point numbers are within of eachother /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool CloseTo(float a, float b, float tolerance = .0000001f) { return MathF.Abs(a - b) <= tolerance; } /// /// Returns whether two floating point numbers are within of eachother /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool CloseTo(double a, double b, double tolerance = .0000001) { return Math.Abs(a - b) <= tolerance; } #endregion #region Lerp /// /// Linearly interpolates between to , returning the value at . /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double Lerp(double a, double b, double blend) { return a + (b - a) * blend; } /// /// Linearly interpolates between to , returning the value at . /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static float Lerp(float a, float b, float blend) { return a + (b - a) * blend; } public static TimeSpan Lerp(TimeSpan a, TimeSpan b, double t) { return a + t * (b - a); } #endregion Lerp #region InterpolateCubic /// /// Cubic interpolates form to , where and are handles and returns the position at /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static float InterpolateCubic(float preA, float a, float b, float postB, float t) { return a + 0.5f * t * (b - preA + t * (2.0f * preA - 5.0f * a + 4.0f * b - postB + t * (3.0f * (a - b) + postB - preA))); } /// /// Cubic interpolates form to , where and are handles and returns the position at /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double InterpolateCubic(double preA, double a, double b, double postB, double t) { return a + 0.5 * t * (b - preA + t * (2.0 * preA - 5.0 * a + 4.0 * b - postB + t * (3.0 * (a - b) + postB - preA))); } #endregion InterpolateCubic #region Intersections // MIT License // Copyright (c) 2019 Erin Catto // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. /// /// Gets the intersection between a line and a circle. /// Essentially a reduced raycast. /// /// public static bool TryGetIntersecting(Vector2 start, Vector2 end, float radius, [NotNullWhen(true)] out Vector2? point) { var maxFraction = (end - start).Length(); float b = Vector2.Dot(start, start) - radius * radius; // Solve quadratic equation. var r = end - start; float c = Vector2.Dot(start, r); float rr = Vector2.Dot(r, r); float sigma = c * c - rr * b; // Check for negative discriminant and short segment. if (sigma < 0.0f || rr < float.Epsilon) { point = null; return false; } // Find the point of intersection of the line with the circle. float a = -(c + MathF.Sqrt(sigma)); // Is the intersection point on the segment? if (0.0f <= a && a <= maxFraction * rr) { a /= rr; var lineToEnd = end - start; // a is a fraction so need to work out the distance along the line we need to be. point = start + lineToEnd * a; return true; } point = null; return false; } #endregion /// /// Round up (ceiling) a value to a multiple of a known power of two. /// /// The value to round up. /// /// The power of two to round up to a multiple of. The result is undefined if this is not a power of two. /// /// /// The result is undefined if either value is negative. /// /// The type of integer to operate on. /// /// /// MathHelper.CeilMultiplyPowerOfTwo(5, 4) // 8 /// MathHelper.CeilMultiplyPowerOfTwo(4, 4) // 4 /// MathHelper.CeilMultiplyPowerOfTwo(8, 4) // 8 /// /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static T CeilMultipleOfPowerOfTwo(T value, T powerOfTwo) where T : IBinaryInteger { var mask = powerOfTwo - T.One; var remainder = value & mask; return remainder == T.Zero ? value : (value | mask) + T.One; } public static bool IsValid(this float value) { if (float.IsNaN(value)) { return false; } if (float.IsInfinity(value)) { return false; } return true; } #endregion Public Members } }