2019-08-06 14:20:11 +02:00
using System ;
2021-02-04 12:24:23 +01:00
using System.Collections.Generic ;
2019-08-07 00:45:40 +02:00
using Microsoft.Xna.Framework ;
2019-11-02 14:53:59 +01:00
using MLEM.Misc ;
2019-08-06 14:20:11 +02:00
namespace MLEM.Extensions {
2020-05-21 12:53:42 +02:00
/// <summary>
2020-07-19 23:12:12 +02:00
/// A set of extensions for dealing with <see cref="float"/>, <see cref="Vector2"/>, <see cref="Vector3"/>, <see cref="Vector4"/>, <see cref="Point"/>, <see cref="Matrix"/>, <see cref="Rectangle"/> and <see cref="RectangleF"/>
2020-05-21 12:53:42 +02:00
/// </summary>
2019-08-06 14:20:11 +02:00
public static class NumberExtensions {
2020-05-20 23:59:40 +02:00
/// <inheritdoc cref="Math.Floor(decimal)"/>
2019-08-06 14:20:11 +02:00
public static int Floor ( this float f ) {
return ( int ) Math . Floor ( f ) ;
}
2020-05-20 23:59:40 +02:00
/// <inheritdoc cref="Math.Ceiling(decimal)"/>
2019-08-06 14:20:11 +02:00
public static int Ceil ( this float f ) {
return ( int ) Math . Ceiling ( f ) ;
}
2020-05-20 23:59:40 +02:00
/// <summary>
/// Checks for decimal equality with a given tolerance.
/// </summary>
/// <param name="first">The first number to equate</param>
/// <param name="second">The second number to equate</param>
/// <param name="tolerance">The equality tolerance</param>
2020-05-21 17:21:34 +02:00
/// <returns>Whether or not the two values are different by at most <c>tolerance</c></returns>
2019-12-26 12:49:04 +01:00
public static bool Equals ( this float first , float second , float tolerance ) {
2020-05-20 23:59:40 +02:00
return Math . Abs ( first - second ) < = tolerance ;
2019-12-26 12:49:04 +01:00
}
2020-05-20 23:59:40 +02:00
/// <inheritdoc cref="Equals(float,float,float)"/>
2019-12-26 12:49:04 +01:00
public static bool Equals ( this Vector2 first , Vector2 second , float tolerance ) {
return Math . Abs ( first . X - second . X ) < = tolerance & & Math . Abs ( first . Y - second . Y ) < = tolerance ;
}
2020-05-20 23:59:40 +02:00
/// <inheritdoc cref="Equals(float,float,float)"/>
2019-12-26 12:49:04 +01:00
public static bool Equals ( this Vector3 first , Vector3 second , float tolerance ) {
return Math . Abs ( first . X - second . X ) < = tolerance & & Math . Abs ( first . Y - second . Y ) < = tolerance & & Math . Abs ( first . Z - second . Z ) < = tolerance ;
}
2020-05-20 23:59:40 +02:00
/// <inheritdoc cref="Equals(float,float,float)"/>
2019-12-26 12:49:04 +01:00
public static bool Equals ( this Vector4 first , Vector4 second , float tolerance ) {
return Math . Abs ( first . X - second . X ) < = tolerance & & Math . Abs ( first . Y - second . Y ) < = tolerance & & Math . Abs ( first . Z - second . Z ) < = tolerance & & Math . Abs ( first . W - second . W ) < = tolerance ;
}
2020-05-20 23:59:40 +02:00
/// <inheritdoc cref="Math.Floor(decimal)"/>
2020-07-27 00:24:49 +02:00
public static Vector2 FloorCopy ( this Vector2 vec ) {
2019-08-07 00:45:40 +02:00
return new Vector2 ( vec . X . Floor ( ) , vec . Y . Floor ( ) ) ;
}
2020-05-20 23:59:40 +02:00
/// <inheritdoc cref="Math.Floor(decimal)"/>
2020-07-27 00:24:49 +02:00
public static Vector3 FloorCopy ( this Vector3 vec ) {
2019-08-07 00:45:40 +02:00
return new Vector3 ( vec . X . Floor ( ) , vec . Y . Floor ( ) , vec . Z . Floor ( ) ) ;
}
2020-05-20 23:59:40 +02:00
/// <inheritdoc cref="Math.Floor(decimal)"/>
2020-07-27 00:24:49 +02:00
public static Vector4 FloorCopy ( this Vector4 vec ) {
2019-08-07 00:45:40 +02:00
return new Vector4 ( vec . X . Floor ( ) , vec . Y . Floor ( ) , vec . Z . Floor ( ) , vec . W . Floor ( ) ) ;
}
2019-08-06 14:20:11 +02:00
2020-08-10 02:16:35 +02:00
/// <inheritdoc cref="Math.Ceiling(decimal)"/>
public static Vector2 CeilCopy ( this Vector2 vec ) {
return new Vector2 ( vec . X . Ceil ( ) , vec . Y . Ceil ( ) ) ;
}
/// <inheritdoc cref="Math.Ceiling(decimal)"/>
public static Vector3 CeilCopy ( this Vector3 vec ) {
return new Vector3 ( vec . X . Ceil ( ) , vec . Y . Ceil ( ) , vec . Z . Ceil ( ) ) ;
}
/// <inheritdoc cref="Math.Ceiling(decimal)"/>
public static Vector4 CeilCopy ( this Vector4 vec ) {
return new Vector4 ( vec . X . Ceil ( ) , vec . Y . Ceil ( ) , vec . Z . Ceil ( ) , vec . W . Ceil ( ) ) ;
}
2020-05-20 23:59:40 +02:00
/// <summary>
/// Multiplies a point by a given scalar.
/// </summary>
/// <param name="point">The point</param>
/// <param name="f">The scalar</param>
/// <returns>The point, multiplied by the scalar memberwise</returns>
2019-08-11 21:24:09 +02:00
public static Point Multiply ( this Point point , float f ) {
return new Point ( ( point . X * f ) . Floor ( ) , ( point . Y * f ) . Floor ( ) ) ;
}
2020-05-20 23:59:40 +02:00
/// <summary>
/// Divides a point by a given scalar.
/// </summary>
/// <param name="point">The point</param>
/// <param name="f">The scalar</param>
/// <returns>The point, divided by the scalar memberwise</returns>
2019-08-15 14:59:15 +02:00
public static Point Divide ( this Point point , float f ) {
return new Point ( ( point . X / f ) . Floor ( ) , ( point . Y / f ) . Floor ( ) ) ;
}
2020-05-20 23:59:40 +02:00
/// <summary>
/// Transforms a point by a given matrix.
/// </summary>
/// <param name="position">The point</param>
/// <param name="matrix">The matrix</param>
/// <returns>The point, transformed by the matrix</returns>
2019-09-02 19:55:26 +02:00
public static Point Transform ( this Point position , Matrix matrix ) {
return new Point (
( position . X * matrix . M11 + position . Y * matrix . M21 + matrix . M41 ) . Floor ( ) ,
( position . X * matrix . M12 + position . Y * matrix . M22 + matrix . M42 ) . Floor ( ) ) ;
}
2020-05-20 23:59:40 +02:00
/// <summary>
/// Returns a copy of the given rectangle, moved by the given point.
/// The rectangle's size remains unchanged.
/// </summary>
/// <param name="rect">The rectangle to move</param>
/// <param name="offset">The amount to move by</param>
/// <returns>The moved rectangle</returns>
2019-08-12 19:44:16 +02:00
public static Rectangle OffsetCopy ( this Rectangle rect , Point offset ) {
rect . X + = offset . X ;
rect . Y + = offset . Y ;
return rect ;
}
2020-05-20 23:59:40 +02:00
/// <inheritdoc cref="OffsetCopy(Microsoft.Xna.Framework.Rectangle,Microsoft.Xna.Framework.Point)"/>
2019-11-02 14:53:59 +01:00
public static RectangleF OffsetCopy ( this RectangleF rect , Vector2 offset ) {
rect . X + = offset . X ;
rect . Y + = offset . Y ;
return rect ;
}
2020-05-20 23:59:40 +02:00
/// <summary>
/// Shrinks the rectangle by the given padding, causing its size to decrease by twice the amount and its position to be moved inwards by the amount.
/// </summary>
/// <param name="rect">The rectangle to shrink</param>
/// <param name="padding">The padding to shrink by</param>
/// <returns>The shrunk rectangle</returns>
2019-09-10 23:28:25 +02:00
public static Rectangle Shrink ( this Rectangle rect , Point padding ) {
rect . X + = padding . X ;
rect . Y + = padding . Y ;
rect . Width - = padding . X * 2 ;
rect . Height - = padding . Y * 2 ;
return rect ;
}
2020-05-20 23:59:40 +02:00
/// <inheritdoc cref="Shrink(Microsoft.Xna.Framework.Rectangle,Microsoft.Xna.Framework.Point)"/>
2019-11-02 14:53:59 +01:00
public static RectangleF Shrink ( this RectangleF rect , Vector2 padding ) {
rect . X + = padding . X ;
rect . Y + = padding . Y ;
rect . Width - = padding . X * 2 ;
rect . Height - = padding . Y * 2 ;
return rect ;
}
2020-05-20 23:59:40 +02:00
/// <inheritdoc cref="Shrink(Microsoft.Xna.Framework.Rectangle,Microsoft.Xna.Framework.Point)"/>
2019-12-14 14:00:12 +01:00
public static RectangleF Shrink ( this RectangleF rect , Padding padding ) {
rect . X + = padding . Left ;
2019-12-14 14:07:00 +01:00
rect . Y + = padding . Top ;
2019-12-14 14:00:12 +01:00
rect . Width - = padding . Width ;
rect . Height - = padding . Height ;
return rect ;
2021-02-04 12:24:23 +01:00
}
/// <summary>
/// Returns a set of <see cref="Point"/> values that are contained in the given <see cref="Rectangle"/>.
/// Note that <see cref="Rectangle.Left"/> and <see cref="Rectangle.Top"/> are inclusive, but <see cref="Rectangle.Right"/> and <see cref="Rectangle.Bottom"/> are not.
/// </summary>
/// <param name="area">The area whose points to get</param>
/// <returns>The points contained in the area</returns>
public static IEnumerable < Point > GetPoints ( this Rectangle area ) {
for ( var x = area . Left ; x < area . Right ; x + + ) {
for ( var y = area . Top ; y < area . Bottom ; y + + )
yield return new Point ( x , y ) ;
}
}
/// <summary>
/// Returns a set of <see cref="Vector2"/> values that are contained in the given <see cref="RectangleF"/>.
/// Note that <see cref="RectangleF.Left"/> and <see cref="RectangleF.Top"/> are inclusive, but <see cref="RectangleF.Right"/> and <see cref="RectangleF.Bottom"/> are not.
/// </summary>
/// <param name="area">The area whose points to get</param>
/// <param name="interval">The distance that should be traveled between each point that is to be returned</param>
/// <returns>The points contained in the area</returns>
public static IEnumerable < Vector2 > GetPoints ( this RectangleF area , float interval = 1 ) {
for ( var x = area . Left ; x < area . Right ; x + = interval ) {
for ( var y = area . Top ; y < area . Bottom ; y + = interval )
yield return new Vector2 ( x , y ) ;
}
2019-12-14 14:00:12 +01:00
}
2020-07-19 23:12:12 +02:00
/// <summary>
/// Turns the given 3-dimensional vector into a 2-dimensional vector by chopping off the z coordinate.
/// </summary>
/// <param name="vector">The vector to convert</param>
/// <returns>The resulting 2-dimensional vector</returns>
public static Vector2 ToVector2 ( this Vector3 vector ) {
return new Vector2 ( vector . X , vector . Y ) ;
}
/// <summary>
/// Returns the 3-dimensional scale of the given matrix.
/// </summary>
/// <param name="matrix">The matrix</param>
/// <returns>The scale of the matrix</returns>
public static Vector3 Scale ( this Matrix matrix ) {
float xs = Math . Sign ( matrix . M11 * matrix . M12 * matrix . M13 * matrix . M14 ) < 0 ? - 1 : 1 ;
float ys = Math . Sign ( matrix . M21 * matrix . M22 * matrix . M23 * matrix . M24 ) < 0 ? - 1 : 1 ;
float zs = Math . Sign ( matrix . M31 * matrix . M32 * matrix . M33 * matrix . M34 ) < 0 ? - 1 : 1 ;
Vector3 scale ;
scale . X = xs * ( float ) Math . Sqrt ( matrix . M11 * matrix . M11 + matrix . M12 * matrix . M12 + matrix . M13 * matrix . M13 ) ;
scale . Y = ys * ( float ) Math . Sqrt ( matrix . M21 * matrix . M21 + matrix . M22 * matrix . M22 + matrix . M23 * matrix . M23 ) ;
scale . Z = zs * ( float ) Math . Sqrt ( matrix . M31 * matrix . M31 + matrix . M32 * matrix . M32 + matrix . M33 * matrix . M33 ) ;
return scale ;
}
/// <summary>
/// Returns the rotation that the given matrix represents, as a <see cref="Quaternion"/>.
/// Returns <see cref="Quaternion.Identity"/> if the matrix does not contain valid rotation information.
/// </summary>
/// <param name="matrix">The matrix</param>
/// <returns>The rotation of the matrix</returns>
public static Quaternion Rotation ( this Matrix matrix ) {
var ( scX , scY , scZ ) = matrix . Scale ( ) ;
2020-07-31 17:14:25 +02:00
if ( scX = = 0 | | scY = = 0 | | scZ = = 0 )
2020-07-19 23:12:12 +02:00
return Quaternion . Identity ;
return Quaternion . CreateFromRotationMatrix ( new Matrix (
matrix . M11 / scX , matrix . M12 / scX , matrix . M13 / scX , 0 ,
matrix . M21 / scY , matrix . M22 / scY , matrix . M23 / scY , 0 ,
matrix . M31 / scZ , matrix . M32 / scZ , matrix . M33 / scZ , 0 ,
0 , 0 , 0 , 1 ) ) ;
}
2020-08-10 02:16:35 +02:00
2020-10-05 23:23:30 +02:00
/// <summary>
2020-10-12 21:08:15 +02:00
/// Calculates the amount that the rectangle <paramref name="rect"/> is penetrating the rectangle <paramref name="other"/> by.
2020-10-05 23:23:30 +02:00
/// If a penetration on both axes is occuring, the one with the lower value is returned.
/// This is useful for collision detection, as it can be used to push colliding objects out of each other.
/// </summary>
/// <param name="rect">The rectangle to do the penetration</param>
/// <param name="other">The rectangle that should be penetrated</param>
2020-10-12 21:08:15 +02:00
/// <param name="normal">The direction that the penetration occured in</param>
/// <param name="penetration">The amount that the penetration occured by, in the direction of <paramref name="normal"/></param>
/// <returns>Whether or not a penetration occured</returns>
public static bool Penetrate ( this RectangleF rect , RectangleF other , out Vector2 normal , out float penetration ) {
var ( offsetX , offsetY ) = other . Center - rect . Center ;
var overlapX = rect . Width / 2 + other . Width / 2 - Math . Abs ( offsetX ) ;
if ( overlapX > 0 ) {
var overlapY = rect . Height / 2 + other . Height / 2 - Math . Abs ( offsetY ) ;
if ( overlapY > 0 ) {
if ( overlapX < overlapY ) {
normal = new Vector2 ( offsetX < 0 ? - 1 : 1 , 0 ) ;
penetration = overlapX ;
} else {
normal = new Vector2 ( 0 , offsetY < 0 ? - 1 : 1 ) ;
penetration = overlapY ;
}
return true ;
}
2020-10-05 23:23:30 +02:00
}
2020-10-12 21:08:15 +02:00
normal = Vector2 . Zero ;
penetration = 0 ;
return false ;
2020-10-05 23:23:30 +02:00
}
2019-08-06 14:20:11 +02:00
}
}