// Copyright (c) Xenko contributors (https://xenko.com) and Silicon Studio Corp. (https://www.siliconstudio.co.jp)
// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.
//
// -----------------------------------------------------------------------------
// Original code from SlimMath project. http://code.google.com/p/slimmath/
// Greetings to SlimDX Group. Original code published with the following license:
// -----------------------------------------------------------------------------
/*
* Copyright (c) 2007-2011 SlimDX Group
*
* 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.
*/
using System;
using System.Runtime.CompilerServices;
using System.Runtime.Serialization;
namespace math
{
///
/// Common utility methods for math operations.
///
public static class MathUtil
{
///
/// The value for which all absolute numbers smaller than are considered equal to zero.
///
public const float ZeroTolerance = 1e-6f; // Value a 8x higher than 1.19209290E-07F
///
/// The value for which all absolute numbers smaller than are considered equal to zero.
///
public const double ZeroToleranceDouble = double.Epsilon * 8;
///
/// A value specifying the approximation of π which is 180 degrees.
///
public const float Pi = (float)Math.PI;
///
/// A value specifying the approximation of 2π which is 360 degrees.
///
public const float TwoPi = (float)(2 * Math.PI);
///
/// A value specifying the approximation of π/2 which is 90 degrees.
///
public const float PiOverTwo = (float)(Math.PI / 2);
///
/// A value specifying the approximation of π/4 which is 45 degrees.
///
public const float PiOverFour = (float)(Math.PI / 4);
///
/// Checks if a and b are almost equals, taking into account the magnitude of floating point numbers (unlike method). See Remarks.
/// See remarks.
///
/// The left value to compare.
/// The right value to compare.
/// true if a almost equal to b, false otherwise
///
/// The code is using the technique described by Bruce Dawson in
/// Comparing Floating point numbers 2012 edition.
///
public static unsafe bool NearEqual(float a, float b)
{
// Check if the numbers are really close -- needed
// when comparing numbers near zero.
if (IsZero(a - b))
return true;
// Original from Bruce Dawson: http://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/
int aInt = *(int*)&a;
int bInt = *(int*)&b;
// Different signs means they do not match.
if ((aInt < 0) != (bInt < 0))
return false;
// Find the difference in ULPs.
int ulp = Math.Abs(aInt - bInt);
// Choose of maxUlp = 4
// according to http://code.google.com/p/googletest/source/browse/trunk/include/gtest/internal/gtest-internal.h
const int maxUlp = 4;
return (ulp <= maxUlp);
}
///
/// Determines whether the specified value is close to zero (0.0f).
///
/// The floating value.
/// true if the specified value is close to zero (0.0f); otherwise, false.
public static bool IsZero(float a)
{
return Math.Abs(a) < ZeroTolerance;
}
///
/// Determines whether the specified value is close to zero (0.0f).
///
/// The floating value.
/// true if the specified value is close to zero (0.0f); otherwise, false.
public static bool IsZero(double a)
{
return Math.Abs(a) < ZeroToleranceDouble;
}
///
/// Determines whether the specified value is close to one (1.0f).
///
/// The floating value.
/// true if the specified value is close to one (1.0f); otherwise, false.
public static bool IsOne(float a)
{
return IsZero(a - 1.0f);
}
///
/// Checks if a - b are almost equals within a float epsilon.
///
/// The left value to compare.
/// The right value to compare.
/// Epsilon value
/// true if a almost equal to b within a float epsilon, false otherwise
public static bool WithinEpsilon(float a, float b, float epsilon)
{
float num = a - b;
return ((-epsilon <= num) && (num <= epsilon));
}
///
/// Creates a one-dimensional array of the specified and filled with the specified .
///
/// The Type of the array to create.
/// The value to fill the array with.
/// The size of the array to create.
/// A new one-dimensional array of the specified type with the specified length and filled with the specified value.
public static T[] Array(T value, int length)
{
var result = new T[length];
for (var i = 0; i < length; i++)
result[i] = value;
return result;
}
///
/// Converts revolutions to degrees.
///
/// The value to convert.
/// The converted value.
public static float RevolutionsToDegrees(float revolution)
{
return revolution * 360.0f;
}
///
/// Converts revolutions to radians.
///
/// The value to convert.
/// The converted value.
public static float RevolutionsToRadians(float revolution)
{
return revolution * TwoPi;
}
///
/// Converts revolutions to gradians.
///
/// The value to convert.
/// The converted value.
public static float RevolutionsToGradians(float revolution)
{
return revolution * 400.0f;
}
///
/// Converts degrees to revolutions.
///
/// The value to convert.
/// The converted value.
public static float DegreesToRevolutions(float degree)
{
return degree / 360.0f;
}
///
/// Converts degrees to radians.
///
/// The value to convert.
/// The converted value.
public static float DegreesToRadians(float degree)
{
return degree * (Pi / 180.0f);
}
///
/// Converts radians to revolutions.
///
/// The value to convert.
/// The converted value.
public static float RadiansToRevolutions(float radian)
{
return radian / TwoPi;
}
///
/// Converts radians to gradians.
///
/// The value to convert.
/// The converted value.
public static float RadiansToGradians(float radian)
{
return radian * (200.0f / Pi);
}
///
/// Converts gradians to revolutions.
///
/// The value to convert.
/// The converted value.
public static float GradiansToRevolutions(float gradian)
{
return gradian / 400.0f;
}
///
/// Converts gradians to degrees.
///
/// The value to convert.
/// The converted value.
public static float GradiansToDegrees(float gradian)
{
return gradian * (9.0f / 10.0f);
}
///
/// Converts gradians to radians.
///
/// The value to convert.
/// The converted value.
public static float GradiansToRadians(float gradian)
{
return gradian * (Pi / 200.0f);
}
///
/// Converts radians to degrees.
///
/// The value to convert.
/// The converted value.
public static float RadiansToDegrees(float radian)
{
return radian * (180.0f / Pi);
}
///
/// Clamps the specified value.
///
/// The value.
/// The min.
/// The max.
/// The result of clamping a value between min and max
public static float Clamp(float value, float min, float max)
{
return value < min ? min : value > max ? max : value;
}
///
/// Clamps the specified value.
///
/// The value.
/// The min.
/// The max.
/// The result of clamping a value between min and max
public static double Clamp(double value, double min, double max)
{
return value < min ? min : value > max ? max : value;
}
///
/// Clamps the specified value.
///
/// The value.
/// The min.
/// The max.
/// The result of clamping a value between min and max
public static int Clamp(int value, int min, int max)
{
return value < min ? min : value > max ? max : value;
}
///
/// Inverse-interpolates a value linearly.
///
/// Minimum value that takes place in inverse-interpolation.
/// Maximum value that takes place in inverse-interpolation.
/// Value to get inverse interpolation.
/// Returns an inverse-linearly interpolated coeficient.
public static float InverseLerp(float min, float max, float value)
{
if (IsZero(Math.Abs(max - min)))
return float.NaN;
return (value - min) / (max - min);
}
///
/// Inverse-interpolates a value linearly.
///
/// Minimum value that takes place in inverse-interpolation.
/// Maximum value that takes place in inverse-interpolation.
/// Value to get inverse interpolation.
/// Returns an inverse-linearly interpolated coeficient.
public static double InverseLerp(double min, double max, double value)
{
if (IsZero(Math.Abs(max - min)))
return double.NaN;
return (value - min) / (max - min);
}
///
/// Interpolates between two values using a linear function by a given amount.
///
///
/// See http://www.encyclopediaofmath.org/index.php/Linear_interpolation and
/// http://fgiesen.wordpress.com/2012/08/15/linear-interpolation-past-present-and-future/
///
/// Value to interpolate from.
/// Value to interpolate to.
/// Interpolation amount.
/// The result of linear interpolation of values based on the amount.
public static double Lerp(double from, double to, double amount)
{
return (1 - amount) * from + amount * to;
}
///
/// Interpolates between two values using a linear function by a given amount.
///
///
/// See http://www.encyclopediaofmath.org/index.php/Linear_interpolation and
/// http://fgiesen.wordpress.com/2012/08/15/linear-interpolation-past-present-and-future/
///
/// Value to interpolate from.
/// Value to interpolate to.
/// Interpolation amount.
/// The result of linear interpolation of values based on the amount.
public static float Lerp(float from, float to, float amount)
{
return (1 - amount) * from + amount * to;
}
///
/// Interpolates between two values using a linear function by a given amount.
///
///
/// See http://www.encyclopediaofmath.org/index.php/Linear_interpolation and
/// http://fgiesen.wordpress.com/2012/08/15/linear-interpolation-past-present-and-future/
///
/// Value to interpolate from.
/// Value to interpolate to.
/// Interpolation amount.
/// The result of linear interpolation of values based on the amount.
public static byte Lerp(byte from, byte to, float amount)
{
return (byte)Lerp((float)from, (float)to, amount);
}
///
/// Performs smooth (cubic Hermite) interpolation between 0 and 1.
///
///
/// See https://en.wikipedia.org/wiki/Smoothstep
///
/// Value between 0 and 1 indicating interpolation amount.
public static float SmoothStep(float amount)
{
return (amount <= 0) ? 0
: (amount >= 1) ? 1
: amount * amount * (3 - (2 * amount));
}
///
/// Performs a smooth(er) interpolation between 0 and 1 with 1st and 2nd order derivatives of zero at endpoints.
///
///
/// See https://en.wikipedia.org/wiki/Smoothstep
///
/// Value between 0 and 1 indicating interpolation amount.
public static float SmootherStep(float amount)
{
return (amount <= 0) ? 0
: (amount >= 1) ? 1
: amount * amount * amount * (amount * ((amount * 6) - 15) + 10);
}
///
/// Determines whether the value is inside the given range (inclusively).
///
/// The value.
/// The minimum value of the range.
/// The maximum value of the range.
/// true if value is inside the specified range; otherwise, false.
public static bool IsInRange(float value, float min, float max)
{
return min <= value && value <= max;
}
///
/// Determines whether the value is inside the given range (inclusively).
///
/// The value.
/// The minimum value of the range.
/// The maximum value of the range.
/// true if value is inside the specified range; otherwise, false.
public static bool IsInRange(int value, int min, int max)
{
return min <= value && value <= max;
}
///
/// Determines whether the specified x is pow2.
///
/// The x.
/// true if the specified x is pow2; otherwise, false.
public static bool IsPow2(int x)
{
return ((x != 0) && (x & (x - 1)) == 0);
}
///
/// Converts a float value from sRGB to linear.
///
/// The sRGB value.
/// A linear value.
public static float SRgbToLinear(float sRgbValue)
{
if (sRgbValue < 0.04045f) return sRgbValue / 12.92f;
return (float)Math.Pow((sRgbValue + 0.055) / 1.055, 2.4);
}
///
/// Converts a float value from linear to sRGB.
///
/// The linear value.
/// The encoded sRGB value.
public static float LinearToSRgb(float linearValue)
{
if (linearValue < 0.0031308f) return linearValue * 12.92f;
return (float)(1.055 * Math.Pow(linearValue, 1 / 2.4) - 0.055);
}
///
/// Calculate the logarithm 2 of a floating point.
///
/// The input float
/// Log2(x)
public static float Log2(float x)
{
return (float)Math.Log(x) / 0.6931471805599453f;
}
///
/// Calculate the logarithm 2 of an integer.
///
/// The input integer
/// the log2(i) rounded to lower integer
public static int Log2(int i)
{
var r = 0;
while ((i >>= 1) != 0)
++r;
return r;
}
///
/// Get the next power of two of an integer.
///
/// The size.
/// System.Int32.
/// https://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2
public static int NextPowerOfTwo(int x)
{
if (x < 0)
return 0;
x--;
x |= x >> 1;
x |= x >> 2;
x |= x >> 4;
x |= x >> 8;
x |= x >> 16;
return x + 1;
}
///
/// Get the next power of two for a size.
///
/// The size.
/// System.Int32.
public static float NextPowerOfTwo(float size)
{
return (float)Math.Pow(2, Math.Ceiling(Math.Log(size, 2)));
}
///
/// Get the previous power of two of the provided integer.
///
/// The value
public static int PreviousPowerOfTwo(int size)
{
return 1 << (int)Math.Floor(Math.Log(size, 2));
}
///
/// Get the previous power of two of the provided float.
///
/// The value
public static float PreviousPowerOfTwo(float size)
{
return (float)Math.Pow(2, Math.Floor(Math.Log(size, 2)));
}
///
/// Alignes value up to match desire alignment.
///
/// The value.
/// The alignment.
/// Aligned value (multiple of alignment).
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int AlignUp(int value, int alignment)
{
int mask = alignment - 1;
return (value + mask) & ~mask;
}
///
/// Alignes value down to match desire alignment.
///
/// The value.
/// The alignment.
/// Aligned value (multiple of alignment).
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int AlignDown(int value, int alignment)
{
int mask = alignment - 1;
return value & ~mask;
}
///
/// Determines whether the specified value is aligned.
///
/// The value.
/// The alignment.
/// true if the specified value is aligned; otherwise, false.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsAligned(int value, int alignment)
{
return (value & (alignment - 1)) == 0;
}
///
/// Snaps a value to the nearest interval.
///
/// The value to snap.
/// The interval gap.
/// The nearest interval to the provided value.
public static float Snap(float value, float gap)
{
if (gap == 0)
return value;
return (float)Math.Round((value / gap), MidpointRounding.AwayFromZero) * gap;
}
///
/// Snaps a value to the nearest interval.
///
/// The value to snap.
/// The interval gap.
/// The nearest interval to the provided value.
public static double Snap(double value, double gap)
{
if (gap == 0)
return value;
return Math.Round((value / gap), MidpointRounding.AwayFromZero) * gap;
}
///
/// Snaps all vector components to the nearest interval.
///
/// The vector to snap.
/// The interval gap.
/// A vector which components are snapped to the nearest interval.
public static Vec2 Snap(Vec2 value, float gap)
{
if (gap == 0)
return value;
return new Vec2(
(float)Math.Round((value.X / gap), MidpointRounding.AwayFromZero) * gap,
(float)Math.Round((value.Y / gap), MidpointRounding.AwayFromZero) * gap);
}
///
/// Snaps all vector components to the nearest interval.
///
/// The vector to snap.
/// The interval gap.
/// A vector which components are snapped to the nearest interval.
public static Vector3 Snap(Vector3 value, float gap)
{
if (gap == 0)
return value;
return new Vector3(
(float)Math.Round((value.X / gap), MidpointRounding.AwayFromZero) * gap,
(float)Math.Round((value.Y / gap), MidpointRounding.AwayFromZero) * gap,
(float)Math.Round((value.Z / gap), MidpointRounding.AwayFromZero) * gap);
}
///
/// Snaps all vector components to the nearest interval.
///
/// The vector to snap.
/// The interval gap.
/// A vector which components are snapped to the nearest interval.
public static Vector4 Snap(Vector4 value, float gap)
{
if (gap == 0)
return value;
return new Vector4(
(float)Math.Round((value.X / gap), MidpointRounding.AwayFromZero) * gap,
(float)Math.Round((value.Y / gap), MidpointRounding.AwayFromZero) * gap,
(float)Math.Round((value.Z / gap), MidpointRounding.AwayFromZero) * gap,
(float)Math.Round((value.W / gap), MidpointRounding.AwayFromZero) * gap);
}
}
}