// 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 Vec3 Snap( Vec3 value, float gap ) { if( gap == 0 ) return value; return new Vec3( (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 Vec4 Snap( Vec4 value, float gap ) { if( gap == 0 ) return value; return new Vec4( (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 ); } } }